导引
最近遇到了一个需要在yog2框架中增加websocket服务的案子,此文主要讲在案子过程中碰到的一些问题和解决方案
前言
一、如果你只是想要解决socket.io与express共享解析session中间件的问题,本文运用了此文的解决方案:How to share sessions with Socket.IO 1.x and Express 4.x? — Epeli的答案。简单概括一下就是:
var session = require("express-session");
var sessionMiddleware = session({
store: new RedisStore({}), // XXX redis server config
secret: "keyboard cat",
});
// sio是socket.io的实例
sio.use(function(socket, next) {
sessionMiddleware(socket.request, socket.request.res, next);
});
// app是express的实例
app.use(sessionMiddleware);
二、由于此文和yog2这个框架高度相关,如需了解yog2请移步 https://github.com/fex-team/yog2
yog2 是一个专注于 Node.js UI 中间层的应用框架。它基于 express 和 fis 开发,在享受 express 的灵活扩展能力和 fis 强大的前端工程化能力的同时,引入了自动路由、app 拆分以及后端服务管理模块来保证UI中间层的快速开发与稳定可靠。
我的做法
需要在yog框架中增加websocket服务,我的做法是在yog的入口文件yog/app.js
中将socket.io服务器绑定在原有express的server上(也可以绑定在新端口如8081上,看你喜欢),然后在别处[1] 进行业务逻辑和路由
[1] :别处的意思是将业务代码最好放在别的文件夹里,这个根据你们的项目自己决定
我遇到的问题
socket.io服务器的中间件(主要是解析/验证session的那个)需要重新去写,当前项目使用配置过的express-session
中间件完成对session的解析,这完全是重复造轮子
对此问题我的想法
让socket.io服务器共享express的中间件!
受Epeli的答案启发,可以将express-session
模块定义的中间件提出来给socket.io用。
但是问题来了,yog2已经对中间件的摆放位置进行了一下处理,主要在yog/conf/plugins/http.js
内实现(见下文结构树,或见官方文档),如何将已定义的中间件提出来呢?
解决方案
新建自己的session解析中间件模块mysession
,在yog2的结构树中的摆放位置如下
├── yog
├── app
├── conf
│ ├── plugins
│ │ ├── http.js
│ │ ├── dispatcher.js
│ │ ├── log.js
│ │ ├── view.js
│ │ ├── mysession.js - mysession模块的配置文件
├── plugins
│ ├── mysession
│ │ ├── index.js - mysession模块文件
├── static
├── views
└── app.js
模块文件放置入yog/plugins/mysession/index.js
,详见用户插件相关文档
module.exports.mysession = function(app, conf){
return function(){
app.use(conf);
};
};
那么坑来了,官方文档里说
app为yog.app对象,即Express的app
conf为插件的配置项
module.exports后的属性名就是插件的真实名称
app
参数解释很清楚,那么conf
参数是啥呢?!
通过安装中间件文档末尾中提到的session模块
yog2 plugin install session
我发现,这个conf
参数将从yog/conf/plugins/你的模块名.js
中export的变量中获取。
一个字,坑!
于是照葫芦画瓢,将mysession
模块的配置文件放置入yog/conf/plugins/mysession.js
(你可以用你自己的session解析模块,比如connect-memcached
也可以,此处用session-file-store
)
// example using session-file-store to store session
var maxAge = 30 * 24 * 3600 * 1000;
var ttl = maxAge / 1000;
var sessionFileStoreGenerator = require('session-file-store');
var SessionFileStore = sessionFileStoreGenerator(session);
sessionStore = new SessionFileStore({ ttl: ttl });
var conf = session({
secret: 'mysession-key',
store: sessionStore,
resave: false,
saveUninitialized: true,
cookie: {
maxAge: maxAge
}
});
module.exports.mysession = conf;
最后在yog/conf/plugins/http.js
中加入这个自定义中间件
// ...
module.exports.http = {
middleware: [
// ...
'views',
'methodOverride',
'mysession', //加入我的新中间件
'dispatcher',
'notFound',
'error',
// ...
]
};
// ...
至此,已完成将session解析中间件提取出来,剩下的就是如Epeli的答案中提到的,使用如下代码共用这个中间件
sio.use(function(socket, next) {
sessionMiddleware(socket.request, socket.request.res, next);
});
具体实现代码如下:编辑入口文件yog/app.js
var yog = require('yog2-kernel');
// 此处载入这个共享的session解析中间件
var sessionMiddleware = require('./conf/plugins/mysession.js').mysession;
var app = yog.bootstrap({
rootPath: __dirname
}, function(){
var server = yog.server = app.listen(process.env.PORT || 8080);
// 以下是为socket.io新加入的代码
// 此处绑定在express的server实例上,你也可以绑定一个新端口
var sio = require('socket.io')(server);
sio.use(function(socket, next) {
sessionMiddleware(socket.request, socket.request.res, next);
});
require('../你的socket.io业务逻辑处理与路由代码文件')(sio);
// 以上是为socket.io新加入的代码
});
结语
当然,我对自己的这些解决方案也不是完美认同,比如:
编辑入口文件某些情况下并不是best practice,也可以在别处启动socket服务。
通过改变yog2的已有架构来使其更好的支持代码重用
在入口文件中引用模块也是一个很糟糕的做法,有没有更优雅的做法呢?
对于在yog2内使用socket的问题、共享中间件的问题等,一定还有更好的解决方案,所以在此抛砖引玉一下,大家都会怎么做?
更多兴趣阅读
在探索的过程中,读到一篇好答案,关于express的session解析模块的sign机制。
感兴趣的请移步 Why is it insecure to store the session ID in a cookie directly?