天天看点

没有 undici 就没有 NodeJS 的 fetch!

作者:高级前端进阶

大家好,很高兴又见面了,我是"高级前端‬进阶‬",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!

没有 undici 就没有 NodeJS 的 fetch!

高级前端‬进阶

今天给大家带来的主题是undici,即Nodejs的fetch的底层实现。话不多说,直接开始!

1.什么是 undici

Undici 发音为/undiʧi]/,是一个快速、可靠且符合规范的 HTTP/1.1 客户端,它是从头开始编写的,代表了 Node.js HTTP 堆栈的演变。 它正迅速成为最受欢迎的软件包之一,目前 Undici 在 Github 上有超过 4.6K 的 star、每周在 npm 上的平均下载量接近 2795K 次,有超过 202K 的项目使用它。

Undici 在意大利语中是十一的意思, 1.1 -> 11 -> 十一 -> Undici,它也是《怪奇物语》的参考资料。

在 Node.js V18 中,默认提供了一个全局 fetch,该 fetch 的底层实现就是来自 undici。

2.快速使用 undici

首先需要使用 npm 安装 undici:

npm install undici -S           

undici 导出了一个对象,该对象提供了以下几个 API:

  • undici.fetch : 发起请求,与 fetch 一致
  • undici.request : 发起请求的请求库,该方法支持 Promise;
  • undici.stream :处理文件流,可用于下载文件;

但是需要注意的是 undici 依赖的 Node 版本需要>= v16.8.0。

// 以下来自undici的版本判断
if (util.nodeMajor > 16 || (util.nodeMajor === 16 && util.nodeMinor >= 8)) {
  let fetchImpl = null;
  module.exports.fetch = async function fetch(resource) {
    if (!fetchImpl) {
      fetchImpl = require('./lib/fetch').fetch;
    }

    try {
      return await fetchImpl(...arguments);
    } catch (err) {
      Error.captureStackTrace(err, this);
      throw err;
    }
  };
  //一系列导出对象,包括Headers、Response、Request、FormData、
  // File、FileReader、setGlobalOrigin、getGlobalOrigin等等
}
           

下面重点介绍undici几个核心方法,即request、fetch、stream方法。

2.1 undici.request

下面示例展示了如何使用 request 发起请求,同时通过 for await 来获取请求结果。

import { request } from 'undici';
const { statusCode, headers, trailers, body } = await request(
  'http://localhost:3000/foo'
);
console.log('response received', statusCode);
console.log('headers', headers);
for await (const data of body) {
  console.log('data', data);
}
console.log('trailers', trailers);
           

2.2 undici.fetch

undici.fetch 使用方法与原生 fetch 类似,比如下面的示例使用 undici.fetch 发起一个 POST 请求。

const { fetch } = require('undici');
const getData = async () => {
  const api = 'http://localhost:3100/login';
  const rsp = await fetch(api, {
    method: 'POST',
    headers: {
      'content-type': 'application/json',
    },
    body: JSON.stringify({
      account: 'shenfq',
      password: '123456',
    }),
  });
  if (rsp.status !== 200) {
    console.log(rsp.status, '请求失败');
    return;
  }
  const json = await rsp.json();
  console.log(rsp.status, json);
};
getData();
           

2.3 undici.stream

undici.steam 方法可用于下载文件或接口代理。比如下面是文件下载示例:

const fs = require('fs');
const { stream } = require('undici');
const out = fs.createWriteStream('./a.jpg');
const url = 'https://xxx.jpg';
stream(
  url,
  {
    opaque: out,
  },
  (opaque) => opaque
);
           

下面是使用 undici 将 3100 端口的请求,代理到 80 端口的示例:

const http = require('http');
const undici = require('undici');
// 将 3100 端口的请求,代理到 80 端口
const client = new undici.Client('http://localhost');
http
  .createServer((req, res) => {
    const { url, method } = req;
    client.stream({ method, path: url, opaque: res }, ({ opaque }) => opaque);
  })
  .listen(3100);
           

4.Body Mixins

body mixin 是格式化请求/响应主体的最常见方式。Mixins 包括:

  • .formData()
  • .json()
  • .text() 等用法示例:
import { request } from 'undici';
const { statusCode, headers, trailers, body } = await request(
  'http://localhost:3000/foo'
);

console.log('response received', statusCode);
console.log('headers', headers);
console.log('data', await body.json());
// 调用.json的mixin
console.log('trailers', trailers);           

注意:一旦一个 mixin 被调用,body 就不能被重用,因此在 .body 上调用额外的 mixin,例如 .body.json(), .body.text() 将导致错误 “TypeError: unusable ”被抛出并通过 Promise reject 返回。

如果需要在使用 mixin 后以纯文本形式访问正文,最佳做法是首先使用 .text() mixin,然后手动将文本解析为所需格式。

5.undici的其他 API 方法

  • undici.pipeline([url, options, ]handler):返回 stream.Duplex 对象
  • undici.connect([url, options]):返回带有 Dispatcher.connect 方法结果的 Promise
  • request.body:undici 的 body 可以是: ArrayBuffer、ArrayBufferView、AsyncIterables、Blob、 可迭代对象、字符串、URLSearchParams、FormData 等诸多类型。在 undici 这个 fetch 的实现中,request.body 甚至可以接受 Async Iterables,虽然它不存在于 Fetch 标准中。
import { fetch } from 'undici';
const data = {
  async *[Symbol.asyncIterator]() {
    yield 'hello';
    yield 'world';
  },
};
await fetch('https://example.com', {
  body: data,
  method: 'POST',
  duplex: 'half',
});           
  • undici.upgrade([url, options]):返回带有 Dispatcher.upgrade 方法结果的 Promise
  • undici.setGlobalDispatcher(dispatcher):设置通用 API 方法使用的全局调度程序(global dispatcher )。

6.本文总结

本文主要和大家介绍 undici ,即NodeJS fetch的底层实现。文章从什么是 undici 、快速使用 undici 、undici 常用方法等诸多维度展开。

但是,网上关于undici的资料真是非常少,文章也没有过多展开,如果有兴趣,可以在我的主页继续阅读,同时文末的参考资料提供了大量优秀文档以供学习。最后,欢迎大家点赞、评论、转发、收藏!

参考资料

https://undici.nodejs.org/#/?id=undici

https://segmentfault.com/a/1190000040837026/en

https://github.com/nodejs/undici

https://nodejs.medium.com/introducing-undici-4-1e321243e007

https://nodejs.dev/en/blog/announcements/v18-release-announce

继续阅读