View Javadoc

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