配置ExpressJS CORS适配EventSource/SSE遇跨域问题求助
SSE(fetchEventSource)与Express交互的CORS问题
使用React的fetchEventSource请求Express的/client_notifications SSE端点时,预检OPTIONS请求正常,但后续GET请求触发CORS错误:
Access to fetch at 'https://api.healthinbound.com/client_notifications' from origin 'http://localhost:5173' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource
其他非SSE路由的CORS配置正常,尝试在端点处理中手动设置响应头无效。
Express的app.js配置
const clientNotificationEventsHandler = require("./notificationsEventHandlers/clientNoti").notificationEventsHandler; const app = express(); app.use(cors({withCredentials: true, origin: true})); // 以下为尝试过的手动CORS配置(已注释) // app.all('*', function(req, res, next) { // res.header("Access-Control-Allow-Origin", "*"); // res.header("Access-Control-Allow-Headers", "X-Requested-With"); // res.header('Access-Control-Allow-Headers', 'Content-Type'); // res.header('Access-Control-Allow-Methods', "GET,DELETE,HEAD,OPTIONS"); // if (req.method === "OPTIONS") { // console.log("OPTIONS FOUND") // return res.status(200).end(); // } // next(); // }); app.get("/client_notifications", clientNotificationEventsHandler)
SSE端点处理代码
async function notificationEventsHandler(req, res) { const headers = { "Content-Type": "text/event-stream", Connection: "keep-alive", "Cache-Control": "no-cache", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "X-Requested-With, Content-Type", "Access-Control-Allow-Methods": "GET,DELETE,HEAD,OPTIONS" }; const clientEmail = req.headers.clientemail; const data = await ApptNotificationData.findAll({ where: { patientEmail: clientEmail, accepted: "1", }, }); res.writeHead(200, headers); await res.write(`data:${JSON.stringify(data)}\n\n`); const newClientStream = { clientEmail: clientEmail, res, }; clientStreams.push(newClientStream); req.on("close", () => { console.log(`${clientEmail} Connection closed`); clientStreams = clientStreams.filter( (client) => client.clientEmail !== clientEmail ); }); return res; }
Nginx配置
server { listen 80; listen [::]:80; server_name api.***.com; location /client_notifications { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_pass http://nodejs:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # 添加CORS响应头 add_header Access-Control-Allow-Origin $http_origin; add_header Access-Control-Allow-Credentials true; add_header Access-Control-Allow-Methods "GET,OPTIONS"; add_header Access-Control-Allow-Headers "Content-Type,clientemail"; # 直接处理OPTIONS请求 if ($request_method = OPTIONS) { return 204; } } } server { listen 443 ssl; server_name api.***.com www.api.***.com; ssl_certificate /etc/ssl/final.crt; ssl_certificate_key /etc/ssl/key.key; location /client_notifications { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_pass http://nodejs:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # 添加CORS响应头 add_header Access-Control-Allow-Origin $http_origin; add_header Access-Control-Allow-Credentials true; add_header Access-Control-Allow-Methods "GET,OPTIONS"; add_header Access-Control-Allow-Headers "Content-Type,clientemail"; # 直接处理OPTIONS请求 if ($request_method = OPTIONS) { return 204; } } #root var/www/html; index index.php index.html index.htm; }
解决方案要点
- 修复Header覆盖问题:原代码中
Access-Control-Allow-Headers被重复赋值,需合并为逗号分隔的字符串,避免覆盖。 - 修正连接清理逻辑:原filter条件
client.clientEmail !== client.clientEmail永远为false,无法移除断开的连接,改为和传入的clientEmail变量比较。 - Nginx层面补全CORS配置:作为反向代理,Nginx可能会拦截后端返回的CORS头,需在location块中明确添加响应头,并直接处理OPTIONS请求,避免转发到后端。
- 保持CORS配置一致性:如果前端使用
withCredentials: true,Access-Control-Allow-Origin不能设为*,需指定前端域名,或用Nginx的$http_origin动态匹配(注意限制信任源,避免安全风险)。
内容的提问来源于stack exchange,提问作者Amon
相关产品推荐
相关产品推荐

