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

This page was automatically generated by Maven