博客迁移
NodeRed 提供了一个 Hook 模块,此模块向消息生命周期的节点注册,控制消息的流转。
1. 消息路由是什么? 有几种实现方式 ?
消息路由即通过路由规则动态规划消息的传输路径,使消息按照过滤条件,从消息源路由到目标节点。通过消息路由,可实现对数据路由的灵活控制和提高数据安全性。
主要有两种方式:
-
堆栈式
类 Express 的 middleware,每新增加一个处理模块,会在处理堆栈的顶端,最终形成一个消息处理堆栈。 -
Hook 式
以 Hook(钩子)的形式实现,这需要在特定的节点处,增加对事件的回调,在有事件触发时,按序执行回调。
2. NodeRed 中消息如何流转
2.1. NodeRed 消息流转图
从消息发送到处理完毕,有以下几个事件:
-
onSend
-
preRoute
-
preDeliver
-
postDeliver
-
onReceive
-
postReceive
-
onComplete
2.2. SendEvent object
{
"msg": "<message object>",
"source": {
"id": "<node-id>",
"node": "<node-object>",
"port": "<index of port being sent on>",
},
"destination": {
"id": "<node-id>",
"node": undefined,
},
"cloneMessage": "true|false"
}
2.3. ReceiveEvent object
{
"msg": "<message object>",
"destination": {
"id": "<node-id>",
"node": "<node-object>",
}
}
2.4. CompleteEvent object
{
"msg": "<message object>",
"node": {
"id": "<node-id>",
"node": "<node-object>"
},
"error": "<error passed to done, otherwise, undefined>"
}
2.5. 有哪些参与者
2.5.1. packages/node_modules/@node-red/util/lib/hooks.js
- 新增 Hook(以链表存储 Hook )
function add(hookId, callback) {
...
let tailItem = hooks[id];
if (tailItem === undefined) {
hooks[id] = hookItem;
} else {
while(tailItem.nextHook !== null) {
tailItem = tailItem.nextHook
}
tailItem.nextHook = hookItem;
hookItem.previousHook = tailItem;
}
if (label) {
labelledHooks[label] = labelledHooks[label]||{};
labelledHooks[label][id] = hookItem;
}
...
}
- 触发消息流
支持回调方式,Promise 方式
function trigger(hookId, payload, done) {
let hookItem = hooks[hookId];
if (!hookItem) { //没有钩子执行回调或返回
if (done) {
done();
return;
} else {
return Promise.resolve();
}
}
if (!done) { //有钩子没有回调,以微任务方式处理所有Hook
return new Promise((resolve,reject) => {
invokeStack(hookItem,payload,function(err) {
if (err !== undefined && err !== false) {
if (!(err instanceof Error)) {
err = new Error(err);
}
err.hook = hookId
reject(err);
} else {
resolve(err);
}
})
});
} else { //有钩子有回调,处理所有Hook
invokeStack(hookItem,payload,done)
}
}
- 处理 Hook
function invokeStack(hookItem,payload,done) {
function callNextHook(err) {
if (!hookItem || err) {
done(err);
return;
}
if (hookItem.removed) {
hookItem = hookItem.nextHook;
callNextHook();
return;
}
const callback = hookItem.cb;
if (callback.length === 1) { //回调函数一个参数
try {
let result = callback(payload);
if (result === false) {
// Halting the flow
done(false);
return
}
if (result && typeof result.then === 'function') {
result.then(handleResolve, callNextHook)
return;
}
hookItem = hookItem.nextHook;
callNextHook();
} catch(err) {
done(err);
return;
}
} else { //回调函数两个参数
try {
callback(payload,handleResolve)
} catch(err) {
done(err);
return;
}
}
}
function handleResolve(result) {
if (result === undefined) {
hookItem = hookItem.nextHook;
callNextHook();
} else {
done(result);
}
}
callNextHook();
}
2.5.2. packages/node_modules/@node-red/runtime/lib/flows/Flow.js
2.6. 流程哪些
3. NodeRed 为什么这么设计,这种设计的优劣有哪些
堆栈式有一个问题,当以动态方式增加处理模块后,在不需要处理模块的情况下,删除增加的处理模块需要恢复原有的堆栈,增加了处理复杂度。以 Hook 方式动态的增加删除 Hook 不会影响原有消息路由。
这种选择也是根据 NodeRed 实际情况而来,在原有消息的传递机制上增加”关键点“的方式好实现。
4. Hook 模块与其他模块的关系
Node、Flow 模块调用 Hook 模块,触发 Hook 流转。
自定义模块调用 Hook 模块,增加 Hook 处理。