剖析Python的Tornado框架中session支持的实现代码
tornado里面没有session?不,当然有~我知道github上肯定有人帮我写好了~O(∩_∩)O~
于是乎,找到下面这个项目,用memcached实现tornado的session。光会用可不行啊,让我们看看是怎么写的~
项目地址:tornado-memcached-sessions
让我们先从demo看起....
app.py中:
首先可以注意到,这里定义了一个新的Application类,继承于tornado.web.Application,在该类的初始化方法中,设定了应用参数settings,之后初始化父类和session_manager.(这是什么?暂时不管它...)
classApplication(tornado.web.Application): def__init__(self): settings=dict( #设定cookie_secret,用于secure_cookie cookie_secret="e446976943b4e8442f099fed1f3fea28462d5832f483a0ed9a3d5d3859f==78d", #设定session_secret用于生成session_id session_secret="3cdcb1f00803b6e78ab50b466a40b9977db396840c28307f428b25e2277f1bcc", #memcached地址 memcached_address=["127.0.0.1:11211"], #session过期时间 session_timeout=60, template_path=os.path.join(os.path.dirname(__file__),"templates"), static_path=os.path.join(os.path.dirname(__file__),"static"), xsrf_cookies=True, login_url="/login", ) handlers=[ (r"/",MainHandler), (r"/login",LoginHandler) ] #初始化父类tornado.web.Application tornado.web.Application.__init__(self,handlers,**settings) #初始化该类的session_manager self.session_manager=session.SessionManager(settings["session_secret"],settings["memcached_address"],settings["session_timeout"])
在下面的LoginHandler中我们可以看到session的使用:
classLoginHandler(BaseHandler): defget(self): self.render("login.html") defpost(self): #以字典的键值对形式存取 self.session["user_name"]=self.get_argument("name") #修改完要调用session的save,否则等于没有修改哦... self.session.save() self.redirect("/")
从使用来看是不是非常简洁和清晰?那么,细心的你是不是发现现在的handler没有继承于tornado.web.RequestHandler?带着强烈的探(zuo)索(si)精神我们打开了base.py。天啊,好短....(噢,你想到哪里去了...)
BaseHandler的方法只是初始化,并重写了get_current_user的用于用户登录验证的方法。
classBaseHandler(tornado.web.RequestHandler): def__init__(self,*argc,**argkw): super(BaseHandler,self).__init__(*argc,**argkw) #定义handler的session,注意,根据HTTP特点,每次访问都会初始化一个Session实例哦,这对于你后面的理解很重要 self.session=session.Session(self.application.session_manager,self) #这是干嘛的?用于验证登录...请google关于tornado.web.authenticated,其实就是tornado提供的用户验证 defget_current_user(self): returnself.session.get("user_name")
看到这里,是不是心满意足?噢,我终于理解了!。。。喂,说好的探(zuo)索(si)精神呢?关键在于session.py啊!你一脸茫然地回过了头....
首先看看需要的库:
pickle一个用于序列化反序列化的库(听不懂?你直接看成和json一样作用就行了...)
hmac和hashlib用于生成加密字符串
uuid用于生成一个唯一id
memcache Python的memcache客户端
这里面有三个类,SessionDataSession和SessionManager。先看最简单的SessionData。
SessionData用于以字典的结构存储session数据,继承于字典,其实只比字典多了两个成员变量:
#继承字典,因为session的存取类似于字典 classSessionData(dict): #初始化时提供sessionid和hmac_key def__init__(self,session_id,hmac_key): self.session_id=session_id self.hmac_key=hmac_key
然后就是真正的Session类了。Session类继承于SessionData,注意,它还是十分像内置类型字典,只是重写了自己的初始化方法,并定义了save接口——用于保存修改后的session数据。
#继承SessionData类 classSession(SessionData): #初始化,绑定session_manager和tornado的对应handler def__init__(self,session_manager,request_handler): self.session_manager=session_manager self.request_handler=request_handler try: #正常是获取该session的所有数据,以SessionData的形式保存 current_session=session_manager.get(request_handler) exceptInvalidSessionException: #如果是第一次访问会抛出异常,异常的时候是获取了一个空的SessionData对象,里面没有数据,但包含新生成的 #session_id和hmac_key current_session=session_manager.get() #取出current_session中的数据,以键值对的形式迭代存下 forkey,dataincurrent_session.iteritems(): self[key]=data #保存下session_id self.session_id=current_session.session_id #以及对应的hmac_key self.hmac_key=current_session.hmac_key #定义save方法,用于session修改后的保存,实际调用session_manager的set方法 defsave(self): self.session_manager.set(self.request_handler,self)
__init__方法比较难理解,基本流程是定义自己的session_manager和handler处理对象。然后通过session_manager获得已有的session数据,用这些数据初始化一个访问的用户的session,如果用户是第一次访问,那么他拿到的是一个新的SessionData对象,因为有可能是新用户,所以这里要对session_id和hmac_key(什么鬼)进行赋值。
而save方法是提供了对修改session数据后的保存接口,实际是调用session_manager的set方法,具体实现先不考虑。
看到这两个类,你就应该对session的工作有基本理解,可以从用户访问的流程来考虑。注意BaseHandler这个入口,每个用户的访问都是一次HTTP请求。当用户第一次访问或者上一次的session过期了,这时用户访问时tornado建立了一个handler对象(该handler一定继承于BaseHandler),并且在初始化时建立了一个session对象,因为是新访问,所以目前session里面没有数据,在之后采用键/值对的形式读写session(不要忘了Session具有字典的所有操作),修改后通过save方法保存session。如果用户不是新访问,那么也是按照上述的流程,不过session初始化时把之前的数据取出来保存在该实例中。当用户结束访问,HTTP断开连接,handler实例销毁,session实例销毁(注意,是实例销毁,不是数据销毁)。
下面准备讲SessionManager是吧,来~一个一个函数看~
首先是初始化,设置密钥,memcache地址,session超时时间。
#初始化需要一个用于session加密的secret,memcache地址,session的过期时间 def__init__(self,secret,memcached_address,session_timeout): self.secret=secret self.memcached_address=memcached_address self.session_timeout=session_timeout
接着是_fetch方法,以session_id 为键从memcached中取出数据,并用pickle反序列化解析数据:
#该方法用session_id从memcache中取出数据 def_fetch(self,session_id): try: #连接memcache服务器 mc=memcache.Client(self.memcached_address,debug=0) #获取数据 session_data=raw_data=mc.get(session_id) ifraw_data!=None: #为了重新刷新timeout mc.replace(session_id,raw_data,self.session_timeout,0) #反序列化 session_data=pickle.loads(raw_data) #如果拿到的数据是字典形式,才进行返回 iftype(session_data)==type({}): returnsession_data else: return{} exceptIOError: return{}
get经过安全检查后,以SessionData的形式返回memcached的数据(调用了_fetch)方法。
defget(self,request_handler=None): #获取对应的session_id和hmac_key if(request_handler==None): session_id=None hmac_key=None else: #session的基础还是靠cookie session_id=request_handler.get_secure_cookie("session_id") hmac_key=request_handler.get_secure_cookie("verification") #session_id不存在的时候则生成一个新的session_id和hmac_key ifsession_id==None: session_exists=False session_id=self._generate_id() hmac_key=self._generate_hmac(session_id) else: session_exists=True #检查hmac_key check_hmac=self._generate_hmac(session_id) #不通过则抛出异常 ifhmac_key!=check_hmac: raiseInvalidSessionException() #新建SessionData对象 session=SessionData(session_id,hmac_key) ifsession_exists: #通过_fetch方法获取memcache中该session的所有数据 session_data=self._fetch(session_id) forkey,datainsession_data.iteritems(): session[key]=data returnsession
至于set方法,是为了更新memcached的数据。
#设置新的session,需要设置handler的cookie和memcache客户端 defset(self,request_handler,session): #设置浏览器的cookie request_handler.set_secure_cookie("session_id",session.session_id) request_handler.set_secure_cookie("verification",session.hmac_key) #用pickle进行序列化 session_data=pickle.dumps(dict(session.items()),pickle.HIGHEST_PROTOCOL) #连接memcache服务器 mc=memcache.Client(self.memcached_address,debug=0) #写入memcache mc.set(session.session_id,session_data,self.session_timeout,0)
最后的两个函数,一个是生成session_id,另一个用session_id与密钥加密后生成一个加密字符串,用于验证。
#生成session_id def_generate_id(self): new_id=hashlib.sha256(self.secret+str(uuid.uuid4())) returnnew_id.hexdigest() #生成hmac_key def_generate_hmac(self,session_id): returnhmac.new(session_id,self.secret,hashlib.sha256).hexdigest()
我们在哪里初始化了SessionManager呢?还记得第一篇里面的Application类吗?噢...快回去翻翻。