簡體   English   中英

如何/在哪里檢查 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 狀態碼?

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 狀態的推薦方法。 您將依賴未指定的行為(盡管它可能會達到您的預期)。

如果您想在狀態為 = 200: 的情況下返回異常:那么我的建議是編寫一個正文訂閱者:

  1. 返回一個失敗的 CompletionStage (你會在你的異常情況下異常完成)
  2. 取消訂閱(或將其轉發給 BodySubscribers.discarding() 訂閱者)

另一方面 - 如果您想要在 status != 200 的情況下使用不同的結果類型,您可以編寫一個返回元組 (response, error) 的BodyHandler ,其中 response 是一種類型,而 error 是另一種類型。 像這樣的東西:

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();
    }
}

我很驚訝 JDK 文檔在這樣一個常見且必要的用例上如此含糊:正確檢測 HTTP 請求的失敗。

閱讀 API 文檔后,仔細閱讀此處的討論,並深入研究源代碼; 我已經確定適當的邏輯可能像這樣簡單:

final BodyHandler<Path> bodyHandler = responseInfo -> {
  final BodySubscriber<Path> bodySubscriber;
  final int statusCode = responseInfo.statusCode();
  if(statusCode == 200) {
    bodySubscriber = BodySubscribers.ofFile(toFile);
  } else {
    bodySubscriber = BodySubscribers.replacing(null);
    bodySubscriber.onError(new IOException("HTTP request failed with response status code "
        + statusCode + "."));
  }
  return bodySubscriber;
};
getHttpClient().send(httpRequest, bodyHandler);

這看起來對嗎? 我是否正確使用了 API? 基本上我只想丟棄正文,如果有人要求返回null Path (表明正文未保存在文件中),並導致完成階段失敗。

(JDK 確實需要一種更好的方法來解決這個問題,至少包括一些預制類。)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM