view src/org/eclipse/jetty/server/AbstractHttpConnection.java @ 1052:4a2489f1d5fe

remove JBuffer.peek()
author Franklin Schmidt <fschmidt@gmail.com>
date Tue, 08 Nov 2016 00:27:16 -0700
parents 2b769da7f67d
children 013939bfc9e8
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.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.JBuffer;
import org.eclipse.jetty.io.Buffers;
import org.eclipse.jetty.io.BufferUtil;
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 = new HttpURI();

	protected volatile HttpParser _parser;
	protected final HttpFields _requestFields = new HttpFields();
	public volatile Request _request;
	private volatile ServletInputStream _in;

	protected volatile HttpGenerator _generator;
	final HttpFields _responseFields = new HttpFields();
	protected volatile 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;
		_server = connector.server;
		init();
	}

	void initParser() {
		Buffers buffers = _connector.getRequestBuffers();
		_parser = new HttpParser(buffers.getHeader(), buffers.getBuffer(), _endp, new RequestHandler());
	}

	void initGenerator() {
		if( _generator != null )
			_generator.shutdown();
		_generator = new HttpGenerator(_connector.getResponseBuffers(), _endp);
	}

	private void init() {
		initParser();
		initGenerator();
		_request = new Request(this);
		_response = new Response(this);
	}

	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._header.remaining()<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()
	{
		_requestFields.clear();
		_responseFields.clear();
		_uri.clear();
		_writer = null;
		_earlyEOF = false;
		init();
	}

	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();
				initGenerator();
				_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();
				initGenerator();
				_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(String method, String uri, String version) throws IOException
	{
		_host = false;
		_expect = false;
		_expect100Continue = false;
		_delayedHandling=false;
		_charset=null;

		if(_request.getTimeStamp()==0)
			_request.setTimeStamp(System.currentTimeMillis());
		_request.setMethod(method);

		try
		{
			_head = false;
			byte[] bytes = StringUtil.getBytes(uri);
			switch (HttpMethods.CACHE.getOrdinal(method))
			{
			  case HttpMethods.CONNECT_ORDINAL:
				  _uri.parseConnect(bytes, 0, bytes.length);
				  break;

			  case HttpMethods.HEAD_ORDINAL:
				  _head = true;
				  _uri.parse(bytes, 0, bytes.length);
				  break;

			  default:
				  _uri.parse(bytes, 0, bytes.length);
			}

			_request.setUri(_uri);

			if (version==null)
			{
				_request.setProtocol(HttpVersions.HTTP_0_9);
				_version = HttpVersions.HTTP_0_9_ORDINAL;
			}
			else
			{
				if (!HttpVersions.CACHE.contains(version))
					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(String name, String 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++)
							{
								int cb = HttpHeaderValues.CACHE.getOrdinal(values[i].trim());
								if (cb == -1)
									_expect = true;
								else
								{
									switch(cb)
									{
										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, HttpHeaderValues.KEEP_ALIVE);
					_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,HttpHeaderValues.CLOSE);
					_generator.setPersistent(false);
				}

				if (!_host)
				{
					LOG.debug("!host {}",this);
					_generator.setResponse(HttpStatus.BAD_REQUEST_400, null);
					_responseFields.put(HttpHeaders.CONNECTION, HttpHeaderValues.CLOSE);
					_generator.completeHeader(_responseFields, true);
					_generator.complete();
					return;
				}

				if (_expect)
				{
					LOG.debug("!expectation {}",this);
					_generator.setResponse(HttpStatus.EXPECTATION_FAILED_417, null);
					_responseFields.put(HttpHeaders.CONNECTION, HttpHeaderValues.CLOSE);
					_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() 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(String method, String uri, String version) throws IOException
		{
			AbstractHttpConnection.this.startRequest(method, uri, version);
		}

		@Override
		public void parsedHeader(String name, String value) throws IOException
		{
			AbstractHttpConnection.this.parsedHeader(name, value);
		}

		@Override
		public void headerComplete() throws IOException
		{
			AbstractHttpConnection.this.headerComplete();
		}

		@Override
		public void content() throws IOException
		{
			AbstractHttpConnection.this.content();
		}

		@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 JBuffer _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(BufferUtil.wrap(b,off,len));
		}
	
		@Override
		public final void write(byte[] b) throws IOException
		{
			write(BufferUtil.wrap(b));
		}
	
		@Override
		public final void write(int b) throws IOException
		{
			if (_onebyte==null)
				_onebyte = BufferUtil.newBuffer(1);
			else
				_onebyte.clear();
			_onebyte.put((byte)b);
			write(_onebyte);
		}
	
		private void write(JBuffer 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.remaining() > 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 final void sendContent(InputStream in) throws IOException
		{
			if (_closed)
				throw new IOException("Closed");

			if (_generator.isWritten())
				throw new IllegalStateException("!empty");

			try
			{
				int max = _generator.prepareUncheckedAddContent();
				JBuffer 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
			{
				in.close();
			}
		}
	}


	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;
			JBuffer 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();
		}
	}

}