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
7
package winstone.ajp13;
9
import java.io.ByteArrayInputStream;
10
import java.io.IOException;
11
import java.io.InputStream;
12
import java.io.InterruptedIOException;
13
import java.io.OutputStream;
14
import java.io.UnsupportedEncodingException;
15
import java.io.DataInputStream;
16
import java.net.InetAddress;
17
import java.net.ServerSocket;
18
import java.net.Socket;
19
import java.net.SocketException;
20
import java.security.cert.CertificateException;
21
import java.security.cert.CertificateFactory;
22
import java.security.cert.X509Certificate;
23
import java.util.Arrays;
24
import java.util.Iterator;
27
import winstone.HostGroup;
28
import winstone.Launcher;
29
import winstone.Listener;
30
import winstone.Logger;
31
import winstone.ObjectPool;
32
import winstone.RequestHandlerThread;
33
import winstone.WebAppConfiguration;
34
import winstone.WinstoneException;
35
import winstone.WinstoneInputStream;
36
import winstone.WinstoneOutputStream;
37
import winstone.WinstoneRequest;
38
import winstone.WinstoneResourceBundle;
39
import winstone.WinstoneResponse;
42
* Implements the main listener daemon thread. This is the class that gets
43
* launched by the command line, and owns the server socket, etc.
45
* @author mailto: <a href="rick_knowles@hotmail.com">Rick Knowles</a>
46
* @version $Id: Ajp13Listener.java,v 1.12 2006/03/24 17:24:22 rickknowles Exp $
48
public class Ajp13Listener implements Listener, Runnable {
49
public final static WinstoneResourceBundle AJP_RESOURCES = new WinstoneResourceBundle("winstone.ajp13.LocalStrings");
51
private final static int LISTENER_TIMEOUT = 5000; // every 5s reset the listener socket
52
private final static int DEFAULT_PORT = 8009;
53
private final static int CONNECTION_TIMEOUT = 60000;
54
private final static int BACKLOG_COUNT = 1000;
55
private final static int KEEP_ALIVE_TIMEOUT = -1;
56
// private final static int KEEP_ALIVE_SLEEP = 50;
57
// private final static int KEEP_ALIVE_SLEEP_MAX = 500;
58
private final static String TEMPORARY_URL_STASH = "winstone.ajp13.TemporaryURLAttribute";
60
private HostGroup hostGroup;
61
private ObjectPool objectPool;
62
private int listenPort;
63
private boolean interrupted;
64
private String listenAddress;
69
public Ajp13Listener(Map args, ObjectPool objectPool, HostGroup hostGroup) {
71
this.hostGroup = hostGroup;
72
this.objectPool = objectPool;
74
this.listenPort = Integer.parseInt(WebAppConfiguration.stringArg(args,
75
"ajp13Port", "" + DEFAULT_PORT));
76
this.listenAddress = WebAppConfiguration.stringArg(args,
77
"ajp13ListenAddress", null);
80
public boolean start() {
81
if (this.listenPort < 0) {
84
this.interrupted = false;
85
Thread thread = new Thread(this, Launcher.RESOURCES.getString(
86
"Listener.ThreadName", new String[] { "ajp13",
87
"" + this.listenPort }));
88
thread.setDaemon(true);
95
* The main run method. This handles the normal thread processing.
99
ServerSocket ss = this.listenAddress == null ? new ServerSocket(
100
this.listenPort, BACKLOG_COUNT) : new ServerSocket(
101
this.listenPort, BACKLOG_COUNT, InetAddress
102
.getByName(this.listenAddress));
103
ss.setSoTimeout(LISTENER_TIMEOUT);
104
Logger.log(Logger.INFO, AJP_RESOURCES, "Ajp13Listener.StartupOK",
105
this.listenPort + "");
107
// Enter the main loop
108
while (!interrupted) {
113
} catch (java.io.InterruptedIOException err) {
117
// if we actually got a socket, process it. Otherwise go around
120
this.objectPool.handleRequest(s, this);
123
// Close server socket
126
Logger.log(Logger.INFO, AJP_RESOURCES, "Ajp13Listener.ShutdownOK");
127
} catch (Throwable err) {
128
Logger.log(Logger.ERROR, AJP_RESOURCES,
129
"Ajp13Listener.ShutdownError", err);
134
* Interrupts the listener thread. This will trigger a listener shutdown
135
* once the so timeout has passed.
137
public void destroy() {
138
this.interrupted = true;
142
* Called by the request handler thread, because it needs specific setup
143
* code for this connection's protocol (ie construction of request/response
144
* objects, in/out streams, etc).
146
* This implementation parses incoming AJP13 packets, and builds an
147
* outputstream that is capable of writing back the response in AJP13
150
public void allocateRequestResponse(Socket socket, InputStream inSocket,
151
OutputStream outSocket, RequestHandlerThread handler,
152
boolean iAmFirst) throws SocketException, IOException {
153
WinstoneRequest req = this.objectPool.getRequestFromPool();
154
WinstoneResponse rsp = this.objectPool.getResponseFromPool();
156
req.setHostGroup(this.hostGroup);
157
// rsp.updateContentTypeHeader("text/html");
159
if (iAmFirst || (KEEP_ALIVE_TIMEOUT == -1))
160
socket.setSoTimeout(CONNECTION_TIMEOUT);
162
socket.setSoTimeout(KEEP_ALIVE_TIMEOUT);
163
Ajp13IncomingPacket headers = null;
165
headers = new Ajp13IncomingPacket(inSocket, handler);
166
} catch (InterruptedIOException err) {
167
// keep alive timeout ? ignore if not first
171
deallocateRequestResponse(handler, req, rsp, null, null);
175
try {socket.setSoTimeout(CONNECTION_TIMEOUT);} catch (Throwable err) {}
178
if (headers.getPacketLength() > 0) {
179
headers.parsePacket("8859_1");
180
parseSocketInfo(headers, req);
181
req.parseHeaders(Arrays.asList(headers.getHeaders()));
182
String servletURI = parseURILine(headers, req, rsp);
183
req.setAttribute(TEMPORARY_URL_STASH, servletURI);
185
// If content-length present and non-zero, download the other
187
WinstoneInputStream inData = null;
188
int contentLength = req.getContentLength();
189
if (contentLength > 0) {
190
byte bodyContent[] = new byte[contentLength];
192
while (position < contentLength) {
193
outSocket.write(getBodyRequestPacket(Math.min(contentLength
195
position = getBodyResponsePacket(inSocket, bodyContent,
197
Logger.log(Logger.FULL_DEBUG, AJP_RESOURCES,
198
"Ajp13Listener.ReadBodyProgress", new String[] {
199
"" + position, "" + contentLength });
202
inData = new WinstoneInputStream(bodyContent);
203
inData.setContentLength(contentLength);
205
inData = new WinstoneInputStream(new byte[0]);
206
req.setInputStream(inData);
208
// Build input/output streams, plus request/response
209
WinstoneOutputStream outData = new Ajp13OutputStream(socket
210
.getOutputStream(), "8859_1");
211
outData.setResponse(rsp);
212
rsp.setOutputStream(outData);
214
// Set the handler's member variables so it can execute the servlet
215
handler.setRequest(req);
216
handler.setResponse(rsp);
217
handler.setInStream(inData);
218
handler.setOutStream(outData);
223
* Called by the request handler thread, because it needs specific shutdown
224
* code for this connection's protocol (ie releasing input/output streams,
227
public void deallocateRequestResponse(RequestHandlerThread handler,
228
WinstoneRequest req, WinstoneResponse rsp,
229
WinstoneInputStream inData, WinstoneOutputStream outData)
231
handler.setInStream(null);
232
handler.setOutStream(null);
233
handler.setRequest(null);
234
handler.setResponse(null);
236
this.objectPool.releaseRequestToPool(req);
238
this.objectPool.releaseResponseToPool(rsp);
242
* This is kind of a hack, since we have already parsed the uri to get the
243
* input stream. Just pass back the request uri
245
public String parseURI(RequestHandlerThread handler, WinstoneRequest req,
246
WinstoneResponse rsp, WinstoneInputStream inData, Socket socket,
247
boolean iAmFirst) throws IOException {
248
String uri = (String) req.getAttribute(TEMPORARY_URL_STASH);
249
req.removeAttribute(TEMPORARY_URL_STASH);
254
* Called by the request handler thread, because it needs specific shutdown
255
* code for this connection's protocol if the keep-alive period expires (ie
256
* closing sockets, etc).
258
* This implementation simply shuts down the socket and streams.
260
public void releaseSocket(Socket socket, InputStream inSocket,
261
OutputStream outSocket) throws IOException {
262
// Logger.log(Logger.FULL_DEBUG, "Releasing socket: " +
263
// Thread.currentThread().getName());
270
* Extract the header details relating to socket stuff from the ajp13 header
273
private void parseSocketInfo(Ajp13IncomingPacket headers,
274
WinstoneRequest req) {
275
req.setServerPort(headers.getServerPort());
276
req.setRemoteIP(headers.getRemoteAddress());
277
req.setServerName(headers.getServerName());
278
req.setLocalPort(headers.getServerPort());
279
req.setLocalAddr(headers.getServerName());
280
req.setRemoteIP(headers.getRemoteAddress());
281
if ((headers.getRemoteHost() != null)
282
&& !headers.getRemoteHost().equals(""))
283
req.setRemoteName(headers.getRemoteHost());
285
req.setRemoteName(headers.getRemoteAddress());
286
req.setScheme(headers.isSSL() ? "https" : "http");
287
req.setIsSecure(headers.isSSL());
291
* Extract the header details relating to protocol, uri, etc from the ajp13
294
private String parseURILine(Ajp13IncomingPacket headers,
295
WinstoneRequest req, WinstoneResponse rsp)
296
throws UnsupportedEncodingException {
297
req.setMethod(headers.getMethod());
298
req.setProtocol(headers.getProtocol());
299
rsp.setProtocol(headers.getProtocol());
300
rsp.extractRequestKeepAliveHeader(req);
301
// req.setServletPath(headers.getURI());
302
// req.setRequestURI(headers.getURI());
304
// Get query string if supplied
305
for (Iterator i = headers.getAttributes().keySet().iterator(); i
307
String attName = (String) i.next();
308
if (attName.equals("query_string")) {
309
String qs = (String) headers.getAttributes().get("query_string");
310
req.setQueryString(qs);
311
// req.getParameters().putAll(WinstoneRequest.extractParameters(qs,
312
// req.getEncoding(), mainResources));
313
// req.setRequestURI(headers.getURI() + "?" + qs);
314
} else if (attName.equals("ssl_cert")) {
315
String certValue = (String) headers.getAttributes().get(
317
InputStream certStream = new ByteArrayInputStream(certValue
318
.getBytes("8859_1"));
319
X509Certificate certificateArray[] = new X509Certificate[1];
321
certificateArray[0] = (X509Certificate) CertificateFactory
322
.getInstance("X.509").generateCertificate(
324
} catch (CertificateException err) {
325
Logger.log(Logger.DEBUG, AJP_RESOURCES,
326
"Ajp13Listener.SkippingCert", certValue);
328
req.setAttribute("javax.servlet.request.X509Certificate",
330
req.setIsSecure(true);
331
} else if (attName.equals("ssl_cipher")) {
332
String cipher = (String) headers.getAttributes().get(
334
req.setAttribute("javax.servlet.request.cipher_suite", cipher);
335
req.setAttribute("javax.servlet.request.key_size",
337
req.setIsSecure(true);
338
} else if (attName.equals("ssl_session")) {
339
req.setAttribute("javax.servlet.request.ssl_session", headers
340
.getAttributes().get("ssl_session"));
341
req.setIsSecure(true);
343
Logger.log(Logger.DEBUG, AJP_RESOURCES,
344
"Ajp13Listener.UnknownAttribute", new String[] {
346
"" + headers.getAttributes().get(attName) });
348
return headers.getURI();
352
private Integer getKeySize(String cipherSuite) {
353
if (cipherSuite.indexOf("_WITH_NULL_") != -1)
354
return new Integer(0);
355
else if (cipherSuite.indexOf("_WITH_IDEA_CBC_") != -1)
356
return new Integer(128);
357
else if (cipherSuite.indexOf("_WITH_RC2_CBC_40_") != -1)
358
return new Integer(40);
359
else if (cipherSuite.indexOf("_WITH_RC4_40_") != -1)
360
return new Integer(40);
361
else if (cipherSuite.indexOf("_WITH_RC4_128_") != -1)
362
return new Integer(128);
363
else if (cipherSuite.indexOf("_WITH_DES40_CBC_") != -1)
364
return new Integer(40);
365
else if (cipherSuite.indexOf("_WITH_DES_CBC_") != -1)
366
return new Integer(56);
367
else if (cipherSuite.indexOf("_WITH_3DES_EDE_CBC_") != -1)
368
return new Integer(168);
374
* Tries to wait for extra requests on the same socket. If any are found
375
* before the timeout expires, it exits with a true, indicating a new
376
* request is waiting. If the timeout expires, return a false, instructing
377
* the handler thread to begin shutting down the socket and relase itself.
379
public boolean processKeepAlive(WinstoneRequest request,
380
WinstoneResponse response, InputStream inSocket)
381
throws IOException, InterruptedException {
386
* Build the packet needed for asking for a body chunk
388
private byte[] getBodyRequestPacket(int desiredPacketLength) {
389
byte getBodyRequestPacket[] = new byte[] { 0x41, 0x42, 0x00, 0x03,
391
Ajp13OutputStream.setIntBlock(desiredPacketLength,
392
getBodyRequestPacket, 5);
393
return getBodyRequestPacket;
397
* Process the server response to a get_body_chunk request. This loads the
398
* packet from the stream, and unpacks it into the buffer at the right
401
private int getBodyResponsePacket(InputStream in, byte buffer[], int offset)
403
DataInputStream din = new DataInputStream(in);
404
// Get the incoming packet flag
405
byte headerBuffer[] = new byte[4];
406
din.readFully(headerBuffer);
407
if ((headerBuffer[0] != 0x12) || (headerBuffer[1] != 0x34))
408
throw new WinstoneException(AJP_RESOURCES
409
.getString("Ajp13Listener.InvalidHeader"));
411
// Read in the whole packet
412
int packetLength = ((headerBuffer[2] & 0xFF) << 8)
413
+ (headerBuffer[3] & 0xFF);
414
if (packetLength == 0)
417
// Look for packet length
418
byte bodyLengthBuffer[] = new byte[2];
419
din.readFully(bodyLengthBuffer);
420
int bodyLength = ((bodyLengthBuffer[0] & 0xFF) << 8)
421
+ (bodyLengthBuffer[1] & 0xFF);
422
din.readFully(buffer, offset, bodyLength);
424
return bodyLength + offset;
428
// * Useful method for dumping out the contents of a packet in hex form
430
// public static void packetDump(byte packetBytes[], int packetLength) {
432
// for (int n = 0; n < packetLength; n+=16) {
433
// String line = Integer.toHexString((n >> 4) & 0xF) + "0:";
434
// for (int j = 0; j < Math.min(packetLength - n, 16); j++)
435
// line = line + " " + ((packetBytes[n + j] & 0xFF) < 16 ? "0" : "") +
436
// Integer.toHexString(packetBytes[n + j] & 0xFF);
438
// line = line + " ";
439
// for (int j = 0; j < Math.min(packetLength - n, 16); j++) {
440
// byte me = (byte) (packetBytes[n + j] & 0xFF);
441
// line = line + (((me > 32) && (me < 123)) ? (char) me : '.');
443
// dump = dump + line + "\r\n";
445
// System.out.println(dump);