我正在尝试通过okhttp将文件上传到服务器,我使用的是使用MultipartBuilder的表单。
OkHttpClient client = new OkHttpClient();
MediaType OCTET_STREAM = MediaType.parse("application/octet-stream");
RequestBody body = new MultipartBuilder()
.type(MultipartBuilder.FORM)
.addPart(Headers.of("Content-Disposition",
"form-data; name=\"breadcrumb\"; filename=\"myfile.bin\"",
"Content-Transfer-Encoding", "binary"),
RequestBody.create(OCTET_STREAM, file))
.build();
Request request = new Request.Builder()
.header("Authorization", cred)
.url(url)
.post(body)
.build();
Response response = client.newCall(request).execute();
但是当追踪线上发生的事情时:
POST /breadcrumb/ HTTP/1.1
Authorization: Basic aHl6OmhvbGExMjM=
Content-Type: multipart/form-data; boundary=9bc835d6-24b8-42c4-ae8d-5bc89b3fe68f
Transfer-Encoding: chunked
Host: myurl:8000
Connection: Keep-Alive
Accept-Encoding: gzip
10e
--9bc835d6-24b8-42c4-ae8d-5bc89b3fe68f
Content-Disposition: form-data; name="breadcrumb"; filename="myfile.bin"
Content-Transfer-Encoding: binary
Content-Type: application/octet-stream
Content-Length: 21
some file content in binary
--9bc835d6-24b8-42c4-ae8d-5bc89b3fe68f--
0
当我使用传统的Apache http构建器时,它看起来很相似,但我没有在开头和结尾看到奇怪的字符(10e,0)。有什么想法吗?
感谢您的帮助。
答案 0 :(得分:1)
您没有指定确切的Content-Length标头,因此OkHttpClient开始使用分块传输编码。
在HTTP协议中,接收器必须始终知道内容的确切长度(用于分配内存或其他资源),然后才能将内容真实地发送到服务器。有两种方式可以发送它 - Content-Length标头中的整个内容长度,或者如果在请求开始时无法计算内容长度,则使用chucked编码。
这一行:
10e
只是说在该行之后客户端将发送一些长度为0x10e(270)字节的数据。
答案 1 :(得分:0)
当前的MultipartBuilder
实现不支持设置固定的Content-Length。一种选择是实现FixedMultipartBuilder
,它在public long contentLength()
方法中发挥其神奇作用。现在计算长度,而不是返回-1L
,例如:
import com.squareup.okhttp.Headers;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.MultipartBuilder;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.internal.Util;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import okio.BufferedSink;
import okio.ByteString;
/**
* Fluent API to build <a href="http://www.ietf.org/rfc/rfc2387.txt">RFC
* 2387</a>-compliant request bodies.
*/
public final class FixedMultipartBuilder {
private static final byte[] COLONSPACE = { ':', ' ' };
private static final byte[] CRLF = { '\r', '\n' };
private static final byte[] DASHDASH = { '-', '-' };
private final ByteString boundary;
private MediaType type = MultipartBuilder.MIXED;
// Parallel lists of nullable headers and non-null bodies.
private final List<Headers> partHeaders = new ArrayList<>();
private final List<RequestBody> partBodies = new ArrayList<>();
/** Creates a new multipart builder that uses a random boundary token. */
public FixedMultipartBuilder() {
this(UUID.randomUUID().toString());
}
/**
* Creates a new multipart builder that uses {@code boundary} to separate
* parts. Prefer the no-argument constructor to defend against injection
* attacks.
*/
public FixedMultipartBuilder(String boundary) {
this.boundary = ByteString.encodeUtf8(boundary);
}
/**
* Set the MIME type. Expected values for {@code type} are
* {@link com.squareup.okhttp.MultipartBuilder#MIXED} (the default),
* {@link com.squareup.okhttp.MultipartBuilder#ALTERNATIVE},
* {@link com.squareup.okhttp.MultipartBuilder#DIGEST},
* {@link com.squareup.okhttp.MultipartBuilder#PARALLEL} and
* {@link com.squareup.okhttp.MultipartBuilder#FORM}.
*/
public FixedMultipartBuilder type(MediaType type) {
if (type == null) {
throw new NullPointerException("type == null");
}
if (!type.type().equals("multipart")) {
throw new IllegalArgumentException("multipart != " + type);
}
this.type = type;
return this;
}
/** Add a part to the body. */
public FixedMultipartBuilder addPart(RequestBody body) {
return addPart(null, body);
}
/** Add a part to the body. */
public FixedMultipartBuilder addPart(Headers headers, RequestBody body) {
if (body == null) {
throw new NullPointerException("body == null");
}
if (headers != null && headers.get("Content-Type") != null) {
throw new IllegalArgumentException("Unexpected header: Content-Type");
}
if (headers != null && headers.get("Content-Length") != null) {
throw new IllegalArgumentException("Unexpected header: Content-Length");
}
partHeaders.add(headers);
partBodies.add(body);
return this;
}
/**
* Appends a quoted-string to a StringBuilder.
*
* <p>RFC 2388 is rather vague about how one should escape special characters
* in form-data parameters, and as it turns out Firefox and Chrome actually
* do rather different things, and both say in their comments that they're
* not really sure what the right approach is. We go with Chrome's behavior
* (which also experimentally seems to match what IE does), but if you
* actually want to have a good chance of things working, please avoid
* double-quotes, newlines, percent signs, and the like in your field names.
*/
private static StringBuilder appendQuotedString(StringBuilder target, String key) {
target.append('"');
for (int i = 0, len = key.length(); i < len; i++) {
char ch = key.charAt(i);
switch (ch) {
case '\n':
target.append("%0A");
break;
case '\r':
target.append("%0D");
break;
case '"':
target.append("%22");
break;
default:
target.append(ch);
break;
}
}
target.append('"');
return target;
}
/** Add a form data part to the body. */
public FixedMultipartBuilder addFormDataPart(String name, String value) {
return addFormDataPart(name, null, RequestBody.create(null, value));
}
/** Add a form data part to the body. */
public FixedMultipartBuilder addFormDataPart(String name, String filename, RequestBody value) {
if (name == null) {
throw new NullPointerException("name == null");
}
StringBuilder disposition = new StringBuilder("form-data; name=");
appendQuotedString(disposition, name);
if (filename != null) {
disposition.append("; filename=");
appendQuotedString(disposition, filename);
}
return addPart(Headers.of("Content-Disposition", disposition.toString()), value);
}
/** Assemble the specified parts into a request body. */
public RequestBody build() {
if (partHeaders.isEmpty()) {
throw new IllegalStateException("Multipart body must have at least one part.");
}
return new MultipartRequestBody(type, boundary, partHeaders, partBodies);
}
private static final class MultipartRequestBody extends RequestBody {
private final ByteString boundary;
private final MediaType contentType;
private final List<Headers> partHeaders;
private final List<RequestBody> partBodies;
public MultipartRequestBody(MediaType type, ByteString boundary, List<Headers> partHeaders,
List<RequestBody> partBodies) {
if (type == null) throw new NullPointerException("type == null");
this.boundary = boundary;
this.contentType = MediaType.parse(type + "; boundary=" + boundary.utf8());
this.partHeaders = Util.immutableList(partHeaders);
this.partBodies = Util.immutableList(partBodies);
}
@Override public MediaType contentType() {
return contentType;
}
private long contentLengthForPart(Headers headers, RequestBody body) throws IOException {
// Check if the body has an contentLength != -1, otherwise cancel!
long bodyContentLength = body.contentLength();
if(bodyContentLength < 0L) {
return -1L;
}
long length = 0;
length += DASHDASH.length + boundary.size() + CRLF.length;
if (headers != null) {
for (int h = 0, headerCount = headers.size(); h < headerCount; h++) {
length += headers.name(h).getBytes().length
+ COLONSPACE.length
+ headers.value(h).getBytes().length
+ CRLF.length;
}
}
MediaType contentType = body.contentType();
if (contentType != null) {
length += "Content-Type: ".getBytes().length
+ contentType.toString().getBytes().length
+ CRLF.length;
}
length += CRLF.length;
length += bodyContentLength;
length += CRLF.length;
return length;
}
@Override public long contentLength() throws IOException {
long length = 0;
for (int p = 0, partCount = partHeaders.size(); p < partCount; p++) {
long contentPartLength = contentLengthForPart(partHeaders.get(p), partBodies.get(p));
if(contentPartLength < 0) {
// Too bad, can't get contentPartLength!
return -1L;
}
length += contentPartLength;
}
length += DASHDASH.length + boundary.size() + DASHDASH.length + CRLF.length;
return length;
}
@Override public void writeTo(BufferedSink sink) throws IOException {
for (int p = 0, partCount = partHeaders.size(); p < partCount; p++) {
Headers headers = partHeaders.get(p);
RequestBody body = partBodies.get(p);
sink.write(DASHDASH);
sink.write(boundary);
sink.write(CRLF);
if (headers != null) {
for (int h = 0, headerCount = headers.size(); h < headerCount; h++) {
sink.writeUtf8(headers.name(h))
.write(COLONSPACE)
.writeUtf8(headers.value(h))
.write(CRLF);
}
}
MediaType contentType = body.contentType();
if (contentType != null) {
sink.writeUtf8("Content-Type: ")
.writeUtf8(contentType.toString())
.write(CRLF);
}
// Skipping the Content-Length for individual parts
sink.write(CRLF);
partBodies.get(p).writeTo(sink);
sink.write(CRLF);
}
sink.write(DASHDASH);
sink.write(boundary);
sink.write(DASHDASH);
sink.write(CRLF);
}
}
}
很抱歉长篇邮件...遗憾的是MultipartBuilder
是final
所以我们必须复制大部分来源,而不是简单地扩展它。