/* Exception reporting policy method. * @param e the Throwable to report. */ private void exception(Throwable e) { try { _persistent = false; int error_code = HttpResponse.__500_Internal_Server_Error; if (e instanceof HttpException) { error_code = ((HttpException) e).getCode(); if (_request == null) log.warn(e.toString()); else log.warn(_request.getRequestLine() + " " + e.toString()); log.debug(LogSupport.EXCEPTION, e); } else if (e instanceof EOFException) { LogSupport.ignore(log, e); return; } else { _request.setAttribute("javax.servlet.error.exception_type", e.getClass()); _request.setAttribute("javax.servlet.error.exception", e); if (_request == null) log.warn(LogSupport.EXCEPTION, e); else log.warn(_request.getRequestLine(), e); } if (_response != null && !_response.isCommitted()) { _response.reset(); _response.removeField(HttpFields.__TransferEncoding); _response.setField(HttpFields.__Connection, HttpFields.__Close); _response.sendError(error_code); } } catch (Exception ex) { LogSupport.ignore(log, ex); } }
/** Destroy the connection. called by handle when handleNext returns false. */ protected void destroy() { try { close(); } catch (IOException e) { LogSupport.ignore(log, e); } catch (Exception e) { log.warn(LogSupport.EXCEPTION, e); } // Destroy request and response if (_request != null) _request.destroy(); if (_response != null) _response.destroy(); if (_inputStream != null) _inputStream.destroy(); if (_outputStream != null) _outputStream.destroy(); _inputStream = null; _outputStream = null; _request = null; _response = null; _handlingThread = null; if (_statsOn) { _tmpTime = System.currentTimeMillis(); if (_reqTime > 0) _httpServer.statsEndRequest(_tmpTime - _reqTime, false); _httpServer.statsCloseConnection(_tmpTime - _openTime, _requests); } }
/** * Output Notifications. Trigger header and/or filters from output stream observations. Also * finalizes method of indicating response content length. Called as a result of the connection * subscribing for notifications to the HttpOutputStream. * * @see HttpOutputStream * @param out The output stream observed. * @param action The action. */ public void outputNotify(OutputStream out, int action, Object ignoredData) throws IOException { if (_response == null) return; switch (action) { case OutputObserver.__FIRST_WRITE: if (!_firstWrite) { firstWrite(); _firstWrite = true; } break; case OutputObserver.__RESET_BUFFER: resetBuffer(); break; case OutputObserver.__COMMITING: commit(); break; case OutputObserver.__CLOSING: if (_response != null) { completing(); if (!_response.isCommitted() && _request.getState() == HttpMessage.__MSG_RECEIVED) commit(); } break; case OutputObserver.__CLOSED: break; } }
/* ------------------------------------------------------------ */ protected void statsRequestStart() { if (_statsOn) { if (_reqTime > 0) statsRequestEnd(); _requests++; _tmpTime = _request.getTimeStamp(); _reqTime = _tmpTime; _httpServer.statsGotRequest(); } }
/** * Setup the reponse output stream. Use the current state of the request and response, to set * tranfer parameters such as chunking and content length. */ protected void firstWrite() throws IOException { if (_response.isCommitted()) return; // Nobble the OutputStream for HEAD requests if (HttpRequest.__HEAD.equals(_request.getMethod())) _outputStream.nullOutput(); int length = _response.getIntField(HttpFields.__ContentLength); if (length >= 0) _outputStream.setContentLength(length); }
/* Verify HTTP/1.1 request * @exception HttpException problem with the request. * @exception IOException problem with the connection. */ private void verifyHTTP_1_1() throws HttpException, IOException { // Check Host Field exists String host = _request.getField(HttpFields.__Host); if (host == null) throw new HttpException(HttpResponse.__400_Bad_Request); // check and enable requests transfer encodings. String transfer_coding = _request.getField(HttpFields.__TransferEncoding); if (transfer_coding != null && transfer_coding.length() > 0) { // Handling of codings other than chunking is now // the responsibility of handlers, filters or servlets. // Thanks to the compression filter, we now don't know if // what we can handle here. if (transfer_coding.equalsIgnoreCase(HttpFields.__Chunked) || StringUtil.endsWithIgnoreCase(transfer_coding, HttpFields.__Chunked)) _inputStream.setChunking(); else if (StringUtil.asciiToLowerCase(transfer_coding).indexOf(HttpFields.__Chunked) >= 0) throw new HttpException(HttpResponse.__400_Bad_Request); } // Check input content length can be determined int content_length = _request.getIntField(HttpFields.__ContentLength); String content_type = _request.getField(HttpFields.__ContentType); if (!_inputStream.isChunking()) { // If we have a content length, use it if (content_length >= 0) _inputStream.setContentLength(content_length); // else if we have no content else if (content_type == null || content_type.length() == 0) _inputStream.setContentLength(0); // else we need a content length else { // TODO - can't do this check as IE stuff up on // a redirect. // throw new HttpException(HttpResponse.__411_Length_Required); _inputStream.setContentLength(0); } } // Handle Continue Expectations String expect = _request.getField(HttpFields.__Expect); if (expect != null && expect.length() > 0) { if (StringUtil.asciiToLowerCase(expect).equals(HttpFields.__ExpectContinue)) { _inputStream.setExpectContinues(_outputStream.getOutputStream()); } else throw new HttpException(HttpResponse.__417_Expectation_Failed); } else if (__2068_Continues && _inputStream.available() <= 0 && (HttpRequest.__PUT.equals(_request.getMethod()) || HttpRequest.__POST.equals(_request.getMethod()))) { // Send continue for RFC 2068 exception OutputStream real_out = _outputStream.getOutputStream(); real_out.write(HttpResponse.__Continue); real_out.flush(); } // Persistent unless requested otherwise _persistent = !_close; }
/* Verify HTTP/1.0 request * @exception HttpException problem with the request. * @exception IOException problem with the connection. */ private void verifyHTTP_1_0() { // Set content length int content_length = _request.getIntField(HttpFields.__ContentLength); if (content_length >= 0) _inputStream.setContentLength(content_length); else if (content_length < 0) { // TODO - can't do this check because IE does this after // a redirect. // Can't have content without a content length // String content_type=_request.getField(HttpFields.__ContentType); // if (content_type!=null && content_type.length()>0) // throw new HttpException(_HttpResponse.__411_Length_Required); _inputStream.setContentLength(0); } // Check netscape proxy connection - this is not strictly correct. if (!_keepAlive && HttpFields.__KeepAlive.equalsIgnoreCase(_request.getField(HttpFields.__ProxyConnection))) _keepAlive = true; // persistent connections in HTTP/1.0 only if requested. _persistent = _keepAlive; }
/** Recycle the connection. called by handle when handleNext returns true. */ protected void recycle() { _listener.persistConnection(this); if (_request != null) _request.recycle(this); if (_response != null) _response.recycle(this); }
/** * Handle next request off the connection. The service(request,response) method is called by * handle to service each request received on the connection. If the thread is a PoolThread, the * thread is set as inactive when waiting for a request. * * <p>If a HttpTunnel has been set on this connection, it's handle method is called and when that * completes, false is return from this method. * * <p>The Connection is set as a ThreadLocal of the calling thread and is available via the * getHttpConnection() method. * * @return true if the connection is still open and may provide more requests. */ public boolean handleNext() { // Handle a HTTP tunnel if (_tunnel != null) { if (log.isDebugEnabled()) log.debug("Tunnel: " + _tunnel); _outputStream.resetObservers(); _tunnel.handle(_inputStream.getInputStream(), _outputStream.getOutputStream()); return false; } // Normal handling. HttpContext context = null; boolean stats = false; try { // Assume the connection is not persistent, // unless told otherwise. _persistent = false; _close = false; _keepAlive = false; _firstWrite = false; _completing = false; _dotVersion = 0; // Read requests readRequest(); if (_listener == null || !_listener.isStarted()) { // dead connection _response.destroy(); _response = null; _persistent = false; return false; } _listener.customizeRequest(this, _request); if (_request.getState() != HttpMessage.__MSG_RECEIVED) throw new HttpException(HttpResponse.__400_Bad_Request); // We have a valid request! statsRequestStart(); stats = true; // Pick response version, we assume that _request.getVersion() == 1 _dotVersion = _request.getDotVersion(); if (_dotVersion > 1) { _dotVersion = 1; } // Common fields on the response _response.setVersion(HttpMessage.__HTTP_1_1); _response.setField(HttpFields.__Date, _request.getTimeStampStr()); if (!Version.isParanoid()) _response.setField(HttpFields.__Server, Version.getDetail()); // Handle Connection header field Enumeration connectionValues = _request.getFieldValues(HttpFields.__Connection, HttpFields.__separators); if (connectionValues != null) { while (connectionValues.hasMoreElements()) { String token = connectionValues.nextElement().toString(); // handle close token if (token.equalsIgnoreCase(HttpFields.__Close)) { _close = true; _response.setField(HttpFields.__Connection, HttpFields.__Close); } else if (token.equalsIgnoreCase(HttpFields.__KeepAlive) && _dotVersion == 0) _keepAlive = true; // Remove headers for HTTP/1.0 requests if (_dotVersion == 0) _request.forceRemoveField(token); } } // Handle version specifics if (_dotVersion == 1) verifyHTTP_1_1(); else if (_dotVersion == 0) verifyHTTP_1_0(); else if (_dotVersion != -1) throw new HttpException(HttpResponse.__505_HTTP_Version_Not_Supported); if (log.isDebugEnabled()) log.debug("REQUEST from " + _listener + ":\n" + _request); // handle HttpListener handlers if (!_request.isHandled() && _listener.getHttpHandler() != null) _listener.getHttpHandler().handle("", null, _request, _response); // service the request if (!_request.isHandled()) context = service(_request, _response); } catch (HttpException e) { exception(e); } catch (IOException e) { if (_request.getState() != HttpMessage.__MSG_RECEIVED) { if (log.isDebugEnabled()) { if (log.isTraceEnabled()) log.trace(LogSupport.EXCEPTION, e); else if (log.isDebugEnabled()) log.debug(e.toString()); } _response.destroy(); _response = null; } else exception(e); } catch (Exception e) { exception(e); } catch (Error e) { exception(e); } finally { int bytes_written = 0; int content_length = _response == null ? -1 : _response.getIntField(HttpFields.__ContentLength); // Complete the request if (_persistent) { boolean no_continue_sent = false; try { if (_inputStream.getExpectContinues() != null) { _inputStream.setExpectContinues(null); no_continue_sent = true; } else { int remaining = _inputStream.getContentLength(); if (remaining != 0) // Read remaining input while (_inputStream.skip(4096) > 0 || _inputStream.read() >= 0) ; } } catch (IOException e) { if (_inputStream.getContentLength() > 0) _inputStream.setContentLength(0); _persistent = false; LogSupport.ignore(log, e); exception(new HttpException(HttpResponse.__400_Bad_Request, "Missing Content")); } // Check for no more content if (!no_continue_sent && _inputStream.getContentLength() > 0) { _inputStream.setContentLength(0); _persistent = false; exception(new HttpException(HttpResponse.__400_Bad_Request, "Missing Content")); } // Commit the response try { _outputStream.close(); bytes_written = _outputStream.getBytesWritten(); _outputStream.resetStream(); _outputStream.addObserver(this); _inputStream.resetStream(); } catch (IOException e) { exception(e); } } else if (_response != null) // There was a request { // half hearted attempt to eat any remaining input try { if (_inputStream.getContentLength() > 0) while (_inputStream.skip(4096) > 0 || _inputStream.read() >= 0) ; _inputStream.resetStream(); } catch (IOException e) { LogSupport.ignore(log, e); } // commit non persistent try { _outputStream.flush(); _response.commit(); bytes_written = _outputStream.getBytesWritten(); _outputStream.close(); _outputStream.resetStream(); } catch (IOException e) { exception(e); } } // Check response length if (_response != null) { if (log.isDebugEnabled()) log.debug("RESPONSE:\n" + _response); if (_persistent && content_length >= 0 && bytes_written > 0 && content_length != bytes_written) { log.warn( "Invalid length: Content-Length=" + content_length + " written=" + bytes_written + " for " + _request.getRequestURL()); _persistent = false; try { _outputStream.close(); } catch (IOException e) { log.warn(LogSupport.EXCEPTION, e); } } } // stats & logging if (stats) statsRequestEnd(); if (context != null) context.log(_request, _response, bytes_written); } return (_tunnel != null) || _persistent; }
/* ------------------------------------------------------------ */ protected void readRequest() throws IOException { _request.readHeader((LineInput) (_inputStream).getInputStream()); }
/* ------------------------------------------------------------ */ protected void commit() throws IOException { if (_response.isCommitted()) return; int status = _response.getStatus(); int length = -1; // Check if there is missing content expectations if (_inputStream.getExpectContinues() != null) { // No input read yet - so assume it never will be _inputStream.setExpectContinues(null); _inputStream.unsafeSetContentLength(0); } // Handler forced close, listener stopped or no idle threads left. boolean has_close = HttpFields.__Close.equals(_response.getField(HttpFields.__Connection)); if (!_persistent || _close || _listener != null && (!_listener.isStarted() || _listener.isOutOfResources())) { _close = true; if (!has_close) _response.setField(HttpFields.__Connection, HttpFields.__Close); has_close = true; } if (_close) _persistent = false; // Determine how to limit content length if (_persistent) { switch (_dotVersion) { case 1: { String transfer_coding = _response.getField(HttpFields.__TransferEncoding); if (transfer_coding == null || transfer_coding.length() == 0 || HttpFields.__Identity.equalsIgnoreCase(transfer_coding)) { // if (can have content and no content length) if (status != HttpResponse.__304_Not_Modified && status != HttpResponse.__204_No_Content && _response.getField(HttpFields.__ContentLength) == null) { if (_completing) { length = _outputStream.getBytesWritten(); _response.setContentLength(length); } else { // Chunk it! _response.setField(HttpFields.__TransferEncoding, HttpFields.__Chunked); _outputStream.setChunking(); } } } else { // Use transfer encodings to determine length _response.removeField(HttpFields.__ContentLength); _outputStream.setChunking(); if (!HttpFields.__Chunked.equalsIgnoreCase(transfer_coding)) { // Check against any TE field List te = _request.getAcceptableTransferCodings(); Enumeration enm = _response.getFieldValues( HttpFields.__TransferEncoding, HttpFields.__separators); while (enm.hasMoreElements()) { String coding = (String) enm.nextElement(); if (HttpFields.__Identity.equalsIgnoreCase(coding) || HttpFields.__Chunked.equalsIgnoreCase(coding)) continue; if (te == null || !te.contains(coding)) throw new HttpException(HttpResponse.__501_Not_Implemented, coding); } } } } break; case 0: { // if (can have content and no content length) _response.removeField(HttpFields.__TransferEncoding); if (_keepAlive) { if (status != HttpResponse.__304_Not_Modified && status != HttpResponse.__204_No_Content && _response.getField(HttpFields.__ContentLength) == null) { if (_completing) { length = _outputStream.getBytesWritten(); _response.setContentLength(length); _response.setField(HttpFields.__Connection, HttpFields.__KeepAlive); } else { _response.setField(HttpFields.__Connection, HttpFields.__Close); has_close = _close = true; _persistent = false; } } else _response.setField(HttpFields.__Connection, HttpFields.__KeepAlive); } else if (!has_close) _response.setField(HttpFields.__Connection, HttpFields.__Close); break; } default: { _close = true; _persistent = false; _keepAlive = false; } } } // Mark request as handled. _request.setHandled(true); _outputStream.writeHeader(_response); _outputStream.flush(); }