情况是这样的,在开发的过程中出现了一个很奇怪的 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 之类的。