使用Vertx编写HTTP客户端

谈谈Apache的HTTP Client

谈到HTTP客户端, 在Java界最有名的当属Apache HTTP Client库了。我相信绝大多数人在使用Apache HTTP Client时都是使用的同步版本,即请求发起后需要一直等待响应返回。如果你对程序的吞吐有着更高的要求,可能会尝试使用HttpAsyncClient, 但是使用起来貌似并不那么优雅,而且还有点"浪费"。不优雅指的是其API的设计不够"fluent", 代码写起来跟同步调用其实很像; 而"浪费"指的是你在构造Client对象时需要创建一个IO线程池,但此线程池只能是Http Client收发请求使用,别的地方是用不了的。其实,当你需要异步HTTP客户端时,Vert.x可能更适合你。

什么时候需要异步HTTP客户端

在两种情况下你会需要一个异步HTTP客户端:一是如果你的整个程序都是建立在异步I/O的基础之上,那么你并不希望发起HTTP请求会阻塞不应该阻塞的线程; 二是你需要大量重复且快速的调用某一个HTTP接口来实现"灌库"的功能,多见于离线定时任务。例如,你需要把一个大txt文件的内容逐行读取出来然后调用对方的HTTP接口发送出去,对方告诉你最高可承受2000QPS的并发,难道你要开辟一个容量为2000的线程池来发请求吗?显然,你需要异步。

Vert.x HTTP Client基本用法

首先,你需要简单的引入一个依赖

        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-web</artifactId>
            <version>3.5.4</version>
        </dependency>

就可以畅玩HTTP Client了。

一个最简单的Client长这样:

Vertx vertx = Vertx.vertx(); // (0)
HttpClient client = vertx.createHttpClient(); // (1)
client.getNow("www.baidu.com", "/", resp -> System.out.println(resp.statusCode())); // (2)

TimeUnit.SECONDS.sleep(10); // (3)

(0): 先创建一个Vertx对象以提供所需线程。

(1): 构造一个HttpClient对象。

(2): 向http://www.baidu.com发起一个GET请求,并注册一个回调方法,在回调中我们打印出了服务器返回的状态码。

(3): 让当前线程等会。

好吧,其实这就是玩具代码,生产环境可不能这么用。但这已经足够展示出Vertx相比Apache HTTP Client更加易用了。那么接下来的问题是,怎么获取body?如下:

Handler<Buffer> bodyHandler = buffer -> System.out.println(buffer.toString()); // (0)
client.getNow("www.baidu.com", "/", resp -> resp.bodyHandler(bodyHandler)); // (1)

(0): 先定义一个处理器,用于处理HTTP body buffer,这里我们将其转成String并打印出来。

(1): 在调用getNow()时使用lambda注册一个响应Handler,此Handler会在程序接收到并解析完成HTTP Header时调用。由于这时候body可能还没有接收完,因此要打印body, 我们需要再注册一个Handler, 也就是上一行创建的bodyHandler,此Handler会在完整接收到响应 body 后调用,所以传进来的Buffer对象里完整的,可以直接用。

到这里还是不够,如果baidu因为压力太大半天不发送响应怎么办?如下:

        client.get("www.baidu.com", "/", resp -> resp.bodyHandler(bodyHandler)) // (0)
                .setTimeout(1000) // (1)
                .exceptionHandler(Throwable::printStackTrace) // (2)
                .end(); // (3)

(0): 这里我们调用HttpClientget()方法而不是getNow(), 这两个方法的区别在于getNow()执行完后会马上将你的请求发出去,而get()则会返回一个HttpClientRequest对象,它其实是在构造你的请求,而不是发送请求。

(1): 设置超时时间为1000毫秒。

(2): 注册一个异步处理器,只要调用出现错误就会执行此处理器。我们这里简单的把栈信息打印出来。

(3): 执行到这一行你的请求才会被发送,前面的步骤都只是在构造请求对象而已。

接下来,我还想添加一个Header, 想POST一些数据过去该怎么办?如下:

        client.post("www.baidu.com", "/", resp -> resp.bodyHandler(bodyHandler)) // (0)
                .setTimeout(1000)
                .exceptionHandler(Throwable::printStackTrace)
                .putHeader("My-Header", "HelloWorld") // (1)
                .setChunked(true) // (2)
                .write("Hi~") // (3)
                .end();

(0): 我们将请求方法改成了POST。不过GET方法也可以有请求体的,不信你试试。

(1): 添加了一个自定义请求头。

(2): 如果你没有设置Content-Length请求头,那么就必须加上这行。

(3): 在body中添加一些数据。

如果你不想用setChunked()的话,也可以使用end()的重载方法,直接将body中的字符串传进去:

        client.post("www.baidu.com", "/", resp -> resp.bodyHandler(bodyHandler)) // (0)
                .setTimeout(1000)
                .exceptionHandler(Throwable::printStackTrace)
                .putHeader("My-Header", "HelloWorld")
                .end("Hi~"); // (0)

(0): 在end()方法中指定要发送的数据时Vertx会自动计算出字节长度将设置请求头。

以上就是Vert.x里自带HTTP客户端的基本用法,这对于绝大多数不需要在单请求中传输大量数据的业务场景来说已经足够使用了。如果你想要更高级的功能,如自动encode, decode body成对象、传输大文件等,可以使用Vert.x的Web Client模块,只需要多加一个依赖就可以了:

<dependency>
 <groupId>io.vertx</groupId>
 <artifactId>vertx-web-client</artifactId>
 <version>3.5.4</version>
</dependency>

如何在1s内发出1000个请求

现在想另一个问题,我想让我的客户端能在1s内发出1000个请求该怎么办?这时候就需要设置一些参数了,如下:

        // 构造HTTP Client
        HttpClientOptions options = new HttpClientOptions()
                .setKeepAlive(true)
                .setConnectTimeout(1000)
                .setIdleTimeout(10)
                .setMaxWaitQueueSize(500) // (0)
                .setMaxPoolSize(1000); // (1)
        HttpClient client = vertx.createHttpClient(options);

(0): 将请求队列的最大长度设为1500。这个参数决定了当连接池不够用时,最多可以有多少请求在排队发送,如果超了则会报错。

(1): 将连接池最大连接数设为1000。这个操作非常重要,因为HTTP协议不是全双工的,你使用一个Connection发出一个请求后,在收到响应之前这个Connection是什么也干不了的,无法复用。因此要想一次性发出1000个请求就需要使用1000条连接,这样就必须设置连接池了。如果连接池小于1000, 如500, 那么就会导致后500个请求在发送时需要等待前500个请求完成,这样就降低了吞吐。

到这里请求是发出去了,接下来怎么才能"协调"这1000个请求的结果呢?也就是说怎么才能判断出来1000个请求都发完且收到响应了,可以继续发第二批1000个请求了呢?其实这个问题只要看过我上一篇文章 使用Vert.x + SpringBoot编写业务系统 的朋友应该都知道了,就不再重复了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章