通過Python中的CGI接口講解什么是WSGI
前言
今天在 git.oschina 的首頁上看到他們推出演示平臺,其中,Python 的演示平臺支持 WSGI 接口的應(yīng)用。雖然,這個演示平臺連它自己提供的示例都跑不起來,但是,它還是成功的勾起了我對 WSGI 的好奇心。一番了解,對該機制的認識,總結(jié)如下。如有不妥,還望斧正。
為什么是 WSGI?
寫過網(wǎng)頁應(yīng)用的各位親,應(yīng)該對 CGI 有了解,我們知道,CGI 的全程是“Common Gateway Interface”,即 “通用 Gateway Interface“。沒錯,這里的 WSGI,就是只針對 Python的網(wǎng)頁應(yīng)用接口“Python Web Server Gateway Interface”。通過這樣的類比,想必大家對他的地位就有所了解了。
它只是一個接口定義:它不負責(zé)服務(wù)器的實現(xiàn),也不負責(zé)網(wǎng)頁應(yīng)用的實現(xiàn),它只是一個兩邊接口方式的約定。所以,它并不是另一個 WEB 應(yīng)用框架。通常意義上的 WEB 應(yīng)用框架,也只相當于 WSGI 網(wǎng)頁應(yīng)用端的一種實現(xiàn)。
這樣做的好處是?PEP 0333 中的解釋是,為了實現(xiàn)一個類似于 Java Servelet 的 API,使得遵循該接口的應(yīng)用擁有更廣泛的適用性。是的,有了該接口,你就不用去考慮,服務(wù)器對 Python 的支持到底是如何實現(xiàn)——無論是“ 直接用 Python 實現(xiàn)的服務(wù)器”,還是“服務(wù)器嵌入 Python”,或者是 “ 通過網(wǎng)關(guān)接口(CGI, Fastcgi...)”——應(yīng)用程序都有很好的適用性。就像是今天故事的開始,我們遇到了云平臺,它提供了對 WSGI 接口的支持,那么,只要應(yīng)用是基于 WSGI 的,那么應(yīng)用就可以直接跑起來。
此外,WSGI 的設(shè)計,也提供了另外一種可能性,那就是中間件(middleware)?;蛘哒f,我們可以寫一些對 server 和 application 都兼容的模塊,我們可以把他們部署在 Server 端,也可以部署在 Application 端,完成比如緩存、字符編碼轉(zhuǎn)換、根據(jù) url 做應(yīng)用 routing 等功能。這種設(shè)計模式,是 WSGI 降低了 server 和 application 耦合度之后的產(chǎn)物,同時,它從另一個角度大大提升了設(shè)計的靈活性。
WSGI 實施概略
上一小節(jié),簡要對 WSGI 做了介紹。這里從 application、server、middleware 三個角度對 WSGI 稍微進行深入,使我們對它有一個更具體的印象。
1)Application 端
WSGI 要求,應(yīng)用端必須提供一個可被調(diào)用的實體(PEP 0333 使用的是 Object,文檔還特別解釋這有別于Object instance),該實體可以是:一個函數(shù)(function)、一個方法(method)、一個類(class)、或者是有__call__方法的對象(Object instance)。
這里有兩個網(wǎng)頁應(yīng)用端的實現(xiàn)示例,一個是 function object,一個 class object:
def simple_app(environ, start_response):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return ['Hello world!\n']上面的 function 只是直接對請求直接做了 “200 ok” 回應(yīng),并沒有處理傳進來的參數(shù) environ——里面是由 WSGI Server 端提供的各種 HTTP 請求參數(shù)。需要特別注意的是,這個函數(shù)在最后,返回的一個 list(用“[]”包含在內(nèi))以保證結(jié)果的 iterable。下面的 class 類似。
在下面例子中,AppClass 作為應(yīng)用實體。當調(diào)用發(fā)生時,其實是對 class 進行了例化( python 固有特性,可以參考后面 server 端的實現(xiàn)代碼進一步理解),正如我們看到,這次調(diào)用(call)的返回值也是可迭代的——雖然只迭代一次(yield)。
class AppClass:
def __init__(self, environ, start_response):
self.environ = environ
self.start = start_response
def __iter__(self):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
self.start(status, response_headers)
yield "Hello world!\n"
""" In fact, the interator ‘ends‘ here because of no more yield field"""與上面兩種情形不同,使用 object instance 作為應(yīng)用實體時,需要為類定義添加 __call__ 方法,同時,參考上面使用 function 作為實體時情形,__call__ 方法的返回值需為 iterable(比如 return [ something ])。
最后,不管我們的 app 是 function 還是 class, application 都需要處理兩個參數(shù),而且是兩個位置相關(guān)的參數(shù)(不是命名參數(shù)),分別是:一個存放了 CGI 環(huán)境變量的 dictionary object,和一個可調(diào)用實體(需要給它三個位置相關(guān)的參數(shù),兩個必須,一個可選)。
其中,可調(diào)用實體(前例中的 start_response)必須調(diào)用一次,兩個必須的參數(shù)分別為“ HTTP Response的狀態(tài)(str 類型)“ 和 “HTTP Response Header(list of tuples)“;
一個可選的參數(shù)exc_info,必須是 Python sys.exc_info() tuple,只有在出錯需要顯示錯誤信息時使用。完整調(diào)用:start_response(status, response_headers,exc_info).
2)Server 端
下面是從 PEP 0333 拿來的一個簡單的 WSGI 容器,適用于 Python 作為某 WEB Server 上 CGI 時的應(yīng)用情形。
import os, sys
def run_with_cgi(application):
environ = dict(os.environ.items())
environ['wsgi.input'] = sys.stdin
environ['wsgi.errors'] = sys.stderr
environ['wsgi.version'] = (1, 0)
environ['wsgi.multithread'] = False
environ['wsgi.multiprocess'] = True
environ['wsgi.run_once'] = True
if environ.get('HTTPS', 'off') in ('on', '1'):
environ['wsgi.url_scheme'] = 'https'
else:
environ['wsgi.url_scheme'] = 'http'
headers_set = []
headers_sent = []
def write(data):
if not headers_set:
raise AssertionError("write() before start_response()")
elif not headers_sent:
# Before the first output, send the stored headers
status, response_headers = headers_sent[:] = headers_set
sys.stdout.write('Status: %s\r\n' % status)
for header in response_headers:
sys.stdout.write('%s: %s\r\n' % header)
sys.stdout.write('\r\n')
sys.stdout.write(data)
sys.stdout.flush()
def start_response(status, response_headers, exc_info=None):
if exc_info:
try:
if headers_sent:
# Re-raise original exception if headers sent
raise exc_info[0], exc_info[1], exc_info[2]
finally:
exc_info = None # avoid dangling circular ref
elif headers_set:
raise AssertionError("Headers already set!")
headers_set[:] = [status, response_headers]
return write
result = application(environ, start_response)
try:
for data in result:
if data: # don't send headers until body appears
write(data)
if not headers_sent:
write('') # send headers now if body was empty
finally:
if hasattr(result, 'close'):
result.close()上面的容器,大概實現(xiàn)了:
- a)將 CGI 環(huán)境變量放入 dictionary object (environ)中,供 Application 實體使用;
- b)定義了 start_response 方法,供 Application 實體調(diào)用;
- c)調(diào)用 application 實體,對 web 請求進行處理;
- d)將 application 的返回結(jié)果,以及通過 start_response 設(shè)置的 HTTP Response HEADER,寫到 stdout ——像其他 CGI 一樣,實際上是被發(fā)往網(wǎng)頁。
3) 作為 middleware
因為 WSGI 的寬松耦合的特性,我們可以輕松的在 Application 和 Server 之前插入任何的中間插件,在不需要改動 Server 和 Application 的前提下,實現(xiàn)一些特殊功能。但是,這種放在 Server 和 Application “中間”的模塊,并不是這里要講的 middleware ;或者,這只能算是一種特殊的 middleware,因為它僅僅是實現(xiàn)了 PEP 0333 中 middleware 定義的 Application 側(cè)的功能。這種僅實施在一側(cè)的 middleware,需要在發(fā)布時,特別的聲明。
PEP 0333 中約定,中間件是一些即可以在 Server 端實施,又可以在 Application 端實施的模塊。所以,在設(shè)計的時候,對兩邊的特性都要做適當考慮。幸好,WSGI 接口設(shè)計的足夠簡單。
class Router():
def __init__(self):
self.path_info = {}
def route(self, environ, start_response):
application = self.path_info[environ['PATH_INFO']]
return application(environ, start_response)
def __call__(self, path):
def wrapper(application):
self.path_info[path] = application
return wrapper
""" The above is the middleware"""
router = Router()
@router('/world')
def world(environ, start_response):
status = '200 OK'
output = 'World!'start_response(status, response_headers)
return [output]
@router('/hello')
def hello(environ, start_response):
status = '200 OK'
output = 'Hello'
response_headers = [('Content-type', 'text/plain'), ('Content-Length', str(len(output)))]
start_response(status, response_headers)
return [output]簡單解釋一下:
- 作為 Application 時,我們用 Router 實例化一個對象。然后對 “ PATH-APP “ 進行注冊,根據(jù)不同的 PATH,我們要進一步選擇哪個 App。接著,就是把 router.route() 喂給 Server ,作為 Application 側(cè)的可調(diào)用實體。有請求到來時,根據(jù)已經(jīng)注冊的 “PATH-APP” 對選擇應(yīng)用并執(zhí)行。
- Server 端類似,我們要先實例化并完成注冊。然后,比如,拿我們上一小節(jié)實現(xiàn)的 WSGI 容器為例,我們需要修改 result = router.route(environ, start_response),同樣完成了router的功能。
下面是另外一個,實現(xiàn)了 postprocessor 的一個例子,在 Application 返回的 HTTP Header 里面再加一個 Header。
def myapp(environ, start_response):
response_headers = [('content-type', 'text/plain')]
start_response('200 OK', response_headers)
return ['Check the headers!']
class Middleware:
def __init__(self, app):
self.wrapped_app = app
def __call__(self, environ, start_response):
def custom_start_response(status, headers, exc_info=None):
headers.append(('X-A-SIMPLE-TOKEN', "1234567890"))
return start_response(status, headers, exc_info)
return self.wrapped_app(environ, custom_start_response)
app = Middleware(myapp)這里通過改寫傳遞給 Application 的實體,實現(xiàn)了 postprocess 的目的。
其他資源:
- WSGI 的一些詳細資料,包括應(yīng)用列表什么的:https://wsgi.readthedocs.io/en/latest/
- 支持 WSGI 的多線程 WEB 服務(wù)器,基于SimpleHttpServer:
http://www.owlfish.com/software/wsgiutils/
-Paste為構(gòu)建以 WSGI 為基礎(chǔ)的 WEB 應(yīng)用程序或框架提供一個良好的基礎(chǔ)
- 官方的 WSGI 實現(xiàn)參考:https://pypi.org/project/wsgiref/
- 啄木鳥社區(qū)的 WSGI 中文 wiki:https://wiki.woodpecker.org.cn/moin/WSGI
- 和 Paste 一樣有名的基本架構(gòu):https://pypi.org/project/Pylons/1.0/
- 目前 Python 比較流行的三大 WEB 框架:TurboGears,Django,web2py。+1,代碼在 K 級別的服務(wù)小框架:webpy。
- 另外三個據(jù)說高性能的 App 開發(fā)框架:Falcon、Tornado、Bootle.py.
- 還有個價格不錯的 vps,恩:https://www.hostwinds.com/
以上就是通過Python中的CGI接口講解什么是WSGI的詳細內(nèi)容,更多關(guān)于Python中CGI接口講解WSGI的資料請關(guān)注腳本之家其它相關(guān)文章!
- Python?Web開發(fā)通信協(xié)議WSGI?uWSGI?uwsgi使用對比全面介紹
- Python安裝和配置uWSGI的詳細過程
- django生產(chǎn)環(huán)境搭建(uWSGI+django+nginx+python+MySQL)
- Python WSGI 規(guī)范簡介
- 淺析Python 中的 WSGI 接口和 WSGI 服務(wù)的運行
- Docker構(gòu)建python Flask+ nginx+uwsgi容器
- python web框架 django wsgi原理解析
- Python開發(fā)之Nginx+uWSGI+virtualenv多項目部署教程
- Python從wsgi導(dǎo)入失敗的問題解決方法
相關(guān)文章
Pytorch數(shù)據(jù)拼接與拆分操作實現(xiàn)圖解
這篇文章主要介紹了Pytorch數(shù)據(jù)拼接與拆分操作實現(xiàn)圖解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-04-04
Python文本轉(zhuǎn)語音引擎pyttsx3的使用完全指南
在開發(fā)需要語音輸出功能的應(yīng)用時,文本轉(zhuǎn)語音技術(shù)是一個非常有用的工具,Python的pyttsx3庫提供了一個簡單且離線的方式來實現(xiàn)這一功能,下面小編就來和大家介紹一下pyttsx3的具體使用吧2025-04-04
Python3實現(xiàn)的反轉(zhuǎn)單鏈表算法示例
這篇文章主要介紹了Python3實現(xiàn)的反轉(zhuǎn)單鏈表算法,結(jié)合實例形式總結(jié)分析了Python基于迭代算法與遞歸算法實現(xiàn)的翻轉(zhuǎn)單鏈表相關(guān)操作技巧,需要的朋友可以參考下2019-03-03
Pycharm編輯器功能之代碼折疊效果的實現(xiàn)代碼
這篇文章主要介紹了Pycharm編輯器功能之代碼折疊效果的實現(xiàn)代碼,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-10-10

