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      String _method = HttpMethods.GET;
80      Buffer _scheme = HttpSchemes.HTTP_BUFFER;
81      String _uri;
82      int _version = HttpVersions.HTTP_1_1_ORDINAL;
83      Address _address;
84      HttpFields _requestFields = new HttpFields();
85      Buffer _requestContent;
86      InputStream _requestContentSource;
87  
88      volatile int _status = STATUS_START;
89      Buffer _requestContentChunk;
90      boolean _retryStatus = false;
91      // controls if the exchange will have listeners autoconfigured by the destination
92      boolean _configureListeners = true;
93      private HttpEventListener _listener = new Listener();
94  
95      boolean _onRequestCompleteDone;
96      boolean _onResponseCompleteDone;
97      boolean _onDone; // == onConnectionFail || onException || onExpired || onCancelled || onResponseCompleted && onRequestCompleted
98  
99      /* ------------------------------------------------------------ */
100     public int getStatus()
101     {
102         return _status;
103     }
104 
105     /* ------------------------------------------------------------ */
106     /**
107      * @deprecated
108      */
109     public void waitForStatus(int status) throws InterruptedException
110     {
111         synchronized (this)
112         {
113             while (_status < status)
114             {
115                 this.wait();
116             }
117         }
118     }
119 
120 
121     /**
122      * Wait until the exchange is "done".  
123      * Done is defined as when a final state has been passed to the 
124      * HttpExchange via the associated onXxx call.  Note that an
125      * exchange can transit a final state when being used as part
126      * of a dialog (eg {@link SecurityListener}.   Done status
127      * is thus defined as:<pre>
128      *   done == onConnectionFailed 
129      *        || onException
130      *        || onExpire
131      *        || onRequestComplete && onResponseComplete
132      * </pre>
133      * @return
134      * @throws InterruptedException
135      */
136     public int waitForDone () throws InterruptedException
137     {
138         synchronized (this)
139         {
140             while (!isDone(_status))
141                 this.wait();
142             return _status;
143         }
144     }
145 
146 
147 
148 
149     /* ------------------------------------------------------------ */
150     public void reset()
151     {
152         // TODO - this should do a cancel and wakeup everybody that was waiting.
153         // might need a version number concept 
154         synchronized(this)
155         {
156             _onRequestCompleteDone=false;
157             _onResponseCompleteDone=false;
158             _onDone=false;
159             setStatus(STATUS_START);
160         }
161     }
162 
163     /* ------------------------------------------------------------ */
164     void setStatus(int status)
165     {
166         // _status is volatile
167         _status = status;
168 
169         try
170         {
171             switch (status)
172             {
173                 case STATUS_WAITING_FOR_CONNECTION:
174                     break;
175 
176                 case STATUS_WAITING_FOR_COMMIT:
177                     break;
178 
179                 case STATUS_SENDING_REQUEST:
180                     break;
181 
182                 case HttpExchange.STATUS_WAITING_FOR_RESPONSE:
183                     getEventListener().onRequestCommitted();
184                     break;
185 
186                 case STATUS_PARSING_HEADERS:
187                     break;
188 
189                 case STATUS_PARSING_CONTENT:
190                     getEventListener().onResponseHeaderComplete();
191                     break;
192 
193                 case STATUS_COMPLETED:
194                     getEventListener().onResponseComplete();
195                     break;
196 
197                 case STATUS_EXPIRED:
198                     getEventListener().onExpire();
199                     break;
200 
201             }
202         }
203         catch (IOException e)
204         {
205             Log.warn(e);
206         }
207     }
208 
209     /* ------------------------------------------------------------ */
210     public boolean isDone (int status)
211     {
212         synchronized (this)
213         {
214             return _onDone;
215         }
216     }
217 
218     /* ------------------------------------------------------------ */
219     public HttpEventListener getEventListener()
220     {
221         return _listener;
222     }
223 
224     /* ------------------------------------------------------------ */
225     public void setEventListener(HttpEventListener listener)
226     {
227         _listener=listener;
228     }
229 
230     /* ------------------------------------------------------------ */
231     /**
232      * @param url Including protocol, host and port
233      */
234     public void setURL(String url)
235     {
236         HttpURI uri = new HttpURI(url);
237         String scheme = uri.getScheme();
238         if (scheme != null)
239         {
240             if (HttpSchemes.HTTP.equalsIgnoreCase(scheme))
241                 setScheme(HttpSchemes.HTTP_BUFFER);
242             else if (HttpSchemes.HTTPS.equalsIgnoreCase(scheme))
243                 setScheme(HttpSchemes.HTTPS_BUFFER);
244             else
245                 setScheme(new ByteArrayBuffer(scheme));
246         }
247 
248         int port = uri.getPort();
249         if (port <= 0)
250             port = "https".equalsIgnoreCase(scheme)?443:80;
251 
252         setAddress(new Address(uri.getHost(),port));
253 
254         String completePath = uri.getCompletePath();
255         if (completePath == null)
256             completePath = "/";
257         
258         setURI(completePath);
259     }
260 
261     /* ------------------------------------------------------------ */
262     /**
263      * @param address
264      */
265     public void setAddress(Address address)
266     {
267         _address = address;
268     }
269 
270     /* ------------------------------------------------------------ */
271     /**
272      * @return
273      */
274     public Address getAddress()
275     {
276         return _address;
277     }
278 
279     /* ------------------------------------------------------------ */
280     /**
281      * @param scheme
282      */
283     public void setScheme(Buffer scheme)
284     {
285         _scheme = scheme;
286     }
287 
288     /* ------------------------------------------------------------ */
289     /**
290      * @return
291      */
292     public Buffer getScheme()
293     {
294         return _scheme;
295     }
296 
297     /* ------------------------------------------------------------ */
298     /**
299      * @param version as integer, 9, 10 or 11 for 0.9, 1.0 or 1.1
300      */
301     public void setVersion(int version)
302     {
303         _version = version;
304     }
305 
306     /* ------------------------------------------------------------ */
307     public void setVersion(String version)
308     {
309         CachedBuffer v = HttpVersions.CACHE.get(version);
310         if (v == null)
311             _version = 10;
312         else
313             _version = v.getOrdinal();
314     }
315 
316     /* ------------------------------------------------------------ */
317     /**
318      * @return
319      */
320     public int getVersion()
321     {
322         return _version;
323     }
324 
325     /* ------------------------------------------------------------ */
326     /**
327      * @param method
328      */
329     public void setMethod(String method)
330     {
331         _method = method;
332     }
333 
334     /* ------------------------------------------------------------ */
335     /**
336      * @return
337      */
338     public String getMethod()
339     {
340         return _method;
341     }
342 
343     /* ------------------------------------------------------------ */
344     /**
345      * @return
346      */
347     public String getURI()
348     {
349         return _uri;
350     }
351 
352     /* ------------------------------------------------------------ */
353     /**
354      * @param uri
355      */
356     public void setURI(String uri)
357     {
358         _uri = uri;
359     }
360 
361     /* ------------------------------------------------------------ */
362     /**
363      * @param name
364      * @param value
365      */
366     public void addRequestHeader(String name, String value)
367     {
368         getRequestFields().add(name,value);
369     }
370 
371     /* ------------------------------------------------------------ */
372     /**
373      * @param name
374      * @param value
375      */
376     public void addRequestHeader(Buffer name, Buffer value)
377     {
378         getRequestFields().add(name,value);
379     }
380 
381     /* ------------------------------------------------------------ */
382     /**
383      * @param name
384      * @param value
385      */
386     public void setRequestHeader(String name, String value)
387     {
388         getRequestFields().put(name,value);
389     }
390 
391     /* ------------------------------------------------------------ */
392     /**
393      * @param name
394      * @param value
395      */
396     public void setRequestHeader(Buffer name, Buffer value)
397     {
398         getRequestFields().put(name,value);
399     }
400 
401     /* ------------------------------------------------------------ */
402     /**
403      * @param value
404      */
405     public void setRequestContentType(String value)
406     {
407         getRequestFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,value);
408     }
409 
410     /* ------------------------------------------------------------ */
411     /**
412      * @return
413      */
414     public HttpFields getRequestFields()
415     {
416         return _requestFields;
417     }
418 
419     /* ------------------------------------------------------------ */
420     /* ------------------------------------------------------------ */
421     /* ------------------------------------------------------------ */
422     // methods to commit and/or send the request
423 
424     /* ------------------------------------------------------------ */
425     /**
426      * @param requestContent
427      */
428     public void setRequestContent(Buffer requestContent)
429     {
430         _requestContent = requestContent;
431     }
432 
433     /* ------------------------------------------------------------ */
434     /**
435      * @param in
436      */
437     public void setRequestContentSource(InputStream in)
438     {
439         _requestContentSource = in;
440         if (_requestContentSource.markSupported())
441             _requestContentSource.mark(Integer.MAX_VALUE);
442     }
443 
444     /* ------------------------------------------------------------ */
445     public InputStream getRequestContentSource()
446     {
447         return _requestContentSource;
448     }
449 
450     /* ------------------------------------------------------------ */
451     public Buffer getRequestContentChunk() throws IOException
452     {
453         synchronized (this)
454         {
455             if (_requestContentChunk == null)
456                 _requestContentChunk = new ByteArrayBuffer(4096); // TODO configure
457             else
458             {
459                 if (_requestContentChunk.hasContent())
460                     throw new IllegalStateException();
461                 _requestContentChunk.clear();
462             }
463 
464             int read = _requestContentChunk.capacity();
465             int length = _requestContentSource.read(_requestContentChunk.array(),0,read);
466             if (length >= 0)
467             {
468                 _requestContentChunk.setPutIndex(length);
469                 return _requestContentChunk;
470             }
471             return null;
472         }
473     }
474 
475     /* ------------------------------------------------------------ */
476     public Buffer getRequestContent()
477     {
478         return _requestContent;
479     }
480 
481     /* ------------------------------------------------------------ */
482     public boolean getRetryStatus()
483     {
484         return _retryStatus;
485     }
486 
487     /* ------------------------------------------------------------ */
488     public void setRetryStatus( boolean retryStatus )
489     {
490         _retryStatus = retryStatus;
491     }
492 
493     /* ------------------------------------------------------------ */
494     /** Cancel this exchange
495      * Currently this implementation does nothing.
496      */
497     public void cancel()
498     {
499 
500     }
501 
502     /* ------------------------------------------------------------ */
503     public String toString()
504     {
505         return getClass().getName() + "@" + hashCode() + "=" + _method + "//" + _address + _uri + "#" + _status;
506     }
507 
508 
509 
510     /* ------------------------------------------------------------ */
511     /* ------------------------------------------------------------ */
512     /* ------------------------------------------------------------ */
513     // methods to handle response
514     
515     /**
516      * Called when the request headers has been sent
517      * @throws IOException
518      */
519     protected void onRequestCommitted() throws IOException
520     {
521     }
522 
523     /**
524      * Called when the request and it's body have been sent.
525      * @throws IOException
526      */
527     protected void onRequestComplete() throws IOException
528     {
529     }
530 
531     /**
532      * Called when a response status line has been received.
533      * @param version HTTP version
534      * @param status HTTP status code
535      * @param reason HTTP status code reason string
536      * @throws IOException
537      */
538     protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
539     {
540     }
541 
542     /**
543      * Called for each response header received
544      * @param name header name
545      * @param value header value
546      * @throws IOException
547      */
548     protected void onResponseHeader(Buffer name, Buffer value) throws IOException
549     {
550     }
551 
552     /**
553      * Called when the response header has been completely received.
554      * @throws IOException
555      */
556     protected void onResponseHeaderComplete() throws IOException
557     {
558     }
559 
560     /**
561      * Called for each chunk of the response content received.
562      * @param content
563      * @throws IOException
564      */
565     protected void onResponseContent(Buffer content) throws IOException
566     {
567     }
568 
569     /**
570      * Called when the entire response has been received
571      * @throws IOException
572      */
573     protected void onResponseComplete() throws IOException
574     {
575     }
576 
577     /**
578      * Called when an exception was thrown during an attempt to open a connection
579      * @param ex
580      */
581     protected void onConnectionFailed(Throwable ex)
582     {
583         Log.warn("CONNECTION FAILED on " + this,ex);
584     }
585 
586     /**
587      * Called when any other exception occurs during handling for the exchange
588      * @param ex
589      */
590     protected void onException(Throwable ex)
591     {
592         Log.warn("EXCEPTION on " + this,ex);
593     }
594 
595     /**
596      * Called when no response has been received within the timeout.
597      */
598     protected void onExpire()
599     {
600         Log.warn("EXPIRED " + this);
601     }
602 
603     /**
604      * Called when the request is retried (due to failures or authentication).
605      * Implementations may need to reset any consumable content that needs to
606      * be sent.
607      * @throws IOException
608      */
609     protected void onRetry() throws IOException
610     {
611         if (_requestContentSource != null)
612         {
613             if (_requestContentSource.markSupported())
614             {
615                 _requestContent = null;
616                 _requestContentSource.reset();
617             }
618             else
619             {
620                 throw new IOException("Unsupported retry attempt");
621             }
622         }
623     }
624 
625     /**
626      * true of the exchange should have listeners configured for it by the destination
627      *
628      * false if this is being managed elsewhere
629      *
630      * @return
631      */
632     public boolean configureListeners()
633     {
634         return _configureListeners;
635     }
636 
637     public void setConfigureListeners(boolean autoConfigure )
638     {
639         this._configureListeners = autoConfigure;
640     }
641 
642     private class Listener implements HttpEventListener
643     {
644         public void onConnectionFailed(Throwable ex)
645         {
646             try
647             {
648                 HttpExchange.this.onConnectionFailed(ex);
649             }
650             finally
651             {
652                 synchronized(HttpExchange.this)
653                 {
654                     _onDone=true;
655                     HttpExchange.this.notifyAll();
656                 }
657             }
658         }
659 
660         public void onException(Throwable ex)
661         {
662             try
663             {
664                 HttpExchange.this.onException(ex);
665             }
666             finally
667             {
668                 synchronized(HttpExchange.this)
669                 {
670                     _onDone=true;
671                     HttpExchange.this.notifyAll();
672                 }
673             }
674         }
675 
676         public void onExpire()
677         {
678             try
679             {
680                 HttpExchange.this.onExpire();
681             }
682             finally
683             {
684                 synchronized(HttpExchange.this)
685                 {
686                     _onDone=true;
687                     HttpExchange.this.notifyAll();
688                 }
689             }
690         }
691 
692         public void onRequestCommitted() throws IOException
693         {
694             HttpExchange.this.onRequestCommitted();
695         }
696 
697         public void onRequestComplete() throws IOException
698         {
699             try
700             {
701                 HttpExchange.this.onRequestComplete();
702             }
703             finally
704             {
705                 synchronized(HttpExchange.this)
706                 {
707                     _onRequestCompleteDone=true;
708                     _onDone=_onResponseCompleteDone;
709                     HttpExchange.this.notifyAll();
710                 }
711             }
712         }
713 
714         public void onResponseComplete() throws IOException
715         {
716             try
717             {
718                 HttpExchange.this.onResponseComplete();
719             }
720             finally
721             {
722                 synchronized(HttpExchange.this)
723                 {
724                     _onResponseCompleteDone=true;
725                     _onDone=_onRequestCompleteDone;
726                     HttpExchange.this.notifyAll();
727                 }
728             }
729         }
730 
731         public void onResponseContent(Buffer content) throws IOException
732         {
733             HttpExchange.this.onResponseContent(content);
734         }
735 
736         public void onResponseHeader(Buffer name, Buffer value) throws IOException
737         {
738             HttpExchange.this.onResponseHeader(name,value);
739         }
740 
741         public void onResponseHeaderComplete() throws IOException
742         {
743             HttpExchange.this.onResponseHeaderComplete();
744         }
745 
746         public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
747         {
748             HttpExchange.this.onResponseStatus(version,status,reason);
749         }
750 
751         public void onRetry()
752         {
753             HttpExchange.this.setRetryStatus( true );
754             try
755             {
756                 HttpExchange.this.onRetry();
757             }
758             catch (IOException e)
759             {
760                 onException(e);
761             }
762         }
763     }
764 
765     /**
766      * @deprecated use {@link org.mortbay.jetty.client.CachedExchange}
767      *
768      */
769     public static class CachedExchange extends org.mortbay.jetty.client.CachedExchange
770     {
771         public CachedExchange(boolean cacheFields)
772         {
773             super(cacheFields);
774         }
775     }
776 
777     /**
778      * @deprecated use {@link org.mortbay.jetty.client.ContentExchange}
779      *
780      */
781     public static class ContentExchange extends org.mortbay.jetty.client.ContentExchange
782     {
783 
784     }
785 
786 
787 
788 }