关于 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 必须了解的同步问题的一些关键点和解决方案。