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

This page was automatically generated by Maven