View Javadoc

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