View Javadoc
1 /* 2 * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpConnection.java,v 1.67.2.2 2003/07/31 17:59:17 olegk Exp $ 3 * $Revision: 1.67.2.2 $ 4 * $Date: 2003/07/31 17:59:17 $ 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.2 $ $Date: 2003/07/31 17:59:17 $ 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 socket.setSoTimeout(soTimeout); 908 } 909 } 910 return result; 911 } 912 913 /*** 914 * Write the specified bytes to my output stream. 915 * 916 * @param data the data to be written 917 * @throws HttpRecoverableException if a SocketException occurs 918 * @throws IllegalStateException if not connected 919 * @throws IOException if an I/O problem occurs 920 * @see #write(byte[],int,int) 921 */ 922 public void write(byte[] data) 923 throws IOException, IllegalStateException, HttpRecoverableException { 924 LOG.trace("enter HttpConnection.write(byte[])"); 925 this.write(data, 0, data.length); 926 } 927 928 /*** 929 * Write <i>length</i> bytes in <i>data</i> starting at 930 * <i>offset</i> to my output stream. 931 * 932 * The general contract for 933 * write(b, off, len) is that some of the bytes in the array b are written 934 * to the output stream in order; element b[off] is the first byte written 935 * and b[off+len-1] is the last byte written by this operation. 936 * 937 * @param data array containing the data to be written. 938 * @param offset the start offset in the data. 939 * @param length the number of bytes to write. 940 * @throws HttpRecoverableException if a SocketException occurs 941 * @throws IllegalStateException if not connected 942 * @throws IOException if an I/O problem occurs 943 */ 944 public void write(byte[] data, int offset, int length) 945 throws IOException, IllegalStateException, HttpRecoverableException { 946 LOG.trace("enter HttpConnection.write(byte[], int, int)"); 947 948 if (offset + length > data.length) { 949 throw new HttpRecoverableException("Unable to write:" 950 + " offset=" + offset + " length=" + length 951 + " data.length=" + data.length); 952 } else if (data.length <= 0) { 953 throw new HttpRecoverableException( 954 "Unable to write:" + " data.length=" + data.length); 955 } 956 957 assertOpen(); 958 959 try { 960 outputStream.write(data, offset, length); 961 } catch (HttpRecoverableException hre) { 962 throw hre; 963 } catch (SocketException se) { 964 LOG.debug( 965 "HttpConnection: Socket exception while writing data", 966 se); 967 throw new HttpRecoverableException(se.toString()); 968 } catch (IOException ioe) { 969 LOG.debug("HttpConnection: Exception while writing data", ioe); 970 throw ioe; 971 } 972 } 973 974 /*** 975 * Write the specified bytes, followed by <tt>"\r\n".getBytes()</tt> to my 976 * output stream. 977 * 978 * @param data the bytes to be written 979 * @throws HttpRecoverableException when socket exceptions occur writing data 980 * @throws IllegalStateException if I am not connected 981 * @throws IOException if an I/O problem occurs 982 */ 983 public void writeLine(byte[] data) 984 throws IOException, IllegalStateException, HttpRecoverableException { 985 LOG.trace("enter HttpConnection.writeLine(byte[])"); 986 write(data); 987 writeLine(); 988 } 989 990 /*** 991 * Write <tt>"\r\n".getBytes()</tt> to my output stream. 992 * 993 * @throws HttpRecoverableException when socket exceptions occur writing 994 * data 995 * @throws IllegalStateException if I am not connected 996 * @throws IOException if an I/O problem occurs 997 */ 998 public void writeLine() 999 throws IOException, IllegalStateException, HttpRecoverableException { 1000 LOG.trace("enter HttpConnection.writeLine()"); 1001 write(CRLF); 1002 } 1003 1004 /*** 1005 * Write the specified String (as bytes) to my output stream. 1006 * 1007 * @param data the string to be written 1008 * @throws HttpRecoverableException when socket exceptions occur writing 1009 * data 1010 * @throws IllegalStateException if I am not connected 1011 * @throws IOException if an I/O problem occurs 1012 */ 1013 public void print(String data) 1014 throws IOException, IllegalStateException, HttpRecoverableException { 1015 LOG.trace("enter HttpConnection.print(String)"); 1016 write(HttpConstants.getBytes(data)); 1017 } 1018 1019 /*** 1020 * Write the specified String (as bytes), followed by 1021 * <tt>"\r\n".getBytes()</tt> to my output stream. 1022 * 1023 * @param data the data to be written 1024 * @throws HttpRecoverableException when socket exceptions occur writing 1025 * data 1026 * @throws IllegalStateException if I am not connected 1027 * @throws IOException if an I/O problem occurs 1028 */ 1029 public void printLine(String data) 1030 throws IOException, IllegalStateException, HttpRecoverableException { 1031 LOG.trace("enter HttpConnection.printLine(String)"); 1032 writeLine(HttpConstants.getBytes(data)); 1033 } 1034 1035 /*** 1036 * Write <tt>"\r\n".getBytes()</tt> to my output stream. 1037 * 1038 * @throws HttpRecoverableException when socket exceptions occur writing 1039 * data 1040 * @throws IllegalStateException if I am not connected 1041 * @throws IOException if an I/O problem occurs 1042 */ 1043 public void printLine() 1044 throws IOException, IllegalStateException, HttpRecoverableException { 1045 LOG.trace("enter HttpConnection.printLine()"); 1046 writeLine(); 1047 } 1048 1049 /*** 1050 * Read up to <tt>"\n"</tt> from my (unchunked) input stream. 1051 * If the stream ends before the line terminator is found, 1052 * the last part of the string will still be returned. 1053 * 1054 * @throws IllegalStateException if I am not connected 1055 * @throws IOException if an I/O problem occurs 1056 * @return a line from the response 1057 */ 1058 public String readLine() throws IOException, IllegalStateException { 1059 LOG.trace("enter HttpConnection.readLine()"); 1060 1061 assertOpen(); 1062 return HttpParser.readLine(inputStream); 1063 } 1064 1065 /*** 1066 * Shutdown my {@link Socket}'s output, via Socket.shutdownOutput() 1067 * when running on JVM 1.3 or higher. 1068 */ 1069 public void shutdownOutput() { 1070 LOG.trace("enter HttpConnection.shutdownOutput()"); 1071 1072 try { 1073 // Socket.shutdownOutput is a JDK 1.3 1074 // method. We'll use reflection in case 1075 // we're running in an older VM 1076 Class[] paramsClasses = new Class[0]; 1077 Method shutdownOutput = 1078 socket.getClass().getMethod("shutdownOutput", paramsClasses); 1079 Object[] params = new Object[0]; 1080 shutdownOutput.invoke(socket, params); 1081 } catch (Exception ex) { 1082 LOG.debug("Unexpected Exception caught", ex); 1083 // Ignore, and hope everything goes right 1084 } 1085 // close output stream? 1086 } 1087 1088 /*** 1089 * Close my socket and streams. 1090 */ 1091 public void close() { 1092 LOG.trace("enter HttpConnection.close()"); 1093 closeSocketAndStreams(); 1094 } 1095 1096 /*** 1097 * Returns the httpConnectionManager. 1098 * @return HttpConnectionManager 1099 */ 1100 public HttpConnectionManager getHttpConnectionManager() { 1101 return httpConnectionManager; 1102 } 1103 1104 /*** 1105 * Sets the httpConnectionManager. 1106 * @param httpConnectionManager The httpConnectionManager to set 1107 */ 1108 public void setHttpConnectionManager(HttpConnectionManager httpConnectionManager) { 1109 this.httpConnectionManager = httpConnectionManager; 1110 } 1111 1112 /*** 1113 * Release the connection. 1114 */ 1115 public void releaseConnection() { 1116 LOG.trace("enter HttpConnection.releaseConnection()"); 1117 1118 // we are assuming that the connection will only be released once used 1119 used = true; 1120 if (httpConnectionManager != null) { 1121 httpConnectionManager.releaseConnection(this); 1122 } 1123 } 1124 1125 // ------------------------------------------------------ Protected Methods 1126 1127 /*** 1128 * Close everything out. 1129 */ 1130 protected void closeSocketAndStreams() { 1131 LOG.trace("enter HttpConnection.closeSockedAndStreams()"); 1132 1133 // no longer care about previous responses... 1134 lastResponseInputStream = null; 1135 1136 if (null != inputStream) { 1137 InputStream temp = inputStream; 1138 inputStream = null; 1139 try { 1140 temp.close(); 1141 } catch (Exception ex) { 1142 LOG.debug("Exception caught when closing input", ex); 1143 // ignored 1144 } 1145 } 1146 1147 if (null != outputStream) { 1148 OutputStream temp = outputStream; 1149 outputStream = null; 1150 try { 1151 temp.close(); 1152 } catch (Exception ex) { 1153 LOG.debug("Exception caught when closing output", ex); 1154 // ignored 1155 } 1156 } 1157 1158 if (null != socket) { 1159 Socket temp = socket; 1160 socket = null; 1161 try { 1162 temp.close(); 1163 } catch (Exception ex) { 1164 LOG.debug("Exception caught when closing socket", ex); 1165 // ignored 1166 } 1167 } 1168 isOpen = false; 1169 used = false; 1170 tunnelEstablished = false; 1171 usingSecureSocket = false; 1172 } 1173 1174 /*** 1175 * Throw an {@link IllegalStateException} if I am connected. 1176 * 1177 * @throws IllegalStateException if connected 1178 */ 1179 protected void assertNotOpen() throws IllegalStateException { 1180 if (isOpen) { 1181 throw new IllegalStateException("Connection is open"); 1182 } 1183 } 1184 1185 /*** 1186 * Throw an {@link IllegalStateException} if I am not connected. 1187 * 1188 * @throws IllegalStateException if not connected 1189 */ 1190 protected void assertOpen() throws IllegalStateException { 1191 if (!isOpen) { 1192 throw new IllegalStateException("Connection is not open"); 1193 } 1194 } 1195 1196 /*** 1197 * Gets the socket's sendBufferSize. 1198 * 1199 * @return the size of the buffer for the socket OutputStream, -1 if the value 1200 * has not been set and the socket has not been opened 1201 * 1202 * @throws SocketException if an error occurs while getting the socket value 1203 * 1204 * @see Socket#getSendBufferSize() 1205 */ 1206 public int getSendBufferSize() throws SocketException { 1207 if (socket == null) { 1208 return -1; 1209 } else { 1210 return socket.getSendBufferSize(); 1211 } 1212 } 1213 1214 /*** 1215 * Sets the socket's sendBufferSize. 1216 * 1217 * @param sendBufferSize the size to set for the socket OutputStream 1218 * 1219 * @throws SocketException if an error occurs while setting the socket value 1220 * 1221 * @see Socket#setSendBufferSize(int) 1222 */ 1223 public void setSendBufferSize(int sendBufferSize) throws SocketException { 1224 this.sendBufferSize = sendBufferSize; 1225 if (socket != null) { 1226 socket.setSendBufferSize(sendBufferSize); 1227 } 1228 } 1229 1230 // -- Timeout Exception 1231 /*** 1232 * Signals that a timeout occured while opening the socket. 1233 */ 1234 public class ConnectionTimeoutException extends IOException { 1235 /*** Create an instance */ 1236 public ConnectionTimeoutException() { 1237 } 1238 } 1239 1240 1241 /*** 1242 * Helper class for wrapping socket based tasks. 1243 */ 1244 private abstract class SocketTask implements Runnable { 1245 1246 /*** The socket */ 1247 private Socket socket; 1248 /*** The exception */ 1249 private IOException exception; 1250 1251 /*** 1252 * Set the socket. 1253 * @param newSocket The new socket. 1254 */ 1255 protected void setSocket(final Socket newSocket) { 1256 socket = newSocket; 1257 } 1258 1259 /*** 1260 * Return the socket. 1261 * @return Socket The socket. 1262 */ 1263 protected Socket getSocket() { 1264 return socket; 1265 } 1266 /*** 1267 * Perform the logic. 1268 * @throws IOException If an IO problem occurs 1269 */ 1270 public abstract void doit() throws IOException; 1271 1272 /*** Execute the logic in this object and keep track of any exceptions. */ 1273 public void run() { 1274 try { 1275 doit(); 1276 } catch (IOException e) { 1277 exception = e; 1278 } 1279 } 1280 } 1281 1282 /*** 1283 * A wrapper for output streams that closes the connection and converts to recoverable 1284 * exceptions as appropriable when an IOException occurs. 1285 */ 1286 private class WrappedOutputStream extends OutputStream { 1287 1288 /*** the actual output stream */ 1289 private OutputStream out; 1290 1291 /*** 1292 * @param out the output stream to wrap 1293 */ 1294 public WrappedOutputStream(OutputStream out) { 1295 this.out = out; 1296 } 1297 1298 /*** 1299 * Closes the connection and conditionally converts exception to recoverable. 1300 * @param ioe the exception that occurred 1301 * @return the exception to be thrown 1302 */ 1303 private IOException handleException(IOException ioe) { 1304 // keep the original value of used, as it will be set to false by close(). 1305 boolean tempUsed = used; 1306 HttpConnection.this.close(); 1307 if (tempUsed) { 1308 LOG.debug( 1309 "Output exception occurred on a used connection. Will treat as recoverable.", 1310 ioe 1311 ); 1312 return new HttpRecoverableException(ioe.toString()); 1313 } else { 1314 return ioe; 1315 } 1316 } 1317 1318 public void write(int b) throws IOException { 1319 try { 1320 out.write(b); 1321 } catch (IOException ioe) { 1322 throw handleException(ioe); 1323 } 1324 } 1325 1326 public void flush() throws IOException { 1327 try { 1328 out.flush(); 1329 } catch (IOException ioe) { 1330 throw handleException(ioe); 1331 } 1332 } 1333 1334 public void close() throws IOException { 1335 try { 1336 out.close(); 1337 } catch (IOException ioe) { 1338 throw handleException(ioe); 1339 } 1340 } 1341 1342 public void write(byte[] b, int off, int len) throws IOException { 1343 try { 1344 out.write(b, off, len); 1345 } catch (IOException ioe) { 1346 throw handleException(ioe); 1347 } 1348 } 1349 1350 public void write(byte[] b) throws IOException { 1351 try { 1352 out.write(b); 1353 } catch (IOException ioe) { 1354 throw handleException(ioe); 1355 } 1356 } 1357 1358 } 1359 1360 // ------------------------------------------------------- Static Variable 1361 1362 /*** <tt>"\r\n"</tt>, as bytes. */ 1363 private static final byte[] CRLF = new byte[] {(byte) 13, (byte) 10}; 1364 1365 /*** Log object for this class. */ 1366 private static final Log LOG = LogFactory.getLog(HttpConnection.class); 1367 1368 // ----------------------------------------------------- Instance Variables 1369 1370 /*** A flag indicating if this connection has been used since being opened */ 1371 private boolean used = false; 1372 1373 /*** My host. */ 1374 private String hostName = null; 1375 1376 /*** My virtual host. */ 1377 private String virtualName = null; 1378 1379 /*** My port. */ 1380 private int portNumber = -1; 1381 1382 /*** My proxy host. */ 1383 private String proxyHostName = null; 1384 1385 /*** My proxy port. */ 1386 private int proxyPortNumber = -1; 1387 1388 /*** My client Socket. */ 1389 private Socket socket = null; 1390 1391 /*** My InputStream. */ 1392 private PushbackInputStream inputStream = null; 1393 1394 /*** My OutputStream. */ 1395 private OutputStream outputStream = null; 1396 1397 /*** the size of the buffer to use for the socket OutputStream */ 1398 private int sendBufferSize = -1; 1399 1400 /*** An {@link InputStream} for the response to an individual request. */ 1401 private InputStream lastResponseInputStream = null; 1402 1403 /*** Whether or not I am connected. */ 1404 protected boolean isOpen = false; 1405 1406 /*** the protocol being used */ 1407 private Protocol protocolInUse; 1408 1409 /*** SO_TIMEOUT socket value */ 1410 private int soTimeout = 0; 1411 1412 /*** TCP_NODELAY socket value */ 1413 private boolean soNodelay = true; 1414 1415 /*** Whether or not the socket is a secure one. */ 1416 private boolean usingSecureSocket = false; 1417 1418 /*** Whether I am tunneling a proxy or not */ 1419 private boolean tunnelEstablished = false; 1420 1421 /*** Whether or not isStale() is used by isOpen() */ 1422 private boolean staleCheckingEnabled = true; 1423 1424 /*** Timeout until connection established (Socket created). 0 means no timeout. */ 1425 private int connectTimeout = 0; 1426 1427 /*** the connection manager that created this connection or null */ 1428 private HttpConnectionManager httpConnectionManager; 1429 1430 /*** The local interface on which the connection is created, or null for the default */ 1431 private InetAddress localAddress; 1432 }

This page was automatically generated by Maven