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

Express-session会话管理:ERR_HTTP_HEADERS_SENT错误及跳转异常解决

基于express-session的会话管理应用问题排查与解决

问题背景

开发基于express-session的会话管理应用,需求为:用户会话存在时跳转至dashboard页面,否则跳转至登录页。开发过程中先后遇到两个问题:注册时触发ERR_HTTP_HEADERS_SENT错误,修复后又出现/dashboard路由无限302跳转的情况。

初始错误:ERR_HTTP_HEADERS_SENT

注册操作时多次弹出以下错误:

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at new NodeError (node:internal/errors:372:5)
    at ServerResponse.setHeader (node:_http_outgoing:576:11)
    at ServerResponse.header (/home/deep/Node_API/node_modules/express/lib/response.js:794:10)
    at ServerResponse.location (/home/deep/Node_API/node_modules/express/lib/response.js:915:15)
    at ServerResponse.redirect (/home/deep/Node_API/node_modules/express/lib/response.js:953:18)
    at sessionChecker (/home/deep/Node_API/server/middlewares/session.middleware.js:5:13)
    at Layer.handle [as handle_request] (/home/deep/Node_API/node_modules/express/lib/router/layer.js:95:5)
    at trim_prefix (/home/deep/Node_API/node_modules/express/lib/router/index.js:328:13)
    at /home/deep/Node_API/node_modules/express/lib/router/index.js:286:9
    at Function.process_params (/home/deep/Node_API/node_modules/express/lib/router/index.js:346:12)

应用核心代码

const https = require("https");
const http = require("http");
const path = require("path");
const express = require("express");
const cookieParser = require("cookie-parser");
const dotenv = require("dotenv");
const cors = require("cors");
const helmet = require("helmet");
const logger = require("morgan");
const compression = require("compression");
const session = require("express-session");
const pgSession = require("connect-pg-simple")(session);
const api = require("../server/routes");
const pool = require("../config/database");
/** ====== DotENV configuration */
dotenv.config({ path: require("find-config")(".env") });

const configurations = {
    production: { ssl: true, port: process.env.PORT, hostname: "" },
    development: {
        ssl: false,
        port: process.env.PORT,
        hostname: process.env.STAGE_HOSTNAME,
    },
};

const environment = process.env.NODE_ENV || "development";
const config = configurations[environment];

const {
    responseHandlerMiddleware,
    sessionChecker,
} = require("../server/middlewares");

const app = express();

var credentials = {
    // key: fs.readFileSync("/etc/letsencrypt/live/myresorts.in/privkey.pem"),
    // cert: fs.readFileSync("/etc/letsencrypt/live/myresorts.in/fullchain.pem"),
};

let server = config.ssl
    ? https.createServer(credentials, app)
    : http.createServer(app);

app.use(cors());
app.options("*", cors());
app.use(helmet());
app.use(compression());
app.use(logger("dev"));
app.use(express.json({ limit: "20mb" }));
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
app.set("trust proxy", 1);
app.use(
    session({
        store: new pgSession({
            pool,
            tableName: "session",
        }),
        name: "user_sid",
        secret: process.env.SESSION_SECRET,
        resave: true,
        cookie: { secure: false, maxAge: 60000 * 5 },
        saveUninitialized: true,
    })
);


app.use((req, res, next) => {
    if (req.session.user && req.cookies.user_sid) {
        res.redirect("/dashboard");
    }
    return next();
});
app.get("/", sessionChecker, (req, res) => {
    res.redirect("/login");
});
app.set("views", path.resolve(__dirname, "../views"));
app.set("view engine", "ejs");
app.use(express.static("../public"));
app.use("/", sessionChecker, api);
// app.use(responseHandlerMiddleware);

app.route("/login").get(sessionChecker, (req, res) => {
    res.render("pages/login");
});
app.route("/register").get(sessionChecker, (req, res) => {
    res.render("pages/register");
});
app.route("/dashboard").get(sessionChecker, (req, res) => {
    if (req.session.user && req.cookies.user_sid) {
        res.render("pages/dashboard");
    } else {
        res.redirect("/login");
    }
});

module.exports = server;

会话检查中间件代码

const sessionChecker = (req, res, next) => {
    console.log("sesson ---------", req.session);
    console.log("cookies ---------", JSON.stringify(req.cookies));
    if (req.session.user && req.cookies.user_sid) {
        res.redirect("/dashboard");
    } else {
        return next();
    }
};

中间件修改后的新问题

修改会话检查中间件修复了ERR_HTTP_HEADERS_SENT错误,但注册完成后出现/dashboard路由无限302跳转的情况,且该路由的处理函数从未被执行:

GET /dashboard 302 246.426 ms - 64
GET /dashboard 302 247.005 ms - 64
GET /dashboard 302 245.620 ms - 64
GET /dashboard 302 247.940 ms - 64
GET /dashboard 302 246.114 ms - 64

用户注册控制器代码

const registerUser = catchAsync(async (req, res, next) => {
    const { email, password } = req.body;
    const user = await pool.query("select * from find_user_by_email($1)", [
        email,
    ]);
    if (user.rowCount) {
        res.error = "User already exists";
        return next(500);
    }
    const saltHash = generatePassword(password);
    const response = await pool.query("select * from create_user($1, $2, $3)", [
        email,
        saltHash.salt,
        saltHash.hash,
    ]);
    if (response.rowCount) {
        req.session.user = { email: response.rows[0].create_user };
        res.redirect("/dashboard");
    } else {
        res.redirect("/register");
    }
});

最终解决方案

排查后发现问题根源:在需要渲染页面的场景下,错误使用了res.redirect而非res.render。将所有涉及页面渲染的res.redirect替换为res.render后,所有问题均得到解决。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 23:41:11