在 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.jsfile 成为我们的新依赖项,
存储我们实现消息处理所需的所有代码。
这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.jsfile 将产生
在一堆应该在浏览器中运行的 JS 代码中,
即加载后立即加载。
这不是我们真正想要的。
相反,我们希望有一个导出的函数,该函数
可以从 nginx 配置中引用。
这需要一些包装代码。
在本指南中,我们使用 njs cli 为简单起见。 在现实生活中,您将使用 nginx njs 模块来运行您的代码。
这load.jsfile 包含库加载代码,该代码
将其句柄存储在全局命名空间中:
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.jsfile 是我们的入口点:
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
}