tornado 06集成session - nuanxin1111/react GitHub Wiki
6.1 SESSION和COOKIE
什么cookie
cookie分为二种
- 以文件方式存在硬盘空间上的长期性的cookie
- 停留在浏览器所占内存中的临时性的cookie
浏览网站时,你会经常发现网站登录的地方,会有提示,问你是不是要记住自己的登录状态,像这种情况,登录时填写的一些信息会被以文件的方式存放在客户端的硬盘上。 当用户登录后,session会在cookie端产生一个session_id,这个session_id是存于浏览器所占用的内存当中。当你关闭浏览器后,session_id也要消失了。 cookie采用的是在客户端保持状态的方案,它是客户端的会话状态的一种储存机制。它是服务器在本地机器上存储的小段文本或者是内存中的一段数据,并随每一个请求发送至同一个服务器。IETF RFC 2965 HTTP State Management Mechanism 是通用cookie规范。网络服务器用HTTP头信息向客户端发送cookies,在客户终端,浏览器解析这些cookies并将它们保存为一个本地文件,或者本地内存中数据,它会自动将同一服务器的任何请求缚上这些cookies,由于采用服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制借助于cookie机制来达到保存标识的目的,这样就可以解决HTTP协议无状态的缺陷。
什么是session
session是一种服务器端的信息管理机制,它把这些文件信息以文件的形势存放在服务器的硬盘空间上,这种情况是默认的,可以用memcache或redis把这种数据放到内存里面。 其中一种实现方式是,当客户端向服务器发出请求时,要求服务器端产生一个session时,服务器端会先检查一下,客户端的cookie里面有没有session_id,是否已经过期。如果有这样的session_id的话,服务器端会根据cookie里的session_id把服务器的session检索出来。如果没有这样的session_id的话,服务器端会重新建立一个。SESSIONID是一串加了密的字符串,它的生成按照一定的规则来执行。同一客户端启动二次生成这个字符串的话, SESSIONID是不一样的。
6.2 在tornado中集成session
资料:http://developer.51cto.com/art/201405/438313.htm 我们把这个轮子集成到我们的项目中,实现登录。 首先util/session.py
import tornado.web
import hmac
import ujson
import hashlib
import redis
import uuid
#继承字典,session存取模仿字典使用
class SessionData(dict):
#初始化提供session_id和hmac_key
def __init__(self, session_id, hmac_key):
self.session_id = session_id
self.hmac_key = hmac_key
#继承SessionData
class Session(SessionData):
#初始化,绑定session_manager和tornado对应的handler
def __init__(self, session_manager, request_handler):
self.session_manager = session_manager
self.request_handler = request_handler
try:
#正常访问
current_session = session_manager.get(request_handler)
except InvalidSessionException:
current_session = session_manager.get()
#取出current_session中的数据
for key, data in current_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 方法
def save(self):
self.session_manager.set(self.request_handler, self)
class SessionManager(object):
def __init__(self, secret, store_options, session_timeout):
self.secret = secret
self.session_timeout = session_timeout
try:
if store_options['redis_pass']:
self.redis = redis.StrictRedis(host=store_options['redis_host'], port=store_options['redis_port'], password=store_options['redis_pass'])
else:
self.redis = redis.StrictRedis(host=store_options['redis_host'], port=store_options['redis_port'])
except Exception as e:
print e
def _fetch(self, session_id):
try:
session_data = raw_data = self.redis.get(session_id)
if raw_data != None:
self.redis.setex(session_id, self.session_timeout, raw_data)
session_data = ujson.loads(raw_data)
if type(session_data) == type({}):
return session_data
else:
return {}
except IOError:
return {}
def get(self, request_handler = None):
if (request_handler == None):
session_id = None
hmac_key = None
else:
session_id = request_handler.get_secure_cookie("session_id")
hmac_key = request_handler.get_secure_cookie("verification")
if session_id == None:
session_exists = False
session_id = self._generate_id()
hmac_key = self._generate_hmac(session_id)
else:
session_exists = True
check_hmac = self._generate_hmac(session_id)
if hmac_key != check_hmac:
raise InvalidSessionException()
session = SessionData(session_id, hmac_key)
if session_exists:
session_data = self._fetch(session_id)
for key, data in session_data.iteritems():
session[key] = data
return session
def set(self, request_handler, session):
request_handler.set_secure_cookie("session_id", session.session_id)
request_handler.set_secure_cookie("verification", session.hmac_key)
session_data = ujson.dumps(dict(session.items()))
self.redis.setex(session.session_id, self.session_timeout, session_data)
def _generate_id(self):
new_id = hashlib.sha256(self.secret + str(uuid.uuid4()))
return new_id.hexdigest()
def _generate_hmac(self, session_id):
return hmac.new(session_id, self.secret, hashlib.sha256).hexdigest()
class InvalidSessionException(Exception):
pass
现在我们在我们的项目中把这个东西用起来,首先我们已经在settings中配置了关于redis的部分,
#redis设置
"store_options":{
'redis_host':'localhost',
'redis_port':6379,
'redis_pass':'',
},
然后我们在server.py中初始化:
class Application(tornado.web.Application):
def __init__(self):
handlers = urls.urls
setting = settings.settings
#connect mongo
client = pymongo.MongoClient(setting['mongo_options']['hostaddress'])
self.myMongodb = client[setting['mongo_options']['collection']]
tornado.web.Application.__init__(self, handlers, **setting)
self.session_manager = session.SessionManager(
setting['session_secret'],
setting['store_options'],
setting['session_timeout']
)
然后我们去看看session.py中的SessionManager中的部分:
self.redis = redis.StrictRedis(host=store_options['redis_host'], port=store_options['redis_port'], password=store_options['redis_pass'])
需要安装redis模块
pip install redis
我们在SessionManager的__init__中对redis完成了连接。
6.3 用session实现的登录
好了,现在看看我们session的简单应用,用session实现的登录handler .
class LoginHandler(BaseHandler):
def get(self):
logging.info("login html")
self.render("login.html")
def post(self):
"""handle the login request
"""
username = self.get_argument('username')
passwd = self.get_argument('passwd')
db = self.application.myMongodb
user = db.users.find_one({'name':username, 'passwd':passwd})
if user is not None:
del user['_id']
context = {'status':'success'}
#存储进session
self.session['username'] = user['name']
self.session.save()
self.write(json.dumps(context))
else:
context = {'status':'error'}
self.write(json.dumps(context))
很简单的一个例子,我们判断用户的登录是否合法,如果mongodb数据库中有这条纪录,那么我们把username存储进session. 需要登录使用session的handler都需要继承一个我们自己写的BaseHandler,看看在这个类中我们做了哪些操作.
class BaseHandler(tornado.web.RequestHandler):
def __init__(self, *argc, **kwarg):
super(BaseHandler, self).__init__(*argc, **kwarg)
#定义handler的session,每次访问都会初始化一个Session
self.session = session.Session(self.application.session_manager, self)
def get(self):
"""捕获404
"""
self.send_error(404)
def get_current_user(self):
"""复写get_current_user方法,在使用登录装饰器的时候判断是否登录
"""
return self.session.get('username')
def write_error(self, status_code, **kwargs):
"""重写404错误页
"""
if status_code == 404:
self.render("public/404.html")
elif status_code == 500:
self.render("public/500.html")
else:
self.write('error' + str(status_code))
我们可以在404.html和500.html中做一些自定义的信息,来使提示变得更友善。我们重写了get_current_user方法,在需要登录才能访问的模块需要继承这个类,然后需要使用装饰器@tornado.web.authenticated. 我们在初始化的使用都初始了一个Session供使用。
session生命周期,我们在settings中定义
#session生存周期 seconds
"session_timeout":30*60,
注意这个的单位是秒,在控制session的生命周期的时候在这儿控制。