org.browsermob.proxy.jetty.http.HttpConnection Maven / Gradle / Ivy
Show all versions of browsermob-proxy Show documentation
// ========================================================================
// $Id: HttpConnection.java,v 1.94 2006/02/28 12:45:01 gregwilkins Exp $
// Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================
package org.browsermob.proxy.jetty.http;
import org.apache.commons.logging.Log;
import org.browsermob.proxy.jetty.log.LogFactory;
import org.browsermob.proxy.jetty.util.*;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Enumeration;
import java.util.List;
/* ------------------------------------------------------------ */
/** A HTTP Connection.
* This class provides the generic HTTP handling for
* a connection to a HTTP server. An instance of HttpConnection
* is normally created by a HttpListener and then given control
* in order to run the protocol handling before and after passing
* a request to the HttpServer of the HttpListener.
*
* This class is not synchronized as it should only ever be known
* to a single thread.
*
* @see HttpListener
* @see HttpServer
* @version $Id: HttpConnection.java,v 1.94 2006/02/28 12:45:01 gregwilkins Exp $
* @author Greg Wilkins (gregw)
*/
public class HttpConnection
implements OutputObserver
{
private static Log log = LogFactory.getLog(HttpConnection.class);
/* ------------------------------------------------------------ */
private static ThreadLocal __threadConnection=new ThreadLocal();
/** Support for FRC2068 Continues.
* If true, then 100 Continues will be sent when expected or for POST requests. If false, 100 Continues will
* only be sent if expected. Can be configured with the org.mortbay.http.HttpConnection.2068Continue system
* property.
*/
private static boolean __2068_Continues=Boolean.getBoolean("org.browsermob.proxy.jetty.http.HttpConnection.2068Continue");
/* ------------------------------------------------------------ */
protected HttpRequest _request;
protected HttpResponse _response;
protected boolean _persistent;
protected boolean _keepAlive;
protected int _dotVersion;
private HttpListener _listener;
private HttpInputStream _inputStream;
private HttpOutputStream _outputStream;
private boolean _close;
private boolean _firstWrite;
private boolean _completing;
private Thread _handlingThread;
private InetAddress _remoteInetAddress;
private String _remoteAddr;
private String _remoteHost;
private HttpServer _httpServer;
private Object _connection;
private boolean _throttled ;
private boolean _statsOn;
private long _tmpTime;
private long _openTime;
private long _reqTime;
private int _requests;
private Object _object;
private HttpTunnel _tunnel ;
private boolean _resolveRemoteHost;
/* ------------------------------------------------------------ */
/** Constructor.
* @param listener The listener that created this connection.
* @param remoteAddr The address of the remote end or null.
* @param in InputStream to read request(s) from.
* @param out OutputputStream to write response(s) to.
* @param connection The underlying connection object, most likely
* a socket. This is not used by HttpConnection other than to make
* it available via getConnection().
*/
public HttpConnection(HttpListener listener,
InetAddress remoteAddr,
InputStream in,
OutputStream out,
Object connection)
{
if(log.isDebugEnabled())log.debug("new HttpConnection: "+connection);
_listener=listener;
_remoteInetAddress=remoteAddr;
int bufferSize=listener==null?4096:listener.getBufferSize();
int reserveSize=listener==null?512:listener.getBufferReserve();
_inputStream=new HttpInputStream(in,bufferSize);
_outputStream=new HttpOutputStream(out,bufferSize,reserveSize);
_outputStream.addObserver(this);
_firstWrite=false;
if (_listener!=null)
_httpServer=_listener.getHttpServer();
_connection=connection;
_statsOn=_httpServer!=null && _httpServer.getStatsOn();
if (_statsOn)
{
_openTime=System.currentTimeMillis();
_httpServer.statsOpenConnection();
}
_reqTime=0;
_requests=0;
_request = new HttpRequest(this);
_response = new HttpResponse(this);
_resolveRemoteHost=_listener!=null && _listener.getHttpServer()!=null && _listener.getHttpServer().getResolveRemoteHost();
}
/* ------------------------------------------------------------ */
/** Get the ThreadLocal HttpConnection.
* The ThreadLocal HttpConnection is set by the handle() method.
* @return HttpConnection for this thread.
*/
static HttpConnection getHttpConnection()
{
return (HttpConnection)__threadConnection.get();
}
/* ------------------------------------------------------------ */
/** Get the Remote address.
* @return the remote address
*/
public InetAddress getRemoteInetAddress()
{
return _remoteInetAddress;
}
/* ------------------------------------------------------------ */
/** Get the Remote address.
* @return the remote host name
*/
public String getRemoteAddr()
{
if (_remoteAddr==null)
{
if (_remoteInetAddress==null)
return "127.0.0.1";
_remoteAddr=_remoteInetAddress.getHostAddress();
}
return _remoteAddr;
}
/* ------------------------------------------------------------ */
/** Get the Remote address.
* @return the remote host name
*/
public String getRemoteHost()
{
if (_remoteHost==null)
{
if (_resolveRemoteHost)
{
if (_remoteInetAddress==null)
return "localhost";
_remoteHost=_remoteInetAddress.getHostName();
}
else
{
if (_remoteInetAddress==null)
return "127.0.0.1";
_remoteHost=getRemoteAddr();
}
}
return _remoteHost;
}
/* ------------------------------------------------------------ */
/** Get the connections InputStream.
* @return the connections InputStream
*/
public HttpInputStream getInputStream()
{
return _inputStream;
}
/* ------------------------------------------------------------ */
/** Get the connections OutputStream.
* @return the connections OutputStream
*/
public HttpOutputStream getOutputStream()
{
return _outputStream;
}
/* ------------------------------------------------------------ */
/** Get the underlying connection object.
* This opaque object, most likely a socket. This is not used by
* HttpConnection other than to make it available via getConnection().
* @return Connection abject
*/
public Object getConnection()
{
return _connection;
}
/* ------------------------------------------------------------ */
/** Get the request.
* @return the request
*/
public HttpRequest getRequest()
{
return _request;
}
/* ------------------------------------------------------------ */
/** Get the response.
* @return the response
*/
public HttpResponse getResponse()
{
return _response;
}
/* ------------------------------------------------------------ */
/** Force the connection to not be persistent.
*/
public void forceClose()
{
_persistent=false;
_close=true;
}
/* ------------------------------------------------------------ */
/** Close the connection.
* This method calls close on the input and output streams and
* interrupts any thread in the handle method.
* may be specialized to close sockets etc.
* @exception IOException
*/
public void close()
throws IOException
{
try{
_completing=true;
if (_connection instanceof Socket && !(_connection instanceof SSLSocket))
((Socket)_connection).shutdownOutput();
_outputStream.close();
_inputStream.close();
}
finally
{
if (_handlingThread!=null && Thread.currentThread()!=_handlingThread)
_handlingThread.interrupt();
}
}
/* ------------------------------------------------------------ */
/** Get the connections listener.
* @return HttpListener that created this Connection.
*/
public HttpListener getListener()
{
return _listener;
}
/* ------------------------------------------------------------ */
/** Get the listeners HttpServer .
* Conveniance method equivalent to getListener().getHttpServer().
* @return HttpServer.
*/
public HttpServer getHttpServer()
{
return _httpServer;
}
/* ------------------------------------------------------------ */
/** Get the listeners Default scheme.
* Conveniance method equivalent to getListener().getDefaultProtocol().
* @return HttpServer.
*/
public String getDefaultScheme()
{
return _listener.getDefaultScheme();
}
/* ------------------------------------------------------------ */
/** Get the listeners HttpServer.
* But if the name is 0.0.0.0, then the real interface address is used.
* @return HttpServer.
*/
public String getServerName()
{
String host=_listener.getHost();
if (InetAddrPort.__0_0_0_0.equals(host) &&
_connection instanceof Socket)
host = ((Socket)_connection).getLocalAddress().getHostName();
return host;
}
/* ------------------------------------------------------------ */
/** Get the listeners HttpServer.
* @return HttpServer.
*/
public String getServerAddr()
{
if (_connection instanceof Socket)
return ((Socket)_connection).getLocalAddress().getHostAddress();
return _listener.getHost();
}
/* ------------------------------------------------------------ */
/** Get the listeners Port .
* Conveniance method equivalent to getListener().getPort().
* @return local port.
*/
public int getServerPort()
{
return _listener.getPort();
}
/* ------------------------------------------------------------ */
/** Get the remote Port .
* @return remote port.
*/
public int getRemotePort()
{
if (_connection instanceof Socket)
return ((Socket)_connection).getPort();
return 0;
}
/* ------------------------------------------------------------ */
/**
* @return True if this connections state has been altered due
* to low resources.
*/
public boolean isThrottled()
{
return _throttled;
}
/* ------------------------------------------------------------ */
/**
* @param throttled True if this connections state has been altered due
* to low resources.
*/
public void setThrottled(boolean throttled)
{
_throttled = throttled;
}
/* ------------------------------------------------------------ */
/** Get associated object.
* Used by a particular HttpListener implementation to associate
* private datastructures with the connection.
* @return An object associated with the connecton by setObject.
*/
public Object getObject()
{
return _object;
}
/* ------------------------------------------------------------ */
/** Set associated object.
* Used by a particular HttpListener implementation to associate
* private datastructures with the connection.
* @param o An object associated with the connecton.
*/
public void setObject(Object o)
{
_object=o;
}
/* ------------------------------------------------------------ */
/**
* @return The HttpTunnel set for the connection or null.
*/
public HttpTunnel getHttpTunnel()
{
return _tunnel;
}
/* ------------------------------------------------------------ */
/** Set a HttpTunnel for the connection.
* A HTTP tunnel is used if the connection is to be taken over for
* non-HTTP communications. An example of this is the CONNECT method
* in proxy handling. If a HttpTunnel is set on a connection, then it's
* handle method is called bu the next call to handleNext().
* @param tunnel The HttpTunnel set for the connection or null.
*/
public void setHttpTunnel(HttpTunnel tunnel)
{
_tunnel = tunnel;
}
/* ------------------------------------------------------------ */
/* Verify HTTP/1.0 request
* @exception HttpException problem with the request.
* @exception IOException problem with the connection.
*/
private void verifyHTTP_1_0()
{
// Set content length
int content_length=
_request.getIntField(HttpFields.__ContentLength);
if (content_length>=0)
_inputStream.setContentLength(content_length);
else if (content_length<0)
{
// TODO - can't do this check because IE does this after
// a redirect.
// Can't have content without a content length
// String content_type=_request.getField(HttpFields.__ContentType);
// if (content_type!=null && content_type.length()>0)
// throw new HttpException(_HttpResponse.__411_Length_Required);
_inputStream.setContentLength(0);
}
// Check netscape proxy connection - this is not strictly correct.
if (!_keepAlive && HttpFields.__KeepAlive.equalsIgnoreCase(_request.getField(HttpFields.__ProxyConnection)))
_keepAlive=true;
// persistent connections in HTTP/1.0 only if requested.
_persistent=_keepAlive;
}
/* ------------------------------------------------------------ */
/* Verify HTTP/1.1 request
* @exception HttpException problem with the request.
* @exception IOException problem with the connection.
*/
private void verifyHTTP_1_1()
throws HttpException, IOException
{
// Check Host Field exists
String host=_request.getField(HttpFields.__Host);
if (host==null)
throw new HttpException(HttpResponse.__400_Bad_Request);
// check and enable requests transfer encodings.
String transfer_coding=
_request.getField(HttpFields.__TransferEncoding);
if (transfer_coding!=null && transfer_coding.length()>0)
{
// Handling of codings other than chunking is now
// the responsibility of handlers, filters or servlets.
// Thanks to the compression filter, we now don't know if
// what we can handle here.
if (transfer_coding.equalsIgnoreCase(HttpFields.__Chunked) ||
StringUtil.endsWithIgnoreCase(transfer_coding,HttpFields.__Chunked))
_inputStream.setChunking();
else if (StringUtil.asciiToLowerCase(transfer_coding)
.indexOf(HttpFields.__Chunked)>=0)
throw new HttpException(HttpResponse.__400_Bad_Request);
}
// Check input content length can be determined
int content_length=_request.getIntField(HttpFields.__ContentLength);
String content_type=_request.getField(HttpFields.__ContentType);
if (!_inputStream.isChunking())
{
// If we have a content length, use it
if (content_length>=0)
_inputStream.setContentLength(content_length);
// else if we have no content
else if (content_type==null || content_type.length()==0)
_inputStream.setContentLength(0);
// else we need a content length
else
{
// TODO - can't do this check as IE stuff up on
// a redirect.
// throw new HttpException(HttpResponse.__411_Length_Required);
_inputStream.setContentLength(0);
}
}
// Handle Continue Expectations
String expect=_request.getField(HttpFields.__Expect);
if (expect!=null && expect.length()>0)
{
if (StringUtil.asciiToLowerCase(expect).equals(HttpFields.__ExpectContinue))
{
_inputStream.setExpectContinues(_outputStream.getOutputStream());
}
else
throw new HttpException(HttpResponse.__417_Expectation_Failed);
}
else if (__2068_Continues &&
_inputStream.available()<=0 &&
(HttpRequest.__PUT.equals(_request.getMethod()) ||
HttpRequest.__POST.equals(_request.getMethod())))
{
// Send continue for RFC 2068 exception
OutputStream real_out=_outputStream.getOutputStream();
real_out.write(HttpResponse.__Continue);
real_out.flush();
}
// Persistent unless requested otherwise
_persistent=!_close;
}
/* ------------------------------------------------------------ */
/** Output Notifications.
* Trigger header and/or filters from output stream observations.
* Also finalizes method of indicating response content length.
* Called as a result of the connection subscribing for notifications
* to the HttpOutputStream.
* @see HttpOutputStream
* @param out The output stream observed.
* @param action The action.
*/
public void outputNotify(OutputStream out, int action, Object ignoredData)
throws IOException
{
if (_response==null)
return;
switch(action)
{
case OutputObserver.__FIRST_WRITE:
if (!_firstWrite)
{
firstWrite();
_firstWrite=true;
}
break;
case OutputObserver.__RESET_BUFFER:
resetBuffer();
break;
case OutputObserver.__COMMITING:
commit();
break;
case OutputObserver.__CLOSING:
if (_response!=null)
{
completing();
if (!_response.isCommitted() &&
_request.getState()==HttpMessage.__MSG_RECEIVED)
commit();
}
break;
case OutputObserver.__CLOSED:
break;
}
}
/* ------------------------------------------------------------ */
/** Setup the reponse output stream.
* Use the current state of the request and response, to set tranfer
* parameters such as chunking and content length.
*/
protected void firstWrite()
throws IOException
{
if (_response.isCommitted())
return;
// Nobble the OutputStream for HEAD requests
if (HttpRequest.__HEAD.equals(_request.getMethod()))
_outputStream.nullOutput();
int length=_response.getIntField(HttpFields.__ContentLength);
if (length>=0)
_outputStream.setContentLength(length);
}
/**
* Reset headers after response reset
*/
private void resetBuffer()
{
}
/* ------------------------------------------------------------ */
/* Signal that the next commit/flush is the last */
void completing()
{
_completing=true;
}
/* ------------------------------------------------------------ */
protected void commit()
throws IOException
{
if (_response.isCommitted())
return;
int status=_response.getStatus();
int length=-1;
// Check if there is missing content expectations
if (_inputStream.getExpectContinues()!=null)
{
// No input read yet - so assume it never will be
_inputStream.setExpectContinues(null);
_inputStream.unsafeSetContentLength(0);
}
// Handler forced close, listener stopped or no idle threads left.
boolean has_close=HttpFields.__Close.equals(_response.getField(HttpFields.__Connection));
if (!_persistent || _close || _listener!=null && (!_listener.isStarted()||_listener.isOutOfResources()))
{
_close=true;
if (!has_close)
_response.setField(HttpFields.__Connection,HttpFields.__Close);
has_close=true;
}
if (_close)
_persistent=false;
// Determine how to limit content length
if (_persistent)
{
switch(_dotVersion)
{
case 1:
{
String transfer_coding=_response.getField(HttpFields.__TransferEncoding);
if (transfer_coding==null || transfer_coding.length()==0 || HttpFields.__Identity.equalsIgnoreCase(transfer_coding))
{
// if (can have content and no content length)
if (status!=HttpResponse.__304_Not_Modified &&
status!=HttpResponse.__204_No_Content &&
_response.getField(HttpFields.__ContentLength)==null)
{
if (_completing)
{
length=_outputStream.getBytesWritten();
_response.setContentLength(length);
}
else
{
// Chunk it!
_response.setField(HttpFields.__TransferEncoding,HttpFields.__Chunked);
_outputStream.setChunking();
}
}
}
else
{
// Use transfer encodings to determine length
_response.removeField(HttpFields.__ContentLength);
_outputStream.setChunking();
if (!HttpFields.__Chunked.equalsIgnoreCase(transfer_coding))
{
// Check against any TE field
List te = _request.getAcceptableTransferCodings();
Enumeration enm =
_response.getFieldValues(HttpFields.__TransferEncoding,
HttpFields.__separators);
while (enm.hasMoreElements())
{
String coding=(String)enm.nextElement();
if (HttpFields.__Identity.equalsIgnoreCase(coding) ||
HttpFields.__Chunked.equalsIgnoreCase(coding))
continue;
if (te==null || !te.contains(coding))
throw new HttpException(HttpResponse.__501_Not_Implemented,coding);
}
}
}
}
break;
case 0:
{
// if (can have content and no content length)
_response.removeField(HttpFields.__TransferEncoding);
if (_keepAlive)
{
if (status!=HttpResponse.__304_Not_Modified &&
status!=HttpResponse.__204_No_Content &&
_response.getField(HttpFields.__ContentLength)==null)
{
if (_completing)
{
length=_outputStream.getBytesWritten();
_response.setContentLength(length);
_response.setField(HttpFields.__Connection,HttpFields.__KeepAlive);
}
else
{
_response.setField(HttpFields.__Connection,HttpFields.__Close);
has_close=_close=true;
_persistent=false;
}
}
else
_response.setField(HttpFields.__Connection,HttpFields.__KeepAlive);
}
else if (!has_close)
_response.setField(HttpFields.__Connection,HttpFields.__Close);
break;
}
default:
{
_close=true;
_persistent=false;
_keepAlive=false;
}
}
}
// Mark request as handled.
_request.setHandled(true);
_outputStream.writeHeader(_response);
_outputStream.flush();
}
/* ------------------------------------------------------------ */
/* Exception reporting policy method.
* @param e the Throwable to report.
*/
private void exception(Throwable e)
{
try
{
_persistent=false;
int error_code=HttpResponse.__500_Internal_Server_Error;
if (e instanceof HttpException)
{
error_code=((HttpException)e).getCode();
if (_request==null)
log.warn(e.toString());
else
log.warn(_request.getRequestLine()+" "+e.toString());
log.debug(LogSupport.EXCEPTION,e);
}
else if (e instanceof EOFException)
{
LogSupport.ignore(log,e);
return;
}
else
{
_request.setAttribute("javax.servlet.error.exception_type",e.getClass());
_request.setAttribute("javax.servlet.error.exception",e);
if (_request==null)
log.warn(LogSupport.EXCEPTION,e);
else
log.warn(_request.getRequestLine(),e);
}
if (_response != null && !_response.isCommitted())
{
_response.reset();
_response.removeField(HttpFields.__TransferEncoding);
_response.setField(HttpFields.__Connection,HttpFields.__Close);
_response.sendError(error_code);
}
}
catch(Exception ex)
{
LogSupport.ignore(log,ex);
}
}
/* ------------------------------------------------------------ */
/** Service a Request.
* This implementation passes the request and response to the
* service method of the HttpServer for this connections listener.
* If no HttpServer has been associated, the 503 is returned.
* This method may be specialized to implement other ways of
* servicing a request.
* @param request The request
* @param response The response
* @return The HttpContext that completed handling of the request or null.
* @exception HttpException
* @exception IOException
*/
protected HttpContext service(HttpRequest request, HttpResponse response)
throws HttpException, IOException
{
if (_httpServer==null)
throw new HttpException(HttpResponse.__503_Service_Unavailable);
return _httpServer.service(request,response);
}
/* ------------------------------------------------------------ */
/** Handle the connection.
* Once the connection has been created, this method is called
* to handle one or more requests that may be received on the
* connection. The method only returns once all requests have been
* handled, an error has been returned to the requestor or the
* connection has been closed.
* The handleNext() is called in a loop until it returns false.
*/
public final void handle()
{
try
{
associateThread();
while(_listener.isStarted() && handleNext())
recycle();
}
finally
{
disassociateThread();
destroy();
}
}
/* ------------------------------------------------------------ */
protected void associateThread()
{
__threadConnection.set(this);
_handlingThread=Thread.currentThread();
}
/* ------------------------------------------------------------ */
protected void disassociateThread()
{
_handlingThread=null;
__threadConnection.set(null);
}
/* ------------------------------------------------------------ */
protected void readRequest()
throws IOException
{
_request.readHeader((LineInput)(_inputStream)
.getInputStream());
}
/* ------------------------------------------------------------ */
/** Handle next request off the connection.
* The service(request,response) method is called by handle to
* service each request received on the connection.
* If the thread is a PoolThread, the thread is set as inactive
* when waiting for a request.
*
* If a HttpTunnel has been set on this connection, it's handle method is
* called and when that completes, false is return from this method.
*
* The Connection is set as a ThreadLocal of the calling thread and is
* available via the getHttpConnection() method.
* @return true if the connection is still open and may provide
* more requests.
*/
public boolean handleNext()
{
// Handle a HTTP tunnel
if (_tunnel!=null)
{
if(log.isDebugEnabled())log.debug("Tunnel: "+_tunnel);
_outputStream.resetObservers();
_tunnel.handle(_inputStream.getInputStream(),
_outputStream.getOutputStream());
return false;
}
// Normal handling.
HttpContext context=null;
boolean stats=false;
try
{
// Assume the connection is not persistent,
// unless told otherwise.
_persistent=false;
_close=false;
_keepAlive=false;
_firstWrite=false;
_completing=false;
_dotVersion=0;
// Read requests
readRequest();
if (_listener==null || !_listener.isStarted())
{
// dead connection
_response.destroy();
_response=null;
_persistent=false;
return false;
}
_listener.customizeRequest(this,_request);
if (_request.getState()!=HttpMessage.__MSG_RECEIVED)
throw new HttpException(HttpResponse.__400_Bad_Request);
// We have a valid request!
statsRequestStart();
stats=true;
// Pick response version, we assume that _request.getVersion() == 1
_dotVersion=_request.getDotVersion();
if (_dotVersion>1)
{
_dotVersion=1;
}
// Common fields on the response
_response.setVersion(HttpMessage.__HTTP_1_1);
_response.setField(HttpFields.__Date,_request.getTimeStampStr());
if (!Version.isParanoid())
_response.setField(HttpFields.__Server,Version.getDetail());
// Handle Connection header field
Enumeration connectionValues =
_request.getFieldValues(HttpFields.__Connection,
HttpFields.__separators);
if (connectionValues!=null)
{
while (connectionValues.hasMoreElements())
{
String token=connectionValues.nextElement().toString();
// handle close token
if (token.equalsIgnoreCase(HttpFields.__Close))
{
_close=true;
_response.setField(HttpFields.__Connection,
HttpFields.__Close);
}
else if (token.equalsIgnoreCase(HttpFields.__KeepAlive) &&
_dotVersion==0)
_keepAlive=true;
// Remove headers for HTTP/1.0 requests
if (_dotVersion==0)
_request.forceRemoveField(token);
}
}
// Handle version specifics
if (_dotVersion==1)
verifyHTTP_1_1();
else if (_dotVersion==0)
verifyHTTP_1_0();
else if (_dotVersion!=-1)
throw new HttpException(HttpResponse.__505_HTTP_Version_Not_Supported);
if(log.isDebugEnabled())log.debug("REQUEST from "+_listener+":\n"+_request);
// handle HttpListener handlers
if (!_request.isHandled() && _listener.getHttpHandler()!=null)
_listener.getHttpHandler().handle("",null, _request, _response);
// service the request
if (!_request.isHandled())
context=service(_request,_response);
}
catch(HttpException e) {exception(e);}
catch (IOException e)
{
if (_request.getState()!=HttpMessage.__MSG_RECEIVED)
{
if (log.isDebugEnabled())
{
if (log.isTraceEnabled()) log.trace(LogSupport.EXCEPTION,e);
else if(log.isDebugEnabled())log.debug(e.toString());
}
_response.destroy();
_response=null;
}
else
exception(e);
}
catch (Exception e) {exception(e);}
catch (Error e) {exception(e);}
finally
{
int bytes_written=0;
int content_length = _response==null
?-1:_response.getIntField(HttpFields.__ContentLength);
// Complete the request
if (_persistent)
{
boolean no_continue_sent=false;
try{
if (_inputStream.getExpectContinues()!=null)
{
_inputStream.setExpectContinues(null);
no_continue_sent=true;
}
else
{
int remaining = _inputStream.getContentLength();
if (remaining!=0)
// Read remaining input
while(_inputStream.skip(4096)>0 || _inputStream.read()>=0);
}
}
catch(IOException e)
{
if (_inputStream.getContentLength()>0)
_inputStream.setContentLength(0);
_persistent=false;
LogSupport.ignore(log,e);
exception(new HttpException(HttpResponse.__400_Bad_Request,
"Missing Content"));
}
// Check for no more content
if (!no_continue_sent && _inputStream.getContentLength()>0)
{
_inputStream.setContentLength(0);
_persistent=false;
exception (new HttpException(HttpResponse.__400_Bad_Request,"Missing Content"));
}
// Commit the response
try{
_outputStream.close();
bytes_written=_outputStream.getBytesWritten();
_outputStream.resetStream();
_outputStream.addObserver(this);
_inputStream.resetStream();
}
catch(IOException e) {exception(e);}
}
else if (_response!=null) // There was a request
{
// half hearted attempt to eat any remaining input
try{
if (_inputStream.getContentLength()>0)
while(_inputStream.skip(4096)>0 || _inputStream.read()>=0);
_inputStream.resetStream();
}
catch(IOException e){LogSupport.ignore(log,e);}
// commit non persistent
try{
_outputStream.flush();
_response.commit();
bytes_written=_outputStream.getBytesWritten();
_outputStream.close();
_outputStream.resetStream();
}
catch(IOException e) {exception(e);}
}
// Check response length
if (_response!=null)
{
if(log.isDebugEnabled())log.debug("RESPONSE:\n"+_response);
if (_persistent && content_length>=0 && bytes_written>0 && content_length!=bytes_written)
{
log.warn("Invalid length: Content-Length="+content_length+
" written="+bytes_written+
" for "+_request.getRequestURL());
_persistent=false;
try{_outputStream.close();}
catch(IOException e) {log.warn(LogSupport.EXCEPTION,e);}
}
}
// stats & logging
if (stats)
statsRequestEnd();
if (context!=null)
context.log(_request,_response,bytes_written);
}
return (_tunnel!=null) || _persistent;
}
/* ------------------------------------------------------------ */
protected void statsRequestStart()
{
if (_statsOn)
{
if (_reqTime>0)
statsRequestEnd();
_requests++;
_tmpTime=_request.getTimeStamp();
_reqTime=_tmpTime;
_httpServer.statsGotRequest();
}
}
/* ------------------------------------------------------------ */
protected void statsRequestEnd()
{
if (_statsOn && _reqTime>0)
{
_httpServer.statsEndRequest(System.currentTimeMillis()-_reqTime,
(_response!=null));
_reqTime=0;
}
}
/* ------------------------------------------------------------ */
/** Recycle the connection.
* called by handle when handleNext returns true.
*/
protected void recycle()
{
_listener.persistConnection(this);
if (_request!=null)
_request.recycle(this);
if (_response!=null)
_response.recycle(this);
}
/* ------------------------------------------------------------ */
/** Destroy the connection.
* called by handle when handleNext returns false.
*/
protected void destroy()
{
try{close();}
catch (IOException e){LogSupport.ignore(log,e);}
catch (Exception e){log.warn(LogSupport.EXCEPTION,e);}
// Destroy request and response
if (_request!=null)
_request.destroy();
if (_response!=null)
_response.destroy();
if (_inputStream!=null)
_inputStream.destroy();
if (_outputStream!=null)
_outputStream.destroy();
_inputStream=null;
_outputStream=null;
_request=null;
_response=null;
_handlingThread=null;
if (_statsOn)
{
_tmpTime=System.currentTimeMillis();
if (_reqTime>0)
_httpServer.statsEndRequest(_tmpTime-_reqTime,false);
_httpServer.statsCloseConnection(_tmpTime-_openTime,_requests);
}
}
}