Java11的HttpClient的特性是什么

Java11的HttpClient的特性是什么,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。

变化

Java11的HttpClient的特性是什么

  1. 从java9的jdk.incubator.httpclient模块迁移到java.net.http模块,包名由jdk.incubator.http改为java.net.http

  2. 原来的诸如HttpResponse.BodyHandler.asString()方法变更为HttpResponse.BodyHandlers.ofString(),变化一为BodyHandler改为BodyHandlers,变化二为asXXX()之类的方法改为ofXXX(),由as改为of

实例

设置超时时间

@Test
publicvoidtestTimeout()throwsIOException,InterruptedException{
//1.setconnecttimeout
HttpClientclient=HttpClient.newBuilder()
.connectTimeout(Duration.ofMillis(5000))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();

//2.setreadtimeout
HttpRequestrequest=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实例

Causedby:java.net.http.HttpConnectTimeoutException:HTTPconnecttimedout
atjava.net.http/jdk.internal.net.http.ResponseTimerEvent.handle(ResponseTimerEvent.java:68)
atjava.net.http/jdk.internal.net.http.HttpClientImpl.purgeTimeoutsAndReturnNextDeadline(HttpClientImpl.java:1248)
atjava.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.run(HttpClientImpl.java:877)
Causedby:java.net.ConnectException:HTTPconnecttimedout
atjava.net.http/jdk.internal.net.http.ResponseTimerEvent.handle(ResponseTimerEvent.java:69)
...2more

HttpTimeoutException实例

java.net.http.HttpTimeoutException:requesttimedout

atjava.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:559)
atjava.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:119)
atcom.example.HttpClientTest.testTimeout(HttpClientTest.java:40)

设置authenticator

@Test
publicvoidtestBasicAuth()throwsIOException,InterruptedException{
HttpClientclient=HttpClient.newBuilder()
.connectTimeout(Duration.ofMillis(5000))
.authenticator(newAuthenticator(){
@Override
protectedPasswordAuthenticationgetPasswordAuthentication(){
returnnewPasswordAuthentication("admin","password".toCharArray());
}
})
.build();

HttpRequestrequest=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());
}
  1. authenticator可以用来设置HTTP authentication,比如Basic authentication

  2. 虽然Basic authentication也可以自己设置header,不过通过authenticator省得自己去构造header

设置header

@Test
publicvoidtestCookies()throwsIOException,InterruptedException{
HttpClientclient=HttpClient.newBuilder()
.connectTimeout(Duration.ofMillis(5000))
.build();
HttpRequestrequest=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

同步

@Test
publicvoidtestSyncGet()throwsIOException,InterruptedException{
HttpClientclient=HttpClient.newHttpClient();
HttpRequestrequest=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
publicvoidtestAsyncGet()throwsExecutionException,InterruptedException{
HttpClientclient=HttpClient.newHttpClient();
HttpRequestrequest=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
publicvoidtestPostForm()throwsIOException,InterruptedException{
HttpClientclient=HttpClient.newBuilder().build();
HttpRequestrequest=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

@Test
publicvoidtestPostJsonGetJson()throwsExecutionException,InterruptedException,JsonProcessingException{
ObjectMapperobjectMapper=newObjectMapper();
StockDtodto=newStockDto();
dto.setName("hj");
dto.setSymbol("hj");
dto.setType(StockDto.StockType.SH);
StringrequestBody=objectMapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(dto);

HttpRequestrequest=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{
returnobjectMapper.readValue(body,StockDto.class);
}catch(IOExceptione){
returnnewStockDto();
}
});
System.out.println(result.get());
}

post json的话,body自己json化为string,然后header指定是json格式

文件上传

@Test
publicvoidtestUploadFile()throwsIOException,InterruptedException,URISyntaxException{
HttpClientclient=HttpClient.newHttpClient();
Pathpath=Path.of(getClass().getClassLoader().getResource("body.txt").toURI());
Filefile=path.toFile();

StringmultipartFormDataBoundary="Java11HttpClientFormBoundary";
org.apache.http.HttpEntitymultipartEntity=MultipartEntityBuilder.create()
.addPart("file",newFileBody(file,ContentType.DEFAULT_BINARY))
.setBoundary(multipartFormDataBoundary)//要设置,否则阻塞
.build();

HttpRequestrequest=HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8080/file/upload"))
.header("Content-Type","multipart/form-data;boundary="+multipartFormDataBoundary)
.POST(HttpRequest.BodyPublishers.ofInputStream(()->{
try{
returnmultipartEntity.getContent();
}catch(IOExceptione){
e.printStackTrace();
thrownewRuntimeException(e);
}
}))
.build();

HttpResponse<String>response=
client.send(request,HttpResponse.BodyHandlers.ofString());

System.out.println(response.body());
}
  1. 官方的HttpClient并没有提供类似WebClient那种现成的BodyInserters.fromMultipartData方法,因此这里需要自己转换

  2. 这里使用org.apache.httpcomponents(httpclient及httpmime)的MultipartEntityBuilder构建multipartEntity,最后通过HttpRequest.BodyPublishers.ofInputStream来传递内容

  3. 这里header要指定Content-Type值为multipart/form-data以及boundary的值,否则服务端可能无法解析

文件下载

@Test
publicvoidtestAsyncDownload()throwsExecutionException,InterruptedException{
HttpClientclient=HttpClient.newHttpClient();
HttpRequestrequest=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来接收文件

并发请求

@Test
publicvoidtestConcurrentRequests(){
HttpClientclient=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完成

错误处理

@Test
publicvoidtestHandleException()throwsExecutionException,InterruptedException{
HttpClientclient=HttpClient.newBuilder()
.connectTimeout(Duration.ofMillis(5000))
.build();
HttpRequestrequest=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

@Test
publicvoidtestHttp2()throwsURISyntaxException{
HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.NEVER)
.version(HttpClient.Version.HTTP_2)
.build()
.sendAsync(HttpRequest.newBuilder()
.uri(newURI("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

@Test
publicvoidtestWebSocket()throwsInterruptedException{
HttpClientclient=HttpClient.newHttpClient();
WebSocketwebSocket=client.newWebSocketBuilder()
.buildAsync(URI.create("ws://localhost:8080/echo"),newWebSocket.Listener(){

@Override
publicCompletionStage<?>onText(WebSocketwebSocket,CharSequencedata,booleanlast){
//requestonemore
webSocket.request(1);

//Printthemessagewhenit'savailable
returnCompletableFuture.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

publicstaticclassByteArraySubscriber<T>implementsBodySubscriber<T>{
privatefinalFunction<byte[],T>finisher;
privatefinalCompletableFuture<T>result=newMinimalFuture<>();
privatefinalList<ByteBuffer>received=newArrayList<>();

privatevolatileFlow.Subscriptionsubscription;

publicByteArraySubscriber(Function<byte[],T>finisher){
this.finisher=finisher;
}

@Override
publicvoidonSubscribe(Flow.Subscriptionsubscription){
if(this.subscription!=null){
subscription.cancel();
return;
}
this.subscription=subscription;
//Wecanhandlewhateveryou'vegot
subscription.request(Long.MAX_VALUE);
}

@Override
publicvoidonNext(List<ByteBuffer>items){
//incomingbuffersareallocatedbyhttpclientinternally,
//andwon'tbeusedanywhereexceptthisplace.
//Soit'sfreesimplytostorethemforfurtherprocessing.
assertUtils.hasRemaining(items);
received.addAll(items);
}

@Override
publicvoidonError(Throwablethrowable){
received.clear();
result.completeExceptionally(throwable);
}

staticprivatebyte[]join(List<ByteBuffer>bytes){
intsize=Utils.remaining(bytes,Integer.MAX_VALUE);
byte[]res=newbyte[size];
intfrom=0;
for(ByteBufferb:bytes){
intl=b.remaining();
b.get(res,from,l);
from+=l;
}
returnres;
}

@Override
publicvoidonComplete(){
try{
result.complete(finisher.apply(join(received)));
received.clear();
}catch(IllegalArgumentExceptione){
result.completeExceptionally(e);
}
}

@Override
publicCompletionStage<T>getBody(){
returnresult;
}
}
  1. BodySubscriber接口继承了Flow.Subscriber<List<ByteBuffer>>接口

  2. 这里的Subscription来自Flow类,该类是java9引入的,里头包含了支持Reactive Streams的实现

小结

HttpClient在Java11从incubator变为正式版,相对于传统的HttpUrlConnection其提升可不是一点半点,不仅支持异步,也支持reactive streams,同时也支持了HTTP2以及WebSocket,非常值得大家使用。

关于Java11的HttpClient的特性是什么问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注恰卡编程网行业资讯频道了解更多相关知识。

发布于 2021-06-13 23:18:55
收藏
分享
海报
0 条评论
160
上一篇:Android中微信小程序的图片优化技巧分享 下一篇:JVM逃逸的原理是什么
目录

    0 条评论

    本站已关闭游客评论,请登录或者注册后再评论吧~

    忘记密码?

    图形验证码