mvc框架打造筆記之wsgi協(xié)議的優(yōu)缺點以及接口實現(xiàn)
前言:
又是WSGI ,這是我曾經(jīng)比較熟悉的協(xié)議,以前針對實現(xiàn)了wsgi server的unicorn和uwsgi都寫過源碼解析的文章。 其實他們的實現(xiàn)也很簡單,就是給flask、django這樣的application傳遞environ,start_response 。

什么是WSGI協(xié)議,什么是WSGI Server,他們的區(qū)別是什么?
上線的架構圖很容易誤導別人,乍一看有nginx這樣的web服務器,又有gunicorn這樣的wsgi server。 我們先說明wsgi 跟 wsgi server的關系,wsgi是個協(xié)議,是web底層跟application解耦的協(xié)議。wsgi server是自己做web服務器借用wsgi協(xié)議來調用application。 我們需要明確一點,nginx是無法直接跟flask application做通信,需要借用wsgi server。flask本身也有個web服務器是werkzeug,so 才能啟動服務并監(jiān)聽端口。記得以前uwsgi沒名氣的時候,我們都在使用apache + mode_wsgi模式,apache也無法直接跟tornado通信,是借用mod_wsgi把torando做成了unix socket服務,說白了也是啟動了一個服務,靠apache來轉發(fā)。
nginx、apache在這里只是啟動了proxy的作用,那為什么不直接把uwsgi和gunicorn給暴露出去,因為nginx的靜態(tài)文件處理能力極強。

WSGI怎么工作的
wsgi主要是兩層,服務器方 和 應用程序 :
1 服務器方:從底層解析http解析,然后調用應用程序,給應用程序提供(環(huán)境信息)和(回調函數(shù)), 這個回調函數(shù)是用來將應用程序設置的http header和status等信息傳遞給服務器方.
2 應用程序:用來生成返回的header,body和status,以便返回給服務器方。
WSGI把來自socket的數(shù)據(jù)包解析為http格式,然后進而變化為environ變量,這environ變量里面有wsgi本身的信息(比如 host, post,進程模式等),還有client的header及body信息。start_respnse是一個函調函數(shù),必須要附帶兩個參數(shù),一個是status(http狀態(tài)),response_headers(響應的header頭信息)。
像flask、django、tornado都會暴露WSGI協(xié)議入口,我們只需要自己實現(xiàn)WSGI協(xié)議,wsgi server然后給flask傳遞environ,及start_response, 等到application返回值之后,我再socket send返回客戶端。
WSGI的優(yōu)點、缺點是什么?
優(yōu)點:
多樣的部署選擇和組件之間的高度解耦
由于上面提到的高度解耦特性,理論上,任何一個符合WSGI規(guī)范的App都可以部署在任何一個實現(xiàn)了WSGI規(guī)范的Server上,這給Python Web應用的部署帶來了極大的靈活性。
Flask自帶了一個基于Werkzeug的調試用服務器。根據(jù)Flask的文檔,在生產環(huán)境不應該使用內建的調試服務器,而應該采取以下方式之一進行部署:
GUNICORN
UWSGI
缺點:
沒有
我們在wsgi層可以做什么時尚的操作:
- 黑白名單規(guī)則防御.
- 可以通過重寫環(huán)境變量,根據(jù)目標URL,將請求消息路由到不同的應用對象。這意思就是說,實現(xiàn)一套類似nginx location proxy的規(guī)則,可以把阻塞高性能轉給tornado的app. 當然這是理想化的操作.
- 允許在一個進程中同時運行多個應用程序或應用框架.
- 負載均衡和遠程處理,通過在網(wǎng)絡上轉發(fā)請求和響應消息.
我們用python具體實現(xiàn)這個wsgi server及協(xié)議.
#xiaorui.cc
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):
# 創(chuàng)建一個可用的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)
# Bind
listen_socket.bind(server_address) #綁定地址
# Activate
listen_socket.listen(self.request_queue_size) #監(jiān)聽文件描述符
# Get server host name and 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
def serve_forever(self): #啟動WSGI server服務,不停的監(jiān)聽并獲取socket數(shù)據(jù)。
listen_socket = self.listen_socket
while True:
self.client_connection, client_address = listen_socket.accept()
self.handle_one_request() #處理新連接
def handle_one_request(self): #主要處理函數(shù)
self.request_data = request_data = self.client_connection.recv(1024)
print(''.join(
'< {line}\n'.format(line=line)
for line in request_data.splitlines()
))
self.parse_request(request_data)
env = self.get_environ()
#給flask\tornado傳遞兩個參數(shù),environ,start_response
result = self.application(env, self.start_response)
# Construct a response and send it back to the client
self.finish_response(result)
def parse_request(self, text): #處理socket的http協(xié)議
request_line = text.splitlines()[0]
request_line = request_line.rstrip('\r\n')
# Break down the request line into components
(self.request_method, # GET
self.path, # /hello
self.request_version # HTTP/1.1
) = request_line.split()
def get_environ(self): #獲取environ數(shù)據(jù)
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
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): #創(chuàng)建回調函數(shù).
server_headers = [
('Date', 'Tue, 31 Mar 2015 12:54:48 GMT'),
('Server', 'WSGIServer 0.3'),
]
self.headers_set = [status, response_headers + server_headers]
def finish_response(self, result): #把application返回給WSGI的數(shù)據(jù)返回給客戶端。
try:
status, response_headers = self.headers_set
response = 'HTTP/1.1 {status}\r\n'.format(status=status)
for header in response_headers:
response += '{0}: {1}\r\n'.format(*header)
response += '\r\n'
for data in result:
response += data
# Print formatted response data a la 'curl -v'
print(''.join(
'> {line}\n'.format(line=line)
for line in response.splitlines()
))
self.client_connection.sendall(response)
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(':')
module = __import__(module) #動態(tài)加載模塊
application = getattr(module, application) #使用自省的模式加載application的WSGI協(xié)議入口。
httpd = make_server(SERVER_ADDRESS, application)
print('WSGIServer: Serving HTTP on port {port} ...\n'.format(port=PORT))
httpd.serve_forever()
下面是flask application的實例, 我們會發(fā)現(xiàn)python的常見web框架都兼容了wsgi接口,沒有例外。
#xiaorui.cc
from flask import Flask
from flask import Response
flask_app = Flask('flaskapp')
@flask_app.route('/search')
def hello_world():
return Response(
'xiaorui.cc Golang vs python !\n',
mimetype='text/plain'
)
app = flask_app.wsgi_app
運行方式很簡單:
python webserver2.py flaskapp:app
這樣一個wsgi就構成了,下次我們會借用這wsgi框架擴展成prefork wsgi server,類似gunicorn那樣。 以前在wsgi做過一些application的分流,但涉及到高并發(fā)的場景下的分流,還是建議直接在nginx層面做。 現(xiàn)在nginx lua的編程能力越來越強,大家都在使用nginx lua做網(wǎng)關及入口的開發(fā)。
參考文章:
https://ruslanspivak.com/lsbaws-part2/
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關文章
Python實現(xiàn)二維數(shù)組按照某行或列排序的方法【numpy lexsort】
這篇文章主要介紹了Python實現(xiàn)二維數(shù)組按照某行或列排序的方法,結合具體實例形式分析了Python使用numpy模塊的lexsort方法針對二維數(shù)組進行排序的常用操作技巧,需要的朋友可以參考下2017-09-09
Python無法安裝包的一種解決(Requirement already satisfied問題)
這篇文章主要介紹了Python無法安裝包的一種解決(Requirement already satisfied問題),具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-08-08
python提取具有某種特定字符串的行數(shù)據(jù)方法
今天小編就為大家分享一篇python提取具有某種特定字符串的行數(shù)據(jù)方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-12-12

