

Java HTTP/2 客户端:从阻塞到异步 - sanjeevr
source link: https://www.jdon.com/60487
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Java HTTP/2 客户端:从阻塞到异步
一个HttpClient可以用来通过HTTP访问网络上的任何资源。
在Java 11之前,开发者不得不使用传统的HttpUrlConnection类,它被认为是更抽象的,或者使用第三方库,如Apache HttpClient,或OkHttp。
从JDK11开始,它支持HTTP/1.1和HTTP/2,支持同步和异步编程模型,将请求和响应体作为反应流处理,并遵循熟悉的构建器模式。默认情况下,客户端将使用HTTP/2发送请求。发送到尚不支持HTTP/2的服务器的请求将自动降级为HTTP/1.1。
新的API现在通过CompletableFutures提供非阻塞的请求和响应处理。其他概念,如反压和流量控制,已经通过java.uti.consurrent.Flow API由反应式流提供。
让我们深入了解一下使用Java HTTP客户端执行普通任务的例子和配方。
同步GET
响应主体是一个字符串
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, BodyHandlers.ofString())。 System.out.println(response.body())。 } |
响应体是一个文件
public void get(String uri) throws Exception { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(uri)) .build()。 HttpResponse<Path> response = client.send(request, BodyHandlers.ofFile(Paths.get("body.txt"))。 System.out.println("文件中的响应:" + response.body())。 } |
异步GET
异步API立即返回一个CompletableFuture,当HttpResponse可用时,它就会完成。CompletableFuture是在Java 8中添加的,支持可组合的异步编程。
响应体是一个字符串
public CompletableFuture<String> get(String uri) { HttpClient client = HttpClient.newHttpClient()。 HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(uri)) .build()。 return client.sendAsync(request, BodyHandlers.ofString()) .thenApply(HttpResponse::body)。 } CompletableFuture.thenApply(Function)方法可用于将HttpResponse映射到其body类型、状态代码等。 |
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(BodyPublishers.ofString(data)) .build()。 HttpResponse<?> response = client.send(request, BodyHandlers.discarding())。 System.out.println(response.statusCode())。 } |
上面的例子使用ofString BodyPublisher将给定的字符串转换为请求体字节。
BodyPublisher是一个反应式流发布器,按需发布请求体的流。HttpRequest.Builder有一些允许设置BodyPublisher的方法;Builder::POST, Builder::PUT, 和Builder::method。HttpRequest.BodyPublishers类有一些方便的静态工厂方法,可以为常见的数据类型创建一个BodyPublisher;ofString、ofByteArray、ofFile。
丢弃的BodyHandler可以用来接收和丢弃响应体,当它不感兴趣的时候。
并发请求
结合Java Streams和CompletableFuture API来发出一些请求并等待其响应是很容易的。下面的例子为列表中的每个URI发送了一个GET请求,并将所有的响应存储为字符串。
public void getURIs(List<URI> uris) { HttpClient client = HttpClient.newHttpClient(); List<HttpRequest> requests = uris.stream() .map(HttpRequest::newBuilder) .map(reqBuilder -> reqBuilder.build()) .collect(toList())。 CompletableFuture.allOf(request.stream() .map(request -> client.sendAsync(request, ofString())) .toArray(CompletableFuture<?>[]:new)) .join()。 } |
获取JSON
在许多情况下,响应体将是一些更高级别的格式。可以使用方便的响应体处理程序,同时使用第三方库将响应体转换为该格式。
下面的例子演示了如何使用Jackson库,结合BodyHandlers::ofString,将JSON响应转换为String键/值对的Map。
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, 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); } } |
上面的例子使用ofString,它在内存中积累响应体的字节。另外,也可以使用一个流式订阅器,比如ofInputStream。
POST JSON
在许多情况下,请求体将是一些更高层次的格式。可以使用方便的请求体处理程序,以及一个第三方库,将请求体转换为该格式。
下面的例子演示了如何使用Jackson库,结合BodyPublishers::ofString将String键/值对的Map转换成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(BodyPublishers.ofString(requestBody)) .build(); return HttpClient.newHttpClient() .sendAsync(request, BodyHandlers.ofString()) .thenApply(HttpResponse::statusCode) .thenAccept(System.out::println); } |
设置一个代理
可以通过客户端的Builder::proxy方法在HttpClient上配置一个ProxySelector。ProxySelector API为一个给定的URI返回一个特定的代理。在许多情况下,一个单一的静态代理就足够了。ProxySelector::of static工厂方法可以用来创建这样一个选择器。
响应主体是一个带有指定代理的字符串
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, BodyHandlers.ofString()) .thenApply(HttpResponse::body)。 } |
另外,也可以使用全系统默认的代理选择器,这在macOS上是默认的。
HttpClient.newBuilder() .proxy(ProxySelector.getDefault()) .build(); |
</div
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK