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