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