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

配置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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 19:07:33