WCGI
動機
. CGI的目的是爲了動態的創建內容,但是這種方式無法擴展,CGI進程針對每個請求進行創建(就像是打開Python解釋器),用完就拋棄,如果接受了上千個請求,創建大量的語言解釋器進程很快就會導致服務器停機。有兩種方法來解決這個問題:服務器集成和外部進程。
服務器集成
. 也稱服務器API。當前應用最廣泛的服務器解決方案是Apache HTTP Web 服務器,這是一個開源的解決方案,通常稱爲 Apache,擁有一個服務器 API。
這類解決方案將網關集成進服務器。換句話說不是將服務器切分成多個語言解釋器來分別處理請求,而是生成函數調用,運行應用程序代碼,在運行過程中進行響應。
其缺點也有很多,如:含有bug的代碼會影響服務器實現的執行效率、不同語言的實現無法完全兼容,應用程序必須是線程安全的等等。
外部進程
. 這種方案是讓CGI應用在服務器外部運行,當有請求時,服務器將請求傳遞到外部進程中。外部進程的存在時間很長,不會再處理玩請求後就結束。使用外部進程最廣爲人知的方案是FastCGI,外部進程相比服務器集成是更優的選擇。
同時,爲了降低開發者尤其是Web礦建開發者的負擔,建立了Web服務器網類接口(Web Server Gateway Interface,WSGI)標準。
WSGI簡介
. WSGI規範創建於2003年,用於處理日益增多的不同的Web框架、Web服務器,及其它調用方式。
根據WSGI定義,其應用是可調用的對象,其參數固定爲以下兩個:一個是含有服務器環境變量的字典,另一個是可調用對象,該對象使用HTTP狀態碼和會返回給客戶端的HTTP頭來初始化響應。其必須返回一個可迭代對象用於組成響應負載。以下是一個簡單的示例:
def simple_wsgi_app(environ, start_response):
status = '200 OK'
headers = [('Content-type', 'text/plain')]
start_response(status, headers)
return ['Hello world!']
. start_response()這個可調用對象必須在應用執行,生成最終會發送回客戶端的響應。響應必須含有 HTTP 返回碼(200、300 等),以及 HTTP 響應頭。另外返回的內容必須是可迭代的,如列表、生成器等,他們生成實際的響應負載。
start_response()還有一個可選的第三參數(exc_info),這個參數含有異常信息。應用剛開始使用正常的一對參數執行,當發生錯誤的時候,會再次調用start_response(),但會將新的狀態碼、http頭和exc_info一起傳入,替換原有的內容。如果第二次調用時沒有提供exc_info,則會發生錯誤。
參考服務器
. Python在標準庫中提供了簡單的參考服務器:wsgiref.simple_server.WSGIServer。
可以用這個類直接構建一個服務器。wsgiref包還提供了一個方便的函數:make_server(),通過這個函數可部署一個用於簡單訪問的參考服務器,下面是示例:
from wsgiref.simple_server import make_server
def simple_wsgi_app(environ, start_response):
status = '200 OK'
headers = [('Content-type', 'text/plain')]
start_response(status, headers)
return [u'Hello world!'.encode('utf8')] #這裏不encode會報錯
httpd = make_server('',8000,simple_wsgi_app)
print('started app serving on port 8000...')
httpd.serve_forever()
. 運行這個模塊後,在瀏覽器訪問http://localhost:8000
可以看到純文本形式的Hello world!。其實wsgiref 模塊還有一個示例應用demo_app(),可以導入demo_app,並將make_server的第三個參數換成demo_app()看看有什麼不同。
中間件機封裝WSGI應用
. 在某些情況下,除了應用程序本身之外,還希望在應用程序執行之前或是執行之後添加一些處理程序,即中間件。其要麼對來自客戶的數據進行預處理,然後發送給應用,要麼在應用將響應負載發送給用戶之前,對結果數據進行一些最終的調整。
下面的例子是爲之前的simple_wsgi_app的返回結果打上時間戳,並且使用類封裝的方式,使參數整合到一個元組變量中,減少代碼量:
from wsgiref.simple_server import make_server
from time import ctime
def simple_wsgi_app(environ, start_response):
status = '200 OK'
headers = [('Content-type', 'text/plain')]
start_response(status, headers)
return ['Hello world!\n','nice'] #這裏和之前不一樣,沒有先encode了
class Ts_ci_wrapp(object):
def __init__(self,app):
self.orig_app = app
def __call__(self,*stuff):
return (('[%s] %s' % (ctime(),x)).encode() for x in self.orig_app(*stuff))
httpd = make_server('',8000,Ts_ci_wrapp(simple_wsgi_app))
print('started app serving on port 8000...')
httpd.serve_forever()