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

This page was automatically generated by Maven