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