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