Files

153 lines
4.8 KiB
JavaScript

import http from 'http';
import process from 'process';
import { config } from '../../internal/config/config.js';
import { logger } from '../../internal/logger/logger.js';
import { RateLimiter } from '../../internal/middleware/ratelimit.js';
import { Store } from '../../internal/session/store.js';
import { Hub } from '../../internal/ws/hub.js';
import { setupWebSocket } from '../../internal/ws/handler.js';
import { startTimer } from '../../internal/timer/timer.js';
import { initializeDatabase, logSessionCreated } from '../../internal/db/db.js';
// Bootstrapping log
logger.info('server_initiating', {
addr: config.serverAddr,
environment: config.environment
});
const store = new Store();
const hub = new Hub(store);
const limiter = new RateLimiter(config.rateLimitPerMin, 60000); // 1 minute window
// Configure standard HTTP server to manage REST actions
const server = http.createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
// Endpoint: GET /health
if (url.pathname === '/health' && req.method === 'GET') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
status: 'ok',
active_sessions: store.getActiveCount(),
connected_clients: hub.getConnectedClientsCount()
}));
return;
}
// Endpoint: POST /sessions
// Pre-creates call session mapping. Authenticated using X-API-Key header.
if (url.pathname === '/sessions' && req.method === 'POST') {
const apiKeyHeader = req.headers['x-api-key'];
if (!apiKeyHeader || apiKeyHeader !== config.apiKey) {
logger.warn('unauthorized_session_creation_attempt', {
remote_ip: req.socket.remoteAddress
});
res.writeHead(401, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'unauthorized' }));
return;
}
let body = '';
req.on('data', chunk => {
body += chunk;
});
req.on('end', async () => {
let params;
try {
params = JSON.parse(body);
} catch (err) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'invalid_json' }));
return;
}
const { ride_id, driver_id, passenger_id, driver_ip, passenger_ip } = params;
if (!ride_id || !driver_id || !passenger_id) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'missing_parameters' }));
return;
}
let sess;
try {
// Enforce hard 120s maximum call duration
sess = store.createSession(ride_id, driver_id, passenger_id, 120000, driver_ip, passenger_ip);
} catch (err) {
logger.warn('session_creation_failed', { ride_id, error: err.message });
res.writeHead(409, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'session_exists' }));
return;
}
// Start 120s timeout countdown
sess.timer = startTimer(120000, () => {
hub.forceEndSession(sess.sessionID, 'max_duration_reached');
});
// Log session creation to database
await logSessionCreated(sess.sessionID, sess.rideID, sess.driverID, sess.passengerID, sess.driverIP, sess.passengerIP);
logger.info('session_created', {
session_id: sess.sessionID,
ride_id: sess.rideID,
driver_id: sess.driverID,
passenger_id: sess.passengerID,
driver_ip: sess.driverIP,
passenger_ip: sess.passengerIP
});
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
session_id: sess.sessionID,
ride_id: sess.rideID,
expires_in: 120
}));
});
return;
}
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
});
// Bind WebSocket upgrades interceptor
setupWebSocket(server, hub, limiter);
// Resolve address binding parts
const [host, portStr] = config.serverAddr.split(':');
const port = parseInt(portStr, 10);
// Initialize database first then start server
initializeDatabase().then(() => {
server.listen(port, host, () => {
logger.info('server_running', { addr: config.serverAddr });
});
});
// Graceful exit handling
function shutdown(signal) {
logger.info('server_stopping', { signal });
// Close HTTP server to stop accepting new traffic
server.close((err) => {
if (err) {
logger.error('server_close_error', { error: err.message });
}
logger.info('server_stopped_cleanly');
process.exit(0);
});
// Warn active sockets and terminate connections
hub.shutdownGracefully();
// Force close after 10s maximum timeout
setTimeout(() => {
logger.warn('server_shutdown_timeout_force_exit');
process.exit(1);
}, 10000).unref();
}
process.on('SIGINT', () => shutdown('SIGINT'));
process.on('SIGTERM', () => shutdown('SIGTERM'));