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 }