995 líneas
28 KiB
TypeScript
995 líneas
28 KiB
TypeScript
/**
|
|
* HTTP Server for CUDA Quantum MCP
|
|
* Provides REST API endpoints and Server-Sent Events for quantum computing operations
|
|
*/
|
|
|
|
import express, { Request, Response } from 'express';
|
|
import cors from 'cors';
|
|
import helmet from 'helmet';
|
|
import swaggerJsdoc from 'swagger-jsdoc';
|
|
import swaggerUiExpress from 'swagger-ui-express';
|
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
import { createServer } from 'http';
|
|
import { initializePythonBridge, getPythonBridge } from './bridge/python-bridge.js';
|
|
import { Logger, LogLevel } from './utils/logger.js';
|
|
|
|
/**
|
|
* HTTP Server configuration
|
|
*/
|
|
interface HttpServerConfig {
|
|
port: number;
|
|
host: string;
|
|
corsOrigins: string[];
|
|
logLevel: LogLevel;
|
|
pythonPath?: string;
|
|
}
|
|
|
|
/**
|
|
* SSE Client interface
|
|
*/
|
|
interface SSEClient {
|
|
id: string;
|
|
response: Response;
|
|
subscriptions: Set<string>;
|
|
}
|
|
|
|
/**
|
|
* WebSocket Client interface
|
|
*/
|
|
interface WSClient {
|
|
id: string;
|
|
ws: WebSocket;
|
|
subscriptions: Set<string>;
|
|
}
|
|
|
|
/**
|
|
* Swagger API Documentation Configuration
|
|
*/
|
|
const swaggerOptions = {
|
|
definition: {
|
|
openapi: '3.0.0',
|
|
info: {
|
|
title: 'CUDA Quantum MCP API',
|
|
version: '1.0.0',
|
|
description: 'RESTful API for CUDA Quantum operations with GPU acceleration',
|
|
contact: {
|
|
name: 'MCP Quantum Team',
|
|
email: 'quantum@mcp.dev'
|
|
},
|
|
license: {
|
|
name: 'MIT',
|
|
url: 'https://opensource.org/licenses/MIT'
|
|
}
|
|
},
|
|
servers: [
|
|
{
|
|
url: 'http://localhost:3000',
|
|
description: 'Development server'
|
|
}
|
|
],
|
|
paths: {
|
|
'/health': {
|
|
get: {
|
|
summary: 'Health check endpoint',
|
|
tags: ['System'],
|
|
responses: {
|
|
'200': {
|
|
description: 'Server is healthy',
|
|
content: {
|
|
'application/json': {
|
|
schema: { $ref: '#/components/schemas/ApiResponse' }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
'/api/kernels': {
|
|
post: {
|
|
summary: 'Create a new quantum kernel',
|
|
tags: ['Quantum Kernels'],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': {
|
|
schema: { $ref: '#/components/schemas/QuantumKernel' }
|
|
}
|
|
}
|
|
},
|
|
responses: {
|
|
'200': { description: 'Kernel created successfully' },
|
|
'400': { description: 'Invalid request parameters' }
|
|
}
|
|
},
|
|
get: {
|
|
summary: 'List all quantum kernels',
|
|
tags: ['Quantum Kernels'],
|
|
responses: {
|
|
'200': { description: 'List of kernels retrieved successfully' }
|
|
}
|
|
}
|
|
},
|
|
'/api/gates': {
|
|
post: {
|
|
summary: 'Apply a quantum gate to a kernel',
|
|
tags: ['Quantum Gates'],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': {
|
|
schema: { $ref: '#/components/schemas/QuantumGate' }
|
|
}
|
|
}
|
|
},
|
|
responses: {
|
|
'200': { description: 'Gate applied successfully' }
|
|
}
|
|
}
|
|
},
|
|
'/api/sample': {
|
|
post: {
|
|
summary: 'Sample quantum circuit measurements',
|
|
tags: ['Quantum Execution'],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': {
|
|
schema: { $ref: '#/components/schemas/SampleRequest' }
|
|
}
|
|
}
|
|
},
|
|
responses: {
|
|
'200': { description: 'Sampling completed successfully' }
|
|
}
|
|
}
|
|
},
|
|
'/api/observe': {
|
|
post: {
|
|
summary: 'Calculate Hamiltonian expectation value',
|
|
tags: ['Quantum Execution'],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': {
|
|
schema: { $ref: '#/components/schemas/ObserveRequest' }
|
|
}
|
|
}
|
|
},
|
|
responses: {
|
|
'200': { description: 'Expectation value calculated successfully' }
|
|
}
|
|
}
|
|
},
|
|
'/api/state/{kernelName}': {
|
|
get: {
|
|
summary: 'Get quantum state vector',
|
|
tags: ['Quantum Execution'],
|
|
parameters: [
|
|
{
|
|
in: 'path',
|
|
name: 'kernelName',
|
|
required: true,
|
|
schema: { type: 'string' }
|
|
}
|
|
],
|
|
responses: {
|
|
'200': { description: 'State vector retrieved successfully' }
|
|
}
|
|
}
|
|
},
|
|
'/api/targets': {
|
|
get: {
|
|
summary: 'List available quantum targets',
|
|
tags: ['Quantum Backends'],
|
|
responses: {
|
|
'200': { description: 'Available targets retrieved successfully' }
|
|
}
|
|
},
|
|
post: {
|
|
summary: 'Set quantum computing target',
|
|
tags: ['Quantum Backends'],
|
|
requestBody: {
|
|
required: true,
|
|
content: {
|
|
'application/json': {
|
|
schema: { $ref: '#/components/schemas/TargetRequest' }
|
|
}
|
|
}
|
|
},
|
|
responses: {
|
|
'200': { description: 'Target set successfully' }
|
|
}
|
|
}
|
|
},
|
|
'/api/platform': {
|
|
get: {
|
|
summary: 'Get platform information',
|
|
tags: ['System'],
|
|
responses: {
|
|
'200': { description: 'Platform info retrieved successfully' }
|
|
}
|
|
}
|
|
},
|
|
'/api/events': {
|
|
get: {
|
|
summary: 'Server-Sent Events stream',
|
|
tags: ['Real-time'],
|
|
parameters: [
|
|
{
|
|
in: 'query',
|
|
name: 'topics',
|
|
schema: { type: 'string' },
|
|
description: 'Comma-separated list of topics to subscribe to'
|
|
}
|
|
],
|
|
responses: {
|
|
'200': {
|
|
description: 'SSE stream established',
|
|
content: {
|
|
'text/event-stream': {
|
|
schema: { type: 'string' }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
components: {
|
|
schemas: {
|
|
QuantumKernel: {
|
|
type: 'object',
|
|
required: ['name', 'num_qubits'],
|
|
properties: {
|
|
name: {
|
|
type: 'string',
|
|
description: 'Unique kernel identifier'
|
|
},
|
|
num_qubits: {
|
|
type: 'integer',
|
|
minimum: 1,
|
|
maximum: 64,
|
|
description: 'Number of qubits in the circuit'
|
|
},
|
|
parameters: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
name: { type: 'string' },
|
|
type: { type: 'string', enum: ['float', 'int', 'complex', 'angle'] },
|
|
description: { type: 'string' }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
QuantumGate: {
|
|
type: 'object',
|
|
required: ['kernel_name', 'gate_name', 'target_qubits'],
|
|
properties: {
|
|
kernel_name: { type: 'string' },
|
|
gate_name: { type: 'string', enum: ['h', 'x', 'y', 'z', 'cnot', 'rx', 'ry', 'rz', 's', 't'] },
|
|
target_qubits: {
|
|
type: 'array',
|
|
items: { type: 'integer', minimum: 0 }
|
|
},
|
|
control_qubits: {
|
|
type: 'array',
|
|
items: { type: 'integer', minimum: 0 }
|
|
},
|
|
parameters: {
|
|
type: 'array',
|
|
items: { type: 'number' }
|
|
},
|
|
adjoint: { type: 'boolean', default: false }
|
|
}
|
|
},
|
|
SampleRequest: {
|
|
type: 'object',
|
|
required: ['kernel_name'],
|
|
properties: {
|
|
kernel_name: { type: 'string' },
|
|
shots: { type: 'integer', minimum: 1, default: 1000 },
|
|
parameters: { type: 'object' }
|
|
}
|
|
},
|
|
ObserveRequest: {
|
|
type: 'object',
|
|
required: ['kernel_name', 'hamiltonian_terms'],
|
|
properties: {
|
|
kernel_name: { type: 'string' },
|
|
hamiltonian_terms: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
coefficient: { type: 'number' },
|
|
pauli_string: { type: 'string' }
|
|
}
|
|
}
|
|
},
|
|
shots: { type: 'integer', minimum: 1, default: 1000 },
|
|
parameters: { type: 'object' }
|
|
}
|
|
},
|
|
TargetRequest: {
|
|
type: 'object',
|
|
required: ['target'],
|
|
properties: {
|
|
target: { type: 'string' },
|
|
configuration: { type: 'object' }
|
|
}
|
|
},
|
|
ApiResponse: {
|
|
type: 'object',
|
|
properties: {
|
|
success: { type: 'boolean' },
|
|
data: {},
|
|
error: { type: 'string' },
|
|
timestamp: { type: 'string', format: 'date-time' }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
apis: [] // We define paths directly in the definition above
|
|
};
|
|
|
|
export class CudaQuantumHttpServer {
|
|
private app: express.Application;
|
|
private server!: any;
|
|
private wsServer!: WebSocketServer;
|
|
private logger: Logger;
|
|
private sseClients: Map<string, SSEClient>;
|
|
private wsClients: Map<string, WSClient>;
|
|
private config: HttpServerConfig;
|
|
|
|
constructor(config: HttpServerConfig) {
|
|
this.config = config;
|
|
this.app = express();
|
|
this.logger = new Logger('HttpServer', { level: config.logLevel });
|
|
this.sseClients = new Map();
|
|
this.wsClients = new Map();
|
|
|
|
this.setupMiddleware();
|
|
this.setupRoutes();
|
|
this.setupSwagger();
|
|
}
|
|
|
|
/**
|
|
* Setup Express middleware
|
|
*/
|
|
private setupMiddleware(): void {
|
|
// Security middleware
|
|
this.app.use(helmet({
|
|
contentSecurityPolicy: false // Allow Swagger UI
|
|
}));
|
|
|
|
// CORS configuration
|
|
this.app.use(cors({
|
|
origin: this.config.corsOrigins,
|
|
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
credentials: true
|
|
}));
|
|
|
|
// Body parsing
|
|
this.app.use(express.json({ limit: '10mb' }));
|
|
this.app.use(express.urlencoded({ extended: true }));
|
|
|
|
// Request logging
|
|
this.app.use((req, res, next) => {
|
|
this.logger.debug(`${req.method} ${req.path}`, {
|
|
ip: req.ip,
|
|
userAgent: req.get('User-Agent')
|
|
});
|
|
next();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Setup API routes with Swagger documentation
|
|
*/
|
|
private setupRoutes(): void {
|
|
// Health check
|
|
this.app.get('/health', (req, res) => {
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
status: 'healthy',
|
|
timestamp: new Date().toISOString(),
|
|
uptime: process.uptime()
|
|
},
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
});
|
|
|
|
// Quantum Kernels
|
|
this.app.post('/api/kernels', this.handleCreateKernel.bind(this));
|
|
this.app.get('/api/kernels', this.handleListKernels.bind(this));
|
|
|
|
// Quantum Gates
|
|
this.app.post('/api/gates', this.handleApplyGate.bind(this));
|
|
|
|
// Quantum Execution
|
|
this.app.post('/api/sample', this.handleSample.bind(this));
|
|
this.app.post('/api/observe', this.handleObserve.bind(this));
|
|
this.app.get('/api/state/:kernelName', this.handleGetState.bind(this));
|
|
|
|
// Quantum Backends
|
|
this.app.get('/api/targets', this.handleGetTargets.bind(this));
|
|
this.app.post('/api/targets', this.handleSetTarget.bind(this));
|
|
this.app.get('/api/platform', this.handleGetPlatform.bind(this));
|
|
|
|
// Server-Sent Events
|
|
this.app.get('/api/events', this.handleSSE.bind(this));
|
|
}
|
|
|
|
/**
|
|
* Setup Swagger documentation
|
|
*/
|
|
private setupSwagger(): void {
|
|
const specs = swaggerJsdoc(swaggerOptions);
|
|
this.app.use('/api-docs', swaggerUiExpress.serve, swaggerUiExpress.setup(specs, {
|
|
explorer: true,
|
|
customSiteTitle: 'CUDA Quantum MCP API Documentation',
|
|
customfavIcon: '/favicon.ico'
|
|
}));
|
|
|
|
// Serve raw OpenAPI spec
|
|
this.app.get('/api-docs.json', (req, res) => {
|
|
res.json(specs);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handle kernel creation
|
|
*/
|
|
private async handleCreateKernel(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const { name, num_qubits, parameters } = req.body;
|
|
|
|
if (!name || !num_qubits) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Missing required parameters: name, num_qubits',
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
return;
|
|
}
|
|
|
|
const bridge = getPythonBridge();
|
|
const result = await bridge.createKernel(name, num_qubits, parameters);
|
|
|
|
// Broadcast to SSE clients
|
|
this.broadcastSSE('kernel_created', { name, num_qubits, result });
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
this.logger.error('Error creating kernel:', { error: errorMessage, stack: error instanceof Error ? error.stack : undefined });
|
|
res.status(500).json({
|
|
success: false,
|
|
error: errorMessage,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle kernel listing
|
|
*/
|
|
private async handleListKernels(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const bridge = getPythonBridge();
|
|
const result = await bridge.listKernels();
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
this.logger.error('Error listing kernels:', { error: errorMessage, stack: error instanceof Error ? error.stack : undefined });
|
|
res.status(500).json({
|
|
success: false,
|
|
error: errorMessage,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle gate application
|
|
*/
|
|
private async handleApplyGate(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const { kernel_name, gate_name, target_qubits, control_qubits, parameters, adjoint } = req.body;
|
|
|
|
if (!kernel_name || !gate_name || !target_qubits) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Missing required parameters: kernel_name, gate_name, target_qubits',
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
return;
|
|
}
|
|
|
|
const bridge = getPythonBridge();
|
|
const result = await bridge.applyGate(
|
|
kernel_name, gate_name, target_qubits, control_qubits, parameters, adjoint
|
|
);
|
|
|
|
// Broadcast to SSE clients
|
|
this.broadcastSSE('gate_applied', { kernel_name, gate_name, result });
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
} catch (error) {
|
|
this.logger.error('Error applying gate:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Unknown error',
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle quantum sampling
|
|
*/
|
|
private async handleSample(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const { kernel_name, shots = 1000, parameters } = req.body;
|
|
|
|
if (!kernel_name) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Missing required parameter: kernel_name',
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
return;
|
|
}
|
|
|
|
const bridge = getPythonBridge();
|
|
const result = await bridge.sample(kernel_name, shots, parameters);
|
|
|
|
// Broadcast to SSE clients
|
|
this.broadcastSSE('sampling_completed', { kernel_name, shots, result });
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
} catch (error) {
|
|
this.logger.error('Error sampling:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Unknown error',
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle Hamiltonian observation
|
|
*/
|
|
private async handleObserve(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const { kernel_name, hamiltonian_terms, shots = 1000, parameters } = req.body;
|
|
|
|
if (!kernel_name || !hamiltonian_terms) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Missing required parameters: kernel_name, hamiltonian_terms',
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
return;
|
|
}
|
|
|
|
const bridge = getPythonBridge();
|
|
const result = await bridge.observe(kernel_name, hamiltonian_terms, shots, parameters);
|
|
|
|
// Broadcast to SSE clients
|
|
this.broadcastSSE('observation_completed', { kernel_name, result });
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
} catch (error) {
|
|
this.logger.error('Error observing:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Unknown error',
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle state vector retrieval
|
|
*/
|
|
private async handleGetState(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const { kernelName } = req.params;
|
|
const { parameters } = req.query;
|
|
|
|
if (!kernelName) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Missing required parameter: kernelName',
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
return;
|
|
}
|
|
|
|
const bridge = getPythonBridge();
|
|
const result = await bridge.getState(
|
|
kernelName,
|
|
parameters ? JSON.parse(parameters as string) : undefined
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
} catch (error) {
|
|
this.logger.error('Error getting state:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Unknown error',
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle target listing
|
|
*/
|
|
private async handleGetTargets(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const bridge = getPythonBridge();
|
|
const result = await bridge.getAvailableTargets();
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
this.logger.error('Error getting targets:', { error: errorMessage, stack: error instanceof Error ? error.stack : undefined });
|
|
res.status(500).json({
|
|
success: false,
|
|
error: errorMessage,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle target setting
|
|
*/
|
|
private async handleSetTarget(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const { target, configuration } = req.body;
|
|
|
|
if (!target) {
|
|
res.status(400).json({
|
|
success: false,
|
|
error: 'Missing required parameter: target',
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
return;
|
|
}
|
|
|
|
const bridge = getPythonBridge();
|
|
const result = await bridge.setTarget(target, configuration);
|
|
|
|
// Broadcast to SSE clients
|
|
this.broadcastSSE('target_changed', { target, result });
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
} catch (error) {
|
|
this.logger.error('Error setting target:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Unknown error',
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle platform info
|
|
*/
|
|
private async handleGetPlatform(req: Request, res: Response): Promise<void> {
|
|
try {
|
|
const bridge = getPythonBridge();
|
|
const result = await bridge.getPlatformInfo();
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
this.logger.error('Error getting platform info:', { error: errorMessage, stack: error instanceof Error ? error.stack : undefined });
|
|
res.status(500).json({
|
|
success: false,
|
|
error: errorMessage,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle Server-Sent Events
|
|
*/
|
|
private handleSSE(req: Request, res: Response): void {
|
|
const clientId = Math.random().toString(36).substring(2, 15);
|
|
const topics = req.query.topics ? (req.query.topics as string).split(',') : ['all'];
|
|
|
|
// Setup SSE headers
|
|
res.writeHead(200, {
|
|
'Content-Type': 'text/event-stream',
|
|
'Cache-Control': 'no-cache',
|
|
'Connection': 'keep-alive',
|
|
'Access-Control-Allow-Origin': '*',
|
|
'Access-Control-Allow-Headers': 'Cache-Control'
|
|
});
|
|
|
|
// Store client
|
|
const client: SSEClient = {
|
|
id: clientId,
|
|
response: res,
|
|
subscriptions: new Set(topics)
|
|
};
|
|
|
|
this.sseClients.set(clientId, client);
|
|
|
|
// Send initial connection event
|
|
res.write(`data: ${JSON.stringify({
|
|
type: 'connection',
|
|
clientId: clientId,
|
|
timestamp: new Date().toISOString()
|
|
})}\\n\\n`);
|
|
|
|
// Handle client disconnect
|
|
req.on('close', () => {
|
|
this.sseClients.delete(clientId);
|
|
this.logger.debug(`SSE client ${clientId} disconnected`);
|
|
});
|
|
|
|
this.logger.debug(`SSE client ${clientId} connected with topics: ${topics.join(', ')}`);
|
|
}
|
|
|
|
/**
|
|
* Broadcast event to SSE clients
|
|
*/
|
|
private broadcastSSE(eventType: string, data: any): void {
|
|
const message = {
|
|
type: eventType,
|
|
data: data,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
|
|
for (const [clientId, client] of this.sseClients) {
|
|
if (client.subscriptions.has('all') || client.subscriptions.has(eventType)) {
|
|
try {
|
|
client.response.write(`data: ${JSON.stringify(message)}\\n\\n`);
|
|
} catch (error) {
|
|
this.logger.warn(`Failed to send SSE to client ${clientId}:`, error);
|
|
this.sseClients.delete(clientId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Setup WebSocket server
|
|
*/
|
|
private setupWebSocket(): void {
|
|
this.wsServer.on('connection', (ws: WebSocket, req: any) => {
|
|
const clientId = Math.random().toString(36).substring(2, 15);
|
|
const client: WSClient = {
|
|
id: clientId,
|
|
ws: ws,
|
|
subscriptions: new Set(['all'])
|
|
};
|
|
|
|
this.wsClients.set(clientId, client);
|
|
this.logger.debug(`WebSocket client ${clientId} connected`);
|
|
|
|
// Send welcome message
|
|
ws.send(JSON.stringify({
|
|
type: 'connection',
|
|
clientId: clientId,
|
|
timestamp: new Date().toISOString()
|
|
}));
|
|
|
|
// Handle messages
|
|
ws.on('message', (message: Buffer) => {
|
|
try {
|
|
const data = JSON.parse(message.toString());
|
|
this.handleWebSocketMessage(clientId, data);
|
|
} catch (error) {
|
|
this.logger.warn(`Invalid WebSocket message from ${clientId}:`, error);
|
|
}
|
|
});
|
|
|
|
// Handle disconnect
|
|
ws.on('close', () => {
|
|
this.wsClients.delete(clientId);
|
|
this.logger.debug(`WebSocket client ${clientId} disconnected`);
|
|
});
|
|
|
|
ws.on('error', (error: any) => {
|
|
this.logger.error(`WebSocket error for client ${clientId}:`, error);
|
|
this.wsClients.delete(clientId);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handle WebSocket messages
|
|
*/
|
|
private handleWebSocketMessage(clientId: string, message: any): void {
|
|
const client = this.wsClients.get(clientId);
|
|
if (!client) return;
|
|
|
|
switch (message.type) {
|
|
case 'subscribe':
|
|
if (message.topics && Array.isArray(message.topics)) {
|
|
message.topics.forEach((topic: string) => {
|
|
client.subscriptions.add(topic);
|
|
});
|
|
}
|
|
break;
|
|
|
|
case 'unsubscribe':
|
|
if (message.topics && Array.isArray(message.topics)) {
|
|
message.topics.forEach((topic: string) => {
|
|
client.subscriptions.delete(topic);
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Start the HTTP server
|
|
*/
|
|
async start(): Promise<void> {
|
|
try {
|
|
// Initialize Python bridge
|
|
this.logger.info('Initializing Python bridge...');
|
|
await initializePythonBridge({ pythonPath: this.config.pythonPath });
|
|
this.logger.info('Python bridge initialized successfully');
|
|
|
|
// Create HTTP server
|
|
this.server = createServer(this.app);
|
|
|
|
// Setup WebSocket server
|
|
this.wsServer = new WebSocketServer({ server: this.server });
|
|
this.setupWebSocket();
|
|
|
|
// Start listening
|
|
await new Promise<void>((resolve, reject) => {
|
|
this.server.listen(this.config.port, this.config.host, () => {
|
|
resolve();
|
|
}).on('error', reject);
|
|
});
|
|
|
|
this.logger.info(`CUDA Quantum HTTP Server started on ${this.config.host}:${this.config.port}`);
|
|
this.logger.info(`API Documentation: http://${this.config.host}:${this.config.port}/api-docs`);
|
|
this.logger.info(`Health Check: http://${this.config.host}:${this.config.port}/health`);
|
|
this.logger.info(`SSE Endpoint: http://${this.config.host}:${this.config.port}/api/events`);
|
|
|
|
} catch (error) {
|
|
this.logger.error('Failed to start HTTP server:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stop the HTTP server
|
|
*/
|
|
async stop(): Promise<void> {
|
|
this.logger.info('Shutting down HTTP server...');
|
|
|
|
// Close WebSocket connections
|
|
for (const [clientId, client] of this.wsClients) {
|
|
client.ws.close();
|
|
}
|
|
this.wsClients.clear();
|
|
|
|
// Close SSE connections
|
|
for (const [clientId, client] of this.sseClients) {
|
|
client.response.end();
|
|
}
|
|
this.sseClients.clear();
|
|
|
|
// Close WebSocket server
|
|
if (this.wsServer) {
|
|
this.wsServer.close();
|
|
}
|
|
|
|
// Close HTTP server
|
|
if (this.server) {
|
|
await new Promise<void>((resolve) => {
|
|
this.server.close(() => resolve());
|
|
});
|
|
}
|
|
|
|
// Close Python bridge
|
|
const bridge = getPythonBridge();
|
|
if (bridge) {
|
|
await bridge.close();
|
|
}
|
|
|
|
this.logger.info('HTTP server shutdown complete');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Main entry point for HTTP server
|
|
*/
|
|
async function main(): Promise<void> {
|
|
const config: HttpServerConfig = {
|
|
port: parseInt(process.env.HTTP_PORT || '3000'),
|
|
host: process.env.HTTP_HOST || 'localhost',
|
|
corsOrigins: process.env.CORS_ORIGINS ? process.env.CORS_ORIGINS.split(',') : ['*'],
|
|
pythonPath: process.env.CUDAQ_PYTHON_PATH,
|
|
logLevel: process.env.LOG_LEVEL === 'debug' ? LogLevel.DEBUG :
|
|
process.env.LOG_LEVEL === 'warn' ? LogLevel.WARN :
|
|
process.env.LOG_LEVEL === 'error' ? LogLevel.ERROR :
|
|
LogLevel.INFO
|
|
};
|
|
|
|
const server = new CudaQuantumHttpServer(config);
|
|
|
|
// Handle graceful shutdown
|
|
process.on('SIGTERM', async () => {
|
|
await server.stop();
|
|
process.exit(0);
|
|
});
|
|
|
|
process.on('SIGINT', async () => {
|
|
await server.stop();
|
|
process.exit(0);
|
|
});
|
|
|
|
try {
|
|
await server.start();
|
|
} catch (error) {
|
|
console.error('Failed to start CUDA Quantum HTTP Server:', error);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Start the server if this file is run directly
|
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
main().catch((error) => {
|
|
console.error('Fatal error:', error);
|
|
process.exit(1);
|
|
});
|
|
} |