View Javadoc

    1   /*
    2    * $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 $
    3    * $Revision: 480424 $
    4    * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
    5    *
    6    * ====================================================================
    7    *
    8    *  Licensed to the Apache Software Foundation (ASF) under one or more
    9    *  contributor license agreements.  See the NOTICE file distributed with
    10   *  this work for additional information regarding copyright ownership.
    11   *  The ASF licenses this file to You under the Apache License, Version 2.0
    12   *  (the "License"); you may not use this file except in compliance with
    13   *  the License.  You may obtain a copy of the License at
    14   *
    15   *      http://www.apache.org/licenses/LICENSE-2.0
    16   *
    17   *  Unless required by applicable law or agreed to in writing, software
    18   *  distributed under the License is distributed on an "AS IS" BASIS,
    19   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    20   *  See the License for the specific language governing permissions and
    21   *  limitations under the License.
    22   * ====================================================================
    23   *
    24   * This software consists of voluntary contributions made by many
    25   * individuals on behalf of the Apache Software Foundation.  For more
    26   * information on the Apache Software Foundation, please see
    27   * <http://www.apache.org/>.
    28   *
    29   */
    30
    31  package org.apache.commons.httpclient;
    32
    33  import java.io.BufferedInputStream;
    34  import java.io.BufferedOutputStream;
    35  import java.io.IOException;
    36  import java.io.InputStream;
    37  import java.io.InterruptedIOException;
    38  import java.io.OutputStream;
    39  import java.lang.reflect.Method;
    40  import java.net.InetAddress;
    41  import java.net.Socket;
    42  import java.net.SocketException;
    43
    44  import org.apache.commons.httpclient.params.HttpConnectionParams;
    45  import org.apache.commons.httpclient.protocol.Protocol;
    46  import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
    47  import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
    48  import org.apache.commons.httpclient.util.EncodingUtil;
    49  import org.apache.commons.httpclient.util.ExceptionUtil;
    50  import org.apache.commons.logging.Log;
    51  import org.apache.commons.logging.LogFactory;
    52
    53  /**
    54   * An abstraction of an HTTP {@link InputStream} and {@link OutputStream}
    55   * pair, together with the relevant attributes.
    56   * <p>
    57   * The following options are set on the socket before getting the input/output
    58   * streams in the {@link #open()} method:
    59   * <table border=1><tr>
    60   *    <th>Socket Method
    61   *    <th>Sockets Option
    62   *    <th>Configuration
    63   * </tr><tr>
    64   *    <td>{@link java.net.Socket#setTcpNoDelay(boolean)}
    65   *    <td>SO_NODELAY
    66   *    <td>{@link HttpConnectionParams#setTcpNoDelay(boolean)}
    67   * </tr><tr>
    68   *    <td>{@link java.net.Socket#setSoTimeout(int)}
    69   *    <td>SO_TIMEOUT
    70   *    <td>{@link HttpConnectionParams#setSoTimeout(int)}
    71   * </tr><tr>
    72   *    <td>{@link java.net.Socket#setSendBufferSize(int)}
    73   *    <td>SO_SNDBUF
    74   *    <td>{@link HttpConnectionParams#setSendBufferSize(int)}
    75   * </tr><tr>
    76   *    <td>{@link java.net.Socket#setReceiveBufferSize(int)}
    77   *    <td>SO_RCVBUF
    78   *    <td>{@link HttpConnectionParams#setReceiveBufferSize(int)}
    79   * </tr></table>
    80   *
    81   * @author Rod Waldhoff
    82   * @author Sean C. Sullivan
    83   * @author Ortwin Glueck
    84   * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
    85   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
    86   * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
    87   * @author Michael Becke
    88   * @author Eric E Johnson
    89   * @author Laura Werner
    90   *
    91   * @version   $Revision: 480424 $ $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
    92   */
    93  public class HttpConnection {
    94
    95      // ----------------------------------------------------------- Constructors
    96
    97      /**
    98       * Creates a new HTTP connection for the given host and port.
    99       *
    100      * @param host the host to connect to
    101      * @param port the port to connect to
    102      */
    103     public HttpConnection(String host, int port) {
    104         this(null, -1, host, null, port, Protocol.getProtocol("http"));
    105     }
    106
    107     /**
    108      * Creates a new HTTP connection for the given host and port
    109      * using the given protocol.
    110      *
    111      * @param host the host to connect to
    112      * @param port the port to connect to
    113      * @param protocol the protocol to use
    114      */
    115     public HttpConnection(String host, int port, Protocol protocol) {
    116         this(null, -1, host, null, port, protocol);
    117     }
    118
    119     /**
    120      * Creates a new HTTP connection for the given host with the virtual
    121      * alias and port using given protocol.
    122      *
    123      * @param host the host to connect to
    124      * @param virtualHost the virtual host requests will be sent to
    125      * @param port the port to connect to
    126      * @param protocol the protocol to use
    127      */
    128     public HttpConnection(String host, String virtualHost, int port, Protocol protocol) {
    129         this(null, -1, host, virtualHost, port, protocol);
    130     }
    131
    132     /**
    133      * Creates a new HTTP connection for the given host and port via the
    134      * given proxy host and port using the default protocol.
    135      *
    136      * @param proxyHost the host to proxy via
    137      * @param proxyPort the port to proxy via
    138      * @param host the host to connect to
    139      * @param port the port to connect to
    140      */
    141     public HttpConnection(
    142         String proxyHost,
    143         int proxyPort,
    144         String host,
    145         int port) {
    146         this(proxyHost, proxyPort, host, null, port, Protocol.getProtocol("http"));
    147     }
    148
    149     /**
    150      * Creates a new HTTP connection for the given host configuration.
    151      *
    152      * @param hostConfiguration the host/proxy/protocol to use
    153      */
    154     public HttpConnection(HostConfiguration hostConfiguration) {
    155         this(hostConfiguration.getProxyHost(),
    156              hostConfiguration.getProxyPort(),
    157              hostConfiguration.getHost(),
    158              hostConfiguration.getPort(),
    159              hostConfiguration.getProtocol());
    160         this.localAddress = hostConfiguration.getLocalAddress();
    161     }
    162
    163     /**
    164      * Creates a new HTTP connection for the given host with the virtual
    165      * alias and port via the given proxy host and port using the given
    166      * protocol.
    167      *
    168      * @param proxyHost the host to proxy via
    169      * @param proxyPort the port to proxy via
    170      * @param host the host to connect to. Parameter value must be non-null.
    171      * @param virtualHost No longer applicable.
    172      * @param port the port to connect to
    173      * @param protocol The protocol to use. Parameter value must be non-null.
    174      *
    175      * @deprecated use #HttpConnection(String, int, String, int, Protocol)
    176      */
    177     public HttpConnection(
    178         String proxyHost,
    179         int proxyPort,
    180         String host,
    181         String virtualHost,
    182         int port,
    183         Protocol protocol) {
    184     	this(proxyHost, proxyPort, host, port, protocol);
    185     }
    186
    187     /**
    188      * Creates a new HTTP connection for the given host with the virtual
    189      * alias and port via the given proxy host and port using the given
    190      * protocol.
    191      *
    192      * @param proxyHost the host to proxy via
    193      * @param proxyPort the port to proxy via
    194      * @param host the host to connect to. Parameter value must be non-null.
    195      * @param port the port to connect to
    196      * @param protocol The protocol to use. Parameter value must be non-null.
    197      */
    198     public HttpConnection(
    199         String proxyHost,
    200         int proxyPort,
    201         String host,
    202         int port,
    203         Protocol protocol) {
    204
    205         if (host == null) {
    206             throw new IllegalArgumentException("host parameter is null");
    207         }
    208         if (protocol == null) {
    209             throw new IllegalArgumentException("protocol is null");
    210         }
    211
    212         proxyHostName = proxyHost;
    213         proxyPortNumber = proxyPort;
    214         hostName = host;
    215         portNumber = protocol.resolvePort(port);
    216         protocolInUse = protocol;
    217     }
    218
    219     // ------------------------------------------ Attribute Setters and Getters
    220
    221     /**
    222      * Returns the connection socket.
    223      *
    224      * @return the socket.
    225      *
    226      * @since 3.0
    227      */
    228     protected Socket getSocket() {
    229         return this.socket;
    230     }
    231
    232     /**
    233      * Returns the host.
    234      *
    235      * @return the host.
    236      */
    237     public String getHost() {
    238         return hostName;
    239     }
    240
    241     /**
    242      * Sets the host to connect to.
    243      *
    244      * @param host the host to connect to. Parameter value must be non-null.
    245      * @throws IllegalStateException if the connection is already open
    246      */
    247     public void setHost(String host) throws IllegalStateException {
    248         if (host == null) {
    249             throw new IllegalArgumentException("host parameter is null");
    250         }
    251         assertNotOpen();
    252         hostName = host;
    253     }
    254
    255     /**
    256      * Returns the target virtual host.
    257      *
    258      * @return the virtual host.
    259      *
    260      * @deprecated no longer applicable
    261      */
    262
    263     public String getVirtualHost() {
    264         return this.hostName;
    265     }
    266
    267     /**
    268      * Sets the virtual host to target.
    269      *
    270      * @param host the virtual host name that should be used instead of
    271      *        physical host name when sending HTTP requests. Virtual host
    272      *        name can be set to <tt> null</tt> if virtual host name is not
    273      *        to be used
    274      *
    275      * @throws IllegalStateException if the connection is already open
    276      *
    277      * @deprecated no longer applicable
    278      */
    279
    280     public void setVirtualHost(String host) throws IllegalStateException {
    281         assertNotOpen();
    282     }
    283
    284     /**
    285      * Returns the port of the host.
    286      *
    287      * If the port is -1 (or less than 0) the default port for
    288      * the current protocol is returned.
    289      *
    290      * @return the port.
    291      */
    292     public int getPort() {
    293         if (portNumber < 0) {
    294             return isSecure() ? 443 : 80;
    295         } else {
    296             return portNumber;
    297         }
    298     }
    299
    300     /**
    301      * Sets the port to connect to.
    302      *
    303      * @param port the port to connect to
    304      *
    305      * @throws IllegalStateException if the connection is already open
    306      */
    307     public void setPort(int port) throws IllegalStateException {
    308         assertNotOpen();
    309         portNumber = port;
    310     }
    311
    312     /**
    313      * Returns the proxy host.
    314      *
    315      * @return the proxy host.
    316      */
    317     public String getProxyHost() {
    318         return proxyHostName;
    319     }
    320
    321     /**
    322      * Sets the host to proxy through.
    323      *
    324      * @param host the host to proxy through.
    325      *
    326      * @throws IllegalStateException if the connection is already open
    327      */
    328     public void setProxyHost(String host) throws IllegalStateException {
    329         assertNotOpen();
    330         proxyHostName = host;
    331     }
    332
    333     /**
    334      * Returns the port of the proxy host.
    335      *
    336      * @return the proxy port.
    337      */
    338     public int getProxyPort() {
    339         return proxyPortNumber;
    340     }
    341
    342     /**
    343      * Sets the port of the host to proxy through.
    344      *
    345      * @param port the port of the host to proxy through.
    346      *
    347      * @throws IllegalStateException if the connection is already open
    348      */
    349     public void setProxyPort(int port) throws IllegalStateException {
    350         assertNotOpen();
    351         proxyPortNumber = port;
    352     }
    353
    354     /**
    355      * Returns <tt>true</tt> if the connection is established over
    356      * a secure protocol.
    357      *
    358      * @return <tt>true</tt> if connected over a secure protocol.
    359      */
    360     public boolean isSecure() {
    361         return protocolInUse.isSecure();
    362     }
    363
    364     /**
    365      * Returns the protocol used to establish the connection.
    366      * @return The protocol
    367      */
    368     public Protocol getProtocol() {
    369         return protocolInUse;
    370     }
    371
    372     /**
    373      * Sets the protocol used to establish the connection
    374      *
    375      * @param protocol The protocol to use.
    376      *
    377      * @throws IllegalStateException if the connection is already open
    378      */
    379     public void setProtocol(Protocol protocol) {
    380         assertNotOpen();
    381
    382         if (protocol == null) {
    383             throw new IllegalArgumentException("protocol is null");
    384         }
    385
    386         protocolInUse = protocol;
    387
    388     }
    389
    390     /**
    391      * Return the local address used when creating the connection.
    392      * If <tt>null</tt>, the default address is used.
    393      *
    394      * @return InetAddress the local address to be used when creating Sockets
    395      */
    396     public InetAddress getLocalAddress() {
    397         return this.localAddress;
    398     }
    399
    400     /**
    401      * Set the local address used when creating the connection.
    402      * If unset or <tt>null</tt>, the default address is used.
    403      *
    404      * @param localAddress the local address to use
    405      */
    406     public void setLocalAddress(InetAddress localAddress) {
    407         assertNotOpen();
    408         this.localAddress = localAddress;
    409     }
    410
    411     /**
    412      * Tests if the connection is open.
    413      *
    414      * @return <code>true</code> if the connection is open
    415      */
    416     public boolean isOpen() {
    417         return isOpen;
    418     }
    419
    420     /**
    421      * Closes the connection if stale.
    422      *
    423      * @return <code>true</code> if the connection was stale and therefore closed,
    424      * <code>false</code> otherwise.
    425      *
    426      * @see #isStale()
    427      *
    428      * @since 3.0
    429      */
    430     public boolean closeIfStale() throws IOException {
    431         if (isOpen && isStale()) {
    432             LOG.debug("Connection is stale, closing...");
    433             close();
    434             return true;
    435         }
    436         return false;
    437     }
    438
    439     /**
    440      * Tests if stale checking is enabled.
    441      *
    442      * @return <code>true</code> if enabled
    443      *
    444      * @see #isStale()
    445      *
    446      * @deprecated Use {@link HttpConnectionParams#isStaleCheckingEnabled()},
    447      * {@link HttpConnection#getParams()}.
    448      */
    449     public boolean isStaleCheckingEnabled() {
    450         return this.params.isStaleCheckingEnabled();
    451     }
    452
    453     /**
    454      * Sets whether or not isStale() will be called when testing if this connection is open.
    455      *
    456      * <p>Setting this flag to <code>false</code> will increase performance when reusing
    457      * connections, but it will also make them less reliable.  Stale checking ensures that
    458      * connections are viable before they are used.  When set to <code>false</code> some
    459      * method executions will result in IOExceptions and they will have to be retried.</p>
    460      *
    461      * @param staleCheckEnabled <code>true</code> to enable isStale()
    462      *
    463      * @see #isStale()
    464      * @see #isOpen()
    465      *
    466      * @deprecated Use {@link HttpConnectionParams#setStaleCheckingEnabled(boolean)},
    467      * {@link HttpConnection#getParams()}.
    468      */
    469     public void setStaleCheckingEnabled(boolean staleCheckEnabled) {
    470         this.params.setStaleCheckingEnabled(staleCheckEnabled);
    471     }
    472
    473     /**
    474      * Determines whether this connection is "stale", which is to say that either
    475      * it is no longer open, or an attempt to read the connection would fail.
    476      *
    477      * <p>Unfortunately, due to the limitations of the JREs prior to 1.4, it is
    478      * not possible to test a connection to see if both the read and write channels
    479      * are open - except by reading and writing.  This leads to a difficulty when
    480      * some connections leave the "write" channel open, but close the read channel
    481      * and ignore the request.  This function attempts to ameliorate that
    482      * problem by doing a test read, assuming that the caller will be doing a
    483      * write followed by a read, rather than the other way around.
    484      * </p>
    485      *
    486      * <p>To avoid side-effects, the underlying connection is wrapped by a
    487      * {@link BufferedInputStream}, so although data might be read, what is visible
    488      * to clients of the connection will not change with this call.</p.
    489      *
    490      * @throws IOException if the stale connection test is interrupted.
    491      *
    492      * @return <tt>true</tt> if the connection is already closed, or a read would
    493      * fail.
    494      */
    495     protected boolean isStale() throws IOException {
    496         boolean isStale = true;
    497         if (isOpen) {
    498             // the connection is open, but now we have to see if we can read it
    499             // assume the connection is not stale.
    500             isStale = false;
    501             try {
    502                 if (inputStream.available() <= 0) {
    503                     try {
    504                         socket.setSoTimeout(1);
    505                         inputStream.mark(1);
    506                         int byteRead = inputStream.read();
    507                         if (byteRead == -1) {
    508                             // again - if the socket is reporting all data read,
    509                             // probably stale
    510                             isStale = true;
    511                         } else {
    512                             inputStream.reset();
    513                         }
    514                     } finally {
    515                         socket.setSoTimeout(this.params.getSoTimeout());
    516                     }
    517                 }
    518             } catch (InterruptedIOException e) {
    519                 if (!ExceptionUtil.isSocketTimeoutException(e)) {
    520                     throw e;
    521                 }
    522                 // aha - the connection is NOT stale - continue on!
    523             } catch (IOException e) {
    524                 // oops - the connection is stale, the read or soTimeout failed.
    525                 LOG.debug(
    526                     "An error occurred while reading from the socket, is appears to be stale",
    527                     e
    528                 );
    529                 isStale = true;
    530             }
    531         }
    532
    533         return isStale;
    534     }
    535
    536     /**
    537      * Returns <tt>true</tt> if the connection is established via a proxy,
    538      * <tt>false</tt> otherwise.
    539      *
    540      * @return <tt>true</tt> if a proxy is used to establish the connection,
    541      * <tt>false</tt> otherwise.
    542      */
    543     public boolean isProxied() {
    544         return (!(null == proxyHostName || 0 >= proxyPortNumber));
    545     }
    546
    547     /**
    548      * Set the state to keep track of the last response for the last request.
    549      *
    550      * <p>The connection managers use this to ensure that previous requests are
    551      * properly closed before a new request is attempted.  That way, a GET
    552      * request need not be read in its entirety before a new request is issued.
    553      * Instead, this stream can be closed as appropriate.</p>
    554      *
    555      * @param inStream  The stream associated with an HttpMethod.
    556      */
    557     public void setLastResponseInputStream(InputStream inStream) {
    558         lastResponseInputStream = inStream;
    559     }
    560
    561     /**
    562      * Returns the stream used to read the last response's body.
    563      *
    564      * <p>Clients will generally not need to call this function unless
    565      * using HttpConnection directly, instead of calling {@link HttpClient#executeMethod}.
    566      * For those clients, call this function, and if it returns a non-null stream,
    567      * close the stream before attempting to execute a method.  Note that
    568      * calling "close" on the stream returned by this function <i>may</i> close
    569      * the connection if the previous response contained a "Connection: close" header. </p>
    570      *
    571      * @return An {@link InputStream} corresponding to the body of the last
    572      *  response.
    573      */
    574     public InputStream getLastResponseInputStream() {
    575         return lastResponseInputStream;
    576     }
    577
    578     // --------------------------------------------------- Other Public Methods
    579
    580     /**
    581      * Returns {@link HttpConnectionParams HTTP protocol parameters} associated with this method.
    582      *
    583      * @return HTTP parameters.
    584      *
    585      * @since 3.0
    586      */
    587     public HttpConnectionParams getParams() {
    588         return this.params;
    589     }
    590
    591     /**
    592      * Assigns {@link HttpConnectionParams HTTP protocol parameters} for this method.
    593      *
    594      * @since 3.0
    595      *
    596      * @see HttpConnectionParams
    597      */
    598     public void setParams(final HttpConnectionParams params) {
    599         if (params == null) {
    600             throw new IllegalArgumentException("Parameters may not be null");
    601         }
    602         this.params = params;
    603     }
    604
    605     /**
    606      * Set the {@link Socket}'s timeout, via {@link Socket#setSoTimeout}.  If the
    607      * connection is already open, the SO_TIMEOUT is changed.  If no connection
    608      * is open, then subsequent connections will use the timeout value.
    609      * <p>
    610      * Note: This is not a connection timeout but a timeout on network traffic!
    611      *
    612      * @param timeout the timeout value
    613      * @throws SocketException - if there is an error in the underlying
    614      * protocol, such as a TCP error.
    615      *
    616      * @deprecated Use {@link HttpConnectionParams#setSoTimeout(int)},
    617      * {@link HttpConnection#getParams()}.
    618      */
    619     public void setSoTimeout(int timeout)
    620         throws SocketException, IllegalStateException {
    621         this.params.setSoTimeout(timeout);
    622         if (this.socket != null) {
    623             this.socket.setSoTimeout(timeout);
    624         }
    625     }
    626
    627     /**
    628      * Sets <code>SO_TIMEOUT</code> value directly on the underlying {@link Socket socket}.
    629      * This method does not change the default read timeout value set via
    630      * {@link HttpConnectionParams}.
    631      *
    632      * @param timeout the timeout value
    633      * @throws SocketException - if there is an error in the underlying
    634      * protocol, such as a TCP error.
    635      * @throws IllegalStateException if not connected
    636      *
    637      * @since 3.0
    638      */
    639     public void setSocketTimeout(int timeout)
    640         throws SocketException, IllegalStateException {
    641         assertOpen();
    642         if (this.socket != null) {
    643             this.socket.setSoTimeout(timeout);
    644         }
    645     }
    646
    647     /**
    648      * Returns the {@link Socket}'s timeout, via {@link Socket#getSoTimeout}, if the
    649      * connection is already open. If no connection is open, return the value subsequent
    650      * connection will use.
    651      * <p>
    652      * Note: This is not a connection timeout but a timeout on network traffic!
    653      *
    654      * @return the timeout value
    655      *
    656      * @deprecated Use {@link HttpConnectionParams#getSoTimeout()},
    657      * {@link HttpConnection#getParams()}.
    658      */
    659     public int getSoTimeout() throws SocketException {
    660         return this.params.getSoTimeout();
    661     }
    662
    663     /**
    664      * Sets the connection timeout. This is the maximum time that may be spent
    665      * until a connection is established. The connection will fail after this
    666      * amount of time.
    667      * @param timeout The timeout in milliseconds. 0 means timeout is not used.
    668      *
    669      * @deprecated Use {@link HttpConnectionParams#setConnectionTimeout(int)},
    670      * {@link HttpConnection#getParams()}.
    671      */
    672     public void setConnectionTimeout(int timeout) {
    673         this.params.setConnectionTimeout(timeout);
    674     }
    675
    676     /**
    677      * Establishes a connection to the specified host and port
    678      * (via a proxy if specified).
    679      * The underlying socket is created from the {@link ProtocolSocketFactory}.
    680      *
    681      * @throws IOException if an attempt to establish the connection results in an
    682      *   I/O error.
    683      */
    684     public void open() throws IOException {
    685         LOG.trace("enter HttpConnection.open()");
    686
    687         final String host = (proxyHostName == null) ? hostName : proxyHostName;
    688         final int port = (proxyHostName == null) ? portNumber : proxyPortNumber;
    689         assertNotOpen();
    690
    691         if (LOG.isDebugEnabled()) {
    692             LOG.debug("Open connection to " + host + ":" + port);
    693         }
    694
    695         try {
    696             if (this.socket == null) {
    697                 usingSecureSocket = isSecure() && !isProxied();
    698                 // use the protocol's socket factory unless this is a secure
    699                 // proxied connection
    700                 ProtocolSocketFactory socketFactory = null;
    701                 if (isSecure() && isProxied()) {
    702                     Protocol defaultprotocol = Protocol.getProtocol("http");
    703                     socketFactory = defaultprotocol.getSocketFactory();
    704                 } else {
    705                     socketFactory = this.protocolInUse.getSocketFactory();
    706                 }
    707                 this.socket = socketFactory.createSocket(
    708                             host, port,
    709                             localAddress, 0,
    710                             this.params);
    711             }
    712
    713             /*
    714             "Nagling has been broadly implemented across networks,
    715             including the Internet, and is generally performed by default
    716             - although it is sometimes considered to be undesirable in
    717             highly interactive environments, such as some client/server
    718             situations. In such cases, nagling may be turned off through
    719             use of the TCP_NODELAY sockets option." */
    720
    721             socket.setTcpNoDelay(this.params.getTcpNoDelay());
    722             socket.setSoTimeout(this.params.getSoTimeout());
    723
    724             int linger = this.params.getLinger();
    725             if (linger >= 0) {
    726                 socket.setSoLinger(linger > 0, linger);
    727             }
    728
    729             int sndBufSize = this.params.getSendBufferSize();
    730             if (sndBufSize >= 0) {
    731                 socket.setSendBufferSize(sndBufSize);
    732             }
    733             int rcvBufSize = this.params.getReceiveBufferSize();
    734             if (rcvBufSize >= 0) {
    735                 socket.setReceiveBufferSize(rcvBufSize);
    736             }
    737             int outbuffersize = socket.getSendBufferSize();
    738             if ((outbuffersize > 2048) || (outbuffersize <= 0)) {
    739                 outbuffersize = 2048;
    740             }
    741             int inbuffersize = socket.getReceiveBufferSize();
    742             if ((inbuffersize > 2048) || (inbuffersize <= 0)) {
    743                 inbuffersize = 2048;
    744             }
    745             inputStream = new BufferedInputStream(socket.getInputStream(), inbuffersize);
    746             outputStream = new BufferedOutputStream(socket.getOutputStream(), outbuffersize);
    747             isOpen = true;
    748         } catch (IOException e) {
    749             // Connection wasn't opened properly
    750             // so close everything out
    751             closeSocketAndStreams();
    752             throw e;
    753         }
    754     }
    755
    756     /**
    757      * Instructs the proxy to establish a secure tunnel to the host. The socket will
    758      * be switched to the secure socket. Subsequent communication is done via the secure
    759      * socket. The method can only be called once on a proxied secure connection.
    760      *
    761      * @throws IllegalStateException if connection is not secure and proxied or
    762      * if the socket is already secure.
    763      * @throws IOException if an attempt to establish the secure tunnel results in an
    764      *   I/O error.
    765      */
    766     public void tunnelCreated() throws IllegalStateException, IOException {
    767         LOG.trace("enter HttpConnection.tunnelCreated()");
    768
    769         if (!isSecure() || !isProxied()) {
    770             throw new IllegalStateException(
    771                 "Connection must be secure "
    772                     + "and proxied to use this feature");
    773         }
    774
    775         if (usingSecureSocket) {
    776             throw new IllegalStateException("Already using a secure socket");
    777         }
    778
    779         if (LOG.isDebugEnabled()) {
    780             LOG.debug("Secure tunnel to " + this.hostName + ":" + this.portNumber);
    781         }
    782
    783         SecureProtocolSocketFactory socketFactory =
    784             (SecureProtocolSocketFactory) protocolInUse.getSocketFactory();
    785
    786         socket = socketFactory.createSocket(socket, hostName, portNumber, true);
    787         int sndBufSize = this.params.getSendBufferSize();
    788         if (sndBufSize >= 0) {
    789             socket.setSendBufferSize(sndBufSize);
    790         }
    791         int rcvBufSize = this.params.getReceiveBufferSize();
    792         if (rcvBufSize >= 0) {
    793             socket.setReceiveBufferSize(rcvBufSize);
    794         }
    795         int outbuffersize = socket.getSendBufferSize();
    796         if (outbuffersize > 2048) {
    797             outbuffersize = 2048;
    798         }
    799         int inbuffersize = socket.getReceiveBufferSize();
    800         if (inbuffersize > 2048) {
    801             inbuffersize = 2048;
    802         }
    803         inputStream = new BufferedInputStream(socket.getInputStream(), inbuffersize);
    804         outputStream = new BufferedOutputStream(socket.getOutputStream(), outbuffersize);
    805         usingSecureSocket = true;
    806         tunnelEstablished = true;
    807     }
    808
    809     /**
    810      * Indicates if the connection is completely transparent from end to end.
    811      *
    812      * @return true if conncetion is not proxied or tunneled through a transparent
    813      * proxy; false otherwise.
    814      */
    815     public boolean isTransparent() {
    816         return !isProxied() || tunnelEstablished;
    817     }
    818
    819     /**
    820      * Flushes the output request stream.  This method should be called to
    821      * ensure that data written to the request OutputStream is sent to the server.
    822      *
    823      * @throws IOException if an I/O problem occurs
    824      */
    825     public void flushRequestOutputStream() throws IOException {
    826         LOG.trace("enter HttpConnection.flushRequestOutputStream()");
    827         assertOpen();
    828         outputStream.flush();
    829     }
    830
    831     /**
    832      * Returns an {@link OutputStream} suitable for writing the request.
    833      *
    834      * @throws IllegalStateException if the connection is not open
    835      * @throws IOException if an I/O problem occurs
    836      * @return a stream to write the request to
    837      */
    838     public OutputStream getRequestOutputStream()
    839         throws IOException, IllegalStateException {
    840         LOG.trace("enter HttpConnection.getRequestOutputStream()");
    841         assertOpen();
    842         OutputStream out = this.outputStream;
    843         if (Wire.CONTENT_WIRE.enabled()) {
    844             out = new WireLogOutputStream(out, Wire.CONTENT_WIRE);
    845         }
    846         return out;
    847     }
    848
    849     /**
    850      * Return a {@link InputStream} suitable for reading the response.
    851      * @return InputStream The response input stream.
    852      * @throws IOException If an IO problem occurs
    853      * @throws IllegalStateException If the connection isn't open.
    854      */
    855     public InputStream getResponseInputStream()
    856         throws IOException, IllegalStateException {
    857         LOG.trace("enter HttpConnection.getResponseInputStream()");
    858         assertOpen();
    859         return inputStream;
    860     }
    861
    862     /**
    863      * Tests if input data avaialble. This method returns immediately
    864      * and does not perform any read operations on the input socket
    865      *
    866      * @return boolean <tt>true</tt> if input data is available,
    867      *                 <tt>false</tt> otherwise.
    868      *
    869      * @throws IOException If an IO problem occurs
    870      * @throws IllegalStateException If the connection isn't open.
    871      */
    872     public boolean isResponseAvailable()
    873         throws IOException {
    874         LOG.trace("enter HttpConnection.isResponseAvailable()");
    875         if (this.isOpen) {
    876             return this.inputStream.available() > 0;
    877         } else {
    878             return false;
    879         }
    880     }
    881
    882     /**
    883      * Tests if input data becomes available within the given period time in milliseconds.
    884      *
    885      * @param timeout The number milliseconds to wait for input data to become available
    886      * @return boolean <tt>true</tt> if input data is availble,
    887      *                 <tt>false</tt> otherwise.
    888      *
    889      * @throws IOException If an IO problem occurs
    890      * @throws IllegalStateException If the connection isn't open.
    891      */
    892     public boolean isResponseAvailable(int timeout)
    893         throws IOException {
    894         LOG.trace("enter HttpConnection.isResponseAvailable(int)");
    895         assertOpen();
    896         boolean result = false;
    897         if (this.inputStream.available() > 0) {
    898             result = true;
    899         } else {
    900             try {
    901                 this.socket.setSoTimeout(timeout);
    902                 inputStream.mark(1);
    903                 int byteRead = inputStream.read();
    904                 if (byteRead != -1) {
    905                     inputStream.reset();
    906                     LOG.debug("Input data available");
    907                     result = true;
    908                 } else {
    909                     LOG.debug("Input data not available");
    910                 }
    911             } catch (InterruptedIOException e) {
    912                 if (!ExceptionUtil.isSocketTimeoutException(e)) {
    913                     throw e;
    914                 }
    915                 if (LOG.isDebugEnabled()) {
    916                     LOG.debug("Input data not available after " + timeout + " ms");
    917                 }
    918             } finally {
    919                 try {
    920                     socket.setSoTimeout(this.params.getSoTimeout());
    921                 } catch (IOException ioe) {
    922                     LOG.debug("An error ocurred while resetting soTimeout, we will assume that"
    923                         + " no response is available.",
    924                         ioe);
    925                     result = false;
    926                 }
    927             }
    928         }
    929         return result;
    930     }
    931
    932     /**
    933      * Writes the specified bytes to the output stream.
    934      *
    935      * @param data the data to be written
    936      * @throws IllegalStateException if not connected
    937      * @throws IOException if an I/O problem occurs
    938      * @see #write(byte[],int,int)
    939      */
    940     public void write(byte[] data)
    941         throws IOException, IllegalStateException {
    942         LOG.trace("enter HttpConnection.write(byte[])");
    943         this.write(data, 0, data.length);
    944     }
    945
    946     /**
    947      * Writes <i>length</i> bytes in <i>data</i> starting at
    948      * <i>offset</i> to the output stream.
    949      *
    950      * The general contract for
    951      * write(b, off, len) is that some of the bytes in the array b are written
    952      * to the output stream in order; element b[off] is the first byte written
    953      * and b[off+len-1] is the last byte written by this operation.
    954      *
    955      * @param data array containing the data to be written.
    956      * @param offset the start offset in the data.
    957      * @param length the number of bytes to write.
    958      * @throws IllegalStateException if not connected
    959      * @throws IOException if an I/O problem occurs
    960      */
    961     public void write(byte[] data, int offset, int length)
    962         throws IOException, IllegalStateException {
    963         LOG.trace("enter HttpConnection.write(byte[], int, int)");
    964
    965         if (offset < 0) {
    966             throw new IllegalArgumentException("Array offset may not be negative");
    967         }
    968         if (length < 0) {
    969             throw new IllegalArgumentException("Array length may not be negative");
    970         }
    971         if (offset + length > data.length) {
    972             throw new IllegalArgumentException("Given offset and length exceed the array length");
    973         }
    974         assertOpen();
    975         this.outputStream.write(data, offset, length);
    976     }
    977
    978     /**
    979      * Writes the specified bytes, followed by <tt>"\r\n".getBytes()</tt> to the
    980      * output stream.
    981      *
    982      * @param data the bytes to be written
    983      * @throws IllegalStateException if the connection is not open
    984      * @throws IOException if an I/O problem occurs
    985      */
    986     public void writeLine(byte[] data)
    987         throws IOException, IllegalStateException {
    988         LOG.trace("enter HttpConnection.writeLine(byte[])");
    989         write(data);
    990         writeLine();
    991     }
    992
    993     /**
    994      * Writes <tt>"\r\n".getBytes()</tt> to the output stream.
    995      *
    996      * @throws IllegalStateException if the connection is not open
    997      * @throws IOException if an I/O problem occurs
    998      */
    999     public void writeLine()
    1000         throws IOException, IllegalStateException {
    1001         LOG.trace("enter HttpConnection.writeLine()");
    1002         write(CRLF);
    1003     }
    1004
    1005     /**
    1006      * @deprecated Use {@link #print(String, String)}
    1007      *
    1008      * Writes the specified String (as bytes) to the output stream.
    1009      *
    1010      * @param data the string 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 print(String data)
    1015         throws IOException, IllegalStateException {
    1016         LOG.trace("enter HttpConnection.print(String)");
    1017         write(EncodingUtil.getBytes(data, "ISO-8859-1"));
    1018     }
    1019
    1020     /**
    1021      * Writes the specified String (as bytes) to the output stream.
    1022      *
    1023      * @param data the string to be written
    1024      * @param charset the charset to use for writing the data
    1025      * @throws IllegalStateException if the connection is not open
    1026      * @throws IOException if an I/O problem occurs
    1027      *
    1028      * @since 3.0
    1029      */
    1030     public void print(String data, String charset)
    1031     	throws IOException, IllegalStateException {
    1032         LOG.trace("enter HttpConnection.print(String)");
    1033         write(EncodingUtil.getBytes(data, charset));
    1034     }
    1035
    1036     /**
    1037      * @deprecated Use {@link #printLine(String, String)}
    1038      *
    1039      * Writes the specified String (as bytes), followed by
    1040      * <tt>"\r\n".getBytes()</tt> to the output stream.
    1041      *
    1042      * @param data the data to be written
    1043      * @throws IllegalStateException if the connection is not open
    1044      * @throws IOException if an I/O problem occurs
    1045      */
    1046     public void printLine(String data)
    1047         throws IOException, IllegalStateException {
    1048         LOG.trace("enter HttpConnection.printLine(String)");
    1049         writeLine(EncodingUtil.getBytes(data, "ISO-8859-1"));
    1050     }
    1051
    1052     /**
    1053      * Writes the specified String (as bytes), followed by
    1054      * <tt>"\r\n".getBytes()</tt> to the output stream.
    1055      *
    1056      * @param data the data to be written
    1057      * @param charset the charset to use for writing the data
    1058      * @throws IllegalStateException if the connection is not open
    1059      * @throws IOException if an I/O problem occurs
    1060      *
    1061      * @since 3.0
    1062      */
    1063     public void printLine(String data, String charset)
    1064     	throws IOException, IllegalStateException {
    1065         LOG.trace("enter HttpConnection.printLine(String)");
    1066         writeLine(EncodingUtil.getBytes(data, charset));
    1067     }
    1068
    1069     /**
    1070      * Writes <tt>"\r\n".getBytes()</tt> to the output stream.
    1071      *
    1072      * @throws IllegalStateException if the connection is not open
    1073      * @throws IOException if an I/O problem occurs
    1074      */
    1075     public void printLine()
    1076         throws IOException, IllegalStateException {
    1077         LOG.trace("enter HttpConnection.printLine()");
    1078         writeLine();
    1079     }
    1080
    1081     /**
    1082      * Reads up to <tt>"\n"</tt> from the (unchunked) input stream.
    1083      * If the stream ends before the line terminator is found,
    1084      * the last part of the string will still be returned.
    1085      *
    1086      * @throws IllegalStateException if the connection is not open
    1087      * @throws IOException if an I/O problem occurs
    1088      * @return a line from the response
    1089      *
    1090      * @deprecated use #readLine(String)
    1091      */
    1092     public String readLine() throws IOException, IllegalStateException {
    1093         LOG.trace("enter HttpConnection.readLine()");
    1094
    1095         assertOpen();
    1096         return HttpParser.readLine(inputStream);
    1097     }
    1098
    1099     /**
    1100      * Reads up to <tt>"\n"</tt> from the (unchunked) input stream.
    1101      * If the stream ends before the line terminator is found,
    1102      * the last part of the string will still be returned.
    1103      *
    1104      * @param charset the charset to use for reading the data
    1105      *
    1106      * @throws IllegalStateException if the connection is not open
    1107      * @throws IOException if an I/O problem occurs
    1108      * @return a line from the response
    1109      *
    1110      * @since 3.0
    1111      */
    1112     public String readLine(final String charset) throws IOException, IllegalStateException {
    1113         LOG.trace("enter HttpConnection.readLine()");
    1114
    1115         assertOpen();
    1116         return HttpParser.readLine(inputStream, charset);
    1117     }
    1118
    1119     /**
    1120      * Attempts to shutdown the {@link Socket}'s output, via Socket.shutdownOutput()
    1121      * when running on JVM 1.3 or higher.
    1122      *
    1123      * @deprecated unused
    1124      */
    1125     public void shutdownOutput() {
    1126         LOG.trace("enter HttpConnection.shutdownOutput()");
    1127
    1128         try {
    1129             // Socket.shutdownOutput is a JDK 1.3
    1130             // method. We'll use reflection in case
    1131             // we're running in an older VM
    1132             Class[] paramsClasses = new Class[0];
    1133             Method shutdownOutput =
    1134                 socket.getClass().getMethod("shutdownOutput", paramsClasses);
    1135             Object[] params = new Object[0];
    1136             shutdownOutput.invoke(socket, params);
    1137         } catch (Exception ex) {
    1138             LOG.debug("Unexpected Exception caught", ex);
    1139             // Ignore, and hope everything goes right
    1140         }
    1141         // close output stream?
    1142     }
    1143
    1144     /**
    1145      * Closes the socket and streams.
    1146      */
    1147     public void close() {
    1148         LOG.trace("enter HttpConnection.close()");
    1149         closeSocketAndStreams();
    1150     }
    1151
    1152     /**
    1153      * Returns the httpConnectionManager.
    1154      * @return HttpConnectionManager
    1155      */
    1156     public HttpConnectionManager getHttpConnectionManager() {
    1157         return httpConnectionManager;
    1158     }
    1159
    1160     /**
    1161      * Sets the httpConnectionManager.
    1162      * @param httpConnectionManager The httpConnectionManager to set
    1163      */
    1164     public void setHttpConnectionManager(HttpConnectionManager httpConnectionManager) {
    1165         this.httpConnectionManager = httpConnectionManager;
    1166     }
    1167
    1168     /**
    1169      * Releases the connection. If the connection is locked or does not have a connection
    1170      * manager associated with it, this method has no effect. Note that it is completely safe
    1171      * to call this method multiple times.
    1172      */
    1173     public void releaseConnection() {
    1174         LOG.trace("enter HttpConnection.releaseConnection()");
    1175         if (locked) {
    1176             LOG.debug("Connection is locked.  Call to releaseConnection() ignored.");
    1177         } else if (httpConnectionManager != null) {
    1178             LOG.debug("Releasing connection back to connection manager.");
    1179             httpConnectionManager.releaseConnection(this);
    1180         } else {
    1181             LOG.warn("HttpConnectionManager is null.  Connection cannot be released.");
    1182         }
    1183     }
    1184
    1185     /**
    1186      * Tests if the connection is locked. Locked connections cannot be released.
    1187      * An attempt to release a locked connection will have no effect.
    1188      *
    1189      * @return <tt>true</tt> if the connection is locked, <tt>false</tt> otherwise.
    1190      *
    1191      * @since 3.0
    1192      */
    1193     protected boolean isLocked() {
    1194         return locked;
    1195     }
    1196
    1197     /**
    1198      * Locks or unlocks the connection. Locked connections cannot be released.
    1199      * An attempt to release a locked connection will have no effect.
    1200      *
    1201      * @param locked <tt>true</tt> to lock the connection, <tt>false</tt> to unlock
    1202      *  the connection.
    1203      *
    1204      * @since 3.0
    1205      */
    1206     protected void setLocked(boolean locked) {
    1207         this.locked = locked;
    1208     }
    1209     // ------------------------------------------------------ Protected Methods
    1210
    1211     /**
    1212      * Closes everything out.
    1213      */
    1214     protected void closeSocketAndStreams() {
    1215         LOG.trace("enter HttpConnection.closeSockedAndStreams()");
    1216
    1217         isOpen = false;
    1218
    1219         // no longer care about previous responses...
    1220         lastResponseInputStream = null;
    1221
    1222         if (null != outputStream) {
    1223             OutputStream temp = outputStream;
    1224             outputStream = null;
    1225             try {
    1226                 temp.close();
    1227             } catch (Exception ex) {
    1228                 LOG.debug("Exception caught when closing output", ex);
    1229                 // ignored
    1230             }
    1231         }
    1232
    1233         if (null != inputStream) {
    1234             InputStream temp = inputStream;
    1235             inputStream = null;
    1236             try {
    1237                 temp.close();
    1238             } catch (Exception ex) {
    1239                 LOG.debug("Exception caught when closing input", ex);
    1240                 // ignored
    1241             }
    1242         }
    1243
    1244         if (null != socket) {
    1245             Socket temp = socket;
    1246             socket = null;
    1247             try {
    1248                 temp.close();
    1249             } catch (Exception ex) {
    1250                 LOG.debug("Exception caught when closing socket", ex);
    1251                 // ignored
    1252             }
    1253         }
    1254
    1255         tunnelEstablished = false;
    1256         usingSecureSocket = false;
    1257     }
    1258
    1259     /**
    1260      * Throws an {@link IllegalStateException} if the connection is already open.
    1261      *
    1262      * @throws IllegalStateException if connected
    1263      */
    1264     protected void assertNotOpen() throws IllegalStateException {
    1265         if (isOpen) {
    1266             throw new IllegalStateException("Connection is open");
    1267         }
    1268     }
    1269
    1270     /**
    1271      * Throws an {@link IllegalStateException} if the connection is not open.
    1272      *
    1273      * @throws IllegalStateException if not connected
    1274      */
    1275     protected void assertOpen() throws IllegalStateException {
    1276         if (!isOpen) {
    1277             throw new IllegalStateException("Connection is not open");
    1278         }
    1279     }
    1280
    1281     /**
    1282      * Gets the socket's sendBufferSize.
    1283      *
    1284      * @return the size of the buffer for the socket OutputStream, -1 if the value
    1285      * has not been set and the socket has not been opened
    1286      *
    1287      * @throws SocketException if an error occurs while getting the socket value
    1288      *
    1289      * @see Socket#getSendBufferSize()
    1290      */
    1291     public int getSendBufferSize() throws SocketException {
    1292         if (socket == null) {
    1293             return -1;
    1294         } else {
    1295             return socket.getSendBufferSize();
    1296         }
    1297     }
    1298
    1299     /**
    1300      * Sets the socket's sendBufferSize.
    1301      *
    1302      * @param sendBufferSize the size to set for the socket OutputStream
    1303      *
    1304      * @throws SocketException if an error occurs while setting the socket value
    1305      *
    1306      * @see Socket#setSendBufferSize(int)
    1307      *
    1308      * @deprecated Use {@link HttpConnectionParams#setSendBufferSize(int)},
    1309      * {@link HttpConnection#getParams()}.
    1310      */
    1311     public void setSendBufferSize(int sendBufferSize) throws SocketException {
    1312         this.params.setSendBufferSize(sendBufferSize);
    1313     }
    1314
    1315     // ------------------------------------------------------- Static Variable
    1316
    1317     /** <tt>"\r\n"</tt>, as bytes. */
    1318     private static final byte[] CRLF = new byte[] {(byte) 13, (byte) 10};
    1319
    1320     /** Log object for this class. */
    1321     private static final Log LOG = LogFactory.getLog(HttpConnection.class);
    1322
    1323     // ----------------------------------------------------- Instance Variables
    1324
    1325     /** My host. */
    1326     private String hostName = null;
    1327
    1328     /** My port. */
    1329     private int portNumber = -1;
    1330
    1331     /** My proxy host. */
    1332     private String proxyHostName = null;
    1333
    1334     /** My proxy port. */
    1335     private int proxyPortNumber = -1;
    1336
    1337     /** My client Socket. */
    1338     private Socket socket = null;
    1339
    1340     /** My InputStream. */
    1341     private InputStream inputStream = null;
    1342
    1343     /** My OutputStream. */
    1344     private OutputStream outputStream = null;
    1345
    1346     /** An {@link InputStream} for the response to an individual request. */
    1347     private InputStream lastResponseInputStream = null;
    1348
    1349     /** Whether or not the connection is connected. */
    1350     protected boolean isOpen = false;
    1351
    1352     /** the protocol being used */
    1353     private Protocol protocolInUse;
    1354
    1355     /** Collection of HTTP parameters associated with this HTTP connection*/
    1356     private HttpConnectionParams params = new HttpConnectionParams();
    1357
    1358     /** flag to indicate if this connection can be released, if locked the connection cannot be
    1359      * released */
    1360     private boolean locked = false;
    1361
    1362     /** Whether or not the socket is a secure one. */
    1363     private boolean usingSecureSocket = false;
    1364
    1365     /** Whether the connection is open via a secure tunnel or not */
    1366     private boolean tunnelEstablished = false;
    1367
    1368     /** the connection manager that created this connection or null */
    1369     private HttpConnectionManager httpConnectionManager;
    1370
    1371     /** The local interface on which the connection is created, or null for the default */
    1372     private InetAddress localAddress;
    1373 }