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