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