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;
008:
009: import java.io.IOException;
010: import java.io.InputStream;
011: import java.io.InterruptedIOException;
012: import java.io.OutputStream;
013: import java.net.InetAddress;
014: import java.net.ServerSocket;
015: import java.net.Socket;
016: import java.net.SocketException;
017: import java.util.ArrayList;
018: import java.util.List;
019: import java.util.Map;
020:
021: /**
022: * Implements the main listener daemon thread. This is the class that gets
023: * launched by the command line, and owns the server socket, etc. Note that this
024: * class is also used as the base class for the HTTPS listener.
025: *
026: * @author <a href="mailto:rick_knowles@hotmail.com">Rick Knowles</a>
027: * @version $Id: HttpListener.java,v 1.15 2007/05/01 04:39:49 rickknowles Exp $
028: */
029: public class HttpListener implements Listener, Runnable {
030: protected static int LISTENER_TIMEOUT = 5000; // every 5s reset the
031: // listener socket
032: protected static int CONNECTION_TIMEOUT = 60000;
033: protected static int BACKLOG_COUNT = 5000;
034: protected static boolean DEFAULT_HNL = false;
035: protected static int KEEP_ALIVE_TIMEOUT = 10000;
036: protected static int KEEP_ALIVE_SLEEP = 20;
037: protected static int KEEP_ALIVE_SLEEP_MAX = 500;
038: protected HostGroup hostGroup;
039: protected ObjectPool objectPool;
040: protected boolean doHostnameLookups;
041: protected int listenPort;
042: protected String listenAddress;
043: protected boolean interrupted;
044:
045: protected HttpListener() {
046: }
047:
048: /**
049: * Constructor
050: */
051: public HttpListener(Map args, ObjectPool objectPool,
052: HostGroup hostGroup) throws IOException {
053: // Load resources
054: this .hostGroup = hostGroup;
055: this .objectPool = objectPool;
056: this .listenPort = Integer.parseInt(WebAppConfiguration
057: .stringArg(args, getConnectorName() + "Port", ""
058: + getDefaultPort()));
059: this .listenAddress = WebAppConfiguration.stringArg(args,
060: getConnectorName() + "ListenAddress", null);
061: this .doHostnameLookups = WebAppConfiguration.booleanArg(args,
062: getConnectorName() + "DoHostnameLookups", DEFAULT_HNL);
063: }
064:
065: public boolean start() {
066: if (this .listenPort < 0) {
067: return false;
068: } else {
069: this .interrupted = false;
070: Thread thread = new Thread(this , Launcher.RESOURCES
071: .getString("Listener.ThreadName", new String[] {
072: getConnectorName(), "" + this .listenPort }));
073: thread.setDaemon(true);
074: thread.start();
075: return true;
076: }
077: }
078:
079: /**
080: * The default port to use - this is just so that we can override for the
081: * SSL connector.
082: */
083: protected int getDefaultPort() {
084: return 8080;
085: }
086:
087: /**
088: * The name to use when getting properties - this is just so that we can
089: * override for the SSL connector.
090: */
091: protected String getConnectorName() {
092: return getConnectorScheme();
093: }
094:
095: protected String getConnectorScheme() {
096: return "http";
097: }
098:
099: /**
100: * Gets a server socket - this is mostly for the purpose of allowing an
101: * override in the SSL connector.
102: */
103: protected ServerSocket getServerSocket() throws IOException {
104: ServerSocket ss = this .listenAddress == null ? new ServerSocket(
105: this .listenPort, BACKLOG_COUNT)
106: : new ServerSocket(this .listenPort, BACKLOG_COUNT,
107: InetAddress.getByName(this .listenAddress));
108: return ss;
109: }
110:
111: /**
112: * The main run method. This continually listens for incoming connections,
113: * and allocates any that it finds to a request handler thread, before going
114: * back to listen again.
115: */
116: public void run() {
117: try {
118: ServerSocket ss = getServerSocket();
119: ss.setSoTimeout(LISTENER_TIMEOUT);
120: Logger.log(Logger.INFO, Launcher.RESOURCES,
121: "HttpListener.StartupOK", new String[] {
122: getConnectorName().toUpperCase(),
123: this .listenPort + "" });
124:
125: // Enter the main loop
126: while (!interrupted) {
127: // Get the listener
128: Socket s = null;
129: try {
130: s = ss.accept();
131: } catch (java.io.InterruptedIOException err) {
132: s = null;
133: }
134:
135: // if we actually got a socket, process it. Otherwise go around
136: // again
137: if (s != null)
138: this .objectPool.handleRequest(s, this );
139: }
140:
141: // Close server socket
142: ss.close();
143: } catch (Throwable err) {
144: Logger.log(Logger.ERROR, Launcher.RESOURCES,
145: "HttpListener.ShutdownError", getConnectorName()
146: .toUpperCase(), err);
147: }
148:
149: Logger.log(Logger.INFO, Launcher.RESOURCES,
150: "HttpListener.ShutdownOK", getConnectorName()
151: .toUpperCase());
152: }
153:
154: /**
155: * Interrupts the listener thread. This will trigger a listener shutdown
156: * once the so timeout has passed.
157: */
158: public void destroy() {
159: this .interrupted = true;
160: }
161:
162: /**
163: * Called by the request handler thread, because it needs specific setup
164: * code for this connection's protocol (ie construction of request/response
165: * objects, in/out streams, etc).
166: *
167: * This implementation parses incoming AJP13 packets, and builds an
168: * outputstream that is capable of writing back the response in AJP13
169: * packets.
170: */
171: public void allocateRequestResponse(Socket socket,
172: InputStream inSocket, OutputStream outSocket,
173: RequestHandlerThread handler, boolean iAmFirst)
174: throws SocketException, IOException {
175: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
176: "HttpListener.AllocatingRequest", Thread
177: .currentThread().getName());
178: socket.setSoTimeout(CONNECTION_TIMEOUT);
179:
180: // Build input/output streams, plus request/response
181: WinstoneInputStream inData = new WinstoneInputStream(inSocket);
182: WinstoneOutputStream outData = new WinstoneOutputStream(
183: outSocket, false);
184: WinstoneRequest req = this .objectPool.getRequestFromPool();
185: WinstoneResponse rsp = this .objectPool.getResponseFromPool();
186: outData.setResponse(rsp);
187: req.setInputStream(inData);
188: rsp.setOutputStream(outData);
189: rsp.setRequest(req);
190: // rsp.updateContentTypeHeader("text/html");
191: req.setHostGroup(this .hostGroup);
192:
193: // Set the handler's member variables so it can execute the servlet
194: handler.setRequest(req);
195: handler.setResponse(rsp);
196: handler.setInStream(inData);
197: handler.setOutStream(outData);
198:
199: // If using this listener, we must set the server header now, because it
200: // must be the first header. Ajp13 listener can defer to the Apache Server
201: // header
202: rsp.setHeader("Server", Launcher.RESOURCES
203: .getString("ServerVersion"));
204: }
205:
206: /**
207: * Called by the request handler thread, because it needs specific shutdown
208: * code for this connection's protocol (ie releasing input/output streams,
209: * etc).
210: */
211: public void deallocateRequestResponse(RequestHandlerThread handler,
212: WinstoneRequest req, WinstoneResponse rsp,
213: WinstoneInputStream inData, WinstoneOutputStream outData)
214: throws IOException {
215: handler.setInStream(null);
216: handler.setOutStream(null);
217: handler.setRequest(null);
218: handler.setResponse(null);
219: if (req != null)
220: this .objectPool.releaseRequestToPool(req);
221: if (rsp != null)
222: this .objectPool.releaseResponseToPool(rsp);
223: }
224:
225: public String parseURI(RequestHandlerThread handler,
226: WinstoneRequest req, WinstoneResponse rsp,
227: WinstoneInputStream inData, Socket socket, boolean iAmFirst)
228: throws IOException {
229: parseSocketInfo(socket, req);
230:
231: // Read the header line (because this is the first line of the request,
232: // apply keep-alive timeouts to it if we are not the first request)
233: if (!iAmFirst) {
234: socket.setSoTimeout(KEEP_ALIVE_TIMEOUT);
235: }
236:
237: byte uriBuffer[] = null;
238: try {
239: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
240: "HttpListener.WaitingForURILine");
241: uriBuffer = inData.readLine();
242: } catch (InterruptedIOException err) {
243: // keep alive timeout ? ignore if not first
244: if (iAmFirst) {
245: throw err;
246: } else {
247: return null;
248: }
249: } finally {
250: try {
251: socket.setSoTimeout(CONNECTION_TIMEOUT);
252: } catch (Throwable err) {
253: }
254: }
255: handler.setRequestStartTime();
256:
257: // Get header data (eg protocol, method, uri, headers, etc)
258: String uriLine = new String(uriBuffer);
259: if (uriLine.trim().equals(""))
260: throw new SocketException("Empty URI Line");
261: String servletURI = parseURILine(uriLine, req, rsp);
262: parseHeaders(req, inData);
263: rsp.extractRequestKeepAliveHeader(req);
264: int contentLength = req.getContentLength();
265: if (contentLength != -1)
266: inData.setContentLength(contentLength);
267: return servletURI;
268: }
269:
270: /**
271: * Called by the request handler thread, because it needs specific shutdown
272: * code for this connection's protocol if the keep-alive period expires (ie
273: * closing sockets, etc).
274: *
275: * This implementation simply shuts down the socket and streams.
276: */
277: public void releaseSocket(Socket socket, InputStream inSocket,
278: OutputStream outSocket) throws IOException {
279: // Logger.log(Logger.FULL_DEBUG, "Releasing socket: " +
280: // Thread.currentThread().getName());
281: inSocket.close();
282: outSocket.close();
283: socket.close();
284: }
285:
286: protected void parseSocketInfo(Socket socket, WinstoneRequest req)
287: throws IOException {
288: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
289: "HttpListener.ParsingSocketInfo");
290: req.setScheme(getConnectorScheme());
291: req.setServerPort(socket.getLocalPort());
292: req.setLocalPort(socket.getLocalPort());
293: req.setLocalAddr(socket.getLocalAddress().getHostAddress());
294: req.setRemoteIP(socket.getInetAddress().getHostAddress());
295: req.setRemotePort(socket.getPort());
296: if (this .doHostnameLookups) {
297: req.setServerName(socket.getLocalAddress().getHostName());
298: req.setRemoteName(socket.getInetAddress().getHostName());
299: req.setLocalName(socket.getLocalAddress().getHostName());
300: } else {
301: req
302: .setServerName(socket.getLocalAddress()
303: .getHostAddress());
304: req.setRemoteName(socket.getInetAddress().getHostAddress());
305: req.setLocalName(socket.getLocalAddress().getHostAddress());
306: }
307: }
308:
309: /**
310: * Tries to wait for extra requests on the same socket. If any are found
311: * before the timeout expires, it exits with a true, indicating a new
312: * request is waiting. If the protocol does not support keep-alives, or the
313: * request instructed us to close the connection, or the timeout expires,
314: * return a false, instructing the handler thread to begin shutting down the
315: * socket and relase itself.
316: */
317: public boolean processKeepAlive(WinstoneRequest request,
318: WinstoneResponse response, InputStream inSocket)
319: throws IOException, InterruptedException {
320: // Try keep alive if allowed
321: boolean continueFlag = !response.closeAfterRequest();
322: return continueFlag;
323: }
324:
325: /**
326: * Processes the uri line into it's component parts, determining protocol,
327: * method and uri
328: */
329: private String parseURILine(String uriLine, WinstoneRequest req,
330: WinstoneResponse rsp) {
331: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
332: "HttpListener.UriLine", uriLine.trim());
333:
334: // Method
335: int spacePos = uriLine.indexOf(' ');
336: if (spacePos == -1)
337: throw new WinstoneException(Launcher.RESOURCES.getString(
338: "HttpListener.ErrorUriLine", uriLine));
339: String method = uriLine.substring(0, spacePos).toUpperCase();
340: String fullURI = null;
341:
342: // URI
343: String remainder = uriLine.substring(spacePos + 1);
344: spacePos = remainder.indexOf(' ');
345: if (spacePos == -1) {
346: fullURI = trimHostName(remainder.trim());
347: req.setProtocol("HTTP/0.9");
348: rsp.setProtocol("HTTP/0.9");
349: } else {
350: fullURI = trimHostName(remainder.substring(0, spacePos)
351: .trim());
352: String protocol = remainder.substring(spacePos + 1).trim()
353: .toUpperCase();
354: req.setProtocol(protocol);
355: rsp.setProtocol(protocol);
356: }
357:
358: req.setMethod(method);
359: // req.setRequestURI(fullURI);
360: return fullURI;
361: }
362:
363: private String trimHostName(String input) {
364: if (input == null)
365: return null;
366: else if (input.startsWith("/"))
367: return input;
368:
369: int hostStart = input.indexOf("://");
370: if (hostStart == -1)
371: return input;
372: String hostName = input.substring(hostStart + 3);
373: int pathStart = hostName.indexOf('/');
374: if (pathStart == -1)
375: return "/";
376: else
377: return hostName.substring(pathStart);
378: }
379:
380: /**
381: * Parse the incoming stream into a list of headers (stopping at the first
382: * blank line), then call the parseHeaders(req, list) method on that list.
383: */
384: public void parseHeaders(WinstoneRequest req,
385: WinstoneInputStream inData) throws IOException {
386: List headerList = new ArrayList();
387:
388: if (!req.getProtocol().startsWith("HTTP/0")) {
389: // Loop to get headers
390: byte headerBuffer[] = inData.readLine();
391: String headerLine = new String(headerBuffer);
392:
393: while (headerLine.trim().length() > 0) {
394: if (headerLine.indexOf(':') != -1) {
395: headerList.add(headerLine.trim());
396: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
397: "HttpListener.Header", headerLine.trim());
398: }
399: headerBuffer = inData.readLine();
400: headerLine = new String(headerBuffer);
401: }
402: }
403:
404: // If no headers available, parse an empty list
405: req.parseHeaders(headerList);
406: }
407: }
|