您好,登錄后才能下訂單哦!
Java11的HttpClient的特性是什么,針對(duì)這個(gè)問(wèn)題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問(wèn)題的小伙伴找到更簡(jiǎn)單易行的方法。
變化
從java9的jdk.incubator.httpclient模塊遷移到j(luò)ava.net.http模塊,包名由jdk.incubator.http改為java.net.http
原來(lái)的諸如HttpResponse.BodyHandler.asString()方法變更為HttpResponse.BodyHandlers.ofString(),變化一為BodyHandler改為BodyHandlers,變化二為asXXX()之類的方法改為ofXXX(),由as改為of
實(shí)例
設(shè)置超時(shí)時(shí)間
@Test public void testTimeout() throws IOException, InterruptedException { //1.set connect timeout HttpClient client = HttpClient.newBuilder() .connectTimeout(Duration.ofMillis(5000)) .followRedirects(HttpClient.Redirect.NORMAL) .build(); //2.set read timeout HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("http://openjdk.java.net/")) .timeout(Duration.ofMillis(5009)) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); }
HttpConnectTimeoutException實(shí)例
Caused by: java.net.http.HttpConnectTimeoutException: HTTP connect timed out at java.net.http/jdk.internal.net.http.ResponseTimerEvent.handle(ResponseTimerEvent.java:68) at java.net.http/jdk.internal.net.http.HttpClientImpl.purgeTimeoutsAndReturnNextDeadline(HttpClientImpl.java:1248) at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.run(HttpClientImpl.java:877) Caused by: java.net.ConnectException: HTTP connect timed out at java.net.http/jdk.internal.net.http.ResponseTimerEvent.handle(ResponseTimerEvent.java:69) ... 2 more
HttpTimeoutException實(shí)例
java.net.http.HttpTimeoutException: request timed out at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:559) at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:119) at com.example.HttpClientTest.testTimeout(HttpClientTest.java:40)
設(shè)置authenticator
@Test public void testBasicAuth() throws IOException, InterruptedException { HttpClient client = HttpClient.newBuilder() .connectTimeout(Duration.ofMillis(5000)) .authenticator(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication("admin","password".toCharArray()); } }) .build(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("http://localhost:8080/json/info")) .timeout(Duration.ofMillis(5009)) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.statusCode()); System.out.println(response.body()); }
authenticator可以用來(lái)設(shè)置HTTP authentication,比如Basic authentication
雖然Basic authentication也可以自己設(shè)置header,不過(guò)通過(guò)authenticator省得自己去構(gòu)造header
設(shè)置header
@Test public void testCookies() throws IOException, InterruptedException { HttpClient client = HttpClient.newBuilder() .connectTimeout(Duration.ofMillis(5000)) .build(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("http://localhost:8080/json/cookie")) .header("Cookie","JSESSIONID=4f994730-32d7-4e22-a18b-25667ddeb636; userId=java11") .timeout(Duration.ofMillis(5009)) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.statusCode()); System.out.println(response.body()); }
通過(guò)request可以自己設(shè)置header
GET
同步
@Test public void testSyncGet() throws IOException, InterruptedException { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://www.baidu.com")) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); }
異步
@Test public void testAsyncGet() throws ExecutionException, InterruptedException { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://www.baidu.com")) .build(); CompletableFuture<String> result = client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenApply(HttpResponse::body); System.out.println(result.get()); }
POST表單
@Test public void testPostForm() throws IOException, InterruptedException { HttpClient client = HttpClient.newBuilder().build(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("http://www.w3school.com.cn/demo/demo_form.asp")) .header("Content-Type","application/x-www-form-urlencoded") .POST(HttpRequest.BodyPublishers.ofString("name1=value1&name2=value2")) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.statusCode()); }
header指定內(nèi)容是表單類型,然后通過(guò)BodyPublishers.ofString傳遞表單數(shù)據(jù),需要自己構(gòu)建表單參數(shù)
POST JSON
@Test public void testPostJsonGetJson() throws ExecutionException, InterruptedException, JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); StockDto dto = new StockDto(); dto.setName("hj"); dto.setSymbol("hj"); dto.setType(StockDto.StockType.SH); String requestBody = objectMapper .writerWithDefaultPrettyPrinter() .writeValueAsString(dto); HttpRequest request = HttpRequest.newBuilder(URI.create("http://localhost:8080/json/demo")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(requestBody)) .build(); CompletableFuture<StockDto> result = HttpClient.newHttpClient() .sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenApply(HttpResponse::body) .thenApply(body -> { try { return objectMapper.readValue(body,StockDto.class); } catch (IOException e) { return new StockDto(); } }); System.out.println(result.get()); }
post json的話,body自己json化為string,然后header指定是json格式
文件上傳
@Test public void testUploadFile() throws IOException, InterruptedException, URISyntaxException { HttpClient client = HttpClient.newHttpClient(); Path path = Path.of(getClass().getClassLoader().getResource("body.txt").toURI()); File file = path.toFile(); String multipartFormDataBoundary = "Java11HttpClientFormBoundary"; org.apache.http.HttpEntity multipartEntity = MultipartEntityBuilder.create() .addPart("file", new FileBody(file, ContentType.DEFAULT_BINARY)) .setBoundary(multipartFormDataBoundary) //要設(shè)置,否則阻塞 .build(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("http://localhost:8080/file/upload")) .header("Content-Type", "multipart/form-data; boundary=" + multipartFormDataBoundary) .POST(HttpRequest.BodyPublishers.ofInputStream(() -> { try { return multipartEntity.getContent(); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e); } })) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); }
官方的HttpClient并沒(méi)有提供類似WebClient那種現(xiàn)成的BodyInserters.fromMultipartData方法,因此這里需要自己轉(zhuǎn)換
這里使用org.apache.httpcomponents(httpclient及httpmime)的MultipartEntityBuilder構(gòu)建multipartEntity,最后通過(guò)HttpRequest.BodyPublishers.ofInputStream來(lái)傳遞內(nèi)容
這里header要指定Content-Type值為multipart/form-data以及boundary的值,否則服務(wù)端可能無(wú)法解析
文件下載
@Test public void testAsyncDownload() throws ExecutionException, InterruptedException { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("http://localhost:8080/file/download")) .build(); CompletableFuture<Path> result = client.sendAsync(request, HttpResponse.BodyHandlers.ofFile(Paths.get("/tmp/body.txt"))) .thenApply(HttpResponse::body); System.out.println(result.get()); }
使用HttpResponse.BodyHandlers.ofFile來(lái)接收文件
并發(fā)請(qǐng)求
@Test 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(); }
sendAsync方法返回的是CompletableFuture,可以方便地進(jìn)行轉(zhuǎn)換、組合等操作
這里使用CompletableFuture.allOf組合在一起,最后調(diào)用join等待所有future完成
錯(cuò)誤處理
@Test public void testHandleException() throws ExecutionException, InterruptedException { HttpClient client = HttpClient.newBuilder() .connectTimeout(Duration.ofMillis(5000)) .build(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://twitter.com")) .build(); CompletableFuture<String> result = client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) // .whenComplete((resp,err) -> { // if(err != null){ // err.printStackTrace(); // }else{ // System.out.println(resp.body()); // System.out.println(resp.statusCode()); // } // }) .thenApply(HttpResponse::body) .exceptionally(err -> { err.printStackTrace(); return "fallback"; }); System.out.println(result.get()); }
HttpClient異步請(qǐng)求返回的是CompletableFuture<HttpResponse<T>>,其自帶exceptionally方法可以用來(lái)做fallback處理
另外值得注意的是HttpClient不像WebClient那樣,它沒(méi)有對(duì)4xx或5xx的狀態(tài)碼拋出異常,需要自己根據(jù)情況來(lái)處理,手動(dòng)檢測(cè)狀態(tài)碼拋出異?;蛘叻祷仄渌麅?nèi)容
HTTP2
@Test public void testHttp2() throws URISyntaxException { HttpClient.newBuilder() .followRedirects(HttpClient.Redirect.NEVER) .version(HttpClient.Version.HTTP_2) .build() .sendAsync(HttpRequest.newBuilder() .uri(new URI("https://http2.akamai.com/demo")) .GET() .build(), HttpResponse.BodyHandlers.ofString()) .whenComplete((resp,t) -> { if(t != null){ t.printStackTrace(); }else{ System.out.println(resp.version()); System.out.println(resp.statusCode()); } }).join(); }
執(zhí)行之后可以看到返回的response的version為HTTP_2
WebSocket
@Test public void testWebSocket() throws InterruptedException { HttpClient client = HttpClient.newHttpClient(); WebSocket webSocket = client.newWebSocketBuilder() .buildAsync(URI.create("ws://localhost:8080/echo"), new WebSocket.Listener() { @Override public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) { // request one more webSocket.request(1); // Print the message when it's available return CompletableFuture.completedFuture(data) .thenAccept(System.out::println); } }).join(); webSocket.sendText("hello ", false); webSocket.sendText("world ",true); TimeUnit.SECONDS.sleep(10); webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok").join(); }
HttpClient支持HTTP2,也包含了WebSocket,通過(guò)newWebSocketBuilder去構(gòu)造WebSocket
傳入listener進(jìn)行接收消息,要發(fā)消息的話,使用WebSocket來(lái)發(fā)送,關(guān)閉使用sendClose方法
reactive streams
HttpClient本身就是reactive的,支持reactive streams,這里舉ResponseSubscribers.ByteArraySubscriber的源碼看看:
java.net.http/jdk/internal/net/http/ResponseSubscribers.java
public static class ByteArraySubscriber<T> implements BodySubscriber<T> { private final Function<byte[], T> finisher; private final CompletableFuture<T> result = new MinimalFuture<>(); private final List<ByteBuffer> received = new ArrayList<>(); private volatile Flow.Subscription subscription; public ByteArraySubscriber(Function<byte[],T> finisher) { this.finisher = finisher; } @Override public void onSubscribe(Flow.Subscription subscription) { if (this.subscription != null) { subscription.cancel(); return; } this.subscription = subscription; // We can handle whatever you've got subscription.request(Long.MAX_VALUE); } @Override public void onNext(List<ByteBuffer> items) { // incoming buffers are allocated by http client internally, // and won't be used anywhere except this place. // So it's free simply to store them for further processing. assert Utils.hasRemaining(items); received.addAll(items); } @Override public void onError(Throwable throwable) { received.clear(); result.completeExceptionally(throwable); } static private byte[] join(List<ByteBuffer> bytes) { int size = Utils.remaining(bytes, Integer.MAX_VALUE); byte[] res = new byte[size]; int from = 0; for (ByteBuffer b : bytes) { int l = b.remaining(); b.get(res, from, l); from += l; } return res; } @Override public void onComplete() { try { result.complete(finisher.apply(join(received))); received.clear(); } catch (IllegalArgumentException e) { result.completeExceptionally(e); } } @Override public CompletionStage<T> getBody() { return result; } }
BodySubscriber接口繼承了Flow.Subscriber<List<ByteBuffer>>接口
這里的Subscription來(lái)自Flow類,該類是java9引入的,里頭包含了支持Reactive Streams的實(shí)現(xiàn)
小結(jié)
HttpClient在Java11從incubator變?yōu)檎桨?,相?duì)于傳統(tǒng)的HttpUrlConnection其提升可不是一點(diǎn)半點(diǎn),不僅支持異步,也支持reactive streams,同時(shí)也支持了HTTP2以及WebSocket,非常值得大家使用。
關(guān)于Java11的HttpClient的特性是什么問(wèn)題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒(méi)有解開(kāi),可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識(shí)。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。