我有这个代码,我用它做两件事
它写得很糟糕,所以任何指针都会很棒(但是它很好)
// This originated from https://gist.github.com/CalebEverett/bed94582b437ffe88f650819d772b682
// and was modified to suite our needs
const fs = require('fs'),
WebSocket = require('ws'),
express = require('express'),
https = require('https'),
mysql = require('mysql'),
expressWs = require('express-ws'),
path = require('path'),
cors = require('cors');
const envImportResult = require('dotenv').config({
path: "/var/www/LxdMosaic/.env"
});
if (envImportResult.error) {
throw envImportResult.error
}
// Https certificate and key file location for secure websockets + https server
var privateKey = fs.readFileSync(process.env.CERT_PRIVATE_KEY, 'utf8'),
certificate = fs.readFileSync(process.env.CERT_PATH, 'utf8');
certDir = "/var/www/LxdMosaic/src/sensitiveData/certs/",
lxdConsoles = [],
credentials = {
key: privateKey,
cert: certificate
},
app = express();
app.use(cors());
var bodyParser = require('body-parser')
app.use( bodyParser.json() ); // to support JSON-encoded bodies
app.use(bodyParser.urlencoded({ // to support URL-encoded bodies
extended: true
}));
var httpsServer = https.createServer(credentials, app);
var io = require('socket.io')(httpsServer);
var operationSocket = io.of("/operations")
// expressWs(app, httpsServer);
var con = mysql.createConnection({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME
});
var hostDetails = {};
function createExecOptions(host, container) {
return {
method: 'POST',
host: hostDetails[host].hostWithOutProtoOrPort,
port: hostDetails[host].port,
path: '/1.0/containers/' + container + '/exec',
cert: fs.readFileSync(hostDetails[host].cert),
key: fs.readFileSync(hostDetails[host].key),
rejectUnauthorized: false
}
}
const lxdExecBody = JSON.stringify({
"command": ["bash"],
"environment": {
"HOME": "/root",
"TERM": "xterm",
"USER": "root"
},
"wait-for-websocket": true,
"interactive": true,
})
con.connect(function(err) {
if (err) {
throw err;
}
});
function createWebSockets() {
con.query("SELECT * FROM Hosts", function(err, result, fields) {
if (err) {
throw err;
}
for (i = 0; i < result.length; i++) {
let lxdClientCert = certDir + result[i].Host_Cert_Only_File
let lxdClientKey = certDir + result[i].Host_Key_File
if(result[i].Host_Online == 0){
continue;
}
// Connecting to the lxd server/s
const wsoptions = {
cert: fs.readFileSync(lxdClientCert),
key: fs.readFileSync(lxdClientKey),
rejectUnauthorized: false,
}
var portRegex = /:[0-9]+/;
let stringUrl = result[i].Host_Url_And_Port;
let urlURL = new URL(result[i].Host_Url_And_Port);
let hostWithOutProto = stringUrl.replace("https://", "");
let hostWithOutProtoOrPort = hostWithOutProto.replace(portRegex, "");
hostDetails[result[i].Host_Url_And_Port] = {
cert: lxdClientCert,
key: lxdClientKey,
hostWithOutProtoOrPort: hostWithOutProtoOrPort,
port: urlURL.port
};
var ws = new WebSocket('wss://' + hostWithOutProto + '/1.0/events?type=operation', wsoptions);
ws.on('message', function(data, flags) {
var buf = Buffer.from(data)
let message = JSON.parse(data.toString());
message.host = hostWithOutProtoOrPort;
operationSocket.emit('operationUpdate', message);
});
}
});
}
httpsServer.listen(3000, function() {});
app.get('/hosts/reload/', function(req, res) {
createWebSockets();
res.send({
success: "reloaded"
});
});
app.post('/hosts/message/', function(req, res) {
console.log(req.body.data);
operationSocket.emit(req.body.type, req.body.data);
res.send({
success: "delivered"
});
});
app.get('/', function(req, res) {
res.sendFile(path.join(__dirname + '/index.html'));
});
var terminalsIo = io.of("/terminals");
terminalsIo.on("connect", function(socket) {
let indentifier = socket.handshake.query.pid;
if(lxdConsoles[indentifier] == undefined) {
let host = socket.handshake.query.host;
let container = socket.handshake.query.container;
let execOptions = createExecOptions(host, container);
const wsoptions = {
cert: execOptions.cert,
key: execOptions.key,
rejectUnauthorized: false
}
const lxdReq = https.request(execOptions, res => {
res.on('data', d => {
const output = JSON.parse(d);
if(output.hasOwnProperty("error") && output.error !== ""){
socket.emit("data", "Container Offline");
return false;
}
const lxdWs = new WebSocket('wss://' +
execOptions.host + ':' + execOptions.port + output.operation +
'/websocket?secret=' + output.metadata.metadata.fds['0'],
wsoptions
);
lxdWs.on('error', error => console.log(error));
lxdWs.on('message', data => {
try {
const buf = Buffer.from(data);
data = buf.toString();
socket.emit("data", data);
} catch (ex) {
// The WebSocket is not open, ignore
}
});
lxdConsoles.push(lxdWs);
});
});
lxdReq.write(lxdExecBody);
lxdReq.end();
}
//NOTE When user inputs from browser
socket.on('data', function(msg) {
lxdConsoles[indentifier].send(msg, {
binary: true
}, () => {});
});
socket.on('close', function(indentifier) {
setTimeout(() => {
if(lxdConsoles[indentifier] == undefined){
return
}
lxdConsoles[indentifier].send('exit \r', { binary: true }, function(){
lxdConsoles[indentifier].close();
delete lxdConsoles[indentifier];
});
}, 100);
});
});
app.post('/terminals', function(req, res) {
// Create a indentifier for the console, this should allow multiple consolses
// per user
res.send(lxdConsoles.length.toString());
});
createWebSockets();(这来自我维护这里的一个开源项目,所以如果您给出了一个答案,请注意它可能会在那里使用)
发布于 2019-08-21 06:39:03
在节点中,抛出错误而不捕获它将终止进程。这似乎是主要的错误处理模式(基于这,您似乎依赖于pm2来重新启动进程)。这意味着,即使是一个客户端也会触发代码中的错误,每个客户端都将被断开连接。解决这一问题需要重写大量代码以正确处理异步错误--例如,如果DB查询错误,createWebSockets返回承诺并拒绝该承诺,然后在/hosts/reload/中处理该错误并将错误传递给客户端。
createWebSockets()在循环中调用fs.readFileSync --如果有大量的主机,这可能导致整个服务器在每个主机重新读取证书/键时冻结。您可以考虑已经读取的缓存键(所以您不只是在每次重新加载时重新读取相同的键)和/或使用异步fs.readFile API。(如果您熟悉承诺,可以使用异步/等待和fs.promises.readFile来保持类似的代码结构)
/hosts/reload/听起来似乎是为了重新加载主机列表而多次运行,但是createWebSockets()处理重新加载的方式与最初的设置没有任何不同。这有几个问题:
hostDetails仍将包括有关主机的信息。events?type=operation websockets,但随后抛出了它们的句柄。因此,不仅错误和主机删除没有处理,而且还将为上一次重新加载期间已经存在的主机创建一个新的websocket。这意味着将有多个websockets连接到单个主机,其侦听器仍在转发消息。这不仅是内存泄漏,还意味着将有重复的operationUpdate消息被发送到operationSocket上。if(lxdConsoles[indentifier] == undefined) {
和
res.send(lxdConsoles.length.toString());
看起来,lxdConsoles.length被用作唯一标识符(这里) --但如果两个客户端同时尝试并打开控制台,这并不能保证工作。可能会发生这样的情况:它们都请求POST /terminals,获得相同的id,然后都尝试将相同的id传递回websocket。
一个简单的解决方法是将lxdConsoles转换为对象而不是数组,让POST /terminals处理程序只返回一个uuid而不是lxdConsoles.length。虽然服务器根本没有对令牌进行身份验证,但我们最好让客户机生成uuid。
res.on('data', d => {不是从HTTP请求读取响应数据的正确方式。data事件表示已经接收到单个数据块,而不是完整的消息。从技术上讲,这段代码需要一个缓冲区,存储所有数据块,然后在end事件上处理整个缓冲区。
大致如下:
let body = '';
res.on('data', d => body += d.toString('utf-8'));
res.on('end', () => { /* do something with the complete "body" */ });或者使用一个更简单的HTTP库,比如请求-承诺
基于LXD文档,返回的消息保证很小.但这段代码仍然很脆弱(如果LXD曾经更改以返回更多数据,或者如果URL更改为返回更多数据的内容,这可能会中断)。
try {
const buf = Buffer.from(data);
data = buf.toString();
socket.emit("data", data);
} catch (ex) {
// The WebSocket is not open, ignore
}这是关于: try/catch被用来在没有记录错误的情况下丢弃错误(因此用户可能有神秘的行为,甚至看不到日志中的原因),而且注释读起来很奇怪:这里有两个websockets (客户机socket和服务器lxdWs,所以注释应该指定哪个错误,并描述错误不需要处理的原因)
socket.on('close', function(indentifier) {
setTimeout(() => {
// ...
}, 100);
});目前还不清楚setTimeout在这里做什么--如果它需要用于特定的时间安排,那么发表评论将是个好主意。否则你可以移除它。
小风格的东西:
indentifier拼错了。https://codereview.stackexchange.com/questions/217391
复制相似问题