/** * 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; } }
/** * 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; }