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