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.ByteArrayBuffer;
23  import org.mortbay.io.BufferCache.CachedBuffer;
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  import sun.security.action.GetLongAction;
33  
34  
35  /**
36   * An HTTP client API that encapsulates Exchange with a HTTP server.
37   * 
38   * This object encapsulates:<ul>
39   * <li>The HTTP server. (see {@link #setAddress(InetSocketAddress)} or {@link #setURL(String)})
40   * <li>The HTTP request method, URI and HTTP version (see {@link #setMethod(String)}, {@link #setURI(String)}, and {@link #setVersion(int)}
41   * <li>The Request headers (see {@link #addRequestHeader(String, String)} or {@link #setRequestHeader(String, String)})
42   * <li>The Request content (see {@link #setRequestContent(Buffer)} or {@link #setRequestContentSource(InputStream)})
43   * <li>The status of the exchange (see {@link #getStatus()})
44   * <li>Callbacks to handle state changes (see the onXxx methods such as {@link #onRequestComplete()} or {@link #onResponseComplete()}) 
45   * <li>The ability to intercept callbacks (see {@link #setEventListener(HttpEventListener)}
46   * </ul>
47   * 
48   * The HttpExchange class is intended to be used by a developer wishing to have close asynchronous 
49   * interaction with the the exchange.  Typically a developer will extend the HttpExchange class with a derived
50   * class that implements some or all of the onXxx callbacks.  There are also some predefined HttpExchange subtypes
51   * that can be used as a basis (see {@link ContentExchange} and {@link CachedExchange}.
52   * 
53   * <p>Typically the HttpExchange is passed to a the {@link HttpClient#send(HttpExchange)} method, which in 
54   * turn selects a {@link HttpDestination} and calls it's {@link HttpDestination#send(HttpExchange), which 
55   * then creates or selects a {@link HttpConnection} and calls its {@link HttpConnection#send(HttpExchange).
56   * A developer may wish to directly call send on the destination or connection if they wish to bypass 
57   * some handling provided (eg Cookie handling in the HttpDestination).
58   * 
59   * <p>In some circumstances, the HttpClient or HttpDestination may wish to retry a HttpExchange (eg. failed 
60   * pipeline request, authentication retry or redirection).  In such cases, the HttpClient and/or HttpDestination
61   * may insert their own HttpExchangeListener to intercept and filter the call backs intended for the
62   * HttpExchange.
63   * 
64   * @author gregw
65   * @author Guillaume Nodet
66   */
67  public class HttpExchange
68  {
69      public static final int STATUS_START = 0;
70      public static final int STATUS_WAITING_FOR_CONNECTION = 1;
71      public static final int STATUS_WAITING_FOR_COMMIT = 2;
72      public static final int STATUS_SENDING_REQUEST = 3;
73      public static final int STATUS_WAITING_FOR_RESPONSE = 4;
74      public static final int STATUS_PARSING_HEADERS = 5;
75      public static final int STATUS_PARSING_CONTENT = 6;
76      public static final int STATUS_COMPLETED = 7;
77      public static final int STATUS_EXPIRED = 8; 
78      public static final int STATUS_EXCEPTED = 9;
79  
80      InetSocketAddress _address;
81      String _method = HttpMethods.GET;
82      Buffer _scheme = HttpSchemes.HTTP_BUFFER;
83      int _version = HttpVersions.HTTP_1_1_ORDINAL;
84      String _uri;
85      int _status = STATUS_START;
86      HttpFields _requestFields = new HttpFields();
87      Buffer _requestContent;
88      InputStream _requestContentSource;
89      Buffer _requestContentChunk;
90      boolean _retryStatus = false;
91      
92      
93      private HttpEventListener _listener = new Listener();
94      
95      /* ------------------------------------------------------------ */
96      /* ------------------------------------------------------------ */
97      /* ------------------------------------------------------------ */
98      // methods to build request
99  
100     /* ------------------------------------------------------------ */
101     public int getStatus()
102     {
103         return _status;
104     }
105 
106     /* ------------------------------------------------------------ */
107     /** 
108      * @deprecated
109      */
110     public void waitForStatus(int status) throws InterruptedException
111     {
112         synchronized (this)
113         {
114             while (_status < status)
115             {
116                 this.wait();
117             }
118         }
119     }
120 
121     /* ------------------------------------------------------------ */
122     public void reset() 
123     {
124         setStatus(STATUS_START);
125     }
126     
127     /* ------------------------------------------------------------ */
128     void setStatus(int status)
129     {
130         synchronized (this)
131         {
132             _status = status;
133             this.notifyAll();
134 
135             try
136             {
137                 switch (status)
138                 {
139                     case STATUS_WAITING_FOR_CONNECTION:
140                         break;
141 
142                     case STATUS_WAITING_FOR_COMMIT:
143                         break;
144 
145                     case STATUS_SENDING_REQUEST:
146                         break;
147 
148                     case HttpExchange.STATUS_WAITING_FOR_RESPONSE:
149                         getEventListener().onRequestCommitted();
150                         break;
151 
152                     case STATUS_PARSING_HEADERS:
153                         break;
154 
155                     case STATUS_PARSING_CONTENT:
156                         getEventListener().onResponseHeaderComplete();
157                         break;
158 
159                     case STATUS_COMPLETED:
160                         getEventListener().onResponseComplete();
161                         break;
162 
163                     case STATUS_EXPIRED:
164                         getEventListener().onExpire();
165                         break;
166 
167                 }
168             }
169             catch (IOException e)
170             {
171                 Log.warn(e);
172             }
173         }
174     }
175 
176     /* ------------------------------------------------------------ */
177     public HttpEventListener getEventListener()
178     {
179         return _listener;
180     }
181     
182     /* ------------------------------------------------------------ */
183     public void setEventListener(HttpEventListener listener)
184     {
185         _listener=listener;
186     }
187     
188     /* ------------------------------------------------------------ */
189     /**
190      * @param url Including protocol, host and port
191      */
192     public void setURL(String url)
193     {
194         HttpURI uri = new HttpURI(url);
195         String scheme = uri.getScheme();
196         if (scheme != null)
197         {
198             if (HttpSchemes.HTTP.equalsIgnoreCase(scheme))
199                 setScheme(HttpSchemes.HTTP_BUFFER);
200             else if (HttpSchemes.HTTPS.equalsIgnoreCase(scheme))
201                 setScheme(HttpSchemes.HTTPS_BUFFER);
202             else
203                 setScheme(new ByteArrayBuffer(scheme));
204         }
205 
206         int port = uri.getPort();
207         if (port <= 0)
208             port = "https".equalsIgnoreCase(scheme)?443:80;
209 
210         setAddress(new InetSocketAddress(uri.getHost(),port));
211         
212         String completePath = uri.getCompletePath();
213         if (completePath != null)
214             setURI(completePath);
215     }
216 
217     /* ------------------------------------------------------------ */
218     /**
219      * @param address
220      */
221     public void setAddress(InetSocketAddress address)
222     {
223         _address = address;
224     }
225 
226     /* ------------------------------------------------------------ */
227     /**
228      * @return
229      */
230     public InetSocketAddress getAddress()
231     {
232         return _address;
233     }
234 
235     /* ------------------------------------------------------------ */
236     /**
237      * @param scheme
238      */
239     public void setScheme(Buffer scheme)
240     {
241         _scheme = scheme;
242     }
243 
244     /* ------------------------------------------------------------ */
245     /**
246      * @return
247      */
248     public Buffer getScheme()
249     {
250         return _scheme;
251     }
252 
253     /* ------------------------------------------------------------ */
254     /**
255      * @param version as integer, 9, 10 or 11 for 0.9, 1.0 or 1.1
256      */
257     public void setVersion(int version)
258     {
259         _version = version;
260     }
261 
262     /* ------------------------------------------------------------ */
263     public void setVersion(String version)
264     {
265         CachedBuffer v = HttpVersions.CACHE.get(version);
266         if (v == null)
267             _version = 10;
268         else
269             _version = v.getOrdinal();
270     }
271 
272     /* ------------------------------------------------------------ */
273     /**
274      * @return
275      */
276     public int getVersion()
277     {
278         return _version;
279     }
280 
281     /* ------------------------------------------------------------ */
282     /**
283      * @param method
284      */
285     public void setMethod(String method)
286     {
287         _method = method;
288     }
289 
290     /* ------------------------------------------------------------ */
291     /**
292      * @return
293      */
294     public String getMethod()
295     {
296         return _method;
297     }
298 
299     /* ------------------------------------------------------------ */
300     /**
301      * @return
302      */
303     public String getURI()
304     {
305         return _uri;
306     }
307 
308     /* ------------------------------------------------------------ */
309     /**
310      * @param uri
311      */
312     public void setURI(String uri)
313     {
314         _uri = uri;
315     }
316 
317     /* ------------------------------------------------------------ */
318     /**
319      * @param name
320      * @param value
321      */
322     public void addRequestHeader(String name, String value)
323     {
324         getRequestFields().add(name,value);
325     }
326 
327     /* ------------------------------------------------------------ */
328     /**
329      * @param name
330      * @param value
331      */
332     public void addRequestHeader(Buffer name, Buffer value)
333     {
334         getRequestFields().add(name,value);
335     }
336 
337     /* ------------------------------------------------------------ */
338     /**
339      * @param name
340      * @param value
341      */
342     public void setRequestHeader(String name, String value)
343     {
344         getRequestFields().put(name,value);
345     }
346 
347     /* ------------------------------------------------------------ */
348     /**
349      * @param name
350      * @param value
351      */
352     public void setRequestHeader(Buffer name, Buffer value)
353     {
354         getRequestFields().put(name,value);
355     }
356 
357     /* ------------------------------------------------------------ */
358     /**
359      * @param value
360      */
361     public void setRequestContentType(String value)
362     {
363         getRequestFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,value);
364     }
365 
366     /* ------------------------------------------------------------ */
367     /**
368      * @return
369      */
370     public HttpFields getRequestFields()
371     {
372         return _requestFields;
373     }
374 
375     /* ------------------------------------------------------------ */
376     /* ------------------------------------------------------------ */
377     /* ------------------------------------------------------------ */
378     // methods to commit and/or send the request
379 
380     /* ------------------------------------------------------------ */
381     /**
382      * @param requestContent
383      */
384     public void setRequestContent(Buffer requestContent)
385     {
386         _requestContent = requestContent;
387     }
388 
389     /* ------------------------------------------------------------ */
390     /**
391      * @param in
392      */
393     public void setRequestContentSource(InputStream in)
394     {
395         _requestContentSource = in;
396     }
397 
398     /* ------------------------------------------------------------ */
399     public InputStream getRequestContentSource()
400     {
401         return _requestContentSource;
402     }
403 
404     /* ------------------------------------------------------------ */
405     public Buffer getRequestContentChunk() throws IOException
406     {
407         synchronized (this)
408         {
409             if (_requestContentChunk == null)
410                 _requestContentChunk = new ByteArrayBuffer(4096); // TODO configure
411             else
412             {
413                 if (_requestContentChunk.hasContent())
414                     throw new IllegalStateException();
415                 _requestContentChunk.clear();
416             }
417 
418             int read = _requestContentChunk.capacity();
419             int length = _requestContentSource.read(_requestContentChunk.array(),0,read);
420             if (length >= 0)
421             {
422                 _requestContentChunk.setPutIndex(length);
423                 return _requestContentChunk;
424             }
425             return null;
426         }
427     }
428 
429     /* ------------------------------------------------------------ */
430     public Buffer getRequestContent()
431     {
432         return _requestContent;
433     }
434 
435     public boolean getRetryStatus() 
436     {
437         return _retryStatus;
438     }
439     
440     public void setRetryStatus( boolean retryStatus ) 
441     {
442         _retryStatus = retryStatus;
443     }
444     
445     /* ------------------------------------------------------------ */
446     /** Cancel this exchange
447      * Currently this implementation does nothing.
448      */
449     public void cancel()
450     {
451         
452     }
453 
454     /* ------------------------------------------------------------ */
455     public String toString()
456     {
457         return "HttpExchange@" + hashCode() + "=" + _method + "//" + _address.getHostName() + ":" + _address.getPort() + _uri + "#" + _status;
458     }
459 
460     
461 
462     /* ------------------------------------------------------------ */
463     /* ------------------------------------------------------------ */
464     /* ------------------------------------------------------------ */
465     // methods to handle response
466     protected void onRequestCommitted() throws IOException
467     {
468     }
469 
470     protected void onRequestComplete() throws IOException
471     {
472     }
473 
474     protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
475     {
476     }
477 
478     protected void onResponseHeader(Buffer name, Buffer value) throws IOException
479     {
480     }
481 
482     protected void onResponseHeaderComplete() throws IOException
483     {
484     }
485 
486     protected void onResponseContent(Buffer content) throws IOException
487     {
488     }
489 
490     protected void onResponseComplete() throws IOException
491     {
492     }
493 
494     protected void onConnectionFailed(Throwable ex)
495     {
496         Log.warn("CONNECTION FAILED on " + this,ex);
497     }
498 
499     protected void onException(Throwable ex)
500     {
501         
502         Log.warn("EXCEPTION on " + this,ex);
503     }
504 
505     protected void onExpire()
506     {
507         Log.debug("EXPIRED " + this);
508     }
509 
510     protected void onRetry()
511     {        
512     }
513     
514     private class Listener implements HttpEventListener
515     {
516         public void onConnectionFailed(Throwable ex)
517         {
518             HttpExchange.this.onConnectionFailed(ex);
519         }
520 
521         public void onException(Throwable ex)
522         {
523             HttpExchange.this.onException(ex);
524         }
525 
526         public void onExpire()
527         {
528             HttpExchange.this.onExpire();
529         }
530 
531         public void onRequestCommitted() throws IOException
532         {
533             HttpExchange.this.onRequestCommitted();
534         }
535 
536         public void onRequestComplete() throws IOException
537         {
538             HttpExchange.this.onRequestComplete();
539         }
540 
541         public void onResponseComplete() throws IOException
542         {
543             HttpExchange.this.onResponseComplete();
544         }
545 
546         public void onResponseContent(Buffer content) throws IOException
547         {
548             HttpExchange.this.onResponseContent(content);
549         }
550 
551         public void onResponseHeader(Buffer name, Buffer value) throws IOException
552         {
553             HttpExchange.this.onResponseHeader(name,value);
554         }
555 
556         public void onResponseHeaderComplete() throws IOException
557         {
558             HttpExchange.this.onResponseHeaderComplete();
559         }
560 
561         public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
562         {
563             HttpExchange.this.onResponseStatus(version,status,reason);
564         }
565 
566         public void onRetry()
567         {
568             HttpExchange.this.setRetryStatus( true );
569             HttpExchange.this.onRetry();
570         }
571     }
572     
573     /**
574      * @deprecated use {@link org.mortbay.jetty.client.CachedExchange}
575      *
576      */
577     public static class CachedExchange extends org.mortbay.jetty.client.CachedExchange
578     {
579         public CachedExchange(boolean cacheFields)
580         {
581             super(cacheFields);
582         }
583     }
584 
585     /**
586      * @deprecated use {@link org.mortbay.jetty.client.ContentExchange}
587      *
588      */
589     public static class ContentExchange extends org.mortbay.jetty.client.ContentExchange
590     {
591         
592     }
593     
594 
595 
596 }