mvc框架打造筆記之wsgi協(xié)議的優(yōu)缺點(diǎn)以及接口實(shí)現(xiàn)
前言:
又是WSGI ,這是我曾經(jīng)比較熟悉的協(xié)議,以前針對(duì)實(shí)現(xiàn)了wsgi server的unicorn和uwsgi都寫過源碼解析的文章。 其實(shí)他們的實(shí)現(xiàn)也很簡(jiǎn)單,就是給flask、django這樣的application傳遞environ,start_response 。
什么是WSGI協(xié)議,什么是WSGI Server,他們的區(qū)別是什么?
上線的架構(gòu)圖很容易誤導(dǎo)別人,乍一看有nginx這樣的web服務(wù)器,又有g(shù)unicorn這樣的wsgi server。 我們先說明wsgi 跟 wsgi server的關(guān)系,wsgi是個(gè)協(xié)議,是web底層跟application解耦的協(xié)議。wsgi server是自己做web服務(wù)器借用wsgi協(xié)議來調(diào)用application。 我們需要明確一點(diǎn),nginx是無法直接跟flask application做通信,需要借用wsgi server。flask本身也有個(gè)web服務(wù)器是werkzeug,so 才能啟動(dòng)服務(wù)并監(jiān)聽端口。記得以前uwsgi沒名氣的時(shí)候,我們都在使用apache + mode_wsgi模式,apache也無法直接跟tornado通信,是借用mod_wsgi把torando做成了unix socket服務(wù),說白了也是啟動(dòng)了一個(gè)服務(wù),靠apache來轉(zhuǎn)發(fā)。
nginx、apache在這里只是啟動(dòng)了proxy的作用,那為什么不直接把uwsgi和gunicorn給暴露出去,因?yàn)閚ginx的靜態(tài)文件處理能力極強(qiáng)。
WSGI怎么工作的
wsgi主要是兩層,服務(wù)器方 和 應(yīng)用程序 :
1 服務(wù)器方:從底層解析http解析,然后調(diào)用應(yīng)用程序,給應(yīng)用程序提供(環(huán)境信息)和(回調(diào)函數(shù)), 這個(gè)回調(diào)函數(shù)是用來將應(yīng)用程序設(shè)置的http header和status等信息傳遞給服務(wù)器方.
2 應(yīng)用程序:用來生成返回的header,body和status,以便返回給服務(wù)器方。
WSGI把來自socket的數(shù)據(jù)包解析為http格式,然后進(jìn)而變化為environ變量,這environ變量里面有wsgi本身的信息(比如 host, post,進(jìn)程模式等),還有client的header及body信息。start_respnse是一個(gè)函調(diào)函數(shù),必須要附帶兩個(gè)參數(shù),一個(gè)是status(http狀態(tài)),response_headers(響應(yīng)的header頭信息)。
像flask、django、tornado都會(huì)暴露WSGI協(xié)議入口,我們只需要自己實(shí)現(xiàn)WSGI協(xié)議,wsgi server然后給flask傳遞environ,及start_response, 等到application返回值之后,我再socket send返回客戶端。
WSGI的優(yōu)點(diǎn)、缺點(diǎn)是什么?
優(yōu)點(diǎn):
多樣的部署選擇和組件之間的高度解耦
由于上面提到的高度解耦特性,理論上,任何一個(gè)符合WSGI規(guī)范的App都可以部署在任何一個(gè)實(shí)現(xiàn)了WSGI規(guī)范的Server上,這給Python Web應(yīng)用的部署帶來了極大的靈活性。
Flask自帶了一個(gè)基于Werkzeug的調(diào)試用服務(wù)器。根據(jù)Flask的文檔,在生產(chǎn)環(huán)境不應(yīng)該使用內(nèi)建的調(diào)試服務(wù)器,而應(yīng)該采取以下方式之一進(jìn)行部署:
GUNICORN
UWSGI
缺點(diǎn):
沒有
我們?cè)趙sgi層可以做什么時(shí)尚的操作:
- 黑白名單規(guī)則防御.
- 可以通過重寫環(huán)境變量,根據(jù)目標(biāo)URL,將請(qǐng)求消息路由到不同的應(yīng)用對(duì)象。這意思就是說,實(shí)現(xiàn)一套類似nginx location proxy的規(guī)則,可以把阻塞高性能轉(zhuǎn)給tornado的app. 當(dāng)然這是理想化的操作.
- 允許在一個(gè)進(jìn)程中同時(shí)運(yùn)行多個(gè)應(yīng)用程序或應(yīng)用框架.
- 負(fù)載均衡和遠(yuǎn)程處理,通過在網(wǎng)絡(luò)上轉(zhuǎn)發(fā)請(qǐng)求和響應(yīng)消息.
我們用python具體實(shí)現(xiàn)這個(gè)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)建一個(gè)可用的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): #啟動(dòng)WSGI server服務(wù),不停的監(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傳遞兩個(gè)參數(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)建回調(diào)函數(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): #把a(bǔ)pplication返回給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) #動(dòng)態(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的實(shí)例, 我們會(huì)發(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
運(yùn)行方式很簡(jiǎn)單:
python webserver2.py flaskapp:app
這樣一個(gè)wsgi就構(gòu)成了,下次我們會(huì)借用這wsgi框架擴(kuò)展成prefork wsgi server,類似gunicorn那樣。 以前在wsgi做過一些application的分流,但涉及到高并發(fā)的場(chǎng)景下的分流,還是建議直接在nginx層面做。 現(xiàn)在nginx lua的編程能力越來越強(qiáng),大家都在使用nginx lua做網(wǎng)關(guān)及入口的開發(fā)。
參考文章:
https://ruslanspivak.com/lsbaws-part2/
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
Python實(shí)現(xiàn)二維數(shù)組按照某行或列排序的方法【numpy lexsort】
這篇文章主要介紹了Python實(shí)現(xiàn)二維數(shù)組按照某行或列排序的方法,結(jié)合具體實(shí)例形式分析了Python使用numpy模塊的lexsort方法針對(duì)二維數(shù)組進(jìn)行排序的常用操作技巧,需要的朋友可以參考下2017-09-09Pytest測(cè)試報(bào)告工具Allure的高級(jí)用法
這篇文章介紹了Pytest測(cè)試報(bào)告工具Allure的高級(jí)用法,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07使用Python給頭像加上圣誕帽或圣誕老人小圖標(biāo)附源碼
圣誕的到來給大家?guī)硐矏?,今天圣誕老人給大家送一頂圣誕帽,今天小編通過代碼給大家分享使用Python給頭像加上圣誕帽或圣誕老人小圖標(biāo)附源碼,需要的朋友一起看看吧2019-12-12Python?異步之如何啟動(dòng)獲取事件循環(huán)
這篇文章主要為大家介紹了Python?異步之如何啟動(dòng)獲取事件循環(huán)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03Python無法安裝包的一種解決(Requirement already satisfied問題)
這篇文章主要介紹了Python無法安裝包的一種解決(Requirement already satisfied問題),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08python提取具有某種特定字符串的行數(shù)據(jù)方法
今天小編就為大家分享一篇python提取具有某種特定字符串的行數(shù)據(jù)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-12-12python定時(shí)檢測(cè)無響應(yīng)進(jìn)程并重啟的實(shí)例代碼
這篇文章主要介紹了python定時(shí)檢測(cè)無響應(yīng)進(jìn)程并重啟的實(shí)例代碼,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-04-04Python爬蟲實(shí)戰(zhàn)項(xiàng)目掌握酷狗音樂的加密過程
在常見的幾個(gè)音樂網(wǎng)站里,酷狗可以說是最好爬取的啦,什么彎都沒有,所以最適合小白入門爬蟲,本篇針對(duì)爬蟲零基礎(chǔ)的小白,所以每一步驟我都截圖并詳細(xì)解釋了,其實(shí)我自己看著都啰嗦,歸根到底就是兩個(gè)步驟的請(qǐng)求,還請(qǐng)大佬繞路勿噴2021-09-09