| ||||
Copyright 1999 Sams Publishing |
|
A Specialized Worker Thread Pool: HttpServer |
In this section, Ill show you how a simple Web page server can utilize thread-pooling techniques to service requests. In this case, the workers are specialized to handle requests for files from Web browsers. |
Web browsers and Web servers communicate with each other using the Hypertext Transfer Protocol (HTTP). HTTP 1.0 (older, but simpler for this example than HTTP 1.1) is fully specified in RFC 1945, which is available at this URL: |
http://www.w3.org/Protocols/rfc1945/rfc1945 |
The basics of this protocol consist of a request from the Web browser client, and a response from the Web server. The communication occurs over the InputStream and OutputStream pair available from a TCP/IP socket. The socket connection is initiated by the client and accepted by the server. The request-response cycle occurs while the socket is open . After the response is sent, the socket is closed. Each request uses a new socket. The client Web browser may make several simultaneous requests to a single server, each over its own socket. |
The request consists of a required request line, followed by optional header lines, followed by a required blank line, followed by an optional message body. In this example, only the request line will be parsed. The request line consists of a request method, a space, the requested resource, a space, and finally the HTTP protocol version being used by the client. The only request method supported here is GET , so a sample request line would be |
GET /dir1/dir2/file.html HTTP/1.0 |
The response consists of a required status line, followed by optional header lines, followed by a required blank line, followed by an optional message body. In this example, if the file is found, the server will return the status line, one header line with the content length, another header line with the content type, and a message body with the bytes of the requested file. The status line consists of the HTTP protocol version, a space, a response code, a space, and finally a textual explanation of the response code. In response to a GET request, a response such as the following would be produced: |
HTTP/1.0 200 OK |
Content-Length: 1967 |
Content-Type: text/html |
<blank line> |
<the 1,967 bytes of the requested file> |
This simple Web server supports three response status lines: |
HTTP/1.0 200 OK |
HTTP/1.0 404 Not Found |
HTTP/1.0 503 Service Unavailable |
The first is used when the requested file is found, the second if the file could not be found, and the third if the server is too busy to service the request properly. |
Class HttpServer |
The HttpServer class, shown in Listing 13.5, serves as the main interface to the Web server and creates several HttpWorker objects (see Listing 13.6 ). The HttpServer object and the HttpWorker objects each have their own internal thread. The workers add themselves to a pool when they are ready to accept another HTTP request. When a request comes in, the server checks the pool for available workers and if one is available, assigns it to the connection. If none are available, the terse Service Unavailable response is returned to the client. |
Listing 13.5 HttpServer.javaA Simple Web Page Server |
1: import java.io.*; |
2: import java.net.*; |
3: |
4: // uses ObjectFIFO from chapter 18 |
5: |
6: public class HttpServer extends Object { |
7: |
8: // currently available HttpWorker objects |
9: private ObjectFIFO idleWorkers; |
10: |
11: // all HttpWorker objects |
12: private HttpWorker[] workerList; |
13: private ServerSocket ss; |
14: |
15: private Thread internalThread; |
16: private volatile boolean noStopRequested; |
17: |
18: public HttpServer( |
19: File docRoot, |
20: int port, |
21: int numberOfWorkers, |
22: int maxPriority |
23: ) throws IOException { |
24: |
25: // Allow a max of 10 sockets to queue up |
26: // waiting for accpet(). |
27: ss = new ServerSocket(port, 10); |
28: |
29: if ((docRoot == null) |
30: !docRoot.exists() |
31: !docRoot.isDirectory() |
32: ) { |
33: |
34: throw new IOException(specified docRoot is null + |
35: or does not exist or is not a directory); |
36: } |
37: |
38: // ensure that at least one worker is created |
39: numberOfWorkers = Math.max(1, numberOfWorkers); |
40: |
41: // Ensure: |
42: // (minAllowed + 2) <= serverPriority <= (maxAllowed - 1) |
43: // which is generally : |
44: // 3 <= serverPriority <= 9 |
45: int serverPriority = Math.max( |
46: Thread.MIN_PRIORITY + 2, |
47: Math.min(maxPriority, Thread.MAX_PRIORITY - 1) |
48: ); |
49: |
50: // Have the workers run at a slightly lower priority so |
51: // that new requests are handled with more urgency than |
52: // in-progress requests. |
53: int workerPriority = serverPriority - 1; |
54: |
55: idleWorkers = new ObjectFIFO(numberOfWorkers); |
56: workerList = new HttpWorker[numberOfWorkers]; |
57: |
58: for (int i = 0; i < numberOfWorkers; i++) { |
59: // Workers get a reference to the FIFO to add |
60: // themselves back in when they are ready to |
61: // handle a new request. |
62: workerList[i] = new HttpWorker( |
63: docRoot, workerPriority, idleWorkers); |
64: } |
65: |
66: // Just before returning, the thread should be |
67: // created and started. |
68: noStopRequested = true; |
69: |
70: Runnable r = new Runnable() { |
71: public void run() { |
72: try { |
73: runWork(); |
74: } catch (Exception x) { |
75: // in case ANY exception slips through |
76: x.printStackTrace(); |
77: } |
78: } |
79: }; |
80: |
81: internalThread = new Thread(r); |
82: internalThread.setPriority(serverPriority); |
83: internalThread.start(); |
84: } |
85: |
86: private void runWork() { |
87: System.out.println( |
88: HttpServer ready to receive requests); |
89: |
90: while (noStopRequested) { |
91: try { |
92: Socket s = ss.accept(); |
93: |
94: if (idleWorkers.isEmpty()) { |
95: System.out.println( |
96: HttpServer too busy, denying request); |
97: |
98: BufferedWriter writer = |
99: new BufferedWriter( |
100: new OutputStreamWriter( |
101: s.getOutputStream())); |
102: |
103: writer.write(HTTP/1.0 503 Service + |
104: Unavailable\r\n\r\n); |
105: |
106: writer.flush(); |
107: writer.close(); |
108: writer = null; |
109: } else { |
110: // No need to be worried that idleWorkers |
111: // will suddenly be empty since this is the |
112: // only thread removing items from the queue. |
113: HttpWorker worker = |
114: (HttpWorker) idleWorkers.remove(); |
115: |
116: worker.processRequest(s); |
117: } |
118: } catch (IOException iox) { |
119: if (noStopRequested) { |
120: iox.printStackTrace(); |
121: } |
122: } catch (InterruptedException x) { |
123: // re-assert interrupt |
124: Thread.currentThread().interrupt(); |
125: } |
126: } |
127: } |
128: |
129: public void stopRequest() { |
130: noStopRequested = false; |
131: internalThread.interrupt(); |
132: |
133: for (int i = 0; i < workerList.length; i++) { |
134: workerList[i].stopRequest(); |
135: } |
136: |
137: if (ss != null) { |
138: try { ss.close(); } catch (IOException iox) { } |
139: ss = null; |
140: } |
141: } |
142: |
143: public boolean isAlive() { |
144: return internalThread.isAlive(); |
145: } |
146: |
147: private static void usageAndExit(String msg, int exitCode) { |
148: System.err.println(msg); |
149: System.err.println(Usage: java HttpServer <port> + |
150: <numWorkers> <documentRoot>); |
151: System.err.println( <port> - port to listen on + |
152: for HTTP requests); |
153: System.err.println( <numWorkers> - number of + |
154: worker threads to create); |
155: System.err.println( <documentRoot> - base + |
156: directory for HTML files); |
157: System.exit(exitCode); |
158: } |
159: |
160: public static void main(String[] args) { |
161: if (args.length != 3) { |
162: usageAndExit(wrong number of arguments, 1); |
163: } |
164: |
165: String portStr = args[0]; |
166: String numWorkersStr = args[1]; |
167: String docRootStr = args[2]; |
168: |
169: int port = 0; |
170: |
171: try { |
172: port = Integer.parseInt(portStr); |
173: } catch (NumberFormatException x) { |
174: usageAndExit(could not parse port number from ˜ + |
175: portStr + ˜, 2); |
176: } |
177: |
178: if (port < 1) { |
179: usageAndExit(invalid port number specified: + |
180: port, 3); |
181: } |
182: |
183: int numWorkers = 0; |
184: |
185: try { |
186: numWorkers = Integer.parseInt(numWorkersStr); |
187: } catch (NumberFormatException x) { |
188: usageAndExit( |
189: could not parse number of workers from ˜ + |
190: numWorkersStr + ˜, 4); |
191: } |
192: |
193: File docRoot = new File(docRootStr); |
194: |
195: try { |
196: new HttpServer(docRoot, port, numWorkers, 6); |
197: } catch (IOException x) { |
198: x.printStackTrace(); |
199: usageAndExit(could not construct HttpServer, 5); |
200: } |
201: } |
202: } |
HttpServer keeps a pool of idle workers in idleWorkers by using an ObjectFIFO (line 9). In addition, it keeps a list of all the HttpWorker objects it created in an array (line 12). It also uses the self-running object pattern shown in Chapter 11 . |
The constructor (lines 1884) takes four parameters. The docRoot parameter (line 19) is a File referring to the directory on the server machine that is the base directory for all HTTP file requests. The port parameter (line 20) is the TCP/IP port that the Web server will be listening to for new sockets (requests). The numberOfWorkers parameter (line 21) indicates the number of HttpWorker objects that should be created to service requests. The maxPriority parameter (line 22) is used to indicate the thread priority for the thread running inside HttpServer . The threads running inside the HttpWorker objects will run at a slightly lower priority: (maxPriority - 1) . |
Inside the constructor, a ServerSocket is created to listen on port (line 27). It also specifies that the VM should accept up to 10 sockets more than what has been returned from the accept() method of ServerSocket . If there are any problems setting up the ServerSocket , an IOException will be thrown and will propagate out of the constructor (line 23). The docRoot parameter is then checked to be sure that it refers to an existing file and that it is also a directory (lines 2936). The numberOfWorkers parameter is silently increased to 1 if it was less than that before (line 39). The priorities for the server thread and the worker threads are silently forced into a valid range (lines 4553), so that |
Thread.MIN_PRIORITY < workerPriority < serverPriority < Thread.MAX_PRIORITY |
where workerPriority is just 1 less than serverPriority. |
An ObjectFIFO is created with enough capacity to hold all the workers (line 55). A list of all the workers, idle or busy, is created (line 56), and each of the HttpWorker objects is constructed and added to this list (lines 5864). Each HttpWorker is passed a reference to the idleWorkers FIFO queue so that it can add itself to the queue when it is ready to process a new request. |
The rest of the constructor (lines 6883) follows the pattern in Chapter 11 , with just one minor addition: The priority of the internal thread is set before it is started (line 82). |
The runWork() method (lines 86127) is invoked by the internal thread. As long as no stop has been requested (line 90), the method continues to accept new sockets (line 92). When a socket is accepted, runWork() checks whether any idle workers are available to process the request (line 94). If the pool is empty, the request is denied , and a minimal response is created and sent back over the socket connection (lines 98108). In HTTP message headers, an end-of-line is marked by a carriage -return, line-feed pair: \r\n . |
If an idle worker is found in the pool, it is removed (lines 113114). The processRequest() method of that HttpWorker is invoked, and the socket that was just accepted is passed to it for servicing (line 116). If an IOException occurs in the process of accepting a socket and handing it off to be processed , and no stop has been requested, the exception will have its stack trace dumped (lines 118121). If an InterruptedException occurs, it is caught, and the interrupt is reasserted (lines 122124). |
The stopRequest() method (lines 129141) follows the pattern of Chapter 11 , but adds another two steps. After signaling the internal thread to stop, it invokes stopRequest() on each of the HttpWorker objects. Because the internal thread may be blocked on the accept() method of ServerSocket (line 92), steps have to be taken to unblock it. It does not respond to being interrupted , so if the internal thread is blocked on accept() , the interrupt() call (line 131) is ineffective . To unblock the accept() method, the ServerSocket is closed (line 138), which causes accept() to throw an IOException . This IOException is caught (line 118), and if a stop has been requested, the exception is ignored. In this case, a stop was requested, so the exception thrown by forcibly closing the ServerSocket is silently ignored. This technique can be used to unblock various I/O methods that do not respond to interrupts. I explain it in detail in Chapter 15, Breaking Out of a Blocked I/O State. |
The static method usageAndExit() (lines 147158) assists main() in reporting command-line mistakes and printing the proper command usage. For example, if HttpServer is run with no command-line arguments, |
java HttpServer |
the following output is produced: |
wrong number of arguments |
Usage: java HttpServer <port> <numWorkers> <documentRoot> |
<port> - port to listen on for HTTP requests |
<numWorkers> - number of worker threads to create |
<documentRoot> - base directory for HTML files |
After the error message (line 148) and the usage lines (lines 149156) are printed, usageAndExit() causes the VM to exit with the exit code that was passed to it (line 157). |
The main() method (lines 160201) is used to parse and validate the command-line options and to construct an HttpServer instance. HttpServer can simply be used as a class in a larger application, or it can be run as its own application using main() . First, the port number passed on the command line is converted to an int (lines 169176) and is checked to be a positive number (lines 178181). If either step fails, usageAndExit() is used to halt the application with an appropriate message. Second, the number of HttpWorker objects to create is parsed and validated (lines 183191). Third, the document root directly passed on the command line is converted into a platform-independent File object (line 193). Finally, an attempt is made to construct an HttpServer object with these parameters and a maximum thread priority of 6 (line 196). |
The HttpServer objects internal thread will run at a priority of 6 , and each of the HttpWorker objects internal threads will run at a priority of 5 (line 53). Constructing an HttpServer object might throw an IOException , especially if the port is already in use. If this or another problem occurs, a message is printed and the VM exits (lines 197199). If all goes well, the constructor returns after starting the internal thread, and the main() method completes. |
Class HttpWorker |
HttpWorker objects, shown in Listing 13.6, are used as the specialized pool of threaded resources accessed from HttpServer . HttpWorker is similar to ThreadPoolWorker (refer to Listing 13.2 ), and Ill point out only the major differences. |
Listing 13.6 HttpWorker.javaThe Helper Class for HttpServer |
1: import java.io.*; |
2: import java.net.*; |
3: import java.util.*; |
4: |
5: // uses class ObjectFIFO from chapter 18 |
6: |
7: public class HttpWorker extends Object { |
8: private static int nextWorkerID = 0; |
9: |
10: private File docRoot; |
11: private ObjectFIFO idleWorkers; |
12: private int workerID; |
13: private ObjectFIFO handoffBox; |
14: |
15: private Thread internalThread; |
16: private volatile boolean noStopRequested; |
17: |
18: public HttpWorker( |
19: File docRoot, |
20: int workerPriority, |
21: ObjectFIFO idleWorkers |
22: ) { |
23: |
24: this.docRoot = docRoot; |
25: this.idleWorkers = idleWorkers; |
26: |
27: workerID = getNextWorkerID(); |
28: handoffBox = new ObjectFIFO(1); // only one slot |
29: |
30: // Just before returning, the thread should be |
31: // created and started. |
32: noStopRequested = true; |
33: |
34: Runnable r = new Runnable() { |
35: public void run() { |
36: try { |
37: runWork(); |
38: } catch (Exception x) { |
39: // in case ANY exception slips through |
40: x.printStackTrace(); |
41: } |
42: } |
43: }; |
44: |
45: internalThread = new Thread(r); |
46: internalThread.setPriority(workerPriority); |
47: internalThread.start(); |
48: } |
49: |
50: public static synchronized int getNextWorkerID() { |
51: // synchronized at the class level to ensure uniqueness |
52: int id = nextWorkerID; |
53: nextWorkerID++; |
54: return id; |
55: } |
56: |
57: public void processRequest(Socket s) |
58: throws InterruptedException { |
59: |
60: handoffBox.add(s); |
61: } |
62: |
63: private void runWork() { |
64: Socket s = null; |
65: InputStream in = null; |
66: OutputStream out = null; |
67: |
68: while (noStopRequested) { |
69: try { |
70: // Worker is ready to receive new service |
71: // requests, so it adds itself to the idle |
72: // worker queue. |
73: idleWorkers.add(this); |
74: |
75: // Wait here until the server puts a request |
76: // into the handoff box. |
77: s = (Socket) handoffBox.remove(); |
78: |
79: in = s.getInputStream(); |
80: out = s.getOutputStream(); |
81: generateResponse(in, out); |
82: out.flush(); |
83: } catch (IOException iox) { |
84: System.err.println( |
85: I/O error while processing request, + |
86: ignoring and adding back to idle + |
87: queue - workerID= + workerID); |
88: } catch (InterruptedException x) { |
89: // re-assert the interrupt |
90: Thread.currentThread().interrupt(); |
91: } finally { |
92: // Try to close everything, ignoring |
93: // any IOExceptions that might occur. |
94: if (in != null) { |
95: try { |
96: in.close(); |
97: } catch (IOException iox) { |
98: // ignore |
99: } finally { |
100: in = null; |
101: } |
102: } |
103: |
104: if (out != null) { |
105: try { |
106: out.close(); |
107: } catch (IOException iox) { |
108: // ignore |
109: } finally { |
110: out = null; |
111: } |
112: } |
113: |
114: if (s != null) { |
115: try { |
116: s.close(); |
117: } catch (IOException iox) { |
118: // ignore |
119: } finally { |
120: s = null; |
121: } |
122: } |
123: } |
124: } |
125: } |
126: |
127: private void generateResponse( |
128: InputStream in, |
129: OutputStream out |
130: ) throws IOException { |
131: |
132: BufferedReader reader = |
133: new BufferedReader(new InputStreamReader(in)); |
134: |
135: String requestLine = reader.readLine(); |
136: |
137: if ((requestLine == null) |
138: (requestLine.length() < 1) |
139: ) { |
140: |
141: throw new IOException(could not read request); |
142: } |
143: |
144: System.out.println(workerID= + workerID + |
145: , requestLine= + requestLine); |
146: |
147: StringTokenizer st = new StringTokenizer(requestLine); |
148: String filename = null; |
149: |
150: try { |
151: // request method, typically ˜GET, but ignored |
152: st.nextToken(); |
153: |
154: // the second token should be the filename |
155: filename = st.nextToken(); |
156: } catch (NoSuchElementException x) { |
157: throw new IOException( |
158: could not parse request line); |
159: } |
160: |
161: File requestedFile = generateFile(filename); |
162: |
163: BufferedOutputStream buffOut = |
164: new BufferedOutputStream(out); |
165: |
166: if (requestedFile.exists()) { |
167: System.out.println(workerID= + workerID + |
168: , 200 OK: + filename); |
169: |
170: int fileLen = (int) requestedFile.length(); |
171: |
172: BufferedInputStream fileIn = |
173: new BufferedInputStream( |
174: new FileInputStream(requestedFile)); |
175: |
176: // Use this utility to make a guess obout the |
177: // content type based on the first few bytes |
178: // in the stream. |
179: String contentType = |
180: URLConnection.guessContentTypeFromStream( |
181: fileIn); |
182: |
183: byte[] headerBytes = createHeaderBytes( |
184: HTTP/1.0 200 OK, |
185: fileLen, |
186: contentType |
187: ); |
188: |
189: buffOut.write(headerBytes); |
190: |
191: byte[] buf = new byte[2048]; |
192: int blockLen = 0; |
193: |
194: while ((blockLen = fileIn.read(buf)) != -1) { |
195: buffOut.write(buf, 0, blockLen); |
196: } |
197: |
198: fileIn.close(); |
199: } else { |
200: System.out.println(workerID= + workerID + |
201: , 404 Not Found: + filename); |
202: |
203: byte[] headerBytes = createHeaderBytes( |
204: HTTP/1.0 404 Not Found, |
205: -1, |
206: null |
207: ); |
208: |
209: buffOut.write(headerBytes); |
210: } |
211: |
212: buffOut.flush(); |
213: } |
214: |
215: private File generateFile(String filename) { |
216: File requestedFile = docRoot; // start at the base |
217: |
218: // Build up the path to the requested file in a |
219: // platform independent way. URLs use ˜/ in their |
220: // path, but this platform may not. |
221: StringTokenizer st = new StringTokenizer(filename, /); |
222: while (st.hasMoreTokens()) { |
223: String tok = st.nextToken(); |
224: |
225: if (tok.equals(..)) { |
226: // Silently ignore parts of path that might |
227: // lead out of the document root area. |
228: continue; |
229: } |
230: |
231: requestedFile = |
232: new File(requestedFile, tok); |
233: } |
234: |
235: if (requestedFile.exists() && |
236: requestedFile.isDirectory() |
237: ) { |
238: |
239: // If a directory was requested, modify the request |
240: // to look for the index.html file in that |
241: // directory. |
242: requestedFile = |
243: new File(requestedFile, index.html); |
244: } |
245: |
246: return requestedFile; |
247: } |
248: |
249: private byte[] createHeaderBytes( |
250: String resp, |
251: int contentLen, |
252: String contentType |
253: ) throws IOException { |
254: |
255: ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
256: BufferedWriter writer = new BufferedWriter( |
257: new OutputStreamWriter(baos)); |
258: |
259: // Write the first line of the response, followed by |
260: // the RFC-specified line termination sequence. |
261: writer.write(resp + \r\n); |
262: |
263: // If a length was specified, add it to the header |
264: if (contentLen != -1) { |
265: writer.write( |
266: Content-Length: + contentLen + \r\n); |
267: } |
268: |
269: // If a type was specified, add it to the header |
270: if (contentType != null) { |
271: writer.write( |
272: Content-Type: + contentType + \r\n); |
273: } |
274: |
275: // A blank line is required after the header. |
276: writer.write(\r\n); |
277: writer.flush(); |
278: |
279: byte[] data = baos.toByteArray(); |
280: writer.close(); |
281: |
282: return data; |
283: } |
284: |
285: public void stopRequest() { |
286: noStopRequested = false; |
287: internalThread.interrupt(); |
288: } |
289: |
290: public boolean isAlive() { |
291: return internalThread.isAlive(); |
292: } |
293: } |
The constructor of HttpWorker (lines 1848) is passed docRoot , the base directory for files (line 19), the priority to use for the internal thread (line 20), and a reference to the idle worker pool. The internal thread priority is set just before the thread is started (line 46). A one-slot handoff box is created (line 28) for passing the socket accepted in the HttpServer thread to this workers internal thread. |
The processRequest() method (lines 5761) is invoked by the HttpServer object and puts the reference to the new socket into the handoff box. It should never block because a worker will add itself to the idle pool only when it is ready to process a new request. Also, the server will invoke the processRequest() methods only on workers it just removed from the idle pool. |
The runWork() method (lines 63125) is where the internal thread does the bulk of the processing. Basically, the internal thread loops until a stop is requested (lines 68124). Each time through, the worker adds itself to the servers list of idle workers (line 73). It then blocks, indefinitely waiting for an assignment to be dropped off in its handoff box (line 77). When a Socket reference is picked up, the worker retrieves the InputStream and OutputStream from the reference and passes them to the generateResponse() method (lines 7981). After generateResponse() has read the request from the InputStream and written the proper response to the OutputStream , the OutputStream is flushed to ensure that the data is pushed through any buffering (line 82). If an IOException occurs during any of this, a message is printed (lines 8387). Whether or not an IOException occurs, the finally clause is used to ensure that the streams and socket are closed properly (lines 91112). Next , the while expression is re-evaluated (line 68). If no stop request has been made, the worker adds itself back to the idle queue and waits for its next assignment. |
The generateResponse() method (lines 127213) is used to analyze the HTTP request on the InputStream and generate an appropriate HTTP response on the OutputStream . The raw OutputStream from the socket is wrapped in a BufferedOutputStream to data transfer efficiently (lines 163164). The raw InputStream from the socket is wrapped in a BufferedReader (lines 132133). The first line is read from the request (line 135142) and broken up into tokens to pick out the filename (lines 147159). The generateFile() method is used to create a new File instance that refers to the requested file (line 161). |
Inside generateFile() (lines 215247), the request is parsed on the ˜/ character to build up a new File object starting from docRoot . When the parsing is complete, a check is done to see if the requested file is a directory (lines 235236). If a directory was requested, index.html is appended as the default file to return from a directory (lines 242243). |
If the requested file is found, it is sent back to the client (lines 166198). First, the length of the file in bytes is determined for the response header (line 170). Next, the content type for the file is guessed by the use of the static method guessContentTypeFromStream() on the class java.net.URLConnection (lines 179181). Then the createHeaderBytes() method (see the following description) is used to format the response header and convert it to an array of bytes (lines 183187). This header information is written to the stream (line 189), followed by the contents of the file (lines 191198). |
If the requested file is not found, a brief response indicating that this is sent back to the client (lines 199209). The createHeaderBytes() method is used to format a 404 Not Found response (lines 203207). These bytes are written back to the client (line 209). |
The createHeaderBytes() method (lines 249283) is used to format a response header and write it to an array of bytes. The resp string passed in is written as is (lines 255261). If a valid content length is passed in, the Content-Length: header field is appended (lines 264267). If a content type is specified, the Content-Type: header field is appended (lines 270273). All headers end with a blank line (line 276). The resulting byte[] is passed back to the caller (lines 185188). |
Sample Files to Be Served |
To demonstrate this simple Web server, I created a directory named htmldir , with the following files and subdirectory: |
./htmldir/index.html |
./htmldir/images/five.gif |
./htmldir/images/four.gif |
./htmldir/images/one.gif |
./htmldir/images/three.gif |
./htmldir/images/two.gif |
The index.html file is the main file served, and it makes references to the graphics stored in the images subdirectory. When the server is launched, htmldir will be used as the document root directory. |
The index.html file, shown in Listing 13.7, uses the Hypertext Markup Language (HTML) to specify the layout of a Web page. You can read more about it at this URL: |
http://www.w3.org/TR/REC-html32.html |
Listing 13.7 index.htmlAn HTML File for Demonstrating HttpServer |
1: <html> |
2: <head><title>Thread Pooling - 1</title></head> |
3: <body bgcolor =#FFFFFF> |
4: <center> |
5: <table border=2 cellspacing=5 cellpadding =2> |
6: <tr> |
7: <td valign=top><img src=./images/one.gif></td> |
8: <td>Thread pooling helps to save the VM the work of creating and |
9: destroying threads when they can be easily recycled.</td> |
10: </tr> |
11: <tr> |
12: <td valign=top><img src=./images/twoDOESNOTEXIST.gif></td> |
13: <td>Thread pooling reduces response time because the worker |
14: thread is already created, started, and running. Its only |
15: only waiting for the signal to <b><i>go</i></b>!</td> |
16: </tr> |
17: <tr> |
18: <td valign=top><img src=./images/three.gif></td> |
19: <td>Thread pooling holds resource usage to a predetermined upper |
20: limit. Instead of starting a new thread for every request |
21: received by an HTTP server, a set of workers is available to |
22: service requests. When this set is being completely used by |
23: other requests, the server does not increase its load, but |
24: rejects requests until a worker becomes available.</td> |
25: </tr> |
26: <tr> |
27: <td valign=top><img src=./images/four.gif></td> |
28: <td>Thread pooling generally works best when a thread is |
29: needed for only a brief period of time.</td> |
30: </tr> |
31: <tr> |
32: <td valign=top><img src=./images/five.gif></td> |
33: <td>When using the thread pooling technique, care must |
34: be taken to reasonably ensure that threads dont become |
35: deadlocked or die.<td> |
36: </tr> |
37: </table> |
38: </body> |
39: </html> |
This HTML file makes references to 5 images that will subsequently be requested by the Web browser. All but one of them exists. Instead of two.gif , twoDOESNOTEXIST.gif is requested (line 12) to cause the simple server to generate a 404 Not Found response. |
Running HttpServer with 3 Workers |
First, Ill show you what happens when the HttpServer application is run with only 3 workers in the pool: |
java HttpServer 2001 3 htmldir |
The Web server will be listening on port 2001 for connections. If you cant use this port on your system, specify a different one. (Generally, Web servers listen to port 80 , but on some systems, only privileged users can run processes that listen to ports less than 1024 .) The second command-line argument indicates that 3 workers should be created to service requests. The third argument is the directory where the HTML files are located. On my machine they are in htmldir , which is a subdirectory of the current directory. You can use a fully qualified pathname if necessary. |
Possible output from this simple Web server application is shown in Listing 13.8. |
Listing 13.8 Possible Output from HttpServer with 3 Threads |
1: HttpServer ready to receive requests |
2: workerID=0, requestLine=GET / HTTP/1.0 |
3: workerID=0, 200 OK: /index.html |
4: HttpServer too busy, denying request |
5: HttpServer too busy, denying request |
6: workerID=1, requestLine=GET /images/one.gif HTTP/1.0 |
7: workerID=0, requestLine=GET /images/five.gif HTTP/1.0 |
8: workerID=2, requestLine=GET /images/twoDOESNOTEXIST.gif HTTP/1.0 |
9: workerID=1, 200 OK: /images/one.gif |
10: workerID=0, 200 OK: /images/five.gif |
11: workerID=2, 404 Not Found: /images/twoDOESNOTEXIST.gif |
12: workerID=1, requestLine=GET / HTTP/1.0 |
13: workerID=1, 200 OK: /index.html |
14: workerID=0, requestLine=GET /images/twoDOESNOTEXIST.gif HTTP/1.0 |
15: workerID=2, requestLine=GET /images/three.gif HTTP/1.0 |
16: workerID=1, requestLine=GET /images/four.gif HTTP/1.0 |
17: workerID=0, 404 Not Found: /images/twoDOESNOTEXIST.gif |
18: workerID=2, 200 OK: /images/three.gif |
19: workerID=1, 200 OK: /images/four.gif |
When the server is up and running and ready to received requests, it prints a message (line 1). I used Netscape 4.5 to request the following URL: |
http://localhost:2001/ |
Note | The hostname localhost is used in TCP/IP networking to generically refer to the current machine. If you dont have localhost defined on your system, you can supply another hostname or an IP address. Additionally, you can consider adding the following line to your machines hosts file (it may be /etc/hosts , C:\ windows \hosts , or something else): |
127.0.0.1 localhost |
The Web browser requests a connection on port 2001 of localhost . Then, it asks for filename / . This request is handed off to worker (line 2). The worker responds with /index.html (line 3). When the browser parses this HTML file, it very quickly requests the 5 graphic files referenced. Only 3 workers are available, so when the server is flooded with these requests, it denies 2 of them (lines 45). Worker 1 is assigned to /images/one.gif (line 6), finds it, and sends it (line 9). Worker is assigned to /images/five.gif (line 7), finds it, and sends it (line 10). Worker 2 looks for the nonexistent file /images/twoDOESNOTEXIST.gif (line 8), cant find it, and sends back the 404 Not Found response (line 11). |
After this, the browser looks like Figure 13.1. Notice that the graphics for 2 , 3 , and 4 are missing. The graphic for 2 does not exist and looks slightly different than the surrogate images supplied by Netscape for 3 and 4 . |
To attempt to get the other graphics to load, I clicked on the Location field in the Web browser and pressed the Enter key to retrieve the page again. Referring back to Listing 13.8 , you can see that the / file is requested again (line 12) and served back by worker 1 (line 13). This time, only 3 images are requested because the other 2 are already loaded. The twoDOESNOTEXIST.gif image is requested again (line 14) by the Web browser in the hope that the image is now there. It is not, and the 404 Not Found response is sent again (line 17). Workers are available this time to send three.gif and four.gif (lines 1516 and 1819). |
After this second try, the browser looks like Figure 13.2. Notice that everything is drawn, except for the missing twoDOESNOTEXIST.gif image. |
Figure 13.1: The first attempt to retrieve the page, with only 3 worker threads. | |
Figure 13.2: The second attempt to retrieve the page. | |
Running HttpServer with 10 Workers |
If, instead, HttpServer is started with 10 workers, |
java HttpServer 2001 10 htmldir |
more resources are available to meet the demands. The Web browser is closed and restarted, and the same URL is requested. This time, the server output is shown in Listing 13.9. The Web browser looks like Figure 13.2 on the first try. |
Listing 13.9 Possible Output from HttpServer with 10 Threads |
1: HttpServer ready to receive requests |
2: workerID=0, requestLine=GET / HTTP/1.0 |
3: workerID=0, 200 OK: /index.html |
4: workerID=4, requestLine=GET /images/four.gif HTTP/1.0 |
5: workerID=1, requestLine=GET /images/one.gif HTTP/1.0 |
6: workerID=2, requestLine=GET /images/twoDOESNOTEXIST.gif HTTP/1.0 |
7: workerID=3, requestLine=GET /images/three.gif HTTP/1.0 |
8: workerID=4, 200 OK: /images/four.gif |
9: workerID=1, 200 OK: /images/one.gif |
10: workerID=3, 200 OK: /images/three.gif |
11: workerID=2, 404 Not Found: /images/twoDOESNOTEXIST.gif |
12: workerID=6, requestLine=GET /images/five.gif HTTP/1.0 |
13: workerID=6, 200 OK: /images/five.gif |
With 10 workers running, none of the requests are deniedthe simple Web server has idle workers to spare. When you run the server, you are likely to see slightly different ordering of requests and responses. |
You can expand on this design to include a short wait before requests are denied. This would allow some time for a worker to finish up and put itself back into the idle queue. |
| |||
Toc |