/** * 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; }
protected void commit() throws HttpException { if (headersProvider != null) { headersProvider.writeHeaders(this); } try { request.writeHeader(HttpConstants.Headers.CONTENT_LENGTH, "0"); request.writeLine(""); request.flush(); status = Status.COMPLETE; } catch (IOException ex) { throw new HttpConnectionException(ex); } }
public void send(String contentType, byte[] content) throws HttpException { if (headersProvider != null) { headersProvider.writeHeaders(this); } try { request.writeHeader(HttpConstants.Headers.CONTENT_TYPE, contentType); request.writeIntHeader(HttpConstants.Headers.CONTENT_LENGTH, content.length); request.writeLine(""); request.write(content); request.flush(); status = Status.COMPLETE; } catch (IOException ex) { throw new HttpConnectionException(ex); } }
public void addIntHeader(String name, int value) throws HttpException { try { request.writeIntHeader(name, value); } catch (IOException ex) { throw new HttpConnectionException(ex); } }
protected void writeLine(String s) throws HttpException { try { request.writeLine(s); } catch (IOException ex) { throw new HttpConnectionException(ex); } }
/** * Constructor. * * @param listener The listener that created this connection. * @param remoteAddr The address of the remote end or null. * @param in InputStream to read request(s) from. * @param out OutputputStream to write response(s) to. * @param connection The underlying connection object, most likely a socket. This is not used by * HttpConnection other than to make it available via getConnection(). */ public HttpConnection( HttpListener listener, InetAddress remoteAddr, InputStream in, OutputStream out, Object connection) { if (log.isDebugEnabled()) log.debug("new HttpConnection: " + connection); _listener = listener; _remoteInetAddress = remoteAddr; int bufferSize = listener == null ? 4096 : listener.getBufferSize(); int reserveSize = listener == null ? 512 : listener.getBufferReserve(); _inputStream = new HttpInputStream(in, bufferSize); _outputStream = new HttpOutputStream(out, bufferSize, reserveSize); _outputStream.addObserver(this); _firstWrite = false; if (_listener != null) _httpServer = _listener.getHttpServer(); _connection = connection; _statsOn = _httpServer != null && _httpServer.getStatsOn(); if (_statsOn) { _openTime = System.currentTimeMillis(); _httpServer.statsOpenConnection(); } _reqTime = 0; _requests = 0; _request = new HttpRequest(this); _response = new HttpResponse(this); _resolveRemoteHost = _listener != null && _listener.getHttpServer() != null && _listener.getHttpServer().getResolveRemoteHost(); }
/** 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); } }
public OutputStream getOutputStream(String contentType) throws HttpException { try { if (headersProvider != null) { headersProvider.writeHeaders(this); } request.writeHeader(HttpConstants.Headers.CONTENT_TYPE, contentType); request.writeHeader(HttpConstants.Headers.TRANSFER_ENCODING, "chunked"); request.flushHeaders(); request.flush(); // TODO: remove this when no longer necessary status = Status.STREAMING; return new CloseListenerOutputStream(new ChunkedOutputStream(request)) { @Override protected void onClosed() { status = Status.COMPLETE; } }; } catch (IOException ex) { throw new HttpConnectionException(ex); } }
/** * Close the connection. This method calls close on the input and output streams and interrupts * any thread in the handle method. may be specialized to close sockets etc. * * @exception IOException */ public void close() throws IOException { try { _completing = true; if (_connection instanceof Socket && !(_connection instanceof SSLSocket)) ((Socket) _connection).shutdownOutput(); _outputStream.close(); _inputStream.close(); } finally { if (_handlingThread != null && Thread.currentThread() != _handlingThread) _handlingThread.interrupt(); } }
/** Handles an HTTP request along with its redirects and authentication */ protected void doRequest() throws IOException { // do nothing if we've already sent the request if (sentRequest) { // If necessary, finish the request by // closing the uncached output stream. if (resHeader == null && os != null) { os.close(); readServerResponse(); getContentStream(); } return; } doRequestInternal(); }
/** * Sends the request header to the remote HTTP server Not all of them are guaranteed to have any * effect on the content the server will return, depending on if the server supports that field. * * <p>Examples : Accept: text/*, text/html, text/html;level=1, Accept-Charset: iso-8859-5, * unicode-1-1;q=0.8 */ private boolean sendRequest() throws IOException { byte[] request = createRequest(); // make sure we have a connection if (!connected) { connect(); } if (null != cacheResponse) { // does not send if already has a response cache return true; } // send out the HTTP request socketOut.write(request); sentRequest = true; // send any output to the socket (i.e. POST data) if (os != null && os.isCached()) { socketOut.write(os.toByteArray()); } if (os == null || os.isCached()) { readServerResponse(); return true; } return false; }
protected void endRequest() throws IOException { if (os != null) { os.close(); } sentRequest = false; }
private byte[] createRequest() throws IOException { StringBuilder output = new StringBuilder(256); output.append(method); output.append(' '); output.append(requestString()); output.append(' '); output.append("HTTP/1."); // $NON-NLS-1$ if (httpVersion == 0) { output.append("0\r\n"); // $NON-NLS-1$ } else { output.append("1\r\n"); // $NON-NLS-1$ } // add user-specified request headers if any boolean hasContentLength = false; for (int i = 0; i < reqHeader.length(); i++) { String key = reqHeader.getKey(i); if (key != null) { String lKey = key.toLowerCase(); if ((os != null && !os.isChunked()) || (!lKey.equals("transfer-encoding") && !lKey //$NON-NLS-1$ .equals("content-length"))) { // $NON-NLS-1$ output.append(key); String value = reqHeader.get(i); /* * duplicates are allowed under certain conditions see * http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 */ if (lKey.equals("content-length")) { // $NON-NLS-1$ hasContentLength = true; /* * if both setFixedLengthStreamingMode and * content-length are set, use fixedContentLength first */ if (fixedContentLength >= 0) { value = String.valueOf(fixedContentLength); } } if (value != null) { output.append(": "); // $NON-NLS-1$ output.append(value); } output.append("\r\n"); // $NON-NLS-1$ } } } if (fixedContentLength >= 0 && !hasContentLength) { output.append("content-length: "); // $NON-NLS-1$ output.append(String.valueOf(fixedContentLength)); output.append("\r\n"); // $NON-NLS-1$ } if (reqHeader.get("User-Agent") == null) { // $NON-NLS-1$ output.append("User-Agent: "); // $NON-NLS-1$ String agent = getSystemProperty("http.agent"); // $NON-NLS-1$ if (agent == null) { output.append("Java"); // $NON-NLS-1$ output.append(getSystemProperty("java.version")); // $NON-NLS-1$ } else { output.append(agent); } output.append("\r\n"); // $NON-NLS-1$ } if (reqHeader.get("Host") == null) { // $NON-NLS-1$ output.append("Host: "); // $NON-NLS-1$ output.append(url.getHost()); int port = url.getPort(); if (port > 0 && port != defaultPort) { output.append(':'); output.append(Integer.toString(port)); } output.append("\r\n"); // $NON-NLS-1$ } if (reqHeader.get("Accept") == null) { // $NON-NLS-1$ output.append("Accept: *; */*\r\n"); // $NON-NLS-1$ } if (httpVersion > 0 && reqHeader.get("Connection") == null) { // $NON-NLS-1$ output.append("Connection: Keep-Alive\r\n"); // $NON-NLS-1$ } // if we are doing output make sure the appropriate headers are sent if (os != null) { if (reqHeader.get("Content-Type") == null) { // $NON-NLS-1$ output.append("Content-Type: application/x-www-form-urlencoded\r\n"); // $NON-NLS-1$ } if (os.isCached()) { if (reqHeader.get("Content-Length") == null) { // $NON-NLS-1$ output.append("Content-Length: "); // $NON-NLS-1$ output.append(Integer.toString(os.size())); output.append("\r\n"); // $NON-NLS-1$ } } else if (os.isChunked()) { output.append("Transfer-Encoding: chunked\r\n"); // $NON-NLS-1$ } } // end the headers output.append("\r\n"); // $NON-NLS-1$ return output.toString().getBytes("ISO8859_1"); // $NON-NLS-1$ }
/** * 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 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(); }