HttpClient 是JDK11提供的一个全新HTTP客户端Api,超级好用。

Multipart 申请

HttpClient 并没有提供 Multipart 申请体的构建Api。然而能够应用apache的开源httpmime库来进行构建。

<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpmime --><dependency>    <groupId>org.apache.httpcomponents</groupId>    <artifactId>httpmime</artifactId>    <version>4.5.13</version></dependency>

构建一个 MultipartBody

// 构建Multipart申请HttpEntity httpEntity = MultipartEntityBuilder.create()        // 表单数据        .addPart("name", new StringBody(UriUtils.encode("SpringBoot中文社区", StandardCharsets.UTF_8), ContentType.APPLICATION_FORM_URLENCODED))        // JSON数据        .addPart("info", new StringBody("{\"site\": \"https://springboot.io\", \"now\": 2021}", ContentType.APPLICATION_JSON))        // 文件数据        .addBinaryBody("file", file, ContentType.APPLICATION_OCTET_STREAM, "eclipse-jee-2019-12-R-win32-x86_64.zip")        .build();ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream((int) httpEntity.getContentLength());// 把body写入到内存httpEntity.writeTo(byteArrayOutputStream);

Multipart 申请能够一次性post多个子body,通常用来上传本地磁盘上的文件。所以这种申请体可能会异样宏大。甚至内存不能残缺的存入整个申请体。那么这个时候有2种方法能够解决。

  1. 先把构建好的Body数据写入到磁盘,再通过IO磁盘数据,提交给服务器
  2. 应用管道流,在读取磁盘数据进行body构建的时候,间接通过管道提交到近程服务器

管道流

管道流,顾名思义,能够往一边写,从另一边读。

// 创立读取流PipedInputStream pipedInputStream = new PipedInputStream();// 创立写入流PipedOutputStream pipedOutputStream = new PipedOutputStream();// 连贯写入和读取流pipedInputStream.connect(pipedOutputStream);

通过往pipedOutputStream写入数据,就能够在pipedInputStream 读取。

不能应用单线程既读又写,因为读写都是阻塞办法。任何一方阻塞住了了,另一方就会始终处于期待状态,导致死锁

残缺Demo

客户端

package io.springcloud.test;import java.io.IOException;import java.io.InputStream;import java.io.PipedInputStream;import java.io.PipedOutputStream;import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpRequest.BodyPublishers;import java.net.http.HttpResponse;import java.net.http.HttpResponse.BodyHandlers;import java.nio.charset.StandardCharsets;import java.nio.file.Files;import java.nio.file.Paths;import org.apache.http.HttpEntity;import org.apache.http.entity.ContentType;import org.apache.http.entity.mime.MultipartEntityBuilder;import org.apache.http.entity.mime.content.StringBody;import org.springframework.web.util.UriUtils;public class MainTest {    public static void main(String[] args) throws Exception {                // 管道流        PipedInputStream pipedInputStream = new PipedInputStream();        PipedOutputStream pipedOutputStream = new PipedOutputStream();        pipedInputStream.connect(pipedOutputStream);                // 本地文件        InputStream file =  Files.newInputStream(Paths.get("D:\\eclipse-jee-2019-12-R-win32-x86_64.zip"));                // 构建Multipart申请        HttpEntity httpEntity = MultipartEntityBuilder.create()                // 表单数据                .addPart("name", new StringBody(UriUtils.encode("SpringBoot中文社区", StandardCharsets.UTF_8), ContentType.APPLICATION_FORM_URLENCODED))                // JSON数据                .addPart("info", new StringBody("{\"site\": \"https://springboot.io\", \"now\": 2021}", ContentType.APPLICATION_JSON))                // 文件数据                .addBinaryBody("file", file, ContentType.APPLICATION_OCTET_STREAM, "eclipse-jee-2019-12-R-win32-x86_64.zip")                .build();                // 异步写入数据到管道流        new Thread(() -> {            try (file; pipedOutputStream){                httpEntity.writeTo(pipedOutputStream);            } catch (IOException e) {                e.printStackTrace();            }        }).start();                HttpClient httpClient = HttpClient.newHttpClient();                try (pipedInputStream){            // 创立申请和申请体            HttpRequest request = HttpRequest                        .newBuilder(new URI("http://localhost/upload"))                        // 设置ContentType                        .header("Content-Type", httpEntity.getContentType().getValue())                        .header("Accept", "text/plain")                           // 从管道流读取数据,提交给服务器                        .POST(BodyPublishers.ofInputStream(() -> pipedInputStream))                        .build();                        // 执行申请,获取响应            HttpResponse<String> responseBody = httpClient.send(request, BodyHandlers.ofString(StandardCharsets.UTF_8));                        System.out.println(responseBody.body());        }    }}

服务端

package io.springcloud.web.controller;import java.nio.charset.StandardCharsets;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestPart;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.multipart.MultipartFile;import org.springframework.web.util.UriUtils;import com.google.gson.JsonObject;@RestController@RequestMapping("/upload")public class UploadController {        private static final Logger LOGGER = LoggerFactory.getLogger(UploadController.class);        @PostMapping    public Object upload (@RequestPart("file") MultipartFile file,                        @RequestPart("info") JsonObject info,                        @RequestPart("name") String name) {                LOGGER.info("file: name={}, size={}", file.getOriginalFilename(), file.getSize());        LOGGER.info("info: {}", info.toString());        LOGGER.info("name: {}", UriUtils.decode(name, StandardCharsets.UTF_8));                return ResponseEntity.ok("success");    }}

启动服务端后,执行客户端申请,服务端日志输入

2021-09-24 13:38:15.067  INFO 2660 --- [  XNIO-1 task-1] i.s.web.controller.UploadController      : file: name=eclipse-jee-2019-12-R-win32-x86_64.zip, size=3696531472021-09-24 13:38:15.067  INFO 2660 --- [  XNIO-1 task-1] i.s.web.controller.UploadController      : info: {"site":"https://springboot.io","now":2021}2021-09-24 13:38:15.067  INFO 2660 --- [  XNIO-1 task-1] i.s.web.controller.UploadController      : name: SpringBoot中文社区

首发:https://springboot.io/t/topic...