百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

Java11新特性-效能翻倍的HttpClient

ztj100 2025-01-19 02:00 36 浏览 0 评论

古老的背景

从JDK1.1开始,JDK中就有HttpURLConnection来提供了网络连接的能力,但是由于实现的比较古早,其有很多的局限性。比如HttpURLConnection是通过底层提供的socket连接来进行通信,而每一个HttpURLConnection实例只能发送一个请求,之后只能通过close()释放请求的网络资源,或在持久化连接时用disconnect()来关闭关闭底层socket。而其基类URLConnection是为了支持很多协议而设计的,但诸如FTP这种协议已经不咋用了。

HttpURLConnection并不是不能使用,由于不需要依赖,在一些demo项目的时候也会偶尔拿来用。但HttpURLConnection本身已经太过古早,并且很难说HttpURLConnection能够胜任包含各种鉴权信息、各种COOKIE信息的访问请求。

针对这种情况网络上各种大神提供了更多高级的封装,比较流行的有Apache的HttpClient、Okhttp Client、Spring Cloud Feign之类的。这些封装提供了更丰富的资源与更便捷的封装,也支持了更高级功能如HTTP/2协议、异步请求等。

不过到了JDK9的时候,Java提供了一个新的Http请求工具HttpClient,经过了JDK10的再次预览,最终在JAVA11中作为正式功能提供使用,同时也完全替换了仅有阻塞模式的HttpURLConnection。

HttpClient简介

作为JDK11中正式推出的新Http连接器,支持的功能还是比较新的,主要的特性有:

  • 完整支持HTTP 2.0 或者HTTP 1.1
  • 支持 HTTPS/TLS
  • 有简单的阻塞使用方法
  • 支持异步发送,异步时间通知
  • 支持WebSocket
  • 支持响应式流

HTTP2.0其他的客户端也能支持,而HttpClient使用CompletableFuture作为异步的返回数据。WebSocket的支持则是HttpClient的优势。响应式流的支持是HttpClient的一大优势。

而HttpClient中的NIO模型、函数式编程、CompletableFuture异步回调、响应式流让HttpClient拥有极强的并发处理能力,所以其性能极高,而内存占用则更少。

HttpClient的主要类有:

  • java.net.http.HttpClient
  • java.net.http.HttpRequest
  • java.net.http.HttpResponse
  • java.net.http.WebSocket(本文就不介绍这个了)

细节会在后文介绍,但是WebSocket用的比较少,本文就略过了。

核心使用

HttpClient 的核心类主要就是 HttpClient、HttpRequest以及HttpResponse,它们都是位于java.net.http 包下,接下来对他们进行一下介绍。

HttpClient

HttpClient类是最核心的类,它支持使用建造者模式进行复杂对象的构建,主要的参数有:

  • Http 协议的版本 (HTTP 1.1 或者 HTTP 2.0),默认是 2.0。
  • 是否遵从服务器发出的重定向
  • 连接超时时间
  • 代理
  • 认证
//可以用参数调整
HttpClient client = HttpClient.newBuilder()
                         .version(Version.HTTP_1_1)
                         .followRedirects(Redirect.NORMAL)
                         .connectTimeout(Duration.ofSeconds(20))
                         .proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 8080)))
                         .authenticator(Authenticator.getDefault())
                         .build();
                         
//也可以直接全部默认的便捷创建
HttpClient clientSimple = HttpClient.newHttpClient();

当创建了HttpClient实例后,可以通过其发送多条请求,不用重复创建。

HttpRequest

HttpRequest 是用语描述请求体的类,也支持通过建造者模式构建复杂对象,主要的参数有:

  • 请求地址
  • 请求方法:GET,POST,DELETE 等(默认是GET)
  • 请求体 (按需设置,例如 GET 不用 body,但是 POST 要设置)
  • 请求超时时间(默认)
  • 请求头
//使用参数组合进行对象构建,读取文件作为请求体
HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("http://www.baidu.com"))
                .timeout(Duration.ofSeconds(20))
                .header("Content-type","application/json")
                .POST(HttpRequest.BodyPublishers.ofFile(Paths.get("data.json")))
                .build();
//直接GET访问
HttpRequest requestSimple = HttpRequest.newBuilder(URI.create("http://www.baidu.com")).build();

HttpRequest 是一个不可变类,可以被多次发送。

HttpResponse

HttpResponse没有提供外部可以创建的实现类,它是一个接口,从client的返回值中创建获得。接口中的主要方法为:

public interface HttpResponse<T> {
        public int statusCode();
        public HttpRequest request();
        public Optional<HttpResponse<T>> previousResponse();
        public HttpHeaders headers();
        public T body();
        public URI uri();
        public Optional<SSLSession> sslSession();
        public HttpClient.Version version();
    }

HttpResponse的返回内容与常识一致,这里就不展开介绍了。

信息发送

HttpClient中可以使用同步发送或者异步发送。

同步 send()

同步发送后,请求会一直阻塞到收到response为止。

final HttpResponse<String> send = client.send(httpRequest, HttpResponse.BodyHandlers.ofString());
System.out.println(send.body());

其中 send的第二个参数是通过HttpResponse.BodyHandlers的静态工厂来返回一个可以将 response 转换为目标类型T的处理器(handler),本例子中的类型是String。HttpResponse.BodyHandlers.ofString()的实现方法为:

public static BodyHandler<String> ofString() {
            return (responseInfo) -> BodySubscribers.ofString(charsetFrom(responseInfo.headers()));
}

其中,BodySubscribers.ofString() 的方法实现是:

public static BodySubscriber<String> ofString(Charset charset) {
    Objects.requireNonNull(charset);
    return new ResponseSubscribers.ByteArraySubscriber<>(
           bytes -> new String(bytes, charset)
    );
}

可以看到最终是返回了一个ResponseSubscribers ,而Subscribers则是我们之前《JDK9响应式编程》中讨论过的订阅者。这个构造方法的入参Function<byte[],T>定义了订阅者中的finisher属性,而这个属性将在响应式流完成订阅的时在onComplete()`方法中调用。

异步 sendAsync()

异步请求发送之后,会立刻返回 CompletableFuture,然后可以使用CompletableFuture中的方法来设置异步处理器。

client.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString())
        .thenApply(HttpResponse::body)
        .thenAccept(System.out::println)
        .join();

而就如同JDK中响应式流中发布者的submit()方法与offer()方法一样,HttpClient中的send()方法知识sendAsync方法的特例,在send()方法中是先调用sendAsync()方法,然后直接阻塞等待响应结束再返回,部分核心代码为:

    @Override
    public <T> HttpResponse<T>
    send(HttpRequest req, BodyHandler<T> responseHandler)
        throws IOException, InterruptedException
    {
        CompletableFuture<HttpResponse<T>> cf = null;

        // if the thread is already interrupted no need to go further.
        // cf.get() would throw anyway.
        if (Thread.interrupted()) throw new InterruptedException();
        try {
            cf = sendAsync(req, responseHandler, null, null);
            return cf.get();
        } catch (InterruptedException ie) {
            if (cf != null )
                cf.cancel(true);
            throw ie;
        }
      ...

响应式流

HttpClient 作为 Request 的发布者 (publisher),将 Request 发布到服务器,作为 Response 的订阅者 (subscriber),从服务器接收 Response。而上文中我们在send()的部分发现,调用链的最底端返回的是一个ResponseSubscribers订阅者。

当然,就如同HttpResponse.BodyHandlers.ofString(),HttpClient默认提供了一系列的默认订阅者,用语处理数据的转换:

HttpRequest.BodyPublishers::ofByteArray(byte[])
HttpRequest.BodyPublishers::ofByteArrays(Iterable)
HttpRequest.BodyPublishers::ofFile(Path)
HttpRequest.BodyPublishers::ofString(String)
HttpRequest.BodyPublishers::ofInputStream(Supplier<InputStream>)

HttpResponse.BodyHandlers::ofByteArray()
HttpResponse.BodyHandlers::ofString()
HttpResponse.BodyHandlers::ofFile(Path)
HttpResponse.BodyHandlers::discarding()

所以在HttpClient的时候我们也可以自己创建一个实现了Flow.Subscriber<List<ByteBuffer>>接口的订阅者,用于消费数据。响应式流完整的简单的例子如下:

public class HttpClientTest {

    public static void main(String[] args) throws IOException, InterruptedException {
        final HttpClient client = HttpClient.newHttpClient();
        final HttpRequest httpRequest = HttpRequest.newBuilder(URI.create("http://www.baidu.com")).build();

        HttpResponse.BodySubscriber<String> subscriber = HttpResponse.BodySubscribers.fromSubscriber(new StringSubscriber(),StringSubscriber::getBody);
        client.sendAsync(httpRequest,responseInfo -> subscriber)
                .thenApply(HttpResponse::body)
                .thenAccept(System.out::println)
                .join();
    }

    static class StringSubscriber implements Flow.Subscriber<List<ByteBuffer>>{
        Flow.Subscription subscription;
        List<ByteBuffer> response = new ArrayList<>();
        String body;
        public String getBody(){
            return body;
        }

        @Override
        public void onSubscribe(Flow.Subscription subscription) {
            this.subscription = subscription;
            subscription.request(1);
        }

        @Override
        public void onNext(List<ByteBuffer> item) {
            response.addAll(item);
            subscription.request(1);
        }

        @Override
        public void onError(Throwable throwable) {
            System.err.println(throwable);
        }

        @Override
        public void onComplete() {
            byte[] data = new byte[response.stream().mapToInt(ByteBuffer::remaining).sum()];
            int offset = 0;
            for(ByteBuffer buffer:response){
                int remain = buffer.remaining();
                buffer.get(data,offset,remain);
                offset += remain;
            }
            body = new String(data);
        }
    }
}

最后

HttpClient是JDK11正式上线的高性能Http客户端。其底层基于响应式流,通过上层封装还提供了异步信息发送、同步信息发送,以及其他完成的HTTP协议内容。在进行响应式编程的方面,HttpClient也是一个十分优秀的参照目标。

相关推荐

使用Python编写Ping监测程序(python 测验)

Ping是一种常用的网络诊断工具,它可以测试两台计算机之间的连通性;如果您需要监测某个IP地址的连通情况,可以使用Python编写一个Ping监测程序;本文将介绍如何使用Python编写Ping监测程...

批量ping!有了这个小工具,python再也香不了一点

号主:老杨丨11年资深网络工程师,更多网工提升干货,请关注公众号:网络工程师俱乐部下午好,我的网工朋友。在咱们网工的日常工作中,经常需要检测多个IP地址的连通性。不知道你是否也有这样的经历:对着电脑屏...

python之ping主机(python获取ping结果)

#coding=utf-8frompythonpingimportpingforiinrange(100,255):ip='192.168.1.'+...

网站安全提速秘籍!Nginx配置HTTPS+反向代理实战指南

太好了,你直接问到重点场景了:Nginx+HTTPS+反向代理,这个组合是现代Web架构中最常见的一种部署方式。咱们就从理论原理→实操配置→常见问题排查→高级玩法一层层剖开说,...

Vue开发中使用iframe(vue 使用iframe)

内容:iframe全屏显示...

Vue3项目实践-第五篇(改造登录页-Axios模拟请求数据)

本文将介绍以下内容:项目中的public目录和访问静态资源文件的方法使用json文件代替http模拟请求使用Axios直接访问json文件改造登录页,配合Axios进行登录请求,并...

Vue基础四——Vue-router配置子路由

我们上节课初步了解Vue-router的初步知识,也学会了基本的跳转,那我们这节课学习一下子菜单的路由方式,也叫子路由。子路由的情况一般用在一个页面有他的基础模版,然后它下面的页面都隶属于这个模版,只...

Vue3.0权限管理实现流程【实践】(vue权限管理系统教程)

作者:lxcan转发链接:https://segmentfault.com/a/1190000022431839一、整体思路...

swiper在vue中正确的使用方法(vue中如何使用swiper)

swiper是网页中非常强大的一款轮播插件,说是轮播插件都不恰当,因为它能做的事情太多了,swiper在vue下也是能用的,需要依赖专门的vue-swiper插件,因为vue是没有操作dom的逻辑的,...

Vue怎么实现权限管理?控制到按钮级别的权限怎么做?

在Vue项目中实现权限管理,尤其是控制到按钮级别的权限控制,通常包括以下几个方面:一、权限管理的层级划分...

【Vue3】保姆级毫无废话的进阶到实战教程 - 01

作为一个React、Vue双修选手,在Vue3逐渐稳定下来之后,是时候摸摸Vue3了。Vue3的变化不可谓不大,所以,本系列主要通过对Vue3中的一些BigChanges做...

Vue3开发极简入门(13):编程式导航路由

前面几节文章,写的都是配置路由。但是在实际项目中,下面这种路由导航的写法才是最常用的:比如登录页面,服务端校验成功后,跳转至系统功能页面;通过浏览器输入URL直接进入系统功能页面后,读取本地存储的To...

vue路由同页面重定向(vue路由重定向到外部url)

在Vue中,可以使用路由的重定向功能来实现同页面的重定向。首先,在路由配置文件(通常是`router/index.js`)中,定义一个新的路由,用于重定向到同一个页面。例如,我们可以定义一个名为`Re...

那个 Vue 的路由,路由是干什么用的?

在Vue里,路由就像“页面导航的指挥官”,专门负责管理页面(组件)的切换和显示逻辑。简单来说,它能让单页应用(SPA)像多页应用一样实现“不同URL对应不同页面”的效果,但整个过程不会刷新网页。一、路...

Vue3项目投屏功能开发!(vue投票功能)

最近接了个大屏项目,产品想在不同的显示器上展示大屏项目不同的页面,做出来的效果图大概长这样...

取消回复欢迎 发表评论: