diff --git a/services/api-gateway/src/server.js b/services/api-gateway/src/server.js index acf83b9..d118a54 100644 --- a/services/api-gateway/src/server.js +++ b/services/api-gateway/src/server.js @@ -210,6 +210,21 @@ const websocketHandlers = websocketAuth(io); // Auth Service - Fixed proxy with proper connection handling console.log('🔧 Registering /api/auth proxy route...'); + +// Use dedicated keep-alive agents to avoid stale sockets and ECONNRESET after container idle/restarts +const http = require('http'); +const https = require('https'); +const axiosAuthUpstream = axios.create({ + timeout: 15000, + // Keep connections healthy and reused properly + httpAgent: new http.Agent({ keepAlive: true, maxSockets: 100 }), + httpsAgent: new https.Agent({ keepAlive: true, maxSockets: 100 }), + decompress: true, + // Don't throw on non-2xx so we can forward exact status/data + validateStatus: () => true, + maxRedirects: 0 +}); + app.use('/api/auth', (req, res, next) => { const authServiceUrl = serviceTargets.USER_AUTH_URL; const targetUrl = `${authServiceUrl}${req.originalUrl}`; @@ -231,7 +246,7 @@ app.use('/api/auth', (req, res, next) => { headers: { 'Content-Type': 'application/json', 'User-Agent': 'API-Gateway/1.0', - 'Connection': 'keep-alive', + // Let the agent manage connection header; forcing keep-alive can cause stale sockets in some environments // Forward Authorization header so protected auth-admin routes work 'Authorization': req.headers.authorization, // Forward all relevant headers @@ -239,9 +254,7 @@ app.use('/api/auth', (req, res, next) => { 'X-Forwarded-Proto': req.protocol, 'X-Forwarded-Host': req.get('host') }, - timeout: 10000, - validateStatus: () => true, - maxRedirects: 0 + timeout: 15000 }; // Always include request body for POST/PUT/PATCH requests @@ -251,7 +264,10 @@ app.use('/api/auth', (req, res, next) => { } console.log(`🚀 [AUTH PROXY] Making request to: ${targetUrl}`); - axios(options) + + const performRequest = () => axiosAuthUpstream(options); + + performRequest() .then(response => { console.log(`✅ [AUTH PROXY] Response: ${response.status} for ${req.method} ${req.originalUrl}`); console.log(`📊 [AUTH PROXY] Response headers:`, response.headers); @@ -278,6 +294,43 @@ app.use('/api/auth', (req, res, next) => { console.error(`❌ [AUTH PROXY ERROR]:`, error.message); console.error(`❌ [AUTH PROXY ERROR CODE]:`, error.code); console.error(`❌ [AUTH PROXY ERROR STACK]:`, error.stack); + // Retry once on transient network/socket errors that can occur after service restarts + const transientCodes = ['ECONNRESET', 'EPIPE', 'ETIMEDOUT', 'ECONNREFUSED']; + if (!req._authRetry && transientCodes.includes(error.code)) { + req._authRetry = true; + console.warn(`⚠️ [AUTH PROXY] Transient error ${error.code}. Retrying once: ${targetUrl}`); + return performRequest() + .then(r => { + if (!res.headersSent) { + const origin = req.headers.origin || '*'; + Object.keys(r.headers).forEach(key => { + const k = key.toLowerCase(); + if (k === 'content-encoding' || k === 'transfer-encoding') return; + if (k.startsWith('access-control-')) return; + res.setHeader(key, r.headers[key]); + }); + res.removeHeader('Access-Control-Allow-Origin'); + res.removeHeader('access-control-allow-origin'); + res.setHeader('Access-Control-Allow-Origin', origin); + res.setHeader('Vary', 'Origin'); + res.setHeader('Access-Control-Allow-Credentials', 'true'); + res.setHeader('Access-Control-Expose-Headers', 'Content-Length, X-Total-Count, X-Gateway-Request-ID, X-Gateway-Timestamp, X-Forwarded-By, X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host'); + return res.status(r.status).json(r.data); + } + }) + .catch(() => { + // Fall through to final handler below + if (!res.headersSent) { + res.status(502).json({ + error: 'Auth service unavailable', + message: error.code || error.message, + service: 'user-auth', + target_url: targetUrl + }); + } + }); + } + if (!res.headersSent) { if (error.response) { console.log(`📊 [AUTH PROXY] Error response status: ${error.response.status}`);