// ======================================================================== // Copyright (c) 2004-2011 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // You may elect to redistribute this code under either of these licenses. // ======================================================================== package org.eclipse.jetty.server; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import javax.servlet.DispatcherType; import javax.servlet.ServletInputStream; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.continuation.ContinuationThrowable; import org.eclipse.jetty.http.EncodedHttpURI; import org.eclipse.jetty.http.Generator; import org.eclipse.jetty.http.HttpBuffers; import org.eclipse.jetty.http.HttpContent; import org.eclipse.jetty.http.HttpException; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpGenerator; import org.eclipse.jetty.http.HttpHeaderValues; import org.eclipse.jetty.http.HttpHeaders; import org.eclipse.jetty.http.HttpMethods; import org.eclipse.jetty.http.HttpParser; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpVersions; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.http.Parser; import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.BufferCache.CachedBuffer; import org.eclipse.jetty.io.Buffers; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.io.RuntimeIOException; import org.eclipse.jetty.io.UncheckedPrintWriter; import org.eclipse.jetty.server.nio.NIOConnector; import org.eclipse.jetty.server.ssl.SslConnector; import org.eclipse.jetty.util.QuotedStringTokenizer; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.Resource; /** *

A HttpConnection represents the connection of a HTTP client to the server * and is created by an instance of a {@link Connector}. It's prime function is * to associate {@link Request} and {@link Response} instances with a {@link EndPoint}. *

*

* A connection is also the prime mechanism used by jetty to recycle objects without * pooling. The {@link Request}, {@link Response}, {@link HttpParser}, {@link HttpGenerator} * and {@link HttpFields} instances are all recycled for the duraction of * a connection. Where appropriate, allocated buffers are also kept associated * with the connection via the parser and/or generator. *

*

* The connection state is held by 3 separate state machines: The request state, the * response state and the continuation state. All three state machines must be driven * to completion for every request, and all three can complete in any order. *

*

* The HttpConnection support protocol upgrade. If on completion of a request, the * response code is 101 (switch protocols), then the org.eclipse.jetty.io.Connection * request attribute is checked to see if there is a new Connection instance. If so, * the new connection is returned from {@link #handle()} and is used for future * handling of the underlying connection. Note that for switching protocols that * don't use 101 responses (eg CONNECT), the response should be sent and then the * status code changed to 101 before returning from the handler. Implementors * of new Connection types should be careful to extract any buffered data from * (HttpParser)http.getParser()).getHeaderBuffer() and * (HttpParser)http.getParser()).getBodyBuffer() to initialise their new connection. *

* */ public abstract class AbstractHttpConnection extends AbstractConnection { private static final Logger LOG = Log.getLogger(AbstractHttpConnection.class); private static final int UNKNOWN = -2; private static final ThreadLocal __currentConnection = new ThreadLocal(); private int _requests; protected final Connector _connector; protected final Server _server; protected final HttpURI _uri; protected final Parser _parser; protected final HttpFields _requestFields; protected final Request _request; protected volatile ServletInputStream _in; protected final Generator _generator; protected final HttpFields _responseFields; protected final Response _response; protected volatile Output _out; protected volatile OutputWriter _writer; protected volatile PrintWriter _printWriter; int _include; private Object _associatedObject; // associated object private int _version = UNKNOWN; private String _charset; private boolean _expect = false; private boolean _expect100Continue = false; private boolean _expect102Processing = false; private boolean _head = false; private boolean _host = false; private boolean _delayedHandling=false; /* ------------------------------------------------------------ */ public static AbstractHttpConnection getCurrentConnection() { return __currentConnection.get(); } /* ------------------------------------------------------------ */ protected static void setCurrentConnection(AbstractHttpConnection connection) { __currentConnection.set(connection); } /* ------------------------------------------------------------ */ public AbstractHttpConnection(Connector connector, EndPoint endpoint, Server server) { super(endpoint); _uri = StringUtil.__UTF8.equals(URIUtil.__CHARSET)?new HttpURI():new EncodedHttpURI(URIUtil.__CHARSET); _connector = connector; HttpBuffers ab = (HttpBuffers)_connector; _parser = newHttpParser(ab.getRequestBuffers(), endpoint, new RequestHandler()); _requestFields = new HttpFields(); _responseFields = new HttpFields(); _request = new Request(this); _response = new Response(this); _generator = newHttpGenerator(ab.getResponseBuffers(), endpoint); _generator.setSendServerVersion(server.getSendServerVersion()); _server = server; } /* ------------------------------------------------------------ */ protected AbstractHttpConnection(Connector connector, EndPoint endpoint, Server server, Parser parser, Generator generator, Request request) { super(endpoint); _uri = URIUtil.__CHARSET.equals(StringUtil.__UTF8)?new HttpURI():new EncodedHttpURI(URIUtil.__CHARSET); _connector = connector; _parser = parser; _requestFields = new HttpFields(); _responseFields = new HttpFields(); _request = request; _response = new Response(this); _generator = generator; _generator.setSendServerVersion(server.getSendServerVersion()); _server = server; } protected HttpParser newHttpParser(Buffers requestBuffers, EndPoint endpoint, HttpParser.EventHandler requestHandler) { return new HttpParser(requestBuffers, endpoint, requestHandler); } protected HttpGenerator newHttpGenerator(Buffers responseBuffers, EndPoint endPoint) { return new HttpGenerator(responseBuffers, endPoint); } /* ------------------------------------------------------------ */ /** * @return the parser used by this connection */ public Parser getParser() { return _parser; } /* ------------------------------------------------------------ */ /** * @return the number of requests handled by this connection */ public int getRequests() { return _requests; } /* ------------------------------------------------------------ */ public Server getServer() { return _server; } /* ------------------------------------------------------------ */ /** * @return Returns the associatedObject. */ public Object getAssociatedObject() { return _associatedObject; } /* ------------------------------------------------------------ */ /** * @param associatedObject The associatedObject to set. */ public void setAssociatedObject(Object associatedObject) { _associatedObject = associatedObject; } /* ------------------------------------------------------------ */ /** * @return Returns the connector. */ public Connector getConnector() { return _connector; } /* ------------------------------------------------------------ */ /** * @return Returns the requestFields. */ public HttpFields getRequestFields() { return _requestFields; } /* ------------------------------------------------------------ */ /** * @return Returns the responseFields. */ public HttpFields getResponseFields() { return _responseFields; } /* ------------------------------------------------------------ */ /** * Find out if the request supports CONFIDENTIAL security. * @param request the incoming HTTP request * @return the result of calling {@link Connector#isConfidential(Request)}, or false * if there is no connector */ public boolean isConfidential(Request request) { return _connector != null && _connector.isConfidential(request); } /* ------------------------------------------------------------ */ /** * Find out if the request supports INTEGRAL security. * @param request the incoming HTTP request * @return the result of calling {@link Connector#isIntegral(Request)}, or false * if there is no connector */ public boolean isIntegral(Request request) { return _connector != null && _connector.isIntegral(request); } /* ------------------------------------------------------------ */ /** * @return false (this method is not yet implemented) */ public boolean getResolveNames() { return _connector.getResolveNames(); } /* ------------------------------------------------------------ */ /** * @return Returns the request. */ public Request getRequest() { return _request; } /* ------------------------------------------------------------ */ /** * @return Returns the response. */ public Response getResponse() { return _response; } /* ------------------------------------------------------------ */ /** * Get the inputStream from the connection. *

* If the associated response has the Expect header set to 100 Continue, * then accessing the input stream indicates that the handler/servlet * is ready for the request body and thus a 100 Continue response is sent. * * @return The input stream for this connection. * The stream will be created if it does not already exist. * @throws IOException if the input stream cannot be retrieved */ public ServletInputStream getInputStream() throws IOException { // If the client is expecting 100 CONTINUE, then send it now. if (_expect100Continue) { // is content missing? if (((HttpParser)_parser).getHeaderBuffer()==null || ((HttpParser)_parser).getHeaderBuffer().length()<2) { if (_generator.isCommitted()) throw new IllegalStateException("Committed before 100 Continues"); ((HttpGenerator)_generator).send1xx(HttpStatus.CONTINUE_100); } _expect100Continue=false; } if (_in == null) _in = new HttpInput(AbstractHttpConnection.this); return _in; } /* ------------------------------------------------------------ */ /** * @return The output stream for this connection. The stream will be created if it does not already exist. */ public ServletOutputStream getOutputStream() { if (_out == null) _out = new Output(); return _out; } /* ------------------------------------------------------------ */ /** * @param encoding the PrintWriter encoding * @return A {@link PrintWriter} wrapping the {@link #getOutputStream output stream}. The writer is created if it * does not already exist. */ public PrintWriter getPrintWriter(String encoding) { getOutputStream(); if (_writer==null) { _writer=new OutputWriter(); if (_server.isUncheckedPrintWriter()) _printWriter=new UncheckedPrintWriter(_writer); else _printWriter = new PrintWriter(_writer) { public void close() { synchronized (lock) { try { out.close(); } catch (IOException e) { setError(); } } } }; } _writer.setCharacterEncoding(encoding); return _printWriter; } /* ------------------------------------------------------------ */ public boolean isResponseCommitted() { return _generator.isCommitted(); } /* ------------------------------------------------------------ */ public void reset() { _parser.reset(); _parser.returnBuffers(); // TODO maybe only on unhandle _requestFields.clear(); _request.recycle(); _generator.reset(); _generator.returnBuffers();// TODO maybe only on unhandle _responseFields.clear(); _response.recycle(); _uri.clear(); } /* ------------------------------------------------------------ */ protected void handleRequest() throws IOException { boolean error = false; String threadName=null; Throwable async_exception=null; try { if (LOG.isDebugEnabled()) { threadName=Thread.currentThread().getName(); Thread.currentThread().setName(threadName+" - "+_uri); } // Loop here to handle async request redispatches. // The loop is controlled by the call to async.unhandle in the // finally block below. If call is from a non-blocking connector, // then the unhandle will return false only if an async dispatch has // already happened when unhandle is called. For a blocking connector, // the wait for the asynchronous dispatch or timeout actually happens // within the call to unhandle(). final Server server=_server; boolean handling=_request._async.handling() && server!=null && server.isRunning(); while (handling) { _request.setHandled(false); String info=null; try { _uri.getPort(); info=URIUtil.canonicalPath(_uri.getDecodedPath()); if (info==null && !_request.getMethod().equals(HttpMethods.CONNECT)) throw new HttpException(400); _request.setPathInfo(info); if (_out!=null) _out.reopen(); if (_request._async.isInitial()) { _request.setDispatcherType(DispatcherType.REQUEST); _connector.customize(_endp, _request); server.handle(this); } else { _request.setDispatcherType(DispatcherType.ASYNC); server.handleAsync(this); } } catch (ContinuationThrowable e) { LOG.ignore(e); } catch (EofException e) { async_exception=e; LOG.debug(e); error=true; _request.setHandled(true); } catch (RuntimeIOException e) { async_exception=e; LOG.debug(e); error=true; _request.setHandled(true); } catch (HttpException e) { LOG.debug(e); error=true; _request.setHandled(true); _response.sendError(e.getStatus(), e.getReason()); } catch (Throwable e) { async_exception=e; LOG.warn(String.valueOf(_uri),e); error=true; _request.setHandled(true); _generator.sendError(info==null?400:500, null, null, true); } finally { handling = !_request._async.unhandle() && server.isRunning() && _server!=null; } } } finally { if (threadName!=null) Thread.currentThread().setName(threadName); if (_request._async.isUncompleted()) { _request._async.doComplete(async_exception); if (_expect100Continue) { LOG.debug("100 continues not sent"); // We didn't send 100 continues, but the latest interpretation // of the spec (see httpbis) is that the client will either // send the body anyway, or close. So we no longer need to // do anything special here other than make the connection not persistent _expect100Continue = false; if (!_response.isCommitted()) _generator.setPersistent(false); } if(_endp.isOpen()) { if (error) { _endp.shutdownOutput(); _generator.setPersistent(false); if (!_generator.isComplete()) _response.complete(); } else { if (!_response.isCommitted() && !_request.isHandled()) _response.sendError(HttpServletResponse.SC_NOT_FOUND); _response.complete(); if (_generator.isPersistent()) _connector.persist(_endp); } } else { _response.complete(); } _request.setHandled(true); } } } /* ------------------------------------------------------------ */ public abstract Connection handle() throws IOException; /* ------------------------------------------------------------ */ public void commitResponse(boolean last) throws IOException { if (!_generator.isCommitted()) { _generator.setResponse(_response.getStatus(), _response.getReason()); try { // If the client was expecting 100 continues, but we sent something // else, then we need to close the connection if (_expect100Continue && _response.getStatus()!=100) _generator.setPersistent(false); _generator.completeHeader(_responseFields, last); } catch(RuntimeException e) { LOG.warn("header full: " + e); _response.reset(); _generator.reset(); _generator.setResponse(HttpStatus.INTERNAL_SERVER_ERROR_500,null); _generator.completeHeader(_responseFields,Generator.LAST); _generator.complete(); throw new HttpException(HttpStatus.INTERNAL_SERVER_ERROR_500); } } if (last) _generator.complete(); } /* ------------------------------------------------------------ */ public void completeResponse() throws IOException { if (!_generator.isCommitted()) { _generator.setResponse(_response.getStatus(), _response.getReason()); try { _generator.completeHeader(_responseFields, Generator.LAST); } catch(RuntimeException e) { LOG.warn("header full: "+e); LOG.debug(e); _response.reset(); _generator.reset(); _generator.setResponse(HttpStatus.INTERNAL_SERVER_ERROR_500,null); _generator.completeHeader(_responseFields,Generator.LAST); _generator.complete(); throw new HttpException(HttpStatus.INTERNAL_SERVER_ERROR_500); } } _generator.complete(); } /* ------------------------------------------------------------ */ public void flushResponse() throws IOException { try { commitResponse(Generator.MORE); _generator.flushBuffer(); } catch(IOException e) { throw (e instanceof EofException) ? e:new EofException(e); } } /* ------------------------------------------------------------ */ public Generator getGenerator() { return _generator; } /* ------------------------------------------------------------ */ public boolean isIncluding() { return _include>0; } /* ------------------------------------------------------------ */ public void include() { _include++; } /* ------------------------------------------------------------ */ public void included() { _include--; if (_out!=null) _out.reopen(); } /* ------------------------------------------------------------ */ public boolean isIdle() { return _generator.isIdle() && (_parser.isIdle() || _delayedHandling); } /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.io.Connection#isSuspended() */ public boolean isSuspended() { return _request.getAsyncContinuation().isSuspended(); } /* ------------------------------------------------------------ */ public void onClose() { LOG.debug("closed {}",this); } /* ------------------------------------------------------------ */ public boolean isExpecting100Continues() { return _expect100Continue; } /* ------------------------------------------------------------ */ public boolean isExpecting102Processing() { return _expect102Processing; } /* ------------------------------------------------------------ */ public int getMaxIdleTime() { if (_connector.isLowResources() && _endp.getMaxIdleTime()==_connector.getMaxIdleTime()) return _connector.getLowResourceMaxIdleTime(); if (_endp.getMaxIdleTime()>0) return _endp.getMaxIdleTime(); return _connector.getMaxIdleTime(); } /* ------------------------------------------------------------ */ public String toString() { return String.format("%s,g=%s,p=%s,r=%d", super.toString(), _generator, _parser, _requests); } protected void startRequest(Buffer method, Buffer uri, Buffer version) throws IOException { uri=uri.asImmutableBuffer(); _host = false; _expect = false; _expect100Continue=false; _expect102Processing=false; _delayedHandling=false; _charset=null; if(_request.getTimeStamp()==0) _request.setTimeStamp(System.currentTimeMillis()); _request.setMethod(method.toString()); try { _head=false; switch (HttpMethods.CACHE.getOrdinal(method)) { case HttpMethods.CONNECT_ORDINAL: _uri.parseConnect(uri.array(), uri.getIndex(), uri.length()); break; case HttpMethods.HEAD_ORDINAL: _head=true; _uri.parse(uri.array(), uri.getIndex(), uri.length()); break; default: _uri.parse(uri.array(), uri.getIndex(), uri.length()); } _request.setUri(_uri); if (version==null) { _request.setProtocol(HttpVersions.HTTP_0_9); _version=HttpVersions.HTTP_0_9_ORDINAL; } else { version= HttpVersions.CACHE.get(version); if (version==null) throw new HttpException(HttpStatus.BAD_REQUEST_400,null); _version = HttpVersions.CACHE.getOrdinal(version); if (_version <= 0) _version = HttpVersions.HTTP_1_0_ORDINAL; _request.setProtocol(version.toString()); } } catch (Exception e) { LOG.debug(e); if (e instanceof HttpException) throw (HttpException)e; throw new HttpException(HttpStatus.BAD_REQUEST_400,null,e); } } protected void parsedHeader(Buffer name, Buffer value) throws IOException { int ho = HttpHeaders.CACHE.getOrdinal(name); switch (ho) { case HttpHeaders.HOST_ORDINAL: // TODO check if host matched a host in the URI. _host = true; break; case HttpHeaders.EXPECT_ORDINAL: value = HttpHeaderValues.CACHE.lookup(value); switch(HttpHeaderValues.CACHE.getOrdinal(value)) { case HttpHeaderValues.CONTINUE_ORDINAL: _expect100Continue=_generator instanceof HttpGenerator; break; case HttpHeaderValues.PROCESSING_ORDINAL: _expect102Processing=_generator instanceof HttpGenerator; break; default: String[] values = value.toString().split(","); for (int i=0;values!=null && i