You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by js...@apache.org on 2002/09/02 16:52:48 UTC
cvs commit: jakarta-commons/httpclient/src/test/org/apache/commons/httpclient TestStreams.java TestNoHost.java TestWebappBase.java TestWebappParameters.java TestWebappRedirect.java
jsdever 2002/09/02 07:52:48
Modified: httpclient/src/java/org/apache/commons/httpclient
HttpConnection.java HttpMethod.java
HttpMethodBase.java HttpMultiClient.java
httpclient/src/java/org/apache/commons/httpclient/methods
GetMethod.java
httpclient/src/test/org/apache/commons/httpclient
TestNoHost.java TestWebappBase.java
TestWebappParameters.java TestWebappRedirect.java
Added: httpclient/src/java/org/apache/commons/httpclient
AutoCloseInputStream.java ChunkedInputStream.java
ContentLengthInputStream.java
WireLogInputStream.java
httpclient/src/test/org/apache/commons/httpclient
TestStreams.java
Log:
Allow for unbuffered input streams.
This monolithic patch contains:
- unbuffered input streams
- webapp tests fixed (including patches by Ryan Lubke)
- authentication retry bug
Contributed by: Ortwin Gluck
Revision Changes Path
1.19 +5 -5 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.18
retrieving revision 1.19
diff -u -r1.18 -r1.19
--- HttpConnection.java 20 Aug 2002 23:22:48 -0000 1.18
+++ HttpConnection.java 2 Sep 2002 14:52:47 -0000 1.19
@@ -457,7 +457,7 @@
throws IOException, IllegalStateException {
log.trace("enter HttpConnection.getRequestOutputStream(HttpMethod)");
assertOpen();
- return new ResponseInputStream(_input,method);
+ return _input;
}
/**
1.15 +21 -4 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethod.java
Index: HttpMethod.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethod.java,v
retrieving revision 1.14
retrieving revision 1.15
diff -u -r1.14 -r1.15
--- HttpMethod.java 28 Jul 2002 18:08:57 -0000 1.14
+++ HttpMethod.java 2 Sep 2002 14:52:47 -0000 1.15
@@ -239,6 +239,18 @@
public Header getResponseHeader(String headerName);
/**
+ * Return an array of my response footers
+ * @return <tt>null</tt> if no footers are available
+ */
+ public Header[] getResponseFooters();
+
+ /**
+ * Return the specified response footer.
+ * Note that footer-name matching is case insensitive.
+ */
+ public Header getResponseFooter(String footerName);
+
+ /**
* Return my response body, if any,
* as a byte array.
* Otherwise return <tt>null</tt>.
@@ -286,4 +298,9 @@
* once this method has been called.
*/
public void recycle();
+
+ /**
+ * Use this method internally to add footers.
+ */
+ public void addResponseFooter(Header footer);
}
1.52 +246 -89 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.51
retrieving revision 1.52
diff -u -r1.51 -r1.52
--- HttpMethodBase.java 1 Sep 2002 14:28:15 -0000 1.51
+++ HttpMethodBase.java 2 Sep 2002 14:52:47 -0000 1.52
@@ -68,6 +68,7 @@
import java.io.OutputStream;
import java.net.URL;
import java.util.Date;
+import java.util.Map;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -184,7 +185,15 @@
/** My response headers, if any. */
private HashMap responseHeaders = new HashMap();
- // ----------------------------------------------------- Instance Variables
+
+ /** My response footers, if any. */
+ private Map responseFooters = null;
+
+ /** Realms that we tried to authenticate to */
+ private Set realms = null;
+
+ /** Proxy Realms that we tried to authenticate to */
+ private Set proxyRealms = null;
/** My request path. */
private String path = null;
@@ -196,6 +205,9 @@
private String statusText = null;
/** The response body, assuming it has not be intercepted by a sub-class. */
+ private InputStream responseStream = null;
+
+ /** Buffer for the response */
private byte[] responseBody = null;
/** Whether or not the request body has been sent. */
@@ -303,6 +315,34 @@
}
/**
+ * 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.
+ *
+ * @param header the header to add to the request
+ */
+ public void addRequestHeader(Header header) {
+ log.trace("HttpMethodBase.addRequestHeader(Header)");
+
+ if (header == null) {
+ log.debug("null header value ignored");
+ } else {
+ addRequestHeader(header.getName(), header.getValue());
+ }
+ }
+
+ /**
+ * adds a response footer to the internal list
+ */
+ public void addResponseFooter(Header footer) {
+ if (responseFooters == null) responseFooters = new HashMap();
+ responseFooters.put(footer.getName().toLowerCase(), footer);
+ }
+
+ /**
* Get the path part of my request.
*
* @return the path to request or "/" if the path is blank.
@@ -403,36 +443,30 @@
}
/**
- * Return my response body, if any, as a byte array. Otherwise return
- * <tt>null</tt>.
+ * Provide access to the status code.
*
- * @return the response body
+ * @return the status code associated with the latest response.
*/
- public byte[] getResponseBody() {
- return responseBody;
+ public int getStatusCode() {
+ return statusCode;
}
/**
- * Return my response body, if any, as an {@link InputStream}. Otherwise
- * return <tt>null</tt>.
- *
- * @return the response body as an {@link InputStream}
- *
- * @throws IOException when there are errors obtaining the response
+ * Checks if response data is available.
+ * @return true if response data is available, false otherwise.
*/
- public InputStream getResponseBodyAsStream() throws IOException {
- return (null == responseBody)
- ? null : new ByteArrayInputStream(responseBody);
+ private boolean responseAvailable() {
+ return (responseBody != null) || (responseStream != null);
}
/**
- * Gets the response body as a string.
+ * Provide access to the response headers
*
- * @return my response body, if any, as a {@link String}. Otherwise return
- * <tt>null</tt>.
+ * @return an array of my response headers.
*/
- public String getResponseBodyAsString() {
- return (null == responseBody) ? null : new String(responseBody);
+ public Header[] getResponseHeaders() {
+ return (Header[]) (responseHeaders.values().toArray(
+ new Header[responseHeaders.size()]));
}
/**
@@ -452,22 +486,98 @@
}
/**
- * Provide access to the response headers
+ * Return my response body, if any, as a byte array.
+ * Otherwise return <tt>null</tt>.
+ */
+ public byte[] getResponseBody() {
+ if (responseBody == null) {
+ try {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ InputStream is = getResponseBodyAsStream();
+ byte[] buffer = new byte[10000];
+ int len;
+ while ((len = is.read(buffer)) > 0) {
+ os.write(buffer, 0, len);
+ }
+ responseBody = os.toByteArray();
+ setResponseStream(null);
+ log.debug("buffering response body");
+ } catch(IOException e) {
+ log.error("getResponseBody failed", e);
+ responseBody = null;
+ }
+ }
+ return responseBody;
+ }
+
+ /**
+ * Return my response body, if any, as an {@link InputStream}. Otherwise
+ * return <tt>null</tt>.
*
- * @return an array of my response headers.
+ * @return the response body as an {@link InputStream}
+ *
+ * @throws IOException when there are errors obtaining the response
*/
- public Header[] getResponseHeaders() {
- return (Header[]) (responseHeaders.values().toArray(
- new Header[responseHeaders.size()]));
+ public InputStream getResponseBodyAsStream() throws IOException {
+ if (responseStream != null) {
+ return responseStream;
+ }
+ if (responseBody != null) {
+ responseStream = new ByteArrayInputStream(responseBody);
+ log.debug("re-creating response stream from byte array");
+ return responseStream;
+ }
+ return null;
}
/**
- * Provide access to the status code.
+ * Gets the response body as a string.
*
- * @return the status code associated with the latest response.
+ * <b>Note:</b> The string conversion done on the data is done with the
+ * default character encoding. The use of this method may be non-portable.
+ *
+ * @return my response body, if any, as a {@link String}. Otherwise return
+ * <tt>null</tt>.
*/
- public int getStatusCode() {
- return statusCode;
+ public String getResponseBodyAsString() {
+ //FIXME: Take into account any character encoding information and
+ //use the correct (rather than default) encoding.
+ return responseAvailable() ? new String(getResponseBody()) : null;
+ }
+
+
+ /**
+ * Return an array of response footers.
+ * @return <tt>null</tt> if no footers are available
+ */
+ public Header[] getResponseFooters() {
+ if (responseFooters == null) {
+ return null;
+ }
+ return (Header[])(responseFooters.values().toArray(
+ new Header[responseFooters.size()]));
+ }
+
+ /**
+ * Get the response footer associated with the given name.
+ * Footer name matching is case insensitive.
+ * <tt>null</tt> will be returned if either <i>footerName</i> is
+ * <tt>null</tt> or there is no matching header for <i>footerName</i>
+ * or there are no footers available.
+ * @param footerName the footer name to match
+ * @return the matching footer
+ */
+ public Header getResponseFooter(String footerName) {
+ if (responseFooters == null) {
+ return null;
+ }
+ return (footerName == null) ? null :
+ (Header)(responseFooters.get(footerName.toLowerCase()));
+ }
+
+
+ protected void setResponseStream(InputStream responseStream) {
+ this.responseStream = responseStream;
}
/**
@@ -528,7 +638,13 @@
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
@@ -536,14 +652,32 @@
*
* @param header the header to add to the request
*/
- public void addRequestHeader(Header header) {
- log.trace("HttpMethodBase.addRequestHeader(Header)");
+ private void closeConnection(HttpConnection connection) {
+ if (shouldCloseConnection(connection)) {
+ connection.close();
+ }
+ }
- if (header == null) {
- log.debug("null header value ignored");
+ private boolean shouldCloseConnection(HttpConnection connection) {
+ if (!http11) {
+ if (getName().equals(ConnectMethod.NAME) &&
+ (statusCode == HttpStatus.SC_OK)) {
+ log.debug("Will leave connection open for tunneling");
+ return false;
+ } else {
+ log.debug("Should close connection since using HTTP/1.0, " +
+ "ConnectMethod and status is OK");
+ return true;
+ }
} else {
- addRequestHeader(header.getName(), header.getValue());
+ Header connectionHeader = getResponseHeader("connection");
+ if (null != connectionHeader
+ && "close".equalsIgnoreCase(connectionHeader.getValue())) {
+ log.debug("Should close connection since \"Connection: close\" header found.");
+ return true;
+ }
}
+ return false;
}
/**
@@ -594,6 +728,8 @@
}
Set visited = new HashSet();
+ realms = new HashSet();
+ proxyRealms = new HashSet();
int forwardCount = 0; //protect from an infinite loop
while (forwardCount++ < maxForwards) {
@@ -607,9 +743,6 @@
//if SC_CONTINUE write the request body
writeRemainingRequestBody(state, conn);
- //close connection if required
- closeConnection(conn);
-
switch (statusCode) {
case HttpStatus.SC_UNAUTHORIZED:
case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
@@ -642,20 +775,20 @@
+ "'");
}
- URL url; //the new url
+ URL url = null; //the new url
- if (isStrictMode()) {
- //rfc2616 demands the location value be a complete URI
- //Location = "Location" ":" absoluteURI
- try {
- url = new URL(location);
- } catch (Exception ex) {
- log.warn("Redirected location '"
- + locationHeader.getValue()
- + "' is not acceptable in strict mode");
- return statusCode;
+ //rfc2616 demands the location value be a complete URI
+ //Location = "Location" ":" absoluteURI
+ try {
+ url = new URL(location);
+ } catch (Exception ex) {
+ if (isStrictMode()) {
+ log.error("Redirected location '" + locationHeader.getValue() +
+ "' is not acceptable in strict mode");
+ return statusCode; //should we throw an exception?
}
- } else { //not strict mode
+ }
+ if (url == null) {
//try to construct the new url based on the current url
try {
URL currentUrl = new URL(conn.getProtocol(),
@@ -714,16 +847,23 @@
return statusCode;
} //end of switch
+/*
+ Revisiting may be desired. We do not know about the server's internal state.
+
//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));
+*/
+
+ //close connection if required
+ closeConnection(conn);
} //end of loop
log.error("Narrowly avoided an infinite loop in execute");
- return statusCode;
+ throw new HttpException("Maximum redirects ("+ maxForwards +") exceeded");
}
/**
@@ -1301,8 +1441,7 @@
OutputStream out)
throws IOException {
log.trace(
- "enter HttpMethodBase.readResponseBody(HttpState, HttpConnection)");
-
+ "enter HttpMethodBase.readResponseBody(HttpState, HttpConnection, OutputStream)");
responseBody = null;
int expectedLength = 0;
int foundLength = 0;
@@ -1366,6 +1505,55 @@
}
/**
+ * Read the response body from the given {@link HttpConnection}.
+ * <p>
+ * The current implementation returns an appropriate stream
+ * (according to the values of the
+ * <tt>Content-Length</tt> and <tt>Transfer-Encoding</tt>
+ * headers, if any).
+ * <p>
+ *
+ * @see #readResponse
+ * @see #processResponseBody
+ *
+ * @param state the client state
+ * @param conn the {@link HttpConnection} to read the response from
+ * @returns InputStream to read the response body from
+ */
+ private InputStream _readResponseBody(HttpState state, HttpConnection conn) throws IOException {
+ log.trace("enter HttpMethodBase.readResponseBody(HttpState, HttpConnection)");
+
+ responseBody = null; // is this desired?
+ Header lengthHeader = getResponseHeader("Content-Length");
+ Header transferEncodingHeader = getResponseHeader("Transfer-Encoding");
+ InputStream is = conn.getResponseInputStream(this);
+ if (wireLog.isDebugEnabled()) {
+ is = new WireLogInputStream(is);
+ }
+ InputStream result = null;
+ if (null != lengthHeader) {
+ try {
+ int expectedLength = Integer.parseInt(lengthHeader.getValue());
+ result = new ContentLengthInputStream(is, expectedLength);
+ } catch(NumberFormatException e) {
+ // ignored
+ }
+ } else if (null != transferEncodingHeader) {
+ if ("chunked".equalsIgnoreCase(transferEncodingHeader.getValue())) {
+ result = new ChunkedInputStream(is, this);
+ }
+ } else if(canResponseHaveBody(statusCode) && !getName().equals(ConnectMethod.NAME)){
+ result = is;
+ }
+ if (result == null) return null;
+
+ if (shouldCloseConnection(conn)) {
+ result = new AutoCloseInputStream(result, conn);
+ }
+ return result;
+ }
+
+ /**
* Read response headers from the given {@link HttpConnection}, populating
* the response headers map.
*
@@ -1711,6 +1899,7 @@
return true;
}
+
/**
* "It must be possible to combine the multiple header fields into one
* "field-name: field-value" pair, without changing the semantics of the
@@ -1772,35 +1961,6 @@
}
/**
- * 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
- */
- private void closeConnection(HttpConnection connection) {
- log.trace("enter closeConnection(HttpConnection)");
-
- if (!http11) {
- if (getName().equals(ConnectMethod.NAME)
- && (statusCode == HttpStatus.SC_OK)) {
- log.debug("Leaving connection open for tunneling");
- } else {
- log.debug("Closing connection since using HTTP/1.0, "
- + "ConnectMethod and status is OK");
- connection.close();
- }
- } else {
- Header connectionHeader = getResponseHeader("connection");
- if ((null != connectionHeader)
- && "close".equalsIgnoreCase(connectionHeader.getValue())) {
- log.debug("Closing connection since \"Connection: close\" "
- + "header found.");
- connection.close();
- }
- }
- }
-
- /**
* Generates a key used for idenifying visited URLs.
*
* @param conn DOCUMENT ME!
@@ -1827,9 +1987,6 @@
log.trace("enter HttpMethodBase.processAuthenticationResponse("
+ "HttpState, HttpConnection)");
- Set realms = new HashSet();
- Set proxyRealms = new HashSet();
-
// handle authentication required
Header wwwauth = null;
Set realmsUsed = null;
@@ -1995,4 +2152,4 @@
readResponse(state, connection);
}
}
-}
\ No newline at end of file
+}
1.15 +4 -4 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMultiClient.java
Index: HttpMultiClient.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMultiClient.java,v
retrieving revision 1.14
retrieving revision 1.15
diff -u -r1.14 -r1.15
--- HttpMultiClient.java 1 Sep 2002 01:27:37 -0000 1.14
+++ HttpMultiClient.java 2 Sep 2002 14:52:48 -0000 1.15
@@ -93,7 +93,7 @@
/** manager of http connections on a host:port basis */
private HttpConnectionManager mgr = new HttpConnectionManager();
/** specifies strict HTTP compliance */
- private boolean strictMode = true;
+ private boolean strictMode = false; //experimental features off
/** how long to wait for a connection to become available */
private int timeoutConnection = 0;
/** how long to wait for a request to complete */
1.1 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/AutoCloseInputStream.java
Index: AutoCloseInputStream.java
===================================================================
/*
* $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/AutoCloseInputStream.java,v 1.1 2002/09/02 14:52:47 jsdever Exp $
* $Revision: 1.1 $
* $Date: 2002/09/02 14:52:47 $
* ====================================================================
*
* 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;
import java.io.*;
/**
* Closes a HttpConnection as soon as the end of the stream is reached.
* @author Ortwin Gl�ck
*
* @since 2.0
*/
class AutoCloseInputStream extends FilterInputStream {
private HttpConnection conn;
public AutoCloseInputStream(InputStream in, HttpConnection conn) {
super(in);
this.conn = conn;
}
public int read() throws java.io.IOException {
int l = super.read();
if (l == -1) conn.close();
return l;
}
public int read(byte[] b, int off, int len) throws java.io.IOException {
int l = super.read(b, off, len);
if (l == -1) conn.close();
return l;
}
public int read(byte[] b) throws java.io.IOException {
int l = super.read(b);
if (l == -1) conn.close();
return l;
}
}
1.1 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ChunkedInputStream.java
Index: ChunkedInputStream.java
===================================================================
/*
* $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ChunkedInputStream.java,v 1.1 2002/09/02 14:52:47 jsdever Exp $
* $Revision: 1.1 $
* $Date: 2002/09/02 14:52:47 $
* ====================================================================
*
* 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;
import java.io.*;
import java.util.*;
/**
* <p>Transparently coalesces chunks of a HTTP stream that uses Transfer-Encoding
* chunked.</p>
* @author Ortwin Gl�ck
* @since 2.0
*/
public class ChunkedInputStream extends InputStream {
private InputStream in;
private int chunkSize, pos;
private boolean eof = false;
private static final String HTTP_ENC = "US-ASCII";
private HttpMethod method;
public ChunkedInputStream(InputStream in, HttpMethod method) throws IOException {
this.in = in;
this.method = method;
this.chunkSize = getChunkSize();
this.pos = 0;
}
/**
* Returns all the data in a chunked stream in coalesced form. A chunk is
* followed by a CRLF. The method returns -1 as soon as a chunksize of 0 is
* detected.
* Footers are read automcatically at the end of the stream and can be obtained
* with the getFooters() method.
*
* @return -1 of the end of the stream has been reached or the next data byte
* @throws IOException
*/
public int read() throws IOException {
if (eof) return -1;
if (pos >= chunkSize) {
nextChunk();
if (eof) return -1;
}
pos++;
return in.read();
}
public int read(byte[] b, int off, int len) throws java.io.IOException {
if (eof) return -1;
if (pos >= chunkSize) {
nextChunk();
if (eof) return -1;
}
len = Math.min(len, chunkSize);
int count = in.read(b, off, len);
pos += count;
return count;
}
public int read(byte[] b) throws java.io.IOException {
return read(b, 0, b.length);
}
private void nextChunk() throws IOException {
int cr = in.read();
int lf = in.read();
if ((cr != '\r') &&
(lf != '\n')) throw new IOException("CRLF expected at end of chunk: "+cr+"/"+lf);
chunkSize = getChunkSize();
pos = 0;
if (chunkSize == 0) {
eof = true;
parseFooters();
}
}
/**
* Expects the stream to start with a chunksize in hex with optional comments
* after a semicolon. The line must end with a CRLF:
* "a3; some comment\r\n"
* Positions the stream at the start of the next line.
*
* @return the chunk size as integer
* @throws IOException when the chunk size could not be parsed
*/
private int getChunkSize() throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b = in.read();
int state = 0;
while (state != 2) {
if (b == -1) throw new IOException("chunked stream ended unexpectedly");
switch (state) {
case 0:
if (b == '\r') state = 1;
break;
case 1:
if (b == '\n') state = 2;
else state = 0;
break;
default: throw new RuntimeException("assertion failed");
}
if (state == 0) baos.write(b);
if (state != 2) b = in.read();
}
//parse data
String dataString = new String(baos.toByteArray(), HTTP_ENC);
int separator = dataString.indexOf(';');
if (separator > 0) dataString = dataString.substring(0, separator).trim();
int result;
try {
result = Integer.parseInt(dataString, 16);
} catch(NumberFormatException e) {
throw new IOException("Bad chunk size: "+dataString);
}
return result;
}
/**
* Stores the footers into map of Headers
*/
private void parseFooters() throws IOException {
String line = readLine();
while ((line != null) && (!line.equals(""))) {
int colonPos = line.indexOf(':');
if (colonPos != -1) {
String key = line.substring(0, colonPos).trim();
String val = line.substring(colonPos+1).trim();
Header footer = new Header(key, val);
method.addResponseFooter(footer);
}
line = readLine();
}
}
private String readLine() throws IOException {
StringBuffer buf = new StringBuffer();
for(;;) {
int ch = in.read();
if(ch < 0) {
if(buf.length() == 0) {
return null;
} else {
break;
}
} else if (ch == '\r') {
continue;
} else if (ch == '\n') {
break;
}
buf.append((char)ch);
}
return (buf.toString());
}
public void close() throws IOException {
in.close();
}
}
1.1 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ContentLengthInputStream.java
Index: ContentLengthInputStream.java
===================================================================
/*
* $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ContentLengthInputStream.java,v 1.1 2002/09/02 14:52:47 jsdever Exp $
* $Revision: 1.1 $
* $Date: 2002/09/02 14:52:47 $
* ====================================================================
*
* 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;
import java.io.*;
/**
* Cuts the wrapped InputStream off after a specified number of bytes.
*
* @author Ortwin Gl�ck
* @since 2.0
*/
public class ContentLengthInputStream extends FilterInputStream {
private int contentLength;
private int pos = 0;
/**
* Creates a new length limited stream
*
* @param in The stream to wrap
* @param contentLength The maximum number of bytes that can be read from
* the stream. Subsequent read operations will return -1.
*/
public ContentLengthInputStream(InputStream in, int contentLength) {
super(in);
this.contentLength = contentLength;
}
public int read() throws java.io.IOException {
if (pos > contentLength) return -1;
pos++;
return super.read();
}
public int read(byte[] b, int off, int len) throws java.io.IOException {
if (pos > contentLength) return -1;
if (pos + len > contentLength) {
len = contentLength - pos;
}
int count = super.read(b, off, len);
pos += count;
return count;
}
public int read(byte[] b) throws java.io.IOException {
return read(b, 0, b.length);
}
}
1.1 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/WireLogInputStream.java
Index: WireLogInputStream.java
===================================================================
package org.apache.commons.httpclient;
import java.io.*;
import org.apache.commons.logging.*;
/**
* Logs all data read to the wire log.
*
* @author Ortwin Gl�ck
* @since 2.0
*/
public class WireLogInputStream extends FilterInputStream {
/** Log for any wire messages. */
private static final Log wireLog = LogFactory.getLog("httpclient.wire");
public WireLogInputStream(InputStream in) {
super(in);
}
public int read(byte[] b, int off, int len) throws java.io.IOException {
int l = super.read(b, off, len);
wireLog.debug("<< "+ new String(b, off, len));
return l;
}
public int read() throws java.io.IOException {
int l = super.read();
if (l > 0) wireLog.debug("<< "+ (char) l);
return l;
}
public int read(byte[] b) throws java.io.IOException {
int l = super.read(b);
wireLog.debug("<< "+ new String(b));
return l;
}
}
1.17 +52 -110 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.16
retrieving revision 1.17
diff -u -r1.16 -r1.17
--- GetMethod.java 1 Sep 2002 01:27:37 -0000 1.16
+++ GetMethod.java 2 Sep 2002 14:52:48 -0000 1.17
@@ -74,6 +74,7 @@
import org.apache.commons.httpclient.HttpConnection;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.HttpState;
+import org.apache.commons.httpclient.HttpException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -96,7 +97,8 @@
/** Temporary directory. */
private static final String TEMP_DIR = "temp/";
- //~ Instance variables �����������������������������������������������������
+
+ // ----------------------------------------------------- Instance Variables
/** File which contains the buffered data. */
private File fileData;
@@ -107,11 +109,6 @@
/** Temporary file to use. */
private String tempFile = null;
- /** If we're not using the HD, we're using a memory byte buffer. */
- private byte[] memoryData;
-
- // ----------------------------------------------------- Instance Variables
-
/** By default, the get method will buffer read data to the memory. */
private boolean useDisk = false;
@@ -241,37 +238,7 @@
log.trace("enter GetMethod.getResponseBody()");
checkUsed();
-
- if (useDisk) {
- try {
- InputStream is = new FileInputStream(fileData);
- byte[] buffer = new byte[4096];
- ByteArrayOutputStream os = new ByteArrayOutputStream();
- int nb = 0;
-
- while (true) {
- nb = is.read(buffer);
-
- if (nb == -1) {
- break;
- }
-
- os.write(buffer, 0, nb);
- }
-
- is.close();
-
- return os.toByteArray();
- } catch (IOException e) {
- log.error("Exception in GetMethod.getResponseBody() while "
- + "retrieving data from file \"" + fileData + "\".",
- e);
-
- return null;
- }
- } else {
- return memoryData;
- }
+ return super.getResponseBody();
}
/**
@@ -288,37 +255,9 @@
log.trace("enter GetMethod.getResponseBodyAsStream()");
checkUsed();
-
- if (useDisk) {
- return new FileInputStream(fileData);
- } else {
- if (null == memoryData) {
- return null;
- } else {
- return new ByteArrayInputStream(memoryData);
- }
- }
+ return super.getResponseBodyAsStream();
}
- /**
- * Return the response body as a String
- *
- * @return Return my response body, if any, as a {@link String}. Otherwise
- * return <tt>null</tt>.
- *
- * @since 2.0
- */
- public String getResponseBodyAsString() {
- log.trace("enter GetMethod.getResponseBodyAsString()");
-
- byte[] data = getResponseBody();
-
- if (null == data) {
- return null;
- } else {
- return new String(data);
- }
- }
/**
* Temporary directory setter.
@@ -402,7 +341,6 @@
log.trace("enter GetMethod.recycle()");
super.recycle();
- this.memoryData = null;
this.fileData = null;
setFollowRedirects(true);
}
@@ -421,51 +359,55 @@
* @since 2.0
*/
protected void readResponseBody(HttpState state, HttpConnection conn)
- throws IOException {
- log.trace("enter GetMethod.readResponseBody(HttpState, "
- + "HttpConnection)");
+ throws IOException, HttpException {
+ log.trace("enter GetMethod.readResponseBody(HttpState, HttpConnection)");
- OutputStream out = null;
+ super.readResponseBody(state, conn);
+
+ OutputStream out = null;
if (useDisk) {
- if (fileData == null) {
- // Create a temporary file on the HD
- File dir = new File(tempDir);
- dir.deleteOnExit();
- dir.mkdirs();
-
- String tempFileName = null;
-
- if (tempFile == null) {
- String encodedPath = URLEncoder.encode(getPath());
- int length = encodedPath.length();
-
- if (length > 200) {
- encodedPath = encodedPath.substring(length - 190,
- length);
- }
-
- tempFileName = System.currentTimeMillis() + "-"
- + encodedPath + ".tmp";
- } else {
- tempFileName = tempFile;
- }
-
- fileData = new File(tempDir, tempFileName);
- fileData.deleteOnExit();
+ out = new FileOutputStream(createTempFile());
+ InputStream in = getResponseBodyAsStream();
+ byte[] buffer = new byte[10000];
+ int len ;
+ while ((len = in.read(buffer)) > 0) {
+ out.write(buffer, 0, len);
}
-
- out = new FileOutputStream(fileData);
- } else {
- out = new ByteArrayOutputStream();
+ out.close();
+ setResponseStream(new FileInputStream(createTempFile()));
}
+ }
- readResponseBody(state, conn, out);
-
- if (!useDisk) {
- memoryData = ((ByteArrayOutputStream) out).toByteArray();
+ /**
+ * Returns the file buffer, creating it if necessary. The created file is
+ * deleted when the VM exits.
+ * @return Temporary file to hold the data buffer.
+ */
+ private File createTempFile() {
+ if (fileData == null) {
+ // Create a temporary file on the HD
+ File dir = new File(tempDir);
+ dir.deleteOnExit();
+ dir.mkdirs();
+ String tempFileName = null;
+ if (tempFile == null) {
+ String encodedPath = URLEncoder.encode(getPath());
+ int length = encodedPath.length();
+ if (length > 200) {
+ encodedPath =
+ encodedPath.substring(length - 190, length);
+ }
+ tempFileName = System.currentTimeMillis() + "-"
+ + encodedPath + ".tmp";
+ } else {
+ tempFileName = tempFile;
+ }
+ fileData = new File(tempDir, tempFileName);
+
+ fileData = new File(tempDir, tempFileName);
+ fileData.deleteOnExit();
}
-
- out.close();
+ return fileData;
}
-}
\ No newline at end of file
+}
1.11 +5 -4 jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestNoHost.java
Index: TestNoHost.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestNoHost.java,v
retrieving revision 1.10
retrieving revision 1.11
diff -u -r1.10 -r1.11
--- TestNoHost.java 2 Aug 2002 11:38:12 -0000 1.10
+++ TestNoHost.java 2 Sep 2002 14:52:48 -0000 1.11
@@ -97,6 +97,7 @@
suite.addTest(TestHttpState.suite());
suite.addTest(TestResponseHeaders.suite());
suite.addTest(TestRequestHeaders.suite());
+ suite.addTest(TestStreams.suite());
return suite;
}
1.3 +8 -7 jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebappBase.java
Index: TestWebappBase.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebappBase.java,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- TestWebappBase.java 4 Oct 2001 17:49:13 -0000 1.2
+++ TestWebappBase.java 2 Sep 2002 14:52:48 -0000 1.3
@@ -72,12 +72,13 @@
* <p>
* The webapp should be deployed in the context "httpclienttest"
* on a servlet engine running on port 8080 on the localhost
- * (IP 127.0.0.1).
+ * (name localhost).
* <p>
* You can change the assumed port by setting the
* "httpclient.test.localPort" property.
* You can change the assumed host by setting the
- * "httpclient.test.localHost" property.
+ * "httpclient.test.localHost" property. A DNS name is preferred over
+ * an IP address here.
* You can change the assumed context by setting the
* "httpclient.test.webappContext" property.
*
@@ -93,7 +94,7 @@
// -------------------------------------------------------------- Constants
protected static final String context = System.getProperty("httpclient.test.webappContext","httpclienttest");
- protected static final String host = System.getProperty("httpclient.test.localHost","127.0.0.1");
+ protected static final String host = System.getProperty("httpclient.test.localHost","localhost");
protected static final int port;
static {
String portString = System.getProperty("httpclient.test.localPort","8080");
1.4 +5 -5 jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebappParameters.java
Index: TestWebappParameters.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebappParameters.java,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -r1.3 -r1.4
--- TestWebappParameters.java 4 Feb 2002 15:26:43 -0000 1.3
+++ TestWebappParameters.java 2 Sep 2002 14:52:48 -0000 1.4
@@ -210,7 +210,7 @@
}
assertTrue(method.getResponseBodyAsString().indexOf("<title>Param Servlet: GET</title>") >= 0);
assertEquals(200,method.getStatusCode());
- assertTrue(method.getResponseBodyAsString().indexOf("<p>QueryString=\"param-without-value\"</p>") >= 0);
+ assertTrue(method.getResponseBodyAsString().indexOf("<p>QueryString=\"param-without-value=\"</p>") >= 0);
}
/**
1.6 +4 -5 jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebappRedirect.java
Index: TestWebappRedirect.java
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebappRedirect.java,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -r1.5 -r1.6
--- TestWebappRedirect.java 23 Jul 2002 14:39:14 -0000 1.5
+++ TestWebappRedirect.java 2 Sep 2002 14:52:48 -0000 1.6
@@ -213,7 +213,6 @@
HttpClient client = new HttpClient();
client.startSession(host, port);
PutMethod method = new PutMethod("/" + context + "/redirect");
- method.setFollowRedirects(true);
method.setQueryString("to=" + URLEncoder.encode("http://" + host + ":" + port + "/" + context + "/body?foo=bar&bar=foo"));
method.setRequestBody("This is data to be sent in the body of an HTTP PUT.");
try {
1.1 jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestStreams.java
Index: TestStreams.java
===================================================================
package org.apache.commons.httpclient;
import java.io.*;
import java.util.Map;
import junit.framework.*;
public class TestStreams extends TestCase {
public TestStreams(String testName) {
super(testName);
}
public void testChunkedInputStream() throws IOException {
String correct = "10; this is comments\r\n1234567890123456\r\n5\r\n12345\r\n0\r\nFooter1: abcde\r\nFooter2: fghij\r\n";
HttpMethod method = new SimpleHttpMethod();
InputStream in = new ChunkedInputStream(new ByteArrayInputStream(correct.getBytes()), method);
byte[] buffer = new byte[300];
ByteArrayOutputStream out = new ByteArrayOutputStream();
int len;
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
String result = new String(out.toByteArray());
assertEquals(result, "123456789012345612345");
Header footer = method.getResponseFooter("footer1");
assertEquals(footer.getValue(), "abcde");
footer = method.getResponseFooter("footer2");
assertEquals(footer.getValue(), "fghij");
}
public void testContentLengthInputStream() throws IOException {
String correct = "1234567890123456";
InputStream in = new ContentLengthInputStream(new ByteArrayInputStream(correct.getBytes()), 10);
byte[] buffer = new byte[50];
int len = in.read(buffer);
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(buffer, 0, len);
String result = new String(out.toByteArray());
assertEquals(result, "1234567890");
}
// ------------------------------------------------------- TestCase Methods
public static Test suite() {
return new TestSuite(TestHeader.class);
}
// ------------------------------------------------------------------- Main
public static void main(String args[]) {
String[] testCaseName = { TestStreams.class.getName() };
junit.textui.TestRunner.main(testCaseName);
}
}
--
To unsubscribe, e-mail: <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>