提交 737b844b 编写于 作者: J Jake Wharton

Roll our own multi-part implementation.

Drop Apache mime4j dependency.

Closes #141.
上级 2fcabb79
......@@ -51,7 +51,6 @@
<android.version>4.1.1.4</android.version>
<android.platform>16</android.platform>
<gson.version>2.2.2</gson.version>
<httpmime.version>4.2.3</httpmime.version>
<javax.inject.version>1</javax.inject.version>
<!-- Test Dependencies -->
......@@ -111,11 +110,6 @@
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>${httpmime.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
......
......@@ -20,10 +20,6 @@
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
</dependency>
<dependency>
<groupId>com.google.android</groupId>
<artifactId>android</artifactId>
......
......@@ -3,64 +3,81 @@ package retrofit.http;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.http.entity.mime.MIME;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.AbstractContentBody;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import retrofit.http.mime.TypedFile;
import retrofit.http.mime.TypedOutput;
final class MultipartTypedOutput implements TypedOutput {
// TODO implement our own Multipart logic instead!
final MultipartEntity cheat = new MultipartEntity();
final Map<String, TypedOutput> parts = new LinkedHashMap<String, TypedOutput>();
private final String boundary;
MultipartTypedOutput() {
boundary = UUID.randomUUID().toString();
}
void addPart(String name, TypedOutput body) {
cheat.addPart(name, new TypedOutputBody(body));
if (name == null) {
throw new NullPointerException("Part name must not be null.");
}
if (body == null) {
throw new NullPointerException("Part body must not be null.");
}
parts.put(name, body);
}
@Override public String mimeType() {
return cheat.getContentType().getValue();
return "multipart/form-data; boundary=" + boundary;
}
@Override public long length() {
return cheat.getContentLength();
return -1;
}
@Override public void writeTo(OutputStream out) throws IOException {
cheat.writeTo(out);
}
/** Adapts {@link org.apache.http.entity.mime.content.ContentBody} to {@link TypedOutput}. */
static class TypedOutputBody extends AbstractContentBody {
final TypedOutput typedBytes;
TypedOutputBody(TypedOutput typedBytes) {
super(typedBytes.mimeType());
this.typedBytes = typedBytes;
boolean first = true;
for (Map.Entry<String, TypedOutput> part : parts.entrySet()) {
writeBoundary(out, boundary, first, false);
writePart(out, part);
first = false;
}
writeBoundary(out, boundary, false, true);
}
@Override public long getContentLength() {
return typedBytes.length();
private static void writeBoundary(OutputStream out, String boundary, boolean first, boolean last)
throws IOException {
StringBuilder sb = new StringBuilder();
if (!first) {
sb.append("\r\n");
}
@Override public String getFilename() {
return null;
sb.append("--");
sb.append(boundary);
if (last) {
sb.append("--");
} else {
sb.append("\r\n");
}
out.write(sb.toString().getBytes("UTF-8"));
}
@Override public String getCharset() {
return null;
}
private static void writePart(OutputStream out, Map.Entry<String, TypedOutput> part)
throws IOException {
String name = part.getKey();
TypedOutput value = part.getValue();
@Override public String getTransferEncoding() {
return MIME.ENC_BINARY;
StringBuilder headers = new StringBuilder();
headers.append("Content-Disposition: form-data; name=\"");
headers.append(name);
if (value instanceof TypedFile) {
headers.append("; filename=\"");
headers.append(((TypedFile) value).file().getName());
}
headers.append("\"\r\nContent-Type: ");
headers.append(value.mimeType());
headers.append("\r\nContent-Transfer-Encoding: binary\r\n\r\n");
out.write(headers.toString().getBytes("UTF-8"));
@Override public void writeTo(OutputStream out) throws IOException {
// Note: We probably want to differentiate I/O errors that occur while reading a file from
// network errors. Network operations can be retried. File operations will probably continue
// to fail.
//
// In the case of photo uploads, we at least check that the file exists before we even try to
// upload it.
typedBytes.writeTo(out);
}
value.writeTo(out);
}
}
......@@ -8,10 +8,10 @@ import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.http.entity.mime.FormBodyPart;
import org.apache.http.entity.mime.HttpMultipart;
import org.junit.Test;
import retrofit.http.client.Request;
import retrofit.http.mime.TypedOutput;
......@@ -20,7 +20,6 @@ import retrofit.http.mime.TypedString;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static retrofit.http.MultipartTypedOutput.TypedOutputBody;
import static retrofit.http.RestMethodInfo.NO_SINGLE_ENTITY;
public class RequestBuilderTest {
......@@ -76,7 +75,7 @@ public class RequestBuilderTest {
assertThat(request.getUrl()).isEqualTo("http://example.com/foo/bar/pong/?kit=kat&riff=raff");
assertThat(request.getBody()).isNull();
}
@Test public void getWithPathAndQueryQuestionMarkParam() throws Exception {
Request request = new Helper() //
.setMethod("GET") //
......@@ -90,7 +89,7 @@ public class RequestBuilderTest {
assertThat(request.getUrl()).isEqualTo("http://example.com/foo/bar/pong%3F/?kit=kat%3F");
assertThat(request.getBody()).isNull();
}
@Test public void getWithPathAndQueryAmpersandParam() throws Exception {
Request request = new Helper() //
.setMethod("GET") //
......@@ -104,7 +103,7 @@ public class RequestBuilderTest {
assertThat(request.getUrl()).isEqualTo("http://example.com/foo/bar/pong%26/?kit=kat%26");
assertThat(request.getBody()).isNull();
}
@Test public void getWithPathAndQueryHashParam() throws Exception {
Request request = new Helper() //
.setMethod("GET") //
......@@ -222,18 +221,18 @@ public class RequestBuilderTest {
assertThat(request.getHeaders()).isEmpty();
assertThat(request.getUrl()).isEqualTo("http://example.com/foo/bar/");
HttpMultipart body = TestingUtils.extractEntity(request.getBody());
assertThat(body.getBodyParts()).hasSize(2);
MultipartTypedOutput body = (MultipartTypedOutput) request.getBody();
assertThat(body.parts).hasSize(2);
Iterator<Map.Entry<String, TypedOutput>> iterator = body.parts.entrySet().iterator();
FormBodyPart part1 = (FormBodyPart) body.getBodyParts().get(0);
assertThat(part1.getName()).isEqualTo("ping");
TypedOutputBody body1 = (TypedOutputBody) part1.getBody();
assertTypedBytes(body1.typedBytes, "pong");
Map.Entry<String, TypedOutput> one = iterator.next();
assertThat(one.getKey()).isEqualTo("ping");
assertTypedBytes(one.getValue(), "pong");
FormBodyPart part2 = (FormBodyPart) body.getBodyParts().get(1);
assertThat(part2.getName()).isEqualTo("kit");
TypedOutputBody body2 = (TypedOutputBody) part2.getBody();
assertTypedBytes(body2.typedBytes, "kat");
Map.Entry<String, TypedOutput> two = iterator.next();
assertThat(two.getKey()).isEqualTo("kit");
assertTypedBytes(two.getValue(), "kat");
}
@Test public void simpleHeaders() throws Exception {
......@@ -325,7 +324,8 @@ public class RequestBuilderTest {
if (singleEntityArgumentIndex != NO_SINGLE_ENTITY) {
throw new IllegalStateException("Single entity param already added.");
}
singleEntityArgumentIndex = namedParams.size(); // Relying on the fact that this is already less one.
// Relying on the fact that this is already less one.
singleEntityArgumentIndex = namedParams.size();
namedParams.add(null);
args.add(value);
return this;
......@@ -377,7 +377,7 @@ public class RequestBuilderTest {
methodInfo.isMultipart = isMultipart;
methodInfo.loaded = true;
return new RequestBuilder(GSON)
return new RequestBuilder(GSON) //
.setApiUrl(url)
.setHeaders(headers)
.setArgs(args.toArray(new Object[args.size()]))
......
// Copyright 2013 Square, Inc.
package retrofit.http;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import org.apache.http.entity.mime.HttpMultipart;
import org.apache.http.entity.mime.MultipartEntity;
import retrofit.http.mime.TypedOutput;
import static org.fest.assertions.api.Assertions.assertThat;
......@@ -28,17 +25,6 @@ public abstract class TestingUtils {
return typedOutput;
}
public static HttpMultipart extractEntity(TypedOutput output)
throws NoSuchFieldException, IllegalAccessException {
if (!(output instanceof MultipartTypedOutput)) {
throw new IllegalArgumentException("TypedOutput was not a MultipartTypedOutput.");
}
MultipartEntity entity = ((MultipartTypedOutput) output).cheat;
Field httpMultipartField = MultipartEntity.class.getDeclaredField("multipart");
httpMultipartField.setAccessible(true);
return (HttpMultipart) httpMultipartField.get(entity);
}
public static void assertMultipart(TypedOutput typedOutput) {
assertThat(typedOutput).isInstanceOf(MultipartTypedOutput.class);
}
......
......@@ -66,9 +66,8 @@ public class UrlConnectionClientTest {
assertThat(connection.getRequestMethod()).isEqualTo("POST");
assertThat(connection.getURL().toString()).isEqualTo(HOST + "/that/");
assertThat(connection.getRequestProperties()).hasSize(2);
assertThat(connection.getRequestProperties()).hasSize(1);
assertThat(connection.getRequestProperty("Content-Type")).startsWith("multipart/form-data;");
assertThat(connection.getRequestProperty("Content-Length")).isNotNull();
assertThat(connection.getOutputStream().toByteArray().length).isGreaterThan(0);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册