View Javadoc

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