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