~james-page/ubuntu/oneiric/jenkins-winstone/827651

« back to all changes in this revision

Viewing changes to src/java/winstone/ajp13/Ajp13Listener.java

  • Committer: Bazaar Package Importer
  • Author(s): James Page
  • Date: 2011-06-29 12:16:17 UTC
  • Revision ID: james.westby@ubuntu.com-20110629121617-vd3ha6lp4nqvxkbr
Tags: upstream-0.9.10-jenkins-25+dfsg
ImportĀ upstreamĀ versionĀ 0.9.10-jenkins-25+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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;
 
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;
 
25
import java.util.Map;
 
26
 
 
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;
 
40
 
 
41
/**
 
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.
 
44
 * 
 
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 $
 
47
 */
 
48
public class Ajp13Listener implements Listener, Runnable {
 
49
    public final static WinstoneResourceBundle AJP_RESOURCES = new WinstoneResourceBundle("winstone.ajp13.LocalStrings");
 
50
    
 
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";
 
59
    
 
60
    private HostGroup hostGroup;
 
61
    private ObjectPool objectPool;
 
62
    private int listenPort;
 
63
    private boolean interrupted;
 
64
    private String listenAddress;
 
65
 
 
66
    /**
 
67
     * Constructor
 
68
     */
 
69
    public Ajp13Listener(Map args, ObjectPool objectPool, HostGroup hostGroup) {
 
70
        // Load resources
 
71
        this.hostGroup = hostGroup;
 
72
        this.objectPool = objectPool;
 
73
 
 
74
        this.listenPort = Integer.parseInt(WebAppConfiguration.stringArg(args,
 
75
                "ajp13Port", "" + DEFAULT_PORT));
 
76
        this.listenAddress = WebAppConfiguration.stringArg(args,
 
77
                "ajp13ListenAddress", null);
 
78
    }
 
79
 
 
80
    public boolean start() {
 
81
        if (this.listenPort < 0) {
 
82
            return false;
 
83
        } else {
 
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);
 
89
            thread.start();
 
90
            return true;
 
91
        }
 
92
    }
 
93
 
 
94
    /**
 
95
     * The main run method. This handles the normal thread processing.
 
96
     */
 
97
    public void run() {
 
98
        try {
 
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 + "");
 
106
 
 
107
            // Enter the main loop
 
108
            while (!interrupted) {
 
109
                // Get the listener
 
110
                Socket s = null;
 
111
                try {
 
112
                    s = ss.accept();
 
113
                } catch (java.io.InterruptedIOException err) {
 
114
                    s = null;
 
115
                }
 
116
 
 
117
                // if we actually got a socket, process it. Otherwise go around
 
118
                // again
 
119
                if (s != null)
 
120
                    this.objectPool.handleRequest(s, this);
 
121
            }
 
122
 
 
123
            // Close server socket
 
124
            ss.close();
 
125
 
 
126
            Logger.log(Logger.INFO, AJP_RESOURCES, "Ajp13Listener.ShutdownOK");
 
127
        } catch (Throwable err) {
 
128
            Logger.log(Logger.ERROR, AJP_RESOURCES,
 
129
                    "Ajp13Listener.ShutdownError", err);
 
130
        }
 
131
    }
 
132
 
 
133
    /**
 
134
     * Interrupts the listener thread. This will trigger a listener shutdown
 
135
     * once the so timeout has passed.
 
136
     */
 
137
    public void destroy() {
 
138
        this.interrupted = true;
 
139
    }
 
140
 
 
141
    /**
 
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).
 
145
     * 
 
146
     * This implementation parses incoming AJP13 packets, and builds an
 
147
     * outputstream that is capable of writing back the response in AJP13
 
148
     * packets.
 
149
     */
 
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();
 
155
        rsp.setRequest(req);
 
156
        req.setHostGroup(this.hostGroup);
 
157
        // rsp.updateContentTypeHeader("text/html");
 
158
 
 
159
        if (iAmFirst || (KEEP_ALIVE_TIMEOUT == -1))
 
160
            socket.setSoTimeout(CONNECTION_TIMEOUT);
 
161
        else
 
162
            socket.setSoTimeout(KEEP_ALIVE_TIMEOUT);
 
163
        Ajp13IncomingPacket headers = null;
 
164
        try {
 
165
            headers = new Ajp13IncomingPacket(inSocket, handler);
 
166
        } catch (InterruptedIOException err) {
 
167
            // keep alive timeout ? ignore if not first
 
168
            if (iAmFirst) {
 
169
                throw err;
 
170
            } else {
 
171
                deallocateRequestResponse(handler, req, rsp, null, null);
 
172
                return;
 
173
            }
 
174
        } finally {
 
175
            try {socket.setSoTimeout(CONNECTION_TIMEOUT);} catch (Throwable err) {}
 
176
        }
 
177
 
 
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);
 
184
 
 
185
            // If content-length present and non-zero, download the other
 
186
            // packets
 
187
            WinstoneInputStream inData = null;
 
188
            int contentLength = req.getContentLength();
 
189
            if (contentLength > 0) {
 
190
                byte bodyContent[] = new byte[contentLength];
 
191
                int position = 0;
 
192
                while (position < contentLength) {
 
193
                    outSocket.write(getBodyRequestPacket(Math.min(contentLength
 
194
                            - position, 8184)));
 
195
                    position = getBodyResponsePacket(inSocket, bodyContent,
 
196
                            position);
 
197
                    Logger.log(Logger.FULL_DEBUG, AJP_RESOURCES,
 
198
                            "Ajp13Listener.ReadBodyProgress", new String[] {
 
199
                                    "" + position, "" + contentLength });
 
200
 
 
201
                }
 
202
                inData = new WinstoneInputStream(bodyContent);
 
203
                inData.setContentLength(contentLength);
 
204
            } else
 
205
                inData = new WinstoneInputStream(new byte[0]);
 
206
            req.setInputStream(inData);
 
207
 
 
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);
 
213
 
 
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);
 
219
        }
 
220
    }
 
221
 
 
222
    /**
 
223
     * Called by the request handler thread, because it needs specific shutdown
 
224
     * code for this connection's protocol (ie releasing input/output streams,
 
225
     * etc).
 
226
     */
 
227
    public void deallocateRequestResponse(RequestHandlerThread handler,
 
228
            WinstoneRequest req, WinstoneResponse rsp,
 
229
            WinstoneInputStream inData, WinstoneOutputStream outData) 
 
230
            throws IOException {
 
231
        handler.setInStream(null);
 
232
        handler.setOutStream(null);
 
233
        handler.setRequest(null);
 
234
        handler.setResponse(null);
 
235
        if (req != null)
 
236
            this.objectPool.releaseRequestToPool(req);
 
237
        if (rsp != null)
 
238
            this.objectPool.releaseResponseToPool(rsp);
 
239
    }
 
240
 
 
241
    /**
 
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
 
244
     */
 
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);
 
250
        return uri;
 
251
    }
 
252
 
 
253
    /**
 
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).
 
257
     * 
 
258
     * This implementation simply shuts down the socket and streams.
 
259
     */
 
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());
 
264
        inSocket.close();
 
265
        outSocket.close();
 
266
        socket.close();
 
267
    }
 
268
 
 
269
    /**
 
270
     * Extract the header details relating to socket stuff from the ajp13 header
 
271
     * packet
 
272
     */
 
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());
 
284
        else
 
285
            req.setRemoteName(headers.getRemoteAddress());
 
286
        req.setScheme(headers.isSSL() ? "https" : "http");
 
287
        req.setIsSecure(headers.isSSL());
 
288
    }
 
289
 
 
290
    /**
 
291
     * Extract the header details relating to protocol, uri, etc from the ajp13
 
292
     * header packet
 
293
     */
 
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());
 
303
 
 
304
        // Get query string if supplied
 
305
        for (Iterator i = headers.getAttributes().keySet().iterator(); i
 
306
                .hasNext();) {
 
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(
 
316
                        "ssl_cert");
 
317
                InputStream certStream = new ByteArrayInputStream(certValue
 
318
                        .getBytes("8859_1"));
 
319
                X509Certificate certificateArray[] = new X509Certificate[1];
 
320
                try {
 
321
                    certificateArray[0] = (X509Certificate) CertificateFactory
 
322
                            .getInstance("X.509").generateCertificate(
 
323
                                    certStream);
 
324
                } catch (CertificateException err) {
 
325
                    Logger.log(Logger.DEBUG, AJP_RESOURCES,
 
326
                            "Ajp13Listener.SkippingCert", certValue);
 
327
                }
 
328
                req.setAttribute("javax.servlet.request.X509Certificate",
 
329
                        certificateArray);
 
330
                req.setIsSecure(true);
 
331
            } else if (attName.equals("ssl_cipher")) {
 
332
                String cipher = (String) headers.getAttributes().get(
 
333
                        "ssl_cipher");
 
334
                req.setAttribute("javax.servlet.request.cipher_suite", cipher);
 
335
                req.setAttribute("javax.servlet.request.key_size",
 
336
                        getKeySize(cipher));
 
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);
 
342
            } else
 
343
                Logger.log(Logger.DEBUG, AJP_RESOURCES,
 
344
                        "Ajp13Listener.UnknownAttribute", new String[] {
 
345
                                attName,
 
346
                                "" + headers.getAttributes().get(attName) });
 
347
        }
 
348
        return headers.getURI();
 
349
 
 
350
    }
 
351
 
 
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);
 
369
        else
 
370
            return null;
 
371
    }
 
372
 
 
373
    /**
 
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.
 
378
     */
 
379
    public boolean processKeepAlive(WinstoneRequest request,
 
380
            WinstoneResponse response, InputStream inSocket)
 
381
            throws IOException, InterruptedException {
 
382
        return true;
 
383
    }
 
384
 
 
385
    /**
 
386
     * Build the packet needed for asking for a body chunk
 
387
     */
 
388
    private byte[] getBodyRequestPacket(int desiredPacketLength) {
 
389
        byte getBodyRequestPacket[] = new byte[] { 0x41, 0x42, 0x00, 0x03,
 
390
                0x06, 0x00, 0x00 };
 
391
        Ajp13OutputStream.setIntBlock(desiredPacketLength,
 
392
                getBodyRequestPacket, 5);
 
393
        return getBodyRequestPacket;
 
394
    }
 
395
 
 
396
    /**
 
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
 
399
     * place.
 
400
     */
 
401
    private int getBodyResponsePacket(InputStream in, byte buffer[], int offset)
 
402
            throws IOException {
 
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"));
 
410
 
 
411
        // Read in the whole packet
 
412
        int packetLength = ((headerBuffer[2] & 0xFF) << 8)
 
413
                + (headerBuffer[3] & 0xFF);
 
414
        if (packetLength == 0)
 
415
            return offset;
 
416
 
 
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);
 
423
 
 
424
        return bodyLength + offset;
 
425
    }
 
426
//
 
427
//    /**
 
428
//     * Useful method for dumping out the contents of a packet in hex form
 
429
//     */
 
430
//    public static void packetDump(byte packetBytes[], int packetLength) {
 
431
//        String dump = "";
 
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);
 
437
//       
 
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 : '.');
 
442
//            }
 
443
//            dump = dump + line + "\r\n";
 
444
//        }
 
445
//        System.out.println(dump);
 
446
//    }
 
447
}