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