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。
當(dāng)前運行在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ù):
- 一個字典,該字典可以包含了客戶端請求的信息以及其他信息,可以認(rèn)為是請求上下文,一般叫做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] # ['']是默認(rèn)值,如果在QUERY_STRING中沒找到age則返回默認(rèn)值
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是當(dāng)前頁面
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)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
- 詳解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構(gòu)建一個簡單的數(shù)據(jù)處理流水線
數(shù)據(jù)處理流水線是數(shù)據(jù)分析和工程中非常常見的概念,通過流水線的設(shè)計,可以將數(shù)據(jù)的采集、處理、存儲等步驟連接起來,實現(xiàn)自動化的數(shù)據(jù)流,使用Python構(gòu)建一個簡單的數(shù)據(jù)處理流水線(Data?Pipeline),一步步構(gòu)建流程,并附上流程圖來幫助你更好地理解數(shù)據(jù)流的工作方式2024-12-12
Matplotlib scatter繪制散點圖的方法實現(xiàn)
這篇文章主要介紹了Matplotlib scatter繪制散點圖的方法實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
python統(tǒng)計中文字符數(shù)量的兩種方法
今天小編就為大家分享一篇python統(tǒng)計中文字符數(shù)量的兩種方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-01-01
一篇文章帶你了解python標(biāo)準(zhǔn)庫--time模塊
下面小編就為大家?guī)硪黄猵ython模塊之time模塊。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2021-08-08
詳解python實現(xiàn)數(shù)據(jù)歸一化處理的方式:(0,1)標(biāo)準(zhǔn)化
這篇文章主要介紹了詳解python實現(xiàn)數(shù)據(jù)歸一化處理的方式:(0,1)標(biāo)準(zhǔn)化,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07
PyTorch之怎樣選擇合適的優(yōu)化器和損失函數(shù)
這篇文章主要介紹了PyTorch怎樣選擇合適的優(yōu)化器和損失函數(shù)問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-02-02
將Python的Django框架與認(rèn)證系統(tǒng)整合的方法
這篇文章主要介紹了將Python的Django框架與認(rèn)證系統(tǒng)整合的方法,包括指定認(rèn)證后臺和編寫認(rèn)證后臺等內(nèi)容,需要的朋友可以參考下2015-07-07

