view src/org/eclipse/jetty/server/AbstractHttpConnection.java @ 954:a021c4c9c244

use just one SelectSet per SelectorManager
author Franklin Schmidt <fschmidt@gmail.com>
date Thu, 13 Oct 2016 00:54:10 -0600
parents 8db5996c8c89
children 1094975d013b
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 javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;
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.Generator;
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.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.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 static final int UNKNOWN = -2;
	private static final ThreadLocal<AbstractHttpConnection> __currentConnection = new ThreadLocal<AbstractHttpConnection>();

	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;
	private boolean _earlyEOF = 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);
		_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;
		_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);
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return Returns the request.
	 */
	public Request getRequest()
	{
		return _request;
	}

	/* ------------------------------------------------------------ */
	/**
	 * @return Returns the response.
	 */
	public 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 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();
			_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 boolean isEarlyEOF()
	{
		return _earlyEOF;
	}

	/* ------------------------------------------------------------ */
	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();
		_writer=null;
		_earlyEOF = false;
	}

	protected 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();

				_request.setDispatcherType(DispatcherType.REQUEST);
				_connector.customize(_endp, _request);
				_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();
					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 false;
	}

	/* ------------------------------------------------------------ */
	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 0;
		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:
				if (_version>=HttpVersions.HTTP_1_1_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<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=_generator instanceof HttpGenerator;
											break;
										case HttpHeaderValues.PROCESSING_ORDINAL:
											_expect102Processing=_generator instanceof HttpGenerator;
											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);
	}

	/* ------------------------------------------------------------ */
	protected void headerComplete() throws IOException
	{
		// Handle idle race
		if (_endp.isOutputShutdown())
		{
			_endp.close();
			return;
		}
		
		_requests++;
		_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 ((((HttpParser)_parser).getContentLength()<=0 && !((HttpParser)_parser).isChunking())||_expect100Continue)
			handleRequest();
		else
			_delayedHandling=true;
	}

	/* ------------------------------------------------------------ */
	protected void content(Buffer buffer) throws IOException
	{
		if (_delayedHandling)
		{
			_delayedHandling=false;
			handleRequest();
		}
	}

	/* ------------------------------------------------------------ */
	public void messageComplete(long contentLength) throws IOException
	{
		if (_delayedHandling)
		{
			_delayedHandling=false;
			handleRequest();
		}
	}

	/* ------------------------------------------------------------ */
	public void earlyEOF()
	{
		_earlyEOF = true;
	}

	/* ------------------------------------------------------------ */
	/* ------------------------------------------------------------ */
	/* ------------------------------------------------------------ */
	private class RequestHandler extends HttpParser.EventHandler
	{
		/*
		 *
		 * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#startRequest(org.eclipse.io.Buffer,
		 *      org.eclipse.io.Buffer, org.eclipse.io.Buffer)
		 */
		@Override
		public void startRequest(Buffer method, Buffer uri, Buffer version) throws IOException
		{
			AbstractHttpConnection.this.startRequest(method, uri, version);
		}

		/*
		 * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#parsedHeaderValue(org.eclipse.io.Buffer)
		 */
		@Override
		public void parsedHeader(Buffer name, Buffer value) throws IOException
		{
			AbstractHttpConnection.this.parsedHeader(name, value);
		}

		/*
		 * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#headerComplete()
		 */
		@Override
		public void headerComplete() throws IOException
		{
			AbstractHttpConnection.this.headerComplete();
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#content(int, org.eclipse.io.Buffer)
		 */
		@Override
		public void content(Buffer ref) throws IOException
		{
			AbstractHttpConnection.this.content(ref);
		}

		/* ------------------------------------------------------------ */
		/*
		 * (non-Javadoc)
		 *
		 * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#messageComplete(int)
		 */
		@Override
		public void messageComplete(long contentLength) throws IOException
		{
			AbstractHttpConnection.this.messageComplete(contentLength);
		}

		/* ------------------------------------------------------------ */
		/*
		 * (non-Javadoc)
		 *
		 * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#startResponse(org.eclipse.io.Buffer, int,
		 *      org.eclipse.io.Buffer)
		 */
		@Override
		public void startResponse(Buffer version, int status, Buffer reason)
		{
			if (LOG.isDebugEnabled())
				LOG.debug("Bad request!: "+version+" "+status+" "+reason);
		}

		/* ------------------------------------------------------------ */
		/*
		 * (non-Javadoc)
		 *
		 * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#earlyEOF()
		 */
		@Override
		public void earlyEOF()
		{
			AbstractHttpConnection.this.earlyEOF();
		}
	}

	/* ------------------------------------------------------------ */
	/* ------------------------------------------------------------ */
	/* ------------------------------------------------------------ */
	public class Output extends HttpOutput
	{
		Output()
		{
			super(AbstractHttpConnection.this);
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see java.io.OutputStream#close()
		 */
		@Override
		public void close() throws IOException
		{
			if (isClosed())
				return;

			if (!isIncluding() && !super._generator.isCommitted())
				commitResponse(Generator.LAST);
			else
				flushResponse();

			super.close();
		}


		/* ------------------------------------------------------------ */
		/*
		 * @see java.io.OutputStream#flush()
		 */
		@Override
		public void flush() throws IOException
		{
			if (!super._generator.isCommitted())
				commitResponse(Generator.MORE);
			super.flush();
		}

		/* ------------------------------------------------------------ */
		/*
		 * @see javax.servlet.ServletOutputStream#print(java.lang.String)
		 */
		@Override
		public void print(String s) throws IOException
		{
			if (isClosed())
				throw new IOException("Closed");
			PrintWriter writer=getPrintWriter(null);
			writer.print(s);
		}

		/* ------------------------------------------------------------ */
		public void sendResponse(Buffer response) throws IOException
		{
			((HttpGenerator)super._generator).sendResponse(response);
		}

		/* ------------------------------------------------------------ */
		public void sendContent(Object content) throws IOException
		{
			Resource resource=null;

			if (isClosed())
				throw new IOException("Closed");

			if (super._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)
			{
				super._generator.addContent((Buffer) content, Generator.LAST);
				commitResponse(Generator.LAST);
			}
			else if (content instanceof InputStream)
			{
				InputStream in = (InputStream)content;

				try
				{
					int max = super._generator.prepareUncheckedAddContent();
					Buffer buffer = super._generator.getUncheckedBuffer();

					int len=buffer.readFrom(in,max);

					while (len>=0)
					{
						super._generator.completeUncheckedAddContent();
						_out.flush();

						max = super._generator.prepareUncheckedAddContent();
						buffer = super._generator.getUncheckedBuffer();
						len=buffer.readFrom(in,max);
					}
					super._generator.completeUncheckedAddContent();
					_out.flush();
				}
				finally
				{
					if (resource!=null)
						resource.release();
					else
						in.close();
				}
			}
			else
				throw new IllegalArgumentException("unknown content type?");


		}
	}

	/* ------------------------------------------------------------ */
	/* ------------------------------------------------------------ */
	/* ------------------------------------------------------------ */
	public class OutputWriter extends HttpWriter
	{
		OutputWriter()
		{
			super(AbstractHttpConnection.this._out);
		}
	}


}