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