探索 Java 中的新 HTTP 客户端

📅 发布时间:2026/7/15 3:02:50 👁️ 浏览次数:
探索 Java 中的新 HTTP 客户端
你是否也遇到过这样的时刻只是想发个 HTTP 请求却被连接管理、重定向、超时与线程阻塞折腾得不亦乐乎那就试试 Java 11 正式标准化了全新的 HttpClient原生支持 HTTP/2、异步与 WebSocket极大简化了客户端网络编程。1. 概览本文将介绍 Java 11 对全新HTTP 客户端 API支持 HTTP/2 与 WebSocket的标准化。它旨在替代 JDK 早期就存在的旧类HttpURLConnection文档见https://docs.oracle.com/en/java/javase/21/docs/api/java.base/...。在不久之前Java 只有较为底层、功能有限且不够友好的HttpURLConnectionAPI。因此社区普遍使用第三方库如 Apache HttpClient、Jetty 以及 Spring 的 RestTemplate。2. 背景该变更由 JEP 321 引入并最终在 Java 11 中定型。2.1. JEP 321 的主要变更Java 9 的孵化版 HTTP API 已正式并入 Java SE API。新的 HTTP APIs 位于java.net.http.*。新版本的 HTTP 协议旨在提升客户端请求与服务器响应的整体性能包括多路复用、头压缩与推送承诺push promise等特性。自 Java 11 起API 全面支持异步相比之下旧的 HTTP/1.1 实现是阻塞式的。异步以CompletableFuture实现阶段式流水线在前一阶段完成后自动衔接执行。新的 HTTP 客户端提供了标准方式执行网络操作原生支持现代 Web 能力如 HTTP/2无需引入第三方依赖。新 API 原生支持 HTTP/1.1 与 HTTP/2 的 WebSocket。核心类型包括HttpClientjava.net.http.HttpClientHttpRequestjava.net.http.HttpRequestHttpResponseTjava.net.http.HttpResponseWebSocketjava.net.http.WebSocket2.2. Java 11 之前客户端的问题旧版HttpURLConnection及其实现存在诸多问题URLConnection为多个如今已不再使用的协议FTP、gopher 等而设计API 早于 HTTP/1.1抽象层级不合时宜仅支持阻塞模式一次请求/响应占用一个线程维护困难。3. HTTP Client API 总览与HttpURLConnection不同新 HTTP 客户端同时提供同步与异步两种请求机制。API 的三大核心HttpRequest要发送的请求HttpClient跨请求的通用配置容器HttpResponse请求的响应结果。下面分别展开先从请求开始。4. HttpRequestHttpRequest表示将要发送的请求可通过HttpRequest.newBuilder()获取构建器构建器提供多种便捷方法配置请求。注JDK 16 新增HttpRequest.newBuilder(HttpRequest request, BiPredicateString,String filter)可基于已有请求复制初始状态再在构建前做修改如移除部分头HttpRequest.newBuilder(request, (name, value) - !name.equalsIgnoreCase(Foo-Bar));4.1. 设置 URI可直接用带URI的构造方式或在构建器上调用uri(URI)HttpRequest.newBuilder(new URI(https://postman-echo.com/get)); HttpRequest.newBuilder() .uri(new URI(https://postman-echo.com/get));4.2. 指定 HTTP 方法构建器提供以下方法GET()POST(BodyPublisher body)PUT(BodyPublisher body)DELETE()一个最简单的 GET 示例HttpRequest request HttpRequest.newBuilder() .uri(new URI(https://postman-echo.com/get)) .GET() .build();常见的附加参数包括HTTP 协议版本、请求头与超时。4.3. 设置协议版本API 默认充分利用 HTTP/2也可显式指定HttpRequest request HttpRequest.newBuilder() .uri(new URI(https://postman-echo.com/get)) .version(HttpClient.Version.HTTP_2) .GET() .build();注意若对端不支持 HTTP/2客户端会回退到 HTTP/1.1。4.4. 设置请求头可用headers(k1,v1,k2,v2,...)一次性传入或多次调用header(k,v)HttpRequest request HttpRequest.newBuilder() .uri(new URI(https://postman-echo.com/get)) .headers(key1, value1, key2, value2) .GET() .build(); HttpRequest request2 HttpRequest.newBuilder() .uri(new URI(https://postman-echo.com/get)) .header(key1, value1) .header(key2, value2) .GET() .build();4.5. 设置超时默认无穷大。可用Duration设置超时会抛出HttpTimeoutExceptionHttpRequest request HttpRequest.newBuilder() .uri(new URI(https://postman-echo.com/get)) .timeout(Duration.ofSeconds(10)) .GET() .build();5. 设置请求体POST(BodyPublisher),PUT(BodyPublisher)可携带请求体DELETE()也支持不带体的删除。常用的BodyPublisher工厂有HttpRequest.BodyPublishers.ofString基于字符串HttpRequest.BodyPublishers.ofInputStream基于输入流以SupplierInputStream形式延迟创建HttpRequest.BodyPublishers.ofByteArray基于字节数组HttpRequest.BodyPublishers.ofFile基于文件路径内容无请求体HttpRequest.BodyPublishers.noBody()。JDK 16 新增BodyPublishers.concat(...)可把多个 publisher 的内容顺序拼接为一个请求体。5.1. 字符串请求体HttpRequest request HttpRequest.newBuilder() .uri(new URI(https://postman-echo.com/post)) .headers(Content-Type, text/plain;charsetUTF-8) .POST(HttpRequest.BodyPublishers.ofString(Sample request body)) .build();5.2. 输入流请求体byte[] sampleData Sample request body.getBytes(); HttpRequest request HttpRequest.newBuilder() .uri(new URI(https://postman-echo.com/post)) .headers(Content-Type, text/plain;charsetUTF-8) .POST(HttpRequest.BodyPublishers .ofInputStream(() - new ByteArrayInputStream(sampleData))) .build();5.3. 字节数组请求体byte[] sampleData Sample request body.getBytes(); HttpRequest request HttpRequest.newBuilder() .uri(new URI(https://postman-echo.com/post)) .headers(Content-Type, text/plain;charsetUTF-8) .POST(HttpRequest.BodyPublishers.ofByteArray(sampleData)) .build();5.4. 文件请求体HttpRequest request HttpRequest.newBuilder() .uri(new URI(https://postman-echo.com/post)) .headers(Content-Type, text/plain;charsetUTF-8) .POST(HttpRequest.BodyPublishers.ofFile( Paths.get(src/test/resources/sample.txt))) .build();6. HttpClient所有请求都由HttpClient发送可通过HttpClient.newBuilder()或HttpClient.newHttpClient()获取。下面看几个常用能力。6.1. 处理响应体新的BodyHandlers工厂提供常见类型的响应体处理器BodyHandlers.ofByteArray; BodyHandlers.ofString; BodyHandlers.ofFile; BodyHandlers.discarding; BodyHandlers.replacing; BodyHandlers.ofLines; BodyHandlers.fromLineSubscriber;Java 11 之前HttpResponseString response client.send(request, HttpResponse.BodyHandler.asString());现在可简化为HttpResponseString response client.send(request, BodyHandlers.ofString());6.2. 设置代理HttpResponseString response HttpClient .newBuilder() .proxy(ProxySelector.getDefault()) .build() .send(request, BodyHandlers.ofString());6.3. 跟随重定向策略HttpResponseString response HttpClient.newBuilder() .followRedirects(HttpClient.Redirect.ALWAYS) .build() .send(request, BodyHandlers.ofString());6.4. 认证器AuthenticatorHttpResponseString response HttpClient.newBuilder() .authenticator(new Authenticator() { Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication( username, password.toCharArray()); } }) .build() .send(request, BodyHandlers.ofString());6.5. 同步与异步发送同步send(...)阻塞直到响应返回异步sendAsync(...)立即返回CompletableFutureHttpResponseT同步示例HttpResponseString response HttpClient.newBuilder() .build() .send(request, BodyHandlers.ofString());异步示例CompletableFutureHttpResponseString response HttpClient.newBuilder() .build() .sendAsync(request, HttpResponse.BodyHandlers.ofString());批量并发请求ListURI targets Arrays.asList( new URI(https://postman-echo.com/get?foo1bar1), new URI(https://postman-echo.com/get?foo2bar2)); HttpClient client HttpClient.newHttpClient(); ListCompletableFutureString futures targets.stream() .map(target - client .sendAsync( HttpRequest.newBuilder(target).GET().build(), HttpResponse.BodyHandlers.ofString()) .thenApply(HttpResponse::body)) .collect(Collectors.toList());6.6. 指定异步执行器ExecutorExecutorService executorService Executors.newFixedThreadPool(2); CompletableFutureHttpResponseString response1 HttpClient.newBuilder() .executor(executorService) .build() .sendAsync(request, HttpResponse.BodyHandlers.ofString()); CompletableFutureHttpResponseString response2 HttpClient.newBuilder() .executor(executorService) .build() .sendAsync(request, HttpResponse.BodyHandlers.ofString());默认执行器为Executors.newCachedThreadPool()。6.7. CookieHandler设置客户端级CookieHandlerHttpClient.newBuilder() .cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_NONE)) .build();若允许存储 Cookie可从CookieManager读取((CookieManager) httpClient.cookieHandler().get()).getCookieStore();7. HttpResponseHttpResponse表示服务端响应核心方法statusCode()返回整型状态码body()返回响应体类型取决于发送时的BodyHandler。其他常用方法还包括uri()、headers()、trailers()与version()。7.1. 响应的 URI由于重定向响应返回的uri()可能与请求不同assertThat(request.uri().toString(), equalTo(http://stackoverflow.com)); assertThat(response.uri().toString(), equalTo(https://stackoverflow.com/));7.2. 响应头HttpResponseString response HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); HttpHeaders responseHeaders response.headers();7.3. 响应协议版本即使请求设置为 HTTP/2服务端也可能以 HTTP/1.1 响应实际版本可从响应读取HttpRequest request HttpRequest.newBuilder() .uri(new URI(https://postman-echo.com/get)) .version(HttpClient.Version.HTTP_2) .GET() .build(); HttpResponseString response HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); assertThat(response.version(), equalTo(HttpClient.Version.HTTP_1_1));8. HTTP/2 推送承诺Push Promise新的HttpClient通过PushPromiseHandler支持服务端主动推送。当客户端请求主资源时服务器可以同时“推送”额外资源从而减少往返次数、加快页面渲染。该能力得益于 HTTP/2 的多路复用。如有推送承诺将由提供的PushPromiseHandler处理若传入null则拒绝所有推送。HttpClient的重载sendAsync可用于处理 push promise。先定义处理器private static PushPromiseHandlerString pushPromiseHandler() { return (HttpRequest initiatingRequest, HttpRequest pushPromiseRequest, FunctionHttpResponse.BodyHandlerString, CompletableFutureHttpResponseString acceptor) - { acceptor.apply(BodyHandlers.ofString()) .thenAccept(resp - { System.out.println(Pushed response: resp.uri() , headers: resp.headers()); }); System.out.println(Promise request: pushPromiseRequest.uri()); System.out.println(Promise request headers: pushPromiseRequest.headers()); }; }再用sendAsync消费它httpClient.sendAsync(pageRequest, BodyHandlers.ofString(), pushPromiseHandler()) .thenAccept(pageResponse - { System.out.println(Page response status code: pageResponse.statusCode()); System.out.println(Page response headers: pageResponse.headers()); String responseBody pageResponse.body(); System.out.println(responseBody); }) .join();9. 总结本文探讨了 Java 11 中标准化后的HttpClientAPI在保留易用性的同时引入了 HTTP/2、异步、推送承诺、代理、重定向策略、认证器、Cookie 管理等现代化能力让 Java 的 HTTP 编程更高效、更现代。行业拓展分享一个面向研发人群使用的前后端分离的低代码软件——JNPF。基于 Java Boot/.Net Core双引擎它适配国产化支持主流数据库和操作系统提供五十几种高频预制组件内置了常用的后台管理系统使用场景和实用模版通过简单的拖拉拽操作开发者能够高效完成软件开发提高开发效率减少代码编写工作。JNPF基于SpringBootVue.js提供了一个适合所有水平用户的低代码学习平台无论是有经验的开发者还是编程新手都可以在这里找到适合自己的学习路径。此外JNPF支持全源码交付完全支持根据公司、项目需求、业务需求进行二次改造开发或内网部署具备多角色门户、登录认证、组织管理、角色授权、表单设计、流程设计、页面配置、报表设计、门户配置、代码生成工具等开箱即用的在线服务。