View Javadoc

    1   // ========================================================================
    2   // Copyright 2006-2007 Mort Bay Consulting Pty. Ltd.
    3   // ------------------------------------------------------------------------
    4   // Licensed under the Apache License, Version 2.0 (the "License");
    5   // you may not use this file except in compliance with the License.
    6   // You may obtain a copy of the License at
    7   // http://www.apache.org/licenses/LICENSE-2.0
    8   // Unless required by applicable law or agreed to in writing, software
    9   // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  // ========================================================================
    14
    15  package org.mortbay.jetty.client;
    16
    17  import java.io.IOException;
    18  import java.io.InputStream;
    19  import java.net.InetSocketAddress;
    20
    21  import org.mortbay.io.Buffer;
    22  import org.mortbay.io.BufferCache.CachedBuffer;
    23  import org.mortbay.io.nio.ChannelEndPoint;
    24  import org.mortbay.io.ByteArrayBuffer;
    25  import org.mortbay.jetty.HttpFields;
    26  import org.mortbay.jetty.HttpHeaders;
    27  import org.mortbay.jetty.HttpMethods;
    28  import org.mortbay.jetty.HttpSchemes;
    29  import org.mortbay.jetty.HttpURI;
    30  import org.mortbay.jetty.HttpVersions;
    31  import org.mortbay.log.Log;
    32
    33
    34  /**
    35   * An HTTP client API that encapsulates Exchange with a HTTP server.
    36   *
    37   * This object encapsulates:<ul>
    38   * <li>The HTTP server. (see {@link #setAddress(InetSocketAddress)} or {@link #setURL(String)})
    39   * <li>The HTTP request method, URI and HTTP version (see {@link #setMethod(String)}, {@link #setURI(String)}, and {@link #setVersion(int)}
    40   * <li>The Request headers (see {@link #addRequestHeader(String, String)} or {@link #setRequestHeader(String, String)})
    41   * <li>The Request content (see {@link #setRequestContent(Buffer)} or {@link #setRequestContentSource(InputStream)})
    42   * <li>The status of the exchange (see {@link #getStatus()})
    43   * <li>Callbacks to handle state changes (see the onXxx methods such as {@link #onRequestComplete()} or {@link #onResponseComplete()})
    44   * <li>The ability to intercept callbacks (see {@link #setEventListener(HttpEventListener)}
    45   * </ul>
    46   *
    47   * The HttpExchange class is intended to be used by a developer wishing to have close asynchronous
    48   * interaction with the the exchange.  Typically a developer will extend the HttpExchange class with a derived
    49   * class that implements some or all of the onXxx callbacks.  There are also some predefined HttpExchange subtypes
    50   * that can be used as a basis (see {@link ContentExchange} and {@link CachedExchange}.
    51   *
    52   * <p>Typically the HttpExchange is passed to a the {@link HttpClient#send(HttpExchange)} method, which in
    53   * turn selects a {@link HttpDestination} and calls it's {@link HttpDestination#send(HttpExchange), which
    54   * then creates or selects a {@link HttpConnection} and calls its {@link HttpConnection#send(HttpExchange).
    55   * A developer may wish to directly call send on the destination or connection if they wish to bypass
    56   * some handling provided (eg Cookie handling in the HttpDestination).
    57   *
    58   * <p>In some circumstances, the HttpClient or HttpDestination may wish to retry a HttpExchange (eg. failed
    59   * pipeline request, authentication retry or redirection).  In such cases, the HttpClient and/or HttpDestination
    60   * may insert their own HttpExchangeListener to intercept and filter the call backs intended for the
    61   * HttpExchange.
    62   *
    63   * @author gregw
    64   * @author Guillaume Nodet
    65   */
    66  public class HttpExchange
    67  {
    68      public static final int STATUS_START = 0;
    69      public static final int STATUS_WAITING_FOR_CONNECTION = 1;
    70      public static final int STATUS_WAITING_FOR_COMMIT = 2;
    71      public static final int STATUS_SENDING_REQUEST = 3;
    72      public static final int STATUS_WAITING_FOR_RESPONSE = 4;
    73      public static final int STATUS_PARSING_HEADERS = 5;
    74      public static final int STATUS_PARSING_CONTENT = 6;
    75      public static final int STATUS_COMPLETED = 7;
    76      public static final int STATUS_EXPIRED = 8;
    77      public static final int STATUS_EXCEPTED = 9;
    78
    79      String _method = HttpMethods.GET;
    80      Buffer _scheme = HttpSchemes.HTTP_BUFFER;
    81      String _uri;
    82      int _version = HttpVersions.HTTP_1_1_ORDINAL;
    83      Address _address;
    84      HttpFields _requestFields = new HttpFields();
    85      Buffer _requestContent;
    86      InputStream _requestContentSource;
    87
    88      volatile int _status = STATUS_START;
    89      Buffer _requestContentChunk;
    90      boolean _retryStatus = false;
    91      // controls if the exchange will have listeners autoconfigured by the destination
    92      boolean _configureListeners = true;
    93      private HttpEventListener _listener = new Listener();
    94
    95      boolean _onRequestCompleteDone;
    96      boolean _onResponseCompleteDone;
    97      boolean _onDone; // == onConnectionFail || onException || onExpired || onCancelled || onResponseCompleted && onRequestCompleted
    98
    99      /* ------------------------------------------------------------ */
    100     public int getStatus()
    101     {
    102         return _status;
    103     }
    104
    105     /* ------------------------------------------------------------ */
    106     /**
    107      * @deprecated
    108      */
    109     public void waitForStatus(int status) throws InterruptedException
    110     {
    111         synchronized (this)
    112         {
    113             while (_status < status)
    114             {
    115                 this.wait();
    116             }
    117         }
    118     }
    119
    120
    121     /**
    122      * Wait until the exchange is "done".
    123      * Done is defined as when a final state has been passed to the
    124      * HttpExchange via the associated onXxx call.  Note that an
    125      * exchange can transit a final state when being used as part
    126      * of a dialog (eg {@link SecurityListener}.   Done status
    127      * is thus defined as:<pre>
    128      *   done == onConnectionFailed
    129      *        || onException
    130      *        || onExpire
    131      *        || onRequestComplete && onResponseComplete
    132      * </pre>
    133      * @return
    134      * @throws InterruptedException
    135      */
    136     public int waitForDone () throws InterruptedException
    137     {
    138         synchronized (this)
    139         {
    140             while (!isDone(_status))
    141                 this.wait();
    142             return _status;
    143         }
    144     }
    145
    146
    147
    148
    149     /* ------------------------------------------------------------ */
    150     public void reset()
    151     {
    152         // TODO - this should do a cancel and wakeup everybody that was waiting.
    153         // might need a version number concept
    154         synchronized(this)
    155         {
    156             _onRequestCompleteDone=false;
    157             _onResponseCompleteDone=false;
    158             _onDone=false;
    159             setStatus(STATUS_START);
    160         }
    161     }
    162
    163     /* ------------------------------------------------------------ */
    164     void setStatus(int status)
    165     {
    166         // _status is volatile
    167         _status = status;
    168
    169         try
    170         {
    171             switch (status)
    172             {
    173                 case STATUS_WAITING_FOR_CONNECTION:
    174                     break;
    175
    176                 case STATUS_WAITING_FOR_COMMIT:
    177                     break;
    178
    179                 case STATUS_SENDING_REQUEST:
    180                     break;
    181
    182                 case HttpExchange.STATUS_WAITING_FOR_RESPONSE:
    183                     getEventListener().onRequestCommitted();
    184                     break;
    185
    186                 case STATUS_PARSING_HEADERS:
    187                     break;
    188
    189                 case STATUS_PARSING_CONTENT:
    190                     getEventListener().onResponseHeaderComplete();
    191                     break;
    192
    193                 case STATUS_COMPLETED:
    194                     getEventListener().onResponseComplete();
    195                     break;
    196
    197                 case STATUS_EXPIRED:
    198                     getEventListener().onExpire();
    199                     break;
    200
    201             }
    202         }
    203         catch (IOException e)
    204         {
    205             Log.warn(e);
    206         }
    207     }
    208
    209     /* ------------------------------------------------------------ */
    210     public boolean isDone (int status)
    211     {
    212         synchronized (this)
    213         {
    214             return _onDone;
    215         }
    216     }
    217
    218     /* ------------------------------------------------------------ */
    219     public HttpEventListener getEventListener()
    220     {
    221         return _listener;
    222     }
    223
    224     /* ------------------------------------------------------------ */
    225     public void setEventListener(HttpEventListener listener)
    226     {
    227         _listener=listener;
    228     }
    229
    230     /* ------------------------------------------------------------ */
    231     /**
    232      * @param url Including protocol, host and port
    233      */
    234     public void setURL(String url)
    235     {
    236         HttpURI uri = new HttpURI(url);
    237         String scheme = uri.getScheme();
    238         if (scheme != null)
    239         {
    240             if (HttpSchemes.HTTP.equalsIgnoreCase(scheme))
    241                 setScheme(HttpSchemes.HTTP_BUFFER);
    242             else if (HttpSchemes.HTTPS.equalsIgnoreCase(scheme))
    243                 setScheme(HttpSchemes.HTTPS_BUFFER);
    244             else
    245                 setScheme(new ByteArrayBuffer(scheme));
    246         }
    247
    248         int port = uri.getPort();
    249         if (port <= 0)
    250             port = "https".equalsIgnoreCase(scheme)?443:80;
    251
    252         setAddress(new Address(uri.getHost(),port));
    253
    254         String completePath = uri.getCompletePath();
    255         if (completePath == null)
    256             completePath = "/";
    257
    258         setURI(completePath);
    259     }
    260
    261     /* ------------------------------------------------------------ */
    262     /**
    263      * @param address
    264      */
    265     public void setAddress(Address address)
    266     {
    267         _address = address;
    268     }
    269
    270     /* ------------------------------------------------------------ */
    271     /**
    272      * @return
    273      */
    274     public Address getAddress()
    275     {
    276         return _address;
    277     }
    278
    279     /* ------------------------------------------------------------ */
    280     /**
    281      * @param scheme
    282      */
    283     public void setScheme(Buffer scheme)
    284     {
    285         _scheme = scheme;
    286     }
    287
    288     /* ------------------------------------------------------------ */
    289     /**
    290      * @return
    291      */
    292     public Buffer getScheme()
    293     {
    294         return _scheme;
    295     }
    296
    297     /* ------------------------------------------------------------ */
    298     /**
    299      * @param version as integer, 9, 10 or 11 for 0.9, 1.0 or 1.1
    300      */
    301     public void setVersion(int version)
    302     {
    303         _version = version;
    304     }
    305
    306     /* ------------------------------------------------------------ */
    307     public void setVersion(String version)
    308     {
    309         CachedBuffer v = HttpVersions.CACHE.get(version);
    310         if (v == null)
    311             _version = 10;
    312         else
    313             _version = v.getOrdinal();
    314     }
    315
    316     /* ------------------------------------------------------------ */
    317     /**
    318      * @return
    319      */
    320     public int getVersion()
    321     {
    322         return _version;
    323     }
    324
    325     /* ------------------------------------------------------------ */
    326     /**
    327      * @param method
    328      */
    329     public void setMethod(String method)
    330     {
    331         _method = method;
    332     }
    333
    334     /* ------------------------------------------------------------ */
    335     /**
    336      * @return
    337      */
    338     public String getMethod()
    339     {
    340         return _method;
    341     }
    342
    343     /* ------------------------------------------------------------ */
    344     /**
    345      * @return
    346      */
    347     public String getURI()
    348     {
    349         return _uri;
    350     }
    351
    352     /* ------------------------------------------------------------ */
    353     /**
    354      * @param uri
    355      */
    356     public void setURI(String uri)
    357     {
    358         _uri = uri;
    359     }
    360
    361     /* ------------------------------------------------------------ */
    362     /**
    363      * @param name
    364      * @param value
    365      */
    366     public void addRequestHeader(String name, String value)
    367     {
    368         getRequestFields().add(name,value);
    369     }
    370
    371     /* ------------------------------------------------------------ */
    372     /**
    373      * @param name
    374      * @param value
    375      */
    376     public void addRequestHeader(Buffer name, Buffer value)
    377     {
    378         getRequestFields().add(name,value);
    379     }
    380
    381     /* ------------------------------------------------------------ */
    382     /**
    383      * @param name
    384      * @param value
    385      */
    386     public void setRequestHeader(String name, String value)
    387     {
    388         getRequestFields().put(name,value);
    389     }
    390
    391     /* ------------------------------------------------------------ */
    392     /**
    393      * @param name
    394      * @param value
    395      */
    396     public void setRequestHeader(Buffer name, Buffer value)
    397     {
    398         getRequestFields().put(name,value);
    399     }
    400
    401     /* ------------------------------------------------------------ */
    402     /**
    403      * @param value
    404      */
    405     public void setRequestContentType(String value)
    406     {
    407         getRequestFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,value);
    408     }
    409
    410     /* ------------------------------------------------------------ */
    411     /**
    412      * @return
    413      */
    414     public HttpFields getRequestFields()
    415     {
    416         return _requestFields;
    417     }
    418
    419     /* ------------------------------------------------------------ */
    420     /* ------------------------------------------------------------ */
    421     /* ------------------------------------------------------------ */
    422     // methods to commit and/or send the request
    423
    424     /* ------------------------------------------------------------ */
    425     /**
    426      * @param requestContent
    427      */
    428     public void setRequestContent(Buffer requestContent)
    429     {
    430         _requestContent = requestContent;
    431     }
    432
    433     /* ------------------------------------------------------------ */
    434     /**
    435      * @param in
    436      */
    437     public void setRequestContentSource(InputStream in)
    438     {
    439         _requestContentSource = in;
    440         if (_requestContentSource.markSupported())
    441             _requestContentSource.mark(Integer.MAX_VALUE);
    442     }
    443
    444     /* ------------------------------------------------------------ */
    445     public InputStream getRequestContentSource()
    446     {
    447         return _requestContentSource;
    448     }
    449
    450     /* ------------------------------------------------------------ */
    451     public Buffer getRequestContentChunk() throws IOException
    452     {
    453         synchronized (this)
    454         {
    455             if (_requestContentChunk == null)
    456                 _requestContentChunk = new ByteArrayBuffer(4096); // TODO configure
    457             else
    458             {
    459                 if (_requestContentChunk.hasContent())
    460                     throw new IllegalStateException();
    461                 _requestContentChunk.clear();
    462             }
    463
    464             int read = _requestContentChunk.capacity();
    465             int length = _requestContentSource.read(_requestContentChunk.array(),0,read);
    466             if (length >= 0)
    467             {
    468                 _requestContentChunk.setPutIndex(length);
    469                 return _requestContentChunk;
    470             }
    471             return null;
    472         }
    473     }
    474
    475     /* ------------------------------------------------------------ */
    476     public Buffer getRequestContent()
    477     {
    478         return _requestContent;
    479     }
    480
    481     /* ------------------------------------------------------------ */
    482     public boolean getRetryStatus()
    483     {
    484         return _retryStatus;
    485     }
    486
    487     /* ------------------------------------------------------------ */
    488     public void setRetryStatus( boolean retryStatus )
    489     {
    490         _retryStatus = retryStatus;
    491     }
    492
    493     /* ------------------------------------------------------------ */
    494     /** Cancel this exchange
    495      * Currently this implementation does nothing.
    496      */
    497     public void cancel()
    498     {
    499
    500     }
    501
    502     /* ------------------------------------------------------------ */
    503     public String toString()
    504     {
    505         return getClass().getName() + "@" + hashCode() + "=" + _method + "//" + _address + _uri + "#" + _status;
    506     }
    507
    508
    509
    510     /* ------------------------------------------------------------ */
    511     /* ------------------------------------------------------------ */
    512     /* ------------------------------------------------------------ */
    513     // methods to handle response
    514
    515     /**
    516      * Called when the request headers has been sent
    517      * @throws IOException
    518      */
    519     protected void onRequestCommitted() throws IOException
    520     {
    521     }
    522
    523     /**
    524      * Called when the request and it's body have been sent.
    525      * @throws IOException
    526      */
    527     protected void onRequestComplete() throws IOException
    528     {
    529     }
    530
    531     /**
    532      * Called when a response status line has been received.
    533      * @param version HTTP version
    534      * @param status HTTP status code
    535      * @param reason HTTP status code reason string
    536      * @throws IOException
    537      */
    538     protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
    539     {
    540     }
    541
    542     /**
    543      * Called for each response header received
    544      * @param name header name
    545      * @param value header value
    546      * @throws IOException
    547      */
    548     protected void onResponseHeader(Buffer name, Buffer value) throws IOException
    549     {
    550     }
    551
    552     /**
    553      * Called when the response header has been completely received.
    554      * @throws IOException
    555      */
    556     protected void onResponseHeaderComplete() throws IOException
    557     {
    558     }
    559
    560     /**
    561      * Called for each chunk of the response content received.
    562      * @param content
    563      * @throws IOException
    564      */
    565     protected void onResponseContent(Buffer content) throws IOException
    566     {
    567     }
    568
    569     /**
    570      * Called when the entire response has been received
    571      * @throws IOException
    572      */
    573     protected void onResponseComplete() throws IOException
    574     {
    575     }
    576
    577     /**
    578      * Called when an exception was thrown during an attempt to open a connection
    579      * @param ex
    580      */
    581     protected void onConnectionFailed(Throwable ex)
    582     {
    583         Log.warn("CONNECTION FAILED on " + this,ex);
    584     }
    585
    586     /**
    587      * Called when any other exception occurs during handling for the exchange
    588      * @param ex
    589      */
    590     protected void onException(Throwable ex)
    591     {
    592         Log.warn("EXCEPTION on " + this,ex);
    593     }
    594
    595     /**
    596      * Called when no response has been received within the timeout.
    597      */
    598     protected void onExpire()
    599     {
    600         Log.warn("EXPIRED " + this);
    601     }
    602
    603     /**
    604      * Called when the request is retried (due to failures or authentication).
    605      * Implementations may need to reset any consumable content that needs to
    606      * be sent.
    607      * @throws IOException
    608      */
    609     protected void onRetry() throws IOException
    610     {
    611         if (_requestContentSource != null)
    612         {
    613             if (_requestContentSource.markSupported())
    614             {
    615                 _requestContent = null;
    616                 _requestContentSource.reset();
    617             }
    618             else
    619             {
    620                 throw new IOException("Unsupported retry attempt");
    621             }
    622         }
    623     }
    624
    625     /**
    626      * true of the exchange should have listeners configured for it by the destination
    627      *
    628      * false if this is being managed elsewhere
    629      *
    630      * @return
    631      */
    632     public boolean configureListeners()
    633     {
    634         return _configureListeners;
    635     }
    636
    637     public void setConfigureListeners(boolean autoConfigure )
    638     {
    639         this._configureListeners = autoConfigure;
    640     }
    641
    642     private class Listener implements HttpEventListener
    643     {
    644         public void onConnectionFailed(Throwable ex)
    645         {
    646             try
    647             {
    648                 HttpExchange.this.onConnectionFailed(ex);
    649             }
    650             finally
    651             {
    652                 synchronized(HttpExchange.this)
    653                 {
    654                     _onDone=true;
    655                     HttpExchange.this.notifyAll();
    656                 }
    657             }
    658         }
    659
    660         public void onException(Throwable ex)
    661         {
    662             try
    663             {
    664                 HttpExchange.this.onException(ex);
    665             }
    666             finally
    667             {
    668                 synchronized(HttpExchange.this)
    669                 {
    670                     _onDone=true;
    671                     HttpExchange.this.notifyAll();
    672                 }
    673             }
    674         }
    675
    676         public void onExpire()
    677         {
    678             try
    679             {
    680                 HttpExchange.this.onExpire();
    681             }
    682             finally
    683             {
    684                 synchronized(HttpExchange.this)
    685                 {
    686                     _onDone=true;
    687                     HttpExchange.this.notifyAll();
    688                 }
    689             }
    690         }
    691
    692         public void onRequestCommitted() throws IOException
    693         {
    694             HttpExchange.this.onRequestCommitted();
    695         }
    696
    697         public void onRequestComplete() throws IOException
    698         {
    699             try
    700             {
    701                 HttpExchange.this.onRequestComplete();
    702             }
    703             finally
    704             {
    705                 synchronized(HttpExchange.this)
    706                 {
    707                     _onRequestCompleteDone=true;
    708                     _onDone=_onResponseCompleteDone;
    709                     HttpExchange.this.notifyAll();
    710                 }
    711             }
    712         }
    713
    714         public void onResponseComplete() throws IOException
    715         {
    716             try
    717             {
    718                 HttpExchange.this.onResponseComplete();
    719             }
    720             finally
    721             {
    722                 synchronized(HttpExchange.this)
    723                 {
    724                     _onResponseCompleteDone=true;
    725                     _onDone=_onRequestCompleteDone;
    726                     HttpExchange.this.notifyAll();
    727                 }
    728             }
    729         }
    730
    731         public void onResponseContent(Buffer content) throws IOException
    732         {
    733             HttpExchange.this.onResponseContent(content);
    734         }
    735
    736         public void onResponseHeader(Buffer name, Buffer value) throws IOException
    737         {
    738             HttpExchange.this.onResponseHeader(name,value);
    739         }
    740
    741         public void onResponseHeaderComplete() throws IOException
    742         {
    743             HttpExchange.this.onResponseHeaderComplete();
    744         }
    745
    746         public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
    747         {
    748             HttpExchange.this.onResponseStatus(version,status,reason);
    749         }
    750
    751         public void onRetry()
    752         {
    753             HttpExchange.this.setRetryStatus( true );
    754             try
    755             {
    756                 HttpExchange.this.onRetry();
    757             }
    758             catch (IOException e)
    759             {
    760                 onException(e);
    761             }
    762         }
    763     }
    764
    765     /**
    766      * @deprecated use {@link org.mortbay.jetty.client.CachedExchange}
    767      *
    768      */
    769     public static class CachedExchange extends org.mortbay.jetty.client.CachedExchange
    770     {
    771         public CachedExchange(boolean cacheFields)
    772         {
    773             super(cacheFields);
    774         }
    775     }
    776
    777     /**
    778      * @deprecated use {@link org.mortbay.jetty.client.ContentExchange}
    779      *
    780      */
    781     public static class ContentExchange extends org.mortbay.jetty.client.ContentExchange
    782     {
    783
    784     }
    785
    786
    787
    788 }