JDK11 JEP321: HTTP Client (Standard) 詳解 + demo
一·背景描述
從jdk9開始引入HTTP Client 標準化,根據使用者的反饋在jdk10開始更新,有了顯著的改進,使用方式基本保持不變。通過CompletableFutures提供了非阻塞請求和響應式。流量控制可以在java.util.concurrent.Flow API 提供支援。 在jdk9和jdk時進行時幾乎完全重寫了實現,實現了完全非同步,以前的http1.1實現是阻塞的,RX Flow概念的使用已經實現,現在可以更容易的跟蹤資料流:從使用者請求釋出者和響應訂閱者,一直到底層套接字。顯著的減少了程式碼的複雜性,並最大化了HTTP/1.1和HTTP/2之間重用的可能性。
二·demo
1·Synchronous Get :
(1)Response body as a String
public void get(String uri) throws Exception { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(uri)) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); }
上面的例項使用HttpResponse.BodyHandlers.ofString() 將響應位元組轉換為字串,我們必須為每一個HttpRequest提供一個HttpResponse.BodyHandler,一旦response的的頭和狀態碼可用就會在收到response位元組之前呼叫BodyHandlers,BodyHandler負責建立BodySubscriber,它是一個響應流的訂閱者,BodySubscriber負責將接受到的位元組轉換為更高階的java型別。
(2)Response body as a File
public void getToFile(String uri) throws Exception { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(uri)) .build(); HttpResponse<Path> response = client.send(request, HttpResponse.BodyHandlers.ofFile(Paths.get("body.txt"))); System.out.println("Response in file:" + response.body()); }
HttpResponse為建立BodyHandler提供了很多方便的靜態方法,有一些響應式的位元組會在記憶體中一直累計,直到完全接收,然後將其轉換為更高階的java型別,例如HttpResponse.BodyHandlers.ofByteArray()和HttpResponse.BodyHandlers.ofString(),另一些則在完全接收資料後HttpResponse.BodyHandlers.ofFile(),HttpResponse.BodyHandlers.ofByteArrayConsumer(),HttpResponse.BodyHandlers.ofInputStream(),當然你也可以自定義處理方式,如 轉換為JSON物件。
2·Asynchronous Get
非同步的api理解返回一個CompletableFuture,當HttpResponse接收完時可以使用它,在java8時開始支援,並支援非同步程式設計。
(1)Response body as a String
public CompletableFuture<String> getCompletableFuture(String uri) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.build();
return client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body);
}
CompletableFuture.thenApply(Function)方法可以將HttpResponse對映到它的實體型別,狀態碼等。
(2)Response body as a File
public CompletableFuture<Path> getCompletableFuturePath(String uri) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.build();
return client.sendAsync(request, HttpResponse.BodyHandlers.ofFile(Paths.get("body.txt")))
.thenApply(HttpResponse::body);
}
3·Post
請求的資料由HttpRequest.BodyPublisher提供。
public void post(String uri, String data) throws Exception {
HttpClient client = HttpClient.newBuilder().build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.POST(HttpRequest.BodyPublishers.ofString(data))
.build();
HttpResponse<?> response = client.send(request, HttpResponse.BodyHandlers.discarding());
System.out.println(response.statusCode());
}
上面的示例,通過使用BodyPublisher.fromString將字串轉為請求需要的位元組。BodyPublisher是一個響應流的釋出者,HttpRequest.Builder 支援 POST PUT 等方法。
4·Concurrent Requests
我們很容易結合 Java Streams 和CompletableFuture 來併發請求,並等待他們的響應結果,下面的示例為list中每個uri傳送請求,並將請求轉換為字串。
public void testConcurrentRequests(){
HttpClient client = HttpClient.newHttpClient();
List<String> urls = List.of("http://www.baidu.com","http://www.alibaba.com/","http://www.tencent.com");
List<HttpRequest> requests = urls.stream()
.map(url -> HttpRequest.newBuilder(URI.create(url)))
.map(reqBuilder -> reqBuilder.build())
.collect(Collectors.toList());
List<CompletableFuture<HttpResponse<String>>> futures = requests.stream()
.map(request -> client.sendAsync(request, HttpResponse.BodyHandlers.ofString()))
.collect(Collectors.toList());
futures.stream()
.forEach(e -> e.whenComplete((resp,err) -> {
if(err != null){
err.printStackTrace();
}else{
System.out.println(resp.body());
System.out.println(resp.statusCode());
}
}));
CompletableFuture.allOf(futures
.toArray(CompletableFuture<?>[]::new))
.join();
}
5·Get JSON
很多情況下,響應結果 是更高階的格式(json),可以使用一些第三方JSON工具類把響應結果做轉換。
public CompletableFuture<Map<String,String>> JSONBodyAsMap(URI uri) {
UncheckedObjectMapper objectMapper = new UncheckedObjectMapper();
HttpRequest request = HttpRequest.newBuilder(uri)
.header("Accept", "application/json")
.build();
return HttpClient.newHttpClient()
.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenApply(objectMapper::readValue);
}
class UncheckedObjectMapper extends com.fasterxml.jackson.databind.ObjectMapper {
/** Parses the given JSON string into a Map. */
Map<String,String> readValue(String content) {
try {
return this.readValue(content, new TypeReference<>(){});
} catch (IOException ioe) {
throw new CompletionException(ioe);
}
}
6·Post JSON 很多情況下我們提交的請求提是JSON格式,我們通過使用BodyPublisher::fromString + 第三方JSON工具 將請求資料key/value 對映為JSON.
public CompletableFuture<Void> postJSON(URI uri, Map<String, String> map)
throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
String requestBody = objectMapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(map);
HttpRequest request = HttpRequest.newBuilder(uri)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
return HttpClient.newHttpClient()
.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::statusCode)
.thenAccept(System.out::println);
}
7·Setting a Proxy 我們可以通過ProxySelector在HttpClient配置一個proxy。
public CompletableFuture<String> get(String uri) {
HttpClient client = HttpClient.newBuilder()
.proxy(ProxySelector.of(new InetSocketAddress("www-proxy.com", 8080)))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(uri))
.build();
return client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body);
}
也可以使用系統預設的ip代理
HttpClient.newBuilder()
.proxy(ProxySelector.getDefault())
.build();