Java 中的新 HTTP 客戶端深入解析
你是否也遇到過這樣的時刻:只是想發(fā)個 HTTP 請求,卻被連接管理、重定向、超時與線程阻塞折騰得不亦樂乎?那就試試 Java 11 正式標準化了全新的 HttpClient,原生支持 HTTP/2、異步與 WebSocket,極大簡化了客戶端網(wǎng)絡(luò)編程。
1. 概覽
本文將介紹 Java 11 對全新 HTTP 客戶端 API(支持 HTTP/2 與 WebSocket) 的標準化。
它旨在替代 JDK 早期就存在的舊類 HttpURLConnection(文檔見:https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/net/HttpURLConnection.html)。
在不久之前,Java 只有較為底層、功能有限且不夠友好的 HttpURLConnection API。因此社區(qū)普遍使用第三方庫,如 Apache HttpClient、Jetty 以及 Spring 的 RestTemplate。
2. 背景
該變更由 JEP 321 引入并最終在 Java 11 中定型。
2.1. JEP 321 的主要變更
- Java 9 的孵化版 HTTP API 已正式并入 Java SE API。新的 HTTP APIs 位于
java.net.http.*。 - 新版本的 HTTP 協(xié)議旨在提升客戶端請求與服務(wù)器響應(yīng)的整體性能,包括多路復用、頭壓縮與推送承諾(push promise)等特性。
- 自 Java 11 起,API 全面支持異步(相比之下,舊的 HTTP/1.1 實現(xiàn)是阻塞式的)。異步以
CompletableFuture實現(xiàn),階段式流水線在前一階段完成后自動銜接執(zhí)行。 - 新的 HTTP 客戶端提供了標準方式執(zhí)行網(wǎng)絡(luò)操作,原生支持現(xiàn)代 Web 能力(如 HTTP/2),無需引入第三方依賴。
- 新 API 原生支持 HTTP/1.1 與 HTTP/2 的 WebSocket。核心類型包括:
HttpClient(java.net.http.HttpClient)HttpRequest(java.net.http.HttpRequest)HttpResponse<T>(java.net.http.HttpResponse)WebSocket(java.net.http.WebSocket)
2.2. Java 11 之前客戶端的問題
舊版 HttpURLConnection 及其實現(xiàn)存在諸多問題:
URLConnection為多個如今已不再使用的協(xié)議(FTP、gopher 等)而設(shè)計;- API 早于 HTTP/1.1,抽象層級不合時宜;
- 僅支持阻塞模式(一次請求/響應(yīng)占用一個線程);
- 維護困難。
3. HTTP Client API 總覽
與 HttpURLConnection 不同,新 HTTP 客戶端同時提供同步與異步兩種請求機制。
API 的三大核心:
HttpRequest:要發(fā)送的請求;HttpClient:跨請求的通用配置容器;HttpResponse:請求的響應(yīng)結(jié)果。
下面分別展開,先從請求開始。
4. HttpRequest
HttpRequest 表示將要發(fā)送的請求,可通過 HttpRequest.newBuilder() 獲取構(gòu)建器;構(gòu)建器提供多種便捷方法配置請求。
注:JDK 16 新增 HttpRequest.newBuilder(HttpRequest request, BiPredicate<String,String> filter),可基于已有請求復制初始狀態(tài),再在構(gòu)建前做修改(如移除部分頭):
HttpRequest.newBuilder(request, (name, value) -> !name.equalsIgnoreCase("Foo-Bar"));4.1. 設(shè)置 URI
可直接用帶 URI 的構(gòu)造方式,或在構(gòu)建器上調(diào)用 uri(URI):
HttpRequest.newBuilder(new URI("https://postman-echo.com/get"));
HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/get"));4.2. 指定 HTTP 方法
構(gòu)建器提供以下方法:
GET()POST(BodyPublisher body)PUT(BodyPublisher body)DELETE()
一個最簡單的 GET 示例:
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/get"))
.GET()
.build();常見的附加參數(shù)包括:HTTP 協(xié)議版本、請求頭與超時。
4.3. 設(shè)置協(xié)議版本
API 默認充分利用 HTTP/2,也可顯式指定:
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/get"))
.version(HttpClient.Version.HTTP_2)
.GET()
.build();注意:若對端不支持 HTTP/2,客戶端會回退到 HTTP/1.1。
4.4. 設(shè)置請求頭
可用 headers(k1,v1,k2,v2,...) 一次性傳入,或多次調(diào)用 header(k,v):
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/get"))
.headers("key1", "value1", "key2", "value2")
.GET()
.build();
HttpRequest request2 = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/get"))
.header("key1", "value1")
.header("key2", "value2")
.GET()
.build();4.5. 設(shè)置超時
默認無窮大。可用 Duration 設(shè)置,超時會拋出 HttpTimeoutException:
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/get"))
.timeout(Duration.ofSeconds(10))
.GET()
.build();5. 設(shè)置請求體
POST(BodyPublisher), PUT(BodyPublisher) 可攜帶請求體(DELETE() 也支持不帶體的刪除)。常用的 BodyPublisher 工廠有:
HttpRequest.BodyPublishers.ofString:基于字符串;HttpRequest.BodyPublishers.ofInputStream:基于輸入流(以Supplier<InputStream>形式延遲創(chuàng)建);HttpRequest.BodyPublishers.ofByteArray:基于字節(jié)數(shù)組;HttpRequest.BodyPublishers.ofFile:基于文件路徑內(nèi)容;- 無請求體:
HttpRequest.BodyPublishers.noBody()。
JDK 16 新增 BodyPublishers.concat(...),可把多個 publisher 的內(nèi)容順序拼接為一個請求體。
5.1. 字符串請求體
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/post"))
.headers("Content-Type", "text/plain;charset=UTF-8")
.POST(HttpRequest.BodyPublishers.ofString("Sample request body"))
.build();5.2. 輸入流請求體
byte[] sampleData = "Sample request body".getBytes();
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/post"))
.headers("Content-Type", "text/plain;charset=UTF-8")
.POST(HttpRequest.BodyPublishers
.ofInputStream(() -> new ByteArrayInputStream(sampleData)))
.build();5.3. 字節(jié)數(shù)組請求體
byte[] sampleData = "Sample request body".getBytes();
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/post"))
.headers("Content-Type", "text/plain;charset=UTF-8")
.POST(HttpRequest.BodyPublishers.ofByteArray(sampleData))
.build();5.4. 文件請求體
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/post"))
.headers("Content-Type", "text/plain;charset=UTF-8")
.POST(HttpRequest.BodyPublishers.ofFile(
Paths.get("src/test/resources/sample.txt")))
.build();6. HttpClient
所有請求都由 HttpClient 發(fā)送,可通過 HttpClient.newBuilder() 或 HttpClient.newHttpClient() 獲取。下面看幾個常用能力。
6.1. 處理響應(yīng)體
新的 BodyHandlers 工廠提供常見類型的響應(yīng)體處理器:
BodyHandlers.ofByteArray; BodyHandlers.ofString; BodyHandlers.ofFile; BodyHandlers.discarding; BodyHandlers.replacing; BodyHandlers.ofLines; BodyHandlers.fromLineSubscriber;
Java 11 之前:
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandler.asString());
現(xiàn)在可簡化為:
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
6.2. 設(shè)置代理
HttpResponse<String> response = HttpClient .newBuilder() .proxy(ProxySelector.getDefault()) .build() .send(request, BodyHandlers.ofString());
6.3. 跟隨重定向策略
HttpResponse<String> response = HttpClient.newBuilder() .followRedirects(HttpClient.Redirect.ALWAYS) .build() .send(request, BodyHandlers.ofString());
6.4. 認證器(Authenticator)
HttpResponse<String> response = HttpClient.newBuilder()
.authenticator(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(
"username",
"password".toCharArray());
}
})
.build()
.send(request, BodyHandlers.ofString());6.5. 同步與異步發(fā)送
- 同步:
send(...)(阻塞直到響應(yīng)返回) - 異步:
sendAsync(...)(立即返回CompletableFuture<HttpResponse<T>>)
同步示例:
HttpResponse<String> response = HttpClient.newBuilder() .build() .send(request, BodyHandlers.ofString());
異步示例:
CompletableFuture<HttpResponse<String>> response = HttpClient.newBuilder() .build() .sendAsync(request, HttpResponse.BodyHandlers.ofString());
批量并發(fā)請求:
List<URI> targets = Arrays.asList(
new URI("https://postman-echo.com/get?foo1=bar1"),
new URI("https://postman-echo.com/get?foo2=bar2"));
HttpClient client = HttpClient.newHttpClient();
List<CompletableFuture<String>> futures = targets.stream()
.map(target -> client
.sendAsync(
HttpRequest.newBuilder(target).GET().build(),
HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body))
.collect(Collectors.toList());6.6. 指定異步執(zhí)行器(Executor)
ExecutorService executorService = Executors.newFixedThreadPool(2); CompletableFuture<HttpResponse<String>> response1 = HttpClient.newBuilder() .executor(executorService) .build() .sendAsync(request, HttpResponse.BodyHandlers.ofString()); CompletableFuture<HttpResponse<String>> response2 = HttpClient.newBuilder() .executor(executorService) .build() .sendAsync(request, HttpResponse.BodyHandlers.ofString());
默認執(zhí)行器為 Executors.newCachedThreadPool()。
6.7. CookieHandler
設(shè)置客戶端級 CookieHandler:
HttpClient.newBuilder() .cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_NONE)) .build();
若允許存儲 Cookie,可從 CookieManager 讀?。?/p>
((CookieManager) httpClient.cookieHandler().get()).getCookieStore();
7. HttpResponse
HttpResponse 表示服務(wù)端響應(yīng),核心方法:
statusCode():返回整型狀態(tài)碼;body():返回響應(yīng)體(類型取決于發(fā)送時的BodyHandler)。
其他常用方法還包括 uri()、headers()、trailers() 與 version()。
7.1. 響應(yīng)的 URI
由于重定向,響應(yīng)返回的 uri() 可能與請求不同:
assertThat(request.uri().toString(), equalTo("http://stackoverflow.com"));
assertThat(response.uri().toString(), equalTo("https://stackoverflow.com/"));7.2. 響應(yīng)頭
HttpResponse<String> response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); HttpHeaders responseHeaders = response.headers();
7.3. 響應(yīng)協(xié)議版本
即使請求設(shè)置為 HTTP/2,服務(wù)端也可能以 HTTP/1.1 響應(yīng),實際版本可從響應(yīng)讀?。?/p>
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/get"))
.version(HttpClient.Version.HTTP_2)
.GET()
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
assertThat(response.version(), equalTo(HttpClient.Version.HTTP_1_1));8. HTTP/2 推送承諾(Push Promise)
新的 HttpClient 通過 PushPromiseHandler 支持服務(wù)端主動推送。當客戶端請求主資源時,服務(wù)器可以同時“推送”額外資源,從而減少往返次數(shù)、加快頁面渲染。該能力得益于 HTTP/2 的多路復用。
如有推送承諾,將由提供的 PushPromiseHandler 處理;若傳入 null,則拒絕所有推送。
HttpClient 的重載 sendAsync 可用于處理 push promise。先定義處理器:
private static PushPromiseHandler<String> pushPromiseHandler() {
return (HttpRequest initiatingRequest,
HttpRequest pushPromiseRequest,
Function<HttpResponse.BodyHandler<String>,
CompletableFuture<HttpResponse<String>>> acceptor) -> {
acceptor.apply(BodyHandlers.ofString())
.thenAccept(resp -> {
System.out.println("Pushed response: " + resp.uri() + ", headers: " + resp.headers());
});
System.out.println("Promise request: " + pushPromiseRequest.uri());
System.out.println("Promise request headers: " + pushPromiseRequest.headers());
};
}再用 sendAsync 消費它:
httpClient.sendAsync(pageRequest, BodyHandlers.ofString(), pushPromiseHandler())
.thenAccept(pageResponse -> {
System.out.println("Page response status code: " + pageResponse.statusCode());
System.out.println("Page response headers: " + pageResponse.headers());
String responseBody = pageResponse.body();
System.out.println(responseBody);
})
.join();9. 總結(jié)
本文探討了 Java 11 中標準化后的 HttpClient API:在保留易用性的同時,引入了 HTTP/2、異步、推送承諾、代理、重定向策略、認證器、Cookie 管理等現(xiàn)代化能力,讓 Java 的 HTTP 編程更高效、更現(xiàn)代。
到此這篇關(guān)于探索 Java 中的新 HTTP 客戶端的文章就介紹到這了,更多相關(guān)Java HTTP 客戶端內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot整合Mybatis和SQLite的詳細過程
這篇文章主要介紹了Springboot整合Mybatis和SQLite的詳細過程,本文通過圖文示例相結(jié)合給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧2024-07-07
使用IDEA啟動項目build時出現(xiàn)OOM相關(guān)異常的解決
這篇文章主要介紹了使用IDEA啟動項目build時出現(xiàn)OOM相關(guān)異常的解決,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-06-06
Spring使用AspectJ的注解式實現(xiàn)AOP面向切面編程
這篇文章主要介紹了Spring使用AspectJ的注解式實現(xiàn)AOP面向切面編程的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06
spring boot使用i18n時properties文件中文亂碼問題的解決方法
這篇文章主要介紹了spring boot使用i18n時properties文件中文亂碼問題的解決方法,需要的朋友可以參考下2017-11-11
Java 實現(xiàn)簡單靜態(tài)資源Web服務(wù)器的示例
這篇文章主要介紹了Java 實現(xiàn)簡單靜態(tài)資源Web服務(wù)器的示例,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下2020-11-11

