org.mortbay.jetty.client.HttpExchange Maven / Gradle / Ivy
// ========================================================================
// Copyright 2006-2007 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.mortbay.jetty.client;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import org.mortbay.io.Buffer;
import org.mortbay.io.BufferCache.CachedBuffer;
import org.mortbay.io.nio.ChannelEndPoint;
import org.mortbay.io.ByteArrayBuffer;
import org.mortbay.jetty.HttpFields;
import org.mortbay.jetty.HttpHeaders;
import org.mortbay.jetty.HttpMethods;
import org.mortbay.jetty.HttpSchemes;
import org.mortbay.jetty.HttpURI;
import org.mortbay.jetty.HttpVersions;
import org.mortbay.log.Log;
/**
* An HTTP client API that encapsulates Exchange with a HTTP server.
*
* This object encapsulates:
* - The HTTP server. (see {@link #setAddress(InetSocketAddress)} or {@link #setURL(String)})
*
- The HTTP request method, URI and HTTP version (see {@link #setMethod(String)}, {@link #setURI(String)}, and {@link #setVersion(int)}
*
- The Request headers (see {@link #addRequestHeader(String, String)} or {@link #setRequestHeader(String, String)})
*
- The Request content (see {@link #setRequestContent(Buffer)} or {@link #setRequestContentSource(InputStream)})
*
- The status of the exchange (see {@link #getStatus()})
*
- Callbacks to handle state changes (see the onXxx methods such as {@link #onRequestComplete()} or {@link #onResponseComplete()})
*
- The ability to intercept callbacks (see {@link #setEventListener(HttpEventListener)}
*
*
* The HttpExchange class is intended to be used by a developer wishing to have close asynchronous
* interaction with the the exchange. Typically a developer will extend the HttpExchange class with a derived
* class that implements some or all of the onXxx callbacks. There are also some predefined HttpExchange subtypes
* that can be used as a basis (see {@link ContentExchange} and {@link CachedExchange}.
*
* Typically the HttpExchange is passed to a the {@link HttpClient#send(HttpExchange)} method, which in
* turn selects a {@link HttpDestination} and calls it's {@link HttpDestination#send(HttpExchange), which
* then creates or selects a {@link HttpConnection} and calls its {@link HttpConnection#send(HttpExchange).
* A developer may wish to directly call send on the destination or connection if they wish to bypass
* some handling provided (eg Cookie handling in the HttpDestination).
*
*
In some circumstances, the HttpClient or HttpDestination may wish to retry a HttpExchange (eg. failed
* pipeline request, authentication retry or redirection). In such cases, the HttpClient and/or HttpDestination
* may insert their own HttpExchangeListener to intercept and filter the call backs intended for the
* HttpExchange.
*
* @author gregw
* @author Guillaume Nodet
*/
public class HttpExchange
{
public static final int STATUS_START = 0;
public static final int STATUS_WAITING_FOR_CONNECTION = 1;
public static final int STATUS_WAITING_FOR_COMMIT = 2;
public static final int STATUS_SENDING_REQUEST = 3;
public static final int STATUS_WAITING_FOR_RESPONSE = 4;
public static final int STATUS_PARSING_HEADERS = 5;
public static final int STATUS_PARSING_CONTENT = 6;
public static final int STATUS_COMPLETED = 7;
public static final int STATUS_EXPIRED = 8;
public static final int STATUS_EXCEPTED = 9;
String _method = HttpMethods.GET;
Buffer _scheme = HttpSchemes.HTTP_BUFFER;
String _uri;
int _version = HttpVersions.HTTP_1_1_ORDINAL;
Address _address;
HttpFields _requestFields = new HttpFields();
Buffer _requestContent;
InputStream _requestContentSource;
volatile int _status = STATUS_START;
Buffer _requestContentChunk;
boolean _retryStatus = false;
// controls if the exchange will have listeners autoconfigured by the destination
boolean _configureListeners = true;
private HttpEventListener _listener = new Listener();
boolean _onRequestCompleteDone;
boolean _onResponseCompleteDone;
boolean _onDone; // == onConnectionFail || onException || onExpired || onCancelled || onResponseCompleted && onRequestCompleted
/* ------------------------------------------------------------ */
public int getStatus()
{
return _status;
}
/* ------------------------------------------------------------ */
/**
* @deprecated
*/
public void waitForStatus(int status) throws InterruptedException
{
synchronized (this)
{
while (_status < status)
{
this.wait();
}
}
}
/**
* Wait until the exchange is "done".
* Done is defined as when a final state has been passed to the
* HttpExchange via the associated onXxx call. Note that an
* exchange can transit a final state when being used as part
* of a dialog (eg {@link SecurityListener}. Done status
* is thus defined as:
* done == onConnectionFailed
* || onException
* || onExpire
* || onRequestComplete && onResponseComplete
*
* @return
* @throws InterruptedException
*/
public int waitForDone () throws InterruptedException
{
synchronized (this)
{
while (!isDone(_status))
this.wait();
return _status;
}
}
/* ------------------------------------------------------------ */
public void reset()
{
// TODO - this should do a cancel and wakeup everybody that was waiting.
// might need a version number concept
synchronized(this)
{
_onRequestCompleteDone=false;
_onResponseCompleteDone=false;
_onDone=false;
setStatus(STATUS_START);
}
}
/* ------------------------------------------------------------ */
void setStatus(int status)
{
// _status is volatile
_status = status;
try
{
switch (status)
{
case STATUS_WAITING_FOR_CONNECTION:
break;
case STATUS_WAITING_FOR_COMMIT:
break;
case STATUS_SENDING_REQUEST:
break;
case HttpExchange.STATUS_WAITING_FOR_RESPONSE:
getEventListener().onRequestCommitted();
break;
case STATUS_PARSING_HEADERS:
break;
case STATUS_PARSING_CONTENT:
getEventListener().onResponseHeaderComplete();
break;
case STATUS_COMPLETED:
getEventListener().onResponseComplete();
break;
case STATUS_EXPIRED:
getEventListener().onExpire();
break;
}
}
catch (IOException e)
{
Log.warn(e);
}
}
/* ------------------------------------------------------------ */
public boolean isDone (int status)
{
synchronized (this)
{
return _onDone;
}
}
/* ------------------------------------------------------------ */
public HttpEventListener getEventListener()
{
return _listener;
}
/* ------------------------------------------------------------ */
public void setEventListener(HttpEventListener listener)
{
_listener=listener;
}
/* ------------------------------------------------------------ */
/**
* @param url Including protocol, host and port
*/
public void setURL(String url)
{
HttpURI uri = new HttpURI(url);
String scheme = uri.getScheme();
if (scheme != null)
{
if (HttpSchemes.HTTP.equalsIgnoreCase(scheme))
setScheme(HttpSchemes.HTTP_BUFFER);
else if (HttpSchemes.HTTPS.equalsIgnoreCase(scheme))
setScheme(HttpSchemes.HTTPS_BUFFER);
else
setScheme(new ByteArrayBuffer(scheme));
}
int port = uri.getPort();
if (port <= 0)
port = "https".equalsIgnoreCase(scheme)?443:80;
setAddress(new Address(uri.getHost(),port));
String completePath = uri.getCompletePath();
if (completePath == null)
completePath = "/";
setURI(completePath);
}
/* ------------------------------------------------------------ */
/**
* @param address
*/
public void setAddress(Address address)
{
_address = address;
}
/* ------------------------------------------------------------ */
/**
* @return
*/
public Address getAddress()
{
return _address;
}
/* ------------------------------------------------------------ */
/**
* @param scheme
*/
public void setScheme(Buffer scheme)
{
_scheme = scheme;
}
/* ------------------------------------------------------------ */
/**
* @return
*/
public Buffer getScheme()
{
return _scheme;
}
/* ------------------------------------------------------------ */
/**
* @param version as integer, 9, 10 or 11 for 0.9, 1.0 or 1.1
*/
public void setVersion(int version)
{
_version = version;
}
/* ------------------------------------------------------------ */
public void setVersion(String version)
{
CachedBuffer v = HttpVersions.CACHE.get(version);
if (v == null)
_version = 10;
else
_version = v.getOrdinal();
}
/* ------------------------------------------------------------ */
/**
* @return
*/
public int getVersion()
{
return _version;
}
/* ------------------------------------------------------------ */
/**
* @param method
*/
public void setMethod(String method)
{
_method = method;
}
/* ------------------------------------------------------------ */
/**
* @return
*/
public String getMethod()
{
return _method;
}
/* ------------------------------------------------------------ */
/**
* @return
*/
public String getURI()
{
return _uri;
}
/* ------------------------------------------------------------ */
/**
* @param uri
*/
public void setURI(String uri)
{
_uri = uri;
}
/* ------------------------------------------------------------ */
/**
* @param name
* @param value
*/
public void addRequestHeader(String name, String value)
{
getRequestFields().add(name,value);
}
/* ------------------------------------------------------------ */
/**
* @param name
* @param value
*/
public void addRequestHeader(Buffer name, Buffer value)
{
getRequestFields().add(name,value);
}
/* ------------------------------------------------------------ */
/**
* @param name
* @param value
*/
public void setRequestHeader(String name, String value)
{
getRequestFields().put(name,value);
}
/* ------------------------------------------------------------ */
/**
* @param name
* @param value
*/
public void setRequestHeader(Buffer name, Buffer value)
{
getRequestFields().put(name,value);
}
/* ------------------------------------------------------------ */
/**
* @param value
*/
public void setRequestContentType(String value)
{
getRequestFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,value);
}
/* ------------------------------------------------------------ */
/**
* @return
*/
public HttpFields getRequestFields()
{
return _requestFields;
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
// methods to commit and/or send the request
/* ------------------------------------------------------------ */
/**
* @param requestContent
*/
public void setRequestContent(Buffer requestContent)
{
_requestContent = requestContent;
}
/* ------------------------------------------------------------ */
/**
* @param in
*/
public void setRequestContentSource(InputStream in)
{
_requestContentSource = in;
if (_requestContentSource.markSupported())
_requestContentSource.mark(Integer.MAX_VALUE);
}
/* ------------------------------------------------------------ */
public InputStream getRequestContentSource()
{
return _requestContentSource;
}
/* ------------------------------------------------------------ */
public Buffer getRequestContentChunk() throws IOException
{
synchronized (this)
{
if (_requestContentChunk == null)
_requestContentChunk = new ByteArrayBuffer(4096); // TODO configure
else
{
if (_requestContentChunk.hasContent())
throw new IllegalStateException();
_requestContentChunk.clear();
}
int read = _requestContentChunk.capacity();
int length = _requestContentSource.read(_requestContentChunk.array(),0,read);
if (length >= 0)
{
_requestContentChunk.setPutIndex(length);
return _requestContentChunk;
}
return null;
}
}
/* ------------------------------------------------------------ */
public Buffer getRequestContent()
{
return _requestContent;
}
/* ------------------------------------------------------------ */
public boolean getRetryStatus()
{
return _retryStatus;
}
/* ------------------------------------------------------------ */
public void setRetryStatus( boolean retryStatus )
{
_retryStatus = retryStatus;
}
/* ------------------------------------------------------------ */
/** Cancel this exchange
* Currently this implementation does nothing.
*/
public void cancel()
{
}
/* ------------------------------------------------------------ */
public String toString()
{
return getClass().getName() + "@" + hashCode() + "=" + _method + "//" + _address + _uri + "#" + _status;
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
// methods to handle response
/**
* Called when the request headers has been sent
* @throws IOException
*/
protected void onRequestCommitted() throws IOException
{
}
/**
* Called when the request and it's body have been sent.
* @throws IOException
*/
protected void onRequestComplete() throws IOException
{
}
/**
* Called when a response status line has been received.
* @param version HTTP version
* @param status HTTP status code
* @param reason HTTP status code reason string
* @throws IOException
*/
protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
{
}
/**
* Called for each response header received
* @param name header name
* @param value header value
* @throws IOException
*/
protected void onResponseHeader(Buffer name, Buffer value) throws IOException
{
}
/**
* Called when the response header has been completely received.
* @throws IOException
*/
protected void onResponseHeaderComplete() throws IOException
{
}
/**
* Called for each chunk of the response content received.
* @param content
* @throws IOException
*/
protected void onResponseContent(Buffer content) throws IOException
{
}
/**
* Called when the entire response has been received
* @throws IOException
*/
protected void onResponseComplete() throws IOException
{
}
/**
* Called when an exception was thrown during an attempt to open a connection
* @param ex
*/
protected void onConnectionFailed(Throwable ex)
{
Log.warn("CONNECTION FAILED on " + this,ex);
}
/**
* Called when any other exception occurs during handling for the exchange
* @param ex
*/
protected void onException(Throwable ex)
{
Log.warn("EXCEPTION on " + this,ex);
}
/**
* Called when no response has been received within the timeout.
*/
protected void onExpire()
{
Log.warn("EXPIRED " + this);
}
/**
* Called when the request is retried (due to failures or authentication).
* Implementations may need to reset any consumable content that needs to
* be sent.
* @throws IOException
*/
protected void onRetry() throws IOException
{
if (_requestContentSource != null)
{
if (_requestContentSource.markSupported())
{
_requestContent = null;
_requestContentSource.reset();
}
else
{
throw new IOException("Unsupported retry attempt");
}
}
}
/**
* true of the exchange should have listeners configured for it by the destination
*
* false if this is being managed elsewhere
*
* @return
*/
public boolean configureListeners()
{
return _configureListeners;
}
public void setConfigureListeners(boolean autoConfigure )
{
this._configureListeners = autoConfigure;
}
private class Listener implements HttpEventListener
{
public void onConnectionFailed(Throwable ex)
{
try
{
HttpExchange.this.onConnectionFailed(ex);
}
finally
{
synchronized(HttpExchange.this)
{
_onDone=true;
HttpExchange.this.notifyAll();
}
}
}
public void onException(Throwable ex)
{
try
{
HttpExchange.this.onException(ex);
}
finally
{
synchronized(HttpExchange.this)
{
_onDone=true;
HttpExchange.this.notifyAll();
}
}
}
public void onExpire()
{
try
{
HttpExchange.this.onExpire();
}
finally
{
synchronized(HttpExchange.this)
{
_onDone=true;
HttpExchange.this.notifyAll();
}
}
}
public void onRequestCommitted() throws IOException
{
HttpExchange.this.onRequestCommitted();
}
public void onRequestComplete() throws IOException
{
try
{
HttpExchange.this.onRequestComplete();
}
finally
{
synchronized(HttpExchange.this)
{
_onRequestCompleteDone=true;
_onDone=_onResponseCompleteDone;
HttpExchange.this.notifyAll();
}
}
}
public void onResponseComplete() throws IOException
{
try
{
HttpExchange.this.onResponseComplete();
}
finally
{
synchronized(HttpExchange.this)
{
_onResponseCompleteDone=true;
_onDone=_onRequestCompleteDone;
HttpExchange.this.notifyAll();
}
}
}
public void onResponseContent(Buffer content) throws IOException
{
HttpExchange.this.onResponseContent(content);
}
public void onResponseHeader(Buffer name, Buffer value) throws IOException
{
HttpExchange.this.onResponseHeader(name,value);
}
public void onResponseHeaderComplete() throws IOException
{
HttpExchange.this.onResponseHeaderComplete();
}
public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
{
HttpExchange.this.onResponseStatus(version,status,reason);
}
public void onRetry()
{
HttpExchange.this.setRetryStatus( true );
try
{
HttpExchange.this.onRetry();
}
catch (IOException e)
{
onException(e);
}
}
}
/**
* @deprecated use {@link org.mortbay.jetty.client.CachedExchange}
*
*/
public static class CachedExchange extends org.mortbay.jetty.client.CachedExchange
{
public CachedExchange(boolean cacheFields)
{
super(cacheFields);
}
}
/**
* @deprecated use {@link org.mortbay.jetty.client.ContentExchange}
*
*/
public static class ContentExchange extends org.mortbay.jetty.client.ContentExchange
{
}
}