1. 程式人生 > >JDK11 JEP321: HTTP Client (Standard) 詳解 + demo

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