Our goal is to build a server that does the following:
WebServer.java
Here is the code for a simple Web server:
import java.io.*;
import java.net.*;
import java.util.*;
class WebServer{
public static void main(String argv[]) throws Exception {
String requestMessageLine;
String fileName;
ServerSocket listenSocket = new ServerSocket(6789);
Socket connectionSocket = listenSocket.accept();
BufferedReader inFromClient =
new BufferedReader(new InputStreamReader(connectionSocket.getInputStream()));
DataOutputStream outToClient =
new DataOutputStream(connectionSocket.getOutputStream());
requestMessageLine = inFromClient.readLine();
StringTokenizer tokenizedLine =
new StringTokenizer(requestMessageLine);
if (tokenizedLine.nextToken().equals("GET")){
fileName = tokenizedLine.nextToken();
if (fileName.startsWith("/") == true )
fileName = fileName.substring(1);
File file = new
File(fileName);
int numOfBytes = (int) file.length();
FileInputStream inFile = new FileInputStream (fileName);
byte[] fileInBytes
= new byte[numOfBytes];
inFile.read(fileInBytes);
outToClient.writeBytes("HTTP/1.0 200 Document Follows\r\n");
if (fileName.endsWith(".jpg"))
outToClient.writeBytes("Content-Type: image/jpeg\r\n");
if (fileName.endsWith(".gif"))
outToClient.writeBytes("Content-Type: image/gif\r\n");
outToClient.writeBytes("Content-Length: " + numOfBytes + "\r\n");
outToClient.writeBytes("\r\n");
outToClient.write(fileInBytes, 0, numOfBytes);
connectionSocket.close();
}
else System.out.println("Bad Request Message");
}
}
Let us now take a look at the code. The first half the program is almost
identical to TCPServer.java. As with TCPServer.java, we import the java.io
and java.net packages. In addition to these two packages we also
import the java.util package, which contains the StringTokenizer
class, which is used for parsing HTTP request messages. Looking now
at the lines within the class WebServer, we define two string objects:
String
requestMessageLine;
String fileName;
The object requestMessageLine is a string that will contain the first line in the HTTP request message. The object fileName is a string that will contain the file name of the requested file. The next set of commands is identical to the corresponding set of commands in TCPServer.java.
ServerSocket listenSocket = new ServerSocket(6789);
Socket connectionSocket = listenSocket.accept();
BufferedReader inFromClient =
new BufferedReader(new InputStreamReader(connectionSocket.getInputStream()));
DataOutputStream outToClient =
new DataOutputStream(connectionSocket.getOutputStream());
Two socket-like objects are created. The first of these objects is listenSocket, which is of type ServerSocket. The object listenSocket is created by the server program before receiving a request for a TCP connection from a client. It listens at port 6789, and waits for a request from some client to establish a TCP connection. When a request for a connection arrives, the accept() method of listenSocket creates a new object, connectionSocket, of type Socket. Next two streams are created: the BufferedReader inFromClient and the DataOutputStream outToClient. The HTTP request message comes from the network, through connectionSocket and into inFromClient; the HTTP response message goes into outToClient, through connectionSocket and into the network. The remaining portion of the code differs significantly from TCPServer.java.
requestMessageLine = inFromClient.readLine();
The above command reads the first line of the HTTP request message. This line is supposed to be of the form:
StringTokenizer tokenizedLine = new StringTokenizer(requestMessageLine);
if (tokenizedLine.nextToken().equals("GET")){
fileName = tokenizedLine.nextToken();
if (fileName.startsWith("/") == true )
fileName = fileName.substring( 1
);
The above commands parse the first line of the request message to obtain the requested filename. The object tokenizedLine can be thought of as the original request line with each of the "words" GET, file_name and HTTP/1.0 placed in a separate place holder called a token. The server knows from the HTTP RFC that the file name for the requested file is contained in the token that follows the token containing "GET". This file name is put in a string called fileName. The purpose of the last if statement in the above code is to remove the backslash that may precede the filename.
FileInputStream inFile = new FileInputStream (fileName);
The above command attaches a stream, inFile, to the file fileName.
byte[] fileInBytes = new byte[numOfBytes];
inFile.read(fileInBytes);
The above commands determine the size of the
file and construct an array of bytes of that size. The name of the array
is fileInBytes. The last command reads from the stream inFile
to the byte array fileInBytes. The program must convert to bytes
because the output stream outToClient may only be fed with bytes.
Now we are ready to construct the HTTP response message. To this end we must first send the HTTP response header lines into the DataOutputStream outToClient:
outToClient.writeBytes("HTTP/1.0 200 Document
Follows\r\n");
if (fileName.endsWith(".jpg"))
outToClient.writeBytes("Content-Type:
image/jpeg\r\n");
if (fileName.endsWith(".gif"))
outToClient.writeBytes("Content-Type:
image/gif\r\n");
outToClient.writeBytes("Content-Length:
" + numOfBytes + "\r\n");
outToClient.writeBytes("\r\n");
The above set of commands are particularly interesting. These commands prepare the header lines for HTTP response message and send the header lines to the TCP send buffer. The first command sends the mandatory status line: HTTP/1.0 200 Document Follows, followed by a carriage return and a line feed. The next two command lines prepare a single content-type header line. If the server is to transfer a gif image, then the server prepares the header line Content-Type: image/jpeg. If, on the other hand, the server is to transfer a jpeg image, then the server prepares the header line Content-Type: image/gif. (In this simple Web server, no content line is sent if the object is neither a gif nor a jpeg image.) The server then prepares and sends a content-length header line and a mandatory blank line to precede the object itself that is to be sent. We now must send the file FileName into the DataOutputStream outToClient. But because outToClient works with bytes, we first must perform a conversion to bytes:
We can now send the requested file:
outToClient.write(fileInBytes,
0, numOfBytes);
The above command sends the requested file, fileInBytes, to the TCP send buffer. TCP will concatenate the file, fileInBytes, to the header lines just created, segment the concatenation if necessary, and send the TCP segments to the client.
connectionSocket.close();
After serving one request for one file, the server performs some housekeeping by closing the socket connectionSocket.
To test this web server, install it on a host. Also put some files in
the host. Then use a browser running on any machine to request a file from
the server. When you request a file, you will need to use the port number
that you include in the server code (e.g., 6789). So if your server is
located at somehost.somewhere.edu, the file is somefile.html, and the port
number is 6789, then the browser should request http://somehost.somewhere.edu:6789/somefile.html
.
Copyright 1996-2000 Keith W. Ross and James F. Kurose