2、高級應用html
2.1 web.ctxpython
獲取客戶端信息,好比:來源頁面、客戶端瀏覽器類型等。web
web.ctx基於 threadeddict類,又被叫作 ThreadDict。這個類建立了一個相似字典(dictionary-like)的對象,對象中的值都是與線程 id 相對應的。這樣作很妙,由於不少用戶同時訪問系統時,這個字典對象能作到僅爲某一特定的 HTTP 請求提供數據(由於沒有數據共享,因此對象是線程安全的)。瀏覽器
web.ctx保存每一個HTTP請求的特定信息,好比客戶端環境變量。假設,咱們想知道正在訪問某頁面的用戶從哪一個網頁跳轉而來的:緩存
class example: def GET(self): referer = web.ctx.env.get("HTTP_REFERER", "http://gogle.com") raise web.seeother(referer)
上述代碼用 web.ctx.env 獲取 HTTP_REFERER 的值。若是 HTTP_REFERER 不存在,就會將 google.com 作爲默認值。接下來,用戶就會被重定向回到以前的來源頁面。安全
web.ctx 另外一個特性,是它能夠被 loadhook 賦值。例如:當一個請求被處理時,會話(Session)就會被設置並保存在 web.ctx 中。因爲 web.ctx是線程安全的,因此咱們能夠象使用普通的 python 對象同樣,來操做會話(Session)。服務器
ctx中的數據成員 request environ 又被寫做.evn -- 包含標準WSGI環境變量的字典 home -- 應用的http根路徑(註釋:能夠理解爲應用的起始網址,協議+站點域名+應用所在路徑)例如:http://example.org/admin homedomain -- 應用所在站點(能夠理解爲協議+域名)http://example.org host -- 主機名(域名)+用戶請求的端口(若是沒有的話,就是默認的 80 端口),例如: example.org, example.org:8080 ip –- 用戶的 IP 地址,例如: xxx.xxx.xxx.xxx method – 所用的 HTTP 方法,例如: GET path –- 用戶請求路徑,它是基於當前應用的相對路徑。在子應用中,匹配外部應用的那部分網址將被去掉。例如:主應用在 code.py中,而子應用在 admin.py 中。在 code.py中, 咱們將/admin 關聯到admin.app。 在 admin.py 中, 將/stories 關聯到 stories類。在stories 中, web.ctx.path 就是/stories, 而非/admin/stories。形如: /articles/845 protocol –- 所用協議,例如: https query –- 跟在'?'字符後面的查詢字符串。若是不存在查詢參數,它就是一個空字符串。例如: ?fourlegs=good&twolegs=bad fullpath 能夠視爲 path + query – 包查詢參數的請求路徑,但不包括'homepath'。例如:/articles/845?fourlegs=good&twolegs=bad response status –- HTTP 狀態碼(默認是'200 OK') 401 Unauthorized 經受權 headers –- 包 HTTP 頭信息(headers)的二元組列表。 output –- 包響應實體的字符串。
2.2 應用處理器(Application processors)session
使用應用處理器加載鉤子(loadhook)和卸載鉤子(unloadhook)。多線程
web.py能夠在處理請求以前或以後,經過添加處理器(processor)來完成某些操做:app
def my_processor(handler): print "before handling" result = handler() print "after handling" return result app.add_processor(my_processor)
web.py也能夠經過加載鉤子(loadhook)和卸載鉤子(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))
上邊兩個例子,若是加在主應用裏面,則主應用和子應用的接口都會加上這個處理器;若是隻在某個子應用裏面加這個處理器,則只有這個子應用的接口被加了這個處理器。若是一個加在主應用,另外一個加載子應用,則主應用的屬於全局,不論訪問主應用合適訪問子應用都要走;子應用的不只要走主應用的處理器,還要走本身的處理器。順序以下:主befor 》子befor 》子after 》主after
2.3 web.background
web.background 和 web.backgrounder 都是 python 裝飾器,它可讓某個函式在一個單獨的 background 線程中運行,而主線程繼續處理當前的 HTTP 請求,並在稍後報告 background 線程的狀態(事實上,後臺函式的標準輸出(stdout)被返回給啓動該線程的"backrounder")。 譯註:我原本想將 background thread 翻譯爲後臺線程,後來認爲做者本意是想表達「被 background 修飾的函式所在的線程」,最後翻譯採用「background 線程」。 這樣,服務器就能夠在處理其餘 http 請求的同時,快速及時地響應當前客戶端請求。同時,background 線程繼續執行須要長時間運行的函式。
#!/usr/bin/env python # -*- coding: utf-8 -*- from web import run, background, backgrounder from datetime import datetime; now = datetime.now from time import sleep urls = (
'/', 'index',
) class index: @backgrounder def GET(self): print "Started at %s" % now() print "hit f5 to refresh!" longrunning() @background def longrunning(): for i in range(10): sleep(1) print "%s: %s" % (i, now()) if __name__ == '__main__': run(urls, globals())
在請求 http://localhost:8080/時,將自動重定向到相似http://localhost:8080/?_t=3080772748 的網址(t 後面的數字就是background 線程 id),接下來(在點擊幾回刷新以後)就會看到以下信息:
Started at 2008-06-14 15:50:26.764474
hit f5 to refresh!
0: 2008-06-14 15:50:27.763813
1: 2008-06-14 15:50:28.763861
2: 2008-06-14 15:50:29.763844
3: 2008-06-14 15:50:30.763853
4: 2008-06-14 15:50:31.764778
5: 2008-06-14 15:50:32.763852
6: 2008-06-14 15:50:33.764338
7: 2008-06-14 15:50:34.763925
8: 2008-06-14 15:50:35.763854
9: 2008-06-14 15:50:36.763789
web.py 在 background.threaddb 字典中保存線程信息。這就很容易檢查線程的狀態:
class threaddbviewer: def GET(self): for k, v in background.threaddb.items(): print "%s - %s" % ( k, v )
web.py 並不會主動去清空 threaddb 詞典,這使得輸出(如http://localhost:8080/?_t=3080772748)會一直執行,直到內存被用滿。
一般是在 backgrounder 函式中作線程清理工做,是由於 backgrounder能夠得到線程 id(經過 web.input()獲得"_t"的值,就是線程 id),從而根據線程 id 來回收資源。這是由於雖然 background 能知道本身什麼時候結束,但它沒法得到本身的線程 id,因此 background 沒法本身完成線程清理。
還要注意 How not to do thread local storage with Python 在 python 中如何避免多線程本地存儲 - 線程 ID 有時會被重用(可能會引起錯誤) 。
在使用 web.background 時,仍是那句話--「當心爲上」!
2.4 自定義NotFound消息
import web urls = ( ... ) app = web.application(urls, globals()) def notfound(): return web.notfound("Sorry, the page you were looking for was not found!" # return web.notfound(render.notfound()) # return web.notfound(str(render.notfound())) app.notfound = notfound
要返回自定義的NotFound消息,這麼作便可:
class example: def GET(self): raise web.notfound()
這個返回的是 404 Not Found
也能夠用一樣的方法自定義500錯誤消息:
def internalerror(): return web.internalerror("Bad, bad server. No donut for you." app.internalerror = internalerror
2.5 使用流來傳輸大文件
要用流的方式傳輸大文件,須要添加傳輸譯碼(Transfer-Eencoding)區塊頭,這樣才能一邊下載一邊顯示。不然,瀏覽器將緩存全部數據直到下載完畢才顯示。
若是這樣寫:直接修改基礎字符串(例中就是j),而後用yield返回——是沒有效果的。若是要用yield,就要向全部內容使用yield。由於這個函式此時是一個產生器。
# coding: utf-8 # 簡單流式服務器演示 # 使用time.sleep模擬大文件讀取 import web import time
urls = (
"/", "count_holder",
"/(.*)", "count_down",
)
app = web.application(urls, globals()) class count_down: def GET(self, count): # 這些頭使它在瀏覽器中工做 web.header("Content-Type", "text/html") web.header("Transfer-Encoding", "chunked") yield "<h2>Prepare for Launch!</h2>" j = "<li>Liftoff in %s...</li>" yield "<ul>" count = int(count) for i in range(count, 0, -1): out = j % i time.sleep(1) yield out yield "</ul>" time.sleep(1) yield "<h1>Life off</h1>" class count_holder: def GET(self): web.header("Content-Type", "text/html") web.header("Transfer-Encoding", "chunked") boxes = 4 delay = 3 countdown = 10 for i in range(boxes): output = "<iframe src='/%d' width='200' height='500'></iframe>"%(countdown - i) yield output time.sleep(delay) if __name__ == "__main__": app.run()
2.6 管理自帶的webserver日誌
咱們能夠用wsgilog來操做內置的webserver的日誌,並將其做爲中間件加到應用中。
寫一個Log類繼承wsgilog.WsgiLog,在__init__中把參數傳給基類。
以下:
import sys, logging from wsgilog from WsgiLog, LogI0 import config class Log(WsgiLog): def __init__(self, application): WsgiLog.__init__( self, application, logformat = "%(message)s", tofile = True, file = config.log_file, interval = config.log_interval, backups = config.log_backups ) sys.stdout = LogIO(self.logger, logging.INFO) sys.stderr = LogIO(self.logger, logging.ERROR)
接下來,當應用運行時,傳遞一個引用給上例中的 Log 類便可(假設上面代碼是'mylog'模塊的一部分,代碼以下):
from mylog import Log application = web.application(urls, globals()) application.run(Log)
2.7 用cheerypy提供SSL支持
import web from web.wsgiserver import CherryPyWSGIServer CherryPyWSGIServer.ssl_certificate = "path/to/ssl_certificate" CherryPyWSGIServer.ssl_private_key = "path/to/ssl_private_key" urls = ("/.*", "hello")
app = web.application(urls, globals()) class hello:
def GET(self):
return 'Hello, world!' if __name__ == "__main__":
app.run()
2.8 實時語言切換
首先你必須閱讀模板語言中的i18n支持, 而後嘗試下面的代碼。
文件: code.py
import os import sys import gettext import web # File location directory. rootdir = os.path.abspath(os.path.dirname(__file__)) # i18n directory. localedir = rootdir + '/i18n' # Object used to store all translations. allTranslations = web.storage() def get_translations(lang='en_US'): # Init translation. if allTranslations.has_key(lang): translation = allTranslations[lang] elif lang is None: translation = gettext.NullTranslations() else: try: translation = gettext.translation( 'messages', localedir, languages=[lang], ) except IOError: translation = gettext.NullTranslations() return translation def load_translations(lang): """Return the translations for the locale.""" lang = str(lang) translation = allTranslations.get(lang) if translation is None: translation = get_translations(lang) allTranslations[lang] = translation # Delete unused translations. for lk in allTranslations.keys(): if lk != lang: del allTranslations[lk] return translation def custom_gettext(string): """Translate a given string to the language of the application.""" translation = load_translations(session.get('lang')) if translation is None: return unicode(string) return translation.ugettext(string) urls = ( '/', 'index' ) render = web.template.render('templates/', globals={ '_': custom_gettext, } ) app = web.application(urls, globals()) # Init session. session = web.session.Session(app, web.session.DiskStore('sessions'), initializer={ 'lang': 'en_US', } ) class index: def GET(self): i = web.input() lang = i.get('lang', 'en_US') # Debug. print >> sys.stderr, 'Language:', lang session['lang'] = lang return render.index() if __name__ == "__main__": app.run()
模板文件: templates/index.html.
$_('Hello')
不要忘記生成必要的 po&mo 語言文件。參考: 模板語言中的i18n支持
如今運行 code.py:
$ python code.py
http://0.0.0.0:8080/
而後用你喜歡的瀏覽器訪問下面的地址,檢查語言是否改變:
http://your_server:8080/ http://your_server:8080/?lang=en_US http://your_server:8080/?lang=zh_CN
你必須:
參考: