mockサーバーを実装する

This commit is contained in:
ry-yamafuji 2024-06-17 06:15:40 +09:00
parent 5e28005d75
commit a87a3bd052
10 changed files with 2942 additions and 3 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules/

View File

@ -14,7 +14,7 @@ RUN npm install
COPY . .
# Expose the MQTT port
EXPOSE 1883
EXPOSE 3000 1883 8888
# Run the MQTT broker
CMD ["node", "broker.js"]
CMD ["node", "index.js"]

View File

@ -1,5 +1,7 @@
npm install aedes --save
npm install aedes-cli --global
npm install express aedes ws
```
@ -22,4 +24,8 @@ aedes.on('clientDisconnect', function (client) {
aedes.on('publish', function (packet, client) {
console.log('Published:', packet.payload.toString());
});
```
```
mosquitto_pub -h localhost -p 1883 -t test-sub -m "Server Push"
mosquitto_pub -h localhost -p 1883 -t test-sub -m "Server Push" -u "test_user" -P "test_pass"

14
docker-compose.yaml Normal file
View File

@ -0,0 +1,14 @@
version: '3.8'
services:
node-server:
build: .
ports:
- "3000:3000"
- "1883:1883"
- "8888:8888"
tty: true
volumes:
- .:/usr/src/app
environment:
- NODE_ENV=development

17
documents/web-api.yml Normal file
View File

@ -0,0 +1,17 @@
openapi: "3.0.2"
info:
title: WEB API
version: "1.0"
servers:
- url: http://localhost:8888
paths:
/helloworld:
get:
responses:
"200":
description: OK
/timeout:
get:
responses:
"200":
description: OK

2701
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

24
package.json Normal file
View File

@ -0,0 +1,24 @@
{
"name": "npm-mqtt-server-mock",
"version": "1.0.0",
"description": "npm install aedes --save\r npm install aedes-cli --global",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://appj.pglikers.com/gitbucket/git/mock/npm-mqtt-server-mock.git"
},
"author": "ry.yamafuji",
"license": "ISC",
"dependencies": {
"aedes": "^0.51.2",
"aedes-persistence": "^9.1.2",
"aedes-persistence-level": "^8.0.1",
"express": "^4.19.2",
"mqemitter": "^6.0.0",
"mqtt": "^5.7.0",
"ws": "^8.17.0"
}
}

24
src/client.js Normal file
View File

@ -0,0 +1,24 @@
const mqtt = require('mqtt');
const PUB_TOPIC = "test-sub";
const MESSAGE = "Message from server";
const options = {
clientId: 'test_device2',
username: 'test_user',
password: 'test_pass',
};
const client = mqtt.connect('mqtt://localhost:1883', options);
client.on('connect', function () {
console.log('Connected to MQTT broker');
setInterval(function () {
client.publish(PUB_TOPIC, MESSAGE);
console.log(`Published: ${MESSAGE} to ${PUB_TOPIC}`);
}, 10000); // 30秒ごとにパブリッシュ
});
client.on('error', function (err) {
console.error('MQTT client error:', err);
});

80
src/index.js Normal file
View File

@ -0,0 +1,80 @@
// index.js
const http = require('http');
const aedes = require('aedes')({
connectTimeout: 1000 * 60 * 2
}
);
const net = require('net');
const common = require('./lib/common'); //CommonJS
// import common from './lib/common'; //ESモジュール
const MQTT_PORT = 1883;
const mqttServer = net.createServer(aedes.handle);
mqttServer.listen(MQTT_PORT, () => {
common.logger(`Mocke MQTTerver Starting port ${MQTT_PORT}... [${common.toJaTimeFormat(new Date(), "YYYY-MM-DD hh:nn:ss")}]`);
});
const PUB_TOPIC = "test/sub";
const MESSAGE = "Message from server";
aedes.on('client', function (client) {
console.log('Client Connected: ', client.id);
});
aedes.on('clientDisconnect', function (client) {
console.log('Client Disconnected: ', client.id);
});
aedes.on('publish', function (packet, client) {
console.log('Message Published: ', packet.payload.toString());
console.log('Topic: ', packet.topic.toString());
});
// クライアント接続前に認証を行う
aedes.authenticate = function (client, username, password, callback) {
if ((username !== 'test_user' && username !== 'test_user_pass') || password.toString() !== 'test_pass') {
const error = new Error('Authentication failed');
error.returnCode = 4; // 認証エラー
callback(error, false);
} else if ((client.id !== 'test' && client.id !== 'test_device' && client.id !== 'test_device2')) {
const error = new Error('Client ID banned');
callback(error, false);
} else {
callback(null, true);
}
};
// クライアント接続時に特定のクライアントIDを拒否する
aedes.authorizePublish = function (client, packet, callback) {
common.logger(`authorizePublish ${client.id}`)
if (client.id !== 'test_device' && client.id !== 'test_device2') {
common.error("authorizePublish Failer")
const error = new Error('Client not authorized');
callback(error);
} else {
callback(null);
}
};
const httpserver = http.createServer()
/* httpサーバー APIより受付*/
common.logger(`Web Mocke APIServer Starting... [${common.toJaTimeFormat(new Date(), "YYYY-MM-DD hh:nn:ss")}]`);
httpserver.on('request', async (req, res) => {
if (req.url === '/helloworld') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World');
} else if (req.url === '/timeout') {
// 応答をまったく返さないことでクライアントにタイムアウトを引き起こす
common.logger('Timeout endpoint was hit, not sending response.');
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
})
httpserver.listen(8888, '0.0.0.0');

72
src/lib/common.js Normal file
View File

@ -0,0 +1,72 @@
/**
* common.ts
* 汎用的な関数を設置
*
* @module
*/
const common = {
/**
* ログを出力する(ログレベルinfo)
*
* @method logger
* @param message ログ出力内容
*/
logger: (message, ...optionalParams) => {
console.log(message, ...optionalParams);
// logger.info(message);
},
debug: (message, ...optionalParams) => {
console.debug(message, ...optionalParams);
// logger.debug(message);
},
error: (message, ...optionalParams) => {
console.error(message, ...optionalParams);
// logger.error(message);
},
/**
* 日付修正 OCPP(UTC)からDB用に変換(JTS)
* UTCの時間を日本時間に変換する(+9)
* - prismaにrecordを更新する時の調整に使用
* - OCPPの仕様ではUTC時間で通信をおこなう
* @method toJaTimeString
* @param dt 日付UTC
* @returns {string} DB(JTS)
*/
toJaTimeString: (dt) => {
dt.setHours(dt.getHours() + 9)
return dt.toISOString()
},
/**
* toJaTimeFormat 日付フォーマット
* 日付を指定の形で整形する
* @method toJaTimeFormat
* @param {Date} dt 日付
* @param {string} format 整形(YYなどで指定する)
* @returns {string} 整形された文字列
*/
toJaTimeFormat: (dt, format = "") => {
dt = new Date(dt)
dt.setHours(dt.getHours() + 9)
const y = dt.getFullYear().toString();
const m = ('00' + (dt.getMonth() + 1)).slice(-2);
const d = ('00' + dt.getDate()).slice(-2);
const h = ('00' + dt.getHours()).slice(-2);
const n = ('00' + dt.getMinutes()).slice(-2);
const s = ('00' + dt.getSeconds()).slice(-2);
format = format.replace(/YYYY/, y);
format = format.replace(/MM/, m);
format = format.replace(/DD/, d);
format = format.replace(/hh/, h);
format = format.replace(/nn/, n);
format = format.replace(/ss/, s);
return format
},
};
// export default common;
module.exports = common;