View Javadoc

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