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     }
441 
442     /* ------------------------------------------------------------ */
443     public InputStream getRequestContentSource()
444     {
445         return _requestContentSource;
446     }
447 
448     /* ------------------------------------------------------------ */
449     public Buffer getRequestContentChunk() throws IOException
450     {
451         synchronized (this)
452         {
453             if (_requestContentChunk == null)
454                 _requestContentChunk = new ByteArrayBuffer(4096); // TODO configure
455             else
456             {
457                 if (_requestContentChunk.hasContent())
458                     throw new IllegalStateException();
459                 _requestContentChunk.clear();
460             }
461 
462             int read = _requestContentChunk.capacity();
463             int length = _requestContentSource.read(_requestContentChunk.array(),0,read);
464             if (length >= 0)
465             {
466                 _requestContentChunk.setPutIndex(length);
467                 return _requestContentChunk;
468             }
469             return null;
470         }
471     }
472 
473     /* ------------------------------------------------------------ */
474     public Buffer getRequestContent()
475     {
476         return _requestContent;
477     }
478 
479     public boolean getRetryStatus()
480     {
481         return _retryStatus;
482     }
483 
484     public void setRetryStatus( boolean retryStatus )
485     {
486         _retryStatus = retryStatus;
487     }
488 
489     /* ------------------------------------------------------------ */
490     /** Cancel this exchange
491      * Currently this implementation does nothing.
492      */
493     public void cancel()
494     {
495 
496     }
497 
498     /* ------------------------------------------------------------ */
499     public String toString()
500     {
501         return getClass().getName() + "@" + hashCode() + "=" + _method + "//" + _address + _uri + "#" + _status;
502     }
503 
504 
505 
506     /* ------------------------------------------------------------ */
507     /* ------------------------------------------------------------ */
508     /* ------------------------------------------------------------ */
509     // methods to handle response
510     
511     /**
512      * Called when the request headers has been sent
513      * @throws IOException
514      */
515     protected void onRequestCommitted() throws IOException
516     {
517     }
518 
519     /**
520      * Called when the request and it's body have been sent.
521      * @throws IOException
522      */
523     protected void onRequestComplete() throws IOException
524     {
525     }
526 
527     /**
528      * Called when a response status line has been received.
529      * @param version HTTP version
530      * @param status HTTP status code
531      * @param reason HTTP status code reason string
532      * @throws IOException
533      */
534     protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
535     {
536     }
537 
538     /**
539      * Called for each response header received
540      * @param name header name
541      * @param value header value
542      * @throws IOException
543      */
544     protected void onResponseHeader(Buffer name, Buffer value) throws IOException
545     {
546     }
547 
548     /**
549      * Called when the response header has been completely received.
550      * @throws IOException
551      */
552     protected void onResponseHeaderComplete() throws IOException
553     {
554     }
555 
556     /**
557      * Called for each chunk of the response content received.
558      * @param content
559      * @throws IOException
560      */
561     protected void onResponseContent(Buffer content) throws IOException
562     {
563     }
564 
565     /**
566      * Called when the entire response has been received
567      * @throws IOException
568      */
569     protected void onResponseComplete() throws IOException
570     {
571     }
572 
573     /**
574      * Called when an exception was thrown during an attempt to open a connection
575      * @param ex
576      */
577     protected void onConnectionFailed(Throwable ex)
578     {
579         Log.warn("CONNECTION FAILED on " + this,ex);
580     }
581 
582     /**
583      * Called when any other exception occurs during handling for the exchange
584      * @param ex
585      */
586     protected void onException(Throwable ex)
587     {
588         Log.warn("EXCEPTION on " + this,ex);
589     }
590 
591     /**
592      * Called when no response has been received within the timeout.
593      */
594     protected void onExpire()
595     {
596         Log.warn("EXPIRED " + this);
597     }
598 
599     /**
600      * Called when the request is retried (due to failures or authentication).
601      * Implementations may need to reset any consumable content that needs to
602      * be sent.
603      * @throws IOException
604      */
605     protected void onRetry() throws IOException
606     {
607     }
608 
609     /**
610      * true of the exchange should have listeners configured for it by the destination
611      *
612      * false if this is being managed elsewhere
613      *
614      * @return
615      */
616     public boolean configureListeners()
617     {
618         return _configureListeners;
619     }
620 
621     public void setConfigureListeners(boolean autoConfigure )
622     {
623         this._configureListeners = autoConfigure;
624     }
625 
626     private class Listener implements HttpEventListener
627     {
628         public void onConnectionFailed(Throwable ex)
629         {
630             try
631             {
632                 HttpExchange.this.onConnectionFailed(ex);
633             }
634             finally
635             {
636                 synchronized(HttpExchange.this)
637                 {
638                     _onDone=true;
639                     HttpExchange.this.notifyAll();
640                 }
641             }
642         }
643 
644         public void onException(Throwable ex)
645         {
646             try
647             {
648                 HttpExchange.this.onException(ex);
649             }
650             finally
651             {
652                 synchronized(HttpExchange.this)
653                 {
654                     _onDone=true;
655                     HttpExchange.this.notifyAll();
656                 }
657             }
658         }
659 
660         public void onExpire()
661         {
662             try
663             {
664                 HttpExchange.this.onExpire();
665             }
666             finally
667             {
668                 synchronized(HttpExchange.this)
669                 {
670                     _onDone=true;
671                     HttpExchange.this.notifyAll();
672                 }
673             }
674         }
675 
676         public void onRequestCommitted() throws IOException
677         {
678             HttpExchange.this.onRequestCommitted();
679         }
680 
681         public void onRequestComplete() throws IOException
682         {
683             try
684             {
685                 HttpExchange.this.onRequestComplete();
686             }
687             finally
688             {
689                 synchronized(HttpExchange.this)
690                 {
691                     _onRequestCompleteDone=true;
692                     _onDone=_onResponseCompleteDone;
693                     HttpExchange.this.notifyAll();
694                 }
695             }
696         }
697 
698         public void onResponseComplete() throws IOException
699         {
700             try
701             {
702                 HttpExchange.this.onResponseComplete();
703             }
704             finally
705             {
706                 synchronized(HttpExchange.this)
707                 {
708                     _onResponseCompleteDone=true;
709                     _onDone=_onRequestCompleteDone;
710                     HttpExchange.this.notifyAll();
711                 }
712             }
713         }
714 
715         public void onResponseContent(Buffer content) throws IOException
716         {
717             HttpExchange.this.onResponseContent(content);
718         }
719 
720         public void onResponseHeader(Buffer name, Buffer value) throws IOException
721         {
722             HttpExchange.this.onResponseHeader(name,value);
723         }
724 
725         public void onResponseHeaderComplete() throws IOException
726         {
727             HttpExchange.this.onResponseHeaderComplete();
728         }
729 
730         public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
731         {
732             HttpExchange.this.onResponseStatus(version,status,reason);
733         }
734 
735         public void onRetry()
736         {
737             HttpExchange.this.setRetryStatus( true );
738             try
739             {
740                 HttpExchange.this.onRetry();
741             }
742             catch (IOException e)
743             {
744                 Log.debug(e);
745             }
746         }
747     }
748 
749     /**
750      * @deprecated use {@link org.mortbay.jetty.client.CachedExchange}
751      *
752      */
753     public static class CachedExchange extends org.mortbay.jetty.client.CachedExchange
754     {
755         public CachedExchange(boolean cacheFields)
756         {
757             super(cacheFields);
758         }
759     }
760 
761     /**
762      * @deprecated use {@link org.mortbay.jetty.client.ContentExchange}
763      *
764      */
765     public static class ContentExchange extends org.mortbay.jetty.client.ContentExchange
766     {
767 
768     }
769 
770 
771 
772 }