序
本文主要研究一下java11的httpclient的基本使用。
變化
- 從java9的jdk.incubator.httpclient模塊遷移到java.net.http模塊,包名由jdk.incubator.http改為java.net.http
- 原來的諸如httpresponse.bodyhandler.asstring()方法變更為httpresponse.bodyhandlers.ofstring(),變化一為bodyhandler改為bodyhandlers,變化二為asxxx()之類的方法改為ofxxx(),由as改為of
實例
設置超時時間
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@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實例
1
2
3
4
5
6
7
|
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實例
1
2
3
4
5
|
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 ) |
設置authenticator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
@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可以用來設置http authentication,比如basic authentication
- 雖然basic authentication也可以自己設置header,不過通過authenticator省得自己去構造header
設置header
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@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可以自己設置header
get
同步
1
2
3
4
5
6
7
8
9
10
11
12
|
@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()); } |
異步
1
2
3
4
5
6
7
8
9
10
11
|
@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表單
1
2
3
4
5
6
7
8
9
10
11
12
|
@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指定內容是表單類型,然后通過bodypublishers.ofstring傳遞表單數據,需要自己構建表單參數
post json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
@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格式
文件上傳
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
@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) //要設置,否則阻塞 .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那種現成的bodyinserters.frommultipartdata方法,因此這里需要自己轉換
- 這里使用org.apache.httpcomponents(httpclient及httpmime)的multipartentitybuilder構建multipartentity,最后通過httprequest.bodypublishers.ofinputstream來傳遞內容
- 這里header要指定content-type值為multipart/form-data以及boundary的值,否則服務端可能無法解析
文件下載
1
2
3
4
5
6
7
8
9
10
11
|
@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來接收文件
并發請求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
@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,可以方便地進行轉換、組合等操作
- 這里使用completablefuture.allof組合在一起,最后調用join等待所有future完成
錯誤處理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
@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的狀態碼拋出異常,需要自己根據情況來處理,手動檢測狀態碼拋出異常或者返回其他內容
http2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@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(); } |
執行之后可以看到返回的response的version為http_2
websocket
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@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去構造websocket
- 傳入listener進行接收消息,要發消息的話,使用websocket來發送,關閉使用sendclose方法
reactive streams
httpclient本身就是reactive的,支持reactive streams,這里舉responsesubscribers.bytearraysubscriber的源碼看看:
java.net.http/jdk/internal/net/http/responsesubscribers.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
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的實現
小結
httpclient在java11從incubator變為正式版,相對于傳統的httpurlconnection其提升可不是一點半點,不僅支持異步,也支持reactive streams,同時也支持了http2以及websocket,非常值得大家使用。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://segmentfault.com/a/1190000016555671