View Javadoc

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