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;
        }