情况是这样的,在开发的过程中出现了一个很奇怪的 BUG,现象是:在输入验证码的时候,不论怎么样第一次输入的验证码总是错误的。
需要了解的问题是 express 中的 session 的解析依赖 cookieParser,首先是从 cookie 中读取加密 connect sid,再通过 cookieParser 解析成一个对应的 session id,该 session id 保存在 req.sessionID 中(因此 cookieParser中间件应该放到 session 之前)。在通过 session 中间件的时候,首先通过 session 的 Store 对象来读取当前的 session 数据,所以当多个请求并发过来的时候,他们拿到的会是同一份 session 数据。每个协议调用在 res.end() 的时候这个阶段 session 的数据会被自动 save 一次(req.session.save() 可以主动保存)。
也就是说在获取验证码的协议(该协议在 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 之类的。