webpy使用筆記(二) session的使用
webpy使用系列之session的使用,雖然工作中使用的是django,但是自己並不喜歡那種大而全的東西~什么都給你准備好了,自己好像一個機器人一樣趕着重復的基本工作,從在學校時候就養成了追究原理的習慣,從而有了這篇session的使用和說明。
PS:其實有些總結的東西挺好的,想分享給大家看,而不是枯燥的代碼,這東西說實話對其他人用處不大,但都被移除首頁了~~
webpy中的session
下面為官方的例子,用session來存儲頁面訪問的次數,從而實現對訪問次數的記錄。
(PS,這里記錄是針對一個客戶端來說的訪問次數,而不是官方文檔說的統計有多少人正在使用session,因為每個客戶端的session並不相同,服務器會根據不同的sessionid來區分不同的客戶端的session)
需要注意的是,官方說明在調試情況下,session並不能正常的運行,所以需要在非調試摸下測試,那么就有了下面的這個例子。
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插件分析網頁時,會在火狐瀏覽器之外單獨對該網頁發起請求,所以相當於同時訪問該網頁兩次)
為了解決上述問題,官方給出了進一步的解決方法,如下
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在下面的圖中並沒有特殊的結果,像字典一樣~
#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()。
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))
結果如下,我在處理中打印了session:
從而,可以再web.loadhook()中加載session信息,在處理之前從web.ctx.session中獲取session了,甚至可以在應用處理器中添加認證等操作。
#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')
sessionid
對於服務器來說,怎樣才能區分不同客戶端呢,怎樣才能區分不同客戶端的session呢?
是通過sessionid來實現的,最初我還傻傻的分不清session和cookie,以及不同用戶之間的信息室如何分配的!
如上圖,是生成sessionid的代碼段,其中包含了隨機數、時間、ip以及秘鑰。
在客戶端訪問服務器時,服務器會根據上述信息來計算一個針對客戶端唯一的sessionid,並通過cookie保存在客戶端中。
客戶端用cookie保存了sessionID,當我們請求服務器的時候,會把這個sessionID一起發給服務器,服務器會到內存中搜索對應的sessionID,如果找到了對應的 sessionID,說明我們處於登錄狀態,有相應的權限;如果沒有找到對應的sessionID,這說明:要么是我們把瀏覽器關掉了(后面會說明為什 么),要么session超時了(沒有請求服務器超過20分鍾),session被服務器清除了,則服務器會給你分配一個新的sessionID。你得重新登錄並把這個新的sessionID保存在cookie中。
session的結構
上面提到了session在webpy中式一種dict的方式存儲,

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,分別為硬盤存儲和數據庫存儲。
而session的存儲也可以看出來,把sessionid作為key來存儲session信息
參考
http://doc.outofmemory.cn/python/webpy-cookbook/
http://webpy.org/docs/0.3/tutorial