HttpClient
is a new client tool class provided by JDK11 under the java.net.http
package. This article will teach you how to send multipart/form-data requests through HttpClient.
The types in the MIME standard can be divided into two categories: standalone types and Multipart types.
Standalone type is a type that represents only a single file or media, indicating the classification of files for the transferred data. Examples include text
, application
, audio
, image
, video
, etc. Multipart type, on the other hand, specifies that the data being transferred can be divided into several separate blocks of data, each of which can have its own separate meaning and MIME type.
Multipart/form-data is the most common subtype of the Multipart type. Most commonly used in HTTP POST requests for form data and file uploads, the multipart/form-data format was first defined in the RFC2388 specification published in 1998. This specification was superseded in 2015 by the newly released RFC7578 specification.
Java does not provide a ready-made encoding tool class for multipart/form-data
, so you need to use a third-party implementation. Here we recommend using the apache
open source httpmime
toolkit. Next I will show a demo of using HttpClient
to send a file upload request.
httpmime:
1
2
3
4
5
|
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpmime -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
</dependency>
|
Demo
Server
A very simple controller that handles the file uploaded by the client, as well as the JSON and form data submitted at the same time. If the output log is correct, then the client has successfully sent the multipart/form-data
request.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
package io.springboot.demo.web.controller;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.http.MediaType;
import org.springframework.util.StreamUtils;
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 com.google.gson.JsonObject;
import lombok.extern.slf4j.Slf4j;
@RestController
@RequestMapping("/upload")
@Slf4j
public class UploadController {
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
public Object upload(@RequestPart("logo") MultipartFile logo,
@RequestPart("name") String name,
@RequestPart("info") JsonObject info) throws IOException {
log.info("name = {}", name);
log.info("info = {}", info);
log.info("logo = contentType: {}, fileName: {}, formName: {}, size: {}", logo.getContentType(),
logo.getOriginalFilename(), logo.getName(), logo.getSize());
try (InputStream inputStream = logo.getInputStream()){
StreamUtils.drain(inputStream);
}
return "ok";
}
}
|
Client
Use MultipartEntityBuilder
to create a Multipart
request body, which contains a file, a form data and a JSON data. And write to the network using a pipeline stream to avoid memory overflow due to oversized request bodies.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
package io.springcloud.test;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
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.channels.Channels;
import java.nio.channels.Pipe;
import java.nio.charset.StandardCharsets;
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;
public class MultipartRequest {
public static void main(String[] args) throws Exception {
/**
* Create a Multipart request body with MultipartEntityBuilder.
*/
HttpEntity httpEntity = MultipartEntityBuilder.create()
// FORM
.addPart("name",
new StringBody("<Spring Cloud>",
ContentType.create("application/x-www-form-urlencoded", StandardCharsets.UTF_8)))
// JSON
.addPart("info",
new StringBody("{\"site\": \"https://www.springcloud.io\"}", ContentType.APPLICATION_JSON))
// FILE
.addBinaryBody("logo", new File("C:\\Users\\KevinBlandy\\Desktop\\logo.png"), ContentType.IMAGE_PNG,
"logo.png")
.build();
/**
* Use pipeline streams to write the encoded data directly to the network
* instead of caching it in memory. Because Multipart request bodies contain
* files, they can cause memory overflows if cached in memory.
*/
Pipe pipe = Pipe.open();
// Pipeline streams must be used in a multi-threaded environment. Using one
// thread for simultaneous reads and writes can lead to deadlocks.
new Thread(() -> {
try (OutputStream outputStream = Channels.newOutputStream(pipe.sink())) {
// Write the encoded data to the pipeline.
httpEntity.writeTo(outputStream);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder(new URI("http://localhost/upload"))
// The Content-Type header is important, don't forget to set it.
.header("Content-Type", httpEntity.getContentType().getValue())
// Reads data from a pipeline stream.
.POST(BodyPublishers.ofInputStream(() -> Channels.newInputStream(pipe.source()))).build();
HttpResponse<String> responseBody = httpClient.send(request, BodyHandlers.ofString(StandardCharsets.UTF_8));
System.out.println(responseBody.body());
}
}
|
Testing
Start the server first, before running the client.
Server Side Console:
1
2
3
|
2022-04-27 15:52:58.834 INFO 8392 --- [ XNIO-1 task-1] i.s.d.web.controller.UploadController : name = <Spring Cloud>
2022-04-27 15:52:58.834 INFO 8392 --- [ XNIO-1 task-1] i.s.d.web.controller.UploadController : info = {"site":"https://www.springcloud.io"}
2022-04-27 15:52:58.834 INFO 8392 --- [ XNIO-1 task-1] i.s.d.web.controller.UploadController : logo = contentType: image/png, fileName: logo.png, formName: logo, size: 17389
|
Client side console:
As you can see, the client successfully sent the multipart/form-data
request.