如何/在哪里检查 java.net.http.HttpClient HTTP 响应状态代码

How / where to check for java.net.http.HttpClient HTTP response status codes

Java 11 的 java.net.http.HttpClient 似乎不检查 HTTP 状态代码,除了重定向(如果启用)。 wiki 和 Java API 文档中的所有示例始终假定 HTTP 请求成功,即它们似乎从不检查响应的状态代码。

这很可能永远不是期望的行为,因为来自 HTTP 500(服务器错误)响应的错误页面可能具有不同的格式或没有格式,因此应用程序无法处理。

应该在哪里检查 HTTP 状态代码?

documentation of HttpResponse.BodyHandler 包含以下示例片段:

BodyHandler<Path> bodyHandler = (rspInfo) -> rspInfo.statusCode() == 200
    ? BodySubscribers.ofFile(Paths.get("/tmp/f"))
    : BodySubscribers.replacing(Paths.get("/NULL"));

但是,您必须检查两次状态代码,一次是在上面显示的 BodyHandler 中,一次是在处理响应时(因为尝试从 "/NULL" 读取正文会失败)。

对我来说,仅在 BodyHandler 中执行 HTTP 状态代码检查似乎是最合理的,例如:

BodyHandler<Path> bodyHandler = (rspInfo) -> {
    if (rspInfo.statusCode() == 200) {
        return BodySubscribers.ofFile(Paths.get("/tmp/f"));
    } else {
        throw new RuntimeException("Request failed");
    }
};

但是,BodyHandler 文档没有提到是否允许抛出异常,或者 HttpClient 在这种情况下会如何表现。

令我惊讶的是,JDK 似乎没有提供开箱即用的处理不成功 HTTP 响应的功能,或者我忽略了什么?

HttpClient 试图很好地捕获用户代码抛出的异常 - 但这不是处理非 200 状态的推荐方法。您将依赖未指定的行为(尽管它可能会按照您的预期进行)。

如果您想 return 状态为 != 200 的例外情况,那么我的建议是编写一个正文订阅者:

  1. Return 一个失败的 CompletionStage(如果有异常,您本可以异常完成)
  2. 取消订阅(或转发给 BodySubscribers.discarding() 订阅者)

另一方面 - 如果你想要一个不同的结果类型用于状态!= 200 你可以写一个 BodyHandler 那 returns 一个元组(响应,错误)其中响应是是一种类型,错误是另一种类型。像这样:

record Response<R,T>(R response, T error) {}
static class ErrorBodyHandler<R,T> implements BodyHandler<Response<R,T>> {
    final BodyHandler<R> responseHandler;
    final BodyHandler<T> errorHandler;
    public ErrorBodyHandler(BodyHandler<R> responseHandler, BodyHandler<T> errorHandler) {
        this.responseHandler = responseHandler;
        this.errorHandler = errorHandler;
    }
    @Override
    public BodySubscriber<Response<R, T>> apply(ResponseInfo responseInfo) {
        if (responseInfo.statusCode() == 200) {
            return BodySubscribers.mapping(responseHandler.apply(responseInfo),
                    (r) -> new Response<>(r, null));
        } else {
            return BodySubscribers.mapping(errorHandler.apply(responseInfo),
                    (t) -> new Response<>(null, t));
        }
    }
}

public static void main(String[] args) throws Exception {
    var client = HttpClient.newHttpClient();
    var handler = new ErrorBodyHandler<>(BodyHandlers.ofFileDownload(Path.of(".")),
            BodyHandlers.ofString());
    var request = HttpRequest
            .newBuilder(URI.create("http://host:port/"))
            .build();
    var httpResponse = 
            client.send(request, handler);
    if (httpResponse.statusCode() == 200) {
        Path path = httpResponse.body().response();
    } else {
        String error = httpResponse.body().error();
    }
}