Skip to content

Commit cf6509a

Browse files
committed
Merge pull request AsyncHttpClient#410 from carryel/ahc-1.7.x
Fixed for AsyncHttpClient#409 "MultipartBody generates wrong body bytes" and added the ...
2 parents 8b512ef + 47afbcf commit cf6509a

File tree

3 files changed

+178
-27
lines changed

3 files changed

+178
-27
lines changed

src/main/java/com/ning/http/client/providers/grizzly/GrizzlyAsyncHttpProvider.java

Lines changed: 58 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012 Sonatype, Inc. All rights reserved.
2+
* Copyright (c) 2012-2013 Sonatype, Inc. All rights reserved.
33
*
44
* This program is licensed to you under the Apache License Version 2.0,
55
* and you may not use this file except in compliance with the Apache License Version 2.0.
@@ -16,6 +16,8 @@
1616
import static com.ning.http.util.MiscUtil.isNonEmpty;
1717

1818
import com.ning.http.client.AsyncHttpClient;
19+
import com.ning.http.client.Part;
20+
import com.ning.http.multipart.MultipartBody;
1921
import com.ning.org.jboss.netty.handler.codec.http.CookieDecoder;
2022
import com.ning.http.client.AsyncHandler;
2123
import com.ning.http.client.AsyncHttpClientConfig;
@@ -2072,31 +2074,64 @@ public boolean handlesBodyType(final Request request) {
20722074
return isNonEmpty(request.getParts());
20732075
}
20742076

2075-
@SuppressWarnings({"unchecked"})
20762077
public boolean doHandle(final FilterChainContext ctx,
2077-
final Request request,
2078-
final HttpRequestPacket requestPacket)
2079-
throws IOException {
2080-
2081-
MultipartRequestEntity mre =
2082-
AsyncHttpProviderUtils.createMultipartRequestEntity(
2083-
request.getParts(),
2084-
request.getHeaders());
2085-
requestPacket.setContentLengthLong(mre.getContentLength());
2086-
requestPacket.setContentType(mre.getContentType());
2087-
final MemoryManager mm = ctx.getMemoryManager();
2088-
Buffer b = mm.allocate(512);
2089-
BufferOutputStream o = new BufferOutputStream(mm, b, true);
2090-
mre.writeRequest(o);
2091-
b = o.getBuffer();
2092-
b.trim();
2093-
if (b.hasRemaining()) {
2094-
final HttpContent content = requestPacket.httpContentBuilder().content(b).build();
2095-
content.setLast(true);
2096-
ctx.write(content, ((!requestPacket.isCommitted()) ? ctx.getTransportContext().getCompletionHandler() : null));
2078+
final Request request,
2079+
final HttpRequestPacket requestPacket)
2080+
throws IOException {
2081+
2082+
final List<Part> parts = request.getParts();
2083+
final MultipartRequestEntity mre = AsyncHttpProviderUtils.createMultipartRequestEntity(parts, request.getHeaders());
2084+
final long contentLength = mre.getContentLength();
2085+
final String contentType = mre.getContentType();
2086+
requestPacket.setContentLengthLong(contentLength);
2087+
requestPacket.setContentType(contentType);
2088+
if (LOGGER.isDebugEnabled()) {
2089+
LOGGER.debug("REQUEST(modified): contentLength={}, contentType={}", new Object[]{requestPacket.getContentLength(), requestPacket.getContentType()});
20972090
}
20982091

2099-
return true;
2092+
final FeedableBodyGenerator generator = new FeedableBodyGenerator() {
2093+
@Override
2094+
public Body createBody() throws IOException {
2095+
return new MultipartBody(parts, contentType, String.valueOf(contentLength));
2096+
}
2097+
};
2098+
generator.setFeeder(new FeedableBodyGenerator.BaseFeeder(generator) {
2099+
@Override
2100+
public void flush() throws IOException {
2101+
final Body bodyLocal = feedableBodyGenerator.createBody();
2102+
try {
2103+
final MemoryManager mm = ctx.getMemoryManager();
2104+
boolean last = false;
2105+
while (!last) {
2106+
Buffer buffer = mm.allocate(BodyHandler.MAX_CHUNK_SIZE);
2107+
buffer.allowBufferDispose(true);
2108+
final long readBytes = bodyLocal.read(buffer.toByteBuffer());
2109+
if (readBytes > 0) {
2110+
buffer.position((int) readBytes);
2111+
buffer.trim();
2112+
} else {
2113+
buffer.dispose();
2114+
if (readBytes < 0) {
2115+
last = true;
2116+
buffer = Buffers.EMPTY_BUFFER;
2117+
} else {
2118+
throw new IllegalStateException("MultipartBody unexpectedly returned 0 bytes available");
2119+
}
2120+
}
2121+
feed(buffer, last);
2122+
}
2123+
} finally {
2124+
if (bodyLocal != null) {
2125+
try {
2126+
bodyLocal.close();
2127+
} catch (IOException ignore) {
2128+
}
2129+
}
2130+
}
2131+
}
2132+
});
2133+
generator.initializeAsynchronousTransfer(ctx, requestPacket);
2134+
return false;
21002135
}
21012136

21022137
} // END PartsBodyHandler

src/main/java/com/ning/http/multipart/MultipartBody.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,10 @@ public long read(ByteBuffer buffer) throws IOException {
7878
try {
7979
int overallLength = 0;
8080

81-
int maxLength = buffer.capacity();
81+
final int maxLength = buffer.remaining();
82+
if (maxLength <= 0) {
83+
return maxLength;
84+
}
8285

8386
if (startPart == parts.size() && endWritten) {
8487
return -1;
@@ -132,6 +135,7 @@ public long read(ByteBuffer buffer) throws IOException {
132135
initializeFileEnd(currentFilePart);
133136
} else if (fileLocation == FileLocation.END) {
134137
startPart++;
138+
fileLocation = FileLocation.NONE;
135139
if (startPart == parts.size() && currentStream.available() == 0) {
136140
doneWritingParts = true;
137141
}
@@ -146,6 +150,7 @@ public long read(ByteBuffer buffer) throws IOException {
146150
initializeFileEnd(currentFilePart);
147151
} else if (fileLocation == FileLocation.END) {
148152
startPart++;
153+
fileLocation = FileLocation.NONE;
149154
if (startPart == parts.size() && currentStream.available() == 0) {
150155
doneWritingParts = true;
151156
}
@@ -165,6 +170,7 @@ public long read(ByteBuffer buffer) throws IOException {
165170
initializeFileEnd(currentFilePart);
166171
} else if (fileLocation == FileLocation.END) {
167172
startPart++;
173+
fileLocation = FileLocation.NONE;
168174
if (startPart == parts.size() && currentStream.available() == 0) {
169175
doneWritingParts = true;
170176
}
@@ -386,14 +392,15 @@ private StringPart generateClientStringpart(com.ning.http.client.Part part) {
386392
private long handleByteArrayPart(WritableByteChannel target,
387393
FilePart filePart, byte[] data) throws IOException {
388394

389-
ByteArrayOutputStream output = generateByteArrayBody(filePart);
395+
final ByteArrayOutputStream output = new ByteArrayOutputStream();
396+
Part.sendPart(output, filePart, boundary);
390397
return writeToTarget(target, output);
391398
}
392399

393400
private ByteArrayOutputStream generateByteArrayBody(FilePart filePart)
394401
throws IOException {
395-
ByteArrayOutputStream output = new ByteArrayOutputStream();
396-
Part.sendPart(output, filePart, boundary);
402+
final ByteArrayOutputStream output = new ByteArrayOutputStream();
403+
filePart.sendData(output);
397404
return output;
398405
}
399406

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright (c) 2013 Sonatype, Inc. All rights reserved.
3+
*
4+
* This program is licensed to you under the Apache License Version 2.0,
5+
* and you may not use this file except in compliance with the Apache License Version 2.0.
6+
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
7+
*
8+
* Unless required by applicable law or agreed to in writing,
9+
* software distributed under the Apache License Version 2.0 is distributed on an
10+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
12+
*/
13+
package com.ning.http.multipart;
14+
15+
import com.ning.http.client.*;
16+
import com.ning.http.client.Part;
17+
import com.ning.http.util.AsyncHttpProviderUtils;
18+
import org.testng.Assert;
19+
import org.testng.annotations.Test;
20+
21+
import java.io.File;
22+
import java.io.FileNotFoundException;
23+
import java.io.IOException;
24+
import java.io.UnsupportedEncodingException;
25+
import java.net.URISyntaxException;
26+
import java.net.URL;
27+
import java.nio.ByteBuffer;
28+
import java.util.ArrayList;
29+
import java.util.List;
30+
31+
public class MultipartBodyTest {
32+
33+
@Test(groups = "fast")
34+
public void testBasics() {
35+
final List<Part> parts = new ArrayList<Part>();
36+
37+
// add a file
38+
final File testFile = getTestfile();
39+
try {
40+
parts.add(new FilePart("filePart", testFile));
41+
} catch (FileNotFoundException fne) {
42+
Assert.fail("file not found: " + testFile);
43+
}
44+
45+
// add a byte array
46+
try {
47+
parts.add(new ByteArrayPart("baPart", "fileName", "testMultiPart".getBytes("utf-8"), "application/test", "utf-8"));
48+
} catch (UnsupportedEncodingException ignore) {
49+
}
50+
51+
// add a string
52+
parts.add(new StringPart("stringPart", "testString", "utf-8"));
53+
54+
compareContentLength(parts);
55+
}
56+
57+
private static File getTestfile() {
58+
final ClassLoader cl = MultipartBodyTest.class.getClassLoader();
59+
final URL url = cl.getResource("textfile.txt");
60+
Assert.assertNotNull(url);
61+
File file = null;
62+
try {
63+
file = new File(url.toURI());
64+
} catch (URISyntaxException use) {
65+
Assert.fail("uri syntax error");
66+
}
67+
return file;
68+
}
69+
70+
private static void compareContentLength(final List<Part> parts) {
71+
Assert.assertNotNull(parts);
72+
// get expected values
73+
MultipartRequestEntity mre = null;
74+
try {
75+
mre = AsyncHttpProviderUtils.createMultipartRequestEntity(parts, new FluentCaseInsensitiveStringsMap());
76+
} catch (FileNotFoundException fne) {
77+
Assert.fail("file not found: " + parts);
78+
}
79+
final long expectedContentLength = mre.getContentLength();
80+
81+
// get real bytes
82+
final Body multipartBody = new MultipartBody(parts, mre.getContentType(), String.valueOf(expectedContentLength));
83+
try {
84+
final ByteBuffer buffer = ByteBuffer.allocate(8192);
85+
boolean last = false;
86+
long totalBytes = 0;
87+
while (!last) {
88+
long readBytes = 0;
89+
try {
90+
readBytes = multipartBody.read(buffer);
91+
} catch (IOException ie) {
92+
Assert.fail("read failure");
93+
}
94+
if (readBytes >= 0) {
95+
totalBytes += readBytes;
96+
} else {
97+
last = true;
98+
}
99+
buffer.clear();
100+
}
101+
Assert.assertEquals(totalBytes, expectedContentLength);
102+
} finally {
103+
try {
104+
multipartBody.close();
105+
} catch (IOException ignore) {
106+
}
107+
}
108+
}
109+
}

0 commit comments

Comments
 (0)