使用 Java 11 HTTP Client API 實現 HTTP/2 伺服器推送
對 HttpUrlConnection 你還有印象嗎?JDK 11為 HttpUrlConnection 重新設計了 HTTP Client API。HTTP Client API 使用簡單,支援 HTTP/2(預設)和 HTTP/1.1。為了向後相容,當伺服器不支援 HTTP/2時,HTTP Client API 會自動從 HTTP/2 降到 HTTP1.1。
此外,HTTP Client API 支援同步和非同步程式設計模型,並依靠 stream 傳輸資料(reactive stream)。它還支援 WebSocket 協議,用於實時 Web 應用程式,降低客戶端與伺服器間通訊開銷。
除了多路複用(Multiplexing),HTTP/2 另一個強大的功能是 伺服器推送 。傳統方法(HTTP/1.1)中,主要透過瀏覽器發起請求 HTML 頁面,解析接收的標記(Markup)並標識引用的資源(例如JS、CSS、影像等)。
為了獲取資源,瀏覽器會繼續傳送資源請求(每個資源一個請求)。相反,HTTP/2 會傳送 HTML 頁面和引用的資源,不需要瀏覽器主動請求。因此,瀏覽器請求 HTML 頁面後,就能收到頁面以及顯示所需的所有其他資訊。HTTP Client API 透過 PushPromiseHandler 介面支援 HTTP/2 功能。
介面實現必須作為 send() 或 sendAsync() 方法的第三個引數填入。PushPromiseHandler 依賴下面三項協同:
-
客戶端發起的 send request(initiatingRequest)
-
合成 push request(pushPromiseRequest)
-
acceptor 函式,必須成功呼叫該函式才能接受 push promise(acceptor)
呼叫特定 acceptor 函式接受 push promise。acceptor 函式必須傳入一個 BodyHandler(不能為 null)用來處理 Promise 的 request body。acceptor 函式會返回一個 CompletableFuture 例項,完成 promise response。
基於以上資訊,看一下 PushPromiseHandler 實現:
private
static final List<CompletableFuture<Void>>
asyncPushRequests =
new CopyOnWriteArrayList<>();
...
private
static HttpResponse.PushPromiseHandler<String> pushPromiseHandler() {
return (HttpRequest initiatingRequest,
HttpRequest pushPromiseRequest,
Function<HttpResponse.BodyHandler<String> ,
CompletableFuture<HttpResponse<String>>> acceptor) -> {
CompletableFuture<Void> pushcf =
acceptor.apply(HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept((b) -> System.out.println(
"\nPushed resource body:\n " + b));
asyncPushRequests.add(pushcf);
System.out.println(
"\nJust got promise push number: " +
asyncPushRequests.size());
System.out.println(
"\nInitial push request: " +
initiatingRequest.uri());
System.out.println(
"Initial push headers: " +
initiatingRequest.headers());
System.out.println(
"Promise push request: " +
pushPromiseRequest.uri());
System.out.println(
"Promise push headers: " +
pushPromiseRequest.headers());
};
}
現在,觸發一個 request 把 PushPromiseHandler 傳給 sendAsync():
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(
"))
.build();
client.sendAsync(request,
HttpResponse.BodyHandlers.ofString(), pushPromiseHandler())
.thenApply(HttpResponse::body)
.thenAccept((b) -> System.out.println(
"\nMain resource:\n" + b))
.join();
asyncPushRequests.forEach(CompletableFuture::join);
System.out.println(
"\nFetched a total of " +
asyncPushRequests.size() +
" push requests");
完整原始碼可在 GitHub 上找到。
github.com/PacktPublishing/Java-Coding-Problems/tree/master/Chapter13/P268_ServerPush
如果要把所有 push promise 及 response 彙總到指定的 map 中,可以使用 PushPromiseHandler.of() 方法,如下所示:
private
static
final ConcurrentMap<HttpRequest,
CompletableFuture<HttpResponse<String>>> promisesMap
=
new
ConcurrentHashMap<>();
private
static
final Function<HttpRequest,
HttpResponse.BodyHandler<String>> promiseHandler
= (HttpRequest req) -> HttpResponse.BodyHandlers.ofString();
public
static
void
main
(String[] args)
throws IOException, InterruptedException
{
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(
"
))
.build();
client.sendAsync(request,
HttpResponse.BodyHandlers.ofString(), pushPromiseHandler())
.thenApply(HttpResponse::body)
.thenAccept((b) -> System.out.println(
"\nMain resource:\n"
+ b))
.join();
function(){ //外匯跟單
System.out.println(
"\nPush promises map size: "
+
promisesMap.size() +
"\n"
);
promisesMap.entrySet().forEach((entry) -> {
System.out.println(
"Request = "
+ entry.getKey() +
", \nResponse = "
+ entry.getValue().join().body());
});
}
private
static
HttpResponse.PushPromiseHandler<String> pushPromiseHandler() {
return
HttpResponse.PushPromiseHandler.of(promiseHandler, promisesMap);
}
完整原始碼可在 GitHub 上找到。
github.com/PacktPublishing/Java-Coding-Problems/tree/master/Chapter13/P268_ServerPushToMap
前面兩個解決方案中 BodyHandler 都用到了 String 型別的 ofString()。如果伺服器還需要推送二進位制資料(比如影像),就不是很適用。因此,如果要處理二進位制資料,則需要用 ofByteArray() 切換到byte[] 型別的 BodyHandler。也可以用 ofFile() 把 push 資源儲存到磁碟,下面的解決方案是之前方案的改進版:
private
static final ConcurrentMap<HttpRequest,
CompletableFuture<HttpResponse<Path>>>
promisesMap =
new ConcurrentHashMap<>();
private
static final Function<HttpRequest,
HttpResponse.BodyHandler<Path>> promiseHandler
= (HttpRequest req) -> HttpResponse.BodyHandlers.ofFile(
Paths.get(req.uri().getPath()).getFileName());
public
static
void
main
(String[] args)
throws IOException, InterruptedException {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(
"))
.build();
client.sendAsync(request, HttpResponse.BodyHandlers.ofFile(
Path.of(
"index.html")), pushPromiseHandler())
.thenApply(HttpResponse::body)
.thenAccept((b) -> System.out.println(
"\nMain resource:\n" + b))
.join();
System.out.println(
"\nPush promises map size: " +
promisesMap.size() +
"\n");
promisesMap.entrySet().forEach((entry) -> {
System.out.println(
"Request = " + entry.getKey() +
", \nResponse = " + entry.getValue().join().body());
});
}
private
static HttpResponse.PushPromiseHandler<Path> pushPromiseHandler() {
return HttpResponse.PushPromiseHandler.of(promiseHandler, promisesMap);
}
上面的程式碼把 push 資源儲存到應用程式 classpath 中, 完整原始碼可在 GitHub 上找到。
github.com/PacktPublishing/Java-Coding-Problems/tree/master/Chapter13/P268_ServerPushToDisk
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69946337/viewspace-2662056/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- HTTP/2 伺服器推送(Server Push)教程HTTP伺服器Server
- HTTP/2之伺服器推送(Server Push)最佳實踐HTTP伺服器Server
- netty系列之:使用netty實現支援http2的伺服器NettyHTTP伺服器
- SAP ABAP Gateway Client 的 ABAP 實現,重用 HTTP ConnectionGatewayclientHTTP
- Go的http clientGoHTTPclient
- Java實現Http請求JavaHTTP
- Golang 使用http Client下載檔案GolangHTTPclient
- 構建api gateway之 http路由實現APIGatewayHTTP路由
- Golang如何實現HTTP代理伺服器GolangHTTP伺服器
- Go標準包—http clientGoHTTPclient
- 在Java中,使用HttpUtils實現傳送HTTP請求JavaHTTP
- 使用 HTTP2 做開發伺服器 (上)HTTP伺服器
- 【轉載】CL_HTTP_CLIENT的HTTP和SOAP用法示例HTTPclient
- 使用Netty實現HTTP2伺服器/客戶端的原始碼和教程 - BaeldungNettyHTTP伺服器客戶端原始碼
- Netty(二) 實現簡單Http伺服器NettyHTTP伺服器
- Netty實現Http高效能伺服器NettyHTTP伺服器
- 使用Java Socket手擼一個http伺服器JavaHTTP伺服器
- Java 用jetty實現HTTP伺服器 獲取 Get 請求體JavaJettyHTTP伺服器
- Elasticsearch 入門實戰(9)--Java API Client 使用二ElasticsearchJavaAPIclient
- [譯] Node.js 能進行 HTTP/2 推送啦!Node.jsHTTP
- HTTP協議之:HTTP/1.1和HTTP/2HTTP協議
- HTTP、HTTP1.1、HTTP/2的區別HTTP
- Grafana 系列文章(三):Tempo-使用 HTTP 推送 SpansGrafanaHTTP
- PHP 實現 HTTP 表單請求伺服器PHPHTTP伺服器
- 用 Python 快速實現 HTTP 和 FTP 伺服器PythonHTTPFTP伺服器
- Node.js實現一個HTTP伺服器Node.jsHTTP伺服器
- 使用Python搭建http伺服器PythonHTTP伺服器
- NGINX使用rewrite實現http 跳轉 httpsNginxHTTP
- Java9系列第九篇-對HTTP2協議的支援與非阻塞HTTP-APIJavaHTTP協議API
- ABP Framework Consuming HTTP APIs from a .NET ClientFrameworkHTTPAPIclient
- 【筆試題解】http-client-module筆試HTTPclient
- 微服務實戰SpringCloud之Spring Cloud Feign替代HTTP Client微服務SpringGCCloudHTTPclient
- Fetch API HTTP請求實用指南APIHTTP
- http2與http1HTTP
- netty系列之:搭建客戶端使用http1.1的方式連線http2伺服器Netty客戶端HTTP伺服器
- HTTP1.1、HTTP2、HTTP3 演變HTTP
- 關於 SAP ABAP CL_HTTP_CLIENT API 中的 SSL_ID 引數HTTPclientAPI
- 開啟 Fidder 出現 Referer: http://fiddler2.com/client/5.0.20204.45441 是什麼問題呢?HTTPclient