mbecke 2004/10/05 20:39:59
Modified: httpclient/src/test/org/apache/commons/httpclient
TestWebapp.java TestEffectiveHttpVersion.java
TestNoHost.java
httpclient/src/java/org/apache/commons/httpclient/methods/multipart
Part.java
httpclient/xdocs preference-api.xml
httpclient/src/java/org/apache/commons/httpclient/params
HttpMethodParams.java
httpclient/src/examples MultipartFileUploadApp.java
httpclient/src/java/org/apache/commons/httpclient/methods
MultipartPostMethod.java
httpclient/src/test/org/apache/commons/httpclient/server
SimpleHttpServerConnection.java SimpleRequest.java
Added: httpclient/src/test/org/apache/commons/httpclient
EchoService.java TestMultipartPost.java
SimpleChunkedInputStream.java
httpclient/src/java/org/apache/commons/httpclient/methods/multipart
MultipartRequestEntity.java
Removed: httpclient/src/test/org/apache/commons/httpclient
TestWebappMultiPostMethod.java
Log:
Moves multipart requests to a new MultipartRequestEntity.
PR: 31378
Submitted by: Michael Becke
Reviewed by: Ortwin Gl?ck and Oleg Kalnichevski
Revision Changes Path
1.12 +4 -5
jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebapp.java
Index: TestWebapp.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebapp.java,v
retrieving revision 1.11
retrieving revision 1.12
diff -u -r1.11 -r1.12
--- TestWebapp.java 2 Oct 2004 21:22:03 -0000 1.11
+++ TestWebapp.java 6 Oct 2004 03:39:58 -0000 1.12
@@ -66,7 +66,6 @@
suite.addTest(TestWebappHeaders.suite());
suite.addTest(TestWebappBasicAuth.suite());
suite.addTest(TestWebappPostMethod.suite());
- suite.addTest(TestWebappMultiPostMethod.suite());
suite.addTest(TestWebappNoncompliant.suite());
suite.addTest(TestProxy.suite());
return suite;
1.3 +4 -23
jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestEffectiveHttpVersion.java
Index: TestEffectiveHttpVersion.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestEffectiveHttpVersion.java,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- TestEffectiveHttpVersion.java 14 Sep 2004 15:50:40 -0000 1.2
+++ TestEffectiveHttpVersion.java 6 Oct 2004 03:39:58 -0000 1.3
@@ -36,9 +36,6 @@
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
-import org.apache.commons.httpclient.server.HttpService;
-import org.apache.commons.httpclient.server.SimpleRequest;
-import org.apache.commons.httpclient.server.SimpleResponse;
/**
* HTTP protocol versioning tests.
@@ -64,22 +61,6 @@
public static Test suite() {
return new TestSuite(TestEffectiveHttpVersion.class);
- }
-
- private class EchoService implements HttpService {
-
- public EchoService() {
- super();
- }
-
- public boolean process(final SimpleRequest request, final SimpleResponse
response)
- throws IOException
- {
- HttpVersion httpversion = request.getRequestLine().getHttpVersion();
- response.setStatusLine(httpversion, HttpStatus.SC_OK);
- response.setBodyString(request.getBodyString());
- return true;
- }
}
public void testClientLevelHttpVersion() throws IOException {
1.41 +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.40
retrieving revision 1.41
diff -u -r1.40 -r1.41
--- TestNoHost.java 28 Sep 2004 21:08:48 -0000 1.40
+++ TestNoHost.java 6 Oct 2004 03:39:58 -0000 1.41
@@ -91,6 +91,7 @@
suite.addTestSuite(TestIdleConnectionTimeout.class);
suite.addTest(TestMethodAbort.suite());
suite.addTest(TestHttpParams.suite());
+ suite.addTest(TestMultipartPost.suite());
return suite;
}
1.1
jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/EchoService.java
Index: EchoService.java
===================================================================
/*
* $Header:
/home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/EchoService.java,v
1.1 2004/10/06 03:39:58 mbecke Exp $
* $Revision: 1.1 $
* $Date: 2004/10/06 03:39:58 $
*
* ====================================================================
*
* Copyright 2002-2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ====================================================================
*
* 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/>.
*
*/
package org.apache.commons.httpclient;
import java.io.IOException;
import org.apache.commons.httpclient.server.HttpService;
import org.apache.commons.httpclient.server.SimpleRequest;
import org.apache.commons.httpclient.server.SimpleResponse;
/**
* A service that echos the request body.
*/
class EchoService implements HttpService {
public EchoService() {
super();
}
public boolean process(final SimpleRequest request, final SimpleResponse
response)
throws IOException
{
HttpVersion httpversion = request.getRequestLine().getHttpVersion();
response.setStatusLine(httpversion, HttpStatus.SC_OK);
if (request.containsHeader("Content-Length")) {
response.addHeader(request.getFirstHeader("Content-Length"));
}
if (request.containsHeader("Content-Type")) {
response.addHeader(request.getFirstHeader("Content-Type"));
}
response.setBodyString(request.getBodyString());
return true;
}
}
1.1
jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestMultipartPost.java
Index: TestMultipartPost.java
===================================================================
/*
* $Header:
/home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestMultipartPost.java,v
1.1 2004/10/06 03:39:58 mbecke Exp $
* $Revision: 1.1 $
* $Date: 2004/10/06 03:39:58 $
*
* ====================================================================
*
* Copyright 2003-2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ====================================================================
*
* 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 junit.framework.Test;
import junit.framework.TestSuite;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.multipart.ByteArrayPartSource;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.methods.multipart.StringPart;
/**
* Webapp tests specific to the MultiPostMethod.
*
* @author <a href="[EMAIL PROTECTED]">Oleg Kalnichevski</a>
*/
public class TestMultipartPost extends HttpClientTestBase {
public TestMultipartPost(String testName) {
super(testName);
}
public static Test suite() {
TestSuite suite = new TestSuite(TestMultipartPost.class);
return suite;
}
public static void main(String args[]) {
String[] testCaseName = { TestMultipartPost.class.getName() };
junit.textui.TestRunner.main(testCaseName);
}
// ------------------------------------------------------------------ Tests
/**
* Test that the body consisting of a string part can be posted.
*/
public void testPostStringPart() throws Exception {
this.server.setHttpService(new EchoService());
PostMethod method = new PostMethod();
MultipartRequestEntity entity = new MultipartRequestEntity(
new Part[] { new StringPart("param", "Hello", "ISO-8859-1") },
method.getParams());
method.setRequestEntity(entity);
client.executeMethod(method);
assertEquals(200,method.getStatusCode());
String body = method.getResponseBodyAsString();
assertTrue(body.indexOf("Content-Disposition: form-data; name=\"param\"") >=
0);
assertTrue(body.indexOf("Content-Type: text/plain; charset=ISO-8859-1") >=
0);
assertTrue(body.indexOf("Content-Transfer-Encoding: 8bit") >= 0);
assertTrue(body.indexOf("Hello") >= 0);
}
/**
* Test that the body consisting of a file part can be posted.
*/
public void testPostFilePart() throws Exception {
this.server.setHttpService(new EchoService());
PostMethod method = new PostMethod();
byte[] content = "Hello".getBytes();
MultipartRequestEntity entity = new MultipartRequestEntity(
new Part[] {
new FilePart(
"param1",
new ByteArrayPartSource("filename.txt", content),
"text/plain",
"ISO-8859-1") },
method.getParams());
method.setRequestEntity(entity);
client.executeMethod(method);
assertEquals(200,method.getStatusCode());
String body = method.getResponseBodyAsString();
assertTrue(body.indexOf("Content-Disposition: form-data; name=\"param1\";
filename=\"filename.txt\"") >= 0);
assertTrue(body.indexOf("Content-Type: text/plain; charset=ISO-8859-1") >=
0);
assertTrue(body.indexOf("Content-Transfer-Encoding: binary") >= 0);
assertTrue(body.indexOf("Hello") >= 0);
}
}
1.1
jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/SimpleChunkedInputStream.java
Index: SimpleChunkedInputStream.java
===================================================================
/*
* $Header:
/home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/SimpleChunkedInputStream.java,v
1.1 2004/10/06 03:39:58 mbecke Exp $
* $Revision: 1.1 $
* $Date: 2004/10/06 03:39:58 $
*
* ====================================================================
*
* Copyright 2002-2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ====================================================================
*
* 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/>.
*
*/
package org.apache.commons.httpclient;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.httpclient.util.EncodingUtil;
import org.apache.commons.httpclient.util.ExceptionUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* <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>
*
* @author Ortwin Gl?ck
* @author Sean C. Sullivan
* @author Martin Elwin
* @author Eric Johnson
* @author <a href="mailto:[EMAIL PROTECTED]">Mike Bowler</a>
* @author Michael Becke
* @author <a href="mailto:[EMAIL PROTECTED]">Oleg Kalnichevski</a>
*
* @since 2.0
*
*/
public class SimpleChunkedInputStream extends InputStream {
/** The inputstream that we're wrapping */
private InputStream in;
/** The chunk size */
private int chunkSize;
/** The current position within the current chunk */
private int pos;
/** True if we'are at the beginning of stream */
private boolean bof = true;
/** True if we've reached the end of stream */
private boolean eof = false;
/** True if this stream is closed */
private boolean closed = false;
private String elementCharset = null;
/** Log object for this class. */
private static final Log LOG = LogFactory.getLog(ChunkedInputStream.class);
/**
*
*
* @param in must be non-null
* @param method must be non-null
*
* @throws IOException If an IO error occurs
*/
public SimpleChunkedInputStream(
final InputStream in, final String elementCharset) throws IOException {
if (in == null) {
throw new IllegalArgumentException("InputStream parameter may not be
null");
}
this.elementCharset = elementCharset;
this.in = in;
this.pos = 0;
}
/**
* <p> 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.</p>
*
* <p> Trailer headers are read automcatically at the end of the stream and
* can be obtained with the getResponseFooters() method.</p>
*
* @return -1 of the end of the stream has been reached or the next data
* byte
* @throws IOException If an IO problem occurs
*
* @see HttpMethod#getResponseFooters()
*/
public int read() throws IOException {
if (closed) {
throw new IOException("Attempted read from closed stream.");
}
if (eof) {
return -1;
}
if (pos >= chunkSize) {
nextChunk();
if (eof) {
return -1;
}
}
pos++;
return in.read();
}
/**
* Read some bytes from the stream.
* @param b The byte array that will hold the contents from the stream.
* @param off The offset into the byte array at which bytes will start to be
* placed.
* @param len the maximum number of bytes that can be returned.
* @return The number of bytes returned or -1 if the end of stream has been
* reached.
* @see java.io.InputStream#read(byte[], int, int)
* @throws IOException if an IO problem occurs.
*/
public int read (byte[] b, int off, int len) throws IOException {
if (closed) {
throw new IOException("Attempted read from closed stream.");
}
if (eof) {
return -1;
}
if (pos >= chunkSize) {
nextChunk();
if (eof) {
return -1;
}
}
len = Math.min(len, chunkSize - pos);
int count = in.read(b, off, len);
pos += count;
return count;
}
/**
* Read some bytes from the stream.
* @param b The byte array that will hold the contents from the stream.
* @return The number of bytes returned or -1 if the end of stream has been
* reached.
* @see java.io.InputStream#read(byte[])
* @throws IOException if an IO problem occurs.
*/
public int read (byte[] b) throws IOException {
return read(b, 0, b.length);
}
/**
* Read the CRLF terminator.
* @throws IOException If an IO error occurs.
*/
private void readCRLF() 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);
}
}
/**
* Read the next chunk.
* @throws IOException If an IO error occurs.
*/
private void nextChunk() throws IOException {
if (!bof) {
readCRLF();
}
chunkSize = getChunkSizeFromInputStream(in);
bof = false;
pos = 0;
if (chunkSize == 0) {
eof = true;
parseTrailerHeaders();
}
}
/**
* 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.
*
* @param in The new input stream.
* @param required <tt>true<tt/> if a valid chunk must be present,
* <tt>false<tt/> otherwise.
*
* @return the chunk size as integer
*
* @throws IOException when the chunk size could not be parsed
*/
private static int getChunkSizeFromInputStream(final InputStream in)
throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// States: 0=normal, 1=\r was scanned, 2=inside quoted string, -1=end
int state = 0;
while (state != -1) {
int b = in.read();
if (b == -1) {
throw new IOException("chunked stream ended unexpectedly");
}
switch (state) {
case 0:
switch (b) {
case '\r':
state = 1;
break;
case '\"':
state = 2;
/* fall through */
default:
baos.write(b);
}
break;
case 1:
if (b == '\n') {
state = -1;
} else {
// this was not CRLF
throw new IOException("Protocol violation: Unexpected"
+ " single newline character in chunk size");
}
break;
case 2:
switch (b) {
case '\\':
b = in.read();
baos.write(b);
break;
case '\"':
state = 0;
/* fall through */
default:
baos.write(b);
}
break;
default: throw new RuntimeException("assertion failed");
}
}
//parse data
String dataString = EncodingUtil.getAsciiString(baos.toByteArray());
int separator = dataString.indexOf(';');
dataString = (separator > 0)
? dataString.substring(0, separator).trim()
: dataString.trim();
int result;
try {
result = Integer.parseInt(dataString.trim(), 16);
} catch (NumberFormatException e) {
throw new IOException ("Bad chunk size: " + dataString);
}
return result;
}
/**
* Reads and stores the Trailer headers.
* @throws IOException If an IO problem occurs
*/
private void parseTrailerHeaders() throws IOException {
try {
// ignore trailer headers
while (true) {
String line = HttpParser.readLine(in, elementCharset);
if ((line == null) || (line.length() < 1)) {
break;
}
}
} catch(HttpException e) {
LOG.error("Error parsing trailer headers", e);
IOException ioe = new IOException(e.getMessage());
ExceptionUtil.initCause(ioe, e);
throw ioe;
}
}
/**
* 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.
* @throws IOException If an IO problem occurs.
*/
public void close() throws IOException {
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 [EMAIL PROTECTED] InputStream} to exhaust.
* @throws IOException If an IO problem occurs
*/
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.15 +110 -22
jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/multipart/Part.java
Index: Part.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/multipart/Part.java,v
retrieving revision 1.14
retrieving revision 1.15
diff -u -r1.14 -r1.15
--- Part.java 18 Apr 2004 23:51:37 -0000 1.14
+++ Part.java 6 Oct 2004 03:39:59 -0000 1.15
@@ -53,13 +53,23 @@
/** Log object for this class. */
private static final Log LOG = LogFactory.getLog(Part.class);
- //TODO: Make this configurable
-
- /** The boundary */
+ /**
+ * The boundary
+ * @deprecated use [EMAIL PROTECTED]
org.apache.commons.httpclient.params.HttpMethodParams#MULTIPART_BOUNDARY}
+ */
protected static final String BOUNDARY =
"----------------314159265358979323846";
- /** The boundary as a byte array */
+ /**
+ * The boundary as a byte array.
+ * @deprecated
+ */
protected static final byte[] BOUNDARY_BYTES =
EncodingUtil.getAsciiBytes(BOUNDARY);
+
+ /**
+ * The default boundary to be used if [EMAIL PROTECTED]
#setBoundaryBytes(byte[])) has not
+ * been called.
+ */
+ private static final byte[] DEFAULT_BOUNDARY_BYTES = BOUNDARY_BYTES;
/** Carriage return/linefeed */
protected static final String CRLF = "\r\n";
@@ -112,10 +122,16 @@
/**
* Return the boundary string.
* @return the boundary string
+ * @deprecated
*/
public static String getBoundary() {
return BOUNDARY;
}
+
+ /**
+ * The ASCII bytes to use as the multipart boundary.
+ */
+ private byte[] boundaryBytes;
/**
* Return the name of this part.
@@ -143,6 +159,41 @@
public abstract String getTransferEncoding();
/**
+ * Gets the part boundary to be used.
+ * @return
+ * @since 3.0
+ */
+ protected byte[] getPartBoundary() {
+ if (boundaryBytes == null) {
+ // custom boundary bytes have not been set, use the default.
+ return DEFAULT_BOUNDARY_BYTES;
+ } else {
+ return boundaryBytes;
+ }
+ }
+
+ /**
+ * Sets the part boundary. Only meant to be used by
+ * [EMAIL PROTECTED] Part#sendParts(OutputStream, Part[], byte[])}
+ * and [EMAIL PROTECTED] Part#getLengthOfParts(Part[], byte[])}
+ * @param boundaryBytes An array of ASCII bytes.
+ * @since 3.0
+ */
+ void setPartBoundary(byte[] boundaryBytes) {
+ this.boundaryBytes = boundaryBytes;
+ }
+
+ /**
+ * Tests if this part can be sent more than once.
+ * @return <code>true</code> if [EMAIL PROTECTED] #sendData(OutputStream)} can
be successfully called
+ * more than once.
+ * @since 3.0
+ */
+ public boolean isRepeatable() {
+ return true;
+ }
+
+ /**
* Write the start to the specified output stream
* @param out The output stream
* @throws IOException If an IO problem occurs.
@@ -150,7 +201,7 @@
protected void sendStart(OutputStream out) throws IOException {
LOG.trace("enter sendStart(OutputStream out)");
out.write(EXTRA_BYTES);
- out.write(BOUNDARY_BYTES);
+ out.write(getPartBoundary());
out.write(CRLF_BYTES);
}
@@ -291,49 +342,86 @@
}
/**
- * Write all parts and the last boundary to the specified output stream
+ * Write all parts and the last boundary to the specified output stream.
*
- * @param out The output stream
- * @param parts The array of parts to be sent
+ * @param out The stream to write to.
+ * @param parts The parts to write.
*
- * @throws IOException If an IO problem occurs.
+ * @throws IOException If an I/O error occurs while writing the parts.
*/
public static void sendParts(OutputStream out, final Part[] parts)
- throws IOException {
- LOG.trace("enter sendParts(OutputStream out, Parts[])");
+ throws IOException {
+ sendParts(out, parts, DEFAULT_BOUNDARY_BYTES);
+ }
+
+ /**
+ * Write all parts and the last boundary to the specified output stream.
+ *
+ * @param out The stream to write to.
+ * @param parts The parts to write.
+ * @param partBoundary The ASCII bytes to use as the part boundary.
+ *
+ * @throws IOException If an I/O error occurs while writing the parts.
+ *
+ * @since 3.0
+ */
+ public static void sendParts(OutputStream out, Part[] parts, byte[]
partBoundary)
+ throws IOException {
+
if (parts == null) {
throw new IllegalArgumentException("Parts may not be null");
}
+ if (partBoundary == null || partBoundary.length == 0) {
+ throw new IllegalArgumentException("partBoundary may not be empty");
+ }
for (int i = 0; i < parts.length; i++) {
+ // set the part boundary before the part is sent
+ parts[i].setPartBoundary(partBoundary);
parts[i].send(out);
}
out.write(EXTRA_BYTES);
- out.write(BOUNDARY_BYTES);
+ out.write(partBoundary);
out.write(EXTRA_BYTES);
out.write(CRLF_BYTES);
}
-
+
/**
* Return the total sum of all parts and that of the last boundary
*
- * @param parts The array of parts
- *
- * @return the total length
+ * @param parts The parts.
+ * @return The total length
*
- * @throws IOException If an IO problem occurs.
+ * @throws IOException If an I/O error occurs while writing the parts.
*/
- public static long getLengthOfParts(final Part[] parts)
+ public static long getLengthOfParts(Part[] parts)
throws IOException {
+ return getLengthOfParts(parts, DEFAULT_BOUNDARY_BYTES);
+ }
+
+ /**
+ * Gets the length of the multipart message including the given parts.
+ *
+ * @param parts The parts.
+ * @param partBoundary The ASCII bytes to use as the part boundary.
+ * @return The total length
+ *
+ * @throws IOException If an I/O error occurs while writing the parts.
+ *
+ * @since 3.0
+ */
+ public static long getLengthOfParts(Part[] parts, byte[] partBoundary) throws
IOException {
LOG.trace("getLengthOfParts(Parts[])");
if (parts == null) {
throw new IllegalArgumentException("Parts may not be null");
}
long total = 0;
for (int i = 0; i < parts.length; i++) {
+ // set the part boundary before we calculate the part's length
+ parts[i].setPartBoundary(partBoundary);
total += parts[i].length();
}
total += EXTRA_BYTES.length;
- total += BOUNDARY_BYTES.length;
+ total += partBoundary.length;
total += EXTRA_BYTES.length;
total += CRLF_BYTES.length;
return total;
1.1
jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/multipart/MultipartRequestEntity.java
Index: MultipartRequestEntity.java
===================================================================
/*
* $Header:
/home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/multipart/MultipartRequestEntity.java,v
1.1 2004/10/06 03:39:59 mbecke Exp $
* $Revision: 1.1 $
* $Date: 2004/10/06 03:39:59 $
*
* ====================================================================
*
* Copyright 2002-2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ====================================================================
*
* 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/>.
*
*/
package org.apache.commons.httpclient.methods.multipart;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.httpclient.util.EncodingUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Implements a request entity suitable for an HTTP multipart POST method.
* <p>
* The HTTP multipart POST method is defined in section 3.3 of
* <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC1867</a>:
* <blockquote>
* The media-type multipart/form-data follows the rules of all multipart
* MIME data streams as outlined in RFC 1521. The multipart/form-data contains
* a series of parts. Each part is expected to contain a content-disposition
* header where the value is "form-data" and a name attribute specifies
* the field name within the form, e.g., 'content-disposition: form-data;
* name="xxxxx"', where xxxxx is the field name corresponding to that field.
* Field names originally in non-ASCII character sets may be encoded using
* the method outlined in RFC 1522.
* </blockquote>
* </p>
* <p>This entity is designed to be used in conjunction with the
* [EMAIL PROTECTED] org.apache.commons.httpclient.methods.PostMethod post method}
to provide
* multipart posts. Example usage:</p>
* <code>
* File f = new File("/path/fileToUpload.txt");
* PostMethod filePost = new PostMethod("http://host/some_path");
* Part[] parts = {
* new StringPart("param_name", "value"),
* new FilePart(f.getName(), f)
* };
* filePost.setRequestEntity(
* new MultipartRequestEntity(parts, filePost.getParams())
* );
* HttpClient client = new HttpClient();
* int status = client.executeMethod(filePost);
* </code>
*
* @since 3.0
*/
public class MultipartRequestEntity implements RequestEntity {
private static final Log log = LogFactory.getLog(MultipartRequestEntity.class);
/** The Content-Type for multipart/form-data. */
private static final String MULTIPART_FORM_CONTENT_TYPE = "multipart/form-data";
/**
* The pool of ASCII chars to be used for generating a multipart boundary.
*/
private static byte[] MULTIPART_CHARS = EncodingUtil.getAsciiBytes(
"-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
/**
* Generates a random multipart boundary string.
* @return
*/
private static byte[] generateMultipartBoundary() {
Random rand = new Random();
byte[] bytes = new byte[rand.nextInt(11) + 30]; // a random size from 30 to
40
for (int i = 0; i < bytes.length; i++) {
bytes[i] = MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)];
}
return bytes;
}
private Part[] parts;
private byte[] multipartBoundary;
private HttpMethodParams params;
/**
* Creates a new multipart entity containing the given parts.
* @param parts The parts to include.
* @param params The params of the HttpMethod using this entity.
*/
public MultipartRequestEntity(Part[] parts, HttpMethodParams params) {
if (parts == null) {
throw new IllegalArgumentException("parts cannot be null");
}
if (params == null) {
throw new IllegalArgumentException("params cannot be null");
}
this.parts = parts;
this.params = params;
}
private byte[] getMultipartBoundary() {
if (multipartBoundary == null) {
String temp = (String)
params.getParameter(HttpMethodParams.MULTIPART_BOUNDARY);
if (temp != null) {
multipartBoundary = EncodingUtil.getAsciiBytes(temp);
} else {
multipartBoundary = generateMultipartBoundary();
}
}
return multipartBoundary;
}
/**
* Returns <code>true</code> if all parts are repeatable, <code>false</code>
otherwise.
* @see org.apache.commons.httpclient.methods.RequestEntity#isRepeatable()
*/
public boolean isRepeatable() {
for (int i = 0; i < parts.length; i++) {
if (!parts[i].isRepeatable()) {
return false;
}
}
return true;
}
/* (non-Javadoc)
* @see
org.apache.commons.httpclient.methods.RequestEntity#writeRequest(java.io.OutputStream)
*/
public void writeRequest(OutputStream out) throws IOException {
Part.sendParts(out, parts, getMultipartBoundary());
}
/* (non-Javadoc)
* @see org.apache.commons.httpclient.methods.RequestEntity#getContentLength()
*/
public long getContentLength() {
try {
return Part.getLengthOfParts(parts, getMultipartBoundary());
} catch (Exception e) {
log.error("An exception occurred while getting the length of the parts",
e);
return 0;
}
}
/* (non-Javadoc)
* @see org.apache.commons.httpclient.methods.RequestEntity#getContentType()
*/
public String getContentType() {
StringBuffer buffer = new StringBuffer(MULTIPART_FORM_CONTENT_TYPE);
buffer.append("; boundary=");
buffer.append(EncodingUtil.getAsciiString(getMultipartBoundary()));
return buffer.toString();
}
}
1.4 +14 -1 jakarta-commons/httpclient/xdocs/preference-api.xml
Index: preference-api.xml
===================================================================
RCS file: /home/cvs/jakarta-commons/httpclient/xdocs/preference-api.xml,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -r1.3 -r1.4
--- preference-api.xml 22 Sep 2004 17:23:33 -0000 1.3
+++ preference-api.xml 6 Oct 2004 03:39:59 -0000 1.4
@@ -393,6 +393,19 @@
<p><code><undefined></code></p>
</td>
</tr>
+ <tr>
+ <td><p>http.method.multipart.boundary</p></td>
+ <td><p>String</p></td>
+ <td><p>The multipart boundary string to use in conjunction with the
+ <a
href="apidocs/org/apache/commons/httpclient/params/MultipartRequestEntity.html">
+ MultipartRequestEntity</a>.
+ When not set a random value will be generated for each request.
+ </p>
+ </td>
+ <td>
+ <p><code><undefined></code></p>
+ </td>
+ </tr>
</table>
<p>
Whenever a parameter is left undefined (no value is explicitly set
anywhere in
1.16 +13 -4
jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/params/HttpMethodParams.java
Index: HttpMethodParams.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/params/HttpMethodParams.java,v
retrieving revision 1.15
retrieving revision 1.16
diff -u -r1.15 -r1.16
--- HttpMethodParams.java 17 Sep 2004 08:00:51 -0000 1.15
+++ HttpMethodParams.java 6 Oct 2004 03:39:59 -0000 1.16
@@ -258,6 +258,15 @@
public static final String BUFFER_WARN_TRIGGER_LIMIT =
"http.method.response.buffer.warnlimit";
/**
+ * Sets the value to use as the multipart boundary.
+ * <p>
+ * This parameter expects a value if type [EMAIL PROTECTED] String}.
+ * </p>
+ * @see org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity
+ */
+ public static final String MULTIPART_BOUNDARY =
"http.method.multipart.boundary";
+
+ /**
* Creates a new collection of parameters with the collection returned
* by [EMAIL PROTECTED] #getDefaultParams()} as a parent. The collection will
defer
* to its parent for a default value if a particular parameter is not
1.10 +12 -5
jakarta-commons/httpclient/src/examples/MultipartFileUploadApp.java
Index: MultipartFileUploadApp.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/httpclient/src/examples/MultipartFileUploadApp.java,v
retrieving revision 1.9
retrieving revision 1.10
diff -u -r1.9 -r1.10
--- MultipartFileUploadApp.java 22 Feb 2004 18:08:45 -0000 1.9
+++ MultipartFileUploadApp.java 6 Oct 2004 03:39:59 -0000 1.10
@@ -39,8 +39,8 @@
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
-import javax.swing.JComboBox;
import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
@@ -50,7 +50,10 @@
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
-import org.apache.commons.httpclient.methods.MultipartPostMethod;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.commons.httpclient.methods.multipart.FilePart;
+import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
+import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.params.HttpMethodParams;
/**
@@ -147,14 +150,18 @@
cmbURLModel.addElement(targetURL);
}
- MultipartPostMethod filePost =
- new MultipartPostMethod(targetURL);
+ PostMethod filePost = new PostMethod(targetURL);
filePost.getParams().setBooleanParameter(HttpMethodParams.USE_EXPECT_CONTINUE,
cbxExpectHeader.isSelected());
try {
appendMessage("Uploading " + targetFile.getName() + " to "
+ targetURL);
- filePost.addParameter(targetFile.getName(), targetFile);
+ Part[] parts = {
+ new FilePart(targetFile.getName(), targetFile)
+ };
+ filePost.setRequestEntity(
+ new MultipartRequestEntity(parts, filePost.getParams())
+ );
HttpClient client = new HttpClient();
client.getHttpConnectionManager().
getParams().setConnectionTimeout(5000);
1.27 +6 -3
jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/MultipartPostMethod.java
Index: MultipartPostMethod.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/MultipartPostMethod.java,v
retrieving revision 1.26
retrieving revision 1.27
diff -u -r1.26 -r1.27
--- MultipartPostMethod.java 13 Jun 2004 20:22:19 -0000 1.26
+++ MultipartPostMethod.java 6 Oct 2004 03:39:59 -0000 1.27
@@ -71,6 +71,9 @@
* @author <a href="mailto:[EMAIL PROTECTED]">Oleg Kalnichevski</a>
*
* @since 2.0
+ *
+ * @deprecated Use [EMAIL PROTECTED]
org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity}
+ * in conjunction with [EMAIL PROTECTED]
org.apache.commons.httpclient.methods.PostMethod} instead.
*/
public class MultipartPostMethod extends ExpectContinueMethod {
1.11 +56 -7
jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/server/SimpleHttpServerConnection.java
Index: SimpleHttpServerConnection.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/server/SimpleHttpServerConnection.java,v
retrieving revision 1.10
retrieving revision 1.11
diff -u -r1.10 -r1.11
--- SimpleHttpServerConnection.java 14 Sep 2004 15:50:41 -0000 1.10
+++ SimpleHttpServerConnection.java 6 Oct 2004 03:39:59 -0000 1.11
@@ -31,15 +31,20 @@
package org.apache.commons.httpclient.server;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
+import org.apache.commons.httpclient.ContentLengthInputStream;
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.HeaderGroup;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpParser;
import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.SimpleChunkedInputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -52,6 +57,7 @@
private static final Log LOG =
LogFactory.getLog(SimpleHttpServerConnection.class);
private static final String HTTP_ELEMENT_CHARSET = "US-ASCII";
+ public static final String DEFAULT_CONTENT_CHARSET = "ISO-8859-1";
private SimpleHttpServer server;
private Socket socket;
@@ -65,6 +71,7 @@
public SimpleHttpServerConnection(SimpleHttpServer server, Socket socket)
throws IOException {
this.server = server;
this.socket = socket;
+ this.socket.setSoTimeout(2000);
this.in = socket.getInputStream();
this.out = socket.getOutputStream();
}
@@ -152,10 +159,9 @@
SimpleRequest request = null;
try {
- request = new SimpleRequest(
- RequestLine.parseLine(line),
- HttpParser.parseHeaders(in, HTTP_ELEMENT_CHARSET),
- null);
+ RequestLine statusLine = RequestLine.parseLine(line);
+ Header[] headers = HttpParser.parseHeaders(in, HTTP_ELEMENT_CHARSET);
+ request = new SimpleRequest(statusLine, headers, getBody(statusLine,
headers, in));
} catch (HttpException e) {
connectionClose();
SimpleResponse response = ErrorResponse.getInstance().
@@ -169,6 +175,49 @@
}
server.processRequest(this, request);
out.flush();
+ }
+
+ /**
+ * Reads the request body into a byte[].
+ * @param statusLine The request status line.
+ * @param headers The request headers.
+ * @param is The request input stream.
+ * @return The content as an arra of bytes.
+ * @throws IOException
+ */
+ private byte[] getBody(RequestLine statusLine, Header[] headers, InputStream
is) throws IOException {
+
+ // only PUT and POST have content
+ if (
+ !statusLine.getMethod().equalsIgnoreCase("POST")
+ && !statusLine.getMethod().equalsIgnoreCase("PUT")
+ ) {
+ return new byte[0];
+ }
+
+ HeaderGroup headerGroup = new HeaderGroup();
+ headerGroup.setHeaders(headers);
+ Header contentLength = headerGroup.getFirstHeader("Content-Length");
+ InputStream contentStream = is;
+ int length = -1;
+ if (contentLength != null) {
+ length = Integer.parseInt(contentLength.getValue().trim());
+ contentStream = new ContentLengthInputStream(is, (long) length);
+ } else {
+ Header transferEncoding =
headerGroup.getFirstHeader("Transfer-Encoding");
+ if (transferEncoding != null &&
transferEncoding.getValue().indexOf("chunked") != -1) {
+ contentStream = new SimpleChunkedInputStream(is,
DEFAULT_CONTENT_CHARSET);
+ }
+ }
+
+ byte[] buff = new byte[4096];
+ int bytesRead = 0;
+ ByteArrayOutputStream os = new ByteArrayOutputStream(Math.max(length,
4096));
+ while ((bytesRead = contentStream.read(buff)) != -1) {
+ os.write(buff, 0, bytesRead);
+ }
+
+ return os.toByteArray();
}
public InputStream getInputStream() {
1.2 +18 -10
jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/server/SimpleRequest.java
Index: SimpleRequest.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/server/SimpleRequest.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- SimpleRequest.java 27 Feb 2004 19:06:19 -0000 1.1
+++ SimpleRequest.java 6 Oct 2004 03:39:59 -0000 1.2
@@ -31,6 +31,7 @@
package org.apache.commons.httpclient.server;
+import java.io.IOException;
import java.util.Iterator;
import org.apache.commons.httpclient.Header;
@@ -48,7 +49,7 @@
private RequestLine requestLine = null;
private String contentType = "text/plain";
private String charSet = null;
- private String bodyString = null;
+ private byte[] body = null;
private HeaderGroup headers = new HeaderGroup();
public SimpleRequest() {
@@ -58,13 +59,14 @@
public SimpleRequest(
final RequestLine requestLine,
final Header[] headers,
- final String bodyString)
+ final byte[] body)
{
super();
if (requestLine == null) {
throw new IllegalArgumentException("Request line may not be null");
}
this.requestLine = requestLine;
+ this.body = body;
if (headers != null) {
this.headers.setHeaders(headers);
Header content = this.headers.getFirstHeader("Content-Type");
@@ -79,9 +81,6 @@
}
}
}
- this.bodyString = bodyString;
-
-
}
public String getContentType() {
@@ -92,8 +91,17 @@
return this.charSet;
}
- public String getBodyString() {
- return this.bodyString;
+ public byte[] getBody() {
+ return body;
+ }
+
+ public String getBodyString() throws IOException {
+ return new String(
+ body,
+ charSet == null
+ ? SimpleHttpServerConnection.DEFAULT_CONTENT_CHARSET
+ : charSet
+ );
}
public RequestLine getRequestLine() {
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]