UNPKG

bigbasealpha

Version:

Enterprise-Grade NoSQL Database System with Modular Logger & Offline HSM Security - Complete database platform with professional text-based logging, encryption, caching, indexing, JWT authentication, auto-generated REST API, real-time dashboard, and maste

725 lines (623 loc) 19.1 kB
import { EventEmitter } from 'events'; import http from 'http'; import https from 'https'; import { URL } from 'url'; import crypto from 'crypto'; /** * BigBaseAlpha API Gateway & Microservices Engine * Advanced API gateway with load balancing, authentication, rate limiting */ export class APIGateway extends EventEmitter { constructor(config = {}) { super(); this.config = { port: config.gatewayPort || 3001, httpsPort: config.httpsPort || 3443, enableHTTPS: config.enableHTTPS || false, enableLoadBalancing: config.enableLoadBalancing !== false, enableRateLimiting: config.enableRateLimiting !== false, enableCaching: config.enableCaching !== false, enableAuth: config.enableAuth !== false, defaultRateLimit: config.defaultRateLimit || 100, // per minute cacheTimeout: config.cacheTimeout || 300000, // 5 minutes healthCheckInterval: config.healthCheckInterval || 30000, requestTimeout: config.requestTimeout || 30000, ...config }; this.database = null; // HTTP servers this.httpServer = null; this.httpsServer = null; // Service registry this.services = new Map(); this.routes = new Map(); this.middlewares = []; // Load balancing this.loadBalancers = new Map(); // Rate limiting this.rateLimits = new Map(); // Caching this.cache = new Map(); // Authentication this.authTokens = new Map(); this.apiKeys = new Map(); // Health monitoring this.healthChecks = new Map(); // Statistics this.stats = { totalRequests: 0, successfulRequests: 0, failedRequests: 0, averageResponseTime: 0, cachedResponses: 0, rateLimitedRequests: 0, startTime: null }; this.isInitialized = false; } /** * Initialize API Gateway */ async init() { try { this.stats.startTime = new Date(); // Create HTTP server this.httpServer = http.createServer((req, res) => { this._handleRequest(req, res); }); // Create HTTPS server if enabled if (this.config.enableHTTPS) { // Note: In production, load SSL certificates this.httpsServer = https.createServer({ // key: fs.readFileSync('private-key.pem'), // cert: fs.readFileSync('certificate.pem') }, (req, res) => { this._handleRequest(req, res); }); } // Initialize built-in services this._initializeBuiltInServices(); // Start health checks this._startHealthChecks(); // Start servers await this._startServers(); this.isInitialized = true; console.log(`✅ API Gateway initialized on port ${this.config.port}`); this.emit('initialized'); } catch (error) { throw new Error(`Failed to initialize API Gateway: ${error.message}`); } } /** * Set database instance */ setDatabase(database) { this.database = database; } /** * Register microservice */ registerService(name, config) { const serviceId = this._generateId(); const service = { id: serviceId, name, config: { url: config.url, healthCheck: config.healthCheck || `${config.url}/health`, version: config.version || '1.0.0', timeout: config.timeout || this.config.requestTimeout, retries: config.retries || 3, weight: config.weight || 1, ...config }, status: 'unknown', lastHealthCheck: null, registeredAt: new Date(), stats: { requests: 0, errors: 0, averageResponseTime: 0 } }; this.services.set(serviceId, service); // Create load balancer if multiple instances if (!this.loadBalancers.has(name)) { this.loadBalancers.set(name, { algorithm: 'round-robin', services: [], currentIndex: 0 }); } this.loadBalancers.get(name).services.push(serviceId); console.log(`🔗 Service registered: ${name} (${serviceId})`); this.emit('serviceRegistered', { name, serviceId, service }); return serviceId; } /** * Register API route */ registerRoute(path, config) { const route = { path, method: config.method || 'GET', service: config.service, target: config.target, middleware: config.middleware || [], rateLimit: config.rateLimit || this.config.defaultRateLimit, auth: config.auth || false, cache: config.cache || false, timeout: config.timeout || this.config.requestTimeout, createdAt: new Date() }; const routeKey = `${route.method}:${path}`; this.routes.set(routeKey, route); console.log(`📍 Route registered: ${route.method} ${path} → ${config.service || config.target}`); return routeKey; } /** * Add middleware */ addMiddleware(middleware) { this.middlewares.push(middleware); } /** * Handle incoming request */ async _handleRequest(req, res) { const startTime = Date.now(); const requestId = this._generateId(); // CORS headers res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-Key'); res.setHeader('X-Request-ID', requestId); if (req.method === 'OPTIONS') { res.writeHead(200); res.end(); return; } this.stats.totalRequests++; try { // Find matching route const route = this._findRoute(req.method, req.url); if (!route) { return this._sendError(res, 404, 'Route not found'); } // Apply middlewares for (const middleware of this.middlewares.concat(route.middleware)) { const result = await middleware(req, res); if (result === false) return; // Middleware blocked the request } // Check authentication if (route.auth && !await this._checkAuth(req)) { return this._sendError(res, 401, 'Authentication required'); } // Check rate limiting if (this.config.enableRateLimiting && !this._checkRateLimit(req, route)) { this.stats.rateLimitedRequests++; return this._sendError(res, 429, 'Rate limit exceeded'); } // Check cache if (this.config.enableCaching && route.cache && req.method === 'GET') { const cached = this._getFromCache(req.url); if (cached) { this.stats.cachedResponses++; res.writeHead(200, { 'Content-Type': 'application/json', 'X-Cache': 'HIT' }); res.end(JSON.stringify(cached)); return; } } // Proxy to target service const response = await this._proxyRequest(req, route); // Cache response if enabled if (this.config.enableCaching && route.cache && req.method === 'GET' && response.statusCode === 200) { this._saveToCache(req.url, response.data); } // Send response res.writeHead(response.statusCode, response.headers); res.end(response.body); this.stats.successfulRequests++; this._updateResponseTime(Date.now() - startTime); } catch (error) { console.error(`Gateway request error (${requestId}):`, error); this.stats.failedRequests++; this._sendError(res, 500, 'Internal gateway error'); } } /** * Find matching route */ _findRoute(method, url) { const urlObj = new URL(url, 'http://localhost'); const pathname = urlObj.pathname; // Exact match first const exactKey = `${method}:${pathname}`; if (this.routes.has(exactKey)) { return this.routes.get(exactKey); } // Pattern matching for (const [routeKey, route] of this.routes) { const [routeMethod, routePath] = routeKey.split(':'); if (routeMethod === method && this._matchPath(routePath, pathname)) { return route; } } return null; } /** * Match path patterns */ _matchPath(pattern, path) { // Convert pattern to regex (simple implementation) const regexPattern = pattern .replace(/\*/g, '.*') .replace(/:\w+/g, '[^/]+'); const regex = new RegExp(`^${regexPattern}$`); return regex.test(path); } /** * Proxy request to target service */ async _proxyRequest(req, route) { return new Promise((resolve, reject) => { let targetUrl; if (route.service) { // Load balance to service const serviceId = this._selectService(route.service); if (!serviceId) { return reject(new Error('No healthy service available')); } const service = this.services.get(serviceId); targetUrl = service.config.url; } else { targetUrl = route.target; } const url = new URL(req.url, targetUrl); const options = { hostname: url.hostname, port: url.port, path: url.pathname + url.search, method: req.method, headers: { ...req.headers }, timeout: route.timeout }; // Remove host header to avoid conflicts delete options.headers.host; const protocol = url.protocol === 'https:' ? https : http; const proxyReq = protocol.request(options, (proxyRes) => { let body = ''; proxyRes.on('data', chunk => { body += chunk; }); proxyRes.on('end', () => { resolve({ statusCode: proxyRes.statusCode, headers: proxyRes.headers, body, data: this._tryParseJSON(body) }); }); }); proxyReq.on('error', reject); proxyReq.on('timeout', () => { proxyReq.destroy(); reject(new Error('Request timeout')); }); // Forward request body if (req.method !== 'GET' && req.method !== 'HEAD') { let body = ''; req.on('data', chunk => { body += chunk; }); req.on('end', () => { proxyReq.write(body); proxyReq.end(); }); } else { proxyReq.end(); } }); } /** * Select service using load balancing */ _selectService(serviceName) { const balancer = this.loadBalancers.get(serviceName); if (!balancer || balancer.services.length === 0) { return null; } // Round-robin algorithm const serviceId = balancer.services[balancer.currentIndex]; balancer.currentIndex = (balancer.currentIndex + 1) % balancer.services.length; // Check if service is healthy const service = this.services.get(serviceId); if (!service || service.status !== 'healthy') { // Try next service const nextIndex = balancer.currentIndex; for (let i = 0; i < balancer.services.length; i++) { const nextServiceId = balancer.services[(nextIndex + i) % balancer.services.length]; const nextService = this.services.get(nextServiceId); if (nextService && nextService.status === 'healthy') { return nextServiceId; } } return null; // No healthy services } return serviceId; } /** * Check authentication */ async _checkAuth(req) { const authHeader = req.headers.authorization; const apiKey = req.headers['x-api-key']; if (authHeader && authHeader.startsWith('Bearer ')) { const token = authHeader.substring(7); return this.authTokens.has(token); } if (apiKey) { return this.apiKeys.has(apiKey); } return false; } /** * Check rate limiting */ _checkRateLimit(req, route) { const clientId = req.headers['x-forwarded-for'] || req.connection.remoteAddress; const key = `${clientId}:${route.path}`; const now = Date.now(); const windowStart = Math.floor(now / 60000) * 60000; // 1-minute window if (!this.rateLimits.has(key)) { this.rateLimits.set(key, { count: 0, windowStart }); } const limit = this.rateLimits.get(key); // Reset if new window if (limit.windowStart !== windowStart) { limit.count = 0; limit.windowStart = windowStart; } limit.count++; return limit.count <= route.rateLimit; } /** * Cache operations */ _getFromCache(key) { const cached = this.cache.get(key); if (cached && Date.now() < cached.expiry) { return cached.data; } this.cache.delete(key); return null; } _saveToCache(key, data) { this.cache.set(key, { data, expiry: Date.now() + this.config.cacheTimeout }); } /** * Initialize built-in services */ _initializeBuiltInServices() { // Register database service routes this.registerRoute('/api/database/*', { method: 'GET', target: 'http://localhost:3000', auth: false, cache: true, rateLimit: 200 }); this.registerRoute('/api/database/*', { method: 'POST', target: 'http://localhost:3000', auth: true, rateLimit: 100 }); // Health check route this.registerRoute('/health', { method: 'GET', target: 'internal', auth: false, cache: false }); // Gateway stats route this.registerRoute('/gateway/stats', { method: 'GET', target: 'internal', auth: true, cache: false }); } /** * Start health checks */ _startHealthChecks() { setInterval(() => { this._performHealthChecks(); }, this.config.healthCheckInterval); } /** * Perform health checks on all services */ async _performHealthChecks() { for (const [serviceId, service] of this.services) { try { const response = await this._makeHealthCheckRequest(service.config.healthCheck); service.status = response.statusCode === 200 ? 'healthy' : 'unhealthy'; } catch (error) { service.status = 'unhealthy'; } service.lastHealthCheck = new Date(); } } /** * Make health check request */ _makeHealthCheckRequest(url) { return new Promise((resolve, reject) => { const protocol = url.startsWith('https') ? https : http; const req = protocol.get(url, resolve); req.on('error', reject); req.setTimeout(5000, () => { req.destroy(); reject(new Error('Health check timeout')); }); }); } /** * Start HTTP/HTTPS servers */ async _startServers() { return new Promise((resolve, reject) => { this.httpServer.listen(this.config.port, (err) => { if (err) reject(err); else { console.log(`🚀 API Gateway HTTP server running on port ${this.config.port}`); if (this.config.enableHTTPS && this.httpsServer) { this.httpsServer.listen(this.config.httpsPort, (err) => { if (err) reject(err); else { console.log(`🔒 API Gateway HTTPS server running on port ${this.config.httpsPort}`); resolve(); } }); } else { resolve(); } } }); }); } /** * Send error response */ _sendError(res, statusCode, message) { res.writeHead(statusCode, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: message, statusCode, timestamp: new Date().toISOString() })); } /** * Update response time statistics */ _updateResponseTime(responseTime) { const currentAvg = this.stats.averageResponseTime; const totalRequests = this.stats.totalRequests; this.stats.averageResponseTime = (currentAvg * (totalRequests - 1) + responseTime) / totalRequests; } /** * Try to parse JSON */ _tryParseJSON(str) { try { return JSON.parse(str); } catch { return null; } } /** * Generate API key */ generateAPIKey(name, permissions = []) { const apiKey = crypto.randomBytes(32).toString('hex'); this.apiKeys.set(apiKey, { name, permissions, createdAt: new Date(), lastUsed: null }); return apiKey; } /** * Generate auth token */ generateAuthToken(userId, expiresIn = 3600000) { const token = crypto.randomBytes(32).toString('base64'); this.authTokens.set(token, { userId, expiresAt: Date.now() + expiresIn, createdAt: new Date() }); return token; } /** * Get gateway statistics */ getStats() { return { ...this.stats, services: { total: this.services.size, healthy: Array.from(this.services.values()).filter(s => s.status === 'healthy').length, unhealthy: Array.from(this.services.values()).filter(s => s.status === 'unhealthy').length }, routes: { total: this.routes.size }, cache: { size: this.cache.size, hitRate: this.stats.cachedResponses / Math.max(this.stats.totalRequests, 1) * 100 }, loadBalancers: { total: this.loadBalancers.size } }; } /** * Get registered services */ getServices() { return Array.from(this.services.values()); } /** * Get registered routes */ getRoutes() { return Array.from(this.routes.values()); } /** * Generate unique ID */ _generateId() { return `gateway_${Date.now()}_${crypto.randomBytes(4).toString('hex')}`; } /** * Close API Gateway */ async close() { if (this.httpServer) { this.httpServer.close(); } if (this.httpsServer) { this.httpsServer.close(); } console.log('✅ API Gateway closed'); } /** * Shutdown API Gateway */ async shutdown() { console.log('🔄 Shutting down API Gateway...'); // Close servers if (this.httpServer) { this.httpServer.close(); } if (this.httpsServer) { this.httpsServer.close(); } // Clear data structures this.services.clear(); this.routes.clear(); this.apiKeys.clear(); this.rateLimits.clear(); this.cache.clear(); this.loadBalancers.clear(); console.log('✅ API Gateway shutdown complete'); } } export default APIGateway;