The following classes are legal:
//Blocking Classes
java.net.Socket
java.net.ServerSocket
//Non Blocking Classes
java.nio.channels.SelectionKey
java.nio.channels.Selector
java.nio.channels.ServerSocketChannel
java.nio.channels.SocketChannel
java.nio.ByteBuffer
java.nio.Files
//Misc Classes
java.net.InetAddress
java.net.InetSocketAddress
java.net.URL
java.net.URI
<other typical classes>
The following classes are NOT legal:
java.net.HttpUrlConnection
java.net.HttpsUrlConnection
java.net.UrlConnection
java.net.JarUrlConnection
<any wrapper classes for sockets from github>
<any wrapper classes attempting to make a socket's use 'simpler'>
A server is a special process that runs in the background of a kernel. It is also referenced as a daemon, since it provides a service. It's purpose is to facilitate client requests.
Unlike a client (httpc), the server (httpfs) needs to have a 'unique' ip address and port number. This 'unique' (ip,port) pair allows for a communication to be established from the client to the server.
The constraint of a server in this course is that it must use the loopback ip address.
The loopback IP address of a system depends from one system to another since the range of a loopback IP address is from 127.0.0.1 to 127.255.255.254 (or 127.0.0.1/8). However, many systems use 127.0.0.1.
To verify the loopback IP address, you can enter the following commands in terminal/cmd:
//On Mac/Linux:
$ifconfig | grep 127.
//On Windows (not too sure here):
ipconfig /all
//or
ping 127.0.0.1 //if successful, then it is that ip
Computer Systems have 65,535 different ports. Ports are classified into two categories: reserved and non-reserved (actually there are 3, but let's stay in scope).
Reserved port range: [1,2^10 - 1] = [1,1023]
Non-Reserved port range: [2^10,2^16 - 1] = [1024,65535]
For this course, the constraint is that the server cannot be assigned reserved ports. Additionally, a port must not be used by any other server/service.
Keep this in mind
Here is a (fun) example that can give you a little bit of insight as to why you cannot do this in the context of this course:
A server socket is a special type of socket that listens for incoming client requests:
ServerSocket(int port);
ServerSocket(int port, int backlog, InetAddress bindAddr);
ServerSocket(InetSocketAddress bindAddr);
When this constructor is called, the default address is 0.0.0.0(/0.0.0.0) and the port number is the specified port number:
After declaring and initializing the ServerSocket object, notice that the address of the server is 0.0.0.0 with a subnet of 0.0.0.0.
This address is special to the ServerSocket object because it knows that for it to have an IP address, it needs to check the system's hosts file for the localhost definition.
The hosts files of a machine is found in:
/etc/hosts - For systems running on a Linux/Mac kernel
c:\Windows\System32\Drivers\etc\hosts - For systems running on a Windows 10 kernel
Overall, calling this constructor will create a ServerSocket object with the IP address of 127.0.0.1 and port 8080.
This constructor specifies the IP address, port number, and queue size of the ServerSocket.
Backlog is an attribute that defines the maximum allowable queue size when clients want to connect.
For example, if Bob and Marie use their httpc client to connect to a server with a backlog of 0, then either Bob or Marie will be 'serviced'. One of them kicked out of the connection and needs to reconnect to the server at a later time.
On the other hand, if the server had a backlog of 1, then either Bob or Marie will be 'serviced', while the other waits for the server to 'serve' them. The person waiting in the queue remains connected.
This constructor doesn't bind the ServerSocket to any IP address nor port number.
By itself, this constructor doesn't do much. However, when paired with ServerSocket's bind method, it becomes really useful:
A ServerSocket constructor can throw an Exception if the ip/port pair is either invalid, bound to some other service, or is in the reserved list*:
Once a ServerSocket object has been successfully created, it can be used to listen for incoming requests from httpc:
The ServerSocket's accept method blocks the current thread until httpc connects to the specified ip address and port number.
Once the ServerSocket object connects with a client, it returns a new Socket object that can be used to respond to the client in a very similar fashion that the httpc requests something from a server.
In the example below, The web browser (= client = httpc) connected to the server. The server then created a BufferedReader to read the request of the client. This request was printed locally on the server using System.out:
From this, we can assume that the client was using FireFox, set to the US-English language, with a request to keep the connection alive. Notice that it's also a get request, that's why it contains no body.
Also, a person with a keen eye can see that jshell is being blocked by an infinite while loop. This must be fixed, but how? Finding the solution requires a little bit of research.
In the example below, The web browser(= client = httpc) connected to the server. The server (ignoring the request), decides to output a simple hello message while respecting the HTTP structure:
On the client side (web browser = httpc), we will see this response:
Servers need to understand requests coming from a client, and need to respond with an appropriate message.
This server will closely resemble an Apache (HTTP) Server, except we're doing it from ground up.
The server needs to be able to decode an incoming request by referring to the formatting rules of an HTTP Request:
The server needs to adhere to the formatting rules of an HTTP Response:
-
The Version field refers to the request message's version field.
-
The Status Code and Phrase fields are self created by the server. It is required for the server to have at least all of the following status codes and phrases:
<Status Code : Phrase> <-- Definitions> 200 : OK -- Server successfully found the resource and has sent it back to the client. 404 : Not Found -- Server has not found anything matching the requested URI. -- For example, if the client wants /directory/hello.txt, and the server doesn't have it in /directory/hello.txt 201 : Created -- Server has successfully created a resource from a POST ONLY request. -- It is NOT mandatory to return a URI entity. 400 : Bad Request -- Server doesn't understand the request. -- For example, if the request doesn't follow proper syntax or is malformed. 403 : Forbidden -- Server understood the request, but refuses to 'service' it. -- For example, if the file has it's permissions set to not Readable/Writeable
-
The Header Field name of the Server should contain the same Header fields of the client if it is a valid request. The server may add to the header field if required. For example, if the server responds to a GET request, it should contain headers Content-Length and Content-Type.
-
Entity Body is used only when a server receives a GET request. It needs to send a body of information to the client. Otherwise for POST, it can simply use the status code and phrase to denote the server's response.
HTTPc client sends the following message:
GET /hello.txt HTTP/1.0
User-Agent: Concordia
HTTPfs server receives that message, and supposing that hello.txt exists, then will reply to the client by writing to its output stream the following:
HTTP/1.0 200 OK
User-Agent: Concordia
Content-Length: 12
Content-Type: text/html
Hello World!
If the file does not exist, then the server replies with:
HTTP/1.0 404 Not Found
User-Agent: Concordia
If the file was not publicly readable (chmod), then the server replies with:
HTTP/1.0 403 Forbidden
User-Agent: Concordia
It is required that the httpfs options have the following syntax:
httpfs [-v] [-p PORT] [-d PATH-TO-DIR]
httpfs help //this prints HELP
-
The options should be detected in an unordered fashion.
-
The port number should be verified (see above discussion).
-
The path root path of the server should be verified (must exist & must be a directory).
- NOTE: Suppose the root path is /home/user/Documents. The server should understand this, and use it. The client, however does not need to specify /home/user/Documents in the request path. It simply uses /. For example, /home/user/Documents/ contains file hello. The client, httpc, will only request for /hello. The Server will understand that /hello = /home/user/Documents/hello.
-
Running verbose should print enough information on the server side for someone else to read it and understand it.
-
There should be a default path if -d is not defined. This path must be safe, we do not want to accidentally change kernel files.
-
There should be a default port of 8080 if -p is not defined.
Please read the entire document for the lab. You will see the clear path to how the server should respond in the described situations.
Your server should be running in an infinite loop, especially after a client has completed their request. You can implement a stop key to close the program.
For a POST request, if the server doesn't have a path specified, it should handle it properly. For example:
httpc post -h Content-Length:12 -d "Hello world!" http://localhost:8080/hey/my/name/is/bob.txt
If it doesn't have the directories/file, the server will create the directories hey, my, name, is and then the file bob.txt containing the text Hello world!.
For a request, you can have httpc detect if the url contains an IP address or not. This will simplify your task:
httpc post -h Content-Length:12 -d "Hello world!" http://127.0.0.1:8080/bob.txt
//is equivalent to
httpc post -h Content-Length:12 -d "Hello world!" http://localhost:8080/bob.txt
Your httpc client from Lab 1 should be able to connect to the server. For example, if the server has an IP address of 127.0.0.1 and port of 8080, then running the following:
httpc (get|post) [-v] (-h k:v)* [-d data] [-f file] URL
where URL = http://localhost:8080/hello.txt
\\OR
URL = http://localhost:8080/hey/my/name/is/bob.txt
Should be valid (assuming that the file exists if GET).