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.ByteArrayBuffer;
    24  import org.mortbay.jetty.HttpFields;
    25  import org.mortbay.jetty.HttpHeaders;
    26  import org.mortbay.jetty.HttpMethods;
    27  import org.mortbay.jetty.HttpSchemes;
    28  import org.mortbay.jetty.HttpURI;
    29  import org.mortbay.jetty.HttpVersions;
    30  import org.mortbay.log.Log;
    31
    32
    33  /**
    34   * An HTTP client API that encapsulates Exchange with a HTTP server.
    35   *
    36   * This object encapsulates:<ul>
    37   * <li>The HTTP server. (see {@link #setAddress(InetSocketAddress)} or {@link #setURL(String)})
    38   * <li>The HTTP request method, URI and HTTP version (see {@link #setMethod(String)}, {@link #setURI(String)}, and {@link #setVersion(int)}
    39   * <li>The Request headers (see {@link #addRequestHeader(String, String)} or {@link #setRequestHeader(String, String)})
    40   * <li>The Request content (see {@link #setRequestContent(Buffer)} or {@link #setRequestContentSource(InputStream)})
    41   * <li>The status of the exchange (see {@link #getStatus()})
    42   * <li>Callbacks to handle state changes (see the onXxx methods such as {@link #onRequestComplete()} or {@link #onResponseComplete()})
    43   * <li>The ability to intercept callbacks (see {@link #setEventListener(HttpEventListener)}
    44   * </ul>
    45   *
    46   * The HttpExchange class is intended to be used by a developer wishing to have close asynchronous
    47   * interaction with the the exchange.  Typically a developer will extend the HttpExchange class with a derived
    48   * class that implements some or all of the onXxx callbacks.  There are also some predefined HttpExchange subtypes
    49   * that can be used as a basis (see {@link ContentExchange} and {@link CachedExchange}.
    50   *
    51   * <p>Typically the HttpExchange is passed to a the {@link HttpClient#send(HttpExchange)} method, which in
    52   * turn selects a {@link HttpDestination} and calls it's {@link HttpDestination#send(HttpExchange), which
    53   * then creates or selects a {@link HttpConnection} and calls its {@link HttpConnection#send(HttpExchange).
    54   * A developer may wish to directly call send on the destination or connection if they wish to bypass
    55   * some handling provided (eg Cookie handling in the HttpDestination).
    56   *
    57   * <p>In some circumstances, the HttpClient or HttpDestination may wish to retry a HttpExchange (eg. failed
    58   * pipeline request, authentication retry or redirection).  In such cases, the HttpClient and/or HttpDestination
    59   * may insert their own HttpExchangeListener to intercept and filter the call backs intended for the
    60   * HttpExchange.
    61   *
    62   * @author gregw
    63   * @author Guillaume Nodet
    64   */
    65  public class HttpExchange
    66  {
    67      public static final int STATUS_START = 0;
    68      public static final int STATUS_WAITING_FOR_CONNECTION = 1;
    69      public static final int STATUS_WAITING_FOR_COMMIT = 2;
    70      public static final int STATUS_SENDING_REQUEST = 3;
    71      public static final int STATUS_WAITING_FOR_RESPONSE = 4;
    72      public static final int STATUS_PARSING_HEADERS = 5;
    73      public static final int STATUS_PARSING_CONTENT = 6;
    74      public static final int STATUS_COMPLETED = 7;
    75      public static final int STATUS_EXPIRED = 8;
    76      public static final int STATUS_EXCEPTED = 9;
    77
    78      String _method = HttpMethods.GET;
    79      Buffer _scheme = HttpSchemes.HTTP_BUFFER;
    80      String _uri;
    81      int _version = HttpVersions.HTTP_1_1_ORDINAL;
    82      Address _address;
    83      HttpFields _requestFields = new HttpFields();
    84      Buffer _requestContent;
    85      InputStream _requestContentSource;
    86      long _timeout = -1;
    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     public long getTimeout()
    495     {
    496         return _timeout;
    497     }
    498
    499     /* ------------------------------------------------------------ */
    500     public void setTimeout(long timeout)
    501     {
    502         _timeout = timeout;
    503     }
    504
    505     /* ------------------------------------------------------------ */
    506     /** Cancel this exchange
    507      * Currently this implementation does nothing.
    508      */
    509     public void cancel()
    510     {
    511
    512     }
    513
    514     /* ------------------------------------------------------------ */
    515     public String toString()
    516     {
    517         return getClass().getName() + "@" + hashCode() + "=" + _method + "//" + _address + _uri + "#" + _status;
    518     }
    519
    520
    521
    522     /* ------------------------------------------------------------ */
    523     /* ------------------------------------------------------------ */
    524     /* ------------------------------------------------------------ */
    525     // methods to handle response
    526
    527     /**
    528      * Called when the request headers has been sent
    529      * @throws IOException
    530      */
    531     protected void onRequestCommitted() throws IOException
    532     {
    533     }
    534
    535     /**
    536      * Called when the request and it's body have been sent.
    537      * @throws IOException
    538      */
    539     protected void onRequestComplete() throws IOException
    540     {
    541     }
    542
    543     /**
    544      * Called when a response status line has been received.
    545      * @param version HTTP version
    546      * @param status HTTP status code
    547      * @param reason HTTP status code reason string
    548      * @throws IOException
    549      */
    550     protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
    551     {
    552     }
    553
    554     /**
    555      * Called for each response header received
    556      * @param name header name
    557      * @param value header value
    558      * @throws IOException
    559      */
    560     protected void onResponseHeader(Buffer name, Buffer value) throws IOException
    561     {
    562     }
    563
    564     /**
    565      * Called when the response header has been completely received.
    566      * @throws IOException
    567      */
    568     protected void onResponseHeaderComplete() throws IOException
    569     {
    570     }
    571
    572     /**
    573      * Called for each chunk of the response content received.
    574      * @param content
    575      * @throws IOException
    576      */
    577     protected void onResponseContent(Buffer content) throws IOException
    578     {
    579     }
    580
    581     /**
    582      * Called when the entire response has been received
    583      * @throws IOException
    584      */
    585     protected void onResponseComplete() throws IOException
    586     {
    587     }
    588
    589     /**
    590      * Called when an exception was thrown during an attempt to open a connection
    591      * @param ex
    592      */
    593     protected void onConnectionFailed(Throwable ex)
    594     {
    595         Log.warn("CONNECTION FAILED on " + this,ex);
    596     }
    597
    598     /**
    599      * Called when any other exception occurs during handling for the exchange
    600      * @param ex
    601      */
    602     protected void onException(Throwable ex)
    603     {
    604         Log.warn("EXCEPTION on " + this,ex);
    605     }
    606
    607     /**
    608      * Called when no response has been received within the timeout.
    609      */
    610     protected void onExpire()
    611     {
    612         Log.warn("EXPIRED " + this);
    613     }
    614
    615     /**
    616      * Called when the request is retried (due to failures or authentication).
    617      * Implementations may need to reset any consumable content that needs to
    618      * be sent.
    619      * @throws IOException
    620      */
    621     protected void onRetry() throws IOException
    622     {
    623         if (_requestContentSource != null)
    624         {
    625             if (_requestContentSource.markSupported())
    626             {
    627                 _requestContent = null;
    628                 _requestContentSource.reset();
    629             }
    630             else
    631             {
    632                 throw new IOException("Unsupported retry attempt");
    633             }
    634         }
    635     }
    636
    637     /**
    638      * true of the exchange should have listeners configured for it by the destination
    639      *
    640      * false if this is being managed elsewhere
    641      *
    642      * @return
    643      */
    644     public boolean configureListeners()
    645     {
    646         return _configureListeners;
    647     }
    648
    649     public void setConfigureListeners(boolean autoConfigure )
    650     {
    651         this._configureListeners = autoConfigure;
    652     }
    653
    654     private class Listener implements HttpEventListener
    655     {
    656         public void onConnectionFailed(Throwable ex)
    657         {
    658             try
    659             {
    660                 HttpExchange.this.onConnectionFailed(ex);
    661             }
    662             finally
    663             {
    664                 synchronized(HttpExchange.this)
    665                 {
    666                     _onDone=true;
    667                     HttpExchange.this.notifyAll();
    668                 }
    669             }
    670         }
    671
    672         public void onException(Throwable ex)
    673         {
    674             try
    675             {
    676                 HttpExchange.this.onException(ex);
    677             }
    678             finally
    679             {
    680                 synchronized(HttpExchange.this)
    681                 {
    682                     _onDone=true;
    683                     HttpExchange.this.notifyAll();
    684                 }
    685             }
    686         }
    687
    688         public void onExpire()
    689         {
    690             try
    691             {
    692                 HttpExchange.this.onExpire();
    693             }
    694             finally
    695             {
    696                 synchronized(HttpExchange.this)
    697                 {
    698                     _onDone=true;
    699                     HttpExchange.this.notifyAll();
    700                 }
    701             }
    702         }
    703
    704         public void onRequestCommitted() throws IOException
    705         {
    706             HttpExchange.this.onRequestCommitted();
    707         }
    708
    709         public void onRequestComplete() throws IOException
    710         {
    711             try
    712             {
    713                 HttpExchange.this.onRequestComplete();
    714             }
    715             finally
    716             {
    717                 synchronized(HttpExchange.this)
    718                 {
    719                     _onRequestCompleteDone=true;
    720                     _onDone=_onResponseCompleteDone;
    721                     HttpExchange.this.notifyAll();
    722                 }
    723             }
    724         }
    725
    726         public void onResponseComplete() throws IOException
    727         {
    728             try
    729             {
    730                 HttpExchange.this.onResponseComplete();
    731             }
    732             finally
    733             {
    734                 synchronized(HttpExchange.this)
    735                 {
    736                     _onResponseCompleteDone=true;
    737                     _onDone=_onRequestCompleteDone;
    738                     HttpExchange.this.notifyAll();
    739                 }
    740             }
    741         }
    742
    743         public void onResponseContent(Buffer content) throws IOException
    744         {
    745             HttpExchange.this.onResponseContent(content);
    746         }
    747
    748         public void onResponseHeader(Buffer name, Buffer value) throws IOException
    749         {
    750             HttpExchange.this.onResponseHeader(name,value);
    751         }
    752
    753         public void onResponseHeaderComplete() throws IOException
    754         {
    755             HttpExchange.this.onResponseHeaderComplete();
    756         }
    757
    758         public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
    759         {
    760             HttpExchange.this.onResponseStatus(version,status,reason);
    761         }
    762
    763         public void onRetry()
    764         {
    765             HttpExchange.this.setRetryStatus( true );
    766             try
    767             {
    768                 HttpExchange.this.onRetry();
    769             }
    770             catch (IOException e)
    771             {
    772                 onException(e);
    773             }
    774         }
    775     }
    776
    777     /**
    778      * @deprecated use {@link org.mortbay.jetty.client.CachedExchange}
    779      *
    780      */
    781     public static class CachedExchange extends org.mortbay.jetty.client.CachedExchange
    782     {
    783         public CachedExchange(boolean cacheFields)
    784         {
    785             super(cacheFields);
    786         }
    787     }
    788
    789     /**
    790      * @deprecated use {@link org.mortbay.jetty.client.ContentExchange}
    791      *
    792      */
    793     public static class ContentExchange extends org.mortbay.jetty.client.ContentExchange
    794     {
    795
    796     }
    797
    798
    799
    800 }