网络编程Java 11标准化的HTTP Client

举报
xcc-2022 发表于 2022/07/21 22:27:51 2022/07/21
【摘要】 一·背景描述从jdk9开始引入HTTP Client 标准化,根据用户的反馈在jdk10开始更新,有了显著的改进,使用方式基本保持不变。通过CompletableFutures提供了非阻塞请求和响应式。流量控制可以在java.util.concurrent.Flow API 提供支持。在jdk9和jdk10时进行时几乎完全重写了实现,实现了完全异步,以前的http1.1实现是阻塞的,RX F...

一·背景描述

从jdk9开始引入HTTP Client 标准化,根据用户的反馈在jdk10开始更新,有了显著的改进,使用方式基本保持不变。通过CompletableFutures提供了非阻塞请求和响应式。流量控制可以在java.util.concurrent.Flow API 提供支持。
在jdk9和jdk10时进行时几乎完全重写了实现,实现了完全异步,以前的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();
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。