在 njs 中使用 node 模块
环境 Protobufjs DNS-packet |
通常,开发人员希望使用第三方代码 通常以某种 library 的形式提供。 在 JavaScript 世界中,模块的概念相对较新, 所以直到最近才有标准。 许多平台(浏览器)仍然不支持模块,这使得代码 更难重用。 本文介绍了在 njs 中重用Node.js代码的方法。
本文中的示例使用了 njs 0.3.8 中出现的功能
存在许多问题 当第三方代码添加到 NJS 时,可能会出现这种情况:
- 相互引用的多个文件及其依赖关系
- 特定于平台的 API
- 现代标准语言结构
好消息是,这些问题并不是 njs 特有的新鲜事物或特有的问题。 JavaScript 开发人员每天都会面临这些问题 尝试支持多个不同的平台时 具有非常不同的特性。 有一些工具旨在解决上述问题。
- 相互引用的多个文件及其依赖关系
这可以通过将所有相互依赖的代码合并到一个文件中来解决。 browserify 或 webpack 等工具接受整个项目并生成一个包含 您的代码和所有依赖项。
- 特定于平台的 API
您可以使用多个库来实现此类 API 以与平台无关的方式(尽管以牺牲性能为代价)。 特定功能也可以使用 polyfill 方法实现。
- 现代标准语言结构
这样的代码可以被转译为: 这意味着执行许多转换 根据旧标准重写较新的语言功能。 例如, babel 项目 可以用于此目的。
在本指南中,我们将使用两个相对较大的 npm 托管库:
- protobufjs — 用于创建和解析 gRPC 协议使用的 protobuf 消息的库
- dns-packet — 一个用于处理 DNS 协议数据包的库
环境
本文档主要采用通用方法 并避免提供有关 Node.js 的特定最佳实践建议 和 JavaScript。 请务必查阅相应软件包的手册 在按照此处建议的步骤作之前。
首先(假设 Node.js 已安装并运行),让我们创建一个 空项目并安装一些依赖项; 下面的命令假设我们在工作目录中:
$ mkdir my_project && cd my_project $ npx license choose_your_license_here > LICENSE $ npx gitignore node $ cat > package.json <<EOF { "name": "foobar", "version": "0.0.1", "description": "", "main": "index.js", "keywords": [], "author": "somename <some.email@example.com> (https://example.com)", "license": "some_license_here", "private": true, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" } } EOF $ npm init -y $ npm install browserify
Protobufjs
该库提供了一个解析器
对于.proto
接口定义
以及用于消息解析和生成的代码生成器。
在此示例中,我们将使用 helloworld.proto 文件
来自 gRPC 示例。
我们的目标是创建两条消息:HelloRequest
和HelloResponse
.
我们将使用 protobufjs 的静态模式,而不是动态生成类,因为
njs 不支持动态添加新函数
出于安全考虑。
接下来,安装库并 实现消息封送的 JavaScript 代码 从协议定义生成:
$ npm install protobufjs $ npx pbjs -t static-module helloworld.proto > static.js
因此,static.js
file 成为我们的新依赖项,
存储我们实现消息处理所需的所有代码。
这set_buffer()
函数包含使用
库创建带有序列化HelloRequest
消息。
该代码位于code.js
文件:
var pb = require('./static.js'); // Example usage of protobuf library: prepare a buffer to send function set_buffer(pb) { // set fields of gRPC payload var payload = { name: "TestString" }; // create an object var message = pb.helloworld.HelloRequest.create(payload); // serialize object to buffer var buffer = pb.helloworld.HelloRequest.encode(message).finish(); var n = buffer.length; var frame = new Uint8Array(5 + buffer.length); frame[0] = 0; // 'compressed' flag frame[1] = (n & 0xFF000000) >>> 24; // length: uint32 in network byte order frame[2] = (n & 0x00FF0000) >>> 16; frame[3] = (n & 0x0000FF00) >>> 8; frame[4] = (n & 0x000000FF) >>> 0; frame.set(buffer, 5); return frame; } var frame = set_buffer(pb);
为了确保它正常工作,我们使用 node 执行代码:
$ node ./code.js Uint8Array [ 0, 0, 0, 0, 12, 10, 10, 84, 101, 115, 116, 83, 116, 114, 105, 110, 103 ]
你可以看到,这让我们得到了一个正确的编码gRPC
框架。
现在让我们用 njs 来运行它:
$ njs ./code.js Thrown: Error: Cannot find module "./static.js" at require (native) at main (native)
不支持模块,因此我们收到了一个异常。
为了解决这个问题,让我们使用browserify
或其他类似工具。
尝试处理我们现有的code.js
file 将产生
在一堆应该在浏览器中运行的 JS 代码中,
即加载后立即加载。
这不是我们真正想要的。
相反,我们希望有一个导出的函数,该函数
可以从 nginx 配置中引用。
这需要一些包装代码。
在本指南中,我们使用 njs cli 为简单起见。 在现实生活中,您将使用 nginx njs 模块来运行您的代码。
这load.js
file 包含库加载代码,该代码
将其句柄存储在全局命名空间中:
global.hello = require('./static.js');
此代码将替换为合并的内容。
我们的代码将使用 ”global.hello
“ 句柄访问
库。
接下来,我们用browserify
要将所有依赖项放入单个文件中,请执行以下作:
$ npx browserify load.js -o bundle.js -d
结果是一个包含我们所有依赖项的大文件:
(function(){function...... ... ... },{"protobufjs/minimal":9}]},{},[1]) //# sourceMappingURL..............
要获得最终”njs_bundle.js
“文件
"bundle.js
“ 和以下代码:
// Example usage of protobuf library: prepare a buffer to send function set_buffer(pb) { // set fields of gRPC payload var payload = { name: "TestString" }; // create an object var message = pb.helloworld.HelloRequest.create(payload); // serialize object to buffer var buffer = pb.helloworld.HelloRequest.encode(message).finish(); var n = buffer.length; var frame = new Uint8Array(5 + buffer.length); frame[0] = 0; // 'compressed' flag frame[1] = (n & 0xFF000000) >>> 24; // length: uint32 in network byte order frame[2] = (n & 0x00FF0000) >>> 16; frame[3] = (n & 0x0000FF00) >>> 8; frame[4] = (n & 0x000000FF) >>> 0; frame.set(buffer, 5); return frame; } // functions to be called from outside function setbuf() { return set_buffer(global.hello); } // call the code var frame = setbuf(); console.log(frame);
让我们使用 node 运行文件以确保一切仍然有效:
$ node ./njs_bundle.js Uint8Array [ 0, 0, 0, 0, 12, 10, 10, 84, 101, 115, 116, 83, 116, 114, 105, 110, 103 ]
现在让我们进一步使用 njs:
$ njs ./njs_bundle.js Uint8Array [0,0,0,0,12,10,10,84,101,115,116,83,116,114,105,110,103]
最后一件事是使用特定于 njs 的 API 进行转换
数组转换为字节字符串,以便 nginx 模块可以使用它。
我们可以在这行之前添加以下代码段return frame; }
:
if (global.njs) { return String.bytesFrom(frame) }
最后,我们让它工作起来:
$ njs ./njs_bundle.js |hexdump -C 00000000 00 00 00 00 0c 0a 0a 54 65 73 74 53 74 72 69 6e |.......TestStrin| 00000010 67 0a |g.| 00000012
这是预期的结果。 响应解析可以类似地实现:
function parse_msg(pb, msg) { // convert byte string into integer array var bytes = msg.split('').map(v=>v.charCodeAt(0)); if (bytes.length < 5) { throw 'message too short'; } // first 5 bytes is gRPC frame (compression + length) var head = bytes.splice(0, 5); // ensure we have proper message length var len = (head[1] << 24) + (head[2] << 16) + (head[3] << 8) + head[4]; if (len != bytes.length) { throw 'header length mismatch'; } // invoke protobufjs to decode message var response = pb.helloworld.HelloReply.decode(bytes); console.log('Reply is:' + response.message); }
DNS 数据包
此示例使用库来生成和解析 DNS 数据包。 这是一个值得考虑的情况,因为库及其依赖项 使用 NJS 尚不支持的现代语言结构。 反过来,这需要我们执行一个额外的步骤:转译源代码。
需要额外的节点包:
$ npm install @babel/core @babel/cli @babel/preset-env babel-loader $ npm install webpack webpack-cli $ npm install buffer $ npm install dns-packet
配置文件 webpack.config.js:
const path = require('path'); module.exports = { entry: './load.js', mode: 'production', output: { filename: 'wp_out.js', path: path.resolve(__dirname, 'dist'), }, optimization: { minimize: false }, node: { global: true, }, module : { rules: [{ test: /\.m?js$$/, exclude: /(bower_components)/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } }] } };
请注意,我们使用的是”production
“模式。
在此模式下,webpack 不使用”eval
“ 施工
不受 NJS 支持。
引用的load.js
file 是我们的入口点:
global.dns = require('dns-packet') global.Buffer = require('buffer/').Buffer
我们以同样的方式开始,为库生成一个文件:
$ npx browserify load.js -o bundle.js -d
接下来,我们使用 webpack 处理文件,它本身会调用 babel:
$ npx webpack --config webpack.config.js
此命令会生成dist/wp_out.js
文件,它是一个
的转译版本bundle.js
.
我们需要将其与code.js
它存储我们的代码:
function set_buffer(dnsPacket) { // create DNS packet bytes var buf = dnsPacket.encode({ type: 'query', id: 1, flags: dnsPacket.RECURSION_DESIRED, questions: [{ type: 'A', name: 'google.com' }] }) return buf; }
请注意,在此示例中,生成的代码没有包装到 function 中,我们
不需要显式调用它。
结果显示在”dist
“ 目录下:
$ cat dist/wp_out.js code.js > njs_dns_bundle.js
让我们在文件末尾调用我们的代码:
var b = set_buffer(global.dns); console.log(b);
并使用 node 执行它:
$ node ./njs_dns_bundle_final.js Buffer [Uint8Array] [ 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 6, 103, 111, 111, 103, 108, 101, 3, 99, 111, 109, 0, 0, 1, 0, 1 ]
确保它按预期工作,然后使用 njs 运行它:
$ njs ./njs_dns_bundle_final.js Uint8Array [0,1,1,0,0,1,0,0,0,0,0,0,6,103,111,111,103,108,101,3,99,111,109,0,0,1,0,1]
响应可以按如下方式解析:
function parse_response(buf) { var bytes = buf.split('').map(v=>v.charCodeAt(0)); var b = global.Buffer.from(bytes); var packet = dnsPacket.decode(b); var resolved_name = packet.answers[0].name; // expected name is 'google.com', according to our request above }