i’m writing an express middleware that know how to respond to many endpoints, most of them pure http, but some of them are special that require bi-directional communication (websockets).
normally, when you have access to the http server you can achive this with the following:
import http from 'http';
const server = http.createServer(app);
app.listen = function serverListen(...args) {
return server.listen(...args);
};
const wss = new WebSocketServer({ server: server });
wss.on('connection', (ws) => {
ws.on('message', (message) => {
console.log('Received:', message);
ws.send('Message received: ' + message);
});
});
server.on('upgrade', function upgrade(request, socket, head) {
wss.handleUpgrade(request, socket, head, function done(ws) {
wss.emit('connection', ws, request);
});
});
the above implementation works as expected. the upgrade
event provide us request
, socket
, and head
parameters which are crucial in order to call correctly to wss.handleUpgrade
.
the things is, that i don’t have access to the underlying http server, and i couldn’t find any implementation on the internet that extract request, socket, head
directly from express request
and response
objects passed to the middlewares. (before anyone suggest, express-ws also relies on http server).
currently, i was able to correctly extract request
and socket
parameter from express, but i am missing the head
parameter.
wss.handleUpgrade(req, req.socket, ???, (websocket) => {
wss.emit('connection', websocket, req);
});
as you can see: we get request
from express.request
, socket
from express.request.socket
but head
is missing.
with the help of AI and MDN docs i was able to get one step closer in re-constructing the head
parameter.
here the code:
if (path === '/transaction/.websocket') {
// this is a transaction request
console.log('Transaction request');
// Check if the request is a WebSocket upgrade request
if (!req.headers.upgrade || req.headers.upgrade.toLowerCase() !== 'websocket') {
return res.status(400).send('Invalid WebSocket upgrade request');
}
// Get the value of the 'Sec-WebSocket-Key' header
const secWebSocketKey = req.headers['sec-websocket-key'];
if (!secWebSocketKey) {
return res.status(400).send('Missing Sec-WebSocket-Key header');
}
// Construct the `head` buffer according to the WebSocket protocol
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-WebSocket-Accept
const secWebSocketAccept = crypto
.createHash('sha1')
.update(secWebSocketKey + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11=')
.digest('base64');
const head = Buffer.from(
`HTTP/1.1 101 Switching ProtocolsrnUpgrade: websocketrnConnection: UpgradernSec-WebSocket-Accept: ${secWebSocketAccept}rnrn`,
);
// Handle the WebSocket upgrade
wss.handleUpgrade(req, req.socket, head, (websocket) => {
wss.emit('connection', websocket, req);
});
}
but not i get on the server response Uncaught RangeError: Invalid WebSocket frame: RSV1 must be clear
.
this is where i was able to get so far. any help would be appreciated.
relevant:
- NodeJS, WebSockets, upgrade head