Skip to content

http-proxy-middleware转发SSE接口延迟问题

公司的一个老项目使用http-proxy-middleware代理转发本地接口,在对接一个SSE流式接口的时候发现转发总是很慢,如果直接在降级浏览器中请求真实地址发现响应速度很快,由此断定本地开发慢的原因大概率是代理库引起的问题。

经过一番和Cursor以及豆包的沟通,终于修复了问题,特此记录下来

proxy.js

js
const proxy = require("http-proxy-middleware");
module.exports = function(app) {
    app.use(
        proxy('/ai_agent', {
            target: 'http://xxx.xxxx.xxxxx',
            secure: false,
            changeOrigin: true,
            pathRewrite: { '^/ai_agent': '' },
            buffer: false,
            proxyTimeout: 300000,
            timeout: 300000,
            compress: false,
            followRedirects: false,
            selfHandleResponse: true, // 完全自己处理响应,避免重复

            onProxyReq: (proxyReq, req, res) => {
                const requestId = Date.now() + Math.random().toString(36).substr(2, 9);
                req.requestId = requestId;
                console.log(`[${requestId}] 代理请求: ${req.method} ${req.url}`);
            },

            onProxyRes: (proxyRes, req, res) => {
                // 设置SSE响应头
                res.setHeader('Content-Type', 'text/event-stream');
                res.setHeader('Cache-Control', 'no-cache');
                res.setHeader('Connection', 'keep-alive');
                res.setHeader('Access-Control-Allow-Origin', '*');
                res.setHeader('Access-Control-Allow-Headers', 'Cache-Control');
                
                // 删除可能影响SSE的响应头
                delete proxyRes.headers['content-length'];
                delete proxyRes.headers['transfer-encoding'];
                delete proxyRes.headers['content-encoding'];

                // 防止重复监听
                if (proxyRes._sseHandled) return;
                proxyRes._sseHandled = true;

                let chunkCount = 0;

                // 直接监听数据事件并立即转发,但防止重复
                proxyRes.on('data', (chunk) => {
                    chunkCount++;

                    // 立即写入响应,确保实时性
                    res.write(chunk);
                    
                    // 强制刷新缓冲区
                    if (res.flush) res.flush();
                });

                proxyRes.on('end', () => {
                    res.end();
                });

                proxyRes.on('error', (err) => {
                    res.end();
                });
            }
        })
    );
};

关键点在于res的响应方式,使用res.write(chunk)可以立即写入确保实时性,但是有可能会重复写入两次chunk,可以添加了一个_sseHandled变量解决这个问题。

最开始满的根本原因是使用了proxyRes.pipe(res)将数据写回,代理服务器接受到了所有的消息后才会一次性发回,这就导致了流一直没有响应。

上次更新于: