blob: 0e0042cddfea408895f3fc37c71ce17a105b2131 [file] [log] [blame]
//
// ========================================================================
// 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.client;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jetty.client.security.Authentication;
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.HttpSchemes;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersions;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.Buffer;
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.View;
import org.eclipse.jetty.util.component.AggregateLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.Timeout;
/**
*
* @version $Revision: 879 $ $Date: 2009-09-11 16:13:28 +0200 (Fri, 11 Sep 2009) $
*/
public abstract class AbstractHttpConnection extends AbstractConnection implements Dumpable
{
private static final Logger LOG = Log.getLogger(AbstractHttpConnection.class);
protected HttpDestination _destination;
protected HttpGenerator _generator;
protected HttpParser _parser;
protected boolean _http11 = true;
protected int _status;
protected Buffer _connectionHeader;
protected boolean _reserved;
// The current exchange waiting for a response
protected volatile HttpExchange _exchange;
protected HttpExchange _pipeline;
private final Timeout.Task _idleTimeout = new ConnectionIdleTask();
private AtomicBoolean _idle = new AtomicBoolean(false);
AbstractHttpConnection(Buffers requestBuffers, Buffers responseBuffers, EndPoint endp)
{
super(endp);
_generator = new HttpGenerator(requestBuffers,endp);
_parser = new HttpParser(responseBuffers,endp,new Handler());
}
public void setReserved (boolean reserved)
{
_reserved = reserved;
}
public boolean isReserved()
{
return _reserved;
}
public HttpDestination getDestination()
{
return _destination;
}
public void setDestination(HttpDestination destination)
{
_destination = destination;
}
public boolean send(HttpExchange ex) throws IOException
{
LOG.debug("Send {} on {}",ex,this);
synchronized (this)
{
if (_exchange != null)
{
if (_pipeline != null)
throw new IllegalStateException(this + " PIPELINED!!! _exchange=" + _exchange);
_pipeline = ex;
return true;
}
_exchange = ex;
_exchange.associate(this);
// The call to associate() may have closed the connection, check if it's the case
if (!_endp.isOpen())
{
_exchange.disassociate();
_exchange = null;
return false;
}
_exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_COMMIT);
adjustIdleTimeout();
return true;
}
}
private void adjustIdleTimeout() throws IOException
{
// Adjusts the idle timeout in case the default or exchange timeout
// are greater. This is needed for long polls, where one wants an
// aggressive releasing of idle connections (so idle timeout is small)
// but still allow long polls to complete normally
long timeout = _exchange.getTimeout();
if (timeout <= 0)
timeout = _destination.getHttpClient().getTimeout();
long endPointTimeout = _endp.getMaxIdleTime();
if (timeout > 0 && timeout > endPointTimeout)
{
// Make it larger than the exchange timeout so that there are
// no races between the idle timeout and the exchange timeout
// when trying to close the endpoint
_endp.setMaxIdleTime(2 * (int)timeout);
}
}
public abstract Connection handle() throws IOException;
public boolean isIdle()
{
synchronized (this)
{
return _exchange == null;
}
}
public boolean isSuspended()
{
return false;
}
public void onClose()
{
}
/**
* @throws IOException
*/
protected void commitRequest() throws IOException
{
synchronized (this)
{
_status=0;
if (_exchange.getStatus() != HttpExchange.STATUS_WAITING_FOR_COMMIT)
throw new IllegalStateException();
_exchange.setStatus(HttpExchange.STATUS_SENDING_REQUEST);
_generator.setVersion(_exchange.getVersion());
String method=_exchange.getMethod();
String uri = _exchange.getRequestURI();
if (_destination.isProxied())
{
if (!HttpMethods.CONNECT.equals(method) && uri.startsWith("/"))
{
boolean secure = _destination.isSecure();
String host = _destination.getAddress().getHost();
int port = _destination.getAddress().getPort();
StringBuilder absoluteURI = new StringBuilder();
absoluteURI.append(secure ? HttpSchemes.HTTPS : HttpSchemes.HTTP);
absoluteURI.append("://");
absoluteURI.append(host);
// Avoid adding default ports
if (!(secure && port == 443 || !secure && port == 80))
absoluteURI.append(":").append(port);
absoluteURI.append(uri);
uri = absoluteURI.toString();
}
Authentication auth = _destination.getProxyAuthentication();
if (auth != null)
auth.setCredentials(_exchange);
}
_generator.setRequest(method, uri);
_parser.setHeadResponse(HttpMethods.HEAD.equalsIgnoreCase(method));
HttpFields requestHeaders = _exchange.getRequestFields();
if (_exchange.getVersion() >= HttpVersions.HTTP_1_1_ORDINAL)
{
if (!requestHeaders.containsKey(HttpHeaders.HOST_BUFFER))
requestHeaders.add(HttpHeaders.HOST_BUFFER,_destination.getHostHeader());
}
Buffer requestContent = _exchange.getRequestContent();
if (requestContent != null)
{
requestHeaders.putLongField(HttpHeaders.CONTENT_LENGTH, requestContent.length());
_generator.completeHeader(requestHeaders,false);
_generator.addContent(new View(requestContent),true);
_exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
}
else
{
InputStream requestContentStream = _exchange.getRequestContentSource();
if (requestContentStream != null)
{
_generator.completeHeader(requestHeaders, false);
}
else
{
requestHeaders.remove(HttpHeaders.CONTENT_LENGTH);
_generator.completeHeader(requestHeaders, true);
_exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
}
}
}
}
protected void reset() throws IOException
{
_connectionHeader = null;
_parser.reset();
_generator.reset();
_http11 = true;
}
private class Handler extends HttpParser.EventHandler
{
@Override
public void startRequest(Buffer method, Buffer url, Buffer version) throws IOException
{
// System.out.println( method.toString() + "///" + url.toString() +
// "///" + version.toString() );
// TODO validate this is acceptable, the <!DOCTYPE goop was coming
// out here
// throw new IllegalStateException();
}
@Override
public void startResponse(Buffer version, int status, Buffer reason) throws IOException
{
HttpExchange exchange = _exchange;
if (exchange==null)
{
LOG.warn("No exchange for response");
_endp.close();
return;
}
switch(status)
{
case HttpStatus.CONTINUE_100:
case HttpStatus.PROCESSING_102:
// TODO check if appropriate expect was sent in the request.
exchange.setEventListener(new NonFinalResponseListener(exchange));
break;
case HttpStatus.OK_200:
// handle special case for CONNECT 200 responses
if (HttpMethods.CONNECT.equalsIgnoreCase(exchange.getMethod()))
_parser.setHeadResponse(true);
break;
}
_http11 = HttpVersions.HTTP_1_1_BUFFER.equals(version);
_status=status;
exchange.getEventListener().onResponseStatus(version,status,reason);
exchange.setStatus(HttpExchange.STATUS_PARSING_HEADERS);
}
@Override
public void parsedHeader(Buffer name, Buffer value) throws IOException
{
HttpExchange exchange = _exchange;
if (exchange!=null)
{
if (HttpHeaders.CACHE.getOrdinal(name) == HttpHeaders.CONNECTION_ORDINAL)
{
_connectionHeader = HttpHeaderValues.CACHE.lookup(value);
}
exchange.getEventListener().onResponseHeader(name,value);
}
}
@Override
public void headerComplete() throws IOException
{
HttpExchange exchange = _exchange;
if (exchange!=null)
{
exchange.setStatus(HttpExchange.STATUS_PARSING_CONTENT);
if (HttpMethods.CONNECT.equalsIgnoreCase(exchange.getMethod()))
_parser.setPersistent(true);
}
}
@Override
public void content(Buffer ref) throws IOException
{
HttpExchange exchange = _exchange;
if (exchange!=null)
exchange.getEventListener().onResponseContent(ref);
}
@Override
public void messageComplete(long contextLength) throws IOException
{
HttpExchange exchange = _exchange;
if (exchange!=null)
exchange.setStatus(HttpExchange.STATUS_COMPLETED);
}
@Override
public void earlyEOF()
{
HttpExchange exchange = _exchange;
if (exchange!=null)
{
if (!exchange.isDone())
{
if (exchange.setStatus(HttpExchange.STATUS_EXCEPTED))
exchange.getEventListener().onException(new EofException("early EOF"));
}
}
}
}
@Override
public String toString()
{
return String.format("%s %s g=%s p=%s",
super.toString(),
_destination == null ? "?.?.?.?:??" : _destination.getAddress(),
_generator,
_parser);
}
public String toDetailString()
{
return toString() + " ex=" + _exchange + " idle for " + _idleTimeout.getAge();
}
public void close() throws IOException
{
//if there is a live, unfinished exchange, set its status to be
//excepted and wake up anyone waiting on waitForDone()
HttpExchange exchange = _exchange;
if (exchange != null && !exchange.isDone())
{
switch (exchange.getStatus())
{
case HttpExchange.STATUS_CANCELLED:
case HttpExchange.STATUS_CANCELLING:
case HttpExchange.STATUS_COMPLETED:
case HttpExchange.STATUS_EXCEPTED:
case HttpExchange.STATUS_EXPIRED:
break;
case HttpExchange.STATUS_PARSING_CONTENT:
if (_endp.isInputShutdown() && _parser.isState(HttpParser.STATE_EOF_CONTENT))
break;
default:
String exch= exchange.toString();
String reason = _endp.isOpen()?(_endp.isInputShutdown()?"half closed: ":"local close: "):"closed: ";
if (exchange.setStatus(HttpExchange.STATUS_EXCEPTED))
exchange.getEventListener().onException(new EofException(reason+exch));
}
}
if (_endp.isOpen())
{
_endp.close();
_destination.returnConnection(this, true);
}
}
public void setIdleTimeout()
{
synchronized (this)
{
if (_idle.compareAndSet(false, true))
_destination.getHttpClient().scheduleIdle(_idleTimeout);
else
throw new IllegalStateException();
}
}
public boolean cancelIdleTimeout()
{
synchronized (this)
{
if (_idle.compareAndSet(true, false))
{
_destination.getHttpClient().cancel(_idleTimeout);
return true;
}
}
return false;
}
protected void exchangeExpired(HttpExchange exchange)
{
synchronized (this)
{
// We are expiring an exchange, but the exchange is pending
// Cannot reuse the connection because the reply may arrive, so close it
if (_exchange == exchange)
{
try
{
_destination.returnConnection(this, true);
}
catch (IOException x)
{
LOG.ignore(x);
}
}
}
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.util.component.Dumpable#dump()
*/
public String dump()
{
return AggregateLifeCycle.dump(this);
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.util.component.Dumpable#dump(java.lang.Appendable, java.lang.String)
*/
public void dump(Appendable out, String indent) throws IOException
{
synchronized (this)
{
out.append(String.valueOf(this)).append("\n");
AggregateLifeCycle.dump(out,indent,Collections.singletonList(_endp));
}
}
/* ------------------------------------------------------------ */
private class ConnectionIdleTask extends Timeout.Task
{
/* ------------------------------------------------------------ */
@Override
public void expired()
{
// Connection idle, close it
if (_idle.compareAndSet(true, false))
{
_destination.returnIdleConnection(AbstractHttpConnection.this);
}
}
}
/* ------------------------------------------------------------ */
private class NonFinalResponseListener implements HttpEventListener
{
final HttpExchange _exchange;
final HttpEventListener _next;
/* ------------------------------------------------------------ */
public NonFinalResponseListener(HttpExchange exchange)
{
_exchange=exchange;
_next=exchange.getEventListener();
}
/* ------------------------------------------------------------ */
public void onRequestCommitted() throws IOException
{
}
/* ------------------------------------------------------------ */
public void onRequestComplete() throws IOException
{
}
/* ------------------------------------------------------------ */
public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
{
}
/* ------------------------------------------------------------ */
public void onResponseHeader(Buffer name, Buffer value) throws IOException
{
_next.onResponseHeader(name,value);
}
/* ------------------------------------------------------------ */
public void onResponseHeaderComplete() throws IOException
{
_next.onResponseHeaderComplete();
}
/* ------------------------------------------------------------ */
public void onResponseContent(Buffer content) throws IOException
{
}
/* ------------------------------------------------------------ */
public void onResponseComplete() throws IOException
{
_exchange.setEventListener(_next);
_exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
_parser.reset();
}
/* ------------------------------------------------------------ */
public void onConnectionFailed(Throwable ex)
{
_exchange.setEventListener(_next);
_next.onConnectionFailed(ex);
}
/* ------------------------------------------------------------ */
public void onException(Throwable ex)
{
_exchange.setEventListener(_next);
_next.onException(ex);
}
/* ------------------------------------------------------------ */
public void onExpire()
{
_exchange.setEventListener(_next);
_next.onExpire();
}
/* ------------------------------------------------------------ */
public void onRetry()
{
_exchange.setEventListener(_next);
_next.onRetry();
}
}
}