It is unfortunate that this has come up so late the JDK 11 lifecycle,
but since this has API impact, and is the right thing to do, it is
certainly worth addressing now ( rather than waiting to do something in
a future release).

TL;DR provide API support for setting connection and response specific
timeouts, while continuing to support a single overall request timeout.

The following issue has been file to track this:
  https://bugs.openjdk.java.net/browse/JDK-8208391

Propose
-------

In the `HttpRequest.Builder` class remove the single `timeout` method
and replace it with a pair of `connectTimeout` and `responseTimeout`.
The `connectTimeout` method can be used to set a timeout duration that
is specific to the connection phase. The `responseTimeout` method can be
used to set a timeout for the actual of the HTTP response. For example,

    HttpRequest request = HttpRequest.newBuilder()
           .uri(URI.create("https://foo.com/";))
           .connectTimeout(Duration.ofSeconds(20))
           .responseTimeout(Duration.ofMinutes(1))
           .header("Content-Type", "application/json")
           .POST(BodyPublishers.ofFile(Paths.get("file.json")))
           .build();

We consider the ability to be able to set a single timeout for the
overall response to be important, so the `responseTimeout` method can be
used to set an overall response timeout, in the absence of a more
specific `connectTimeout`. This means that the newly named
`responseTimeout` is semantically equivalent to the old `timeout` when
used without the new `connectTimeout`.

Add a new public exception type, `HttpConnectTimeoutException` that is
thrown when a connection times out. This exception type will be a 
subtype of the existing `HttpTimeoutException`, thus allowing for more
fine grained exception handling to discern connection timeouts from 
response timeouts, where desired. The use of a subtype reduces the
exception handling overhead for those that do not wish to take that
burden, a timeout is just a timeout.

Appropriate accessors will be added to `HttpRequest` to retrieve the
Optional containing the connect timeout duration, and the response
timeout duration.

A sketch of the javadoc ( in HttpRequest.Builder ):

    /**
     * Sets the response timeout duration for this request.
     *
     * <p> If the response headers are not received within the specified
     * duration, then an {@link HttpTimeoutException} is thrown from {@link
     * HttpClient#send(java.net.http.HttpRequest,
     * java.net.http.HttpResponse.BodyHandler) HttpClient::send}, or
     * {@link HttpClient#sendAsync(java.net.http.HttpRequest,
     * java.net.http.HttpResponse.BodyHandler) HttpClient::sendAsync}
     * completes exceptionally with an {@code HttpTimeoutException}.
     *
     * <p> If a {@linkplain #connectTimeout(Duration) connect timeout duration}
     * has also been specified, then this timeout duration starts after the
     * connection has been established, and the connection setup phase is
     * timed separately by the connect timeout duration. If no connect
     * timeout duration has been specified, then this timeout duration
     * covers both connection setup and receiving of the response.
     *
     * <p> The effect of not setting a timeout duration is the same as
     * setting an infinite duration, ie. block forever, or the Operating
     * System default.
     *
     * @param duration the duration to allow the response headers to be
     *                 received
     * @return this builder
     * @throws IllegalArgumentException if the duration is non-positive
     */
    public abstract Builder responseTimeout(Duration duration);

    /**
     * Sets the connect timeout duration for this request.
     *
     * <p> In the case where a new connection needs to be established, if
     * the connection cannot be established within the given {@code
     * duration}, then a {@link HttpConnectTimeoutException} is thrown
     * from {@link HttpClient#send(java.net.http.HttpRequest,
     * java.net.http.HttpResponse.BodyHandler) HttpClient::send}, or
     * {@link HttpClient#sendAsync(java.net.http.HttpRequest,
     * java.net.http.HttpResponse.BodyHandler) HttpClient::sendAsync}
     * completes exceptionally with a {@code HttpConnectTimeoutException}.
     * If a new connection does not need to be established, for example
     * if a connection can be reused from a previous request, then this
     * timeout duration has no effect.
     *
     * <p> The effect of not setting a connect timeout duration is defined
     * in the method {@link #responseTimeout(Duration) responseTimeout}.
     *
     * @param duration the duration to allow the underlying connection to be
     *                 established
     * @return this builder
     * @throws IllegalArgumentException if the duration is non-positive
     */
     public abstract Builder connectTimeout(Duration duration);

New public exception type: 
    
  public class HttpConnectTimeoutException extends HttpTimeoutException {
    ...
  }
  
To HttpRequest add:
  
  /**
   * Returns an {@code Optional} containing this request's response timeout
   * duration. If the {@linkplain Builder#responseTimeout(Duration) response
   * timeout duration} was not set in the request's builder, then the
   * {@code Optional} is empty.
   *
   * @return an {@code Optional} containing this request's response timeout
   *         duration
   */
  public abstract Optional<Duration> responseTimeout();

  /**
   * Returns an {@code Optional} containing this request's connect timeout
   * duration. If the {@linkplain Builder#connectTimeout(Duration) connect
   * timeout duration} was not set in the request's builder, then the
   * {@code Optional} is empty.
   *
   * @return an {@code Optional} containing this request's connect timeout
   *         duration
   */
  public abstract Optional<Duration> connectTimeout();


-Chris.

Reply via email to