/* * Copyright 2003-2006 Rick Knowles <winstone-devel at lists sourceforge net> * Distributed under the terms of either: * - the common development and distribution license (CDDL), v1.0; or * - the GNU Lesser General Public License, v2.1 or later */ package winstone; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; import java.net.Socket; import java.net.SocketException; import javax.servlet.ServletException; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; /** * The threads to which incoming requests get allocated. * * @author <a href="mailto:rick_knowles@hotmail.com">Rick Knowles</a> * @version $Id: RequestHandlerThread.java,v 1.21 2007/04/23 02:55:35 rickknowles Exp $ */ public class RequestHandlerThread implements Runnable { private Thread thread; private ObjectPool objectPool; private WinstoneInputStream inData; private WinstoneOutputStream outData; private WinstoneRequest req; private WinstoneResponse rsp; private Listener listener; private Socket socket; private String threadName; private long requestStartTime; private boolean simulateModUniqueId; private boolean saveSessions; // private Object processingMonitor = new Boolean(true); /** * Constructor - this is called by the handler pool, and just sets up for * when a real request comes along. */ public RequestHandlerThread(ObjectPool objectPool, int threadIndex, boolean simulateModUniqueId, boolean saveSessions) { this.objectPool = objectPool; this.simulateModUniqueId = simulateModUniqueId; this.saveSessions = saveSessions; this.threadName = Launcher.RESOURCES.getString( "RequestHandlerThread.ThreadName", "" + threadIndex); // allocate a thread to run on this object this.thread = new Thread(this, threadName); this.thread.setDaemon(true); } /** * The main thread execution code. */ public void run() { boolean interrupted = false; while (!interrupted) { // Start request processing InputStream inSocket = null; OutputStream outSocket = null; boolean iAmFirst = true; try { // Get input/output streams inSocket = socket.getInputStream(); outSocket = socket.getOutputStream(); // The keep alive loop - exiting from here means the connection has closed boolean continueFlag = true; while (continueFlag && !interrupted) { try { long requestId = System.currentTimeMillis(); this.listener.allocateRequestResponse(socket, inSocket, outSocket, this, iAmFirst); if (this.req == null) { // Dead request - happens sometimes with ajp13 - discard this.listener.deallocateRequestResponse(this, req, rsp, inData, outData); continue; } String servletURI = this.listener.parseURI(this, this.req, this.rsp, this.inData, this.socket, iAmFirst); if (servletURI == null) { Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "RequestHandlerThread.KeepAliveTimedOut", this.threadName); // Keep alive timed out - deallocate and go into wait state this.listener.deallocateRequestResponse(this, req, rsp, inData, outData); continueFlag = false; continue; } if (this.simulateModUniqueId) { req.setAttribute("UNIQUE_ID", "" + requestId); } long headerParseTime = getRequestProcessTime(); iAmFirst = false; HostConfiguration hostConfig = req.getHostGroup().getHostByName(req.getServerName()); Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "RequestHandlerThread.StartRequest", new String[] {"" + requestId, hostConfig.getHostname()}); // Get the URI from the request, check for prefix, then // match it to a requestDispatcher WebAppConfiguration webAppConfig = hostConfig.getWebAppByURI(servletURI); if (webAppConfig == null) { webAppConfig = hostConfig.getWebAppByURI("/"); } if (webAppConfig == null) { Logger.log(Logger.WARNING, Launcher.RESOURCES, "RequestHandlerThread.UnknownWebapp", new String[] { servletURI }); rsp.sendError(WinstoneResponse.SC_NOT_FOUND, Launcher.RESOURCES.getString("RequestHandlerThread.UnknownWebappPage", servletURI)); rsp.flushBuffer(); req.discardRequestBody(); writeToAccessLog(servletURI, req, rsp, null); // Process keep-alive continueFlag = this.listener.processKeepAlive(req, rsp, inSocket); this.listener.deallocateRequestResponse(this, req, rsp, inData, outData); Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "RequestHandlerThread.FinishRequest", "" + requestId); Logger.log(Logger.SPEED, Launcher.RESOURCES, "RequestHandlerThread.RequestTime", new String[] { servletURI, "" + headerParseTime, "" + getRequestProcessTime() }); continue; } req.setWebAppConfig(webAppConfig); // Now we've verified it's in the right webapp, send // request in scope notify ServletRequestListener reqLsnrs[] = webAppConfig.getRequestListeners(); for (int n = 0; n < reqLsnrs.length; n++) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(webAppConfig.getLoader()); reqLsnrs[n].requestInitialized(new ServletRequestEvent(webAppConfig, req)); Thread.currentThread().setContextClassLoader(cl); } // Lookup a dispatcher, then process with it processRequest(webAppConfig, req, rsp, webAppConfig.getServletURIFromRequestURI(servletURI)); writeToAccessLog(servletURI, req, rsp, webAppConfig); this.outData.finishResponse(); this.inData.finishRequest(); Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "RequestHandlerThread.FinishRequest", "" + requestId); // Process keep-alive continueFlag = this.listener.processKeepAlive(req, rsp, inSocket); // Set last accessed time on session as start of this // request req.markSessionsAsRequestFinished(this.requestStartTime, this.saveSessions); // send request listener notifies for (int n = 0; n < reqLsnrs.length; n++) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(webAppConfig.getLoader()); reqLsnrs[n].requestDestroyed(new ServletRequestEvent(webAppConfig, req)); Thread.currentThread().setContextClassLoader(cl); } req.setWebAppConfig(null); rsp.setWebAppConfig(null); req.setRequestAttributeListeners(null); this.listener.deallocateRequestResponse(this, req, rsp, inData, outData); Logger.log(Logger.SPEED, Launcher.RESOURCES, "RequestHandlerThread.RequestTime", new String[] { servletURI, "" + headerParseTime, "" + getRequestProcessTime() }); } catch (InterruptedIOException errIO) { continueFlag = false; Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "RequestHandlerThread.SocketTimeout", errIO); } catch (SocketException errIO) { continueFlag = false; } } this.listener.deallocateRequestResponse(this, req, rsp, inData, outData); this.listener.releaseSocket(this.socket, inSocket, outSocket); // shut sockets } catch (Throwable err) { try { this.listener.deallocateRequestResponse(this, req, rsp, inData, outData); } catch (Throwable errClose) { } try { this.listener.releaseSocket(this.socket, inSocket, outSocket); // shut sockets } catch (Throwable errClose) { } Logger.log(Logger.ERROR, Launcher.RESOURCES, "RequestHandlerThread.RequestError", err); } this.objectPool.releaseRequestHandler(this); if (!interrupted) { // Suspend this thread until we get assigned and woken up Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "RequestHandlerThread.EnterWaitState"); try { synchronized (this) { this.wait(); } } catch (InterruptedException err) { interrupted = true; } Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "RequestHandlerThread.WakingUp"); } } Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "RequestHandlerThread.ThreadExit"); } /** * Actually process the request. This takes the request and response, and feeds * them to the desired servlet, which then processes them or throws them off to * another servlet. */ private void processRequest(WebAppConfiguration webAppConfig, WinstoneRequest req, WinstoneResponse rsp, String path) throws IOException, ServletException { RequestDispatcher rd = null; javax.servlet.RequestDispatcher rdError = null; try { rd = webAppConfig.getInitialDispatcher(path, req, rsp); // Null RD means an error or we have been redirected to a welcome page if (rd != null) { Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES, "RequestHandlerThread.HandlingRD", rd.getName()); rd.forward(req, rsp); } // if null returned, assume we were redirected } catch (Throwable err) { Logger.log(Logger.WARNING, Launcher.RESOURCES, "RequestHandlerThread.UntrappedError", err); rdError = webAppConfig.getErrorDispatcherByClass(err); } // If there was any kind of error, execute the error dispatcher here if (rdError != null) { try { if (rsp.isCommitted()) { rdError.include(req, rsp); } else { rsp.resetBuffer(); rdError.forward(req, rsp); } } catch (Throwable err) { Logger.log(Logger.ERROR, Launcher.RESOURCES, "RequestHandlerThread.ErrorInErrorServlet", err); } // rsp.sendUntrappedError(err, req, rd != null ? rd.getName() : null); } rsp.flushBuffer(); rsp.getWinstoneOutputStream().setClosed(true); req.discardRequestBody(); } /** * Assign a socket to the handler */ public void commenceRequestHandling(Socket socket, Listener listener) { this.listener = listener; this.socket = socket; if (this.thread.isAlive()) synchronized (this) { this.notifyAll(); } else this.thread.start(); } public void setRequest(WinstoneRequest request) { this.req = request; } public void setResponse(WinstoneResponse response) { this.rsp = response; } public void setInStream(WinstoneInputStream inStream) { this.inData = inStream; } public void setOutStream(WinstoneOutputStream outStream) { this.outData = outStream; } public void setRequestStartTime() { this.requestStartTime = System.currentTimeMillis(); } public long getRequestProcessTime() { return System.currentTimeMillis() - this.requestStartTime; } /** * Trigger the thread destruction for this handler */ public void destroy() { if (this.thread.isAlive()) { this.thread.interrupt(); } } protected void writeToAccessLog(String originalURL, WinstoneRequest request, WinstoneResponse response, WebAppConfiguration webAppConfig) { if (webAppConfig != null) { // Log a row containing appropriate data AccessLogger logger = webAppConfig.getAccessLogger(); if (logger != null) { logger.log(originalURL, request, response); } } } }