View Javadoc
1 /* 2 * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpConnection.java,v 1.67.2.4 2003/09/10 21:34:11 mbecke Exp $ 3 * $Revision: 1.67.2.4 $ 4 * $Date: 2003/09/10 21:34:11 $ 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.67.2.4 $ $Date: 2003/09/10 21:34:11 $ 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 proxyHostName = proxyHost; 251 proxyPortNumber = proxyPort; 252 hostName = host; 253 virtualName = virtualHost; 254 portNumber = protocol.resolvePort(port); 255 protocolInUse = protocol; 256 } 257 258 // ------------------------------------------ Attribute Setters and Getters 259 260 /*** 261 * Return my host name. 262 * 263 * @return my host. 264 */ 265 public String getHost() { 266 return hostName; 267 } 268 269 /*** 270 * Set my host name. 271 * 272 * @param host the host I should connect to. Parameter value must be non-null. 273 * @throws IllegalStateException if I am already connected 274 */ 275 public void setHost(String host) throws IllegalStateException { 276 if (host == null) { 277 throw new IllegalArgumentException("host parameter is null"); 278 } 279 assertNotOpen(); 280 hostName = host; 281 } 282 283 /*** 284 * Return my virtual host name. 285 * 286 * @return my virtual host. 287 */ 288 public String getVirtualHost() { 289 return virtualName; 290 } 291 292 /*** 293 * Set my virtual host name. 294 * 295 * @param host the virtual host name that should be used instead of 296 * physical host name when sending HTTP requests. Virtual host 297 * name can be set to <tt> null</tt> if virtual host name is not 298 * to be used 299 * @throws IllegalStateException if I am already connected 300 */ 301 public void setVirtualHost(String host) throws IllegalStateException { 302 assertNotOpen(); 303 virtualName = host; 304 } 305 306 /*** 307 * Return my port. 308 * 309 * If the port is -1 (or less than 0) the default port for 310 * the current protocol is returned. 311 * 312 * @return my port. 313 */ 314 public int getPort() { 315 if (portNumber < 0) { 316 return isSecure() ? 443 : 80; 317 } else { 318 return portNumber; 319 } 320 } 321 322 /*** 323 * Set my port. 324 * 325 * @param port the port I should connect to 326 * @throws IllegalStateException if I am already connected 327 */ 328 public void setPort(int port) throws IllegalStateException { 329 assertNotOpen(); 330 portNumber = port; 331 } 332 333 /*** 334 * Return my proxy host. 335 * 336 * @return my proxy host. 337 */ 338 public String getProxyHost() { 339 return proxyHostName; 340 } 341 342 /*** 343 * Set the host I should proxy through. 344 * 345 * @param host the host I should proxy through. 346 * @throws IllegalStateException if I am already connected 347 */ 348 public void setProxyHost(String host) throws IllegalStateException { 349 assertNotOpen(); 350 proxyHostName = host; 351 } 352 353 /*** 354 * Return my proxy port. 355 * 356 * @return my proxy port. 357 */ 358 public int getProxyPort() { 359 return proxyPortNumber; 360 } 361 362 /*** 363 * Set the port I should proxy through. 364 * 365 * @param port the host I should proxy through. 366 * @throws IllegalStateException if I am already connected 367 */ 368 public void setProxyPort(int port) throws IllegalStateException { 369 assertNotOpen(); 370 proxyPortNumber = port; 371 } 372 373 /*** 374 * Return <tt>true</tt> if I will (or I am) connected over a 375 * secure (HTTPS/SSL) protocol. 376 * 377 * @return <tt>true</tt> if I will (or I am) connected over a 378 * secure (HTTPS/SSL) protocol. 379 */ 380 public boolean isSecure() { 381 return protocolInUse.isSecure(); 382 } 383 384 /*** 385 * Get the protocol. 386 * @return HTTPS if secure, HTTP otherwise 387 */ 388 public Protocol getProtocol() { 389 return protocolInUse; 390 } 391 392 /*** 393 * Set whether or not I should connect over HTTPS (SSL). 394 * 395 * @param secure whether or not I should connect over HTTPS (SSL). 396 * @throws IllegalStateException if I am already connected 397 * 398 * @deprecated use setProtocol(Protocol) 399 * 400 * @see #setProtocol(Protocol) 401 */ 402 public void setSecure(boolean secure) throws IllegalStateException { 403 assertNotOpen(); 404 protocolInUse = secure 405 ? Protocol.getProtocol("https") 406 : Protocol.getProtocol("http"); 407 } 408 409 /*** 410 * Sets the protocol used by this connection. 411 * 412 * @param protocol The new protocol. 413 */ 414 public void setProtocol(Protocol protocol) { 415 assertNotOpen(); 416 417 if (protocol == null) { 418 throw new IllegalArgumentException("protocol is null"); 419 } 420 421 protocolInUse = protocol; 422 423 } 424 425 /*** 426 * Return the local address used when creating the connection. 427 * If <tt>null</tt>, the default address is used. 428 * 429 * @return InetAddress the local address to be used when creating Sockets 430 */ 431 public InetAddress getLocalAddress() { 432 return this.localAddress; 433 } 434 435 /*** 436 * Set the local address used when creating the connection. 437 * If unset or <tt>null</tt>, the default address is used. 438 * 439 * @param localAddress the local address to use 440 */ 441 public void setLocalAddress(InetAddress localAddress) { 442 assertNotOpen(); 443 this.localAddress = localAddress; 444 } 445 446 /*** 447 * Return <tt>true</tt> if I am connected, 448 * <tt>false</tt> otherwise. 449 * 450 * @return <tt>true</tt> if I am connected 451 */ 452 public boolean isOpen() { 453 if (used && isStaleCheckingEnabled() && isStale()) { 454 LOG.debug("Connection is stale, closing..."); 455 close(); 456 } 457 return isOpen; 458 } 459 460 /*** 461 * Tests if stale checking is enabled. 462 * 463 * @return <code>true</code> if enabled 464 * 465 * @see #isStale() 466 */ 467 public boolean isStaleCheckingEnabled() { 468 return staleCheckingEnabled; 469 } 470 471 /*** 472 * Sets whether or not isStale() will be called when testing if this connection is open. 473 * 474 * @param staleCheckEnabled <code>true</code> to enable isStale() 475 * 476 * @see #isStale() 477 * @see #isOpen() 478 */ 479 public void setStaleCheckingEnabled(boolean staleCheckEnabled) { 480 this.staleCheckingEnabled = staleCheckEnabled; 481 } 482 483 /*** 484 * Determines whether this connection is "stale", which is to say that either 485 * it is no longer open, or an attempt to read the connection would fail. 486 * 487 * <p>Unfortunately, due to the limitations of the JREs prior to 1.4, it is 488 * not possible to test a connection to see if both the read and write channels 489 * are open - except by reading and writing. This leads to a difficulty when 490 * some connections leave the "write" channel open, but close the read channel 491 * and ignore the request. This function attempts to ameliorate that 492 * problem by doing a test read, assuming that the caller will be doing a 493 * write followed by a read, rather than the other way around. 494 * </p> 495 * 496 * <p>To avoid side-effects, the underlying connection is wrapped by a 497 * {@link PushbackInputStream}, so although data might be read, what is visible 498 * to clients of the connection will not change with this call.</p. 499 * 500 * @return <tt>true</tt> if the connection is already closed, or a read would 501 * fail. 502 */ 503 protected boolean isStale() { 504 boolean isStale = true; 505 if (isOpen) { 506 // the connection is open, but now we have to see if we can read it 507 // assume the connection is not stale. 508 isStale = false; 509 try { 510 if (inputStream.available() == 0) { 511 try { 512 socket.setSoTimeout(1); 513 int byteRead = inputStream.read(); 514 if (byteRead == -1) { 515 // again - if the socket is reporting all data read, 516 // probably stale 517 isStale = true; 518 } else { 519 inputStream.unread(byteRead); 520 } 521 } finally { 522 socket.setSoTimeout(soTimeout); 523 } 524 } 525 } catch (InterruptedIOException e) { 526 // aha - the connection is NOT stale - continue on! 527 } catch (IOException e) { 528 // oops - the connection is stale, the read or soTimeout failed. 529 LOG.debug( 530 "An error occurred while reading from the socket, is appears to be stale", 531 e 532 ); 533 isStale = true; 534 } 535 } 536 537 return isStale; 538 } 539 540 /*** 541 * Return <tt>true</tt> if I am (or I will be) 542 * connected via a proxy, <tt>false</tt> otherwise. 543 * 544 * @return <tt>true</tt> if I am (or I will be) 545 * connected via a proxy, <tt>false</tt> otherwise. 546 */ 547 public boolean isProxied() { 548 return (!(null == proxyHostName || 0 >= proxyPortNumber)); 549 } 550 551 /*** 552 * Set the state to keep track of the last response for the last request. 553 * 554 * <p>The connection managers use this to ensure that previous requests are 555 * properly closed before a new request is attempted. That way, a GET 556 * request need not be read in its entirety before a new request is issued. 557 * Instead, this stream can be closed as appropriate.</p> 558 * 559 * @param inStream The stream associated with an HttpMethod. 560 */ 561 public void setLastResponseInputStream(InputStream inStream) { 562 lastResponseInputStream = inStream; 563 } 564 565 /*** 566 * Returns the stream used to read the last response's body. 567 * 568 * <p>Clients will generally not need to call this function unless 569 * using HttpConnection directly, instead of calling {@link HttpClient#executeMethod}. 570 * For those clients, call this function, and if it returns a non-null stream, 571 * close the stream before attempting to execute a method. Note that 572 * calling "close" on the stream returned by this function <i>may</i> close 573 * the connection if the previous response contained a "Connection: close" header. </p> 574 * 575 * @return An {@link InputStream} corresponding to the body of the last 576 * response. 577 */ 578 public InputStream getLastResponseInputStream() { 579 return lastResponseInputStream; 580 } 581 582 // --------------------------------------------------- Other Public Methods 583 584 /*** 585 * Set my {@link Socket}'s timeout, via {@link Socket#setSoTimeout}. If the 586 * connection is already open, the SO_TIMEOUT is changed. If no connection 587 * is open, then subsequent connections will use the timeout value. 588 * <p> 589 * Note: This is not a connection timeout but a timeout on network traffic! 590 * 591 * @param timeout the timeout value 592 * @throws SocketException - if there is an error in the underlying 593 * protocol, such as a TCP error. 594 * @throws IllegalStateException if I am not connected 595 */ 596 public void setSoTimeout(int timeout) 597 throws SocketException, IllegalStateException { 598 LOG.debug("HttpConnection.setSoTimeout(" + timeout + ")"); 599 soTimeout = timeout; 600 if (socket != null) { 601 socket.setSoTimeout(timeout); 602 } 603 } 604 605 /*** 606 * Return my {@link Socket}'s timeout, via {@link Socket#setSoTimeout}, if the 607 * connection is already open. If no connection is open, return the value subsequent 608 * connection will use. 609 * <p> 610 * Note: This is not a connection timeout but a timeout on network traffic! 611 * 612 * @return the timeout value 613 */ 614 public int getSoTimeout() throws SocketException { 615 LOG.debug("HttpConnection.getSoTimeout()"); 616 if (this.socket != null) { 617 return this.socket.getSoTimeout(); 618 } else { 619 return this.soTimeout; 620 } 621 } 622 623 /*** 624 * Sets the connection timeout. This is the maximum time that may be spent 625 * until a connection is established. The connection will fail after this 626 * amount of time. 627 * @param timeout The timeout in milliseconds. 0 means timeout is not used. 628 */ 629 public void setConnectionTimeout(int timeout) { 630 this.connectTimeout = timeout; 631 } 632 633 /*** 634 * Open this connection to the current host and port 635 * (via a proxy if so configured). 636 * The underlying socket is created from the {@link ProtocolSocketFactory}. 637 * 638 * @throws IOException when there are errors opening the connection 639 */ 640 public void open() throws IOException { 641 LOG.trace("enter HttpConnection.open()"); 642 643 assertNotOpen(); // ??? is this worth doing? 644 try { 645 if (null == socket) { 646 647 final String host = (null == proxyHostName) ? hostName : proxyHostName; 648 final int port = (null == proxyHostName) ? portNumber : proxyPortNumber; 649 650 usingSecureSocket = isSecure() && !isProxied(); 651 652 // use the protocol's socket factory unless this is a secure 653 // proxied connection 654 final ProtocolSocketFactory socketFactory = 655 (isSecure() && isProxied() 656 ? new DefaultProtocolSocketFactory() 657 : protocolInUse.getSocketFactory()); 658 659 if (connectTimeout == 0) { 660 if (localAddress != null) { 661 socket = socketFactory.createSocket(host, port, localAddress, 0); 662 } else { 663 socket = socketFactory.createSocket(host, port); 664 } 665 } else { 666 SocketTask task = new SocketTask() { 667 public void doit() throws IOException { 668 if (localAddress != null) { 669 setSocket(socketFactory.createSocket(host, port, localAddress, 0)); 670 } else { 671 setSocket(socketFactory.createSocket(host, port)); 672 } 673 } 674 }; 675 TimeoutController.execute(task, connectTimeout); 676 socket = task.getSocket(); 677 if (task.exception != null) { 678 throw task.exception; 679 } 680 } 681 682 } 683 684 /* 685 "Nagling has been broadly implemented across networks, 686 including the Internet, and is generally performed by default 687 - although it is sometimes considered to be undesirable in 688 highly interactive environments, such as some client/server 689 situations. In such cases, nagling may be turned off through 690 use of the TCP_NODELAY sockets option." */ 691 692 socket.setTcpNoDelay(soNodelay); 693 socket.setSoTimeout(soTimeout); 694 if (sendBufferSize != -1) { 695 socket.setSendBufferSize(sendBufferSize); 696 } 697 inputStream = new PushbackInputStream(socket.getInputStream()); 698 outputStream = new BufferedOutputStream( 699 new WrappedOutputStream(socket.getOutputStream()), 700 socket.getSendBufferSize() 701 ); 702 isOpen = true; 703 used = false; 704 } catch (IOException e) { 705 // Connection wasn't opened properly 706 // so close everything out 707 closeSocketAndStreams(); 708 throw e; 709 } catch (TimeoutController.TimeoutException e) { 710 if (LOG.isWarnEnabled()) { 711 LOG.warn("The host " + hostName + ":" + portNumber 712 + " (or proxy " + proxyHostName + ":" + proxyPortNumber 713 + ") did not accept the connection within timeout of " 714 + connectTimeout + " milliseconds"); 715 } 716 throw new ConnectionTimeoutException(); 717 } 718 } 719 720 /*** 721 * Calling this method indicates that the proxy has successfully created the 722 * tunnel to the host. The socket will be switched to the secure socket. 723 * Subsequent communication is done via the secure socket. The method can 724 * only be called once on a proxied secure connection. 725 * 726 * @throws IllegalStateException if connection is not secure and proxied or 727 * if the socket is already secure. 728 * @throws IOException if an error occured creating the secure socket 729 */ 730 public void tunnelCreated() throws IllegalStateException, IOException { 731 LOG.trace("enter HttpConnection.tunnelCreated()"); 732 733 if (!isSecure() || !isProxied()) { 734 throw new IllegalStateException( 735 "Connection must be secure " 736 + "and proxied to use this feature"); 737 } 738 739 if (usingSecureSocket) { 740 throw new IllegalStateException("Already using a secure socket"); 741 } 742 743 SecureProtocolSocketFactory socketFactory = 744 (SecureProtocolSocketFactory) protocolInUse.getSocketFactory(); 745 746 socket = socketFactory.createSocket(socket, hostName, portNumber, true); 747 if (sendBufferSize != -1) { 748 socket.setSendBufferSize(sendBufferSize); 749 } 750 inputStream = new PushbackInputStream(socket.getInputStream()); 751 outputStream = new BufferedOutputStream( 752 new WrappedOutputStream(socket.getOutputStream()), 753 socket.getSendBufferSize() 754 ); 755 usingSecureSocket = true; 756 tunnelEstablished = true; 757 LOG.debug("Secure tunnel created"); 758 } 759 760 /*** 761 * Indicates if the connection is completely transparent from end to end. 762 * 763 * @return true if conncetion is not proxied or tunneled through a transparent 764 * proxy; false otherwise. 765 */ 766 public boolean isTransparent() { 767 return !isProxied() || tunnelEstablished; 768 } 769 770 /*** 771 * Flushes the output request stream. This method should be called to 772 * ensure that data written to the request OutputStream is sent to the server. 773 * 774 * @throws IOException if an I/O problem occurs 775 */ 776 public void flushRequestOutputStream() throws IOException { 777 LOG.trace("enter HttpConnection.flushRequestOutputStream()"); 778 assertOpen(); 779 outputStream.flush(); 780 } 781 782 /*** 783 * Return a {@link OutputStream} suitable for writing (possibly 784 * chunked) bytes to my {@link OutputStream}. 785 * 786 * @throws IllegalStateException if I am not connected 787 * @throws IOException if an I/O problem occurs 788 * @return a stream to write the request to 789 */ 790 public OutputStream getRequestOutputStream() 791 throws IOException, IllegalStateException { 792 LOG.trace("enter HttpConnection.getRequestOutputStream()"); 793 assertOpen(); 794 OutputStream out = this.outputStream; 795 if (Wire.enabled()) { 796 out = new WireLogOutputStream(out); 797 } 798 return out; 799 } 800 801 /*** 802 * Return a {@link OutputStream} suitable for writing (possibly 803 * chunked) bytes to my {@link OutputStream}. 804 * 805 * @param useChunking when <tt>true</tt> the chunked transfer-encoding will 806 * be used 807 * @throws IllegalStateException if I am not connected 808 * @throws IOException if an I/O problem occurs 809 * @return a stream to write the request to 810 * @deprecated Use new ChunkedOutputStream(httpConnecion.getRequestOutputStream()); 811 */ 812 public OutputStream getRequestOutputStream(boolean useChunking) 813 throws IOException, IllegalStateException { 814 LOG.trace("enter HttpConnection.getRequestOutputStream(boolean)"); 815 816 OutputStream out = getRequestOutputStream(); 817 if (useChunking) { 818 out = new ChunkedOutputStream(out); 819 } 820 return out; 821 } 822 823 /*** 824 * Return a {@link InputStream} suitable for reading (possibly 825 * chunked) bytes from my {@link InputStream}. 826 * <p> 827 * If the given {@link HttpMethod} contains 828 * a <tt>Transfer-Encoding: chunked</tt> header, 829 * the returned stream will be configured 830 * to read chunked bytes. 831 * 832 * @param method This argument is ignored. 833 * @throws IllegalStateException if I am not connected 834 * @throws IOException if an I/O problem occurs 835 * @return a stream to read the response from 836 * @deprecated Use getResponseInputStream() instead. 837 */ 838 public InputStream getResponseInputStream(HttpMethod method) 839 throws IOException, IllegalStateException { 840 LOG.trace("enter HttpConnection.getResponseInputStream(HttpMethod)"); 841 return getResponseInputStream(); 842 } 843 844 /*** 845 * Return the response input stream 846 * @return InputStream The response input stream. 847 * @throws IOException If an IO problem occurs 848 * @throws IllegalStateException If the connection isn't open. 849 */ 850 public InputStream getResponseInputStream() 851 throws IOException, IllegalStateException { 852 LOG.trace("enter HttpConnection.getResponseInputStream()"); 853 assertOpen(); 854 return inputStream; 855 } 856 857 /*** 858 * Tests if input data avaialble. This method returns immediately 859 * and does not perform any read operations on the input socket 860 * 861 * @return boolean <tt>true</tt> if input data is availble, 862 * <tt>false</tt> otherwise. 863 * 864 * @throws IOException If an IO problem occurs 865 * @throws IllegalStateException If the connection isn't open. 866 */ 867 public boolean isResponseAvailable() 868 throws IOException { 869 LOG.trace("enter HttpConnection.isResponseAvailable()"); 870 assertOpen(); 871 return this.inputStream.available() > 0; 872 } 873 874 /*** 875 * Tests if input data becomes available within the given period time in milliseconds. 876 * 877 * @param timeout The number milliseconds to wait for input data to become available 878 * @return boolean <tt>true</tt> if input data is availble, 879 * <tt>false</tt> otherwise. 880 * 881 * @throws IOException If an IO problem occurs 882 * @throws IllegalStateException If the connection isn't open. 883 */ 884 public boolean isResponseAvailable(int timeout) 885 throws IOException { 886 LOG.trace("enter HttpConnection.isResponseAvailable(int)"); 887 assertOpen(); 888 boolean result = false; 889 if (this.inputStream.available() > 0) { 890 result = true; 891 } else { 892 try { 893 this.socket.setSoTimeout(timeout); 894 int byteRead = inputStream.read(); 895 if (byteRead != -1) { 896 inputStream.unread(byteRead); 897 LOG.debug("Input data available"); 898 result = true; 899 } else { 900 LOG.debug("Input data not available"); 901 } 902 } catch (InterruptedIOException e) { 903 if (LOG.isDebugEnabled()) { 904 LOG.debug("Input data not available after " + timeout + " ms"); 905 } 906 } finally { 907 try { 908 socket.setSoTimeout(soTimeout); 909 } catch (IOException ioe) { 910 LOG.debug("An error ocurred while resetting soTimeout, we will assume that" 911 + " no response is available.", 912 ioe); 913 result = false; 914 } 915 } 916 } 917 return result; 918 } 919 920 /*** 921 * Write the specified bytes to my output stream. 922 * 923 * @param data the data to be written 924 * @throws HttpRecoverableException if a SocketException occurs 925 * @throws IllegalStateException if not connected 926 * @throws IOException if an I/O problem occurs 927 * @see #write(byte[],int,int) 928 */ 929 public void write(byte[] data) 930 throws IOException, IllegalStateException, HttpRecoverableException { 931 LOG.trace("enter HttpConnection.write(byte[])"); 932 this.write(data, 0, data.length); 933 } 934 935 /*** 936 * Write <i>length</i> bytes in <i>data</i> starting at 937 * <i>offset</i> to my output stream. 938 * 939 * The general contract for 940 * write(b, off, len) is that some of the bytes in the array b are written 941 * to the output stream in order; element b[off] is the first byte written 942 * and b[off+len-1] is the last byte written by this operation. 943 * 944 * @param data array containing the data to be written. 945 * @param offset the start offset in the data. 946 * @param length the number of bytes to write. 947 * @throws HttpRecoverableException if a SocketException occurs 948 * @throws IllegalStateException if not connected 949 * @throws IOException if an I/O problem occurs 950 */ 951 public void write(byte[] data, int offset, int length) 952 throws IOException, IllegalStateException, HttpRecoverableException { 953 LOG.trace("enter HttpConnection.write(byte[], int, int)"); 954 955 if (offset + length > data.length) { 956 throw new HttpRecoverableException("Unable to write:" 957 + " offset=" + offset + " length=" + length 958 + " data.length=" + data.length); 959 } else if (data.length <= 0) { 960 throw new HttpRecoverableException( 961 "Unable to write:" + " data.length=" + data.length); 962 } 963 964 assertOpen(); 965 966 try { 967 outputStream.write(data, offset, length); 968 } catch (HttpRecoverableException hre) { 969 throw hre; 970 } catch (SocketException se) { 971 LOG.debug( 972 "HttpConnection: Socket exception while writing data", 973 se); 974 throw new HttpRecoverableException(se.toString()); 975 } catch (IOException ioe) { 976 LOG.debug("HttpConnection: Exception while writing data", ioe); 977 throw ioe; 978 } 979 } 980 981 /*** 982 * Write the specified bytes, followed by <tt>"\r\n".getBytes()</tt> to my 983 * output stream. 984 * 985 * @param data the bytes to be written 986 * @throws HttpRecoverableException when socket exceptions occur writing data 987 * @throws IllegalStateException if I am not connected 988 * @throws IOException if an I/O problem occurs 989 */ 990 public void writeLine(byte[] data) 991 throws IOException, IllegalStateException, HttpRecoverableException { 992 LOG.trace("enter HttpConnection.writeLine(byte[])"); 993 write(data); 994 writeLine(); 995 } 996 997 /*** 998 * Write <tt>"\r\n".getBytes()</tt> to my output stream. 999 * 1000 * @throws HttpRecoverableException when socket exceptions occur writing 1001 * data 1002 * @throws IllegalStateException if I am not connected 1003 * @throws IOException if an I/O problem occurs 1004 */ 1005 public void writeLine() 1006 throws IOException, IllegalStateException, HttpRecoverableException { 1007 LOG.trace("enter HttpConnection.writeLine()"); 1008 write(CRLF); 1009 } 1010 1011 /*** 1012 * Write the specified String (as bytes) to my output stream. 1013 * 1014 * @param data the string to be written 1015 * @throws HttpRecoverableException when socket exceptions occur writing 1016 * data 1017 * @throws IllegalStateException if I am not connected 1018 * @throws IOException if an I/O problem occurs 1019 */ 1020 public void print(String data) 1021 throws IOException, IllegalStateException, HttpRecoverableException { 1022 LOG.trace("enter HttpConnection.print(String)"); 1023 write(HttpConstants.getBytes(data)); 1024 } 1025 1026 /*** 1027 * Write the specified String (as bytes), followed by 1028 * <tt>"\r\n".getBytes()</tt> to my output stream. 1029 * 1030 * @param data the data to be written 1031 * @throws HttpRecoverableException when socket exceptions occur writing 1032 * data 1033 * @throws IllegalStateException if I am not connected 1034 * @throws IOException if an I/O problem occurs 1035 */ 1036 public void printLine(String data) 1037 throws IOException, IllegalStateException, HttpRecoverableException { 1038 LOG.trace("enter HttpConnection.printLine(String)"); 1039 writeLine(HttpConstants.getBytes(data)); 1040 } 1041 1042 /*** 1043 * Write <tt>"\r\n".getBytes()</tt> to my output stream. 1044 * 1045 * @throws HttpRecoverableException when socket exceptions occur writing 1046 * data 1047 * @throws IllegalStateException if I am not connected 1048 * @throws IOException if an I/O problem occurs 1049 */ 1050 public void printLine() 1051 throws IOException, IllegalStateException, HttpRecoverableException { 1052 LOG.trace("enter HttpConnection.printLine()"); 1053 writeLine(); 1054 } 1055 1056 /*** 1057 * Read up to <tt>"\n"</tt> from my (unchunked) input stream. 1058 * If the stream ends before the line terminator is found, 1059 * the last part of the string will still be returned. 1060 * 1061 * @throws IllegalStateException if I am not connected 1062 * @throws IOException if an I/O problem occurs 1063 * @return a line from the response 1064 */ 1065 public String readLine() throws IOException, IllegalStateException { 1066 LOG.trace("enter HttpConnection.readLine()"); 1067 1068 assertOpen(); 1069 return HttpParser.readLine(inputStream); 1070 } 1071 1072 /*** 1073 * Shutdown my {@link Socket}'s output, via Socket.shutdownOutput() 1074 * when running on JVM 1.3 or higher. 1075 */ 1076 public void shutdownOutput() { 1077 LOG.trace("enter HttpConnection.shutdownOutput()"); 1078 1079 try { 1080 // Socket.shutdownOutput is a JDK 1.3 1081 // method. We'll use reflection in case 1082 // we're running in an older VM 1083 Class[] paramsClasses = new Class[0]; 1084 Method shutdownOutput = 1085 socket.getClass().getMethod("shutdownOutput", paramsClasses); 1086 Object[] params = new Object[0]; 1087 shutdownOutput.invoke(socket, params); 1088 } catch (Exception ex) { 1089 LOG.debug("Unexpected Exception caught", ex); 1090 // Ignore, and hope everything goes right 1091 } 1092 // close output stream? 1093 } 1094 1095 /*** 1096 * Close my socket and streams. 1097 */ 1098 public void close() { 1099 LOG.trace("enter HttpConnection.close()"); 1100 closeSocketAndStreams(); 1101 } 1102 1103 /*** 1104 * Returns the httpConnectionManager. 1105 * @return HttpConnectionManager 1106 */ 1107 public HttpConnectionManager getHttpConnectionManager() { 1108 return httpConnectionManager; 1109 } 1110 1111 /*** 1112 * Sets the httpConnectionManager. 1113 * @param httpConnectionManager The httpConnectionManager to set 1114 */ 1115 public void setHttpConnectionManager(HttpConnectionManager httpConnectionManager) { 1116 this.httpConnectionManager = httpConnectionManager; 1117 } 1118 1119 /*** 1120 * Release the connection. 1121 */ 1122 public void releaseConnection() { 1123 LOG.trace("enter HttpConnection.releaseConnection()"); 1124 1125 // we are assuming that the connection will only be released once used 1126 used = true; 1127 if (httpConnectionManager != null) { 1128 httpConnectionManager.releaseConnection(this); 1129 } 1130 } 1131 1132 // ------------------------------------------------------ Protected Methods 1133 1134 /*** 1135 * Close everything out. 1136 */ 1137 protected void closeSocketAndStreams() { 1138 LOG.trace("enter HttpConnection.closeSockedAndStreams()"); 1139 1140 // no longer care about previous responses... 1141 lastResponseInputStream = null; 1142 1143 if (null != outputStream) { 1144 OutputStream temp = outputStream; 1145 outputStream = null; 1146 try { 1147 temp.close(); 1148 } catch (Exception ex) { 1149 LOG.debug("Exception caught when closing output", ex); 1150 // ignored 1151 } 1152 } 1153 1154 if (null != inputStream) { 1155 InputStream temp = inputStream; 1156 inputStream = null; 1157 try { 1158 temp.close(); 1159 } catch (Exception ex) { 1160 LOG.debug("Exception caught when closing input", ex); 1161 // ignored 1162 } 1163 } 1164 1165 if (null != socket) { 1166 Socket temp = socket; 1167 socket = null; 1168 try { 1169 temp.close(); 1170 } catch (Exception ex) { 1171 LOG.debug("Exception caught when closing socket", ex); 1172 // ignored 1173 } 1174 } 1175 isOpen = false; 1176 used = false; 1177 tunnelEstablished = false; 1178 usingSecureSocket = false; 1179 } 1180 1181 /*** 1182 * Throw an {@link IllegalStateException} if I am connected. 1183 * 1184 * @throws IllegalStateException if connected 1185 */ 1186 protected void assertNotOpen() throws IllegalStateException { 1187 if (isOpen) { 1188 throw new IllegalStateException("Connection is open"); 1189 } 1190 } 1191 1192 /*** 1193 * Throw an {@link IllegalStateException} if I am not connected. 1194 * 1195 * @throws IllegalStateException if not connected 1196 */ 1197 protected void assertOpen() throws IllegalStateException { 1198 if (!isOpen) { 1199 throw new IllegalStateException("Connection is not open"); 1200 } 1201 } 1202 1203 /*** 1204 * Gets the socket's sendBufferSize. 1205 * 1206 * @return the size of the buffer for the socket OutputStream, -1 if the value 1207 * has not been set and the socket has not been opened 1208 * 1209 * @throws SocketException if an error occurs while getting the socket value 1210 * 1211 * @see Socket#getSendBufferSize() 1212 */ 1213 public int getSendBufferSize() throws SocketException { 1214 if (socket == null) { 1215 return -1; 1216 } else { 1217 return socket.getSendBufferSize(); 1218 } 1219 } 1220 1221 /*** 1222 * Sets the socket's sendBufferSize. 1223 * 1224 * @param sendBufferSize the size to set for the socket OutputStream 1225 * 1226 * @throws SocketException if an error occurs while setting the socket value 1227 * 1228 * @see Socket#setSendBufferSize(int) 1229 */ 1230 public void setSendBufferSize(int sendBufferSize) throws SocketException { 1231 this.sendBufferSize = sendBufferSize; 1232 if (socket != null) { 1233 socket.setSendBufferSize(sendBufferSize); 1234 } 1235 } 1236 1237 // -- Timeout Exception 1238 /*** 1239 * Signals that a timeout occured while opening the socket. 1240 */ 1241 public class ConnectionTimeoutException extends IOException { 1242 /*** Create an instance */ 1243 public ConnectionTimeoutException() { 1244 } 1245 } 1246 1247 1248 /*** 1249 * Helper class for wrapping socket based tasks. 1250 */ 1251 private abstract class SocketTask implements Runnable { 1252 1253 /*** The socket */ 1254 private Socket socket; 1255 /*** The exception */ 1256 private IOException exception; 1257 1258 /*** 1259 * Set the socket. 1260 * @param newSocket The new socket. 1261 */ 1262 protected void setSocket(final Socket newSocket) { 1263 socket = newSocket; 1264 } 1265 1266 /*** 1267 * Return the socket. 1268 * @return Socket The socket. 1269 */ 1270 protected Socket getSocket() { 1271 return socket; 1272 } 1273 /*** 1274 * Perform the logic. 1275 * @throws IOException If an IO problem occurs 1276 */ 1277 public abstract void doit() throws IOException; 1278 1279 /*** Execute the logic in this object and keep track of any exceptions. */ 1280 public void run() { 1281 try { 1282 doit(); 1283 } catch (IOException e) { 1284 exception = e; 1285 } 1286 } 1287 } 1288 1289 /*** 1290 * A wrapper for output streams that closes the connection and converts to recoverable 1291 * exceptions as appropriable when an IOException occurs. 1292 */ 1293 private class WrappedOutputStream extends OutputStream { 1294 1295 /*** the actual output stream */ 1296 private OutputStream out; 1297 1298 /*** 1299 * @param out the output stream to wrap 1300 */ 1301 public WrappedOutputStream(OutputStream out) { 1302 this.out = out; 1303 } 1304 1305 /*** 1306 * Closes the connection and conditionally converts exception to recoverable. 1307 * @param ioe the exception that occurred 1308 * @return the exception to be thrown 1309 */ 1310 private IOException handleException(IOException ioe) { 1311 // keep the original value of used, as it will be set to false by close(). 1312 boolean tempUsed = used; 1313 HttpConnection.this.close(); 1314 if (tempUsed) { 1315 LOG.debug( 1316 "Output exception occurred on a used connection. Will treat as recoverable.", 1317 ioe 1318 ); 1319 return new HttpRecoverableException(ioe.toString()); 1320 } else { 1321 return ioe; 1322 } 1323 } 1324 1325 public void write(int b) throws IOException { 1326 try { 1327 out.write(b); 1328 } catch (IOException ioe) { 1329 throw handleException(ioe); 1330 } 1331 } 1332 1333 public void flush() throws IOException { 1334 try { 1335 out.flush(); 1336 } catch (IOException ioe) { 1337 throw handleException(ioe); 1338 } 1339 } 1340 1341 public void close() throws IOException { 1342 try { 1343 out.close(); 1344 } catch (IOException ioe) { 1345 throw handleException(ioe); 1346 } 1347 } 1348 1349 public void write(byte[] b, int off, int len) throws IOException { 1350 try { 1351 out.write(b, off, len); 1352 } catch (IOException ioe) { 1353 throw handleException(ioe); 1354 } 1355 } 1356 1357 public void write(byte[] b) throws IOException { 1358 try { 1359 out.write(b); 1360 } catch (IOException ioe) { 1361 throw handleException(ioe); 1362 } 1363 } 1364 1365 } 1366 1367 // ------------------------------------------------------- Static Variable 1368 1369 /*** <tt>"\r\n"</tt>, as bytes. */ 1370 private static final byte[] CRLF = new byte[] {(byte) 13, (byte) 10}; 1371 1372 /*** Log object for this class. */ 1373 private static final Log LOG = LogFactory.getLog(HttpConnection.class); 1374 1375 // ----------------------------------------------------- Instance Variables 1376 1377 /*** A flag indicating if this connection has been used since being opened */ 1378 private boolean used = false; 1379 1380 /*** My host. */ 1381 private String hostName = null; 1382 1383 /*** My virtual host. */ 1384 private String virtualName = null; 1385 1386 /*** My port. */ 1387 private int portNumber = -1; 1388 1389 /*** My proxy host. */ 1390 private String proxyHostName = null; 1391 1392 /*** My proxy port. */ 1393 private int proxyPortNumber = -1; 1394 1395 /*** My client Socket. */ 1396 private Socket socket = null; 1397 1398 /*** My InputStream. */ 1399 private PushbackInputStream inputStream = null; 1400 1401 /*** My OutputStream. */ 1402 private OutputStream outputStream = null; 1403 1404 /*** the size of the buffer to use for the socket OutputStream */ 1405 private int sendBufferSize = -1; 1406 1407 /*** An {@link InputStream} for the response to an individual request. */ 1408 private InputStream lastResponseInputStream = null; 1409 1410 /*** Whether or not I am connected. */ 1411 protected boolean isOpen = false; 1412 1413 /*** the protocol being used */ 1414 private Protocol protocolInUse; 1415 1416 /*** SO_TIMEOUT socket value */ 1417 private int soTimeout = 0; 1418 1419 /*** TCP_NODELAY socket value */ 1420 private boolean soNodelay = true; 1421 1422 /*** Whether or not the socket is a secure one. */ 1423 private boolean usingSecureSocket = false; 1424 1425 /*** Whether I am tunneling a proxy or not */ 1426 private boolean tunnelEstablished = false; 1427 1428 /*** Whether or not isStale() is used by isOpen() */ 1429 private boolean staleCheckingEnabled = true; 1430 1431 /*** Timeout until connection established (Socket created). 0 means no timeout. */ 1432 private int connectTimeout = 0; 1433 1434 /*** the connection manager that created this connection or null */ 1435 private HttpConnectionManager httpConnectionManager; 1436 1437 /*** The local interface on which the connection is created, or null for the default */ 1438 private InetAddress localAddress; 1439 }

This page was automatically generated by Maven