View Javadoc

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