Web基础(三)Python Web
文章目錄
- Python Web基礎
- 1. WSGI
- 1.1 概述
- 1.2 實現原理
- 1、WSGI Server/gateway
- 2、WSGI Application
- 3、WSGI MiddleWare
- 1.3 測試 WSGI服務器
- 代碼簡析
- 1.4 實現WSGI服務器
- 1.5 生產環境中的Web服務器
- [Gunicorn](https://github.com/benoitc/gunicorn "Gunicorn")
- [uWSGI ](https://github.com/unbit/uwsgi-docs "uWSGI ")
- [bjoern](https://github.com/jonashaag/bjoern#libev "bjoern")
- 2. Web應用開發
- 2.1 服務器架構
- 2.1.1 Nginx
- 反向代理
- Nginx的優勢
- 附錄
Python Web基礎
Web應用的本質:
1. 瀏覽器發送一個HTTP請求
2. 服務器收到請求,生成一個HTML文檔
3. 服務器把HTML文檔作為HTTP響應的Body發送給瀏覽器
4. 瀏覽器收到HTTP響應,從HTTP Body取出HTML文檔并顯示
所以,最簡單的Web應用就是先把HTML用文件保存好,用一個現成的HTTP服務器軟件,接收用戶請求,從文件中讀取HTML,返回。我們上兩篇博客已經詳細講解并實現了這樣的HTTP服務器zjhttp,除此外Apache、Nginx、Lighttpd等這些常見的靜態服務器就是干這件事情的。
如果要動態生成HTML,就需要把上述步驟自己來實現。不過,接受HTTP請求、解析HTTP請求、發送HTTP響應都是苦力活,如果我們自己來寫這些底層代碼,還沒開始寫動態HTML呢,就得花個把月去讀HTTP規范。正確的做法是底層代碼由專門的服務器軟件實現,我們用Python專注于生成HTML文檔。
1. WSGI
Web服務器網關接口(Python Web Server Gateway Interface,縮寫為WSGI)是為Python語言定義的Web服務器和Web應用程序或框架之間的一種簡單而通用的接口。自從WSGI被開發出來以后,許多其它語言中也出現了類似接口。
以前,如何選擇合適的Web應用程序框架成為困擾Python初學者的一個問題,這是因為,一般而言,Web應用框架的選擇將限制可用的Web服務器的選擇,反之亦然。那時的Python應用程序通常是為CGI,FastCGI,mod_python中的一個而設計,甚至是為特定Web服務器的自定義的API接口而設計的。
WSGI(有時發音作’wiz-gee’)是作為Web服務器與Web應用程序或應用框架之間的一種低級別的接口,以提升可移植Web應用開發的共同點。WSGI是基于現存的CGI標準而設計的。WSGI沒有官方的實現, 因為WSGI更像一個協議。只要遵照這些協議,WSGI應用(Application)都可以在任何服務器(Server)上運行, 反之亦然。WSGI就是Python的CGI包裝,相對于Fastcgi是PHP的CGI包裝
1.1 概述
WSGI區分為兩個部分
1. 為“服務器”或“網關”。它用于接收、整理客戶端發送的請求
2. 為“應用程序”或“應用框架”。處理服務器程序傳遞過來的請求
如上圖,Web服務器即第一部分,接收、整理客戶端發送的請求,咱們前兩篇博客使用C語言實現的zjhttp就是屬于Web服務器部分;Web框架即為第二部分,即所謂的Web應用程序。開發Web應用程序的時候,通常會把常用的功能封裝起來,成為各種框架,比如Flask,Django,Tornado(使用某框架進行web開發,相當于開發服務端的應用程序,處理后臺邏輯)。但是,服務器程序和應用程序互相配合才能給用戶提供服務,而不同應用程序(不同框架)會有不同的函數、功能。 此時,我們就需要一個標準,讓服務器程序和應用程序都支持這個標準,那么,二者就能很好的配合了,這個標準就是 WSGI。
在處理一個WSGI請求時,服務器會為應用程序提供環境信息及一個回調函數(Callback Function)。當應用程序完成處理請求后,透過前述的回調函數,將結果回傳給服務器。
所謂的 WSGI 中間件同時實現了API的兩方,因此可以在WSGI服務器和WSGI應用之間起調解作用。從Web服務器的角度來說,中間件扮演應用程序,而從應用程序的角度來說,中間件扮演服務器。“中間件”組件可以執行以下功能:
1. 重寫環境變量后,根據目標URL,將請求消息路由到不同的應用對象。
2. 允許在一個進程中同時運行多個應用程序或應用框架。
3. 負載均衡和遠程處理,通過在網絡上轉發請求和響應消息。
4. 進行內容后處理,例如應用XSLT樣式表。
1.2 實現原理
WSGI 將 Web 組件分為三類
- web服務器
- web中間件
- web應用程序
wsgi基本處理模式為:
WSGI Server -> WSGI Middleware -> WSGI Application
1、WSGI Server/gateway
wsgi server可以理解為一個符合wsgi規范的web server,接收request請求,封裝一系列環境變量,按照wsgi規范調用注冊的wsgi app,最后將response返回給客戶端。以python自帶的wsgiref為例,wsgiref是按照wsgi規范實現的一個簡單wsgi server。它的代碼不復雜。
2、WSGI Application
wsgi application就是一個普通的callable對象,當有請求到來時,wsgi server會調用這個wsgi app。這個對象接收兩個參數,通常為environ,start_response。environ就像前面介紹的,可以理解為環境變量,跟一次請求相關的所有信息都保存在了這個環境變量中,包括服務器信息,客戶端信息,請求信息。start_response是一個callback函數,wsgi application通過調用start_response,將response headers/status 返回給wsgi server。此外這個wsgi app會return 一個iterator對象 ,這個iterator就是response body。這么空講感覺很虛,對著下面這個簡單的例子看就明白很多了。
3、WSGI MiddleWare
有些功能可能介于服務器程序和應用程序之間,例如,服務器拿到了客戶端請求的URL, 不同的URL需要交由不同的函數處理,這個功能叫做 URL Routing,這個功能就可以放在二者中間實現,這個中間層就是 middleware。middleware對服務器程序和應用是透明的,也就是說,服務器程序以為它就是應用程序,而應用程序以為它就是服務器。這就告訴我們,middleware需要把自己偽裝成一個服務器,接受應用程序,調用它,同時middleware還需要把自己偽裝成一個應用程序,傳給服務器程序。
論是服務器程序、middleware 還是應用程序,都在服務端,為客戶端提供服務,之所以把他們抽象成不同層,就是為了控制復雜度,使得每一次都不太復雜,各司其職。
1.3 測試 WSGI服務器
原理說得太多未免過于抽象,現在使用Python內置的純Python代碼編寫的wsgiref服務器來體驗一把WSGI服務器是如何工作的
-
編寫hello.py 作為一個Web應用程序
def application(environ, start_response):start_response('200 OK', [('Content-Type', 'text/html')])return [b'<h1>Hello, World!</h1>'] -
編寫server.py作為一個WSGI服務器
from wsgiref.simple_server import make_server # 導入編寫的application函數 from hello import application# 創建一個服務器,IP地址為空,端口是8000,傳入函數application httpd = make_server('', 8000, application) print('Serving HTTP on port 8000...') # 開始監聽HTTP請求: httpd.serve_forever() -
啟動WSGI服務器
python server.py -
使用客戶端訪問
打開瀏覽器,輸入http://localhost:8000/ ,在瀏覽器正常顯示“Hello, World!”
代碼簡析
上面的application()函數就是符合WSGI標準的一個HTTP處理函數,它接收兩個參數:
- environ:一個包含所有HTTP請求信息的dict對象
- start_response:一個發送HTTP響應的函數
而在application()函數中又調用了start_response函數
該函數發送了HTTP響應的Header,注意Header只能發送一次,也就是只能調用一次start_response()函數。start_response()函數接收兩個參數,一個是HTTP響應碼,一個是一組list表示的HTTP Header,每個Header用一個包含兩個str的tuple表示。
通常情況下,都應該把Content-Type頭發送給瀏覽器。其他很多常用的HTTP Header也應該發送。然后,函數的返回值b'<h1>Hello, web!</h1>'將作為HTTP響應的Body發送給瀏覽器。
有了WSGI,我們關心的就是如何從environ這個dict對象拿到HTTP請求信息,然后構造HTML,通過start_response()發送Header,最后返回Body。
整個application()函數本身沒有涉及到任何解析HTTP的部分,也就是說,底層代碼不需要我們自己編寫,我們只負責在更高層次上考慮如何響應請求就可以了。
需要注意的是,application()函數必須由WSGI服務器來調用。有很多符合WSGI規范的服務器,我們可以挑選一個來用。但是我們僅將內置的wsgiref服務器用于測試,使我們編寫的Web應用程序立馬跑起來。
1.4 實現WSGI服務器
為了了解wsgi的工作原理,我們可以參照wsgiref源碼,使用Python簡單實現一個WSGI服務器
import socket import StringIO import sysclass WSGIServer(object):address_family = socket.AF_INETsocket_type = socket.SOCK_STREAMrequest_queue_size = 1def __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和porthost, port = self.listen_socket.getsockname()[:2]self.server_name = socket.getfqdn(host)self.server_port = portself.headers_set = []def set_app(self, application):self.application = application #啟動WSGI server服務,不停的監聽并獲取socket數據。def serve_forever(self):listen_socket = self.listen_socketwhile 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)# Construct environment dictionary using request dataenv = self.get_environ()#給flask\tornado傳遞兩個參數,environ,start_responseresult = 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('\r\n')(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.stderrenv['wsgi.multithread'] = Falseenv['wsgi.multiprocess'] = Falseenv['wsgi.run_once'] = False# Required CGI variablesenv['REQUEST_METHOD'] = self.request_method # GETenv['PATH_INFO'] = self.path # /helloenv['SERVER_NAME'] = self.server_name # localhostenv['SERVER_PORT'] = str(self.server_port) # 8888return envdef 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_setresponse = '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 += dataself.client_connection.sendall(response)print(''.join(['> {line}\n'.format(line=line) for line in response.splitlines()]))finally:self.client_connection.close()SERVER_ADDRESS = (HOST, PORT) = '', 8888def make_server(server_address, application):server = WSGIServer(server_address)server.set_app(application)return serverif __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]) -> valuehttpd = make_server(SERVER_ADDRESS, application)print('WSGIServer: Serving HTTP on port {port} ...\n'.format(port=PORT))httpd.serve_forever()1.5 生產環境中的Web服務器
每個web框架都不是專注于實現服務器方面的,因此,在生產環境部署的時候,使用的服務器也不會簡單的使用web框架自帶的服務器,那么用于生產環境的服務器有哪些呢?
Gunicorn
Gunicorn(從Ruby下面的Unicorn得到的啟發)應運而生:依賴Nginx的代理行為,同Nginx進行功能上的分離。由于不需要直接處理用戶來的請求(都被Nginx先處理),Gunicorn不需要完成相關的功能,其內部邏輯非常簡單:接受從Nginx來的動態請求,處理完之后返回給Nginx,由后者返回給用戶。
由于功能定位很明確,Gunicorn得以用純Python開發:大大縮短了開發時間的同時,性能上也不會很掉鏈子。同時,它也可以配合Nginx的代理之外的別的Proxy模塊工作,其配置也相應比較簡單
uWSGI
使用C語言開發,和底層接觸的更好,配置也比較方便,目前和gunicorn兩個算是部署時的唯二之選。由于其可擴展的架構,它能夠被無限制的擴展用來支持更多的平臺和語言。目前,可以使用C,C++和Objective-C來編寫插件
uWSGI 既不使用wsgi協議也不用FastCGI協議,而是自創了一個uwsgi的協議,uwsgi協議是一個uWSGI服務器自有的協議,它用于定義傳輸信息的類型(type of information),每一個uwsgi packet前4byte為傳輸信息類型描述,它與WSGI相比是兩樣東西。據說該協議大約是fcgi協議的10倍那么快
主要特點如下:
- 超快的性能
- 低內存占用(實測為apache2的mod_wsgi的一半左右)
- 多app管理
- 詳盡的日志功能(可以用來分析app性能和瓶頸)
- 高度可定制(內存大小限制,服務一定次數后重啟等)
uWSGI 服務器自己實現了基于uwsgi協議的server部分,因此我們只需要在uwsgi的配置文件中指定application的地址,uWSGI 就能直接和應用框架中的WSGI application通信
bjoern
是一個用C語言編寫的,快速超輕量級的 Python WSGI服務器。
它是最快速的,最小的并且是最輕量級的WSGI服務器。有以下特性:
- 1000 行的C代碼
- 占用內存 600KB
- 單線程沒有其他協同程序
- 可以綁定到TCP主機:端口地址和Unix套接字
- 支持HTTP1.0/1.1,包含支持HTTP1.1的分塊響應
如果單純追求性能,那uWSGI會更好一點,而Gunicorn則會更易安裝和結合gevent。在阻塞響應較多的情況下,Gunicorn的gevent模式無疑性能會更加強大。功能實現方面,uWSGI會更多一些,配置也會更加復雜一些。
2. Web應用開發
常見的Python Web應用框架:
- Django:全能型Web框架
- Flask:一個使用Python編寫的輕量級Web框架
- web.py:一個小巧的Web框架
- Bottle:和Flask類似的Web框架
- Tornado:Facebook的開源異步Web框架
2.1 服務器架構
2.1.1 Nginx
Nginx(發音同engine x)是一個異步框架的 Web服務器,也可以用作反向代理,負載平衡器 和 HTTP緩存。該軟件由 Igor Sysoev 創建,并于2004年首次公開發布。同名公司成立于2011年,以提供支持。
Nginx是一款免費的開源軟件,根據類BSD許可證的條款發布。一大部分Web服務器使用Nginx,通常作為負載均衡器。
Nginx是一款面向性能設計的HTTP服務器,相較于Apache、lighttpd具有占有內存少,穩定性高等優勢。與舊版本(<=2.2)的Apache不同,Nginx不采用每客戶機一線程的設計模型,而是充分使用異步邏輯從而削減了上下文調度開銷,所以并發服務能力更強。整體采用模塊化設計,有豐富的模塊庫和第三方模塊庫,配置靈活。 在Linux操作系統下,Nginx使用epoll事件模型,得益于此,Nginx在Linux操作系統下效率相當高。同時Nginx在OpenBSD或FreeBSD操作系統上采用類似于epoll的高效事件模型kqueue。
Nginx在官方測試的結果中,能夠支持五萬個并行連接,而在實際的運作中,可以支持二萬至四萬個并行連接
反向代理
正向代理是指瀏覽器主動請求代理服務器,代理服務器轉發請求到對應的目標服務器。而反向代理則部署在Web服務器上,代理所有外部網絡對內部網絡的訪問。瀏覽器訪問服務器,必須經過這個代理,是被動的。正向代理的主動方是客戶端,反向代理的主動方是Web服務器
在Python的Web開發中,較為成熟穩定的服務器架構一般是Nginx + uWSGI + Django。而實際上Nginx服務器并不是必須的,直接使用uWSGI + Djang完全是可以的,但這樣一來,直接將uWSGI服務器暴露給了瀏覽器客戶端,由此會導致諸多隱患。
Nginx的優勢
附錄
支持WSGI的Web應用框架有很多
- BlueBream
- bobo
- Bottle
- CherryPy
- Django
- Flask
- Google App Engine’s webapp2
- Gunicorn
- prestans
- Pylons
- Pyramid
- restlite
- Tornado
- Trac
- TurboGears
- Uliweb
- web.py
- web2py
- weblayer
- Werkzeug
參考
[參考資料1]
[參考資料2]
[參考資料3]
[參考資料4]
[參考資料5]
總結
以上是生活随笔為你收集整理的Web基础(三)Python Web的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第4章第11节:图表:使用柱形图表制作学
- 下一篇: Redis哨兵模式(一主二从三哨兵)