HttpClient is a new HTTP client Api provided by JDK11.

Multipart request

HttpClient does not provide a build Api for the Multipart request body. However, you can use Apache’s open source HttpMIME library to build.

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

Build a MultipartBody

// Build the Multipart request
HttpEntity httpEntity = MultipartEntityBuilder.create()
		// Form data
		.addPart("name".new StringBody(UriUtils.encode("SpringBoot Chinese Community", StandardCharsets.UTF_8), ContentType.APPLICATION_FORM_URLENCODED))
		/ / the JSON data
		.addPart("info".new StringBody("{\"site\": \"https://springboot.io\", \"now\": 2021}", ContentType.APPLICATION_JSON))
		// File data
		.addBinaryBody("file", file, ContentType.APPLICATION_OCTET_STREAM, "eclipse-jee-2019-12-R-win32-x86_64.zip")
		.build();

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream((int) httpEntity.getContentLength());

// Write the body to memory
httpEntity.writeTo(byteArrayOutputStream);
Copy the code

A Multipart request can post multiple sub-bodies at once, usually to upload files on a local disk. So the request body can be very large. Even memory does not fit the entire request body. So there are two ways to solve this problem.

  1. The Body data is first written to disk and then submitted to the server via IO disk data
  2. Using a pipe flow, you pipe directly to a remote server while reading disk data for a body build

The pipe flow

Pipe flows, as the name implies, can be written to one side and read from the other.

// Create a read stream
PipedInputStream pipedInputStream = new PipedInputStream();
// Create a write stream
PipedOutputStream pipedOutputStream = new PipedOutputStream();
// Connect the write and read streams
pipedInputStream.connect(pipedOutputStream);
Copy the code

By writing data to pipedOutputStream, you can read from pipedInputStream.

You cannot use a single thread to read and write at the same time, because reading and writing are blocking methods. If either party blocks, the other party will remain in a waiting state, resulting in a deadlock

Complete Demo

The client

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 {
		
		/ / pipe flow
		PipedInputStream pipedInputStream = new PipedInputStream();
		PipedOutputStream pipedOutputStream = new PipedOutputStream();
		pipedInputStream.connect(pipedOutputStream);
		
		// Local file
		InputStream file =  Files.newInputStream(Paths.get("D:\\eclipse-jee-2019-12-R-win32-x86_64.zip"));
		
		// Build the Multipart request
		HttpEntity httpEntity = MultipartEntityBuilder.create()
				// Form data
				.addPart("name".new StringBody(UriUtils.encode("SpringBoot Chinese Community", StandardCharsets.UTF_8), ContentType.APPLICATION_FORM_URLENCODED))
				/ / the JSON data
				.addPart("info".new StringBody("{\"site\": \"https://springboot.io\", \"now\": 2021}", ContentType.APPLICATION_JSON))
				// File data
				.addBinaryBody("file", file, ContentType.APPLICATION_OCTET_STREAM, "eclipse-jee-2019-12-R-win32-x86_64.zip")
				.build();
		
		// Write data asynchronously to the pipe stream
		new Thread(() -> {
			try (file; pipedOutputStream){
				httpEntity.writeTo(pipedOutputStream);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}).start();
		
		HttpClient httpClient = HttpClient.newHttpClient();
		
		try (pipedInputStream){
			// Create the request and request body
			HttpRequest request = HttpRequest
						.newBuilder(new URI("http://localhost/upload"))
						/ / set the ContentType
						.header("Content-Type", httpEntity.getContentType().getValue())
						.header("Accept"."text/plain")   
						// Read data from the pipeline stream and submit it to the server
						.POST(BodyPublishers.ofInputStream(() -> pipedInputStream))
						.build();
			
			// Execute the request and get the responseHttpResponse<String> responseBody = httpClient.send(request, BodyHandlers.ofString(StandardCharsets.UTF_8)); System.out.println(responseBody.body()); }}}Copy the code

The service side

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"); }}Copy the code

After the server is started, the client requests are executed and the server logs are generated

The 13:38:15 2021-09-24. 2660-067 the INFO [task XNIO - 1-1] I.S.W eb. Controller. UploadController: file: name=eclipse-jee-2019-12-R-win32-x86_64.zip, Size = 369653147 2021-09-24 13:38:15. 2660-067 the INFO] [task XNIO - 1-1 I.S.W eb. Controller. UploadController: the INFO: {"site":"https://springboot.io","now":2021} 2021-09-24 13:38:15.067 INFO 2660 -- [xnio-1 task-1] I.S.W eb. Controller. UploadController: name: SpringBoot Chinese communityCopy the code

Starting: springboot. IO/topic / 418 / t…