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