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      Address _address;
79      String _method = HttpMethods.GET;
80      Buffer _scheme = HttpSchemes.HTTP_BUFFER;
81      int _version = HttpVersions.HTTP_1_1_ORDINAL;
82      String _uri;
83      int _status = STATUS_START;
84      HttpFields _requestFields = new HttpFields();
85      Buffer _requestContent;
86      InputStream _requestContentSource;
87      Buffer _requestContentChunk;
88      boolean _retryStatus = false;
89  
90  
91      /**
92       * boolean controlling if the exchange will have listeners autoconfigured by
93       * the destination
94       */
95      boolean _configureListeners = true;
96  
97  
98      private HttpEventListener _listener = new Listener();
99  
100     /* ------------------------------------------------------------ */
101     /* ------------------------------------------------------------ */
102     /* ------------------------------------------------------------ */
103     // methods to build request
104 
105     /* ------------------------------------------------------------ */
106     public int getStatus()
107     {
108         return _status;
109     }
110 
111     /* ------------------------------------------------------------ */
112     /**
113      * @deprecated
114      */
115     public void waitForStatus(int status) throws InterruptedException
116     {
117         synchronized (this)
118         {
119             while (_status < status)
120             {
121                 this.wait();
122             }
123         }
124     }
125 
126 
127     public int waitForDone () throws InterruptedException
128     {
129         synchronized (this)
130         {
131             while (!isDone(_status))
132                 this.wait();
133         }
134         return _status;
135     }
136 
137 
138 
139 
140     /* ------------------------------------------------------------ */
141     public void reset()
142     {
143         setStatus(STATUS_START);
144     }
145 
146     /* ------------------------------------------------------------ */
147     void setStatus(int status)
148     {
149         synchronized (this)
150         {
151             _status = status;
152             this.notifyAll();
153 
154             try
155             {
156                 switch (status)
157                 {
158                     case STATUS_WAITING_FOR_CONNECTION:
159                         break;
160 
161                     case STATUS_WAITING_FOR_COMMIT:
162                         break;
163 
164                     case STATUS_SENDING_REQUEST:
165                         break;
166 
167                     case HttpExchange.STATUS_WAITING_FOR_RESPONSE:
168                         getEventListener().onRequestCommitted();
169                         break;
170 
171                     case STATUS_PARSING_HEADERS:
172                         break;
173 
174                     case STATUS_PARSING_CONTENT:
175                         getEventListener().onResponseHeaderComplete();
176                         break;
177 
178                     case STATUS_COMPLETED:
179                         getEventListener().onResponseComplete();
180                         break;
181 
182                     case STATUS_EXPIRED:
183                         getEventListener().onExpire();
184                         break;
185 
186                 }
187             }
188             catch (IOException e)
189             {
190                 Log.warn(e);
191             }
192         }
193     }
194 
195     /* ------------------------------------------------------------ */
196     public boolean isDone (int status)
197     {
198         return ((status == STATUS_COMPLETED) || (status == STATUS_EXPIRED) || (status == STATUS_EXCEPTED));
199     }
200 
201     /* ------------------------------------------------------------ */
202     public HttpEventListener getEventListener()
203     {
204         return _listener;
205     }
206 
207     /* ------------------------------------------------------------ */
208     public void setEventListener(HttpEventListener listener)
209     {
210         _listener=listener;
211     }
212 
213     /* ------------------------------------------------------------ */
214     /**
215      * @param url Including protocol, host and port
216      */
217     public void setURL(String url)
218     {
219         HttpURI uri = new HttpURI(url);
220         String scheme = uri.getScheme();
221         if (scheme != null)
222         {
223             if (HttpSchemes.HTTP.equalsIgnoreCase(scheme))
224                 setScheme(HttpSchemes.HTTP_BUFFER);
225             else if (HttpSchemes.HTTPS.equalsIgnoreCase(scheme))
226                 setScheme(HttpSchemes.HTTPS_BUFFER);
227             else
228                 setScheme(new ByteArrayBuffer(scheme));
229         }
230 
231         int port = uri.getPort();
232         if (port <= 0)
233             port = "https".equalsIgnoreCase(scheme)?443:80;
234 
235         setAddress(new Address(uri.getHost(),port));
236 
237         String completePath = uri.getCompletePath();
238         if (completePath != null)
239             setURI(completePath);
240     }
241 
242     /* ------------------------------------------------------------ */
243     /**
244      * @param address
245      */
246     public void setAddress(Address address)
247     {
248         _address = address;
249     }
250 
251     /* ------------------------------------------------------------ */
252     /**
253      * @return
254      */
255     public Address getAddress()
256     {
257         return _address;
258     }
259 
260     /* ------------------------------------------------------------ */
261     /**
262      * @param scheme
263      */
264     public void setScheme(Buffer scheme)
265     {
266         _scheme = scheme;
267     }
268 
269     /* ------------------------------------------------------------ */
270     /**
271      * @return
272      */
273     public Buffer getScheme()
274     {
275         return _scheme;
276     }
277 
278     /* ------------------------------------------------------------ */
279     /**
280      * @param version as integer, 9, 10 or 11 for 0.9, 1.0 or 1.1
281      */
282     public void setVersion(int version)
283     {
284         _version = version;
285     }
286 
287     /* ------------------------------------------------------------ */
288     public void setVersion(String version)
289     {
290         CachedBuffer v = HttpVersions.CACHE.get(version);
291         if (v == null)
292             _version = 10;
293         else
294             _version = v.getOrdinal();
295     }
296 
297     /* ------------------------------------------------------------ */
298     /**
299      * @return
300      */
301     public int getVersion()
302     {
303         return _version;
304     }
305 
306     /* ------------------------------------------------------------ */
307     /**
308      * @param method
309      */
310     public void setMethod(String method)
311     {
312         _method = method;
313     }
314 
315     /* ------------------------------------------------------------ */
316     /**
317      * @return
318      */
319     public String getMethod()
320     {
321         return _method;
322     }
323 
324     /* ------------------------------------------------------------ */
325     /**
326      * @return
327      */
328     public String getURI()
329     {
330         return _uri;
331     }
332 
333     /* ------------------------------------------------------------ */
334     /**
335      * @param uri
336      */
337     public void setURI(String uri)
338     {
339         _uri = uri;
340     }
341 
342     /* ------------------------------------------------------------ */
343     /**
344      * @param name
345      * @param value
346      */
347     public void addRequestHeader(String name, String value)
348     {
349         getRequestFields().add(name,value);
350     }
351 
352     /* ------------------------------------------------------------ */
353     /**
354      * @param name
355      * @param value
356      */
357     public void addRequestHeader(Buffer name, Buffer value)
358     {
359         getRequestFields().add(name,value);
360     }
361 
362     /* ------------------------------------------------------------ */
363     /**
364      * @param name
365      * @param value
366      */
367     public void setRequestHeader(String name, String value)
368     {
369         getRequestFields().put(name,value);
370     }
371 
372     /* ------------------------------------------------------------ */
373     /**
374      * @param name
375      * @param value
376      */
377     public void setRequestHeader(Buffer name, Buffer value)
378     {
379         getRequestFields().put(name,value);
380     }
381 
382     /* ------------------------------------------------------------ */
383     /**
384      * @param value
385      */
386     public void setRequestContentType(String value)
387     {
388         getRequestFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,value);
389     }
390 
391     /* ------------------------------------------------------------ */
392     /**
393      * @return
394      */
395     public HttpFields getRequestFields()
396     {
397         return _requestFields;
398     }
399 
400     /* ------------------------------------------------------------ */
401     /* ------------------------------------------------------------ */
402     /* ------------------------------------------------------------ */
403     // methods to commit and/or send the request
404 
405     /* ------------------------------------------------------------ */
406     /**
407      * @param requestContent
408      */
409     public void setRequestContent(Buffer requestContent)
410     {
411         _requestContent = requestContent;
412     }
413 
414     /* ------------------------------------------------------------ */
415     /**
416      * @param in
417      */
418     public void setRequestContentSource(InputStream in)
419     {
420         _requestContentSource = in;
421     }
422 
423     /* ------------------------------------------------------------ */
424     public InputStream getRequestContentSource()
425     {
426         return _requestContentSource;
427     }
428 
429     /* ------------------------------------------------------------ */
430     public Buffer getRequestContentChunk() throws IOException
431     {
432         synchronized (this)
433         {
434             if (_requestContentChunk == null)
435                 _requestContentChunk = new ByteArrayBuffer(4096); // TODO configure
436             else
437             {
438                 if (_requestContentChunk.hasContent())
439                     throw new IllegalStateException();
440                 _requestContentChunk.clear();
441             }
442 
443             int read = _requestContentChunk.capacity();
444             int length = _requestContentSource.read(_requestContentChunk.array(),0,read);
445             if (length >= 0)
446             {
447                 _requestContentChunk.setPutIndex(length);
448                 return _requestContentChunk;
449             }
450             return null;
451         }
452     }
453 
454     /* ------------------------------------------------------------ */
455     public Buffer getRequestContent()
456     {
457         return _requestContent;
458     }
459 
460     public boolean getRetryStatus()
461     {
462         return _retryStatus;
463     }
464 
465     public void setRetryStatus( boolean retryStatus )
466     {
467         _retryStatus = retryStatus;
468     }
469 
470     /* ------------------------------------------------------------ */
471     /** Cancel this exchange
472      * Currently this implementation does nothing.
473      */
474     public void cancel()
475     {
476 
477     }
478 
479     /* ------------------------------------------------------------ */
480     public String toString()
481     {
482         return "HttpExchange@" + hashCode() + "=" + _method + "//" + _address.getHost() + ":" + _address.getPort() + _uri + "#" + _status;
483     }
484 
485 
486 
487     /* ------------------------------------------------------------ */
488     /* ------------------------------------------------------------ */
489     /* ------------------------------------------------------------ */
490     // methods to handle response
491     protected void onRequestCommitted() throws IOException
492     {
493     }
494 
495     protected void onRequestComplete() throws IOException
496     {
497     }
498 
499     protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
500     {
501     }
502 
503     protected void onResponseHeader(Buffer name, Buffer value) throws IOException
504     {
505     }
506 
507     protected void onResponseHeaderComplete() throws IOException
508     {
509     }
510 
511     protected void onResponseContent(Buffer content) throws IOException
512     {
513     }
514 
515     protected void onResponseComplete() throws IOException
516     {
517     }
518 
519     protected void onConnectionFailed(Throwable ex)
520     {
521         Log.warn("CONNECTION FAILED on " + this,ex);
522     }
523 
524     protected void onException(Throwable ex)
525     {
526 
527         Log.warn("EXCEPTION on " + this,ex);
528     }
529 
530     protected void onExpire()
531     {
532         Log.debug("EXPIRED " + this);
533     }
534 
535     protected void onRetry() throws IOException
536     {}
537 
538     /**
539      * true of the exchange should have listeners configured for it by the destination
540      *
541      * false if this is being managed elsewhere
542      *
543      * @return
544      */
545     public boolean configureListeners()
546     {
547         return _configureListeners;
548     }
549 
550     public void setConfigureListeners(boolean autoConfigure )
551     {
552         this._configureListeners = autoConfigure;
553     }
554 
555     private class Listener implements HttpEventListener
556     {
557         public void onConnectionFailed(Throwable ex)
558         {
559             HttpExchange.this.onConnectionFailed(ex);
560         }
561 
562         public void onException(Throwable ex)
563         {
564             HttpExchange.this.onException(ex);
565         }
566 
567         public void onExpire()
568         {
569             HttpExchange.this.onExpire();
570         }
571 
572         public void onRequestCommitted() throws IOException
573         {
574             HttpExchange.this.onRequestCommitted();
575         }
576 
577         public void onRequestComplete() throws IOException
578         {
579             HttpExchange.this.onRequestComplete();
580         }
581 
582         public void onResponseComplete() throws IOException
583         {
584             HttpExchange.this.onResponseComplete();
585         }
586 
587         public void onResponseContent(Buffer content) throws IOException
588         {
589             HttpExchange.this.onResponseContent(content);
590         }
591 
592         public void onResponseHeader(Buffer name, Buffer value) throws IOException
593         {
594             HttpExchange.this.onResponseHeader(name,value);
595         }
596 
597         public void onResponseHeaderComplete() throws IOException
598         {
599             HttpExchange.this.onResponseHeaderComplete();
600         }
601 
602         public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
603         {
604             HttpExchange.this.onResponseStatus(version,status,reason);
605         }
606 
607         public void onRetry()
608         {
609             HttpExchange.this.setRetryStatus( true );
610             try
611             {
612                 HttpExchange.this.onRetry();
613             }
614             catch (IOException e)
615             {
616                 e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
617             }
618         }
619     }
620 
621     /**
622      * @deprecated use {@link org.mortbay.jetty.client.CachedExchange}
623      *
624      */
625     public static class CachedExchange extends org.mortbay.jetty.client.CachedExchange
626     {
627         public CachedExchange(boolean cacheFields)
628         {
629             super(cacheFields);
630         }
631     }
632 
633     /**
634      * @deprecated use {@link org.mortbay.jetty.client.ContentExchange}
635      *
636      */
637     public static class ContentExchange extends org.mortbay.jetty.client.ContentExchange
638     {
639 
640     }
641 
642 
643 
644 }