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