View Javadoc
1 /* 2 * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v 1.159.2.23 2004/02/15 15:48:43 mbecke Exp $ 3 * $Revision: 1.159.2.23 $ 4 * $Date: 2004/02/15 15:48:43 $ 5 * 6 * ==================================================================== 7 * 8 * The Apache Software License, Version 1.1 9 * 10 * Copyright (c) 1999-2003 The Apache Software Foundation. All rights 11 * reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 17 * 1. Redistributions of source code must retain the above copyright 18 * notice, this list of conditions and the following disclaimer. 19 * 20 * 2. Redistributions in binary form must reproduce the above copyright 21 * notice, this list of conditions and the following disclaimer in 22 * the documentation and/or other materials provided with the 23 * distribution. 24 * 25 * 3. The end-user documentation included with the redistribution, if 26 * any, must include the following acknowlegement: 27 * "This product includes software developed by the 28 * Apache Software Foundation (http://www.apache.org/)." 29 * Alternately, this acknowlegement may appear in the software itself, 30 * if and wherever such third-party acknowlegements normally appear. 31 * 32 * 4. The names "The Jakarta Project", "Commons", and "Apache Software 33 * Foundation" must not be used to endorse or promote products derived 34 * from this software without prior written permission. For written 35 * permission, please contact apache@apache.org. 36 * 37 * 5. Products derived from this software may not be called "Apache" 38 * nor may "Apache" appear in their names without prior written 39 * permission of the Apache Group. 40 * 41 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 42 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 43 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 44 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR 45 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 46 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 47 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 48 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 49 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 50 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 51 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 52 * SUCH DAMAGE. 53 * ==================================================================== 54 * 55 * This software consists of voluntary contributions made by many 56 * individuals on behalf of the Apache Software Foundation. For more 57 * information on the Apache Software Foundation, please see 58 * <http://www.apache.org/>. 59 * 60 * [Additional notices, if required by prior licensing conditions] 61 * 62 */ 63 64 package org.apache.commons.httpclient; 65 66 import java.io.ByteArrayInputStream; 67 import java.io.ByteArrayOutputStream; 68 import java.io.IOException; 69 import java.io.InputStream; 70 import java.io.InterruptedIOException; 71 import java.util.HashSet; 72 import java.util.Set; 73 74 import org.apache.commons.httpclient.auth.AuthScheme; 75 import org.apache.commons.httpclient.auth.AuthenticationException; 76 import org.apache.commons.httpclient.auth.HttpAuthenticator; 77 import org.apache.commons.httpclient.auth.MalformedChallengeException; 78 import org.apache.commons.httpclient.auth.NTLMScheme; 79 import org.apache.commons.httpclient.cookie.CookiePolicy; 80 import org.apache.commons.httpclient.cookie.CookieSpec; 81 import org.apache.commons.httpclient.cookie.MalformedCookieException; 82 import org.apache.commons.httpclient.protocol.Protocol; 83 import org.apache.commons.httpclient.util.EncodingUtil; 84 import org.apache.commons.logging.Log; 85 import org.apache.commons.logging.LogFactory; 86 87 /*** 88 * An abstract base implementation of HttpMethod. 89 * <p> 90 * At minimum, subclasses will need to override: 91 * <ul> 92 * <li>{@link #getName} to return the approriate name for this method 93 * </li> 94 * </ul> 95 * 96 * <p> 97 * When a method's request may contain a body, subclasses will typically want 98 * to override: 99 * <ul> 100 * <li>{@link #getRequestContentLength} to indicate the length (in bytes) 101 * of that body</li> 102 * <li>{@link #writeRequestBody writeRequestBody(HttpState,HttpConnection)} 103 * to write the body</li> 104 * </ul> 105 * </p> 106 * 107 * <p> 108 * When a method requires additional request headers, subclasses will typically 109 * want to override: 110 * <ul> 111 * <li>{@link #addRequestHeaders addRequestHeaders(HttpState,HttpConnection)} 112 * to write those headers 113 * </li> 114 * </ul> 115 * </p> 116 * 117 * <p> 118 * When a method expects specific response headers, subclasses may want to 119 * override: 120 * <ul> 121 * <li>{@link #processResponseHeaders processResponseHeaders(HttpState,HttpConnection)} 122 * to handle those headers 123 * </li> 124 * </ul> 125 * </p> 126 * 127 * 128 * @author <a href="mailto:remm@apache.org">Remy Maucherat</a> 129 * @author Rodney Waldhoff 130 * @author Sean C. Sullivan 131 * @author <a href="mailto:dion@apache.org">dIon Gillard</a> 132 * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a> 133 * @author <a href="mailto:dims@apache.org">Davanum Srinivas</a> 134 * @author Ortwin Glück 135 * @author Eric Johnson 136 * @author Michael Becke 137 * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a> 138 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> 139 * @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a> 140 * 141 * @version $Revision: 1.159.2.23 $ $Date: 2004/02/15 15:48:43 $ 142 */ 143 public abstract class HttpMethodBase implements HttpMethod { 144 145 /*** Maximum number of redirects and authentications that will be followed */ 146 private static final int MAX_FORWARDS = 100; 147 148 // -------------------------------------------------------------- Constants 149 150 /*** Log object for this class. */ 151 private static final Log LOG = LogFactory.getLog(HttpMethodBase.class); 152 153 /*** The User-Agent header sent on every request. */ 154 protected static final Header USER_AGENT; 155 156 static { 157 String agent = null; 158 try { 159 agent = System.getProperty("httpclient.useragent"); 160 } catch (SecurityException ignore) { 161 } 162 if (agent == null) { 163 agent = "Jakarta Commons-HttpClient/2.0final"; 164 } 165 USER_AGENT = new Header("User-Agent", agent); 166 } 167 168 // ----------------------------------------------------- Instance variables 169 170 /*** Request headers, if any. */ 171 private HeaderGroup requestHeaders = new HeaderGroup(); 172 173 /*** The Status-Line from the response. */ 174 private StatusLine statusLine = null; 175 176 /*** Response headers, if any. */ 177 private HeaderGroup responseHeaders = new HeaderGroup(); 178 179 /*** Response trailer headers, if any. */ 180 private HeaderGroup responseTrailerHeaders = new HeaderGroup(); 181 182 /*** Authentication scheme used to authenticate againt the target server */ 183 private AuthScheme authScheme = null; 184 185 /*** Realms this method tried to authenticate to */ 186 private Set realms = null; 187 188 /*** Actual authentication realm */ 189 private String realm = null; 190 191 /*** Authentication scheme used to authenticate againt the proxy server */ 192 private AuthScheme proxyAuthScheme = null; 193 194 /*** Proxy Realms this method tried to authenticate to */ 195 private Set proxyRealms = null; 196 197 /*** Actual proxy authentication realm */ 198 private String proxyRealm = null; 199 200 /*** Path of the HTTP method. */ 201 private String path = null; 202 203 /*** Query string of the HTTP method, if any. */ 204 private String queryString = null; 205 206 /*** The response body of the HTTP method, assuming it has not be 207 * intercepted by a sub-class. */ 208 private InputStream responseStream = null; 209 210 /*** The connection that the response stream was read from. */ 211 private HttpConnection responseConnection = null; 212 213 /*** Buffer for the response */ 214 private byte[] responseBody = null; 215 216 /*** True if the HTTP method should automatically follow 217 * HTTP redirects. */ 218 private boolean followRedirects = false; 219 220 /*** True if the HTTP method should automatically handle 221 * HTTP authentication challenges. */ 222 private boolean doAuthentication = true; 223 224 /*** True if version 1.1 of the HTTP protocol should be used per default. */ 225 private boolean http11 = true; 226 227 /*** True if this HTTP method should strictly follow the HTTP protocol 228 * specification. */ 229 private boolean strictMode = false; 230 231 /*** True if this method has already been executed. */ 232 private boolean used = false; 233 234 /*** Count of how many times did this HTTP method transparently handle 235 * a recoverable exception. */ 236 private int recoverableExceptionCount = 0; 237 238 /*** The host configuration for this HTTP method, can be null */ 239 private HostConfiguration hostConfiguration; 240 241 /*** 242 * Handles method retries 243 */ 244 private MethodRetryHandler methodRetryHandler; 245 246 /*** True if this method is currently being executed. */ 247 private boolean inExecute = false; 248 249 /*** True if this HTTP method is finished with the connection */ 250 private boolean doneWithConnection = false; 251 252 /*** True if the connection must be closed when no longer needed */ 253 private boolean connectionCloseForced = false; 254 255 /*** Number of milliseconds to wait for 100-contunue response. */ 256 private static final int RESPONSE_WAIT_TIME_MS = 3000; 257 258 // ----------------------------------------------------------- Constructors 259 260 /*** 261 * No-arg constructor. 262 */ 263 public HttpMethodBase() { 264 } 265 266 /*** 267 * Constructor specifying a URI. 268 * It is responsibility of the caller to ensure that URI elements 269 * (path & query parameters) are properly encoded (URL safe). 270 * 271 * @param uri either an absolute or relative URI. The URI is expected 272 * to be URL-encoded 273 * 274 * @throws IllegalArgumentException when URI is invalid 275 * @throws IllegalStateException when protocol of the absolute URI is not recognised 276 */ 277 public HttpMethodBase(String uri) 278 throws IllegalArgumentException, IllegalStateException { 279 280 try { 281 282 // create a URI and allow for null/empty uri values 283 if (uri == null || uri.equals("")) { 284 uri = "/"; 285 } 286 URI parsedURI = new URI(uri.toCharArray()); 287 288 // only set the host if specified by the URI 289 if (parsedURI.isAbsoluteURI()) { 290 hostConfiguration = new HostConfiguration(); 291 hostConfiguration.setHost( 292 parsedURI.getHost(), 293 parsedURI.getPort(), 294 parsedURI.getScheme() 295 ); 296 } 297 298 // set the path, defaulting to root 299 setPath( 300 parsedURI.getPath() == null 301 ? "/" 302 : parsedURI.getEscapedPath() 303 ); 304 setQueryString(parsedURI.getEscapedQuery()); 305 306 } catch (URIException e) { 307 throw new IllegalArgumentException("Invalid uri '" 308 + uri + "': " + e.getMessage() 309 ); 310 } 311 } 312 313 // ------------------------------------------- Property Setters and Getters 314 315 /*** 316 * Obtains the name of the HTTP method as used in the HTTP request line, 317 * for example <tt>"GET"</tt> or <tt>"POST"</tt>. 318 * 319 * @return the name of this method 320 */ 321 public abstract String getName(); 322 323 /*** 324 * Returns the URI of the HTTP method 325 * 326 * @return The URI 327 * 328 * @throws URIException If the URI cannot be created. 329 * 330 * @see org.apache.commons.httpclient.HttpMethod#getURI() 331 */ 332 public URI getURI() throws URIException { 333 334 if (hostConfiguration == null) { 335 // just use a relative URI, the host hasn't been set 336 URI tmpUri = new URI(null, null, path, null, null); 337 tmpUri.setEscapedQuery(queryString); 338 return tmpUri; 339 } else { 340 341 // we only want to include the port if it's not the default 342 int port = hostConfiguration.getPort(); 343 if (port == hostConfiguration.getProtocol().getDefaultPort()) { 344 port = -1; 345 } 346 347 URI tmpUri = new URI( 348 hostConfiguration.getProtocol().getScheme(), 349 null, 350 hostConfiguration.getHost(), 351 port, 352 path, 353 null // to set an escaped form 354 ); 355 tmpUri.setEscapedQuery(queryString); 356 return tmpUri; 357 358 } 359 360 } 361 362 /*** 363 * Sets whether or not the HTTP method should automatically follow HTTP redirects 364 * (status code 302, etc.) 365 * 366 * @param followRedirects <tt>true</tt> if the method will automatically follow redirects, 367 * <tt>false</tt> otherwise. 368 */ 369 public void setFollowRedirects(boolean followRedirects) { 370 this.followRedirects = followRedirects; 371 } 372 373 /*** 374 * Returns <tt>true</tt> if the HTTP method should automatically follow HTTP redirects 375 * (status code 302, etc.), <tt>false</tt> otherwise. 376 * 377 * @return <tt>true</tt> if the method will automatically follow HTTP redirects, 378 * <tt>false</tt> otherwise. 379 */ 380 public boolean getFollowRedirects() { 381 return this.followRedirects; 382 } 383 384 /*** 385 /** Sets whether version 1.1 of the HTTP protocol should be used per default. 386 * 387 * @param http11 <tt>true</tt> to use HTTP/1.1, <tt>false</tt> to use 1.0 388 */ 389 public void setHttp11(boolean http11) { 390 this.http11 = http11; 391 } 392 393 /*** 394 * Returns <tt>true</tt> if the HTTP method should automatically handle HTTP 395 * authentication challenges (status code 401, etc.), <tt>false</tt> otherwise 396 * 397 * @return <tt>true</tt> if authentication challenges will be processed 398 * automatically, <tt>false</tt> otherwise. 399 * 400 * @since 2.0 401 */ 402 public boolean getDoAuthentication() { 403 return doAuthentication; 404 } 405 406 /*** 407 * Sets whether or not the HTTP method should automatically handle HTTP 408 * authentication challenges (status code 401, etc.) 409 * 410 * @param doAuthentication <tt>true</tt> to process authentication challenges 411 * authomatically, <tt>false</tt> otherwise. 412 * 413 * @since 2.0 414 */ 415 public void setDoAuthentication(boolean doAuthentication) { 416 this.doAuthentication = doAuthentication; 417 } 418 419 // ---------------------------------------------- Protected Utility Methods 420 421 /*** 422 * Returns <tt>true</tt> if version 1.1 of the HTTP protocol should be 423 * used per default, <tt>false</tt> if version 1.0 should be used. 424 * 425 * @return <tt>true</tt> to use HTTP/1.1, <tt>false</tt> to use 1.0 426 */ 427 public boolean isHttp11() { 428 return http11; 429 } 430 431 /*** 432 * Sets the path of the HTTP method. 433 * It is responsibility of the caller to ensure that the path is 434 * properly encoded (URL safe). 435 * 436 * @param path the path of the HTTP method. The path is expected 437 * to be URL-encoded 438 */ 439 public void setPath(String path) { 440 this.path = path; 441 } 442 443 /*** 444 * Adds the specified request header, NOT overwriting any previous value. 445 * Note that header-name matching is case insensitive. 446 * 447 * @param header the header to add to the request 448 */ 449 public void addRequestHeader(Header header) { 450 LOG.trace("HttpMethodBase.addRequestHeader(Header)"); 451 452 if (header == null) { 453 LOG.debug("null header value ignored"); 454 } else { 455 getRequestHeaderGroup().addHeader(header); 456 } 457 } 458 459 /*** 460 * Use this method internally to add footers. 461 * 462 * @param footer The footer to add. 463 */ 464 public void addResponseFooter(Header footer) { 465 getResponseTrailerHeaderGroup().addHeader(footer); 466 } 467 468 /*** 469 * Gets the path of this HTTP method. 470 * Calling this method <em>after</em> the request has been executed will 471 * return the <em>actual</em> path, following any redirects automatically 472 * handled by this HTTP method. 473 * 474 * @return the path to request or "/" if the path is blank. 475 */ 476 public String getPath() { 477 return (path == null || path.equals("")) ? "/" : path; 478 } 479 480 /*** 481 * Sets the query string of this HTTP method. The caller must ensure that the string 482 * is properly URL encoded. The query string should not start with the question 483 * mark character. 484 * 485 * @param queryString the query string 486 * 487 * @see EncodingUtil#formUrlEncode(NameValuePair[], String) 488 */ 489 public void setQueryString(String queryString) { 490 this.queryString = queryString; 491 } 492 493 /*** 494 * Sets the query string of this HTTP method. The pairs are encoded as UTF-8 characters. 495 * To use a different charset the parameters can be encoded manually using EncodingUtil 496 * and set as a single String. 497 * 498 * @param params an array of {@link NameValuePair}s to add as query string 499 * parameters. The name/value pairs will be automcatically 500 * URL encoded 501 * 502 * @see EncodingUtil#formUrlEncode(NameValuePair[], String) 503 * @see #setQueryString(String) 504 */ 505 public void setQueryString(NameValuePair[] params) { 506 LOG.trace("enter HttpMethodBase.setQueryString(NameValuePair[])"); 507 queryString = EncodingUtil.formUrlEncode(params, "UTF-8"); 508 } 509 510 /*** 511 * Gets the query string of this HTTP method. 512 * 513 * @return The query string 514 */ 515 public String getQueryString() { 516 return queryString; 517 } 518 519 /*** 520 * Set the specified request header, overwriting any previous value. Note 521 * that header-name matching is case-insensitive. 522 * 523 * @param headerName the header's name 524 * @param headerValue the header's value 525 */ 526 public void setRequestHeader(String headerName, String headerValue) { 527 Header header = new Header(headerName, headerValue); 528 setRequestHeader(header); 529 } 530 531 /*** 532 * Sets the specified request header, overwriting any previous value. 533 * Note that header-name matching is case insensitive. 534 * 535 * @param header the header 536 */ 537 public void setRequestHeader(Header header) { 538 539 Header[] headers = getRequestHeaderGroup().getHeaders(header.getName()); 540 541 for (int i = 0; i < headers.length; i++) { 542 getRequestHeaderGroup().removeHeader(headers[i]); 543 } 544 545 getRequestHeaderGroup().addHeader(header); 546 547 } 548 549 /*** 550 * Returns the specified request header. Note that header-name matching is 551 * case insensitive. <tt>null</tt> will be returned if either 552 * <i>headerName</i> is <tt>null</tt> or there is no matching header for 553 * <i>headerName</i>. 554 * 555 * @param headerName The name of the header to be returned. 556 * 557 * @return The specified request header. 558 */ 559 public Header getRequestHeader(String headerName) { 560 if (headerName == null) { 561 return null; 562 } else { 563 return getRequestHeaderGroup().getCondensedHeader(headerName); 564 } 565 } 566 567 /*** 568 * Returns an array of the requests headers that the HTTP method currently has 569 * 570 * @return an array of my request headers. 571 */ 572 public Header[] getRequestHeaders() { 573 return getRequestHeaderGroup().getAllHeaders(); 574 } 575 576 /*** 577 * Gets the {@link HeaderGroup header group} storing the request headers. 578 * 579 * @return a HeaderGroup 580 * 581 * @since 2.0beta1 582 */ 583 protected HeaderGroup getRequestHeaderGroup() { 584 return requestHeaders; 585 } 586 587 /*** 588 * Gets the {@link HeaderGroup header group} storing the response trailer headers 589 * as per RFC 2616 section 3.6.1. 590 * 591 * @return a HeaderGroup 592 * 593 * @since 2.0beta1 594 */ 595 protected HeaderGroup getResponseTrailerHeaderGroup() { 596 return responseTrailerHeaders; 597 } 598 599 /*** 600 * Gets the {@link HeaderGroup header group} storing the response headers. 601 * 602 * @return a HeaderGroup 603 * 604 * @since 2.0beta1 605 */ 606 protected HeaderGroup getResponseHeaderGroup() { 607 return responseHeaders; 608 } 609 610 /*** 611 * Returns the response status code. 612 * 613 * @return the status code associated with the latest response. 614 */ 615 public int getStatusCode() { 616 return statusLine.getStatusCode(); 617 } 618 619 /*** 620 * Provides access to the response status line. 621 * 622 * @return the status line object from the latest response. 623 * @since 2.0 624 */ 625 public StatusLine getStatusLine() { 626 return statusLine; 627 } 628 629 /*** 630 * Checks if response data is available. 631 * @return <tt>true</tt> if response data is available, <tt>false</tt> otherwise. 632 */ 633 private boolean responseAvailable() { 634 return (responseBody != null) || (responseStream != null); 635 } 636 637 /*** 638 * Returns an array of the response headers that the HTTP method currently has 639 * in the order in which they were read. 640 * 641 * @return an array of response headers. 642 */ 643 public Header[] getResponseHeaders() { 644 return getResponseHeaderGroup().getAllHeaders(); 645 } 646 647 /*** 648 * Gets the response header associated with the given name. Header name 649 * matching is case insensitive. <tt>null</tt> will be returned if either 650 * <i>headerName</i> is <tt>null</tt> or there is no matching header for 651 * <i>headerName</i>. 652 * 653 * @param headerName the header name to match 654 * 655 * @return the matching header 656 */ 657 public Header getResponseHeader(String headerName) { 658 if (headerName == null) { 659 return null; 660 } else { 661 return getResponseHeaderGroup().getCondensedHeader(headerName); 662 } 663 } 664 665 666 /*** 667 * Return the length (in bytes) of the response body, as specified in a 668 * <tt>Content-Length</tt> header. 669 * 670 * <p> 671 * Return <tt>-1</tt> when the content-length is unknown. 672 * </p> 673 * 674 * @return content length, if <tt>Content-Length</tt> header is available. 675 * <tt>0</tt> indicates that the request has no body. 676 * If <tt>Content-Length</tt> header is not present, the method 677 * returns <tt>-1</tt>. 678 */ 679 protected int getResponseContentLength() { 680 Header[] headers = getResponseHeaderGroup().getHeaders("Content-Length"); 681 if (headers.length == 0) { 682 return -1; 683 } 684 if (headers.length > 1) { 685 LOG.warn("Multiple content-length headers detected"); 686 } 687 for (int i = headers.length - 1; i >= 0; i++) { 688 Header header = headers[i]; 689 try { 690 return Integer.parseInt(header.getValue()); 691 } catch (NumberFormatException e) { 692 if (LOG.isWarnEnabled()) { 693 LOG.warn("Invalid content-length value: " + e.getMessage()); 694 } 695 } 696 // See if we can have better luck with another header, if present 697 } 698 return -1; 699 } 700 701 702 /*** 703 * Returns the response body of the HTTP method, if any, as an array of bytes. 704 * If response body is not available or cannot be read, returns <tt>null</tt> 705 * 706 * @return The response body. 707 */ 708 public byte[] getResponseBody() { 709 if (this.responseBody == null) { 710 try { 711 InputStream instream = getResponseBodyAsStream(); 712 if (instream != null) { 713 LOG.debug("Buffering response body"); 714 ByteArrayOutputStream outstream = new ByteArrayOutputStream(); 715 byte[] buffer = new byte[4096]; 716 int len; 717 while ((len = instream.read(buffer)) > 0) { 718 outstream.write(buffer, 0, len); 719 } 720 outstream.close(); 721 setResponseStream(null); 722 this.responseBody = outstream.toByteArray(); 723 } 724 } catch (IOException e) { 725 LOG.error("I/O failure reading response body", e); 726 this.responseBody = null; 727 } 728 } 729 return this.responseBody; 730 } 731 732 /*** 733 * Returns the response body of the HTTP method, if any, as an {@link InputStream}. 734 * If response body is not available, returns <tt>null</tt> 735 * 736 * @return The response body 737 * 738 * @throws IOException If an I/O (transport) problem occurs while obtaining the 739 * response body. 740 */ 741 public InputStream getResponseBodyAsStream() throws IOException { 742 if (responseStream != null) { 743 return responseStream; 744 } 745 if (responseBody != null) { 746 InputStream byteResponseStream = new ByteArrayInputStream(responseBody); 747 LOG.debug("re-creating response stream from byte array"); 748 return byteResponseStream; 749 } 750 return null; 751 } 752 753 /*** 754 * Returns the response body of the HTTP method, if any, as a {@link String}. 755 * If response body is not available or cannot be read, returns <tt>null</tt> 756 * The string conversion on the data is done using the character encoding specified 757 * in <tt>Content-Type</tt> header. 758 * 759 * @return The response body. 760 */ 761 public String getResponseBodyAsString() { 762 byte[] rawdata = null; 763 if (responseAvailable()) { 764 rawdata = getResponseBody(); 765 } 766 if (rawdata != null) { 767 return HttpConstants.getContentString(rawdata, getResponseCharSet()); 768 } else { 769 return null; 770 } 771 } 772 773 /*** 774 * Returns an array of the response footers that the HTTP method currently has 775 * in the order in which they were read. 776 * 777 * @return an array of footers 778 */ 779 public Header[] getResponseFooters() { 780 return getResponseTrailerHeaderGroup().getAllHeaders(); 781 } 782 783 /*** 784 * Gets the response footer associated with the given name. 785 * Footer name matching is case insensitive. 786 * <tt>null</tt> will be returned if either <i>footerName</i> is 787 * <tt>null</tt> or there is no matching footer for <i>footerName</i> 788 * or there are no footers available. If there are multiple footers 789 * with the same name, there values will be combined with the ',' separator 790 * as specified by RFC2616. 791 * 792 * @param footerName the footer name to match 793 * @return the matching footer 794 */ 795 public Header getResponseFooter(String footerName) { 796 if (footerName == null) { 797 return null; 798 } else { 799 return getResponseTrailerHeaderGroup().getCondensedHeader(footerName); 800 } 801 } 802 803 /*** 804 * Sets the response stream. 805 * @param responseStream The new response stream. 806 */ 807 protected void setResponseStream(InputStream responseStream) { 808 this.responseStream = responseStream; 809 } 810 811 /*** 812 * Returns a stream from which the body of the current response may be read. 813 * If the method has not yet been executed, if <code>responseBodyConsumed</code> 814 * has been called, or if the stream returned by a previous call has been closed, 815 * <code>null</code> will be returned. 816 * 817 * @return the current response stream 818 */ 819 protected InputStream getResponseStream() { 820 return responseStream; 821 } 822 823 /*** 824 * Returns the status text (or "reason phrase") associated with the latest 825 * response. 826 * 827 * @return The status text. 828 */ 829 public String getStatusText() { 830 return statusLine.getReasonPhrase(); 831 } 832 833 /*** 834 * Defines how strictly HttpClient follows the HTTP protocol specification 835 * (RFC 2616 and other relevant RFCs). In the strict mode HttpClient precisely 836 * implements the requirements of the specification, whereas in non-strict mode 837 * it attempts to mimic the exact behaviour of commonly used HTTP agents, 838 * which many HTTP servers expect. 839 * 840 * @param strictMode <tt>true</tt> for strict mode, <tt>false</tt> otherwise 841 */ 842 public void setStrictMode(boolean strictMode) { 843 this.strictMode = strictMode; 844 } 845 846 /*** 847 * Returns the value of the strict mode flag. 848 * 849 * @return <tt>true</tt> if strict mode is enabled, <tt>false</tt> otherwise 850 */ 851 public boolean isStrictMode() { 852 return strictMode; 853 } 854 855 /*** 856 * Adds the specified request header, NOT overwriting any previous value. 857 * Note that header-name matching is case insensitive. 858 * 859 * @param headerName the header's name 860 * @param headerValue the header's value 861 */ 862 public void addRequestHeader(String headerName, String headerValue) { 863 addRequestHeader(new Header(headerName, headerValue)); 864 } 865 866 /*** 867 * Tests if the connection should be force-closed when no longer needed. 868 * 869 * @return <code>true</code> if the connection must be closed 870 */ 871 protected boolean isConnectionCloseForced() { 872 return this.connectionCloseForced; 873 } 874 875 /*** 876 * Sets whether or not the connection should be force-closed when no longer 877 * needed. This value should only be set to <code>true</code> in abnormal 878 * circumstances, such as HTTP protocol violations. 879 * 880 * @param b <code>true</code> if the connection must be closed, <code>false</code> 881 * otherwise. 882 */ 883 protected void setConnectionCloseForced(boolean b) { 884 if (LOG.isDebugEnabled()) { 885 LOG.debug("Force-close connection: " + b); 886 } 887 this.connectionCloseForced = b; 888 } 889 890 /*** 891 * Tests if the connection should be closed after the method has been executed. 892 * The connection will be left open when using HTTP/1.1 or if <tt>Connection: 893 * keep-alive</tt> header was sent. 894 * 895 * @param conn the connection in question 896 * 897 * @return boolean true if we should close the connection. 898 */ 899 protected boolean shouldCloseConnection(HttpConnection conn) { 900 901 // Connection must be closed due to an abnormal circumstance 902 if (isConnectionCloseForced()) { 903 LOG.debug("Should force-close connection."); 904 return true; 905 } 906 907 Header connectionHeader = null; 908 // In case being connected via a proxy server 909 if (!conn.isTransparent()) { 910 // Check for 'proxy-connection' directive 911 connectionHeader = responseHeaders.getFirstHeader("proxy-connection"); 912 } 913 // In all cases Check for 'connection' directive 914 // some non-complaint proxy servers send it instread of 915 // expected 'proxy-connection' directive 916 if (connectionHeader == null) { 917 connectionHeader = responseHeaders.getFirstHeader("connection"); 918 } 919 if (connectionHeader != null) { 920 if (connectionHeader.getValue().equalsIgnoreCase("close")) { 921 if (LOG.isDebugEnabled()) { 922 LOG.debug("Should close connection in response to " 923 + connectionHeader.toExternalForm()); 924 } 925 return true; 926 } else if (connectionHeader.getValue().equalsIgnoreCase("keep-alive")) { 927 if (LOG.isDebugEnabled()) { 928 LOG.debug("Should NOT close connection in response to " 929 + connectionHeader.toExternalForm()); 930 } 931 return false; 932 } else { 933 if (LOG.isDebugEnabled()) { 934 LOG.debug("Unknown directive: " + connectionHeader.toExternalForm()); 935 } 936 } 937 } 938 LOG.debug("Resorting to protocol version default close connection policy"); 939 // missing or invalid connection header, do the default 940 if (http11) { 941 LOG.debug("Should NOT close connection, using HTTP/1.1."); 942 } else { 943 LOG.debug("Should close connection, using HTTP/1.0."); 944 } 945 return !http11; 946 } 947 948 /*** 949 * Tests if the method needs to be retried. 950 * @param statusCode The status code 951 * @param state the {@link HttpState state} information associated with this method 952 * @param conn the {@link HttpConnection connection} to be used 953 * @return boolean true if a retry is needed. 954 */ 955 private boolean isRetryNeeded(int statusCode, HttpState state, HttpConnection conn) { 956 switch (statusCode) { 957 case HttpStatus.SC_UNAUTHORIZED: 958 case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: 959 LOG.debug("Authorization required"); 960 if (doAuthentication) { //process authentication response 961 //if the authentication is successful, return the statusCode 962 //otherwise, drop through the switch and try again. 963 if (processAuthenticationResponse(state, conn)) { 964 return false; 965 } 966 } else { //let the client handle the authenticaiton 967 return false; 968 } 969 break; 970 971 case HttpStatus.SC_MOVED_TEMPORARILY: 972 case HttpStatus.SC_MOVED_PERMANENTLY: 973 case HttpStatus.SC_SEE_OTHER: 974 case HttpStatus.SC_TEMPORARY_REDIRECT: 975 LOG.debug("Redirect required"); 976 977 if (!processRedirectResponse(conn)) { 978 return false; 979 } 980 break; 981 982 default: 983 // neither an unauthorized nor a redirect response 984 return false; 985 } //end of switch 986 987 return true; 988 } 989 990 /*** 991 * Tests if the this method is ready to be executed. 992 * 993 * @param state the {@link HttpState state} information associated with this method 994 * @param conn the {@link HttpConnection connection} to be used 995 * @throws HttpException If the method is in invalid state. 996 */ 997 private void checkExecuteConditions(HttpState state, HttpConnection conn) 998 throws HttpException { 999 1000 if (state == null) { 1001 throw new IllegalArgumentException("HttpState parameter may not be null"); 1002 } 1003 if (conn == null) { 1004 throw new IllegalArgumentException("HttpConnection parameter may not be null"); 1005 } 1006 if (hasBeenUsed()) { 1007 throw new HttpException("Already used, but not recycled."); 1008 } 1009 if (!validate()) { 1010 throw new HttpException("Not valid"); 1011 } 1012 if (inExecute) { 1013 throw new IllegalStateException("Execute invoked recursively, or exited abnormally."); 1014 } 1015 } 1016 1017 /*** 1018 * Execute this HTTP method. Note that we cannot currently support redirects 1019 * that change the connection parameters (host, port, protocol) because 1020 * we don't yet have a good way to get the new connection. For the time 1021 * being, we just return the redirect response code, and allow the user 1022 * agent to resubmit if desired. 1023 * 1024 * @param state {@link HttpState state} information to associate with this 1025 * request. Must be non-null. 1026 * @param conn the {@link HttpConnection connection} to used to execute 1027 * this HTTP method. Must be non-null. 1028 * Note that we cannot currently support redirects that 1029 * change the HttpConnection parameters (host, port, protocol) 1030 * because we don't yet have a good way to get the new connection. 1031 * For the time being, we just return the 302 response, and allow 1032 * the user agent to resubmit if desired. 1033 * 1034 * @return the integer status code if one was obtained, or <tt>-1</tt> 1035 * 1036 * @throws IOException if an I/O (transport) error occurs 1037 * @throws HttpException if a protocol exception occurs. 1038 * @throws HttpRecoverableException if a recoverable transport error occurs. 1039 * Usually this kind of exceptions can be recovered from by 1040 * retrying the HTTP method 1041 */ 1042 public int execute(HttpState state, HttpConnection conn) 1043 throws HttpException, HttpRecoverableException, 1044 IOException { 1045 1046 LOG.trace("enter HttpMethodBase.execute(HttpState, HttpConnection)"); 1047 1048 // this is our connection now, assign it to a local variable so 1049 // that it can be released later 1050 this.responseConnection = conn; 1051 1052 checkExecuteConditions(state, conn); 1053 inExecute = true; 1054 1055 try { 1056 //pre-emptively add the authorization header, if required. 1057 if (state.isAuthenticationPreemptive()) { 1058 1059 LOG.debug("Preemptively sending default basic credentials"); 1060 1061 try { 1062 if (HttpAuthenticator.authenticateDefault(this, conn, state)) { 1063 LOG.debug("Default basic credentials applied"); 1064 } 1065 if (conn.isProxied()) { 1066 if (HttpAuthenticator.authenticateProxyDefault(this, conn, state)) { 1067 LOG.debug("Default basic proxy credentials applied"); 1068 } 1069 } 1070 } catch (AuthenticationException e) { 1071 // Log error and move on 1072 LOG.error(e.getMessage(), e); 1073 } 1074 } 1075 1076 realms = new HashSet(); 1077 proxyRealms = new HashSet(); 1078 int forwardCount = 0; //protect from an infinite loop 1079 1080 while (forwardCount++ < MAX_FORWARDS) { 1081 // on every retry, reset this state information. 1082 conn.setLastResponseInputStream(null); 1083 1084 if (LOG.isDebugEnabled()) { 1085 LOG.debug("Execute loop try " + forwardCount); 1086 } 1087 1088 // Discard status line 1089 this.statusLine = null; 1090 this.connectionCloseForced = false; 1091 1092 //write the request and read the response, will retry 1093 processRequest(state, conn); 1094 1095 if (!isRetryNeeded(statusLine.getStatusCode(), state, conn)) { 1096 // nope, no retry needed, exit loop. 1097 break; 1098 } 1099 1100 // retry - close previous stream. Caution - this causes 1101 // responseBodyConsumed to be called, which may also close the 1102 // connection. 1103 if (responseStream != null) { 1104 responseStream.close(); 1105 } 1106 1107 } //end of retry loop 1108 1109 if (forwardCount >= MAX_FORWARDS) { 1110 LOG.error("Narrowly avoided an infinite loop in execute"); 1111 throw new HttpRecoverableException("Maximum redirects (" 1112 + MAX_FORWARDS + ") exceeded"); 1113 } 1114 1115 } finally { 1116 inExecute = false; 1117 // If the response has been fully processed, return the connection 1118 // to the pool. Use this flag, rather than other tests (like 1119 // responseStream == null), as subclasses, might reset the stream, 1120 // for example, reading the entire response into a file and then 1121 // setting the file as the stream. 1122 if (doneWithConnection) { 1123 ensureConnectionRelease(); 1124 } 1125 } 1126 1127 return statusLine.getStatusCode(); 1128 } 1129 1130 /*** 1131 * Process the redirect response. 1132 * @param conn the {@link HttpConnection connection} used to execute 1133 * this HTTP method 1134 * @return boolean <tt>true</tt> if the redirect was successful, <tt>false</tt> 1135 * otherwise. 1136 */ 1137 private boolean processRedirectResponse(HttpConnection conn) { 1138 1139 if (!getFollowRedirects()) { 1140 LOG.info("Redirect requested but followRedirects is " 1141 + "disabled"); 1142 return false; 1143 } 1144 1145 //get the location header to find out where to redirect to 1146 Header locationHeader = getResponseHeader("location"); 1147 if (locationHeader == null) { 1148 // got a redirect response, but no location header 1149 LOG.error("Received redirect response " + getStatusCode() 1150 + " but no location header"); 1151 return false; 1152 } 1153 String location = locationHeader.getValue(); 1154 if (LOG.isDebugEnabled()) { 1155 LOG.debug("Redirect requested to location '" + location 1156 + "'"); 1157 } 1158 1159 //rfc2616 demands the location value be a complete URI 1160 //Location = "Location" ":" absoluteURI 1161 URI redirectUri = null; 1162 URI currentUri = null; 1163 1164 try { 1165 currentUri = new URI( 1166 conn.getProtocol().getScheme(), 1167 null, 1168 conn.getHost(), 1169 conn.getPort(), 1170 this.getPath() 1171 ); 1172 redirectUri = new URI(location.toCharArray()); 1173 if (redirectUri.isRelativeURI()) { 1174 if (isStrictMode()) { 1175 LOG.warn("Redirected location '" + location 1176 + "' is not acceptable in strict mode"); 1177 return false; 1178 } else { 1179 //location is incomplete, use current values for defaults 1180 LOG.debug("Redirect URI is not absolute - parsing as relative"); 1181 redirectUri = new URI(currentUri, redirectUri); 1182 } 1183 } 1184 } catch (URIException e) { 1185 LOG.warn("Redirected location '" + location + "' is malformed"); 1186 return false; 1187 } 1188 1189 //check for redirect to a different protocol, host or port 1190 try { 1191 checkValidRedirect(currentUri, redirectUri); 1192 } catch (HttpException ex) { 1193 //LOG the error and let the client handle the redirect 1194 LOG.warn(ex.getMessage()); 1195 return false; 1196 } 1197 1198 //invalidate the list of authentication attempts 1199 this.realms.clear(); 1200 //remove exisitng authentication headers 1201 if (this.proxyAuthScheme instanceof NTLMScheme) { 1202 removeRequestHeader(HttpAuthenticator.PROXY_AUTH_RESP); 1203 } 1204 removeRequestHeader(HttpAuthenticator.WWW_AUTH_RESP); 1205 //update the current location with the redirect location. 1206 //avoiding use of URL.getPath() and URL.getQuery() to keep 1207 //jdk1.2 comliance. 1208 setPath(redirectUri.getEscapedPath()); 1209 setQueryString(redirectUri.getEscapedQuery()); 1210 1211 if (LOG.isDebugEnabled()) { 1212 LOG.debug("Redirecting from '" + currentUri.getEscapedURI() 1213 + "' to '" + redirectUri.getEscapedURI()); 1214 } 1215 1216 return true; 1217 } 1218 1219 /*** 1220 * Check for a valid redirect given the current connection and new URI. 1221 * Redirect to a different protocol, host or port are checked for validity. 1222 * 1223 * @param currentUri The current URI (redirecting from) 1224 * @param redirectUri The new URI to redirect to 1225 * @throws HttpException if the redirect is invalid 1226 * @since 2.0 1227 */ 1228 private static void checkValidRedirect(URI currentUri, URI redirectUri) 1229 throws HttpException { 1230 LOG.trace("enter HttpMethodBase.checkValidRedirect(HttpConnection, URL)"); 1231 1232 String oldProtocol = currentUri.getScheme(); 1233 String newProtocol = redirectUri.getScheme(); 1234 if (!oldProtocol.equals(newProtocol)) { 1235 throw new HttpException("Redirect from protocol " + oldProtocol 1236 + " to " + newProtocol + " is not supported"); 1237 } 1238 1239 try { 1240 String oldHost = currentUri.getHost(); 1241 String newHost = redirectUri.getHost(); 1242 if (!oldHost.equalsIgnoreCase(newHost)) { 1243 throw new HttpException("Redirect from host " + oldHost 1244 + " to " + newHost + " is not supported"); 1245 } 1246 } catch (URIException e) { 1247 LOG.warn("Error getting URI host", e); 1248 throw new HttpException("Invalid Redirect URI from: " 1249 + currentUri.getEscapedURI() + " to: " + redirectUri.getEscapedURI() 1250 ); 1251 } 1252 1253 int oldPort = currentUri.getPort(); 1254 if (oldPort < 0) { 1255 oldPort = getDefaultPort(oldProtocol); 1256 } 1257 int newPort = redirectUri.getPort(); 1258 if (newPort < 0) { 1259 newPort = getDefaultPort(newProtocol); 1260 } 1261 if (oldPort != newPort) { 1262 throw new HttpException("Redirect from port " + oldPort 1263 + " to " + newPort + " is not supported"); 1264 } 1265 } 1266 1267 /*** 1268 * Returns the default port for the given protocol. 1269 * 1270 * @param protocol the given protocol. 1271 * @return the default port of the given protocol or -1 if the 1272 * protocol is not recognized. 1273 * 1274 * @since 2.0 1275 * 1276 */ 1277 private static int getDefaultPort(String protocol) { 1278 String proto = protocol.toLowerCase().trim(); 1279 if (proto.equals("http")) { 1280 return 80; 1281 } else if (proto.equals("https")) { 1282 return 443; 1283 } 1284 return -1; 1285 } 1286 1287 /*** 1288 * Returns <tt>true</tt> if the HTTP method has been already {@link #execute executed}, 1289 * but not {@link #recycle recycled}. 1290 * 1291 * @return <tt>true</tt> if the method has been executed, <tt>false</tt> otherwise 1292 */ 1293 public boolean hasBeenUsed() { 1294 return used; 1295 } 1296 1297 /*** 1298 * Recycles the HTTP method so that it can be used again. 1299 * Note that all of the instance variables will be reset 1300 * once this method has been called. This method will also 1301 * release the connection being used by this HTTP method. 1302 * 1303 * @see #releaseConnection() 1304 */ 1305 public void recycle() { 1306 LOG.trace("enter HttpMethodBase.recycle()"); 1307 1308 releaseConnection(); 1309 1310 path = null; 1311 followRedirects = false; 1312 doAuthentication = true; 1313 authScheme = null; 1314 realm = null; 1315 proxyAuthScheme = null; 1316 proxyRealm = null; 1317 queryString = null; 1318 getRequestHeaderGroup().clear(); 1319 getResponseHeaderGroup().clear(); 1320 getResponseTrailerHeaderGroup().clear(); 1321 statusLine = null; 1322 used = false; 1323 http11 = true; 1324 responseBody = null; 1325 recoverableExceptionCount = 0; 1326 inExecute = false; 1327 doneWithConnection = false; 1328 connectionCloseForced = false; 1329 } 1330 1331 /*** 1332 * Releases the connection being used by this HTTP method. In particular the 1333 * connection is used to read the response(if there is one) and will be held 1334 * until the response has been read. If the connection can be reused by other 1335 * HTTP methods it is NOT closed at this point. 1336 * 1337 * @since 2.0 1338 */ 1339 public void releaseConnection() { 1340 1341 if (responseStream != null) { 1342 try { 1343 // FYI - this may indirectly invoke responseBodyConsumed. 1344 responseStream.close(); 1345 } catch (IOException e) { 1346 // the connection may not have been released, let's make sure 1347 ensureConnectionRelease(); 1348 } 1349 } else { 1350 // Make sure the connection has been released. If the response 1351 // stream has not been set, this is the only way to release the 1352 // connection. 1353 ensureConnectionRelease(); 1354 } 1355 } 1356 1357 /*** 1358 * Remove the request header associated with the given name. Note that 1359 * header-name matching is case insensitive. 1360 * 1361 * @param headerName the header name 1362 */ 1363 public void removeRequestHeader(String headerName) { 1364 1365 Header[] headers = getRequestHeaderGroup().getHeaders(headerName); 1366 for (int i = 0; i < headers.length; i++) { 1367 getRequestHeaderGroup().removeHeader(headers[i]); 1368 } 1369 1370 } 1371 1372 // ---------------------------------------------------------------- Queries 1373 1374 /*** 1375 * Returns <tt>true</tt> the method is ready to execute, <tt>false</tt> otherwise. 1376 * 1377 * @return This implementation always returns <tt>true</tt>. 1378 */ 1379 public boolean validate() { 1380 return true; 1381 } 1382 1383 /*** 1384 * Return the length (in bytes) of my request body, suitable for use in a 1385 * <tt>Content-Length</tt> header. 1386 * 1387 * <p> 1388 * Return <tt>-1</tt> when the content-length is unknown. 1389 * </p> 1390 * 1391 * <p> 1392 * This implementation returns <tt>0</tt>, indicating that the request has 1393 * no body. 1394 * </p> 1395 * 1396 * @return <tt>0</tt>, indicating that the request has no body. 1397 */ 1398 protected int getRequestContentLength() { 1399 return 0; 1400 } 1401 1402 /*** 1403 * Generates <tt>Authorization</tt> request header if needed, as long as no 1404 * <tt>Authorization</tt> request header already exists. 1405 * 1406 * @param state the {@link HttpState state} information associated with this method 1407 * @param conn the {@link HttpConnection connection} used to execute 1408 * this HTTP method 1409 * 1410 * @throws IOException if an I/O (transport) error occurs 1411 * @throws HttpException if a protocol exception occurs. 1412 * @throws HttpRecoverableException if a recoverable transport error occurs. 1413 * Usually this kind of exceptions can be recovered from by 1414 * retrying the HTTP method 1415 */ 1416 protected void addAuthorizationRequestHeader(HttpState state, 1417 HttpConnection conn) 1418 throws IOException, HttpException { 1419 LOG.trace("enter HttpMethodBase.addAuthorizationRequestHeader(" 1420 + "HttpState, HttpConnection)"); 1421 1422 // add authorization header, if needed 1423 if (getRequestHeader(HttpAuthenticator.WWW_AUTH_RESP) == null) { 1424 Header[] challenges = getResponseHeaderGroup().getHeaders( 1425 HttpAuthenticator.WWW_AUTH); 1426 if (challenges.length > 0) { 1427 try { 1428 this.authScheme = HttpAuthenticator.selectAuthScheme(challenges); 1429 HttpAuthenticator.authenticate(this.authScheme, this, conn, state); 1430 } catch (HttpException e) { 1431 // log and move on 1432 if (LOG.isErrorEnabled()) { 1433 LOG.error(e.getMessage(), e); 1434 } 1435 } 1436 } 1437 } 1438 } 1439 1440 /*** 1441 * Generates <tt>Content-Length</tt> or <tt>Transfer-Encoding: Chunked</tt> 1442 * request header, as long as no <tt>Content-Length</tt> request header 1443 * already exists. 1444 * 1445 * @param state the {@link HttpState state} information associated with this method 1446 * @param conn the {@link HttpConnection connection} used to execute 1447 * this HTTP method 1448 * 1449 * @throws IOException if an I/O (transport) error occurs 1450 * @throws HttpException if a protocol exception occurs. 1451 * @throws HttpRecoverableException if a recoverable transport error occurs. 1452 * Usually this kind of exceptions can be recovered from by 1453 * retrying the HTTP method 1454 */ 1455 protected void addContentLengthRequestHeader(HttpState state, 1456 HttpConnection conn) 1457 throws IOException, HttpException { 1458 LOG.trace("enter HttpMethodBase.addContentLengthRequestHeader(" 1459 + "HttpState, HttpConnection)"); 1460 1461 // add content length or chunking 1462 int len = getRequestContentLength(); 1463 if (getRequestHeader("content-length") == null) { 1464 if (0 < len) { 1465 setRequestHeader("Content-Length", String.valueOf(len)); 1466 } else if (http11 && (len < 0)) { 1467 setRequestHeader("Transfer-Encoding", "chunked"); 1468 } 1469 } 1470 } 1471 1472 /*** 1473 * Generates <tt>Cookie</tt> request headers for those {@link Cookie cookie}s 1474 * that match the given host, port and path. 1475 * 1476 * @param state the {@link HttpState state} information associated with this method 1477 * @param conn the {@link HttpConnection connection} used to execute 1478 * this HTTP method 1479 * 1480 * @throws IOException if an I/O (transport) error occurs 1481 * @throws HttpException if a protocol exception occurs. 1482 * @throws HttpRecoverableException if a recoverable transport error occurs. 1483 * Usually this kind of exceptions can be recovered from by 1484 * retrying the HTTP method 1485 */ 1486 protected void addCookieRequestHeader(HttpState state, HttpConnection conn) 1487 throws IOException, HttpException { 1488 1489 LOG.trace("enter HttpMethodBase.addCookieRequestHeader(HttpState, " 1490 + "HttpConnection)"); 1491 1492 // Clean up the cookie headers 1493 removeRequestHeader("cookie"); 1494 1495 CookieSpec matcher = CookiePolicy.getSpecByPolicy(state.getCookiePolicy()); 1496 Cookie[] cookies = matcher.match(conn.getHost(), conn.getPort(), 1497 getPath(), conn.isSecure(), state.getCookies()); 1498 if ((cookies != null) && (cookies.length > 0)) { 1499 if (this.isStrictMode()) { 1500 // In strict mode put all cookies on the same header 1501 getRequestHeaderGroup().addHeader( 1502 matcher.formatCookieHeader(cookies)); 1503 } else { 1504 // In non-strict mode put each cookie on a separate header 1505 for (int i = 0; i < cookies.length; i++) { 1506 getRequestHeaderGroup().addHeader( 1507 matcher.formatCookieHeader(cookies[i])); 1508 } 1509 } 1510 } 1511 } 1512 1513 /*** 1514 * Generates <tt>Host</tt> request header, as long as no <tt>Host</tt> request 1515 * header already exists. 1516 * 1517 * @param state the {@link HttpState state} information associated with this method 1518 * @param conn the {@link HttpConnection connection} used to execute 1519 * this HTTP method 1520 * 1521 * @throws IOException if an I/O (transport) error occurs 1522 * @throws HttpException if a protocol exception occurs. 1523 * @throws HttpRecoverableException if a recoverable transport error occurs. 1524 * Usually this kind of exceptions can be recovered from by 1525 * retrying the HTTP method 1526 */ 1527 protected void addHostRequestHeader(HttpState state, HttpConnection conn) 1528 throws IOException, HttpException { 1529 LOG.trace("enter HttpMethodBase.addHostRequestHeader(HttpState, " 1530 + "HttpConnection)"); 1531 1532 // Per 19.6.1.1 of RFC 2616, it is legal for HTTP/1.0 based 1533 // applications to send the Host request-header. 1534 // TODO: Add the ability to disable the sending of this header for 1535 // HTTP/1.0 requests. 1536 String host = conn.getVirtualHost(); 1537 if (host != null) { 1538 LOG.debug("Using virtual host name: " + host); 1539 } else { 1540 host = conn.getHost(); 1541 } 1542 int port = conn.getPort(); 1543 1544 if (getRequestHeader("host") != null) { 1545 LOG.debug( 1546 "Request to add Host header ignored: header already added"); 1547 return; 1548 } 1549 1550 // Note: RFC 2616 uses the term "internet host name" for what goes on the 1551 // host line. It would seem to imply that host should be blank if the 1552 // host is a number instead of an name. Based on the behavior of web 1553 // browsers, and the fact that RFC 2616 never defines the phrase "internet 1554 // host name", and the bad behavior of HttpClient that follows if we 1555 // send blank, I interpret this as a small misstatement in the RFC, where 1556 // they meant to say "internet host". So IP numbers get sent as host 1557 // entries too. -- Eric Johnson 12/13/2002 1558 if (LOG.isDebugEnabled()) { 1559 LOG.debug("Adding Host request header"); 1560 } 1561 1562 //appends the port only if not using the default port for the protocol 1563 if (conn.getProtocol().getDefaultPort() != port) { 1564 host += (":" + port); 1565 } 1566 1567 setRequestHeader("Host", host); 1568 } 1569 1570 /*** 1571 * Generates <tt>Proxy-Authorization</tt> request header if needed, as long as no 1572 * <tt>Proxy-Authorization</tt> request header already exists. 1573 * 1574 * @param state the {@link HttpState state} information associated with this method 1575 * @param conn the {@link HttpConnection connection} used to execute 1576 * this HTTP method 1577 * 1578 * @throws IOException if an I/O (transport) error occurs 1579 * @throws HttpException if a protocol exception occurs. 1580 * @throws HttpRecoverableException if a recoverable transport error occurs. 1581 * Usually this kind of exceptions can be recovered from by 1582 * retrying the HTTP method 1583 */ 1584 protected void addProxyAuthorizationRequestHeader(HttpState state, 1585 HttpConnection conn) 1586 throws IOException, HttpException { 1587 LOG.trace("enter HttpMethodBase.addProxyAuthorizationRequestHeader(" 1588 + "HttpState, HttpConnection)"); 1589 1590 // add proxy authorization header, if needed 1591 if (getRequestHeader(HttpAuthenticator.PROXY_AUTH_RESP) == null) { 1592 Header[] challenges = getResponseHeaderGroup().getHeaders( 1593 HttpAuthenticator.PROXY_AUTH); 1594 if (challenges.length > 0) { 1595 try { 1596 this.proxyAuthScheme = HttpAuthenticator.selectAuthScheme(challenges); 1597 HttpAuthenticator.authenticateProxy(this.proxyAuthScheme, this, conn, state); 1598 } catch (HttpException e) { 1599 // log and move on 1600 if (LOG.isErrorEnabled()) { 1601 LOG.error(e.getMessage(), e); 1602 } 1603 } 1604 } 1605 } 1606 } 1607 1608 /*** 1609 * Generates <tt>Proxy-Connection: Keep-Alive</tt> request header when 1610 * communicating via a proxy server. 1611 * 1612 * @param state the {@link HttpState state} information associated with this method 1613 * @param conn the {@link HttpConnection connection} used to execute 1614 * this HTTP method 1615 * 1616 * @throws IOException if an I/O (transport) error occurs 1617 * @throws HttpException if a protocol exception occurs. 1618 * @throws HttpRecoverableException if a recoverable transport error occurs. 1619 * Usually this kind of exceptions can be recovered from by 1620 * retrying the HTTP method 1621 */ 1622 protected void addProxyConnectionHeader(HttpState state, 1623 HttpConnection conn) 1624 throws IOException, HttpException { 1625 LOG.trace("enter HttpMethodBase.addProxyConnectionHeader(" 1626 + "HttpState, HttpConnection)"); 1627 if (!conn.isTransparent()) { 1628 setRequestHeader("Proxy-Connection", "Keep-Alive"); 1629 } 1630 } 1631 1632 /*** 1633 * Generates all the required request {@link Header header}s 1634 * to be submitted via the given {@link HttpConnection connection}. 1635 * 1636 * <p> 1637 * This implementation adds <tt>User-Agent</tt>, <tt>Host</tt>, 1638 * <tt>Cookie</tt>, <tt>Content-Length</tt>, <tt>Transfer-Encoding</tt>, 1639 * and <tt>Authorization</tt> headers, when appropriate. 1640 * </p> 1641 * 1642 * <p> 1643 * Subclasses may want to override this method to to add additional 1644 * headers, and may choose to invoke this implementation (via 1645 * <tt>super</tt>) to add the "standard" headers. 1646 * </p> 1647 * 1648 * @param state the {@link HttpState state} information associated with this method 1649 * @param conn the {@link HttpConnection connection} used to execute 1650 * this HTTP method 1651 * 1652 * @throws IOException if an I/O (transport) error occurs 1653 * @throws HttpException if a protocol exception occurs. 1654 * @throws HttpRecoverableException if a recoverable transport error occurs. 1655 * Usually this kind of exceptions can be recovered from by 1656 * retrying the HTTP method 1657 * 1658 * @see #writeRequestHeaders 1659 */ 1660 protected void addRequestHeaders(HttpState state, HttpConnection conn) 1661 throws IOException, HttpException { 1662 LOG.trace("enter HttpMethodBase.addRequestHeaders(HttpState, " 1663 + "HttpConnection)"); 1664 1665 addUserAgentRequestHeader(state, conn); 1666 addHostRequestHeader(state, conn); 1667 addCookieRequestHeader(state, conn); 1668 addAuthorizationRequestHeader(state, conn); 1669 addProxyAuthorizationRequestHeader(state, conn); 1670 addProxyConnectionHeader(state, conn); 1671 addContentLengthRequestHeader(state, conn); 1672 } 1673 1674 /*** 1675 * Generates default <tt>User-Agent</tt> request header, as long as no 1676 * <tt>User-Agent</tt> request header already exists. 1677 * 1678 * @param state the {@link HttpState state} information associated with this method 1679 * @param conn the {@link HttpConnection connection} used to execute 1680 * this HTTP method 1681 * 1682 * @throws IOException if an I/O (transport) error occurs 1683 * @throws HttpException if a protocol exception occurs. 1684 * @throws HttpRecoverableException if a recoverable transport error occurs. 1685 * Usually this kind of exceptions can be recovered from by 1686 * retrying the HTTP method 1687 */ 1688 protected void addUserAgentRequestHeader(HttpState state, 1689 HttpConnection conn) 1690 throws IOException, HttpException { 1691 LOG.trace("enter HttpMethodBase.addUserAgentRequestHeaders(HttpState, " 1692 + "HttpConnection)"); 1693 1694 if (getRequestHeader("user-agent") == null) { 1695 setRequestHeader(HttpMethodBase.USER_AGENT); 1696 } 1697 } 1698 1699 /*** 1700 * Throws an {@link IllegalStateException} if the HTTP method has been already 1701 * {@link #execute executed}, but not {@link #recycle recycled}. 1702 * 1703 * @throws IllegalStateException if the method has been used and not 1704 * recycled 1705 */ 1706 protected void checkNotUsed() throws IllegalStateException { 1707 if (used) { 1708 throw new IllegalStateException("Already used."); 1709 } 1710 } 1711 1712 /*** 1713 * Throws an {@link IllegalStateException} if the HTTP method has not been 1714 * {@link #execute executed} since last {@link #recycle recycle}. 1715 * 1716 * 1717 * @throws IllegalStateException if not used 1718 */ 1719 protected void checkUsed() throws IllegalStateException { 1720 if (!used) { 1721 throw new IllegalStateException("Not Used."); 1722 } 1723 } 1724 1725 // ------------------------------------------------- Static Utility Methods 1726 1727 /*** 1728 * Generates HTTP request line according to the specified attributes. 1729 * 1730 * @param connection the {@link HttpConnection connection} used to execute 1731 * this HTTP method 1732 * @param name the method name generate a request for 1733 * @param requestPath the path string for the request 1734 * @param query the query string for the request 1735 * @param version the protocol version to use (e.g. HTTP/1.0) 1736 * 1737 * @return HTTP request line 1738 */ 1739 protected static String generateRequestLine(HttpConnection connection, 1740 String name, String requestPath, String query, String version) { 1741 LOG.trace("enter HttpMethodBase.generateRequestLine(HttpConnection, " 1742 + "String, String, String, String)"); 1743 1744 StringBuffer buf = new StringBuffer(); 1745 // Append method name 1746 buf.append(name); 1747 buf.append(" "); 1748 // Absolute or relative URL? 1749 if (!connection.isTransparent()) { 1750 Protocol protocol = connection.getProtocol(); 1751 buf.append(protocol.getScheme().toLowerCase()); 1752 buf.append("://"); 1753 buf.append(connection.getHost()); 1754 if ((connection.getPort() != -1) 1755 && (connection.getPort() != protocol.getDefaultPort()) 1756 ) { 1757 buf.append(":"); 1758 buf.append(connection.getPort()); 1759 } 1760 } 1761 // Append path, if any 1762 if (requestPath == null) { 1763 buf.append("/"); 1764 } else { 1765 if (!connection.isTransparent() && !requestPath.startsWith("/")) { 1766 buf.append("/"); 1767 } 1768 buf.append(requestPath); 1769 } 1770 // Append query, if any 1771 if (query != null) { 1772 if (query.indexOf("?") != 0) { 1773 buf.append("?"); 1774 } 1775 buf.append(query); 1776 } 1777 // Append protocol 1778 buf.append(" "); 1779 buf.append(version); 1780 buf.append("\r\n"); 1781 1782 return buf.toString(); 1783 } 1784 1785 /*** 1786 * This method is invoked immediately after 1787 * {@link #readResponseBody(HttpState,HttpConnection)} and can be overridden by 1788 * sub-classes in order to provide custom body processing. 1789 * 1790 * <p> 1791 * This implementation does nothing. 1792 * </p> 1793 * 1794 * @param state the {@link HttpState state} information associated with this method 1795 * @param conn the {@link HttpConnection connection} used to execute 1796 * this HTTP method 1797 * 1798 * @see #readResponse 1799 * @see #readResponseBody 1800 */ 1801 protected void processResponseBody(HttpState state, HttpConnection conn) { 1802 } 1803 1804 /*** 1805 * This method is invoked immediately after 1806 * {@link #readResponseHeaders(HttpState,HttpConnection)} and can be overridden by 1807 * sub-classes in order to provide custom response headers processing. 1808 1809 * <p> 1810 * This implementation will handle the <tt>Set-Cookie</tt> and 1811 * <tt>Set-Cookie2</tt> headers, if any, adding the relevant cookies to 1812 * the given {@link HttpState}. 1813 * </p> 1814 * 1815 * @param state the {@link HttpState state} information associated with this method 1816 * @param conn the {@link HttpConnection connection} used to execute 1817 * this HTTP method 1818 * 1819 * @see #readResponse 1820 * @see #readResponseHeaders 1821 */ 1822 protected void processResponseHeaders(HttpState state, 1823 HttpConnection conn) { 1824 LOG.trace("enter HttpMethodBase.processResponseHeaders(HttpState, " 1825 + "HttpConnection)"); 1826 1827 Header[] headers = getResponseHeaderGroup().getHeaders("set-cookie2"); 1828 //Only process old style set-cookie headers if new style headres 1829 //are not present 1830 if (headers.length == 0) { 1831 headers = getResponseHeaderGroup().getHeaders("set-cookie"); 1832 } 1833 1834 CookieSpec parser = CookiePolicy.getSpecByPolicy(state.getCookiePolicy()); 1835 for (int i = 0; i < headers.length; i++) { 1836 Header header = headers[i]; 1837 Cookie[] cookies = null; 1838 try { 1839 cookies = parser.parse( 1840 conn.getHost(), 1841 conn.getPort(), 1842 getPath(), 1843 conn.isSecure(), 1844 header); 1845 } catch (MalformedCookieException e) { 1846 if (LOG.isWarnEnabled()) { 1847 LOG.warn("Invalid cookie header: \"" 1848 + header.getValue() 1849 + "\". " + e.getMessage()); 1850 } 1851 } 1852 if (cookies != null) { 1853 for (int j = 0; j < cookies.length; j++) { 1854 Cookie cookie = cookies[j]; 1855 try { 1856 parser.validate( 1857 conn.getHost(), 1858 conn.getPort(), 1859 getPath(), 1860 conn.isSecure(), 1861 cookie); 1862 state.addCookie(cookie); 1863 if (LOG.isDebugEnabled()) { 1864 LOG.debug("Cookie accepted: \"" 1865 + parser.formatCookie(cookie) + "\""); 1866 } 1867 } catch (MalformedCookieException e) { 1868 if (LOG.isWarnEnabled()) { 1869 LOG.warn("Cookie rejected: \"" + parser.formatCookie(cookie) 1870 + "\". " + e.getMessage()); 1871 } 1872 } 1873 } 1874 } 1875 } 1876 } 1877 1878 /*** 1879 * This method is invoked immediately after 1880 * {@link #readStatusLine(HttpState,HttpConnection)} and can be overridden by 1881 * sub-classes in order to provide custom response status line processing. 1882 * 1883 * @param state the {@link HttpState state} information associated with this method 1884 * @param conn the {@link HttpConnection connection} used to execute 1885 * this HTTP method 1886 * 1887 * @see #readResponse 1888 * @see #readStatusLine 1889 */ 1890 protected void processStatusLine(HttpState state, HttpConnection conn) { 1891 } 1892 1893 /*** 1894 * Reads the response from the given {@link HttpConnection connection}. 1895 * 1896 * <p> 1897 * The response is processed as the following sequence of actions: 1898 * 1899 * <ol> 1900 * <li> 1901 * {@link #readStatusLine(HttpState,HttpConnection)} is 1902 * invoked to read the request line. 1903 * </li> 1904 * <li> 1905 * {@link #processStatusLine(HttpState,HttpConnection)} 1906 * is invoked, allowing the method to process the status line if 1907 * desired. 1908 * </li> 1909 * <li> 1910 * {@link #readResponseHeaders(HttpState,HttpConnection)} is invoked to read 1911 * the associated headers. 1912 * </li> 1913 * <li> 1914 * {@link #processResponseHeaders(HttpState,HttpConnection)} is invoked, allowing 1915 * the method to process the headers if desired. 1916 * </li> 1917 * <li> 1918 * {@link #readResponseBody(HttpState,HttpConnection)} is 1919 * invoked to read the associated body (if any). 1920 * </li> 1921 * <li> 1922 * {@link #processResponseBody(HttpState,HttpConnection)} is invoked, allowing the 1923 * method to process the response body if desired. 1924 * </li> 1925 * </ol> 1926 * 1927 * Subclasses may want to override one or more of the above methods to to 1928 * customize the processing. (Or they may choose to override this method 1929 * if dramatically different processing is required.) 1930 * </p> 1931 * 1932 * @param state the {@link HttpState state} information associated with this method 1933 * @param conn the {@link HttpConnection connection} used to execute 1934 * this HTTP method 1935 * 1936 * @throws HttpException if a protocol exception occurs. 1937 * @throws HttpRecoverableException if a recoverable transport error occurs. 1938 * Usually this kind of exceptions can be recovered from by 1939 * retrying the HTTP method 1940 */ 1941 protected void readResponse(HttpState state, HttpConnection conn) 1942 throws HttpException { 1943 LOG.trace( 1944 "enter HttpMethodBase.readResponse(HttpState, HttpConnection)"); 1945 try { 1946 // Status line & line may have already been received 1947 // if 'expect - continue' handshake has been used 1948 while (this.statusLine == null) { 1949 readStatusLine(state, conn); 1950 processStatusLine(state, conn); 1951 readResponseHeaders(state, conn); 1952 processResponseHeaders(state, conn); 1953 1954 int status = this.statusLine.getStatusCode(); 1955 if ((status >= 100) && (status < 200)) { 1956 if (LOG.isInfoEnabled()) { 1957 LOG.info("Discarding unexpected response: " + this.statusLine.toString()); 1958 } 1959 this.statusLine = null; 1960 } 1961 } 1962 readResponseBody(state, conn); 1963 processResponseBody(state, conn); 1964 } catch (IOException e) { 1965 throw new HttpRecoverableException(e.toString()); 1966 } 1967 } 1968 1969 /*** 1970 * Read the response body from the given {@link HttpConnection}. 1971 * 1972 * <p> 1973 * The current implementation wraps the socket level stream with 1974 * an appropriate stream for the type of response (chunked, content-length, 1975 * or auto-close). If there is no response body, the connection associated 1976 * with the request will be returned to the connection manager. 1977 * </p> 1978 * 1979 * <p> 1980 * Subclasses may want to override this method to to customize the 1981 * processing. 1982 * </p> 1983 * 1984 * @param state the {@link HttpState state} information associated with this method 1985 * @param conn the {@link HttpConnection connection} used to execute 1986 * this HTTP method 1987 * 1988 * @throws IOException if an I/O (transport) error occurs 1989 * @throws HttpException if a protocol exception occurs. 1990 * @throws HttpRecoverableException if a recoverable transport error occurs. 1991 * Usually this kind of exceptions can be recovered from by 1992 * retrying the HTTP method 1993 * 1994 * @see #readResponse 1995 * @see #processResponseBody 1996 */ 1997 protected void readResponseBody(HttpState state, HttpConnection conn) 1998 throws IOException, HttpException { 1999 LOG.trace( 2000 "enter HttpMethodBase.readResponseBody(HttpState, HttpConnection)"); 2001 2002 // assume we are not done with the connection if we get a stream 2003 doneWithConnection = false; 2004 InputStream stream = readResponseBody(conn); 2005 if (stream == null) { 2006 // done using the connection! 2007 responseBodyConsumed(); 2008 } else { 2009 conn.setLastResponseInputStream(stream); 2010 setResponseStream(stream); 2011 } 2012 } 2013 2014 /*** 2015 * Returns the response body as an {@link InputStream input stream} 2016 * corresponding to the values of the <tt>Content-Length</tt> and 2017 * <tt>Transfer-Encoding</tt> headers. If no response body is available 2018 * returns <tt>null</tt>. 2019 * <p> 2020 * 2021 * @see #readResponse 2022 * @see #processResponseBody 2023 * 2024 * @param conn the {@link HttpConnection connection} used to execute 2025 * this HTTP method 2026 * 2027 * @throws IOException if an I/O (transport) error occurs 2028 * @throws HttpException if a protocol exception occurs. 2029 * @throws HttpRecoverableException if a recoverable transport error occurs. 2030 * Usually this kind of exceptions can be recovered from by 2031 * retrying the HTTP method 2032 */ 2033 private InputStream readResponseBody(HttpConnection conn) 2034 throws IOException { 2035 2036 LOG.trace("enter HttpMethodBase.readResponseBody(HttpState, HttpConnection)"); 2037 2038 responseBody = null; // is this desired? 2039 InputStream is = conn.getResponseInputStream(); 2040 if (Wire.enabled()) { 2041 is = new WireLogInputStream(is); 2042 } 2043 InputStream result = null; 2044 Header transferEncodingHeader = responseHeaders.getFirstHeader("Transfer-Encoding"); 2045 // We use Transfer-Encoding if present and ignore Content-Length. 2046 // RFC2616, 4.4 item number 3 2047 if (transferEncodingHeader != null) { 2048 2049 String transferEncoding = transferEncodingHeader.getValue(); 2050 if (!"chunked".equalsIgnoreCase(transferEncoding) 2051 && !"identity".equalsIgnoreCase(transferEncoding)) { 2052 if (LOG.isWarnEnabled()) { 2053 LOG.warn("Unsupported transfer encoding: " + transferEncoding); 2054 } 2055 } 2056 HeaderElement[] encodings = transferEncodingHeader.getValues(); 2057 // The chunck encoding must be the last one applied 2058 // RFC2616, 14.41 2059 int len = encodings.length; 2060 if ((len > 0) && ("chunked".equalsIgnoreCase(encodings[len - 1].getName()))) { 2061 // if response body is empty 2062 if (conn.isResponseAvailable(conn.getSoTimeout())) { 2063 result = new ChunkedInputStream(is, this); 2064 } else { 2065 if (isStrictMode()) { 2066 throw new HttpException("Chunk-encoded body declared but not sent"); 2067 } else { 2068 LOG.warn("Chunk-encoded body missing"); 2069 } 2070 } 2071 } else { 2072 if (LOG.isWarnEnabled()) { 2073 LOG.warn("Transfer-Encoding is set but does not contain \"chunked\": " 2074 + transferEncoding); 2075 } 2076 // The connection must be terminated by closing 2077 // the socket as per RFC 2616, 3.6 2078 setConnectionCloseForced(true); 2079 result = is; 2080 } 2081 } else { 2082 int expectedLength = getResponseContentLength(); 2083 if (expectedLength == -1) { 2084 if (canResponseHaveBody(statusLine.getStatusCode())) { 2085 Header connectionHeader = responseHeaders.getFirstHeader("Connection"); 2086 String connectionDirective = null; 2087 if (connectionHeader != null) { 2088 connectionDirective = connectionHeader.getValue(); 2089 } 2090 if (!"close".equalsIgnoreCase(connectionDirective)) { 2091 LOG.warn("Response content length is not known"); 2092 setConnectionCloseForced(true); 2093 } 2094 result = is; 2095 } 2096 } else { 2097 result = new ContentLengthInputStream(is, expectedLength); 2098 } 2099 } 2100 // if there is a result - ALWAYS wrap it in an observer which will 2101 // close the underlying stream as soon as it is consumed, and notify 2102 // the watcher that the stream has been consumed. 2103 if (result != null) { 2104 2105 result = new AutoCloseInputStream( 2106 result, 2107 new ResponseConsumedWatcher() { 2108 public void responseConsumed() { 2109 responseBodyConsumed(); 2110 } 2111 } 2112 ); 2113 } 2114 2115 return result; 2116 } 2117 2118 /*** 2119 * Reads the response headers from the given {@link HttpConnection connection}. 2120 * 2121 * <p> 2122 * Subclasses may want to override this method to to customize the 2123 * processing. 2124 * </p> 2125 * 2126 * <p> 2127 * "It must be possible to combine the multiple header fields into one 2128 * "field-name: field-value" pair, without changing the semantics of the 2129 * message, by appending each subsequent field-value to the first, each 2130 * separated by a comma." - HTTP/1.0 (4.3) 2131 * </p> 2132 * 2133 * @param state the {@link HttpState state} information associated with this method 2134 * @param conn the {@link HttpConnection connection} used to execute 2135 * this HTTP method 2136 * 2137 * @throws IOException if an I/O (transport) error occurs 2138 * @throws HttpException if a protocol exception occurs. 2139 * @throws HttpRecoverableException if a recoverable transport error occurs. 2140 * Usually this kind of exceptions can be recovered from by 2141 * retrying the HTTP method 2142 * 2143 * @see #readResponse 2144 * @see #processResponseHeaders 2145 */ 2146 protected void readResponseHeaders(HttpState state, HttpConnection conn) 2147 throws IOException, HttpException { 2148 LOG.trace("enter HttpMethodBase.readResponseHeaders(HttpState," 2149 + "HttpConnection)"); 2150 2151 getResponseHeaderGroup().clear(); 2152 Header[] headers = HttpParser.parseHeaders(conn.getResponseInputStream()); 2153 if (Wire.enabled()) { 2154 for (int i = 0; i < headers.length; i++) { 2155 Wire.input(headers[i].toExternalForm()); 2156 } 2157 } 2158 getResponseHeaderGroup().setHeaders(headers); 2159 } 2160 2161 /*** 2162 * Read the status line from the given {@link HttpConnection}, setting my 2163 * {@link #getStatusCode status code} and {@link #getStatusText status 2164 * text}. 2165 * 2166 * <p> 2167 * Subclasses may want to override this method to to customize the 2168 * processing. 2169 * </p> 2170 * 2171 * @param state the {@link HttpState state} information associated with this method 2172 * @param conn the {@link HttpConnection connection} used to execute 2173 * this HTTP method 2174 * 2175 * @throws IOException if an I/O (transport) error occurs 2176 * @throws HttpException if a protocol exception occurs. 2177 * @throws HttpRecoverableException if a recoverable transport error occurs. 2178 * Usually this kind of exceptions can be recovered from by 2179 * retrying the HTTP method 2180 * 2181 * @see StatusLine 2182 */ 2183 protected void readStatusLine(HttpState state, HttpConnection conn) 2184 throws IOException, HttpRecoverableException, HttpException { 2185 LOG.trace("enter HttpMethodBase.readStatusLine(HttpState, HttpConnection)"); 2186 2187 //read out the HTTP status string 2188 String s = conn.readLine(); 2189 while ((s != null) && !StatusLine.startsWithHTTP(s)) { 2190 if (Wire.enabled()) { 2191 Wire.input(s + "\r\n"); 2192 } 2193 s = conn.readLine(); 2194 } 2195 if (s == null) { 2196 // A null statusString means the connection was lost before we got a 2197 // response. Try again. 2198 throw new HttpRecoverableException("Error in parsing the status " 2199 + " line from the response: unable to find line starting with" 2200 + " \"HTTP\""); 2201 } 2202 if (Wire.enabled()) { 2203 Wire.input(s + "\r\n"); 2204 } 2205 //create the status line from the status string 2206 statusLine = new StatusLine(s); 2207 2208 //check for a valid HTTP-Version 2209 String httpVersion = statusLine.getHttpVersion(); 2210 if (httpVersion.equals("HTTP/1.0")) { 2211 http11 = false; 2212 } else if (httpVersion.equals("HTTP/1.1")) { 2213 http11 = true; 2214 } else if (httpVersion.equals("HTTP")) { 2215 // some servers do not specify the version correctly, we will just assume 1.0 2216 http11 = false; 2217 } else { 2218 throw new HttpException("Unrecognized server protocol: '" 2219 + httpVersion + "'"); 2220 } 2221 2222 } 2223 2224 // ------------------------------------------------------ Protected Methods 2225 2226 /*** 2227 * <p> 2228 * Sends the request via the given {@link HttpConnection connection}. 2229 * </p> 2230 * 2231 * <p> 2232 * The request is written as the following sequence of actions: 2233 * </p> 2234 * 2235 * <ol> 2236 * <li> 2237 * {@link #writeRequestLine(HttpState, HttpConnection)} is invoked to 2238 * write the request line. 2239 * </li> 2240 * <li> 2241 * {@link #writeRequestHeaders(HttpState, HttpConnection)} is invoked 2242 * to write the associated headers. 2243 * </li> 2244 * <li> 2245 * <tt>\r\n</tt> is sent to close the head part of the request. 2246 * </li> 2247 * <li> 2248 * {@link #writeRequestBody(HttpState, HttpConnection)} is invoked to 2249 * write the body part of the request. 2250 * </li> 2251 * </ol> 2252 * 2253 * <p> 2254 * Subclasses may want to override one or more of the above methods to to 2255 * customize the processing. (Or they may choose to override this method 2256 * if dramatically different processing is required.) 2257 * </p> 2258 * 2259 * @param state the {@link HttpState state} information associated with this method 2260 * @param conn the {@link HttpConnection connection} used to execute 2261 * this HTTP method 2262 * 2263 * @throws IOException if an I/O (transport) error occurs 2264 * @throws HttpException if a protocol exception occurs. 2265 * @throws HttpRecoverableException if a recoverable transport error occurs. 2266 * Usually this kind of exceptions can be recovered from by 2267 * retrying the HTTP method 2268 */ 2269 protected void writeRequest(HttpState state, HttpConnection conn) 2270 throws IOException, HttpException { 2271 LOG.trace( 2272 "enter HttpMethodBase.writeRequest(HttpState, HttpConnection)"); 2273 writeRequestLine(state, conn); 2274 writeRequestHeaders(state, conn); 2275 conn.writeLine(); // close head 2276 // make sure the status line and headers have been sent 2277 conn.flushRequestOutputStream(); 2278 if (Wire.enabled()) { 2279 Wire.output("\r\n"); 2280 } 2281 2282 Header expectheader = getRequestHeader("Expect"); 2283 String expectvalue = null; 2284 if (expectheader != null) { 2285 expectvalue = expectheader.getValue(); 2286 } 2287 if ((expectvalue != null) 2288 && (expectvalue.compareToIgnoreCase("100-continue") == 0)) { 2289 if (this.isHttp11()) { 2290 int readTimeout = conn.getSoTimeout(); 2291 try { 2292 conn.setSoTimeout(RESPONSE_WAIT_TIME_MS); 2293 readStatusLine(state, conn); 2294 processStatusLine(state, conn); 2295 readResponseHeaders(state, conn); 2296 processResponseHeaders(state, conn); 2297 2298 if (this.statusLine.getStatusCode() == HttpStatus.SC_CONTINUE) { 2299 // Discard status line 2300 this.statusLine = null; 2301 LOG.debug("OK to continue received"); 2302 } else { 2303 return; 2304 } 2305 } catch (InterruptedIOException e) { 2306 // Most probably Expect header is not recongnized 2307 // Remove the header to signal the method 2308 // that it's okay to go ahead with sending data 2309 removeRequestHeader("Expect"); 2310 LOG.info("100 (continue) read timeout. Resume sending the request"); 2311 } finally { 2312 conn.setSoTimeout(readTimeout); 2313 } 2314 2315 } else { 2316 removeRequestHeader("Expect"); 2317 LOG.info("'Expect: 100-continue' handshake is only supported by " 2318 + "HTTP/1.1 or higher"); 2319 } 2320 } 2321 2322 writeRequestBody(state, conn); 2323 // make sure the entire request body has been sent 2324 conn.flushRequestOutputStream(); 2325 } 2326 2327 /*** 2328 * Writes the request body to the given {@link HttpConnection connection}. 2329 * 2330 * <p> 2331 * This method should return <tt>true</tt> if the request body was actually 2332 * sent (or is empty), or <tt>false</tt> if it could not be sent for some 2333 * reason. 2334 * </p> 2335 * 2336 * <p> 2337 * This implementation writes nothing and returns <tt>true</tt>. 2338 * </p> 2339 * 2340 * @param state the {@link HttpState state} information associated with this method 2341 * @param conn the {@link HttpConnection connection} used to execute 2342 * this HTTP method 2343 * 2344 * @return <tt>true</tt> 2345 * 2346 * @throws IOException if an I/O (transport) error occurs 2347 * @throws HttpException if a protocol exception occurs. 2348 * @throws HttpRecoverableException if a recoverable transport error occurs. 2349 * Usually this kind of exceptions can be recovered from by 2350 * retrying the HTTP method 2351 */ 2352 protected boolean writeRequestBody(HttpState state, HttpConnection conn) 2353 throws IOException, HttpException { 2354 return true; 2355 } 2356 2357 /*** 2358 * Writes the request headers to the given {@link HttpConnection connection}. 2359 * 2360 * <p> 2361 * This implementation invokes {@link #addRequestHeaders(HttpState,HttpConnection)}, 2362 * and then writes each header to the request stream. 2363 * </p> 2364 * 2365 * <p> 2366 * Subclasses may want to override this method to to customize the 2367 * processing. 2368 * </p> 2369 * 2370 * @param state the {@link HttpState state} information associated with this method 2371 * @param conn the {@link HttpConnection connection} used to execute 2372 * this HTTP method 2373 * 2374 * @throws IOException if an I/O (transport) error occurs 2375 * @throws HttpException if a protocol exception occurs. 2376 * @throws HttpRecoverableException if a recoverable transport error occurs. 2377 * Usually this kind of exceptions can be recovered from by 2378 * retrying the HTTP method 2379 * 2380 * @see #addRequestHeaders 2381 * @see #getRequestHeaders 2382 */ 2383 protected void writeRequestHeaders(HttpState state, HttpConnection conn) 2384 throws IOException, HttpException { 2385 LOG.trace("enter HttpMethodBase.writeRequestHeaders(HttpState," 2386 + "HttpConnection)"); 2387 addRequestHeaders(state, conn); 2388 2389 Header[] headers = getRequestHeaders(); 2390 for (int i = 0; i < headers.length; i++) { 2391 String s = headers[i].toExternalForm(); 2392 if (Wire.enabled()) { 2393 Wire.output(s); 2394 } 2395 conn.print(s); 2396 } 2397 } 2398 2399 /*** 2400 * Writes the request line to the given {@link HttpConnection connection}. 2401 * 2402 * <p> 2403 * Subclasses may want to override this method to to customize the 2404 * processing. 2405 * </p> 2406 * 2407 * @param state the {@link HttpState state} information associated with this method 2408 * @param conn the {@link HttpConnection connection} used to execute 2409 * this HTTP method 2410 * 2411 * @throws IOException if an I/O (transport) error occurs 2412 * @throws HttpException if a protocol exception occurs. 2413 * @throws HttpRecoverableException if a recoverable transport error occurs. 2414 * Usually this kind of exceptions can be recovered from by 2415 * retrying the HTTP method 2416 * 2417 * @see #generateRequestLine 2418 */ 2419 protected void writeRequestLine(HttpState state, HttpConnection conn) 2420 throws IOException, HttpException { 2421 LOG.trace( 2422 "enter HttpMethodBase.writeRequestLine(HttpState, HttpConnection)"); 2423 String requestLine = getRequestLine(conn); 2424 if (Wire.enabled()) { 2425 Wire.output(requestLine); 2426 } 2427 conn.print(requestLine); 2428 } 2429 2430 /*** 2431 * Returns the request line. 2432 * 2433 * @param conn the {@link HttpConnection connection} used to execute 2434 * this HTTP method 2435 * 2436 * @return The request line. 2437 */ 2438 private String getRequestLine(HttpConnection conn) { 2439 return HttpMethodBase.generateRequestLine(conn, getName(), 2440 getPath(), getQueryString(), getHttpVersion()); 2441 } 2442 2443 /*** 2444 * Get the HTTP version. 2445 * 2446 * @return HTTP/1.1 if version 1.1 of HTTP protocol is used, HTTP/1.0 otherwise 2447 * 2448 * @since 2.0 2449 */ 2450 private String getHttpVersion() { 2451 return (http11 ? "HTTP/1.1" : "HTTP/1.0"); 2452 } 2453 2454 /*** 2455 * Per RFC 2616 section 4.3, some response can never contain a message 2456 * body. 2457 * 2458 * @param status - the HTTP status code 2459 * 2460 * @return <tt>true</tt> if the message may contain a body, <tt>false</tt> if it can not 2461 * contain a message body 2462 */ 2463 private static boolean canResponseHaveBody(int status) { 2464 LOG.trace("enter HttpMethodBase.canResponseHaveBody(int)"); 2465 2466 boolean result = true; 2467 2468 if ((status >= 100 && status <= 199) || (status == 204) 2469 || (status == 304)) { // NOT MODIFIED 2470 result = false; 2471 } 2472 2473 return result; 2474 } 2475 2476 /*** 2477 * Processes a response that requires authentication 2478 * 2479 * @param state the {@link HttpState state} information associated with this method 2480 * @param conn the {@link HttpConnection connection} used to execute 2481 * this HTTP method 2482 * 2483 * @return true if the request has completed process, false if more 2484 * attempts are needed 2485 */ 2486 private boolean processAuthenticationResponse(HttpState state, HttpConnection conn) { 2487 LOG.trace("enter HttpMethodBase.processAuthenticationResponse(" 2488 + "HttpState, HttpConnection)"); 2489 2490 if (this.proxyAuthScheme instanceof NTLMScheme) { 2491 removeRequestHeader(HttpAuthenticator.PROXY_AUTH_RESP); 2492 } 2493 if (this.authScheme instanceof NTLMScheme) { 2494 removeRequestHeader(HttpAuthenticator.WWW_AUTH_RESP); 2495 } 2496 int statusCode = statusLine.getStatusCode(); 2497 // handle authentication required 2498 Header[] challenges = null; 2499 Set realmsUsed = null; 2500 String host = null; 2501 switch (statusCode) { 2502 case HttpStatus.SC_UNAUTHORIZED: 2503 challenges = getResponseHeaderGroup().getHeaders(HttpAuthenticator.WWW_AUTH); 2504 realmsUsed = realms; 2505 host = conn.getVirtualHost(); 2506 if (host == null) { 2507 host = conn.getHost(); 2508 } 2509 break; 2510 case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: 2511 challenges = getResponseHeaderGroup().getHeaders(HttpAuthenticator.PROXY_AUTH); 2512 realmsUsed = proxyRealms; 2513 host = conn.getProxyHost(); 2514 break; 2515 } 2516 boolean authenticated = false; 2517 // if there was a header requesting authentication 2518 if (challenges.length > 0) { 2519 AuthScheme authscheme = null; 2520 try { 2521 authscheme = HttpAuthenticator.selectAuthScheme(challenges); 2522 } catch (MalformedChallengeException e) { 2523 if (LOG.isErrorEnabled()) { 2524 LOG.error(e.getMessage(), e); 2525 } 2526 return true; 2527 } catch (UnsupportedOperationException e) { 2528 if (LOG.isErrorEnabled()) { 2529 LOG.error(e.getMessage(), e); 2530 } 2531 return true; 2532 } 2533 2534 StringBuffer buffer = new StringBuffer(); 2535 buffer.append(host); 2536 buffer.append('#'); 2537 buffer.append(authscheme.getID()); 2538 String realm = buffer.toString(); 2539 2540 if (realmsUsed.contains(realm)) { 2541 if (LOG.isInfoEnabled()) { 2542 buffer = new StringBuffer(); 2543 buffer.append("Already tried to authenticate with '"); 2544 buffer.append(authscheme.getRealm()); 2545 buffer.append("' authentication realm at "); 2546 buffer.append(host); 2547 buffer.append(", but still receiving: "); 2548 buffer.append(statusLine.toString()); 2549 LOG.info(buffer.toString()); 2550 } 2551 return true; 2552 } else { 2553 realmsUsed.add(realm); 2554 } 2555 2556 try { 2557 //remove preemptive header and reauthenticate 2558 switch (statusCode) { 2559 case HttpStatus.SC_UNAUTHORIZED: 2560 removeRequestHeader(HttpAuthenticator.WWW_AUTH_RESP); 2561 authenticated = HttpAuthenticator.authenticate( 2562 authscheme, this, conn, state); 2563 this.realm = authscheme.getRealm(); 2564 this.authScheme = authscheme; 2565 break; 2566 case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: 2567 removeRequestHeader(HttpAuthenticator.PROXY_AUTH_RESP); 2568 authenticated = HttpAuthenticator.authenticateProxy( 2569 authscheme, this, conn, state); 2570 this.proxyRealm = authscheme.getRealm(); 2571 this.proxyAuthScheme = authscheme; 2572 break; 2573 } 2574 } catch (AuthenticationException e) { 2575 LOG.warn(e.getMessage()); 2576 return true; // finished request 2577 } 2578 if (!authenticated) { 2579 // won't be able to authenticate to this challenge 2580 // without additional information 2581 LOG.debug("HttpMethodBase.execute(): Server demands " 2582 + "authentication credentials, but none are " 2583 + "available, so aborting."); 2584 } else { 2585 LOG.debug("HttpMethodBase.execute(): Server demanded " 2586 + "authentication credentials, will try again."); 2587 // let's try it again, using the credentials 2588 } 2589 } 2590 2591 return !authenticated; // finished processing if we aren't authenticated 2592 } 2593 2594 /*** 2595 * Returns proxy authentication realm, if it has been used during authentication process. 2596 * Otherwise returns <tt>null</tt>. 2597 * 2598 * @return proxy authentication realm 2599 */ 2600 public String getProxyAuthenticationRealm() { 2601 return this.proxyRealm; 2602 } 2603 2604 /*** 2605 * Returns authentication realm, if it has been used during authentication process. 2606 * Otherwise returns <tt>null</tt>. 2607 * 2608 * @return authentication realm 2609 */ 2610 public String getAuthenticationRealm() { 2611 return this.realm; 2612 } 2613 2614 /*** 2615 * Sends the request and reads the response. The request will be retried 2616 * {@link #maxRetries} times if the operation fails with a 2617 * {@link HttpRecoverableException}. 2618 * 2619 * <p> 2620 * The {@link #isUsed()} is set to true if the write succeeds. 2621 * </p> 2622 * 2623 * @param state the {@link HttpState state} information associated with this method 2624 * @param conn the {@link HttpConnection connection} used to execute 2625 * this HTTP method 2626 * 2627 * @throws IOException if an I/O (transport) error occurs 2628 * @throws HttpException if a protocol exception occurs. 2629 * @throws HttpRecoverableException if a recoverable transport error occurs. 2630 * Usually this kind of exceptions can be recovered from by 2631 * retrying the HTTP method 2632 * 2633 * @see #writeRequest(HttpState,HttpConnection) 2634 * @see #readResponse(HttpState,HttpConnection) 2635 */ 2636 private void processRequest(HttpState state, HttpConnection connection) 2637 throws HttpException, IOException { 2638 LOG.trace("enter HttpMethodBase.processRequest(HttpState, HttpConnection)"); 2639 2640 int execCount = 0; 2641 boolean requestSent = false; 2642 2643 // loop until the method is successfully processed, the retryHandler 2644 // returns false or a non-recoverable exception is thrown 2645 while (true) { 2646 execCount++; 2647 requestSent = false; 2648 2649 if (LOG.isTraceEnabled()) { 2650 LOG.trace("Attempt number " + execCount + " to process request"); 2651 } 2652 try { 2653 if (!connection.isOpen()) { 2654 LOG.debug("Opening the connection."); 2655 connection.open(); 2656 } 2657 writeRequest(state, connection); 2658 requestSent = true; 2659 readResponse(state, connection); 2660 // the method has successfully executed 2661 used = true; 2662 break; 2663 } catch (HttpRecoverableException httpre) { 2664 if (LOG.isDebugEnabled()) { 2665 LOG.debug("Closing the connection."); 2666 } 2667 connection.close(); 2668 LOG.info("Recoverable exception caught when processing request"); 2669 // update the recoverable exception count. 2670 recoverableExceptionCount++; 2671 2672 // test if this method should be retried 2673 if (!getMethodRetryHandler().retryMethod( 2674 this, 2675 connection, 2676 httpre, 2677 execCount, 2678 requestSent) 2679 ) { 2680 LOG.warn( 2681 "Recoverable exception caught but MethodRetryHandler.retryMethod() " 2682 + "returned false, rethrowing exception" 2683 ); 2684 // this connection can no longer be used, it has been closed 2685 doneWithConnection = true; 2686 throw httpre; 2687 } 2688 } catch (IOException e) { 2689 connection.close(); 2690 doneWithConnection = true; 2691 throw e; 2692 } catch (RuntimeException e) { 2693 connection.close(); 2694 doneWithConnection = true; 2695 throw e; 2696 } 2697 } 2698 } 2699 2700 /*** 2701 * Returns the character set from the <tt>Content-Type</tt> header. 2702 * @param contentheader The content header. 2703 * @return String The character set. 2704 */ 2705 protected static String getContentCharSet(Header contentheader) { 2706 LOG.trace("enter getContentCharSet( Header contentheader )"); 2707 String charset = null; 2708 if (contentheader != null) { 2709 try { 2710 HeaderElement values[] = contentheader.getValues(); 2711 // I expect only one header element to be there 2712 // No more. no less 2713 if (values.length == 1) { 2714 NameValuePair param = values[0].getParameterByName("charset"); 2715 if (param != null) { 2716 // If I get anything "funny" 2717 // UnsupportedEncondingException will result 2718 charset = param.getValue(); 2719 } 2720 } 2721 } catch (HttpException e) { 2722 LOG.error(e); 2723 } 2724 } 2725 if (charset == null) { 2726 if (LOG.isDebugEnabled()) { 2727 LOG.debug("Default charset used: " + HttpConstants.DEFAULT_CONTENT_CHARSET); 2728 } 2729 charset = HttpConstants.DEFAULT_CONTENT_CHARSET; 2730 } 2731 return charset; 2732 } 2733 2734 2735 /*** 2736 * Returns the character encoding of the request from the <tt>Content-Type</tt> header. 2737 * 2738 * @return String The character set. 2739 */ 2740 public String getRequestCharSet() { 2741 return getContentCharSet(getRequestHeader("Content-Type")); 2742 } 2743 2744 2745 /*** 2746 * Returns the character encoding of the response from the <tt>Content-Type</tt> header. 2747 * 2748 * @return String The character set. 2749 */ 2750 public String getResponseCharSet() { 2751 return getContentCharSet(getResponseHeader("Content-Type")); 2752 } 2753 2754 /*** 2755 * Returns the number of "recoverable" exceptions thrown and handled, to 2756 * allow for monitoring the quality of the connection. 2757 * 2758 * @return The number of recoverable exceptions handled by the method. 2759 */ 2760 public int getRecoverableExceptionCount() { 2761 return recoverableExceptionCount; 2762 } 2763 2764 /*** 2765 * A response has been consumed. 2766 * 2767 * <p>The default behavior for this class is to check to see if the connection 2768 * should be closed, and close if need be, and to ensure that the connection 2769 * is returned to the connection manager - if and only if we are not still 2770 * inside the execute call.</p> 2771 * 2772 */ 2773 protected void responseBodyConsumed() { 2774 2775 // make sure this is the initial invocation of the notification, 2776 // ignore subsequent ones. 2777 responseStream = null; 2778 if (responseConnection != null) { 2779 responseConnection.setLastResponseInputStream(null); 2780 2781 if (shouldCloseConnection(responseConnection)) { 2782 responseConnection.close(); 2783 } 2784 } 2785 this.connectionCloseForced = false; 2786 doneWithConnection = true; 2787 if (!inExecute) { 2788 ensureConnectionRelease(); 2789 } 2790 } 2791 2792 /*** 2793 * Insure that the connection is released back to the pool. 2794 */ 2795 private void ensureConnectionRelease() { 2796 if (responseConnection != null) { 2797 responseConnection.releaseConnection(); 2798 responseConnection = null; 2799 } 2800 } 2801 2802 /*** 2803 * Returns the {@link HostConfiguration host configuration}. 2804 * 2805 * @return the host configuration 2806 */ 2807 public HostConfiguration getHostConfiguration() { 2808 return hostConfiguration; 2809 } 2810 2811 /*** 2812 * Sets the {@link HostConfiguration host configuration}. 2813 * 2814 * @param hostConfiguration The hostConfiguration to set 2815 */ 2816 public void setHostConfiguration(HostConfiguration hostConfiguration) { 2817 this.hostConfiguration = hostConfiguration; 2818 } 2819 2820 /*** 2821 * Returns the {@link MethodRetryHandler retry handler} for this HTTP method 2822 * 2823 * @return the methodRetryHandler 2824 */ 2825 public MethodRetryHandler getMethodRetryHandler() { 2826 2827 if (methodRetryHandler == null) { 2828 methodRetryHandler = new DefaultMethodRetryHandler(); 2829 } 2830 2831 return methodRetryHandler; 2832 } 2833 2834 /*** 2835 * Sets the {@link MethodRetryHandler retry handler} for this HTTP method 2836 * 2837 * @param handler the methodRetryHandler to use when this method executed 2838 */ 2839 public void setMethodRetryHandler(MethodRetryHandler handler) { 2840 methodRetryHandler = handler; 2841 } 2842 2843 /*** 2844 * This method is a dirty hack intended to work around 2845 * current (2.0) design flaw that prevents the user from 2846 * obtaining correct status code, headers and response body from the 2847 * preceding HTTP CONNECT method. 2848 * 2849 * TODO: Remove this crap as soon as possible 2850 */ 2851 protected void fakeResponse( 2852 StatusLine statusline, 2853 HeaderGroup responseheaders, 2854 InputStream responseStream 2855 ) { 2856 // set used so that the response can be read 2857 this.used = true; 2858 this.statusLine = statusline; 2859 this.responseHeaders = responseheaders; 2860 this.responseBody = null; 2861 this.responseStream = responseStream; 2862 } 2863 }

This page was automatically generated by Maven