HttpConnection.java
   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  }