Python WSGI的深入理解
前言
本文主要介紹的是Python WSGI相關(guān)內(nèi)容,主要來自以下網(wǎng)址:
可以看成一次簡單粗暴的翻譯。
什么是WSGI
WSGI的全稱是Web Server Gateway Interface,這是一個規(guī)范,描述了web server如何與web application交互、web application如何處理請求。該規(guī)范的具體描述在PEP 3333。注意,WSGI既要實現(xiàn)web server,也要實現(xiàn)web application。
實現(xiàn)了WSGI的模塊/庫有wsgiref(python內(nèi)置)、werkzeug.serving、twisted.web等,具體可見Servers which support WSGI。
當前運行在WSGI之上的web框架有Bottle、Flask、Django等,具體可見Frameworks that run on WSGI。
WSGI server所做的工作僅僅是將從客戶端收到的請求傳遞給WSGI application,然后將WSGI application的返回值作為響應(yīng)傳給客戶端。WSGI applications 可以是棧式的,這個棧的中間部分叫做中間件,兩端是必須要實現(xiàn)的application和server。
WSGI教程
這部分內(nèi)容主要來自WSGI Tutorial。
WSGI application接口
WSGI application接口應(yīng)該實現(xiàn)為一個可調(diào)用對象,例如函數(shù)、方法、類、含__call__方法的實例。這個可調(diào)用對象可以接收2個參數(shù):
- 一個字典,該字典可以包含了客戶端請求的信息以及其他信息,可以認為是請求上下文,一般叫做environment(編碼中多簡寫為environ、env);
- 一個用于發(fā)送HTTP響應(yīng)狀態(tài)(HTTP status )、響應(yīng)頭(HTTP headers)的回調(diào)函數(shù)。
同時,可調(diào)用對象的返回值是響應(yīng)正文(response body),響應(yīng)正文是可迭代的、并包含了多個字符串。
WSGI application結(jié)構(gòu)如下:
def application (environ, start_response): response_body = 'Request method: %s' % environ['REQUEST_METHOD'] # HTTP響應(yīng)狀態(tài) status = '200 OK' # HTTP響應(yīng)頭,注意格式 response_headers = [ ('Content-Type', 'text/plain'), ('Content-Length', str(len(response_body))) ] # 將響應(yīng)狀態(tài)和響應(yīng)頭交給WSGI server start_response(status, response_headers) # 返回響應(yīng)正文 return [response_body]
Environment
下面的程序可以將environment字典的內(nèi)容返回給客戶端(environment.py):
# ! /usr/bin/env python # -*- coding: utf-8 -*- # 導(dǎo)入python內(nèi)置的WSGI server from wsgiref.simple_server import make_server def application (environ, start_response): response_body = [ '%s: %s' % (key, value) for key, value in sorted(environ.items()) ] response_body = '\n'.join(response_body) # 由于下面將Content-Type設(shè)置為text/plain,所以`\n`在瀏覽器中會起到換行的作用 status = '200 OK' response_headers = [ ('Content-Type', 'text/plain'), ('Content-Length', str(len(response_body))) ] start_response(status, response_headers) return [response_body] # 實例化WSGI server httpd = make_server ( '127.0.0.1', 8051, # port application # WSGI application,此處就是一個函數(shù) ) # handle_request函數(shù)只能處理一次請求,之后就在控制臺`print 'end'`了 httpd.handle_request() print 'end'
瀏覽器(或者curl、wget等)訪問http://127.0.0.1:8051/,可以看到environment的內(nèi)容。
另外,瀏覽器請求一次后,environment.py就結(jié)束了,程序在終端中輸出內(nèi)容如下:
127.0.0.1 - - [09/Sep/2015 23:39:09] "GET / HTTP/1.1" 200 5540
end
可迭代的響應(yīng)
如果把上面的可調(diào)用對象application的返回值:
return [response_body]
改成:
return response_body
這會導(dǎo)致WSGI程序的響應(yīng)變慢。原因是字符串response_body也是可迭代的,它的每一次迭代只能得到1 byte的數(shù)據(jù)量,這也意味著每一次只向客戶端發(fā)送1 byte的數(shù)據(jù),直到發(fā)送完畢為止。所以,推薦使用return [response_body]。
如果可迭代響應(yīng)含有多個字符串,那么Content-Length應(yīng)該是這些字符串長度之和:
# ! /usr/bin/env python # -*- coding: utf-8 -*- from wsgiref.simple_server import make_server def application(environ, start_response): response_body = [ '%s: %s' % (key, value) for key, value in sorted(environ.items()) ] response_body = '\n'.join(response_body) response_body = [ 'The Beggining\n', '*' * 30 + '\n', response_body, '\n' + '*' * 30 , '\nThe End' ] # 求Content-Length content_length = sum([len(s) for s in response_body]) status = '200 OK' response_headers = [ ('Content-Type', 'text/plain'), ('Content-Length', str(content_length)) ] start_response(status, response_headers) return response_body httpd = make_server('localhost', 8051, application) httpd.handle_request() print 'end'
解析GET請求
運行environment.py,在瀏覽器中訪問http://localhost:8051/?age=10&hobbies=software&hobbies=tunning,可以在響應(yīng)的內(nèi)容中找到:
QUERY_STRING: age=10&hobbies=software&hobbies=tunning REQUEST_METHOD: GET
cgi.parse_qs()函數(shù)可以很方便的處理QUERY_STRING,同時需要cgi.escape()處理特殊字符以防止腳本注入,下面是個例子:
# ! /usr/bin/env python # -*- coding: utf-8 -*- from cgi import parse_qs, escape QUERY_STRING = 'age=10&hobbies=software&hobbies=tunning' d = parse_qs(QUERY_STRING) print d.get('age', [''])[0] # ['']是默認值,如果在QUERY_STRING中沒找到age則返回默認值 print d.get('hobbies', []) print d.get('name', ['unknown']) print 10 * '*' print escape('<script>alert(123);</script>')
輸出如下:
10
['software', 'tunning']
['unknown']
**********
<script>alert(123);</script>
然后,我們可以寫一個基本的處理GET請求的動態(tài)網(wǎng)頁了:
# ! /usr/bin/env python # -*- coding: utf-8 -*- from wsgiref.simple_server import make_server from cgi import parse_qs, escape # html中form的method是get,action是當前頁面 html = """ <html> <body> <form method="get" action=""> <p> Age: <input type="text" name="age" value="%(age)s"> </p> <p> Hobbies: <input name="hobbies" type="checkbox" value="software" %(checked-software)s > Software <input name="hobbies" type="checkbox" value="tunning" %(checked-tunning)s > Auto Tunning </p> <p> <input type="submit" value="Submit"> </p> </form> <p> Age: %(age)s<br> Hobbies: %(hobbies)s </p> </body> </html> """ def application (environ, start_response): # 解析QUERY_STRING d = parse_qs(environ['QUERY_STRING']) age = d.get('age', [''])[0] # 返回age對應(yīng)的值 hobbies = d.get('hobbies', []) # 以list形式返回所有的hobbies # 防止腳本注入 age = escape(age) hobbies = [escape(hobby) for hobby in hobbies] response_body = html % { 'checked-software': ('', 'checked')['software' in hobbies], 'checked-tunning': ('', 'checked')['tunning' in hobbies], 'age': age or 'Empty', 'hobbies': ', '.join(hobbies or ['No Hobbies?']) } status = '200 OK' # 這次的content type是text/html response_headers = [ ('Content-Type', 'text/html'), ('Content-Length', str(len(response_body))) ] start_response(status, response_headers) return [response_body] httpd = make_server('localhost', 8051, application) # 能夠一直處理請求 httpd.serve_forever() print 'end'
啟動程序,在瀏覽器中訪問http://localhost:8051/、http://localhost:8051/?age=10&hobbies=software&hobbies=tunning感受一下~
這個程序會一直運行,可以使用快捷鍵Ctrl-C終止它。
這段代碼涉及兩個我個人之前沒用過的小技巧:
>>> "Age: %(age)s" % {'age':12} 'Age: 12' >>> >>> hobbies = ['software'] >>> ('', 'checked')['software' in hobbies] 'checked' >>> ('', 'checked')['tunning' in hobbies] ''
解析POST請求
對于POST請求,查詢字符串(query string)是放在HTTP請求正文(request body)中的,而不是放在URL中。請求正文在environment字典變量中鍵wsgi.input對應(yīng)的值中,這是一個類似file的變量,這個值是一個。The PEP 3333 指出,請求頭中CONTENT_LENGTH字段表示正文的大小,但是可能為空、或者不存在,所以讀取請求正文時候要用try/except。
下面是一個可以處理POST請求的動態(tài)網(wǎng)站:
# ! /usr/bin/env python # -*- coding: utf-8 -*- from wsgiref.simple_server import make_server from cgi import parse_qs, escape # html中form的method是post html = """ <html> <body> <form method="post" action=""> <p> Age: <input type="text" name="age" value="%(age)s"> </p> <p> Hobbies: <input name="hobbies" type="checkbox" value="software" %(checked-software)s > Software <input name="hobbies" type="checkbox" value="tunning" %(checked-tunning)s > Auto Tunning </p> <p> <input type="submit" value="Submit"> </p> </form> <p> Age: %(age)s<br> Hobbies: %(hobbies)s </p> </body> </html> """ def application(environ, start_response): # CONTENT_LENGTH 可能為空,或者沒有 try: request_body_size = int(environ.get('CONTENT_LENGTH', 0)) except (ValueError): request_body_size = 0 request_body = environ['wsgi.input'].read(request_body_size) d = parse_qs(request_body) # 獲取數(shù)據(jù) age = d.get('age', [''])[0] hobbies = d.get('hobbies', []) # 轉(zhuǎn)義,防止腳本注入 age = escape(age) hobbies = [escape(hobby) for hobby in hobbies] response_body = html % { 'checked-software': ('', 'checked')['software' in hobbies], 'checked-tunning': ('', 'checked')['tunning' in hobbies], 'age': age or 'Empty', 'hobbies': ', '.join(hobbies or ['No Hobbies?']) } status = '200 OK' response_headers = [ ('Content-Type', 'text/html'), ('Content-Length', str(len(response_body))) ] start_response(status, response_headers) return [response_body] httpd = make_server('localhost', 8051, application) httpd.serve_forever() print 'end'
Python WSGI入門
這段內(nèi)容參考自An Introduction to the Python Web Server Gateway Interface (WSGI) 。
Web server
WSGI server就是一個web server,其處理一個HTTP請求的邏輯如下:
iterable = app(environ, start_response) for data in iterable: # send data to client
app即WSGI application,environ即上文中的environment。可調(diào)用對象app返回一個可迭代的值,WSGI server獲得這個值后將數(shù)據(jù)發(fā)送給客戶端。
Web framework/app
即WSGI application。
中間件(Middleware)
中間件位于WSGI server和WSGI application之間,所以
一個示例
該示例中使用了中間件。
# ! /usr/bin/env python # -*- coding: utf-8 -*- from wsgiref.simple_server import make_server def application(environ, start_response): response_body = 'hello world!' status = '200 OK' response_headers = [ ('Content-Type', 'text/plain'), ('Content-Length', str(len(response_body))) ] start_response(status, response_headers) return [response_body] # 中間件 class Upperware: def __init__(self, app): self.wrapped_app = app def __call__(self, environ, start_response): for data in self.wrapped_app(environ, start_response): yield data.upper() wrapped_app = Upperware(application) httpd = make_server('localhost', 8051, wrapped_app) httpd.serve_forever() print 'end'
然后
有了這些基礎(chǔ)知識,就可以打造一個web框架了。感興趣的話,可以閱讀一下Bottle、Flask等的源碼。
在Learn about WSGI還有更多關(guān)于WSGI的內(nèi)容。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
- 詳解Python程序與服務(wù)器連接的WSGI接口
- Docker構(gòu)建python Flask+ nginx+uwsgi容器
- python 解決flask uwsgi 獲取不到全局變量的問題
- VPS CENTOS 上配置python,mysql,nginx,uwsgi,django的方法詳解
- Python開發(fā)之Nginx+uWSGI+virtualenv多項目部署教程
- CentOS7部署Flask(Apache、mod_wsgi、Python36、venv)
- 詳解如何在Apache中運行Python WSGI應(yīng)用
- python Web開發(fā)你要理解的WSGI & uwsgi詳解
- Python模塊WSGI使用詳解
- 詳解python使用Nginx和uWSGI來運行Python應(yīng)用
- 解決python3中自定義wsgi函數(shù),make_server函數(shù)報錯的問題
- 詳解使用Nginx和uWSGI配置Python的web項目的方法
- 淺析Python 中的 WSGI 接口和 WSGI 服務(wù)的運行
相關(guān)文章
python GUI庫圖形界面開發(fā)之PyQt5時間控件QTimer詳細使用方法與實例
這篇文章主要介紹了python GUI庫圖形界面開發(fā)之PyQt5時間控件QTimer詳細使用方法與實例,需要的朋友可以參考下2020-02-02分享Pytest fixture參數(shù)傳遞的幾種方式
這篇文章主要分享的是Pytest fixture參數(shù)傳遞的幾種方式,文章基于python的相關(guān)資料展開對主題的詳細介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-04-04Django restframework 框架認證、權(quán)限、限流用法示例
這篇文章主要介紹了Django restframework 框架認證、權(quán)限、限流用法,結(jié)合實例形式詳細分析了Djangorestframework 框架認證、權(quán)限、限流的具體使用方法及相關(guān)操作注意事項,需要的朋友可以參考下2019-12-12解決pycharm安裝scrapy DLL load failed:找不到指定的程序的問題
很多朋友向小編求助pycharm安裝scrapy DLL load failed:找不到指定的程序的問題,就這一問題小編在window10 + anaconda3 +pycharm2020.1.1 + scrapy安裝親測可用,下面把我的處理過程分享到腳本之家平臺,供大家參考2021-06-06