org.aoju.bus.http.bodys.MultipartBody Maven / Gradle / Ivy
The newest version!
/*********************************************************************************
* *
* The MIT License (MIT) *
* *
* Copyright (c) 2015-2022 aoju.org and other contributors. *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy *
* of this software and associated documentation files (the "Software"), to deal *
* in the Software without restriction, including without limitation the rights *
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell *
* copies of the Software, and to permit persons to whom the Software is *
* furnished to do so, subject to the following conditions: *
* *
* The above copyright notice and this permission notice shall be included in *
* all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN *
* THE SOFTWARE. *
* *
********************************************************************************/
package org.aoju.bus.http.bodys;
import org.aoju.bus.core.io.ByteString;
import org.aoju.bus.core.io.buffer.Buffer;
import org.aoju.bus.core.io.sink.BufferSink;
import org.aoju.bus.core.lang.Header;
import org.aoju.bus.core.lang.MediaType;
import org.aoju.bus.core.lang.Symbol;
import org.aoju.bus.http.Headers;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* The MIME Multipart/Related Content-type
* 用于复合对象
*
* @author Kimi Liu
* @since Java 17+
*/
public class MultipartBody extends RequestBody {
private static final byte[] COLONSPACE = {Symbol.C_COLON, Symbol.C_SPACE};
private static final byte[] CRLF = {Symbol.C_CR, Symbol.C_LF};
private static final byte[] DASHDASH = {Symbol.C_MINUS, Symbol.C_MINUS};
private final ByteString boundary;
private final MediaType originalType;
private final MediaType mediaType;
private final List parts;
private long contentLength = -1L;
MultipartBody(ByteString boundary, MediaType mediaType, List parts) {
this.boundary = boundary;
this.originalType = mediaType;
this.mediaType = MediaType.valueOf(mediaType.toString() + "; boundary=" + boundary.utf8());
this.parts = org.aoju.bus.http.Builder.immutableList(parts);
}
/**
* Appends a quoted-string to a StringBuilder
* 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.
*/
static void appendQuotedString(StringBuilder target, String key) {
target.append(Symbol.C_DOUBLE_QUOTES);
for (int i = 0, len = key.length(); i < len; i++) {
char ch = key.charAt(i);
switch (ch) {
case Symbol.C_LF:
target.append("%0A");
break;
case Symbol.C_CR:
target.append("%0D");
break;
case Symbol.C_DOUBLE_QUOTES:
target.append("%22");
break;
default:
target.append(ch);
break;
}
}
target.append(Symbol.C_DOUBLE_QUOTES);
}
public MediaType type() {
return originalType;
}
public String boundary() {
return boundary.utf8();
}
/**
* The number of parts in this multipart body.
*/
public int size() {
return parts.size();
}
public List parts() {
return parts;
}
public Part part(int index) {
return parts.get(index);
}
/**
* A combination of {@link #type()} and {@link #boundary()}.
*/
@Override
public MediaType mediaType() {
return mediaType;
}
@Override
public long length() throws IOException {
long result = contentLength;
if (result != -1L) return result;
return contentLength = writeOrCountBytes(null, true);
}
@Override
public void writeTo(BufferSink sink) throws IOException {
writeOrCountBytes(sink, false);
}
/**
* 将此请求写入{@code sink}或测量其内容长度。我们有一种方法可以
* 同时确保计数和内容是一致的,特别是当涉及到一些棘手的操作时,
* 比如测量报头字符串的编码长度,或者编码整数的位数长度
*
* @param sink 缓冲区
* @param countBytes 统计写入大小
* @return 当前写入大小信息
* @throws IOException 异常
*/
private long writeOrCountBytes(BufferSink sink, boolean countBytes) throws IOException {
long byteCount = 0L;
Buffer byteCountBuffer = null;
if (countBytes) {
sink = byteCountBuffer = new Buffer();
}
for (int p = 0, partCount = parts.size(); p < partCount; p++) {
Part part = parts.get(p);
Headers headers = part.headers;
RequestBody body = part.body;
sink.write(DASHDASH);
sink.write(boundary);
sink.write(CRLF);
if (null != headers) {
for (int h = 0, headerCount = headers.size(); h < headerCount; h++) {
sink.writeUtf8(headers.name(h))
.write(COLONSPACE)
.writeUtf8(headers.value(h))
.write(CRLF);
}
}
MediaType mediaType = body.mediaType();
if (null != mediaType) {
sink.writeUtf8(Header.CONTENT_TYPE + ": ")
.writeUtf8(mediaType.toString())
.write(CRLF);
}
long contentLength = body.length();
if (contentLength != -1) {
sink.writeUtf8("Content-Length: ")
.writeDecimalLong(contentLength)
.write(CRLF);
} else if (countBytes) {
byteCountBuffer.clear();
return -1L;
}
sink.write(CRLF);
if (countBytes) {
byteCount += contentLength;
} else {
body.writeTo(sink);
}
sink.write(CRLF);
}
sink.write(DASHDASH);
sink.write(boundary);
sink.write(DASHDASH);
sink.write(CRLF);
if (countBytes) {
byteCount += byteCountBuffer.size();
byteCountBuffer.clear();
}
return byteCount;
}
public static class Part {
final Headers headers;
final RequestBody body;
private Part(Headers headers, RequestBody body) {
this.headers = headers;
this.body = body;
}
public static Part create(RequestBody body) {
return create(null, body);
}
public static Part create(Headers headers, RequestBody body) {
if (null == body) {
throw new NullPointerException("body == null");
}
if (null != headers && null != headers.get(Header.CONTENT_TYPE)) {
throw new IllegalArgumentException("Unexpected header: Content-Type");
}
if (null != headers && null != headers.get(Header.CONTENT_LENGTH)) {
throw new IllegalArgumentException("Unexpected header: Content-Length");
}
return new Part(headers, body);
}
public static Part createFormData(String name, String value) {
return createFormData(name, null, RequestBody.create(null, value));
}
public static Part createFormData(String name, String filename, RequestBody body) {
if (null == name) {
throw new NullPointerException("name == null");
}
StringBuilder disposition = new StringBuilder("form-data; name=");
appendQuotedString(disposition, name);
if (null != filename) {
disposition.append("; filename=");
appendQuotedString(disposition, filename);
}
Headers headers = new Headers.Builder()
.addUnsafeNonAscii(Header.CONTENT_DISPOSITION, disposition.toString())
.build();
return create(headers, body);
}
public Headers headers() {
return headers;
}
public RequestBody body() {
return body;
}
}
public static class Builder {
private final ByteString boundary;
private final List parts = new ArrayList<>();
private MediaType type = MediaType.MULTIPART_MIXED_TYPE;
public Builder() {
this(UUID.randomUUID().toString());
}
public Builder(String boundary) {
this.boundary = ByteString.encodeUtf8(boundary);
}
/**
* Set the MIME type. Expected values for {@code type} are {@link MediaType#MULTIPART_MIXED} (the default), {@link
* MediaType#MULTIPART_ALTERNATIVE}, {@link MediaType#MULTIPART_DIGEST}, {@link MediaType#MULTIPART_parallel} and {@link MediaType#APPLICATION_FORM_URLENCODED}.
*/
public Builder setType(MediaType type) {
if (null == type) {
throw new NullPointerException("type == null");
}
if (!"multipart".equals(type.type())) {
throw new IllegalArgumentException("multipart != " + type);
}
this.type = type;
return this;
}
/**
* 增加part至请求体
*/
public Builder addPart(RequestBody body) {
return addPart(Part.create(body));
}
/**
* 增加part至请求体
*/
public Builder addPart(Headers headers, RequestBody body) {
return addPart(Part.create(headers, body));
}
/**
* 将表单数据部分添加到主体中
*/
public Builder addFormDataPart(String name, String value) {
return addPart(Part.createFormData(name, value));
}
/**
* 将表单数据部分添加到主体中
*/
public Builder addFormDataPart(String name, String filename, RequestBody body) {
return addPart(Part.createFormData(name, filename, body));
}
/**
* 增加part至请求体
*/
public Builder addPart(Part part) {
if (part == null) throw new NullPointerException("part == null");
parts.add(part);
return this;
}
/**
* 将指定的部分组装成请求体
*/
public MultipartBody build() {
if (parts.isEmpty()) {
throw new IllegalStateException("Multipart body must have at least one part.");
}
return new MultipartBody(boundary, type, parts);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy