146 lines
3.6 KiB
JavaScript
146 lines
3.6 KiB
JavaScript
const crypto = require('crypto');
|
|
const http = require('http');
|
|
|
|
const PORT = Number(process.env.CRANK_E2E_STREAM_FIXTURE_PORT || 3310);
|
|
|
|
function writeJson(response, status, payload) {
|
|
response.writeHead(status, {
|
|
'Content-Type': 'application/json; charset=utf-8',
|
|
'Cache-Control': 'no-store',
|
|
});
|
|
response.end(JSON.stringify(payload));
|
|
}
|
|
|
|
const server = http.createServer((request, response) => {
|
|
if (request.url === '/health') {
|
|
writeJson(response, 200, { ok: true });
|
|
return;
|
|
}
|
|
|
|
if (request.url === '/sse/logs') {
|
|
response.writeHead(200, {
|
|
'Content-Type': 'text/event-stream; charset=utf-8',
|
|
'Cache-Control': 'no-cache',
|
|
Connection: 'keep-alive',
|
|
});
|
|
|
|
const events = [
|
|
{ level: 'info', message: 'billing started', cursor: 'c1' },
|
|
{ level: 'warn', message: 'cache warmup slow', cursor: 'c2' },
|
|
{ level: 'error', message: 'invoice timeout', cursor: 'c3' },
|
|
];
|
|
|
|
let index = 0;
|
|
const timer = setInterval(() => {
|
|
if (index >= events.length) {
|
|
response.write('event: done\n');
|
|
response.write('data: {"done":true}\n\n');
|
|
clearInterval(timer);
|
|
response.end();
|
|
return;
|
|
}
|
|
|
|
response.write('event: message\n');
|
|
response.write(`data: ${JSON.stringify(events[index])}\n\n`);
|
|
index += 1;
|
|
}, 150);
|
|
|
|
request.on('close', () => {
|
|
clearInterval(timer);
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
if (request.url === '/snapshot/metrics') {
|
|
writeJson(response, 200, {
|
|
summary: {
|
|
service: 'billing',
|
|
error_rate: 0.12,
|
|
},
|
|
items: [
|
|
{ timestamp: '2026-04-06T10:00:00Z', cpu: 0.41, memory: 0.67 },
|
|
{ timestamp: '2026-04-06T10:00:05Z', cpu: 0.39, memory: 0.65 },
|
|
],
|
|
done: true,
|
|
});
|
|
return;
|
|
}
|
|
|
|
writeJson(response, 404, { error: 'not_found' });
|
|
});
|
|
|
|
function writeWebSocketFrame(socket, payload) {
|
|
const body = Buffer.from(payload, 'utf8');
|
|
const header = [];
|
|
header.push(0x81);
|
|
if (body.length < 126) {
|
|
header.push(body.length);
|
|
} else if (body.length < 65536) {
|
|
header.push(126, (body.length >> 8) & 0xff, body.length & 0xff);
|
|
} else {
|
|
throw new Error('fixture payload is unexpectedly large');
|
|
}
|
|
|
|
socket.write(Buffer.concat([Buffer.from(header), body]));
|
|
}
|
|
|
|
function acceptWebSocket(request, socket) {
|
|
const key = request.headers['sec-websocket-key'];
|
|
if (!key) {
|
|
socket.destroy();
|
|
return;
|
|
}
|
|
|
|
const accept = crypto
|
|
.createHash('sha1')
|
|
.update(`${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`)
|
|
.digest('base64');
|
|
|
|
socket.write(
|
|
[
|
|
'HTTP/1.1 101 Switching Protocols',
|
|
'Upgrade: websocket',
|
|
'Connection: Upgrade',
|
|
`Sec-WebSocket-Accept: ${accept}`,
|
|
'\r\n',
|
|
].join('\r\n'),
|
|
);
|
|
|
|
const messages = [
|
|
{ type: 'tick', seq: 1, value: 101 },
|
|
{ type: 'tick', seq: 2, value: 102 },
|
|
{ type: 'tick', seq: 3, value: 103 },
|
|
];
|
|
|
|
let index = 0;
|
|
const timer = setInterval(() => {
|
|
if (index >= messages.length) {
|
|
clearInterval(timer);
|
|
socket.end();
|
|
return;
|
|
}
|
|
writeWebSocketFrame(socket, JSON.stringify(messages[index]));
|
|
index += 1;
|
|
}, 150);
|
|
|
|
socket.on('close', () => clearInterval(timer));
|
|
socket.on('end', () => clearInterval(timer));
|
|
socket.on('error', () => clearInterval(timer));
|
|
}
|
|
|
|
server.on('upgrade', (request, socket, head) => {
|
|
if (request.url !== '/events') {
|
|
socket.destroy();
|
|
return;
|
|
}
|
|
if (head && head.length) {
|
|
socket.unshift(head);
|
|
}
|
|
acceptWebSocket(request, socket);
|
|
});
|
|
|
|
server.listen(PORT, '127.0.0.1', () => {
|
|
console.log(`Streaming fixtures listening on http://127.0.0.1:${PORT}`);
|
|
});
|