Java 11:用Java处理HTTP和WebSockets的新方法!

Posted 21CTO

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 11:用Java处理HTTP和WebSockets的新方法!相关的知识,希望对你有一定的参考价值。

导读:Java 11为开发者们处理编码和API带来了一些更有用的变化。本文我们将探讨如何在Java 11中处理HTTP客户端和WebSocket。



曾几何时,使用Java SE(标准版)API执行常见的HTTP操作(例如REST API调用)被开发者描述为不自然而且非常繁琐。


Java 11正式改变了这一点。Java 9中的HTTP API现已正式合并到Java SE API中。 JDK增强提案(JEP:http://openjdk.java.net/jeps/321)321目的就是这项工作。自从集成到Java 11以来,API发生了一些显著变化,现在完全实现了异步。本文通过执行REST API调用方式展示新API的基本用法。


注意:我们使用的是OpenJDK 11。


API使用java.util.concurrent.CompleteableFuture 来实现异步,非阻塞的请求/响应行为,并允许依赖操作。 API使用新式OOP方法链接,就像构建器一样,返回一个可以受方法调用有效的对象。


除了标准文档之外,我们还可以在此处学习如何实际应用CompleteableFuture  API。开发者可以在java.net.HTTP.*中找到新的HTTP API,新API为HTTP 1.1/2 WebSocket提供本地支持。


提供核心功能的核心类和接口包括如下:


  • HttpClient类:java.net.http.HttpClient

  • HttpRequest类:java.net.http.HttpRequest

  • HttpResponse 接口:java.net.http.HttpResponse

  • WebSocket接口:java.net.http.WebSocket


API中的类型:


java.net.http.HttpClient

java.net.http.HttpHeaders

java.net.http.HttpRequest

java.net.http.HttpRequest.BodyPublishers

java.net.http.HttpRequest.BodyHandlers

java.net.http.HttpRequest.BodySubscribers


接口

java.net.http.HttpClient.Builder

java.net.http.HttpClient.BodyPublisher

java.net.http.HttpRequest.BodyPublisher

java.net.http.HttpResponse<T>

java.net.http.HttpResponse.BodyHandler<T>

java.net.http.HttpResponse.BodySubscriber<T>

java.net.http.HttpResponse.PushPromiseHandler<T>

java.net.http.HttpResponse.ResponseInfo

java.net.http.WebSocket

java.net.http.WebSocket.Builder

java.net.http.WebSocket.Listener


枚举

java.net.http.HttpClient.Redirect

java.net.http.HttpClient.Version


例外

java.net.http.HttpTimeoutException

java.net.http.WebSocketHandshakeException


Pre-Java 11 与 Java 11正式版处理REST API调用的对比


Pre-Java 11



/*

Copyright (C) 2018 Adrian D. Finlay. All rights reserved.

Licensed under the MIT License, Version 2.0 (the "License");

you may not use this file except in compliance with the License.

You may obtain a copy of the License at

    https://opensource.org/licenses/MIT

Permission is hereby granted, free of charge, to any person obtaining a copy

of this software and associated documentation files (the "Software"), to deal

in the Software without restriction, including without limitation the rights

to use, copy, modify, merge, publish, distribute, sublicense, and/or sell

copies of the Software, and to permit persons to whom the Software is

furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all

copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

LIABILITY, WHETHER INCLUDING AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE

SOFTWARE.

==============================================================================

**/

package com.adriandavid.http.prejava11;

import java.io.File;

import java.net.URI;

import java.util.Scanner;

import java.time.Duration;

import java.nio.file.Path;

import java.nio.file.Paths;

import java.nio.file.Files;

import java.io.InputStream;

import java.io.FileOutputStream;

import java.nio.charset.Charset;

import java.net.Authenticator;

import java.net.ProxySelector;

import java.net.http.WebSocket;

import java.net.http.HttpClient;

import java.net.http.HttpRequest;

import java.net.http.HttpResponse;

import java.net.HttpURLConnection;

import java.nio.file.StandardOpenOption;

import java.nio.file.StandardCopyOption;

/* HTTP GET Request, Response (Pre-Java 11 API)   */

public class RESTDemo {

private String[] args;

private final String API_ENDPOINT = "https://onyxfx-api.herokuapp.com/nbaBasicStatBean?";

private final String API_ENDPOINT2 = "http://api.giphy.com/v1/gifs/";

    private final String API_ENDPOINT2_ID = "yoJC2COHSxjIqadyZW";

    private final String API_ENDPOINT2_KEY = "ZtFzb5dH6w9aYjoffJQ0RqlAsS5s0xwR";

public RESTDemo (String[] args) {

        this.args = args;

    };

    public void call() throws Exception {

        //HTTP GET REQUEST

        var HTTP_CLIENT= (HttpURLConnection) 

                            URI.create(

                                new StringBuilder(API_ENDPOINT)

                                .append("firstName=").append(args[0])

                                .append("&surname=").append(args[1])

                                .append("&season=").append(args[2])

                                .toString())

                            .toURL()

                            .openConnection();

        HTTP_CLIENT.setRequestMethod("GET");

        var HTTP_CLIENT2 = (HttpURLConnection)

                            URI.create( //Set the appropriate endpoint

                                new StringBuilder(API_ENDPOINT2)

                                .append(API_ENDPOINT2_ID)

                                .append("?api_key=").append(API_ENDPOINT2_KEY)

                                .append("&data")

                                .append("&type")

                                .append("&images")

                                .toString() )

                            .toURL()

                            .openConnection();

        HTTP_CLIENT2.setRequestMethod("GET");

        //HTTP RESPONSE

        var HTTP_RESPONSE = HTTP_CLIENT.getInputStream();

        var scn = new Scanner(HTTP_RESPONSE);

        var json_sb = new StringBuilder();

        while (scn.hasNext()) {

            json_sb.append(scn.next());

        }

        var JSON = json_sb.toString();

        if (HTTP_CLIENT2.getContentType().contains("json")) {

            var stream_in = (InputStream)(HTTP_CLIENT2.getContent());

            var stream_out = new FileOutputStream (new File("response1.json"));

            stream_in.transferTo(stream_out);

            stream_in.close();

            stream_out.close();

        }

        else

            return; // suffice for now.

        // HTTP STATUS

        var statusCode = HTTP_CLIENT.getResponseCode();

        var statusCode2 = HTTP_CLIENT2.getResponseCode();

        // HANDLE RESPONSE

        if (statusCode == 200 || statusCode == 201)

            System.out.println("Success! -- Pre-Java 11 REST API Call\n" + 

                args[1] + ", " + args[0] + " [" + args[2] +"]\n" + JSON);

        else

            System.out.println("Failure! -- Pre-Java 11 REST API Call");

        System.out.println("---------------------------------");

        if (statusCode2 == 200 || statusCode2 == 201) {

            System.out.println("Success! -- Pre-Java 11 REST API Call\n" + "Let's download the file!" );

        }

        else

            System.out.println("Failure! -- Pre-Java 11 REST API Call");

    System.out.println("---------------------------------");

    };

};


Java 11


/*

Copyright (C) 2018 Adrian D. Finlay. All rights reserved.

Licensed under the MIT License, Version 2.0 (the "License");

you may not use this file except in compliance with the License.

You may obtain a copy of the License at

    https://opensource.org/licenses/MIT

Permission is hereby granted, free of charge, to any person obtaining a copy

of this software and associated documentation files (the "Software"), to deal

in the Software without restriction, including without limitation the rights

to use, copy, modify, merge, publish, distribute, sublicense, and/or sell

copies of the Software, and to permit persons to whom the Software is

furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all

copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

LIABILITY, WHETHER INCLUDING AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE

SOFTWARE.

==============================================================================

**/

package com.adriandavid.http.java11;

import java.io.File;

import java.net.URI;

import java.util.Scanner;

import java.time.Duration;

import java.nio.file.Path;

import java.nio.file.Paths;

import java.nio.file.Files;

import java.io.InputStream;

import java.io.FileOutputStream;

import java.nio.charset.Charset;

import java.net.Authenticator;

import java.net.ProxySelector;

import java.net.http.WebSocket;

import java.net.http.HttpClient;

import java.net.http.HttpRequest;

import java.net.http.HttpResponse;

import java.net.HttpURLConnection;

import java.nio.file.StandardOpenOption;

import java.nio.file.StandardCopyOption;

/* HTTP GET Request, Response (Java 11 APIs)  */

public class RESTDemo {

    private final String API_ENDPOINT = "https://onyxfx-api.herokuapp.com/nbaBasicStatBean?";

    private final String API_ENDPOINT2 = "http://api.giphy.com/v1/gifs/";

    private final String API_ENDPOINT2_ID = "yoJC2COHSxjIqadyZW";

    private final String API_ENDPOINT2_KEY = "ZtFzb5dH6w9aYjoffJQ0RqlAsS5s0xwR";

    private final HttpResponse.BodyHandler<String> asString = HttpResponse.BodyHandlers.ofString();

    private final HttpResponse.BodyHandler<Void> asDiscarded = HttpResponse.BodyHandlers.discarding();

    private final HttpResponse.BodyHandler<InputStream> asInputStream= HttpResponse.BodyHandlers.ofInputStream();

    private final HttpClient HTTP_CLIENT = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2)

                            .followRedirects(HttpClient.Redirect.NORMAL).proxy(ProxySelector.getDefault()).build();

    private String[] args;

    public RESTDemo (String[] args) {

        this.args = args;

    };

    public void call() throws Exception {

        if (args.length < 4) {

            System.out.println("An invalid amount of arguments was supplied.");

            return;

        }

        System.out.println("---------------------------------");

        // HTTP GET REQUEST 

        var HTTP_REQUEST = HttpRequest.newBuilder()

            .uri(URI.create( //Set the appropriate endpoint

                    new StringBuilder(API_ENDPOINT)

                    .append("firstName=").append(args[0])

                    .append("&surname=").append(args[1])

                    .append("&season=").append(args[2])

                    .toString() ) )

            .timeout(Duration.ofMinutes(1))

            .header("Content-Type", "application/json")

            .build(); 

        var HTTP_REQUEST2 = HttpRequest.newBuilder()

            .uri(URI.create( //Set the appropriate endpoint

                    new StringBuilder(API_ENDPOINT2)

                    .append(API_ENDPOINT2_ID)

                    .append("?api_key=").append(API_ENDPOINT2_KEY)

                    .append("&data")

                    .append("&type")

                    .append("&images")

                    .toString() ) )

            .timeout(Duration.ofMinutes(1))

            .header("Content-Type", "application/json")

            .build();

        // SEND HTTP GET REQUEST, RECIEVE OBJECT FOR HTTP GET RESPONSE 

        var HTTP_RESPONSE = HTTP_CLIENT.send(HTTP_REQUEST, asString);

        var HTTP_RESPONSE2 = HTTP_CLIENT.send(HTTP_REQUEST, asDiscarded);

        var HTTP_RESPONSE3 = HTTP_CLIENT.send(HTTP_REQUEST2, asInputStream);

        // HTTP STATUS CODE 

        var statusCode = HTTP_RESPONSE.statusCode();

        var statusCode2 = HTTP_RESPONSE2.statusCode();

        var statusCode3 = HTTP_RESPONSE3.statusCode();

        // HANDLE RESPONSE

        if (statusCode == 200 || statusCode == 201)

            System.out.println("Success! -- Java 11 REST API Call\n" + 

                args[1] + ", " + args[0] + " [" + args[3] +"]\n" + HTTP_RESPONSE.body());

        else 

            System.out.println("Failure! -- Java 11 REST API Call");

        System.out.println("---------------------------------");                

        if (statusCode2 == 200 || statusCode2 == 201)

            if (HTTP_RESPONSE2.body() == null)

            System.out.println("Success! -- Java 11 REST API Call\n" + 

                args[1] + ", " + args[0] + " [" + args[3] +"]\n" + "Data was discarded.");

        else 

            System.out.println("Failure! -- Java 11 REST API Call");

        System.out.println("---------------------------------");

        if (statusCode3 == 200 || statusCode3 == 201) {

            System.out.println("Success! -- Java 11 REST API Call\n" + "Let's download the file! ");

            var HTTP_STREAM = HTTP_RESPONSE3.body();

            Files.copy(HTTP_STREAM, new File("response2.json").toPath(), StandardCopyOption.REPLACE_EXISTING);

            HTTP_STREAM.close();

        }

        else 

            System.out.println("Failure! -- Java 11 REST API Call");                                               

        System.out.println("---------------------------------");

    };

};




从语义上讲,新API更完整,第二,更模块化,第三,它遵循新的OOP方式链接(或使用构建器)来构造对象,第四,它让HTTP更加本地化。


一些有意思的提示


Java的一大祸害就是(可能对某些人来说)语法一直都是冗长的。


在Java中,与大多数弱类型语言不同,眼睛就能识别出代码的类型和行为。 Java API中通常遵循自然的命名方法,正如人们在现实世界中所期望的那样,因此它通常是直观的。此外,Java的输入语义是显式的,增加了这种语法冗长。


现在我经常使用Java 10推出的var,代码中减少了很多典型的声明。但是说实话,API还是感觉有点冗长。


但是,我们需要给予API编写者信心和支持,以便更好地实现按需配置。就此而言,还应该注意到,在HTTP请求/响应周期中还要配置一些事情。可以预计会有更多冗长的语法。


模块化


遵循长期以来的Unix传统,长期以来受Java社区的影响,目前API非常模块化,并且 API经过了精心分解,没有任何缺陷。


争论这个规则的一个例外是创建WebSocket。创建WebSocket最直接(和API推荐)的方法是使用WebSocket.Builder的实例。使用java.net.http.HttpClient.newWebSocketBuilder()可以直接创建WebSocket.Builder实例。


调用完成后,再调用java.net.WebSocket.Builder.buildAsynce(URI uri,WebSocket.LIstener)来生成与WebSocket关联的CompleteableFuture <WebSocket>对象,然后可以通过调用CompleteableFuture对象上的get()来取得WebSocket。


方法链


有的开发者喜欢用方法链作为构造对象的方法来设计API,它允许对象配置,或按照开发者希望的那样进行高度配置。结果可以通过重载构造函数来实现,在创建对象之后,须调用setter()方法来更新对象属性。


使用构建器,可以在一次调用中检索修改后的对象。它可以很好地处理像HTTP这样的东西,它需要一些要指定的属性。


本地HTTP


Java API 更加像原生HTTP。方法名称(如body()和headers())比getContent(),getHeaderField()和getHeaderKey()更适合于命名方法。在笔者看来,旧的API是抽象的,并且与一般意义上的网络有关,而不是HTTP,在指定BodyHandlers等方面,它也更直观。在旧的API中,感觉是一种不自然的操作,更确切地说,感觉就像是美国在吸引外国公民。目前 API的设计和使用可能会导致一些意外行为。


HTTP UrlConnection的优点:


  • 改进了对HTTP和HTTP/2的支持

  • 原生HTTP的感觉,HTTP是一流的公民

  • 异步,非阻塞的实现

  • 新的API可以更自然地使用现代语言功能


本文完整的源代码,包括使用WebSockets的示例,可以在https://github.com/afinlay5/Java11HttpWs找到。


编译:乔乔

原文:https://hackernoon.com/javahttp2-3a9b398c826d


以上是关于Java 11:用Java处理HTTP和WebSockets的新方法!的主要内容,如果未能解决你的问题,请参考以下文章

tomcat调优

java 11 标准Java异步HTTP客户端

服务器:[http-nio-8080-exec-7] org.apache.coyote.http11.Http11Processor.service 处理请求时出错 java.lang.NullPo

怎样用JAVA实现模拟HTTP请求,得到服务器的响应时间等参数

jdk11新特性——标准Java异步HTTP客户端

jdk11新特性——标准Java异步HTTP客户端