Java 9 HttpClient发送多部分/表单数据请求
以下为表格:Java 9 HttpClient发送多部分/表单数据请求,java,http,multipartform-data,http2,java-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>
<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>
请注意“请求有效负载”,您可以在表单中看到两个参数,用户名和img(表单数据;name=“img”;filename=“out.txt”),finename是文件系统中的真实文件名(或路径),您将在后端(如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
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());
但在java 9中,我们可以编写这样的代码:
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
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());
现在您知道了,我如何设置参数的“名称”?您可以实现进行多种形式数据调用的方向如下: 可以与它们的默认实现一起使用,或者也可以使用自定义实现。使用它们的方法很少有:
HttpRequest.BodyProcessor dataProcessor = HttpRequest.BodyProcessor.fromString("{\"username\":\"foo\"}")
Path path = Paths.get("/path/to/your/file"); // in your case path to 'img'
HttpRequest.BodyProcessor fileProcessor = HttpRequest.BodyProcessor.fromFile(path);
apache.commons.lang
(或您可以想出的自定义方法)将文件输入转换为字节数组,以添加一个小的util,如:
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();
然后字节[]可以与BodyProcessor
一起使用,如下所示:
HttpRequest.BodyProcessor byteProcessor = HttpRequest.BodyProcessor.fromByteArray();
此外,您还可以创建请求,如下所示:
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();
对于相同的响应可以作为文件处理,并使用新的
HttpClient
HttpResponse.BodyHandler bodyHandler = HttpResponse.BodyHandler.asFile(Paths.get("/path"));
HttpClient client = HttpClient.newBuilder().build();
作为:
可以使用
多部分/表单数据
或任何其他内容类型,但您必须自己以正确的格式对正文进行编码。客户端本身不根据内容类型进行任何编码
这意味着您最好的选择是使用另一个HTTP客户机,如客户机,或者只使用另一个库的编码器,如的答案示例中所示
如果您自己对身体进行编码,请注意不能多次调用
POST
等方法POST
只需设置BodyProcessor
,再次调用它将覆盖以前设置的任何处理器。您必须实现一个以正确格式生成整个身体的处理器
对于多部分/表单数据
,这意味着:
边界
标题设置为适当的值boundary + "\nContent-Disposition: form-data; name=\"" + name + "\"\n\n" + value + "\n"
这里,名称指的是HTML表单中的name
属性。对于问题中的文件输入,这将img
,值将是编码的文件内容我为这个问题挣扎了一段时间,甚至在看过这一页之后。但是,使用本页上的答案为我指明了正确的方向,阅读了更多关于多部分表单和边界的内容,并进行了修补,我能够创建一个有效的解决方案 该解决方案的要点是使用Apache的MultipartEntityBuilder创建实体及其边界(
HttpExceptionBuilder
是一个自主开发的类):
我希望在不需要拉入Apache客户端的情况下为项目执行此操作,因此我编写了一个
MultiPartBodyPublisher
(Java 11,仅供参考):
请注意,输入流的addPart
实际上需要一个供应商
,而不仅仅是您可以使用的输入流。它包含一个MultipartBodyPublisher
,带有一个方便易用的MultipartBodyPublisher.Builder
。下面是一个使用它的示例(需要JDK11或更高版本):
var multipartBody=MultipartBodyPublisher.newBuilder()
.textPart(“foo”、“foo_text”)
.filePart(“bar”,Path.of(“Path/to/file.txt”))
.formPart(“baz”,BodyPublishers.of InputStream(()->…)
.build();
var request=HttpRequest.newBuilder()
.uri(uri.create(“https://example.com/"))
.POST(多部分主体)
.build();
请注意,您可以添加所需的任何BodyPublisher
或HttpHeaders
。查看以了解更多信息。虽然正确答案是完整的实现,并且可能是正确的,但它对我不起作用
我的解决方案从中获得灵感。我只是清理了我的用例中不需要的部分。我个人使用多部分表单只上传图片或zip文件(单数)。守则:
public static HttpRequest buildMultiformRequest(byte[] body) {
String boundary = "-------------" + UUID.randomUUID().toString();
Map<String, byte[]> data = Map.of("formFile", body);
return HttpRequest.newBuilder()
.uri(URI.create(<URL>))
.POST(HttpRequest.BodyPublishers.ofByteArrays(buildMultipartData(data, boundary, "filename.jpeg", MediaType.IMAGE_JPEG_VALUE)))
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
.header("Accept", MediaType.APPLICATION_JSON_VALUE)
.timeout(Duration.of(5, ChronoUnit.SECONDS))
.build();
}
public static ArrayList<byte[]> buildMultipartData(Map<String, byte[]> data, String boundary, String filename, String mediaType) {
var byteArrays = new ArrayList<byte[]>();
var separator = ("--" + boundary + "\r\nContent-Disposition: form-data; name=").getBytes(StandardCharsets.UTF_8);
for (var entry : data.entrySet()) {
byteArrays.add(separator);
byteArrays.add(("\"" + entry.getKey() + "\"; filename=\"" + filename + "\"\r\nContent-Type:" + mediaType + "\r\n\r\n").getBytes(StandardCharsets.UTF_8));
byteArrays.add(entry.getValue());
byteArrays.add("\r\n".getBytes(StandardCharsets.UTF_8));
}
byteArrays.add(("--" + boundary + "--").getBytes(StandardCharsets.UTF_8));
return byteArrays;
}
公共静态HttpRequest buildMultiformRequest(字节[]体){
字符串边界=“--------------”+UUID.randomUUID().toString();
映射数据=Map.of(“formFile”,body);
返回HttpRequest.newBuilder()
.uri(uri.create())
.POST(HttpRequest.bodypublisher.of字节数组(buildMultipartData(数据、边界、“filename.jpeg”、MediaType.IMAGE\u jpeg\u值)))
.header(“内容类型”、“多部分/表单数据;边界=“+boundary”)
.header(“接受”,MediaType.APPLICATION\u JSON\u值)
.超时(持续时间(5秒)
.build();
}
公共静态ArrayList buildMultipartData(映射数据、字符串边界、字符串文件名、字符串媒体类型){
var byteArrays=newArrayList();
var separator=(“--”+boundary+”\r\n内容处理:表单数据;name=“).getBytes(StandardCharsets.UTF_8);
for(变量条目:data.entrySet()){
添加(分隔符);
byteArrays.add(“\”“+entry.getKey()+”\“\”文件名=\”“+filename+“\”\r\n内容类型:“+mediaType+”\r\n\r\n”).getBytes(StandardCharsets.UTF\u 8));
add(entry.getValue());
添加(“\r\n”.getBytes(StandardCharsets.UTF_8));
}
添加((“-”+边界+“-”).getBytes(StandardCharsets.UTF_8));
返回航班;
}
您是否可以共享单击按钮时进行的示例API调用。您可以使用浏览器的“检查”部分中的“网络设置”监视相同的内容。您好,我知道如何监视网络请求,也知道如何使用HttpClient HttpClient发送此类请求。什么使我困惑
private String doUpload(final File uploadFile, final String filePostUrl) {
assert uploadFile != null : "uploadFile cannot be null";
assert uploadFile.exists() : "uploadFile must exist";
assert StringUtils.notBlank(filePostUrl, "filePostUrl cannot be blank");
final URI uri = URI.create(filePostUrl);
final HttpEntity entity = HttpUtils.getFileAsBufferedMultipartEntity(uploadFile, Optional.of("partName"));
final String response;
try {
final Builder requestBuilder = HttpRequest.newBuilder(uri)
.POST(BodyPublisher.fromInputStream(HttpUtils.getInputStreamFromHttpEntity(entity)))
.header("Content-Type", "multipart/form-data; boundary=" + HttpUtils.MULTIPART_FORM_DATA_BOUNDARY);
response = this.httpClient.send(requestBuilder.build(), BodyHandler.asString());
} catch (InterruptedException | ExecutionException e) {
throw HttpExceptionBuilder.create().withMessage("Unable to get InputStream from HttpEntity")
.withThrowable(e).build();
}
LOGGER.info("Http Response: {}", response);
return response;
}
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);
}
}
}
}
}
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();
public static HttpRequest buildMultiformRequest(byte[] body) {
String boundary = "-------------" + UUID.randomUUID().toString();
Map<String, byte[]> data = Map.of("formFile", body);
return HttpRequest.newBuilder()
.uri(URI.create(<URL>))
.POST(HttpRequest.BodyPublishers.ofByteArrays(buildMultipartData(data, boundary, "filename.jpeg", MediaType.IMAGE_JPEG_VALUE)))
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
.header("Accept", MediaType.APPLICATION_JSON_VALUE)
.timeout(Duration.of(5, ChronoUnit.SECONDS))
.build();
}
public static ArrayList<byte[]> buildMultipartData(Map<String, byte[]> data, String boundary, String filename, String mediaType) {
var byteArrays = new ArrayList<byte[]>();
var separator = ("--" + boundary + "\r\nContent-Disposition: form-data; name=").getBytes(StandardCharsets.UTF_8);
for (var entry : data.entrySet()) {
byteArrays.add(separator);
byteArrays.add(("\"" + entry.getKey() + "\"; filename=\"" + filename + "\"\r\nContent-Type:" + mediaType + "\r\n\r\n").getBytes(StandardCharsets.UTF_8));
byteArrays.add(entry.getValue());
byteArrays.add("\r\n".getBytes(StandardCharsets.UTF_8));
}
byteArrays.add(("--" + boundary + "--").getBytes(StandardCharsets.UTF_8));
return byteArrays;
}