KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > winstone > HttpListener


1 /*
2  * Copyright 2003-2006 Rick Knowles <winstone-devel at lists sourceforge net>
3  * Distributed under the terms of either:
4  * - the common development and distribution license (CDDL), v1.0; or
5  * - the GNU Lesser General Public License, v2.1 or later
6  */

7 package winstone;
8
9 import java.io.IOException JavaDoc;
10 import java.io.InputStream JavaDoc;
11 import java.io.InterruptedIOException JavaDoc;
12 import java.io.OutputStream JavaDoc;
13 import java.net.InetAddress JavaDoc;
14 import java.net.ServerSocket JavaDoc;
15 import java.net.Socket JavaDoc;
16 import java.net.SocketException JavaDoc;
17 import java.util.ArrayList JavaDoc;
18 import java.util.List JavaDoc;
19 import java.util.Map JavaDoc;
20
21 /**
22  * Implements the main listener daemon thread. This is the class that gets
23  * launched by the command line, and owns the server socket, etc. Note that this
24  * class is also used as the base class for the HTTPS listener.
25  *
26  * @author <a HREF="mailto:rick_knowles@hotmail.com">Rick Knowles</a>
27  * @version $Id: HttpListener.java,v 1.14 2006/03/09 14:09:09 rickknowles Exp $
28  */

29 public class HttpListener implements Listener, Runnable JavaDoc {
30     protected static int LISTENER_TIMEOUT = 5000; // every 5s reset the
31
// listener socket
32
protected static int CONNECTION_TIMEOUT = 60000;
33     protected static int BACKLOG_COUNT = 5000;
34     protected static boolean DEFAULT_HNL = false;
35     protected static int KEEP_ALIVE_TIMEOUT = 10000;
36     protected static int KEEP_ALIVE_SLEEP = 20;
37     protected static int KEEP_ALIVE_SLEEP_MAX = 500;
38     protected HostGroup hostGroup;
39     protected ObjectPool objectPool;
40     protected boolean doHostnameLookups;
41     protected int listenPort;
42     protected String JavaDoc listenAddress;
43     protected boolean interrupted;
44
45     protected HttpListener() {
46     }
47
48     /**
49      * Constructor
50      */

51     public HttpListener(Map JavaDoc args, ObjectPool objectPool, HostGroup hostGroup) throws IOException JavaDoc {
52         // Load resources
53
this.hostGroup = hostGroup;
54         this.objectPool = objectPool;
55         this.listenPort = Integer.parseInt(WebAppConfiguration.stringArg(args,
56                 getConnectorName() + "Port", "" + getDefaultPort()));
57         this.listenAddress = WebAppConfiguration.stringArg(args,
58                 getConnectorName() + "ListenAddress", null);
59         this.doHostnameLookups = WebAppConfiguration.booleanArg(args,
60                 getConnectorName() + "DoHostnameLookups", DEFAULT_HNL);
61     }
62
63     public boolean start() {
64         if (this.listenPort < 0) {
65             return false;
66         } else {
67             this.interrupted = false;
68             Thread JavaDoc thread = new Thread JavaDoc(this, Launcher.RESOURCES.getString(
69                     "Listener.ThreadName", new String JavaDoc[] { getConnectorName(),
70                             "" + this.listenPort }));
71             thread.setDaemon(true);
72             thread.start();
73             return true;
74         }
75     }
76     
77     /**
78      * The default port to use - this is just so that we can override for the
79      * SSL connector.
80      */

81     protected int getDefaultPort() {
82         return 8080;
83     }
84
85     /**
86      * The name to use when getting properties - this is just so that we can
87      * override for the SSL connector.
88      */

89     protected String JavaDoc getConnectorName() {
90         return getConnectorScheme();
91     }
92
93     protected String JavaDoc getConnectorScheme() {
94         return "http";
95     }
96     
97     /**
98      * Gets a server socket - this is mostly for the purpose of allowing an
99      * override in the SSL connector.
100      */

101     protected ServerSocket JavaDoc getServerSocket() throws IOException JavaDoc {
102         ServerSocket JavaDoc ss = this.listenAddress == null ? new ServerSocket JavaDoc(
103                 this.listenPort, BACKLOG_COUNT) : new ServerSocket JavaDoc(
104                 this.listenPort, BACKLOG_COUNT, InetAddress
105                         .getByName(this.listenAddress));
106         return ss;
107     }
108
109     /**
110      * The main run method. This continually listens for incoming connections,
111      * and allocates any that it finds to a request handler thread, before going
112      * back to listen again.
113      */

114     public void run() {
115         try {
116             ServerSocket JavaDoc ss = getServerSocket();
117             ss.setSoTimeout(LISTENER_TIMEOUT);
118             Logger.log(Logger.INFO, Launcher.RESOURCES, "HttpListener.StartupOK",
119                     new String JavaDoc[] { getConnectorName().toUpperCase(),
120                             this.listenPort + "" });
121
122             // Enter the main loop
123
while (!interrupted) {
124                 // Get the listener
125
Socket JavaDoc s = null;
126                 try {
127                     s = ss.accept();
128                 } catch (java.io.InterruptedIOException JavaDoc err) {
129                     s = null;
130                 }
131
132                 // if we actually got a socket, process it. Otherwise go around
133
// again
134
if (s != null)
135                     this.objectPool.handleRequest(s, this);
136             }
137
138             // Close server socket
139
ss.close();
140         } catch (Throwable JavaDoc err) {
141             Logger.log(Logger.ERROR, Launcher.RESOURCES, "HttpListener.ShutdownError",
142                     getConnectorName().toUpperCase(), err);
143         }
144
145         Logger.log(Logger.INFO, Launcher.RESOURCES, "HttpListener.ShutdownOK",
146                 getConnectorName().toUpperCase());
147     }
148
149     /**
150      * Interrupts the listener thread. This will trigger a listener shutdown
151      * once the so timeout has passed.
152      */

153     public void destroy() {
154         this.interrupted = true;
155     }
156
157     /**
158      * Called by the request handler thread, because it needs specific setup
159      * code for this connection's protocol (ie construction of request/response
160      * objects, in/out streams, etc).
161      *
162      * This implementation parses incoming AJP13 packets, and builds an
163      * outputstream that is capable of writing back the response in AJP13
164      * packets.
165      */

166     public void allocateRequestResponse(Socket JavaDoc socket, InputStream JavaDoc inSocket,
167             OutputStream JavaDoc outSocket, RequestHandlerThread handler,
168             boolean iAmFirst) throws SocketException JavaDoc, IOException JavaDoc {
169         Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
170                 "HttpListener.AllocatingRequest", Thread.currentThread()
171                         .getName());
172         socket.setSoTimeout(CONNECTION_TIMEOUT);
173
174         // Build input/output streams, plus request/response
175
WinstoneInputStream inData = new WinstoneInputStream(inSocket);
176         WinstoneOutputStream outData = new WinstoneOutputStream(outSocket, false);
177         WinstoneRequest req = this.objectPool.getRequestFromPool();
178         WinstoneResponse rsp = this.objectPool.getResponseFromPool();
179         outData.setResponse(rsp);
180         req.setInputStream(inData);
181         rsp.setOutputStream(outData);
182         rsp.setRequest(req);
183         // rsp.updateContentTypeHeader("text/html");
184
req.setHostGroup(this.hostGroup);
185
186         // Set the handler's member variables so it can execute the servlet
187
handler.setRequest(req);
188         handler.setResponse(rsp);
189         handler.setInStream(inData);
190         handler.setOutStream(outData);
191         
192         // If using this listener, we must set the server header now, because it
193
// must be the first header. Ajp13 listener can defer to the Apache Server
194
// header
195
rsp.setHeader(WinstoneResponse.SERVER_HEADER,
196                 Launcher.RESOURCES.getString("ServerVersion"));
197     }
198
199     /**
200      * Called by the request handler thread, because it needs specific shutdown
201      * code for this connection's protocol (ie releasing input/output streams,
202      * etc).
203      */

204     public void deallocateRequestResponse(RequestHandlerThread handler,
205             WinstoneRequest req, WinstoneResponse rsp,
206             WinstoneInputStream inData, WinstoneOutputStream outData)
207             throws IOException JavaDoc {
208         handler.setInStream(null);
209         handler.setOutStream(null);
210         handler.setRequest(null);
211         handler.setResponse(null);
212         if (req != null)
213             this.objectPool.releaseRequestToPool(req);
214         if (rsp != null)
215             this.objectPool.releaseResponseToPool(rsp);
216     }
217
218     public String JavaDoc parseURI(RequestHandlerThread handler, WinstoneRequest req,
219             WinstoneResponse rsp, WinstoneInputStream inData, Socket JavaDoc socket,
220             boolean iAmFirst) throws IOException JavaDoc {
221         parseSocketInfo(socket, req);
222
223         // Read the header line (because this is the first line of the request,
224
// apply keep-alive timeouts to it if we are not the first request)
225
if (!iAmFirst) {
226             socket.setSoTimeout(KEEP_ALIVE_TIMEOUT);
227         }
228         
229         byte uriBuffer[] = null;
230         try {
231             Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "HttpListener.WaitingForURILine");
232             uriBuffer = inData.readLine();
233         } catch (InterruptedIOException JavaDoc err) {
234             // keep alive timeout ? ignore if not first
235
if (iAmFirst) {
236                 throw err;
237             } else {
238                 return null;
239             }
240         } finally {
241             try {socket.setSoTimeout(CONNECTION_TIMEOUT);} catch (Throwable JavaDoc err) {}
242         }
243         handler.setRequestStartTime();
244
245         // Get header data (eg protocol, method, uri, headers, etc)
246
String JavaDoc uriLine = new String JavaDoc(uriBuffer);
247         if (uriLine.trim().equals(""))
248             throw new SocketException JavaDoc("Empty URI Line");
249         String JavaDoc servletURI = parseURILine(uriLine, req, rsp);
250         parseHeaders(req, inData);
251         rsp.extractRequestKeepAliveHeader(req);
252         int contentLength = req.getContentLength();
253         if (contentLength != -1)
254             inData.setContentLength(contentLength);
255         return servletURI;
256     }
257
258     /**
259      * Called by the request handler thread, because it needs specific shutdown
260      * code for this connection's protocol if the keep-alive period expires (ie
261      * closing sockets, etc).
262      *
263      * This implementation simply shuts down the socket and streams.
264      */

265     public void releaseSocket(Socket JavaDoc socket, InputStream JavaDoc inSocket,
266             OutputStream JavaDoc outSocket) throws IOException JavaDoc {
267         // Logger.log(Logger.FULL_DEBUG, "Releasing socket: " +
268
// Thread.currentThread().getName());
269
inSocket.close();
270         outSocket.close();
271         socket.close();
272     }
273
274     protected void parseSocketInfo(Socket JavaDoc socket, WinstoneRequest req)
275             throws IOException JavaDoc {
276         Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "HttpListener.ParsingSocketInfo");
277         req.setScheme(getConnectorScheme());
278         req.setServerPort(socket.getLocalPort());
279         req.setLocalPort(socket.getLocalPort());
280         req.setLocalAddr(socket.getLocalAddress().getHostAddress());
281         req.setRemoteIP(socket.getInetAddress().getHostAddress());
282         req.setRemotePort(socket.getPort());
283         if (this.doHostnameLookups) {
284             req.setServerName(socket.getLocalAddress().getHostName());
285             req.setRemoteName(socket.getInetAddress().getHostName());
286             req.setLocalName(socket.getLocalAddress().getHostName());
287         } else {
288             req.setServerName(socket.getLocalAddress().getHostAddress());
289             req.setRemoteName(socket.getInetAddress().getHostAddress());
290             req.setLocalName(socket.getLocalAddress().getHostAddress());
291         }
292     }
293
294     /**
295      * Tries to wait for extra requests on the same socket. If any are found
296      * before the timeout expires, it exits with a true, indicating a new
297      * request is waiting. If the protocol does not support keep-alives, or the
298      * request instructed us to close the connection, or the timeout expires,
299      * return a false, instructing the handler thread to begin shutting down the
300      * socket and relase itself.
301      */

302     public boolean processKeepAlive(WinstoneRequest request,
303             WinstoneResponse response, InputStream JavaDoc inSocket)
304             throws IOException JavaDoc, InterruptedException JavaDoc {
305         // Try keep alive if allowed
306
boolean continueFlag = !response.closeAfterRequest();
307         return continueFlag;
308     }
309
310     /**
311      * Processes the uri line into it's component parts, determining protocol,
312      * method and uri
313      */

314     private String JavaDoc parseURILine(String JavaDoc uriLine, WinstoneRequest req,
315             WinstoneResponse rsp) {
316         Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "HttpListener.UriLine", uriLine.trim());
317         
318         // Method
319
int spacePos = uriLine.indexOf(' ');
320         if (spacePos == -1)
321             throw new WinstoneException(Launcher.RESOURCES.getString(
322                     "HttpListener.ErrorUriLine", uriLine));
323         String JavaDoc method = uriLine.substring(0, spacePos).toUpperCase();
324         String JavaDoc fullURI = null;
325
326         // URI
327
String JavaDoc remainder = uriLine.substring(spacePos + 1);
328         spacePos = remainder.indexOf(' ');
329         if (spacePos == -1) {
330             fullURI = trimHostName(remainder.trim());
331             req.setProtocol("HTTP/0.9");
332             rsp.setProtocol("HTTP/0.9");
333         } else {
334             fullURI = trimHostName(remainder.substring(0, spacePos).trim());
335             String JavaDoc protocol = remainder.substring(spacePos + 1).trim().toUpperCase();
336             req.setProtocol(protocol);
337             rsp.setProtocol(protocol);
338         }
339
340         req.setMethod(method);
341         // req.setRequestURI(fullURI);
342
return fullURI;
343     }
344
345     private String JavaDoc trimHostName(String JavaDoc input) {
346         if (input == null)
347             return null;
348         else if (input.startsWith("/"))
349             return input;
350
351         int hostStart = input.indexOf("://");
352         if (hostStart == -1)
353             return input;
354         String JavaDoc hostName = input.substring(hostStart + 3);
355         int pathStart = hostName.indexOf('/');
356         if (pathStart == -1)
357             return "/";
358         else
359             return hostName.substring(pathStart);
360     }
361
362     /**
363      * Parse the incoming stream into a list of headers (stopping at the first
364      * blank line), then call the parseHeaders(req, list) method on that list.
365      */

366     public void parseHeaders(WinstoneRequest req, WinstoneInputStream inData)
367             throws IOException JavaDoc {
368         List JavaDoc headerList = new ArrayList JavaDoc();
369
370         if (!req.getProtocol().startsWith("HTTP/0")) {
371             // Loop to get headers
372
byte headerBuffer[] = inData.readLine();
373             String JavaDoc headerLine = new String JavaDoc(headerBuffer);
374
375             while (headerLine.trim().length() > 0) {
376                 if (headerLine.indexOf(':') != -1) {
377                     headerList.add(headerLine.trim());
378                     Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
379                             "HttpListener.Header", headerLine.trim());
380                 }
381                 headerBuffer = inData.readLine();
382                 headerLine = new String JavaDoc(headerBuffer);
383             }
384         }
385
386         // If no headers available, parse an empty list
387
req.parseHeaders(headerList);
388     }
389 }
390
Popular Tags