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