? stale_conn.patch ? lib/junit.jar ? lib/servlet.jar Index: src/java/org/apache/commons/httpclient/HttpConnection.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpConnection.java,v retrieving revision 1.56 diff -u -r1.56 HttpConnection.java --- src/java/org/apache/commons/httpclient/HttpConnection.java 17 Apr 2003 11:34:19 -0000 1.56 +++ src/java/org/apache/commons/httpclient/HttpConnection.java 17 Apr 2003 16:11:49 -0000 @@ -63,9 +63,7 @@ package org.apache.commons.httpclient; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; +import java.io.*; import java.lang.reflect.Method; import java.net.Socket; import java.net.SocketException; @@ -187,8 +185,8 @@ * * @deprecated use HttpConnection(String, int, String, int, Protocol) * - * @see #HttpConnection(String, int, String, int, Protocol) - * + * @see #HttpConnection(String, int, String, String, int, Protocol) + * */ public HttpConnection( String proxyHost, @@ -451,6 +449,63 @@ } /** + * Determines whether a connection is "stale", which is to say that either + * it is no longer open, or an attempt to read the connection would fail. + * + *

Unfortunately, due to the limitations of the JREs prior to 1.4, it is + * not possible to test a connection to see if both the read and write channels + * are open - except by reading and writing. This leads to a difficulty when + * some connections leave the "write" channel open, but close the read channel + * and ignore the request. This function attempts to ameliorate that + * problem by doing a test read, assuming that the caller will be doing a + * write followed by a read, rather than the other way around. + *

+ * + *

To avoid side-effects, the underlying connection is wrapped by a + * {@link PushbackInputStream}, so although data might be read, what is visible + * to clients of the connection will not change with this call.true if the connection is already closed, or a read would + * fail. + */ + public boolean isStale() { + boolean isStale = true; + if (isOpen) { + // the connection is open, but now we have to see if we can read it + // assume the connection is not stale. + isStale = false; + try { + if (inputStream.available() == 0) { + socket.setSoTimeout(1); + int byteRead = inputStream.read(); + if (byteRead == -1) { + // again - if the socket is reporting all data read, + // probably stale + isStale = true; + } + else { + inputStream.unread(byteRead); + } + } + } catch (InterruptedIOException e) { + // aha - the connection is NOT stale - continue on! + } catch (IOException e) { + // oops - the connection is stale, the read failed. + isStale = true; + } + try { + socket.setSoTimeout(soTimeout); + } catch (SocketException e) { + // if we cannot even reset the timeout on the connection probably + // safe to treat it as stale. + isStale = true; + } + } + + return isStale; + } + + /** * Return true if I am (or I will be) * connected via a proxy, false otherwise. * @@ -596,7 +651,7 @@ socket.setTcpNoDelay(soNodelay); socket.setSoTimeout(soTimeout); - inputStream = socket.getInputStream(); + inputStream = new PushbackInputStream(socket.getInputStream()); outputStream = new WrappedOutputStream(socket.getOutputStream()); isOpen = true; used = false; @@ -643,7 +698,7 @@ (SecureProtocolSocketFactory) protocolInUse.getSocketFactory(); socket = socketFactory.createSocket(socket, hostName, portNumber, true); - inputStream = socket.getInputStream(); + inputStream = new PushbackInputStream( socket.getInputStream() ); outputStream = socket.getOutputStream(); usingSecureSocket = true; tunnelEstablished = true; @@ -661,7 +716,7 @@ } /** - * Return a {@link RequestOutputStream} suitable for writing (possibly + * Return a {@link OutputStream} suitable for writing (possibly * chunked) bytes to my {@link OutputStream}. * * @throws IllegalStateException if I am not connected @@ -680,7 +735,7 @@ } /** - * Return a {@link RequestOutputStream} suitable for writing (possibly + * Return a {@link OutputStream} suitable for writing (possibly * chunked) bytes to my {@link OutputStream}. * * @param useChunking when true the chunked transfer-encoding will @@ -702,7 +757,7 @@ } /** - * Return a {@link ResponseInputStream} suitable for reading (possibly + * Return a {@link InputStream} suitable for reading (possibly * chunked) bytes from my {@link InputStream}. *

* If the given {@link HttpMethod} contains @@ -1193,8 +1248,8 @@ private Socket socket = null; /** My InputStream. */ - private InputStream inputStream = null; - + private PushbackInputStream inputStream = null; + /** My OutputStream. */ private OutputStream outputStream = null; Index: src/java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java,v retrieving revision 1.14 diff -u -r1.14 MultiThreadedHttpConnectionManager.java --- src/java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java 13 Apr 2003 03:51:39 -0000 1.14 +++ src/java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java 17 Apr 2003 16:11:50 -0000 @@ -234,6 +234,8 @@ HttpConnection connection = null; + boolean isExistingConnection = false; + synchronized (connectionPool) { // we clone the hostConfiguration @@ -249,11 +251,13 @@ while (connection == null) { + isExistingConnection = false; // happen to have a free connection with the right specs // if (hostPool.freeConnections.size() > 0) { connection = connectionPool.getFreeConnection(hostConfiguration); + isExistingConnection = true; // have room to make more // } else if ((hostPool.numConnections < maxHostConnections) @@ -316,6 +320,14 @@ } } } + + // if we are reusing an existing connection, check to see if it is stale, + // and if so, close it. + if (isExistingConnection && connection != null) { + if (connection.isStale()) { + connection.close(); + } + } return connection; } @@ -812,6 +824,16 @@ return wrappedConnection.isOpen(); } else { return false; + } + } + + // this ought never be called, as the isStale is called on the + // underlying connection before being wrapped. + public boolean isStale() { + if (hasConnection()) { + return wrappedConnection.isStale(); + } else { + return true; } } Index: src/java/org/apache/commons/httpclient/SimpleHttpConnectionManager.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/SimpleHttpConnectionManager.java,v retrieving revision 1.11 diff -u -r1.11 SimpleHttpConnectionManager.java --- src/java/org/apache/commons/httpclient/SimpleHttpConnectionManager.java 9 Apr 2003 18:37:59 -0000 1.11 +++ src/java/org/apache/commons/httpclient/SimpleHttpConnectionManager.java 17 Apr 2003 16:11:50 -0000 @@ -111,6 +111,7 @@ String virtualHost = hostConfiguration.getVirtualHost(); int port = hostConfiguration.getPort(); + boolean reusingConnection = false; if (httpConnection == null) { if (hostConfiguration.isProxySet()) { @@ -150,7 +151,13 @@ httpConnection.setProxyPort(hostConfiguration.getProxyPort()); } else { finishLastResponse(httpConnection); + reusingConnection = true; } + } + + // if the connection is being reused, but is stale, then close it. + if (reusingConnection && httpConnection.isStale() ) { + httpConnection.close(); } return httpConnection;