Java.net.http 包的 HTTP API 示例
HTTP 客户端和 WebSocket API 为 HTTP(版本 1.1 和 2)提供高级客户端接口,为 WebSocket 提供低级客户端接口。定义的主要类型如下:
- 客户端
- 请求
- 响应
特定于协议的要求在超文本传输协议版本 2 (HTTP/2)、超文本传输协议 (HTTP/1.1) 和 WebSocket 协议中定义。
通常,异步任务要么在调用操作的线程中执行,例如发送 HTTP 请求,要么在客户端执行程序提供的线程中执行。依赖任务,即由返回的 CompletionStages 或 CompletableFutures 触发的任务,未明确指定执行程序,在与 CompletableFuture 相同的默认执行程序中执行,或者如果操作在依赖任务注册之前完成,则在调用线程中执行。
此 API 返回的 CompletableFutures 将为其 obtrudeValue 和 obtrudeException 方法抛出 UnsupportedOperationException。在此 API 返回的 CompletableFuture 上调用取消方法可能不会中断底层操作,但对于完成尚未完成的特殊阶段可能很有用。
除非另有说明,空参数值将导致该包中所有类的方法抛出 NullPointerException。
一、界面总结
界面汇总如下表格式:HTTP Components Action performed HttpClient.Builder A builder of HTTP Clients. HttpRequest.BodyPublisher A BodyPublisher converts high-level Java objects into a flow of byte buffers suitable for sending as a request body. HttpRequest.Builder A builder of HTTP requests. HttpResponse An HTTP response. HttpResponse.BodyHandler A handler for response bodies. HttpResponse.BodySubscriber A BodySubscriber consumes response body bytes and converts them into a higher-level Java type. HttpResponse.PushPromiseHandler A handler for push promises. HttpResponse.ResponseInfo Initial response information supplied to a BodyHandler when a response is initially received and before the body is processed.
2. WebSocket 客户端。
- WebSocket.Builder:WebSocket 客户端的构建器。
- WebSocket.Listener:WebSocket.2 的接收接口。班级
- 班级说明
- 客户端
3. HTTP 客户端
- HttpHeaders:一组 HTTP 标头的只读视图。
- HttpRequest:一个 HTTP 请求。
- HttpRequest.BodyPublishers:BodyPublisher 的实现,它实现了各种有用的发布者,例如从字符串或文件发布请求正文。
- HttpResponse.BodyHandlers:BodyHandler 的实现,它实现了各种有用的处理程序,例如将响应正文作为字符串处理,或将响应正文流式传输到文件。
- HttpResponse.BodySubscribers:BodySubscriber 的实现,实现了各种有用的订阅者,例如将响应正文字节转换为字符串,或将字节流式传输到文件。
4.枚举总结
- HttpClient.Redirect:定义自动重定向策略。
- HttpClient.Version HTTP 协议版本。
5.异常总结gHttpConnectTimeoutException Thrown when a connection, over which an HttpRequest is intended to be sent, is not successfully established within a specified time period. HttpTimeoutException Thrown when a response is not received within a specified time period. WebSocketHandshakeException Thrown when the opening handshake has failed.
方法:
有 5 种发出 HTTP 请求的方法是现代编程的核心特征,并且通常是您在学习新的编程语言时首先要做的事情之一。对于Java程序员来说,有很多方法可以做到——JDK 中的核心库和第三方库。它们列出如下:
- 在 J2SE 中使用 HttpURLConnection
- 在 J2SE 中使用 HttpClient
- 使用ApacheHttpClient第三方库
- 使用OkHttp第三方库
- 使用Retrofit第三方库
让我们用一个例证来讨论它们。
方式一:核心Java
用于发出Java HTTP 请求的核心Java API。从Java 1.1 开始,JDK 提供的核心库中有一个 HTTP 客户端。在Java 11 中添加了一个新客户端。如果您对向项目添加额外的依赖项很敏感,那么其中之一可能是一个不错的选择。
Note: For codes coverage here astronomy picture of the Day API from the NASA APIs for the code samples.
1.1 Java 1.1 HttpURLConnection
首先,我们是否将类名中的首字母缩写词大写?做好决定。不管怎样,闭上你的眼睛,回到 1997 年。泰坦尼克号震撼了票房并激发了一千个模因,辣妹有一张最畅销的专辑,但今年最大的新闻肯定是 HttpURLConnection 被添加到Java 1.1。它是合理的,在下面提供的离子插画:
插图:
用法以及如何获取 GET 请求以获取 APOD 数据
// Step 1: Create a neat value object to hold the URL
URL url = new URL("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY");
// Step 2: Open a connection(?) on the URL(??) and cast the response(???)
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// Now it's "open", we can set the request method, headers etc.
connection.setRequestProperty("accept", "application/json");
// This line makes the request
InputStream responseStream = connection.getInputStream();
// Step 3: Manually converting the response body InputStream to
// APOD using Jackson
ObjectMapper mapper = new ObjectMapper();
APOD apod = mapper.readValue(responseStream, APOD.class);
// Step 5: Finally, display the response we have
System.out.println(apod.title);
这看起来很冗长,我发现我们必须做的事情的顺序令人困惑(为什么我们在打开 URL 后设置标题?)。如果您需要使用 POST 主体或自定义超时等发出更复杂的请求,那么这一切都是可能的,但我从来没有发现这个 API 很直观。
那么什么时候使用 HTTPUrlConnection 呢?如果您支持使用旧版本Java,并且您无法添加依赖项,那么这可能适合您。我怀疑这只是一小部分开发人员,但您可能会在较旧的代码库中看到更现代的方法,请继续阅读。
1.2 Java 11 HttpClient
在 HttpURLConnection 之后二十多年,我们在电影院看到了 Black Panther,并且在Java 11 中添加了一个新的 HTTP 客户端: Java.net.http.HttpClient。它有一个更合乎逻辑的 API,可以处理 HTTP/2 和 Websockets。它还可以选择使用 CompletableFuture API 同步或异步发出请求。
当我发出 HTTP 请求时,100 次中有 99 次我想将响应正文读入我的代码中。使这变得困难的图书馆不会激发我的快乐。 HttpClient 接受一个 BodyHandler,它可以将 HTTP 响应转换为您选择的类。有一些内置处理程序:String、用于二进制数据的 byte[]、用于分割署名的 Stream
插图:同步请求
// Step 1: Create a client
var client = HttpClient.newHttpClient();
// Step 2: Create a request
var request = HttpRequest.newBuilder(URI.create("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY")).header("accept", "application/json").build();
// Step 3: Now use the client to send the request
var response = client.send(request, new JsonBodyHandler<>(APOD.class));
// Response
System.out.println(response.body().get().title);
// For an asynchronous request the client and request are made
// in the same way
// Step 3: After this call .sendAsync instead of .send:
// Step 4: Use the client to send the request
var responseFuture = client.sendAsync(request, new JsonBodyHandler<>(APOD.class));
// We can do other things here while the request is in-flight
// This blocks until the request is complete
var response = responseFuture.get();
// Response
System.out.println(response.body().get().title);
方式 2 :第三方Java HTTP 客户端库。如果内置客户端不适合您,请不要担心!您可以将大量库带入您的项目,它们可以完成这项工作。
2.1库1:Apache HttpClient
Apache 软件基金会的 HTTP 客户端已经存在很长时间了。它们被广泛使用,并且是许多高级库的基础。历史有点混乱。旧的 Commons HttpClient 不再开发,新版本(也称为 HttpClient)在 HttpComponents 项目下。 5.0 版于 2020 年初发布,增加了对 HTTP/2 的支持。该库还支持同步和异步请求。
总的来说,API 是相当低级的,你需要自己实现很多。以下代码调用 NASA API。它看起来并不太难使用,但我已经跳过了很多您在生产代码中想要的错误处理,并且我不得不再次添加 Jackson 代码来解析 JSON 响应。您可能还想配置一个日志框架以避免在 stdout 上出现警告(没什么大不了的,但它确实让我有点恼火)。
Apache provides several more examples for sync and async requests.
插图:
ObjectMapper mapper = new ObjectMapper();
try (CloseableHttpClient client = HttpClients.createDefault())
{
HttpGet request =
new HttpGet("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY");
APOD response = client.execute(request, httpResponse ->
mapper.readValue(httpResponse.getEntity().getContent(), APOD.class));
System.out.println(response.title);
}
2.2库 2: OkHttp
OkHttp 是来自 Square 的 HTTP 客户端,具有许多有用的内置功能,例如自动处理 GZIP、响应缓存和重试或在网络错误的情况下回退到其他主机以及 HTTP/2 和 WebSocket 支持。 API 是干净的,虽然没有内置的 JSON 响应解析。
插图:使用 Jackson 解析 JSON
ObjectMapper mapper = new ObjectMapper();
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY").build(); // defaults to GET
Response response = client.newCall(request).execute();
APOD apod = mapper.readValue(response.body().byteStream(), APOD.class);
System.out.println(apod.title);
Note: This is fine, but the real power of OkHttp is clear when you add Retrofit over the top.
2.3库 3: 改造
Retrofit 是 Square 的另一个库,建立在 OkHttp 之上。除了 OkHttp 的所有低级特性外,它还添加了一种构建Java类的方法,这些类抽象了 HTTP 细节并提供了一个很好的 Java 友好 API。
2.3.1首先,我们需要创建一个接口来声明我们要针对 APOD API 调用的方法,并使用注释定义这些方法如何对应于 HTTP 请求,如下所示:
public interface APODClient
{
@GET("/planetary/apod")
@Headers("accept: application/json")
CompletableFuture getApod(@Query("api_key") String apiKey);
}
2.3.2 CompletableFuture
2.3.3在声明接口之后,我们要求 Retrofit 创建一个实现,我们可以使用它来针对给定的基本 URL 发出请求。能够切换基本 URL 对集成测试也很有帮助。
插图:生成客户端
Retrofit retrofit = new Retrofit.Builder().baseUrl("https://api.nasa.gov").addConverterFactory(JacksonConverterFactory.create()).build();
APODClient apodClient = retrofit.create(APODClient.class);
CompletableFuture response = apodClient.getApod("DEMO_KEY");
// Do all other stuffs here
// while the request is in-flight
APOD apod = response.get();
System.out.println(apod.title);
接口认证
如果我们的接口中有多个方法都需要一个 API 密钥,则可以通过向基本 OkHttpClient 添加一个 HttpInterceptor 来配置它。自定义客户端可以添加到 Retrofit.Builder。示例代码如下:
实施:创建自定义客户端
private OkHttpClient clientWithApiKey(String apiKey) {
return new OkHttpClient.Builder()
.addInterceptor(chain -> {
Request originalRequest = chain.request();
HttpUrl newUrl = originalRequest.url().newBuilder()
.addQueryParameter("api_key", apiKey).build();
Request request = originalRequest.newBuilder().url(newUrl).build();
return chain.proceed(request);
}).build();
}
在这里,除了最简单的情况外,这种Java API 是首选。构建类来表示远程 API 是一个很好的抽象,可以很好地与依赖注入配合使用,让 Retrofit 基于可定制的 OkHttp 客户端为您创建它们很棒。
用于Java 的其他 HTTP 客户端
如果以上都不是您想要的,请查看下面列出的建议:
- REST Assured,一个用于测试 REST 服务的 HTTP 客户端。提供用于发出请求的流畅界面和用于对响应进行断言的有用方法。
- cvurl是Java 11 HttpClient 的包装器,它可以消除您在发出复杂请求时可能遇到的一些尖锐边缘。
- Feign – 与 Retrofit 类似,Feign 可以从带注释的接口构建类。 Feign 非常灵活,具有多种选项来发出和读取请求、指标、重试等。
- Spring RestTemplate(同步)和 WebClient(异步)客户端——如果你已经将 Spring 用于项目中的其他所有内容,那么坚持使用该生态系统可能是个好主意。 Baeldung 有一篇文章比较了它们。
- MicroProfile Rest Client – “从带注释的接口构建类”模式中的另一个客户端,这个很有趣,因为您也可以重用相同的接口来创建 Web 服务器,并确保客户端和服务器匹配。如果您正在为该服务构建服务和客户端,那么它可能适合您。
A.密码认证
除了最简单的情况外,我喜欢这种Java API。构建类来表示远程 API 是一个很好的抽象,可以很好地与依赖注入配合使用,让 Retrofit 基于可定制的 OkHttp 客户端为您创建它们很棒。这里我们可以使用 PasswordAuthentication 类,它只是这些值的持有者。
Note: Every request should use the same username and password. The Authenticator class provides a number of getXXX (for example, getRequestingSite()) methods that can be used to find out what values should be provided.
例子
import java.io.*;
class {
public static void main (String[] args) {
HttpClient.newBuilder().authenticator(new Authenticator() {
// @Override
protectedPasswordAuthenticationgetPasswordAuthentication() {
return new PasswordAuthentication( "username", "password".toCharArray());
}
}).build();
}
}
B.设置重定向策略
调用网站页面时,有时您要访问的页面已移至其他地址。在这种情况下,您将收到 HTTP 状态代码 3xx,通常带有有关新 URI 的信息。通过设置适当的重定向策略,HttpClient 可以自动将请求重定向到新的 URI。所有重定向策略都在枚举中定义和描述,名称为 HttpClient.Redirect。
Note: Using the followRedirects() method, you can set the redirect polic
HttpClient.newBuilder()
followRedirects(HttpClient.Redirect.ALWAYS)
build();
C.发送同步或异步请求
- HttpClient 提供了两种向服务器发送请求的可能性:
- send(...) 同步(阻塞直到响应到来)
- sendAsync(...) 异步(不等待响应,非阻塞)
- 到目前为止,send(…) 方法自然会等待响应:
示例 1:
HttpResponse response = HttpClient.newBuilder()
.build()
.send(request, BodyHandlers.ofString());
此调用返回一个 HttpResponse 对象,这意味着只有在响应已经返回时才会执行应用程序流中的下一条指令
这种方法有很多缺点,尤其是在处理大量数据时。为了克服这个限制,你可以使用 sendAsync(…) 方法,它返回一个 CompletableFeature
CompletableFuture response = HttpClient.newBuilder()
.build()
.sendAsync(request, BodyHandlers.ofString())
.thenApply(HttpResponse::body);
Note: The API can also deal with multiple responses and stream the request and response bodies
示例 2:
class {
public static void main (String[] args) {
Listuris = Arrays.asList()
new URI("https://postman-echo.com/get?foo1=bar1"),
new URI("https://postman-echo.com/get?foo2=bar2");
HttpClient client = HttpClient.newHttpClient();
List requests = uris.stream()
.map(HttpRequest::newBuilder)
.map(reqBuilder ->reqBuilder.build())
.collect(Collectors.toList());
CompletableFuture.allOf(requests.stream()
.map(request ->client.sendAsync(request, ofString()))
.toArray(CompletableFuture>[]::new))
.join();
}
}
CompletableFuture.allOf(requests.stream()
.map(request ->client.sendAsync(request, ofString()))
.toArray(CompletableFuture>[]::new))
.join();
}
}
代码说明及链接如下:
所以你的主代码会继续执行,以后配置回调,然后Accept。但是只有在服务器返回响应时才会触发此回调。 HTTP 客户端将使用后台线程进行调用。请注意,服务器响应需要一段时间。与此同时,您的申请将结束。那么你如何使这个例子工作呢?以后调用join方法。这将加入您的代码在未来运行的应用程序线程。此时在代码中,join 方法将等待,直到 future 完成。如果它已完成,那也意味着您的 thenAccept 回调将运行。事实上,当你运行这个例子时,你会得到预期的结果。
示例 3:
class {
// Main driver method
public static void main (String[] args) {
HttpClient.newBuilder().authenticator(new Authenticator() {
// @Override
protectedPasswordAuthenticationgetPasswordAuthentication() {
return new PasswordAuthentication("username", "password".toCharArray());
}
}).build();
}
}
CompletableFuture> response1 = HttpClient.newBuilder()
.executor(executor)
.build()
.sendAsync(request,asString());
Note: By default, the HttpClient uses executor java.util.concurrent.Executors.newCachedThreadPool().
同步和阻塞发送 API 更易于使用,但异步 API 将帮助您创建响应式和更具可扩展性的应用程序,因此您必须选择最适合您的用例的应用程序。
HTTP 客户端 API 的目标
HTTP 客户端 API 有许多旨在帮助您了解此 API 的重要特征以及如何在编程中使用它的目标:
- 易于用于常见情况,包括简单的阻塞模式。
- 简单简洁的 API,满足 80-90% 的应用需求
- 支持标准和通用的身份验证机制
- 轻松设置WebSocket接口握手
- 对嵌入式系统要求友好;特别是避免永久运行计时器线程
Note: Supports HTTPS/TLS
- It must be performance oriented and its memory footprint less than older and third-party APIs
- Provides non-blocking request and response semantics through CompletableFuture, which can be chained to trigger dependent actions.