Session In Webpy

Webpy中Session的实现原理。

虽然工作中使用的是django,但是自己并不喜欢那种大而全的东西~什么都给你准备好了,自己好像一个机器人一样赶着重复的基本工作,从在学校时候就养成了追究原理的习惯,从而有了这篇session的使用和说明。

webpy中的session

下面为官方的例子,用session来存储页面访问的次数,从而实现对访问次数的记录。

需要注意的是,官方说明在调试情况下,session并不能正常的运行,所以需要在非调试摸下测试,那么就有了下面的这个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import web

#非调试模式
web.config.debug = False
urls = (
"/count", "count",
"/reset", "reset"
)
app = web.application(urls, locals())
session = web.session.Session(app, web.session.DiskStore('sessions'), initializer={'count': 0})

class count:
def GET(self):
session.count += 1
return str(session.count)

class reset:
def GET(self):
session.kill()
return ""

if __name__ == "__main__":
app.run()

在官方文档中,对上述debug模式的现象给出了这样的解释:

session与调试模试下的重调用相冲突(有点类似firefox下著名的Firebug插件,使用Firebug插件分析网页时,会在火狐浏览器之外单独对该网页发起请求,所以相当于同时访问该网页两次)
  
为了解决上述问题,官方给出了进一步的解决方法,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import web

urls = ("/", "hello")

app = web.application(urls, globals())

if web.config.get('_session') is None:
session = web.session.Session(app, web.session.DiskStore('sessions'), {'count': 0})
web.config._session = session
else:
session = web.config._session

class hello:
def GET(self):
print 'session', session
session.count += 1
return 'Hello, %s!' % session.count

if __name__ == "__main__":
app.run()

由于web.session.Session会重载两次,但是在上面的_session并不会重载两次,因为上面多了一个判断_session是否存在于web.config中。

其实,在web.py文件中,定义了config,而Storage在下面的图中并没有特殊的结果,像字典一样~

1
2
3
4
5
#web.py
config = storage()

#utils.py
storage = Storage

webpy的子程序中使用session

虽然官方文档中提到,只能在主程序中使用session,但是通过添加__init__.py可以条用到该页面的session,也就是说一样使用session。

官方给出的方法更加合理化一点,通过应用处理器,加载钩子(loadhooks)

在webpy中,应用处理器为app.add_processor(my_processor),下面的代码添加到上述的完整例子中,可以再处理请求前和处理请求后分别条用my_loadhook()`和my_unloadhook()`,

1
2
3
4
5
6
7
8
def my_loadhook():
print "my load hook"

def my_unloadhook():
print "my unload hook"

app.add_processor(web.loadhook(my_loadhook))
app.add_processor(web.unloadhook(my_unloadhook))

从而,可以再web.loadhook()中加载session信息,在处理之前从web.ctx.session中获取session了,甚至可以在应用处理器中添加认证等操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#main.py
def session_hook():
  web.ctx.session = session
app.add_processor(web.loadhook(session_hook))

#views.py
class edit:
def GET(self):
try:
session = web.ctx.session
username = session.username
if not username:
return web.redirect('/login')
except Exception as e:
return web.redirect('/login')
return render_template('edit.html')

session id

对于服务器来说,怎样才能区分不同客户端呢,怎样才能区分不同客户端的session呢?

是通过sessionid来实现的,最初我还傻傻的分不清session和cookie,以及不同用户之间的信息室如何分配的!

生成sessionid的代码段,其中包含了随机数、时间、ip以及秘钥,

1
2
3
4
5
6
7
8
9
10
11
def _generate_session_id(self):
"""Generate a random id for session"""
while True:
rand = os.urandom(16)
now = time.time()
secret_key = self._config.secret_key
session_id = sha1("%s%s%s%s" % (rand, now, utils.safestr(web.ctx.ip), secret_key))
session_id = session_id.hexdigest()
if session_id not in self.store:
break
return session_id

  
在客户端访问服务器时,服务器会根据上述信息来计算一个针对客户端唯一的sessionid,并通过cookie保存在客户端中。

客户端用cookie保存了sessionID,当我们请求服务器的时候,会把这个sessionID一起发给服务器,服务器会到内存中搜索对应的sessionID,如果找到了对应的 sessionID,说明我们处于登录状态,有相应的权限;如果没有找到对应的sessionID,这说明:要么是我们把浏览器关掉了(后面会说明为什 么),要么session超时了(没有请求服务器超过20分钟),session被服务器清除了,则服务器会给你分配一个新的sessionID。你得重新登录并把这个新的sessionID保存在cookie中。

session的结构

上面提到了session在webpy中式一种dict的方式存储,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
class Session(object):
"""Session management for web.py
"""
__slots__ = [
"store", "_initializer", "_last_cleanup_time", "_config", "_data",
"__getitem__", "__setitem__", "__delitem__"
]

def __init__(self, app, store, initializer=None):
self.store = store
self._initializer = initializer
self._last_cleanup_time = 0
self._config = utils.storage(web.config.session_parameters)
self._data = utils.threadeddict()

self.__getitem__ = self._data.__getitem__
self.__setitem__ = self._data.__setitem__
self.__delitem__ = self._data.__delitem__

if app:
app.add_processor(self._processor)

def __contains__(self, name):
return name in self._data

def __getattr__(self, name):
return getattr(self._data, name)

def __setattr__(self, name, value):
if name in self.__slots__:
object.__setattr__(self, name, value)
else:
setattr(self._data, name, value)

def __delattr__(self, name):
delattr(self._data, name)

def _processor(self, handler):
"""Application processor to setup session for every request"""
self._cleanup()
self._load()

try:
return handler()
finally:
self._save()

def _load(self):
"""Load the session from the store, by the id from cookie"""
cookie_name = self._config.cookie_name
cookie_domain = self._config.cookie_domain
cookie_path = self._config.cookie_path
httponly = self._config.httponly
self.session_id = web.cookies().get(cookie_name)

# protection against session_id tampering
if self.session_id and not self._valid_session_id(self.session_id):
self.session_id = None

self._check_expiry()
if self.session_id:
d = self.store[self.session_id]
self.update(d)
self._validate_ip()

if not self.session_id:
self.session_id = self._generate_session_id()

if self._initializer:
if isinstance(self._initializer, dict):
self.update(deepcopy(self._initializer))
elif hasattr(self._initializer, '__call__'):
self._initializer()

self.ip = web.ctx.ip

def _check_expiry(self):
# check for expiry
if self.session_id and self.session_id not in self.store:
if self._config.ignore_expiry:
self.session_id = None
else:
return self.expired()

def _validate_ip(self):
# check for change of IP
if self.session_id and self.get('ip', None) != web.ctx.ip:
if not self._config.ignore_change_ip:
return self.expired()

def _save(self):
if not self.get('_killed'):
self._setcookie(self.session_id)
self.store[self.session_id] = dict(self._data)
else:
self._setcookie(self.session_id, expires=-1)

def _setcookie(self, session_id, expires='', **kw):
cookie_name = self._config.cookie_name
cookie_domain = self._config.cookie_domain
cookie_path = self._config.cookie_path
httponly = self._config.httponly
secure = self._config.secure
web.setcookie(cookie_name, session_id, expires=expires, domain=cookie_domain, httponly=httponly, secure=secure, path=cookie_path)

def _generate_session_id(self):
"""Generate a random id for session"""

while True:
rand = os.urandom(16)
now = time.time()
secret_key = self._config.secret_key
session_id = sha1("%s%s%s%s" %(rand, now, utils.safestr(web.ctx.ip), secret_key))
session_id = session_id.hexdigest()
if session_id not in self.store:
break
return session_id

def _valid_session_id(self, session_id):
rx = utils.re_compile('^[0-9a-fA-F]+$')
return rx.match(session_id)

def _cleanup(self):
"""Cleanup the stored sessions"""
current_time = time.time()
timeout = self._config.timeout
if current_time - self._last_cleanup_time > timeout:
self.store.cleanup(timeout)
self._last_cleanup_time = current_time

def expired(self):
"""Called when an expired session is atime"""
self._killed = True
self._save()
raise SessionExpired(self._config.expired_message)

def kill(self):
"""Kill the session, make it no longer available"""
del self.store[self.session_id]
self._killed = True

在webpy的session中,存储方式包括两种DiskStore和DBStore,分别为硬盘存储和数据库存储。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class DiskStore(Store):
"""
Store for saving a session on disk
"""
...

class DBStore(Store):
"""
Store for saving a session in database
Needs a table with following columns:
session_id CHAR(128) UNIQUE NOT NULL,
atime DATATIME NOT NULL DEFAULT current_timestamp,
data TEXT
"""
...

```  

而session的存储也可以看出来,把sessionid作为key来存储session信息

```python
def _save(self):
if not self.get('_killed'):
self._set_cookie(self.session_id)
self.store[self.session_id] = dict(self._data)
else:
self._setcookie(self.session_id, expires=-1)

  

参考

http://doc.outofmemory.cn/python/webpy-cookbook/

http://webpy.org/docs/0.3/tutorial