WSGI
轉:https://www.jianshu.com/p/29f66eb4e55a
Web Server Gateway Interface 的縮寫,即 Web 服務器網關接口。
Python web開發中,服務端程序分爲兩個部分
-
服務器程序(用來接收、整理客戶端發送的請求)
-
應用程序(處理服務器程序傳遞過來的請求)
其實我們使用的Flask,Django,Tornado等框架其實相當於開發服務端的應用程序,處理後臺邏輯
但是,服務器程序和應用程序互相配合才能給用戶提供服務,而不同應用程序(不同框架)會有不同的函數、功能。 此時,我們就需要一個標準,讓服務器程序和應用程序都支持這個標準,那麼,二者就能很好的配合了
WSGI:wsgi是python web開發的標準,類似於協議。它是服務器程序和應用程序的一個約定,規定了各自使用的接口和功能,以便二和互相配合
WSGI 規範了些什麼,下圖能很直觀的說明。
首先,我們對WSGI有個簡單的理解,下面我將通過一個實例來幫助大家理解
首先假設我們有這麼一個服務器
wsgi/server.py
# coding=utf-8
import socket
# 建立TCP套接字
listener = socket.socket()
# 設置端口可複用
listener.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR, 1)
# 綁定IP和端口
listener.bind(('0.0.0.0', 8080))
# 設置監聽隊列,並創建監聽套接字
listener.listen(5)
print('Serving HTTP on 0.0.0.0 port 8080 ...')
while True:
# 等待客戶端連接
client_connection, client_address = listener.accept()
print(f'Server received connection'
f' from {client_address}')
request = client_connection.recv(1024)
print(f'request we received: {request}')
headers_set = None
# 服務器要傳遞給應用程序的start_response函數,應用程序在返回數據前需要先調用
def start_response(status, headers):
"""
paramer status:狀態碼
paramer headers:是由 (header_name, header_value) 這樣的元祖組成的列表
[('Content-type', 'text/plain')]
"""
global headers_set
headers_set = [status, headers]
# 獲取請求類型和請求地址
method, path, _ = request.split(b' ', 2)
environ = {'REQUEST_METHOD': method.decode('utf-8'),
'PATH_INFO': path.decode('utf-8')}
# 調用應用程序
from wsgi_app import simple_app
app_result = simple_app(environ, start_response)
# 組織reponse並返回,要符合http協議的規範,響應行 相應頭 空行 相應體
response_status, response_headers = headers_set
response = f'HTTP/1.1 {response_status}\r\n'
for header in response_headers:
response += f'{header[0]}: {header[1]}\r\n'
response += '\r\n'
response = response.encode('utf-8')
for data in app_result:
response += data
client_connection.sendall(response)
client_connection.close()
通過上面的圖,我們可以清晰的知道服務器每接收到一個請求,就會通過 application_callable(environ, start_response) 調用應用程序。
而應用程序在處理完請求準備返回數據的時候,會先調用服務傳給它的函數 start_response(status, headers, exec_info),最後再返回可迭代對象作爲數據
另外我們在創建一個Web應用
wsgi/app.py
def simple_app(environ, start_response):
"""
paramer 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
}
paramer start_reponse:應用程序在返回數據之前會先調用,用於告訴服務設置響應的頭部信息或錯誤處理等
"""
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return [f'Request {environ["REQUEST_METHOD"]}'
f' {environ["PATH_INFO"]} has been'
f' processed\r\n'.encode('utf-8')]
好了,啓動服務器後(即執行服務器代碼,和之前的類似,這裏不贅述),然後請求看看結果
➜ ~ curl 127.0.0.1:8080/user/1
Request GET /user/1 has been processed
嗯,程序是正常的。
上面爲了說明,代碼耦合性較大,如果服務器需要更換應用的話,還得修改服務器代碼,這顯然是有問題的。現在原理差不多說清楚了,我們把代碼優化下
wsgig/wsgi_server_oop.py
# coding=utf-8
import socket
import sys
class WSGIServer:
def __init__(self):
self.listener = socket.socket()
self.listener.setsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR, 1)
self.listener.bind(('0.0.0.0', 8080))
self.listener.listen(1)
print('Serving HTTP on 0.0.0.0'
' port 8080 ...')
self.app = None
self.headers_set = None
def set_app(self, application):
self.app = application
def start_response(self, status, headers):
self.headers_set = [status, headers]
def serve_forever(self):
while True:
listener = self.listener
client_connection, client_address = \
listener.accept()
print(f'Server received connection'
f' from {client_address}')
request = client_connection.recv(1024)
print(f'request we received: {request}')
method, path, _ = request.split(b' ', 2)
# 爲簡潔的說明問題,這裏填充的內容有些隨意
# 如果有需要,可以自行完善
environ = {
'wsgi.version': (1, 0),
'wsgi.url_scheme': 'http',
'wsgi.input': request,
'wsgi.errors': sys.stderr,
'wsgi.multithread': False,
'wsgi.multiprocess': False,
'wsgi.run_once': False,
'REQUEST_METHOD': method.decode('utf-8'),
'PATH_INFO': path.decode('utf-8'),
'SERVER_NAME': '127.0.0.1',
'SERVER_PORT': '8080',
}
app_result = self.app(environ, self.start_response)
response_status, response_headers = self.headers_set
response = f'HTTP/1.1 {response_status}\r\n'
for header in response_headers:
response += f'{header[0]}: {header[1]}\r\n'
response += '\r\n'
response = response.encode('utf-8')
for data in app_result:
response += data
client_connection.sendall(response)
client_connection.close()
if __name__ == '__main__':
if len(sys.argv) < 2:
sys.exit('Argv Error')
app_path = sys.argv[1]
module, app = app_path.split(':')
# 導入wsgi_app
module = __import__(module)
app = getattr(module, app)
server = WSGIServer()
server.set_app(app)
server.serve_forever()
基本原理沒變,只是使用了面向對象的方式修改了下原來的代碼,同時 environ 添加了一些必要的環境信息。
可以使用以前的應用測試一下
➜ wsgi python wsgi_server_oop.py wsgi_app:simple_app
Serving HTTP on 0.0.0.0 port 8080 ...
然後發送請求
➜ ~ curl 127.0.0.1:8080/user/1
Request GET /user/1 has been processed
也可以用flask框架創建一個應用試試
wsgi/flask_app.py
# coding=utf-8
from flask import Flask
from flask import Response
flask_app = Flask(__name__)
@flask_app.route('/user/<int:user_id>',
methods=['GET'])
def hello_world(user_id):
return Response(
f'Get /user/{user_id} has been'
f' processed in flask app\r\n',
mimetype='text/plain'
)
重新啓動服務器
➜ wsgi python wsgi_server_oop.py flask_app:flask_app
Serving HTTP on 0.0.0.0 port 8080 ...
然後發送請求
➜ ~ curl 127.0.0.1:8080/user/1
Get /user/1 has been processed in flask app
因爲 Flask 也是遵守 WSGI 規範的,所以執行也沒有問題。
再來談談中間件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的應用程序
start_response(status, response_headers)
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服務器程序的要求 —> 調用應用程序