1. 概述

在这个教程中,我们将展示如何在Java 11及更高版本中使用新的HTTP客户端设置超时。如果需要复习基础知识,可以从Java HTTP客户端教程开始。

另一方面,如果你想了解如何使用旧库设置超时,请参考*HttpUrlConnection*。

2. 设置超时

首先,我们需要创建一个HttpClient来执行HTTP请求:

private static HttpClient getHttpClientWithTimeout(int seconds) {
    return HttpClient.newBuilder()
      .connectTimeout(Duration.ofSeconds(seconds))
      .build();
}

上面的方法返回一个配置了参数定义的超时时间的HttpClient。简而言之,我们使用构建器模式来实例化HttpClient,并通过connectTimeout方法配置超时。此外,我们使用静态方法ofSeconds创建一个Duration对象实例,用以定义我们的超时时间(以秒为单位)。

接下来,我们需要检查HttpClient的超时设置是否正确:

httpClient.connectTimeout().map(Duration::toSeconds)
  .ifPresent(sec -> System.out.println("Timeout in seconds: " + sec));

我们使用connectTimeout方法获取超时设置。结果是一个包含Duration的可选值,我们将其转换为秒。

3. 处理超时

然后,我们需要创建一个HttpRequest对象,供客户端用于发送HTTP请求:

HttpRequest httpRequest = HttpRequest.newBuilder()
  .uri(URI.create("http://10.255.255.1")).GET().build();

为了模拟超时,我们可以调用一个不可路由的IP地址。换句话说,所有TCP包都会被丢弃,并在先前配置的预定义时间内强制超时。

现在,让我们深入了解如何处理超时。

3.1. 处理同步请求超时

例如,对于同步请求,可以使用send方法:

HttpConnectTimeoutException thrown = assertThrows(
  HttpConnectTimeoutException.class,
  () -> httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()),
  "Expected send() to throw HttpConnectTimeoutException, but it didn't");
assertTrue(thrown.getMessage().contains("timed out"));

同步请求会强制捕获IOException,而HttpConnectTimeoutException是其子类。因此,在上述测试中,我们期望得到带有错误消息的HttpConnectTimeoutException

3.2. 处理异步请求超时

同样,对于异步请求,可以使用sendAsync方法:

CompletableFuture<String> completableFuture = httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString())
  .thenApply(HttpResponse::body)
  .exceptionally(Throwable::getMessage);
String response = completableFuture.get(5, TimeUnit.SECONDS);
assertTrue(response.contains("timed out"));

sendAsync的调用返回一个CompletableFuture<HttpResponse>。因此,我们需要定义如何从响应功能上处理它。具体来说,如果没有错误发生,我们从响应中获取主体;否则,从throwable中获取错误消息。最后,我们等待最多5秒从CompletableFuture中获取结果。再次强调,这个请求在3秒后预期会抛出HttpConnectTimeoutException

4. 在请求级别配置超时

在前面的例子中,我们重用了同一个客户端实例进行同步和异步请求。然而,我们可能希望为每个请求处理不同的超时。同样,我们可以为单个请求设置超时:

HttpRequest httpRequest = HttpRequest.newBuilder()
  .uri(URI.create("http://10.255.255.1"))
  .timeout(Duration.ofSeconds(1))
  .GET()
  .build();

同样地,我们使用timeout方法为这次请求设置超时。这里,我们为这次请求配置了1秒的超时时间。

客户端到请求之间的最小持续时间决定了请求的超时时间。

5. 总结

在这篇文章中,我们成功地使用新的Java HTTP客户端配置了超时,并在超时溢出时优雅地处理请求。

如往常一样,示例代码可以在GitHub上找到。