/*
* $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 $
* $Revision: 480424 $
* $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
*
* ====================================================================
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.commons.httpclient;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
import org.apache.commons.httpclient.util.EncodingUtil;
import org.apache.commons.httpclient.util.ExceptionUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* An abstraction of an HTTP {@link InputStream} and {@link OutputStream}
* pair, together with the relevant attributes.
* <p>
* The following options are set on the socket before getting the input/output
* streams in the {@link #open()} method:
* <table border=1><tr>
* <th>Socket Method
* <th>Sockets Option
* <th>Configuration
* </tr><tr>
* <td>{@link java.net.Socket#setTcpNoDelay(boolean)}
* <td>SO_NODELAY
* <td>{@link HttpConnectionParams#setTcpNoDelay(boolean)}
* </tr><tr>
* <td>{@link java.net.Socket#setSoTimeout(int)}
* <td>SO_TIMEOUT
* <td>{@link HttpConnectionParams#setSoTimeout(int)}
* </tr><tr>
* <td>{@link java.net.Socket#setSendBufferSize(int)}
* <td>SO_SNDBUF
* <td>{@link HttpConnectionParams#setSendBufferSize(int)}
* </tr><tr>
* <td>{@link java.net.Socket#setReceiveBufferSize(int)}
* <td>SO_RCVBUF
* <td>{@link HttpConnectionParams#setReceiveBufferSize(int)}
* </tr></table>
*
* @author Rod Waldhoff
* @author Sean C. Sullivan
* @author Ortwin Glueck
* @author <a href="mailto:[email protected]">Jeff Dever</a>
* @author <a href="mailto:[email protected]">Mike Bowler</a>
* @author <a href="mailto:[email protected]">Oleg Kalnichevski</a>
* @author Michael Becke
* @author Eric E Johnson
* @author Laura Werner
*
* @version $Revision: 480424 $ $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
*/
public class HttpConnection {
// ----------------------------------------------------------- Constructors
/**
* Creates a new HTTP connection for the given host and port.
*
* @param host the host to connect to
* @param port the port to connect to
*/
public HttpConnection(String host, int port) {
this(null, -1, host, null, port, Protocol.getProtocol("http"));
}
/**
* Creates a new HTTP connection for the given host and port
* using the given protocol.
*
* @param host the host to connect to
* @param port the port to connect to
* @param protocol the protocol to use
*/
public HttpConnection(String host, int port, Protocol protocol) {
this(null, -1, host, null, port, protocol);
}
/**
* Creates a new HTTP connection for the given host with the virtual
* alias and port using given protocol.
*
* @param host the host to connect to
* @param virtualHost the virtual host requests will be sent to
* @param port the port to connect to
* @param protocol the protocol to use
*/
public HttpConnection(String host, String virtualHost, int port, Protocol protocol) {
this(null, -1, host, virtualHost, port, protocol);
}
/**
* Creates a new HTTP connection for the given host and port via the
* given proxy host and port using the default protocol.
*
* @param proxyHost the host to proxy via
* @param proxyPort the port to proxy via
* @param host the host to connect to
* @param port the port to connect to
*/
public HttpConnection(
String proxyHost,
int proxyPort,
String host,
int port) {
this(proxyHost, proxyPort, host, null, port, Protocol.getProtocol("http"));
}
/**
* Creates a new HTTP connection for the given host configuration.
*
* @param hostConfiguration the host/proxy/protocol to use
*/
public HttpConnection(HostConfiguration hostConfiguration) {
this(hostConfiguration.getProxyHost(),
hostConfiguration.getProxyPort(),
hostConfiguration.getHost(),
hostConfiguration.getPort(),
hostConfiguration.getProtocol());
this.localAddress = hostConfiguration.getLocalAddress();
}
/**
* Creates a new HTTP connection for the given host with the virtual
* alias and port via the given proxy host and port using the given
* protocol.
*
* @param proxyHost the host to proxy via
* @param proxyPort the port to proxy via
* @param host the host to connect to. Parameter value must be non-null.
* @param virtualHost No longer applicable.
* @param port the port to connect to
* @param protocol The protocol to use. Parameter value must be non-null.
*
* @deprecated use #HttpConnection(String, int, String, int, Protocol)
*/
public HttpConnection(
String proxyHost,
int proxyPort,
String host,
String virtualHost,
int port,
Protocol protocol) {
this(proxyHost, proxyPort, host, port, protocol);
}
/**
* Creates a new HTTP connection for the given host with the virtual
* alias and port via the given proxy host and port using the given
* protocol.
*
* @param proxyHost the host to proxy via
* @param proxyPort the port to proxy via
* @param host the host to connect to. Parameter value must be non-null.
* @param port the port to connect to
* @param protocol The protocol to use. Parameter value must be non-null.
*/
public HttpConnection(
String proxyHost,
int proxyPort,
String host,
int port,
Protocol protocol) {
if (host == null) {
throw new IllegalArgumentException("host parameter is null");
}
if (protocol == null) {
throw new IllegalArgumentException("protocol is null");
}
proxyHostName = proxyHost;
proxyPortNumber = proxyPort;
hostName = host;
portNumber = protocol.resolvePort(port);
protocolInUse = protocol;
}
// ------------------------------------------ Attribute Setters and Getters
/**
* Returns the connection socket.
*
* @return the socket.
*
* @since 3.0
*/
protected Socket getSocket() {
return this.socket;
}
/**
* Returns the host.
*
* @return the host.
*/
public String getHost() {
return hostName;
}
/**
* Sets the host to connect to.
*
* @param host the host to connect to. Parameter value must be non-null.
* @throws IllegalStateException if the connection is already open
*/
public void setHost(String host) throws IllegalStateException {
if (host == null) {
throw new IllegalArgumentException("host parameter is null");
}
assertNotOpen();
hostName = host;
}
/**
* Returns the target virtual host.
*
* @return the virtual host.
*
* @deprecated no longer applicable
*/
public String getVirtualHost() {
return this.hostName;
}
/**
* Sets the virtual host to target.
*
* @param host the virtual host name that should be used instead of
* physical host name when sending HTTP requests. Virtual host
* name can be set to <tt> null</tt> if virtual host name is not
* to be used
*
* @throws IllegalStateException if the connection is already open
*
* @deprecated no longer applicable
*/
public void setVirtualHost(String host) throws IllegalStateException {
assertNotOpen();
}
/**
* Returns the port of the host.
*
* If the port is -1 (or less than 0) the default port for
* the current protocol is returned.
*
* @return the port.
*/
public int getPort() {
if (portNumber < 0) {
return isSecure() ? 443 : 80;
} else {
return portNumber;
}
}
/**
* Sets the port to connect to.
*
* @param port the port to connect to
*
* @throws IllegalStateException if the connection is already open
*/
public void setPort(int port) throws IllegalStateException {
assertNotOpen();
portNumber = port;
}
/**
* Returns the proxy host.
*
* @return the proxy host.
*/
public String getProxyHost() {
return proxyHostName;
}
/**
* Sets the host to proxy through.
*
* @param host the host to proxy through.
*
* @throws IllegalStateException if the connection is already open
*/
public void setProxyHost(String host) throws IllegalStateException {
assertNotOpen();
proxyHostName = host;
}
/**
* Returns the port of the proxy host.
*
* @return the proxy port.
*/
public int getProxyPort() {
return proxyPortNumber;
}
/**
* Sets the port of the host to proxy through.
*
* @param port the port of the host to proxy through.
*
* @throws IllegalStateException if the connection is already open
*/
public void setProxyPort(int port) throws IllegalStateException {
assertNotOpen();
proxyPortNumber = port;
}
/**
* Returns <tt>true</tt> if the connection is established over
* a secure protocol.
*
* @return <tt>true</tt> if connected over a secure protocol.
*/
public boolean isSecure() {
return protocolInUse.isSecure();
}
/**
* Returns the protocol used to establish the connection.
* @return The protocol
*/
public Protocol getProtocol() {
return protocolInUse;
}
/**
* Sets the protocol used to establish the connection
*
* @param protocol The protocol to use.
*
* @throws IllegalStateException if the connection is already open
*/
public void setProtocol(Protocol protocol) {
assertNotOpen();
if (protocol == null) {
throw new IllegalArgumentException("protocol is null");
}
protocolInUse = protocol;
}
/**
* Return the local address used when creating the connection.
* If <tt>null</tt>, the default address is used.
*
* @return InetAddress the local address to be used when creating Sockets
*/
public InetAddress getLocalAddress() {
return this.localAddress;
}
/**
* Set the local address used when creating the connection.
* If unset or <tt>null</tt>, the default address is used.
*
* @param localAddress the local address to use
*/
public void setLocalAddress(InetAddress localAddress) {
assertNotOpen();
this.localAddress = localAddress;
}
/**
* Tests if the connection is open.
*
* @return <code>true</code> if the connection is open
*/
public boolean isOpen() {
return isOpen;
}
/**
* Closes the connection if stale.
*
* @return <code>true</code> if the connection was stale and therefore closed,
* <code>false</code> otherwise.
*
* @see #isStale()
*
* @since 3.0
*/
public boolean closeIfStale() throws IOException {
if (isOpen && isStale()) {
LOG.debug("Connection is stale, closing...");
close();
return true;
}
return false;
}
/**
* Tests if stale checking is enabled.
*
* @return <code>true</code> if enabled
*
* @see #isStale()
*
* @deprecated Use {@link HttpConnectionParams#isStaleCheckingEnabled()},
* {@link HttpConnection#getParams()}.
*/
public boolean isStaleCheckingEnabled() {
return this.params.isStaleCheckingEnabled();
}
/**
* Sets whether or not isStale() will be called when testing if this connection is open.
*
* <p>Setting this flag to <code>false</code> will increase performance when reusing
* connections, but it will also make them less reliable. Stale checking ensures that
* connections are viable before they are used. When set to <code>false</code> some
* method executions will result in IOExceptions and they will have to be retried.</p>
*
* @param staleCheckEnabled <code>true</code> to enable isStale()
*
* @see #isStale()
* @see #isOpen()
*
* @deprecated Use {@link HttpConnectionParams#setStaleCheckingEnabled(boolean)},
* {@link HttpConnection#getParams()}.
*/
public void setStaleCheckingEnabled(boolean staleCheckEnabled) {
this.params.setStaleCheckingEnabled(staleCheckEnabled);
}
/**
* Determines whether this connection is "stale", which is to say that either
* it is no longer open, or an attempt to read the connection would fail.
*
* <p>Unfortunately, due to the limitations of the JREs prior to 1.4, it is
* not possible to test a connection to see if both the read and write channels
* are open - except by reading and writing. This leads to a difficulty when
* some connections leave the "write" channel open, but close the read channel
* and ignore the request. This function attempts to ameliorate that
* problem by doing a test read, assuming that the caller will be doing a
* write followed by a read, rather than the other way around.
* </p>
*
* <p>To avoid side-effects, the underlying connection is wrapped by a
* {@link BufferedInputStream}, so although data might be read, what is visible
* to clients of the connection will not change with this call.</p.
*
* @throws IOException if the stale connection test is interrupted.
*
* @return <tt>true</tt> if the connection is already closed, or a read would
* fail.
*/
protected boolean isStale() throws IOException {
boolean isStale = true;
if (isOpen) {
// the connection is open, but now we have to see if we can read it
// assume the connection is not stale.
isStale = false;
try {
if (inputStream.available() <= 0) {
try {
socket.setSoTimeout(1);
inputStream.mark(1);
int byteRead = inputStream.read();
if (byteRead == -1) {
// again - if the socket is reporting all data read,
// probably stale
isStale = true;
} else {
inputStream.reset();
}
} finally {
socket.setSoTimeout(this.params.getSoTimeout());
}
}
} catch (InterruptedIOException e) {
if (!ExceptionUtil.isSocketTimeoutException(e)) {
throw e;
}
// aha - the connection is NOT stale - continue on!
} catch (IOException e) {
// oops - the connection is stale, the read or soTimeout failed.
LOG.debug(
"An error occurred while reading from the socket, is appears to be stale",
e
);
isStale = true;
}
}
return isStale;
}
/**
* Returns <tt>true</tt> if the connection is established via a proxy,
* <tt>false</tt> otherwise.
*
* @return <tt>true</tt> if a proxy is used to establish the connection,
* <tt>false</tt> otherwise.
*/
public boolean isProxied() {
return (!(null == proxyHostName || 0 >= proxyPortNumber));
}
/**
* Set the state to keep track of the last response for the last request.
*
* <p>The connection managers use this to ensure that previous requests are
* properly closed before a new request is attempted. That way, a GET
* request need not be read in its entirety before a new request is issued.
* Instead, this stream can be closed as appropriate.</p>
*
* @param inStream The stream associated with an HttpMethod.
*/
public void setLastResponseInputStream(InputStream inStream) {
lastResponseInputStream = inStream;
}
/**
* Returns the stream used to read the last response's body.
*
* <p>Clients will generally not need to call this function unless
* using HttpConnection directly, instead of calling {@link HttpClient#executeMethod}.
* For those clients, call this function, and if it returns a non-null stream,
* close the stream before attempting to execute a method. Note that
* calling "close" on the stream returned by this function <i>may</i> close
* the connection if the previous response contained a "Connection: close" header. </p>
*
* @return An {@link InputStream} corresponding to the body of the last
* response.
*/
public InputStream getLastResponseInputStream() {
return lastResponseInputStream;
}
// --------------------------------------------------- Other Public Methods
/**
* Returns {@link HttpConnectionParams HTTP protocol parameters} associated with this method.
*
* @return HTTP parameters.
*
* @since 3.0
*/
public HttpConnectionParams getParams() {
return this.params;
}
/**
* Assigns {@link HttpConnectionParams HTTP protocol parameters} for this method.
*
* @since 3.0
*
* @see HttpConnectionParams
*/
public void setParams(final HttpConnectionParams params) {
if (params == null) {
throw new IllegalArgumentException("Parameters may not be null");
}
this.params = params;
}
/**
* Set the {@link Socket}'s timeout, via {@link Socket#setSoTimeout}. If the
* connection is already open, the SO_TIMEOUT is changed. If no connection
* is open, then subsequent connections will use the timeout value.
* <p>
* Note: This is not a connection timeout but a timeout on network traffic!
*
* @param timeout the timeout value
* @throws SocketException - if there is an error in the underlying
* protocol, such as a TCP error.
*
* @deprecated Use {@link HttpConnectionParams#setSoTimeout(int)},
* {@link HttpConnection#getParams()}.
*/
public void setSoTimeout(int timeout)
throws SocketException, IllegalStateException {
this.params.setSoTimeout(timeout);
if (this.socket != null) {
this.socket.setSoTimeout(timeout);
}
}
/**
* Sets <code>SO_TIMEOUT</code> value directly on the underlying {@link Socket socket}.
* This method does not change the default read timeout value set via
* {@link HttpConnectionParams}.
*
* @param timeout the timeout value
* @throws SocketException - if there is an error in the underlying
* protocol, such as a TCP error.
* @throws IllegalStateException if not connected
*
* @since 3.0
*/
public void setSocketTimeout(int timeout)
throws SocketException, IllegalStateException {
assertOpen();
if (this.socket != null) {
this.socket.setSoTimeout(timeout);
}
}
/**
* Returns the {@link Socket}'s timeout, via {@link Socket#getSoTimeout}, if the
* connection is already open. If no connection is open, return the value subsequent
* connection will use.
* <p>
* Note: This is not a connection timeout but a timeout on network traffic!
*
* @return the timeout value
*
* @deprecated Use {@link HttpConnectionParams#getSoTimeout()},
* {@link HttpConnection#getParams()}.
*/
public int getSoTimeout() throws SocketException {
return this.params.getSoTimeout();
}
/**
* Sets the connection timeout. This is the maximum time that may be spent
* until a connection is established. The connection will fail after this
* amount of time.
* @param timeout The timeout in milliseconds. 0 means timeout is not used.
*
* @deprecated Use {@link HttpConnectionParams#setConnectionTimeout(int)},
* {@link HttpConnection#getParams()}.
*/
public void setConnectionTimeout(int timeout) {
this.params.setConnectionTimeout(timeout);
}
/**
* Establishes a connection to the specified host and port
* (via a proxy if specified).
* The underlying socket is created from the {@link ProtocolSocketFactory}.
*
* @throws IOException if an attempt to establish the connection results in an
* I/O error.
*/
public void open() throws IOException {
LOG.trace("enter HttpConnection.open()");
final String host = (proxyHostName == null) ? hostName : proxyHostName;
final int port = (proxyHostName == null) ? portNumber : proxyPortNumber;
assertNotOpen();
if (LOG.isDebugEnabled()) {
LOG.debug("Open connection to " + host + ":" + port);
}
try {
if (this.socket == null) {
usingSecureSocket = isSecure() && !isProxied();
// use the protocol's socket factory unless this is a secure
// proxied connection
ProtocolSocketFactory socketFactory = null;
if (isSecure() && isProxied()) {
Protocol defaultprotocol = Protocol.getProtocol("http");
socketFactory = defaultprotocol.getSocketFactory();
} else {
socketFactory = this.protocolInUse.getSocketFactory();
}
this.socket = socketFactory.createSocket(
host, port,
localAddress, 0,
this.params);
}
/*
"Nagling has been broadly implemented across networks,
including the Internet, and is generally performed by default
- although it is sometimes considered to be undesirable in
highly interactive environments, such as some client/server
situations. In such cases, nagling may be turned off through
use of the TCP_NODELAY sockets option." */
socket.setTcpNoDelay(this.params.getTcpNoDelay());
socket.setSoTimeout(this.params.getSoTimeout());
int linger = this.params.getLinger();
if (linger >= 0) {
socket.setSoLinger(linger > 0, linger);
}
int sndBufSize = this.params.getSendBufferSize();
if (sndBufSize >= 0) {
socket.setSendBufferSize(sndBufSize);
}
int rcvBufSize = this.params.getReceiveBufferSize();
if (rcvBufSize >= 0) {
socket.setReceiveBufferSize(rcvBufSize);
}
int outbuffersize = socket.getSendBufferSize();
if ((outbuffersize > 2048) || (outbuffersize <= 0)) {
outbuffersize = 2048;
}
int inbuffersize = socket.getReceiveBufferSize();
if ((inbuffersize > 2048) || (inbuffersize <= 0)) {
inbuffersize = 2048;
}
inputStream = new BufferedInputStream(socket.getInputStream(), inbuffersize);
outputStream = new BufferedOutputStream(socket.getOutputStream(), outbuffersize);
isOpen = true;
} catch (IOException e) {
// Connection wasn't opened properly
// so close everything out
closeSocketAndStreams();
throw e;
}
}
/**
* Instructs the proxy to establish a secure tunnel to the host. The socket will
* be switched to the secure socket. Subsequent communication is done via the secure
* socket. The method can only be called once on a proxied secure connection.
*
* @throws IllegalStateException if connection is not secure and proxied or
* if the socket is already secure.
* @throws IOException if an attempt to establish the secure tunnel results in an
* I/O error.
*/
public void tunnelCreated() throws IllegalStateException, IOException {
LOG.trace("enter HttpConnection.tunnelCreated()");
if (!isSecure() || !isProxied()) {
throw new IllegalStateException(
"Connection must be secure "
+ "and proxied to use this feature");
}
if (usingSecureSocket) {
throw new IllegalStateException("Already using a secure socket");
}
if (LOG.isDebugEnabled()) {
LOG.debug("Secure tunnel to " + this.hostName + ":" + this.portNumber);
}
SecureProtocolSocketFactory socketFactory =
(SecureProtocolSocketFactory) protocolInUse.getSocketFactory();
socket = socketFactory.createSocket(socket, hostName, portNumber, true);
int sndBufSize = this.params.getSendBufferSize();
if (sndBufSize >= 0) {
socket.setSendBufferSize(sndBufSize);
}
int rcvBufSize = this.params.getReceiveBufferSize();
if (rcvBufSize >= 0) {
socket.setReceiveBufferSize(rcvBufSize);
}
int outbuffersize = socket.getSendBufferSize();
if (outbuffersize > 2048) {
outbuffersize = 2048;
}
int inbuffersize = socket.getReceiveBufferSize();
if (inbuffersize > 2048) {
inbuffersize = 2048;
}
inputStream = new BufferedInputStream(socket.getInputStream(), inbuffersize);
outputStream = new BufferedOutputStream(socket.getOutputStream(), outbuffersize);
usingSecureSocket = true;
tunnelEstablished = true;
}
/**
* Indicates if the connection is completely transparent from end to end.
*
* @return true if conncetion is not proxied or tunneled through a transparent
* proxy; false otherwise.
*/
public boolean isTransparent() {
return !isProxied() || tunnelEstablished;
}
/**
* Flushes the output request stream. This method should be called to
* ensure that data written to the request OutputStream is sent to the server.
*
* @throws IOException if an I/O problem occurs
*/
public void flushRequestOutputStream() throws IOException {
LOG.trace("enter HttpConnection.flushRequestOutputStream()");
assertOpen();
outputStream.flush();
}
/**
* Returns an {@link OutputStream} suitable for writing the request.
*
* @throws IllegalStateException if the connection is not open
* @throws IOException if an I/O problem occurs
* @return a stream to write the request to
*/
public OutputStream getRequestOutputStream()
throws IOException, IllegalStateException {
LOG.trace("enter HttpConnection.getRequestOutputStream()");
assertOpen();
OutputStream out = this.outputStream;
if (Wire.CONTENT_WIRE.enabled()) {
out = new WireLogOutputStream(out, Wire.CONTENT_WIRE);
}
return out;
}
/**
* Return a {@link InputStream} suitable for reading the response.
* @return InputStream The response input stream.
* @throws IOException If an IO problem occurs
* @throws IllegalStateException If the connection isn't open.
*/
public InputStream getResponseInputStream()
throws IOException, IllegalStateException {
LOG.trace("enter HttpConnection.getResponseInputStream()");
assertOpen();
return inputStream;
}
/**
* Tests if input data avaialble. This method returns immediately
* and does not perform any read operations on the input socket
*
* @return boolean <tt>true</tt> if input data is available,
* <tt>false</tt> otherwise.
*
* @throws IOException If an IO problem occurs
* @throws IllegalStateException If the connection isn't open.
*/
public boolean isResponseAvailable()
throws IOException {
LOG.trace("enter HttpConnection.isResponseAvailable()");
if (this.isOpen) {
return this.inputStream.available() > 0;
} else {
return false;
}
}
/**
* Tests if input data becomes available within the given period time in milliseconds.
*
* @param timeout The number milliseconds to wait for input data to become available
* @return boolean <tt>true</tt> if input data is availble,
* <tt>false</tt> otherwise.
*
* @throws IOException If an IO problem occurs
* @throws IllegalStateException If the connection isn't open.
*/
public boolean isResponseAvailable(int timeout)
throws IOException {
LOG.trace("enter HttpConnection.isResponseAvailable(int)");
assertOpen();
boolean result = false;
if (this.inputStream.available() > 0) {
result = true;
} else {
try {
this.socket.setSoTimeout(timeout);
inputStream.mark(1);
int byteRead = inputStream.read();
if (byteRead != -1) {
inputStream.reset();
LOG.debug("Input data available");
result = true;
} else {
LOG.debug("Input data not available");
}
} catch (InterruptedIOException e) {
if (!ExceptionUtil.isSocketTimeoutException(e)) {
throw e;
}
if (LOG.isDebugEnabled()) {
LOG.debug("Input data not available after " + timeout + " ms");
}
} finally {
try {
socket.setSoTimeout(this.params.getSoTimeout());
} catch (IOException ioe) {
LOG.debug("An error ocurred while resetting soTimeout, we will assume that"
+ " no response is available.",
ioe);
result = false;
}
}
}
return result;
}
/**
* Writes the specified bytes to the output stream.
*
* @param data the data to be written
* @throws IllegalStateException if not connected
* @throws IOException if an I/O problem occurs
* @see #write(byte[],int,int)
*/
public void write(byte[] data)
throws IOException, IllegalStateException {
LOG.trace("enter HttpConnection.write(byte[])");
this.write(data, 0, data.length);
}
/**
* Writes <i>length</i> bytes in <i>data</i> starting at
* <i>offset</i> to the output stream.
*
* The general contract for
* write(b, off, len) is that some of the bytes in the array b are written
* to the output stream in order; element b[off] is the first byte written
* and b[off+len-1] is the last byte written by this operation.
*
* @param data array containing the data to be written.
* @param offset the start offset in the data.
* @param length the number of bytes to write.
* @throws IllegalStateException if not connected
* @throws IOException if an I/O problem occurs
*/
public void write(byte[] data, int offset, int length)
throws IOException, IllegalStateException {
LOG.trace("enter HttpConnection.write(byte[], int, int)");
if (offset < 0) {
throw new IllegalArgumentException("Array offset may not be negative");
}
if (length < 0) {
throw new IllegalArgumentException("Array length may not be negative");
}
if (offset + length > data.length) {
throw new IllegalArgumentException("Given offset and length exceed the array length");
}
assertOpen();
this.outputStream.write(data, offset, length);
}
/**
* Writes the specified bytes, followed by <tt>"\r\n".getBytes()</tt> to the
* output stream.
*
* @param data the bytes to be written
* @throws IllegalStateException if the connection is not open
* @throws IOException if an I/O problem occurs
*/
public void writeLine(byte[] data)
throws IOException, IllegalStateException {
LOG.trace("enter HttpConnection.writeLine(byte[])");
write(data);
writeLine();
}
/**
* Writes <tt>"\r\n".getBytes()</tt> to the output stream.
*
* @throws IllegalStateException if the connection is not open
* @throws IOException if an I/O problem occurs
*/
public void writeLine()
throws IOException, IllegalStateException {
LOG.trace("enter HttpConnection.writeLine()");
write(CRLF);
}
/**
* @deprecated Use {@link #print(String, String)}
*
* Writes the specified String (as bytes) to the output stream.
*
* @param data the string to be written
* @throws IllegalStateException if the connection is not open
* @throws IOException if an I/O problem occurs
*/
public void print(String data)
throws IOException, IllegalStateException {
LOG.trace("enter HttpConnection.print(String)");
write(EncodingUtil.getBytes(data, "ISO-8859-1"));
}
/**
* Writes the specified String (as bytes) to the output stream.
*
* @param data the string to be written
* @param charset the charset to use for writing the data
* @throws IllegalStateException if the connection is not open
* @throws IOException if an I/O problem occurs
*
* @since 3.0
*/
public void print(String data, String charset)
throws IOException, IllegalStateException {
LOG.trace("enter HttpConnection.print(String)");
write(EncodingUtil.getBytes(data, charset));
}
/**
* @deprecated Use {@link #printLine(String, String)}
*
* Writes the specified String (as bytes), followed by
* <tt>"\r\n".getBytes()</tt> to the output stream.
*
* @param data the data to be written
* @throws IllegalStateException if the connection is not open
* @throws IOException if an I/O problem occurs
*/
public void printLine(String data)
throws IOException, IllegalStateException {
LOG.trace("enter HttpConnection.printLine(String)");
writeLine(EncodingUtil.getBytes(data, "ISO-8859-1"));
}
/**
* Writes the specified String (as bytes), followed by
* <tt>"\r\n".getBytes()</tt> to the output stream.
*
* @param data the data to be written
* @param charset the charset to use for writing the data
* @throws IllegalStateException if the connection is not open
* @throws IOException if an I/O problem occurs
*
* @since 3.0
*/
public void printLine(String data, String charset)
throws IOException, IllegalStateException {
LOG.trace("enter HttpConnection.printLine(String)");
writeLine(EncodingUtil.getBytes(data, charset));
}
/**
* Writes <tt>"\r\n".getBytes()</tt> to the output stream.
*
* @throws IllegalStateException if the connection is not open
* @throws IOException if an I/O problem occurs
*/
public void printLine()
throws IOException, IllegalStateException {
LOG.trace("enter HttpConnection.printLine()");
writeLine();
}
/**
* Reads up to <tt>"\n"</tt> from the (unchunked) input stream.
* If the stream ends before the line terminator is found,
* the last part of the string will still be returned.
*
* @throws IllegalStateException if the connection is not open
* @throws IOException if an I/O problem occurs
* @return a line from the response
*
* @deprecated use #readLine(String)
*/
public String readLine() throws IOException, IllegalStateException {
LOG.trace("enter HttpConnection.readLine()");
assertOpen();
return HttpParser.readLine(inputStream);
}
/**
* Reads up to <tt>"\n"</tt> from the (unchunked) input stream.
* If the stream ends before the line terminator is found,
* the last part of the string will still be returned.
*
* @param charset the charset to use for reading the data
*
* @throws IllegalStateException if the connection is not open
* @throws IOException if an I/O problem occurs
* @return a line from the response
*
* @since 3.0
*/
public String readLine(final String charset) throws IOException, IllegalStateException {
LOG.trace("enter HttpConnection.readLine()");
assertOpen();
return HttpParser.readLine(inputStream, charset);
}
/**
* Attempts to shutdown the {@link Socket}'s output, via Socket.shutdownOutput()
* when running on JVM 1.3 or higher.
*
* @deprecated unused
*/
public void shutdownOutput() {
LOG.trace("enter HttpConnection.shutdownOutput()");
try {
// Socket.shutdownOutput is a JDK 1.3
// method. We'll use reflection in case
// we're running in an older VM
Class[] paramsClasses = new Class[0];
Method shutdownOutput =
socket.getClass().getMethod("shutdownOutput", paramsClasses);
Object[] params = new Object[0];
shutdownOutput.invoke(socket, params);
} catch (Exception ex) {
LOG.debug("Unexpected Exception caught", ex);
// Ignore, and hope everything goes right
}
// close output stream?
}
/**
* Closes the socket and streams.
*/
public void close() {
LOG.trace("enter HttpConnection.close()");
closeSocketAndStreams();
}
/**
* Returns the httpConnectionManager.
* @return HttpConnectionManager
*/
public HttpConnectionManager getHttpConnectionManager() {
return httpConnectionManager;
}
/**
* Sets the httpConnectionManager.
* @param httpConnectionManager The httpConnectionManager to set
*/
public void setHttpConnectionManager(HttpConnectionManager httpConnectionManager) {
this.httpConnectionManager = httpConnectionManager;
}
/**
* Releases the connection. If the connection is locked or does not have a connection
* manager associated with it, this method has no effect. Note that it is completely safe
* to call this method multiple times.
*/
public void releaseConnection() {
LOG.trace("enter HttpConnection.releaseConnection()");
if (locked) {
LOG.debug("Connection is locked. Call to releaseConnection() ignored.");
} else if (httpConnectionManager != null) {
LOG.debug("Releasing connection back to connection manager.");
httpConnectionManager.releaseConnection(this);
} else {
LOG.warn("HttpConnectionManager is null. Connection cannot be released.");
}
}
/**
* Tests if the connection is locked. Locked connections cannot be released.
* An attempt to release a locked connection will have no effect.
*
* @return <tt>true</tt> if the connection is locked, <tt>false</tt> otherwise.
*
* @since 3.0
*/
protected boolean isLocked() {
return locked;
}
/**
* Locks or unlocks the connection. Locked connections cannot be released.
* An attempt to release a locked connection will have no effect.
*
* @param locked <tt>true</tt> to lock the connection, <tt>false</tt> to unlock
* the connection.
*
* @since 3.0
*/
protected void setLocked(boolean locked) {
this.locked = locked;
}
// ------------------------------------------------------ Protected Methods
/**
* Closes everything out.
*/
protected void closeSocketAndStreams() {
LOG.trace("enter HttpConnection.closeSockedAndStreams()");
isOpen = false;
// no longer care about previous responses...
lastResponseInputStream = null;
if (null != outputStream) {
OutputStream temp = outputStream;
outputStream = null;
try {
temp.close();
} catch (Exception ex) {
LOG.debug("Exception caught when closing output", ex);
// ignored
}
}
if (null != inputStream) {
InputStream temp = inputStream;
inputStream = null;
try {
temp.close();
} catch (Exception ex) {
LOG.debug("Exception caught when closing input", ex);
// ignored
}
}
if (null != socket) {
Socket temp = socket;
socket = null;
try {
temp.close();
} catch (Exception ex) {
LOG.debug("Exception caught when closing socket", ex);
// ignored
}
}
tunnelEstablished = false;
usingSecureSocket = false;
}
/**
* Throws an {@link IllegalStateException} if the connection is already open.
*
* @throws IllegalStateException if connected
*/
protected void assertNotOpen() throws IllegalStateException {
if (isOpen) {
throw new IllegalStateException("Connection is open");
}
}
/**
* Throws an {@link IllegalStateException} if the connection is not open.
*
* @throws IllegalStateException if not connected
*/
protected void assertOpen() throws IllegalStateException {
if (!isOpen) {
throw new IllegalStateException("Connection is not open");
}
}
/**
* Gets the socket's sendBufferSize.
*
* @return the size of the buffer for the socket OutputStream, -1 if the value
* has not been set and the socket has not been opened
*
* @throws SocketException if an error occurs while getting the socket value
*
* @see Socket#getSendBufferSize()
*/
public int getSendBufferSize() throws SocketException {
if (socket == null) {
return -1;
} else {
return socket.getSendBufferSize();
}
}
/**
* Sets the socket's sendBufferSize.
*
* @param sendBufferSize the size to set for the socket OutputStream
*
* @throws SocketException if an error occurs while setting the socket value
*
* @see Socket#setSendBufferSize(int)
*
* @deprecated Use {@link HttpConnectionParams#setSendBufferSize(int)},
* {@link HttpConnection#getParams()}.
*/
public void setSendBufferSize(int sendBufferSize) throws SocketException {
this.params.setSendBufferSize(sendBufferSize);
}
// ------------------------------------------------------- Static Variable
/** <tt>"\r\n"</tt>, as bytes. */
private static final byte[] CRLF = new byte[] {(byte) 13, (byte) 10};
/** Log object for this class. */
private static final Log LOG = LogFactory.getLog(HttpConnection.class);
// ----------------------------------------------------- Instance Variables
/** My host. */
private String hostName = null;
/** My port. */
private int portNumber = -1;
/** My proxy host. */
private String proxyHostName = null;
/** My proxy port. */
private int proxyPortNumber = -1;
/** My client Socket. */
private Socket socket = null;
/** My InputStream. */
private InputStream inputStream = null;
/** My OutputStream. */
private OutputStream outputStream = null;
/** An {@link InputStream} for the response to an individual request. */
private InputStream lastResponseInputStream = null;
/** Whether or not the connection is connected. */
protected boolean isOpen = false;
/** the protocol being used */
private Protocol protocolInUse;
/** Collection of HTTP parameters associated with this HTTP connection*/
private HttpConnectionParams params = new HttpConnectionParams();
/** flag to indicate if this connection can be released, if locked the connection cannot be
* released */
private boolean locked = false;
/** Whether or not the socket is a secure one. */
private boolean usingSecureSocket = false;
/** Whether the connection is open via a secure tunnel or not */
private boolean tunnelEstablished = false;
/** the connection manager that created this connection or null */
private HttpConnectionManager httpConnectionManager;
/** The local interface on which the connection is created, or null for the default */
private InetAddress localAddress;
}
|