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.Socket;
014: import java.net.SocketException;
015:
016: import javax.servlet.ServletException;
017: import javax.servlet.ServletRequestEvent;
018: import javax.servlet.ServletRequestListener;
019:
020: /**
021: * The threads to which incoming requests get allocated.
022: *
023: * @author <a href="mailto:rick_knowles@hotmail.com">Rick Knowles</a>
024: * @version $Id: RequestHandlerThread.java,v 1.21 2007/04/23 02:55:35 rickknowles Exp $
025: */
026: public class RequestHandlerThread implements Runnable {
027: private Thread thread;
028: private ObjectPool objectPool;
029: private WinstoneInputStream inData;
030: private WinstoneOutputStream outData;
031: private WinstoneRequest req;
032: private WinstoneResponse rsp;
033: private Listener listener;
034: private Socket socket;
035: private String threadName;
036: private long requestStartTime;
037: private boolean simulateModUniqueId;
038: private boolean saveSessions;
039:
040: // private Object processingMonitor = new Boolean(true);
041:
042: /**
043: * Constructor - this is called by the handler pool, and just sets up for
044: * when a real request comes along.
045: */
046: public RequestHandlerThread(ObjectPool objectPool, int threadIndex,
047: boolean simulateModUniqueId, boolean saveSessions) {
048: this .objectPool = objectPool;
049: this .simulateModUniqueId = simulateModUniqueId;
050: this .saveSessions = saveSessions;
051: this .threadName = Launcher.RESOURCES.getString(
052: "RequestHandlerThread.ThreadName", "" + threadIndex);
053:
054: // allocate a thread to run on this object
055: this .thread = new Thread(this , threadName);
056: this .thread.setDaemon(true);
057: }
058:
059: /**
060: * The main thread execution code.
061: */
062: public void run() {
063:
064: boolean interrupted = false;
065: while (!interrupted) {
066: // Start request processing
067: InputStream inSocket = null;
068: OutputStream outSocket = null;
069: boolean iAmFirst = true;
070: try {
071: // Get input/output streams
072: inSocket = socket.getInputStream();
073: outSocket = socket.getOutputStream();
074:
075: // The keep alive loop - exiting from here means the connection has closed
076: boolean continueFlag = true;
077: while (continueFlag && !interrupted) {
078: try {
079: long requestId = System.currentTimeMillis();
080: this .listener.allocateRequestResponse(socket,
081: inSocket, outSocket, this , iAmFirst);
082: if (this .req == null) {
083: // Dead request - happens sometimes with ajp13 - discard
084: this .listener.deallocateRequestResponse(
085: this , req, rsp, inData, outData);
086: continue;
087: }
088: String servletURI = this .listener.parseURI(
089: this , this .req, this .rsp, this .inData,
090: this .socket, iAmFirst);
091: if (servletURI == null) {
092: Logger
093: .log(
094: Logger.FULL_DEBUG,
095: Launcher.RESOURCES,
096: "RequestHandlerThread.KeepAliveTimedOut",
097: this .threadName);
098:
099: // Keep alive timed out - deallocate and go into wait state
100: this .listener.deallocateRequestResponse(
101: this , req, rsp, inData, outData);
102: continueFlag = false;
103: continue;
104: }
105:
106: if (this .simulateModUniqueId) {
107: req.setAttribute("UNIQUE_ID", ""
108: + requestId);
109: }
110: long headerParseTime = getRequestProcessTime();
111: iAmFirst = false;
112:
113: HostConfiguration hostConfig = req
114: .getHostGroup().getHostByName(
115: req.getServerName());
116: Logger.log(Logger.FULL_DEBUG,
117: Launcher.RESOURCES,
118: "RequestHandlerThread.StartRequest",
119: new String[] { "" + requestId,
120: hostConfig.getHostname() });
121:
122: // Get the URI from the request, check for prefix, then
123: // match it to a requestDispatcher
124: WebAppConfiguration webAppConfig = hostConfig
125: .getWebAppByURI(servletURI);
126: if (webAppConfig == null) {
127: webAppConfig = hostConfig
128: .getWebAppByURI("/");
129: }
130: if (webAppConfig == null) {
131: Logger
132: .log(
133: Logger.WARNING,
134: Launcher.RESOURCES,
135: "RequestHandlerThread.UnknownWebapp",
136: new String[] { servletURI });
137: rsp
138: .sendError(
139: WinstoneResponse.SC_NOT_FOUND,
140: Launcher.RESOURCES
141: .getString(
142: "RequestHandlerThread.UnknownWebappPage",
143: servletURI));
144: rsp.flushBuffer();
145: req.discardRequestBody();
146: writeToAccessLog(servletURI, req, rsp, null);
147:
148: // Process keep-alive
149: continueFlag = this .listener
150: .processKeepAlive(req, rsp,
151: inSocket);
152: this .listener.deallocateRequestResponse(
153: this , req, rsp, inData, outData);
154: Logger
155: .log(
156: Logger.FULL_DEBUG,
157: Launcher.RESOURCES,
158: "RequestHandlerThread.FinishRequest",
159: "" + requestId);
160: Logger
161: .log(
162: Logger.SPEED,
163: Launcher.RESOURCES,
164: "RequestHandlerThread.RequestTime",
165: new String[] {
166: servletURI,
167: ""
168: + headerParseTime,
169: ""
170: + getRequestProcessTime() });
171: continue;
172: }
173: req.setWebAppConfig(webAppConfig);
174:
175: // Now we've verified it's in the right webapp, send
176: // request in scope notify
177: ServletRequestListener reqLsnrs[] = webAppConfig
178: .getRequestListeners();
179: for (int n = 0; n < reqLsnrs.length; n++) {
180: ClassLoader cl = Thread.currentThread()
181: .getContextClassLoader();
182: Thread.currentThread()
183: .setContextClassLoader(
184: webAppConfig.getLoader());
185: reqLsnrs[n]
186: .requestInitialized(new ServletRequestEvent(
187: webAppConfig, req));
188: Thread.currentThread()
189: .setContextClassLoader(cl);
190: }
191:
192: // Lookup a dispatcher, then process with it
193: processRequest(
194: webAppConfig,
195: req,
196: rsp,
197: webAppConfig
198: .getServletURIFromRequestURI(servletURI));
199: writeToAccessLog(servletURI, req, rsp,
200: webAppConfig);
201:
202: this .outData.finishResponse();
203: this .inData.finishRequest();
204:
205: Logger.log(Logger.FULL_DEBUG,
206: Launcher.RESOURCES,
207: "RequestHandlerThread.FinishRequest",
208: "" + requestId);
209:
210: // Process keep-alive
211: continueFlag = this .listener.processKeepAlive(
212: req, rsp, inSocket);
213:
214: // Set last accessed time on session as start of this
215: // request
216: req.markSessionsAsRequestFinished(
217: this .requestStartTime,
218: this .saveSessions);
219:
220: // send request listener notifies
221: for (int n = 0; n < reqLsnrs.length; n++) {
222: ClassLoader cl = Thread.currentThread()
223: .getContextClassLoader();
224: Thread.currentThread()
225: .setContextClassLoader(
226: webAppConfig.getLoader());
227: reqLsnrs[n]
228: .requestDestroyed(new ServletRequestEvent(
229: webAppConfig, req));
230: Thread.currentThread()
231: .setContextClassLoader(cl);
232: }
233:
234: req.setWebAppConfig(null);
235: rsp.setWebAppConfig(null);
236: req.setRequestAttributeListeners(null);
237:
238: this .listener.deallocateRequestResponse(this ,
239: req, rsp, inData, outData);
240: Logger.log(Logger.SPEED, Launcher.RESOURCES,
241: "RequestHandlerThread.RequestTime",
242: new String[] { servletURI,
243: "" + headerParseTime,
244: "" + getRequestProcessTime() });
245: } catch (InterruptedIOException errIO) {
246: continueFlag = false;
247: Logger.log(Logger.FULL_DEBUG,
248: Launcher.RESOURCES,
249: "RequestHandlerThread.SocketTimeout",
250: errIO);
251: } catch (SocketException errIO) {
252: continueFlag = false;
253: }
254: }
255: this .listener.deallocateRequestResponse(this , req, rsp,
256: inData, outData);
257: this .listener.releaseSocket(this .socket, inSocket,
258: outSocket); // shut sockets
259: } catch (Throwable err) {
260: try {
261: this .listener.deallocateRequestResponse(this , req,
262: rsp, inData, outData);
263: } catch (Throwable errClose) {
264: }
265: try {
266: this .listener.releaseSocket(this .socket, inSocket,
267: outSocket); // shut sockets
268: } catch (Throwable errClose) {
269: }
270: Logger.log(Logger.ERROR, Launcher.RESOURCES,
271: "RequestHandlerThread.RequestError", err);
272: }
273:
274: this .objectPool.releaseRequestHandler(this );
275:
276: if (!interrupted) {
277: // Suspend this thread until we get assigned and woken up
278: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
279: "RequestHandlerThread.EnterWaitState");
280: try {
281: synchronized (this ) {
282: this .wait();
283: }
284: } catch (InterruptedException err) {
285: interrupted = true;
286: }
287: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
288: "RequestHandlerThread.WakingUp");
289: }
290: }
291: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
292: "RequestHandlerThread.ThreadExit");
293: }
294:
295: /**
296: * Actually process the request. This takes the request and response, and feeds
297: * them to the desired servlet, which then processes them or throws them off to
298: * another servlet.
299: */
300: private void processRequest(WebAppConfiguration webAppConfig,
301: WinstoneRequest req, WinstoneResponse rsp, String path)
302: throws IOException, ServletException {
303: RequestDispatcher rd = null;
304: javax.servlet.RequestDispatcher rdError = null;
305: try {
306: rd = webAppConfig.getInitialDispatcher(path, req, rsp);
307:
308: // Null RD means an error or we have been redirected to a welcome page
309: if (rd != null) {
310: Logger
311: .log(Logger.FULL_DEBUG, Launcher.RESOURCES,
312: "RequestHandlerThread.HandlingRD", rd
313: .getName());
314: rd.forward(req, rsp);
315: }
316: // if null returned, assume we were redirected
317: } catch (Throwable err) {
318: Logger.log(Logger.WARNING, Launcher.RESOURCES,
319: "RequestHandlerThread.UntrappedError", err);
320: rdError = webAppConfig.getErrorDispatcherByClass(err);
321: }
322:
323: // If there was any kind of error, execute the error dispatcher here
324: if (rdError != null) {
325: try {
326: if (rsp.isCommitted()) {
327: rdError.include(req, rsp);
328: } else {
329: rsp.resetBuffer();
330: rdError.forward(req, rsp);
331: }
332: } catch (Throwable err) {
333: Logger
334: .log(
335: Logger.ERROR,
336: Launcher.RESOURCES,
337: "RequestHandlerThread.ErrorInErrorServlet",
338: err);
339: }
340: // rsp.sendUntrappedError(err, req, rd != null ? rd.getName() : null);
341: }
342: rsp.flushBuffer();
343: rsp.getWinstoneOutputStream().setClosed(true);
344: req.discardRequestBody();
345: }
346:
347: /**
348: * Assign a socket to the handler
349: */
350: public void commenceRequestHandling(Socket socket, Listener listener) {
351: this .listener = listener;
352: this .socket = socket;
353: if (this .thread.isAlive())
354: synchronized (this ) {
355: this .notifyAll();
356: }
357: else
358: this .thread.start();
359: }
360:
361: public void setRequest(WinstoneRequest request) {
362: this .req = request;
363: }
364:
365: public void setResponse(WinstoneResponse response) {
366: this .rsp = response;
367: }
368:
369: public void setInStream(WinstoneInputStream inStream) {
370: this .inData = inStream;
371: }
372:
373: public void setOutStream(WinstoneOutputStream outStream) {
374: this .outData = outStream;
375: }
376:
377: public void setRequestStartTime() {
378: this .requestStartTime = System.currentTimeMillis();
379: }
380:
381: public long getRequestProcessTime() {
382: return System.currentTimeMillis() - this .requestStartTime;
383: }
384:
385: /**
386: * Trigger the thread destruction for this handler
387: */
388: public void destroy() {
389: if (this .thread.isAlive()) {
390: this .thread.interrupt();
391: }
392: }
393:
394: protected void writeToAccessLog(String originalURL,
395: WinstoneRequest request, WinstoneResponse response,
396: WebAppConfiguration webAppConfig) {
397: if (webAppConfig != null) {
398: // Log a row containing appropriate data
399: AccessLogger logger = webAppConfig.getAccessLogger();
400: if (logger != null) {
401: logger.log(originalURL, request, response);
402: }
403: }
404: }
405: }
|