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

Express+Nginx环境下CORS拦截与502错误排查求助

问题概述

部署在Nginx反向代理后的ExpressJS应用,已配置cors模块实现跨域,但通过localhost:3000客户端多次发送POST文件上传请求后,会随机出现CORS拦截错误,同时伴随Nginx返回的502 Bad Gateway错误。排查发现应用能正常执行到业务逻辑someFunction,PM2状态正常,但响应返回环节失败,且Nginx日志中出现来源不明的GET /?name=example.com&type=A请求。


当前配置

Express的CORS配置

var express = require("express");
var cors = require("cors");

var app = express();

var corsOptions = {
      origin: "http://localhost:3000",
      credentials: true,
      optionSuccessRate: 200
};

app.use(cors(corsOptions));

app.options("*", cors());

Nginx配置

/etc/nginx/nginx.conf

user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules-enabled/*.conf;

events {
        worker_connections 768;
        # multi_accept on;
}

http {
        sendfile on;
        tcp_nopush on;
        types_hash_max_size 2048;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
        ssl_prefer_server_ciphers on;

        access_log /var/log/nginx/access.log;

        gzip on;

        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;

        client_max_body_size 100M;
}

/etc/nginx/sites-available/default

server {

        server_name DOMAIN_NAME;

        location / {
                proxy_pass http://127.0.0.1:5000;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection 'upgrade';
                proxy_set_header Host $host;
                proxy_cache_bypass $http_upgrade;
                proxy_redirect off;
                proxy_request_buffering off;
        }


    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/[DOMAIN_NAME]/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/[DOMAIN_NAME]/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = DOMAIN_NAME) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


        listen 80 default_server;
        listen [::]:80 default_server;

        server_name DOMAIN_NAME;
    return 404; # managed by Certbot
}

Express应用核心代码

import fs from "fs";
import path from "path";
import cors from "cors";
import express from "express";
import formidable from "formidable";

import someFunction from "./someFunction.js";

const app = express();

var corsOptions = {
    origin: "http://localhost:3000",
    credentials: true,
    optionSuccessStatus: 200
};

app.use(cors(corsOptions));

app.options("*", cors());

const port = 5000;

const __dirname = path.resolve();
    
app.post("/", function(req, res, next) {
    const form = formidable({ 
        uploadDir: "uploads" 
    });

    form.parse(req, function(err, fields, files) {
       try {
        if(err) {
            next(err);
            res.send("Error");
        }

        const metadata = JSON.parse(fields["config"][0]);
        
        const dir = `${form.uploadDir}/${metadata.dir}`;
        const fileName = files["files"][0].originalFilename;

        fs.mkdirSync(dir);

        fs.rename(files["files"][0].filepath, `${dir}/${fileName}`, function(err) {
            if(err) throw err;
        });
        
        // do conversion here-------------------------
        someFunction(fileName, metadata)
            .then(() => {
                res.write("Done");
                res.end();
            })
            .catch(er => {
                throw er;
            });
        //--------------------------------------------
            
       }catch(e) {
        console.log(e);
        res.write("Error");
        res.end();
       }
    });
});

app.listen(port, function() {
    console.log("Server initialized");
});

出现的错误

Access to XMLHttpRequest at SERVER_DOMAIN from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

POST SERVER_DOMAIN net::ERR_FAILED 502 (Bad Gateway)


排查发现的异常

  • 应用能正常执行到someFunction,但调用res.write返回响应时失败
  • Nginx日志中出现异常请求:
request: "GET /?name=example.com&type=A HTTP/1.1", upstream: "http://127.0.0.1:5000/?name=example.com&type=A", host: "........."

问题原因分析

  1. Express未处理异步/同步异常,导致进程无响应:
    • fs.mkdirSync(dir)若目录已存在会抛出异常,虽有try/catch,但fs.rename回调内的throw err无法被外层try/catch捕获,导致Express未处理该错误,进程挂起或崩溃,Nginx返回502。
    • someFunction的catch块中throw er会抛出未捕获异常,同样导致进程异常。
  2. CORS错误是502的次生问题:Nginx返回502时,不会携带Express设置的CORS响应头,浏览器因此触发CORS拦截提示,混淆了问题本质。
  3. 异常GET请求来源:大概率是DNS探测工具或爬虫发送的请求,虽不直接导致502,但暴露了应用未处理非POST请求的漏洞。

修复方案

1. 完善Express错误处理逻辑

修改POST接口代码,解决未捕获异常问题:

app.post("/", function(req, res) {
    const form = formidable({ 
        uploadDir: "uploads" 
    });

    form.parse(req, function(err, fields, files) {
        // 优先处理表单解析错误
        if(err) {
            console.error("表单解析错误:", err);
            return res.status(400).send("表单解析失败");
        }

        try {
            const metadata = JSON.parse(fields["config"][0]);
            const dir = `${form.uploadDir}/${metadata.dir}`;
            const fileName = files["files"][0].originalFilename;

            // 创建目录时允许递归创建,避免已存在报错
            fs.mkdirSync(dir, { recursive: true });

            // 使用Promise版rename替代回调,统一异步错误处理
            fs.promises.rename(files["files"][0].filepath, `${dir}/${fileName}`)
                .then(() => someFunction(fileName, metadata))
                .then(() => {
                    res.send("处理完成");
                })
                .catch(er => {
                    console.error("业务处理错误:", er);
                    res.status(500).send("业务处理失败");
                });
            
        }catch(e) {
            console.error("请求处理错误:", e);
            res.status(500).send("请求处理失败");
        }
    });
});
  • 核心优化点:
    • 提前处理表单解析错误,直接返回响应
    • fs.mkdirSync添加{ recursive: true }参数,避免目录已存在抛出异常
    • 用fs.promises.rename替代回调式rename,确保异步错误能被捕获
    • 所有错误都在catch块中处理,返回明确的错误状态码和信息,不抛出未捕获异常
    • 替换res.write为res.send,简化响应逻辑

2. 优化Nginx配置,增强稳定性

在location块中添加超时配置和全局CORS头,确保错误场景下也能返回跨域头:

location / {
        proxy_pass http://127.0.0.1:5000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_cache_bypass $http_upgrade;
        proxy_redirect off;
        proxy_request_buffering off;

        # 配置超时时间(根据业务逻辑调整)
        proxy_connect_timeout 30s;
        proxy_send_timeout 30s;
        proxy_read_timeout 60s;

        # 强制添加CORS头,即使返回错误状态码
        add_header Access-Control-Allow-Origin http://localhost:3000 always;
        add_header Access-Control-Allow-Credentials true always;
}
  • 核心优化点:
    • always关键字确保Nginx返回任何状态码时都携带CORS头,避免浏览器触发不必要的CORS拦截
    • 配置合理的超时时间,适配业务逻辑的执行时长

3. 处理非POST请求

添加全局404处理,避免异常请求引发错误:

app.all("*", (req, res) => {
    res.status(404).send("接口不存在");
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 17:26:05