Mercurial Hosting > luan
view src/org/eclipse/jetty/server/AbstractHttpConnection.java @ 1004:3fa54d9d19cd
better handling of BindException
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Sat, 22 Oct 2016 23:00:57 -0600 |
parents | 39154cfa58e4 |
children | 0e96ce3db20a |
line wrap: on
line source
// // ======================================================================== // Copyright (c) 1995-2014 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 java.io.Writer; import javax.servlet.ServletInputStream; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.EncodedHttpURI; import org.eclipse.jetty.http.HttpBuffers; 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.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.ByteArrayBuffer; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.io.RuntimeIOException; import org.eclipse.jetty.util.ByteArrayOutputStream2; import org.eclipse.jetty.util.QuotedStringTokenizer; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.URIUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.eclipse.jetty.util.resource.Resource; /** * <p>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}. * </p> * <p> * 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. * </p> * <p> * 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. * </p> * <p> * 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. * </p> * */ public abstract class AbstractHttpConnection extends AbstractConnection { private static final Logger LOG = LoggerFactory.getLogger(AbstractHttpConnection.class); private final Connector _connector; private final Server _server; protected final HttpURI _uri = StringUtil.__UTF8.equals(URIUtil.__CHARSET)?new HttpURI():new EncodedHttpURI(URIUtil.__CHARSET); protected final HttpParser _parser; protected final HttpFields _requestFields = new HttpFields(); public final Request _request; private volatile ServletInputStream _in; protected final HttpGenerator _generator; final HttpFields _responseFields = new HttpFields(); protected final Response _response; private volatile Output _out; private volatile HttpWriter _writer; private volatile PrintWriter _printWriter; private int _version = -2; // UNKNOWN private String _charset; private boolean _expect = false; private boolean _expect100Continue = false; private boolean _head = false; private boolean _host = false; private boolean _delayedHandling = false; private boolean _earlyEOF = false; protected AbstractHttpConnection(Connector connector, EndPoint endpoint) { super(endpoint); _connector = connector; HttpBuffers ab = _connector; _parser = new HttpParser(ab.getRequestBuffers(), endpoint, new RequestHandler()); _request = new Request(this); _response = new Response(this); _generator = new HttpGenerator(ab.getResponseBuffers(), endpoint); _server = connector.server; } public final Connector getConnector() { return _connector; } public final Request getRequest() { return _request; } public final Response getResponse() { return _response; } /* ------------------------------------------------------------ */ /** * Get the inputStream from the connection. * <p> * 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 final ServletInputStream getInputStream() throws IOException { // If the client is expecting 100 CONTINUE, then send it now. if (_expect100Continue) { // is content missing? if (_parser.getHeaderBuffer()==null || _parser.getHeaderBuffer().length()<2) { if (_generator.isCommitted()) throw new IllegalStateException("Committed before 100 Continues"); _generator.send1xx(HttpStatus.CONTINUE_100); } _expect100Continue=false; } if (_in == null) _in = new HttpInput(); return _in; } /* ------------------------------------------------------------ */ /** * @return The output stream for this connection. The stream will be created if it does not already exist. */ public final 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 final PrintWriter getPrintWriter(String encoding) { getOutputStream(); if (_writer==null) { _writer = new HttpWriter(_out); _printWriter = new PrintWriter(_writer) { public void close() { synchronized (lock) { try { out.close(); } catch (IOException e) { setError(); } } } }; } _writer.setCharacterEncoding(encoding); return _printWriter; } protected 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(); _writer = null; _earlyEOF = false; } private void handleRequest() throws IOException { boolean error = false; try { // 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(). _request.setHandled(false); String info=null; try { _uri.getPort(); String path = null; try { path = _uri.getDecodedPath(); } catch (Exception e) { LOG.warn("Failed UTF-8 decode for request path, trying ISO-8859-1"); LOG.trace("",e); path = _uri.getDecodedPath(StringUtil.__ISO_8859_1); } info = URIUtil.canonicalPath(path); if (info==null && !_request.getMethod().equals(HttpMethods.CONNECT)) { if (path==null && _uri.getScheme()!=null && _uri.getHost()!=null) { info="/"; _request.setRequestURI(""); } else throw new HttpException(400); } _request.setPathInfo(info); if (_out!=null) _out.reopen(); _connector.customize(this); _server.handle(this); } catch (EofException e) { LOG.debug("",e); error=true; _request.setHandled(true); if (!_response.isCommitted()) _generator.sendError(500, null, null, true); } catch (RuntimeIOException 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) { LOG.warn(String.valueOf(_uri),e); error=true; _request.setHandled(true); _generator.sendError(info==null?400:500, null, null, true); } } finally { 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(); } } else { _response.complete(); } _request.setHandled(true); } } public final 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,HttpGenerator.LAST); _generator.complete(); throw new HttpException(HttpStatus.INTERNAL_SERVER_ERROR_500); } } if (last) _generator.complete(); } public final void completeResponse() throws IOException { if (!_generator.isCommitted()) { _generator.setResponse(_response.getStatus(), _response.getReason()); try { _generator.completeHeader(_responseFields, HttpGenerator.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,HttpGenerator.LAST); _generator.complete(); throw new HttpException(HttpStatus.INTERNAL_SERVER_ERROR_500); } } _generator.complete(); } public final void flushResponse() throws IOException { try { commitResponse(HttpGenerator.MORE); _generator.flushBuffer(); } catch(IOException e) { throw (e instanceof EofException) ? e:new EofException(e); } } public final int getMaxIdleTime() { if (_connector.server.isLowOnThreads() && _endp.getMaxIdleTime()==_connector.getMaxIdleTime()) return 0; if (_endp.getMaxIdleTime()>0) return _endp.getMaxIdleTime(); return _connector.getMaxIdleTime(); } @Override public String toString() { return String.format("%s,g=%s,p=%s", super.toString(), _generator, _parser); } private void startRequest(Buffer method, Buffer uri, Buffer version) throws IOException { uri=uri.asImmutableBuffer(); _host = false; _expect = false; _expect100Continue = 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); } } private 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: if (_version>=HttpVersions.HTTP_1_1_ORDINAL) { value = HttpHeaderValues.CACHE.lookup(value); switch(HttpHeaderValues.CACHE.getOrdinal(value)) { case HttpHeaderValues.CONTINUE_ORDINAL: _expect100Continue = true; break; default: String[] values = value.toString().split(","); for (int i=0;values!=null && i<values.length;i++) { CachedBuffer cb=HttpHeaderValues.CACHE.get(values[i].trim()); if (cb==null) _expect = true; else { switch(cb.getOrdinal()) { case HttpHeaderValues.CONTINUE_ORDINAL: _expect100Continue = true; break; default: _expect = true; } } } } } break; case HttpHeaders.ACCEPT_ENCODING_ORDINAL: case HttpHeaders.USER_AGENT_ORDINAL: value = HttpHeaderValues.CACHE.lookup(value); break; case HttpHeaders.CONTENT_TYPE_ORDINAL: value = MimeTypes.CACHE.lookup(value); _charset=MimeTypes.getCharsetFromContentType(value); break; } _requestFields.add(name, value); } private void headerComplete() throws IOException { // Handle idle race if (_endp.isOutputShutdown()) { _endp.close(); return; } _generator.setVersion(_version); switch (_version) { case HttpVersions.HTTP_0_9_ORDINAL: break; case HttpVersions.HTTP_1_0_ORDINAL: _generator.setHead(_head); if (_parser.isPersistent()) { _responseFields.add(HttpHeaders.CONNECTION_BUFFER, HttpHeaderValues.KEEP_ALIVE_BUFFER); _generator.setPersistent(true); } else if (HttpMethods.CONNECT.equals(_request.getMethod())) { _generator.setPersistent(true); _parser.setPersistent(true); } break; case HttpVersions.HTTP_1_1_ORDINAL: _generator.setHead(_head); if (!_parser.isPersistent()) { _responseFields.add(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.CLOSE_BUFFER); _generator.setPersistent(false); } if (!_host) { LOG.debug("!host {}",this); _generator.setResponse(HttpStatus.BAD_REQUEST_400, null); _responseFields.put(HttpHeaders.CONNECTION_BUFFER, HttpHeaderValues.CLOSE_BUFFER); _generator.completeHeader(_responseFields, true); _generator.complete(); return; } if (_expect) { LOG.debug("!expectation {}",this); _generator.setResponse(HttpStatus.EXPECTATION_FAILED_417, null); _responseFields.put(HttpHeaders.CONNECTION_BUFFER, HttpHeaderValues.CLOSE_BUFFER); _generator.completeHeader(_responseFields, true); _generator.complete(); return; } break; default: } if(_charset!=null) _request.setCharacterEncodingUnchecked(_charset); // Either handle now or wait for first content if ((_parser.getContentLength()<=0 && !_parser.isChunking())||_expect100Continue) handleRequest(); else _delayedHandling = true; } private void content(Buffer buffer) throws IOException { if (_delayedHandling) { _delayedHandling = false; handleRequest(); } } private void messageComplete(long contentLength) throws IOException { if (_delayedHandling) { _delayedHandling = false; handleRequest(); } } private void earlyEOF() { _earlyEOF = true; } private class RequestHandler implements HttpParser.EventHandler { @Override public void startRequest(Buffer method, Buffer uri, Buffer version) throws IOException { AbstractHttpConnection.this.startRequest(method, uri, version); } @Override public void parsedHeader(Buffer name, Buffer value) throws IOException { AbstractHttpConnection.this.parsedHeader(name, value); } @Override public void headerComplete() throws IOException { AbstractHttpConnection.this.headerComplete(); } @Override public void content(Buffer ref) throws IOException { AbstractHttpConnection.this.content(ref); } @Override public void messageComplete(long contentLength) throws IOException { AbstractHttpConnection.this.messageComplete(contentLength); } @Override public void earlyEOF() { AbstractHttpConnection.this.earlyEOF(); } } public final class Output extends ServletOutputStream { private boolean _closed; private ByteArrayBuffer _onebyte; // These are held here for reuse by Writer String _characterEncoding; Writer _converter; char[] _chars; ByteArrayOutputStream2 _bytes; public final void reopen() { _closed = false; } @Override public final void write(byte[] b, int off, int len) throws IOException { write(new ByteArrayBuffer(b,off,len)); } @Override public final void write(byte[] b) throws IOException { write(new ByteArrayBuffer(b)); } @Override public final void write(int b) throws IOException { if (_onebyte==null) _onebyte = new ByteArrayBuffer(1); else _onebyte.clear(); _onebyte.put((byte)b); write(_onebyte); } private void write(Buffer buffer) throws IOException { if (_closed) throw new IOException("Closed"); if (!_generator.isOpen()) throw new EofException(); // Block until we can add _content. while (_generator.isBufferFull()) { _generator.blockForOutput(getMaxIdleTime()); if (_closed) throw new IOException("Closed"); if (!_generator.isOpen()) throw new EofException(); } // Add the _content _generator.addContent(buffer, HttpGenerator.MORE); // Have to flush and complete headers? if (_generator.isAllContentWritten()) { flush(); close(); } else if (_generator.isBufferFull()) commitResponse(HttpGenerator.MORE); // Block until our buffer is free while (buffer.length() > 0 && _generator.isOpen()) { _generator.blockForOutput(getMaxIdleTime()); } } /* ------------------------------------------------------------ */ /* * @see java.io.OutputStream#close() */ @Override public void close() throws IOException { if (_closed) return; if (!_generator.isCommitted()) commitResponse(HttpGenerator.LAST); else flushResponse(); _closed = true; } /* ------------------------------------------------------------ */ /* * @see java.io.OutputStream#flush() */ @Override public void flush() throws IOException { if (!_generator.isCommitted()) commitResponse(HttpGenerator.MORE); _generator.flush(getMaxIdleTime()); } /* ------------------------------------------------------------ */ /* * @see javax.servlet.ServletOutputStream#print(java.lang.String) */ @Override public void print(String s) throws IOException { if (_closed) throw new IOException("Closed"); PrintWriter writer = getPrintWriter(null); writer.print(s); } public void sendContent(Object content) throws IOException { Resource resource=null; if (_closed) throw new IOException("Closed"); if (_generator.isWritten()) throw new IllegalStateException("!empty"); if (content instanceof Resource) { resource=(Resource)content; _responseFields.putDateField(HttpHeaders.LAST_MODIFIED_BUFFER, resource.lastModified()); content=resource.getInputStream(); } // Process content. if (content instanceof Buffer) { _generator.addContent((Buffer) content, HttpGenerator.LAST); commitResponse(HttpGenerator.LAST); } else if (content instanceof InputStream) { InputStream in = (InputStream)content; try { int max = _generator.prepareUncheckedAddContent(); Buffer buffer = _generator.getUncheckedBuffer(); int len=buffer.readFrom(in,max); while (len>=0) { _generator.completeUncheckedAddContent(); _out.flush(); max = _generator.prepareUncheckedAddContent(); buffer = _generator.getUncheckedBuffer(); len=buffer.readFrom(in,max); } _generator.completeUncheckedAddContent(); _out.flush(); } finally { if (resource!=null) resource.release(); else in.close(); } } else throw new IllegalArgumentException("unknown content type?"); } } private final class HttpInput extends ServletInputStream { /* ------------------------------------------------------------ */ /* * @see java.io.InputStream#read() */ @Override public int read() throws IOException { byte[] bytes = new byte[1]; int read = read(bytes, 0, 1); return read < 0 ? -1 : 0xff & bytes[0]; } /* ------------------------------------------------------------ */ /* * @see java.io.InputStream#read(byte[], int, int) */ @Override public int read(byte[] b, int off, int len) throws IOException { int l = -1; Buffer content = _parser.blockForContent(getMaxIdleTime()); if (content!=null) l = content.get(b, off, len); else if (_earlyEOF) throw new EofException("early EOF"); return l; } @Override public int available() throws IOException { return _parser.available(); } } }