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.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      String _method = HttpMethods.GET;
79      Buffer _scheme = HttpSchemes.HTTP_BUFFER;
80      String _uri;
81      int _version = HttpVersions.HTTP_1_1_ORDINAL;
82      Address _address;
83      HttpFields _requestFields = new HttpFields();
84      Buffer _requestContent;
85      InputStream _requestContentSource;
86      long _timeout = -1;
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     public long getTimeout()
495     {
496         return _timeout;
497     }
498 
499     /* ------------------------------------------------------------ */
500     public void setTimeout(long timeout)
501     {
502         _timeout = timeout;
503     }
504 
505     /* ------------------------------------------------------------ */
506     /** Cancel this exchange
507      * Currently this implementation does nothing.
508      */
509     public void cancel()
510     {
511 
512     }
513 
514     /* ------------------------------------------------------------ */
515     public String toString()
516     {
517         return getClass().getName() + "@" + hashCode() + "=" + _method + "//" + _address + _uri + "#" + _status;
518     }
519 
520 
521 
522     /* ------------------------------------------------------------ */
523     /* ------------------------------------------------------------ */
524     /* ------------------------------------------------------------ */
525     // methods to handle response
526     
527     /**
528      * Called when the request headers has been sent
529      * @throws IOException
530      */
531     protected void onRequestCommitted() throws IOException
532     {
533     }
534 
535     /**
536      * Called when the request and it's body have been sent.
537      * @throws IOException
538      */
539     protected void onRequestComplete() throws IOException
540     {
541     }
542 
543     /**
544      * Called when a response status line has been received.
545      * @param version HTTP version
546      * @param status HTTP status code
547      * @param reason HTTP status code reason string
548      * @throws IOException
549      */
550     protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
551     {
552     }
553 
554     /**
555      * Called for each response header received
556      * @param name header name
557      * @param value header value
558      * @throws IOException
559      */
560     protected void onResponseHeader(Buffer name, Buffer value) throws IOException
561     {
562     }
563 
564     /**
565      * Called when the response header has been completely received.
566      * @throws IOException
567      */
568     protected void onResponseHeaderComplete() throws IOException
569     {
570     }
571 
572     /**
573      * Called for each chunk of the response content received.
574      * @param content
575      * @throws IOException
576      */
577     protected void onResponseContent(Buffer content) throws IOException
578     {
579     }
580 
581     /**
582      * Called when the entire response has been received
583      * @throws IOException
584      */
585     protected void onResponseComplete() throws IOException
586     {
587     }
588 
589     /**
590      * Called when an exception was thrown during an attempt to open a connection
591      * @param ex
592      */
593     protected void onConnectionFailed(Throwable ex)
594     {
595         Log.warn("CONNECTION FAILED on " + this,ex);
596     }
597 
598     /**
599      * Called when any other exception occurs during handling for the exchange
600      * @param ex
601      */
602     protected void onException(Throwable ex)
603     {
604         Log.warn("EXCEPTION on " + this,ex);
605     }
606 
607     /**
608      * Called when no response has been received within the timeout.
609      */
610     protected void onExpire()
611     {
612         Log.warn("EXPIRED " + this);
613     }
614 
615     /**
616      * Called when the request is retried (due to failures or authentication).
617      * Implementations may need to reset any consumable content that needs to
618      * be sent.
619      * @throws IOException
620      */
621     protected void onRetry() throws IOException
622     {
623         if (_requestContentSource != null)
624         {
625             if (_requestContentSource.markSupported())
626             {
627                 _requestContent = null;
628                 _requestContentSource.reset();
629             }
630             else
631             {
632                 throw new IOException("Unsupported retry attempt");
633             }
634         }
635     }
636 
637     /**
638      * true of the exchange should have listeners configured for it by the destination
639      *
640      * false if this is being managed elsewhere
641      *
642      * @return
643      */
644     public boolean configureListeners()
645     {
646         return _configureListeners;
647     }
648 
649     public void setConfigureListeners(boolean autoConfigure )
650     {
651         this._configureListeners = autoConfigure;
652     }
653 
654     private class Listener implements HttpEventListener
655     {
656         public void onConnectionFailed(Throwable ex)
657         {
658             try
659             {
660                 HttpExchange.this.onConnectionFailed(ex);
661             }
662             finally
663             {
664                 synchronized(HttpExchange.this)
665                 {
666                     _onDone=true;
667                     HttpExchange.this.notifyAll();
668                 }
669             }
670         }
671 
672         public void onException(Throwable ex)
673         {
674             try
675             {
676                 HttpExchange.this.onException(ex);
677             }
678             finally
679             {
680                 synchronized(HttpExchange.this)
681                 {
682                     _onDone=true;
683                     HttpExchange.this.notifyAll();
684                 }
685             }
686         }
687 
688         public void onExpire()
689         {
690             try
691             {
692                 HttpExchange.this.onExpire();
693             }
694             finally
695             {
696                 synchronized(HttpExchange.this)
697                 {
698                     _onDone=true;
699                     HttpExchange.this.notifyAll();
700                 }
701             }
702         }
703 
704         public void onRequestCommitted() throws IOException
705         {
706             HttpExchange.this.onRequestCommitted();
707         }
708 
709         public void onRequestComplete() throws IOException
710         {
711             try
712             {
713                 HttpExchange.this.onRequestComplete();
714             }
715             finally
716             {
717                 synchronized(HttpExchange.this)
718                 {
719                     _onRequestCompleteDone=true;
720                     _onDone=_onResponseCompleteDone;
721                     HttpExchange.this.notifyAll();
722                 }
723             }
724         }
725 
726         public void onResponseComplete() throws IOException
727         {
728             try
729             {
730                 HttpExchange.this.onResponseComplete();
731             }
732             finally
733             {
734                 synchronized(HttpExchange.this)
735                 {
736                     _onResponseCompleteDone=true;
737                     _onDone=_onRequestCompleteDone;
738                     HttpExchange.this.notifyAll();
739                 }
740             }
741         }
742 
743         public void onResponseContent(Buffer content) throws IOException
744         {
745             HttpExchange.this.onResponseContent(content);
746         }
747 
748         public void onResponseHeader(Buffer name, Buffer value) throws IOException
749         {
750             HttpExchange.this.onResponseHeader(name,value);
751         }
752 
753         public void onResponseHeaderComplete() throws IOException
754         {
755             HttpExchange.this.onResponseHeaderComplete();
756         }
757 
758         public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
759         {
760             HttpExchange.this.onResponseStatus(version,status,reason);
761         }
762 
763         public void onRetry()
764         {
765             HttpExchange.this.setRetryStatus( true );
766             try
767             {
768                 HttpExchange.this.onRetry();
769             }
770             catch (IOException e)
771             {
772                 onException(e);
773             }
774         }
775     }
776 
777     /**
778      * @deprecated use {@link org.mortbay.jetty.client.CachedExchange}
779      *
780      */
781     public static class CachedExchange extends org.mortbay.jetty.client.CachedExchange
782     {
783         public CachedExchange(boolean cacheFields)
784         {
785             super(cacheFields);
786         }
787     }
788 
789     /**
790      * @deprecated use {@link org.mortbay.jetty.client.ContentExchange}
791      *
792      */
793     public static class ContentExchange extends org.mortbay.jetty.client.ContentExchange
794     {
795 
796     }
797 
798 
799 
800 }