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 /* ------------------------------------------------------------ */ getStatus()106 public int getStatus() 107 { 108 return _status; 109 } 110 111 /* ------------------------------------------------------------ */ 112 /** 113 * @deprecated 114 */ waitForStatus(int status)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 waitForDone()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 /* ------------------------------------------------------------ */ reset()141 public void reset() 142 { 143 setStatus(STATUS_START); 144 } 145 146 /* ------------------------------------------------------------ */ setStatus(int status)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 /* ------------------------------------------------------------ */ isDone(int status)196 public boolean isDone (int status) 197 { 198 return ((status == STATUS_COMPLETED) || (status == STATUS_EXPIRED) || (status == STATUS_EXCEPTED)); 199 } 200 201 /* ------------------------------------------------------------ */ getEventListener()202 public HttpEventListener getEventListener() 203 { 204 return _listener; 205 } 206 207 /* ------------------------------------------------------------ */ setEventListener(HttpEventListener listener)208 public void setEventListener(HttpEventListener listener) 209 { 210 _listener=listener; 211 } 212 213 /* ------------------------------------------------------------ */ 214 /** 215 * @param url Including protocol, host and port 216 */ setURL(String url)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 */ setAddress(Address address)246 public void setAddress(Address address) 247 { 248 _address = address; 249 } 250 251 /* ------------------------------------------------------------ */ 252 /** 253 * @return 254 */ getAddress()255 public Address getAddress() 256 { 257 return _address; 258 } 259 260 /* ------------------------------------------------------------ */ 261 /** 262 * @param scheme 263 */ setScheme(Buffer scheme)264 public void setScheme(Buffer scheme) 265 { 266 _scheme = scheme; 267 } 268 269 /* ------------------------------------------------------------ */ 270 /** 271 * @return 272 */ getScheme()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 */ setVersion(int version)282 public void setVersion(int version) 283 { 284 _version = version; 285 } 286 287 /* ------------------------------------------------------------ */ setVersion(String version)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 */ getVersion()301 public int getVersion() 302 { 303 return _version; 304 } 305 306 /* ------------------------------------------------------------ */ 307 /** 308 * @param method 309 */ setMethod(String method)310 public void setMethod(String method) 311 { 312 _method = method; 313 } 314 315 /* ------------------------------------------------------------ */ 316 /** 317 * @return 318 */ getMethod()319 public String getMethod() 320 { 321 return _method; 322 } 323 324 /* ------------------------------------------------------------ */ 325 /** 326 * @return 327 */ getURI()328 public String getURI() 329 { 330 return _uri; 331 } 332 333 /* ------------------------------------------------------------ */ 334 /** 335 * @param uri 336 */ setURI(String uri)337 public void setURI(String uri) 338 { 339 _uri = uri; 340 } 341 342 /* ------------------------------------------------------------ */ 343 /** 344 * @param name 345 * @param value 346 */ addRequestHeader(String name, String value)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 */ addRequestHeader(Buffer name, Buffer value)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 */ setRequestHeader(String name, String value)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 */ setRequestHeader(Buffer name, Buffer value)377 public void setRequestHeader(Buffer name, Buffer value) 378 { 379 getRequestFields().put(name,value); 380 } 381 382 /* ------------------------------------------------------------ */ 383 /** 384 * @param value 385 */ setRequestContentType(String value)386 public void setRequestContentType(String value) 387 { 388 getRequestFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,value); 389 } 390 391 /* ------------------------------------------------------------ */ 392 /** 393 * @return 394 */ getRequestFields()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 */ setRequestContent(Buffer requestContent)409 public void setRequestContent(Buffer requestContent) 410 { 411 _requestContent = requestContent; 412 } 413 414 /* ------------------------------------------------------------ */ 415 /** 416 * @param in 417 */ setRequestContentSource(InputStream in)418 public void setRequestContentSource(InputStream in) 419 { 420 _requestContentSource = in; 421 } 422 423 /* ------------------------------------------------------------ */ getRequestContentSource()424 public InputStream getRequestContentSource() 425 { 426 return _requestContentSource; 427 } 428 429 /* ------------------------------------------------------------ */ getRequestContentChunk()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 /* ------------------------------------------------------------ */ getRequestContent()455 public Buffer getRequestContent() 456 { 457 return _requestContent; 458 } 459 getRetryStatus()460 public boolean getRetryStatus() 461 { 462 return _retryStatus; 463 } 464 setRetryStatus( boolean retryStatus )465 public void setRetryStatus( boolean retryStatus ) 466 { 467 _retryStatus = retryStatus; 468 } 469 470 /* ------------------------------------------------------------ */ 471 /** Cancel this exchange 472 * Currently this implementation does nothing. 473 */ cancel()474 public void cancel() 475 { 476 477 } 478 479 /* ------------------------------------------------------------ */ toString()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 onRequestCommitted()491 protected void onRequestCommitted() throws IOException 492 { 493 } 494 onRequestComplete()495 protected void onRequestComplete() throws IOException 496 { 497 } 498 onResponseStatus(Buffer version, int status, Buffer reason)499 protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException 500 { 501 } 502 onResponseHeader(Buffer name, Buffer value)503 protected void onResponseHeader(Buffer name, Buffer value) throws IOException 504 { 505 } 506 onResponseHeaderComplete()507 protected void onResponseHeaderComplete() throws IOException 508 { 509 } 510 onResponseContent(Buffer content)511 protected void onResponseContent(Buffer content) throws IOException 512 { 513 } 514 onResponseComplete()515 protected void onResponseComplete() throws IOException 516 { 517 } 518 onConnectionFailed(Throwable ex)519 protected void onConnectionFailed(Throwable ex) 520 { 521 Log.warn("CONNECTION FAILED on " + this,ex); 522 } 523 onException(Throwable ex)524 protected void onException(Throwable ex) 525 { 526 527 Log.warn("EXCEPTION on " + this,ex); 528 } 529 onExpire()530 protected void onExpire() 531 { 532 Log.debug("EXPIRED " + this); 533 } 534 onRetry()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 */ configureListeners()545 public boolean configureListeners() 546 { 547 return _configureListeners; 548 } 549 setConfigureListeners(boolean autoConfigure )550 public void setConfigureListeners(boolean autoConfigure ) 551 { 552 this._configureListeners = autoConfigure; 553 } 554 555 private class Listener implements HttpEventListener 556 { onConnectionFailed(Throwable ex)557 public void onConnectionFailed(Throwable ex) 558 { 559 HttpExchange.this.onConnectionFailed(ex); 560 } 561 onException(Throwable ex)562 public void onException(Throwable ex) 563 { 564 HttpExchange.this.onException(ex); 565 } 566 onExpire()567 public void onExpire() 568 { 569 HttpExchange.this.onExpire(); 570 } 571 onRequestCommitted()572 public void onRequestCommitted() throws IOException 573 { 574 HttpExchange.this.onRequestCommitted(); 575 } 576 onRequestComplete()577 public void onRequestComplete() throws IOException 578 { 579 HttpExchange.this.onRequestComplete(); 580 } 581 onResponseComplete()582 public void onResponseComplete() throws IOException 583 { 584 HttpExchange.this.onResponseComplete(); 585 } 586 onResponseContent(Buffer content)587 public void onResponseContent(Buffer content) throws IOException 588 { 589 HttpExchange.this.onResponseContent(content); 590 } 591 onResponseHeader(Buffer name, Buffer value)592 public void onResponseHeader(Buffer name, Buffer value) throws IOException 593 { 594 HttpExchange.this.onResponseHeader(name,value); 595 } 596 onResponseHeaderComplete()597 public void onResponseHeaderComplete() throws IOException 598 { 599 HttpExchange.this.onResponseHeaderComplete(); 600 } 601 onResponseStatus(Buffer version, int status, Buffer reason)602 public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException 603 { 604 HttpExchange.this.onResponseStatus(version,status,reason); 605 } 606 onRetry()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 { CachedExchange(boolean cacheFields)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 } 645