Java11 HttpClient小試牛刀

本文主要研究一下Java11的HttpClient的基本使用跪呈。

變化

  • 從java9的jdk.incubator.httpclient模塊遷移到j(luò)ava.net.http模塊,包名由jdk.incubator.http改為java.net.http
  • 原來的諸如HttpResponse.BodyHandler.asString()方法變更為HttpResponse.BodyHandlers.ofString()强饮,變化一為BodyHandler改為BodyHandlers,變化二為asXXX()之類的方法改為ofXXX()驱还,由as改為of

實例

設(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實例
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實例
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可以用來設(shè)置HTTP authentication闭树,比如Basic authentication
  • 雖然Basic authentication也可以自己設(shè)置header,不過通過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());
    }
  • 通過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)容是表單類型即寒,然后通過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并沒有提供類似WebClient那種現(xiàn)成的BodyInserters.fromMultipartData方法具滴,因此這里需要自己轉(zhuǎn)換
  • 這里使用org.apache.httpcomponents(httpclient及httpmime)的MultipartEntityBuilder構(gòu)建multipartEntity凹嘲,最后通過HttpRequest.BodyPublishers.ofInputStream來傳遞內(nèi)容
  • 這里header要指定Content-Type值為multipart/form-data以及boundary的值,否則服務(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來接收文件

并發(fā)請求

    @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完成

錯誤處理

    @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異步請求返回的是CompletableFuture<HttpResponse<T>>疲恢,其自帶exceptionally方法可以用來做fallback處理
  • 另外值得注意的是HttpClient不像WebClient那樣凶朗,它沒有對4xx或5xx的狀態(tài)碼拋出異常,需要自己根據(jù)情況來處理显拳,手動檢測狀態(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,通過newWebSocketBuilder去構(gòu)造WebSocket
  • 傳入listener進(jìn)行接收消息宛畦,要發(fā)消息的話瘸洛,使用WebSocket來發(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來自Flow類,該類是java9引入的斯够,里頭包含了支持Reactive Streams的實現(xiàn)

小結(jié)

HttpClient在Java11從incubator變?yōu)檎桨媲裘担鄬τ趥鹘y(tǒng)的HttpUrlConnection其提升可不是一點半點,不僅支持異步读规,也支持reactive streams,同時也支持了HTTP2以及WebSocket燃少,非常值得大家使用束亏。

doc

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市阵具,隨后出現(xiàn)的幾起案子碍遍,更是在濱河造成了極大的恐慌,老刑警劉巖阳液,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怕敬,死亡現(xiàn)場離奇詭異,居然都是意外死亡帘皿,警方通過查閱死者的電腦和手機东跪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鹰溜,“玉大人虽填,你說我怎么就攤上這事〔芏” “怎么了斋日?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長墓陈。 經(jīng)常有香客問我恶守,道長,這世上最難降的妖魔是什么贡必? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任兔港,我火速辦了婚禮,結(jié)果婚禮上赊级,老公的妹妹穿的比我還像新娘押框。我一直安慰自己,他們只是感情好理逊,可當(dāng)我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布橡伞。 她就那樣靜靜地躺著盒揉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪兑徘。 梳的紋絲不亂的頭發(fā)上刚盈,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天,我揣著相機與錄音挂脑,去河邊找鬼藕漱。 笑死,一個胖子當(dāng)著我的面吹牛崭闲,可吹牛的內(nèi)容都是我干的肋联。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼刁俭,長吁一口氣:“原來是場噩夢啊……” “哼橄仍!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起牍戚,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤侮繁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后如孝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宪哩,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年第晰,在試婚紗的時候發(fā)現(xiàn)自己被綠了锁孟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡但荤,死狀恐怖罗岖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情腹躁,我是刑警寧澤桑包,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站纺非,受9級特大地震影響哑了,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜烧颖,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一弱左、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧炕淮,春花似錦拆火、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽币叹。三九已至,卻和暖如春模狭,著一層夾襖步出監(jiān)牢的瞬間颈抚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工嚼鹉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留贩汉,地道東北人。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓锚赤,卻偏偏與公主長得像匹舞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子宴树,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,927評論 2 355