vic*_*fan 9 java http multipartform-data http2 java-9
以下是表格:
<form action="/example/html5/demo_form.asp" method="post"
enctype=”multipart/form-data”>
<input type="file" name="img" />
<input type="text" name=username" value="foo"/>
<input type="submit" />
</form>
Run Code Online (Sandbox Code Playgroud)
何时提交此表单,请求将如下所示:
POST /example/html5/demo_form.asp HTTP/1.1
Host: 10.143.47.59:9093
Connection: keep-alive
Content-Length: 326
Accept: application/json, text/javascript, */*; q=0.01
Origin: http://10.143.47.59:9093
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryEDKBhMZFowP9Leno
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4
Request Payload
------WebKitFormBoundaryEDKBhMZFowP9Leno
Content-Disposition: form-data; name="username"
foo
------WebKitFormBoundaryEDKBhMZFowP9Leno
Content-Disposition: form-data; name="img"; filename="out.txt"
Content-Type: text/plain
------WebKitFormBoundaryEDKBhMZFowP9Leno--
Run Code Online (Sandbox Code Playgroud)
请注意"请求有效负载",你可以在表单中看到两个参数,用户名和img(form-data; name ="img"; filename ="out.txt"),并且名字是文件系统中的实际文件名(或路径),您将在后端按名称(而不是文件名)接收文件(例如spring controller).
如果我们使用Apache Httpclient来模拟请求,我们将编写这样的代码:
MultipartEntity mutiEntity = newMultipartEntity();
File file = new File("/path/to/your/file");
mutiEntity.addPart("username",new StringBody("foo", Charset.forName("utf-8")));
mutiEntity.addPart("img", newFileBody(file)); //img is name, file is path
Run Code Online (Sandbox Code Playgroud)
但在java 9中,我们可以编写这样的代码:
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.
newBuilder(new URI("http:///example/html5/demo_form.asp"))
.method("post",HttpRequest.BodyProcessor.fromString("foo"))
.method("post", HttpRequest.BodyProcessor.fromFile(Paths.get("/path/to/your/file")))
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandler.asString());
System.out.println(response.body());
Run Code Online (Sandbox Code Playgroud)
现在你看,我怎么能设置参数的"名称"?
itt*_*elo 10
我想在不引入Apache客户端的情况下为项目执行此操作,因此我编写了MultiPartBodyPublisher(Java 11,fyi):
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.net.http.HttpRequest;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Supplier;
public class MultiPartBodyPublisher {
private List<PartsSpecification> partsSpecificationList = new ArrayList<>();
private String boundary = UUID.randomUUID().toString();
public HttpRequest.BodyPublisher build() {
if (partsSpecificationList.size() == 0) {
throw new IllegalStateException("Must have at least one part to build multipart message.");
}
addFinalBoundaryPart();
return HttpRequest.BodyPublishers.ofByteArrays(PartsIterator::new);
}
public String getBoundary() {
return boundary;
}
public MultiPartBodyPublisher addPart(String name, String value) {
PartsSpecification newPart = new PartsSpecification();
newPart.type = PartsSpecification.TYPE.STRING;
newPart.name = name;
newPart.value = value;
partsSpecificationList.add(newPart);
return this;
}
public MultiPartBodyPublisher addPart(String name, Path value) {
PartsSpecification newPart = new PartsSpecification();
newPart.type = PartsSpecification.TYPE.FILE;
newPart.name = name;
newPart.path = value;
partsSpecificationList.add(newPart);
return this;
}
public MultiPartBodyPublisher addPart(String name, Supplier<InputStream> value, String filename, String contentType) {
PartsSpecification newPart = new PartsSpecification();
newPart.type = PartsSpecification.TYPE.STREAM;
newPart.name = name;
newPart.stream = value;
newPart.filename = filename;
newPart.contentType = contentType;
partsSpecificationList.add(newPart);
return this;
}
private void addFinalBoundaryPart() {
PartsSpecification newPart = new PartsSpecification();
newPart.type = PartsSpecification.TYPE.FINAL_BOUNDARY;
newPart.value = "--" + boundary + "--";
partsSpecificationList.add(newPart);
}
static class PartsSpecification {
public enum TYPE {
STRING, FILE, STREAM, FINAL_BOUNDARY
}
PartsSpecification.TYPE type;
String name;
String value;
Path path;
Supplier<InputStream> stream;
String filename;
String contentType;
}
class PartsIterator implements Iterator<byte[]> {
private Iterator<PartsSpecification> iter;
private InputStream currentFileInput;
private boolean done;
private byte[] next;
PartsIterator() {
iter = partsSpecificationList.iterator();
}
@Override
public boolean hasNext() {
if (done) return false;
if (next != null) return true;
try {
next = computeNext();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
if (next == null) {
done = true;
return false;
}
return true;
}
@Override
public byte[] next() {
if (!hasNext()) throw new NoSuchElementException();
byte[] res = next;
next = null;
return res;
}
private byte[] computeNext() throws IOException {
if (currentFileInput == null) {
if (!iter.hasNext()) return null;
PartsSpecification nextPart = iter.next();
if (PartsSpecification.TYPE.STRING.equals(nextPart.type)) {
String part =
"--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=" + nextPart.name + "\r\n" +
"Content-Type: text/plain; charset=UTF-8\r\n\r\n" +
nextPart.value + "\r\n";
return part.getBytes(StandardCharsets.UTF_8);
}
if (PartsSpecification.TYPE.FINAL_BOUNDARY.equals(nextPart.type)) {
return nextPart.value.getBytes(StandardCharsets.UTF_8);
}
String filename;
String contentType;
if (PartsSpecification.TYPE.FILE.equals(nextPart.type)) {
Path path = nextPart.path;
filename = path.getFileName().toString();
contentType = Files.probeContentType(path);
if (contentType == null) contentType = "application/octet-stream";
currentFileInput = Files.newInputStream(path);
} else {
filename = nextPart.filename;
contentType = nextPart.contentType;
if (contentType == null) contentType = "application/octet-stream";
currentFileInput = nextPart.stream.get();
}
String partHeader =
"--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=" + nextPart.name + "; filename=" + filename + "\r\n" +
"Content-Type: " + contentType + "\r\n\r\n";
return partHeader.getBytes(StandardCharsets.UTF_8);
} else {
byte[] buf = new byte[8192];
int r = currentFileInput.read(buf);
if (r > 0) {
byte[] actualBytes = new byte[r];
System.arraycopy(buf, 0, actualBytes, 0, r);
return actualBytes;
} else {
currentFileInput.close();
currentFileInput = null;
return "\r\n".getBytes(StandardCharsets.UTF_8);
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
您可以像这样大致使用它:
MultiPartBodyPublisher publisher = new MultiPartBodyPublisher()
.addPart("someString", "foo")
.addPart("someInputStream", () -> this.getClass().getResourceAsStream("test.txt"), "test.txt", "text/plain")
.addPart("someFile", pathObject);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://www.example.com/dosomething"))
.header("Content-Type", "multipart/form-data; boundary=" + publisher.getBoundary())
.timeout(Duration.ofMinutes(1))
.POST(publisher.build())
.build();
Run Code Online (Sandbox Code Playgroud)
请注意,addPart对于输入流,实际上需要一个Supplier<InputStream>,而不仅仅是一个InputStream。
您可以使用甲醇。它包含一个MultipartBodyPublisher方便且易于使用的MultipartBodyPublisher.Builder. 这是使用它的示例(需要 JDK11 或更高版本):
var multipartBody = MultipartBodyPublisher.newBuilder()
.textPart("foo", "foo_text")
.filePart("bar", Path.of("path/to/file.txt"))
.formPart("baz", BodyPublishers.ofInputStream(() -> ...))
.build();
var request = HttpRequest.newBuilder()
.uri(URI.create("https://example.com/"))
.POST(multipartBody)
.build();
Run Code Online (Sandbox Code Playgroud)
请注意,您可以添加任何BodyPublisher或HttpHeaders您想要的。查看文档以获取更多信息。
您可以通过以下方式实现多格式数据调用:
BodyProcessor可以与它们的默认实现一起使用,或者也可以使用自定义实现。使用它们的方法如下:
通过字符串读取处理器,如下所示:
HttpRequest.BodyProcessor dataProcessor = HttpRequest.BodyProcessor.fromString("{\"username\":\"foo\"}")
Run Code Online (Sandbox Code Playgroud)使用文件路径从文件创建处理器
Path path = Paths.get("/path/to/your/file"); // in your case path to 'img'
HttpRequest.BodyProcessor fileProcessor = HttpRequest.BodyProcessor.fromFile(path);
Run Code Online (Sandbox Code Playgroud)或者
您可以使用apache.commons.lang(或您可以想到的自定义方法)将文件输入转换为字节数组,以添加一个小实用程序,例如:
org.apache.commons.fileupload.FileItem file;
org.apache.http.HttpEntity multipartEntity = org.apache.http.entity.mime.MultipartEntityBuilder.create()
.addPart("username",new StringBody("foo", Charset.forName("utf-8")))
.addPart("img", newFileBody(file))
.build();
multipartEntity.writeTo(byteArrayOutputStream);
byte[] bytes = byteArrayOutputStream.toByteArray();
Run Code Online (Sandbox Code Playgroud)
然后 byte[] 可以用作BodyProcessor:
HttpRequest.BodyProcessor byteProcessor = HttpRequest.BodyProcessor.fromByteArray();
Run Code Online (Sandbox Code Playgroud)此外,您可以将请求创建为:
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("http:///example/html5/demo_form.asp"))
.headers("Content-Type","multipart/form-data","boundary","boundaryValue") // appropriate boundary values
.POST(dataProcessor)
.POST(fileProcessor)
.POST(byteProcessor) //self-sufficient
.build();
Run Code Online (Sandbox Code Playgroud)
相同的响应可以作为文件处理,并使用新的HttpClient使用
HttpResponse.BodyHandler bodyHandler = HttpResponse.BodyHandler.asFile(Paths.get("/path"));
HttpClient client = HttpClient.newBuilder().build();
Run Code Online (Sandbox Code Playgroud)
作为:
HttpResponse response = client.send(request, bodyHandler);
System.out.println(response.body());
Run Code Online (Sandbox Code Playgroud)