KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > winstone > ajp13 > Ajp13Listener


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.ajp13;
8
9 import java.io.ByteArrayInputStream JavaDoc;
10 import java.io.IOException JavaDoc;
11 import java.io.InputStream JavaDoc;
12 import java.io.InterruptedIOException JavaDoc;
13 import java.io.OutputStream JavaDoc;
14 import java.io.UnsupportedEncodingException JavaDoc;
15 import java.net.InetAddress JavaDoc;
16 import java.net.ServerSocket JavaDoc;
17 import java.net.Socket JavaDoc;
18 import java.net.SocketException JavaDoc;
19 import java.security.cert.CertificateException JavaDoc;
20 import java.security.cert.CertificateFactory JavaDoc;
21 import java.security.cert.X509Certificate JavaDoc;
22 import java.util.Arrays JavaDoc;
23 import java.util.Iterator JavaDoc;
24 import java.util.Map JavaDoc;
25
26 import winstone.HostGroup;
27 import winstone.Launcher;
28 import winstone.Listener;
29 import winstone.Logger;
30 import winstone.ObjectPool;
31 import winstone.RequestHandlerThread;
32 import winstone.WebAppConfiguration;
33 import winstone.WinstoneException;
34 import winstone.WinstoneInputStream;
35 import winstone.WinstoneOutputStream;
36 import winstone.WinstoneRequest;
37 import winstone.WinstoneResourceBundle;
38 import winstone.WinstoneResponse;
39
40 /**
41  * Implements the main listener daemon thread. This is the class that gets
42  * launched by the command line, and owns the server socket, etc.
43  *
44  * @author mailto: <a HREF="rick_knowles@hotmail.com">Rick Knowles</a>
45  * @version $Id: Ajp13Listener.java,v 1.12 2006/03/24 17:24:22 rickknowles Exp $
46  */

47 public class Ajp13Listener implements Listener, Runnable JavaDoc {
48     public final static WinstoneResourceBundle AJP_RESOURCES = new WinstoneResourceBundle("winstone.ajp13.LocalStrings");
49     
50     private final static int LISTENER_TIMEOUT = 5000; // every 5s reset the listener socket
51
private final static int DEFAULT_PORT = 8009;
52     private final static int CONNECTION_TIMEOUT = 60000;
53     private final static int BACKLOG_COUNT = 1000;
54     private final static int KEEP_ALIVE_TIMEOUT = -1;
55 // private final static int KEEP_ALIVE_SLEEP = 50;
56
// private final static int KEEP_ALIVE_SLEEP_MAX = 500;
57
private final static String JavaDoc TEMPORARY_URL_STASH = "winstone.ajp13.TemporaryURLAttribute";
58     
59     private HostGroup hostGroup;
60     private ObjectPool objectPool;
61     private int listenPort;
62     private boolean interrupted;
63     private String JavaDoc listenAddress;
64
65     /**
66      * Constructor
67      */

68     public Ajp13Listener(Map JavaDoc args, ObjectPool objectPool, HostGroup hostGroup) {
69         // Load resources
70
this.hostGroup = hostGroup;
71         this.objectPool = objectPool;
72
73         this.listenPort = Integer.parseInt(WebAppConfiguration.stringArg(args,
74                 "ajp13Port", "" + DEFAULT_PORT));
75         this.listenAddress = WebAppConfiguration.stringArg(args,
76                 "ajp13ListenAddress", null);
77     }
78
79     public boolean start() {
80         if (this.listenPort < 0) {
81             return false;
82         } else {
83             this.interrupted = false;
84             Thread JavaDoc thread = new Thread JavaDoc(this, Launcher.RESOURCES.getString(
85                     "Listener.ThreadName", new String JavaDoc[] { "ajp13",
86                             "" + this.listenPort }));
87             thread.setDaemon(true);
88             thread.start();
89             return true;
90         }
91     }
92
93     /**
94      * The main run method. This handles the normal thread processing.
95      */

96     public void run() {
97         try {
98             ServerSocket JavaDoc ss = this.listenAddress == null ? new ServerSocket JavaDoc(
99                     this.listenPort, BACKLOG_COUNT) : new ServerSocket JavaDoc(
100                     this.listenPort, BACKLOG_COUNT, InetAddress
101                             .getByName(this.listenAddress));
102             ss.setSoTimeout(LISTENER_TIMEOUT);
103             Logger.log(Logger.INFO, AJP_RESOURCES, "Ajp13Listener.StartupOK",
104                     this.listenPort + "");
105
106             // Enter the main loop
107
while (!interrupted) {
108                 // Get the listener
109
Socket JavaDoc s = null;
110                 try {
111                     s = ss.accept();
112                 } catch (java.io.InterruptedIOException JavaDoc err) {
113                     s = null;
114                 }
115
116                 // if we actually got a socket, process it. Otherwise go around
117
// again
118
if (s != null)
119                     this.objectPool.handleRequest(s, this);
120             }
121
122             // Close server socket
123
ss.close();
124         } catch (Throwable JavaDoc err) {
125             Logger.log(Logger.ERROR, AJP_RESOURCES,
126                     "Ajp13Listener.ShutdownError", err);
127         }
128
129         Logger.log(Logger.INFO, AJP_RESOURCES, "Ajp13Listener.ShutdownOK");
130     }
131
132     /**
133      * Interrupts the listener thread. This will trigger a listener shutdown
134      * once the so timeout has passed.
135      */

136     public void destroy() {
137         this.interrupted = true;
138     }
139
140     /**
141      * Called by the request handler thread, because it needs specific setup
142      * code for this connection's protocol (ie construction of request/response
143      * objects, in/out streams, etc).
144      *
145      * This implementation parses incoming AJP13 packets, and builds an
146      * outputstream that is capable of writing back the response in AJP13
147      * packets.
148      */

149     public void allocateRequestResponse(Socket JavaDoc socket, InputStream JavaDoc inSocket,
150             OutputStream JavaDoc outSocket, RequestHandlerThread handler,
151             boolean iAmFirst) throws SocketException JavaDoc, IOException JavaDoc {
152         WinstoneRequest req = this.objectPool.getRequestFromPool();
153         WinstoneResponse rsp = this.objectPool.getResponseFromPool();
154         rsp.setRequest(req);
155         req.setHostGroup(this.hostGroup);
156         // rsp.updateContentTypeHeader("text/html");
157

158         if (iAmFirst || (KEEP_ALIVE_TIMEOUT == -1))
159             socket.setSoTimeout(CONNECTION_TIMEOUT);
160         else
161             socket.setSoTimeout(KEEP_ALIVE_TIMEOUT);
162         Ajp13IncomingPacket headers = null;
163         try {
164             headers = new Ajp13IncomingPacket(inSocket, handler);
165         } catch (InterruptedIOException JavaDoc err) {
166             // keep alive timeout ? ignore if not first
167
if (iAmFirst) {
168                 throw err;
169             } else {
170                 deallocateRequestResponse(handler, req, rsp, null, null);
171                 return;
172             }
173         } finally {
174             try {socket.setSoTimeout(CONNECTION_TIMEOUT);} catch (Throwable JavaDoc err) {}
175         }
176
177         if (headers.getPacketLength() > 0) {
178             headers.parsePacket("8859_1");
179             parseSocketInfo(headers, req);
180             req.parseHeaders(Arrays.asList(headers.getHeaders()));
181             String JavaDoc servletURI = parseURILine(headers, req, rsp);
182             req.setAttribute(TEMPORARY_URL_STASH, servletURI);
183
184             // If content-length present and non-zero, download the other
185
// packets
186
WinstoneInputStream inData = null;
187             int contentLength = req.getContentLength();
188             if (contentLength > 0) {
189                 byte bodyContent[] = new byte[contentLength];
190                 int position = 0;
191                 while (position < contentLength) {
192                     outSocket.write(getBodyRequestPacket(Math.min(contentLength
193                             - position, 8184)));
194                     position = getBodyResponsePacket(inSocket, bodyContent,
195                             position);
196                     Logger.log(Logger.FULL_DEBUG, AJP_RESOURCES,
197                             "Ajp13Listener.ReadBodyProgress", new String JavaDoc[] {
198                                     "" + position, "" + contentLength });
199
200                 }
201                 inData = new WinstoneInputStream(bodyContent);
202                 inData.setContentLength(contentLength);
203             } else
204                 inData = new WinstoneInputStream(new byte[0]);
205             req.setInputStream(inData);
206
207             // Build input/output streams, plus request/response
208
WinstoneOutputStream outData = new Ajp13OutputStream(socket
209                     .getOutputStream(), "8859_1");
210             outData.setResponse(rsp);
211             rsp.setOutputStream(outData);
212
213             // Set the handler's member variables so it can execute the servlet
214
handler.setRequest(req);
215             handler.setResponse(rsp);
216             handler.setInStream(inData);
217             handler.setOutStream(outData);
218         }
219     }
220
221     /**
222      * Called by the request handler thread, because it needs specific shutdown
223      * code for this connection's protocol (ie releasing input/output streams,
224      * etc).
225      */

226     public void deallocateRequestResponse(RequestHandlerThread handler,
227             WinstoneRequest req, WinstoneResponse rsp,
228             WinstoneInputStream inData, WinstoneOutputStream outData)
229             throws IOException JavaDoc {
230         handler.setInStream(null);
231         handler.setOutStream(null);
232         handler.setRequest(null);
233         handler.setResponse(null);
234         if (req != null)
235             this.objectPool.releaseRequestToPool(req);
236         if (rsp != null)
237             this.objectPool.releaseResponseToPool(rsp);
238     }
239
240     /**
241      * This is kind of a hack, since we have already parsed the uri to get the
242      * input stream. Just pass back the request uri
243      */

244     public String JavaDoc parseURI(RequestHandlerThread handler, WinstoneRequest req,
245             WinstoneResponse rsp, WinstoneInputStream inData, Socket JavaDoc socket,
246             boolean iAmFirst) throws IOException JavaDoc {
247         String JavaDoc uri = (String JavaDoc) req.getAttribute(TEMPORARY_URL_STASH);
248         req.removeAttribute(TEMPORARY_URL_STASH);
249         return uri;
250     }
251
252     /**
253      * Called by the request handler thread, because it needs specific shutdown
254      * code for this connection's protocol if the keep-alive period expires (ie
255      * closing sockets, etc).
256      *
257      * This implementation simply shuts down the socket and streams.
258      */

259     public void releaseSocket(Socket JavaDoc socket, InputStream JavaDoc inSocket,
260             OutputStream JavaDoc outSocket) throws IOException JavaDoc {
261         // Logger.log(Logger.FULL_DEBUG, "Releasing socket: " +
262
// Thread.currentThread().getName());
263
inSocket.close();
264         outSocket.close();
265         socket.close();
266     }
267
268     /**
269      * Extract the header details relating to socket stuff from the ajp13 header
270      * packet
271      */

272     private void parseSocketInfo(Ajp13IncomingPacket headers,
273             WinstoneRequest req) {
274         req.setServerPort(headers.getServerPort());
275         req.setRemoteIP(headers.getRemoteAddress());
276         req.setServerName(headers.getServerName());
277         req.setLocalPort(headers.getServerPort());
278         req.setLocalAddr(headers.getServerName());
279         req.setRemoteIP(headers.getRemoteAddress());
280         if ((headers.getRemoteHost() != null)
281                 && !headers.getRemoteHost().equals(""))
282             req.setRemoteName(headers.getRemoteHost());
283         else
284             req.setRemoteName(headers.getRemoteAddress());
285         req.setScheme(headers.isSSL() ? "https" : "http");
286         req.setIsSecure(headers.isSSL());
287     }
288
289     /**
290      * Extract the header details relating to protocol, uri, etc from the ajp13
291      * header packet
292      */

293     private String JavaDoc parseURILine(Ajp13IncomingPacket headers,
294             WinstoneRequest req, WinstoneResponse rsp)
295             throws UnsupportedEncodingException JavaDoc {
296         req.setMethod(headers.getMethod());
297         req.setProtocol(headers.getProtocol());
298         rsp.setProtocol(headers.getProtocol());
299         rsp.extractRequestKeepAliveHeader(req);
300         // req.setServletPath(headers.getURI());
301
// req.setRequestURI(headers.getURI());
302

303         // Get query string if supplied
304
for (Iterator JavaDoc i = headers.getAttributes().keySet().iterator(); i
305                 .hasNext();) {
306             String JavaDoc attName = (String JavaDoc) i.next();
307             if (attName.equals("query_string")) {
308                 String JavaDoc qs = (String JavaDoc) headers.getAttributes().get("query_string");
309                 req.setQueryString(qs);
310                 // req.getParameters().putAll(WinstoneRequest.extractParameters(qs,
311
// req.getEncoding(), mainResources));
312
// req.setRequestURI(headers.getURI() + "?" + qs);
313
} else if (attName.equals("ssl_cert")) {
314                 String JavaDoc certValue = (String JavaDoc) headers.getAttributes().get(
315                         "ssl_cert");
316                 InputStream JavaDoc certStream = new ByteArrayInputStream JavaDoc(certValue
317                         .getBytes("8859_1"));
318                 X509Certificate JavaDoc certificateArray[] = new X509Certificate JavaDoc[1];
319                 try {
320                     certificateArray[0] = (X509Certificate JavaDoc) CertificateFactory
321                             .getInstance("X.509").generateCertificate(
322                                     certStream);
323                 } catch (CertificateException JavaDoc err) {
324                     Logger.log(Logger.DEBUG, AJP_RESOURCES,
325                             "Ajp13Listener.SkippingCert", certValue);
326                 }
327                 req.setAttribute("javax.servlet.request.X509Certificate",
328                         certificateArray);
329                 req.setIsSecure(true);
330             } else if (attName.equals("ssl_cipher")) {
331                 String JavaDoc cipher = (String JavaDoc) headers.getAttributes().get(
332                         "ssl_cipher");
333                 req.setAttribute("javax.servlet.request.cipher_suite", cipher);
334                 req.setAttribute("javax.servlet.request.key_size",
335                         getKeySize(cipher));
336                 req.setIsSecure(true);
337             } else if (attName.equals("ssl_session")) {
338                 req.setAttribute("javax.servlet.request.ssl_session", headers
339                         .getAttributes().get("ssl_session"));
340                 req.setIsSecure(true);
341             } else
342                 Logger.log(Logger.DEBUG, AJP_RESOURCES,
343                         "Ajp13Listener.UnknownAttribute", new String JavaDoc[] {
344                                 attName,
345                                 "" + headers.getAttributes().get(attName) });
346         }
347         return headers.getURI();
348
349     }
350
351     private Integer JavaDoc getKeySize(String JavaDoc cipherSuite) {
352         if (cipherSuite.indexOf("_WITH_NULL_") != -1)
353             return new Integer JavaDoc(0);
354         else if (cipherSuite.indexOf("_WITH_IDEA_CBC_") != -1)
355             return new Integer JavaDoc(128);
356         else if (cipherSuite.indexOf("_WITH_RC2_CBC_40_") != -1)
357             return new Integer JavaDoc(40);
358         else if (cipherSuite.indexOf("_WITH_RC4_40_") != -1)
359             return new Integer JavaDoc(40);
360         else if (cipherSuite.indexOf("_WITH_RC4_128_") != -1)
361             return new Integer JavaDoc(128);
362         else if (cipherSuite.indexOf("_WITH_DES40_CBC_") != -1)
363             return new Integer JavaDoc(40);
364         else if (cipherSuite.indexOf("_WITH_DES_CBC_") != -1)
365             return new Integer JavaDoc(56);
366         else if (cipherSuite.indexOf("_WITH_3DES_EDE_CBC_") != -1)
367             return new Integer JavaDoc(168);
368         else
369             return null;
370     }
371
372     /**
373      * Tries to wait for extra requests on the same socket. If any are found
374      * before the timeout expires, it exits with a true, indicating a new
375      * request is waiting. If the timeout expires, return a false, instructing
376      * the handler thread to begin shutting down the socket and relase itself.
377      */

378     public boolean processKeepAlive(WinstoneRequest request,
379             WinstoneResponse response, InputStream JavaDoc inSocket)
380             throws IOException JavaDoc, InterruptedException JavaDoc {
381         return true;
382     }
383
384     /**
385      * Build the packet needed for asking for a body chunk
386      */

387     private byte[] getBodyRequestPacket(int desiredPacketLength) {
388         byte getBodyRequestPacket[] = new byte[] { 0x41, 0x42, 0x00, 0x03,
389                 0x06, 0x00, 0x00 };
390         Ajp13OutputStream.setIntBlock(desiredPacketLength,
391                 getBodyRequestPacket, 5);
392         return getBodyRequestPacket;
393     }
394
395     /**
396      * Process the server response to a get_body_chunk request. This loads the
397      * packet from the stream, and unpacks it into the buffer at the right
398      * place.
399      */

400     private int getBodyResponsePacket(InputStream JavaDoc in, byte buffer[], int offset)
401             throws IOException JavaDoc {
402         // Get the incoming packet flag
403
byte headerBuffer[] = new byte[4];
404         int headerBytesRead = in.read(headerBuffer);
405         if (headerBytesRead != 4)
406             throw new WinstoneException(AJP_RESOURCES
407                     .getString("Ajp13Listener.InvalidHeader"));
408         else if ((headerBuffer[0] != 0x12) || (headerBuffer[1] != 0x34))
409             throw new WinstoneException(AJP_RESOURCES
410                     .getString("Ajp13Listener.InvalidHeader"));
411
412         // Read in the whole packet
413
int packetLength = ((headerBuffer[2] & 0xFF) << 8)
414                 + (headerBuffer[3] & 0xFF);
415         if (packetLength == 0)
416             return offset;
417
418         // Look for packet length
419
byte bodyLengthBuffer[] = new byte[2];
420         in.read(bodyLengthBuffer);
421         int bodyLength = ((bodyLengthBuffer[0] & 0xFF) << 8)
422                 + (bodyLengthBuffer[1] & 0xFF);
423         int packetBytesRead = in.read(buffer, offset, bodyLength);
424
425         if (packetBytesRead < bodyLength)
426             throw new WinstoneException(AJP_RESOURCES
427                     .getString("Ajp13Listener.ShortPacket"));
428         else
429             return packetBytesRead + offset;
430     }
431 //
432
// /**
433
// * Useful method for dumping out the contents of a packet in hex form
434
// */
435
// public static void packetDump(byte packetBytes[], int packetLength) {
436
// String dump = "";
437
// for (int n = 0; n < packetLength; n+=16) {
438
// String line = Integer.toHexString((n >> 4) & 0xF) + "0:";
439
// for (int j = 0; j < Math.min(packetLength - n, 16); j++)
440
// line = line + " " + ((packetBytes[n + j] & 0xFF) < 16 ? "0" : "") +
441
// Integer.toHexString(packetBytes[n + j] & 0xFF);
442
//
443
// line = line + " ";
444
// for (int j = 0; j < Math.min(packetLength - n, 16); j++) {
445
// byte me = (byte) (packetBytes[n + j] & 0xFF);
446
// line = line + (((me > 32) && (me < 123)) ? (char) me : '.');
447
// }
448
// dump = dump + line + "\r\n";
449
// }
450
// System.out.println(dump);
451
// }
452
}
453
Popular Tags