mm_mqtt_server 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/mqtt_server.js +127 -38
- package/package.json +1 -1
package/lib/mqtt_server.js
CHANGED
|
@@ -5,6 +5,12 @@ const { Aedes } = require('aedes');
|
|
|
5
5
|
const { Mod } = require('mm_mod');
|
|
6
6
|
const { eventAdmin } = require('mm_event');
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* 代理自发布标记:打在发布报文上,用于在 broker 的 publish 事件中避免回环。
|
|
10
|
+
* 使用 Symbol 避免污染报文序列化,aedes 转投给订阅者时按字段复制也不会带上该标记。
|
|
11
|
+
*/
|
|
12
|
+
const SELF_PUBLISH = Symbol('mm_mqtt_server.self_publish');
|
|
13
|
+
|
|
8
14
|
/**
|
|
9
15
|
* MqttServer —— 最薄的 MQTT 代理传输层(入站适配器模块)。
|
|
10
16
|
*
|
|
@@ -33,9 +39,10 @@ class MqttServer extends Mod {
|
|
|
33
39
|
title: 'MQTT代理服务器模块',
|
|
34
40
|
description: 'MQTT 传输层:消息直达事件管理器,不做路由与插件',
|
|
35
41
|
version: '2.0.0',
|
|
42
|
+
allow_anonymous: true,
|
|
43
|
+
scope: 'mqtt',
|
|
36
44
|
host: '0.0.0.0',
|
|
37
45
|
port: 1883,
|
|
38
|
-
allow_anonymous: true,
|
|
39
46
|
username: '',
|
|
40
47
|
password: '',
|
|
41
48
|
options: {},
|
|
@@ -80,22 +87,41 @@ class MqttServer extends Mod {
|
|
|
80
87
|
*/
|
|
81
88
|
constructor(config, server) {
|
|
82
89
|
super(config, server);
|
|
83
|
-
this.#events = eventAdmin(
|
|
90
|
+
this.#events = eventAdmin(this.config.scope);
|
|
84
91
|
}
|
|
85
92
|
|
|
86
93
|
// ---------- 生命周期钩子 ----------
|
|
87
94
|
/**
|
|
88
|
-
* 启动钩子:创建 broker 与 TCP
|
|
95
|
+
* 启动钩子:创建 broker 与 TCP 服务器并开始监听,失败时回滚已创建的资源
|
|
89
96
|
* @returns {Promise<void>} 无返回值
|
|
90
97
|
*/
|
|
91
98
|
async onStart() {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
try {
|
|
100
|
+
const broker = await this._createBroker();
|
|
101
|
+
this.#broker = broker;
|
|
102
|
+
this._bindBrokerEvents(broker);
|
|
103
|
+
const server = net.createServer((stream) => broker.handle(stream));
|
|
104
|
+
this.#server = server;
|
|
105
|
+
await this._listen(server);
|
|
106
|
+
server.on('error', (err) => this._emitError(err));
|
|
107
|
+
} catch (err) {
|
|
108
|
+
await this._rollbackStart();
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 启动失败时回滚已创建的资源(服务器与 broker 的内部定时器)
|
|
115
|
+
* @returns {Promise<void>} 无返回值
|
|
116
|
+
*/
|
|
117
|
+
async _rollbackStart() {
|
|
118
|
+
try {
|
|
119
|
+
this._closeAllClients();
|
|
120
|
+
await this._closeServer();
|
|
121
|
+
await this._closeBroker();
|
|
122
|
+
} catch (err) {
|
|
123
|
+
this._emitError(err);
|
|
124
|
+
}
|
|
99
125
|
}
|
|
100
126
|
|
|
101
127
|
/**
|
|
@@ -197,14 +223,19 @@ class MqttServer extends Mod {
|
|
|
197
223
|
const broker = this.#broker;
|
|
198
224
|
if (!broker) return Promise.resolve(false);
|
|
199
225
|
const packet = this._buildPacket(topic, payload, opts);
|
|
226
|
+
packet[SELF_PUBLISH] = true;
|
|
200
227
|
return new Promise((resolve, reject) => {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
228
|
+
try {
|
|
229
|
+
broker.publish(packet, (err) => {
|
|
230
|
+
if (err) {
|
|
231
|
+
reject(err);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
resolve(true);
|
|
235
|
+
});
|
|
236
|
+
} catch (err) {
|
|
237
|
+
reject(err);
|
|
238
|
+
}
|
|
208
239
|
});
|
|
209
240
|
}
|
|
210
241
|
|
|
@@ -284,12 +315,10 @@ class MqttServer extends Mod {
|
|
|
284
315
|
* @param {Object} broker aedes broker 实例
|
|
285
316
|
*/
|
|
286
317
|
_bindBrokerEvents(broker) {
|
|
318
|
+
broker.on('error', (err) => this._emitError(err));
|
|
287
319
|
broker.on('client', (client) => this._registerClient(client));
|
|
288
|
-
broker.on('clientDisconnect', (client) => this
|
|
289
|
-
broker.on('publish', (packet, client) =>
|
|
290
|
-
if (!client) return;
|
|
291
|
-
this._handlePublish(client, packet);
|
|
292
|
-
});
|
|
320
|
+
broker.on('clientDisconnect', (client) => this._unregisterClient(client));
|
|
321
|
+
broker.on('publish', (packet, client) => this._dispatchPublish(packet, client));
|
|
293
322
|
broker.on('clientError', (client, err) => this._emitError(err));
|
|
294
323
|
broker.on('connectionError', (client, err) => this._emitError(err));
|
|
295
324
|
}
|
|
@@ -308,21 +337,92 @@ class MqttServer extends Mod {
|
|
|
308
337
|
}
|
|
309
338
|
|
|
310
339
|
/**
|
|
311
|
-
*
|
|
340
|
+
* 注销客户端:仅当连接表中记录的仍是该实例时才删除,避免误删同 ID 的新连接
|
|
312
341
|
* @param {Object} client aedes client 实例
|
|
342
|
+
*/
|
|
343
|
+
_unregisterClient(client) {
|
|
344
|
+
const entry = this.#clients.get(client.id);
|
|
345
|
+
if (entry && entry.client === client) this.#clients.delete(client.id);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* 分发 broker 发布事件:过滤系统主题与代理自发布后转交事件管理器
|
|
350
|
+
* @param {Object} packet aedes 发布报文
|
|
351
|
+
* @param {Object|null} client 发布客户端,内部消息与代理自发布时为 null
|
|
352
|
+
*/
|
|
353
|
+
_dispatchPublish(packet, client) {
|
|
354
|
+
if (this._isInternalTopic(packet.topic)) return;
|
|
355
|
+
if (!client && this._isSelfPublish(packet)) return;
|
|
356
|
+
this._handlePublish(client, packet);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* 判断报文是否由代理自身发布(按报文上的自发布标记判断)
|
|
361
|
+
* @param {Object} packet aedes 发布报文
|
|
362
|
+
* @returns {boolean} 是否为代理自发布
|
|
363
|
+
*/
|
|
364
|
+
_isSelfPublish(packet) {
|
|
365
|
+
return packet[SELF_PUBLISH] === true;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* 判断是否为 broker 系统主题
|
|
370
|
+
* @param {string} topic 主题
|
|
371
|
+
* @returns {boolean} 是否为系统主题
|
|
372
|
+
*/
|
|
373
|
+
_isInternalTopic(topic) {
|
|
374
|
+
return topic === '$SYS' || topic.startsWith('$SYS/');
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* 处理入站发布消息:交给事件管理器,返回发布描述时由代理重发
|
|
379
|
+
* @param {Object|null} client aedes client 实例,遗嘱等内部来源可能为 null
|
|
313
380
|
* @param {Object} packet aedes 发布报文
|
|
314
381
|
* @returns {Promise<void>} 无返回值
|
|
315
382
|
*/
|
|
316
383
|
async _handlePublish(client, packet) {
|
|
317
|
-
const
|
|
318
|
-
|
|
384
|
+
const client_id = client ? client.id : null;
|
|
385
|
+
const params = this._buildParams(client_id, packet);
|
|
386
|
+
const ctx = this._buildContext(client_id, packet, client);
|
|
387
|
+
const options = client_id ? this._clientOptions(client_id) : {};
|
|
388
|
+
try {
|
|
389
|
+
const result = await this.#events.run(packet.topic, params, ctx, options);
|
|
390
|
+
const error = ctx.error || options.error;
|
|
391
|
+
if (error) this._emitError(error);
|
|
392
|
+
if (this._isRepublish(result)) {
|
|
393
|
+
await this.publish(result.topic, result.payload, result);
|
|
394
|
+
}
|
|
395
|
+
} catch (err) {
|
|
396
|
+
this._emitError(err);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* 构建事件执行参数
|
|
402
|
+
* @param {string|null} client_id 客户端编号
|
|
403
|
+
* @param {Object} packet aedes 发布报文
|
|
404
|
+
* @returns {Object} 参数对象
|
|
405
|
+
*/
|
|
406
|
+
_buildParams(client_id, packet) {
|
|
407
|
+
return {
|
|
408
|
+
client_id,
|
|
319
409
|
topic: packet.topic,
|
|
320
410
|
payload: packet.payload.toString('utf8'),
|
|
321
411
|
qos: packet.qos,
|
|
322
412
|
retain: packet.retain
|
|
323
413
|
};
|
|
324
|
-
|
|
325
|
-
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* 构建事件执行上下文
|
|
418
|
+
* @param {string|null} client_id 客户端编号
|
|
419
|
+
* @param {Object} packet aedes 发布报文
|
|
420
|
+
* @param {Object|null} client aedes client 实例
|
|
421
|
+
* @returns {Object} 上下文对象
|
|
422
|
+
*/
|
|
423
|
+
_buildContext(client_id, packet, client) {
|
|
424
|
+
return {
|
|
425
|
+
client_id,
|
|
326
426
|
topic: packet.topic,
|
|
327
427
|
qos: packet.qos,
|
|
328
428
|
retain: packet.retain,
|
|
@@ -330,17 +430,6 @@ class MqttServer extends Mod {
|
|
|
330
430
|
client,
|
|
331
431
|
broker: this.#broker
|
|
332
432
|
};
|
|
333
|
-
const options = this._clientOptions(client.id);
|
|
334
|
-
try {
|
|
335
|
-
const result = await this.#events.run(packet.topic, params, ctx, options);
|
|
336
|
-
const error = ctx.error || options.error;
|
|
337
|
-
if (error) this._emitError(error);
|
|
338
|
-
if (this._isRepublish(result)) {
|
|
339
|
-
await this.publish(result.topic, result.payload, result);
|
|
340
|
-
}
|
|
341
|
-
} catch (err) {
|
|
342
|
-
this._emitError(err);
|
|
343
|
-
}
|
|
344
433
|
}
|
|
345
434
|
|
|
346
435
|
/**
|