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

This page was automatically generated by Maven