Update server.js
Browse files
server.js
CHANGED
|
@@ -76,7 +76,7 @@ app.use((err, req, res, next) => {
|
|
| 76 |
|
| 77 |
// --- API KEY AUTHENTICATION MIDDLEWARE ---
|
| 78 |
app.use((req, res, next) => {
|
| 79 |
-
if (req.path === '/' || req.path === '/health') {
|
| 80 |
return next();
|
| 81 |
}
|
| 82 |
|
|
@@ -265,37 +265,80 @@ async function resolveChatflowId(instanceNum, botName) {
|
|
| 265 |
return { id: match.id, instance };
|
| 266 |
}
|
| 267 |
|
| 268 |
-
// --- STREAMING HANDLER ---
|
| 269 |
async function handleStreamingResponse(flowiseResponse, clientRes) {
|
| 270 |
clientRes.setHeader('Content-Type', 'text/event-stream');
|
| 271 |
clientRes.setHeader('Cache-Control', 'no-cache');
|
| 272 |
clientRes.setHeader('Connection', 'keep-alive');
|
|
|
|
| 273 |
|
| 274 |
console.log('[Streaming] Forwarding SSE stream...');
|
| 275 |
|
| 276 |
let streamStarted = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
|
| 278 |
flowiseResponse.body.on('data', (chunk) => {
|
|
|
|
| 279 |
streamStarted = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
clientRes.write(chunk);
|
| 281 |
});
|
| 282 |
|
| 283 |
flowiseResponse.body.on('end', () => {
|
| 284 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
clientRes.end();
|
| 286 |
});
|
| 287 |
|
| 288 |
flowiseResponse.body.on('error', (err) => {
|
|
|
|
| 289 |
console.error('[Streaming Error]', err.message);
|
| 290 |
|
| 291 |
-
if (streamStarted) {
|
| 292 |
clientRes.write(`\n\nevent: error\ndata: {"error": "Stream interrupted"}\n\n`);
|
|
|
|
|
|
|
| 293 |
}
|
| 294 |
clientRes.end();
|
| 295 |
});
|
| 296 |
}
|
| 297 |
|
| 298 |
-
// ---
|
| 299 |
app.post('/api/v1/prediction/:instanceNum/:botName', async (req, res) => {
|
| 300 |
try {
|
| 301 |
const instanceNum = parseInt(req.params.instanceNum);
|
|
@@ -322,6 +365,10 @@ app.post('/api/v1/prediction/:instanceNum/:botName', async (req, res) => {
|
|
| 322 |
headers['Authorization'] = `Bearer ${instance.key}`;
|
| 323 |
}
|
| 324 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 325 |
const response = await fetchWithTimeout(
|
| 326 |
`${instance.url}/api/v1/prediction/${id}`,
|
| 327 |
{
|
|
@@ -329,9 +376,12 @@ app.post('/api/v1/prediction/:instanceNum/:botName', async (req, res) => {
|
|
| 329 |
headers,
|
| 330 |
body: JSON.stringify(req.body)
|
| 331 |
},
|
| 332 |
-
|
| 333 |
);
|
| 334 |
|
|
|
|
|
|
|
|
|
|
| 335 |
if (!response.ok) {
|
| 336 |
const errorText = await response.text();
|
| 337 |
console.error(`[Error] Instance returned ${response.status}: ${errorText.substring(0, 200)}`);
|
|
@@ -368,6 +418,7 @@ app.post('/api/v1/prediction/:instanceNum/:botName', async (req, res) => {
|
|
| 368 |
}
|
| 369 |
});
|
| 370 |
|
|
|
|
| 371 |
app.get('/api/v1/public-chatbotConfig/:instanceNum/:botName', async (req, res) => {
|
| 372 |
try {
|
| 373 |
const instanceNum = parseInt(req.params.instanceNum);
|
|
@@ -399,6 +450,7 @@ app.get('/api/v1/public-chatbotConfig/:instanceNum/:botName', async (req, res) =
|
|
| 399 |
}
|
| 400 |
});
|
| 401 |
|
|
|
|
| 402 |
app.get('/api/v1/chatflows-streaming/:instanceNum/:botName', async (req, res) => {
|
| 403 |
try {
|
| 404 |
const instanceNum = parseInt(req.params.instanceNum);
|
|
@@ -430,6 +482,43 @@ app.get('/api/v1/chatflows-streaming/:instanceNum/:botName', async (req, res) =>
|
|
| 430 |
}
|
| 431 |
});
|
| 432 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 433 |
app.get('/', (req, res) => res.send('Federated Proxy Active'));
|
| 434 |
|
| 435 |
app.get('/health', (req, res) => {
|
|
@@ -438,21 +527,30 @@ app.get('/health', (req, res) => {
|
|
| 438 |
instances: INSTANCES.length,
|
| 439 |
cached_bots: flowCache.size,
|
| 440 |
daily_active_ips: dailyUsage.size,
|
| 441 |
-
uptime: process.uptime()
|
|
|
|
| 442 |
});
|
| 443 |
});
|
| 444 |
|
|
|
|
| 445 |
app.use((req, res) => {
|
| 446 |
res.status(404).json({ error: 'Route not found' });
|
| 447 |
});
|
| 448 |
|
|
|
|
| 449 |
app.use((err, req, res, next) => {
|
| 450 |
console.error('[Error] Unhandled error:', err);
|
| 451 |
res.status(500).json({ error: 'Internal server error' });
|
| 452 |
});
|
| 453 |
|
|
|
|
| 454 |
const server = app.listen(7860, '0.0.0.0', () => {
|
| 455 |
-
console.log('Federated Proxy
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 456 |
});
|
| 457 |
|
| 458 |
process.on('SIGTERM', () => {
|
|
|
|
| 76 |
|
| 77 |
// --- API KEY AUTHENTICATION MIDDLEWARE ---
|
| 78 |
app.use((req, res, next) => {
|
| 79 |
+
if (req.path === '/' || req.path === '/health' || req.path.startsWith('/test-')) {
|
| 80 |
return next();
|
| 81 |
}
|
| 82 |
|
|
|
|
| 265 |
return { id: match.id, instance };
|
| 266 |
}
|
| 267 |
|
| 268 |
+
// --- IMPROVED STREAMING HANDLER WITH TIMEOUT ---
|
| 269 |
async function handleStreamingResponse(flowiseResponse, clientRes) {
|
| 270 |
clientRes.setHeader('Content-Type', 'text/event-stream');
|
| 271 |
clientRes.setHeader('Cache-Control', 'no-cache');
|
| 272 |
clientRes.setHeader('Connection', 'keep-alive');
|
| 273 |
+
clientRes.setHeader('X-Accel-Buffering', 'no');
|
| 274 |
|
| 275 |
console.log('[Streaming] Forwarding SSE stream...');
|
| 276 |
|
| 277 |
let streamStarted = false;
|
| 278 |
+
let dataReceived = false;
|
| 279 |
+
let lastDataTime = Date.now();
|
| 280 |
+
let totalBytes = 0;
|
| 281 |
+
|
| 282 |
+
// Check for stream timeout every 5 seconds
|
| 283 |
+
const timeoutCheck = setInterval(() => {
|
| 284 |
+
const timeSinceData = Date.now() - lastDataTime;
|
| 285 |
+
|
| 286 |
+
if (timeSinceData > 45000) { // 45 seconds without data
|
| 287 |
+
console.error(`[Streaming] Timeout - no data for ${(timeSinceData/1000).toFixed(1)}s`);
|
| 288 |
+
clearInterval(timeoutCheck);
|
| 289 |
+
|
| 290 |
+
if (!dataReceived) {
|
| 291 |
+
console.error('[Streaming] Stream completed with NO data received!');
|
| 292 |
+
if (!streamStarted) {
|
| 293 |
+
clientRes.status(504).json({
|
| 294 |
+
error: 'Gateway timeout',
|
| 295 |
+
message: 'No response from chatbot within 45 seconds'
|
| 296 |
+
});
|
| 297 |
+
} else {
|
| 298 |
+
clientRes.write('\n\nevent: error\ndata: {"error": "Response timeout - no data received"}\n\n');
|
| 299 |
+
}
|
| 300 |
+
}
|
| 301 |
+
clientRes.end();
|
| 302 |
+
}
|
| 303 |
+
}, 5000);
|
| 304 |
|
| 305 |
flowiseResponse.body.on('data', (chunk) => {
|
| 306 |
+
clearTimeout(timeoutCheck);
|
| 307 |
streamStarted = true;
|
| 308 |
+
dataReceived = true;
|
| 309 |
+
lastDataTime = Date.now();
|
| 310 |
+
totalBytes += chunk.length;
|
| 311 |
+
|
| 312 |
+
console.log(`[Streaming] Received chunk: ${chunk.length} bytes (total: ${totalBytes})`);
|
| 313 |
clientRes.write(chunk);
|
| 314 |
});
|
| 315 |
|
| 316 |
flowiseResponse.body.on('end', () => {
|
| 317 |
+
clearInterval(timeoutCheck);
|
| 318 |
+
|
| 319 |
+
if (dataReceived) {
|
| 320 |
+
console.log(`[Streaming] Stream completed successfully - ${totalBytes} bytes total`);
|
| 321 |
+
} else {
|
| 322 |
+
console.warn('[Streaming] Stream completed but NO data was received!');
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
clientRes.end();
|
| 326 |
});
|
| 327 |
|
| 328 |
flowiseResponse.body.on('error', (err) => {
|
| 329 |
+
clearInterval(timeoutCheck);
|
| 330 |
console.error('[Streaming Error]', err.message);
|
| 331 |
|
| 332 |
+
if (streamStarted && dataReceived) {
|
| 333 |
clientRes.write(`\n\nevent: error\ndata: {"error": "Stream interrupted"}\n\n`);
|
| 334 |
+
} else if (!streamStarted) {
|
| 335 |
+
clientRes.status(500).json({ error: 'Stream failed to start' });
|
| 336 |
}
|
| 337 |
clientRes.end();
|
| 338 |
});
|
| 339 |
}
|
| 340 |
|
| 341 |
+
// --- ROUTE 1: PREDICTION (WITH IMPROVED TIMEOUT) ---
|
| 342 |
app.post('/api/v1/prediction/:instanceNum/:botName', async (req, res) => {
|
| 343 |
try {
|
| 344 |
const instanceNum = parseInt(req.params.instanceNum);
|
|
|
|
| 365 |
headers['Authorization'] = `Bearer ${instance.key}`;
|
| 366 |
}
|
| 367 |
|
| 368 |
+
// TIMING: Track how long Flowise takes
|
| 369 |
+
const startTime = Date.now();
|
| 370 |
+
console.log(`[Timing] Calling Flowise at ${new Date().toISOString()}`);
|
| 371 |
+
|
| 372 |
const response = await fetchWithTimeout(
|
| 373 |
`${instance.url}/api/v1/prediction/${id}`,
|
| 374 |
{
|
|
|
|
| 376 |
headers,
|
| 377 |
body: JSON.stringify(req.body)
|
| 378 |
},
|
| 379 |
+
60000 // ← INCREASED TO 60 SECONDS
|
| 380 |
);
|
| 381 |
|
| 382 |
+
const duration = Date.now() - startTime;
|
| 383 |
+
console.log(`[Timing] Flowise responded in ${duration}ms (${(duration/1000).toFixed(1)}s)`);
|
| 384 |
+
|
| 385 |
if (!response.ok) {
|
| 386 |
const errorText = await response.text();
|
| 387 |
console.error(`[Error] Instance returned ${response.status}: ${errorText.substring(0, 200)}`);
|
|
|
|
| 418 |
}
|
| 419 |
});
|
| 420 |
|
| 421 |
+
// --- ROUTE 2: CHATBOT CONFIG ---
|
| 422 |
app.get('/api/v1/public-chatbotConfig/:instanceNum/:botName', async (req, res) => {
|
| 423 |
try {
|
| 424 |
const instanceNum = parseInt(req.params.instanceNum);
|
|
|
|
| 450 |
}
|
| 451 |
});
|
| 452 |
|
| 453 |
+
// --- ROUTE 3: STREAMING CHECK ---
|
| 454 |
app.get('/api/v1/chatflows-streaming/:instanceNum/:botName', async (req, res) => {
|
| 455 |
try {
|
| 456 |
const instanceNum = parseInt(req.params.instanceNum);
|
|
|
|
| 482 |
}
|
| 483 |
});
|
| 484 |
|
| 485 |
+
// --- TEST ENDPOINT: Stream Test ---
|
| 486 |
+
app.get('/test-stream', (req, res) => {
|
| 487 |
+
res.setHeader('Content-Type', 'text/event-stream');
|
| 488 |
+
res.setHeader('Cache-Control', 'no-cache');
|
| 489 |
+
res.setHeader('Connection', 'keep-alive');
|
| 490 |
+
|
| 491 |
+
let count = 0;
|
| 492 |
+
const interval = setInterval(() => {
|
| 493 |
+
count++;
|
| 494 |
+
res.write(`data: {"message": "Test chunk ${count}", "timestamp": "${new Date().toISOString()}"}\n\n`);
|
| 495 |
+
|
| 496 |
+
if (count >= 5) {
|
| 497 |
+
clearInterval(interval);
|
| 498 |
+
res.write('data: {"message": "Test complete"}\n\n');
|
| 499 |
+
res.end();
|
| 500 |
+
}
|
| 501 |
+
}, 500);
|
| 502 |
+
});
|
| 503 |
+
|
| 504 |
+
// --- TEST ENDPOINT: Delay Test ---
|
| 505 |
+
app.post('/test-delay', async (req, res) => {
|
| 506 |
+
const start = Date.now();
|
| 507 |
+
const delaySeconds = req.body.delay || 35;
|
| 508 |
+
|
| 509 |
+
console.log(`[Test] Starting ${delaySeconds}s delay test...`);
|
| 510 |
+
await new Promise(resolve => setTimeout(resolve, delaySeconds * 1000));
|
| 511 |
+
|
| 512 |
+
const duration = Date.now() - start;
|
| 513 |
+
res.json({
|
| 514 |
+
message: 'Test completed',
|
| 515 |
+
duration_ms: duration,
|
| 516 |
+
duration_sec: (duration/1000).toFixed(1),
|
| 517 |
+
requested_delay: delaySeconds
|
| 518 |
+
});
|
| 519 |
+
});
|
| 520 |
+
|
| 521 |
+
// --- HEALTH CHECK ---
|
| 522 |
app.get('/', (req, res) => res.send('Federated Proxy Active'));
|
| 523 |
|
| 524 |
app.get('/health', (req, res) => {
|
|
|
|
| 527 |
instances: INSTANCES.length,
|
| 528 |
cached_bots: flowCache.size,
|
| 529 |
daily_active_ips: dailyUsage.size,
|
| 530 |
+
uptime: process.uptime(),
|
| 531 |
+
memory: process.memoryUsage()
|
| 532 |
});
|
| 533 |
});
|
| 534 |
|
| 535 |
+
// --- 404 HANDLER ---
|
| 536 |
app.use((req, res) => {
|
| 537 |
res.status(404).json({ error: 'Route not found' });
|
| 538 |
});
|
| 539 |
|
| 540 |
+
// --- GLOBAL ERROR HANDLER ---
|
| 541 |
app.use((err, req, res, next) => {
|
| 542 |
console.error('[Error] Unhandled error:', err);
|
| 543 |
res.status(500).json({ error: 'Internal server error' });
|
| 544 |
});
|
| 545 |
|
| 546 |
+
// --- GRACEFUL SHUTDOWN ---
|
| 547 |
const server = app.listen(7860, '0.0.0.0', () => {
|
| 548 |
+
console.log('===== Federated Proxy Started =====');
|
| 549 |
+
console.log('Port: 7860');
|
| 550 |
+
console.log(`Instances: ${INSTANCES.length}`);
|
| 551 |
+
console.log(`Allowed Origins: ${allowedOrigins.length || 'Open mode'}`);
|
| 552 |
+
console.log(`API Keys: ${API_KEYS.length || 'None'}`);
|
| 553 |
+
console.log('====================================');
|
| 554 |
});
|
| 555 |
|
| 556 |
process.on('SIGTERM', () => {
|