使用服务器发送的事件:如何存储与客户端的连接?
Using server-sent events : how to store a connection to a client?
我在,
我已经在 react-native 应用程序和 NodeJS 服务器之间建立了 SSE 连接。
我遵循了一些指导原则在客户端进行设置,polyfilling EventSource
等等,这非常简单。
但是在服务器端,我在寻找如何存储与客户端的连接时遇到了一些问题。我选择将响应存储在 global
对象中,但我觉得这不是正确的做法。有人可以建议吗?
下面是我的代码
const SSE_RESPONSE_HEADER = {
'Connection': 'keep-alive',
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'X-Accel-Buffering': 'no'
};
const getUserId = (req, from) => {
try {
// console.log(from, req.body, req.params)
if (!req) return null;
if (Boolean(req.body) && req.body.userId) return req.body.userId;
if (Boolean(req.params) && req.params.userId) return req.params.userId;
return null
} catch (e) {
console.log('getUserId error', e)
return null;
}
}
global.usersStreams = {}
exports.setupStream = (req, res, next) => {
let userId = getUserId(req);
if (!userId) {
next({ message: 'stream.no-user' })
return;
}
// Stores this connection
global.usersStreams[userId] = {
res,
lastInteraction: null,
}
// Writes response header.
res.writeHead(200, SSE_RESPONSE_HEADER);
// Note: Heatbeat for avoidance of client's request timeout of first time (30 sec)
const heartbeat = {type: 'heartbeat'}
res.write(`data: ${JSON.stringify(heartbeat)}\n\n`);
global.usersStreams[userId].lastInteraction = Date.now()
// Interval loop
const maxInterval = 55000;
const interval = 3000;
let intervalId = setInterval(function() {
if (!global.usersStreams[userId]) return;
if (Date.now() - global.usersStreams[userId].lastInteraction < maxInterval) return;
res.write(`data: ${JSON.stringify(heartbeat)}\n\n`);
global.usersStreams[userId].lastInteraction = Date.now()
}, interval);
req.on("close", function() {
let userId = getUserId(req, 'setupStream on close');
// Breaks the interval loop on client disconnected
clearInterval(intervalId);
// Remove from connections
delete global.usersStreams[userId];
});
req.on("end", function() {
let userId = getUserId(req, 'setupStream on end');
clearInterval(intervalId);
delete global.usersStreams[userId];
});
};
exports.sendStream = async (userId, data) => {
if (!userId) return;
if (!global.usersStreams[userId]) return;
if (!data) return;
const { res } = global.usersStreams[userId];
res.write(`data: ${JSON.stringify({ type: 'event', data })}\n\n`);
global.usersStreams[userId].lastInteraction = Date.now();
};
我的第一个技巧是简单地摆脱 global
;在模块的闭包中有一个变量是可以的。您的模块可以封装此 "global" 状态,而无需让所有其他模块全局访问它。
const usersStreams = {};
其次,同一个用户建立多个连接可能并非不可能。我建议,如果您在 userId
上键入这些连接,您应该将这些键的值 userStreams
中作为集合,以便您可以写入多个。或者你需要一个更独特的钥匙。
我在,
我已经在 react-native 应用程序和 NodeJS 服务器之间建立了 SSE 连接。
我遵循了一些指导原则在客户端进行设置,polyfilling EventSource
等等,这非常简单。
但是在服务器端,我在寻找如何存储与客户端的连接时遇到了一些问题。我选择将响应存储在 global
对象中,但我觉得这不是正确的做法。有人可以建议吗?
下面是我的代码
const SSE_RESPONSE_HEADER = {
'Connection': 'keep-alive',
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'X-Accel-Buffering': 'no'
};
const getUserId = (req, from) => {
try {
// console.log(from, req.body, req.params)
if (!req) return null;
if (Boolean(req.body) && req.body.userId) return req.body.userId;
if (Boolean(req.params) && req.params.userId) return req.params.userId;
return null
} catch (e) {
console.log('getUserId error', e)
return null;
}
}
global.usersStreams = {}
exports.setupStream = (req, res, next) => {
let userId = getUserId(req);
if (!userId) {
next({ message: 'stream.no-user' })
return;
}
// Stores this connection
global.usersStreams[userId] = {
res,
lastInteraction: null,
}
// Writes response header.
res.writeHead(200, SSE_RESPONSE_HEADER);
// Note: Heatbeat for avoidance of client's request timeout of first time (30 sec)
const heartbeat = {type: 'heartbeat'}
res.write(`data: ${JSON.stringify(heartbeat)}\n\n`);
global.usersStreams[userId].lastInteraction = Date.now()
// Interval loop
const maxInterval = 55000;
const interval = 3000;
let intervalId = setInterval(function() {
if (!global.usersStreams[userId]) return;
if (Date.now() - global.usersStreams[userId].lastInteraction < maxInterval) return;
res.write(`data: ${JSON.stringify(heartbeat)}\n\n`);
global.usersStreams[userId].lastInteraction = Date.now()
}, interval);
req.on("close", function() {
let userId = getUserId(req, 'setupStream on close');
// Breaks the interval loop on client disconnected
clearInterval(intervalId);
// Remove from connections
delete global.usersStreams[userId];
});
req.on("end", function() {
let userId = getUserId(req, 'setupStream on end');
clearInterval(intervalId);
delete global.usersStreams[userId];
});
};
exports.sendStream = async (userId, data) => {
if (!userId) return;
if (!global.usersStreams[userId]) return;
if (!data) return;
const { res } = global.usersStreams[userId];
res.write(`data: ${JSON.stringify({ type: 'event', data })}\n\n`);
global.usersStreams[userId].lastInteraction = Date.now();
};
我的第一个技巧是简单地摆脱 global
;在模块的闭包中有一个变量是可以的。您的模块可以封装此 "global" 状态,而无需让所有其他模块全局访问它。
const usersStreams = {};
其次,同一个用户建立多个连接可能并非不可能。我建议,如果您在 userId
上键入这些连接,您应该将这些键的值 userStreams
中作为集合,以便您可以写入多个。或者你需要一个更独特的钥匙。