Java11的HttpClient的特性是什么
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
实例
设置超时时间
@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()); }
authenticator可以用来设置HTTP authentication,比如Basic authentication
虽然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()); }
官方的HttpClient并没有提供类似WebClient那种现成的BodyInserters.fromMultipartData方法,因此这里需要自己转换
这里使用org.apache.httpcomponents(httpclient及httpmime)的MultipartEntityBuilder构建multipartEntity,最后通过HttpRequest.BodyPublishers.ofInputStream来传递内容
这里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; } }
BodySubscriber接口继承了Flow.Subscriber<List<ByteBuffer>>接口
这里的Subscription来自Flow类,该类是java9引入的,里头包含了支持Reactive Streams的实现
小结
HttpClient在Java11从incubator变为正式版,相对于传统的HttpUrlConnection其提升可不是一点半点,不仅支持异步,也支持reactive streams,同时也支持了HTTP2以及WebSocket,非常值得大家使用。
关于Java11的HttpClient的特性是什么问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注恰卡编程网行业资讯频道了解更多相关知识。
推荐阅读
-
Notepad++ 插件推荐:代码折叠、语法高亮增强工具合集
-
Emacs Evil 模式:Vim 用户快速上手 Emacs 的过渡方案
-
Vim 寄存器深度解析:多缓冲区操作与复杂文本处理
-
Atom 主题切换:Material Design 与扁平化风格对比推荐
-
Lightly IDE 快捷键:Python 开发者必学的效率提升操作
-
Xcode 模拟器调试:多设备同步与性能监控技巧
-
PyCharm 代码格式化:黑魔法工具 Black 与自动规范配置
-
IntelliJ IDEA Docker 集成:微服务本地调试与镜像构建
-
VS Code Remote SSH:远程服务器开发环境搭建全流程
-
Retool 数据库连接:支持 MySQL、PostgreSQL 等多数据源配置
-
Notepad++ 插件推荐:代码折叠、语法高亮增强工具合集
-
Emacs Evil 模式:Vim 用户快速上手 Emacs 的过渡方案
-
Vim 寄存器深度解析:多缓冲区操作与复杂文本处理
-
Atom 主题切换:Material Design 与扁平化风格对比推荐
-
Lightly IDE 快捷键:Python 开发者必学的效率提升操作
-
Xcode 模拟器调试:多设备同步与性能监控技巧
-
PyCharm 代码格式化:黑魔法工具 Black 与自动规范配置
-
IntelliJ IDEA Docker 集成:微服务本地调试与镜像构建
-
VS Code Remote SSH:远程服务器开发环境搭建全流程
-
Retool 数据库连接:支持 MySQL、PostgreSQL 等多数据源配置
-
Notepad++ 插件推荐:代码折叠、语法高亮增强工具合集
-
Emacs Evil 模式:Vim 用户快速上手 Emacs 的过渡方案
-
Vim 寄存器深度解析:多缓冲区操作与复杂文本处理
-
Atom 主题切换:Material Design 与扁平化风格对比推荐
-
Lightly IDE 快捷键:Python 开发者必学的效率提升操作
-
Xcode 模拟器调试:多设备同步与性能监控技巧
-
PyCharm 代码格式化:黑魔法工具 Black 与自动规范配置
-
IntelliJ IDEA Docker 集成:微服务本地调试与镜像构建
-
VS Code Remote SSH:远程服务器开发环境搭建全流程
-
Retool 数据库连接:支持 MySQL、PostgreSQL 等多数据源配置