1 // ========================================================================
2 // Copyright 2006-2007 Mort Bay Consulting Pty. Ltd.
3 // ------------------------------------------------------------------------
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 // ========================================================================
14 
15 package org.mortbay.jetty.client;
16 
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.net.InetSocketAddress;
20 
21 import org.mortbay.io.Buffer;
22 import org.mortbay.io.BufferCache.CachedBuffer;
23 import org.mortbay.io.ByteArrayBuffer;
24 import org.mortbay.jetty.HttpFields;
25 import org.mortbay.jetty.HttpHeaders;
26 import org.mortbay.jetty.HttpMethods;
27 import org.mortbay.jetty.HttpSchemes;
28 import org.mortbay.jetty.HttpURI;
29 import org.mortbay.jetty.HttpVersions;
30 import org.mortbay.log.Log;
31 
32 
33 /**
34  * An HTTP client API that encapsulates Exchange with a HTTP server.
35  *
36  * This object encapsulates:<ul>
37  * <li>The HTTP server. (see {@link #setAddress(InetSocketAddress)} or {@link #setURL(String)})
38  * <li>The HTTP request method, URI and HTTP version (see {@link #setMethod(String)}, {@link #setURI(String)}, and {@link #setVersion(int)}
39  * <li>The Request headers (see {@link #addRequestHeader(String, String)} or {@link #setRequestHeader(String, String)})
40  * <li>The Request content (see {@link #setRequestContent(Buffer)} or {@link #setRequestContentSource(InputStream)})
41  * <li>The status of the exchange (see {@link #getStatus()})
42  * <li>Callbacks to handle state changes (see the onXxx methods such as {@link #onRequestComplete()} or {@link #onResponseComplete()})
43  * <li>The ability to intercept callbacks (see {@link #setEventListener(HttpEventListener)}
44  * </ul>
45  *
46  * The HttpExchange class is intended to be used by a developer wishing to have close asynchronous
47  * interaction with the the exchange.  Typically a developer will extend the HttpExchange class with a derived
48  * class that implements some or all of the onXxx callbacks.  There are also some predefined HttpExchange subtypes
49  * that can be used as a basis (see {@link ContentExchange} and {@link CachedExchange}.
50  *
51  * <p>Typically the HttpExchange is passed to a the {@link HttpClient#send(HttpExchange)} method, which in
52  * turn selects a {@link HttpDestination} and calls it's {@link HttpDestination#send(HttpExchange), which
53  * then creates or selects a {@link HttpConnection} and calls its {@link HttpConnection#send(HttpExchange).
54  * A developer may wish to directly call send on the destination or connection if they wish to bypass
55  * some handling provided (eg Cookie handling in the HttpDestination).
56  *
57  * <p>In some circumstances, the HttpClient or HttpDestination may wish to retry a HttpExchange (eg. failed
58  * pipeline request, authentication retry or redirection).  In such cases, the HttpClient and/or HttpDestination
59  * may insert their own HttpExchangeListener to intercept and filter the call backs intended for the
60  * HttpExchange.
61  *
62  * @author gregw
63  * @author Guillaume Nodet
64  */
65 public class HttpExchange
66 {
67     public static final int STATUS_START = 0;
68     public static final int STATUS_WAITING_FOR_CONNECTION = 1;
69     public static final int STATUS_WAITING_FOR_COMMIT = 2;
70     public static final int STATUS_SENDING_REQUEST = 3;
71     public static final int STATUS_WAITING_FOR_RESPONSE = 4;
72     public static final int STATUS_PARSING_HEADERS = 5;
73     public static final int STATUS_PARSING_CONTENT = 6;
74     public static final int STATUS_COMPLETED = 7;
75     public static final int STATUS_EXPIRED = 8;
76     public static final int STATUS_EXCEPTED = 9;
77 
78     Address _address;
79     String _method = HttpMethods.GET;
80     Buffer _scheme = HttpSchemes.HTTP_BUFFER;
81     int _version = HttpVersions.HTTP_1_1_ORDINAL;
82     String _uri;
83     int _status = STATUS_START;
84     HttpFields _requestFields = new HttpFields();
85     Buffer _requestContent;
86     InputStream _requestContentSource;
87     Buffer _requestContentChunk;
88     boolean _retryStatus = false;
89 
90 
91     /**
92      * boolean controlling if the exchange will have listeners autoconfigured by
93      * the destination
94      */
95     boolean _configureListeners = true;
96 
97 
98     private HttpEventListener _listener = new Listener();
99 
100     /* ------------------------------------------------------------ */
101     /* ------------------------------------------------------------ */
102     /* ------------------------------------------------------------ */
103     // methods to build request
104 
105     /* ------------------------------------------------------------ */
getStatus()106     public int getStatus()
107     {
108         return _status;
109     }
110 
111     /* ------------------------------------------------------------ */
112     /**
113      * @deprecated
114      */
waitForStatus(int status)115     public void waitForStatus(int status) throws InterruptedException
116     {
117         synchronized (this)
118         {
119             while (_status < status)
120             {
121                 this.wait();
122             }
123         }
124     }
125 
126 
waitForDone()127     public int waitForDone () throws InterruptedException
128     {
129         synchronized (this)
130         {
131             while (!isDone(_status))
132                 this.wait();
133         }
134         return _status;
135     }
136 
137 
138 
139 
140     /* ------------------------------------------------------------ */
reset()141     public void reset()
142     {
143         setStatus(STATUS_START);
144     }
145 
146     /* ------------------------------------------------------------ */
setStatus(int status)147     void setStatus(int status)
148     {
149         synchronized (this)
150         {
151             _status = status;
152             this.notifyAll();
153 
154             try
155             {
156                 switch (status)
157                 {
158                     case STATUS_WAITING_FOR_CONNECTION:
159                         break;
160 
161                     case STATUS_WAITING_FOR_COMMIT:
162                         break;
163 
164                     case STATUS_SENDING_REQUEST:
165                         break;
166 
167                     case HttpExchange.STATUS_WAITING_FOR_RESPONSE:
168                         getEventListener().onRequestCommitted();
169                         break;
170 
171                     case STATUS_PARSING_HEADERS:
172                         break;
173 
174                     case STATUS_PARSING_CONTENT:
175                         getEventListener().onResponseHeaderComplete();
176                         break;
177 
178                     case STATUS_COMPLETED:
179                         getEventListener().onResponseComplete();
180                         break;
181 
182                     case STATUS_EXPIRED:
183                         getEventListener().onExpire();
184                         break;
185 
186                 }
187             }
188             catch (IOException e)
189             {
190                 Log.warn(e);
191             }
192         }
193     }
194 
195     /* ------------------------------------------------------------ */
isDone(int status)196     public boolean isDone (int status)
197     {
198         return ((status == STATUS_COMPLETED) || (status == STATUS_EXPIRED) || (status == STATUS_EXCEPTED));
199     }
200 
201     /* ------------------------------------------------------------ */
getEventListener()202     public HttpEventListener getEventListener()
203     {
204         return _listener;
205     }
206 
207     /* ------------------------------------------------------------ */
setEventListener(HttpEventListener listener)208     public void setEventListener(HttpEventListener listener)
209     {
210         _listener=listener;
211     }
212 
213     /* ------------------------------------------------------------ */
214     /**
215      * @param url Including protocol, host and port
216      */
setURL(String url)217     public void setURL(String url)
218     {
219         HttpURI uri = new HttpURI(url);
220         String scheme = uri.getScheme();
221         if (scheme != null)
222         {
223             if (HttpSchemes.HTTP.equalsIgnoreCase(scheme))
224                 setScheme(HttpSchemes.HTTP_BUFFER);
225             else if (HttpSchemes.HTTPS.equalsIgnoreCase(scheme))
226                 setScheme(HttpSchemes.HTTPS_BUFFER);
227             else
228                 setScheme(new ByteArrayBuffer(scheme));
229         }
230 
231         int port = uri.getPort();
232         if (port <= 0)
233             port = "https".equalsIgnoreCase(scheme)?443:80;
234 
235         setAddress(new Address(uri.getHost(),port));
236 
237         String completePath = uri.getCompletePath();
238         if (completePath != null)
239             setURI(completePath);
240     }
241 
242     /* ------------------------------------------------------------ */
243     /**
244      * @param address
245      */
setAddress(Address address)246     public void setAddress(Address address)
247     {
248         _address = address;
249     }
250 
251     /* ------------------------------------------------------------ */
252     /**
253      * @return
254      */
getAddress()255     public Address getAddress()
256     {
257         return _address;
258     }
259 
260     /* ------------------------------------------------------------ */
261     /**
262      * @param scheme
263      */
setScheme(Buffer scheme)264     public void setScheme(Buffer scheme)
265     {
266         _scheme = scheme;
267     }
268 
269     /* ------------------------------------------------------------ */
270     /**
271      * @return
272      */
getScheme()273     public Buffer getScheme()
274     {
275         return _scheme;
276     }
277 
278     /* ------------------------------------------------------------ */
279     /**
280      * @param version as integer, 9, 10 or 11 for 0.9, 1.0 or 1.1
281      */
setVersion(int version)282     public void setVersion(int version)
283     {
284         _version = version;
285     }
286 
287     /* ------------------------------------------------------------ */
setVersion(String version)288     public void setVersion(String version)
289     {
290         CachedBuffer v = HttpVersions.CACHE.get(version);
291         if (v == null)
292             _version = 10;
293         else
294             _version = v.getOrdinal();
295     }
296 
297     /* ------------------------------------------------------------ */
298     /**
299      * @return
300      */
getVersion()301     public int getVersion()
302     {
303         return _version;
304     }
305 
306     /* ------------------------------------------------------------ */
307     /**
308      * @param method
309      */
setMethod(String method)310     public void setMethod(String method)
311     {
312         _method = method;
313     }
314 
315     /* ------------------------------------------------------------ */
316     /**
317      * @return
318      */
getMethod()319     public String getMethod()
320     {
321         return _method;
322     }
323 
324     /* ------------------------------------------------------------ */
325     /**
326      * @return
327      */
getURI()328     public String getURI()
329     {
330         return _uri;
331     }
332 
333     /* ------------------------------------------------------------ */
334     /**
335      * @param uri
336      */
setURI(String uri)337     public void setURI(String uri)
338     {
339         _uri = uri;
340     }
341 
342     /* ------------------------------------------------------------ */
343     /**
344      * @param name
345      * @param value
346      */
addRequestHeader(String name, String value)347     public void addRequestHeader(String name, String value)
348     {
349         getRequestFields().add(name,value);
350     }
351 
352     /* ------------------------------------------------------------ */
353     /**
354      * @param name
355      * @param value
356      */
addRequestHeader(Buffer name, Buffer value)357     public void addRequestHeader(Buffer name, Buffer value)
358     {
359         getRequestFields().add(name,value);
360     }
361 
362     /* ------------------------------------------------------------ */
363     /**
364      * @param name
365      * @param value
366      */
setRequestHeader(String name, String value)367     public void setRequestHeader(String name, String value)
368     {
369         getRequestFields().put(name,value);
370     }
371 
372     /* ------------------------------------------------------------ */
373     /**
374      * @param name
375      * @param value
376      */
setRequestHeader(Buffer name, Buffer value)377     public void setRequestHeader(Buffer name, Buffer value)
378     {
379         getRequestFields().put(name,value);
380     }
381 
382     /* ------------------------------------------------------------ */
383     /**
384      * @param value
385      */
setRequestContentType(String value)386     public void setRequestContentType(String value)
387     {
388         getRequestFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,value);
389     }
390 
391     /* ------------------------------------------------------------ */
392     /**
393      * @return
394      */
getRequestFields()395     public HttpFields getRequestFields()
396     {
397         return _requestFields;
398     }
399 
400     /* ------------------------------------------------------------ */
401     /* ------------------------------------------------------------ */
402     /* ------------------------------------------------------------ */
403     // methods to commit and/or send the request
404 
405     /* ------------------------------------------------------------ */
406     /**
407      * @param requestContent
408      */
setRequestContent(Buffer requestContent)409     public void setRequestContent(Buffer requestContent)
410     {
411         _requestContent = requestContent;
412     }
413 
414     /* ------------------------------------------------------------ */
415     /**
416      * @param in
417      */
setRequestContentSource(InputStream in)418     public void setRequestContentSource(InputStream in)
419     {
420         _requestContentSource = in;
421     }
422 
423     /* ------------------------------------------------------------ */
getRequestContentSource()424     public InputStream getRequestContentSource()
425     {
426         return _requestContentSource;
427     }
428 
429     /* ------------------------------------------------------------ */
getRequestContentChunk()430     public Buffer getRequestContentChunk() throws IOException
431     {
432         synchronized (this)
433         {
434             if (_requestContentChunk == null)
435                 _requestContentChunk = new ByteArrayBuffer(4096); // TODO configure
436             else
437             {
438                 if (_requestContentChunk.hasContent())
439                     throw new IllegalStateException();
440                 _requestContentChunk.clear();
441             }
442 
443             int read = _requestContentChunk.capacity();
444             int length = _requestContentSource.read(_requestContentChunk.array(),0,read);
445             if (length >= 0)
446             {
447                 _requestContentChunk.setPutIndex(length);
448                 return _requestContentChunk;
449             }
450             return null;
451         }
452     }
453 
454     /* ------------------------------------------------------------ */
getRequestContent()455     public Buffer getRequestContent()
456     {
457         return _requestContent;
458     }
459 
getRetryStatus()460     public boolean getRetryStatus()
461     {
462         return _retryStatus;
463     }
464 
setRetryStatus( boolean retryStatus )465     public void setRetryStatus( boolean retryStatus )
466     {
467         _retryStatus = retryStatus;
468     }
469 
470     /* ------------------------------------------------------------ */
471     /** Cancel this exchange
472      * Currently this implementation does nothing.
473      */
cancel()474     public void cancel()
475     {
476 
477     }
478 
479     /* ------------------------------------------------------------ */
toString()480     public String toString()
481     {
482         return "HttpExchange@" + hashCode() + "=" + _method + "//" + _address.getHost() + ":" + _address.getPort() + _uri + "#" + _status;
483     }
484 
485 
486 
487     /* ------------------------------------------------------------ */
488     /* ------------------------------------------------------------ */
489     /* ------------------------------------------------------------ */
490     // methods to handle response
onRequestCommitted()491     protected void onRequestCommitted() throws IOException
492     {
493     }
494 
onRequestComplete()495     protected void onRequestComplete() throws IOException
496     {
497     }
498 
onResponseStatus(Buffer version, int status, Buffer reason)499     protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
500     {
501     }
502 
onResponseHeader(Buffer name, Buffer value)503     protected void onResponseHeader(Buffer name, Buffer value) throws IOException
504     {
505     }
506 
onResponseHeaderComplete()507     protected void onResponseHeaderComplete() throws IOException
508     {
509     }
510 
onResponseContent(Buffer content)511     protected void onResponseContent(Buffer content) throws IOException
512     {
513     }
514 
onResponseComplete()515     protected void onResponseComplete() throws IOException
516     {
517     }
518 
onConnectionFailed(Throwable ex)519     protected void onConnectionFailed(Throwable ex)
520     {
521         Log.warn("CONNECTION FAILED on " + this,ex);
522     }
523 
onException(Throwable ex)524     protected void onException(Throwable ex)
525     {
526 
527         Log.warn("EXCEPTION on " + this,ex);
528     }
529 
onExpire()530     protected void onExpire()
531     {
532         Log.debug("EXPIRED " + this);
533     }
534 
onRetry()535     protected void onRetry() throws IOException
536     {}
537 
538     /**
539      * true of the exchange should have listeners configured for it by the destination
540      *
541      * false if this is being managed elsewhere
542      *
543      * @return
544      */
configureListeners()545     public boolean configureListeners()
546     {
547         return _configureListeners;
548     }
549 
setConfigureListeners(boolean autoConfigure )550     public void setConfigureListeners(boolean autoConfigure )
551     {
552         this._configureListeners = autoConfigure;
553     }
554 
555     private class Listener implements HttpEventListener
556     {
onConnectionFailed(Throwable ex)557         public void onConnectionFailed(Throwable ex)
558         {
559             HttpExchange.this.onConnectionFailed(ex);
560         }
561 
onException(Throwable ex)562         public void onException(Throwable ex)
563         {
564             HttpExchange.this.onException(ex);
565         }
566 
onExpire()567         public void onExpire()
568         {
569             HttpExchange.this.onExpire();
570         }
571 
onRequestCommitted()572         public void onRequestCommitted() throws IOException
573         {
574             HttpExchange.this.onRequestCommitted();
575         }
576 
onRequestComplete()577         public void onRequestComplete() throws IOException
578         {
579             HttpExchange.this.onRequestComplete();
580         }
581 
onResponseComplete()582         public void onResponseComplete() throws IOException
583         {
584             HttpExchange.this.onResponseComplete();
585         }
586 
onResponseContent(Buffer content)587         public void onResponseContent(Buffer content) throws IOException
588         {
589             HttpExchange.this.onResponseContent(content);
590         }
591 
onResponseHeader(Buffer name, Buffer value)592         public void onResponseHeader(Buffer name, Buffer value) throws IOException
593         {
594             HttpExchange.this.onResponseHeader(name,value);
595         }
596 
onResponseHeaderComplete()597         public void onResponseHeaderComplete() throws IOException
598         {
599             HttpExchange.this.onResponseHeaderComplete();
600         }
601 
onResponseStatus(Buffer version, int status, Buffer reason)602         public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
603         {
604             HttpExchange.this.onResponseStatus(version,status,reason);
605         }
606 
onRetry()607         public void onRetry()
608         {
609             HttpExchange.this.setRetryStatus( true );
610             try
611             {
612                 HttpExchange.this.onRetry();
613             }
614             catch (IOException e)
615             {
616                 e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
617             }
618         }
619     }
620 
621     /**
622      * @deprecated use {@link org.mortbay.jetty.client.CachedExchange}
623      *
624      */
625     public static class CachedExchange extends org.mortbay.jetty.client.CachedExchange
626     {
CachedExchange(boolean cacheFields)627         public CachedExchange(boolean cacheFields)
628         {
629             super(cacheFields);
630         }
631     }
632 
633     /**
634      * @deprecated use {@link org.mortbay.jetty.client.ContentExchange}
635      *
636      */
637     public static class ContentExchange extends org.mortbay.jetty.client.ContentExchange
638     {
639 
640     }
641 
642 
643 
644 }
645