Using an Inner Class to Hide run()

Chapter 13 - Thread Pooling

Java Thread Programming
Paul Hyde
  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


Java Thread Programming
Java Thread Programming
ISBN: 0672315858
EAN: 2147483647
Year: 2005
Pages: 149
Authors: Paul Hyde

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net