ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Deno Node 兼容层深入解析:internal_binding 如何模拟 Node.js 内部 C++ 绑定

Deno Node 兼容层深入解析:internal_binding 如何模拟 Node.js 内部 C++ 绑定 Deno Node 兼容层深入解析internal_binding 如何模拟 Node.js 内部 C 绑定【免费下载链接】denoA modern runtime for JavaScript and TypeScript.项目地址: https://gitcode.com/GitHub_Trending/de/denoDeno 之所以能够直接运行大量为 Node.js 编写的 npm 包关键在于ext/node/polyfills/internal_binding/目录下的一组内部绑定internal bindings模拟实现。本文以 internal_binding 目录 README 为核心结合目录内的 TypeScript 实现、Rust 侧 ops 和消费方 polyfill 源码讲清楚 Deno 是如何用纯 JS/TS辅以少量 Rust FFI复刻 Node.js 源码src/目录中由 C 导出的那批内部模块以及uv、http_parser等关键绑定的具体实现细节。一、什么是 internal bindings文档给出的核心定义ext/node/polyfills/internal_binding/README.md 全文很短但给出了这个目录的准确定位The modules in this directory implement (simulate) C bindings implemented in the./src/directory of the Node.js repository. These bindings are created in the Node.js source code by usingNODE_MODULE_CONTEXT_AWARE_INTERNAL.拆解开有三层含义来源Node.js 仓库的src/目录下存在一批 C 模块它们通过NODE_MODULE_CONTEXT_AWARE_INTERNAL宏注册为内部绑定供 Node 自身的 JS 标准库lib/目录下的内置模块通过process.binding()/internalBinding()调用而非暴露给普通用户代码。目的Node 内置模块fs、net、tls、crypto、dgram 等大量依赖这些绑定获取底层能力libuv 句柄、errno 映射、HTTP 解析器、异步追踪上下文等。要让这些内置模块在 Deno 里跑起来就必须把绑定提供的面API surface复刻出来。方式由于 Deno 的 Node 兼容层运行在 V8 isolate 中、无法按 Node 的方式加载这套 C 扩展internal_binding/目录选择用 TypeScript 直接**实现simulate**这些模块的对外行为真正需要 C 能力如 HTTP 报文解析的部分则下沉到 Rust 侧通过 op/FFI 提供。README 还提示读者参考 Node.js 仓库src/README.md了解内部绑定的注册机制——也就是说本目录是 Node 内部机制的一份镜像文档而真正的证据全部落在下面这些源码里。二、目录结构与绑定清单ext/node/polyfills/internal_binding/目录下共有 30 个 TS/JS 文件每个文件对应一个或一组Node 内部绑定模块文件对应的 Node 绑定实现形态async_wrap.tsasync_wrap异步追踪AsyncResource上下文block_list.tsblock_list底层 IP 封禁列表文件头注释注明 Mirrors NodesinternalBinding(block_list)buffer.tsbufferBuffer 相关常量与能力cares_wrap.tscares_wrapc-ares DNS 解析封装constants.tsconstantsUV / UV_UDP 等常量集crypto.tscrypto底层加密绑定面http_parser.tshttp_parserllhttp 解析器的 JS 门面Rust 实现在 ext/node/ops/llhttp/binding.rshttp2.tshttp2nghttp2 常量与错误字符串ext/node/lib.rs 注释提到用于镜像 Node 的internalBinding(http2).nghttp2ErrorString()inspector.jsinspectorInspector 相关绑定面pipe_wrap.tspipe_wrapUnix 管道句柄封装stream_wrap.tsstream_wraplibuv stream 句柄封装string_decoder.tsstring_decoder字符串解码器symbols.tssymbols内置模块共享的 Symbol 集tcp_wrap.tstcp_wrapTCP 句柄封装tls_wrap.tstls_wrap文件头注释注明 Mirrors NodesinternalBinding(tls_wrap).wrap(handle, context, isServer)tty_wrap.tstty_wrapTTY 句柄封装types.tstypes基于 core 类型判定的对象类型检测udp_wrap.tsudp_wrapUDP 句柄封装util.tsutil内部工具函数uv.tsuvlibuv 错误码/常量映射约 600 行见下文ares.tsaresc-ares 常量其余_libuv_winerror.ts、_listen.ts、_node.ts、_timingSafeEqual.ts、_utils.ts带下划线的私有辅助模块mod.ts—绑定注册表与getBinding()入口三、绑定注册表与 getBindingNode 语义的复刻入口mod.ts 是整个目录的调度中心。它先用core.loadExtScript(ext:deno_node/internal_binding/...)逐个加载上面列出的绑定脚本再组装成一个modules注册表第 86–144 行const modules { async_wrap: asyncWrap, block_list: blockList, buffer, cares_wrap: caresWrap, constants, crypto, http_parser: httpParser, http2: http2Binding, inspector: inspectorBinding, pipe_wrap: pipeWrap, stream_wrap: streamWrap, string_decoder: stringDecoder, symbols, tcp_wrap: tcpWrap, tty_wrap: ttyWrap, types, udp_wrap: udpWrap, util, uv, // …其余条目 }; export type BindingName keyof typeof modules; export function getBinding(name: BindingName) { const mod modules[name]; if (!mod) { throw new Error(No such module: ${name}); } return mod; }两个值得注意的设计类型层面的完整性BindingName keyof typeof modules让调用方获得编译期约束运行时getBinding对未知名字抛出No such module: name与 Node 中internalBinding查询失败的行为保持一致。空对象占位策略注册表里还有一批值为{}的条目如config、contextify、credentials、errors、fs、fs_dir、fs_event_wrap、heap_utils、icu、js_stream、messaging、module_wrap、native_module、natives、options、os、process_methods、report、serdes、signal_wrap、spawn_sync、task_queue、tls_wrap、trace_events、url、v8、worker、zlib。从源码结构看这些绑定尚未被 Node 兼容层真正需要Deno 选择注册空对象而非让它们抛错保证依赖它们存在性的内置模块代码至少可以走到运行时再按需补全。小型内联实现注册表里还有两处直接内联的极简实现例如timers.getLibuvNow()返回MathFloor(performance.now())用 Web Performance 时间线模拟 libuv 的当前时刻performance.observerCounts则是一个长度为 9 的全零数组按 Node 的 observer 条目类型索引初始化。四、uv绑定深读跨平台 errno 映射与只读常量uv.ts 是目录中体量最大的文件之一它对应 Nodesrc/uv.cc导出的uv绑定。文件头部注释交代了移植背景In Node these values are coming from libuv…… Since there is no easy way to port code from libuv and these maps are changing very rarely, we simply extract them from Node and store here.也就是说Deno 没有把 libuv 的 C 头文件重新编译一遍而是直接从 Node 运行期抽取了错误码表以静态数据形式内嵌进 TS 源码。4.1 五套平台错误码表文件内依次定义了codeToErrorWindows、codeToErrorDarwin、codeToErrorLinux、codeToErrorFreebsd、codeToErrorOpenBSD五份表每份都是[errno, [错误名, 描述]]的三元组数组。同一语义在不同平台上的数值不同例如ECONNREFUSED在 Linux 上是-111、在 Darwin 上是-61、在 Windows 上是-4078而EAI_*系列c-ares 解析错误各平台共用-30xx段。反向表errorToCodeXxx则由ArrayPrototypeMap从正向表自动翻转生成保证两份表永不失配。4.2 为什么必须用真 Map构造errorMap/codeMap时第 508–541 行有一个细节非常讲究源码注释写得很直白// Must be a real Map (not SafeMap): it is returned to userland via // getErrorMap() / process.binding(uv).getErrorMap() and must pass // instanceof Map (SafeMaps prototype chain does not include Map). // deno-lint-ignore deno-internal/prefer-primordials const errorMap new Mapnumber, [string, string]( osType windows ? codeToErrorWindows : osType darwin ? codeToErrorDarwin : osType linux ? codeToErrorLinux : osType android ? codeToErrorLinux : osType freebsd ? codeToErrorFreebsd : osType openbsd ? codeToErrorOpenBSD : unreachable(), );Deno 内部代码一般强制使用原型链被切断的 primordial 安全对象SafeMap但这份表会直接返回给用户代码——Node 生态里存在errorMap instanceof Map之类的写法SafeMap 的原型链不含Map会检测失败。因此这里显式豁免 lint 规则deno-lint-ignore deno-internal/prefer-primordials改用真Map。平台选择依据core.loadExtScript(ext:deno_node/_util/os.ts)提供的osTypeAndroid 复用 Linux 表未知平台直接unreachable()抛错。对外暴露的 API 为errname(errno)、getErrorMessage(errno)、getErrorMap()、getCodeMap()以及mapSysErrnoToUvErrno()——后者在 Windows 上先经 _libuv_winerror.ts 的uvTranslateSysError把 Win32 错误码翻译成标准 errno 名再查表其它平台直接取负。这些函数被 _utils.ts、internal/errors.ts、fs.ts 等内置模块广泛使用是 Deno 中 Node 风格错误对象err.code ENOENT背后的数据来源。4.3 UV_* 常量的冻结mod.ts 在把uv.ts的结果挂到注册表前还做了一次浅拷贝// Mutable shallow copy so callers can replace properties (e.g. wrap // errname with a deprecation warning when --pending-deprecation is set). // Match Nodes C binding: UV_* error code constants are read-only and // non-deletable. See Initialize in src/uv.cc. const uv: Recordstring, unknown {}; for (const key of new SafeArrayIterator(ObjectKeys(uvNamespace))) { const value (uvNamespace as Recordstring, unknown)[key]; if (StringPrototypeStartsWith(key, UV_)) { ObjectDefineProperty(uv, key, { __proto__: null, value, writable: false, enumerable: true, configurable: false, }); } else { uv[key] value; } }这里再次体现了镜像 Node 行为的原则Node 的src/uv.cc::Initialize把UV_*错误码常量定义为只读、不可删除的属性Deno 就用ObjectDefineProperty逐一定义出writable: false, configurable: false的属性来复现而函数类属性如errname保持可写以便 Node 在--pending-deprecation场景下对其打补丁——这段注释直接说明了对齐动机。五、http_parser绑定JS 门面 Rust/FFI 底座的协作uv绑定是纯数据模拟而http_parser绑定展示了另一类实现路径——Rust 侧真正的 FFI 绑定。ext/node/ops/llhttp/binding.rs 的模块注释开门见山CppGC-based HTTPParser binding forinternalBinding(http_parser). This exposes llhttp to JavaScript matching Node.jss nativeHTTPParserclass.从源码结构看其协作方式是Rust 侧维护llhttp_t解析器实例Inner结构体含llhttp_settings_t、头部缓冲、max_header_size等状态并把 JS 回调存为 parser 对象上的索引属性常量K_ON_MESSAGE_BEGIN0 … K_ON_EXECUTE5与 JS 端 http_parser.ts 中的索引一一对应注释明确must match the constants in http_parser.tsexecute()期间llhttp_t.data指向栈上分配的ExecuteContext持有Inner状态与 v8PinScope的原始指针C 回调据此同步调用回 JS头部累计到MAX_HEADER_PAIRS 32对时先经kOnHeaders回调分批刷回 JS与 Node 的批量解析行为保持一致matches Node.js。也就是说internal_binding/http_parser.ts提供的是 Node 语义下的类与方法签名重活逐字节状态机解析由 Rust llhttp 完成。类似地ext/node/lib.rs 中的注释表明http2绑定也镜像了 NodeinternalBinding(http2).nghttp2ErrorString()一类能力。六、types绑定把 V8 类型判定暴露给 JStypes.ts 是一个小巧而关键的绑定。它从coreDeno 内核的 JS 侧 API解构出约 30 个类型判定函数并原样导出const { isAnyArrayBuffer, isArgumentsObject, isArrayBuffer, isAsyncFunction, isBigIntObject, isBooleanObject, isBoxedPrimitive, isDataView, isDate, isGeneratorFunction, isGeneratorObject, isMap, isMapIterator, isModuleNamespaceObject, isNativeError, isNumberObject, isPromise, isProxy, isRegExp, isSet, isSetIterator, isSharedArrayBuffer, isStringObject, isSymbolObject, isTypedArray, isWeakMap, isWeakSet, } core;Node 侧internalBinding(types)提供的是 V8 原语级的IsArrayBuffer、IsPromise等判定能力供assert、buffer等内置模块做精确的对象类型识别。Deno 直接把内核core已具备的同族判定函数转发出去文件顶部保留了 Adapted from Node.js 的署名注释属于典型的能力对齐型绑定。其消费方之一 assert.ts 就在模块头部加载了它。七、两种消费路径getBinding 之外的大头理解internal_binding/目录时容易忽略的一点是大多数内置模块并不走getBinding()注册表而是用core.loadExtScript直接按文件加载绑定脚本。例如_http_common.js 直接loadExtScript(ext:deno_node/internal_binding/http_parser.ts)_tls_wrap.js 依次加载tcp_wrap.ts、pipe_wrap.ts、tls_wrap.ts、symbols.tsdgram.ts 加载udp_wrap.ts、util.ts、constants.tsdns.ts 加载ares.ts与cares_wrap.tsinternal/buffer.mjs 加载string_decoder.ts、buffer.ts、_utils.ts、util.tsconstants.ts、crypto.ts、_brotli.js、_fs/_fs_constants.ts 等则大量引用constants.ts与uv.ts中的常量表。这种文件级直接引用 注册表兜底的双轨结构可以推断出如下分工loadExtScript直接加载服务于性能与模块初始化顺序内置模块在启动快照中即可依赖具体文件而mod.ts的注册表服务于那些按名字动态查询绑定的第三方/兼容代码路径——即 Node 的process.binding()/internalBinding()语义。后者在 Deno 中由 ext/node/polyfills/internal/test/binding.ts 实现const lazyBindingMod core.createLazyLoader( ext:deno_node/internal_binding/mod.ts, ); function internalBinding(name) { emitBindingWarning(); return lazyBindingMod().getBinding(name); }这里还有两个细节createLazyLoader使注册表首次被查询时才真正加载避免拖慢启动而每次调用都会触发一次process.emitWarning(These APIs are for internal testing only. Do not use them., internal/test/binding)——这与 Node 自身对该 API 的定位一致内部测试用不保证稳定。八、小结一份以文档为纲、以源码为证的对照表回到 README 给出的那句核心定义仓库源码为它提供了完整的实现证据链README 的论断仓库中的证据模拟 Nodesrc/中的 C 绑定mod.ts 的modules注册表 getBinding条目名与 Node 内部绑定名一一对应这些绑定由NODE_MODULE_CONTEXT_AWARE_INTERNAL创建ext/node/lib.rs、block_list.ts、tls_wrap.ts 等文件注释逐处标注 Mirrors NodesinternalBinding(...)并在 binding.ts 中保留internalBinding()调用面详见 Nodesrc/文档uv.ts 头部注释逐条列出移植来源src/uv.cc、deps/uvllhttp/binding.rs 注释对齐 Node 的HTTPParser类行为适用前提与限制也需要说清这些绑定服务于 Deno 的 Node 兼容层内置模块移植与 npm 包运行internalBinding本身被官方定位为内部测试 API注册表中值为{}的条目表示对应绑定尚未实现遇到依赖它们的深层 Node 特性时可能需要回退到 Node 运行环境。对读者而言想排查某个 Node 内置模块在 Deno 下的兼容性差异最有效的起点就是按本文第六节的方法从该内置模块的 polyfill 文件出发找到它loadExtScript的具体internal_binding/*文件再对照 Node 同名绑定核对行为差异。【免费下载链接】denoA modern runtime for JavaScript and TypeScript.项目地址: https://gitcode.com/GitHub_Trending/de/deno创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表