Home | History | Annotate | Download | only in client
      1 //
      2 //  ========================================================================
      3 //  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
      4 //  ------------------------------------------------------------------------
      5 //  All rights reserved. This program and the accompanying materials
      6 //  are made available under the terms of the Eclipse Public License v1.0
      7 //  and Apache License v2.0 which accompanies this distribution.
      8 //
      9 //      The Eclipse Public License is available at
     10 //      http://www.eclipse.org/legal/epl-v10.html
     11 //
     12 //      The Apache License v2.0 is available at
     13 //      http://www.opensource.org/licenses/apache2.0.php
     14 //
     15 //  You may elect to redistribute this code under either of these licenses.
     16 //  ========================================================================
     17 //
     18 
     19 package org.eclipse.jetty.client;
     20 
     21 import java.io.IOException;
     22 import java.io.InputStream;
     23 import java.net.URI;
     24 import java.util.concurrent.atomic.AtomicInteger;
     25 
     26 import org.eclipse.jetty.client.security.SecurityListener;
     27 import org.eclipse.jetty.http.HttpFields;
     28 import org.eclipse.jetty.http.HttpHeaders;
     29 import org.eclipse.jetty.http.HttpMethods;
     30 import org.eclipse.jetty.http.HttpSchemes;
     31 import org.eclipse.jetty.http.HttpURI;
     32 import org.eclipse.jetty.http.HttpVersions;
     33 import org.eclipse.jetty.io.Buffer;
     34 import org.eclipse.jetty.io.BufferCache.CachedBuffer;
     35 import org.eclipse.jetty.io.ByteArrayBuffer;
     36 import org.eclipse.jetty.io.Connection;
     37 import org.eclipse.jetty.io.EndPoint;
     38 import org.eclipse.jetty.util.log.Log;
     39 import org.eclipse.jetty.util.log.Logger;
     40 import org.eclipse.jetty.util.thread.Timeout;
     41 
     42 /**
     43  * <p>
     44  * An HTTP client API that encapsulates an exchange (a request and its response) with a HTTP server.
     45  * </p>
     46  *
     47  * This object encapsulates:
     48  * <ul>
     49  * <li>The HTTP server address, see {@link #setAddress(Address)}, or {@link #setURI(URI)}, or {@link #setURL(String)})
     50  * <li>The HTTP request method, URI and HTTP version (see {@link #setMethod(String)}, {@link #setRequestURI(String)}, and {@link #setVersion(int)})
     51  * <li>The request headers (see {@link #addRequestHeader(String, String)} or {@link #setRequestHeader(String, String)})
     52  * <li>The request content (see {@link #setRequestContent(Buffer)} or {@link #setRequestContentSource(InputStream)})
     53  * <li>The status of the exchange (see {@link #getStatus()})
     54  * <li>Callbacks to handle state changes (see the onXxx methods such as {@link #onRequestComplete()} or {@link #onResponseComplete()})
     55  * <li>The ability to intercept callbacks (see {@link #setEventListener(HttpEventListener)}
     56  * </ul>
     57  *
     58  * <p>
     59  * The HttpExchange class is intended to be used by a developer wishing to have close asynchronous interaction with the the exchange.<br />
     60  * Typically a developer will extend the HttpExchange class with a derived class that overrides some or all of the onXxx callbacks. <br />
     61  * There are also some predefined HttpExchange subtypes that can be used as a basis, see {@link org.eclipse.jetty.client.ContentExchange} and
     62  * {@link org.eclipse.jetty.client.CachedExchange}.
     63  * </p>
     64  *
     65  * <p>
     66  * Typically the HttpExchange is passed to the {@link HttpClient#send(HttpExchange)} method, which in turn selects a {@link HttpDestination} and calls its
     67  * {@link HttpDestination#send(HttpExchange)}, which then creates or selects a {@link AbstractHttpConnection} and calls its {@link AbstractHttpConnection#send(HttpExchange)}. A
     68  * 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
     69  * HttpDestination).
     70  * </p>
     71  *
     72  * <p>
     73  * In some circumstances, the HttpClient or HttpDestination may wish to retry a HttpExchange (eg. failed pipeline request, authentication retry or redirection).
     74  * In such cases, the HttpClient and/or HttpDestination may insert their own HttpExchangeListener to intercept and filter the call backs intended for the
     75  * HttpExchange.
     76  * </p>
     77  */
     78 public class HttpExchange
     79 {
     80     static final Logger LOG = Log.getLogger(HttpExchange.class);
     81 
     82     public static final int STATUS_START = 0;
     83     public static final int STATUS_WAITING_FOR_CONNECTION = 1;
     84     public static final int STATUS_WAITING_FOR_COMMIT = 2;
     85     public static final int STATUS_SENDING_REQUEST = 3;
     86     public static final int STATUS_WAITING_FOR_RESPONSE = 4;
     87     public static final int STATUS_PARSING_HEADERS = 5;
     88     public static final int STATUS_PARSING_CONTENT = 6;
     89     public static final int STATUS_COMPLETED = 7;
     90     public static final int STATUS_EXPIRED = 8;
     91     public static final int STATUS_EXCEPTED = 9;
     92     public static final int STATUS_CANCELLING = 10;
     93     public static final int STATUS_CANCELLED = 11;
     94 
     95     // HTTP protocol fields
     96     private String _method = HttpMethods.GET;
     97     private Buffer _scheme = HttpSchemes.HTTP_BUFFER;
     98     private String _uri;
     99     private int _version = HttpVersions.HTTP_1_1_ORDINAL;
    100     private Address _address;
    101     private final HttpFields _requestFields = new HttpFields();
    102     private Buffer _requestContent;
    103     private InputStream _requestContentSource;
    104 
    105     private AtomicInteger _status = new AtomicInteger(STATUS_START);
    106     private boolean _retryStatus = false;
    107     // controls if the exchange will have listeners autoconfigured by the destination
    108     private boolean _configureListeners = true;
    109     private HttpEventListener _listener = new Listener();
    110     private volatile AbstractHttpConnection _connection;
    111 
    112     private Address _localAddress = null;
    113 
    114     // a timeout for this exchange
    115     private long _timeout = -1;
    116     private volatile Timeout.Task _timeoutTask;
    117     private long _lastStateChange=System.currentTimeMillis();
    118     private long _sent=-1;
    119     private int _lastState=-1;
    120     private int _lastStatePeriod=-1;
    121 
    122     boolean _onRequestCompleteDone;
    123     boolean _onResponseCompleteDone;
    124     boolean _onDone; // == onConnectionFail || onException || onExpired || onCancelled || onResponseCompleted && onRequestCompleted
    125 
    126     protected void expire(HttpDestination destination)
    127     {
    128         AbstractHttpConnection connection = _connection;
    129         if (getStatus() < HttpExchange.STATUS_COMPLETED)
    130             setStatus(HttpExchange.STATUS_EXPIRED);
    131         destination.exchangeExpired(this);
    132         if (connection != null)
    133             connection.exchangeExpired(this);
    134     }
    135 
    136     public int getStatus()
    137     {
    138         return _status.get();
    139     }
    140 
    141     /**
    142      * @param status
    143      *            the status to wait for
    144      * @throws InterruptedException
    145      *             if the waiting thread is interrupted
    146      * @deprecated Use {@link #waitForDone()} instead
    147      */
    148     @Deprecated
    149     public void waitForStatus(int status) throws InterruptedException
    150     {
    151         throw new UnsupportedOperationException();
    152     }
    153 
    154     /**
    155      * 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
    156      * exchange can transit a final state when being used as part of a dialog (eg {@link SecurityListener}. Done status is thus defined as:
    157      *
    158      * <pre>
    159      * done == onConnectionFailed || onException || onExpire || onRequestComplete &amp;&amp; onResponseComplete
    160      * </pre>
    161      *
    162      * @return the done status
    163      * @throws InterruptedException
    164      */
    165     public int waitForDone() throws InterruptedException
    166     {
    167         synchronized (this)
    168         {
    169             while (!isDone())
    170                 this.wait();
    171             return _status.get();
    172         }
    173     }
    174 
    175     public void reset()
    176     {
    177         // TODO - this should do a cancel and wakeup everybody that was waiting.
    178         // might need a version number concept
    179         synchronized (this)
    180         {
    181             _timeoutTask = null;
    182             _onRequestCompleteDone = false;
    183             _onResponseCompleteDone = false;
    184             _onDone = false;
    185             setStatus(STATUS_START);
    186         }
    187     }
    188 
    189     /* ------------------------------------------------------------ */
    190     /**
    191      * @param newStatus
    192      * @return True if the status was actually set.
    193      */
    194     boolean setStatus(int newStatus)
    195     {
    196         boolean set = false;
    197         try
    198         {
    199             int oldStatus = _status.get();
    200             boolean ignored = false;
    201             if (oldStatus != newStatus)
    202             {
    203                 long now = System.currentTimeMillis();
    204                 _lastStatePeriod=(int)(now-_lastStateChange);
    205                 _lastState=oldStatus;
    206                 _lastStateChange=now;
    207                 if (newStatus==STATUS_SENDING_REQUEST)
    208                     _sent=_lastStateChange;
    209             }
    210 
    211             // State machine: from which old status you can go into which new status
    212             switch (oldStatus)
    213             {
    214                 case STATUS_START:
    215                     switch (newStatus)
    216                     {
    217                         case STATUS_START:
    218                         case STATUS_WAITING_FOR_CONNECTION:
    219                         case STATUS_WAITING_FOR_COMMIT:
    220                         case STATUS_CANCELLING:
    221                         case STATUS_EXCEPTED:
    222                             set = _status.compareAndSet(oldStatus,newStatus);
    223                             break;
    224                         case STATUS_EXPIRED:
    225                             set = setStatusExpired(newStatus,oldStatus);
    226                             break;
    227                     }
    228                     break;
    229                 case STATUS_WAITING_FOR_CONNECTION:
    230                     switch (newStatus)
    231                     {
    232                         case STATUS_WAITING_FOR_COMMIT:
    233                         case STATUS_CANCELLING:
    234                         case STATUS_EXCEPTED:
    235                             set = _status.compareAndSet(oldStatus,newStatus);
    236                             break;
    237                         case STATUS_EXPIRED:
    238                             set = setStatusExpired(newStatus,oldStatus);
    239                             break;
    240                     }
    241                     break;
    242                 case STATUS_WAITING_FOR_COMMIT:
    243                     switch (newStatus)
    244                     {
    245                         case STATUS_SENDING_REQUEST:
    246                         case STATUS_CANCELLING:
    247                         case STATUS_EXCEPTED:
    248                             set = _status.compareAndSet(oldStatus,newStatus);
    249                             break;
    250                         case STATUS_EXPIRED:
    251                             set = setStatusExpired(newStatus,oldStatus);
    252                             break;
    253                     }
    254                     break;
    255                 case STATUS_SENDING_REQUEST:
    256                     switch (newStatus)
    257                     {
    258                         case STATUS_WAITING_FOR_RESPONSE:
    259                             if (set = _status.compareAndSet(oldStatus,newStatus))
    260                                 getEventListener().onRequestCommitted();
    261                             break;
    262                         case STATUS_CANCELLING:
    263                         case STATUS_EXCEPTED:
    264                             set = _status.compareAndSet(oldStatus,newStatus);
    265                             break;
    266                         case STATUS_EXPIRED:
    267                             set = setStatusExpired(newStatus,oldStatus);
    268                             break;
    269                     }
    270                     break;
    271                 case STATUS_WAITING_FOR_RESPONSE:
    272                     switch (newStatus)
    273                     {
    274                         case STATUS_PARSING_HEADERS:
    275                         case STATUS_CANCELLING:
    276                         case STATUS_EXCEPTED:
    277                             set = _status.compareAndSet(oldStatus,newStatus);
    278                             break;
    279                         case STATUS_EXPIRED:
    280                             set = setStatusExpired(newStatus,oldStatus);
    281                             break;
    282                     }
    283                     break;
    284                 case STATUS_PARSING_HEADERS:
    285                     switch (newStatus)
    286                     {
    287                         case STATUS_PARSING_CONTENT:
    288                             if (set = _status.compareAndSet(oldStatus,newStatus))
    289                                 getEventListener().onResponseHeaderComplete();
    290                             break;
    291                         case STATUS_CANCELLING:
    292                         case STATUS_EXCEPTED:
    293                             set = _status.compareAndSet(oldStatus,newStatus);
    294                             break;
    295                         case STATUS_EXPIRED:
    296                             set = setStatusExpired(newStatus,oldStatus);
    297                             break;
    298                     }
    299                     break;
    300                 case STATUS_PARSING_CONTENT:
    301                     switch (newStatus)
    302                     {
    303                         case STATUS_COMPLETED:
    304                             if (set = _status.compareAndSet(oldStatus,newStatus))
    305                                 getEventListener().onResponseComplete();
    306                             break;
    307                         case STATUS_CANCELLING:
    308                         case STATUS_EXCEPTED:
    309                             set = _status.compareAndSet(oldStatus,newStatus);
    310                             break;
    311                         case STATUS_EXPIRED:
    312                             set = setStatusExpired(newStatus,oldStatus);
    313                             break;
    314                     }
    315                     break;
    316                 case STATUS_COMPLETED:
    317                     switch (newStatus)
    318                     {
    319                         case STATUS_START:
    320                         case STATUS_EXCEPTED:
    321                         case STATUS_WAITING_FOR_RESPONSE:
    322                             set = _status.compareAndSet(oldStatus,newStatus);
    323                             break;
    324                         case STATUS_CANCELLING:
    325                         case STATUS_EXPIRED:
    326                             // Don't change the status, it's too late
    327                             ignored = true;
    328                             break;
    329                     }
    330                     break;
    331                 case STATUS_CANCELLING:
    332                     switch (newStatus)
    333                     {
    334                         case STATUS_EXCEPTED:
    335                         case STATUS_CANCELLED:
    336                             if (set = _status.compareAndSet(oldStatus,newStatus))
    337                                 done();
    338                             break;
    339                         default:
    340                             // Ignore other statuses, we're cancelling
    341                             ignored = true;
    342                             break;
    343                     }
    344                     break;
    345                 case STATUS_EXCEPTED:
    346                 case STATUS_EXPIRED:
    347                 case STATUS_CANCELLED:
    348                     switch (newStatus)
    349                     {
    350                         case STATUS_START:
    351                             set = _status.compareAndSet(oldStatus,newStatus);
    352                             break;
    353 
    354                         case STATUS_COMPLETED:
    355                             ignored = true;
    356                             done();
    357                             break;
    358 
    359                         default:
    360                             ignored = true;
    361                             break;
    362                     }
    363                     break;
    364                 default:
    365                     // Here means I allowed to set a state that I don't recognize
    366                     throw new AssertionError(oldStatus + " => " + newStatus);
    367             }
    368 
    369             if (!set && !ignored)
    370                 throw new IllegalStateException(toState(oldStatus) + " => " + toState(newStatus));
    371             LOG.debug("setStatus {} {}",newStatus,this);
    372         }
    373         catch (IOException x)
    374         {
    375             LOG.warn(x);
    376         }
    377         return set;
    378     }
    379 
    380     private boolean setStatusExpired(int newStatus, int oldStatus)
    381     {
    382         boolean set;
    383         if (set = _status.compareAndSet(oldStatus,newStatus))
    384             getEventListener().onExpire();
    385         return set;
    386     }
    387 
    388     public boolean isDone()
    389     {
    390         synchronized (this)
    391         {
    392             return _onDone;
    393         }
    394     }
    395 
    396     /**
    397      * @deprecated
    398      */
    399     @Deprecated
    400     public boolean isDone(int status)
    401     {
    402         return isDone();
    403     }
    404 
    405     public HttpEventListener getEventListener()
    406     {
    407         return _listener;
    408     }
    409 
    410     public void setEventListener(HttpEventListener listener)
    411     {
    412         _listener = listener;
    413     }
    414 
    415     public void setTimeout(long timeout)
    416     {
    417         _timeout = timeout;
    418     }
    419 
    420     public long getTimeout()
    421     {
    422         return _timeout;
    423     }
    424 
    425     /**
    426      * @param url
    427      *            an absolute URL (for example 'http://localhost/foo/bar?a=1')
    428      */
    429     public void setURL(String url)
    430     {
    431         setURI(URI.create(url));
    432     }
    433 
    434     /**
    435      * @param address
    436      *            the address of the server
    437      */
    438     public void setAddress(Address address)
    439     {
    440         _address = address;
    441     }
    442 
    443     /**
    444      * @return the address of the server
    445      */
    446     public Address getAddress()
    447     {
    448         return _address;
    449     }
    450 
    451     /**
    452      * the local address used by the connection
    453      *
    454      * Note: this method will not be populated unless the exchange has been executed by the HttpClient
    455      *
    456      * @return the local address used for the running of the exchange if available, null otherwise.
    457      */
    458     public Address getLocalAddress()
    459     {
    460         return _localAddress;
    461     }
    462 
    463     /**
    464      * @param scheme
    465      *            the scheme of the URL (for example 'http')
    466      */
    467     public void setScheme(Buffer scheme)
    468     {
    469         _scheme = scheme;
    470     }
    471 
    472     /**
    473      * @param scheme
    474      *            the scheme of the URL (for example 'http')
    475      */
    476     public void setScheme(String scheme)
    477     {
    478         if (scheme != null)
    479         {
    480             if (HttpSchemes.HTTP.equalsIgnoreCase(scheme))
    481                 setScheme(HttpSchemes.HTTP_BUFFER);
    482             else if (HttpSchemes.HTTPS.equalsIgnoreCase(scheme))
    483                 setScheme(HttpSchemes.HTTPS_BUFFER);
    484             else
    485                 setScheme(new ByteArrayBuffer(scheme));
    486         }
    487     }
    488 
    489     /**
    490      * @return the scheme of the URL
    491      */
    492     public Buffer getScheme()
    493     {
    494         return _scheme;
    495     }
    496 
    497     /**
    498      * @param version
    499      *            the HTTP protocol version as integer, 9, 10 or 11 for 0.9, 1.0 or 1.1
    500      */
    501     public void setVersion(int version)
    502     {
    503         _version = version;
    504     }
    505 
    506     /**
    507      * @param version
    508      *            the HTTP protocol version as string
    509      */
    510     public void setVersion(String version)
    511     {
    512         CachedBuffer v = HttpVersions.CACHE.get(version);
    513         if (v == null)
    514             _version = 10;
    515         else
    516             _version = v.getOrdinal();
    517     }
    518 
    519     /**
    520      * @return the HTTP protocol version as integer
    521      * @see #setVersion(int)
    522      */
    523     public int getVersion()
    524     {
    525         return _version;
    526     }
    527 
    528     /**
    529      * @param method
    530      *            the HTTP method (for example 'GET')
    531      */
    532     public void setMethod(String method)
    533     {
    534         _method = method;
    535     }
    536 
    537     /**
    538      * @return the HTTP method
    539      */
    540     public String getMethod()
    541     {
    542         return _method;
    543     }
    544 
    545     /**
    546      * @return request URI
    547      * @see #getRequestURI()
    548      * @deprecated
    549      */
    550     @Deprecated
    551     public String getURI()
    552     {
    553         return getRequestURI();
    554     }
    555 
    556     /**
    557      * @return request URI
    558      */
    559     public String getRequestURI()
    560     {
    561         return _uri;
    562     }
    563 
    564     /**
    565      * Set the request URI
    566      *
    567      * @param uri
    568      *            new request URI
    569      * @see #setRequestURI(String)
    570      * @deprecated
    571      */
    572     @Deprecated
    573     public void setURI(String uri)
    574     {
    575         setRequestURI(uri);
    576     }
    577 
    578     /**
    579      * Set the request URI
    580      *
    581      * Per RFC 2616 sec5, Request-URI = "*" | absoluteURI | abs_path | authority<br/>
    582      * where:<br/>
    583      * <br/>
    584      * "*" - request applies to server itself<br/>
    585      * absoluteURI - required for proxy requests, e.g. http://localhost:8080/context<br/>
    586      * (this form is generated automatically by HttpClient)<br/>
    587      * abs_path - used for most methods, e.g. /context<br/>
    588      * authority - used for CONNECT method only, e.g. localhost:8080<br/>
    589      * <br/>
    590      * For complete definition of URI components, see RFC 2396 sec3.<br/>
    591      *
    592      * @param uri
    593      *            new request URI
    594      */
    595     public void setRequestURI(String uri)
    596     {
    597         _uri = uri;
    598     }
    599 
    600     /* ------------------------------------------------------------ */
    601     /**
    602      * @param uri
    603      *            an absolute URI (for example 'http://localhost/foo/bar?a=1')
    604      */
    605     public void setURI(URI uri)
    606     {
    607         if (!uri.isAbsolute())
    608             throw new IllegalArgumentException("!Absolute URI: " + uri);
    609 
    610         if (uri.isOpaque())
    611             throw new IllegalArgumentException("Opaque URI: " + uri);
    612 
    613         if (LOG.isDebugEnabled())
    614             LOG.debug("URI = {}",uri.toASCIIString());
    615 
    616         String scheme = uri.getScheme();
    617         int port = uri.getPort();
    618         if (port <= 0)
    619             port = "https".equalsIgnoreCase(scheme)?443:80;
    620 
    621         setScheme(scheme);
    622         setAddress(new Address(uri.getHost(),port));
    623 
    624         HttpURI httpUri = new HttpURI(uri);
    625         String completePath = httpUri.getCompletePath();
    626         setRequestURI(completePath == null?"/":completePath);
    627     }
    628 
    629     /**
    630      * Adds the specified request header
    631      *
    632      * @param name
    633      *            the header name
    634      * @param value
    635      *            the header value
    636      */
    637     public void addRequestHeader(String name, String value)
    638     {
    639         getRequestFields().add(name,value);
    640     }
    641 
    642     /**
    643      * Adds the specified request header
    644      *
    645      * @param name
    646      *            the header name
    647      * @param value
    648      *            the header value
    649      */
    650     public void addRequestHeader(Buffer name, Buffer value)
    651     {
    652         getRequestFields().add(name,value);
    653     }
    654 
    655     /**
    656      * Sets the specified request header
    657      *
    658      * @param name
    659      *            the header name
    660      * @param value
    661      *            the header value
    662      */
    663     public void setRequestHeader(String name, String value)
    664     {
    665         getRequestFields().put(name,value);
    666     }
    667 
    668     /**
    669      * Sets the specified request header
    670      *
    671      * @param name
    672      *            the header name
    673      * @param value
    674      *            the header value
    675      */
    676     public void setRequestHeader(Buffer name, Buffer value)
    677     {
    678         getRequestFields().put(name,value);
    679     }
    680 
    681     /**
    682      * @param value
    683      *            the content type of the request
    684      */
    685     public void setRequestContentType(String value)
    686     {
    687         getRequestFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,value);
    688     }
    689 
    690     /**
    691      * @return the request headers
    692      */
    693     public HttpFields getRequestFields()
    694     {
    695         return _requestFields;
    696     }
    697 
    698     /**
    699      * @param requestContent
    700      *            the request content
    701      */
    702     public void setRequestContent(Buffer requestContent)
    703     {
    704         _requestContent = requestContent;
    705     }
    706 
    707     /**
    708      * @param stream
    709      *            the request content as a stream
    710      */
    711     public void setRequestContentSource(InputStream stream)
    712     {
    713         _requestContentSource = stream;
    714         if (_requestContentSource != null && _requestContentSource.markSupported())
    715             _requestContentSource.mark(Integer.MAX_VALUE);
    716     }
    717 
    718     /**
    719      * @return the request content as a stream
    720      */
    721     public InputStream getRequestContentSource()
    722     {
    723         return _requestContentSource;
    724     }
    725 
    726     public Buffer getRequestContentChunk(Buffer buffer) throws IOException
    727     {
    728         synchronized (this)
    729         {
    730             if (_requestContentSource!=null)
    731             {
    732                 if (buffer == null)
    733                     buffer = new ByteArrayBuffer(8192); // TODO configure
    734 
    735                 int space = buffer.space();
    736                 int length = _requestContentSource.read(buffer.array(),buffer.putIndex(),space);
    737                 if (length >= 0)
    738                 {
    739                     buffer.setPutIndex(buffer.putIndex()+length);
    740                     return buffer;
    741                 }
    742             }
    743             return null;
    744         }
    745     }
    746 
    747     /**
    748      * @return the request content
    749      */
    750     public Buffer getRequestContent()
    751     {
    752         return _requestContent;
    753     }
    754 
    755     /**
    756      * @return whether a retry will be attempted or not
    757      */
    758     public boolean getRetryStatus()
    759     {
    760         return _retryStatus;
    761     }
    762 
    763     /**
    764      * @param retryStatus
    765      *            whether a retry will be attempted or not
    766      */
    767     public void setRetryStatus(boolean retryStatus)
    768     {
    769         _retryStatus = retryStatus;
    770     }
    771 
    772     /**
    773      * Initiates the cancelling of this exchange. The status of the exchange is set to {@link #STATUS_CANCELLING}. Cancelling the exchange is an asynchronous
    774      * operation with respect to the request/response, and as such checking the request/response status of a cancelled exchange may return undefined results
    775      * (for example it may have only some of the response headers being sent by the server). The cancelling of the exchange is completed when the exchange
    776      * status (see {@link #getStatus()}) is {@link #STATUS_CANCELLED}, and this can be waited using {@link #waitForDone()}.
    777      */
    778     public void cancel()
    779     {
    780         setStatus(STATUS_CANCELLING);
    781         abort();
    782     }
    783 
    784     private void done()
    785     {
    786         synchronized (this)
    787         {
    788             disassociate();
    789             _onDone = true;
    790             notifyAll();
    791         }
    792     }
    793 
    794     private void abort()
    795     {
    796         AbstractHttpConnection httpConnection = _connection;
    797         if (httpConnection != null)
    798         {
    799             try
    800             {
    801                 // Closing the connection here will cause the connection
    802                 // to be returned in HttpConnection.handle()
    803                 httpConnection.close();
    804             }
    805             catch (IOException x)
    806             {
    807                 LOG.debug(x);
    808             }
    809             finally
    810             {
    811                 disassociate();
    812             }
    813         }
    814     }
    815 
    816     void associate(AbstractHttpConnection connection)
    817     {
    818         if (connection.getEndPoint().getLocalAddr() != null)
    819             _localAddress = new Address(connection.getEndPoint().getLocalAddr(),connection.getEndPoint().getLocalPort());
    820 
    821         _connection = connection;
    822         if (getStatus() == STATUS_CANCELLING)
    823             abort();
    824     }
    825 
    826     boolean isAssociated()
    827     {
    828         return this._connection != null;
    829     }
    830 
    831     AbstractHttpConnection disassociate()
    832     {
    833         AbstractHttpConnection result = _connection;
    834         this._connection = null;
    835         if (getStatus() == STATUS_CANCELLING)
    836             setStatus(STATUS_CANCELLED);
    837         return result;
    838     }
    839 
    840     public static String toState(int s)
    841     {
    842         String state;
    843         switch (s)
    844         {
    845             case STATUS_START:
    846                 state = "START";
    847                 break;
    848             case STATUS_WAITING_FOR_CONNECTION:
    849                 state = "CONNECTING";
    850                 break;
    851             case STATUS_WAITING_FOR_COMMIT:
    852                 state = "CONNECTED";
    853                 break;
    854             case STATUS_SENDING_REQUEST:
    855                 state = "SENDING";
    856                 break;
    857             case STATUS_WAITING_FOR_RESPONSE:
    858                 state = "WAITING";
    859                 break;
    860             case STATUS_PARSING_HEADERS:
    861                 state = "HEADERS";
    862                 break;
    863             case STATUS_PARSING_CONTENT:
    864                 state = "CONTENT";
    865                 break;
    866             case STATUS_COMPLETED:
    867                 state = "COMPLETED";
    868                 break;
    869             case STATUS_EXPIRED:
    870                 state = "EXPIRED";
    871                 break;
    872             case STATUS_EXCEPTED:
    873                 state = "EXCEPTED";
    874                 break;
    875             case STATUS_CANCELLING:
    876                 state = "CANCELLING";
    877                 break;
    878             case STATUS_CANCELLED:
    879                 state = "CANCELLED";
    880                 break;
    881             default:
    882                 state = "UNKNOWN";
    883         }
    884         return state;
    885     }
    886 
    887     @Override
    888     public String toString()
    889     {
    890         String state=toState(getStatus());
    891         long now=System.currentTimeMillis();
    892         long forMs = now -_lastStateChange;
    893         String s= _lastState>=0
    894             ?String.format("%s@%x=%s//%s%s#%s(%dms)->%s(%dms)",getClass().getSimpleName(),hashCode(),_method,_address,_uri,toState(_lastState),_lastStatePeriod,state,forMs)
    895             :String.format("%s@%x=%s//%s%s#%s(%dms)",getClass().getSimpleName(),hashCode(),_method,_address,_uri,state,forMs);
    896         if (getStatus()>=STATUS_SENDING_REQUEST && _sent>0)
    897             s+="sent="+(now-_sent)+"ms";
    898         return s;
    899     }
    900 
    901     /**
    902      */
    903     protected Connection onSwitchProtocol(EndPoint endp) throws IOException
    904     {
    905         return null;
    906     }
    907 
    908     /**
    909      * Callback called when the request headers have been sent to the server. This implementation does nothing.
    910      *
    911      * @throws IOException
    912      *             allowed to be thrown by overriding code
    913      */
    914     protected void onRequestCommitted() throws IOException
    915     {
    916     }
    917 
    918     /**
    919      * Callback called when the request and its body have been sent to the server. This implementation does nothing.
    920      *
    921      * @throws IOException
    922      *             allowed to be thrown by overriding code
    923      */
    924     protected void onRequestComplete() throws IOException
    925     {
    926     }
    927 
    928     /**
    929      * Callback called when a response status line has been received from the server. This implementation does nothing.
    930      *
    931      * @param version
    932      *            the HTTP version
    933      * @param status
    934      *            the HTTP status code
    935      * @param reason
    936      *            the HTTP status reason string
    937      * @throws IOException
    938      *             allowed to be thrown by overriding code
    939      */
    940     protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
    941     {
    942     }
    943 
    944     /**
    945      * Callback called for each response header received from the server. This implementation does nothing.
    946      *
    947      * @param name
    948      *            the header name
    949      * @param value
    950      *            the header value
    951      * @throws IOException
    952      *             allowed to be thrown by overriding code
    953      */
    954     protected void onResponseHeader(Buffer name, Buffer value) throws IOException
    955     {
    956     }
    957 
    958     /**
    959      * Callback called when the response headers have been completely received from the server. This implementation does nothing.
    960      *
    961      * @throws IOException
    962      *             allowed to be thrown by overriding code
    963      */
    964     protected void onResponseHeaderComplete() throws IOException
    965     {
    966     }
    967 
    968     /**
    969      * Callback called for each chunk of the response content received from the server. This implementation does nothing.
    970      *
    971      * @param content
    972      *            the buffer holding the content chunk
    973      * @throws IOException
    974      *             allowed to be thrown by overriding code
    975      */
    976     protected void onResponseContent(Buffer content) throws IOException
    977     {
    978     }
    979 
    980     /**
    981      * Callback called when the entire response has been received from the server This implementation does nothing.
    982      *
    983      * @throws IOException
    984      *             allowed to be thrown by overriding code
    985      */
    986     protected void onResponseComplete() throws IOException
    987     {
    988     }
    989 
    990     /**
    991      * Callback called when an exception was thrown during an attempt to establish the connection with the server (for example the server is not listening).
    992      * This implementation logs a warning.
    993      *
    994      * @param x
    995      *            the exception thrown attempting to establish the connection with the server
    996      */
    997     protected void onConnectionFailed(Throwable x)
    998     {
    999         LOG.warn("CONNECTION FAILED " + this,x);
   1000     }
   1001 
   1002     /**
   1003      * Callback called when any other exception occurs during the handling of this exchange. This implementation logs a warning.
   1004      *
   1005      * @param x
   1006      *            the exception thrown during the handling of this exchange
   1007      */
   1008     protected void onException(Throwable x)
   1009     {
   1010         LOG.warn("EXCEPTION " + this,x);
   1011     }
   1012 
   1013     /**
   1014      * Callback called when no response has been received within the timeout. This implementation logs a warning.
   1015      */
   1016     protected void onExpire()
   1017     {
   1018         LOG.warn("EXPIRED " + this);
   1019     }
   1020 
   1021     /**
   1022      * Callback called when the request is retried (due to failures or authentication). Implementations must reset any consumable content that needs to be sent.
   1023      *
   1024      * @throws IOException
   1025      *             allowed to be thrown by overriding code
   1026      */
   1027     protected void onRetry() throws IOException
   1028     {
   1029         if (_requestContentSource != null)
   1030         {
   1031             if (_requestContentSource.markSupported())
   1032             {
   1033                 _requestContent = null;
   1034                 _requestContentSource.reset();
   1035             }
   1036             else
   1037             {
   1038                 throw new IOException("Unsupported retry attempt");
   1039             }
   1040         }
   1041     }
   1042 
   1043     /**
   1044      * @return true if the exchange should have listeners configured for it by the destination, false if this is being managed elsewhere
   1045      * @see #setConfigureListeners(boolean)
   1046      */
   1047     public boolean configureListeners()
   1048     {
   1049         return _configureListeners;
   1050     }
   1051 
   1052     /**
   1053      * @param autoConfigure
   1054      *            whether the listeners are configured by the destination or elsewhere
   1055      */
   1056     public void setConfigureListeners(boolean autoConfigure)
   1057     {
   1058         this._configureListeners = autoConfigure;
   1059     }
   1060 
   1061     protected void scheduleTimeout(final HttpDestination destination)
   1062     {
   1063         assert _timeoutTask == null;
   1064 
   1065         _timeoutTask = new Timeout.Task()
   1066         {
   1067             @Override
   1068             public void expired()
   1069             {
   1070                 HttpExchange.this.expire(destination);
   1071             }
   1072         };
   1073 
   1074         HttpClient httpClient = destination.getHttpClient();
   1075         long timeout = getTimeout();
   1076         if (timeout > 0)
   1077             httpClient.schedule(_timeoutTask,timeout);
   1078         else
   1079             httpClient.schedule(_timeoutTask);
   1080     }
   1081 
   1082     protected void cancelTimeout(HttpClient httpClient)
   1083     {
   1084         Timeout.Task task = _timeoutTask;
   1085         if (task != null)
   1086             httpClient.cancel(task);
   1087         _timeoutTask = null;
   1088     }
   1089 
   1090     private class Listener implements HttpEventListener
   1091     {
   1092         public void onConnectionFailed(Throwable ex)
   1093         {
   1094             try
   1095             {
   1096                 HttpExchange.this.onConnectionFailed(ex);
   1097             }
   1098             finally
   1099             {
   1100                 done();
   1101             }
   1102         }
   1103 
   1104         public void onException(Throwable ex)
   1105         {
   1106             try
   1107             {
   1108                 HttpExchange.this.onException(ex);
   1109             }
   1110             finally
   1111             {
   1112                 done();
   1113             }
   1114         }
   1115 
   1116         public void onExpire()
   1117         {
   1118             try
   1119             {
   1120                 HttpExchange.this.onExpire();
   1121             }
   1122             finally
   1123             {
   1124                 done();
   1125             }
   1126         }
   1127 
   1128         public void onRequestCommitted() throws IOException
   1129         {
   1130             HttpExchange.this.onRequestCommitted();
   1131         }
   1132 
   1133         public void onRequestComplete() throws IOException
   1134         {
   1135             try
   1136             {
   1137                 HttpExchange.this.onRequestComplete();
   1138             }
   1139             finally
   1140             {
   1141                 synchronized (HttpExchange.this)
   1142                 {
   1143                     _onRequestCompleteDone = true;
   1144                     // Member _onDone may already be true, for example
   1145                     // because the exchange expired or has been canceled
   1146                     _onDone |= _onResponseCompleteDone;
   1147                     if (_onDone)
   1148                         disassociate();
   1149                     HttpExchange.this.notifyAll();
   1150                 }
   1151             }
   1152         }
   1153 
   1154         public void onResponseComplete() throws IOException
   1155         {
   1156             try
   1157             {
   1158                 HttpExchange.this.onResponseComplete();
   1159             }
   1160             finally
   1161             {
   1162                 synchronized (HttpExchange.this)
   1163                 {
   1164                     _onResponseCompleteDone = true;
   1165                     // Member _onDone may already be true, for example
   1166                     // because the exchange expired or has been canceled
   1167                     _onDone |= _onRequestCompleteDone;
   1168                     if (_onDone)
   1169                         disassociate();
   1170                     HttpExchange.this.notifyAll();
   1171                 }
   1172             }
   1173         }
   1174 
   1175         public void onResponseContent(Buffer content) throws IOException
   1176         {
   1177             HttpExchange.this.onResponseContent(content);
   1178         }
   1179 
   1180         public void onResponseHeader(Buffer name, Buffer value) throws IOException
   1181         {
   1182             HttpExchange.this.onResponseHeader(name,value);
   1183         }
   1184 
   1185         public void onResponseHeaderComplete() throws IOException
   1186         {
   1187             HttpExchange.this.onResponseHeaderComplete();
   1188         }
   1189 
   1190         public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
   1191         {
   1192             HttpExchange.this.onResponseStatus(version,status,reason);
   1193         }
   1194 
   1195         public void onRetry()
   1196         {
   1197             HttpExchange.this.setRetryStatus(true);
   1198             try
   1199             {
   1200                 HttpExchange.this.onRetry();
   1201             }
   1202             catch (IOException e)
   1203             {
   1204                 LOG.debug(e);
   1205             }
   1206         }
   1207     }
   1208 
   1209     /**
   1210      * @deprecated use {@link org.eclipse.jetty.client.CachedExchange} instead
   1211      */
   1212     @Deprecated
   1213     public static class CachedExchange extends org.eclipse.jetty.client.CachedExchange
   1214     {
   1215         public CachedExchange(boolean cacheFields)
   1216         {
   1217             super(cacheFields);
   1218         }
   1219     }
   1220 
   1221     /**
   1222      * @deprecated use {@link org.eclipse.jetty.client.ContentExchange} instead
   1223      */
   1224     @Deprecated
   1225     public static class ContentExchange extends org.eclipse.jetty.client.ContentExchange
   1226     {
   1227     }
   1228 }
   1229