mirror of
https://github.com/square/okhttp.git
synced 2025-11-29 06:23:09 +03:00
Import mimecraft as-is.
This commit is contained in:
@@ -0,0 +1,70 @@
|
|||||||
|
// Copyright 2013 Square, Inc.
|
||||||
|
package com.squareup.okhttp;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.util.Collections;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static com.squareup.okhttp.TestUtils.UTF_8;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class ComplexExamplesTest {
|
||||||
|
@Test public void fieldAndTwoFiles() throws Exception {
|
||||||
|
String expected = ""
|
||||||
|
+ "--AaB03x\r\n"
|
||||||
|
+ "Content-Disposition: form-data; name=\"submit-name\"\r\n"
|
||||||
|
+ "Content-Length: 5\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "Larry\r\n"
|
||||||
|
+ "--AaB03x\r\n"
|
||||||
|
+ "Content-Disposition: form-data; name=\"files\"\r\n"
|
||||||
|
+ "Content-Type: multipart/mixed; boundary=BbC04y\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "--BbC04y\r\n"
|
||||||
|
+ "Content-Disposition: file; filename=\"file1.txt\"\r\n"
|
||||||
|
+ "Content-Type: text/plain\r\n"
|
||||||
|
+ "Content-Length: 29\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "... contents of file1.txt ...\r\n"
|
||||||
|
+ "--BbC04y\r\n"
|
||||||
|
+ "Content-Disposition: file; filename=\"file2.gif\"\r\n"
|
||||||
|
+ "Content-Type: image/gif\r\n"
|
||||||
|
+ "Content-Length: 29\r\n"
|
||||||
|
+ "Content-Transfer-Encoding: binary\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "... contents of file2.gif ...\r\n"
|
||||||
|
+ "--BbC04y--\r\n"
|
||||||
|
+ "--AaB03x--";
|
||||||
|
|
||||||
|
Multipart m = new Multipart.Builder("AaB03x") //
|
||||||
|
.type(Multipart.Type.FORM) //
|
||||||
|
.addPart(new Part.Builder() //
|
||||||
|
.contentDisposition("form-data; name=\"submit-name\"") //
|
||||||
|
.body("Larry") //
|
||||||
|
.build()) //
|
||||||
|
.addPart(new Part.Builder() //
|
||||||
|
.contentDisposition("form-data; name=\"files\"") //
|
||||||
|
.body(new Multipart.Builder("BbC04y") //
|
||||||
|
.addPart(new Part.Builder() //
|
||||||
|
.contentDisposition("file; filename=\"file1.txt\"") //
|
||||||
|
.contentType("text/plain") //
|
||||||
|
.body("... contents of file1.txt ...") //
|
||||||
|
.build()) //
|
||||||
|
.addPart(new Part.Builder() //
|
||||||
|
.contentDisposition("file; filename=\"file2.gif\"") //
|
||||||
|
.contentType("image/gif") //
|
||||||
|
.contentEncoding("binary") //
|
||||||
|
.body("... contents of file2.gif ...") //
|
||||||
|
.build()) //
|
||||||
|
.build()) //
|
||||||
|
.build()) //
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
m.writeBodyTo(out);
|
||||||
|
String actual = new String(out.toByteArray(), UTF_8);
|
||||||
|
assertEquals(expected, actual);
|
||||||
|
assertEquals(Collections.singletonMap("Content-Type", "multipart/form-data; boundary=AaB03x"),
|
||||||
|
m.getHeaders());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
// Copyright 2013 Square, Inc.
|
||||||
|
package com.squareup.okhttp;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.util.Collections;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static com.squareup.okhttp.TestUtils.UTF_8;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class FormWriterTest {
|
||||||
|
@Test public void urlEncoding() throws Exception {
|
||||||
|
FormEncoding fe = new FormEncoding.Builder() //
|
||||||
|
.add("a&b", "c=d") //
|
||||||
|
.add("space, the", "final frontier") //
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertEquals(Collections.singletonMap("Content-Type", "application/x-www-form-urlencoded"),
|
||||||
|
fe.getHeaders());
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
fe.writeBodyTo(out);
|
||||||
|
String actual = new String(out.toByteArray(), UTF_8);
|
||||||
|
assertEquals("a%26b=c%3Dd&space%2C+the=final+frontier", actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void encodedPairs() throws Exception {
|
||||||
|
FormEncoding fe1 = new FormEncoding.Builder() //
|
||||||
|
.add("sim", "ple") //
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ByteArrayOutputStream out1 = new ByteArrayOutputStream();
|
||||||
|
fe1.writeBodyTo(out1);
|
||||||
|
String actual1 = new String(out1.toByteArray(), UTF_8);
|
||||||
|
assertEquals("sim=ple", actual1);
|
||||||
|
|
||||||
|
FormEncoding fe2 = new FormEncoding.Builder() //
|
||||||
|
.add("sim", "ple") //
|
||||||
|
.add("hey", "there") //
|
||||||
|
.add("help", "me") //
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ByteArrayOutputStream out2 = new ByteArrayOutputStream();
|
||||||
|
fe2.writeBodyTo(out2);
|
||||||
|
String actual2 = new String(out2.toByteArray(), UTF_8);
|
||||||
|
assertEquals("sim=ple&hey=there&help=me", actual2);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// Copyright 2013 Square, Inc.
|
||||||
|
package com.squareup.okhttp;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.util.Collections;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static com.squareup.okhttp.TestUtils.UTF_8;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
|
||||||
|
public class MultipartWriterTest {
|
||||||
|
@Test(expected = IllegalStateException.class)
|
||||||
|
public void onePartRequired() throws Exception {
|
||||||
|
new Multipart.Builder().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void singlePart() throws Exception {
|
||||||
|
String expected = "" //
|
||||||
|
+ "--123\r\n" //
|
||||||
|
+ "\r\n" //
|
||||||
|
+ "Hello, World!\r\n" //
|
||||||
|
+ "--123--";
|
||||||
|
|
||||||
|
Multipart m = new Multipart.Builder("123")
|
||||||
|
.addPart(new TestPart("Hello, World!"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
m.writeBodyTo(out);
|
||||||
|
String actual = new String(out.toByteArray(), UTF_8);
|
||||||
|
assertEquals(expected, actual);
|
||||||
|
assertEquals(Collections.singletonMap("Content-Type", "multipart/mixed; boundary=123"),
|
||||||
|
m.getHeaders());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void threeParts() throws Exception {
|
||||||
|
String expected = ""
|
||||||
|
+ "--123\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "Quick\r\n"
|
||||||
|
+ "--123\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "Brown\r\n"
|
||||||
|
+ "--123\r\n"
|
||||||
|
+ "\r\n"
|
||||||
|
+ "Fox\r\n"
|
||||||
|
+ "--123--";
|
||||||
|
|
||||||
|
Multipart m = new Multipart.Builder("123")
|
||||||
|
.addPart(new TestPart("Quick"))
|
||||||
|
.addPart(new TestPart("Brown"))
|
||||||
|
.addPart(new TestPart("Fox"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
m.writeBodyTo(out);
|
||||||
|
String actual = new String(out.toByteArray(), UTF_8);
|
||||||
|
assertEquals(expected, actual);
|
||||||
|
assertEquals(Collections.singletonMap("Content-Type", "multipart/mixed; boundary=123"),
|
||||||
|
m.getHeaders());
|
||||||
|
}
|
||||||
|
}
|
||||||
152
okhttp-tests/src/test/java/com/squareup/okhttp/PartTest.java
Normal file
152
okhttp-tests/src/test/java/com/squareup/okhttp/PartTest.java
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
// Copyright 2013 Square, Inc.
|
||||||
|
package com.squareup.okhttp;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
public class PartTest {
|
||||||
|
@Test(expected = IllegalStateException.class)
|
||||||
|
public void noBodyThrowsException() {
|
||||||
|
new Part.Builder().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void settingHeaderTwiceThrowsException() {
|
||||||
|
try {
|
||||||
|
new Part.Builder().contentEncoding("foo").contentEncoding("bar");
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
new Part.Builder().contentLanguage("foo").contentLanguage("bar");
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
new Part.Builder().contentLength(42).contentLength(108);
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
new Part.Builder().contentType("foo").contentType("bar");
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void settingBodyTwiceThrowsException() {
|
||||||
|
try {
|
||||||
|
new Part.Builder().body("foo").body("bar");
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
byte[] bytes = { 'a', 'b' };
|
||||||
|
new Part.Builder().body(bytes).body(bytes);
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
InputStream is = new ByteArrayInputStream(new byte[0]);
|
||||||
|
new Part.Builder().body(is).body(is);
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
File file = new File("/foo/bar");
|
||||||
|
new Part.Builder().body(file).body(file);
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void blankHeadersThrowsException() {
|
||||||
|
try {
|
||||||
|
new Part.Builder().contentType("");
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
new Part.Builder().contentLength(0);
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
new Part.Builder().contentLength(-1);
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
new Part.Builder().contentLanguage("");
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
new Part.Builder().contentEncoding("");
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void bodyBytesSetsContentLength() {
|
||||||
|
Part.Builder b = new Part.Builder();
|
||||||
|
assertEquals(0, b.headerLength);
|
||||||
|
b.body("1234567");
|
||||||
|
assertEquals(7, b.headerLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void bodyBytesOverridesLength() {
|
||||||
|
Part.Builder b = new Part.Builder();
|
||||||
|
assertEquals(0, b.headerLength);
|
||||||
|
b.contentLength(108);
|
||||||
|
assertEquals(108, b.headerLength);
|
||||||
|
b.body("1234567");
|
||||||
|
assertEquals(7, b.headerLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void completePart() throws Exception {
|
||||||
|
Part p = new Part.Builder() //
|
||||||
|
.contentDisposition("form-data; filename=\"foo.txt\"") //
|
||||||
|
.contentType("application/json") //
|
||||||
|
.contentLength(13) //
|
||||||
|
.contentLanguage("English") //
|
||||||
|
.contentEncoding("UTF-8") //
|
||||||
|
.body("{'foo':'bar'}") //
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
p.writeBodyTo(baos);
|
||||||
|
String actual = new String(baos.toByteArray(), TestUtils.UTF_8);
|
||||||
|
assertEquals("{'foo':'bar'}", actual);
|
||||||
|
|
||||||
|
Map<String, String> expectedHeaders = new LinkedHashMap<String, String>();
|
||||||
|
expectedHeaders.put("Content-Disposition", "form-data; filename=\"foo.txt\"");
|
||||||
|
expectedHeaders.put("Content-Type", "application/json");
|
||||||
|
expectedHeaders.put("Content-Length", "13");
|
||||||
|
expectedHeaders.put("Content-Language", "English");
|
||||||
|
expectedHeaders.put("Content-Transfer-Encoding", "UTF-8");
|
||||||
|
assertEquals(expectedHeaders, p.getHeaders());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void multipartBodySetsType() throws Exception {
|
||||||
|
Multipart m = new Multipart.Builder().addPart(new TestPart("hi")).build();
|
||||||
|
|
||||||
|
try {
|
||||||
|
new Part.Builder().body(m).contentType("break me!");
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Part p = new Part.Builder().body(m).build();
|
||||||
|
assertEquals(Collections.singletonMap("Content-Type", m.getHeaders().get("Content-Type")),
|
||||||
|
p.getHeaders());
|
||||||
|
}
|
||||||
|
}
|
||||||
25
okhttp-tests/src/test/java/com/squareup/okhttp/TestPart.java
Normal file
25
okhttp-tests/src/test/java/com/squareup/okhttp/TestPart.java
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright 2013 Square, Inc.
|
||||||
|
package com.squareup.okhttp;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static com.squareup.okhttp.TestUtils.UTF_8;
|
||||||
|
|
||||||
|
public class TestPart implements Part {
|
||||||
|
private final byte[] content;
|
||||||
|
|
||||||
|
public TestPart(String content) {
|
||||||
|
this.content = content.getBytes(UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public Map<String, String> getHeaders() {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void writeBodyTo(OutputStream out) throws IOException {
|
||||||
|
out.write(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.squareup.okhttp;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
|
final class TestUtils {
|
||||||
|
static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||||
|
|
||||||
|
private TestUtils() {
|
||||||
|
// prevent instantiation
|
||||||
|
}
|
||||||
|
}
|
||||||
64
okhttp/src/main/java/com/squareup/okhttp/FormEncoding.java
Normal file
64
okhttp/src/main/java/com/squareup/okhttp/FormEncoding.java
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
// Copyright 2013 Square, Inc.
|
||||||
|
package com.squareup.okhttp;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="http://www.w3.org/MarkUp/html-spec/html-spec_8.html#SEC8.2.1">HTML 2.0</a>-compliant
|
||||||
|
* form data.
|
||||||
|
*/
|
||||||
|
public final class FormEncoding implements Part {
|
||||||
|
private static final Map<String, String> HEADERS =
|
||||||
|
Collections.singletonMap("Content-Type", "application/x-www-form-urlencoded");
|
||||||
|
|
||||||
|
/** Fluent API to build {@link FormEncoding} instances. */
|
||||||
|
public static class Builder {
|
||||||
|
private final StringBuilder content = new StringBuilder();
|
||||||
|
|
||||||
|
/** Add new key-value pair. */
|
||||||
|
public Builder add(String name, String value) {
|
||||||
|
if (content.length() > 0) {
|
||||||
|
content.append('&');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
content.append(URLEncoder.encode(name, "UTF-8"))
|
||||||
|
.append('=')
|
||||||
|
.append(URLEncoder.encode(value, "UTF-8"));
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create {@link FormEncoding} instance. */
|
||||||
|
public FormEncoding build() {
|
||||||
|
if (content.length() == 0) {
|
||||||
|
throw new IllegalStateException("Form encoded body must have at least one part.");
|
||||||
|
}
|
||||||
|
return new FormEncoding(content.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final byte[] data;
|
||||||
|
|
||||||
|
private FormEncoding(String data) {
|
||||||
|
try {
|
||||||
|
this.data = data.getBytes("UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new IllegalArgumentException("Unable to convert input to UTF-8: " + data, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public Map<String, String> getHeaders() {
|
||||||
|
return HEADERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void writeBodyTo(OutputStream stream) throws IOException {
|
||||||
|
stream.write(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
155
okhttp/src/main/java/com/squareup/okhttp/Multipart.java
Normal file
155
okhttp/src/main/java/com/squareup/okhttp/Multipart.java
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
// Copyright 2013 Square, Inc.
|
||||||
|
package com.squareup.okhttp;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static com.squareup.okhttp.Utils.isNotNull;
|
||||||
|
|
||||||
|
/** <a href="http://www.ietf.org/rfc/rfc2387.txt">RFC 2387</a>-compliant encoded data. */
|
||||||
|
public final class Multipart implements Part {
|
||||||
|
|
||||||
|
/** Multipart MIME types. */
|
||||||
|
public enum Type {
|
||||||
|
/**
|
||||||
|
* The "mixed" subtype of "multipart" is intended for use when the body parts are independent
|
||||||
|
* and need to be bundled in a particular order. Any "multipart" subtypes that an
|
||||||
|
* implementation does not recognize must be treated as being of subtype "mixed".
|
||||||
|
*/
|
||||||
|
MIXED("mixed"),
|
||||||
|
/**
|
||||||
|
* The "multipart/alternative" type is syntactically identical to "multipart/mixed", but the
|
||||||
|
* semantics are different. In particular, each of the body parts is an "alternative" version
|
||||||
|
* of the same information.
|
||||||
|
*/
|
||||||
|
ALTERNATIVE("alternative"),
|
||||||
|
/**
|
||||||
|
* This type is syntactically identical to "multipart/mixed", but the semantics are different.
|
||||||
|
* In particular, in a digest, the default {@code Content-Type} value for a body part is
|
||||||
|
* changed from "text/plain" to "message/rfc822".
|
||||||
|
*/
|
||||||
|
DIGEST("digest"),
|
||||||
|
/**
|
||||||
|
* This type is syntactically identical to "multipart/mixed", but the semantics are different.
|
||||||
|
* In particular, in a parallel entity, the order of body parts is not significant.
|
||||||
|
*/
|
||||||
|
PARALLEL("parallel"),
|
||||||
|
/**
|
||||||
|
* The media-type multipart/form-data follows the rules of all multipart MIME data streams as
|
||||||
|
* outlined in RFC 2046. In forms, there are a series of fields to be supplied by the user who
|
||||||
|
* fills out the form. Each field has a name. Within a given form, the names are unique.
|
||||||
|
*/
|
||||||
|
FORM("form-data");
|
||||||
|
|
||||||
|
final String contentType;
|
||||||
|
|
||||||
|
private Type(String contentType) {
|
||||||
|
this.contentType = contentType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fluent API to build {@link Multipart} instances. */
|
||||||
|
public static class Builder {
|
||||||
|
private final String boundary;
|
||||||
|
private final List<Part> parts = new ArrayList<Part>();
|
||||||
|
private Type type = Type.MIXED;
|
||||||
|
|
||||||
|
public Builder() {
|
||||||
|
this(UUID.randomUUID().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder(String boundary) {
|
||||||
|
this.boundary = boundary;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the MIME type. */
|
||||||
|
public Builder type(Type type) {
|
||||||
|
isNotNull(type, "Type must not be null.");
|
||||||
|
this.type = type;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add a part to the body. */
|
||||||
|
public Builder addPart(Part part) {
|
||||||
|
isNotNull(part, "Part must not be null.");
|
||||||
|
parts.add(part);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Assemble the specified parts into a {@link Multipart}. */
|
||||||
|
public Multipart build() {
|
||||||
|
if (parts.isEmpty()) {
|
||||||
|
throw new IllegalStateException("Multipart body must have at least one part.");
|
||||||
|
}
|
||||||
|
return new Multipart(type, parts, boundary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<Part> parts;
|
||||||
|
private final Map<String, String> headers;
|
||||||
|
private final String boundary;
|
||||||
|
|
||||||
|
private Multipart(Type type, List<Part> parts, String boundary) {
|
||||||
|
isNotNull(type, "Multipart type must not be null.");
|
||||||
|
|
||||||
|
this.parts = parts;
|
||||||
|
this.headers = Collections.singletonMap(
|
||||||
|
"Content-Type", "multipart/" + type.contentType + "; boundary=" + boundary);
|
||||||
|
this.boundary = boundary;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public Map<String, String> getHeaders() {
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void writeBodyTo(OutputStream stream) throws IOException {
|
||||||
|
byte[] boundary = this.boundary.getBytes("UTF-8");
|
||||||
|
boolean first = true;
|
||||||
|
for (Part part : parts) {
|
||||||
|
writeBoundary(stream, boundary, first, false);
|
||||||
|
writePart(stream, part);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
writeBoundary(stream, boundary, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeBoundary(OutputStream out, byte[] boundary, boolean first, boolean last)
|
||||||
|
throws IOException {
|
||||||
|
if (!first) {
|
||||||
|
out.write('\r');
|
||||||
|
out.write('\n');
|
||||||
|
}
|
||||||
|
out.write('-');
|
||||||
|
out.write('-');
|
||||||
|
out.write(boundary);
|
||||||
|
if (last) {
|
||||||
|
out.write('-');
|
||||||
|
out.write('-');
|
||||||
|
} else {
|
||||||
|
out.write('\r');
|
||||||
|
out.write('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writePart(OutputStream out, Part part) throws IOException {
|
||||||
|
Map<String, String> headers = part.getHeaders();
|
||||||
|
if (headers != null) {
|
||||||
|
for (Map.Entry<String, String> header : headers.entrySet()) {
|
||||||
|
out.write(header.getKey().getBytes("UTF-8"));
|
||||||
|
out.write(':');
|
||||||
|
out.write(' ');
|
||||||
|
out.write(header.getValue().getBytes("UTF-8"));
|
||||||
|
out.write('\r');
|
||||||
|
out.write('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.write('\r');
|
||||||
|
out.write('\n');
|
||||||
|
part.writeBodyTo(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
260
okhttp/src/main/java/com/squareup/okhttp/Part.java
Normal file
260
okhttp/src/main/java/com/squareup/okhttp/Part.java
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
// Copyright 2013 Square, Inc.
|
||||||
|
package com.squareup.okhttp;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static com.squareup.okhttp.Utils.copyStream;
|
||||||
|
import static com.squareup.okhttp.Utils.isNotEmpty;
|
||||||
|
import static com.squareup.okhttp.Utils.isNotNull;
|
||||||
|
import static com.squareup.okhttp.Utils.isNotZero;
|
||||||
|
import static com.squareup.okhttp.Utils.isNull;
|
||||||
|
|
||||||
|
/** HTTP request data with associated headers. */
|
||||||
|
public interface Part {
|
||||||
|
/** HTTP headers. */
|
||||||
|
Map<String, String> getHeaders();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write request data to the specified stream. For best performance use a
|
||||||
|
* {@link java.io.BufferedOutputStream BufferedOutputStream}.
|
||||||
|
*/
|
||||||
|
void writeBodyTo(OutputStream stream) throws IOException;
|
||||||
|
|
||||||
|
/** Fluent API to build {@link Part} instances. */
|
||||||
|
public class Builder {
|
||||||
|
private static final int BUFFER_SIZE = 4096;
|
||||||
|
|
||||||
|
private String headerType;
|
||||||
|
int headerLength;
|
||||||
|
private String headerLanguage;
|
||||||
|
private String headerEncoding;
|
||||||
|
private String headerDisposition;
|
||||||
|
private File bodyFile;
|
||||||
|
private InputStream bodyStream;
|
||||||
|
private byte[] bodyBytes;
|
||||||
|
private Multipart bodyMultipart;
|
||||||
|
private boolean hasBody = false;
|
||||||
|
|
||||||
|
private void checkSetBody() {
|
||||||
|
if (hasBody) {
|
||||||
|
throw new IllegalStateException("Only one body per part.");
|
||||||
|
}
|
||||||
|
hasBody = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the {@code Content-Type} header value. */
|
||||||
|
public Builder contentType(String type) {
|
||||||
|
isNotEmpty(type, "Type must not be empty.");
|
||||||
|
isNull(headerType, "Type header already set.");
|
||||||
|
isNull(bodyMultipart, "Type cannot be set with multipart body.");
|
||||||
|
headerType = type;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the {@code Content-Length} header value. */
|
||||||
|
public Builder contentLength(int length) {
|
||||||
|
if (length <= 0) {
|
||||||
|
throw new IllegalStateException("Length must be greater than zero.");
|
||||||
|
}
|
||||||
|
isNotZero(headerLength, "Length header already set.");
|
||||||
|
headerLength = length;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the {@code Content-Language} header value. */
|
||||||
|
public Builder contentLanguage(String language) {
|
||||||
|
isNotEmpty(language, "Language must not be empty.");
|
||||||
|
isNull(headerLanguage, "Language header already set.");
|
||||||
|
headerLanguage = language;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the {@code Content-Transfer-Encoding} header value. */
|
||||||
|
public Builder contentEncoding(String encoding) {
|
||||||
|
isNotEmpty(encoding, "Encoding must not be empty.");
|
||||||
|
isNull(headerEncoding, "Encoding header already set.");
|
||||||
|
headerEncoding = encoding;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the {@code Content-Disposition} header value. */
|
||||||
|
public Builder contentDisposition(String disposition) {
|
||||||
|
isNotEmpty(disposition, "Disposition must not be empty.");
|
||||||
|
isNull(headerDisposition, "Disposition header already set.");
|
||||||
|
headerDisposition = disposition;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Use the specified file as the body. */
|
||||||
|
public Builder body(File body) {
|
||||||
|
isNotNull(body, "File body must not be null.");
|
||||||
|
checkSetBody();
|
||||||
|
bodyFile = body;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Use the specified stream as the body. */
|
||||||
|
public Builder body(InputStream body) {
|
||||||
|
isNotNull(body, "Stream body must not be null.");
|
||||||
|
checkSetBody();
|
||||||
|
bodyStream = body;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Use the specified string as the body. */
|
||||||
|
public Builder body(String body) {
|
||||||
|
isNotNull(body, "String body must not be null.");
|
||||||
|
checkSetBody();
|
||||||
|
byte[] bytes;
|
||||||
|
try {
|
||||||
|
bytes = body.getBytes("UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new IllegalArgumentException("Unable to convert input to UTF-8: " + body, e);
|
||||||
|
}
|
||||||
|
bodyBytes = bytes;
|
||||||
|
headerLength = bytes.length;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Use the specified bytes as the body. */
|
||||||
|
public Builder body(byte[] body) {
|
||||||
|
isNotNull(body, "Byte array body must not be null.");
|
||||||
|
checkSetBody();
|
||||||
|
bodyBytes = body;
|
||||||
|
headerLength = body.length;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Use the specified {@link Multipart} as the body. */
|
||||||
|
public Builder body(Multipart body) {
|
||||||
|
isNotNull(body, "Multipart body must not be null.");
|
||||||
|
if (headerType != null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Content type must not be explicitly set for multipart body.");
|
||||||
|
}
|
||||||
|
checkSetBody();
|
||||||
|
headerType = null;
|
||||||
|
bodyMultipart = body;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Assemble the specified headers and body into a {@link Part}. */
|
||||||
|
public Part build() {
|
||||||
|
Map<String, String> headers = new LinkedHashMap<String, String>();
|
||||||
|
if (headerDisposition != null) {
|
||||||
|
headers.put("Content-Disposition", headerDisposition);
|
||||||
|
}
|
||||||
|
if (headerType != null) {
|
||||||
|
headers.put("Content-Type", headerType);
|
||||||
|
}
|
||||||
|
if (headerLength != 0) {
|
||||||
|
headers.put("Content-Length", Integer.toString(headerLength));
|
||||||
|
}
|
||||||
|
if (headerLanguage != null) {
|
||||||
|
headers.put("Content-Language", headerLanguage);
|
||||||
|
}
|
||||||
|
if (headerEncoding != null) {
|
||||||
|
headers.put("Content-Transfer-Encoding", headerEncoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bodyBytes != null) {
|
||||||
|
return new BytesPart(headers, bodyBytes);
|
||||||
|
}
|
||||||
|
if (bodyStream != null) {
|
||||||
|
return new StreamPart(headers, bodyStream);
|
||||||
|
}
|
||||||
|
if (bodyFile != null) {
|
||||||
|
return new FilePart(headers, bodyFile);
|
||||||
|
}
|
||||||
|
if (bodyMultipart != null) {
|
||||||
|
headers.putAll(bodyMultipart.getHeaders());
|
||||||
|
return new PartPart(headers, bodyMultipart);
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("Part required body to be set.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract static class PartImpl implements Part {
|
||||||
|
private final Map<String, String> headers;
|
||||||
|
|
||||||
|
protected PartImpl(Map<String, String> headers) {
|
||||||
|
this.headers = headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public Map<String, String> getHeaders() {
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class PartPart extends PartImpl {
|
||||||
|
private final Part body;
|
||||||
|
|
||||||
|
protected PartPart(Map<String, String> headers, Part body) {
|
||||||
|
super(headers);
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void writeBodyTo(OutputStream stream) throws IOException {
|
||||||
|
body.writeBodyTo(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class BytesPart extends PartImpl {
|
||||||
|
private final byte[] contents;
|
||||||
|
|
||||||
|
BytesPart(Map<String, String> headers, byte[] contents) {
|
||||||
|
super(headers);
|
||||||
|
this.contents = contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void writeBodyTo(OutputStream out) throws IOException {
|
||||||
|
out.write(contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class StreamPart extends PartImpl {
|
||||||
|
private final InputStream in;
|
||||||
|
private final byte[] buffer = new byte[BUFFER_SIZE];
|
||||||
|
|
||||||
|
private StreamPart(Map<String, String> headers, InputStream in) {
|
||||||
|
super(headers);
|
||||||
|
this.in = in;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void writeBodyTo(OutputStream out) throws IOException {
|
||||||
|
copyStream(in, out, buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class FilePart extends PartImpl {
|
||||||
|
private final File file;
|
||||||
|
private final byte[] buffer = new byte[BUFFER_SIZE];
|
||||||
|
|
||||||
|
private FilePart(Map<String, String> headers, File file) {
|
||||||
|
super(headers);
|
||||||
|
this.file = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void writeBodyTo(OutputStream out) throws IOException {
|
||||||
|
InputStream in = null;
|
||||||
|
try {
|
||||||
|
in = new FileInputStream(file);
|
||||||
|
copyStream(in, out, buffer);
|
||||||
|
} finally {
|
||||||
|
if (in != null) {
|
||||||
|
try {
|
||||||
|
in.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
okhttp/src/main/java/com/squareup/okhttp/Utils.java
Normal file
44
okhttp/src/main/java/com/squareup/okhttp/Utils.java
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// Copyright 2013 Square, Inc.
|
||||||
|
package com.squareup.okhttp;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
final class Utils {
|
||||||
|
private Utils() {
|
||||||
|
// No instances.
|
||||||
|
}
|
||||||
|
|
||||||
|
static void copyStream(InputStream in, OutputStream out, byte[] buffer) throws IOException {
|
||||||
|
int count;
|
||||||
|
while ((count = in.read(buffer)) != -1) {
|
||||||
|
out.write(buffer, 0, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void isNotNull(Object obj, String message) {
|
||||||
|
if (obj == null) {
|
||||||
|
throw new IllegalStateException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void isNull(Object obj, String message) {
|
||||||
|
if (obj != null) {
|
||||||
|
throw new IllegalStateException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void isNotEmpty(String thing, String message) {
|
||||||
|
isNotNull(thing, message);
|
||||||
|
if ("".equals(thing.trim())) {
|
||||||
|
throw new IllegalStateException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void isNotZero(int value, String message) {
|
||||||
|
if (value != 0) {
|
||||||
|
throw new IllegalStateException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user