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.BufferedOutputStream;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.InterruptedIOException;
38 import java.io.OutputStream;
39 import java.io.PushbackInputStream;
40 import java.lang.reflect.Method;
41 import java.net.InetAddress;
42 import java.net.Socket;
43 import java.net.SocketException;
44
45 import org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory;
46 import org.apache.commons.httpclient.protocol.Protocol;
47 import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
48 import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
49 import org.apache.commons.httpclient.util.TimeoutController;
50 import org.apache.commons.logging.Log;
51 import org.apache.commons.logging.LogFactory;
52
53 /***
54 * An abstraction of an HTTP {@link InputStream} and {@link OutputStream}
55 * pair, together with the relevant attributes.
56 * <p>
57 * The following options are set on the socket before getting the input/output
58 * streams in the {@link #open()} method:
59 * <table border=1><tr>
60 * <th>Socket Method
61 * <th>Sockets Option
62 * <th>Configuration
63 * </tr><tr>
64 * <td>{@link java.net.Socket#setTcpNoDelay(boolean)}
65 * <td>SO_NODELAY
66 * <td>None
67 * </tr><tr>
68 * <td>{@link java.net.Socket#setSoTimeout(int)}
69 * <td>SO_TIMEOUT
70 * <td>{@link #setConnectionTimeout(int)}
71 * </tr></table>
72 *
73 * @author Rod Waldhoff
74 * @author Sean C. Sullivan
75 * @author Ortwin Gl??ck
76 * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
77 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
78 * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
79 * @author Michael Becke
80 * @author Eric E Johnson
81 * @author Laura Werner
82 *
83 * @version $Revision: 1.67.2.12 $ $Date: 2004/07/19 20:06:26 $
84 */
85 public class HttpConnection {
86
87
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 int byteRead = inputStream.read();
502 if (byteRead == -1) {
503
504
505 isStale = true;
506 } else {
507 inputStream.unread(byteRead);
508 }
509 } finally {
510 socket.setSoTimeout(soTimeout);
511 }
512 }
513 } catch (InterruptedIOException e) {
514
515 } catch (IOException e) {
516
517 LOG.debug(
518 "An error occurred while reading from the socket, is appears to be stale",
519 e
520 );
521 isStale = true;
522 }
523 }
524
525 return isStale;
526 }
527
528 /***
529 * Returns <tt>true</tt> if the connection is established via a proxy,
530 * <tt>false</tt> otherwise.
531 *
532 * @return <tt>true</tt> if a proxy is used to establish the connection,
533 * <tt>false</tt> otherwise.
534 */
535 public boolean isProxied() {
536 return (!(null == proxyHostName || 0 >= proxyPortNumber));
537 }
538
539 /***
540 * Set the state to keep track of the last response for the last request.
541 *
542 * <p>The connection managers use this to ensure that previous requests are
543 * properly closed before a new request is attempted. That way, a GET
544 * request need not be read in its entirety before a new request is issued.
545 * Instead, this stream can be closed as appropriate.</p>
546 *
547 * @param inStream The stream associated with an HttpMethod.
548 */
549 public void setLastResponseInputStream(InputStream inStream) {
550 lastResponseInputStream = inStream;
551 }
552
553 /***
554 * Returns the stream used to read the last response's body.
555 *
556 * <p>Clients will generally not need to call this function unless
557 * using HttpConnection directly, instead of calling {@link HttpClient#executeMethod}.
558 * For those clients, call this function, and if it returns a non-null stream,
559 * close the stream before attempting to execute a method. Note that
560 * calling "close" on the stream returned by this function <i>may</i> close
561 * the connection if the previous response contained a "Connection: close" header. </p>
562 *
563 * @return An {@link InputStream} corresponding to the body of the last
564 * response.
565 */
566 public InputStream getLastResponseInputStream() {
567 return lastResponseInputStream;
568 }
569
570
571
572 /***
573 * Sets the {@link Socket}'s timeout, via {@link Socket#setSoTimeout}. If the
574 * connection is already open, the SO_TIMEOUT is changed. If no connection
575 * is open, then subsequent connections will use the timeout value.
576 * <p>
577 * Note: This is not a connection timeout but a timeout on network traffic!
578 *
579 * @param timeout the timeout value
580 * @throws SocketException - if there is an error in the underlying
581 * protocol, such as a TCP error.
582 */
583 public void setSoTimeout(int timeout)
584 throws SocketException, IllegalStateException {
585 LOG.debug("HttpConnection.setSoTimeout(" + timeout + ")");
586 soTimeout = timeout;
587 if (socket != null) {
588 socket.setSoTimeout(timeout);
589 }
590 }
591
592 /***
593 * Returns the {@link Socket}'s timeout, via {@link Socket#getSoTimeout}, if the
594 * connection is already open. If no connection is open, return the value subsequent
595 * connection will use.
596 * <p>
597 * Note: This is not a connection timeout but a timeout on network traffic!
598 *
599 * @return the timeout value
600 */
601 public int getSoTimeout() throws SocketException {
602 LOG.debug("HttpConnection.getSoTimeout()");
603 if (this.socket != null) {
604 return this.socket.getSoTimeout();
605 } else {
606 return this.soTimeout;
607 }
608 }
609
610 /***
611 * Sets the connection timeout. This is the maximum time that may be spent
612 * until a connection is established. The connection will fail after this
613 * amount of time.
614 * @param timeout The timeout in milliseconds. 0 means timeout is not used.
615 */
616 public void setConnectionTimeout(int timeout) {
617 this.connectTimeout = timeout;
618 }
619
620 /***
621 * Establishes a connection to the specified host and port
622 * (via a proxy if specified).
623 * The underlying socket is created from the {@link ProtocolSocketFactory}.
624 *
625 * @throws IOException if an attempt to establish the connection results in an
626 * I/O error.
627 */
628 public void open() throws IOException {
629 LOG.trace("enter HttpConnection.open()");
630
631 assertNotOpen();
632 try {
633 if (null == socket) {
634
635 final String host = (null == proxyHostName) ? hostName : proxyHostName;
636 final int port = (null == proxyHostName) ? portNumber : proxyPortNumber;
637
638 usingSecureSocket = isSecure() && !isProxied();
639
640
641
642 final ProtocolSocketFactory socketFactory =
643 (isSecure() && isProxied()
644 ? new DefaultProtocolSocketFactory()
645 : protocolInUse.getSocketFactory());
646
647 if (connectTimeout == 0) {
648 if (localAddress != null) {
649 socket = socketFactory.createSocket(host, port, localAddress, 0);
650 } else {
651 socket = socketFactory.createSocket(host, port);
652 }
653 } else {
654 SocketTask task = new SocketTask() {
655 public void doit() throws IOException {
656 if (localAddress != null) {
657 setSocket(socketFactory.createSocket(host, port, localAddress, 0));
658 } else {
659 setSocket(socketFactory.createSocket(host, port));
660 }
661 }
662 };
663 TimeoutController.execute(task, connectTimeout);
664 socket = task.getSocket();
665 if (task.exception != null) {
666 throw task.exception;
667 }
668 }
669
670 }
671
672
673
674
675
676
677
678
679
680 socket.setTcpNoDelay(soNodelay);
681 socket.setSoTimeout(soTimeout);
682 if (sendBufferSize != -1) {
683 socket.setSendBufferSize(sendBufferSize);
684 }
685 int outbuffersize = socket.getSendBufferSize();
686 if (outbuffersize > 2048) {
687 outbuffersize = 2048;
688 }
689 inputStream = new PushbackInputStream(socket.getInputStream());
690 outputStream = new BufferedOutputStream(
691 new WrappedOutputStream(socket.getOutputStream()),
692 outbuffersize
693 );
694 isOpen = true;
695 used = false;
696 } catch (IOException e) {
697
698
699 closeSocketAndStreams();
700 throw e;
701 } catch (TimeoutController.TimeoutException e) {
702 if (LOG.isWarnEnabled()) {
703 LOG.warn("The host " + hostName + ":" + portNumber
704 + " (or proxy " + proxyHostName + ":" + proxyPortNumber
705 + ") did not accept the connection within timeout of "
706 + connectTimeout + " milliseconds");
707 }
708 throw new ConnectionTimeoutException();
709 }
710 }
711
712 /***
713 * Instructs the proxy to establish a secure tunnel to the host. The socket will
714 * be switched to the secure socket. Subsequent communication is done via the secure
715 * socket. The method can only be called once on a proxied secure connection.
716 *
717 * @throws IllegalStateException if connection is not secure and proxied or
718 * if the socket is already secure.
719 * @throws IOException if an attempt to establish the secure tunnel results in an
720 * I/O error.
721 */
722 public void tunnelCreated() throws IllegalStateException, IOException {
723 LOG.trace("enter HttpConnection.tunnelCreated()");
724
725 if (!isSecure() || !isProxied()) {
726 throw new IllegalStateException(
727 "Connection must be secure "
728 + "and proxied to use this feature");
729 }
730
731 if (usingSecureSocket) {
732 throw new IllegalStateException("Already using a secure socket");
733 }
734
735 SecureProtocolSocketFactory socketFactory =
736 (SecureProtocolSocketFactory) protocolInUse.getSocketFactory();
737
738 socket = socketFactory.createSocket(socket, hostName, portNumber, true);
739 if (sendBufferSize != -1) {
740 socket.setSendBufferSize(sendBufferSize);
741 }
742 int outbuffersize = socket.getSendBufferSize();
743 if (outbuffersize > 2048) {
744 outbuffersize = 2048;
745 }
746 inputStream = new PushbackInputStream(socket.getInputStream());
747 outputStream = new BufferedOutputStream(
748 new WrappedOutputStream(socket.getOutputStream()),
749 outbuffersize
750 );
751 usingSecureSocket = true;
752 tunnelEstablished = true;
753 LOG.debug("Secure tunnel created");
754 }
755
756 /***
757 * Indicates if the connection is completely transparent from end to end.
758 *
759 * @return true if conncetion is not proxied or tunneled through a transparent
760 * proxy; false otherwise.
761 */
762 public boolean isTransparent() {
763 return !isProxied() || tunnelEstablished;
764 }
765
766 /***
767 * Flushes the output request stream. This method should be called to
768 * ensure that data written to the request OutputStream is sent to the server.
769 *
770 * @throws IOException if an I/O problem occurs
771 */
772 public void flushRequestOutputStream() throws IOException {
773 LOG.trace("enter HttpConnection.flushRequestOutputStream()");
774 assertOpen();
775 outputStream.flush();
776 }
777
778 /***
779 * Returns an {@link OutputStream} suitable for writing the request.
780 *
781 * @throws IllegalStateException if the connection is not open
782 * @throws IOException if an I/O problem occurs
783 * @return a stream to write the request to
784 */
785 public OutputStream getRequestOutputStream()
786 throws IOException, IllegalStateException {
787 LOG.trace("enter HttpConnection.getRequestOutputStream()");
788 assertOpen();
789 OutputStream out = this.outputStream;
790 if (Wire.CONTENT_WIRE.enabled()) {
791 out = new WireLogOutputStream(out, Wire.CONTENT_WIRE);
792 }
793 return out;
794 }
795
796 /***
797 * Returns an {@link OutputStream} suitable for writing the request.
798 *
799 * @param useChunking when <tt>true</tt> the chunked transfer-encoding will
800 * be used
801 * @throws IllegalStateException if the connection is not open
802 * @throws IOException if an I/O problem occurs
803 * @return a stream to write the request to
804 * @deprecated Use new ChunkedOutputStream(httpConnecion.getRequestOutputStream());
805 */
806 public OutputStream getRequestOutputStream(boolean useChunking)
807 throws IOException, IllegalStateException {
808 LOG.trace("enter HttpConnection.getRequestOutputStream(boolean)");
809
810 OutputStream out = getRequestOutputStream();
811 if (useChunking) {
812 out = new ChunkedOutputStream(out);
813 }
814 return out;
815 }
816
817 /***
818 * Return a {@link InputStream} suitable for reading the response.
819 * <p>
820 * If the given {@link HttpMethod} contains
821 * a <tt>Transfer-Encoding: chunked</tt> header,
822 * the returned stream will be configured
823 * to read chunked bytes.
824 *
825 * @param method This argument is ignored.
826 * @throws IllegalStateException if the connection is not open
827 * @throws IOException if an I/O problem occurs
828 * @return a stream to read the response from
829 * @deprecated Use getResponseInputStream() instead.
830 */
831 public InputStream getResponseInputStream(HttpMethod method)
832 throws IOException, IllegalStateException {
833 LOG.trace("enter HttpConnection.getResponseInputStream(HttpMethod)");
834 return getResponseInputStream();
835 }
836
837 /***
838 * Return a {@link InputStream} suitable for reading the response.
839 * @return InputStream The response input stream.
840 * @throws IOException If an IO problem occurs
841 * @throws IllegalStateException If the connection isn't open.
842 */
843 public InputStream getResponseInputStream()
844 throws IOException, IllegalStateException {
845 LOG.trace("enter HttpConnection.getResponseInputStream()");
846 assertOpen();
847 return inputStream;
848 }
849
850 /***
851 * Tests if input data avaialble. This method returns immediately
852 * and does not perform any read operations on the input socket
853 *
854 * @return boolean <tt>true</tt> if input data is available,
855 * <tt>false</tt> otherwise.
856 *
857 * @throws IOException If an IO problem occurs
858 * @throws IllegalStateException If the connection isn't open.
859 */
860 public boolean isResponseAvailable()
861 throws IOException {
862 LOG.trace("enter HttpConnection.isResponseAvailable()");
863 assertOpen();
864 return this.inputStream.available() > 0;
865 }
866
867 /***
868 * Tests if input data becomes available within the given period time in milliseconds.
869 *
870 * @param timeout The number milliseconds to wait for input data to become available
871 * @return boolean <tt>true</tt> if input data is availble,
872 * <tt>false</tt> otherwise.
873 *
874 * @throws IOException If an IO problem occurs
875 * @throws IllegalStateException If the connection isn't open.
876 */
877 public boolean isResponseAvailable(int timeout)
878 throws IOException {
879 LOG.trace("enter HttpConnection.isResponseAvailable(int)");
880 assertOpen();
881 boolean result = false;
882 if (this.inputStream.available() > 0) {
883 result = true;
884 } else {
885 try {
886 this.socket.setSoTimeout(timeout);
887 int byteRead = inputStream.read();
888 if (byteRead != -1) {
889 inputStream.unread(byteRead);
890 LOG.debug("Input data available");
891 result = true;
892 } else {
893 LOG.debug("Input data not available");
894 }
895 } catch (InterruptedIOException e) {
896 if (LOG.isDebugEnabled()) {
897 LOG.debug("Input data not available after " + timeout + " ms");
898 }
899 } finally {
900 try {
901 socket.setSoTimeout(soTimeout);
902 } catch (IOException ioe) {
903 LOG.debug("An error ocurred while resetting soTimeout, we will assume that"
904 + " no response is available.",
905 ioe);
906 result = false;
907 }
908 }
909 }
910 return result;
911 }
912
913 /***
914 * Writes the specified bytes to the output stream.
915 *
916 * @param data the data to be written
917 * @throws HttpRecoverableException if a SocketException occurs
918 * @throws IllegalStateException if not connected
919 * @throws IOException if an I/O problem occurs
920 * @see #write(byte[],int,int)
921 */
922 public void write(byte[] data)
923 throws IOException, IllegalStateException, HttpRecoverableException {
924 LOG.trace("enter HttpConnection.write(byte[])");
925 this.write(data, 0, data.length);
926 }
927
928 /***
929 * Writes <i>length</i> bytes in <i>data</i> starting at
930 * <i>offset</i> to the output stream.
931 *
932 * The general contract for
933 * write(b, off, len) is that some of the bytes in the array b are written
934 * to the output stream in order; element b[off] is the first byte written
935 * and b[off+len-1] is the last byte written by this operation.
936 *
937 * @param data array containing the data to be written.
938 * @param offset the start offset in the data.
939 * @param length the number of bytes to write.
940 * @throws HttpRecoverableException if a SocketException occurs
941 * @throws IllegalStateException if not connected
942 * @throws IOException if an I/O problem occurs
943 */
944 public void write(byte[] data, int offset, int length)
945 throws IOException, IllegalStateException, HttpRecoverableException {
946 LOG.trace("enter HttpConnection.write(byte[], int, int)");
947
948 if (offset + length > data.length) {
949 throw new HttpRecoverableException("Unable to write:"
950 + " offset=" + offset + " length=" + length
951 + " data.length=" + data.length);
952 } else if (data.length <= 0) {
953 throw new HttpRecoverableException(
954 "Unable to write:" + " data.length=" + data.length);
955 }
956
957 assertOpen();
958
959 try {
960 outputStream.write(data, offset, length);
961 } catch (HttpRecoverableException hre) {
962 throw hre;
963 } catch (SocketException se) {
964 LOG.debug(
965 "HttpConnection: Socket exception while writing data",
966 se);
967 throw new HttpRecoverableException(se.toString());
968 } catch (IOException ioe) {
969 LOG.debug("HttpConnection: Exception while writing data", ioe);
970 throw ioe;
971 }
972 }
973
974 /***
975 * Writes the specified bytes, followed by <tt>"\r\n".getBytes()</tt> to the
976 * output stream.
977 *
978 * @param data the bytes to be written
979 * @throws HttpRecoverableException when socket exceptions occur writing data
980 * @throws IllegalStateException if the connection is not open
981 * @throws IOException if an I/O problem occurs
982 */
983 public void writeLine(byte[] data)
984 throws IOException, IllegalStateException, HttpRecoverableException {
985 LOG.trace("enter HttpConnection.writeLine(byte[])");
986 write(data);
987 writeLine();
988 }
989
990 /***
991 * Writes <tt>"\r\n".getBytes()</tt> to the output stream.
992 *
993 * @throws HttpRecoverableException when socket exceptions occur writing
994 * data
995 * @throws IllegalStateException if the connection is not open
996 * @throws IOException if an I/O problem occurs
997 */
998 public void writeLine()
999 throws IOException, IllegalStateException, HttpRecoverableException {
1000 LOG.trace("enter HttpConnection.writeLine()");
1001 write(CRLF);
1002 }
1003
1004 /***
1005 * Writes the specified String (as bytes) to the output stream.
1006 *
1007 * @param data the string to be written
1008 * @throws HttpRecoverableException when socket exceptions occur writing
1009 * data
1010 * @throws IllegalStateException if the connection is not open
1011 * @throws IOException if an I/O problem occurs
1012 */
1013 public void print(String data)
1014 throws IOException, IllegalStateException, HttpRecoverableException {
1015 LOG.trace("enter HttpConnection.print(String)");
1016 write(HttpConstants.getBytes(data));
1017 }
1018
1019 /***
1020 * Writes the specified String (as bytes), followed by
1021 * <tt>"\r\n".getBytes()</tt> to the output stream.
1022 *
1023 * @param data the data to be written
1024 * @throws HttpRecoverableException when socket exceptions occur writing
1025 * data
1026 * @throws IllegalStateException if the connection is not open
1027 * @throws IOException if an I/O problem occurs
1028 */
1029 public void printLine(String data)
1030 throws IOException, IllegalStateException, HttpRecoverableException {
1031 LOG.trace("enter HttpConnection.printLine(String)");
1032 writeLine(HttpConstants.getBytes(data));
1033 }
1034
1035 /***
1036 * Writes <tt>"\r\n".getBytes()</tt> to the output stream.
1037 *
1038 * @throws HttpRecoverableException when socket exceptions occur writing
1039 * data
1040 * @throws IllegalStateException if the connection is not open
1041 * @throws IOException if an I/O problem occurs
1042 */
1043 public void printLine()
1044 throws IOException, IllegalStateException, HttpRecoverableException {
1045 LOG.trace("enter HttpConnection.printLine()");
1046 writeLine();
1047 }
1048
1049 /***
1050 * Reads up to <tt>"\n"</tt> from the (unchunked) input stream.
1051 * If the stream ends before the line terminator is found,
1052 * the last part of the string will still be returned.
1053 *
1054 * @throws IllegalStateException if the connection is not open
1055 * @throws IOException if an I/O problem occurs
1056 * @return a line from the response
1057 */
1058 public String readLine() throws IOException, IllegalStateException {
1059 LOG.trace("enter HttpConnection.readLine()");
1060
1061 assertOpen();
1062 return HttpParser.readLine(inputStream);
1063 }
1064
1065 /***
1066 * Attempts to shutdown the {@link Socket}'s output, via Socket.shutdownOutput()
1067 * when running on JVM 1.3 or higher.
1068 */
1069 public void shutdownOutput() {
1070 LOG.trace("enter HttpConnection.shutdownOutput()");
1071
1072 try {
1073
1074
1075
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
1084 }
1085
1086 }
1087
1088 /***
1089 * Closes the socket and streams.
1090 */
1091 public void close() {
1092 LOG.trace("enter HttpConnection.close()");
1093 closeSocketAndStreams();
1094 }
1095
1096 /***
1097 * Returns the httpConnectionManager.
1098 * @return HttpConnectionManager
1099 */
1100 public HttpConnectionManager getHttpConnectionManager() {
1101 return httpConnectionManager;
1102 }
1103
1104 /***
1105 * Sets the httpConnectionManager.
1106 * @param httpConnectionManager The httpConnectionManager to set
1107 */
1108 public void setHttpConnectionManager(HttpConnectionManager httpConnectionManager) {
1109 this.httpConnectionManager = httpConnectionManager;
1110 }
1111
1112 /***
1113 * Release the connection.
1114 */
1115 public void releaseConnection() {
1116 LOG.trace("enter HttpConnection.releaseConnection()");
1117
1118
1119 used = true;
1120 if (httpConnectionManager != null) {
1121 httpConnectionManager.releaseConnection(this);
1122 }
1123 }
1124
1125
1126
1127 /***
1128 * Closes everything out.
1129 */
1130 protected void closeSocketAndStreams() {
1131 LOG.trace("enter HttpConnection.closeSockedAndStreams()");
1132
1133
1134 lastResponseInputStream = null;
1135
1136 if (null != outputStream) {
1137 OutputStream temp = outputStream;
1138 outputStream = null;
1139 try {
1140 temp.close();
1141 } catch (Exception ex) {
1142 LOG.debug("Exception caught when closing output", ex);
1143
1144 }
1145 }
1146
1147 if (null != inputStream) {
1148 InputStream temp = inputStream;
1149 inputStream = null;
1150 try {
1151 temp.close();
1152 } catch (Exception ex) {
1153 LOG.debug("Exception caught when closing input", ex);
1154
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
1166 }
1167 }
1168 isOpen = false;
1169 used = false;
1170 tunnelEstablished = false;
1171 usingSecureSocket = false;
1172 }
1173
1174 /***
1175 * Throws an {@link IllegalStateException} if the connection is already open.
1176 *
1177 * @throws IllegalStateException if connected
1178 */
1179 protected void assertNotOpen() throws IllegalStateException {
1180 if (isOpen) {
1181 throw new IllegalStateException("Connection is open");
1182 }
1183 }
1184
1185 /***
1186 * Throws an {@link IllegalStateException} if the connection is not open.
1187 *
1188 * @throws IllegalStateException if not connected
1189 */
1190 protected void assertOpen() throws IllegalStateException {
1191 if (!isOpen) {
1192 throw new IllegalStateException("Connection is not open");
1193 }
1194 }
1195
1196 /***
1197 * Gets the socket's sendBufferSize.
1198 *
1199 * @return the size of the buffer for the socket OutputStream, -1 if the value
1200 * has not been set and the socket has not been opened
1201 *
1202 * @throws SocketException if an error occurs while getting the socket value
1203 *
1204 * @see Socket#getSendBufferSize()
1205 */
1206 public int getSendBufferSize() throws SocketException {
1207 if (socket == null) {
1208 return -1;
1209 } else {
1210 return socket.getSendBufferSize();
1211 }
1212 }
1213
1214 /***
1215 * Sets the socket's sendBufferSize.
1216 *
1217 * @param sendBufferSize the size to set for the socket OutputStream
1218 *
1219 * @throws SocketException if an error occurs while setting the socket value
1220 *
1221 * @see Socket#setSendBufferSize(int)
1222 */
1223 public void setSendBufferSize(int sendBufferSize) throws SocketException {
1224 this.sendBufferSize = sendBufferSize;
1225 if (socket != null) {
1226 socket.setSendBufferSize(sendBufferSize);
1227 }
1228 }
1229
1230
1231 /***
1232 * Signals that a timeout occured while opening the socket.
1233 */
1234 public static class ConnectionTimeoutException extends IOException {
1235 /*** Create an instance */
1236 public ConnectionTimeoutException() {
1237 }
1238 }
1239
1240
1241 /***
1242 * Helper class for wrapping socket based tasks.
1243 */
1244 private abstract class SocketTask implements Runnable {
1245
1246 /*** The socket */
1247 private Socket socket;
1248 /*** The exception */
1249 private IOException exception;
1250
1251 /***
1252 * Set the socket.
1253 * @param newSocket The new socket.
1254 */
1255 protected void setSocket(final Socket newSocket) {
1256 socket = newSocket;
1257 }
1258
1259 /***
1260 * Return the socket.
1261 * @return Socket The socket.
1262 */
1263 protected Socket getSocket() {
1264 return socket;
1265 }
1266 /***
1267 * Perform the logic.
1268 * @throws IOException If an IO problem occurs
1269 */
1270 public abstract void doit() throws IOException;
1271
1272 /*** Execute the logic in this object and keep track of any exceptions. */
1273 public void run() {
1274 try {
1275 doit();
1276 } catch (IOException e) {
1277 exception = e;
1278 }
1279 }
1280 }
1281
1282 /***
1283 * A wrapper for output streams that closes the connection and converts to recoverable
1284 * exceptions as appropriable when an IOException occurs.
1285 */
1286 private class WrappedOutputStream extends OutputStream {
1287
1288 /*** the actual output stream */
1289 private OutputStream out;
1290
1291 /***
1292 * @param out the output stream to wrap
1293 */
1294 public WrappedOutputStream(OutputStream out) {
1295 this.out = out;
1296 }
1297
1298 /***
1299 * Closes the connection and conditionally converts exception to recoverable.
1300 * @param ioe the exception that occurred
1301 * @return the exception to be thrown
1302 */
1303 private IOException handleException(IOException ioe) {
1304
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
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
1369
1370 /*** A flag indicating if this connection has been used since being opened */
1371 private boolean used = false;
1372
1373 /*** My host. */
1374 private String hostName = null;
1375
1376 /*** My virtual host. */
1377 private String virtualName = null;
1378
1379 /*** My port. */
1380 private int portNumber = -1;
1381
1382 /*** My proxy host. */
1383 private String proxyHostName = null;
1384
1385 /*** My proxy port. */
1386 private int proxyPortNumber = -1;
1387
1388 /*** My client Socket. */
1389 private Socket socket = null;
1390
1391 /*** My InputStream. */
1392 private PushbackInputStream inputStream = null;
1393
1394 /*** My OutputStream. */
1395 private OutputStream outputStream = null;
1396
1397 /*** the size of the buffer to use for the socket OutputStream */
1398 private int sendBufferSize = -1;
1399
1400 /*** An {@link InputStream} for the response to an individual request. */
1401 private InputStream lastResponseInputStream = null;
1402
1403 /*** Whether or not the connection is connected. */
1404 protected boolean isOpen = false;
1405
1406 /*** the protocol being used */
1407 private Protocol protocolInUse;
1408
1409 /*** SO_TIMEOUT socket value */
1410 private int soTimeout = 0;
1411
1412 /*** TCP_NODELAY socket value */
1413 private boolean soNodelay = true;
1414
1415 /*** Whether or not the socket is a secure one. */
1416 private boolean usingSecureSocket = false;
1417
1418 /*** Whether the connection is open via a secure tunnel or not */
1419 private boolean tunnelEstablished = false;
1420
1421 /*** Whether or not isStale() is used by isOpen() */
1422 private boolean staleCheckingEnabled = true;
1423
1424 /*** Timeout until connection established (Socket created). 0 means no timeout. */
1425 private int connectTimeout = 0;
1426
1427 /*** the connection manager that created this connection or null */
1428 private HttpConnectionManager httpConnectionManager;
1429
1430 /*** The local interface on which the connection is created, or null for the default */
1431 private InetAddress localAddress;
1432 }