How to assign content-length header in Java 11+ HTTPClient

Viewed 1401

I am trying to assign the content-length header in Java but it seems impossible.

Here's my code:

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("URL"))
                .header("Content-Type","application/pdf")
                .header("Authorization",String.format("Token token=%s",token))
                .header("Transfer-Encoding","chunked")
                .header("Content-Length",String.valueOf(contentLength))
                .POST(HttpRequest.BodyPublishers.ofByteArray(file))
                .build();

If I run this, then I get the exception "Restricted Header Name: "Content-Length". So then I remove the line where I set the content-length. At this point I get an IOException saying my request doesn't have a content-length header.

How the hell am I supposed to set the content-length header if the HttpClient throws an exception if I, you know, set the content length header?

EDIT:

I know this doesn't solve the question, but I ended up trying non-system standard HTTP Client Libaries. OkHttp for some reason failed on me, but Apache's HttpClient worked fine. It's not as elegant as Java's built in client, but it actually worked.

2 Answers

HttpClient adds Content-Length header only for non-empty bodies. As you've noticed, overriding this header is restricted. Hopefully this can be relaxed since JDK 12 by setting a system property jdk.httpclient.allowRestrictedHeaders, see JDK-8213696.

(This is not really an answer, but the code is too long for a comment)

I tried this code (which is similar to your example), and the response from httpbin.org shows that the Java11 HTTPClient fills in the content-length header with the correct value:

public class HR {
    public static void main(String[] args) throws IOException, InterruptedException {
        byte[] file = {0, 1, 2, 3, 4, 5, 6, 7};
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://httpbin.org/anything"))
                .header("Content-Type", "application/pdf")
                .header("Authorization", "Token token=xyz")
                .header("Transfer-Encoding", "chunked")
//                .header("Content-Length", String.valueOf(file.length))
                .POST(HttpRequest.BodyPublishers.ofByteArray(file))
                .build();

        HttpClient client = HttpClient.newHttpClient();
        client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                .thenApply(HttpResponse::body)
                .thenAccept(System.out::println)
                .join();

        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.body());
    }
}

The response from httpbin.org is (with some sensitive fields masked)

{
  "args": {}, 
  "data": "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Authorization": "Token token=xyz", 
    "Content-Length": "8", 
    "Content-Type": "application/pdf", 
    "Host": "httpbin.org", 
    "User-Agent": "Java-http-client/15.0.1"
  }, 
  "json": null, 
  "method": "POST", 
  "origin": "1.2.3.4", 
  "url": "https://httpbin.org/anything"
}

The response will show up twice (once from the async call, once from the synchronuous call).

I'm therefore not sure on why your code doesn't send the content-length header, the HttpRequest.BodyPublishers.ofByteArray should do that for you too.

Related