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.BufferedInputStream;
33 import java.io.BufferedOutputStream;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.io.InterruptedIOException;
37 import java.io.OutputStream;
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.Protocol;
45 import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
46 import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
47 import org.apache.commons.httpclient.util.EncodingUtil;
48 import org.apache.commons.httpclient.util.ExceptionUtil;
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 Glueck
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: 327792 $ $Date: 2005-10-23 09:34:28 -0400 (Sun, 23 Oct 2005) $
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.getPort(),
158 hostConfiguration.getProtocol());
159 this.localAddress = hostConfiguration.getLocalAddress();
160 }
161
162 /***
163 * Creates a new HTTP connection for the given host with the virtual
164 * alias and port via the given proxy host and port using the given
165 * protocol.
166 *
167 * @param proxyHost the host to proxy via
168 * @param proxyPort the port to proxy via
169 * @param host the host to connect to. Parameter value must be non-null.
170 * @param virtualHost No longer applicable.
171 * @param port the port to connect to
172 * @param protocol The protocol to use. Parameter value must be non-null.
173 *
174 * @deprecated use #HttpConnection(String, int, String, int, Protocol)
175 */
176 public HttpConnection(
177 String proxyHost,
178 int proxyPort,
179 String host,
180 String virtualHost,
181 int port,
182 Protocol protocol) {
183 this(proxyHost, proxyPort, host, port, protocol);
184 }
185
186 /***
187 * Creates a new HTTP connection for the given host with the virtual
188 * alias and port via the given proxy host and port using the given
189 * protocol.
190 *
191 * @param proxyHost the host to proxy via
192 * @param proxyPort the port to proxy via
193 * @param host the host to connect to. Parameter value must be non-null.
194 * @param port the port to connect to
195 * @param protocol The protocol to use. Parameter value must be non-null.
196 */
197 public HttpConnection(
198 String proxyHost,
199 int proxyPort,
200 String host,
201 int port,
202 Protocol protocol) {
203
204 if (host == null) {
205 throw new IllegalArgumentException("host parameter is null");
206 }
207 if (protocol == null) {
208 throw new IllegalArgumentException("protocol is null");
209 }
210
211 proxyHostName = proxyHost;
212 proxyPortNumber = proxyPort;
213 hostName = host;
214 portNumber = protocol.resolvePort(port);
215 protocolInUse = protocol;
216 }
217
218
219
220 /***
221 * Returns the connection socket.
222 *
223 * @return the socket.
224 *
225 * @since 3.0
226 */
227 protected Socket getSocket() {
228 return this.socket;
229 }
230
231 /***
232 * Returns the host.
233 *
234 * @return the host.
235 */
236 public String getHost() {
237 return hostName;
238 }
239
240 /***
241 * Sets the host to connect to.
242 *
243 * @param host the host to connect to. Parameter value must be non-null.
244 * @throws IllegalStateException if the connection is already open
245 */
246 public void setHost(String host) throws IllegalStateException {
247 if (host == null) {
248 throw new IllegalArgumentException("host parameter is null");
249 }
250 assertNotOpen();
251 hostName = host;
252 }
253
254 /***
255 * Returns the target virtual host.
256 *
257 * @return the virtual host.
258 *
259 * @deprecated no longer applicable
260 */
261
262 public String getVirtualHost() {
263 return this.hostName;
264 }
265
266 /***
267 * Sets the virtual host to target.
268 *
269 * @param host the virtual host name that should be used instead of
270 * physical host name when sending HTTP requests. Virtual host
271 * name can be set to <tt> null</tt> if virtual host name is not
272 * to be used
273 *
274 * @throws IllegalStateException if the connection is already open
275 *
276 * @deprecated no longer applicable
277 */
278
279 public void setVirtualHost(String host) throws IllegalStateException {
280 assertNotOpen();
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 * Sets the protocol used to establish the connection
373 *
374 * @param protocol The protocol to use.
375 *
376 * @throws IllegalStateException if the connection is already open
377 */
378 public void setProtocol(Protocol protocol) {
379 assertNotOpen();
380
381 if (protocol == null) {
382 throw new IllegalArgumentException("protocol is null");
383 }
384
385 protocolInUse = protocol;
386
387 }
388
389 /***
390 * Return the local address used when creating the connection.
391 * If <tt>null</tt>, the default address is used.
392 *
393 * @return InetAddress the local address to be used when creating Sockets
394 */
395 public InetAddress getLocalAddress() {
396 return this.localAddress;
397 }
398
399 /***
400 * Set the local address used when creating the connection.
401 * If unset or <tt>null</tt>, the default address is used.
402 *
403 * @param localAddress the local address to use
404 */
405 public void setLocalAddress(InetAddress localAddress) {
406 assertNotOpen();
407 this.localAddress = localAddress;
408 }
409
410 /***
411 * Tests if the connection is open.
412 *
413 * @return <code>true</code> if the connection is open
414 */
415 public boolean isOpen() {
416 return isOpen;
417 }
418
419 /***
420 * Closes the connection if stale.
421 *
422 * @return <code>true</code> if the connection was stale and therefore closed,
423 * <code>false</code> otherwise.
424 *
425 * @see #isStale()
426 *
427 * @since 3.0
428 */
429 public boolean closeIfStale() throws IOException {
430 if (isOpen && isStale()) {
431 LOG.debug("Connection is stale, closing...");
432 close();
433 return true;
434 }
435 return false;
436 }
437
438 /***
439 * Tests if stale checking is enabled.
440 *
441 * @return <code>true</code> if enabled
442 *
443 * @see #isStale()
444 *
445 * @deprecated Use {@link HttpConnectionParams#isStaleCheckingEnabled()},
446 * {@link HttpConnection#getParams()}.
447 */
448 public boolean isStaleCheckingEnabled() {
449 return this.params.isStaleCheckingEnabled();
450 }
451
452 /***
453 * Sets whether or not isStale() will be called when testing if this connection is open.
454 *
455 * <p>Setting this flag to <code>false</code> will increase performance when reusing
456 * connections, but it will also make them less reliable. Stale checking ensures that
457 * connections are viable before they are used. When set to <code>false</code> some
458 * method executions will result in IOExceptions and they will have to be retried.</p>
459 *
460 * @param staleCheckEnabled <code>true</code> to enable isStale()
461 *
462 * @see #isStale()
463 * @see #isOpen()
464 *
465 * @deprecated Use {@link HttpConnectionParams#setStaleCheckingEnabled(boolean)},
466 * {@link HttpConnection#getParams()}.
467 */
468 public void setStaleCheckingEnabled(boolean staleCheckEnabled) {
469 this.params.setStaleCheckingEnabled(staleCheckEnabled);
470 }
471
472 /***
473 * Determines whether this connection is "stale", which is to say that either
474 * it is no longer open, or an attempt to read the connection would fail.
475 *
476 * <p>Unfortunately, due to the limitations of the JREs prior to 1.4, it is
477 * not possible to test a connection to see if both the read and write channels
478 * are open - except by reading and writing. This leads to a difficulty when
479 * some connections leave the "write" channel open, but close the read channel
480 * and ignore the request. This function attempts to ameliorate that
481 * problem by doing a test read, assuming that the caller will be doing a
482 * write followed by a read, rather than the other way around.
483 * </p>
484 *
485 * <p>To avoid side-effects, the underlying connection is wrapped by a
486 * {@link BufferedInputStream}, so although data might be read, what is visible
487 * to clients of the connection will not change with this call.</p.
488 *
489 * @throws IOException if the stale connection test is interrupted.
490 *
491 * @return <tt>true</tt> if the connection is already closed, or a read would
492 * fail.
493 */
494 protected boolean isStale() throws IOException {
495 boolean isStale = true;
496 if (isOpen) {
497
498
499 isStale = false;
500 try {
501 if (inputStream.available() <= 0) {
502 try {
503 socket.setSoTimeout(1);
504 inputStream.mark(1);
505 int byteRead = inputStream.read();
506 if (byteRead == -1) {
507
508
509 isStale = true;
510 } else {
511 inputStream.reset();
512 }
513 } finally {
514 socket.setSoTimeout(this.params.getSoTimeout());
515 }
516 }
517 } catch (InterruptedIOException e) {
518 if (!ExceptionUtil.isSocketTimeoutException(e)) {
519 throw e;
520 }
521
522 } catch (IOException e) {
523
524 LOG.debug(
525 "An error occurred while reading from the socket, is appears to be stale",
526 e
527 );
528 isStale = true;
529 }
530 }
531
532 return isStale;
533 }
534
535 /***
536 * Returns <tt>true</tt> if the connection is established via a proxy,
537 * <tt>false</tt> otherwise.
538 *
539 * @return <tt>true</tt> if a proxy is used to establish the connection,
540 * <tt>false</tt> otherwise.
541 */
542 public boolean isProxied() {
543 return (!(null == proxyHostName || 0 >= proxyPortNumber));
544 }
545
546 /***
547 * Set the state to keep track of the last response for the last request.
548 *
549 * <p>The connection managers use this to ensure that previous requests are
550 * properly closed before a new request is attempted. That way, a GET
551 * request need not be read in its entirety before a new request is issued.
552 * Instead, this stream can be closed as appropriate.</p>
553 *
554 * @param inStream The stream associated with an HttpMethod.
555 */
556 public void setLastResponseInputStream(InputStream inStream) {
557 lastResponseInputStream = inStream;
558 }
559
560 /***
561 * Returns the stream used to read the last response's body.
562 *
563 * <p>Clients will generally not need to call this function unless
564 * using HttpConnection directly, instead of calling {@link HttpClient#executeMethod}.
565 * For those clients, call this function, and if it returns a non-null stream,
566 * close the stream before attempting to execute a method. Note that
567 * calling "close" on the stream returned by this function <i>may</i> close
568 * the connection if the previous response contained a "Connection: close" header. </p>
569 *
570 * @return An {@link InputStream} corresponding to the body of the last
571 * response.
572 */
573 public InputStream getLastResponseInputStream() {
574 return lastResponseInputStream;
575 }
576
577
578
579 /***
580 * Returns {@link HttpConnectionParams HTTP protocol parameters} associated with this method.
581 *
582 * @return HTTP parameters.
583 *
584 * @since 3.0
585 */
586 public HttpConnectionParams getParams() {
587 return this.params;
588 }
589
590 /***
591 * Assigns {@link HttpConnectionParams HTTP protocol parameters} for this method.
592 *
593 * @since 3.0
594 *
595 * @see HttpConnectionParams
596 */
597 public void setParams(final HttpConnectionParams params) {
598 if (params == null) {
599 throw new IllegalArgumentException("Parameters may not be null");
600 }
601 this.params = params;
602 }
603
604 /***
605 * Set the {@link Socket}'s timeout, via {@link Socket#setSoTimeout}. If the
606 * connection is already open, the SO_TIMEOUT is changed. If no connection
607 * is open, then subsequent connections will use the timeout value.
608 * <p>
609 * Note: This is not a connection timeout but a timeout on network traffic!
610 *
611 * @param timeout the timeout value
612 * @throws SocketException - if there is an error in the underlying
613 * protocol, such as a TCP error.
614 *
615 * @deprecated Use {@link HttpConnectionParams#setSoTimeout(int)},
616 * {@link HttpConnection#getParams()}.
617 */
618 public void setSoTimeout(int timeout)
619 throws SocketException, IllegalStateException {
620 this.params.setSoTimeout(timeout);
621 if (this.socket != null) {
622 this.socket.setSoTimeout(timeout);
623 }
624 }
625
626 /***
627 * Sets <code>SO_TIMEOUT</code> value directly on the underlying {@link Socket socket}.
628 * This method does not change the default read timeout value set via
629 * {@link HttpConnectionParams}.
630 *
631 * @param timeout the timeout value
632 * @throws SocketException - if there is an error in the underlying
633 * protocol, such as a TCP error.
634 * @throws IllegalStateException if not connected
635 *
636 * @since 3.0
637 */
638 public void setSocketTimeout(int timeout)
639 throws SocketException, IllegalStateException {
640 assertOpen();
641 if (this.socket != null) {
642 this.socket.setSoTimeout(timeout);
643 }
644 }
645
646 /***
647 * Returns the {@link Socket}'s timeout, via {@link Socket#getSoTimeout}, if the
648 * connection is already open. If no connection is open, return the value subsequent
649 * connection will use.
650 * <p>
651 * Note: This is not a connection timeout but a timeout on network traffic!
652 *
653 * @return the timeout value
654 *
655 * @deprecated Use {@link HttpConnectionParams#getSoTimeout()},
656 * {@link HttpConnection#getParams()}.
657 */
658 public int getSoTimeout() throws SocketException {
659 return this.params.getSoTimeout();
660 }
661
662 /***
663 * Sets the connection timeout. This is the maximum time that may be spent
664 * until a connection is established. The connection will fail after this
665 * amount of time.
666 * @param timeout The timeout in milliseconds. 0 means timeout is not used.
667 *
668 * @deprecated Use {@link HttpConnectionParams#setConnectionTimeout(int)},
669 * {@link HttpConnection#getParams()}.
670 */
671 public void setConnectionTimeout(int timeout) {
672 this.params.setConnectionTimeout(timeout);
673 }
674
675 /***
676 * Establishes a connection to the specified host and port
677 * (via a proxy if specified).
678 * The underlying socket is created from the {@link ProtocolSocketFactory}.
679 *
680 * @throws IOException if an attempt to establish the connection results in an
681 * I/O error.
682 */
683 public void open() throws IOException {
684 LOG.trace("enter HttpConnection.open()");
685
686 final String host = (proxyHostName == null) ? hostName : proxyHostName;
687 final int port = (proxyHostName == null) ? portNumber : proxyPortNumber;
688 assertNotOpen();
689
690 if (LOG.isDebugEnabled()) {
691 LOG.debug("Open connection to " + host + ":" + port);
692 }
693
694 try {
695 if (this.socket == null) {
696 usingSecureSocket = isSecure() && !isProxied();
697
698
699 ProtocolSocketFactory socketFactory = null;
700 if (isSecure() && isProxied()) {
701 Protocol defaultprotocol = Protocol.getProtocol("http");
702 socketFactory = defaultprotocol.getSocketFactory();
703 } else {
704 socketFactory = this.protocolInUse.getSocketFactory();
705 }
706 this.socket = socketFactory.createSocket(
707 host, port,
708 localAddress, 0,
709 this.params);
710 }
711
712
713
714
715
716
717
718
719
720 socket.setTcpNoDelay(this.params.getTcpNoDelay());
721 socket.setSoTimeout(this.params.getSoTimeout());
722
723 int linger = this.params.getLinger();
724 if (linger >= 0) {
725 socket.setSoLinger(linger > 0, linger);
726 }
727
728 int sndBufSize = this.params.getSendBufferSize();
729 if (sndBufSize >= 0) {
730 socket.setSendBufferSize(sndBufSize);
731 }
732 int rcvBufSize = this.params.getReceiveBufferSize();
733 if (rcvBufSize >= 0) {
734 socket.setReceiveBufferSize(rcvBufSize);
735 }
736 int outbuffersize = socket.getSendBufferSize();
737 if ((outbuffersize > 2048) || (outbuffersize <= 0)) {
738 outbuffersize = 2048;
739 }
740 int inbuffersize = socket.getReceiveBufferSize();
741 if ((inbuffersize > 2048) || (inbuffersize <= 0)) {
742 inbuffersize = 2048;
743 }
744 inputStream = new BufferedInputStream(socket.getInputStream(), inbuffersize);
745 outputStream = new BufferedOutputStream(socket.getOutputStream(), outbuffersize);
746 isOpen = true;
747 } catch (IOException e) {
748
749
750 closeSocketAndStreams();
751 throw e;
752 }
753 }
754
755 /***
756 * Instructs the proxy to establish a secure tunnel to the host. The socket will
757 * be switched to the secure socket. Subsequent communication is done via the secure
758 * socket. The method can only be called once on a proxied secure connection.
759 *
760 * @throws IllegalStateException if connection is not secure and proxied or
761 * if the socket is already secure.
762 * @throws IOException if an attempt to establish the secure tunnel results in an
763 * I/O error.
764 */
765 public void tunnelCreated() throws IllegalStateException, IOException {
766 LOG.trace("enter HttpConnection.tunnelCreated()");
767
768 if (!isSecure() || !isProxied()) {
769 throw new IllegalStateException(
770 "Connection must be secure "
771 + "and proxied to use this feature");
772 }
773
774 if (usingSecureSocket) {
775 throw new IllegalStateException("Already using a secure socket");
776 }
777
778 if (LOG.isDebugEnabled()) {
779 LOG.debug("Secure tunnel to " + this.hostName + ":" + this.portNumber);
780 }
781
782 SecureProtocolSocketFactory socketFactory =
783 (SecureProtocolSocketFactory) protocolInUse.getSocketFactory();
784
785 socket = socketFactory.createSocket(socket, hostName, portNumber, true);
786 int sndBufSize = this.params.getSendBufferSize();
787 if (sndBufSize >= 0) {
788 socket.setSendBufferSize(sndBufSize);
789 }
790 int rcvBufSize = this.params.getReceiveBufferSize();
791 if (rcvBufSize >= 0) {
792 socket.setReceiveBufferSize(rcvBufSize);
793 }
794 int outbuffersize = socket.getSendBufferSize();
795 if (outbuffersize > 2048) {
796 outbuffersize = 2048;
797 }
798 int inbuffersize = socket.getReceiveBufferSize();
799 if (inbuffersize > 2048) {
800 inbuffersize = 2048;
801 }
802 inputStream = new BufferedInputStream(socket.getInputStream(), inbuffersize);
803 outputStream = new BufferedOutputStream(socket.getOutputStream(), outbuffersize);
804 usingSecureSocket = true;
805 tunnelEstablished = true;
806 }
807
808 /***
809 * Indicates if the connection is completely transparent from end to end.
810 *
811 * @return true if conncetion is not proxied or tunneled through a transparent
812 * proxy; false otherwise.
813 */
814 public boolean isTransparent() {
815 return !isProxied() || tunnelEstablished;
816 }
817
818 /***
819 * Flushes the output request stream. This method should be called to
820 * ensure that data written to the request OutputStream is sent to the server.
821 *
822 * @throws IOException if an I/O problem occurs
823 */
824 public void flushRequestOutputStream() throws IOException {
825 LOG.trace("enter HttpConnection.flushRequestOutputStream()");
826 assertOpen();
827 outputStream.flush();
828 }
829
830 /***
831 * Returns an {@link OutputStream} suitable for writing the request.
832 *
833 * @throws IllegalStateException if the connection is not open
834 * @throws IOException if an I/O problem occurs
835 * @return a stream to write the request to
836 */
837 public OutputStream getRequestOutputStream()
838 throws IOException, IllegalStateException {
839 LOG.trace("enter HttpConnection.getRequestOutputStream()");
840 assertOpen();
841 OutputStream out = this.outputStream;
842 if (Wire.CONTENT_WIRE.enabled()) {
843 out = new WireLogOutputStream(out, Wire.CONTENT_WIRE);
844 }
845 return out;
846 }
847
848 /***
849 * Return a {@link InputStream} suitable for reading the response.
850 * @return InputStream The response input stream.
851 * @throws IOException If an IO problem occurs
852 * @throws IllegalStateException If the connection isn't open.
853 */
854 public InputStream getResponseInputStream()
855 throws IOException, IllegalStateException {
856 LOG.trace("enter HttpConnection.getResponseInputStream()");
857 assertOpen();
858 return inputStream;
859 }
860
861 /***
862 * Tests if input data avaialble. This method returns immediately
863 * and does not perform any read operations on the input socket
864 *
865 * @return boolean <tt>true</tt> if input data is available,
866 * <tt>false</tt> otherwise.
867 *
868 * @throws IOException If an IO problem occurs
869 * @throws IllegalStateException If the connection isn't open.
870 */
871 public boolean isResponseAvailable()
872 throws IOException {
873 LOG.trace("enter HttpConnection.isResponseAvailable()");
874 if (this.isOpen) {
875 return this.inputStream.available() > 0;
876 } else {
877 return false;
878 }
879 }
880
881 /***
882 * Tests if input data becomes available within the given period time in milliseconds.
883 *
884 * @param timeout The number milliseconds to wait for input data to become available
885 * @return boolean <tt>true</tt> if input data is availble,
886 * <tt>false</tt> otherwise.
887 *
888 * @throws IOException If an IO problem occurs
889 * @throws IllegalStateException If the connection isn't open.
890 */
891 public boolean isResponseAvailable(int timeout)
892 throws IOException {
893 LOG.trace("enter HttpConnection.isResponseAvailable(int)");
894 assertOpen();
895 boolean result = false;
896 if (this.inputStream.available() > 0) {
897 result = true;
898 } else {
899 try {
900 this.socket.setSoTimeout(timeout);
901 inputStream.mark(1);
902 int byteRead = inputStream.read();
903 if (byteRead != -1) {
904 inputStream.reset();
905 LOG.debug("Input data available");
906 result = true;
907 } else {
908 LOG.debug("Input data not available");
909 }
910 } catch (InterruptedIOException e) {
911 if (!ExceptionUtil.isSocketTimeoutException(e)) {
912 throw e;
913 }
914 if (LOG.isDebugEnabled()) {
915 LOG.debug("Input data not available after " + timeout + " ms");
916 }
917 } finally {
918 try {
919 socket.setSoTimeout(this.params.getSoTimeout());
920 } catch (IOException ioe) {
921 LOG.debug("An error ocurred while resetting soTimeout, we will assume that"
922 + " no response is available.",
923 ioe);
924 result = false;
925 }
926 }
927 }
928 return result;
929 }
930
931 /***
932 * Writes the specified bytes to the output stream.
933 *
934 * @param data the data to be written
935 * @throws IllegalStateException if not connected
936 * @throws IOException if an I/O problem occurs
937 * @see #write(byte[],int,int)
938 */
939 public void write(byte[] data)
940 throws IOException, IllegalStateException {
941 LOG.trace("enter HttpConnection.write(byte[])");
942 this.write(data, 0, data.length);
943 }
944
945 /***
946 * Writes <i>length</i> bytes in <i>data</i> starting at
947 * <i>offset</i> to the output stream.
948 *
949 * The general contract for
950 * write(b, off, len) is that some of the bytes in the array b are written
951 * to the output stream in order; element b[off] is the first byte written
952 * and b[off+len-1] is the last byte written by this operation.
953 *
954 * @param data array containing the data to be written.
955 * @param offset the start offset in the data.
956 * @param length the number of bytes to write.
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 {
962 LOG.trace("enter HttpConnection.write(byte[], int, int)");
963
964 if (offset < 0) {
965 throw new IllegalArgumentException("Array offset may not be negative");
966 }
967 if (length < 0) {
968 throw new IllegalArgumentException("Array length may not be negative");
969 }
970 if (offset + length > data.length) {
971 throw new IllegalArgumentException("Given offset and length exceed the array length");
972 }
973 assertOpen();
974 this.outputStream.write(data, offset, length);
975 }
976
977 /***
978 * Writes the specified bytes, followed by <tt>"\r\n".getBytes()</tt> to the
979 * output stream.
980 *
981 * @param data the bytes to be written
982 * @throws IllegalStateException if the connection is not open
983 * @throws IOException if an I/O problem occurs
984 */
985 public void writeLine(byte[] data)
986 throws IOException, IllegalStateException {
987 LOG.trace("enter HttpConnection.writeLine(byte[])");
988 write(data);
989 writeLine();
990 }
991
992 /***
993 * Writes <tt>"\r\n".getBytes()</tt> to the output stream.
994 *
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 {
1000 LOG.trace("enter HttpConnection.writeLine()");
1001 write(CRLF);
1002 }
1003
1004 /***
1005 * @deprecated Use {@link #print(String, String)}
1006 *
1007 * Writes the specified String (as bytes) to the output stream.
1008 *
1009 * @param data the string to be written
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 {
1015 LOG.trace("enter HttpConnection.print(String)");
1016 write(EncodingUtil.getBytes(data, "ISO-8859-1"));
1017 }
1018
1019 /***
1020 * Writes the specified String (as bytes) to the output stream.
1021 *
1022 * @param data the string to be written
1023 * @param charset the charset to use for writing the data
1024 * @throws IllegalStateException if the connection is not open
1025 * @throws IOException if an I/O problem occurs
1026 *
1027 * @since 3.0
1028 */
1029 public void print(String data, String charset)
1030 throws IOException, IllegalStateException {
1031 LOG.trace("enter HttpConnection.print(String)");
1032 write(EncodingUtil.getBytes(data, charset));
1033 }
1034
1035 /***
1036 * @deprecated Use {@link #printLine(String, String)}
1037 *
1038 * Writes the specified String (as bytes), followed by
1039 * <tt>"\r\n".getBytes()</tt> to the output stream.
1040 *
1041 * @param data the data to be written
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 {
1047 LOG.trace("enter HttpConnection.printLine(String)");
1048 writeLine(EncodingUtil.getBytes(data, "ISO-8859-1"));
1049 }
1050
1051 /***
1052 * Writes the specified String (as bytes), followed by
1053 * <tt>"\r\n".getBytes()</tt> to the output stream.
1054 *
1055 * @param data the data to be written
1056 * @param charset the charset to use for writing the data
1057 * @throws IllegalStateException if the connection is not open
1058 * @throws IOException if an I/O problem occurs
1059 *
1060 * @since 3.0
1061 */
1062 public void printLine(String data, String charset)
1063 throws IOException, IllegalStateException {
1064 LOG.trace("enter HttpConnection.printLine(String)");
1065 writeLine(EncodingUtil.getBytes(data, charset));
1066 }
1067
1068 /***
1069 * Writes <tt>"\r\n".getBytes()</tt> to the output stream.
1070 *
1071 * @throws IllegalStateException if the connection is not open
1072 * @throws IOException if an I/O problem occurs
1073 */
1074 public void printLine()
1075 throws IOException, IllegalStateException {
1076 LOG.trace("enter HttpConnection.printLine()");
1077 writeLine();
1078 }
1079
1080 /***
1081 * Reads up to <tt>"\n"</tt> from the (unchunked) input stream.
1082 * If the stream ends before the line terminator is found,
1083 * the last part of the string will still be returned.
1084 *
1085 * @throws IllegalStateException if the connection is not open
1086 * @throws IOException if an I/O problem occurs
1087 * @return a line from the response
1088 *
1089 * @deprecated use #readLine(String)
1090 */
1091 public String readLine() throws IOException, IllegalStateException {
1092 LOG.trace("enter HttpConnection.readLine()");
1093
1094 assertOpen();
1095 return HttpParser.readLine(inputStream);
1096 }
1097
1098 /***
1099 * Reads up to <tt>"\n"</tt> from the (unchunked) input stream.
1100 * If the stream ends before the line terminator is found,
1101 * the last part of the string will still be returned.
1102 *
1103 * @param charset the charset to use for reading the data
1104 *
1105 * @throws IllegalStateException if the connection is not open
1106 * @throws IOException if an I/O problem occurs
1107 * @return a line from the response
1108 *
1109 * @since 3.0
1110 */
1111 public String readLine(final String charset) throws IOException, IllegalStateException {
1112 LOG.trace("enter HttpConnection.readLine()");
1113
1114 assertOpen();
1115 return HttpParser.readLine(inputStream, charset);
1116 }
1117
1118 /***
1119 * Attempts to shutdown the {@link Socket}'s output, via Socket.shutdownOutput()
1120 * when running on JVM 1.3 or higher.
1121 *
1122 * @deprecated unused
1123 */
1124 public void shutdownOutput() {
1125 LOG.trace("enter HttpConnection.shutdownOutput()");
1126
1127 try {
1128
1129
1130
1131 Class[] paramsClasses = new Class[0];
1132 Method shutdownOutput =
1133 socket.getClass().getMethod("shutdownOutput", paramsClasses);
1134 Object[] params = new Object[0];
1135 shutdownOutput.invoke(socket, params);
1136 } catch (Exception ex) {
1137 LOG.debug("Unexpected Exception caught", ex);
1138
1139 }
1140
1141 }
1142
1143 /***
1144 * Closes the socket and streams.
1145 */
1146 public void close() {
1147 LOG.trace("enter HttpConnection.close()");
1148 closeSocketAndStreams();
1149 }
1150
1151 /***
1152 * Returns the httpConnectionManager.
1153 * @return HttpConnectionManager
1154 */
1155 public HttpConnectionManager getHttpConnectionManager() {
1156 return httpConnectionManager;
1157 }
1158
1159 /***
1160 * Sets the httpConnectionManager.
1161 * @param httpConnectionManager The httpConnectionManager to set
1162 */
1163 public void setHttpConnectionManager(HttpConnectionManager httpConnectionManager) {
1164 this.httpConnectionManager = httpConnectionManager;
1165 }
1166
1167 /***
1168 * Releases the connection. If the connection is locked or does not have a connection
1169 * manager associated with it, this method has no effect. Note that it is completely safe
1170 * to call this method multiple times.
1171 */
1172 public void releaseConnection() {
1173 LOG.trace("enter HttpConnection.releaseConnection()");
1174 if (locked) {
1175 LOG.debug("Connection is locked. Call to releaseConnection() ignored.");
1176 } else if (httpConnectionManager != null) {
1177 LOG.debug("Releasing connection back to connection manager.");
1178 httpConnectionManager.releaseConnection(this);
1179 } else {
1180 LOG.warn("HttpConnectionManager is null. Connection cannot be released.");
1181 }
1182 }
1183
1184 /***
1185 * Tests if the connection is locked. Locked connections cannot be released.
1186 * An attempt to release a locked connection will have no effect.
1187 *
1188 * @return <tt>true</tt> if the connection is locked, <tt>false</tt> otherwise.
1189 *
1190 * @since 3.0
1191 */
1192 protected boolean isLocked() {
1193 return locked;
1194 }
1195
1196 /***
1197 * Locks or unlocks the connection. Locked connections cannot be released.
1198 * An attempt to release a locked connection will have no effect.
1199 *
1200 * @param locked <tt>true</tt> to lock the connection, <tt>false</tt> to unlock
1201 * the connection.
1202 *
1203 * @since 3.0
1204 */
1205 protected void setLocked(boolean locked) {
1206 this.locked = locked;
1207 }
1208
1209
1210 /***
1211 * Closes everything out.
1212 */
1213 protected void closeSocketAndStreams() {
1214 LOG.trace("enter HttpConnection.closeSockedAndStreams()");
1215
1216 isOpen = false;
1217
1218
1219 lastResponseInputStream = null;
1220
1221 if (null != outputStream) {
1222 OutputStream temp = outputStream;
1223 outputStream = null;
1224 try {
1225 temp.close();
1226 } catch (Exception ex) {
1227 LOG.debug("Exception caught when closing output", ex);
1228
1229 }
1230 }
1231
1232 if (null != inputStream) {
1233 InputStream temp = inputStream;
1234 inputStream = null;
1235 try {
1236 temp.close();
1237 } catch (Exception ex) {
1238 LOG.debug("Exception caught when closing input", ex);
1239
1240 }
1241 }
1242
1243 if (null != socket) {
1244 Socket temp = socket;
1245 socket = null;
1246 try {
1247 temp.close();
1248 } catch (Exception ex) {
1249 LOG.debug("Exception caught when closing socket", ex);
1250
1251 }
1252 }
1253
1254 tunnelEstablished = false;
1255 usingSecureSocket = false;
1256 }
1257
1258 /***
1259 * Throws an {@link IllegalStateException} if the connection is already open.
1260 *
1261 * @throws IllegalStateException if connected
1262 */
1263 protected void assertNotOpen() throws IllegalStateException {
1264 if (isOpen) {
1265 throw new IllegalStateException("Connection is open");
1266 }
1267 }
1268
1269 /***
1270 * Throws an {@link IllegalStateException} if the connection is not open.
1271 *
1272 * @throws IllegalStateException if not connected
1273 */
1274 protected void assertOpen() throws IllegalStateException {
1275 if (!isOpen) {
1276 throw new IllegalStateException("Connection is not open");
1277 }
1278 }
1279
1280 /***
1281 * Gets the socket's sendBufferSize.
1282 *
1283 * @return the size of the buffer for the socket OutputStream, -1 if the value
1284 * has not been set and the socket has not been opened
1285 *
1286 * @throws SocketException if an error occurs while getting the socket value
1287 *
1288 * @see Socket#getSendBufferSize()
1289 */
1290 public int getSendBufferSize() throws SocketException {
1291 if (socket == null) {
1292 return -1;
1293 } else {
1294 return socket.getSendBufferSize();
1295 }
1296 }
1297
1298 /***
1299 * Sets the socket's sendBufferSize.
1300 *
1301 * @param sendBufferSize the size to set for the socket OutputStream
1302 *
1303 * @throws SocketException if an error occurs while setting the socket value
1304 *
1305 * @see Socket#setSendBufferSize(int)
1306 *
1307 * @deprecated Use {@link HttpConnectionParams#setSendBufferSize(int)},
1308 * {@link HttpConnection#getParams()}.
1309 */
1310 public void setSendBufferSize(int sendBufferSize) throws SocketException {
1311 this.params.setSendBufferSize(sendBufferSize);
1312 }
1313
1314
1315
1316 /*** <tt>"\r\n"</tt>, as bytes. */
1317 private static final byte[] CRLF = new byte[] {(byte) 13, (byte) 10};
1318
1319 /*** Log object for this class. */
1320 private static final Log LOG = LogFactory.getLog(HttpConnection.class);
1321
1322
1323
1324 /*** My host. */
1325 private String hostName = null;
1326
1327 /*** My port. */
1328 private int portNumber = -1;
1329
1330 /*** My proxy host. */
1331 private String proxyHostName = null;
1332
1333 /*** My proxy port. */
1334 private int proxyPortNumber = -1;
1335
1336 /*** My client Socket. */
1337 private Socket socket = null;
1338
1339 /*** My InputStream. */
1340 private InputStream inputStream = null;
1341
1342 /*** My OutputStream. */
1343 private OutputStream outputStream = null;
1344
1345 /*** An {@link InputStream} for the response to an individual request. */
1346 private InputStream lastResponseInputStream = null;
1347
1348 /*** Whether or not the connection is connected. */
1349 protected boolean isOpen = false;
1350
1351 /*** the protocol being used */
1352 private Protocol protocolInUse;
1353
1354 /*** Collection of HTTP parameters associated with this HTTP connection*/
1355 private HttpConnectionParams params = new HttpConnectionParams();
1356
1357 /*** flag to indicate if this connection can be released, if locked the connection cannot be
1358 * released */
1359 private boolean locked = false;
1360
1361 /*** Whether or not the socket is a secure one. */
1362 private boolean usingSecureSocket = false;
1363
1364 /*** Whether the connection is open via a secure tunnel or not */
1365 private boolean tunnelEstablished = false;
1366
1367 /*** the connection manager that created this connection or null */
1368 private HttpConnectionManager httpConnectionManager;
1369
1370 /*** The local interface on which the connection is created, or null for the default */
1371 private InetAddress localAddress;
1372 }