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