关于 express 的 session 必须了解的同步问题(Nodejs版)
关于 express 的 session 必须了解的同步问题(Nodejs版)
情况是这样的,在开发的过程中出现了一个很奇怪的 BUG,现象是:在输入验证码的时候,不论怎么样第一次输入的验证码总是错误的。
在讨论这个问题之前,需要了解的是 express 中的 session 的运作流程。 session 的解析依赖 cookieParser,先是从 cookie 中读取加密 connect sid,再通过 cookieParser 解析成一个对应的 session id,该 session id 保存在 req.sessionID 中(因此 cookieParser中间件应该放到 session 之前)。
epxress.session 用的是 connect 的 session 中间件,而 session 中间件在起作用的时候,先是通过 session 的 Store 对象来读取当前的 session 数据,所以当多个请求并发过来的时候,他们拿到的会是同一份 session 数据。每个协议调用在 res.end() 的时候这个阶段 session 的数据会被自动 save 一次(req.session.save() 可以主动保存)。其内部的代码类似这样的:
function session(options){
// ...
// 根据 options 配置 session
// ...
return function session(req, res, next) {
// 如果已经有 session 的话跳过
if (req.session) return next();
/*
req.session 还没有则
通过 session 的 store 对象获取一份当前的 session 数据
保存在 req.session 中
...
*/
// 劫持 res.end 函数
var end = res.end;
res.end = function(data, encoding){
res.end = end;
// 如果没有 req.session 则直接调用原生方法返回
if (!req.session) return res.end(data, encoding);
// ...
// 如果有则保存 session 之后才调用 res.end
req.session.save(function(err){
// ...
res.end(data, encoding);
});
};
// ...
}
}
也就是说在获取验证码的协议(该协议在 session 中特别保存了一次验证码的数据),之后其他请求其他资源(比如js、图片之类)的协议中附带的 session(没有带验证码的数据)在 res.end() 的时候重复保存然后把开始的验证码数据给覆盖掉了。看起来就好像是一个不同步的问题一样,其实就是并发的时候数据重复写入了。
最后博主的验证码解决方案是,当用户 focus 到验证码输入框的时候再去请求验证码的图片。当然,还有一些其他关于这个问题可以作为解决方案的建议:
将静态资源的处理前置
将处理静态资源的 handler 放在 session 中间件的前面。例如:
app.use(express.static(__dirname + '/public')); // 读取静态资源在前
app.use(cookieParser('keyboard cat'));
app.use(session({ ... }); // session 在后
设置 session 的 ignore
如果你不能把一些 handler 移到 session 的前面,你也可以配置 session 中间件的 express.session.ignore 来忽略一些不使用 req.session 的路径,例如:
express.session.ignore.push('/individual/path');
app.use(express.session({ ... }));
把 session 去掉
如果你的 handler 没有写入 session (比如只是读取)的话,可以在调用 res.end 之前设置 req.session = null,这样就不会导致 session 被重复保存。
总之,建议与 session 有关的操作尽量要放在 post 中处理,如果需要类似 get 的同时处理的话,请先 use 之后再来。或者可以考虑定义一个规则,不符合规的则路由则过滤掉,例如所有有 session 操作的路由可以把他们的 path 的结尾定义为 .php 然后可以通过 use 来将非 .php 结尾的请求中的 session 设置为 null 之类的。
关于 express 的 session 必须了解的同步问题(Nodejs版)
问题背景
在开发过程中遇到的一个奇怪的 Bug:用户在输入验证码时,无论怎样,第一次输入的验证码总是错误的。
Express Session 运作流程
为了理解这个问题,我们需要了解 express 中的 session 的运作机制。session 的解析依赖于 cookieParser,它首先从 Cookie 中读取加密的 connect.sid,然后通过 cookieParser 解析成一个对应的 session id,该 session id 保存在 req.sessionID 中(因此 cookieParser 中间件应该放在 session 之前)。
express.session 使用的是 connect 的 session 中间件。该中间件在起作用时,通过 session 的 Store 对象来读取当前的 session 数据。当多个请求并发过来时,它们会共享同一个 session 数据。每个请求在 res.end() 时会自动保存一次 session 数据(也可以通过 req.session.save() 主动保存)。
问题分析
问题的核心在于并发请求时 session 数据的重复写入。具体来说,当用户请求验证码时,验证码的数据被保存到 session 中。然而,在后续的请求(如加载静态资源)中,这些请求也会尝试保存 session 数据,从而覆盖掉之前的验证码数据。
示例代码
const express = require('express');
const session = require('express-session');
const cookieParser = require('cookie-parser');
const app = express();
// 配置 cookieParser 和 session
app.use(cookieParser('your-secret-key'));
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: true,
cookie: { secure: false } // 在生产环境中应设置为 true
}));
// 验证码生成逻辑
app.get('/captcha', (req, res) => {
const captcha = generateCaptcha(); // 假设这是一个生成验证码的函数
req.session.captcha = captcha;
res.send(captcha);
});
// 处理验证码输入的 POST 请求
app.post('/verify', (req, res) => {
const userCaptcha = req.body.captcha;
if (userCaptcha === req.session.captcha) {
res.send('验证码正确');
} else {
res.send('验证码错误');
}
});
// 静态资源处理
app.use(express.static(__dirname + '/public'));
// 启动服务器
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
解决方案
-
将静态资源处理前置: 将处理静态资源的中间件放在
session中间件之前,确保静态资源不会影响session数据。app.use(express.static(__dirname + '/public')); // 读取静态资源在前 app.use(cookieParser('your-secret-key')); app.use(session({ ... })); // session 在后 -
设置 session 的 ignore: 如果无法将静态资源处理前置,可以配置
session中间件忽略某些路径。session.ignore.push('/individual/path'); app.use(session({ ... })); -
在不需要 session 的地方设置 req.session = null: 如果某个请求不需要操作
session,可以在调用res.end之前将req.session设置为null。if (!needsSessionHandling(req)) { req.session = null; }
总结
确保 session 相关的操作尽可能地放在 POST 请求中处理,或者通过合理的配置避免并发请求对 session 数据的干扰。
good
我想到了,应该是你的验证码中间件放错地方了。这个验证码中间件不应该成为一个全局的中间件,而应该只是对需要使用到验证码的页面起作用。
中间件在 express 里面是可以串着用的,把你的验证码中间件串在需要使用的 controller 之前就好了。
主贴中的任何解决办法感觉都是在为这个中间件的错误放置买单。
额,不仅仅是放置位置的问题。。因为 session 本身是全局的,所有在 session 中间件之后的协议都会拿到一份 session,即使我把验证码的功能限制到只有某个页面能用,但是并发起来其他同时的协议照样会六亲不认的 re-save
要做这个功能的话,就需要有一个路由去处理,而且判断可能是 1分钟判断一次,那么这个请求进来的时候会拿到一个 session ,在 res.end() 的时候这个 session 会 save 一次。
在你做这个判断的时候,应该用一个 get 请求来做,而 session 在面对 get 请求的时候为什么还要进行操作?不仅扰乱逻辑,还浪费性能啊。
你应该在你那个中间件里面,做个 req.method === ‘GET’ 的判断,如果为true,就直接 return 什么都不做。
这样无论是1)你的定时检测 2)还是你的静态资源,都不会触发所谓的 session save。
感谢楼主,最近刚好碰到这个问题。由于本人较懒,故采用 ”将静态资源的处理前置“ 的办法 :)
关于 Express 的 session 必须了解的同步问题,确实需要注意如何正确处理并发请求时的 session 保存问题。以下是一些关键点和解决方案的总结:
关键点
-
Session 工作原理:
- Express 的 session 使用
connect库。 session中间件会根据session ID从存储中读取 session 数据。- 并发请求时,这些请求可能会读取到相同的 session 数据。
- Express 的 session 使用
-
数据覆盖问题:
- 当多个请求并发时,可能会导致某些请求覆盖其他请求的 session 数据,特别是在
res.end()时进行保存操作。
- 当多个请求并发时,可能会导致某些请求覆盖其他请求的 session 数据,特别是在
解决方案
1. 将静态资源处理前置
确保静态资源的处理优先于 session 中间件:
app.use(express.static(__dirname + '/public'));
app.use(cookieParser('keyboard cat'));
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: true,
store: new MemoryStore()
}));
2. 设置 session 的忽略路径
可以配置 ignore 属性来忽略某些不需要 session 的路径:
express.session.ignore.push('/public/*');
app.use(express.session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: true,
store: new MemoryStore()
}));
3. 避免不必要的 session 保存
对于不需要保存 session 的请求,可以在 res.end() 之前设置 req.session = null:
app.get('/non-session-endpoint', (req, res) => {
req.session = null;
// 处理请求逻辑
});
示例代码
假设有一个简单的验证码验证功能,为了避免 session 数据被覆盖,我们可以将其放在一个独立的路由中:
app.use(express.static(__dirname + '/public'));
app.use(cookieParser('keyboard cat'));
app.get('/captcha', (req, res) => {
const captcha = generateCaptcha(); // 假设这是生成验证码的函数
req.session.captcha = captcha;
res.send(captcha);
});
app.post('/verify', (req, res) => {
const { input } = req.body;
if (req.session.captcha === input) {
res.send('验证码正确');
} else {
res.send('验证码错误');
}
});
以上就是关于 Express 的 session 必须了解的同步问题的一些关键点和解决方案。


