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