wsgi.py的詳解
WSGI的理解
Python web開發中,服務端程式可分為2個部分:
- 伺服器程式(用來接收、整理客戶端傳送的請求)
-
應用程式(處理伺服器程式傳遞過來的請求)
在開發應用程式的時候,我們會把常用的功能封裝起來,成為各種框架,比如Flask,Django,Tornado(使用某框架進行web開發,相當於開發服務端的應用程式,處理後臺邏輯)
但是,伺服器程式和應用程式互相配合才能給使用者提供服務,而不同應用程式(不同框架)會有不同的函式、功能。 此時,我們就需要一個標準,讓伺服器程式和應用程式都支援這個標準,那麼,二者就能很好的配合了WSGI:wsgi是python web開發的標準,類似於協議。它是伺服器程式和應用程式的一個約定,規定了各自使用的介面和功能,以便二和互相配合
WSGI應用程式的部分規定
- 應用程式是一個可呼叫的物件
可呼叫的物件有三種:- 一個函式
- 一個類,必須實現__call__()方法
- 一個類的例項
-
這個物件接收兩個引數
從原始碼中,我們可以看到,這兩個引數是environ, start_response. 以可呼叫物件為一個類為例:class application: def __call__(self, environ, start_response): pass
-
可呼叫物件需要返回一個可迭代的值。以可呼叫物件為一個類為例:
class application: def __call__(self, environ, start_response): return [xxx]
WSGI伺服器程式的部分規定
-
伺服器程式需要呼叫應用程式
def run(application): #伺服器程式呼叫應用程式 environ = {} #設定引數 def start_response(xxx): #設定引數 pass result = application(environ, start_response) #呼叫應用程式的__call__函式(這裡應用程式是一個類) def write(data): pass def data in result: #迭代訪問 write(data)
伺服器程式主要做了以下的事: 1. 設定應用程式所需要的引數 2. 呼叫應用程式 3. 迭代訪問應用程式的返回結果,並傳給客戶端
Middleware
middleware是介於伺服器程式和應用程式中間的部分,middleware對伺服器程式和應用程式是透明的。
對於伺服器程式來說,middleware就是應用程式,middleware需要偽裝成應用程式,傳遞給伺服器程式
對於應用程式來說,middleware就是伺服器程式,middleware需要偽裝成伺服器程式,接受並呼叫應用程式
伺服器程式獲取到了客戶端請求的URL,需要把URL交給不同的函式處理,這個功能可以使用middleware實現:
# URL Routing middleware
def urlrouting(url_app_mapping):
def midware_app(environ, start_response): #函式可呼叫,包含2個引數,返回可迭代的值
url = environ['PATH_INFO']
app = url_app_mapping[url] #獲得對應url的應用程式
result = app(environ, start_response) #呼叫應用程式
return result
return midware_app
函式midware_app就是middleware:
一方面,midware_app函式設定了應用程式所需要的變數,並呼叫了應用程式。所以對於應用程式來說,它是一個伺服器程式
另一方面,midware_app函式是一個可呼叫的物件,接收兩個引數,同時可呼叫物件返回了一個可迭代的值。所以對於伺服器程式來說,它是一個應用程式
寫中介軟體(middleware)的邏輯:
1. middleware需要偽裝成應用程式—> WSGI應用程式的要求 —> 1. 可呼叫 2. 兩個引數 3. 返回可迭代的值
2. middleware需要偽裝成伺服器程式 —> WSGI伺服器程式的要求 —> 呼叫應用程式
我們需要了解一下environ
這個變數。在WSGI中, 應用程式需要兩個引數:environ
和start_response
, 在伺服器程式呼叫應用程式之前, 需要先設定這兩個引數。 其中start_response
通常是個可呼叫的方法, 而environ
則是一個字典, 它是在CGI中定義的, 檢視CGI文件The
Common Gateway Interface Specification, 可以找到關於environ
的定義。
以下是environ中的引數:
AUTH_TYPE
CONTENT_LENGTH #HTTP請求中Content-Length的部分
CONTENT_TYPE #HTTP請求中Content-Tpye的部分
GATEWAY_INTERFACE
HTTP_* #包含一系列變數, 如HTTP_HOST,HTTP_ACCEPT等
PATH_INFO #URL路徑除了起始部分後的剩餘部分,用於找到相應的應用程式物件,如果請求的路徑就是根路徑,這個值為空字串
PATH_TRANSLATED
QUERY_STRING #URL路徑中?後面的部分
REMOTE_ADDR
REMOTE_HOST
REMOTE_IDENT
REMOTE_USER
REQUEST_METHOD #HTTP 請求方法,例如 "GET", "POST"
SCRIPT_NAME #URL路徑的起始部分對應的應用程式物件,如果應用程式物件對應伺服器的根,那麼這個值可以為空字串
SERVER_NAME
SERVER_PORT
SERVER_PROTOCOL #客戶端請求的協議(HTTP/1.1 HTTP/1.0)
SERVER_SOFTWARE
舉例:http://localhost:5000/aaa?666
, 變數值為:
REQUEST_METHOD=‘GET’
SCRIPT_NAME=''
SERVER_NAME='localhost'
SERVER_PORT=‘5000’
PATH_INFO='/aaa'
QUERY_STRING='666'
SERVER_PROTOCOL='HTTP/1.1'
CONTENT_TYPE='text/plain'
CONTEN_LENGTH=''
HTTP_HOST = 'localhost:8000'
HTTP_ACCEPT = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
HTTP_ACCEPT_ENCODING = 'gzip,deflate,sdch'
HTTP_ACCEPT_LANGUAGE = 'en-US,en;q=0.8,zh;q=0.6,zh-CN;q=0.4,zh-TW;q=0.2'
HTTP_CONNECTION = 'keep-alive'
HTTP_USER_AGENT = 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36'
對於start_response()
函式:start_response
是HTTP響應的開始, 它的形式為:start_response(status, response_headers, exc_info=None)
status
表示HTTP狀態碼, 比如200 OK
response_headers
是一個列表,列表元素是個tuple
:(header_name, header_value)
exc_info
是個可選引數, 當處理請求的過程中發生錯誤時, 會設定該引數, 同時會呼叫start_response
舉一個werkzeug
官方文件上的例子,我稍作改進,以進行分析(這段建議看完wsgi.py第二部分的SharedDataMiddleware類
再看):
class Shortly(object):
def __init__(self, config):
self.redis = redis.Redis(config['redis_host'], config['redis_port'])
def dispatch_request(self, request):
return Response('Hello World!') #初始化Response類
def wsgi_app(self, environ, start_response):
request = Request(environ)
response = self.dispatch_request(request) #response的型別為Response類
print('%%%%%%%%%%%%%%%%%%%%%%%%%%%')
return response(environ, start_response) #Response的__call__函式就是應用程式,返回迭代物件
def __call__(self, environ, start_response):
print(self.wsgi_app)
print('erghrgheoghegoierge')
return self. wsgi_app(environ, start_response)
def create_app(redis_host='localhost', redis_port=6379, with_static=True):
app = Shortly({
'redis_host': redis_host,
'redis_port': redis_port
})
if with_static:
print('yes')
app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {
'/static': os.path.join(os.path.dirname(__file__), 'static')
})
print('33333333333333333')
return app
#開啟本地伺服器
if __name__ == '__main__':
from werkzeug.serving import run_simple
app = create_app() #建立應用程式的例項
run_simple('127.0.0.1', 5000, app, use_debugger=True, use_reloader=True)
我們檢視Response
的原始碼(Response
繼承了BaseResponse
,檢視BaseResponse
的原始碼即可)可以知道:函式dispatch_request()
返回的值是Request
的建構函式,即返回了一個Response
類, 在函式wsgi_app()
中,request
的值的型別就是Response
,
所以wsgi_app()
的返回值response(environ, start_response)
實際上是呼叫了Response
類的__call__()
函式。
看了原始碼我們可以發現,__call__()
是一個WSGI
的應用程式!
當執行這個程式的時候:
22222222222222222
yes
33333333333333333
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
22222222222222222
yes
33333333333333333
我們先不糾結為什麼讀取了2次。
當我們開啟這個網頁,控制檯的輸出為:
<werkzeug.wsgi.SharedDataMiddleware object at 0x1007be7b8> #說明wsgi_app是SharedDataMiddleware的例項!
erghrgheoghegoierge
%%%%%%%%%%%%%%%%%%%%%%%%%%% #說明執行了原wsgi_app函式中的內容!
127.0.0.1 - - [22/May/2015 21:01:25] "GET / HTTP/1.1" 200 -
可以注意到,在本例中,app.wsgi_app
這個方法已經變成了一個SharedDataMiddleware
類的例項,我很好奇當伺服器把environ和start_response
傳遞給app
後,為什麼wsgi_pp
還會執行原wsgi_app
中的內容呢?
當我們訪問主機地址的時候,伺服器程式接受使用者請求,然後會把environ和start_response
傳遞給應用程式app
,app
會執行__call__函式
,在該函式中,會執行app.wsgi_app
這個函式。 然後wsgi_app
會執行ShareDataMiddleware
的__call__()
函式
這地方需要我們看SharedDataMiddleware
類的__call__()
的原始碼。看了原始碼我們可以發現,由於使用者沒有請求靜態檔案,所以會執行return self.app(environ, start_response)
,在本例中,我們可以看到在create_app()
中,我們定義的ShareDataMiddleware
的應用程式是app.wsgi_app,所以這裡返回的是原wsgi_app函式!所以當然會執行原函式了~
同時,我們也可以在static
資料夾中放一個檔案,然後訪問試一下:127.0.0.1/static/檔名
,此時就能看到那個檔案了!
通過本例,我們可以更深刻的瞭解Middleware的作用:
Middleware介於伺服器程式和應用程式之間,它會接收伺服器發來的訊息(environ和
start_response),並做一定的處理,然後把需要應用程式處理的部分傳遞給應用程式處理
另外, 伺服器程式還需要定義WSGI的相關變數:
wsgi.version
值的形式為 (1, 0) 表示 WSGI 版本 1.0
wsgi.url_scheme
表示 url 的模式,例如 "https" 還是 "http"
wsgi.input
輸入流,HTTP請求的 body 部分可以從這裡讀取
wsgi.erros
輸出流,如果出現錯誤,可以寫往這裡
wsgi.multithread
如果應用程式物件可以被同一程式中的另一執行緒同時呼叫,這個值為True
wsgi.multiprocess
如果應用程式物件可以同時被另一個程式呼叫,這個值為True
wsgi.run_once
如果伺服器希望應用程式物件在包含它的程式中只被呼叫一次,那麼這個值為True
相關文章
- SDWebImage 的詳解Web
- 詳解 go 的切片Go
- CMAKE的使用詳解
- ORACLE的HINT詳解Oracle
- JavaScript中的this詳解JavaScript
- HDFS的詳解(一)
- Rust 的詳解教程Rust
- 詳解 JavaScript 的類JavaScript
- 詳解javascript的類JavaScript
- Mybatis的使用詳解MyBatis
- 詳解JavaScript中的thisJavaScript
- Apache的配置詳解Apache
- nginx的location詳解Nginx
- 詳解Java的物件建立Java物件
- lambda 的入門詳解
- OpenStack的Swift元件詳解Swift元件
- OpenStack的Heat元件詳解元件
- OpenStack的Trove元件詳解元件
- APScheduler的使用詳解
- axios的用法詳解iOS
- 詳解 Github App 的玩法GithubAPP
- Erlang中的Record詳解
- 詳解CentOS的free命令CentOS
- JavaFx ObservableList的使用詳解Java
- MYSQL的事務詳解MySql
- struct的匿名用法詳解Struct
- Pytorch的API詳解PyTorchAPI
- Hive中的UDF詳解Hive
- jQuery 的語法詳解jQuery
- 詳解Vue中的插槽Vue
- oreo上的notification詳解
- Ftp - 常用的命令詳解FTP
- FTP 命令的使用詳解FTP
- Oracle的exp/imp詳解Oracle
- CSS偽類的詳解CSS
- cookie和session的詳解CookieSession
- Hive的Transform功能詳解HiveORM
- 詳解 MySQL 中的 explainMySqlAI