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