Search code examples
javahttpjava-11java-http-client

How to timeout on a slow streaming response body with Java's HttpClient


How should I deal with servers that hang sending an HTTP response body using the HTTP client included in Java 11 onwards, when I need to handle the response in a streaming fashion?

Having read the documentation, I'm aware that it's possible to set a timeout on connection and a timeout on the request:

HttpClient httpClient = HttpClient.newBuilder()
        .connectTimeout(Duration.ofSeconds(2))
        .build();

HttpRequest httpRequest = HttpRequest.newBuilder(URI.create("http://example.com"))
        .timeout(Duration.ofSeconds(5))
        .build();

HttpResponse<Stream<String>> httpResponse = httpClient
        .send(httpRequest, HttpResponse.BodyHandlers.ofLines());

Stream<String> responseLineStream = httpResponse.body();
responseLineStream.count();

In the above code:

  • If a connection cannot be established within 2 seconds, a timeout exception will be thrown.
  • If a response is not received within 5 seconds, a timeout exception will be thrown. By experimentation, the timer starts after the connection is established, and for this type of BodyHandler, a response is considered received when the status line and headers have been received.

This means that when the code executes, within 7 seconds either an exception will have been thrown, or we'll have arrived at the last line. However, the last line isn't constrained by any timeout. If the server stops sending the response body, the last line blocks forever.

How can I prevent the last line hanging in this case?


Solution

  • My guess this is left to the consumer of the stream, since this is part of the handling logic, so the body handling can be still be processed with a CompletableFuture:

    HttpResponse<Stream<String>> httpResponse = httpClient.send(httpRequest,
                                                                HttpResponse.BodyHandlers.ofLines());
    
    Stream<String> responseLineStream = httpResponse.body();
    CompletableFuture<Long> future = CompletableFuture.supplyAsync(() -> responseLineStream.count());
    long count = future.get(3, TimeUnit.SECONDS);
    

    Or just simply a Future executed by a Java Executor.