Refactor proxy middleware for improved SSE and API request handling

Co-authored-by: jxxghp <jxxghp@163.com>
This commit is contained in:
Cursor Agent
2025-07-05 23:56:51 +00:00
co-authored by jxxghp
parent 51fbcdfa56
commit 75252fded6
4 changed files with 330 additions and 70 deletions
+131 -65
View File
@@ -1,6 +1,7 @@
const path = require('node:path')
const express = require('express')
const proxy = require('express-http-proxy')
const { createProxyMiddleware } = require('http-proxy-middleware')
const app = express()
const port = process.env.NGINX_PORT || 3000
@@ -14,76 +15,141 @@ const proxyConfig = {
// 静态文件服务目录
app.use(express.static(__dirname))
// 配置代理中间件将请求转发给后端API
app.use(
'/api',
proxy(`${proxyConfig.URL}:${proxyConfig.PORT}`, {
// 路径加上 /api 前缀
proxyReqPathResolver: (req) => {
return `/api${req.url}`
},
proxyReqOptDecorator: (proxyReqOpts, srcReq) => {
proxyReqOpts.headers = proxyReqOpts.headers || {};
// 创建专门的SSE代理中间件
const sseProxyMiddleware = createProxyMiddleware({
target: `http://${proxyConfig.URL}:${proxyConfig.PORT}`,
changeOrigin: true,
ws: false,
timeout: 0, // 无超时
proxyTimeout: 0, // 无超时
headers: {
'Connection': 'keep-alive',
'Cache-Control': 'no-cache'
},
onProxyRes: (proxyRes, req, res) => {
// 检测SSE响应
const isSSE = proxyRes.headers['content-type'] &&
proxyRes.headers['content-type'].includes('text/event-stream');
if (isSSE) {
// 设置SSE响应头
res.writeHead(proxyRes.statusCode, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Cache-Control, Content-Type, Authorization'
});
// 检测是否为SSE请求
const isSSE = srcReq.headers.accept && srcReq.headers.accept.includes('text/event-stream');
// 直接将代理响应流式传输到客户端
proxyRes.pipe(res);
// 动态设置超时时间:SSE无超时,普通请求600秒超时
proxyReqOpts.timeout = isSSE ? 0 : 600000;
if (isSSE) {
// SSE请求的特殊头部设置
proxyReqOpts.headers['Cache-Control'] = 'no-cache';
proxyReqOpts.headers['Connection'] = 'keep-alive';
proxyReqOpts.headers['Accept'] = 'text/event-stream';
}
return proxyReqOpts;
},
userResDecorator: (proxyRes, proxyResData, userReq, userRes) => {
// 检测响应是否为SSE类型
const isSSEResponse = proxyRes.headers['content-type'] &&
proxyRes.headers['content-type'].includes('text/event-stream');
if (isSSEResponse) {
// SSE响应:设置流式传输头部并禁用缓冲
userRes.writeHead(proxyRes.statusCode, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Cache-Control'
});
return false; // 禁用默认响应处理,让数据直接流向客户端
} else {
// 普通响应:正常处理
return proxyResData;
}
},
// 统一错误处理(添加超时错误处理)
proxyErrorHandler: (err, res, next) => {
// 客户端断开连接的正常情况(常见于SSE)
if (err.code === 'ECONNRESET' || err.code === 'EPIPE') {
console.log('Client disconnected:', err.code);
res.end(); // 优雅结束响应
return;
}
// 添加超时错误处理
if (err.code === 'ETIMEDOUT') {
console.log('Proxy request timed out:', err.code);
if (!res.headersSent) {
res.status(504).send('Gateway Timeout');
// 处理客户端断开连接
req.on('close', () => {
console.log('Client disconnected from SSE stream');
if (proxyRes.destroy) {
proxyRes.destroy();
}
return;
}
});
// 其他错误正常处理
console.error('Proxy error:', err);
next(err);
// 处理代理响应结束
proxyRes.on('end', () => {
console.log('SSE stream ended');
if (!res.headersSent) {
res.end();
}
});
// 处理代理响应错误
proxyRes.on('error', (err) => {
console.error('SSE proxy response error:', err);
if (!res.headersSent) {
res.status(500).end();
}
});
}
})
);
},
onError: (err, req, res) => {
console.error('SSE proxy error:', err);
if (!res.headersSent) {
res.status(500).json({ error: 'Proxy error' });
}
}
});
// 创建普通API代理中间件
const apiProxyMiddleware = proxy(`${proxyConfig.URL}:${proxyConfig.PORT}`, {
// 路径加上 /api 前缀
proxyReqPathResolver: (req) => {
return `/api${req.url}`
},
proxyReqOptDecorator: (proxyReqOpts, srcReq) => {
proxyReqOpts.headers = proxyReqOpts.headers || {};
// 检测是否为SSE请求
const isSSE = srcReq.headers.accept && srcReq.headers.accept.includes('text/event-stream');
if (!isSSE) {
// 普通请求设置超时
proxyReqOpts.timeout = 600000; // 600秒超时
}
return proxyReqOpts;
},
userResDecorator: (proxyRes, proxyResData, userReq, userRes) => {
// 只处理非SSE响应
const isSSEResponse = proxyRes.headers['content-type'] &&
proxyRes.headers['content-type'].includes('text/event-stream');
if (!isSSEResponse) {
// 普通响应:正常处理
return proxyResData;
}
// SSE响应不在这里处理,已经由专门的中间件处理
return proxyResData;
},
// 错误处理
proxyErrorHandler: (err, res, next) => {
// 客户端断开连接的正常情况
if (err.code === 'ECONNRESET' || err.code === 'EPIPE') {
console.log('Client disconnected:', err.code);
if (!res.headersSent) {
res.end();
}
return;
}
// 超时错误处理
if (err.code === 'ETIMEDOUT') {
console.log('Proxy request timed out:', err.code);
if (!res.headersSent) {
res.status(504).send('Gateway Timeout');
}
return;
}
// 其他错误
console.error('Proxy error:', err);
if (!res.headersSent) {
res.status(500).send('Internal Server Error');
}
}
});
// 配置API代理路由
app.use('/api', (req, res, next) => {
// 检测是否为SSE请求
const isSSE = req.headers.accept && req.headers.accept.includes('text/event-stream');
if (isSSE) {
// 使用专门的SSE代理中间件
sseProxyMiddleware(req, res, next);
} else {
// 使用普通API代理中间件
apiProxyMiddleware(req, res, next);
}
});
// 配置代理中间件将CookieCloud请求转发给后端API
app.use(