客户端session vs 服务端session

session想来大家都不会陌生,session主要的作用就是用来记录用户状态,表明用户身份的,session的存储方式也有多样,最为传统的就是服务端保存session的内容,客户端浏览器cookie保存sessionid,服务端通过客户端每次http请求带上的cookie中的sessionid去找到对应此用户的session内容。当然我之前也发过一篇文章讲到过通过etag来做为sessionid,识别用户身份。相关文章链接:http://cnodejs.org/topic/5212d82d0a746c580b43d948

最近在做公司一个项目,我们用到了python的flask框架,一个轻量级,易上手的python web框架,有点类似nodejs中的expressjs,一些周边相关的模块集成的比expressjs好一些,题外话,写了阵python回来写node,代码那是各种整齐啊。
说重点,flask和我之前用过的其他框架有一点不同的是,它的session默认是完全保留在客户端浏览器中的,也就是说我往flask的session中写入数据,最终这些数据将会以json字符串的形式,经过base64编码写入到用户浏览器的cookie里,也就是说无须依赖第三方数据库保存session数据,也无需依赖文件来保存,这一点倒是挺有意思。

估计很多人要问我安全性问题了,session内容保存在客户端cookie,如何保证它的保密性、完整性和容量限制,保密性表示不被其他用户窥探,完整性表示不被恶意用户篡改,容量限制则表示cookie中必然无法保存过多的数据。

1、保密性,flask 这方面可以说是完全不设防,经过简单的base64编码,很容易就可以对它进行decode,然后看到其中的数据,不过话又说回来,session中本来记录的就是当前用户的一些相关身份信息和操作记录,就算被本用户看见又何妨,如果怕网络传输时被截获,那用户其他的一些 http 请求操作也照样被截获,无关乎客户端的cookie session设计

2、完整性,flask 利用开发者定义的一个私钥对数据进行简单的hash签名认证,所以我们会看到在 flask 保存在浏览器cookie中的session值,经过base64解码之后都是这样的:
{“userid”:12345}.xxxxxxxxxxxxx
json字符串后面的xxxx就是利用开发人员定义的密钥对前面json字符串进行hash生成的签名,所以数据的完整性只要保证一个足够强大的密钥不被泄露和就行了。

3、容量限制,这点确实是cookie session的不足,因为客户端cookie是有容量限制的,不可能像使用第三方数据库保存session那样可以存放大量的数据,不过一般我们开发一些web站点,session在大部分情况下主要是用来表示用户身份,比如一个 userid,一个头像地址,或者一个nickname等,真正用户的一些其他信息还是要通过userid去数据库获取的。

所以在开发一些对session保存容量要求不大,但是并发量比较大,而且需要多机器,多进程服务的项目来说,客户端session无疑是一个即轻松又节约设备的方法,至少节约了1台redis服务器。

我在开发完这个 flask 项目后,想到了node也应该在npm上有这样一个包,可惜我搜了下npmjs.org,似乎没有找到特别雷同的cookie session store的包,于是本着重复造轮的思想,写了一个让nodejs也支持客户端 cookie session保存的package,client-session,项目地址:
https://github.com/DoubleSpout/nodeClientSession
安装发放:
npm install client-session

client-session包主要解决了nodejs多进程下内存session无法同步的问题,让吊丝们的1G内存vps也能够不开redis,多进程同步session数据;同时在flask仅利用base64将session内容编码的基础上,做了一个小的加解密方法,让数据看上去更加安全一些,仅是安全一些,还是尽量不要保存用户密码,银行账户信息在里面哦。

client-session对数据加解密,base64编码以及签名和签名验证都是通过c++插件完成,所以相对于纯js性能上可能有那么点优势(没做过测试,不能妄下定论啊),另外在我的压力测试中,利用expressjs框架,多进程使用client-session要比使用本机开启redis数据库存session,性能高出20%左右,测试数据详见项目地址,client-session使用方法也很简单,一个结合expressjs框架返回计数器的代码例子:
var express = require(‘express’);
var path = require(‘path’)
var cs = require(‘client-session’);
var clientSession = cs(‘mysecretkey’);
var app = express();
app.use(clientSession.connect());//调用client-session中间件
app.get(‘/‘, function(req, res){
var count = req.csession[‘count’];
if(!count) count = 1;
else count++;
req.csession[‘count’] = count;
//这边不想暴力的改掉express框架的res.send方法
//就劳烦开发人员手动调用一下csflash,毕竟1台redis服务器是省下了
req.csflush();
res.send(count.toString());
});
app.listen(8124);
console.log(‘Server running at http://127.0.0.1:8124/');
利用ab命令:ab -c 500 -n 20000 http://192.168.150.3:8124/
压力测试内网的一台2cpu 6Gmen linux云主机
express + redis + 2 process session::
Requests per second: 380.54 [#/sec] (mean)

express + client-session +2 process session:
Requests per second: 492.25 [#/sec] (mean)
具体的client-session使用方法,详见项目github地址,有啥问题可以留言给我。

对于客户端session和服务端session的优劣特点,大家都明白了,最终如何选择还是看项目需求吧~

另外expressjs似乎没有在响应前触发的事件吧,比如:
res.on(‘beforeResponse’, function(){
//do something
})