Recipes
In this chapter, the structure of HTTP requests and responses was examined. As shown in Listing 1.3 and 1.4, the structure of HTTP is not terribly complex. As a result, it is relatively easy to create a web server. This is what the two recipes in this chapter will deal with. The first Recipe, 1.1, will show how to create a really simple web server. Next, Recipe 1.2 will show how to extend the simple web server to use HTML and image files, just as a regular web server would.
The recipes in this chapter make use of Java sockets. Sockets are the lowest level that an application programmer will usually get to the Internet connection. The socket level allows a web server to be created. After this chapter, all Java recipes will make use of the Java HTTP classes. If desired, HTTP programming could be performed at the socket level; however, using the Java HTTP classes will get the needed functionality, without the complexity of dealing directly with sockets.
Recipe 1.1: A Simple Web Server
The first recipe is a “Hello World” program of sorts. Recipe 1.1 is a web server. Its purpose is to show how to serve web pages from your program. This very simple program only serves one web page. This page simply says “Hello World”.
When this program is launched the port that web server will listen at must be specified. Normally web servers listen at port 80. However, there may already be a web server running at port 80. If this is the case a higher port number such as 8080 or 8081 should be used. The server is started as follows:
SimpleWebServer 8080
This will start the web server on port 8080. The above command simply shows the abstract format to call this recipe, with the appropriate parameters. For exact information on how to run this recipe refer to Appendix B, C, or D, depending on the operating system you are using. If something already has that port in use then a BindingException error message will be shown, as seen in Listing 1.5.
Listing 1.5: Port Already in Use
java.net.BindException: Address already in use at java.net.PlainSocketImpl.socketBind(Native Method) at java.net.PlainSocketImpl.bind(PlainSocketImpl.java:359) at java.net.ServerSocket.bind(ServerSocket.java:319) at java.net.ServerSocket.<init>(ServerSocket.java:185) at java.net.ServerSocket.<init>(ServerSocket.java:97) at com.heatonresearch.httprecipes.ch1.recipe2.WebServer.<init>(WebServer.java:51) at com.heatonresearch.httprecipes.ch1.recipe2.WebServer.main(WebServer.java:290)
If the web server is started properly, and no error occurs, there should be no output. The web server is now waiting for a connection. Connecting to the web server is easy. Use any web browser and access the following URL:
No matter what request is sent to this web server, it will produce a page that says Hello World. The output from Recipe 1.1 is shown in Figure 1.7.
Figure 1.7: Hello World

Now that the program has been demonstrated, it is time to take a look at what was necessary to implement this program. Recipe 1.1 is shown in Listing 1.6.
Listing 1.6: Simple Web Server (SimpleWebServer.java)
package com.heatonresearch.httprecipes.ch1.recipe1; import java.io.*; import java.net.*; /** * Recipe #1.1: Very Simple Web Server * Copyright 2007 by Jeff Heaton(jeff@jeffheaton.com) * * HTTP Programming Recipes for Java Bots * ISBN: 0-9773206-6-9 * http://www.heatonresearch.com/articles/series/16/ * * A simple web server that will respond to every request * with "Hello World". * * This software is copyrighted. You may use it in programs * of your own, without restriction, but you may not * publish the source code without the author's permission. * For more information on distributing this code, please * visit: * http://www.heatonresearch.com/hr_legal.php * * @author Jeff Heaton * @version 1.1 */ public class SimpleWebServer { /* * The server socket. */ private ServerSocket serverSocket; /** * Construct the web server to listen on the specified * port. * * @param port The port to use for the server. * @throws IOException Thrown if any sort of error occurs. */ public SimpleWebServer(int port) throws IOException { serverSocket = new ServerSocket(port); } /** * The run method endlessly waits for connections. * As each connection is opened (from web browsers) * the connection is passed off to handleClientSession. */ public void run() { for (;;) { try { Socket clientSocket = serverSocket.accept(); handleClientSession(clientSocket); } catch (IOException e) { e.printStackTrace(); } } } /** * Handle a client session. This method displays the incoming * HTTP request and responds with a "Hello World" response. * * @param url The URL to download. * @return The contents of the URL that was downloaded. * @throws IOException Thrown if any sort of error occurs. */ private void handleClientSession(Socket socket) throws IOException { // setup to read from the socket in lines InputStream is = socket.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(is); BufferedReader in = new BufferedReader(inputStreamReader); // setup to write to socket in lines OutputStream os = socket.getOutputStream(); PrintStream out = new PrintStream(os); // read in the first line System.out.println("**New Request**"); String first = in.readLine(); System.out.println(first); // read in headers and post data String line; do { line = in.readLine(); if(line!=null) System.out.println(line); } while (line!=null && line.trim().length() > 0); // write the HTTP response out.println("HTTP/1.1 200 OK"); out.println(""); out.println("<html>"); out.println("<head><title>Simple Web Server</title></head>"); out.println("<body>"); out.println("<h1>Hello World</h1>"); out.println("<//body>"); out.println("</html>"); // close everything up out.close(); in.close(); socket.close(); } /** * Read in the arguments and start the server. * * @param args Web server port. */ public static void main(String args[]) { try { if (args.length < 1) { System.out.println("Usage:\njava SimpleWebServer [port]"); } else { int port; try { port = Integer.parseInt(args[0]); SimpleWebServer server = new SimpleWebServer(port); server.run(); } catch (NumberFormatException e) { System.out.println("Invalid port number"); } } } catch (IOException e) { e.printStackTrace(); } } }
Java supports two primary types of sockets: server sockets and client sockets. Server sockets are implemented using the ServerSocket class. Client sockets are implemented using the Socket class. This program begins by creating a server socket. This is done with the following line of code in the WebServer constructor:
serverSocket = new ServerSocket(port);
Once the server connection has been opened, the program must wait for connections. This is done using the accept function of the ServerSocket object. This is done in the run method. The run method begins by entering an endless loop, as seen here:
for (;;)
{This command causes the web server to wait endlessly for connections. The server does not include a mechanism for shutting itself down. To shut down the server simply, press ctrl-c or close the web server’s window.
Next, the accept function is called to accept a connection. If no connection is available, then the accept function blocks (or waits), until a connection is available. Because of this, the accept call is often put inside of a thread. This would allow the rest of the program to continue executing while the thread waits for a connection. However, for this simple example, everything will be done in a single thread.
try
{
Socket clientSocket = serverSocket.accept();When a connection is made, a Socket object is returned. This object is passed onto the handleClientSession method to fulfill the request.
handleClientSession(clientSocket);
A catch block is also provided to handle any exceptions. If an exception occurs, it will be displayed to the console, and the server will continue to run.
} catch (IOException e)
{
e.printStackTrace();
}
}Once a connection is established, the handleClientSession method is called. This method begins by obtaining an InputStream to the socket. The InputStream is passed into an InputStreamReader, and then to a BufferedReader. This allows the readLine function to read the socket as a series of lines.
// Setup to read from the socket in lines. InputStream is = socket.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(is); BufferedReader in = new BufferedReader(inputStreamReader);
Next, an OutputStream for the socket is created. The OutputStream will allow a socket to be written to. The OutputStream object is used to construct a PrintStream object. Using a PrintStream object will allow the println method to write lines of text to the OutputStream.
// Setup to write to socket in lines. OutputStream os = socket.getOutputStream(); PrintStream out = new PrintStream(os);
Now that the streams are setup, it is time to begin reading from the socket. As was discussed earlier in the chapter, the first line of an HTTP request has a special format. Because of this, the first line is read separately. It is then printed out.
// Read in the first line.
System.out.println("**New Request**");
String first = in.readLine();
System.out.println(first);Once the first line has been read in, the headers can be read. The headers are read until a blank line is found. As was discussed earlier in this chapter, a blank line indicates the end of HTTP headers.
// Read in headers and post data.
String line;
do
{
line = in.readLine();
System.out.println(line);
} while (line.trim().length() > 0);Once the first blank line is hit, the program is done reading the HTTP headers. Because this server only supports GET requests, the program is also done reading the HTTP request. Now it is time to write the HTTP response. The following lines of code write a simple HTML message that says “Hello World” to the browser.
// Write the HTTP response.
out.println("HTTP/1.1 200 OK");
out.println("");
out.println("<html>");
out.println("<head><title>Simple Web Server</title></head>");
out.println("<body>");
out.println("<h1>Hello World</h1>");
out.println("<//body>");
out.println("</html>");Now that the message has been written, it is time to close the streams. The following lines of code do this.
// Close everything up. out.close(); in.close(); socket.close();
The above recipe showed how to create a simple web server. In addition to this basic functionality, this recipe can be expanded to be a custom web server that will respond to different requests. For example, a web server could be constructed to give information about how a process is running or other status information collected by the computer.
Most web servers simply present files to the browser. However, Recipe 1.1 generated its response internally. This can be a very useful technique to display status information for your web server. The next recipe, Recipe 1.2, shows how to create a web server that will allow access to files.
Recipe 1.2: File Based Web Server
This recipe shows how to create a very common sort of web server. This web sever exposes a directory tree to the Internet. This directory tree is called the “HTTP root”, or “web root”. Files are placed into this directory will be accessed by web browsers. A default file, named index.html, should be placed into this directory. This file is displayed when the user browses to the directory. The index.html file usually has links to the other files in that directory, and serves as a starting point.
This web server requires only two configuration arguments. Both of these are specified in the command line. The two parameters are:
- HTTP Port
- HTTP Root Directory
For example, to start the web server using port 8080 and the directory c:\httproot\ as the root directory, the following command is used.
WebServer 8080 c:\httproot\
The above command simply shows the abstract format to call this recipe, with the appropriate parameters. For exact information on how to run this recipe refer to Appendix B, C, or D, depending on the operating system you are using. The web server featured in this recipe is an expanded version of the server featured in Recipe 1.1. Because of this the details will not be repeated that are the same between the two web servers; therefore, Recipe 1.1 should be reviewed for additional information.
Now the construction of the web server will be examined. Listing 1.7 shows the source code necessary for the file based web server.
Listing 1.7: File Based Web Server (WebServer.java)
package com.heatonresearch.httprecipes.ch1.recipe2; import java.io.*; import java.net.*; import java.util.*; /** * Recipe #1.2: Simple File Based Web Server * Copyright 2007 by Jeff Heaton(jeff@jeffheaton.com) * * HTTP Programming Recipes for Java Bots * ISBN: 0-9773206-6-9 * http://www.heatonresearch.com/articles/series/16/ * * A simple web server that exposes a single directory as the * root of a web site. Only the most basic web server functions * are provided, such as index.html redirect. * * This software is copyrighted. You may use it in programs * of your own, without restriction, but you may not * publish the source code without the author's permission. * For more information on distributing this code, please * visit: * http://www.heatonresearch.com/hr_legal.php * * @author Jeff Heaton * @version 1.1 */ public class WebServer { /* * The server socket. */ private ServerSocket serverSocket; /* * The directory to contain HTML and image files. */ private String httproot; /** * Construct the web server to listen on the specified * port. * * @param port The port to use for the server. * @param httproot The root directory for HTML and image files. * @throws IOException Thrown if any sort of error occurs. */ public WebServer(int port, String httproot) throws IOException { serverSocket = new ServerSocket(port); this.httproot = httproot; } /** * The run method endlessly waits for connections. * As each connection is opened(from web browsers) * the connection is passed off to handleClientSession. */ public void run() { for (;;) { try { Socket clientSocket = serverSocket.accept(); handleClientSession(clientSocket); } catch (IOException e) { e.printStackTrace(); } } } /** * Add a slash to the end of a path, if there is not a slash * there already. This method adds the correct type of slash, * depending on the operating system. * * @param path The path to add a slash to. * @return The path with a slash added. */ private String addSlash(String path) { path = path.trim(); if (path.endsWith("" + File.separatorChar)) return path; else return path + File.separatorChar; } /** * Handle a client session. This method displays the incoming * HTTP request and passes the response off to either sendFile * or error. * * @param socket The client socket. * @throws IOException Thrown if any sort of error occurs. */ private void handleClientSession(Socket socket) throws IOException { // setup to read from the socket in lines InputStream is = socket.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(is); BufferedReader in = new BufferedReader(inputStreamReader); // setup to write to socket in lines OutputStream os = socket.getOutputStream(); // read in the first line System.out.println("**New Request**"); String first = in.readLine(); System.out.println(first); // read in headers and post data String line; do { line = in.readLine(); System.out.println(line); } while (line!=null && line.trim().length() > 0); // write the HTTP response StringTokenizer tok = new StringTokenizer(first); String verb = (String) tok.nextElement(); String path = (String) tok.nextElement(); if (verb.equalsIgnoreCase("GET")) sendFile(os, path); else error(os, 500, "Unsupported command"); // close everything up os.close(); in.close(); socket.close(); } /** * Determine the correct "content type" based on the file * extension. * * @param path The file being transfered. * @return The correct content type for this file. * @throws IOException Thrown if any sort of error occurs. */ private String getContent(String path) { path = path.toLowerCase(); if (path.endsWith(".jpg") || path.endsWith(".jpeg")) return "image/jpeg"; else if (path.endsWith(".gif")) return "image/gif"; else if (path.endsWith(".png")) return "image/png"; else return "text/html"; } /** * Send a disk file. The path passed in is from the URL, this * URL is translated into a local disk file, which is then * transfered. * * @param out The output stream. * @param path The file requested from the URL. * @throws IOException Thrown if any sort of error occurs. */ private void sendFile(OutputStream out, String path) throws IOException { // parse the file by /'s and build a local file StringTokenizer tok = new StringTokenizer(path, "/", true); System.out.println(path); String physicalPath = addSlash(httproot); while (tok.hasMoreElements()) { String e = (String) tok.nextElement(); if (!e.trim().equalsIgnoreCase(File.separator)) { if (e.equals("..") || e.equals(".")) { error(out, 500, "Invalid request"); return; } physicalPath += e; } else physicalPath = addSlash(physicalPath); } // if there is no file specified, default // to index.html if (physicalPath.endsWith(File.separator)) { physicalPath = physicalPath + "index.html"; } // open the file and send it if it exists File file = new File(physicalPath); if (file.exists()) { // send the file FileInputStream fis = new FileInputStream(file); byte buffer[] = new byte[(int) file.length()]; fis.read(buffer); fis.close(); this.transmit(out, 200, "OK", buffer, getContent(physicalPath)); } // file does not exist, so send file not found else { this.error(out, 404, "File Not Found"); } } /** * Transmit a HTTP response. All responses are handled by * this method. * * @param out The output stream. * @param code The response code, i.e. 404 for not found. * @param message The message, usually OK or error message. * @param body The data to be transfered. * @param content The content type. * @throws IOException Thrown if any sort of error occurs. */ private void transmit(OutputStream out, int code, String message, byte body[], String content) throws IOException { StringBuilder headers = new StringBuilder(); headers.append("HTTP/1.1 "); headers.append(code); headers.append(' '); headers.append(message); headers.append("\n"); headers.append("Content-Length: " + body.length + "\n"); headers.append("Server: Heaton Research Example Server\n"); headers.append("Connection: close\n"); headers.append("Content-Type: " + content + "\n"); headers.append("\n"); out.write(headers.toString().getBytes()); out.write(body); } /** * Display an error to the web browser. * * @param out The output stream. * @param code The response code, i.e. 404 for not found. * @param message The error that occurred. * @throws IOException Thrown if any sort of error occurs. */ private void error(OutputStream out, int code, String message) throws IOException { StringBuilder body = new StringBuilder(); body.append("<html><head><title>"); body.append(code + ":" + message); body.append("</title></head><body><p>An error occurred.</p><h1>"); body.append(code); body.append("</h1><p>"); body.append(message); body.append("</p></body></html>"); transmit(out, code, message, body.toString().getBytes(), "text/html"); } /** * Read in the arguments and start the server. * * @param args Web server port and http root directory. */ public static void main(String args[]) { try { if (args.length < 2) { System.out.println("Usage:\njava WebServer [port] [http root path]"); } else { int port; try { port = Integer.parseInt(args[0]); WebServer server = new WebServer(port, args[1]); server.run(); } catch (NumberFormatException e) { System.out.println("Invalid port number"); } } } catch (IOException e) { e.printStackTrace(); } } }
The main function, the constructor, and the run method are all nearly the same as those in Recipe 1.1. The only difference is the support of the additional command line argument for the “HTTP root” path. For more information on these three methods, review Recipe 1.1.
The handleClientSession method begins the same as Recipe 1.1; however, once the connection is established, this recipe becomes more complex.
// Write the HTTP response.
StringTokenizer tok = new StringTokenizer(first);
String verb = (String) tok.nextElement();
String path = (String) tok.nextElement();
String version = (String) tok.nextElement();
if (verb.equalsIgnoreCase("GET"))
sendFile(os, path);
else
error(os, 500, "Unsupported command");As can be seen above, the first line of the HTTP request is parsed. The first line of a HTTP request will be something like the following form.
GET /index.html HTTP/1.1
As previously discussed in this chapter, there are three parts of this line, separated by spaces. Using a StringTokenizer, this string can be broken into the three parts. The verb is checked to see if it is a request other than GET. If the request is not a GET request, then an error is displayed. Otherwise, the path is sent onto the sendFile method.
The next few sections will discuss the major methods provided in this recipe.
The Send File Method
The sendFile method is used to send a file to the web browser. This consists of a two step process:
- Figure out the local path to the file
- Read in and transmit the file
An HTTP request will request a file path such as /images/logo.gif. This must be translated to a local path such as c:\httproot\images\logo.gif. This transformation is the first thing that the sendFile method does.
First a StringTokenizer is created to break up the path using slashes (/) as delimiters. This is done using the following lines of code:
// Parse the file by /'s and build a local file. StringTokenizer tok = new StringTokenizer(path, "/", true); System.out.println(path); String physicalPath = addSlash(httproot);
The physicalPath variable will hold the path to the file to be transferred. A slash is added, using the addSlash function. The physicalPath is now ready to have subdirectories or files concatenated to it. The sendFile method will then parse the HTTP path and concatenate any sub directories followed by the file requested to the physicalPath variable. The following lines of code begin this loop:
while (tok.hasMoreElements())
{
String e = (String) tok.nextElement();
if (!e.trim().equalsIgnoreCase(File.separator))
{As the elements of the file are parsed, the program must look out for the previous directory code of “..”. If “..” is allowed to be part of the path, a malicious user could use “..” to access the parent HTTP root directory. This would be a security risk. Therefore, if the string “..” is located inside of the URL, an error is displayed.
if (e.equals("..") || e.equals("."))
{
error(out, 500, "Invalid request");
return;
}For each section, the sub directory, or file, is concatenated to the physicalPath variable. Additionally, a slash is added for each of the sub directory levels.
physicalPath += e; } else physicalPath = addSlash(physicalPath); }
Now, that the entire path has been parsed, it is time to check for a default file. If the path specified by the user is a directory only, the default file index.html needs to be specified as shown below:
// If there is no file specified, default // to index.html. if (physicalPath.endsWith(File.separator)) physicalPath = physicalPath + "index.html";
Once the path is complete, there are really only two possibilities that will occur. Either the file will be transmitted to the user or a 404 error will be generated. The error code 404, which is the most famous of HTTP error codes, means that the file was not found.
Next the file that is to be transmitted must be read. The following lines of code will read the file.
// Open the file and send it if it exists.
File file = new File(physicalPath);
if (file.exists())
{
// Send the file.
FileInputStream fis = new FileInputStream(file);
byte buffer[] = new byte[(int) file.length()];
fis.read(buffer);
fis.close();
this.transmit(out, 200, "OK", buffer, getContent(physicalPath));
}As can be seen from the above lines of code, the file is read into an array of bytes. Once the file has been read, the transmit method is called. The transmit method actually transmits the data to the web browser.
If the file can not be found, an error is sent to the web browser.
// File does not exist, so send file not found. else this.error(out, 404, "File Not Found");
Notice the last parameter sent to the transmit method. It is the content type. This tells the web browser what type of data the file contains. The next section explains how this is determined.
The Get Content Function
Since the transmit method needs to know what type of data is being transferred, the getContent function should be called to determine the content type. The content type will be a string such as image/gif for a GIF image or text/html for an HTML file. This type is determined by the file extension, as shown in the following lines of code:
path = path.toLowerCase();
if (path.endsWith(".jpg") || path.endsWith(".jpeg"))
return "image/jpeg";
else if (path.endsWith(".gif"))
return "image/gif";
else if (path.endsWith(".png"))
return "image/png";
else
return "text/html";The getContent function can be called to quickly determine the content type based on the filename. Content types themselves will be discussed in greater detail in Chapter 4, “Beyond Simple Requests.”
The Error Method
When an error occurs, the error method is called. The error method accepts three arguments:
- The output stream
- The error code
- The error message
The error method works by constructing an HTML page that displays the error. This code can be seen here:
StringBuilder body = new StringBuilder();
body.append("<html><head><title>");
body.append(code + ":" + message);
body.append("</title></head><body><p>An error occured.</p><h1>");
body.append(code);
body.append("</h1><p>");
body.append(message);
body.append("</p></body></html>");This HTML page is then converted into an array of bytes. Next, this array of bytes, along with the code and message, is passed to the transmit method. Finally, the transmit method will send this data to the web browser.
transmit(out, code, message, body.toString().getBytes(), "text/html");
The error method is handy because it can be called from several different locations when an error occurs.
The Transmit Method
Both the error and sendFile methods use the transmit method to actually send the page to the web browser. This is very convenient because the transmit method properly handles all of the HTTP headers, and thus saves both the error and sendFile methods from both having to implement this functionality.
First, the HTTP headers are constructed. The HTTP headers are constructed into a StringBuilder, as seen here:
StringBuilder headers = new StringBuilder();
headers.append("HTTP/1.1 ");
headers.append(code);
headers.append(' ');
headers.append(message);
headers.append("\n");
headers.append("Content-Length: " + body.length + "\n");
headers.append("Server: Heaton Research Example Server\n");
headers.append("Connection: close\n");
headers.append("Content-Type: " + content + "\n");
headers.append("\n");Once the headers have been constructed, both the header and body can be transmitted. This is done using the following two commands. These commands make use of the OutputStream and write the necessary data.
out.write(headers.toString().getBytes()); out.write(body);
As can be seen, Recipe 1.2 implements a very simple, yet functional, web server. This web server is far from being “industrial strength”, but it would serve as a great starting point for any sort of application that would require a built-in web server.












