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