nodeJs中undici请求库的使用
由于request在2020 年 2 月 11 日已经标记为弃用,在 npm 基本上搜索不到了,除非直接访问地址。在这之后 node-fetch、axios 也许是一个不错的选择,但在Node.js官方有一个请求库undici,undici 是意大利语 11 的意思,它比内置的 HTTP 模块还要快,下文有基准测试数据。undici 团队致力于为 Node.js 开发快速、可靠且符合规范的 HTTP 客户端。
基准测试
下面是一个在 Node.js 16 上做的一个基准测试,通过与最慢的数据做对比,之间相差还是挺大的。
Connections 1
| Tests | Samples | Result | Tolerance | Difference with slowest |
|---|---|---|---|---|
| http - no keepalive | 15 | 4.63 req/sec | ± 2.77 % | - |
| http - keepalive | 10 | 4.81 req/sec | ± 2.16 % | + 3.94 % |
| undici - stream | 25 | 62.22 req/sec | ± 2.67 % | + 1244.58 % |
| undici - dispatch | 15 | 64.33 req/sec | ± 2.47 % | + 1290.24 % |
| undici - request | 15 | 66.08 req/sec | ± 2.48 % | + 1327.88 % |
| undici - pipeline | 10 | 66.13 req/sec | ± 1.39 % | + 1329.08 % |
Connections 50
| Tests | Samples | Result | Tolerance | Difference with slowest |
|---|---|---|---|---|
| http - no keepalive | 50 | 3546.49 req/sec | ± 2.90 % | - |
| http - keepalive | 15 | 5692.67 req/sec | ± 2.48 % | + 60.52 % |
| undici - pipeline | 25 | 8478.71 req/sec | ± 2.62 % | + 139.07 % |
| undici - request | 20 | 9766.66 req/sec | ± 2.79 % | + 175.39 % |
| undici - stream | 15 | 10109.74 req/sec | ± 2.94 % | + 185.06 % |
| undici - dispatch | 25 | 10949.73 req/sec | ± 2.54 % | + 208.75 % |
上手
这是一个 NPM 模块,首先你需要安装且引用它,为了能够方便的使用 Top Level Await 这一特性,下文使用 ES Modules 模块规范。
npm i undici -S
import undici from 'undici';undici 对外暴露一个对象,该对象下面提供了几个 api:
- undici.fetch:发起一个请求,和浏览器中的 fetch 方法一致;
- undici.request:发起一个请求,和 request 库有点类似,该方法支持 Promise;
- undici.stream:处理文件流,可以用来进行文件的下载;
undici基础使用
开启一个 Server
开始之前让我们先开启一个 Server,稍后我们使用 undici 的 HTTP 客户端请求本地的 Server 做一些测试。
// server.mjs
import http from 'http';
const server = http.createServer((req, res) => {
res.end(`Ok!`);
});
server.listen(3000, () => {
console.log('server listening at 3000 port');
});
// 终端启动
node server.mjs使用 request 请求接口
它的返回结果支持异步迭代迭代器,你可以使用 for await...of 遍历返回的 body 数据。
const {
statusCode,
headers,
trailers,
body
} = await undici.request('http://localhost:3000/api', {
method: 'GET'
});
console.log('response received', statusCode)
console.log('headers', headers)
for await (const data of body) {
console.log('data', data.toString());
}
console.log('trailers', trailers)创建一个 client 实例请求接口
undici 提供了 Client 类,可以传入 URL 或 URL 对象,它仅包括协议、主机名、端口,用于预先创建一个基础通用的客户端请求实例。我们还可以对返回结果监听 'data' 事件,获取响应的数据,就好比之前以流的方式从文件读取数据,监听 'data' 事件,不过现在以流的方式读取数据也支持异步迭代。
const client = new undici.Client('http://localhost:3000');
const { body } = await client.request({
path: '/api',
method: 'GET',
});
body.setEncoding('utf-8');
body.on('data', console.log);
body.on('end', () => console.log('end'));使用 stream() 方法做接口代理
与 request 方法不同,client.stream 方法期望 factory 返回一个将写入 Response 的 Writable。当用户期望直接将 response body 传递到 Writable 时,通过避免创建一个中间的 Readable 来提高性能。
const factory = ({ opaque: res }) => res;
const client = new undici.Client('http://localhost:3000');
http.createServer((req, res) => {
client.stream({
path: '/',
method: 'GET',
opaque: res
}, factory, (err) => {
if (err) {
console.error('failure', err)
} else {
console.log('success')
}
});
}).listen(3010);使用 stream 从网络读取一张图片写入本地
如果对上个例子 undici.stream 的使用还不了解的,在看看下面这个场景,首先从网络读取图片,返回值本身就是一个可读流对象,现在通过 opaque 指定一个可写流,这个时候图片在读取的过程中就会不断流入到可写流对象所指向的文件。
const factory = ({ opaque: res }) => res;
const url = 'https://images.pexels.com/photos/3599228/pexels-photo-3599228.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=500';
undici.stream(url, {
opaque: fs.createWriteStream('./pexels-photo-3599228.jpeg')
}, factory, (err) => {
if (err) {
console.error('failure', err)
} else {
console.log('success')
}
});
在之前是这样做的。
import request from 'request';
import fs from 'fs';
const readable = request('https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1586785739&di=ba1230a76a1ebd25200448d5cf02ec40&src=http://bbs.jooyoo.net/attachment/Mon_0905/24_65548_2835f8eaa933ff6.jpg');
const writeable = fs.createWriteStream('./pexels-photo-3599228.jpeg');
readable.pipe(writeable)
readable.on('error', function(err) {
writeable.close();
});中止一个请求
可以使用控制器对象 AbortController 或 EventEmitter 中止一个客户端请求。要在 v15.x 之前的版本使用 AbortController 的需要先安装并导入该模块。最新的 v15.x 版本是不需要的,已默认支持。
// npm i abort-controller
import AbortController from "abort-controller"
const abortController = new AbortController();
client.request({
path: '/api',
method: 'GET',
signal: abortController.signal,
}, function (err, data) {
console.log(err.name) // RequestAbortedError
client.close();
});
abortController.abort();
除此之外,任何发出 'abort' 事件的 EventEmitter 实例,都可用作中止控制器。
import { EventEmitter } from 'events';
const ee = new EventEmitter();
client.request({
path: '/api',
method: 'GET',
signal: ee,
}, function (err, data) {
console.log(err.name) // RequestAbortedError
client.close();
});
ee.emit('abort');
undici-fetch
这是一个构建在 undici 之上的 WHATWG fetch 实现,就像你之前使用 node-fetch 一样,你可以选择使用 undici-fetch 简单的处理一些请求。
// npm i undici-fetch
import fetch from 'undici-fetch';
const res = await fetch('http://localhost:3000');
try {
const json = await res.json();
console.log(json);
} catch (err) {
console.log(err);
}
总结
本文只是介绍了 undici 几个 api 的使用方式,看起来 undici 上手难道还是比较低的。但是兼容性还不太行,比如,fetch 只支持 node@v16.5.0 以上的版本。
对于这种比较新的库,个人还是建议多观望一段时间,虽然 request 已经废弃了,我们还是使用一些经过较长时间考验过的库,比如,egg 框架中使用的 urllib,还有一个 node-fetch,上手难道也比较低,与浏览器中的 fetch api 使用方式一致。
本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!