0001: /*
0002: * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/HttpConnection.java,v 1.107 2005/01/14 21:30:59 olegk Exp $
0003: * $Revision: 480424 $
0004: * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
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.BufferedInputStream;
0034: import java.io.BufferedOutputStream;
0035: import java.io.IOException;
0036: import java.io.InputStream;
0037: import java.io.InterruptedIOException;
0038: import java.io.OutputStream;
0039: import java.lang.reflect.Method;
0040: import java.net.InetAddress;
0041: import java.net.Socket;
0042: import java.net.SocketException;
0043:
0044: import org.apache.commons.httpclient.params.HttpConnectionParams;
0045: import org.apache.commons.httpclient.protocol.Protocol;
0046: import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
0047: import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
0048: import org.apache.commons.httpclient.util.EncodingUtil;
0049: import org.apache.commons.httpclient.util.ExceptionUtil;
0050: import org.apache.commons.logging.Log;
0051: import org.apache.commons.logging.LogFactory;
0052:
0053: /**
0054: * An abstraction of an HTTP {@link InputStream} and {@link OutputStream}
0055: * pair, together with the relevant attributes.
0056: * <p>
0057: * The following options are set on the socket before getting the input/output
0058: * streams in the {@link #open()} method:
0059: * <table border=1><tr>
0060: * <th>Socket Method
0061: * <th>Sockets Option
0062: * <th>Configuration
0063: * </tr><tr>
0064: * <td>{@link java.net.Socket#setTcpNoDelay(boolean)}
0065: * <td>SO_NODELAY
0066: * <td>{@link HttpConnectionParams#setTcpNoDelay(boolean)}
0067: * </tr><tr>
0068: * <td>{@link java.net.Socket#setSoTimeout(int)}
0069: * <td>SO_TIMEOUT
0070: * <td>{@link HttpConnectionParams#setSoTimeout(int)}
0071: * </tr><tr>
0072: * <td>{@link java.net.Socket#setSendBufferSize(int)}
0073: * <td>SO_SNDBUF
0074: * <td>{@link HttpConnectionParams#setSendBufferSize(int)}
0075: * </tr><tr>
0076: * <td>{@link java.net.Socket#setReceiveBufferSize(int)}
0077: * <td>SO_RCVBUF
0078: * <td>{@link HttpConnectionParams#setReceiveBufferSize(int)}
0079: * </tr></table>
0080: *
0081: * @author Rod Waldhoff
0082: * @author Sean C. Sullivan
0083: * @author Ortwin Glueck
0084: * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
0085: * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
0086: * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
0087: * @author Michael Becke
0088: * @author Eric E Johnson
0089: * @author Laura Werner
0090: *
0091: * @version $Revision: 480424 $ $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
0092: */
0093: public class HttpConnection {
0094:
0095: // ----------------------------------------------------------- Constructors
0096:
0097: /**
0098: * Creates a new HTTP connection for the given host and port.
0099: *
0100: * @param host the host to connect to
0101: * @param port the port to connect to
0102: */
0103: public HttpConnection(String host, int port) {
0104: this (null, -1, host, null, port, Protocol.getProtocol("http"));
0105: }
0106:
0107: /**
0108: * Creates a new HTTP connection for the given host and port
0109: * using the given protocol.
0110: *
0111: * @param host the host to connect to
0112: * @param port the port to connect to
0113: * @param protocol the protocol to use
0114: */
0115: public HttpConnection(String host, int port, Protocol protocol) {
0116: this (null, -1, host, null, port, protocol);
0117: }
0118:
0119: /**
0120: * Creates a new HTTP connection for the given host with the virtual
0121: * alias and port using given protocol.
0122: *
0123: * @param host the host to connect to
0124: * @param virtualHost the virtual host requests will be sent to
0125: * @param port the port to connect to
0126: * @param protocol the protocol to use
0127: */
0128: public HttpConnection(String host, String virtualHost, int port,
0129: Protocol protocol) {
0130: this (null, -1, host, virtualHost, port, protocol);
0131: }
0132:
0133: /**
0134: * Creates a new HTTP connection for the given host and port via the
0135: * given proxy host and port using the default protocol.
0136: *
0137: * @param proxyHost the host to proxy via
0138: * @param proxyPort the port to proxy via
0139: * @param host the host to connect to
0140: * @param port the port to connect to
0141: */
0142: public HttpConnection(String proxyHost, int proxyPort, String host,
0143: int port) {
0144: this (proxyHost, proxyPort, host, null, port, Protocol
0145: .getProtocol("http"));
0146: }
0147:
0148: /**
0149: * Creates a new HTTP connection for the given host configuration.
0150: *
0151: * @param hostConfiguration the host/proxy/protocol to use
0152: */
0153: public HttpConnection(HostConfiguration hostConfiguration) {
0154: this (hostConfiguration.getProxyHost(), hostConfiguration
0155: .getProxyPort(), hostConfiguration.getHost(),
0156: hostConfiguration.getPort(), hostConfiguration
0157: .getProtocol());
0158: this .localAddress = hostConfiguration.getLocalAddress();
0159: }
0160:
0161: /**
0162: * Creates a new HTTP connection for the given host with the virtual
0163: * alias and port via the given proxy host and port using the given
0164: * protocol.
0165: *
0166: * @param proxyHost the host to proxy via
0167: * @param proxyPort the port to proxy via
0168: * @param host the host to connect to. Parameter value must be non-null.
0169: * @param virtualHost No longer applicable.
0170: * @param port the port to connect to
0171: * @param protocol The protocol to use. Parameter value must be non-null.
0172: *
0173: * @deprecated use #HttpConnection(String, int, String, int, Protocol)
0174: */
0175: public HttpConnection(String proxyHost, int proxyPort, String host,
0176: String virtualHost, int port, Protocol protocol) {
0177: this (proxyHost, proxyPort, host, port, protocol);
0178: }
0179:
0180: /**
0181: * Creates a new HTTP connection for the given host with the virtual
0182: * alias and port via the given proxy host and port using the given
0183: * protocol.
0184: *
0185: * @param proxyHost the host to proxy via
0186: * @param proxyPort the port to proxy via
0187: * @param host the host to connect to. Parameter value must be non-null.
0188: * @param port the port to connect to
0189: * @param protocol The protocol to use. Parameter value must be non-null.
0190: */
0191: public HttpConnection(String proxyHost, int proxyPort, String host,
0192: int port, Protocol protocol) {
0193:
0194: if (host == null) {
0195: throw new IllegalArgumentException("host parameter is null");
0196: }
0197: if (protocol == null) {
0198: throw new IllegalArgumentException("protocol is null");
0199: }
0200:
0201: proxyHostName = proxyHost;
0202: proxyPortNumber = proxyPort;
0203: hostName = host;
0204: portNumber = protocol.resolvePort(port);
0205: protocolInUse = protocol;
0206: }
0207:
0208: // ------------------------------------------ Attribute Setters and Getters
0209:
0210: /**
0211: * Returns the connection socket.
0212: *
0213: * @return the socket.
0214: *
0215: * @since 3.0
0216: */
0217: protected Socket getSocket() {
0218: return this .socket;
0219: }
0220:
0221: /**
0222: * Returns the host.
0223: *
0224: * @return the host.
0225: */
0226: public String getHost() {
0227: return hostName;
0228: }
0229:
0230: /**
0231: * Sets the host to connect to.
0232: *
0233: * @param host the host to connect to. Parameter value must be non-null.
0234: * @throws IllegalStateException if the connection is already open
0235: */
0236: public void setHost(String host) throws IllegalStateException {
0237: if (host == null) {
0238: throw new IllegalArgumentException("host parameter is null");
0239: }
0240: assertNotOpen();
0241: hostName = host;
0242: }
0243:
0244: /**
0245: * Returns the target virtual host.
0246: *
0247: * @return the virtual host.
0248: *
0249: * @deprecated no longer applicable
0250: */
0251:
0252: public String getVirtualHost() {
0253: return this .hostName;
0254: }
0255:
0256: /**
0257: * Sets the virtual host to target.
0258: *
0259: * @param host the virtual host name that should be used instead of
0260: * physical host name when sending HTTP requests. Virtual host
0261: * name can be set to <tt> null</tt> if virtual host name is not
0262: * to be used
0263: *
0264: * @throws IllegalStateException if the connection is already open
0265: *
0266: * @deprecated no longer applicable
0267: */
0268:
0269: public void setVirtualHost(String host)
0270: throws IllegalStateException {
0271: assertNotOpen();
0272: }
0273:
0274: /**
0275: * Returns the port of the host.
0276: *
0277: * If the port is -1 (or less than 0) the default port for
0278: * the current protocol is returned.
0279: *
0280: * @return the port.
0281: */
0282: public int getPort() {
0283: if (portNumber < 0) {
0284: return isSecure() ? 443 : 80;
0285: } else {
0286: return portNumber;
0287: }
0288: }
0289:
0290: /**
0291: * Sets the port to connect to.
0292: *
0293: * @param port the port to connect to
0294: *
0295: * @throws IllegalStateException if the connection is already open
0296: */
0297: public void setPort(int port) throws IllegalStateException {
0298: assertNotOpen();
0299: portNumber = port;
0300: }
0301:
0302: /**
0303: * Returns the proxy host.
0304: *
0305: * @return the proxy host.
0306: */
0307: public String getProxyHost() {
0308: return proxyHostName;
0309: }
0310:
0311: /**
0312: * Sets the host to proxy through.
0313: *
0314: * @param host the host to proxy through.
0315: *
0316: * @throws IllegalStateException if the connection is already open
0317: */
0318: public void setProxyHost(String host) throws IllegalStateException {
0319: assertNotOpen();
0320: proxyHostName = host;
0321: }
0322:
0323: /**
0324: * Returns the port of the proxy host.
0325: *
0326: * @return the proxy port.
0327: */
0328: public int getProxyPort() {
0329: return proxyPortNumber;
0330: }
0331:
0332: /**
0333: * Sets the port of the host to proxy through.
0334: *
0335: * @param port the port of the host to proxy through.
0336: *
0337: * @throws IllegalStateException if the connection is already open
0338: */
0339: public void setProxyPort(int port) throws IllegalStateException {
0340: assertNotOpen();
0341: proxyPortNumber = port;
0342: }
0343:
0344: /**
0345: * Returns <tt>true</tt> if the connection is established over
0346: * a secure protocol.
0347: *
0348: * @return <tt>true</tt> if connected over a secure protocol.
0349: */
0350: public boolean isSecure() {
0351: return protocolInUse.isSecure();
0352: }
0353:
0354: /**
0355: * Returns the protocol used to establish the connection.
0356: * @return The protocol
0357: */
0358: public Protocol getProtocol() {
0359: return protocolInUse;
0360: }
0361:
0362: /**
0363: * Sets the protocol used to establish the connection
0364: *
0365: * @param protocol The protocol to use.
0366: *
0367: * @throws IllegalStateException if the connection is already open
0368: */
0369: public void setProtocol(Protocol protocol) {
0370: assertNotOpen();
0371:
0372: if (protocol == null) {
0373: throw new IllegalArgumentException("protocol is null");
0374: }
0375:
0376: protocolInUse = protocol;
0377:
0378: }
0379:
0380: /**
0381: * Return the local address used when creating the connection.
0382: * If <tt>null</tt>, the default address is used.
0383: *
0384: * @return InetAddress the local address to be used when creating Sockets
0385: */
0386: public InetAddress getLocalAddress() {
0387: return this .localAddress;
0388: }
0389:
0390: /**
0391: * Set the local address used when creating the connection.
0392: * If unset or <tt>null</tt>, the default address is used.
0393: *
0394: * @param localAddress the local address to use
0395: */
0396: public void setLocalAddress(InetAddress localAddress) {
0397: assertNotOpen();
0398: this .localAddress = localAddress;
0399: }
0400:
0401: /**
0402: * Tests if the connection is open.
0403: *
0404: * @return <code>true</code> if the connection is open
0405: */
0406: public boolean isOpen() {
0407: return isOpen;
0408: }
0409:
0410: /**
0411: * Closes the connection if stale.
0412: *
0413: * @return <code>true</code> if the connection was stale and therefore closed,
0414: * <code>false</code> otherwise.
0415: *
0416: * @see #isStale()
0417: *
0418: * @since 3.0
0419: */
0420: public boolean closeIfStale() throws IOException {
0421: if (isOpen && isStale()) {
0422: LOG.debug("Connection is stale, closing...");
0423: close();
0424: return true;
0425: }
0426: return false;
0427: }
0428:
0429: /**
0430: * Tests if stale checking is enabled.
0431: *
0432: * @return <code>true</code> if enabled
0433: *
0434: * @see #isStale()
0435: *
0436: * @deprecated Use {@link HttpConnectionParams#isStaleCheckingEnabled()},
0437: * {@link HttpConnection#getParams()}.
0438: */
0439: public boolean isStaleCheckingEnabled() {
0440: return this .params.isStaleCheckingEnabled();
0441: }
0442:
0443: /**
0444: * Sets whether or not isStale() will be called when testing if this connection is open.
0445: *
0446: * <p>Setting this flag to <code>false</code> will increase performance when reusing
0447: * connections, but it will also make them less reliable. Stale checking ensures that
0448: * connections are viable before they are used. When set to <code>false</code> some
0449: * method executions will result in IOExceptions and they will have to be retried.</p>
0450: *
0451: * @param staleCheckEnabled <code>true</code> to enable isStale()
0452: *
0453: * @see #isStale()
0454: * @see #isOpen()
0455: *
0456: * @deprecated Use {@link HttpConnectionParams#setStaleCheckingEnabled(boolean)},
0457: * {@link HttpConnection#getParams()}.
0458: */
0459: public void setStaleCheckingEnabled(boolean staleCheckEnabled) {
0460: this .params.setStaleCheckingEnabled(staleCheckEnabled);
0461: }
0462:
0463: /**
0464: * Determines whether this connection is "stale", which is to say that either
0465: * it is no longer open, or an attempt to read the connection would fail.
0466: *
0467: * <p>Unfortunately, due to the limitations of the JREs prior to 1.4, it is
0468: * not possible to test a connection to see if both the read and write channels
0469: * are open - except by reading and writing. This leads to a difficulty when
0470: * some connections leave the "write" channel open, but close the read channel
0471: * and ignore the request. This function attempts to ameliorate that
0472: * problem by doing a test read, assuming that the caller will be doing a
0473: * write followed by a read, rather than the other way around.
0474: * </p>
0475: *
0476: * <p>To avoid side-effects, the underlying connection is wrapped by a
0477: * {@link BufferedInputStream}, so although data might be read, what is visible
0478: * to clients of the connection will not change with this call.</p.
0479: *
0480: * @throws IOException if the stale connection test is interrupted.
0481: *
0482: * @return <tt>true</tt> if the connection is already closed, or a read would
0483: * fail.
0484: */
0485: protected boolean isStale() throws IOException {
0486: boolean isStale = true;
0487: if (isOpen) {
0488: // the connection is open, but now we have to see if we can read it
0489: // assume the connection is not stale.
0490: isStale = false;
0491: try {
0492: if (inputStream.available() <= 0) {
0493: try {
0494: socket.setSoTimeout(1);
0495: inputStream.mark(1);
0496: int byteRead = inputStream.read();
0497: if (byteRead == -1) {
0498: // again - if the socket is reporting all data read,
0499: // probably stale
0500: isStale = true;
0501: } else {
0502: inputStream.reset();
0503: }
0504: } finally {
0505: socket.setSoTimeout(this .params.getSoTimeout());
0506: }
0507: }
0508: } catch (InterruptedIOException e) {
0509: if (!ExceptionUtil.isSocketTimeoutException(e)) {
0510: throw e;
0511: }
0512: // aha - the connection is NOT stale - continue on!
0513: } catch (IOException e) {
0514: // oops - the connection is stale, the read or soTimeout failed.
0515: LOG
0516: .debug(
0517: "An error occurred while reading from the socket, is appears to be stale",
0518: e);
0519: isStale = true;
0520: }
0521: }
0522:
0523: return isStale;
0524: }
0525:
0526: /**
0527: * Returns <tt>true</tt> if the connection is established via a proxy,
0528: * <tt>false</tt> otherwise.
0529: *
0530: * @return <tt>true</tt> if a proxy is used to establish the connection,
0531: * <tt>false</tt> otherwise.
0532: */
0533: public boolean isProxied() {
0534: return (!(null == proxyHostName || 0 >= proxyPortNumber));
0535: }
0536:
0537: /**
0538: * Set the state to keep track of the last response for the last request.
0539: *
0540: * <p>The connection managers use this to ensure that previous requests are
0541: * properly closed before a new request is attempted. That way, a GET
0542: * request need not be read in its entirety before a new request is issued.
0543: * Instead, this stream can be closed as appropriate.</p>
0544: *
0545: * @param inStream The stream associated with an HttpMethod.
0546: */
0547: public void setLastResponseInputStream(InputStream inStream) {
0548: lastResponseInputStream = inStream;
0549: }
0550:
0551: /**
0552: * Returns the stream used to read the last response's body.
0553: *
0554: * <p>Clients will generally not need to call this function unless
0555: * using HttpConnection directly, instead of calling {@link HttpClient#executeMethod}.
0556: * For those clients, call this function, and if it returns a non-null stream,
0557: * close the stream before attempting to execute a method. Note that
0558: * calling "close" on the stream returned by this function <i>may</i> close
0559: * the connection if the previous response contained a "Connection: close" header. </p>
0560: *
0561: * @return An {@link InputStream} corresponding to the body of the last
0562: * response.
0563: */
0564: public InputStream getLastResponseInputStream() {
0565: return lastResponseInputStream;
0566: }
0567:
0568: // --------------------------------------------------- Other Public Methods
0569:
0570: /**
0571: * Returns {@link HttpConnectionParams HTTP protocol parameters} associated with this method.
0572: *
0573: * @return HTTP parameters.
0574: *
0575: * @since 3.0
0576: */
0577: public HttpConnectionParams getParams() {
0578: return this .params;
0579: }
0580:
0581: /**
0582: * Assigns {@link HttpConnectionParams HTTP protocol parameters} for this method.
0583: *
0584: * @since 3.0
0585: *
0586: * @see HttpConnectionParams
0587: */
0588: public void setParams(final HttpConnectionParams params) {
0589: if (params == null) {
0590: throw new IllegalArgumentException(
0591: "Parameters may not be null");
0592: }
0593: this .params = params;
0594: }
0595:
0596: /**
0597: * Set the {@link Socket}'s timeout, via {@link Socket#setSoTimeout}. If the
0598: * connection is already open, the SO_TIMEOUT is changed. If no connection
0599: * is open, then subsequent connections will use the timeout value.
0600: * <p>
0601: * Note: This is not a connection timeout but a timeout on network traffic!
0602: *
0603: * @param timeout the timeout value
0604: * @throws SocketException - if there is an error in the underlying
0605: * protocol, such as a TCP error.
0606: *
0607: * @deprecated Use {@link HttpConnectionParams#setSoTimeout(int)},
0608: * {@link HttpConnection#getParams()}.
0609: */
0610: public void setSoTimeout(int timeout) throws SocketException,
0611: IllegalStateException {
0612: this .params.setSoTimeout(timeout);
0613: if (this .socket != null) {
0614: this .socket.setSoTimeout(timeout);
0615: }
0616: }
0617:
0618: /**
0619: * Sets <code>SO_TIMEOUT</code> value directly on the underlying {@link Socket socket}.
0620: * This method does not change the default read timeout value set via
0621: * {@link HttpConnectionParams}.
0622: *
0623: * @param timeout the timeout value
0624: * @throws SocketException - if there is an error in the underlying
0625: * protocol, such as a TCP error.
0626: * @throws IllegalStateException if not connected
0627: *
0628: * @since 3.0
0629: */
0630: public void setSocketTimeout(int timeout) throws SocketException,
0631: IllegalStateException {
0632: assertOpen();
0633: if (this .socket != null) {
0634: this .socket.setSoTimeout(timeout);
0635: }
0636: }
0637:
0638: /**
0639: * Returns the {@link Socket}'s timeout, via {@link Socket#getSoTimeout}, if the
0640: * connection is already open. If no connection is open, return the value subsequent
0641: * connection will use.
0642: * <p>
0643: * Note: This is not a connection timeout but a timeout on network traffic!
0644: *
0645: * @return the timeout value
0646: *
0647: * @deprecated Use {@link HttpConnectionParams#getSoTimeout()},
0648: * {@link HttpConnection#getParams()}.
0649: */
0650: public int getSoTimeout() throws SocketException {
0651: return this .params.getSoTimeout();
0652: }
0653:
0654: /**
0655: * Sets the connection timeout. This is the maximum time that may be spent
0656: * until a connection is established. The connection will fail after this
0657: * amount of time.
0658: * @param timeout The timeout in milliseconds. 0 means timeout is not used.
0659: *
0660: * @deprecated Use {@link HttpConnectionParams#setConnectionTimeout(int)},
0661: * {@link HttpConnection#getParams()}.
0662: */
0663: public void setConnectionTimeout(int timeout) {
0664: this .params.setConnectionTimeout(timeout);
0665: }
0666:
0667: /**
0668: * Establishes a connection to the specified host and port
0669: * (via a proxy if specified).
0670: * The underlying socket is created from the {@link ProtocolSocketFactory}.
0671: *
0672: * @throws IOException if an attempt to establish the connection results in an
0673: * I/O error.
0674: */
0675: public void open() throws IOException {
0676: LOG.trace("enter HttpConnection.open()");
0677:
0678: final String host = (proxyHostName == null) ? hostName
0679: : proxyHostName;
0680: final int port = (proxyHostName == null) ? portNumber
0681: : proxyPortNumber;
0682: assertNotOpen();
0683:
0684: if (LOG.isDebugEnabled()) {
0685: LOG.debug("Open connection to " + host + ":" + port);
0686: }
0687:
0688: try {
0689: if (this .socket == null) {
0690: usingSecureSocket = isSecure() && !isProxied();
0691: // use the protocol's socket factory unless this is a secure
0692: // proxied connection
0693: ProtocolSocketFactory socketFactory = null;
0694: if (isSecure() && isProxied()) {
0695: Protocol defaultprotocol = Protocol
0696: .getProtocol("http");
0697: socketFactory = defaultprotocol.getSocketFactory();
0698: } else {
0699: socketFactory = this .protocolInUse
0700: .getSocketFactory();
0701: }
0702: this .socket = socketFactory.createSocket(host, port,
0703: localAddress, 0, this .params);
0704: }
0705:
0706: /*
0707: "Nagling has been broadly implemented across networks,
0708: including the Internet, and is generally performed by default
0709: - although it is sometimes considered to be undesirable in
0710: highly interactive environments, such as some client/server
0711: situations. In such cases, nagling may be turned off through
0712: use of the TCP_NODELAY sockets option." */
0713:
0714: socket.setTcpNoDelay(this .params.getTcpNoDelay());
0715: socket.setSoTimeout(this .params.getSoTimeout());
0716:
0717: int linger = this .params.getLinger();
0718: if (linger >= 0) {
0719: socket.setSoLinger(linger > 0, linger);
0720: }
0721:
0722: int sndBufSize = this .params.getSendBufferSize();
0723: if (sndBufSize >= 0) {
0724: socket.setSendBufferSize(sndBufSize);
0725: }
0726: int rcvBufSize = this .params.getReceiveBufferSize();
0727: if (rcvBufSize >= 0) {
0728: socket.setReceiveBufferSize(rcvBufSize);
0729: }
0730: int outbuffersize = socket.getSendBufferSize();
0731: if ((outbuffersize > 2048) || (outbuffersize <= 0)) {
0732: outbuffersize = 2048;
0733: }
0734: int inbuffersize = socket.getReceiveBufferSize();
0735: if ((inbuffersize > 2048) || (inbuffersize <= 0)) {
0736: inbuffersize = 2048;
0737: }
0738: inputStream = new BufferedInputStream(socket
0739: .getInputStream(), inbuffersize);
0740: outputStream = new BufferedOutputStream(socket
0741: .getOutputStream(), outbuffersize);
0742: isOpen = true;
0743: } catch (IOException e) {
0744: // Connection wasn't opened properly
0745: // so close everything out
0746: closeSocketAndStreams();
0747: throw e;
0748: }
0749: }
0750:
0751: /**
0752: * Instructs the proxy to establish a secure tunnel to the host. The socket will
0753: * be switched to the secure socket. Subsequent communication is done via the secure
0754: * socket. The method can only be called once on a proxied secure connection.
0755: *
0756: * @throws IllegalStateException if connection is not secure and proxied or
0757: * if the socket is already secure.
0758: * @throws IOException if an attempt to establish the secure tunnel results in an
0759: * I/O error.
0760: */
0761: public void tunnelCreated() throws IllegalStateException,
0762: IOException {
0763: LOG.trace("enter HttpConnection.tunnelCreated()");
0764:
0765: if (!isSecure() || !isProxied()) {
0766: throw new IllegalStateException(
0767: "Connection must be secure "
0768: + "and proxied to use this feature");
0769: }
0770:
0771: if (usingSecureSocket) {
0772: throw new IllegalStateException(
0773: "Already using a secure socket");
0774: }
0775:
0776: if (LOG.isDebugEnabled()) {
0777: LOG.debug("Secure tunnel to " + this .hostName + ":"
0778: + this .portNumber);
0779: }
0780:
0781: SecureProtocolSocketFactory socketFactory = (SecureProtocolSocketFactory) protocolInUse
0782: .getSocketFactory();
0783:
0784: socket = socketFactory.createSocket(socket, hostName,
0785: portNumber, true);
0786: int sndBufSize = this .params.getSendBufferSize();
0787: if (sndBufSize >= 0) {
0788: socket.setSendBufferSize(sndBufSize);
0789: }
0790: int rcvBufSize = this .params.getReceiveBufferSize();
0791: if (rcvBufSize >= 0) {
0792: socket.setReceiveBufferSize(rcvBufSize);
0793: }
0794: int outbuffersize = socket.getSendBufferSize();
0795: if (outbuffersize > 2048) {
0796: outbuffersize = 2048;
0797: }
0798: int inbuffersize = socket.getReceiveBufferSize();
0799: if (inbuffersize > 2048) {
0800: inbuffersize = 2048;
0801: }
0802: inputStream = new BufferedInputStream(socket.getInputStream(),
0803: inbuffersize);
0804: outputStream = new BufferedOutputStream(socket
0805: .getOutputStream(), outbuffersize);
0806: usingSecureSocket = true;
0807: tunnelEstablished = true;
0808: }
0809:
0810: /**
0811: * Indicates if the connection is completely transparent from end to end.
0812: *
0813: * @return true if conncetion is not proxied or tunneled through a transparent
0814: * proxy; false otherwise.
0815: */
0816: public boolean isTransparent() {
0817: return !isProxied() || tunnelEstablished;
0818: }
0819:
0820: /**
0821: * Flushes the output request stream. This method should be called to
0822: * ensure that data written to the request OutputStream is sent to the server.
0823: *
0824: * @throws IOException if an I/O problem occurs
0825: */
0826: public void flushRequestOutputStream() throws IOException {
0827: LOG.trace("enter HttpConnection.flushRequestOutputStream()");
0828: assertOpen();
0829: outputStream.flush();
0830: }
0831:
0832: /**
0833: * Returns an {@link OutputStream} suitable for writing the request.
0834: *
0835: * @throws IllegalStateException if the connection is not open
0836: * @throws IOException if an I/O problem occurs
0837: * @return a stream to write the request to
0838: */
0839: public OutputStream getRequestOutputStream() throws IOException,
0840: IllegalStateException {
0841: LOG.trace("enter HttpConnection.getRequestOutputStream()");
0842: assertOpen();
0843: OutputStream out = this .outputStream;
0844: if (Wire.CONTENT_WIRE.enabled()) {
0845: out = new WireLogOutputStream(out, Wire.CONTENT_WIRE);
0846: }
0847: return out;
0848: }
0849:
0850: /**
0851: * Return a {@link InputStream} suitable for reading the response.
0852: * @return InputStream The response input stream.
0853: * @throws IOException If an IO problem occurs
0854: * @throws IllegalStateException If the connection isn't open.
0855: */
0856: public InputStream getResponseInputStream() throws IOException,
0857: IllegalStateException {
0858: LOG.trace("enter HttpConnection.getResponseInputStream()");
0859: assertOpen();
0860: return inputStream;
0861: }
0862:
0863: /**
0864: * Tests if input data avaialble. This method returns immediately
0865: * and does not perform any read operations on the input socket
0866: *
0867: * @return boolean <tt>true</tt> if input data is available,
0868: * <tt>false</tt> otherwise.
0869: *
0870: * @throws IOException If an IO problem occurs
0871: * @throws IllegalStateException If the connection isn't open.
0872: */
0873: public boolean isResponseAvailable() throws IOException {
0874: LOG.trace("enter HttpConnection.isResponseAvailable()");
0875: if (this .isOpen) {
0876: return this .inputStream.available() > 0;
0877: } else {
0878: return false;
0879: }
0880: }
0881:
0882: /**
0883: * Tests if input data becomes available within the given period time in milliseconds.
0884: *
0885: * @param timeout The number milliseconds to wait for input data to become available
0886: * @return boolean <tt>true</tt> if input data is availble,
0887: * <tt>false</tt> otherwise.
0888: *
0889: * @throws IOException If an IO problem occurs
0890: * @throws IllegalStateException If the connection isn't open.
0891: */
0892: public boolean isResponseAvailable(int timeout) throws IOException {
0893: LOG.trace("enter HttpConnection.isResponseAvailable(int)");
0894: assertOpen();
0895: boolean result = false;
0896: if (this .inputStream.available() > 0) {
0897: result = true;
0898: } else {
0899: try {
0900: this .socket.setSoTimeout(timeout);
0901: inputStream.mark(1);
0902: int byteRead = inputStream.read();
0903: if (byteRead != -1) {
0904: inputStream.reset();
0905: LOG.debug("Input data available");
0906: result = true;
0907: } else {
0908: LOG.debug("Input data not available");
0909: }
0910: } catch (InterruptedIOException e) {
0911: if (!ExceptionUtil.isSocketTimeoutException(e)) {
0912: throw e;
0913: }
0914: if (LOG.isDebugEnabled()) {
0915: LOG.debug("Input data not available after "
0916: + timeout + " ms");
0917: }
0918: } finally {
0919: try {
0920: socket.setSoTimeout(this .params.getSoTimeout());
0921: } catch (IOException ioe) {
0922: LOG
0923: .debug(
0924: "An error ocurred while resetting soTimeout, we will assume that"
0925: + " no response is available.",
0926: ioe);
0927: result = false;
0928: }
0929: }
0930: }
0931: return result;
0932: }
0933:
0934: /**
0935: * Writes the specified bytes to the output stream.
0936: *
0937: * @param data the data to be written
0938: * @throws IllegalStateException if not connected
0939: * @throws IOException if an I/O problem occurs
0940: * @see #write(byte[],int,int)
0941: */
0942: public void write(byte[] data) throws IOException,
0943: IllegalStateException {
0944: LOG.trace("enter HttpConnection.write(byte[])");
0945: this .write(data, 0, data.length);
0946: }
0947:
0948: /**
0949: * Writes <i>length</i> bytes in <i>data</i> starting at
0950: * <i>offset</i> to the output stream.
0951: *
0952: * The general contract for
0953: * write(b, off, len) is that some of the bytes in the array b are written
0954: * to the output stream in order; element b[off] is the first byte written
0955: * and b[off+len-1] is the last byte written by this operation.
0956: *
0957: * @param data array containing the data to be written.
0958: * @param offset the start offset in the data.
0959: * @param length the number of bytes to write.
0960: * @throws IllegalStateException if not connected
0961: * @throws IOException if an I/O problem occurs
0962: */
0963: public void write(byte[] data, int offset, int length)
0964: throws IOException, IllegalStateException {
0965: LOG.trace("enter HttpConnection.write(byte[], int, int)");
0966:
0967: if (offset < 0) {
0968: throw new IllegalArgumentException(
0969: "Array offset may not be negative");
0970: }
0971: if (length < 0) {
0972: throw new IllegalArgumentException(
0973: "Array length may not be negative");
0974: }
0975: if (offset + length > data.length) {
0976: throw new IllegalArgumentException(
0977: "Given offset and length exceed the array length");
0978: }
0979: assertOpen();
0980: this .outputStream.write(data, offset, length);
0981: }
0982:
0983: /**
0984: * Writes the specified bytes, followed by <tt>"\r\n".getBytes()</tt> to the
0985: * output stream.
0986: *
0987: * @param data the bytes to be written
0988: * @throws IllegalStateException if the connection is not open
0989: * @throws IOException if an I/O problem occurs
0990: */
0991: public void writeLine(byte[] data) throws IOException,
0992: IllegalStateException {
0993: LOG.trace("enter HttpConnection.writeLine(byte[])");
0994: write(data);
0995: writeLine();
0996: }
0997:
0998: /**
0999: * Writes <tt>"\r\n".getBytes()</tt> to the output stream.
1000: *
1001: * @throws IllegalStateException if the connection is not open
1002: * @throws IOException if an I/O problem occurs
1003: */
1004: public void writeLine() throws IOException, IllegalStateException {
1005: LOG.trace("enter HttpConnection.writeLine()");
1006: write(CRLF);
1007: }
1008:
1009: /**
1010: * @deprecated Use {@link #print(String, String)}
1011: *
1012: * Writes the specified String (as bytes) to the output stream.
1013: *
1014: * @param data the string to be written
1015: * @throws IllegalStateException if the connection is not open
1016: * @throws IOException if an I/O problem occurs
1017: */
1018: public void print(String data) throws IOException,
1019: IllegalStateException {
1020: LOG.trace("enter HttpConnection.print(String)");
1021: write(EncodingUtil.getBytes(data, "ISO-8859-1"));
1022: }
1023:
1024: /**
1025: * Writes the specified String (as bytes) to the output stream.
1026: *
1027: * @param data the string to be written
1028: * @param charset the charset to use for writing the data
1029: * @throws IllegalStateException if the connection is not open
1030: * @throws IOException if an I/O problem occurs
1031: *
1032: * @since 3.0
1033: */
1034: public void print(String data, String charset) throws IOException,
1035: IllegalStateException {
1036: LOG.trace("enter HttpConnection.print(String)");
1037: write(EncodingUtil.getBytes(data, charset));
1038: }
1039:
1040: /**
1041: * @deprecated Use {@link #printLine(String, String)}
1042: *
1043: * Writes the specified String (as bytes), followed by
1044: * <tt>"\r\n".getBytes()</tt> to the output stream.
1045: *
1046: * @param data the data to be written
1047: * @throws IllegalStateException if the connection is not open
1048: * @throws IOException if an I/O problem occurs
1049: */
1050: public void printLine(String data) throws IOException,
1051: IllegalStateException {
1052: LOG.trace("enter HttpConnection.printLine(String)");
1053: writeLine(EncodingUtil.getBytes(data, "ISO-8859-1"));
1054: }
1055:
1056: /**
1057: * Writes the specified String (as bytes), followed by
1058: * <tt>"\r\n".getBytes()</tt> to the output stream.
1059: *
1060: * @param data the data to be written
1061: * @param charset the charset to use for writing the data
1062: * @throws IllegalStateException if the connection is not open
1063: * @throws IOException if an I/O problem occurs
1064: *
1065: * @since 3.0
1066: */
1067: public void printLine(String data, String charset)
1068: throws IOException, IllegalStateException {
1069: LOG.trace("enter HttpConnection.printLine(String)");
1070: writeLine(EncodingUtil.getBytes(data, charset));
1071: }
1072:
1073: /**
1074: * Writes <tt>"\r\n".getBytes()</tt> to the output stream.
1075: *
1076: * @throws IllegalStateException if the connection is not open
1077: * @throws IOException if an I/O problem occurs
1078: */
1079: public void printLine() throws IOException, IllegalStateException {
1080: LOG.trace("enter HttpConnection.printLine()");
1081: writeLine();
1082: }
1083:
1084: /**
1085: * Reads up to <tt>"\n"</tt> from the (unchunked) input stream.
1086: * If the stream ends before the line terminator is found,
1087: * the last part of the string will still be returned.
1088: *
1089: * @throws IllegalStateException if the connection is not open
1090: * @throws IOException if an I/O problem occurs
1091: * @return a line from the response
1092: *
1093: * @deprecated use #readLine(String)
1094: */
1095: public String readLine() throws IOException, IllegalStateException {
1096: LOG.trace("enter HttpConnection.readLine()");
1097:
1098: assertOpen();
1099: return HttpParser.readLine(inputStream);
1100: }
1101:
1102: /**
1103: * Reads up to <tt>"\n"</tt> from the (unchunked) input stream.
1104: * If the stream ends before the line terminator is found,
1105: * the last part of the string will still be returned.
1106: *
1107: * @param charset the charset to use for reading the data
1108: *
1109: * @throws IllegalStateException if the connection is not open
1110: * @throws IOException if an I/O problem occurs
1111: * @return a line from the response
1112: *
1113: * @since 3.0
1114: */
1115: public String readLine(final String charset) throws IOException,
1116: IllegalStateException {
1117: LOG.trace("enter HttpConnection.readLine()");
1118:
1119: assertOpen();
1120: return HttpParser.readLine(inputStream, charset);
1121: }
1122:
1123: /**
1124: * Attempts to shutdown the {@link Socket}'s output, via Socket.shutdownOutput()
1125: * when running on JVM 1.3 or higher.
1126: *
1127: * @deprecated unused
1128: */
1129: public void shutdownOutput() {
1130: LOG.trace("enter HttpConnection.shutdownOutput()");
1131:
1132: try {
1133: // Socket.shutdownOutput is a JDK 1.3
1134: // method. We'll use reflection in case
1135: // we're running in an older VM
1136: Class[] paramsClasses = new Class[0];
1137: Method shutdownOutput = socket.getClass().getMethod(
1138: "shutdownOutput", paramsClasses);
1139: Object[] params = new Object[0];
1140: shutdownOutput.invoke(socket, params);
1141: } catch (Exception ex) {
1142: LOG.debug("Unexpected Exception caught", ex);
1143: // Ignore, and hope everything goes right
1144: }
1145: // close output stream?
1146: }
1147:
1148: /**
1149: * Closes the socket and streams.
1150: */
1151: public void close() {
1152: LOG.trace("enter HttpConnection.close()");
1153: closeSocketAndStreams();
1154: }
1155:
1156: /**
1157: * Returns the httpConnectionManager.
1158: * @return HttpConnectionManager
1159: */
1160: public HttpConnectionManager getHttpConnectionManager() {
1161: return httpConnectionManager;
1162: }
1163:
1164: /**
1165: * Sets the httpConnectionManager.
1166: * @param httpConnectionManager The httpConnectionManager to set
1167: */
1168: public void setHttpConnectionManager(
1169: HttpConnectionManager httpConnectionManager) {
1170: this .httpConnectionManager = httpConnectionManager;
1171: }
1172:
1173: /**
1174: * Releases the connection. If the connection is locked or does not have a connection
1175: * manager associated with it, this method has no effect. Note that it is completely safe
1176: * to call this method multiple times.
1177: */
1178: public void releaseConnection() {
1179: LOG.trace("enter HttpConnection.releaseConnection()");
1180: if (locked) {
1181: LOG
1182: .debug("Connection is locked. Call to releaseConnection() ignored.");
1183: } else if (httpConnectionManager != null) {
1184: LOG
1185: .debug("Releasing connection back to connection manager.");
1186: httpConnectionManager.releaseConnection(this );
1187: } else {
1188: LOG
1189: .warn("HttpConnectionManager is null. Connection cannot be released.");
1190: }
1191: }
1192:
1193: /**
1194: * Tests if the connection is locked. Locked connections cannot be released.
1195: * An attempt to release a locked connection will have no effect.
1196: *
1197: * @return <tt>true</tt> if the connection is locked, <tt>false</tt> otherwise.
1198: *
1199: * @since 3.0
1200: */
1201: protected boolean isLocked() {
1202: return locked;
1203: }
1204:
1205: /**
1206: * Locks or unlocks the connection. Locked connections cannot be released.
1207: * An attempt to release a locked connection will have no effect.
1208: *
1209: * @param locked <tt>true</tt> to lock the connection, <tt>false</tt> to unlock
1210: * the connection.
1211: *
1212: * @since 3.0
1213: */
1214: protected void setLocked(boolean locked) {
1215: this .locked = locked;
1216: }
1217:
1218: // ------------------------------------------------------ Protected Methods
1219:
1220: /**
1221: * Closes everything out.
1222: */
1223: protected void closeSocketAndStreams() {
1224: LOG.trace("enter HttpConnection.closeSockedAndStreams()");
1225:
1226: isOpen = false;
1227:
1228: // no longer care about previous responses...
1229: lastResponseInputStream = null;
1230:
1231: if (null != outputStream) {
1232: OutputStream temp = outputStream;
1233: outputStream = null;
1234: try {
1235: temp.close();
1236: } catch (Exception ex) {
1237: LOG.debug("Exception caught when closing output", ex);
1238: // ignored
1239: }
1240: }
1241:
1242: if (null != inputStream) {
1243: InputStream temp = inputStream;
1244: inputStream = null;
1245: try {
1246: temp.close();
1247: } catch (Exception ex) {
1248: LOG.debug("Exception caught when closing input", ex);
1249: // ignored
1250: }
1251: }
1252:
1253: if (null != socket) {
1254: Socket temp = socket;
1255: socket = null;
1256: try {
1257: temp.close();
1258: } catch (Exception ex) {
1259: LOG.debug("Exception caught when closing socket", ex);
1260: // ignored
1261: }
1262: }
1263:
1264: tunnelEstablished = false;
1265: usingSecureSocket = false;
1266: }
1267:
1268: /**
1269: * Throws an {@link IllegalStateException} if the connection is already open.
1270: *
1271: * @throws IllegalStateException if connected
1272: */
1273: protected void assertNotOpen() throws IllegalStateException {
1274: if (isOpen) {
1275: throw new IllegalStateException("Connection is open");
1276: }
1277: }
1278:
1279: /**
1280: * Throws an {@link IllegalStateException} if the connection is not open.
1281: *
1282: * @throws IllegalStateException if not connected
1283: */
1284: protected void assertOpen() throws IllegalStateException {
1285: if (!isOpen) {
1286: throw new IllegalStateException("Connection is not open");
1287: }
1288: }
1289:
1290: /**
1291: * Gets the socket's sendBufferSize.
1292: *
1293: * @return the size of the buffer for the socket OutputStream, -1 if the value
1294: * has not been set and the socket has not been opened
1295: *
1296: * @throws SocketException if an error occurs while getting the socket value
1297: *
1298: * @see Socket#getSendBufferSize()
1299: */
1300: public int getSendBufferSize() throws SocketException {
1301: if (socket == null) {
1302: return -1;
1303: } else {
1304: return socket.getSendBufferSize();
1305: }
1306: }
1307:
1308: /**
1309: * Sets the socket's sendBufferSize.
1310: *
1311: * @param sendBufferSize the size to set for the socket OutputStream
1312: *
1313: * @throws SocketException if an error occurs while setting the socket value
1314: *
1315: * @see Socket#setSendBufferSize(int)
1316: *
1317: * @deprecated Use {@link HttpConnectionParams#setSendBufferSize(int)},
1318: * {@link HttpConnection#getParams()}.
1319: */
1320: public void setSendBufferSize(int sendBufferSize)
1321: throws SocketException {
1322: this .params.setSendBufferSize(sendBufferSize);
1323: }
1324:
1325: // ------------------------------------------------------- Static Variable
1326:
1327: /** <tt>"\r\n"</tt>, as bytes. */
1328: private static final byte[] CRLF = new byte[] { (byte) 13,
1329: (byte) 10 };
1330:
1331: /** Log object for this class. */
1332: private static final Log LOG = LogFactory
1333: .getLog(HttpConnection.class);
1334:
1335: // ----------------------------------------------------- Instance Variables
1336:
1337: /** My host. */
1338: private String hostName = null;
1339:
1340: /** My port. */
1341: private int portNumber = -1;
1342:
1343: /** My proxy host. */
1344: private String proxyHostName = null;
1345:
1346: /** My proxy port. */
1347: private int proxyPortNumber = -1;
1348:
1349: /** My client Socket. */
1350: private Socket socket = null;
1351:
1352: /** My InputStream. */
1353: private InputStream inputStream = null;
1354:
1355: /** My OutputStream. */
1356: private OutputStream outputStream = null;
1357:
1358: /** An {@link InputStream} for the response to an individual request. */
1359: private InputStream lastResponseInputStream = null;
1360:
1361: /** Whether or not the connection is connected. */
1362: protected boolean isOpen = false;
1363:
1364: /** the protocol being used */
1365: private Protocol protocolInUse;
1366:
1367: /** Collection of HTTP parameters associated with this HTTP connection*/
1368: private HttpConnectionParams params = new HttpConnectionParams();
1369:
1370: /** flag to indicate if this connection can be released, if locked the connection cannot be
1371: * released */
1372: private boolean locked = false;
1373:
1374: /** Whether or not the socket is a secure one. */
1375: private boolean usingSecureSocket = false;
1376:
1377: /** Whether the connection is open via a secure tunnel or not */
1378: private boolean tunnelEstablished = false;
1379:
1380: /** the connection manager that created this connection or null */
1381: private HttpConnectionManager httpConnectionManager;
1382:
1383: /** The local interface on which the connection is created, or null for the default */
1384: private InetAddress localAddress;
1385: }
|