WSGI协议的原理及实现
本文將介紹WSGI協議的原理,并親自實現一個符合協議規范的WSGI server
WSGI,是Python Web Server Gateway Interface的簡稱,是web底層跟application解耦的協議,我們的web服務器使用WSGI協議來調用application稱為WSGI server。為了各類web server和web application之間能夠相互兼容,常見的web server(如Nginx,Apache)無法與web application(如Flask,django、tornado)直接通信,需要WSGI server作為橋梁。如今WSGI已經成為Python的一種標準協議PEP333。
WSGI的工作原理
WSGI的工作原理分為服務器層和應用程序層:
服務器層:將來自socket的數據包解析為http,調用application,給application提供環境信息environ,這個environ包含wsgi自身的信息(host,post,進程模式等),還有client的header和body信息。同時還給application提供一個start_response的回調函數,這個回調函數主要在應用程序層進行響應信息處理。
應用程序層:在WSGI提供的start_response,生成header,body和status后將這些信息socket send返回給客戶端。
上面的工作原理通過流程圖可以表示如下:
可以看到,wsgi server已經完成了底層http的解析和數據轉發等一系列網絡底層的實現,開發者可以更加專注于開發web application。
使用wsgiref模塊理解WSGI工作流程
Python自帶模塊wsgiref可以實現上述WSGI工作流程,這里使用wsgiref實現一個wsgi server的例子,以對wsgi的流程有感性的認識。
from wsgiref.simple_server import make_server
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'<h1>Hello, web!</h1>']
if __name__ == '__main__':
httpd = make_server('', 9999, application)
print("Serving HTTP on port 9999...")
httpd.serve_forever()
該例子中application就是web application,這里是定義為一個函數,在實際的Flask和bottle中,就是一個類,這些類實現__call__方法,且該方法中帶參數environ和start_response即可,運行server時只需調用self即可,例如:
class Flask:
def __init__(self):
# init params
pass
def __call__(self, environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'<h1>Hello, web!</h1>']
def listen(self, port)
from wsgiref.simple_server import make_server
server = make_server('127.0.0.1', 9999, self)
print('serve on 127.0.0.1:9999')
server.serve_forever()
因此,在這里例子里,可以具體看到wsgi server調用application后,在start_response中application返回header、content-type和body,最后在瀏覽器打開127.0.0.1:9999即可看到由wigs server返回的內容。
自己實現一個wsgi server
了解了wsgi的工作原理和具體的使用例子,我們自己來實現一個wsgi服務器,可以進行請求解析,并可以調用Flask暴露出來的wsgi_app參數來將Flask運行起來。
首先組織代碼結構:
import socket
import StringIO
import sys
class WSGIserver(object):
def __init__(self):
pass
def serve_forever(self):
pass
def handle_one_request(self):
request_data = '' # 暫不處理
self.parse_request(request_data)
env = self.get_environ()
result = self.application(env, self.start_response)
self.finish_response(result)
def parse_request(self, data):
pass
def get_environ(self):
pass
def start_response(self, status, response_headers, exc_info=None):
pass
def finish_response(self, result):
pass
def make_server(server_address, application):
server = WSGIServer(server_address)
server.set_app(application)
return server
if __name__ == '__main__':
httpd = make_server('', 8888, application)
httpd.serve_forever()
分析如下:
__init__:wsgi server的初始化操作
serve_forever:可以讓wsgi server一直監聽客戶端請求
handle_one_request:對每一次請求進行參數解析,包括parse_request和get_environ
start_response:傳遞給application的回調函數,根據PEP333,start_response需要包含status,response_headers,exc_info三個參數。status是http的狀態碼,如“200 OK”,”404 Not Found”。response_headers是一個(header_name, header_value)元組的列表,在進行application開發時需要注意這點。exc_info通常不需要,直接設置為None即可。具體的一些參數的解釋可以參考協議的詳細解釋。
finish_response:解析一次請求后,需要關閉socket端口,同時將application返回的data返回至客戶端。
有了代碼結構,根據PEP333標準協議,那么具體可以完善上述代碼,如下(wsgi.py):
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import StringIO
import sys
class WSGIServer(object):
address_family = socket.AF_INET
socket_type = socket.SOCK_STREAM
request_queue_size = 1
def __init__(self, server_address):
# 創建socket,利用socket獲取客戶端的請求
self.listen_socket = listen_socket = socket.socket(self.address_family, self.socket_type)
# 設置socket的工作模式
listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 綁定socket地址
listen_socket.bind(server_address)
# socket active, 監聽文件描述符
listen_socket.listen(self.request_queue_size)
# 獲得serve的host name和port
host, port = self.listen_socket.getsockname()[:2]
self.server_name = socket.getfqdn(host)
self.server_port = port
self.headers_set = []
def set_app(self, application):
self.application = application
#啟動WSGI server服務,不停的監聽并獲取socket數據。
def serve_forever(self):
listen_socket = self.listen_socket
while True:
self.client_connection, client_address = listen_socket.accept() #接受客戶端請求
#處理請求
self.handle_one_request()
def handle_one_request(self):
self.request_data = request_data = self.client_connection.recv(1024)
self.parse_request(request_data)
# print(''.join(
# '< {line}
'.format(line=line)
# for line in request_data.splitlines()
# ))
# Construct environment dictionary using request data
env = self.get_environ()
#給flask ornado傳遞兩個參數,environ,start_response
result = self.application(env, self.start_response)
self.finish_response(result)
#處理socket的http協議
def parse_request(self, data):
format_data = data.splitlines()
if len(format_data):
request_line = data.splitlines()[0]
request_line = request_line.rstrip('
')
(self.request_method, self.path, self.request_version) = request_line.split() ## ['GET', '/', 'HTTP/1.1']
# 獲取environ數據并設置當前server的工作模式
def get_environ(self):
env = {}
env['wsgi.version'] = (1, 0)
env['wsgi.url_scheme'] = 'http'
env['wsgi.input'] = StringIO.StringIO(self.request_data)
env['wsgi.errors'] = sys.stderr
env['wsgi.multithread'] = False
env['wsgi.multiprocess'] = False
env['wsgi.run_once'] = False
# Required CGI variables
env['REQUEST_METHOD'] = self.request_method # GET
env['PATH_INFO'] = self.path # /hello
env['SERVER_NAME'] = self.server_name # localhost
env['SERVER_PORT'] = str(self.server_port) # 8888
return env
def start_response(self, status, response_headers, exc_info=None):
server_headers = [('Date', 'Tue, 31 Mar 2015 12:54:48 GMT'), ('Server', 'WSGIServer 0.2')]
self.headers_set = [status, response_headers + server_headers]
#把application返回給WSGI的數據返回給客戶端。
def finish_response(self, result):
try:
status, response_headers = self.headers_set
response = 'HTTP/1.1 {status}
'.format(status=status)
for header in response_headers:
response += '{0}: {1}
'.format(*header)
response += '
'
for data in result:
response += data
self.client_connection.sendall(response)
print(''.join(
'> {line}
'.format(line=line)
for line in response.splitlines()
))
finally:
self.client_connection.close()
SERVER_ADDRESS = (HOST, PORT) = '', 8888
def make_server(server_address, application):
server = WSGIServer(server_address)
server.set_app(application)
return server
if __name__ == '__main__':
if len(sys.argv) < 2:
sys.exit('Provide a WSGI application object as module:callable')
app_path = sys.argv[1]
module, application = app_path.split(':') # 第一個參數是文件名,第二個參數時長文件內app的命名
module = __import__(module)
application = getattr(module, application) # getattr(object, name[, default]) -> value
httpd = make_server(SERVER_ADDRESS, application)
print('WSGIServer: Serving HTTP on port {port} ...
'.format(port=PORT))
httpd.serve_forever()
至此,wsgi server的代碼已完成,下面寫一個簡單的Flask實例(flaskapp.py)來驗證這個server是否可用:
from flask import Flask
from flask import Response
flask_app = Flask(__name__)
@flask_app.route('/hello')
def hello_world():
return Response(
'hello',
mimetype='text/plain'
)
app = flask_app.wsgi_app # 調用Flask暴露的wsgi_app,提供給wsgi server作為application,而不是直接run(port)
在終端輸入:
$python wsgi.py flaskapp:app WSGIServer: Serving HTTP on port 8888 ...
打開瀏覽器,輸入http://localhost:8888/hello可看到hello的信息,同時,終端輸出一些request和response的信息:
> HTTP/1.1 200 OK > Content-Type: text/plain; charset=utf-8 > Content-Length: 5 > Date: Tue, 31 Mar 2015 12:54:48 GMT > Server: WSGIServer 0.2 > > hello
總結
本文主要從WSGI的原理入手,同時通過原生wsgiref模塊了解了WSGI的工作流程,最后利用socket模塊自己實現了一個WSGI server,我們可以通過Flask這類web框架暴露出來的WSGI接口啟動應用。
由于WSGI實現了web server和web application的高度解耦,在具體的使用中可以使用一些比較成熟的WSGI server如uwsgi和gunicorn對任何一個符合WSGI規范的app進行部署,這給Python Web應用的部署帶來了極大的靈活性。
http://geocld.github.io/2017/08/14/wsgi/
總結
以上是生活随笔為你收集整理的WSGI协议的原理及实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: go 单元测试testify
- 下一篇: outlier异常值检验原理和处理方法