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         LOG.debug("URI = {}",uri.toASCIIString());
597 
598         String scheme = uri.getScheme();
599         int port = uri.getPort();
600         if (port <= 0)
601             port = "https".equalsIgnoreCase(scheme)?443:80;
602 
603         setScheme(scheme);
604         setAddress(new Address(uri.getHost(),port));
605 
606         HttpURI httpUri = new HttpURI(uri);
607         String completePath = httpUri.getCompletePath();
608         setRequestURI(completePath == null?"/":completePath);
609     }
610 
611     /**
612      * Adds the specified request header
613      *
614      * @param name
615      *            the header name
616      * @param value
617      *            the header value
618      */
619     public void addRequestHeader(String name, String value)
620     {
621         getRequestFields().add(name,value);
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(Buffer name, Buffer value)
633     {
634         getRequestFields().add(name,value);
635     }
636 
637     /**
638      * Sets the specified request header
639      *
640      * @param name
641      *            the header name
642      * @param value
643      *            the header value
644      */
645     public void setRequestHeader(String name, String value)
646     {
647         getRequestFields().put(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(Buffer name, Buffer value)
659     {
660         getRequestFields().put(name,value);
661     }
662 
663     /**
664      * @param value
665      *            the content type of the request
666      */
667     public void setRequestContentType(String value)
668     {
669         getRequestFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,value);
670     }
671 
672     /**
673      * @return the request headers
674      */
675     public HttpFields getRequestFields()
676     {
677         return _requestFields;
678     }
679 
680     /**
681      * @param requestContent
682      *            the request content
683      */
684     public void setRequestContent(Buffer requestContent)
685     {
686         _requestContent = requestContent;
687     }
688 
689     /**
690      * @param stream
691      *            the request content as a stream
692      */
693     public void setRequestContentSource(InputStream stream)
694     {
695         _requestContentSource = stream;
696         if (_requestContentSource != null && _requestContentSource.markSupported())
697             _requestContentSource.mark(Integer.MAX_VALUE);
698     }
699 
700     /**
701      * @return the request content as a stream
702      */
703     public InputStream getRequestContentSource()
704     {
705         return _requestContentSource;
706     }
707 
708     public Buffer getRequestContentChunk() throws IOException
709     {
710         synchronized (this)
711         {
712             if (_requestContentChunk == null)
713                 _requestContentChunk = new ByteArrayBuffer(4096); // TODO configure
714             else
715             {
716                 if (_requestContentChunk.hasContent())
717                     throw new IllegalStateException();
718                 _requestContentChunk.clear();
719             }
720 
721             int read = _requestContentChunk.capacity();
722             int length = _requestContentSource.read(_requestContentChunk.array(),0,read);
723             if (length >= 0)
724             {
725                 _requestContentChunk.setPutIndex(length);
726                 return _requestContentChunk;
727             }
728             return null;
729         }
730     }
731 
732     /**
733      * @return the request content
734      */
735     public Buffer getRequestContent()
736     {
737         return _requestContent;
738     }
739 
740     /**
741      * @return whether a retry will be attempted or not
742      */
743     public boolean getRetryStatus()
744     {
745         return _retryStatus;
746     }
747 
748     /**
749      * @param retryStatus
750      *            whether a retry will be attempted or not
751      */
752     public void setRetryStatus(boolean retryStatus)
753     {
754         _retryStatus = retryStatus;
755     }
756 
757     /**
758      * Initiates the cancelling of this exchange. The status of the exchange is set to {@link #STATUS_CANCELLING}. Cancelling the exchange is an asynchronous
759      * operation with respect to the request/response, and as such checking the request/response status of a cancelled exchange may return undefined results
760      * (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
761      * status (see {@link #getStatus()}) is {@link #STATUS_CANCELLED}, and this can be waited using {@link #waitForDone()}.
762      */
763     public void cancel()
764     {
765         setStatus(STATUS_CANCELLING);
766         abort();
767     }
768 
769     private void done()
770     {
771         synchronized (this)
772         {
773             disassociate();
774             _onDone = true;
775             notifyAll();
776         }
777     }
778 
779     private void abort()
780     {
781         HttpConnection httpConnection = _connection;
782         if (httpConnection != null)
783         {
784             try
785             {
786                 // Closing the connection here will cause the connection
787                 // to be returned in HttpConnection.handle()
788                 httpConnection.close();
789             }
790             catch (IOException x)
791             {
792                 LOG.debug(x);
793             }
794             finally
795             {
796                 disassociate();
797             }
798         }
799     }
800 
801     void associate(HttpConnection connection)
802     {
803         if (connection.getEndPoint().getLocalHost() != null)
804             _localAddress = new Address(connection.getEndPoint().getLocalHost(),connection.getEndPoint().getLocalPort());
805 
806         _connection = connection;
807         if (getStatus() == STATUS_CANCELLING)
808             abort();
809     }
810 
811     boolean isAssociated()
812     {
813         return this._connection != null;
814     }
815 
816     HttpConnection disassociate()
817     {
818         HttpConnection result = _connection;
819         this._connection = null;
820         if (getStatus() == STATUS_CANCELLING)
821             setStatus(STATUS_CANCELLED);
822         return result;
823     }
824 
825     public static String toState(int s)
826     {
827         String state;
828         switch (s)
829         {
830             case STATUS_START:
831                 state = "START";
832                 break;
833             case STATUS_WAITING_FOR_CONNECTION:
834                 state = "CONNECTING";
835                 break;
836             case STATUS_WAITING_FOR_COMMIT:
837                 state = "CONNECTED";
838                 break;
839             case STATUS_SENDING_REQUEST:
840                 state = "SENDING";
841                 break;
842             case STATUS_WAITING_FOR_RESPONSE:
843                 state = "WAITING";
844                 break;
845             case STATUS_PARSING_HEADERS:
846                 state = "HEADERS";
847                 break;
848             case STATUS_PARSING_CONTENT:
849                 state = "CONTENT";
850                 break;
851             case STATUS_COMPLETED:
852                 state = "COMPLETED";
853                 break;
854             case STATUS_EXPIRED:
855                 state = "EXPIRED";
856                 break;
857             case STATUS_EXCEPTED:
858                 state = "EXCEPTED";
859                 break;
860             case STATUS_CANCELLING:
861                 state = "CANCELLING";
862                 break;
863             case STATUS_CANCELLED:
864                 state = "CANCELLED";
865                 break;
866             default:
867                 state = "UNKNOWN";
868         }
869         return state;
870     }
871 
872     @Override
873     public String toString()
874     {
875         String state=toState(getStatus());
876         long now=System.currentTimeMillis();
877         long forMs = now -_lastStateChange;
878         String s= _lastState>=0
879             ?String.format("%s@%x=%s//%s%s#%s(%dms)->%s(%dms)",getClass().getSimpleName(),hashCode(),_method,_address,_uri,toState(_lastState),_lastStatePeriod,state,forMs)
880             :String.format("%s@%x=%s//%s%s#%s(%dms)",getClass().getSimpleName(),hashCode(),_method,_address,_uri,state,forMs);
881         if (getStatus()>=STATUS_SENDING_REQUEST && _sent>0)
882             s+="sent="+(now-_sent)+"ms";
883         return s;
884     }
885 
886     /**
887      */
888     protected Connection onSwitchProtocol(EndPoint endp) throws IOException
889     {
890         return null;
891     }
892 
893     /**
894      * Callback called when the request headers have been sent to the server. This implementation does nothing.
895      *
896      * @throws IOException
897      *             allowed to be thrown by overriding code
898      */
899     protected void onRequestCommitted() throws IOException
900     {
901     }
902 
903     /**
904      * Callback called when the request and its body 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 onRequestComplete() throws IOException
910     {
911     }
912 
913     /**
914      * Callback called when a response status line has been received from the server. This implementation does nothing.
915      *
916      * @param version
917      *            the HTTP version
918      * @param status
919      *            the HTTP status code
920      * @param reason
921      *            the HTTP status reason string
922      * @throws IOException
923      *             allowed to be thrown by overriding code
924      */
925     protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
926     {
927     }
928 
929     /**
930      * Callback called for each response header received from the server. This implementation does nothing.
931      *
932      * @param name
933      *            the header name
934      * @param value
935      *            the header value
936      * @throws IOException
937      *             allowed to be thrown by overriding code
938      */
939     protected void onResponseHeader(Buffer name, Buffer value) throws IOException
940     {
941     }
942 
943     /**
944      * Callback called when the response headers have been completely received from the server. This implementation does nothing.
945      *
946      * @throws IOException
947      *             allowed to be thrown by overriding code
948      */
949     protected void onResponseHeaderComplete() throws IOException
950     {
951     }
952 
953     /**
954      * Callback called for each chunk of the response content received from the server. This implementation does nothing.
955      *
956      * @param content
957      *            the buffer holding the content chunk
958      * @throws IOException
959      *             allowed to be thrown by overriding code
960      */
961     protected void onResponseContent(Buffer content) throws IOException
962     {
963     }
964 
965     /**
966      * Callback called when the entire response has been received from the server This implementation does nothing.
967      *
968      * @throws IOException
969      *             allowed to be thrown by overriding code
970      */
971     protected void onResponseComplete() throws IOException
972     {
973     }
974 
975     /**
976      * Callback called when an exception was thrown during an attempt to establish the connection with the server (for example the server is not listening).
977      * This implementation logs a warning.
978      *
979      * @param x
980      *            the exception thrown attempting to establish the connection with the server
981      */
982     protected void onConnectionFailed(Throwable x)
983     {
984         LOG.warn("CONNECTION FAILED " + this,x);
985     }
986 
987     /**
988      * Callback called when any other exception occurs during the handling of this exchange. This implementation logs a warning.
989      *
990      * @param x
991      *            the exception thrown during the handling of this exchange
992      */
993     protected void onException(Throwable x)
994     {
995         LOG.warn("EXCEPTION " + this,x);
996     }
997 
998     /**
999      * Callback called when no response has been received within the timeout. This implementation logs a warning.
1000      */
1001     protected void onExpire()
1002     {
1003         LOG.warn("EXPIRED " + this);
1004     }
1005 
1006     /**
1007      * Callback called when the request is retried (due to failures or authentication). Implementations must reset any consumable content that needs to be sent.
1008      *
1009      * @throws IOException
1010      *             allowed to be thrown by overriding code
1011      */
1012     protected void onRetry() throws IOException
1013     {
1014         if (_requestContentSource != null)
1015         {
1016             if (_requestContentSource.markSupported())
1017             {
1018                 _requestContent = null;
1019                 _requestContentSource.reset();
1020             }
1021             else
1022             {
1023                 throw new IOException("Unsupported retry attempt");
1024             }
1025         }
1026     }
1027 
1028     /**
1029      * @return true if the exchange should have listeners configured for it by the destination, false if this is being managed elsewhere
1030      * @see #setConfigureListeners(boolean)
1031      */
1032     public boolean configureListeners()
1033     {
1034         return _configureListeners;
1035     }
1036 
1037     /**
1038      * @param autoConfigure
1039      *            whether the listeners are configured by the destination or elsewhere
1040      */
1041     public void setConfigureListeners(boolean autoConfigure)
1042     {
1043         this._configureListeners = autoConfigure;
1044     }
1045 
1046     protected void scheduleTimeout(final HttpDestination destination)
1047     {
1048         assert _timeoutTask == null;
1049 
1050         _timeoutTask = new Timeout.Task()
1051         {
1052             @Override
1053             public void expired()
1054             {
1055                 HttpExchange.this.expire(destination);
1056             }
1057         };
1058 
1059         HttpClient httpClient = destination.getHttpClient();
1060         long timeout = getTimeout();
1061         if (timeout > 0)
1062             httpClient.schedule(_timeoutTask,timeout);
1063         else
1064             httpClient.schedule(_timeoutTask);
1065     }
1066 
1067     protected void cancelTimeout(HttpClient httpClient)
1068     {
1069         Timeout.Task task = _timeoutTask;
1070         if (task != null)
1071             httpClient.cancel(task);
1072         _timeoutTask = null;
1073     }
1074 
1075     private class Listener implements HttpEventListener
1076     {
1077         public void onConnectionFailed(Throwable ex)
1078         {
1079             try
1080             {
1081                 HttpExchange.this.onConnectionFailed(ex);
1082             }
1083             finally
1084             {
1085                 done();
1086             }
1087         }
1088 
1089         public void onException(Throwable ex)
1090         {
1091             try
1092             {
1093                 HttpExchange.this.onException(ex);
1094             }
1095             finally
1096             {
1097                 done();
1098             }
1099         }
1100 
1101         public void onExpire()
1102         {
1103             try
1104             {
1105                 HttpExchange.this.onExpire();
1106             }
1107             finally
1108             {
1109                 done();
1110             }
1111         }
1112 
1113         public void onRequestCommitted() throws IOException
1114         {
1115             HttpExchange.this.onRequestCommitted();
1116         }
1117 
1118         public void onRequestComplete() throws IOException
1119         {
1120             try
1121             {
1122                 HttpExchange.this.onRequestComplete();
1123             }
1124             finally
1125             {
1126                 synchronized (HttpExchange.this)
1127                 {
1128                     _onRequestCompleteDone = true;
1129                     // Member _onDone may already be true, for example
1130                     // because the exchange expired or has been canceled
1131                     _onDone |= _onResponseCompleteDone;
1132                     if (_onDone)
1133                         disassociate();
1134                     HttpExchange.this.notifyAll();
1135                 }
1136             }
1137         }
1138 
1139         public void onResponseComplete() throws IOException
1140         {
1141             try
1142             {
1143                 HttpExchange.this.onResponseComplete();
1144             }
1145             finally
1146             {
1147                 synchronized (HttpExchange.this)
1148                 {
1149                     _onResponseCompleteDone = true;
1150                     // Member _onDone may already be true, for example
1151                     // because the exchange expired or has been canceled
1152                     _onDone |= _onRequestCompleteDone;
1153                     if (_onDone)
1154                         disassociate();
1155                     HttpExchange.this.notifyAll();
1156                 }
1157             }
1158         }
1159 
1160         public void onResponseContent(Buffer content) throws IOException
1161         {
1162             HttpExchange.this.onResponseContent(content);
1163         }
1164 
1165         public void onResponseHeader(Buffer name, Buffer value) throws IOException
1166         {
1167             HttpExchange.this.onResponseHeader(name,value);
1168         }
1169 
1170         public void onResponseHeaderComplete() throws IOException
1171         {
1172             HttpExchange.this.onResponseHeaderComplete();
1173         }
1174 
1175         public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
1176         {
1177             HttpExchange.this.onResponseStatus(version,status,reason);
1178         }
1179 
1180         public void onRetry()
1181         {
1182             HttpExchange.this.setRetryStatus(true);
1183             try
1184             {
1185                 HttpExchange.this.onRetry();
1186             }
1187             catch (IOException e)
1188             {
1189                 LOG.debug(e);
1190             }
1191         }
1192     }
1193 
1194     /**
1195      * @deprecated use {@link org.eclipse.jetty.client.CachedExchange} instead
1196      */
1197     @Deprecated
1198     public static class CachedExchange extends org.eclipse.jetty.client.CachedExchange
1199     {
1200         public CachedExchange(boolean cacheFields)
1201         {
1202             super(cacheFields);
1203         }
1204     }
1205 
1206     /**
1207      * @deprecated use {@link org.eclipse.jetty.client.ContentExchange} instead
1208      */
1209     @Deprecated
1210     public static class ContentExchange extends org.eclipse.jetty.client.ContentExchange
1211     {
1212     }
1213 }