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

This page was automatically generated by Maven