Tips
做一个终身学习的人。
在此章中,主要介绍以下内容:
JDK 9 将 HTTP/2 Client API 作为名为 jdk.incubator.httpclient 的孵化器模块。 该模块导出包含所有公共 API 的 jdk.incubator.http 包。 孵化器模块不是 Java SE 的一部分。 在 Java SE 10 中,它将被标准化,并成为 Java SE 10 的一部分,否则将被删除。 请参阅 http://openjdk.java.net/jeps/11 上的网页,以了解有关 JDK 中孵化器模块的更多信息。
孵化器模块在编译时或运行时未被默认解析,因此需要使用
选项将 jdk.incubator.httpclient 模块添加到默认的根模块中,如下所示:
- --add-modules
- <javac|java|jmod...>
- -add-modules jdk.incubator.httpclient ...
如果另一个模块读取并解析了第二个模块,则也相应解析了孵化器模块。 在本章中,将创建一个读取 jdk.incubator.httpclient 模块的模块,不必使用
选项来解析。
- -add-modules
因为孵化器模块提供的 API 还不是最终的,当在编译时或运行时使用孵化器模块时,会在标准错误上打印警告。 警告信息如下所示:
- WARNING: Using incubator modules: jdk.incubator.httpclient
孵化器模块的名称和包含孵化器 API 的软件包以 jdk.incubator 开始。 一旦它们被标准化并包含在 Java SE 中,它们的名称将被更改为使用标准的 Java 命名约定。 例如,模块名称 jdk.incubator.httpclient 可能会在 Java SE 10 中成为 java.httpclient。
因为 jdk.incubator.httpclient 模块不在 Java SE 中,所以将不会为此模块找到 Javadoc。 为了生成此模块的 Javadoc,并将其包含在本书的源代码中。 可以使用下载的源代码中的 Java9Revealed/jdk.incubator.httpclient/dist/javadoc/index.html 文件访问 Javadoc。 使用 JDK 9 早期访问构建 158 的 JDK 版本来生成 Javadoc。 API 可能会改变,可能需要重新生成 Javadoc。 以下是具体的步骤:
自 JDK 1.0 以来,Java 已经支持 HTTP/1.1。 HTTP API 由 java.net 包中的几种类型组成。 现有的 API 有以下问题:
2015 年 5 月,IETF(Internet Engineering Task Force)发布了 HTTP/2 规范。 有关 HTTP/2 规范的完整文本,请访问 https://tools.ietf.org/html/rfc7540。 HTTP/2 不会修改应用程序级语义。 也就是说,对应用程序中的 HTTP 协议的了解和使用情况并没有改变。 它具有更有效的方式准备数据包,然后发送到客户端和服务器之间的电线。 所有之前知道的 HTTP,如 HTTP 头,方法,状态码,URL 等都保持不变。 HTTP/2 尝试解决与 HTTP/1 连接所面临的许多性能相关的问题:
JDK 9 不是更新现有的 HTTP/1.1 API,而是提供了一个支持 HTTP/1.1 和 HTTP/2 的 HTTP/2 Client API。 该 API 旨在最终取代旧的 API。 新 API 还包含使用 WebSocket 协议开发客户端应用程序的类和接口。 有关完整的 WebSocket 协议规范,请访问 https://tools.ietf.org/html/rfc6455。 新的 HTTP/2 客户端 API 与现有的 API 相比有以下几个好处:
新的 API 由不到 20 种类型组成,其中有四种是主要类型。 当使用这四种类型时,会使用其他类型。 新 API 还使用旧 API 中的几种类型。 新的 API 位于 jdk.incubator.httpclient 模块中的 jdk.incubator.http 包中。 主要类型有三个抽象类和一个接口:
- HttpClient class HttpRequest class HttpResponse class WebSocket interface
类的实例是用于保存可用于多个 HTTP 请求的配置的容器,而不是为每个 HTTP 请求单独设置它们。
- HttpClient
类的实例表示可以发送到服务器的 HTTP 请求。
- HttpRequest
类的实例表示 HTTP 响应。
- HttpResponse
接口的实例表示一个 WebSocket 客户端。 可以使用 Java EE 7 WebSocket API 创建 WebSocket 服务器。
- WebSocket
使用构建器创建
,
- HttpClient
和
- HttpRequest
的实例。 每个类型都包含一个名为
- WebSocket
的嵌套类 / 接口,用于构建该类型的实例。 请注意,不用创建
- Builder
,它作为所做的 HTTP 请求的一部分返回。 新的 HTTP/2 Client API 非常简单,只需在一个语句中读取 HTTP 资源! 以下代码段使用 GET 请求,以 URL https://www.google.com / 作为字符串读取内容:
- HttpResponse
- String responseBody = HttpClient.newHttpClient().send(HttpRequest.newBuilder(new URI("https://www.google.com/")).GET().build(), BodyHandler.asString()).body();
处理 HTTP 请求的典型步骤如下:
在本章中使用了许多涉及与 Web 服务器交互的例子。 不是使用部署在 Internet 上的 Web 应用程序,而是在 NetBeans 中创建了一个可以在本地部署的 Web 应用程序项目。 如果更喜欢使用其他 Web 应用程序,则需要更改示例中使用的 URL。
NetBeans Web 应用程序位于源代码的 webapp 目录中。 通过在 GlassFish 服务器 4.1.1 和 Tomcat 8/9 上部署 Web 应用程序来测试示例。 可以从 https://netbeans.org / 下载带有 GlassFish 服务器的 NetBeans IDE。 在 8080 端口的 GlassFish 服务器上运行 HTTP 监听器。如果在另一个端口上运行 HTTP 监听器,则需要更改示例 URL 中的端口号。
本章的所有 HTTP 客户端程序都位于 com.jdojo.http.client 模块中,其声明如下所示。
- // module-info.java
- module com.jdojo.http.client {
- requires jdk.incubator.httpclient;
- }
HTTP 请求需要将配置信息发送到服务器,以便服务器知道要使用的身份验证器,SSL 配置详细信息,要使用的 cookie 管理器,代理信息,服务器重定向请求时的重定向策略等。
类的实例保存这些特定于请求的配置,它们可以重用于多个请求。 可以根据每个请求覆盖其中的一些配置。 发送 HTTP 请求时,需要指定将提供请求的配置信息的
- HttpClient
对象。
- HttpClient
包含用于所有 HTTP 请求的以下信息:验证器,cookie 管理器,执行器,重定向策略,请求优先级,代理选择器,SSL 上下文,SSL 参数和 HTTP 版本。
- HttpClient
认证者是
类的实例。 它用于 HTTP 身份验证。 默认是不使用验证器。
- java.net.Authenticator
Cookie 管理器用于管理 HTTP Cookie。 它是
类的一个实例。 默认是不使用 cookie 管理器。
- java.net.CookieManager
执行器是
接口的一个实例,用于发送和接收异步 HTTP 请求和响应。 如果未指定,则提供默认执行程序。
- java.util.concurrent.Executor
重定向策略是
枚举的常量,它指定如何处理服务器的重定向问题。 默认值
- HttpClient.Redirect
,这意味着服务器发出的重定向不会被遵循。
- NEVER
请求优先级是 HTTP/2 请求的默认优先级,可以在 1 到 256(含)之间。 这是服务器优先处理请求的一个提示。 更高的值意味着更高的优先级。
代理选择器是
类的一个实例,用于选择要使用的代理服务器。 默认是不使用代理服务器。
- java.net.ProxySelector
SSL 上下文是提供安全套接字协议实现的
类的实例。当不需要指定协议或不需要客户端身份验证时, 提供了一个默认的
- javax.net.ssl.SSLContext
,此选项将起作用。
- SSLContext
SSL 参数是 SSL/TLS/DTLS 连接的参数。 它们保存在
类的实例中。
- javax.net.ssl.SSLParameters
HTTP 版本是 HTTP 的版本,它是 1.1 或 2. 它被指定为
枚举的常量:HTTP_1_1 和 HTTP_2。 它尽可能请求一个特定的 HTTP 协议版本。 默认值为 HTTP_1_1。
- HttpClient.Version
Tips
是不可变的。 当构建这样的请求时,存储在
- HttpClient
中的一些配置可能会被 HTTP 请求覆盖。
- HttpClient
类是抽象的,不能直接创建它的对象。 有两种方法可以创建一个
- HttpClient
对象:
- HttpClient
类的
- HttpClient
静态方法
- newHttpClient()
类的
- HttpClient.Builder
方法
- build()
以下代码段获取默认的
对象:
- HttpClient
- // Get the default HttpClient
- HttpClient defaultClient = HttpClient.newHttpClient();
也可以使用
类创建
- HttpClient.Builder
。
- HttpClient
静态方法返回一个新的
- HttpClient.newBuilder()
类实例。
- HttpClient.Builder
类提供了设置每个配置值的方法。 配置的值被指定为方法的参数,该方法返回构建器对象本身的引用,因此可以链接多个方法。 最后,调用返回
- HttpClient.Builder
对象的
- HttpClient
方法。 以下语句创建一个
- build()
,重定向策略设置为
- HttpClient
,HTTP 版本设置为 HTTP_2:
- ALWAYS
- // Create a custom HttpClient
- HttpClient httpClient = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.ALWAYS).version(HttpClient.Version.HTTP_2).build();
类包含对应于每个配置设置的方法,该设置返回该配置的值。 这些方法如下:
- HttpClient
- Optional < Authenticator > authenticator() Optional < CookieManager > cookieManager() Executor executor() HttpClient.Redirect followRedirects() Optional < ProxySelector > proxy() SSLContext sslContext() Optional < SSLParameters > sslParameters() HttpClient.Version version()
请注意,
类中没有 setter 方法,因为它是不可变的。 不能使用
- HttpClient
自己本身的对象。 在使用
- HttpClient
对象向服务器发送请求之前,需要使用
- HttpClient
对象。
- HttpRequest
类包含以下三种向服务器发送请求的方法:
- HttpClient
- <T>
- HttpResponse
- <T>
- send(HttpRequest req, HttpResponse.BodyHandler
- <T>
- responseBodyHandler)
- <T>
- CompletableFuture
- <HttpResponse<T>
- > sendAsync(HttpRequest req, HttpResponse.BodyHandler
- <T>
- responseBodyHandler)
- <U,T>
- CompletableFuture
- <U>
- sendAsync(HttpRequest req, HttpResponse.MultiProcessor
- <U,T>
- multiProcessor)
方法同步发送请求,而
- send()
方法异步发送请求。
- sendAsync()
客户端应用程序使用 HTTP 请求与 Web 服务器进行通信。 它向服务器发送一个请求,服务器发回对应的 HTTP 响应。
类的实例表示 HTTP 请求。 以下是处理 HTTP 请求所需执行的步骤:
- HttpRequest
需要使用构建器对象,该对象是
类的实例来创建一个
- HttpRequest.Builder
。 可以使用
- HttpRequest
类的以下静态方法获取
- HttpRequest
:
- HttpRequest.Builder
- HttpRequest.Builder newBuilder() HttpRequest.Builder newBuilder(URI uri)
以下代码片段显示了如何使用这些方法来获取
实例:
- HttpRequest.Builder
- // A URI to point to google
- URI googleUri = new URI("http://www.google.com");
- // Get a builder for the google URI
- HttpRequest.Builder builder1 = HttpRequest.newBuilder(googleUri);
- // Get a builder without specifying a URI at this time
- HttpRequest.Builder builder2 = HttpRequest.newBuilder();
拥有 HTTP 请求构建器后,可以使用构建器的方法为请求设置不同的参数。 所有方法返回构建器本身,因此可以链接它们。 这些方法如下:
- HttpRequest.Builder DELETE(HttpRequest.BodyProcessor body) HttpRequest.Builder expectContinue(boolean enable) HttpRequest.Builder GET() HttpRequest.Builder header(String name, String value) HttpRequest.Builder headers(String...headers) HttpRequest.Builder method(String method, HttpRequest.BodyProcessor body) HttpRequest.Builder POST(HttpRequest.BodyProcessor body) HttpRequest.Builder PUT(HttpRequest.BodyProcessor body) HttpRequest.Builder setHeader(String name, String value) HttpRequest.Builder timeout(Duration duration) HttpRequest.Builder uri(URI uri) HttpRequest.Builder version(HttpClient.Version version)
使用
将
- HttpClient
发送到服务器。 当构建 HTTP 请求时,可以使用
- HttpRequest
方法通过
- version()
对象设置 HTTP 版本值,该方法将在发送此请求时覆盖
- HttpRequest.Builder
中设置的 HTTP 版本。 以下代码片段将 HTTP 版本设置为 2.0,以覆盖默认
- HttpClient
对象中的
- HttpClient
的默认值:
- NEVER
- // By default a client uses HTTP 1.1. All requests sent using this
- // HttpClient will use HTTP 1.1 unless overridden by the request
- HttpClient client = HttpClient.newHttpClient();
- // A URI to point to google
- URI googleUri = new URI("http://www.google.com");
- // Get an HttpRequest that uses HTTP 2.0
- HttpRequest request = HttpRequest.newBuilder(googleUri).version(HttpClient.Version.HTTP_2).build();
- // The client object contains HTTP version as 1.1 and the request
- // object contains HTTP version 2.0. The following statement will
- // send the request using HTTP 2.0, which is in the request object.
- HttpResponse < String > r = client.send(request, BodyHandler.asString());
方法指定请求的超时时间。 如果在指定的超时时间内未收到响应,则会抛出
- timeout()
异常。
- HttpTimeoutException
HTTP 请求可能包含名为 expect 的首部字段,其值为 "100-Continue"。 如果设置了此首部字段,则客户端只会向服务器发送头文件,并且预计服务器将发回错误响应或 100-Continue 响应。 收到此响应后,客户端将请求主体发送到服务器。 在客户端发送实际请求体之前,客户端使用此技术来检查服务器是否可以基于请求的首部处理请求。 默认情况下,此首部字段未设置。 需要调用请求构建器的
方法来启用此功能。 请注意,调用请求构建器的
- expectContinue(true)
方法不会启用此功能。 必须使用
- header("expect", "100-Continue")
方法启用它。
- expectContinue(true)
- // Enable the expect=100-Continue header in the request
- HttpRequest.Builder builder = HttpRequest.newBuilder().expectContinue(true);
HTTP 请求中的首部(header)是键值对的形式。 可以有多个首部字段。 可以使用
类的
- HttpRequest.Builder
,
- header()
和
- headers()
方法向请求添加首部字段。 如果
- setHeader()
和
- header()
方法尚未存在,则会添加首部字段。 如果首部字段已经添加,这些方法什么都不做。
- headers()
方法如果存在,将替换首部字段; 否则,它会添加首部字段。
- setHeader()
和
- header()
方法允许一次添加 / 设置一个首部字段,而
- setHeader()
方法可以添加多个。
- headers()
方法采用一个可变参数,它应该按顺序包含键值对。 以下代码片段显示了如何为 HTTP 请求设置首部字段:
- headers()
- // Create a URI
- URI calc = new URI("http://localhost:8080/webapp/Calculator");
- // Use the header() method
- HttpRequest.Builder builder1 = HttpRequest.newBuilder(calc).header("Content-Type", "application/x-www-form-urlencoded").header("Accept", "text/plain");
- // Use the headers() method
- HttpRequest.Builder builder2 = HttpRequest.newBuilder(calc).headers("Content-Type", "application/x-www-form-urlencoded", "Accept", "text/plain");
- // Use the setHeader() method
- HttpRequest.Builder builder3 = HttpRequest.newBuilder(calc).setHeader("Content-Type", "application/x-www-form-urlencoded").setHeader("Accept", "text/plain");
一些 HTTP 请求的主体包含使用 POST 和 PUT 方法的请求等数据。 使用主体处理器设置 HTTP 请求的内容实体,该体处理器是
的静态嵌套接口。
- HttpRequest.BodyProcessor
接口包含以下静态工厂方法,它们返回一个 HTTP 请求的处理器,请求特定类型的资源(例如
- HttpRequest.BodyProcessor
,
- String
或
- byte []
):
- File
- HttpRequest.BodyProcessor fromByteArray(byte[] buf) HttpRequest.BodyProcessor fromByteArray(byte[] buf, int offset, int length) HttpRequest.BodyProcessor fromByteArrays(Iterable < byte[] > iter) HttpRequest.BodyProcessor fromFile(Path path) HttpRequest.BodyProcessor fromInputStream(Supplier < ?extends InputStream > streamSupplier) HttpRequest.BodyProcessor fromString(String body) HttpRequest.BodyProcessor fromString(String s, Charset charset)
这些方法的第一个参数表示请求的内容实体的数据源。 例如,如果 String 对象提供请求的内容实体,则使用
方法获取一个处理器。
- fromString(String body)
Tips
类包含
- HttpRequest
静态方法,该方法返回一个
- noBody()
,它不处理请求内容实体。 通常,当 HTTP 方法不接受正文时,此方法可以与
- HttpRequest.BodyProcessor
方法一起使用,但是
- method()
方法需要传递一个实体处理器。
- method()
一个请求是否可以拥有一个内容实体取决于用于发送请求的 HTTP 方法。 DELETE,POST 和 PUT 方法都有一个实体,而 GET 方法则没有。
类包含一个与 HTTP 方法名称相同的方法来设置请求的方法和实体。 例如,要使用 POST 方法与主体,构建器有
- HttpRequest.Builder
方法。
- POST(HttpRequest.BodyProcessor body)
还有许多其他 HTTP 方法,如 HEAD 和 OPTIONS,它们没有
类的相应方法。 该类包含一个可用于任何 HTTP 方法的
- HttpRequest.Builder
。 当使用
- method(String method, HttpRequest.BodyProcessor body)
方法时,请确保以大写的方式指定方法名称,例如 GET,POST,HEAD 等。以下是这些方法的列表:
- method()
- HttpRequest.Builder DELETE(HttpRequest.BodyProcessor body) HttpRequest.Builder method(String method, HttpRequest.BodyProcessor body) HttpRequest.Builder POST(HttpRequest.BodyProcessor body) HttpRequest.Builder PUT(HttpRequest.BodyProcessor body)
以下代码片段从 String 中设置 HTTP 请求的内容实体,通常在将 HTML 表单发布到 URL 时完成。 表单数据由三个
,
- n1
和
- n2
字段组成。
- op
- URI calc = new URI("http://localhost:8080/webapp/Calculator");
- // Compose the form data with n1 = 10, n2 = 20. And op = +
- String formData = "n1=" + URLEncoder.encode("10", "UTF-8") + "&n2=" + URLEncoder.encode("20", "UTF-8") + "&op=" + URLEncoder.encode("+", "UTF-8");
- HttpRequest.Builder builder = HttpRequest.newBuilder(calc).header("Content-Type", "application/x-www-form-urlencoded").header("Accept", "text/plain").POST(HttpRequest.BodyProcessor.fromString(formData));
创建 HTTP 请求只需调用
上的
- HttpRequest.Builder
方法,该方法返回一个
- build()
对象。 以下代码段创建了使用 HTTP GET 方法的
- HttpRequest
:
- HttpRequest
- HttpRequest request = HttpRequest.newBuilder().uri(new URI("http://www.google.com")).GET().build();
以下代码片段使用 HTTP POST 方法构建首部信息和内容实体的 Http 请求:
- // Build the URI and the form's data
- URI calc = new URI("http://localhost:8080/webapp/Calculator");
- String formData = "n1=" + URLEncoder.encode("10", "UTF-8") + "&n2=" + URLEncoder.encode("20", "UTF-8") + "&op=" + URLEncoder.encode("+", "UTF-8");
- // Build the HttpRequest object
- HttpRequest request = HttpRequest.newBuilder(calc).header("Content-Type", "application/x-www-form-urlencoded").header("Accept", "text/plain").POST(HttpRequest.BodyProcessor.fromString(formData)).build();
请注意,创建
对象不会将请求发送到服务器。 需要调用
- HttpRequest
类的
- HttpClient
或
- send()
方法将请求发送到服务器。
- sendAsync()
以下代码片段使用 HTTP HEAD 请求方法创建一个
对象。 请注意,它使用
- HttpRequest
类的
- HttpRequest.Builder
方法来指定 HTTP 方法。
- method()
- HttpRequest request = HttpRequest.newBuilder(new URI("http://www.google.com")).method("HEAD", HttpRequest.noBody()).build();
一旦拥有
对象,可以将请求发送到服务器并同步或异步地接收响应。
- HttpRequest
类的实例表示从服务器接收到的响应,其中类型参数 T 表示响应内容实体的类型,例如
- HttpResponse<T>
,
- String
或
- byte []
。 可以使用
- Path
类的以下方法发送 HTTP 请求并接收 HTTP 响应:
- HttpRequest
- <T>
- HttpResponse
- <T>
- send(HttpRequest req, HttpResponse.BodyHandler
- <T>
- responseBodyHandler)
- <T>
- CompletableFuture
- <HttpResponse<T>
- > sendAsync(HttpRequest req, HttpResponse.BodyHandler
- <T>
- responseBodyHandler)
- <U,T>
- CompletableFuture
- <U>
- sendAsync(HttpRequest req, HttpResponse.MultiProcessor
- <U,T>
- multiProcessor)
方法是同步的。 也就是说,它会一直阻塞,直到收到响应。
- send()
方法异步处理响应。 它立即返回一个
- sendAsync()
,当响应准备好进行处理时,它就会完成。
- CompletableFuture<HttpResponse>
HTTP 响应包含状态代码,响应首部和响应内容实体。 一旦从服务器接收到状态代码和首部,但在接收到正文之前,
对象就可使用。
- HttpResponse
类的
- HttpResponse
方法返回响应的状态代码,类型为
- statusCode()
。
- int
类的
- HttpResponse
方法返回响应的首部,作为
- headers()
接口的实例。
- HttpHeaders
接口包含以下方法,通过名称或所有首部方便地检索首部的值作为
- HttpHeaders
类型:
- Map <String,List <String >>
- List < String > allValues(String name) Optional < String > firstValue(String name) Optional < Long > firstValueAsLong(String name) Map < String,
- List < String >> map()
下面包含一个完整的程序,用于向 google 发送请求,并附上 HEAD 请求。 它打印接收到的响应的状态代码和首部。 你可能得到不同的输出。
- // GoogleHeadersTest.java
- package com.jdojo.http.client;
- import java.io.IOException;
- import java.net.URI;
- import java.net.URISyntaxException;
- import jdk.incubator.http.HttpClient;
- import jdk.incubator.http.HttpRequest;
- import jdk.incubator.http.HttpResponse;
- public class GoogleHeadersTest {
- public static void main(String[] args) {
- try {
- URI googleUri = new URI("http://www.google.com");
- HttpClient client = HttpClient.newHttpClient();
- HttpRequest request = HttpRequest.newBuilder(googleUri).method("HEAD", HttpRequest.noBody()).build();
- HttpResponse < ?>response = client.send(request, HttpResponse.BodyHandler.discard(null));
- // Print the response status code and headers
- System.out.println("Response Status Code:" + response.statusCode());
- System.out.println("Response Headers are:");
- response.headers().map().entrySet().forEach(System.out: :println);
- } catch(URISyntaxException | InterruptedException | IOException e) {
- e.printStackTrace();
- }
- }
- }
输出的结果为:
- WARNING: Using incubator modules: jdk.incubator.httpclient Response Status Code: 200 Response Headers are: accept - ranges = [none] cache - control = [private, max - age = 0] content - type = [text / html; charset = ISO - 8859 - 1] date = [Sun, 26 Feb 2017 16 : 39 : 36 GMT] expires = [ - 1] p3p = [CP = "This is not a P3P policy! See https://www.google.com/support/accounts/answer/151657?hl=en for more info."] server = [gws] set - cookie = [NID = 97 = Kmz52m8Zdf4lsNDsnMyrJomx_2kD7lnWYcNEuwPWsFTFUZ7yli6DbCB98Wv - SlxOfKA0OoOBIBgysuZw3ALtgJjX67v7 - mC5fPv88n8VpwxrNcjVGCfFrxVro6gRNIrye4dAWZvUVfY28eOM; expires = Mon, 28 - Aug - 2017 16 : 39 : 36 GMT; path = /; domain=.google.com; HttpOnly]
- transfer-encoding=[chunked]
- vary=[Accept-Encoding]
- x-frame-options=[SAMEORIGIN]
- x-xss-protection=[1; mode=block]/
处理 HTTP 响应的内容实体是两步过程:
类的
- HttpClient
或
- send()
方法发送请求时,需要指定响应主体处理程序,它是
- sendAsync()
接口的实例。
- HttpResponse.BodyHandler<T>
方法。 响应状态代码和首部传递给
- apply()
方法。
- apply()
方法返回
- apply()
接口的实例,它读取响应实体并将读取的数据转换为类型 T。
- HttpResponse.BodyProcessor
不要担心处理响应实体的这些细节。 提供了
的几个实现。 可以使用
- HttpResponse.BodyHandler<T>
接口的以下静态工厂方法获取其不同类型参数 T 的实例:
- HttpResponse.BodyHandler
- HttpResponse.BodyHandler < byte[] > asByteArray() HttpResponse.BodyHandler < Void > asByteArrayConsumer(Consumer < Optional < byte[] >> consumer) HttpResponse.BodyHandler < Path > asFile(Path file) HttpResponse.BodyHandler < Path > asFile(Path file, OpenOption...openOptions) HttpResponse.BodyHandler < Path > asFileDownload(Path directory, OpenOption...openOptions) HttpResponse.BodyHandler < String > asString() HttpResponse.BodyHandler < String > asString(Charset charset) < U > HttpResponse.BodyHandler < U > discard(U value)
这些方法的签名足够直观,可以告诉你他们处理什么类型的响应实体。 例如,如果要将响应实体作为
获取,请使用
- String
方法获取一个实体处理程序。
- asString()
方法返回一个实体处理程序,它丢弃响应实体并返回指定的值作为主体。
- discard(U value)
类的
- HttpResponse<T>
方法返回类型为 T 的响应实体。
- body()
以下代码段向 google 发送 GET 请求,并以 String 形式检索响应实体。 这里忽略了了异常处理逻辑。
- import java.net.URI;
- import jdk.incubator.http.HttpClient;
- import jdk.incubator.http.HttpRequest;
- import jdk.incubator.http.HttpResponse;
- import static jdk.incubator.http.HttpResponse.BodyHandler.asString;...
- // Build the request
- HttpRequest request = HttpRequest.newBuilder().uri(new URI("http://google.com")).GET().build();
- // Send the request and get a Response
- HttpResponse < String > response = HttpClient.newHttpClient().send(request, asString());
- // Get the response body and print it
- String body = response.body();
- System.out.println(body);
输出结果为:
- WARNING: Using incubator modules: jdk.incubator.httpclient < HTML > <HEAD > <meta http - equiv = "content-type"content = "text/html;charset=utf-8" > <TITLE > 301 Moved < /TITLE></HEAD > <BODY > <H1 > 301 Moved < /H1>
- The document has moved
- <A HREF="http:/ / www.google.com / ">here</A>.
- </BODY></HTML>"
该示例返回一个状态代码为 301 的响应正文,表示 URL 已经移动。 输出还包含移动的 URL。 如果将
中的以下重定向策略设置为 "ALWAYS",则该请求将重新提交到已移动的 URL。 以下代码片段可解决此问题:
- HttpClient
- // The request will follow the redirects issues by the server
- HttpResponse < String > response = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.ALWAYS).build().send(request, asString());
下面包含一个完整的程序,它显示如何使用一个 POST 请求与内容实体,并异步处理响应。 源代码中的 Web 应用程序包含为 Calculator 的 servlet。 Calculator servlet 的源代码不会在这里显示。 servlet 接受请求中的三个参数,命名为 n1,n2 和 op,其中 n1 和 n2 是两个数字,op 是一个运算符(+, - ,* 或 /)。 响应是一个纯文本,并包含了运算符及其结果。 程序中的 URL 假定你已在本机上部署了 servlet,并且 Web 服务器正在端口 8080 上运行。如果这些假设不正确,请相应地修改程序。 如果 servlet 被成功调用,你将得到这里显示的输出。 否则,将获得不同的输出。
- // CalculatorTest.java
- package com.jdojo.http.client;
- import java.io.IOException;
- import java.net.URI;
- import java.net.URISyntaxException;
- import java.net.URLEncoder;
- import jdk.incubator.http.HttpClient;
- import jdk.incubator.http.HttpRequest;
- import static jdk.incubator.http.HttpRequest.BodyProcessor.fromString;
- import jdk.incubator.http.HttpResponse;
- public class CalculatorTest {
- public static void main(String[] args) {
- try {
- URI calcUri = new URI("http://localhost:8080/webapp/Calculator");
- String formData = "n1=" + URLEncoder.encode("10", "UTF-8") + "&n2=" + URLEncoder.encode("20", "UTF-8") + "&op=" + URLEncoder.encode("+", "UTF-8");
- // Create a request
- HttpRequest request = HttpRequest.newBuilder().uri(calcUri).header("Content-Type", "application/x-www-form-urlencoded").header("Accept", "text/plain").POST(fromString(formData)).build();
- // Process the response asynchronously. When the response
- // is ready, the processResponse() method of this class will
- // be called.
- HttpClient.newHttpClient().sendAsync(request, HttpResponse.BodyHandler.asString()).whenComplete(CalculatorTest: :processResponse);
- try {
- // Let the current thread sleep for 5 seconds,
- // so the async response processing is complete
- Thread.sleep(5000);
- } catch(InterruptedException ex) {
- ex.printStackTrace();
- }
- } catch(URISyntaxException | IOException e) {
- e.printStackTrace();
- }
- }
- private static void processResponse(HttpResponse < String > response, Throwable t) {
- if (t == null) {
- System.out.println("Response Status Code: " + response.statusCode());
- System.out.println("Response Body: " + response.body());
- } else {
- System.out.println("An exception occurred while " + "processing the HTTP request. Error: " + t.getMessage());
- }
- }
- }
输出结果为:
- WARNING: Using incubator modules: jdk.incubator.httpclient Response Status Code: 200 Response Body: 10 + 20 = 30.0
使用响应实体处理程序可以节省开发人员的大量工作。 在一个语句中,可以下载并将 URL 的内容保存在文件中。 以下代码片段将 google 的内容作为 google.html 的文件保存在当前目录中。 下载完成后,打印下载文件的路径。 如果发生错误,则会打印异常的堆栈跟踪。
- HttpClient.newBuilder().followRedirects(HttpClient.Redirect.ALWAYS).build().sendAsync(HttpRequest.newBuilder().uri(new URI("http://www.google.com")).GET().build(), asFile(Paths.get("google.html"))).whenComplete((HttpResponse < Path > response, Throwable exception) - >{
- if (exception == null) {
- System.out.println("File saved to " + response.body().toAbsolutePath());
- } else {
- exception.printStackTrace();
- }
- });
HTTP Trailer 是 HTTP 响应结束后由服务器发送的键值列表。 许多服务器通常不使用 HTTP Trailer。
类包含一个
- HttpResponse
方法,它作为
- trailers()
的实例返回响应 Trailer。 注意返回的对象类型的名称——
- CompletableFuture <HttpHeaders>
。 HTTP/2 Client API 确实有一个名为
- HttpHeaders
的类型。 需要检索响应实体,然后才能检索 Trailer。 目前,HTTP/2 Client API 不支持处理 HTTP Trailer 了。 以下代码片段显示了如何在 API 支持时打印所有响应 Trailer:
- HttpTrailers
- // Get an HTTP response
- HttpResponse < String > response = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.ALWAYS).build().send(HttpRequest.newBuilder().uri(new URI("http://www.google.com")).GET().build(), asString());
- // Read the response body
- String body = response.body();
- // Process trailers
- response.trailers().whenComplete((HttpHeaders trailers, Throwable t) - >{
- if (t == null) {
- trailers.map().entrySet().forEach(System.out: :println);
- } else {
- t.printStackTrace();
- }
- });
一个 HTTP 请求对应的响应,Web 服务器可以返回 3XX 响应状态码,其中 X 是 0 到 9 之间的数字。该状态码表示客户端需要执行附加操作才能完成请求。 例如,状态代码为 301 表示 URL 已被永久移动到新位置。 响应实体包含替代位置。 默认情况下,在收到 3XX 状态代码后,请求不会重新提交到新位置。 可以将
枚举的以下常量设置为
- HttpClient.Redirect
执行的策略,以防返回的响应包含 3XX 响应状态代码:
- HttpClient
指示应始终遵循重定向。 也就是说,请求应该重新提交到新的位置。
- ALWAYS
表示重定向不应该被遵循。 这是默认值。
- NEVER
表示如果旧位置和新位置使用相同的协议(例如 HTTP 到 HTTP 或 HTTPS 到 HTTPS),则可能会发生重定向。
- SAME_PROTOCOL
表示重定向应始终发生,除非旧位置使用 HTTPS,而新的位置使用了 HTTP。
- SECURE
WebSocket 协议在两个 endpoint(客户端 endpoint 和服务器 endpoint)之间提供双向通信。 endpoint 是指使用 WebSocket 协议的连接的两侧中的任何一个。 客户端 endpoint 启动连接,服务器端点接受连接。 连接是双向的,这意味着服务器 endpoint 可以自己将消息推送到客户端端点。 在这种情况下,也会遇到另一个术语,称为对等体 (peer)。 对等体只是连接的另一端。 例如,对于客户端 endpoint,服务器 endpoint 是对等体,对于服务器 endpoint,客户端 endpoint 是对等体。 WebSocket 会话表示 endpoint 和单个对等体之间的一系列交互。
WebSocket 协议可以分为三个部分:
客户端发起与与服务器的打开握手。 使用 HTTP 与 WebSocket 协议的升级请求进行握手。 服务器通过升级响应响应打开握手。 握手成功后,客户端和服务器交换消息。 消息交换可以由客户端或服务器发起。 最后,任一 endpoint 都可以发送关闭握手; 对方以关闭握手回应。 关闭握手成功后,WebSocket 关闭。
JDK 9 中的 HTTP/2 Client API 支持创建 WebSocket 客户端 endpoint。 要拥有使用 WebSocket 协议的完整示例,需要具有服务器 endpoint 和客户端 endpoint。 以下部分涵盖了创建两者。
创建服务器 Endpoint 需要使用 Java EE。 将简要介绍如何创建一个服务器 Endpoint 示例中使用。 使用 Java EE 7 注解创建一个 WebSocket 服务器 Endpoint。
下面包含
类的代码。 该类包含在源代码的 webapp 目录中的 Web 应用程序中。 将 Web 应用程序部署到 Web 服务器时,此类将部署为服务器 Endpoint。
- TimeServerEndPoint
- // TimeServerEndPoint.java
- package com.jdojo.ws;
- import java.io.IOException;
- import java.time.ZonedDateTime;
- import java.util.concurrent.TimeUnit;
- import javax.websocket.CloseReason;
- import javax.websocket.OnMessage;
- import javax.websocket.OnOpen;
- import javax.websocket.OnClose;
- import javax.websocket.OnError;
- import javax.websocket.Session;
- import javax.websocket.server.ServerEndpoint;
- import static javax.websocket.CloseReason.CloseCodes.NORMAL_CLOSURE;@ServerEndpoint("/servertime") public class TimeServerEndPoint {@OnOpen public void onOpen(Session session) {
- System.out.println("Client connected. ");
- }@OnClose public void onClose(Session session) {
- System.out.println("Connection closed.");
- }@OnError public void onError(Session session, Throwable t) {
- System.out.println("Error occurred:" + t.getMessage());
- }@OnMessage public void onMessage(String message, Session session) {
- System.out.println("Client: " + message);
- // Send messages to the client
- sendMessages(session);
- }
- private void sendMessages(Session session) {
- /* Start a new thread and send 3 messages to the
- client. Each message contains the current date and
- time with zone.
- */
- new Thread(() - >{
- for (int i = 0; i < 3; i++) {
- String currentTime = ZonedDateTime.now().toString();
- try {
- session.getBasicRemote().sendText(currentTime, true);
- TimeUnit.SECONDS.sleep(5);
- } catch(InterruptedException | IOException e) {
- e.printStackTrace();
- break;
- }
- }
- try {
- // Let us close the WebSocket
- session.close(new CloseReason(NORMAL_CLOSURE, "Done"));
- } catch(IOException e) {
- e.printStackTrace();
- }
- }).start();
- }
- }
在
类上使用
- TimeServerEndPoint
注解使该类成为服务器 Endpoint,当它部署到 Web 服务器时。注解 value 元素的值为
- @ServerEndpoint("/servertime")
,这将使 Web 服务器在此 URL 发布此 Endpoint。
- /servertime
该类包含四个方法,它们已经添加了
,
- @onOpen
,
- @onMessage
和
- @onClose
注解。 命名这些方法的名字与这些注解相同。 这些方法在服务器 Endpoint 的生命周期的不同点被调用。 他们以
- @onError
对象为参数。
- Session
对象表示此 Endpoint 与其对等体的交互,这将是客户端。
- Session
当与对等体进行握手成功时,将调用
方法。 该方法打印客户端连接的消息。
- onOpen()
当从对等体接收到消息时,会调用
。 该方法打印它接收的消息,并调用一个名为
- onMessage()
的私有方法。
- sendMessages()
方法启动一个新线程,并向对等体发送三条消息。 线程在发送每条消息后休眠五秒钟。 该消息包含当前日期和时间与时区。 可以同步或异步地向对等体发送消息。 要发送消息,需要获得表示与对等体的会话的
- sendMessages()
接口的引用。 在
- RemoteEndpoint
实例上使用
- Session
和
- getBasicRemote()
方法来获取可以分别同步和异步发送消息的
- getAsyncRemote()
和
- RemoteEndpoint.Basic
实例。 一旦得到了对等体(远程 endpoint)的引用,可以调用其几个
- RemoteEndpont.Async
方法来向对等体发送不同类型的数据。
- sendXxx()
- // Send a synchronous text message to the peer
- session.getBasicRemote().sendText(currentTime, true);
方法中的第二个参数指示是否是发送的部分消息的最后一部分。 如果消息完成,请使用 true。
- sendText()
在所有消息发送到对等体后,使用
方法发送关闭消息。 该方法接收封闭了一个关闭代码和一个紧密原因的
- sendClose()
类的对象。 当对等体收到一个关闭消息时,对等体需要响应一个关闭消息,之后 WebSocket 连接被关闭。
- CloseReason
请注意,在发送关闭消息后,服务器 endpoint 不应该向对等体发送更多消息。
当出现错误而不是由 WebSocket 协议处理时,会调用
方法。
- onError()
不能单独使用此 endpoint。 需要创建一个客户端 endpoint,将在下一节中详细介绍。
开发 WebSocket 客户端 Endpoint 涉及使用
接口,它是 JDK 9 中的 HTTP/2 Client API 的一部分。
- WebSocket
接口包含以下嵌套类型:
- WebSocket
接口的实例表示一个 WebSocket 客户端 endpoint。 构建器,它是
- WebSocket
接口的实例,用于创建
- WebSocket.Builder
实例。
- WebSocket
的
- HttpClient类
方法返回一个
- newWebSocketBuilder(URI uri, WebSocket.Listener listener)
接口的实例。
- WebSocket.Builder
当事件发生在客户端 endpoint 时,例如,完成开启握手,消息到达,关闭握手等,通知被发送到一个监听器,该监听器是
接口的实例。 该接口包含每种通知类型的默认方法。 需要创建一个实现此接口的类。 仅实现与接收通知的事件相对应的那些方法。 创建 ·WebSocket· 实例时,需要指定监听器。
- WebSocket.Listener
当向对等体发送关闭消息时,可以指定关闭状态代码。
接口包含以下可以用作
- WebSocket
关闭消息状态代码的
- WebSocket
类型常量:
- int
服务器 Endpoint 可能会发送部分消息。 消息被标记为开始,部分,最后或全部,表示其位置。
枚举定义了与消息的位置相对应的四个常量:
- WebSocket.MessagePart
,
- FIRST
,
- PART
和
- LAST
。 当监听器收到已收到消息的通知时,将这些值作为消息的一部分。
- WHOLE
以下部分将详细介绍设置客户端 Endpoint 的各个步骤。
监听器是
接口的实例。 创建监听器涉及创建实现此接口的类。 该接口包含以下默认方法:
- WebSocket.Listener
- CompletionStage < ?>onBinary(WebSocket webSocket, ByteBuffer message, WebSocket.MessagePart part) CompletionStage < ?>onClose(WebSocket webSocket, int statusCode, String reason) void onError(WebSocket webSocket, Throwable error) void onOpen(WebSocket webSocket) CompletionStage < ?>onPing(WebSocket webSocket, ByteBuffer message) CompletionStage < ?>onPong(WebSocket webSocket, ByteBuffer message) CompletionStage < ?>onText(WebSocket webSocket, CharSequence message, WebSocket.MessagePart part)
当客户端 Endpoint 连接到引用传递给该方法的对等体作为第一个参数时,调用
方法。 默认实现请求一个消息,这意味着该侦听器可以再接收一条消息。 消息请求是使用 WebSocket 接口的
- onOpen()
方法进行的:
- request(long n)
- // Allow one more message to be received
- webSocket.request(1);
如果服务器发送的消息多于请求消息,则消息在 TCP 连接上排队,最终可能强制发送方通过 TCP 流控制停止发送更多消息。 请在适当的时间调用
方法并使用适当的参数值,这样监听器就不会从服务器一直接收消息。 在监听器中重写
- request(long n)
方法是一个常见的错误,而不是调用
- onOpen()
方法,后者会阻止从服务器接收消息。
- webSocket.request(1)
当 endpoint 收到来自对等体的关闭消息时,调用
方法。 这是监听器的最后通知。 从此方法抛出的异常将被忽略。 默认的实现不会做任何事情。 通常,需要向对方发送一条关闭消息,以完成关闭握手。
- onClose()
当 endpoint 从对等体接收到 Ping 消息时,调用
方法。 Ping 消息可以由客户端和服务器 endpoint 发送。 默认实现将相同消息内容的 Pong 消息发送给对等体。
- onPing()
当 endpoint 从对等体接收到 Pong 消息时,调用
方法。 通常作为对先前发送的 Ping 消息的响应来接收 Pong 消息。 endpoint 也可以接收未经请求的 Pong 消息。
- onPong()
方法的默认实现在监听器上再请求一个消息,不执行其他操作。
- onPong()
当 WebSocket 上发生 I/O 或协议错误时,会调用
方法。 从此方法抛出的异常将被忽略。 调用此方法后,监听器不再收到通知。 默认实现什么都不做。
- onError()
当从对等体接收到二进制消息和文本消息时,会调用
和
- onBinary()
方法。 确保检查这些方法的最后一个参数,这表示消息的位置。 如果收到部分消息,需要组装它们以获取整个消息。 从这些方法返回 null 表示消息处理完成。 否则,返回
- onText()
,并在消息处理完成后完成。
- CompletionStage<?>
以下代码段创建一个可以接收信息的 WebSocket 监听器:
- WebSocket.Listener listener = new WebSocket.Listener() {@Override public CompletionStage < ?>onText(WebSocket webSocket, CharSequence message, WebSocket.MessagePart part) {
- // Allow one message to be received by the listener
- webSocket.request(1);
- // Print the message received from the server
- System.out.println("Server: " + message);
- // Return null indicating that we are done processing this message
- return null;
- }
- };
需要构建充当客户端点的
接口的实例。 该实例用于与服务器 Endpoint 连接和交换消息。
- WebSocket
实例使用
- WebSocket
构建。 可以使用
- WebSocket.Builder
类的以下方法获取构建器:
- HttpClient
- WebSocket.Builder newWebSocketBuilder(URI uri, WebSocket.Listener listener)
用于获取
构建器的
- WebSocket
实例提供了
- HttpClient
的连接配置。 指定的
- WebSocket
是服务器 Endpoint 的 URI。 监听器是正在构建的 Endpoint 的监听器, 拥有构建器后,可以调用以下方法来配置 endpoint:
- uri
- WebSocket.Builder connectTimeout(Duration timeout) WebSocket.Builder header(String name, String value) WebSocket.Builder subprotocols(String mostPreferred, String...lesserPreferred)
方法允许指定开启握手的超时时间。 如果开放握手在指定的持续时间内未完成,则从
- connectTimeout()
的
- WebSocket.Builder
方法完成后返回带有异常的
- buildAsync()
的
- HttpTimeoutException
。 可以使用
- CompletableFuture
方法添加任何用于打开握手的自定义首部。 可以使用
- header()
方法在打开握手期间指定给定子协议的请求 —— 只有其中一个将被服务器选择。 子协议由应用程序定义。 客户端和服务器需要同意处理特定的子协议及其细节。
- subprotocols()
最后,调用
接口的
- WebSocket.Builder
方法来构建 Endpoint。 它返回
- buildAsync()
,当该 Endpoint 连接到服务器 Endpoint 时,正常完成; 当有错误时,返回异常。 以下代码片段显示了如何构建和连接客户端 Endpoint。 请注意,服务器的 URI 以
- CompletableFuture <WebSocket>
开头,表示 WebSocket 协议。
- ws
- URI serverUri = new URI("ws://localhost:8080/webapp/servertime");
- // Get a listener
- WebSocket.Listener listener = ...;
- // Build an endpoint using the default HttpClient
- HttpClient.newHttpClient().newWebSocketBuilder(serverUri, listener).buildAsync().whenComplete((WebSocket webSocket, Throwable t) - >{
- // More code goes here
- });
一旦客户端 Endpoint 连接到对等体,则交换消息。
接口的实例表示一个客户端 Endpoint,该接口包含以下方法向对等体发送消息:
- WebSocket
- CompletableFuture < WebSocket > sendBinary(ByteBuffer message, boolean isLast) CompletableFuture < WebSocket > sendClose() CompletableFuture < WebSocket > sendClose(int statusCode, String reason) CompletableFuture < WebSocket > sendPing(ByteBuffer message) CompletableFuture < WebSocket > sendPong(ByteBuffer message) CompletableFuture < WebSocket > sendText(CharSequence message) CompletableFuture < WebSocket > sendText(CharSequence message, boolean isLast)
方法用于向对等体发送信息。 如果发送部分消息,请使用该方法的两个参数的版本。 如果第二个参数为 false,则表示部分消息的一部分。 如果第二个参数为 true,则表示部分消息的最后部分。 如果以前没有发送部分消息,则第二个参数中的 true 表示整个消息。
- sendText()
是一种便捷的方法,它使用 true 作为第二个参数来调用该方法的第二个版本。
- endText(CharSequence message)
方法向对等体发送二进制信息。
- sendBinary()
和
- sendPing()
方法分别向对等体发送 Ping 和 Pong 消息。
- sendPong()
方法向对等体发送 Close 消息。 可以发送关闭消息作为由对等方发起的关闭握手的一部分,或者可以发送它来发起与对等体的闭合握手。
- sendClose()
Tips
如果想要突然关闭 WebSocket,请使用 WebSocket 接口的方法。
- abort()
现在是查看 WebSocket 客户端 endpoint 和 WebSocket 服务器 endpoint 交换消息的时候了。下面包含一个封装客户机 endpoint 的
类的代码。 其用途如下:
- WebSocketClient
- // Create a client WebSocket
- WebSocketClient wsClient = new WebSocketClient(new URI(" < server - uri > "));
- // Connect to the server and exchange messages
- wsClient.connect();
- // WebSocketClient.java
- package com.jdojo.http.client;
- import java.net.URI;
- import java.util.concurrent.CompletionStage;
- import jdk.incubator.http.HttpClient;
- import jdk.incubator.http.WebSocket;
- public class WebSocketClient {
- private WebSocket webSocket;
- private final URI serverUri;
- private boolean inError = false;
- public WebSocketClient(URI serverUri) {
- this.serverUri = serverUri;
- }
- public boolean isClosed() {
- return (webSocket != null && webSocket.isClosed()) || this.inError;
- }
- public void connect() {
- HttpClient.newHttpClient().newWebSocketBuilder(serverUri, this.getListener()).buildAsync().whenComplete(this: :statusChanged);
- }
- private void statusChanged(WebSocket webSocket, Throwable t) {
- this.webSocket = webSocket;
- if (t == null) {
- this.talkToServer();
- } else {
- this.inError = true;
- System.out.println("Could not connect to the server." + " Error: " + t.getMessage());
- }
- }
- private void talkToServer() {
- // Allow one message to be received by the listener
- webSocket.request(1);
- // Send the server a request for time
- webSocket.sendText("Hello");
- }
- private WebSocket.Listener getListener() {
- return new WebSocket.Listener() {@Override public void onOpen(WebSocket webSocket) {
- // Allow one more message to be received by the listener
- webSocket.request(1);
- // Notify the user that we are connected
- System.out.println("A WebSocket has been opened.");
- }@Override public CompletionStage < ?>onClose(WebSocket webSocket, int statusCode, String reason) {
- // Server closed the web socket. Let us respond to
- // the close message from the server
- webSocket.sendClose();
- System.out.println("The WebSocket is closed." + " Close Code: " + statusCode + ", Close Reason: " + reason);
- // Return null indicating that this WebSocket
- // can be closed immediately
- return null;
- }@Override public void onError(WebSocket webSocket, Throwable t) {
- System.out.println("An error occurred: " + t.getMessage());
- }@Override public CompletionStage < ?>onText(WebSocket WebSocket, CharSequence message, WebSocket.MessagePart part) {
- // Allow one more message to be received by the listener
- webSocket.request(1);
- // Print the message received from the server
- System.out.println("Server: " + message);
- // Return null indicating that we are done
- // processing this message
- return null;
- }
- };
- }
- }
类的工作原理如下:
- WebSocketClient
实例变量保存客户端 endpoint 的引用。
- webSocket
实例变量保存服务器端 endpoint 的 URI。
- serverUri
实例变量保存一个指示符,无论该 endpoint 是否出错。
- isError
方法检查 endpoint 是否已经关闭或出错。
- isClosed()
方法中更新。
- statusChanged()
方法构建一个 WebSocket 并启动一个开始握手。 请注意,无论连接状态如何,它在开始握手完成后调用
- connect()
方法。
- statusChanged()
方法通过调用
- tatusChanged()
方法与服务器通信。 否则,它会打印一条错误消息,并将
- talkToServer()
标志设置为 true。
- isError
方法允许监听器再接收一个消息,并向服务器 endpoint 发送一条信息。 请注意,服务器 endpoint 从客户端 endpoint 接收到信息时,会以五秒的间隔发送三个消息。 从
- talkToServer()
方法发送此消息将启动两个 endpoint 之间的消息交换。
- talkToServer()
方法创建并返回一个
- getListener()
实例。 服务器 endpoint 将发送三个消息,后跟一个关闭消息。 监听器中的
- WebSocket.Listener
方法通过发送一个空的关闭消息来响应来自服务器的关闭消息,这将结束客户端 endpoint 操作。
- onClose()
如下包含运行客户端 endpoint 的程序。 如果运行
类,请确保具有服务器 endpoint 的 Web 应用程序正在运行。 还需要修改
- WebSocketClientTest
静态变量以匹配 Web 应用程序的服务器 endpoint 的 URI。 输出将使用时区打印当前日期和时间,因此可能会得到不同的输出。
- SERVER_URI
- // WebSocketClientTest.java
- package com.jdojo.http.client;
- import java.net.URI;
- import java.net.URISyntaxException;
- import java.util.concurrent.TimeUnit;
- public class WebSocketClientTest {
- // Please change the URI to point to your server endpoint
- static final String SERVER_URI = "ws://localhost:8080/webapp/servertime";
- public static void main(String[] args) throws URISyntaxException,
- InterruptedException {
- // Create a client WebSocket
- WebSocketClient wsClient = new WebSocketClient(new URI(SERVER_URI));
- // Connect to the Server
- wsClient.connect();
- // Wait until the WebSocket is closed
- while (!wsClient.isClosed()) {
- TimeUnit.SECONDS.sleep(1);
- }
- // Need to exit
- System.exit(0);
- }
- }
输出结果为:
- A WebSocket has been opened.Server: 2016 - 12 - 15T14: 19 : 53.311 - 06 : 00[America / Chicago] Server: 2016 - 12 - 15T14: 19 : 58.312 - 06 : 00[America / Chicago] Server: 2016 - 12 - 15T14: 20 : 03.313 - 06 : 00[America / Chicago] The WebSocket is closed.Close Code: 1000,
- Close Reason: Done
当测试
应用程序时,会出现一些问题。 下表列出了一些这些问题及其解决方案。
- WebSocket
错误信息 | 解决方案 |
---|---|
Could not connect to the server. Error: java.net.ConnectException: Connection refused: no further information | 表示 Web 服务器未运行或服务器 URI 不正确。 尝试运行 Web 服务器并检查在 类中其 静态变量的指定的服务器 URI。 |
Could not connect to the server. Error: java.net.http.WebSocketHandshakeException: 404: RFC 6455 1.3. Unable to complete handshake; HTTP response status code 404 | 表示服务器 URI 未指向服务器上的正确 endpoint 。 验证 类中 静态变量的值是否正确。 |
A WebSocket has been opened. Dec 15, 2016 2:58:03 PM java.net.http.WS$1 onError WARNING: Failing connectionjava.net.http.WS@162532d6[CONNECTED], reason: 'RFC 6455 7.2.1. Stream ended before a Close frame has been received' An error occurred: null | 表示开启握手后,服务器将自动关闭服务器 endpoint。 这通常由计算机上运行的防病毒程序执行的。 需要配置防病毒程序以允许指定端口上的 HTTP 连接,或者在另一个未被防病毒程序阻止的端口上使用 HTTP 监听器运行 Web 服务器。 |
A WebSocket has been opened. Server: 2016-12-16T07:15:04.586-06:00[America/Chicago] | 在这种情况下,应用程序会打印一行或两行输出并一直等待。 当在客户端 endpoint 逻辑中没有 调用时,会发生这种情况。 服务器正在发送消息,因为不允许更多消息排队。 在 , 和其他事件中调用 方法来解决这个问题。 |
JDK 9 添加了一个 HTTP/2 Client API,可以在 Java 应用程序中使用 HTTP 请求和响应。 API 提供类和接口来开发具有身份验证和 TLS 的 WebSocket 客户端。 API 位于 jdk.incubator.http 包中,该包位于 jdk.incubator.httpclient 模块中。
三个抽象类,
,
- HttpClient
和
- HttpRequest
,
- HttpResponse
接口是 HTTP/2 Client API 的核心。这些类型的实例使用构建器创建。
- WebSocket
类是不可变的。
- HttpClient
类的实例保存可以重复用于多个 HTTP 请求的 HTTP 连接配置。
- HttpClient
类实例表示 HTTP 请求。
- HttpRequest
类的实例表示从服务器接收的 HTTP 响应。可以同步或异步地发送和接收 HTTP 请求和响应。
- HttpResponse
接口的实例表示一个
- WebSocket
客户端 endpoint。与 WebSocket 服务器端 endpoint 的通信是异步完成的。 WebSocket API 是基于事件的。需要为 WebSocket 客户端 endpoint 指定一个监听器,它是
- WebSocket
接口的一个实例。监听器通过调用其适当的方法 —— 当事件发生在 endpoint 上时,例如,当通过调用监听器的
- WebSocket.Listener
方法成功完成与对等体的打开握手时,通知监听器。 API 支持与对等体交换文本以及二进制消息。消息可以部分交换。
- onOpen()
来源: http://www.cnblogs.com/IcanFixIt/p/7229611.html