13

I have a code that makes a POST request for a specific endpoint. This code is using Apache's HttpClient and I would like to start using the native HttpClient from Java (JDK11). But I didn't understand how to specify the parameters of my request.

This is my code using Apache Httpclient:

var path = Path.of("file.txt");
var entity = MultipartEntityBuilder.create()
            .addPart("file", new FileBody(path.toFile()))
            .addTextBody("token", "<any-token>")
            .build();

And the code using HttpClient:

var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
                         .uri(URI.create("https://myendpoint.com/"))
                         .POST( /* How can I set the parameters here? */ );

How can I set file and token parameters?

Mikhail Kholodkov
  • 23,642
  • 17
  • 61
  • 78
Renan Gomes
  • 771
  • 1
  • 15
  • 34
  • The Java 11 HttpClient doesn't appear to support file uploads. Also, it seems to require that you have all the POST arguments (which look identical to GET arguments) already set up somewhere instead of having a builder for them. – Powerlord Jun 06 '19 at 17:43
  • 1
    Having said that, someone else has already [written a custom BodyPublisher](https://stackoverflow.com/a/54675316/15880) to do fileuploads. – Powerlord Jun 06 '19 at 17:46
  • I am not very sure, but maybe try this out `HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://myendpoint.com/")) .header("Content-Type", "multipart/form-data") .POST(HttpRequest.BodyPublishers.ofFile(Path.of("file.txt"))) .POST(HttpRequest.BodyPublishers.ofString("token")) .build();` – Naman Jun 07 '19 at 04:02

2 Answers2

13

Unfortunately the Java 11 HTTP client does not provide any convenient support for multipart kind of body. But we can build custom implementation on top of it:

Map<Object, Object> data = new LinkedHashMap<>();
data.put("token", "some-token-value");
data.put("file", File.createTempFile("temp", "txt").toPath());

// add extra parameters if needed

// Random 256 length string is used as multipart boundary
String boundary = new BigInteger(256, new Random()).toString();

HttpRequest.newBuilder()
              .uri(URI.create("http://example.com"))
              .header("Content-Type", "multipart/form-data;boundary=" + boundary)
              .POST(ofMimeMultipartData(data, boundary))
              .build();

public HttpRequest.BodyPublisher ofMimeMultipartData(Map<Object, Object> data,
                                                     String boundary) throws IOException {
        // Result request body
        List<byte[]> byteArrays = new ArrayList<>();

        // Separator with boundary
        byte[] separator = ("--" + boundary + "\r\nContent-Disposition: form-data; name=").getBytes(StandardCharsets.UTF_8);

        // Iterating over data parts
        for (Map.Entry<Object, Object> entry : data.entrySet()) {

            // Opening boundary
            byteArrays.add(separator);

            // If value is type of Path (file) append content type with file name and file binaries, otherwise simply append key=value
            if (entry.getValue() instanceof Path) {
                var path = (Path) entry.getValue();
                String mimeType = Files.probeContentType(path);
                byteArrays.add(("\"" + entry.getKey() + "\"; filename=\"" + path.getFileName()
                        + "\"\r\nContent-Type: " + mimeType + "\r\n\r\n").getBytes(StandardCharsets.UTF_8));
                byteArrays.add(Files.readAllBytes(path));
                byteArrays.add("\r\n".getBytes(StandardCharsets.UTF_8));
            } else {
                byteArrays.add(("\"" + entry.getKey() + "\"\r\n\r\n" + entry.getValue() + "\r\n")
                        .getBytes(StandardCharsets.UTF_8));
            }
        }

        // Closing boundary
        byteArrays.add(("--" + boundary + "--").getBytes(StandardCharsets.UTF_8));

        // Serializing as byte array
        return HttpRequest.BodyPublishers.ofByteArrays(byteArrays);
    }

Here's working example on Github (you need to change VirusTotal API key)

Ahmed Ashour
  • 5,179
  • 10
  • 35
  • 56
Mikhail Kholodkov
  • 23,642
  • 17
  • 61
  • 78
  • 2
    Just asking `BodyPublishers.ofMimeMultipartData(data)` exists in Java-11? I was unable to find it in Java-12 at least. – Naman Jun 06 '19 at 17:21
  • @Naman same here, I just found reference of `ofMimeMultipartData` [here](https://golb.hplar.ch/2019/01/java-11-http-client.html) and [here](https://github.com/ralscha/blog2019/blob/master/java11httpclient/client/src/main/java/ch/rasc/httpclient/File.java#L69). Looks like a custom implementation, not part of JDK. – Renan Gomes Jun 06 '19 at 17:37
  • @Renan true, so the crux would be relying on `BodyPublishers.ofByteArray(s)` mostly. – Naman Jun 06 '19 at 17:42
  • Sorry guys, missed the part. Yes it's a custom implementation. The links provided by @Renan were correct. Updated the answer. – Mikhail Kholodkov Jun 07 '19 at 15:49
0

The HttpClient doesn't provide any high level API to compose or format data in POST requests. You could either compose and format your post data manually, and then use either one of BodyPublishers.ofString(), BodyPublishers.ofInputStream(), or BodyPublishers.ofByteArrays() etc... to send it, or write your own implementation of BodyPublisher.

daniel
  • 2,665
  • 1
  • 8
  • 18