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