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: 291689 $
4    * $Date: 2005-09-26 13:44:45 -0400 (Mon, 26 Sep 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.DefaultProtocolSocketFactory;
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: 291689 $ $Date: 2005-09-26 13:44:45 -0400 (Mon, 26 Sep 2005) $
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                 final ProtocolSocketFactory socketFactory =
701                     (isSecure() && isProxied()
702                             ? new DefaultProtocolSocketFactory()
703                             : protocolInUse.getSocketFactory());
704                 this.socket = socketFactory.createSocket(
705                             host, port, 
706                             localAddress, 0,
707                             this.params);
708             }
709 
710             /*
711             "Nagling has been broadly implemented across networks, 
712             including the Internet, and is generally performed by default 
713             - although it is sometimes considered to be undesirable in 
714             highly interactive environments, such as some client/server 
715             situations. In such cases, nagling may be turned off through 
716             use of the TCP_NODELAY sockets option." */
717 
718             socket.setTcpNoDelay(this.params.getTcpNoDelay());
719             socket.setSoTimeout(this.params.getSoTimeout());
720             
721             int linger = this.params.getLinger();
722             if (linger >= 0) {
723                 socket.setSoLinger(linger > 0, linger);
724             }
725             
726             int sndBufSize = this.params.getSendBufferSize();
727             if (sndBufSize >= 0) {
728                 socket.setSendBufferSize(sndBufSize);
729             }        
730             int rcvBufSize = this.params.getReceiveBufferSize();
731             if (rcvBufSize >= 0) {
732                 socket.setReceiveBufferSize(rcvBufSize);
733             }        
734             int outbuffersize = socket.getSendBufferSize();
735             if ((outbuffersize > 2048) || (outbuffersize <= 0)) {
736                 outbuffersize = 2048;
737             }
738             int inbuffersize = socket.getReceiveBufferSize();
739             if ((inbuffersize > 2048) || (inbuffersize <= 0)) {
740                 inbuffersize = 2048;
741             }
742             inputStream = new BufferedInputStream(socket.getInputStream(), inbuffersize);
743             outputStream = new BufferedOutputStream(socket.getOutputStream(), outbuffersize);
744             isOpen = true;
745         } catch (IOException e) {
746             // Connection wasn't opened properly
747             // so close everything out
748             closeSocketAndStreams();
749             throw e;
750         }
751     }
752 
753     /***
754      * Instructs the proxy to establish a secure tunnel to the host. The socket will 
755      * be switched to the secure socket. Subsequent communication is done via the secure 
756      * socket. The method can only be called once on a proxied secure connection.
757      *
758      * @throws IllegalStateException if connection is not secure and proxied or
759      * if the socket is already secure.
760      * @throws IOException if an attempt to establish the secure tunnel results in an
761      *   I/O error.
762      */
763     public void tunnelCreated() throws IllegalStateException, IOException {
764         LOG.trace("enter HttpConnection.tunnelCreated()");
765 
766         if (!isSecure() || !isProxied()) {
767             throw new IllegalStateException(
768                 "Connection must be secure "
769                     + "and proxied to use this feature");
770         }
771 
772         if (usingSecureSocket) {
773             throw new IllegalStateException("Already using a secure socket");
774         }
775         
776         if (LOG.isDebugEnabled()) {
777             LOG.debug("Secure tunnel to " + this.hostName + ":" + this.portNumber);
778         }
779 
780         SecureProtocolSocketFactory socketFactory =
781             (SecureProtocolSocketFactory) protocolInUse.getSocketFactory();
782 
783         socket = socketFactory.createSocket(socket, hostName, portNumber, true);
784         int sndBufSize = this.params.getSendBufferSize();
785         if (sndBufSize >= 0) {
786             socket.setSendBufferSize(sndBufSize);
787         }        
788         int rcvBufSize = this.params.getReceiveBufferSize();
789         if (rcvBufSize >= 0) {
790             socket.setReceiveBufferSize(rcvBufSize);
791         }        
792         int outbuffersize = socket.getSendBufferSize();
793         if (outbuffersize > 2048) {
794             outbuffersize = 2048;
795         }
796         int inbuffersize = socket.getReceiveBufferSize();
797         if (inbuffersize > 2048) {
798             inbuffersize = 2048;
799         }
800         inputStream = new BufferedInputStream(socket.getInputStream(), inbuffersize);
801         outputStream = new BufferedOutputStream(socket.getOutputStream(), outbuffersize);
802         usingSecureSocket = true;
803         tunnelEstablished = true;
804     }
805 
806     /***
807      * Indicates if the connection is completely transparent from end to end.
808      *
809      * @return true if conncetion is not proxied or tunneled through a transparent
810      * proxy; false otherwise.
811      */
812     public boolean isTransparent() {
813         return !isProxied() || tunnelEstablished;
814     }
815 
816     /***
817      * Flushes the output request stream.  This method should be called to 
818      * ensure that data written to the request OutputStream is sent to the server.
819      * 
820      * @throws IOException if an I/O problem occurs
821      */
822     public void flushRequestOutputStream() throws IOException {
823         LOG.trace("enter HttpConnection.flushRequestOutputStream()");
824         assertOpen();
825         outputStream.flush();
826     }
827 
828     /***
829      * Returns an {@link OutputStream} suitable for writing the request.
830      *
831      * @throws IllegalStateException if the connection is not open
832      * @throws IOException if an I/O problem occurs
833      * @return a stream to write the request to
834      */
835     public OutputStream getRequestOutputStream()
836         throws IOException, IllegalStateException {
837         LOG.trace("enter HttpConnection.getRequestOutputStream()");
838         assertOpen();
839         OutputStream out = this.outputStream;
840         if (Wire.CONTENT_WIRE.enabled()) {
841             out = new WireLogOutputStream(out, Wire.CONTENT_WIRE);
842         }
843         return out;
844     }
845 
846     /***
847      * Return a {@link InputStream} suitable for reading the response.
848      * @return InputStream The response input stream.
849      * @throws IOException If an IO problem occurs
850      * @throws IllegalStateException If the connection isn't open.
851      */
852     public InputStream getResponseInputStream()
853         throws IOException, IllegalStateException {
854         LOG.trace("enter HttpConnection.getResponseInputStream()");
855         assertOpen();
856         return inputStream;
857     }
858 
859     /***
860      * Tests if input data avaialble. This method returns immediately
861      * and does not perform any read operations on the input socket
862      * 
863      * @return boolean <tt>true</tt> if input data is available, 
864      *                 <tt>false</tt> otherwise.
865      * 
866      * @throws IOException If an IO problem occurs
867      * @throws IllegalStateException If the connection isn't open.
868      */
869     public boolean isResponseAvailable() 
870         throws IOException {
871         LOG.trace("enter HttpConnection.isResponseAvailable()");
872         if (this.isOpen) {
873             return this.inputStream.available() > 0;
874         } else {
875             return false;
876         }
877     }
878 
879     /***
880      * Tests if input data becomes available within the given period time in milliseconds.
881      * 
882      * @param timeout The number milliseconds to wait for input data to become available 
883      * @return boolean <tt>true</tt> if input data is availble, 
884      *                 <tt>false</tt> otherwise.
885      * 
886      * @throws IOException If an IO problem occurs
887      * @throws IllegalStateException If the connection isn't open.
888      */
889     public boolean isResponseAvailable(int timeout) 
890         throws IOException {
891         LOG.trace("enter HttpConnection.isResponseAvailable(int)");
892         assertOpen();
893         boolean result = false;
894         if (this.inputStream.available() > 0) {
895             result = true;
896         } else {
897             try {
898                 this.socket.setSoTimeout(timeout);
899                 inputStream.mark(1);
900                 int byteRead = inputStream.read();
901                 if (byteRead != -1) {
902                     inputStream.reset();
903                     LOG.debug("Input data available");
904                     result = true;
905                 } else {
906                     LOG.debug("Input data not available");
907                 }
908             } catch (InterruptedIOException e) {
909                 if (!ExceptionUtil.isSocketTimeoutException(e)) {
910                     throw e;
911                 }
912                 if (LOG.isDebugEnabled()) {
913                     LOG.debug("Input data not available after " + timeout + " ms");
914                 }
915             } finally {
916                 try {
917                     socket.setSoTimeout(this.params.getSoTimeout());
918                 } catch (IOException ioe) {
919                     LOG.debug("An error ocurred while resetting soTimeout, we will assume that"
920                         + " no response is available.",
921                         ioe);
922                     result = false;
923                 }
924             }
925         }
926         return result;
927     }
928 
929     /***
930      * Writes the specified bytes to the output stream.
931      *
932      * @param data the data to be written
933      * @throws IllegalStateException if not connected
934      * @throws IOException if an I/O problem occurs
935      * @see #write(byte[],int,int)
936      */
937     public void write(byte[] data)
938         throws IOException, IllegalStateException {
939         LOG.trace("enter HttpConnection.write(byte[])");
940         this.write(data, 0, data.length);
941     }
942 
943     /***
944      * Writes <i>length</i> bytes in <i>data</i> starting at
945      * <i>offset</i> to the output stream.
946      *
947      * The general contract for
948      * write(b, off, len) is that some of the bytes in the array b are written
949      * to the output stream in order; element b[off] is the first byte written
950      * and b[off+len-1] is the last byte written by this operation.
951      *
952      * @param data array containing the data to be written.
953      * @param offset the start offset in the data.
954      * @param length the number of bytes to write.
955      * @throws IllegalStateException if not connected
956      * @throws IOException if an I/O problem occurs
957      */
958     public void write(byte[] data, int offset, int length)
959         throws IOException, IllegalStateException {
960         LOG.trace("enter HttpConnection.write(byte[], int, int)");
961 
962         if (offset < 0) {
963             throw new IllegalArgumentException("Array offset may not be negative");
964         }
965         if (length < 0) {
966             throw new IllegalArgumentException("Array length may not be negative");
967         }
968         if (offset + length > data.length) {
969             throw new IllegalArgumentException("Given offset and length exceed the array length");
970         }
971         assertOpen();
972         this.outputStream.write(data, offset, length);
973     }
974 
975     /***
976      * Writes the specified bytes, followed by <tt>"\r\n".getBytes()</tt> to the
977      * output stream.
978      *
979      * @param data the bytes to be written
980      * @throws IllegalStateException if the connection is not open
981      * @throws IOException if an I/O problem occurs
982      */
983     public void writeLine(byte[] data)
984         throws IOException, IllegalStateException {
985         LOG.trace("enter HttpConnection.writeLine(byte[])");
986         write(data);
987         writeLine();
988     }
989 
990     /***
991      * Writes <tt>"\r\n".getBytes()</tt> to the output stream.
992      *
993      * @throws IllegalStateException if the connection is not open
994      * @throws IOException if an I/O problem occurs
995      */
996     public void writeLine()
997         throws IOException, IllegalStateException {
998         LOG.trace("enter HttpConnection.writeLine()");
999         write(CRLF);
1000     }
1001 
1002     /***
1003      * @deprecated Use {@link #print(String, String)}
1004      * 
1005      * Writes the specified String (as bytes) to the output stream.
1006      *
1007      * @param data the string to be written
1008      * @throws IllegalStateException if the connection is not open
1009      * @throws IOException if an I/O problem occurs
1010      */
1011     public void print(String data)
1012         throws IOException, IllegalStateException {
1013         LOG.trace("enter HttpConnection.print(String)");
1014         write(EncodingUtil.getBytes(data, "ISO-8859-1"));
1015     }
1016 
1017     /***
1018      * Writes the specified String (as bytes) to the output stream.
1019      *
1020      * @param data the string to be written
1021      * @param charset the charset to use for writing the data
1022      * @throws IllegalStateException if the connection is not open
1023      * @throws IOException if an I/O problem occurs
1024      * 
1025      * @since 3.0
1026      */
1027     public void print(String data, String charset)
1028     	throws IOException, IllegalStateException {
1029         LOG.trace("enter HttpConnection.print(String)");
1030         write(EncodingUtil.getBytes(data, charset));
1031     }
1032     
1033     /***
1034      * @deprecated Use {@link #printLine(String, String)}
1035      * 
1036      * Writes the specified String (as bytes), followed by
1037      * <tt>"\r\n".getBytes()</tt> to the output stream.
1038      *
1039      * @param data the data to be written
1040      * @throws IllegalStateException if the connection is not open
1041      * @throws IOException if an I/O problem occurs
1042      */
1043     public void printLine(String data)
1044         throws IOException, IllegalStateException {
1045         LOG.trace("enter HttpConnection.printLine(String)");
1046         writeLine(EncodingUtil.getBytes(data, "ISO-8859-1"));
1047     }
1048 
1049     /***
1050      * Writes the specified String (as bytes), followed by
1051      * <tt>"\r\n".getBytes()</tt> to the output stream.
1052      *
1053      * @param data the data to be written
1054      * @param charset the charset to use for writing the data
1055      * @throws IllegalStateException if the connection is not open
1056      * @throws IOException if an I/O problem occurs
1057      * 
1058      * @since 3.0
1059      */
1060     public void printLine(String data, String charset)
1061     	throws IOException, IllegalStateException {
1062         LOG.trace("enter HttpConnection.printLine(String)");
1063         writeLine(EncodingUtil.getBytes(data, charset));
1064     }    
1065     
1066     /***
1067      * Writes <tt>"\r\n".getBytes()</tt> to the output stream.
1068      *
1069      * @throws IllegalStateException if the connection is not open
1070      * @throws IOException if an I/O problem occurs
1071      */
1072     public void printLine()
1073         throws IOException, IllegalStateException {
1074         LOG.trace("enter HttpConnection.printLine()");
1075         writeLine();
1076     }
1077 
1078     /***
1079      * Reads up to <tt>"\n"</tt> from the (unchunked) input stream.
1080      * If the stream ends before the line terminator is found,
1081      * the last part of the string will still be returned.
1082      *
1083      * @throws IllegalStateException if the connection is not open
1084      * @throws IOException if an I/O problem occurs
1085      * @return a line from the response
1086      * 
1087      * @deprecated use #readLine(String)
1088      */
1089     public String readLine() throws IOException, IllegalStateException {
1090         LOG.trace("enter HttpConnection.readLine()");
1091 
1092         assertOpen();
1093         return HttpParser.readLine(inputStream);
1094     }
1095 
1096     /***
1097      * Reads up to <tt>"\n"</tt> from the (unchunked) input stream.
1098      * If the stream ends before the line terminator is found,
1099      * the last part of the string will still be returned.
1100      * 
1101      * @param charset the charset to use for reading the data
1102      *
1103      * @throws IllegalStateException if the connection is not open
1104      * @throws IOException if an I/O problem occurs
1105      * @return a line from the response
1106      * 
1107      * @since 3.0
1108      */
1109     public String readLine(final String charset) throws IOException, IllegalStateException {
1110         LOG.trace("enter HttpConnection.readLine()");
1111 
1112         assertOpen();
1113         return HttpParser.readLine(inputStream, charset);
1114     }
1115 
1116     /***
1117      * Attempts to shutdown the {@link Socket}'s output, via Socket.shutdownOutput()
1118      * when running on JVM 1.3 or higher.
1119      * 
1120      * @deprecated unused
1121      */
1122     public void shutdownOutput() {
1123         LOG.trace("enter HttpConnection.shutdownOutput()");
1124 
1125         try {
1126             // Socket.shutdownOutput is a JDK 1.3
1127             // method. We'll use reflection in case
1128             // we're running in an older VM
1129             Class[] paramsClasses = new Class[0];
1130             Method shutdownOutput =
1131                 socket.getClass().getMethod("shutdownOutput", paramsClasses);
1132             Object[] params = new Object[0];
1133             shutdownOutput.invoke(socket, params);
1134         } catch (Exception ex) {
1135             LOG.debug("Unexpected Exception caught", ex);
1136             // Ignore, and hope everything goes right
1137         }
1138         // close output stream?
1139     }
1140 
1141     /***
1142      * Closes the socket and streams.
1143      */
1144     public void close() {
1145         LOG.trace("enter HttpConnection.close()");
1146         closeSocketAndStreams();
1147     }
1148 
1149     /***
1150      * Returns the httpConnectionManager.
1151      * @return HttpConnectionManager
1152      */
1153     public HttpConnectionManager getHttpConnectionManager() {
1154         return httpConnectionManager;
1155     }
1156 
1157     /***
1158      * Sets the httpConnectionManager.
1159      * @param httpConnectionManager The httpConnectionManager to set
1160      */
1161     public void setHttpConnectionManager(HttpConnectionManager httpConnectionManager) {
1162         this.httpConnectionManager = httpConnectionManager;
1163     }
1164 
1165     /***
1166      * Releases the connection. If the connection is locked or does not have a connection
1167      * manager associated with it, this method has no effect. Note that it is completely safe 
1168      * to call this method multiple times.
1169      */
1170     public void releaseConnection() {
1171         LOG.trace("enter HttpConnection.releaseConnection()");
1172         if (locked) {
1173             LOG.debug("Connection is locked.  Call to releaseConnection() ignored.");
1174         } else if (httpConnectionManager != null) {
1175             LOG.debug("Releasing connection back to connection manager.");
1176             httpConnectionManager.releaseConnection(this);
1177         } else {
1178             LOG.warn("HttpConnectionManager is null.  Connection cannot be released.");
1179         }
1180     }
1181 
1182     /***
1183      * Tests if the connection is locked. Locked connections cannot be released. 
1184      * An attempt to release a locked connection will have no effect.
1185      * 
1186      * @return <tt>true</tt> if the connection is locked, <tt>false</tt> otherwise.
1187      * 
1188      * @since 3.0
1189      */
1190     protected boolean isLocked() {
1191         return locked;
1192     }
1193 
1194     /***
1195      * Locks or unlocks the connection. Locked connections cannot be released. 
1196      * An attempt to release a locked connection will have no effect.
1197      * 
1198      * @param locked <tt>true</tt> to lock the connection, <tt>false</tt> to unlock
1199      *  the connection.
1200      * 
1201      * @since 3.0
1202      */
1203     protected void setLocked(boolean locked) {
1204         this.locked = locked;
1205     }
1206     // ------------------------------------------------------ Protected Methods
1207 
1208     /***
1209      * Closes everything out.
1210      */
1211     protected void closeSocketAndStreams() {
1212         LOG.trace("enter HttpConnection.closeSockedAndStreams()");
1213 
1214         isOpen = false;
1215         
1216         // no longer care about previous responses...
1217         lastResponseInputStream = null;
1218 
1219         if (null != outputStream) {
1220             OutputStream temp = outputStream;
1221             outputStream = null;
1222             try {
1223                 temp.close();
1224             } catch (Exception ex) {
1225                 LOG.debug("Exception caught when closing output", ex);
1226                 // ignored
1227             }
1228         }
1229 
1230         if (null != inputStream) {
1231             InputStream temp = inputStream;
1232             inputStream = null;
1233             try {
1234                 temp.close();
1235             } catch (Exception ex) {
1236                 LOG.debug("Exception caught when closing input", ex);
1237                 // ignored
1238             }
1239         }
1240 
1241         if (null != socket) {
1242             Socket temp = socket;
1243             socket = null;
1244             try {
1245                 temp.close();
1246             } catch (Exception ex) {
1247                 LOG.debug("Exception caught when closing socket", ex);
1248                 // ignored
1249             }
1250         }
1251         
1252         tunnelEstablished = false;
1253         usingSecureSocket = false;
1254     }
1255 
1256     /***
1257      * Throws an {@link IllegalStateException} if the connection is already open.
1258      *
1259      * @throws IllegalStateException if connected
1260      */
1261     protected void assertNotOpen() throws IllegalStateException {
1262         if (isOpen) {
1263             throw new IllegalStateException("Connection is open");
1264         }
1265     }
1266 
1267     /***
1268      * Throws an {@link IllegalStateException} if the connection is not open.
1269      *
1270      * @throws IllegalStateException if not connected
1271      */
1272     protected void assertOpen() throws IllegalStateException {
1273         if (!isOpen) {
1274             throw new IllegalStateException("Connection is not open");
1275         }
1276     }
1277 
1278     /***
1279      * Gets the socket's sendBufferSize.
1280      * 
1281      * @return the size of the buffer for the socket OutputStream, -1 if the value
1282      * has not been set and the socket has not been opened
1283      * 
1284      * @throws SocketException if an error occurs while getting the socket value
1285      * 
1286      * @see Socket#getSendBufferSize()
1287      */
1288     public int getSendBufferSize() throws SocketException {
1289         if (socket == null) {
1290             return -1;
1291         } else {
1292             return socket.getSendBufferSize();
1293         }
1294     }
1295 
1296     /***
1297      * Sets the socket's sendBufferSize.
1298      * 
1299      * @param sendBufferSize the size to set for the socket OutputStream
1300      * 
1301      * @throws SocketException if an error occurs while setting the socket value
1302      * 
1303      * @see Socket#setSendBufferSize(int)
1304      * 
1305      * @deprecated Use {@link HttpConnectionParams#setSendBufferSize(int)},
1306      * {@link HttpConnection#getParams()}.
1307      */
1308     public void setSendBufferSize(int sendBufferSize) throws SocketException {
1309         this.params.setSendBufferSize(sendBufferSize);
1310     }
1311 
1312     // ------------------------------------------------------- Static Variable
1313 
1314     /*** <tt>"\r\n"</tt>, as bytes. */
1315     private static final byte[] CRLF = new byte[] {(byte) 13, (byte) 10};
1316 
1317     /*** Log object for this class. */
1318     private static final Log LOG = LogFactory.getLog(HttpConnection.class);
1319     
1320     // ----------------------------------------------------- Instance Variables
1321     
1322     /*** My host. */
1323     private String hostName = null;
1324     
1325     /*** My port. */
1326     private int portNumber = -1;
1327     
1328     /*** My proxy host. */
1329     private String proxyHostName = null;
1330     
1331     /*** My proxy port. */
1332     private int proxyPortNumber = -1;
1333     
1334     /*** My client Socket. */
1335     private Socket socket = null;
1336     
1337     /*** My InputStream. */
1338     private InputStream inputStream = null;
1339 
1340     /*** My OutputStream. */
1341     private OutputStream outputStream = null;
1342     
1343     /*** An {@link InputStream} for the response to an individual request. */
1344     private InputStream lastResponseInputStream = null;
1345     
1346     /*** Whether or not the connection is connected. */
1347     protected boolean isOpen = false;
1348     
1349     /*** the protocol being used */
1350     private Protocol protocolInUse;
1351     
1352     /*** Collection of HTTP parameters associated with this HTTP connection*/
1353     private HttpConnectionParams params = new HttpConnectionParams();
1354     
1355     /*** flag to indicate if this connection can be released, if locked the connection cannot be 
1356      * released */
1357     private boolean locked = false;
1358     
1359     /*** Whether or not the socket is a secure one. */
1360     private boolean usingSecureSocket = false;
1361     
1362     /*** Whether the connection is open via a secure tunnel or not */
1363     private boolean tunnelEstablished = false;
1364     
1365     /*** the connection manager that created this connection or null */
1366     private HttpConnectionManager httpConnectionManager;
1367     
1368     /*** The local interface on which the connection is created, or null for the default */
1369     private InetAddress localAddress;
1370 }