You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修复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('--------------------------------------');
},

验证修复

  1. 重启代理服务器;
  2. 发起WebSocket连接请求;
  3. 查看代理日志,应该能看到proxyResWs钩子的输出;
  4. 在浏览器开发者工具的Network标签中,确认响应头已被修改为x-xyz-request-id。

内容的提问来源于stack exchange,提问作者S7H

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 12:24:06