You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by og...@apache.org on 2002/12/09 10:16:17 UTC
cvs commit: jakarta-commons/httpclient/src/test/org/apache/commons/httpclient TestGetMethodLocal.java TestHttpClientLocalHost.java TestMethodsLocalHost.java TestMethodsNoHost.java
oglueck 2002/12/09 01:16:17
Modified: httpclient/src/java/org/apache/commons/httpclient
AutoCloseInputStream.java ChunkedInputStream.java
ContentLengthInputStream.java HttpConnection.java
HttpMethodBase.java
MultiThreadedHttpConnectionManager.java
SimpleHttpConnectionManager.java
httpclient/src/java/org/apache/commons/httpclient/methods
GetMethod.java HeadMethod.java PostMethod.java
httpclient/src/test/org/apache/commons/httpclient
TestGetMethodLocal.java
TestHttpClientLocalHost.java
TestMethodsLocalHost.java TestMethodsNoHost.java
Added: httpclient/src/java/org/apache/commons/httpclient
ResponseConsumedWatcher.java
Log:
Handle closing of streams properly to avoid response parse failures.
Contributed by: Eric Johnson.
Revision Changes Path
1.3 +91 -23 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/AutoCloseInputStream.java
Index: AutoCloseInputStream.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/AutoCloseInputStream.java,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- AutoCloseInputStream.java 9 Sep 2002 05:43:39 -0000 1.2
+++ AutoCloseInputStream.java 9 Dec 2002 09:16:16 -0000 1.3
@@ -67,44 +67,58 @@
import java.io.InputStream;
/**
- * Closes a HttpConnection as soon as the end of the stream is reached.
+ * Closes an underlying stream as soon as the end of the stream is reached, and
+ * notifies a client when it has done so.
+ *
* @author Ortwin Gl�ck
+ * @author Eric Johnson
*
* @since 2.0
*/
class AutoCloseInputStream extends FilterInputStream {
- /** the connection the input stream comes from */
- private HttpConnection conn;
+
+ // assume that the underlying stream is open until we get an EOF indication.
+ private boolean streamOpen = true;
+
+ private boolean selfClosed = false;
+
+ /** The watcher is notified when the contents of the stream have been exhausted */
+ private ResponseConsumedWatcher watcher = null;
/**
* Create a new auto closing stream for the provided connection
- *
+ *
* @param in the input stream to read from
- * @param conn the connection to close when done reading
+ * @param watcher To be notified when the contents of the stream have been
+ * consumed.
*/
- public AutoCloseInputStream(InputStream in, HttpConnection conn) {
+ public AutoCloseInputStream(InputStream in, ResponseConsumedWatcher watcher) {
super(in);
- this.conn = conn;
+ this.watcher = watcher;
}
/**
* Reads the next byte of data from the input stream.
- *
+ *
* @throws IOException when there is an error reading
* @return the character read, or -1 for EOF
*/
public int read() throws IOException {
- int l = super.read();
- if (l == -1) {
- conn.close();
+ int l = -1;
+
+ if (isReadAllowed()) {
+ // underlying stream not closed, go ahead and read.
+ l = super.read();
+ checkClose(l);
}
+
return l;
}
/**
* Reads up to <code>len</code> bytes of data from the stream.
- *
+ *
* @param b a <code>byte</code> array to read data into
* @param off an offset within the array to store data
* @param len the maximum number of bytes to read
@@ -112,26 +126,80 @@
* @throws IOException if there are errors reading
*/
public int read(byte[] b, int off, int len) throws IOException {
- int l = super.read(b, off, len);
- if (l == -1) {
- conn.close();
+ int l = -1;
+
+ if ( isReadAllowed() ) {
+ l = super.read(b, off, len);
+ checkClose(l);
}
+
return l;
}
/**
* Reads some number of bytes from the input stream and stores them into the
* buffer array b.
- *
+ *
* @param b a <code>byte</code> array to read data into
* @return the number of bytes read or -1 for EOF
* @throws IOException if there are errors reading
*/
public int read(byte[] b) throws IOException {
- int l = super.read(b);
- if (l == -1) {
- conn.close();
+ int l = -1;
+
+ if ( isReadAllowed() ) {
+ l = super.read(b);
+ checkClose(l);
}
return l;
}
-}
\ No newline at end of file
+
+ /**
+ * Close the stream, and also close the underlying stream if it is not
+ * already closed.
+ */
+ public void close() throws IOException {
+ if (!selfClosed) {
+ selfClosed = true;
+ notifyWatcher();
+ }
+ }
+
+ /**
+ * Close the underlying stream should the end of the stream arrive.
+ *
+ * @param readResult The result of the read operation to check.
+ */
+ private void checkClose(int readResult) throws IOException {
+ if (readResult == -1) {
+ notifyWatcher();
+ }
+ }
+
+ /**
+ * See whether a read of the underlying stream should be allowed, and if
+ * not, check to see whether our stream has already been closed!
+ *
+ * @return <code>true</code> if it is still OK to read from the stream.
+ */
+ private boolean isReadAllowed() throws IOException {
+ if (!streamOpen && selfClosed) {
+ throw new IOException("Attempted read on closed stream.");
+ }
+ return streamOpen;
+ }
+
+ /**
+ * Notify the watcher that the contents have been consumed.
+ */
+ private void notifyWatcher() throws IOException {
+ if (streamOpen) {
+ super.close();
+ streamOpen = false;
+
+ if (watcher != null)
+ watcher.responseConsumed();
+ }
+ }
+}
+
1.7 +52 -6 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ChunkedInputStream.java
Index: ChunkedInputStream.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ChunkedInputStream.java,v
retrieving revision 1.6
retrieving revision 1.7
diff -u -r1.6 -r1.7
--- ChunkedInputStream.java 16 Oct 2002 13:14:11 -0000 1.6
+++ ChunkedInputStream.java 9 Dec 2002 09:16:16 -0000 1.7
@@ -63,17 +63,22 @@
package org.apache.commons.httpclient;
import java.io.*;
-import java.util.*;
/**
* <p>Transparently coalesces chunks of a HTTP stream that uses Transfer-Encoding
* chunked.</p>
*
+ * <p>Note that this class NEVER closes the underlying stream, even when close gets
+ * called. Instead, it will read until the "end" of its chunking on close, which
+ * allows for the seamless invocation of subsequent HTTP 1.1 calls, while not
+ * requiring the client to remember to read the entire contents of the response.</p>
+ *
* @see ResponseInputStream
*
* @author Ortwin Gl�ck
* @author Sean C. Sullivan
* @author Martin Elwin
+ * @author Eric Johnson
*
* @since 2.0
*
@@ -83,6 +88,7 @@
private InputStream in;
private int chunkSize, pos;
private boolean eof = false;
+ private boolean closed = false;
private static final String HTTP_ENC = "US-ASCII";
private HttpMethod method;
@@ -96,7 +102,7 @@
* @throws java.lang.NullPointerException
*
*/
- public ChunkedInputStream(final InputStream in, final HttpMethod method) throws IOException {
+ public ChunkedInputStream(InputStream in, HttpMethod method) throws IOException {
if (null == in) {
throw new NullPointerException("InputStream parameter");
}
@@ -124,6 +130,9 @@
* @throws IOException
*/
public int read() throws IOException {
+
+ if (closed)
+ throw new IOException("Attempted read from closed stream.");
if (eof) return -1;
if (pos >= chunkSize) {
nextChunk();
@@ -134,6 +143,10 @@
}
public int read(byte[] b, int off, int len) throws java.io.IOException {
+
+ if (closed)
+ throw new IOException("Attempted read from closed stream.");
+
if (eof) return -1;
if (pos >= chunkSize) {
nextChunk();
@@ -274,7 +287,40 @@
return (buf.toString());
}
+ /**
+ * Upon close, this reads the remainder of the chunked message,
+ * leaving the underlying socket at a position to start reading the
+ * next response without scanning.
+ */
public void close() throws IOException {
- in.close();
+ if (!closed) {
+ try {
+ if (!eof) {
+ exhaustInputStream(this);
+ }
+ }
+ finally {
+ eof = true;
+ closed = true;
+ }
+ }
+ }
+
+ /**
+ * Exhaust an input stream, reading until EOF has been encountered.
+ *
+ * <p>Note that this function is intended as a non-public utility.
+ * This is a little weird, but it seemed silly to make a utility
+ * class for this one function, so instead it is just static and
+ * shared that way.</p>
+ *
+ * @param inStream The {@link InputStream} to exhaust.
+ */
+ static void exhaustInputStream(InputStream inStream) throws IOException {
+ // read and discard the remainder of the message
+ byte buffer[] = new byte[1024];
+ while ( inStream.read(buffer) >= 0) {
+ ;
+ }
}
}
1.3 +47 -6 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ContentLengthInputStream.java
Index: ContentLengthInputStream.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ContentLengthInputStream.java,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- ContentLengthInputStream.java 19 Sep 2002 10:15:08 -0000 1.2
+++ ContentLengthInputStream.java 9 Dec 2002 09:16:16 -0000 1.3
@@ -68,6 +68,7 @@
* Cuts the wrapped InputStream off after a specified number of bytes.
*
* @author Ortwin Gl�ck
+ * @author Eric Johnson
* @since 2.0
*/
@@ -75,6 +76,8 @@
private int contentLength;
private int pos = 0;
+ private boolean closed = false;
+
/**
* Creates a new length limited stream
*
@@ -87,15 +90,53 @@
this.contentLength = contentLength;
}
+ /**
+ * Reads until the end of the known length of content.
+ *
+ * <p>Does not close the underlying socket input, but instead leaves it
+ * primed to parse the next response.</p>
+ */
+ public void close() throws IOException {
+ if (!closed) {
+ try {
+ ChunkedInputStream.exhaustInputStream(this);
+ } finally {
+ // close after above so that we don't throw an exception trying
+ // to read after closed!
+ closed = true;
+ }
+ }
+ }
+
public int read() throws java.io.IOException {
- if (pos >= contentLength) return -1;
+ if (closed)
+ throw new IOException("Attempted read from closed stream.");
+
+ if (pos >= contentLength)
+ return -1;
pos++;
return super.read();
}
-
+ /**
+ * Does standard {@link InputStream#read(byte[], int, int)} behavior, but
+ * also notifies the watcher when the contents have been consumed.
+ *
+ * @param b The byte array to fill.
+ * @param off Start filling at this position.
+ * @param len The number of bytes to attempt to read.
+ * @return The number of bytes read, or -1 if the end of content has been
+ * reached.
+ *
+ * @throws java.io.IOException Should an error occur on the wrapped stream.
+ */
public int read(byte[] b, int off, int len) throws java.io.IOException {
- if (pos >= contentLength) return -1;
+ if (closed)
+ throw new IOException("Attempted read from closed stream.");
+
+ if (pos >= contentLength)
+ return -1;
+
if (pos + len > contentLength) {
len = contentLength - pos;
}
1.27 +40 -4 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpConnection.java
Index: HttpConnection.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpConnection.java,v
retrieving revision 1.26
retrieving revision 1.27
diff -u -r1.26 -r1.27
--- HttpConnection.java 3 Dec 2002 05:46:15 -0000 1.26
+++ HttpConnection.java 9 Dec 2002 09:16:16 -0000 1.27
@@ -306,6 +306,37 @@
return (!(null == _proxyHost || 0 >= _proxyPort));
}
+ /**
+ * Set the state to keep track of the last response for the last request.
+ *
+ * <p>The connection managers use this to ensure that previous requests are
+ * properly closed before a new request is attempted. That way, a GET
+ * request need not be read in its entirety before a new request is issued.
+ * Instead, this stream can be closed as appropriate.</p>
+ *
+ * @param inStream The stream associated with an HttpMethod.
+ */
+ public void setLastResponseInputStream(InputStream inStream) {
+ _lastResponseInput = inStream;
+ }
+
+ /**
+ * Returns the stream used to read the last response's body.
+ *
+ * <p>Clients will generally not need to call this function unless
+ * using HttpConnection directly, instead of calling {@link HttpClient#executeMethod}.
+ * For those clients, call this function, and if it returns a non-null stream,
+ * close the stream before attempting to execute a method. Note that
+ * calling "close" on the stream returned by this function <i>may</i> close
+ * the connection if the previous response contained a "Connection: close" header. </p>
+ *
+ * @return An {@link InputStream} corresponding to the body of the last
+ * response.
+ */
+ public InputStream getLastResponseInputStream() {
+ return _lastResponseInput;
+ }
+
// --------------------------------------------------- Other Public Methods
/**
@@ -776,6 +807,9 @@
protected void closeSocketAndStreams() {
log.trace("enter HttpConnection.closeSockedAndStreams()");
+ // no longer care about previous responses...
+ _lastResponseInput = null;
+
if (null != _input) {
try {
_input.close();
@@ -891,6 +925,8 @@
private InputStream _input = null;
/** My OutputStream. */
private OutputStream _output = null;
+ /** An {@link InputStream} for the response to an individual request. */
+ private InputStream _lastResponseInput = null;
/** Whether or not I am connected. */
private boolean _open = false;
/** Whether or not I am/should connect via SSL. */
1.85 +210 -194 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java
Index: HttpMethodBase.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v
retrieving revision 1.84
retrieving revision 1.85
diff -u -r1.84 -r1.85
--- HttpMethodBase.java 8 Dec 2002 18:51:06 -0000 1.84
+++ HttpMethodBase.java 9 Dec 2002 09:16:16 -0000 1.85
@@ -158,6 +158,7 @@
* @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
* @author <a href="mailto:dims@apache.org">Davanum Srinivas</a>
* @author Ortwin Gl�ck
+ * @author Eric Johnson
*/
public abstract class HttpMethodBase implements HttpMethod {
//~ Static variables/initializers ������������������������������������������
@@ -235,12 +236,19 @@
/** Whether or not I have been executed. */
private boolean used = false;
+ /** How many times did this transparently handle a recoverable exception? */
+ private int recoverableExceptionCount = 0;
+
/**
* The maximum number of attempts to attempt recovery from an
* HttpRecoverableException.
*/
private int maxRetries = 3;
+ private boolean inExecute = false;
+
+ private boolean doneWithConnection = false;
+
/** Default content encoding chatset */
protected static final String DEFAULT_CHARSET = "ISO-8859-1";
@@ -568,6 +576,8 @@
while ((len = is.read(buffer)) > 0) {
os.write(buffer, 0, len);
}
+ is.close();
+ os.close();
responseBody = os.toByteArray();
setResponseStream(null);
log.debug("buffering response body");
@@ -592,9 +602,9 @@
return responseStream;
}
if (responseBody != null) {
- responseStream = new ByteArrayInputStream(responseBody);
+ InputStream byteResponseStream = new ByteArrayInputStream(responseBody);
log.debug("re-creating response stream from byte array");
- return responseStream;
+ return byteResponseStream;
}
return null;
}
@@ -719,25 +729,7 @@
setRequestHeader(header);
}
-
- /**
- * Close the provided HTTP connection, if:
- * http 1.0 and not using the 'connect' method, or
- * http 1.1 and the Connection: close header is sent
- *
- * @param connection the HTTP connection to process
- * Add the specified request header. If a header of the same name already
- * exists, the new value will be appended onto the the existing value
- * list. A <i>header</i> value of <code>null</code> will be ignored. Note
- * that header-name matching is case insensitive.
- */
- private void closeConnection(HttpConnection connection) {
- if (shouldCloseConnection()) {
- connection.close();
- }
- }
-
- private boolean shouldCloseConnection() {
+ protected boolean shouldCloseConnection() {
if (!http11) {
if (getName().equals(ConnectMethod.NAME) &&
(statusLine.getStatusCode() == HttpStatus.SC_OK)) {
@@ -759,13 +751,59 @@
return false;
}
- private void wrapResponseStream( HttpConnection connection ) {
+ private boolean isRetryNeeded(int statusCode, HttpState state, HttpConnection conn) {
+ switch (statusCode) {
+ case HttpStatus.SC_UNAUTHORIZED:
+ case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
+ log.debug("Authorization required");
+ if (doAuthentication) { //process authentication response
+ //if the authentication is successful, return the statusCode
+ //otherwise, drop through the switch and try again.
+ if (processAuthenticationResponse(state)) {
+ return false;
+ }
+ } else { //let the client handle the authenticaiton
+ return false;
+ }
+ break;
+
+ case HttpStatus.SC_MOVED_TEMPORARILY:
+ case HttpStatus.SC_MOVED_PERMANENTLY:
+ case HttpStatus.SC_TEMPORARY_REDIRECT:
+ log.debug("Redirect required");
+
+ if (! processRedirectResponse(conn)) {
+ return false;
+ }
+ break;
+
+ default:
+ // neither an unauthorized nor a redirect response
+ return false;
+ } //end of switch
+
+ return true;
+ }
+
+ private void checkExecuteConditions(HttpState state, HttpConnection conn)
+ throws HttpException {
- if ( responseStream != null ) {
- this.responseConnection = connection;
- this.responseStream = new ResponseAutoReleaseInputStream(responseStream);
+ if (null == state) {
+ throw new NullPointerException("HttpState parameter");
+ }
+ if (null == conn) {
+ throw new NullPointerException("HttpConnection parameter");
+ }
+ if (hasBeenUsed()) {
+ throw new HttpException("Already used, but not recycled.");
+ }
+ if (!validate()) {
+ throw new HttpException("Not valid");
}
+ if (inExecute) {
+ throw new IllegalStateException("Execute invoked recursively, or exited abnormally.");
+ }
}
/**
@@ -795,104 +833,83 @@
throws HttpException, IOException, NullPointerException {
log.trace("enter HttpMethodBase.execute(HttpState, HttpConnection)");
- //TODO: This method is too large
- //check some error conditions
- if (null == state) {
- throw new NullPointerException("HttpState parameter");
- }
- if (null == conn) {
- throw new NullPointerException("HttpConnection parameter");
- }
- if (hasBeenUsed()) {
- throw new HttpException("Already used, but not recycled.");
- }
- if (!validate()) {
- throw new HttpException("Not valid");
- }
-
- //pre-emptively add the authorization header, if required.
- Authenticator.authenticate(this, state);
- if (conn.isProxied()) {
- Authenticator.authenticateProxy(this, state);
- }
+ checkExecuteConditions(state, conn);
+ inExecute = true;
- //Set visited = new HashSet();
- realms = new HashSet();
- proxyRealms = new HashSet();
- int forwardCount = 0; //protect from an infinite loop
+ try {
+ //TODO: This method is too large
- while (forwardCount++ < maxForwards) {
- if (log.isDebugEnabled()) {
- log.debug("Execute loop try " + forwardCount);
- }
+ //pre-emptively add the authorization header, if required.
+ Authenticator.authenticate(this, state);
+ if (conn.isProxied()) {
+ Authenticator.authenticateProxy(this, state);
+ }
+
+ //Set visited = new HashSet();
+ realms = new HashSet();
+ proxyRealms = new HashSet();
+ int forwardCount = 0; //protect from an infinite loop
+
+ while (forwardCount++ < maxForwards) {
+ // on every retry, reset this state information.
+ responseConnection = conn;
+ conn.setLastResponseInputStream(null);
- //write the request and read the response, will retry
- processRequest(state, conn);
+ if (log.isDebugEnabled()) {
+ log.debug("Execute loop try " + forwardCount);
+ }
- //if SC_CONTINUE write the request body
- writeRemainingRequestBody(state, conn);
+ //write the request and read the response, will retry
+ processRequest(state, conn);
- int statusCode = statusLine.getStatusCode();
- switch (statusCode) {
- case HttpStatus.SC_UNAUTHORIZED:
- case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
- log.debug("Authorization required");
- if (doAuthentication) { //process authentication response
- //if the authentication is successful, return the statusCode
- //otherwise, drop through the switch and try again.
- if (processAuthenticationResponse(state, conn)) {
- wrapResponseStream(conn);
- return statusCode;
- }
- } else { //let the client handle the authenticaiton
- wrapResponseStream(conn);
- return statusCode;
- }
- break;
+ //if SC_CONTINUE write the request body
+ writeRemainingRequestBody(state, conn);
- case HttpStatus.SC_MOVED_TEMPORARILY:
- case HttpStatus.SC_MOVED_PERMANENTLY:
- case HttpStatus.SC_TEMPORARY_REDIRECT:
- log.debug("Redirect required");
-
- if (! processRedirectResponse(state, conn)) {
- wrapResponseStream(conn);
- return statusCode;
- }
+ if (!isRetryNeeded(statusLine.getStatusCode(), state, conn)) {
+ // nope, no retry needed, exit loop.
break;
+ }
+ /*
+ Revisiting may be desired. We do not know about the server's internal state.
- default:
- // neither an unauthorized nor a redirect response
- wrapResponseStream(conn);
+ //check to see if we have visited this url before
+ if (visited.contains(generateVisitedKey(conn))) {
+ log.error("Link " + generateVisitedKey(conn) + "' revisited");
return statusCode;
- } //end of switch
+ }
+ visited.add(generateVisitedKey(conn));
+ */
-/*
- Revisiting may be desired. We do not know about the server's internal state.
+ // retry - close previous stream. Caution - this causes
+ // responseBodyConsumed to be called, which may also close the
+ // connection.
+ if (responseStream != null) {
+ responseStream.close();
+ }
- //check to see if we have visited this url before
- if (visited.contains(generateVisitedKey(conn))) {
- log.error("Link " + generateVisitedKey(conn) + "' revisited");
- return statusCode;
- }
- visited.add(generateVisitedKey(conn));
-*/
+ } //end of retry loop
- //close connection if required
- closeConnection(conn);
- if (conn.isOpen()) {
- //throw away body to position the stream after the response
- getResponseBodyAsString();
+ if (forwardCount >= maxForwards) {
+ log.error("Narrowly avoided an infinite loop in execute");
+ throw new HttpRecoverableException("Maximum redirects ("+ maxForwards +") exceeded");
+ }
+ }
+ finally {
+ inExecute = false;
+ // If the response has been fully processed, return the connection
+ // to the pool. Use this flag, rather than other tests (like
+ // responseStream == null), as subclasses, might reset the stream,
+ // for example, reading the entire response into a file and then
+ // setting the file as the stream.
+ if (doneWithConnection) {
+ ensureConnectionRelease();
}
- } //end of loop
-
- wrapResponseStream(conn);
+ }
- log.error("Narrowly avoided an infinite loop in execute");
- throw new HttpRecoverableException("Maximum redirects ("+ maxForwards +") exceeded");
+ return statusLine.getStatusCode();
}
- private boolean processRedirectResponse(HttpState state, HttpConnection conn) {
+ private boolean processRedirectResponse(HttpConnection conn) {
if (!getFollowRedirects()) {
log.info("Redirect requested but followRedirects is "
@@ -967,8 +984,8 @@
* Check for a valid redirect given the current conn and new url.
* Redirect to a different protocol, host or port are checked for validity.
*
- * @param conn The existing HttpConnection
- * @param url The new URL to redirect to
+ * @param currentUrl The current URL (redirecting from)
+ * @param redirectUrl The new URL to redirect to
* @throws HttpException if the redirect is invalid
* @since 2.0
*/
@@ -1056,6 +1073,9 @@
http11 = true;
bodySent = false;
responseBody = null;
+ recoverableExceptionCount = 0;
+ inExecute = false;
+ doneWithConnection = false;
}
/**
@@ -1065,10 +1085,13 @@
*/
public void releaseConnection() {
- if ( responseConnection != null ) {
- responseConnection.releaseConnection();
- this.responseConnection = null;
- this.responseStream = null;
+ if (responseStream != null) {
+ try {
+ // FYI - this may indirectly invoke responseBodyConsumed.
+ responseStream.close();
+ } catch (IOException e) {
+ // attempting cleanup, don't care about exception.
+ }
}
}
@@ -1603,9 +1626,10 @@
* Read the response body from the given {@link HttpConnection}.
*
* <p>
- * The current implementation simply consumes the expected response body
- * (according to the values of the <tt>Content-Length</tt> and
- * <tt>Transfer-Encoding</tt> headers, if any).
+ * The current implementation wraps the socket level stream with
+ * an appropriate stream for the type of response (chunked, content-length,
+ * or auto-close). If there is no response body, the connection associated
+ * with the request will be returned to the connection manager.
* </p>
*
* <p>
@@ -1626,7 +1650,17 @@
log.trace(
"enter HttpMethodBase.readResponseBody(HttpState, HttpConnection)");
- setResponseStream(_readResponseBody(state, conn));
+ // assume we are not done with the connection if we get a stream
+ doneWithConnection = false;
+ InputStream stream = _readResponseBody(conn);
+ if (stream == null) {
+ // done using the connection!
+ responseBodyConsumed();
+ }
+ else {
+ conn.setLastResponseInputStream(stream);
+ setResponseStream(stream);
+ }
}
/**
@@ -1641,11 +1675,10 @@
* @see #readResponse
* @see #processResponseBody
*
- * @param state the client state
* @param conn the {@link HttpConnection} to read the response from
* @return InputStream to read the response body from
*/
- private InputStream _readResponseBody(HttpState state, HttpConnection conn)
+ private InputStream _readResponseBody(HttpConnection conn)
throws IOException {
log.trace("enter HttpMethodBase.readResponseBody(HttpState, HttpConnection)");
@@ -1697,12 +1730,12 @@
&& !getName().equals(ConnectMethod.NAME)){
result = is;
}
- if (result == null) {
- return null;
- }
- if (shouldCloseConnection()) {
- result = new AutoCloseInputStream(result, conn);
+ // if there is a result - ALWAYS wrap it in an observer which will
+ // close the underlying stream as soon as it is consumed, and notify
+ // the watcher that the stream has been consumed.
+ if (result != null) {
+ result = new AutoCloseInputStream(result, m_responseWatcher);
}
return result;
@@ -2100,29 +2133,14 @@
}
/**
- * Generates a key used for idenifying visited URLs.
- *
- * @param conn DOCUMENT ME!
- *
- * @return DOCUMENT ME!
- */
- private String generateVisitedKey(HttpConnection conn) {
- return conn.getHost() + ":" + conn.getPort() + "|"
- + generateRequestLine(conn, getName(), getPath(),
- getQueryString(), getHttpVersion());
- }
-
- /**
* process a response that requires authentication
*
* @param state the current state
- * @param connection the connection for communication
*
* @return true if the request has completed process, false if more
* attempts are needed
*/
- private boolean processAuthenticationResponse(HttpState state,
- HttpConnection connection) {
+ private boolean processAuthenticationResponse(HttpState state) {
log.trace("enter HttpMethodBase.processAuthenticationResponse("
+ "HttpState, HttpConnection)");
@@ -2210,8 +2228,8 @@
* @throws IOException when an I/O error occurs communicating with the
* server
*
- * @see writeRequest(HttpState,HttpConnection)
- * @see readResponse(HttpState,HttpConnection)
+ * @see #writeRequest(HttpState,HttpConnection)
+ * @see #readResponse(HttpState,HttpConnection)
*/
private void processRequest(HttpState state, HttpConnection connection)
throws HttpException, IOException {
@@ -2238,6 +2256,9 @@
log.debug("Closing the connection.");
}
+ // update the recoverable exception count.
+ recoverableExceptionCount++;
+
connection.close();
log.info("Recoverable exception caught when writing request");
if (retryCount == maxRetries) {
@@ -2335,64 +2356,59 @@
}
/**
- * Releases this connection from its connectionManager when the response has
- * been read.
+ * Returns the number of "recoverable" exceptions thrown and handled, to
+ * allow for monitoring the quality of the connection.
+ *
+ * @return The number of recoverable exceptions handled by the method.
*/
- private class ResponseAutoReleaseInputStream extends InputStream {
+ public int getRecoverableExceptionCount() {
+ return recoverableExceptionCount;
+ }
- private InputStream is;
+ /**
+ * A response has been consumed.
+ *
+ * <p>The default behavior for this class is to check to see if the connection
+ * should be closed, and close if need be, and to ensure that the connection
+ * is returned to the connection manager - if and only if we are not still
+ * inside the execute call.</p>
+ *
+ */
+ protected void responseBodyConsumed() {
- public ResponseAutoReleaseInputStream(InputStream is) {
- this.is = is;
- }
+ // make sure this is the initial invocation of the notification,
+ // ignore subsequent ones.
+ responseStream = null;
+ responseConnection.setLastResponseInputStream(null);
- /**
- * @see java.io.InputStream#close()
- */
- public void close() throws IOException {
- is.close();
- releaseConnection();
+ if (shouldCloseConnection()) {
+ responseConnection.close();
}
- /**
- * @see java.io.InputStream#read()
- */
- public int read() throws IOException {
- int b = is.read();
-
- if ( b == -1 ) {
- releaseConnection();
- }
-
- return b;
+ doneWithConnection = true;
+ if (!inExecute) {
+ ensureConnectionRelease();
}
+ }
- /**
- * @see java.io.InputStream#read(byte, int, int)
- */
- public int read(byte[] array, int off, int len) throws IOException {
- int b = is.read(array, off, len);
-
- if ( b == -1 ) {
- releaseConnection();
- }
-
- return b;
+ /**
+ * Insure that the connection is released back to the pool.
+ */
+ private void ensureConnectionRelease() {
+ if ( responseConnection != null ) {
+ responseConnection.releaseConnection();
+ responseConnection = null;
}
+ }
- /**
- * @see java.io.InputStream#read(byte)
- */
- public int read(byte[] array) throws IOException {
- int b = is.read(array);
-
- if ( b == -1 ) {
- releaseConnection();
- }
-
- return b;
+ /**
+ * This exists so that the public interface to this class need not include
+ * either the responseConsumed or the responseBodyConsumed methods.
+ */
+ private ResponseConsumedWatcher m_responseWatcher = new ResponseConsumedWatcher() {
+ public void responseConsumed() {
+ responseBodyConsumed();
}
-
- }
+ };
}
1.2 +59 -56 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java
Index: MultiThreadedHttpConnectionManager.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- MultiThreadedHttpConnectionManager.java 3 Dec 2002 05:46:15 -0000 1.1
+++ MultiThreadedHttpConnectionManager.java 9 Dec 2002 09:16:16 -0000 1.2
@@ -77,11 +77,12 @@
* Manages a set of HttpConnections for various host:ports.
*
* @author <a href="mailto:becke@u.washington.edu">Michael Becke</a>
- *
+ * @author Eric Johnson
+ *
* @since 2.0
*/
public class MultiThreadedHttpConnectionManager implements HttpConnectionManager {
-
+
// -------------------------------------------------------- Class Variables
/** Log object for this class. */
private static final Log log = LogFactory.getLog(MultiThreadedHttpConnectionManager.class);
@@ -100,12 +101,12 @@
* No-args constructor
*/
public MultiThreadedHttpConnectionManager() {
-
+
this.referenceToHostPort = Collections.synchronizedMap( new HashMap() );
this.referenceQueue = new ReferenceQueue();
-
+
new ReferenceQueueThread().start();
-
+
}
/**
@@ -132,23 +133,23 @@
*/
public HttpConnection getConnection(HostConfiguration hostConfiguration)
throws MalformedURLException {
-
+
while( true ) {
try {
return getConnection(hostConfiguration, 0);
} catch ( HttpException e ) {
- log.debug(
- "Unexpected exception while waiting for connection",
- e
+ log.debug(
+ "Unexpected exception while waiting for connection",
+ e
);
};
}
-
+
}
/**
* Return the port provided if not -1 (default), return 443 if the
- * protocol is HTTPS, otherwise 80.
+ * protocol is HTTPS, otherwise 80.
*
* This functionality is a URLUtil and may be better off in URIUtils
*
@@ -171,11 +172,11 @@
}
return portForProtocol;
}
-
+
/**
* @see HttpConnectionManager#getConnection(HostConfiguration, long)
*/
- public HttpConnection getConnection(HostConfiguration hostConfiguration, long timeout)
+ public HttpConnection getConnection(HostConfiguration hostConfiguration, long timeout)
throws HttpException, MalformedURLException {
log.trace("enter HttpConnectionManager.getConnection(HostConfiguration, long)");
@@ -187,7 +188,7 @@
String protocol = hostConfiguration.getProtocol();
String host = hostConfiguration.getHost();
int port = MultiThreadedHttpConnectionManager.getPort(
- protocol,
+ protocol,
hostConfiguration.getPort()
);
String hostAndPort = host + ":" + port;
@@ -199,8 +200,8 @@
// Look for a list of connections for the given host:port
HostConnectionPool connectionPool = getConnectionPool(hostAndPort);
-
- HttpConnection conn = getConnection(
+
+ HttpConnection conn = getConnection(
connectionPool,
host,
port,
@@ -217,7 +218,7 @@
* Gets a connection or waits if one is not available. A connection is
* available if one exists that is not being used or if fewer than
* maxConnections have been created in the connectionPool.
- *
+ *
* @param connectionPool
* @param host
* @param port
@@ -226,9 +227,9 @@
* @param proxyPort
* @param timeout the number of milliseconds to wait for a connection, 0 to
* wait indefinitely
- *
+ *
* @return HttpConnection an available connection
- *
+ *
* @throws HttpException if a connection does not available in 'timeout'
* milliseconds
*/
@@ -241,15 +242,15 @@
int proxyPort,
long timeout
) throws HttpException {
-
+
HttpConnection connection = null;
-
+
synchronized( connectionPool ) {
-
- // keep trying until a connection is available, should happen at
+
+ // keep trying until a connection is available, should happen at
// most twice
while ( connection == null ) {
-
+
if (connectionPool.freeConnections.size() > 0) {
connection = (HttpConnection)connectionPool.freeConnections.removeFirst();
} else {
@@ -257,9 +258,9 @@
if (connectionPool.numConnections < maxConnections) {
// Create a new connection
connection = new HttpConnection(
- proxyHost,
- proxyPort,
- host,
+ proxyHost,
+ proxyPort,
+ host,
port,
useHttps
);
@@ -271,14 +272,14 @@
new WeakReference( connection, referenceQueue ),
host + ":" + port
);
-
+
} else {
-
+
TimeoutThread threadTimeout = new TimeoutThread();
threadTimeout.setTimeout(timeout);
threadTimeout.setWakeupThread(Thread.currentThread());
threadTimeout.start();
-
+
try {
log.debug(
"HttpConnectionManager.getConnection: waiting for "
@@ -291,14 +292,14 @@
} catch (InterruptedException e) {
throw new HttpException("Timeout waiting for connection.");
}
-
+
}
}
}
-
+
}
-
- return connection;
+
+ return connection;
}
/**
@@ -322,7 +323,7 @@
}
return listConnections;
}
-
+
/**
* Get the number of connections in use for the key
*
@@ -334,21 +335,23 @@
HostConnectionPool connectionPool = getConnectionPool(hostAndPort);
synchronized( connectionPool ) {
- return connectionPool.numConnections;
+ return connectionPool.numConnections;
}
}
-
+
/**
* Make the given HttpConnection available for use by other requests.
* If another thread is blocked in getConnection() waiting for a connection
* for this host:port, they will be woken up.
- *
+ *
* @param conn - The HttpConnection to make available.
*/
public void releaseConnection(HttpConnection conn) {
log.trace("enter HttpConnectionManager.releaseConnection(HttpConnection)");
+ // make sure that the response has been read.
+ SimpleHttpConnectionManager.finishLastResponse(conn);
String host = conn.getHost();
int port = conn.getPort();
String key = host + ":" + port;
@@ -369,38 +372,38 @@
listConnections.notify();
}
}
-
+
/**
* A simple struct-link class to combine the connection list and the count
* of created connections.
*/
private class HostConnectionPool {
-
+
public LinkedList freeConnections = new LinkedList();
public int numConnections = 0;
-
+
}
/**
* A thread for listening for HttpConnections reclaimed by the garbage
* collector.
- */
+ */
private class ReferenceQueueThread extends Thread {
-
+
public ReferenceQueueThread() {
- setDaemon(true);
+ setDaemon(true);
}
-
+
/**
* @see java.lang.Runnable#run()
*/
public void run() {
-
+
while(true) {
-
+
try {
Reference ref = referenceQueue.remove();
-
+
if ( ref != null ) {
String hostPort = (String)referenceToHostPort.get(ref);
referenceToHostPort.remove(ref);
@@ -413,9 +416,9 @@
} catch (InterruptedException e) {
log.debug("ReferenceQueueThread interrupted", e);
}
-
+
}
-
+
}
}
@@ -454,7 +457,7 @@
}
public void run() {
- log.trace("TimeoutThread.run()");
+ log.trace("TimeoutThread.run()");
if(timeout == 0){
return;
}
@@ -466,10 +469,10 @@
sleep(timeout);
thrdWakeup.interrupt();
}catch(InterruptedException e){
- log.debug("InterruptedException caught as expected");
+ log.debug("InterruptedException caught as expected");
// This is expected
}
}
}
-
+
}
1.2 +43 -16 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/SimpleHttpConnectionManager.java
Index: SimpleHttpConnectionManager.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/SimpleHttpConnectionManager.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- SimpleHttpConnectionManager.java 3 Dec 2002 05:46:15 -0000 1.1
+++ SimpleHttpConnectionManager.java 9 Dec 2002 09:16:16 -0000 1.2
@@ -62,14 +62,17 @@
package org.apache.commons.httpclient;
import java.net.MalformedURLException;
+import java.io.InputStream;
+import java.io.IOException;
/**
* A connection manager that provides access to a single HttpConnection. This
* manager makes no attempt to provide exclusive access to the contained
* HttpConnection.
- *
+ *
* @author <a href="mailto:becke@u.washington.edu">Michael Becke</a>
+ * @author Eric Johnson
*
* @since 2.0
*/
@@ -103,27 +106,27 @@
String host = hostConfiguration.getHost();
int port = hostConfiguration.getPort();
boolean isSecure = protocol.equalsIgnoreCase("HTTPS");
-
+
if ( httpConnection == null ) {
-
+
if ( hostConfiguration.isProxySet() ) {
httpConnection = new HttpConnection(
hostConfiguration.getProxyHost(),
hostConfiguration.getProxyPort(),
- host,
- port,
+ host,
+ port,
isSecure
);
- } else {
+ } else {
httpConnection = new HttpConnection(host, port, isSecure);
}
-
+
} else {
-
+
// make sure the host and proxy are correct for this connection
// close it and set the values if they are not
- if (
- !hostConfiguration.hostEquals(httpConnection)
+ if (
+ !hostConfiguration.hostEquals(httpConnection)
|| !hostConfiguration.proxyEquals(httpConnection)
) {
if ( httpConnection.isOpen() ) {
@@ -137,18 +140,42 @@
httpConnection.setProxyHost(hostConfiguration.getProxyHost());
httpConnection.setProxyPort(hostConfiguration.getProxyPort());
- }
-
+ }
+ else {
+ finishLastResponse(httpConnection);
+ }
}
return httpConnection;
-
+
}
/**
* @see org.apache.commons.httpclient.HttpConnectionManager#releaseConnection(org.apache.commons.httpclient.HttpConnection)
*/
public void releaseConnection(HttpConnection conn) {
+ if (conn != httpConnection)
+ throw new IllegalStateException("Unexpected close on a different connection.");
+
+ finishLastResponse(httpConnection);
}
+ /**
+ * Since the same connection is about to be reused, make sure the
+ * previous request was completely processed, and if not
+ * consume it now.
+ */
+ static void finishLastResponse(HttpConnection conn) {
+ InputStream lastResponse = conn.getLastResponseInputStream();
+ if ( lastResponse != null) {
+ conn.setLastResponseInputStream(null);
+ try {
+ lastResponse.close();
+ }
+ catch (IOException ioe) {
+ // badness - close to force reconnect.
+ conn.close();
+ }
+ }
+ }
}
1.1 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ResponseConsumedWatcher.java
Index: ResponseConsumedWatcher.java
===================================================================
/*
* $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ResponseConsumedWatcher.java,v 1.1 2002/12/09 09:16:16 oglueck Exp $
* $Revision: 1.1 $
* $Date: 2002/12/09 09:16:16 $
* ====================================================================
*
* The Apache Software License, Version 1.1
*
* Copyright (c) 1999-2002 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "HttpClient", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
* [Additional notices, if required by prior licensing conditions]
*
*/
package org.apache.commons.httpclient;
/**
* When a response stream has been consumed, various parts of the HttpClient
* implementation need to respond appropriately.
*
* <p>When one of the three types of {@link java.io.InputStream}, one of
* AutoCloseInputStream (package), {@link ContentLengthInputStream}, or
* {@link ChunkedInputStream} finishes with its content, either because
* all content has been consumed, or because it was explicitly closed,
* it notifies its corresponding method via this interface.</p>
*
* @see ContentLengthInputStream
* @see ChunkedInputStream
* @author Eric Johnson
*/
interface ResponseConsumedWatcher {
/**
* A response has been consumed.
*/
void responseConsumed();
}
1.19 +4 -3 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/GetMethod.java
Index: GetMethod.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/GetMethod.java,v
retrieving revision 1.18
retrieving revision 1.19
diff -u -r1.18 -r1.19
--- GetMethod.java 3 Sep 2002 01:36:26 -0000 1.18
+++ GetMethod.java 9 Dec 2002 09:16:17 -0000 1.19
@@ -372,6 +372,7 @@
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
+ in.close();
out.close();
setResponseStream(new FileInputStream(createTempFile()));
}
1.12 +6 -5 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/HeadMethod.java
Index: HeadMethod.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/HeadMethod.java,v
retrieving revision 1.11
retrieving revision 1.12
diff -u -r1.11 -r1.12
--- HeadMethod.java 1 Sep 2002 01:27:37 -0000 1.11
+++ HeadMethod.java 9 Dec 2002 09:16:17 -0000 1.12
@@ -146,8 +146,9 @@
log.trace(
"enter HeadMethod.readResponseBody(HttpState, HttpConnection)");
- // despite the possible presence of a content-length header,
+ // despite the possible presence of a content-length header,
// HEAD returns no response body
+ responseBodyConsumed();
return;
}
-}
\ No newline at end of file
+}
1.28 +5 -3 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/PostMethod.java
Index: PostMethod.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/PostMethod.java,v
retrieving revision 1.27
retrieving revision 1.28
diff -u -r1.27 -r1.28
--- PostMethod.java 12 Nov 2002 09:58:23 -0000 1.27
+++ PostMethod.java 9 Dec 2002 09:16:17 -0000 1.28
@@ -736,6 +736,8 @@
outstream = new ChunkedOutputStream(outstream);
}
if (this.requestContentLength >= 0) {
+ // don't need a watcher here - we're reading from something local,
+ // not server-side.
instream = new ContentLengthInputStream(instream, this.requestContentLength);
}
1.4 +39 -4 jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestGetMethodLocal.java
Index: TestGetMethodLocal.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestGetMethodLocal.java,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -r1.3 -r1.4
--- TestGetMethodLocal.java 4 Feb 2002 15:26:43 -0000 1.3
+++ TestGetMethodLocal.java 9 Dec 2002 09:16:17 -0000 1.4
@@ -89,6 +89,7 @@
// -------------------------------------------------------------- Constants
private static final String host = System.getProperty("httpclient.test.localHost","127.0.0.1");
+ private static final String webAppContext = System.getProperty("httpclient.test.webappContext");
private static final int port;
static {
String portString = System.getProperty("httpclient.test.localPort","8080");
@@ -223,6 +224,40 @@
}
assertEquals(404,method.getStatusCode());
+ }
+
+ /**
+ * The intent of this test is to allow for the incomplete parsing of a GET
+ * response, and to make it particularly tricky, the GET response issues
+ * a Connection: close".
+ *
+ * <p>This wants to insure that a recoverable exception is not unexpectedly
+ * triggered.</p>
+ */
+ public void testGetResponseNotReadAutoRecover() {
+
+ HttpClient client = new HttpClient();
+ client.startSession(host, port);
+
+ try {
+ // issue a GET with a connection: close, and don't parse the body.
+ String path = "/" + webAppContext + "/body";
+ GetMethod method1 = new GetMethod(path);
+ method1.addRequestHeader("Connection", "close");
+ client.executeMethod(method1);
+ assertEquals(0, method1.getRecoverableExceptionCount() );
+
+ // issue another GET.
+ GetMethod method2 = new GetMethod(path);
+ client.executeMethod(method2);
+ assertEquals(0, method2.getRecoverableExceptionCount() );
+
+ client.endSession();
+ }
+ catch (IOException ioe) {
+
+ fail("Problem executing method : " + ioe.toString() );
+ }
}
}
1.5 +13 -7 jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestHttpClientLocalHost.java
Index: TestHttpClientLocalHost.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestHttpClientLocalHost.java,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -r1.4 -r1.5
--- TestHttpClientLocalHost.java 3 Dec 2002 05:46:16 -0000 1.4
+++ TestHttpClientLocalHost.java 9 Dec 2002 09:16:17 -0000 1.5
@@ -84,6 +84,10 @@
// -------------------------------------------------------------- Constants
+ private static final String host = "127.0.0.1";
+ private static final int port = 8080;
+ private static final String webAppContext = System.getProperty("httpclient.test.webappContext");
+
// ------------------------------------------------------------ Constructor
@@ -100,12 +104,12 @@
}
private HttpClient client = null;
+ private String getPath = null;
private GetMethod getSlash = null;
- private GetMethod getSlash2 = null;
public void setUp() {
+ getPath = "/" + webAppContext + "/body";
client = new HttpClient();
- getSlash = new GetMethod("/");
}
public void tearDown() {
@@ -115,6 +119,7 @@
public void testExecuteMethod() throws Exception {
client.startSession(host, port);
+ GetMethod getSlash = new GetMethod(getPath);
assertEquals(200, client.executeMethod(getSlash));
String data = getSlash.getResponseBodyAsString();
assertTrue(null != data);
@@ -125,13 +130,14 @@
public void testExecuteMultipleMethods() throws Exception {
client.startSession(host, port);
+ getSlash = new GetMethod(getPath);
for(int i=0;i<10;i++) {
assertEquals(200, client.executeMethod(getSlash));
String data = getSlash.getResponseBodyAsString();
assertTrue(null != data);
assertTrue(data.length() > 0);
getSlash.recycle();
- getSlash.setPath("/");
+ getSlash.setPath(getPath);
}
client.endSession();
}
1.5 +8 -6 jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestMethodsLocalHost.java
Index: TestMethodsLocalHost.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestMethodsLocalHost.java,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -r1.4 -r1.5
--- TestMethodsLocalHost.java 1 Nov 2002 09:51:05 -0000 1.4
+++ TestMethodsLocalHost.java 9 Dec 2002 09:16:17 -0000 1.5
@@ -88,6 +88,7 @@
// -------------------------------------------------------------- Constants
+ private static final String webAppContext = System.getProperty("httpclient.test.webappContext");
private static final String host = "127.0.0.1";
private static final int port = 8080;
@@ -208,7 +209,8 @@
fail("Unable to execute method : " + t.toString());
}
- HeadMethod method = new HeadMethod("/");
+ String path = "/" + webAppContext + "/body";
+ HeadMethod method = new HeadMethod(path);
try {
client.executeMethod(method);
@@ -220,7 +222,7 @@
assertEquals(200, method.getStatusCode());
method.recycle();
- method.setPath("/index.html");
+ method.setPath(path);
try {
client.executeMethod(method);
1.12 +23 -6 jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestMethodsNoHost.java
Index: TestMethodsNoHost.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestMethodsNoHost.java,v
retrieving revision 1.11
retrieving revision 1.12
diff -u -r1.11 -r1.12
--- TestMethodsNoHost.java 31 Oct 2002 07:45:35 -0000 1.11
+++ TestMethodsNoHost.java 9 Dec 2002 09:16:17 -0000 1.12
@@ -71,6 +71,7 @@
import junit.framework.TestSuite;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.commons.httpclient.methods.HeadMethod;
/**
* @author Rodney Waldhoff
@@ -276,7 +277,8 @@
HttpMethodBase method = new GetMethod("/");
method.execute(new HttpState(), conn);
String responseBody = method.getResponseBodyAsString();
- conn.close();
+ // verify that the connection was closed.
+ conn.assertNotOpen();
assertEquals("1234567890123", responseBody);
}
@@ -300,7 +302,22 @@
while ((c = response.read()) != -1) {
assertEquals((int) 'A', c);
}
- assertTrue(!conn.isOpen());
+ conn.assertNotOpen();
+
+ // note - this test is here because the HEAD method handler overrides the
+ // standard behavior for reading a response body.
+ HeadMethod headMethod = new HeadMethod("/");
+
+ conn.addResponse(headers, "");
+
+ try {
+ headMethod.execute(new HttpState(), conn);
+ conn.assertNotOpen();
+
+ } catch (Throwable t) {
+ t.printStackTrace();
+ fail("Unable to execute method : " + t.toString());
+ }
}
public void testSetGetQueryString1() {
--
To unsubscribe, e-mail: <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>