0001: /*
0002: * $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 $
0003: * $Revision: 539441 $
0004: * $Date: 2007-05-18 14:56:55 +0200 (Fri, 18 May 2007) $
0005: *
0006: * ====================================================================
0007: *
0008: * Licensed to the Apache Software Foundation (ASF) under one or more
0009: * contributor license agreements. See the NOTICE file distributed with
0010: * this work for additional information regarding copyright ownership.
0011: * The ASF licenses this file to You under the Apache License, Version 2.0
0012: * (the "License"); you may not use this file except in compliance with
0013: * the License. You may obtain a copy of the License at
0014: *
0015: * http://www.apache.org/licenses/LICENSE-2.0
0016: *
0017: * Unless required by applicable law or agreed to in writing, software
0018: * distributed under the License is distributed on an "AS IS" BASIS,
0019: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0020: * See the License for the specific language governing permissions and
0021: * limitations under the License.
0022: * ====================================================================
0023: *
0024: * This software consists of voluntary contributions made by many
0025: * individuals on behalf of the Apache Software Foundation. For more
0026: * information on the Apache Software Foundation, please see
0027: * <http://www.apache.org/>.
0028: *
0029: */
0030:
0031: package org.apache.commons.httpclient;
0032:
0033: import java.io.ByteArrayInputStream;
0034: import java.io.ByteArrayOutputStream;
0035: import java.io.IOException;
0036: import java.io.InputStream;
0037: import java.io.InterruptedIOException;
0038: import java.util.Collection;
0039:
0040: import org.apache.commons.httpclient.auth.AuthState;
0041: import org.apache.commons.httpclient.cookie.CookiePolicy;
0042: import org.apache.commons.httpclient.cookie.CookieSpec;
0043: import org.apache.commons.httpclient.cookie.CookieVersionSupport;
0044: import org.apache.commons.httpclient.cookie.MalformedCookieException;
0045: import org.apache.commons.httpclient.params.HttpMethodParams;
0046: import org.apache.commons.httpclient.protocol.Protocol;
0047: import org.apache.commons.httpclient.util.EncodingUtil;
0048: import org.apache.commons.httpclient.util.ExceptionUtil;
0049: import org.apache.commons.logging.Log;
0050: import org.apache.commons.logging.LogFactory;
0051:
0052: /**
0053: * An abstract base implementation of HttpMethod.
0054: * <p>
0055: * At minimum, subclasses will need to override:
0056: * <ul>
0057: * <li>{@link #getName} to return the approriate name for this method
0058: * </li>
0059: * </ul>
0060: * </p>
0061: *
0062: * <p>
0063: * When a method requires additional request headers, subclasses will typically
0064: * want to override:
0065: * <ul>
0066: * <li>{@link #addRequestHeaders addRequestHeaders(HttpState,HttpConnection)}
0067: * to write those headers
0068: * </li>
0069: * </ul>
0070: * </p>
0071: *
0072: * <p>
0073: * When a method expects specific response headers, subclasses may want to
0074: * override:
0075: * <ul>
0076: * <li>{@link #processResponseHeaders processResponseHeaders(HttpState,HttpConnection)}
0077: * to handle those headers
0078: * </li>
0079: * </ul>
0080: * </p>
0081: *
0082: *
0083: * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
0084: * @author Rodney Waldhoff
0085: * @author Sean C. Sullivan
0086: * @author <a href="mailto:dion@apache.org">dIon Gillard</a>
0087: * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
0088: * @author <a href="mailto:dims@apache.org">Davanum Srinivas</a>
0089: * @author Ortwin Glueck
0090: * @author Eric Johnson
0091: * @author Michael Becke
0092: * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
0093: * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
0094: * @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a>
0095: * @author Christian Kohlschuetter
0096: *
0097: * @version $Revision: 539441 $ $Date: 2007-05-18 14:56:55 +0200 (Fri, 18 May 2007) $
0098: */
0099: public abstract class HttpMethodBase implements HttpMethod {
0100:
0101: // -------------------------------------------------------------- Constants
0102:
0103: /** Log object for this class. */
0104: private static final Log LOG = LogFactory
0105: .getLog(HttpMethodBase.class);
0106:
0107: // ----------------------------------------------------- Instance variables
0108:
0109: /** Request headers, if any. */
0110: private HeaderGroup requestHeaders = new HeaderGroup();
0111:
0112: /** The Status-Line from the response. */
0113: protected StatusLine statusLine = null;
0114:
0115: /** Response headers, if any. */
0116: private HeaderGroup responseHeaders = new HeaderGroup();
0117:
0118: /** Response trailer headers, if any. */
0119: private HeaderGroup responseTrailerHeaders = new HeaderGroup();
0120:
0121: /** Path of the HTTP method. */
0122: private String path = null;
0123:
0124: /** Query string of the HTTP method, if any. */
0125: private String queryString = null;
0126:
0127: /** The response body of the HTTP method, assuming it has not be
0128: * intercepted by a sub-class. */
0129: private InputStream responseStream = null;
0130:
0131: /** The connection that the response stream was read from. */
0132: private HttpConnection responseConnection = null;
0133:
0134: /** Buffer for the response */
0135: private byte[] responseBody = null;
0136:
0137: /** True if the HTTP method should automatically follow HTTP redirects.*/
0138: private boolean followRedirects = false;
0139:
0140: /** True if the HTTP method should automatically handle
0141: * HTTP authentication challenges. */
0142: private boolean doAuthentication = true;
0143:
0144: /** HTTP protocol parameters. */
0145: private HttpMethodParams params = new HttpMethodParams();
0146:
0147: /** Host authentication state */
0148: private AuthState hostAuthState = new AuthState();
0149:
0150: /** Proxy authentication state */
0151: private AuthState proxyAuthState = new AuthState();
0152:
0153: /** True if this method has already been executed. */
0154: private boolean used = false;
0155:
0156: /** Count of how many times did this HTTP method transparently handle
0157: * a recoverable exception. */
0158: private int recoverableExceptionCount = 0;
0159:
0160: /** the host for this HTTP method, can be null */
0161: private HttpHost httphost = null;
0162:
0163: /**
0164: * Handles method retries
0165: *
0166: * @deprecated no loner used
0167: */
0168: private MethodRetryHandler methodRetryHandler;
0169:
0170: /** True if the connection must be closed when no longer needed */
0171: private boolean connectionCloseForced = false;
0172:
0173: /** Number of milliseconds to wait for 100-contunue response. */
0174: private static final int RESPONSE_WAIT_TIME_MS = 3000;
0175:
0176: /** HTTP protocol version used for execution of this method. */
0177: protected HttpVersion effectiveVersion = null;
0178:
0179: /** Whether the execution of this method has been aborted */
0180: private volatile boolean aborted = false;
0181:
0182: /** Whether the HTTP request has been transmitted to the target
0183: * server it its entirety */
0184: private boolean requestSent = false;
0185:
0186: /** Actual cookie policy */
0187: private CookieSpec cookiespec = null;
0188:
0189: /** Default initial size of the response buffer if content length is unknown. */
0190: private static final int DEFAULT_INITIAL_BUFFER_SIZE = 4 * 1024; // 4 kB
0191:
0192: // ----------------------------------------------------------- Constructors
0193:
0194: /**
0195: * No-arg constructor.
0196: */
0197: public HttpMethodBase() {
0198: }
0199:
0200: /**
0201: * Constructor specifying a URI.
0202: * It is responsibility of the caller to ensure that URI elements
0203: * (path & query parameters) are properly encoded (URL safe).
0204: *
0205: * @param uri either an absolute or relative URI. The URI is expected
0206: * to be URL-encoded
0207: *
0208: * @throws IllegalArgumentException when URI is invalid
0209: * @throws IllegalStateException when protocol of the absolute URI is not recognised
0210: */
0211: public HttpMethodBase(String uri) throws IllegalArgumentException,
0212: IllegalStateException {
0213:
0214: try {
0215:
0216: // create a URI and allow for null/empty uri values
0217: if (uri == null || uri.equals("")) {
0218: uri = "/";
0219: }
0220: String charset = getParams().getUriCharset();
0221: setURI(new URI(uri, true, charset));
0222: } catch (URIException e) {
0223: throw new IllegalArgumentException("Invalid uri '" + uri
0224: + "': " + e.getMessage());
0225: }
0226: }
0227:
0228: // ------------------------------------------- Property Setters and Getters
0229:
0230: /**
0231: * Obtains the name of the HTTP method as used in the HTTP request line,
0232: * for example <tt>"GET"</tt> or <tt>"POST"</tt>.
0233: *
0234: * @return the name of this method
0235: */
0236: public abstract String getName();
0237:
0238: /**
0239: * Returns the URI of the HTTP method
0240: *
0241: * @return The URI
0242: *
0243: * @throws URIException If the URI cannot be created.
0244: *
0245: * @see org.apache.commons.httpclient.HttpMethod#getURI()
0246: */
0247: public URI getURI() throws URIException {
0248: StringBuffer buffer = new StringBuffer();
0249: if (this .httphost != null) {
0250: buffer.append(this .httphost.getProtocol().getScheme());
0251: buffer.append("://");
0252: buffer.append(this .httphost.getHostName());
0253: int port = this .httphost.getPort();
0254: if (port != -1
0255: && port != this .httphost.getProtocol()
0256: .getDefaultPort()) {
0257: buffer.append(":");
0258: buffer.append(port);
0259: }
0260: }
0261: buffer.append(this .path);
0262: if (this .queryString != null) {
0263: buffer.append('?');
0264: buffer.append(this .queryString);
0265: }
0266: String charset = getParams().getUriCharset();
0267: return new URI(buffer.toString(), true, charset);
0268: }
0269:
0270: /**
0271: * Sets the URI for this method.
0272: *
0273: * @param uri URI to be set
0274: *
0275: * @throws URIException if a URI cannot be set
0276: *
0277: * @since 3.0
0278: */
0279: public void setURI(URI uri) throws URIException {
0280: // only set the host if specified by the URI
0281: if (uri.isAbsoluteURI()) {
0282: this .httphost = new HttpHost(uri);
0283: }
0284: // set the path, defaulting to root
0285: setPath(uri.getPath() == null ? "/" : uri.getEscapedPath());
0286: setQueryString(uri.getEscapedQuery());
0287: }
0288:
0289: /**
0290: * Sets whether or not the HTTP method should automatically follow HTTP redirects
0291: * (status code 302, etc.)
0292: *
0293: * @param followRedirects <tt>true</tt> if the method will automatically follow redirects,
0294: * <tt>false</tt> otherwise.
0295: */
0296: public void setFollowRedirects(boolean followRedirects) {
0297: this .followRedirects = followRedirects;
0298: }
0299:
0300: /**
0301: * Returns <tt>true</tt> if the HTTP method should automatically follow HTTP redirects
0302: * (status code 302, etc.), <tt>false</tt> otherwise.
0303: *
0304: * @return <tt>true</tt> if the method will automatically follow HTTP redirects,
0305: * <tt>false</tt> otherwise.
0306: */
0307: public boolean getFollowRedirects() {
0308: return this .followRedirects;
0309: }
0310:
0311: /** Sets whether version 1.1 of the HTTP protocol should be used per default.
0312: *
0313: * @param http11 <tt>true</tt> to use HTTP/1.1, <tt>false</tt> to use 1.0
0314: *
0315: * @deprecated Use {@link HttpMethodParams#setVersion(HttpVersion)}
0316: */
0317: public void setHttp11(boolean http11) {
0318: if (http11) {
0319: this .params.setVersion(HttpVersion.HTTP_1_1);
0320: } else {
0321: this .params.setVersion(HttpVersion.HTTP_1_0);
0322: }
0323: }
0324:
0325: /**
0326: * Returns <tt>true</tt> if the HTTP method should automatically handle HTTP
0327: * authentication challenges (status code 401, etc.), <tt>false</tt> otherwise
0328: *
0329: * @return <tt>true</tt> if authentication challenges will be processed
0330: * automatically, <tt>false</tt> otherwise.
0331: *
0332: * @since 2.0
0333: */
0334: public boolean getDoAuthentication() {
0335: return doAuthentication;
0336: }
0337:
0338: /**
0339: * Sets whether or not the HTTP method should automatically handle HTTP
0340: * authentication challenges (status code 401, etc.)
0341: *
0342: * @param doAuthentication <tt>true</tt> to process authentication challenges
0343: * authomatically, <tt>false</tt> otherwise.
0344: *
0345: * @since 2.0
0346: */
0347: public void setDoAuthentication(boolean doAuthentication) {
0348: this .doAuthentication = doAuthentication;
0349: }
0350:
0351: // ---------------------------------------------- Protected Utility Methods
0352:
0353: /**
0354: * Returns <tt>true</tt> if version 1.1 of the HTTP protocol should be
0355: * used per default, <tt>false</tt> if version 1.0 should be used.
0356: *
0357: * @return <tt>true</tt> to use HTTP/1.1, <tt>false</tt> to use 1.0
0358: *
0359: * @deprecated Use {@link HttpMethodParams#getVersion()}
0360: */
0361: public boolean isHttp11() {
0362: return this .params.getVersion().equals(HttpVersion.HTTP_1_1);
0363: }
0364:
0365: /**
0366: * Sets the path of the HTTP method.
0367: * It is responsibility of the caller to ensure that the path is
0368: * properly encoded (URL safe).
0369: *
0370: * @param path the path of the HTTP method. The path is expected
0371: * to be URL-encoded
0372: */
0373: public void setPath(String path) {
0374: this .path = path;
0375: }
0376:
0377: /**
0378: * Adds the specified request header, NOT overwriting any previous value.
0379: * Note that header-name matching is case insensitive.
0380: *
0381: * @param header the header to add to the request
0382: */
0383: public void addRequestHeader(Header header) {
0384: LOG.trace("HttpMethodBase.addRequestHeader(Header)");
0385:
0386: if (header == null) {
0387: LOG.debug("null header value ignored");
0388: } else {
0389: getRequestHeaderGroup().addHeader(header);
0390: }
0391: }
0392:
0393: /**
0394: * Use this method internally to add footers.
0395: *
0396: * @param footer The footer to add.
0397: */
0398: public void addResponseFooter(Header footer) {
0399: getResponseTrailerHeaderGroup().addHeader(footer);
0400: }
0401:
0402: /**
0403: * Gets the path of this HTTP method.
0404: * Calling this method <em>after</em> the request has been executed will
0405: * return the <em>actual</em> path, following any redirects automatically
0406: * handled by this HTTP method.
0407: *
0408: * @return the path to request or "/" if the path is blank.
0409: */
0410: public String getPath() {
0411: return (path == null || path.equals("")) ? "/" : path;
0412: }
0413:
0414: /**
0415: * Sets the query string of this HTTP method. The caller must ensure that the string
0416: * is properly URL encoded. The query string should not start with the question
0417: * mark character.
0418: *
0419: * @param queryString the query string
0420: *
0421: * @see EncodingUtil#formUrlEncode(NameValuePair[], String)
0422: */
0423: public void setQueryString(String queryString) {
0424: this .queryString = queryString;
0425: }
0426:
0427: /**
0428: * Sets the query string of this HTTP method. The pairs are encoded as UTF-8 characters.
0429: * To use a different charset the parameters can be encoded manually using EncodingUtil
0430: * and set as a single String.
0431: *
0432: * @param params an array of {@link NameValuePair}s to add as query string
0433: * parameters. The name/value pairs will be automcatically
0434: * URL encoded
0435: *
0436: * @see EncodingUtil#formUrlEncode(NameValuePair[], String)
0437: * @see #setQueryString(String)
0438: */
0439: public void setQueryString(NameValuePair[] params) {
0440: LOG
0441: .trace("enter HttpMethodBase.setQueryString(NameValuePair[])");
0442: queryString = EncodingUtil.formUrlEncode(params, "UTF-8");
0443: }
0444:
0445: /**
0446: * Gets the query string of this HTTP method.
0447: *
0448: * @return The query string
0449: */
0450: public String getQueryString() {
0451: return queryString;
0452: }
0453:
0454: /**
0455: * Set the specified request header, overwriting any previous value. Note
0456: * that header-name matching is case-insensitive.
0457: *
0458: * @param headerName the header's name
0459: * @param headerValue the header's value
0460: */
0461: public void setRequestHeader(String headerName, String headerValue) {
0462: Header header = new Header(headerName, headerValue);
0463: setRequestHeader(header);
0464: }
0465:
0466: /**
0467: * Sets the specified request header, overwriting any previous value.
0468: * Note that header-name matching is case insensitive.
0469: *
0470: * @param header the header
0471: */
0472: public void setRequestHeader(Header header) {
0473:
0474: Header[] headers = getRequestHeaderGroup().getHeaders(
0475: header.getName());
0476:
0477: for (int i = 0; i < headers.length; i++) {
0478: getRequestHeaderGroup().removeHeader(headers[i]);
0479: }
0480:
0481: getRequestHeaderGroup().addHeader(header);
0482:
0483: }
0484:
0485: /**
0486: * Returns the specified request header. Note that header-name matching is
0487: * case insensitive. <tt>null</tt> will be returned if either
0488: * <i>headerName</i> is <tt>null</tt> or there is no matching header for
0489: * <i>headerName</i>.
0490: *
0491: * @param headerName The name of the header to be returned.
0492: *
0493: * @return The specified request header.
0494: *
0495: * @since 3.0
0496: */
0497: public Header getRequestHeader(String headerName) {
0498: if (headerName == null) {
0499: return null;
0500: } else {
0501: return getRequestHeaderGroup().getCondensedHeader(
0502: headerName);
0503: }
0504: }
0505:
0506: /**
0507: * Returns an array of the requests headers that the HTTP method currently has
0508: *
0509: * @return an array of my request headers.
0510: */
0511: public Header[] getRequestHeaders() {
0512: return getRequestHeaderGroup().getAllHeaders();
0513: }
0514:
0515: /**
0516: * @see org.apache.commons.httpclient.HttpMethod#getRequestHeaders(java.lang.String)
0517: */
0518: public Header[] getRequestHeaders(String headerName) {
0519: return getRequestHeaderGroup().getHeaders(headerName);
0520: }
0521:
0522: /**
0523: * Gets the {@link HeaderGroup header group} storing the request headers.
0524: *
0525: * @return a HeaderGroup
0526: *
0527: * @since 2.0beta1
0528: */
0529: protected HeaderGroup getRequestHeaderGroup() {
0530: return requestHeaders;
0531: }
0532:
0533: /**
0534: * Gets the {@link HeaderGroup header group} storing the response trailer headers
0535: * as per RFC 2616 section 3.6.1.
0536: *
0537: * @return a HeaderGroup
0538: *
0539: * @since 2.0beta1
0540: */
0541: protected HeaderGroup getResponseTrailerHeaderGroup() {
0542: return responseTrailerHeaders;
0543: }
0544:
0545: /**
0546: * Gets the {@link HeaderGroup header group} storing the response headers.
0547: *
0548: * @return a HeaderGroup
0549: *
0550: * @since 2.0beta1
0551: */
0552: protected HeaderGroup getResponseHeaderGroup() {
0553: return responseHeaders;
0554: }
0555:
0556: /**
0557: * @see org.apache.commons.httpclient.HttpMethod#getResponseHeaders(java.lang.String)
0558: *
0559: * @since 3.0
0560: */
0561: public Header[] getResponseHeaders(String headerName) {
0562: return getResponseHeaderGroup().getHeaders(headerName);
0563: }
0564:
0565: /**
0566: * Returns the response status code.
0567: *
0568: * @return the status code associated with the latest response.
0569: */
0570: public int getStatusCode() {
0571: return statusLine.getStatusCode();
0572: }
0573:
0574: /**
0575: * Provides access to the response status line.
0576: *
0577: * @return the status line object from the latest response.
0578: * @since 2.0
0579: */
0580: public StatusLine getStatusLine() {
0581: return statusLine;
0582: }
0583:
0584: /**
0585: * Checks if response data is available.
0586: * @return <tt>true</tt> if response data is available, <tt>false</tt> otherwise.
0587: */
0588: private boolean responseAvailable() {
0589: return (responseBody != null) || (responseStream != null);
0590: }
0591:
0592: /**
0593: * Returns an array of the response headers that the HTTP method currently has
0594: * in the order in which they were read.
0595: *
0596: * @return an array of response headers.
0597: */
0598: public Header[] getResponseHeaders() {
0599: return getResponseHeaderGroup().getAllHeaders();
0600: }
0601:
0602: /**
0603: * Gets the response header associated with the given name. Header name
0604: * matching is case insensitive. <tt>null</tt> will be returned if either
0605: * <i>headerName</i> is <tt>null</tt> or there is no matching header for
0606: * <i>headerName</i>.
0607: *
0608: * @param headerName the header name to match
0609: *
0610: * @return the matching header
0611: */
0612: public Header getResponseHeader(String headerName) {
0613: if (headerName == null) {
0614: return null;
0615: } else {
0616: return getResponseHeaderGroup().getCondensedHeader(
0617: headerName);
0618: }
0619: }
0620:
0621: /**
0622: * Return the length (in bytes) of the response body, as specified in a
0623: * <tt>Content-Length</tt> header.
0624: *
0625: * <p>
0626: * Return <tt>-1</tt> when the content-length is unknown.
0627: * </p>
0628: *
0629: * @return content length, if <tt>Content-Length</tt> header is available.
0630: * <tt>0</tt> indicates that the request has no body.
0631: * If <tt>Content-Length</tt> header is not present, the method
0632: * returns <tt>-1</tt>.
0633: */
0634: public long getResponseContentLength() {
0635: Header[] headers = getResponseHeaderGroup().getHeaders(
0636: "Content-Length");
0637: if (headers.length == 0) {
0638: return -1;
0639: }
0640: if (headers.length > 1) {
0641: LOG.warn("Multiple content-length headers detected");
0642: }
0643: for (int i = headers.length - 1; i >= 0; i--) {
0644: Header header = headers[i];
0645: try {
0646: return Long.parseLong(header.getValue());
0647: } catch (NumberFormatException e) {
0648: if (LOG.isWarnEnabled()) {
0649: LOG.warn("Invalid content-length value: "
0650: + e.getMessage());
0651: }
0652: }
0653: // See if we can have better luck with another header, if present
0654: }
0655: return -1;
0656: }
0657:
0658: /**
0659: * Returns the response body of the HTTP method, if any, as an array of bytes.
0660: * If response body is not available or cannot be read, returns <tt>null</tt>.
0661: * Buffers the response and this method can be called several times yielding
0662: * the same result each time.
0663: *
0664: * Note: This will cause the entire response body to be buffered in memory. A
0665: * malicious server may easily exhaust all the VM memory. It is strongly
0666: * recommended, to use getResponseAsStream if the content length of the response
0667: * is unknown or resonably large.
0668: *
0669: * @return The response body.
0670: *
0671: * @throws IOException If an I/O (transport) problem occurs while obtaining the
0672: * response body.
0673: */
0674: public byte[] getResponseBody() throws IOException {
0675: if (this .responseBody == null) {
0676: InputStream instream = getResponseBodyAsStream();
0677: if (instream != null) {
0678: long contentLength = getResponseContentLength();
0679: if (contentLength > Integer.MAX_VALUE) { //guard below cast from overflow
0680: throw new IOException(
0681: "Content too large to be buffered: "
0682: + contentLength + " bytes");
0683: }
0684: int limit = getParams().getIntParameter(
0685: HttpMethodParams.BUFFER_WARN_TRIGGER_LIMIT,
0686: 1024 * 1024);
0687: if ((contentLength == -1) || (contentLength > limit)) {
0688: LOG
0689: .warn("Going to buffer response body of large or unknown size. "
0690: + "Using getResponseBodyAsStream instead is recommended.");
0691: }
0692: LOG.debug("Buffering response body");
0693: ByteArrayOutputStream outstream = new ByteArrayOutputStream(
0694: contentLength > 0 ? (int) contentLength
0695: : DEFAULT_INITIAL_BUFFER_SIZE);
0696: byte[] buffer = new byte[4096];
0697: int len;
0698: while ((len = instream.read(buffer)) > 0) {
0699: outstream.write(buffer, 0, len);
0700: }
0701: outstream.close();
0702: setResponseStream(null);
0703: this .responseBody = outstream.toByteArray();
0704: }
0705: }
0706: return this .responseBody;
0707: }
0708:
0709: /**
0710: * Returns the response body of the HTTP method, if any, as an array of bytes.
0711: * If response body is not available or cannot be read, returns <tt>null</tt>.
0712: * Buffers the response and this method can be called several times yielding
0713: * the same result each time.
0714: *
0715: * Note: This will cause the entire response body to be buffered in memory. This method is
0716: * safe if the content length of the response is unknown, because the amount of memory used
0717: * is limited.<p>
0718: *
0719: * If the response is large this method involves lots of array copying and many object
0720: * allocations, which makes it unsuitable for high-performance / low-footprint applications.
0721: * Those applications should use {@link #getResponseBodyAsStream()}.
0722: *
0723: * @param maxlen the maximum content length to accept (number of bytes).
0724: * @return The response body.
0725: *
0726: * @throws IOException If an I/O (transport) problem occurs while obtaining the
0727: * response body.
0728: */
0729: public byte[] getResponseBody(int maxlen) throws IOException {
0730: if (maxlen < 0)
0731: throw new IllegalArgumentException(
0732: "maxlen must be positive");
0733: if (this .responseBody == null) {
0734: InputStream instream = getResponseBodyAsStream();
0735: if (instream != null) {
0736: // we might already know that the content is larger
0737: long contentLength = getResponseContentLength();
0738: if ((contentLength != -1) && (contentLength > maxlen)) {
0739: throw new HttpContentTooLargeException(
0740: "Content-Length is " + contentLength,
0741: maxlen);
0742: }
0743:
0744: LOG.debug("Buffering response body");
0745: ByteArrayOutputStream rawdata = new ByteArrayOutputStream(
0746: contentLength > 0 ? (int) contentLength
0747: : DEFAULT_INITIAL_BUFFER_SIZE);
0748: byte[] buffer = new byte[2048];
0749: int pos = 0;
0750: int len;
0751: do {
0752: len = instream.read(buffer, 0, Math.min(
0753: buffer.length, maxlen - pos));
0754: if (len == -1)
0755: break;
0756: rawdata.write(buffer, 0, len);
0757: pos += len;
0758: } while (pos < maxlen);
0759:
0760: setResponseStream(null);
0761: // check if there is even more data
0762: if (pos == maxlen) {
0763: if (instream.read() != -1)
0764: throw new HttpContentTooLargeException(
0765: "Content-Length not known but larger than "
0766: + maxlen, maxlen);
0767: }
0768: this .responseBody = rawdata.toByteArray();
0769: }
0770: }
0771: return this .responseBody;
0772: }
0773:
0774: /**
0775: * Returns the response body of the HTTP method, if any, as an {@link InputStream}.
0776: * If response body is not available, returns <tt>null</tt>. If the response has been
0777: * buffered this method returns a new stream object on every call. If the response
0778: * has not been buffered the returned stream can only be read once.
0779: *
0780: * @return The response body or <code>null</code>.
0781: *
0782: * @throws IOException If an I/O (transport) problem occurs while obtaining the
0783: * response body.
0784: */
0785: public InputStream getResponseBodyAsStream() throws IOException {
0786: if (responseStream != null) {
0787: return responseStream;
0788: }
0789: if (responseBody != null) {
0790: InputStream byteResponseStream = new ByteArrayInputStream(
0791: responseBody);
0792: LOG.debug("re-creating response stream from byte array");
0793: return byteResponseStream;
0794: }
0795: return null;
0796: }
0797:
0798: /**
0799: * Returns the response body of the HTTP method, if any, as a {@link String}.
0800: * If response body is not available or cannot be read, returns <tt>null</tt>
0801: * The string conversion on the data is done using the character encoding specified
0802: * in <tt>Content-Type</tt> header. Buffers the response and this method can be
0803: * called several times yielding the same result each time.
0804: *
0805: * Note: This will cause the entire response body to be buffered in memory. A
0806: * malicious server may easily exhaust all the VM memory. It is strongly
0807: * recommended, to use getResponseAsStream if the content length of the response
0808: * is unknown or resonably large.
0809: *
0810: * @return The response body or <code>null</code>.
0811: *
0812: * @throws IOException If an I/O (transport) problem occurs while obtaining the
0813: * response body.
0814: */
0815: public String getResponseBodyAsString() throws IOException {
0816: byte[] rawdata = null;
0817: if (responseAvailable()) {
0818: rawdata = getResponseBody();
0819: }
0820: if (rawdata != null) {
0821: return EncodingUtil
0822: .getString(rawdata, getResponseCharSet());
0823: } else {
0824: return null;
0825: }
0826: }
0827:
0828: /**
0829: * Returns the response body of the HTTP method, if any, as a {@link String}.
0830: * If response body is not available or cannot be read, returns <tt>null</tt>
0831: * The string conversion on the data is done using the character encoding specified
0832: * in <tt>Content-Type</tt> header. Buffers the response and this method can be
0833: * called several times yielding the same result each time.</p>
0834: *
0835: * Note: This will cause the entire response body to be buffered in memory. This method is
0836: * safe if the content length of the response is unknown, because the amount of memory used
0837: * is limited.<p>
0838: *
0839: * If the response is large this method involves lots of array copying and many object
0840: * allocations, which makes it unsuitable for high-performance / low-footprint applications.
0841: * Those applications should use {@link #getResponseBodyAsStream()}.
0842: *
0843: * @param maxlen the maximum content length to accept (number of bytes). Note that,
0844: * depending on the encoding, this is not equal to the number of characters.
0845: * @return The response body or <code>null</code>.
0846: *
0847: * @throws IOException If an I/O (transport) problem occurs while obtaining the
0848: * response body.
0849: */
0850: public String getResponseBodyAsString(int maxlen)
0851: throws IOException {
0852: if (maxlen < 0)
0853: throw new IllegalArgumentException(
0854: "maxlen must be positive");
0855: byte[] rawdata = null;
0856: if (responseAvailable()) {
0857: rawdata = getResponseBody(maxlen);
0858: }
0859: if (rawdata != null) {
0860: return EncodingUtil
0861: .getString(rawdata, getResponseCharSet());
0862: } else {
0863: return null;
0864: }
0865: }
0866:
0867: /**
0868: * Returns an array of the response footers that the HTTP method currently has
0869: * in the order in which they were read.
0870: *
0871: * @return an array of footers
0872: */
0873: public Header[] getResponseFooters() {
0874: return getResponseTrailerHeaderGroup().getAllHeaders();
0875: }
0876:
0877: /**
0878: * Gets the response footer associated with the given name.
0879: * Footer name matching is case insensitive.
0880: * <tt>null</tt> will be returned if either <i>footerName</i> is
0881: * <tt>null</tt> or there is no matching footer for <i>footerName</i>
0882: * or there are no footers available. If there are multiple footers
0883: * with the same name, there values will be combined with the ',' separator
0884: * as specified by RFC2616.
0885: *
0886: * @param footerName the footer name to match
0887: * @return the matching footer
0888: */
0889: public Header getResponseFooter(String footerName) {
0890: if (footerName == null) {
0891: return null;
0892: } else {
0893: return getResponseTrailerHeaderGroup().getCondensedHeader(
0894: footerName);
0895: }
0896: }
0897:
0898: /**
0899: * Sets the response stream.
0900: * @param responseStream The new response stream.
0901: */
0902: protected void setResponseStream(InputStream responseStream) {
0903: this .responseStream = responseStream;
0904: }
0905:
0906: /**
0907: * Returns a stream from which the body of the current response may be read.
0908: * If the method has not yet been executed, if <code>responseBodyConsumed</code>
0909: * has been called, or if the stream returned by a previous call has been closed,
0910: * <code>null</code> will be returned.
0911: *
0912: * @return the current response stream
0913: */
0914: protected InputStream getResponseStream() {
0915: return responseStream;
0916: }
0917:
0918: /**
0919: * Returns the status text (or "reason phrase") associated with the latest
0920: * response.
0921: *
0922: * @return The status text.
0923: */
0924: public String getStatusText() {
0925: return statusLine.getReasonPhrase();
0926: }
0927:
0928: /**
0929: * Defines how strictly HttpClient follows the HTTP protocol specification
0930: * (RFC 2616 and other relevant RFCs). In the strict mode HttpClient precisely
0931: * implements the requirements of the specification, whereas in non-strict mode
0932: * it attempts to mimic the exact behaviour of commonly used HTTP agents,
0933: * which many HTTP servers expect.
0934: *
0935: * @param strictMode <tt>true</tt> for strict mode, <tt>false</tt> otherwise
0936: *
0937: * @deprecated Use {@link org.apache.commons.httpclient.params.HttpParams#setParameter(String, Object)}
0938: * to exercise a more granular control over HTTP protocol strictness.
0939: */
0940: public void setStrictMode(boolean strictMode) {
0941: if (strictMode) {
0942: this .params.makeStrict();
0943: } else {
0944: this .params.makeLenient();
0945: }
0946: }
0947:
0948: /**
0949: * @deprecated Use {@link org.apache.commons.httpclient.params.HttpParams#setParameter(String, Object)}
0950: * to exercise a more granular control over HTTP protocol strictness.
0951: *
0952: * @return <tt>false</tt>
0953: */
0954: public boolean isStrictMode() {
0955: return false;
0956: }
0957:
0958: /**
0959: * Adds the specified request header, NOT overwriting any previous value.
0960: * Note that header-name matching is case insensitive.
0961: *
0962: * @param headerName the header's name
0963: * @param headerValue the header's value
0964: */
0965: public void addRequestHeader(String headerName, String headerValue) {
0966: addRequestHeader(new Header(headerName, headerValue));
0967: }
0968:
0969: /**
0970: * Tests if the connection should be force-closed when no longer needed.
0971: *
0972: * @return <code>true</code> if the connection must be closed
0973: */
0974: protected boolean isConnectionCloseForced() {
0975: return this .connectionCloseForced;
0976: }
0977:
0978: /**
0979: * Sets whether or not the connection should be force-closed when no longer
0980: * needed. This value should only be set to <code>true</code> in abnormal
0981: * circumstances, such as HTTP protocol violations.
0982: *
0983: * @param b <code>true</code> if the connection must be closed, <code>false</code>
0984: * otherwise.
0985: */
0986: protected void setConnectionCloseForced(boolean b) {
0987: if (LOG.isDebugEnabled()) {
0988: LOG.debug("Force-close connection: " + b);
0989: }
0990: this .connectionCloseForced = b;
0991: }
0992:
0993: /**
0994: * Tests if the connection should be closed after the method has been executed.
0995: * The connection will be left open when using HTTP/1.1 or if <tt>Connection:
0996: * keep-alive</tt> header was sent.
0997: *
0998: * @param conn the connection in question
0999: *
1000: * @return boolean true if we should close the connection.
1001: */
1002: protected boolean shouldCloseConnection(HttpConnection conn) {
1003: // Connection must be closed due to an abnormal circumstance
1004: if (isConnectionCloseForced()) {
1005: LOG.debug("Should force-close connection.");
1006: return true;
1007: }
1008:
1009: Header connectionHeader = null;
1010: // In case being connected via a proxy server
1011: if (!conn.isTransparent()) {
1012: // Check for 'proxy-connection' directive
1013: connectionHeader = responseHeaders
1014: .getFirstHeader("proxy-connection");
1015: }
1016: // In all cases Check for 'connection' directive
1017: // some non-complaint proxy servers send it instread of
1018: // expected 'proxy-connection' directive
1019: if (connectionHeader == null) {
1020: connectionHeader = responseHeaders
1021: .getFirstHeader("connection");
1022: }
1023: // In case the response does not contain any explict connection
1024: // directives, check whether the request does
1025: if (connectionHeader == null) {
1026: connectionHeader = requestHeaders
1027: .getFirstHeader("connection");
1028: }
1029: if (connectionHeader != null) {
1030: if (connectionHeader.getValue().equalsIgnoreCase("close")) {
1031: if (LOG.isDebugEnabled()) {
1032: LOG
1033: .debug("Should close connection in response to directive: "
1034: + connectionHeader.getValue());
1035: }
1036: return true;
1037: } else if (connectionHeader.getValue().equalsIgnoreCase(
1038: "keep-alive")) {
1039: if (LOG.isDebugEnabled()) {
1040: LOG
1041: .debug("Should NOT close connection in response to directive: "
1042: + connectionHeader.getValue());
1043: }
1044: return false;
1045: } else {
1046: if (LOG.isDebugEnabled()) {
1047: LOG.debug("Unknown directive: "
1048: + connectionHeader.toExternalForm());
1049: }
1050: }
1051: }
1052: LOG
1053: .debug("Resorting to protocol version default close connection policy");
1054: // missing or invalid connection header, do the default
1055: if (this .effectiveVersion.greaterEquals(HttpVersion.HTTP_1_1)) {
1056: if (LOG.isDebugEnabled()) {
1057: LOG.debug("Should NOT close connection, using "
1058: + this .effectiveVersion.toString());
1059: }
1060: } else {
1061: if (LOG.isDebugEnabled()) {
1062: LOG.debug("Should close connection, using "
1063: + this .effectiveVersion.toString());
1064: }
1065: }
1066: return this .effectiveVersion.lessEquals(HttpVersion.HTTP_1_0);
1067: }
1068:
1069: /**
1070: * Tests if the this method is ready to be executed.
1071: *
1072: * @param state the {@link HttpState state} information associated with this method
1073: * @param conn the {@link HttpConnection connection} to be used
1074: * @throws HttpException If the method is in invalid state.
1075: */
1076: private void checkExecuteConditions(HttpState state,
1077: HttpConnection conn) throws HttpException {
1078:
1079: if (state == null) {
1080: throw new IllegalArgumentException(
1081: "HttpState parameter may not be null");
1082: }
1083: if (conn == null) {
1084: throw new IllegalArgumentException(
1085: "HttpConnection parameter may not be null");
1086: }
1087: if (this .aborted) {
1088: throw new IllegalStateException("Method has been aborted");
1089: }
1090: if (!validate()) {
1091: throw new ProtocolException(
1092: "HttpMethodBase object not valid");
1093: }
1094: }
1095:
1096: /**
1097: * Executes this method using the specified <code>HttpConnection</code> and
1098: * <code>HttpState</code>.
1099: *
1100: * @param state {@link HttpState state} information to associate with this
1101: * request. Must be non-null.
1102: * @param conn the {@link HttpConnection connection} to used to execute
1103: * this HTTP method. Must be non-null.
1104: *
1105: * @return the integer status code if one was obtained, or <tt>-1</tt>
1106: *
1107: * @throws IOException if an I/O (transport) error occurs
1108: * @throws HttpException if a protocol exception occurs.
1109: */
1110: public int execute(HttpState state, HttpConnection conn)
1111: throws HttpException, IOException {
1112:
1113: LOG
1114: .trace("enter HttpMethodBase.execute(HttpState, HttpConnection)");
1115:
1116: // this is our connection now, assign it to a local variable so
1117: // that it can be released later
1118: this .responseConnection = conn;
1119:
1120: checkExecuteConditions(state, conn);
1121: this .statusLine = null;
1122: this .connectionCloseForced = false;
1123:
1124: conn.setLastResponseInputStream(null);
1125:
1126: // determine the effective protocol version
1127: if (this .effectiveVersion == null) {
1128: this .effectiveVersion = this .params.getVersion();
1129: }
1130:
1131: writeRequest(state, conn);
1132: this .requestSent = true;
1133: readResponse(state, conn);
1134: // the method has successfully executed
1135: used = true;
1136:
1137: return statusLine.getStatusCode();
1138: }
1139:
1140: /**
1141: * Aborts the execution of this method.
1142: *
1143: * @since 3.0
1144: */
1145: public void abort() {
1146: if (this .aborted) {
1147: return;
1148: }
1149: this .aborted = true;
1150: HttpConnection conn = this .responseConnection;
1151: if (conn != null) {
1152: conn.close();
1153: }
1154: }
1155:
1156: /**
1157: * Returns <tt>true</tt> if the HTTP method has been already {@link #execute executed},
1158: * but not {@link #recycle recycled}.
1159: *
1160: * @return <tt>true</tt> if the method has been executed, <tt>false</tt> otherwise
1161: */
1162: public boolean hasBeenUsed() {
1163: return used;
1164: }
1165:
1166: /**
1167: * Recycles the HTTP method so that it can be used again.
1168: * Note that all of the instance variables will be reset
1169: * once this method has been called. This method will also
1170: * release the connection being used by this HTTP method.
1171: *
1172: * @see #releaseConnection()
1173: *
1174: * @deprecated no longer supported and will be removed in the future
1175: * version of HttpClient
1176: */
1177: public void recycle() {
1178: LOG.trace("enter HttpMethodBase.recycle()");
1179:
1180: releaseConnection();
1181:
1182: path = null;
1183: followRedirects = false;
1184: doAuthentication = true;
1185: queryString = null;
1186: getRequestHeaderGroup().clear();
1187: getResponseHeaderGroup().clear();
1188: getResponseTrailerHeaderGroup().clear();
1189: statusLine = null;
1190: effectiveVersion = null;
1191: aborted = false;
1192: used = false;
1193: params = new HttpMethodParams();
1194: responseBody = null;
1195: recoverableExceptionCount = 0;
1196: connectionCloseForced = false;
1197: hostAuthState.invalidate();
1198: proxyAuthState.invalidate();
1199: cookiespec = null;
1200: requestSent = false;
1201: }
1202:
1203: /**
1204: * Releases the connection being used by this HTTP method. In particular the
1205: * connection is used to read the response(if there is one) and will be held
1206: * until the response has been read. If the connection can be reused by other
1207: * HTTP methods it is NOT closed at this point.
1208: *
1209: * @since 2.0
1210: */
1211: public void releaseConnection() {
1212: try {
1213: if (this .responseStream != null) {
1214: try {
1215: // FYI - this may indirectly invoke responseBodyConsumed.
1216: this .responseStream.close();
1217: } catch (IOException ignore) {
1218: }
1219: }
1220: } finally {
1221: ensureConnectionRelease();
1222: }
1223: }
1224:
1225: /**
1226: * Remove the request header associated with the given name. Note that
1227: * header-name matching is case insensitive.
1228: *
1229: * @param headerName the header name
1230: */
1231: public void removeRequestHeader(String headerName) {
1232:
1233: Header[] headers = getRequestHeaderGroup().getHeaders(
1234: headerName);
1235: for (int i = 0; i < headers.length; i++) {
1236: getRequestHeaderGroup().removeHeader(headers[i]);
1237: }
1238:
1239: }
1240:
1241: /**
1242: * Removes the given request header.
1243: *
1244: * @param header the header
1245: */
1246: public void removeRequestHeader(final Header header) {
1247: if (header == null) {
1248: return;
1249: }
1250: getRequestHeaderGroup().removeHeader(header);
1251: }
1252:
1253: // ---------------------------------------------------------------- Queries
1254:
1255: /**
1256: * Returns <tt>true</tt> the method is ready to execute, <tt>false</tt> otherwise.
1257: *
1258: * @return This implementation always returns <tt>true</tt>.
1259: */
1260: public boolean validate() {
1261: return true;
1262: }
1263:
1264: /**
1265: * Returns the actual cookie policy
1266: *
1267: * @param state HTTP state. TODO: to be removed in the future
1268: *
1269: * @return cookie spec
1270: */
1271: private CookieSpec getCookieSpec(final HttpState state) {
1272: if (this .cookiespec == null) {
1273: int i = state.getCookiePolicy();
1274: if (i == -1) {
1275: this .cookiespec = CookiePolicy
1276: .getCookieSpec(this .params.getCookiePolicy());
1277: } else {
1278: this .cookiespec = CookiePolicy.getSpecByPolicy(i);
1279: }
1280: this .cookiespec
1281: .setValidDateFormats((Collection) this .params
1282: .getParameter(HttpMethodParams.DATE_PATTERNS));
1283: }
1284: return this .cookiespec;
1285: }
1286:
1287: /**
1288: * Generates <tt>Cookie</tt> request headers for those {@link Cookie cookie}s
1289: * that match the given host, port and path.
1290: *
1291: * @param state the {@link HttpState state} information associated with this method
1292: * @param conn the {@link HttpConnection connection} used to execute
1293: * this HTTP method
1294: *
1295: * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1296: * can be recovered from.
1297: * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
1298: * cannot be recovered from.
1299: */
1300: protected void addCookieRequestHeader(HttpState state,
1301: HttpConnection conn) throws IOException, HttpException {
1302:
1303: LOG
1304: .trace("enter HttpMethodBase.addCookieRequestHeader(HttpState, "
1305: + "HttpConnection)");
1306:
1307: Header[] cookieheaders = getRequestHeaderGroup().getHeaders(
1308: "Cookie");
1309: for (int i = 0; i < cookieheaders.length; i++) {
1310: Header cookieheader = cookieheaders[i];
1311: if (cookieheader.isAutogenerated()) {
1312: getRequestHeaderGroup().removeHeader(cookieheader);
1313: }
1314: }
1315:
1316: CookieSpec matcher = getCookieSpec(state);
1317: String host = this .params.getVirtualHost();
1318: if (host == null) {
1319: host = conn.getHost();
1320: }
1321: Cookie[] cookies = matcher.match(host, conn.getPort(),
1322: getPath(), conn.isSecure(), state.getCookies());
1323: if ((cookies != null) && (cookies.length > 0)) {
1324: if (getParams().isParameterTrue(
1325: HttpMethodParams.SINGLE_COOKIE_HEADER)) {
1326: // In strict mode put all cookies on the same header
1327: String s = matcher.formatCookies(cookies);
1328: getRequestHeaderGroup().addHeader(
1329: new Header("Cookie", s, true));
1330: } else {
1331: // In non-strict mode put each cookie on a separate header
1332: for (int i = 0; i < cookies.length; i++) {
1333: String s = matcher.formatCookie(cookies[i]);
1334: getRequestHeaderGroup().addHeader(
1335: new Header("Cookie", s, true));
1336: }
1337: }
1338: if (matcher instanceof CookieVersionSupport) {
1339: CookieVersionSupport versupport = (CookieVersionSupport) matcher;
1340: int ver = versupport.getVersion();
1341: boolean needVersionHeader = false;
1342: for (int i = 0; i < cookies.length; i++) {
1343: if (ver != cookies[i].getVersion()) {
1344: needVersionHeader = true;
1345: }
1346: }
1347: if (needVersionHeader) {
1348: // Advertise cookie version support
1349: getRequestHeaderGroup().addHeader(
1350: versupport.getVersionHeader());
1351: }
1352: }
1353: }
1354: }
1355:
1356: /**
1357: * Generates <tt>Host</tt> request header, as long as no <tt>Host</tt> request
1358: * header already exists.
1359: *
1360: * @param state the {@link HttpState state} information associated with this method
1361: * @param conn the {@link HttpConnection connection} used to execute
1362: * this HTTP method
1363: *
1364: * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1365: * can be recovered from.
1366: * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
1367: * cannot be recovered from.
1368: */
1369: protected void addHostRequestHeader(HttpState state,
1370: HttpConnection conn) throws IOException, HttpException {
1371: LOG
1372: .trace("enter HttpMethodBase.addHostRequestHeader(HttpState, "
1373: + "HttpConnection)");
1374:
1375: // Per 19.6.1.1 of RFC 2616, it is legal for HTTP/1.0 based
1376: // applications to send the Host request-header.
1377: // TODO: Add the ability to disable the sending of this header for
1378: // HTTP/1.0 requests.
1379: String host = this .params.getVirtualHost();
1380: if (host != null) {
1381: LOG.debug("Using virtual host name: " + host);
1382: } else {
1383: host = conn.getHost();
1384: }
1385: int port = conn.getPort();
1386:
1387: // Note: RFC 2616 uses the term "internet host name" for what goes on the
1388: // host line. It would seem to imply that host should be blank if the
1389: // host is a number instead of an name. Based on the behavior of web
1390: // browsers, and the fact that RFC 2616 never defines the phrase "internet
1391: // host name", and the bad behavior of HttpClient that follows if we
1392: // send blank, I interpret this as a small misstatement in the RFC, where
1393: // they meant to say "internet host". So IP numbers get sent as host
1394: // entries too. -- Eric Johnson 12/13/2002
1395: if (LOG.isDebugEnabled()) {
1396: LOG.debug("Adding Host request header");
1397: }
1398:
1399: //appends the port only if not using the default port for the protocol
1400: if (conn.getProtocol().getDefaultPort() != port) {
1401: host += (":" + port);
1402: }
1403:
1404: setRequestHeader("Host", host);
1405: }
1406:
1407: /**
1408: * Generates <tt>Proxy-Connection: Keep-Alive</tt> request header when
1409: * communicating via a proxy server.
1410: *
1411: * @param state the {@link HttpState state} information associated with this method
1412: * @param conn the {@link HttpConnection connection} used to execute
1413: * this HTTP method
1414: *
1415: * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1416: * can be recovered from.
1417: * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
1418: * cannot be recovered from.
1419: */
1420: protected void addProxyConnectionHeader(HttpState state,
1421: HttpConnection conn) throws IOException, HttpException {
1422: LOG.trace("enter HttpMethodBase.addProxyConnectionHeader("
1423: + "HttpState, HttpConnection)");
1424: if (!conn.isTransparent()) {
1425: if (getRequestHeader("Proxy-Connection") == null) {
1426: addRequestHeader("Proxy-Connection", "Keep-Alive");
1427: }
1428: }
1429: }
1430:
1431: /**
1432: * Generates all the required request {@link Header header}s
1433: * to be submitted via the given {@link HttpConnection connection}.
1434: *
1435: * <p>
1436: * This implementation adds <tt>User-Agent</tt>, <tt>Host</tt>,
1437: * <tt>Cookie</tt>, <tt>Authorization</tt>, <tt>Proxy-Authorization</tt>
1438: * and <tt>Proxy-Connection</tt> headers, when appropriate.
1439: * </p>
1440: *
1441: * <p>
1442: * Subclasses may want to override this method to to add additional
1443: * headers, and may choose to invoke this implementation (via
1444: * <tt>super</tt>) to add the "standard" headers.
1445: * </p>
1446: *
1447: * @param state the {@link HttpState state} information associated with this method
1448: * @param conn the {@link HttpConnection connection} used to execute
1449: * this HTTP method
1450: *
1451: * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1452: * can be recovered from.
1453: * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
1454: * cannot be recovered from.
1455: *
1456: * @see #writeRequestHeaders
1457: */
1458: protected void addRequestHeaders(HttpState state,
1459: HttpConnection conn) throws IOException, HttpException {
1460: LOG.trace("enter HttpMethodBase.addRequestHeaders(HttpState, "
1461: + "HttpConnection)");
1462:
1463: addUserAgentRequestHeader(state, conn);
1464: addHostRequestHeader(state, conn);
1465: addCookieRequestHeader(state, conn);
1466: addProxyConnectionHeader(state, conn);
1467: }
1468:
1469: /**
1470: * Generates default <tt>User-Agent</tt> request header, as long as no
1471: * <tt>User-Agent</tt> request header already exists.
1472: *
1473: * @param state the {@link HttpState state} information associated with this method
1474: * @param conn the {@link HttpConnection connection} used to execute
1475: * this HTTP method
1476: *
1477: * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1478: * can be recovered from.
1479: * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
1480: * cannot be recovered from.
1481: */
1482: protected void addUserAgentRequestHeader(HttpState state,
1483: HttpConnection conn) throws IOException, HttpException {
1484: LOG
1485: .trace("enter HttpMethodBase.addUserAgentRequestHeaders(HttpState, "
1486: + "HttpConnection)");
1487:
1488: if (getRequestHeader("User-Agent") == null) {
1489: String agent = (String) getParams().getParameter(
1490: HttpMethodParams.USER_AGENT);
1491: if (agent == null) {
1492: agent = "Jakarta Commons-HttpClient";
1493: }
1494: setRequestHeader("User-Agent", agent);
1495: }
1496: }
1497:
1498: /**
1499: * Throws an {@link IllegalStateException} if the HTTP method has been already
1500: * {@link #execute executed}, but not {@link #recycle recycled}.
1501: *
1502: * @throws IllegalStateException if the method has been used and not
1503: * recycled
1504: */
1505: protected void checkNotUsed() throws IllegalStateException {
1506: if (used) {
1507: throw new IllegalStateException("Already used.");
1508: }
1509: }
1510:
1511: /**
1512: * Throws an {@link IllegalStateException} if the HTTP method has not been
1513: * {@link #execute executed} since last {@link #recycle recycle}.
1514: *
1515: *
1516: * @throws IllegalStateException if not used
1517: */
1518: protected void checkUsed() throws IllegalStateException {
1519: if (!used) {
1520: throw new IllegalStateException("Not Used.");
1521: }
1522: }
1523:
1524: // ------------------------------------------------- Static Utility Methods
1525:
1526: /**
1527: * Generates HTTP request line according to the specified attributes.
1528: *
1529: * @param connection the {@link HttpConnection connection} used to execute
1530: * this HTTP method
1531: * @param name the method name generate a request for
1532: * @param requestPath the path string for the request
1533: * @param query the query string for the request
1534: * @param version the protocol version to use (e.g. HTTP/1.0)
1535: *
1536: * @return HTTP request line
1537: */
1538: protected static String generateRequestLine(
1539: HttpConnection connection, String name, String requestPath,
1540: String query, String version) {
1541: LOG
1542: .trace("enter HttpMethodBase.generateRequestLine(HttpConnection, "
1543: + "String, String, String, String)");
1544:
1545: StringBuffer buf = new StringBuffer();
1546: // Append method name
1547: buf.append(name);
1548: buf.append(" ");
1549: // Absolute or relative URL?
1550: if (!connection.isTransparent()) {
1551: Protocol protocol = connection.getProtocol();
1552: buf.append(protocol.getScheme().toLowerCase());
1553: buf.append("://");
1554: buf.append(connection.getHost());
1555: if ((connection.getPort() != -1)
1556: && (connection.getPort() != protocol
1557: .getDefaultPort())) {
1558: buf.append(":");
1559: buf.append(connection.getPort());
1560: }
1561: }
1562: // Append path, if any
1563: if (requestPath == null) {
1564: buf.append("/");
1565: } else {
1566: if (!connection.isTransparent()
1567: && !requestPath.startsWith("/")) {
1568: buf.append("/");
1569: }
1570: buf.append(requestPath);
1571: }
1572: // Append query, if any
1573: if (query != null) {
1574: if (query.indexOf("?") != 0) {
1575: buf.append("?");
1576: }
1577: buf.append(query);
1578: }
1579: // Append protocol
1580: buf.append(" ");
1581: buf.append(version);
1582: buf.append("\r\n");
1583:
1584: return buf.toString();
1585: }
1586:
1587: /**
1588: * This method is invoked immediately after
1589: * {@link #readResponseBody(HttpState,HttpConnection)} and can be overridden by
1590: * sub-classes in order to provide custom body processing.
1591: *
1592: * <p>
1593: * This implementation does nothing.
1594: * </p>
1595: *
1596: * @param state the {@link HttpState state} information associated with this method
1597: * @param conn the {@link HttpConnection connection} used to execute
1598: * this HTTP method
1599: *
1600: * @see #readResponse
1601: * @see #readResponseBody
1602: */
1603: protected void processResponseBody(HttpState state,
1604: HttpConnection conn) {
1605: }
1606:
1607: /**
1608: * This method is invoked immediately after
1609: * {@link #readResponseHeaders(HttpState,HttpConnection)} and can be overridden by
1610: * sub-classes in order to provide custom response headers processing.
1611:
1612: * <p>
1613: * This implementation will handle the <tt>Set-Cookie</tt> and
1614: * <tt>Set-Cookie2</tt> headers, if any, adding the relevant cookies to
1615: * the given {@link HttpState}.
1616: * </p>
1617: *
1618: * @param state the {@link HttpState state} information associated with this method
1619: * @param conn the {@link HttpConnection connection} used to execute
1620: * this HTTP method
1621: *
1622: * @see #readResponse
1623: * @see #readResponseHeaders
1624: */
1625: protected void processResponseHeaders(HttpState state,
1626: HttpConnection conn) {
1627: LOG
1628: .trace("enter HttpMethodBase.processResponseHeaders(HttpState, "
1629: + "HttpConnection)");
1630:
1631: CookieSpec parser = getCookieSpec(state);
1632:
1633: // process set-cookie headers
1634: Header[] headers = getResponseHeaderGroup().getHeaders(
1635: "set-cookie");
1636: processCookieHeaders(parser, headers, state, conn);
1637:
1638: // see if the cookie spec supports cookie versioning.
1639: if (parser instanceof CookieVersionSupport) {
1640: CookieVersionSupport versupport = (CookieVersionSupport) parser;
1641: if (versupport.getVersion() > 0) {
1642: // process set-cookie2 headers.
1643: // Cookie2 will replace equivalent Cookie instances
1644: headers = getResponseHeaderGroup().getHeaders(
1645: "set-cookie2");
1646: processCookieHeaders(parser, headers, state, conn);
1647: }
1648: }
1649: }
1650:
1651: /**
1652: * This method processes the specified cookie headers. It is invoked from
1653: * within {@link #processResponseHeaders(HttpState,HttpConnection)}
1654: *
1655: * @param headers cookie {@link Header}s to be processed
1656: * @param state the {@link HttpState state} information associated with
1657: * this HTTP method
1658: * @param conn the {@link HttpConnection connection} used to execute
1659: * this HTTP method
1660: */
1661: protected void processCookieHeaders(final CookieSpec parser,
1662: final Header[] headers, final HttpState state,
1663: final HttpConnection conn) {
1664: LOG
1665: .trace("enter HttpMethodBase.processCookieHeaders(Header[], HttpState, "
1666: + "HttpConnection)");
1667:
1668: String host = this .params.getVirtualHost();
1669: if (host == null) {
1670: host = conn.getHost();
1671: }
1672: for (int i = 0; i < headers.length; i++) {
1673: Header header = headers[i];
1674: Cookie[] cookies = null;
1675: try {
1676: cookies = parser.parse(host, conn.getPort(), getPath(),
1677: conn.isSecure(), header);
1678: } catch (MalformedCookieException e) {
1679: if (LOG.isWarnEnabled()) {
1680: LOG.warn("Invalid cookie header: \""
1681: + header.getValue() + "\". "
1682: + e.getMessage());
1683: }
1684: }
1685: if (cookies != null) {
1686: for (int j = 0; j < cookies.length; j++) {
1687: Cookie cookie = cookies[j];
1688: try {
1689: parser.validate(host, conn.getPort(),
1690: getPath(), conn.isSecure(), cookie);
1691: state.addCookie(cookie);
1692: if (LOG.isDebugEnabled()) {
1693: LOG.debug("Cookie accepted: \""
1694: + parser.formatCookie(cookie)
1695: + "\"");
1696: }
1697: } catch (MalformedCookieException e) {
1698: if (LOG.isWarnEnabled()) {
1699: LOG.warn("Cookie rejected: \""
1700: + parser.formatCookie(cookie)
1701: + "\". " + e.getMessage());
1702: }
1703: }
1704: }
1705: }
1706: }
1707: }
1708:
1709: /**
1710: * This method is invoked immediately after
1711: * {@link #readStatusLine(HttpState,HttpConnection)} and can be overridden by
1712: * sub-classes in order to provide custom response status line processing.
1713: *
1714: * @param state the {@link HttpState state} information associated with this method
1715: * @param conn the {@link HttpConnection connection} used to execute
1716: * this HTTP method
1717: *
1718: * @see #readResponse
1719: * @see #readStatusLine
1720: */
1721: protected void processStatusLine(HttpState state,
1722: HttpConnection conn) {
1723: }
1724:
1725: /**
1726: * Reads the response from the given {@link HttpConnection connection}.
1727: *
1728: * <p>
1729: * The response is processed as the following sequence of actions:
1730: *
1731: * <ol>
1732: * <li>
1733: * {@link #readStatusLine(HttpState,HttpConnection)} is
1734: * invoked to read the request line.
1735: * </li>
1736: * <li>
1737: * {@link #processStatusLine(HttpState,HttpConnection)}
1738: * is invoked, allowing the method to process the status line if
1739: * desired.
1740: * </li>
1741: * <li>
1742: * {@link #readResponseHeaders(HttpState,HttpConnection)} is invoked to read
1743: * the associated headers.
1744: * </li>
1745: * <li>
1746: * {@link #processResponseHeaders(HttpState,HttpConnection)} is invoked, allowing
1747: * the method to process the headers if desired.
1748: * </li>
1749: * <li>
1750: * {@link #readResponseBody(HttpState,HttpConnection)} is
1751: * invoked to read the associated body (if any).
1752: * </li>
1753: * <li>
1754: * {@link #processResponseBody(HttpState,HttpConnection)} is invoked, allowing the
1755: * method to process the response body if desired.
1756: * </li>
1757: * </ol>
1758: *
1759: * Subclasses may want to override one or more of the above methods to to
1760: * customize the processing. (Or they may choose to override this method
1761: * if dramatically different processing is required.)
1762: * </p>
1763: *
1764: * @param state the {@link HttpState state} information associated with this method
1765: * @param conn the {@link HttpConnection connection} used to execute
1766: * this HTTP method
1767: *
1768: * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1769: * can be recovered from.
1770: * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
1771: * cannot be recovered from.
1772: */
1773: protected void readResponse(HttpState state, HttpConnection conn)
1774: throws IOException, HttpException {
1775: LOG
1776: .trace("enter HttpMethodBase.readResponse(HttpState, HttpConnection)");
1777: // Status line & line may have already been received
1778: // if 'expect - continue' handshake has been used
1779: while (this .statusLine == null) {
1780: readStatusLine(state, conn);
1781: processStatusLine(state, conn);
1782: readResponseHeaders(state, conn);
1783: processResponseHeaders(state, conn);
1784:
1785: int status = this .statusLine.getStatusCode();
1786: if ((status >= 100) && (status < 200)) {
1787: if (LOG.isInfoEnabled()) {
1788: LOG.info("Discarding unexpected response: "
1789: + this .statusLine.toString());
1790: }
1791: this .statusLine = null;
1792: }
1793: }
1794: readResponseBody(state, conn);
1795: processResponseBody(state, conn);
1796: }
1797:
1798: /**
1799: * Read the response body from the given {@link HttpConnection}.
1800: *
1801: * <p>
1802: * The current implementation wraps the socket level stream with
1803: * an appropriate stream for the type of response (chunked, content-length,
1804: * or auto-close). If there is no response body, the connection associated
1805: * with the request will be returned to the connection manager.
1806: * </p>
1807: *
1808: * <p>
1809: * Subclasses may want to override this method to to customize the
1810: * processing.
1811: * </p>
1812: *
1813: * @param state the {@link HttpState state} information associated with this method
1814: * @param conn the {@link HttpConnection connection} used to execute
1815: * this HTTP method
1816: *
1817: * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1818: * can be recovered from.
1819: * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
1820: * cannot be recovered from.
1821: *
1822: * @see #readResponse
1823: * @see #processResponseBody
1824: */
1825: protected void readResponseBody(HttpState state, HttpConnection conn)
1826: throws IOException, HttpException {
1827: LOG
1828: .trace("enter HttpMethodBase.readResponseBody(HttpState, HttpConnection)");
1829:
1830: // assume we are not done with the connection if we get a stream
1831: InputStream stream = readResponseBody(conn);
1832: if (stream == null) {
1833: // done using the connection!
1834: responseBodyConsumed();
1835: } else {
1836: conn.setLastResponseInputStream(stream);
1837: setResponseStream(stream);
1838: }
1839: }
1840:
1841: /**
1842: * Returns the response body as an {@link InputStream input stream}
1843: * corresponding to the values of the <tt>Content-Length</tt> and
1844: * <tt>Transfer-Encoding</tt> headers. If no response body is available
1845: * returns <tt>null</tt>.
1846: * <p>
1847: *
1848: * @see #readResponse
1849: * @see #processResponseBody
1850: *
1851: * @param conn the {@link HttpConnection connection} used to execute
1852: * this HTTP method
1853: *
1854: * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1855: * can be recovered from.
1856: * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
1857: * cannot be recovered from.
1858: */
1859: private InputStream readResponseBody(HttpConnection conn)
1860: throws HttpException, IOException {
1861:
1862: LOG
1863: .trace("enter HttpMethodBase.readResponseBody(HttpConnection)");
1864:
1865: responseBody = null;
1866: InputStream is = conn.getResponseInputStream();
1867: if (Wire.CONTENT_WIRE.enabled()) {
1868: is = new WireLogInputStream(is, Wire.CONTENT_WIRE);
1869: }
1870: boolean canHaveBody = canResponseHaveBody(statusLine
1871: .getStatusCode());
1872: InputStream result = null;
1873: Header transferEncodingHeader = responseHeaders
1874: .getFirstHeader("Transfer-Encoding");
1875: // We use Transfer-Encoding if present and ignore Content-Length.
1876: // RFC2616, 4.4 item number 3
1877: if (transferEncodingHeader != null) {
1878:
1879: String transferEncoding = transferEncodingHeader.getValue();
1880: if (!"chunked".equalsIgnoreCase(transferEncoding)
1881: && !"identity".equalsIgnoreCase(transferEncoding)) {
1882: if (LOG.isWarnEnabled()) {
1883: LOG.warn("Unsupported transfer encoding: "
1884: + transferEncoding);
1885: }
1886: }
1887: HeaderElement[] encodings = transferEncodingHeader
1888: .getElements();
1889: // The chunked encoding must be the last one applied
1890: // RFC2616, 14.41
1891: int len = encodings.length;
1892: if ((len > 0)
1893: && ("chunked".equalsIgnoreCase(encodings[len - 1]
1894: .getName()))) {
1895: // if response body is empty
1896: if (conn.isResponseAvailable(conn.getParams()
1897: .getSoTimeout())) {
1898: result = new ChunkedInputStream(is, this );
1899: } else {
1900: if (getParams().isParameterTrue(
1901: HttpMethodParams.STRICT_TRANSFER_ENCODING)) {
1902: throw new ProtocolException(
1903: "Chunk-encoded body declared but not sent");
1904: } else {
1905: LOG.warn("Chunk-encoded body missing");
1906: }
1907: }
1908: } else {
1909: LOG.info("Response content is not chunk-encoded");
1910: // The connection must be terminated by closing
1911: // the socket as per RFC 2616, 3.6
1912: setConnectionCloseForced(true);
1913: result = is;
1914: }
1915: } else {
1916: long expectedLength = getResponseContentLength();
1917: if (expectedLength == -1) {
1918: if (canHaveBody
1919: && this .effectiveVersion
1920: .greaterEquals(HttpVersion.HTTP_1_1)) {
1921: Header connectionHeader = responseHeaders
1922: .getFirstHeader("Connection");
1923: String connectionDirective = null;
1924: if (connectionHeader != null) {
1925: connectionDirective = connectionHeader
1926: .getValue();
1927: }
1928: if (!"close".equalsIgnoreCase(connectionDirective)) {
1929: LOG
1930: .info("Response content length is not known");
1931: setConnectionCloseForced(true);
1932: }
1933: }
1934: result = is;
1935: } else {
1936: result = new ContentLengthInputStream(is,
1937: expectedLength);
1938: }
1939: }
1940:
1941: // See if the response is supposed to have a response body
1942: if (!canHaveBody) {
1943: result = null;
1944: }
1945: // if there is a result - ALWAYS wrap it in an observer which will
1946: // close the underlying stream as soon as it is consumed, and notify
1947: // the watcher that the stream has been consumed.
1948: if (result != null) {
1949:
1950: result = new AutoCloseInputStream(result,
1951: new ResponseConsumedWatcher() {
1952: public void responseConsumed() {
1953: responseBodyConsumed();
1954: }
1955: });
1956: }
1957:
1958: return result;
1959: }
1960:
1961: /**
1962: * Reads the response headers from the given {@link HttpConnection connection}.
1963: *
1964: * <p>
1965: * Subclasses may want to override this method to to customize the
1966: * processing.
1967: * </p>
1968: *
1969: * <p>
1970: * "It must be possible to combine the multiple header fields into one
1971: * "field-name: field-value" pair, without changing the semantics of the
1972: * message, by appending each subsequent field-value to the first, each
1973: * separated by a comma." - HTTP/1.0 (4.3)
1974: * </p>
1975: *
1976: * @param state the {@link HttpState state} information associated with this method
1977: * @param conn the {@link HttpConnection connection} used to execute
1978: * this HTTP method
1979: *
1980: * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1981: * can be recovered from.
1982: * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
1983: * cannot be recovered from.
1984: *
1985: * @see #readResponse
1986: * @see #processResponseHeaders
1987: */
1988: protected void readResponseHeaders(HttpState state,
1989: HttpConnection conn) throws IOException, HttpException {
1990: LOG.trace("enter HttpMethodBase.readResponseHeaders(HttpState,"
1991: + "HttpConnection)");
1992:
1993: getResponseHeaderGroup().clear();
1994:
1995: Header[] headers = HttpParser.parseHeaders(conn
1996: .getResponseInputStream(), getParams()
1997: .getHttpElementCharset());
1998: // Wire logging moved to HttpParser
1999: getResponseHeaderGroup().setHeaders(headers);
2000: }
2001:
2002: /**
2003: * Read the status line from the given {@link HttpConnection}, setting my
2004: * {@link #getStatusCode status code} and {@link #getStatusText status
2005: * text}.
2006: *
2007: * <p>
2008: * Subclasses may want to override this method to to customize the
2009: * processing.
2010: * </p>
2011: *
2012: * @param state the {@link HttpState state} information associated with this method
2013: * @param conn the {@link HttpConnection connection} used to execute
2014: * this HTTP method
2015: *
2016: * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
2017: * can be recovered from.
2018: * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
2019: * cannot be recovered from.
2020: *
2021: * @see StatusLine
2022: */
2023: protected void readStatusLine(HttpState state, HttpConnection conn)
2024: throws IOException, HttpException {
2025: LOG
2026: .trace("enter HttpMethodBase.readStatusLine(HttpState, HttpConnection)");
2027:
2028: final int maxGarbageLines = getParams().getIntParameter(
2029: HttpMethodParams.STATUS_LINE_GARBAGE_LIMIT,
2030: Integer.MAX_VALUE);
2031:
2032: //read out the HTTP status string
2033: int count = 0;
2034: String s;
2035: do {
2036: s = conn.readLine(getParams().getHttpElementCharset());
2037: if (s == null && count == 0) {
2038: // The server just dropped connection on us
2039: throw new NoHttpResponseException("The server "
2040: + conn.getHost() + " failed to respond");
2041: }
2042: if (Wire.HEADER_WIRE.enabled()) {
2043: Wire.HEADER_WIRE.input(s + "\r\n");
2044: }
2045: if (s != null && StatusLine.startsWithHTTP(s)) {
2046: // Got one
2047: break;
2048: } else if (s == null || count >= maxGarbageLines) {
2049: // Giving up
2050: throw new ProtocolException(
2051: "The server "
2052: + conn.getHost()
2053: + " failed to respond with a valid HTTP response");
2054: }
2055: count++;
2056: } while (true);
2057:
2058: //create the status line from the status string
2059: statusLine = new StatusLine(s);
2060:
2061: //check for a valid HTTP-Version
2062: String versionStr = statusLine.getHttpVersion();
2063: if (getParams().isParameterFalse(
2064: HttpMethodParams.UNAMBIGUOUS_STATUS_LINE)
2065: && versionStr.equals("HTTP")) {
2066: getParams().setVersion(HttpVersion.HTTP_1_0);
2067: if (LOG.isWarnEnabled()) {
2068: LOG
2069: .warn("Ambiguous status line (HTTP protocol version missing):"
2070: + statusLine.toString());
2071: }
2072: } else {
2073: this .effectiveVersion = HttpVersion.parse(versionStr);
2074: }
2075:
2076: }
2077:
2078: // ------------------------------------------------------ Protected Methods
2079:
2080: /**
2081: * <p>
2082: * Sends the request via the given {@link HttpConnection connection}.
2083: * </p>
2084: *
2085: * <p>
2086: * The request is written as the following sequence of actions:
2087: * </p>
2088: *
2089: * <ol>
2090: * <li>
2091: * {@link #writeRequestLine(HttpState, HttpConnection)} is invoked to
2092: * write the request line.
2093: * </li>
2094: * <li>
2095: * {@link #writeRequestHeaders(HttpState, HttpConnection)} is invoked
2096: * to write the associated headers.
2097: * </li>
2098: * <li>
2099: * <tt>\r\n</tt> is sent to close the head part of the request.
2100: * </li>
2101: * <li>
2102: * {@link #writeRequestBody(HttpState, HttpConnection)} is invoked to
2103: * write the body part of the request.
2104: * </li>
2105: * </ol>
2106: *
2107: * <p>
2108: * Subclasses may want to override one or more of the above methods to to
2109: * customize the processing. (Or they may choose to override this method
2110: * if dramatically different processing is required.)
2111: * </p>
2112: *
2113: * @param state the {@link HttpState state} information associated with this method
2114: * @param conn the {@link HttpConnection connection} used to execute
2115: * this HTTP method
2116: *
2117: * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
2118: * can be recovered from.
2119: * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
2120: * cannot be recovered from.
2121: */
2122: protected void writeRequest(HttpState state, HttpConnection conn)
2123: throws IOException, HttpException {
2124: LOG
2125: .trace("enter HttpMethodBase.writeRequest(HttpState, HttpConnection)");
2126: writeRequestLine(state, conn);
2127: writeRequestHeaders(state, conn);
2128: conn.writeLine(); // close head
2129: if (Wire.HEADER_WIRE.enabled()) {
2130: Wire.HEADER_WIRE.output("\r\n");
2131: }
2132:
2133: HttpVersion ver = getParams().getVersion();
2134: Header expectheader = getRequestHeader("Expect");
2135: String expectvalue = null;
2136: if (expectheader != null) {
2137: expectvalue = expectheader.getValue();
2138: }
2139: if ((expectvalue != null)
2140: && (expectvalue.compareToIgnoreCase("100-continue") == 0)) {
2141: if (ver.greaterEquals(HttpVersion.HTTP_1_1)) {
2142:
2143: // make sure the status line and headers have been sent
2144: conn.flushRequestOutputStream();
2145:
2146: int readTimeout = conn.getParams().getSoTimeout();
2147: try {
2148: conn.setSocketTimeout(RESPONSE_WAIT_TIME_MS);
2149: readStatusLine(state, conn);
2150: processStatusLine(state, conn);
2151: readResponseHeaders(state, conn);
2152: processResponseHeaders(state, conn);
2153:
2154: if (this .statusLine.getStatusCode() == HttpStatus.SC_CONTINUE) {
2155: // Discard status line
2156: this .statusLine = null;
2157: LOG.debug("OK to continue received");
2158: } else {
2159: return;
2160: }
2161: } catch (InterruptedIOException e) {
2162: if (!ExceptionUtil.isSocketTimeoutException(e)) {
2163: throw e;
2164: }
2165: // Most probably Expect header is not recongnized
2166: // Remove the header to signal the method
2167: // that it's okay to go ahead with sending data
2168: removeRequestHeader("Expect");
2169: LOG
2170: .info("100 (continue) read timeout. Resume sending the request");
2171: } finally {
2172: conn.setSocketTimeout(readTimeout);
2173: }
2174:
2175: } else {
2176: removeRequestHeader("Expect");
2177: LOG
2178: .info("'Expect: 100-continue' handshake is only supported by "
2179: + "HTTP/1.1 or higher");
2180: }
2181: }
2182:
2183: writeRequestBody(state, conn);
2184: // make sure the entire request body has been sent
2185: conn.flushRequestOutputStream();
2186: }
2187:
2188: /**
2189: * Writes the request body to the given {@link HttpConnection connection}.
2190: *
2191: * <p>
2192: * This method should return <tt>true</tt> if the request body was actually
2193: * sent (or is empty), or <tt>false</tt> if it could not be sent for some
2194: * reason.
2195: * </p>
2196: *
2197: * <p>
2198: * This implementation writes nothing and returns <tt>true</tt>.
2199: * </p>
2200: *
2201: * @param state the {@link HttpState state} information associated with this method
2202: * @param conn the {@link HttpConnection connection} used to execute
2203: * this HTTP method
2204: *
2205: * @return <tt>true</tt>
2206: *
2207: * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
2208: * can be recovered from.
2209: * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
2210: * cannot be recovered from.
2211: */
2212: protected boolean writeRequestBody(HttpState state,
2213: HttpConnection conn) throws IOException, HttpException {
2214: return true;
2215: }
2216:
2217: /**
2218: * Writes the request headers to the given {@link HttpConnection connection}.
2219: *
2220: * <p>
2221: * This implementation invokes {@link #addRequestHeaders(HttpState,HttpConnection)},
2222: * and then writes each header to the request stream.
2223: * </p>
2224: *
2225: * <p>
2226: * Subclasses may want to override this method to to customize the
2227: * processing.
2228: * </p>
2229: *
2230: * @param state the {@link HttpState state} information associated with this method
2231: * @param conn the {@link HttpConnection connection} used to execute
2232: * this HTTP method
2233: *
2234: * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
2235: * can be recovered from.
2236: * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
2237: * cannot be recovered from.
2238: *
2239: * @see #addRequestHeaders
2240: * @see #getRequestHeaders
2241: */
2242: protected void writeRequestHeaders(HttpState state,
2243: HttpConnection conn) throws IOException, HttpException {
2244: LOG.trace("enter HttpMethodBase.writeRequestHeaders(HttpState,"
2245: + "HttpConnection)");
2246: addRequestHeaders(state, conn);
2247:
2248: String charset = getParams().getHttpElementCharset();
2249:
2250: Header[] headers = getRequestHeaders();
2251: for (int i = 0; i < headers.length; i++) {
2252: String s = headers[i].toExternalForm();
2253: if (Wire.HEADER_WIRE.enabled()) {
2254: Wire.HEADER_WIRE.output(s);
2255: }
2256: conn.print(s, charset);
2257: }
2258: }
2259:
2260: /**
2261: * Writes the request line to the given {@link HttpConnection connection}.
2262: *
2263: * <p>
2264: * Subclasses may want to override this method to to customize the
2265: * processing.
2266: * </p>
2267: *
2268: * @param state the {@link HttpState state} information associated with this method
2269: * @param conn the {@link HttpConnection connection} used to execute
2270: * this HTTP method
2271: *
2272: * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
2273: * can be recovered from.
2274: * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
2275: * cannot be recovered from.
2276: *
2277: * @see #generateRequestLine
2278: */
2279: protected void writeRequestLine(HttpState state, HttpConnection conn)
2280: throws IOException, HttpException {
2281: LOG
2282: .trace("enter HttpMethodBase.writeRequestLine(HttpState, HttpConnection)");
2283: String requestLine = getRequestLine(conn);
2284: if (Wire.HEADER_WIRE.enabled()) {
2285: Wire.HEADER_WIRE.output(requestLine);
2286: }
2287: conn.print(requestLine, getParams().getHttpElementCharset());
2288: }
2289:
2290: /**
2291: * Returns the request line.
2292: *
2293: * @param conn the {@link HttpConnection connection} used to execute
2294: * this HTTP method
2295: *
2296: * @return The request line.
2297: */
2298: private String getRequestLine(HttpConnection conn) {
2299: return HttpMethodBase.generateRequestLine(conn, getName(),
2300: getPath(), getQueryString(), this .effectiveVersion
2301: .toString());
2302: }
2303:
2304: /**
2305: * Returns {@link HttpMethodParams HTTP protocol parameters} associated with this method.
2306: *
2307: * @return HTTP parameters.
2308: *
2309: * @since 3.0
2310: */
2311: public HttpMethodParams getParams() {
2312: return this .params;
2313: }
2314:
2315: /**
2316: * Assigns {@link HttpMethodParams HTTP protocol parameters} for this method.
2317: *
2318: * @since 3.0
2319: *
2320: * @see HttpMethodParams
2321: */
2322: public void setParams(final HttpMethodParams params) {
2323: if (params == null) {
2324: throw new IllegalArgumentException(
2325: "Parameters may not be null");
2326: }
2327: this .params = params;
2328: }
2329:
2330: /**
2331: * Returns the HTTP version used with this method (may be <tt>null</tt>
2332: * if undefined, that is, the method has not been executed)
2333: *
2334: * @return HTTP version.
2335: *
2336: * @since 3.0
2337: */
2338: public HttpVersion getEffectiveVersion() {
2339: return this .effectiveVersion;
2340: }
2341:
2342: /**
2343: * Per RFC 2616 section 4.3, some response can never contain a message
2344: * body.
2345: *
2346: * @param status - the HTTP status code
2347: *
2348: * @return <tt>true</tt> if the message may contain a body, <tt>false</tt> if it can not
2349: * contain a message body
2350: */
2351: private static boolean canResponseHaveBody(int status) {
2352: LOG.trace("enter HttpMethodBase.canResponseHaveBody(int)");
2353:
2354: boolean result = true;
2355:
2356: if ((status >= 100 && status <= 199) || (status == 204)
2357: || (status == 304)) { // NOT MODIFIED
2358: result = false;
2359: }
2360:
2361: return result;
2362: }
2363:
2364: /**
2365: * Returns proxy authentication realm, if it has been used during authentication process.
2366: * Otherwise returns <tt>null</tt>.
2367: *
2368: * @return proxy authentication realm
2369: *
2370: * @deprecated use #getProxyAuthState()
2371: */
2372: public String getProxyAuthenticationRealm() {
2373: return this .proxyAuthState.getRealm();
2374: }
2375:
2376: /**
2377: * Returns authentication realm, if it has been used during authentication process.
2378: * Otherwise returns <tt>null</tt>.
2379: *
2380: * @return authentication realm
2381: *
2382: * @deprecated use #getHostAuthState()
2383: */
2384: public String getAuthenticationRealm() {
2385: return this .hostAuthState.getRealm();
2386: }
2387:
2388: /**
2389: * Returns the character set from the <tt>Content-Type</tt> header.
2390: *
2391: * @param contentheader The content header.
2392: * @return String The character set.
2393: */
2394: protected String getContentCharSet(Header contentheader) {
2395: LOG.trace("enter getContentCharSet( Header contentheader )");
2396: String charset = null;
2397: if (contentheader != null) {
2398: HeaderElement values[] = contentheader.getElements();
2399: // I expect only one header element to be there
2400: // No more. no less
2401: if (values.length == 1) {
2402: NameValuePair param = values[0]
2403: .getParameterByName("charset");
2404: if (param != null) {
2405: // If I get anything "funny"
2406: // UnsupportedEncondingException will result
2407: charset = param.getValue();
2408: }
2409: }
2410: }
2411: if (charset == null) {
2412: charset = getParams().getContentCharset();
2413: if (LOG.isDebugEnabled()) {
2414: LOG.debug("Default charset used: " + charset);
2415: }
2416: }
2417: return charset;
2418: }
2419:
2420: /**
2421: * Returns the character encoding of the request from the <tt>Content-Type</tt> header.
2422: *
2423: * @return String The character set.
2424: */
2425: public String getRequestCharSet() {
2426: return getContentCharSet(getRequestHeader("Content-Type"));
2427: }
2428:
2429: /**
2430: * Returns the character encoding of the response from the <tt>Content-Type</tt> header.
2431: *
2432: * @return String The character set.
2433: */
2434: public String getResponseCharSet() {
2435: return getContentCharSet(getResponseHeader("Content-Type"));
2436: }
2437:
2438: /**
2439: * @deprecated no longer used
2440: *
2441: * Returns the number of "recoverable" exceptions thrown and handled, to
2442: * allow for monitoring the quality of the connection.
2443: *
2444: * @return The number of recoverable exceptions handled by the method.
2445: */
2446: public int getRecoverableExceptionCount() {
2447: return recoverableExceptionCount;
2448: }
2449:
2450: /**
2451: * A response has been consumed.
2452: *
2453: * <p>The default behavior for this class is to check to see if the connection
2454: * should be closed, and close if need be, and to ensure that the connection
2455: * is returned to the connection manager - if and only if we are not still
2456: * inside the execute call.</p>
2457: *
2458: */
2459: protected void responseBodyConsumed() {
2460:
2461: // make sure this is the initial invocation of the notification,
2462: // ignore subsequent ones.
2463: responseStream = null;
2464: if (responseConnection != null) {
2465: responseConnection.setLastResponseInputStream(null);
2466:
2467: // At this point, no response data should be available.
2468: // If there is data available, regard the connection as being
2469: // unreliable and close it.
2470:
2471: if (shouldCloseConnection(responseConnection)) {
2472: responseConnection.close();
2473: } else {
2474: try {
2475: if (responseConnection.isResponseAvailable()) {
2476: boolean logExtraInput = getParams()
2477: .isParameterTrue(
2478: HttpMethodParams.WARN_EXTRA_INPUT);
2479:
2480: if (logExtraInput) {
2481: LOG
2482: .warn("Extra response data detected - closing connection");
2483: }
2484: responseConnection.close();
2485: }
2486: } catch (IOException e) {
2487: LOG.warn(e.getMessage());
2488: responseConnection.close();
2489: }
2490: }
2491: }
2492: this .connectionCloseForced = false;
2493: ensureConnectionRelease();
2494: }
2495:
2496: /**
2497: * Insure that the connection is released back to the pool.
2498: */
2499: private void ensureConnectionRelease() {
2500: if (responseConnection != null) {
2501: responseConnection.releaseConnection();
2502: responseConnection = null;
2503: }
2504: }
2505:
2506: /**
2507: * Returns the {@link HostConfiguration host configuration}.
2508: *
2509: * @return the host configuration
2510: *
2511: * @deprecated no longer applicable
2512: */
2513: public HostConfiguration getHostConfiguration() {
2514: HostConfiguration hostconfig = new HostConfiguration();
2515: hostconfig.setHost(this .httphost);
2516: return hostconfig;
2517: }
2518:
2519: /**
2520: * Sets the {@link HostConfiguration host configuration}.
2521: *
2522: * @param hostconfig The hostConfiguration to set
2523: *
2524: * @deprecated no longer applicable
2525: */
2526: public void setHostConfiguration(final HostConfiguration hostconfig) {
2527: if (hostconfig != null) {
2528: this .httphost = new HttpHost(hostconfig.getHost(),
2529: hostconfig.getPort(), hostconfig.getProtocol());
2530: } else {
2531: this .httphost = null;
2532: }
2533: }
2534:
2535: /**
2536: * Returns the {@link MethodRetryHandler retry handler} for this HTTP method
2537: *
2538: * @return the methodRetryHandler
2539: *
2540: * @deprecated use {@link HttpMethodParams}
2541: */
2542: public MethodRetryHandler getMethodRetryHandler() {
2543: return methodRetryHandler;
2544: }
2545:
2546: /**
2547: * Sets the {@link MethodRetryHandler retry handler} for this HTTP method
2548: *
2549: * @param handler the methodRetryHandler to use when this method executed
2550: *
2551: * @deprecated use {@link HttpMethodParams}
2552: */
2553: public void setMethodRetryHandler(MethodRetryHandler handler) {
2554: methodRetryHandler = handler;
2555: }
2556:
2557: /**
2558: * This method is a dirty hack intended to work around
2559: * current (2.0) design flaw that prevents the user from
2560: * obtaining correct status code, headers and response body from the
2561: * preceding HTTP CONNECT method.
2562: *
2563: * TODO: Remove this crap as soon as possible
2564: */
2565: void fakeResponse(StatusLine statusline,
2566: HeaderGroup responseheaders, InputStream responseStream) {
2567: // set used so that the response can be read
2568: this .used = true;
2569: this .statusLine = statusline;
2570: this .responseHeaders = responseheaders;
2571: this .responseBody = null;
2572: this .responseStream = responseStream;
2573: }
2574:
2575: /**
2576: * Returns the target host {@link AuthState authentication state}
2577: *
2578: * @return host authentication state
2579: *
2580: * @since 3.0
2581: */
2582: public AuthState getHostAuthState() {
2583: return this .hostAuthState;
2584: }
2585:
2586: /**
2587: * Returns the proxy {@link AuthState authentication state}
2588: *
2589: * @return host authentication state
2590: *
2591: * @since 3.0
2592: */
2593: public AuthState getProxyAuthState() {
2594: return this .proxyAuthState;
2595: }
2596:
2597: /**
2598: * Tests whether the execution of this method has been aborted
2599: *
2600: * @return <tt>true</tt> if the execution of this method has been aborted,
2601: * <tt>false</tt> otherwise
2602: *
2603: * @since 3.0
2604: */
2605: public boolean isAborted() {
2606: return this .aborted;
2607: }
2608:
2609: /**
2610: * Returns <tt>true</tt> if the HTTP has been transmitted to the target
2611: * server in its entirety, <tt>false</tt> otherwise. This flag can be useful
2612: * for recovery logic. If the request has not been transmitted in its entirety,
2613: * it is safe to retry the failed method.
2614: *
2615: * @return <tt>true</tt> if the request has been sent, <tt>false</tt> otherwise
2616: */
2617: public boolean isRequestSent() {
2618: return this.requestSent;
2619: }
2620:
2621: }
|