如何修复http-proxy-middleware的WebSocket代理,实现请求响应拦截及外部服务调用?
解决WebSocket代理中无法拦截后端响应的问题
问题分析
你遇到的核心问题是:proxyRes钩子不会触发WebSocket的HTTP升级响应(状态码101)。proxyRes是http-proxy专门用于处理普通HTTP请求/响应的钩子,而WebSocket的握手流程是通过upgrade事件单独处理的,对应的响应拦截需要使用proxyResWs专属钩子。
另外,你的代码里还有一个变量作用域的小问题:在proxyRes的if(isWebSocketSuccess)块里,你重新声明了const requestId,这会导致外部的requestId变量始终是undefined,即使后端返回了xyz-req-id头也无法被正确提取。
修复方案
1. 替换响应拦截钩子到proxyResWs
将原本在proxyRes中的响应处理逻辑移到proxyResWs钩子中,这个钩子专门负责处理WebSocket的握手响应。
2. 修复变量作用域问题
移除if块内的const声明,直接赋值给外部的requestId变量。
修改后的setupProxy代码
export const setupProxy = (proxyConfig: ProxyConfig): RequestHandler => { const target = proxyConfig.endpoint; const proxyOptions: Options = { target: target, changeOrigin: true, secure: true, ws: true, on: { proxyReq: (proxyReq, req, res) => { log('--------------------------------------'); logRequest(req as Request, 'Original Request from Client'); proxyReq.setHeader('Authorization', `Bearer ${proxyConfig.details.apiKey}`); logRequest(proxyReq, 'MODIFIED request to Backend'); }, proxyReqWs: (proxyReq, req, socket, options, head) => { log('proxyReqWs event triggered for:', { method: req.method, url: req.url }); proxyReq.setHeader('Authorization', `Bearer ${proxyConfig.details.apiKey}`); }, // 专门处理WebSocket握手响应的钩子 proxyResWs: (proxyRes, req, socket, head) => { log('--------------------------------------'); logResponse(proxyRes, req as Request, 'Original Response from Backend'); const isWebSocketSuccess = proxyRes.statusCode === 101; let requestId: string | undefined; if (isWebSocketSuccess) { // 直接赋值,不重新声明变量 requestId = proxyRes.headers['xyz-req-id'] as string; } if (requestId) { log(`Extracted request ID: ${requestId}`); // 修改响应头名称 proxyRes.headers['x-xyz-request-id'] = requestId; delete proxyRes.headers['xyz-req-id']; } logResponse(proxyRes, req as Request, 'MODIFIED response to Client'); log('--------------------------------------'); }, error: (err, req, res) => { logError('Proxy Error:', err.message, 'Target:', target); if (res.writeHead && !res.headersSent) { res.writeHead(502, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Bad Gateway', message: err.message })); } } }, router: (req) => { return proxyConfig.endpoint; } }; return createProxyMiddleware(proxyOptions); };
调用外部服务的实现
在提取到requestId后,你可以使用axios或node-fetch等工具调用外部服务。需要注意两种场景:
- 如果是日志上报这类不需要等待结果的操作,直接异步调用即可,不影响WebSocket握手流程;
- 如果需要根据外部服务的返回结果修改响应头,必须等待异步调用完成后再继续处理响应。
示例(使用axios异步调用外部服务):
// 先安装依赖:npm install axios @types/axios import axios from 'axios'; // 在proxyResWs钩子中添加调用逻辑 proxyResWs: async (proxyRes, req, socket, head) => { log('--------------------------------------'); logResponse(proxyRes, req as Request, 'Original Response from Backend'); const isWebSocketSuccess = proxyRes.statusCode === 101; let requestId: string | undefined; if (isWebSocketSuccess) { requestId = proxyRes.headers['xyz-req-id'] as string; } if (requestId) { log(`Extracted request ID: ${requestId}`); // 调用外部服务(示例:上报请求ID) try { await axios.post('https://your-external-service.com/log', { requestId: requestId, clientIp: req.ip, timestamp: new Date().toISOString() }); log('Successfully reported request ID to external service'); } catch (err) { logError('Failed to call external service:', (err as Error).message); } // 修改响应头 proxyRes.headers['x-xyz-request-id'] = requestId; delete proxyRes.headers['xyz-req-id']; } logResponse(proxyRes, req as Request, 'MODIFIED response to Client'); log('--------------------------------------'); },
验证修复
- 重启代理服务器;
- 发起WebSocket连接请求;
- 查看代理日志,应该能看到
proxyResWs钩子的输出; - 在浏览器开发者工具的Network标签中,确认响应头已被修改为
x-xyz-request-id。
内容的提问来源于stack exchange,提问作者S7H
相关产品推荐
相关产品推荐

