001: /*
002: * Copyright 2003-2006 Rick Knowles <winstone-devel at lists sourceforge net>
003: * Distributed under the terms of either:
004: * - the common development and distribution license (CDDL), v1.0; or
005: * - the GNU Lesser General Public License, v2.1 or later
006: */
007: package winstone.ajp13;
008:
009: import java.io.ByteArrayInputStream;
010: import java.io.IOException;
011: import java.io.InputStream;
012: import java.io.InterruptedIOException;
013: import java.io.OutputStream;
014: import java.io.UnsupportedEncodingException;
015: import java.net.InetAddress;
016: import java.net.ServerSocket;
017: import java.net.Socket;
018: import java.net.SocketException;
019: import java.security.cert.CertificateException;
020: import java.security.cert.CertificateFactory;
021: import java.security.cert.X509Certificate;
022: import java.util.Arrays;
023: import java.util.Iterator;
024: import java.util.Map;
025:
026: import winstone.HostGroup;
027: import winstone.Launcher;
028: import winstone.Listener;
029: import winstone.Logger;
030: import winstone.ObjectPool;
031: import winstone.RequestHandlerThread;
032: import winstone.WebAppConfiguration;
033: import winstone.WinstoneException;
034: import winstone.WinstoneInputStream;
035: import winstone.WinstoneOutputStream;
036: import winstone.WinstoneRequest;
037: import winstone.WinstoneResourceBundle;
038: import winstone.WinstoneResponse;
039:
040: /**
041: * Implements the main listener daemon thread. This is the class that gets
042: * launched by the command line, and owns the server socket, etc.
043: *
044: * @author mailto: <a href="rick_knowles@hotmail.com">Rick Knowles</a>
045: * @version $Id: Ajp13Listener.java,v 1.12 2006/03/24 17:24:22 rickknowles Exp $
046: */
047: public class Ajp13Listener implements Listener, Runnable {
048: public final static WinstoneResourceBundle AJP_RESOURCES = new WinstoneResourceBundle(
049: "winstone.ajp13.LocalStrings");
050:
051: private final static int LISTENER_TIMEOUT = 5000; // every 5s reset the listener socket
052: private final static int DEFAULT_PORT = 8009;
053: private final static int CONNECTION_TIMEOUT = 60000;
054: private final static int BACKLOG_COUNT = 1000;
055: private final static int KEEP_ALIVE_TIMEOUT = -1;
056: // private final static int KEEP_ALIVE_SLEEP = 50;
057: // private final static int KEEP_ALIVE_SLEEP_MAX = 500;
058: private final static String TEMPORARY_URL_STASH = "winstone.ajp13.TemporaryURLAttribute";
059:
060: private HostGroup hostGroup;
061: private ObjectPool objectPool;
062: private int listenPort;
063: private boolean interrupted;
064: private String listenAddress;
065:
066: /**
067: * Constructor
068: */
069: public Ajp13Listener(Map args, ObjectPool objectPool,
070: HostGroup hostGroup) {
071: // Load resources
072: this .hostGroup = hostGroup;
073: this .objectPool = objectPool;
074:
075: this .listenPort = Integer.parseInt(WebAppConfiguration
076: .stringArg(args, "ajp13Port", "" + DEFAULT_PORT));
077: this .listenAddress = WebAppConfiguration.stringArg(args,
078: "ajp13ListenAddress", null);
079: }
080:
081: public boolean start() {
082: if (this .listenPort < 0) {
083: return false;
084: } else {
085: this .interrupted = false;
086: Thread thread = new Thread(this , Launcher.RESOURCES
087: .getString("Listener.ThreadName", new String[] {
088: "ajp13", "" + this .listenPort }));
089: thread.setDaemon(true);
090: thread.start();
091: return true;
092: }
093: }
094:
095: /**
096: * The main run method. This handles the normal thread processing.
097: */
098: public void run() {
099: try {
100: ServerSocket ss = this .listenAddress == null ? new ServerSocket(
101: this .listenPort, BACKLOG_COUNT)
102: : new ServerSocket(this .listenPort, BACKLOG_COUNT,
103: InetAddress.getByName(this .listenAddress));
104: ss.setSoTimeout(LISTENER_TIMEOUT);
105: Logger.log(Logger.INFO, AJP_RESOURCES,
106: "Ajp13Listener.StartupOK", this .listenPort + "");
107:
108: // Enter the main loop
109: while (!interrupted) {
110: // Get the listener
111: Socket s = null;
112: try {
113: s = ss.accept();
114: } catch (java.io.InterruptedIOException err) {
115: s = null;
116: }
117:
118: // if we actually got a socket, process it. Otherwise go around
119: // again
120: if (s != null)
121: this .objectPool.handleRequest(s, this );
122: }
123:
124: // Close server socket
125: ss.close();
126: } catch (Throwable err) {
127: Logger.log(Logger.ERROR, AJP_RESOURCES,
128: "Ajp13Listener.ShutdownError", err);
129: }
130:
131: Logger.log(Logger.INFO, AJP_RESOURCES,
132: "Ajp13Listener.ShutdownOK");
133: }
134:
135: /**
136: * Interrupts the listener thread. This will trigger a listener shutdown
137: * once the so timeout has passed.
138: */
139: public void destroy() {
140: this .interrupted = true;
141: }
142:
143: /**
144: * Called by the request handler thread, because it needs specific setup
145: * code for this connection's protocol (ie construction of request/response
146: * objects, in/out streams, etc).
147: *
148: * This implementation parses incoming AJP13 packets, and builds an
149: * outputstream that is capable of writing back the response in AJP13
150: * packets.
151: */
152: public void allocateRequestResponse(Socket socket,
153: InputStream inSocket, OutputStream outSocket,
154: RequestHandlerThread handler, boolean iAmFirst)
155: throws SocketException, IOException {
156: WinstoneRequest req = this .objectPool.getRequestFromPool();
157: WinstoneResponse rsp = this .objectPool.getResponseFromPool();
158: rsp.setRequest(req);
159: req.setHostGroup(this .hostGroup);
160: // rsp.updateContentTypeHeader("text/html");
161:
162: if (iAmFirst || (KEEP_ALIVE_TIMEOUT == -1))
163: socket.setSoTimeout(CONNECTION_TIMEOUT);
164: else
165: socket.setSoTimeout(KEEP_ALIVE_TIMEOUT);
166: Ajp13IncomingPacket headers = null;
167: try {
168: headers = new Ajp13IncomingPacket(inSocket, handler);
169: } catch (InterruptedIOException err) {
170: // keep alive timeout ? ignore if not first
171: if (iAmFirst) {
172: throw err;
173: } else {
174: deallocateRequestResponse(handler, req, rsp, null, null);
175: return;
176: }
177: } finally {
178: try {
179: socket.setSoTimeout(CONNECTION_TIMEOUT);
180: } catch (Throwable err) {
181: }
182: }
183:
184: if (headers.getPacketLength() > 0) {
185: headers.parsePacket("8859_1");
186: parseSocketInfo(headers, req);
187: req.parseHeaders(Arrays.asList(headers.getHeaders()));
188: String servletURI = parseURILine(headers, req, rsp);
189: req.setAttribute(TEMPORARY_URL_STASH, servletURI);
190:
191: // If content-length present and non-zero, download the other
192: // packets
193: WinstoneInputStream inData = null;
194: int contentLength = req.getContentLength();
195: if (contentLength > 0) {
196: byte bodyContent[] = new byte[contentLength];
197: int position = 0;
198: while (position < contentLength) {
199: outSocket.write(getBodyRequestPacket(Math.min(
200: contentLength - position, 8184)));
201: position = getBodyResponsePacket(inSocket,
202: bodyContent, position);
203: Logger.log(Logger.FULL_DEBUG, AJP_RESOURCES,
204: "Ajp13Listener.ReadBodyProgress",
205: new String[] { "" + position,
206: "" + contentLength });
207:
208: }
209: inData = new WinstoneInputStream(bodyContent);
210: inData.setContentLength(contentLength);
211: } else
212: inData = new WinstoneInputStream(new byte[0]);
213: req.setInputStream(inData);
214:
215: // Build input/output streams, plus request/response
216: WinstoneOutputStream outData = new Ajp13OutputStream(socket
217: .getOutputStream(), "8859_1");
218: outData.setResponse(rsp);
219: rsp.setOutputStream(outData);
220:
221: // Set the handler's member variables so it can execute the servlet
222: handler.setRequest(req);
223: handler.setResponse(rsp);
224: handler.setInStream(inData);
225: handler.setOutStream(outData);
226: }
227: }
228:
229: /**
230: * Called by the request handler thread, because it needs specific shutdown
231: * code for this connection's protocol (ie releasing input/output streams,
232: * etc).
233: */
234: public void deallocateRequestResponse(RequestHandlerThread handler,
235: WinstoneRequest req, WinstoneResponse rsp,
236: WinstoneInputStream inData, WinstoneOutputStream outData)
237: throws IOException {
238: handler.setInStream(null);
239: handler.setOutStream(null);
240: handler.setRequest(null);
241: handler.setResponse(null);
242: if (req != null)
243: this .objectPool.releaseRequestToPool(req);
244: if (rsp != null)
245: this .objectPool.releaseResponseToPool(rsp);
246: }
247:
248: /**
249: * This is kind of a hack, since we have already parsed the uri to get the
250: * input stream. Just pass back the request uri
251: */
252: public String parseURI(RequestHandlerThread handler,
253: WinstoneRequest req, WinstoneResponse rsp,
254: WinstoneInputStream inData, Socket socket, boolean iAmFirst)
255: throws IOException {
256: String uri = (String) req.getAttribute(TEMPORARY_URL_STASH);
257: req.removeAttribute(TEMPORARY_URL_STASH);
258: return uri;
259: }
260:
261: /**
262: * Called by the request handler thread, because it needs specific shutdown
263: * code for this connection's protocol if the keep-alive period expires (ie
264: * closing sockets, etc).
265: *
266: * This implementation simply shuts down the socket and streams.
267: */
268: public void releaseSocket(Socket socket, InputStream inSocket,
269: OutputStream outSocket) throws IOException {
270: // Logger.log(Logger.FULL_DEBUG, "Releasing socket: " +
271: // Thread.currentThread().getName());
272: inSocket.close();
273: outSocket.close();
274: socket.close();
275: }
276:
277: /**
278: * Extract the header details relating to socket stuff from the ajp13 header
279: * packet
280: */
281: private void parseSocketInfo(Ajp13IncomingPacket headers,
282: WinstoneRequest req) {
283: req.setServerPort(headers.getServerPort());
284: req.setRemoteIP(headers.getRemoteAddress());
285: req.setServerName(headers.getServerName());
286: req.setLocalPort(headers.getServerPort());
287: req.setLocalAddr(headers.getServerName());
288: req.setRemoteIP(headers.getRemoteAddress());
289: if ((headers.getRemoteHost() != null)
290: && !headers.getRemoteHost().equals(""))
291: req.setRemoteName(headers.getRemoteHost());
292: else
293: req.setRemoteName(headers.getRemoteAddress());
294: req.setScheme(headers.isSSL() ? "https" : "http");
295: req.setIsSecure(headers.isSSL());
296: }
297:
298: /**
299: * Extract the header details relating to protocol, uri, etc from the ajp13
300: * header packet
301: */
302: private String parseURILine(Ajp13IncomingPacket headers,
303: WinstoneRequest req, WinstoneResponse rsp)
304: throws UnsupportedEncodingException {
305: req.setMethod(headers.getMethod());
306: req.setProtocol(headers.getProtocol());
307: rsp.setProtocol(headers.getProtocol());
308: rsp.extractRequestKeepAliveHeader(req);
309: // req.setServletPath(headers.getURI());
310: // req.setRequestURI(headers.getURI());
311:
312: // Get query string if supplied
313: for (Iterator i = headers.getAttributes().keySet().iterator(); i
314: .hasNext();) {
315: String attName = (String) i.next();
316: if (attName.equals("query_string")) {
317: String qs = (String) headers.getAttributes().get(
318: "query_string");
319: req.setQueryString(qs);
320: // req.getParameters().putAll(WinstoneRequest.extractParameters(qs,
321: // req.getEncoding(), mainResources));
322: // req.setRequestURI(headers.getURI() + "?" + qs);
323: } else if (attName.equals("ssl_cert")) {
324: String certValue = (String) headers.getAttributes()
325: .get("ssl_cert");
326: InputStream certStream = new ByteArrayInputStream(
327: certValue.getBytes("8859_1"));
328: X509Certificate certificateArray[] = new X509Certificate[1];
329: try {
330: certificateArray[0] = (X509Certificate) CertificateFactory
331: .getInstance("X.509").generateCertificate(
332: certStream);
333: } catch (CertificateException err) {
334: Logger.log(Logger.DEBUG, AJP_RESOURCES,
335: "Ajp13Listener.SkippingCert", certValue);
336: }
337: req.setAttribute(
338: "javax.servlet.request.X509Certificate",
339: certificateArray);
340: req.setIsSecure(true);
341: } else if (attName.equals("ssl_cipher")) {
342: String cipher = (String) headers.getAttributes().get(
343: "ssl_cipher");
344: req.setAttribute("javax.servlet.request.cipher_suite",
345: cipher);
346: req.setAttribute("javax.servlet.request.key_size",
347: getKeySize(cipher));
348: req.setIsSecure(true);
349: } else if (attName.equals("ssl_session")) {
350: req.setAttribute("javax.servlet.request.ssl_session",
351: headers.getAttributes().get("ssl_session"));
352: req.setIsSecure(true);
353: } else
354: Logger.log(Logger.DEBUG, AJP_RESOURCES,
355: "Ajp13Listener.UnknownAttribute", new String[] {
356: attName,
357: ""
358: + headers.getAttributes().get(
359: attName) });
360: }
361: return headers.getURI();
362:
363: }
364:
365: private Integer getKeySize(String cipherSuite) {
366: if (cipherSuite.indexOf("_WITH_NULL_") != -1)
367: return new Integer(0);
368: else if (cipherSuite.indexOf("_WITH_IDEA_CBC_") != -1)
369: return new Integer(128);
370: else if (cipherSuite.indexOf("_WITH_RC2_CBC_40_") != -1)
371: return new Integer(40);
372: else if (cipherSuite.indexOf("_WITH_RC4_40_") != -1)
373: return new Integer(40);
374: else if (cipherSuite.indexOf("_WITH_RC4_128_") != -1)
375: return new Integer(128);
376: else if (cipherSuite.indexOf("_WITH_DES40_CBC_") != -1)
377: return new Integer(40);
378: else if (cipherSuite.indexOf("_WITH_DES_CBC_") != -1)
379: return new Integer(56);
380: else if (cipherSuite.indexOf("_WITH_3DES_EDE_CBC_") != -1)
381: return new Integer(168);
382: else
383: return null;
384: }
385:
386: /**
387: * Tries to wait for extra requests on the same socket. If any are found
388: * before the timeout expires, it exits with a true, indicating a new
389: * request is waiting. If the timeout expires, return a false, instructing
390: * the handler thread to begin shutting down the socket and relase itself.
391: */
392: public boolean processKeepAlive(WinstoneRequest request,
393: WinstoneResponse response, InputStream inSocket)
394: throws IOException, InterruptedException {
395: return true;
396: }
397:
398: /**
399: * Build the packet needed for asking for a body chunk
400: */
401: private byte[] getBodyRequestPacket(int desiredPacketLength) {
402: byte getBodyRequestPacket[] = new byte[] { 0x41, 0x42, 0x00,
403: 0x03, 0x06, 0x00, 0x00 };
404: Ajp13OutputStream.setIntBlock(desiredPacketLength,
405: getBodyRequestPacket, 5);
406: return getBodyRequestPacket;
407: }
408:
409: /**
410: * Process the server response to a get_body_chunk request. This loads the
411: * packet from the stream, and unpacks it into the buffer at the right
412: * place.
413: */
414: private int getBodyResponsePacket(InputStream in, byte buffer[],
415: int offset) throws IOException {
416: // Get the incoming packet flag
417: byte headerBuffer[] = new byte[4];
418: int headerBytesRead = in.read(headerBuffer);
419: if (headerBytesRead != 4)
420: throw new WinstoneException(AJP_RESOURCES
421: .getString("Ajp13Listener.InvalidHeader"));
422: else if ((headerBuffer[0] != 0x12) || (headerBuffer[1] != 0x34))
423: throw new WinstoneException(AJP_RESOURCES
424: .getString("Ajp13Listener.InvalidHeader"));
425:
426: // Read in the whole packet
427: int packetLength = ((headerBuffer[2] & 0xFF) << 8)
428: + (headerBuffer[3] & 0xFF);
429: if (packetLength == 0)
430: return offset;
431:
432: // Look for packet length
433: byte bodyLengthBuffer[] = new byte[2];
434: in.read(bodyLengthBuffer);
435: int bodyLength = ((bodyLengthBuffer[0] & 0xFF) << 8)
436: + (bodyLengthBuffer[1] & 0xFF);
437: int packetBytesRead = in.read(buffer, offset, bodyLength);
438:
439: if (packetBytesRead < bodyLength)
440: throw new WinstoneException(AJP_RESOURCES
441: .getString("Ajp13Listener.ShortPacket"));
442: else
443: return packetBytesRead + offset;
444: }
445: //
446: // /**
447: // * Useful method for dumping out the contents of a packet in hex form
448: // */
449: // public static void packetDump(byte packetBytes[], int packetLength) {
450: // String dump = "";
451: // for (int n = 0; n < packetLength; n+=16) {
452: // String line = Integer.toHexString((n >> 4) & 0xF) + "0:";
453: // for (int j = 0; j < Math.min(packetLength - n, 16); j++)
454: // line = line + " " + ((packetBytes[n + j] & 0xFF) < 16 ? "0" : "") +
455: // Integer.toHexString(packetBytes[n + j] & 0xFF);
456: //
457: // line = line + " ";
458: // for (int j = 0; j < Math.min(packetLength - n, 16); j++) {
459: // byte me = (byte) (packetBytes[n + j] & 0xFF);
460: // line = line + (((me > 32) && (me < 123)) ? (char) me : '.');
461: // }
462: // dump = dump + line + "\r\n";
463: // }
464: // System.out.println(dump);
465: // }
466: }
|