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