欧美一区二区三区老妇人-欧美做爰猛烈大尺度电-99久久夜色精品国产亚洲a-亚洲福利视频一区二区

Pythonweb為什么離不開(kāi)WSGI

小編給大家分享一下Python web為什么離不開(kāi)WSGI,希望大家閱讀完這篇文章后大所收獲,下面讓我們一起去探討方法吧!

創(chuàng)新互聯(lián)是一家專(zhuān)業(yè)提供武侯企業(yè)網(wǎng)站建設(shè),專(zhuān)注與網(wǎng)站設(shè)計(jì)制作、成都網(wǎng)站設(shè)計(jì)、H5高端網(wǎng)站建設(shè)、小程序制作等業(yè)務(wù)。10年已為武侯眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專(zhuān)業(yè)網(wǎng)絡(luò)公司優(yōu)惠進(jìn)行中。

在 三百六十行,行行轉(zhuǎn) IT 的現(xiàn)狀下,很多來(lái)自各行各業(yè)的同學(xué),都選擇 Python 這門(mén)膠水語(yǔ)言做為踏入互聯(lián)網(wǎng)大門(mén)的第一塊敲門(mén)磚,在這些人里,又有相當(dāng)大比例的同學(xué)選擇了 Web 開(kāi)發(fā)這個(gè)方向(包括我)。而從事 web 開(kāi)發(fā),繞不過(guò)一個(gè)知識(shí)點(diǎn),就是 WSGI。

不管你是否是這些如上同學(xué)中的一員,都應(yīng)該好好地學(xué)習(xí)一下這個(gè)知識(shí)點(diǎn)。

由于我本人不從事專(zhuān)業(yè)的 python web 開(kāi)發(fā),所以在寫(xiě)這篇文章的時(shí)候,借鑒了許多優(yōu)秀的網(wǎng)絡(luò)博客,并花了很多的精力閱讀了大量的 OpenStack 代碼。

為了寫(xiě)這篇文章,零零散散地花了大概兩個(gè)星期。本來(lái)可以拆成多篇文章,寫(xiě)成一個(gè)系列的,經(jīng)過(guò)一番思慮,還是準(zhǔn)備一篇講完,這就是本篇文章這么長(zhǎng)的原因。

另外,一篇文章是不能吃透一個(gè)知識(shí)點(diǎn)的,本篇涉及的背景知識(shí)也比較多的,若我有講得不到位的,還請(qǐng)你多多查閱其他人的網(wǎng)絡(luò)博客進(jìn)一步學(xué)習(xí)。

Python web為什么離不開(kāi)WSGI

在你往下看之前,我先問(wèn)你幾個(gè)問(wèn)題,你帶著這些問(wèn)題往下看,可能更有目的性,學(xué)習(xí)可能更有效果。

問(wèn)1:一個(gè) HTTP 請(qǐng)求到達(dá)對(duì)應(yīng)的 application處理函數(shù)要經(jīng)過(guò)怎樣的過(guò)程?

問(wèn)2:如何不通過(guò)流行的 web 框架來(lái)寫(xiě)一個(gè)簡(jiǎn)單的web服務(wù)?

一個(gè)HTTP請(qǐng)求的過(guò)程可以分為兩個(gè)階段,第一階段是從客戶端到WSGI Server,第二階段是從WSGI Server 到WSGI Application

今天主要是講第二階段,主要內(nèi)容有以下幾點(diǎn):

  1. WSGI 是什么,因何而生?
  2. HTTP請(qǐng)求是如何到應(yīng)用程序的?
  3. 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 WSGI Server
  4. 實(shí)現(xiàn)“高并發(fā)”的WSGI Server
  5. 第一次路由:PasteDeploy
  6. PasteDeploy 使用說(shuō)明
  7. webob.dec.wsgify 裝飾器
  8. 第二次路由:中間件 routes 路由

01. WSGI 是什么,因何而生?

WSGI是 Web Server Gateway Interface 的縮寫(xiě)。

它是 Python應(yīng)用程序(application)或框架(如 Django)和 Web服務(wù)器之間的一種接口,已經(jīng)被廣泛接受。

它是一種協(xié)議,一種規(guī)范,其是在 PEP 333提出的,并在 PEP 3333 進(jìn)行補(bǔ)充(主要是為了支持 Python3.x)。這個(gè)協(xié)議旨在解決眾多 web 框架和web server軟件的兼容問(wèn)題。有了WSGI,你不用再因?yàn)槟闶褂玫膚eb 框架而去選擇特定的 web server軟件。

常見(jiàn)的web應(yīng)用框架有:Django,F(xiàn)lask等

常用的web服務(wù)器軟件有:uWSGI,Gunicorn等

那這個(gè) WSGI 協(xié)議內(nèi)容是什么呢?知乎上有人將 PEP 3333 翻譯成中文,寫(xiě)得非常好,我將這段協(xié)議的內(nèi)容搬運(yùn)過(guò)來(lái)。

WSGI 接口有服務(wù)端和應(yīng)用端兩部分,服務(wù)端也可以叫網(wǎng)關(guān)端,應(yīng)用端也叫框架端。服務(wù)端調(diào)用一個(gè)由應(yīng)用端提供的可調(diào)用對(duì)象。如何提供這個(gè)對(duì)象,由服務(wù)端決定。例如某些服務(wù)器或者網(wǎng)關(guān)需要應(yīng)用的部署者寫(xiě)一段腳本,以創(chuàng)建服務(wù)器或者網(wǎng)關(guān)的實(shí)例,并且為這個(gè)實(shí)例提供一個(gè)應(yīng)用實(shí)例。另一些服務(wù)器或者網(wǎng)關(guān)則可能使用配置文件或其他方法以指定應(yīng)用實(shí)例應(yīng)該從哪里導(dǎo)入或獲取。

WSGI 對(duì)于 application 對(duì)象有如下三點(diǎn)要求

  1. 必須是一個(gè)可調(diào)用的對(duì)象
  2. 接收兩個(gè)必選參數(shù)environ、start_response。
  3. 返回值必須是可迭代對(duì)象,用來(lái)表示http body。

02. HTTP請(qǐng)求是如何到應(yīng)用程序的?

當(dāng)客戶端發(fā)出一個(gè) HTTP 請(qǐng)求后,是如何轉(zhuǎn)到我們的應(yīng)用程序處理并返回的呢?

關(guān)于這個(gè)過(guò)程,細(xì)節(jié)的點(diǎn)這里沒(méi)法細(xì)講,只能講個(gè)大概。

我根據(jù)其架構(gòu)組成的不同將這個(gè)過(guò)程的實(shí)現(xiàn)分為兩種:

1、兩級(jí)結(jié)構(gòu)在這種結(jié)構(gòu)里,uWSGI作為服務(wù)器,它用到了HTTP協(xié)議以及wsgi協(xié)議,flask應(yīng)用作為application,實(shí)現(xiàn)了wsgi協(xié)議。當(dāng)有客戶端發(fā)來(lái)請(qǐng)求,uWSGI接受請(qǐng)求,調(diào)用flask app得到相應(yīng),之后相應(yīng)給客戶端。 這里說(shuō)一點(diǎn),通常來(lái)說(shuō),F(xiàn)lask等web框架會(huì)自己附帶一個(gè)wsgi服務(wù)器(這就是flask應(yīng)用可以直接啟動(dòng)的原因),但是這只是在開(kāi)發(fā)階段用到的,在生產(chǎn)環(huán)境是不夠用的,所以用到了uwsgi這個(gè)性能高的wsgi服務(wù)器。

2、三級(jí)結(jié)構(gòu)這種結(jié)構(gòu)里,uWSGI作為中間件,它用到了uwsgi協(xié)議(與nginx通信),wsgi協(xié)議(調(diào)用Flask app)。當(dāng)有客戶端發(fā)來(lái)請(qǐng)求,nginx先做處理(靜態(tài)資源是nginx的強(qiáng)項(xiàng)),無(wú)法處理的請(qǐng)求(uWSGI),最后的相應(yīng)也是nginx回復(fù)給客戶端的。 多了一層反向代理有什么好處?

提高web server性能(uWSGI處理靜態(tài)資源不如nginx;nginx會(huì)在收到一個(gè)完整的http請(qǐng)求后再轉(zhuǎn)發(fā)給wWSGI)

nginx可以做負(fù)載均衡(前提是有多個(gè)服務(wù)器),保護(hù)了實(shí)際的web服務(wù)器(客戶端是和nginx交互而不是uWSGI)

03. 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 WSGI Server

在上面的架構(gòu)圖里,不知道你發(fā)現(xiàn)沒(méi)有,有個(gè)庫(kù)叫做 wsgiref ,它是 Python 自帶的一個(gè) wsgi 服務(wù)器模塊。

從其名字上就看出,它是用純Python編寫(xiě)的WSGI服務(wù)器的參考實(shí)現(xiàn)。所謂“參考實(shí)現(xiàn)”是指該實(shí)現(xiàn)完全符合WSGI標(biāo)準(zhǔn),但是不考慮任何運(yùn)行效率,僅供開(kāi)發(fā)和測(cè)試使用。

有了 wsgiref 這個(gè)模塊,你就可以很快速的啟動(dòng)一個(gè)wsgi server。

from wsgiref.simple_server import make_server# 這里的 appclass 暫且不說(shuō),后面會(huì)講到app = appclass()
server = make_server('', 64570, app)
server.serve_forever()

當(dāng)你運(yùn)行這段代碼后,就會(huì)開(kāi)啟一個(gè) wsgi server,監(jiān)聽(tīng) 0.0.0.0:64570 ,并接收請(qǐng)求。

使用 lsof 命令可以查到確實(shí)開(kāi)啟了這個(gè)端口

以上使用 wsgiref 寫(xiě)了一個(gè)demo,讓你對(duì)wsgi有個(gè)初步的了解。其由于只適合在學(xué)習(xí)測(cè)試使用,在生產(chǎn)環(huán)境中應(yīng)該另尋他道。

04. 實(shí)現(xiàn)“高并發(fā)”的 WSGI Server

上面我們說(shuō)不能在生產(chǎn)中使用 wsgiref ,那在生產(chǎn)中應(yīng)該使用什么呢?選擇有挺多的,比如優(yōu)秀的 uWSGI,Gunicore等。但是今天我并不準(zhǔn)備講這些,一是因?yàn)槲也辉趺词煜?,二是因?yàn)槲冶救藦氖?OpenStack 的二次開(kāi)發(fā),對(duì)它比較熟悉。

所以下面,是我花了幾天時(shí)間閱讀 OpenStack 中的 Nova 組件代碼的實(shí)現(xiàn),剛好可以拿過(guò)來(lái)學(xué)習(xí)記錄一下,若有理解偏差,還望你批評(píng)指出。

在 nova 組件里有不少服務(wù),比如 nova-api,nova-compute,nova-conductor,nova-scheduler 等等。

其中,只有 nova-api 有對(duì)外開(kāi)啟 http 接口。

要了解這個(gè)http 接口是如何實(shí)現(xiàn)的,從服務(wù)啟動(dòng)入口開(kāi)始看代碼,肯定能找到一些線索。

從 Service 文件可以得知 nova-api 的入口是 nova.cmd.api:main()

打開(kāi)nova.cmd.api:main() ,一起看看是 OpenStack Nova 的代碼。

在如下的黃框里,可以看到在這里使用了service.WSGIService 啟動(dòng)了一個(gè) server,就是我們所說(shuō)的的 wsgi server

那這里的 WSGI Server 是依靠什么實(shí)現(xiàn)的呢?讓我們繼續(xù)深入源代碼。

wsgi.py 可以看到這里使用了 eventlet 這個(gè)網(wǎng)絡(luò)并發(fā)框架,它先開(kāi)啟了一個(gè)綠色線程池,從配置里可以看到這個(gè)服務(wù)器可以接收的請(qǐng)求并發(fā)量是 1000 。

可是我們還沒(méi)有看到 WSGI Server 的身影,上面使用eventlet 開(kāi)啟了線程池,那線程池里的每個(gè)線程應(yīng)該都是一個(gè)服務(wù)器吧?它是如何接收請(qǐng)求的?

再繼續(xù)往下,可以發(fā)現(xiàn),每個(gè)線程都是使用 eventlet.wsgi.server 開(kāi)啟的 WSGI Server,還是使用的 eventlet。

由于源代碼比較多,我提取了主要的代碼,精簡(jiǎn)如下

# 創(chuàng)建綠色線程池
self._pool = eventlet.GreenPool(self.pool_size)

# 創(chuàng)建 socket:監(jiān)聽(tīng)的ip,端口
bind_addr = (host, port)
self._socket = eventlet.listen(bind_addr, family, backlog=backlog)
dup_socket = self._socket.dup()

# 整理孵化協(xié)程所需的各項(xiàng)參數(shù)
wsgi_kwargs = {    
        'func': eventlet.wsgi.server,    
        'sock': dup_socket,    
        'site': self.app, # 這個(gè)就是 wsgi 的 application 函數(shù)    
        'protocol': self._protocol,    
        'custom_pool': self._pool,    
        'log': self._logger,    
        'log_format': CONF.wsgi.wsgi_log_format,    
        'debug': False,    
        'keepalive': CONF.wsgi.keep_alive,    
        'socket_timeout': self.client_socket_timeout
}

# 孵化協(xié)程
self._server = utils.spawn(**wsgi_kwargs)

就這樣,nova 開(kāi)啟了一個(gè)可以接受1000個(gè)綠色協(xié)程并發(fā)的 WSGI Server。

05. 第一次路由:PasteDeploy

上面我們提到 WSGI Server 的創(chuàng)建要傳入一個(gè) Application,用來(lái)處理接收到的請(qǐng)求,對(duì)于一個(gè)有多個(gè) app 的項(xiàng)目。

比如,你有一個(gè)個(gè)人網(wǎng)站提供了如下幾個(gè)模塊

/blog  # 博客 app
/wiki  # wiki app

如何根據(jù) 請(qǐng)求的url 地址,將請(qǐng)求轉(zhuǎn)發(fā)到對(duì)應(yīng)的application上呢?

答案是,使用 PasteDeploy 這個(gè)庫(kù)(在 OpenStack 中各組件被廣泛使用)。

PasteDeploy 到底是做什么的呢?

根據(jù) 官方文檔 的說(shuō)明,翻譯如下

PasteDeploy 是用來(lái)尋找和配置WSGI應(yīng)用和服務(wù)的系統(tǒng)。PasteDeploy給開(kāi)發(fā)者提供了一個(gè)簡(jiǎn)單的函數(shù)loadapp。通過(guò)這個(gè)函數(shù),可以從一個(gè)配置文件或者Python egg中加載一個(gè)WSGI應(yīng)用。

使用PasteDeploy的其中一個(gè)重要意義在于,系統(tǒng)管理員可以安裝和管理WSGI應(yīng)用,而無(wú)需掌握與Python和WSGI相關(guān)知識(shí)。

由于 PasteDeploy 原來(lái)是屬于 Paste 的,現(xiàn)在獨(dú)立出來(lái)了,但是安裝的時(shí)候還是會(huì)安裝到paste目錄(site-packages\paste\deploy)下。

我會(huì)先講下在 Nova 中,是如何借助 PasteDeploy 實(shí)現(xiàn)對(duì)url的路由轉(zhuǎn)發(fā)。

還記得在上面創(chuàng)建WSGI Server的時(shí)候,傳入了一個(gè) self.app 參數(shù),這個(gè)app并不是一個(gè)固定的app,而是使用 PasteDeploy 中提供的 loadapp 函數(shù)從 paste.ini 配置文件中加載application。

具體可以,看下nova的實(shí)現(xiàn)。

通過(guò)打印的 DEBUG 內(nèi)容得知 config_url 和 app name 的值

app: osapi_compute
config_url: /etc/nova/api-paste.inia

通過(guò)查看 /etc/nova/api-paste.ini  ,在 composite 段里找到了 osapi_compute 這個(gè)app(這里的app和wsgi app 是兩個(gè)概念,需要注意區(qū)分) ,可以看出 nova 目前有兩個(gè)版本的api,一個(gè)是 v2,一個(gè)是v2.1,目前我們?cè)谟玫氖?v2.1,從配置文件中,可以得到其指定的 application 的路徑是nova.api.openstack.compute 這個(gè)模塊下的 APIRouterV21 類(lèi) 的factory方法,這是一個(gè)工廠函數(shù),返回 APIRouterV21 實(shí)例。

[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory
/: oscomputeversions
/v2: openstack_compute_api_v21_legacy_v2_compatible
/v2.1: openstack_compute_api_v21

[app:osapi_compute_app_v21]
paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory

這是 OpenStack 使用 PasteDeploy 實(shí)現(xiàn)的第一層的路由,如果你不感興趣,可以直接略過(guò)本節(jié),進(jìn)入下一節(jié),下一節(jié)是 介紹 PasteDeploy 的使用,教你實(shí)現(xiàn)一個(gè)簡(jiǎn)易的web server demo。推薦一定要看。

06. PasteDeploy 使用說(shuō)明

到上一步,我已經(jīng)得到了 application 的有用的線索??紤]到很多人是第一次接觸 PasteDeploy,所以這里結(jié)合網(wǎng)上博客做了下總結(jié)。對(duì)你入門(mén)會(huì)有幫助。

掌握 PasteDeploy ,你只要按照以下三個(gè)步驟逐個(gè)完成即可。

1、配置 PasteDeploy使用的ini文件;

2、定義WSGI應(yīng)用;

3、通過(guò)loadapp函數(shù)加載WSGI應(yīng)用;

第一步:寫(xiě) paste.ini 文件

在寫(xiě)之前,咱得知道 ini 文件的格式吧。

首先,像下面這樣一個(gè)段叫做 section

[type:name]
key = value
...

其上的type,主要有如下幾種

  1. composite (組合):多個(gè)app的路由分發(fā);

    [composite:main]
    use = egg:Paste#urlmap
    / = home
    /blog = blog
    /wiki = wiki
  2. app(應(yīng)用):指明 WSGI 應(yīng)用的路徑;

    [app:home]
    paste.app_factory = example:Home.factory復(fù)制代碼
  3. pipeline(管道):給一個(gè) app 綁定多個(gè)過(guò)濾器。將多個(gè)filter和最后一個(gè)WSGI應(yīng)用串聯(lián)起來(lái)。

    [pipeline:main]
    pipeline = filter1 filter2 filter3 myapp
    
    [filter:filter1]
    ...
    
    [filter:filter2]
    ...
    
    [app:myapp]
    ...
  4. filter(過(guò)濾器):以 app 做為唯一參數(shù)的函數(shù),并返回一個(gè)“過(guò)濾”后的app。通過(guò)鍵值next可以指定需要將請(qǐng)求傳遞給誰(shuí)。next指定的可以是一個(gè)普通的WSGI應(yīng)用,也可以是另一個(gè)過(guò)濾器。雖然名稱(chēng)上是過(guò)濾器,但是功能上不局限于過(guò)濾功能,可以是其它功能,例如日志功能,即將認(rèn)為重要的請(qǐng)求數(shù)據(jù)記錄下來(lái)。

    [app-filter:filter_name]
    use = egg:...
    next = next_app
    
    [app:next_app]
    ...

對(duì) ini 文件有了一定的了解后,就可以看懂下面這個(gè) ini 配置文件了

[composite:main]
use = egg:Paste#urlmap
/blog = blog
/wiki = wiki

[app:blog]
paste.app_factory = example:Blog.factory

[app:wiki]
paste.app_factory = example:Wiki.factory

第二步是定義一個(gè)符合 WSGI 規(guī)范的 applicaiton 對(duì)象。

符合 WSGI 規(guī)范的 application 對(duì)象,可以有多種形式,函數(shù),方法,類(lèi),實(shí)例對(duì)象。這里僅以實(shí)例對(duì)象為例(需要實(shí)現(xiàn) __call__ 方法),做一個(gè)演示。

import os
from paste import deploy
from wsgiref.simple_server import make_server

class Blog(object):    
    def __init__(self):
     print("Init Blog.") 
        
  def __call__(self, environ, start_response):
     status_code = "200 OK"
     response_headers = [("Content-Type", "text/plain")]
     response_body = "This is Blog's response body.".encode('utf-8')

     start_response(status_code, response_headers)        
     return [response_body]    
     
 @classmethod    
 def factory(cls, global_conf, **kwargs):
    print("Blog factory.")        
    return Blog()

最后,第三步是使用 loadapp 函數(shù)加載 WSGI 應(yīng)用。

loadapp 是 PasteDeploy 提供的一個(gè)函數(shù),使用它可以很方便地從第一步的ini配置文件里加載 app

loadapp 函數(shù)可以接收兩個(gè)實(shí)參:

  • URI:"config:<配置文件的全路徑>"
  • name:WSGI應(yīng)用的名稱(chēng)
conf_path = os.path.abspath('paste.ini')# 加載 appapplications = deploy.loadapp("config:{}".format(conf_path) , "main")# 啟動(dòng) server, 監(jiān)聽(tīng) localhost:22800 server = make_server("localhost", "22800", applications)
server.serve_forever()

applications 是URLMap 對(duì)象。

完善并整合第二步和第三步的內(nèi)容,寫(xiě)成一個(gè) Python 文件(wsgi_server.py)。內(nèi)容如下

import os
from paste import deploy
from wsgiref.simple_server import make_server

class Blog(object):
    def __init__(self):
        print("Init Blog.")

    def __call__(self, environ, start_response):
        status_code = "200 OK"
        response_headers = [("Content-Type", "text/plain")]
        response_body = "This is Blog's response body.".encode('utf-8')

        start_response(status_code, response_headers)
        return [response_body]

    @classmethod
    def factory(cls, global_conf, **kwargs):
        print("Blog factory.")
        return Blog()


class Wiki(object):
    def __init__(self):
        print("Init Wiki.")

    def __call__(self, environ, start_response):
        status_code = "200 OK"
        response_headers = [("Content-Type", "text/plain")]
        response_body = "This is Wiki's response body.".encode('utf-8')

        start_response(status_code, response_headers)
        return [response_body]

    @classmethod
    def factory(cls, global_conf, **kwargs):
        print("Wiki factory.")
        return Wiki()
      

if __name__ == "__main__":
    app = "main"
    port = 22800
    conf_path = os.path.abspath('paste.ini')

    # 加載 app
    applications = deploy.loadapp("config:{}".format(conf_path) , app)
    server = make_server("localhost", port, applications)

    print('Started web server at port {}'.format(port))
    server.serve_forever()

一切都準(zhǔn)備好后,在終端執(zhí)行 python wsgi_server.py來(lái)啟動(dòng) web server

如果像上圖一樣一切正常,那么打開(kāi)瀏覽器

  • 訪問(wèn)http://127.0.0.1:8000/blog,應(yīng)該顯示:This is Blog's response body.
  • 訪問(wèn)http://127.0.0.1:8000/wiki,應(yīng)該顯示:This is Wiki's response body.。

注意:urlmap對(duì)url的大小寫(xiě)是敏感的,例如如果訪問(wèn)http://127.0.0.1:8000/BLOG,在url映射中未能找到大寫(xiě)的BLOG。

到此,你學(xué)會(huì)了使用 PasteDeploy 的簡(jiǎn)單使用。

07. webob.dec.wsgify 裝飾器

經(jīng)過(guò)了 PasteDeploy 的路由調(diào)度,我們找到了 nova.api.openstack.compute:APIRouterV21.factory 這個(gè) application 的入口,看代碼知道它其實(shí)返回了 APIRouterV21 類(lèi)的一個(gè)實(shí)例。

WSGI規(guī)定 application 必須是一個(gè) callable 的對(duì)象,函數(shù)、方法、類(lèi)、實(shí)例,若是一個(gè)類(lèi)實(shí)例,就要求這個(gè)實(shí)例所屬的類(lèi)實(shí)現(xiàn) __call__ 的方法。

APIRouterV21 本身沒(méi)有實(shí)現(xiàn) __call__ ,但它的父類(lèi) Router實(shí)現(xiàn)了 __call__

我們知道,application 必須遵叢 WSGI 的規(guī)范

  1. 必須接收environ, start_response兩個(gè)參數(shù);
  2. 必須返回 「可迭代的對(duì)象」。

但從 Router 的 __call__ 代碼來(lái)看,它并沒(méi)有遵從這個(gè)規(guī)范,它不接收這兩個(gè)參數(shù),也不返回 response,而只是返回另一個(gè) callable 的對(duì)象,就這樣我們的視線被一次又一次的轉(zhuǎn)移,但沒(méi)有關(guān)系,這些__call__都是外衣,只要扒掉這些外衣,我們就能看到核心app。

而負(fù)責(zé)扒掉這層外衣的,就是其頭上的裝飾器 @webob.dec.wsgify ,wsgify 是一個(gè)類(lèi),其 __call__ 源碼實(shí)現(xiàn)如下:

可以看出,wsgify 在這里,會(huì)將 req 這個(gè)原始請(qǐng)求(dict對(duì)象)封裝成 Request 對(duì)象(就是規(guī)范1里提到的 environ)。然后會(huì)一層一層地往里地執(zhí)行被wsgify裝飾的函數(shù)(self._route), 得到最內(nèi)部的核心application。

上面提到了規(guī)范1里的第一個(gè)參數(shù),補(bǔ)充下第二個(gè)參數(shù)start_response,它是在哪定義并傳入的呢?

其實(shí)這個(gè)無(wú)需我們操心,它是由 wsgi server 提供的,如果我們使用的是 wsgiref 庫(kù)做為 server 的話。那這時(shí)的 start_response 就由 wsgiref 提供。

再回到 wsgify,它的作用主要是對(duì) WSGI app 進(jìn)行封裝,簡(jiǎn)化wsgi app的定義與編寫(xiě),它可以很方便的將一個(gè) callable 的函數(shù)或?qū)ο?,封裝成一個(gè) WSGI app。

上面,其實(shí)留下了一個(gè)問(wèn)題,self._route(routes 中間件 RoutesMiddleware對(duì)象)是如何找到真正的 application呢?

帶著這個(gè)問(wèn)題,我們了解下 routes 是如何為我們實(shí)現(xiàn)第二次路由。

08. 第二次路由:中間件 routes 路由

在文章最開(kāi)始處,我們給大家畫(huà)了一張圖。

這張圖把一個(gè) HTTP 請(qǐng)求粗略簡(jiǎn)單地劃分為兩個(gè)過(guò)程。但事實(shí)上,整個(gè)過(guò)程遠(yuǎn)比這個(gè)過(guò)程要復(fù)雜得多。

實(shí)際上在 WSGI Server 到 WSGI Application 這個(gè)過(guò)程中,我們加很多的功能(比如鑒權(quán)、URL路由),而這些功能的實(shí)現(xiàn)方式,我們稱(chēng)之為中間件。

中間件,對(duì)服務(wù)器而言,它是一個(gè)應(yīng)用程序,是一個(gè)可調(diào)用對(duì)象, 有兩個(gè)參數(shù),返回一個(gè)可調(diào)用對(duì)象。而對(duì)應(yīng)用程序而言,它是一個(gè)服務(wù)器,為應(yīng)用程序提供了參數(shù),并且調(diào)用了應(yīng)用程序。

今天以URL路由為例,來(lái)講講中間件在實(shí)際生產(chǎn)中是如何起作用的。

當(dāng)服務(wù)器拿到了客戶端請(qǐng)求的URL,不同的URL需要交由不同的函數(shù)處理,這個(gè)功能叫做 URL Routing。

在 Nova 中是用 routes 這個(gè)庫(kù)來(lái)實(shí)現(xiàn)對(duì)URL的的路由調(diào)度。接下來(lái),我將從源代碼處分析一下這個(gè)過(guò)程。

在routes模塊里有個(gè)中間件,叫 routes.middleware.RoutesMiddleware ,它將接受到的 url,自動(dòng)調(diào)用 map.match()方法,對(duì) url 進(jìn)行路由匹配,并將匹配的結(jié)果存入request請(qǐng)求的環(huán)境變量['wsgiorg.routing_args'],最后會(huì)調(diào)用self._dispatch(dispatch返回真正的application)返回response,最后會(huì)將這個(gè)response返回給 WSGI Server。

這個(gè)中間件的原理,看起來(lái)是挺簡(jiǎn)單的。并沒(méi)有很復(fù)雜的邏輯。

但是,我在閱讀 routes 代碼的時(shí)候,卻發(fā)現(xiàn)了另一個(gè)令我困惑的點(diǎn)。

self._dispatch (也就上圖中的self.app)函數(shù)里,我們看到了 app,controller 這幾個(gè)很重要的字眼,其是否是我苦苦追尋的 application 對(duì)象呢?

要搞明白這個(gè)問(wèn)題,只要看清 match 到是什么東西?

這個(gè) match 對(duì)象 是在 RoutesMiddleware.__call__() 里塞進(jìn) req.environ 的,它是什么東西呢,我將其打印出來(lái)。

{'action': u'detail', 'controller': <nova.api.openstack.wsgi.ResourceV21 object at 0x667bad0>, 'project_id': u'2ac17c7c792d45eaa764c30bac37fad9'}

{'action': u'index', 'controller': <nova.api.openstack.wsgi.ResourceV21 object at 0x6ec8910>, 'project_id': u'2ac17c7c792d45eaa764c30bac37fad9'}

{'action': u'show', 'controller': <nova.api.openstack.wsgi.ResourceV21 object at 0x6ed9710>, 'project_id': u'2ac17c7c792d45eaa764c30bac37fad9', 'id': u'68323d9c-ebe5-499a-92e9-32fea900a892'}

結(jié)果令人在失所望呀,這個(gè) app 并不是我們要尋找的 Controller 對(duì)象。而是 nova.api.openstack.wsgi.ResourceV21 類(lèi)的實(shí)例對(duì)象,說(shuō)白了就是Resource 對(duì)象。

看到這里,我有心態(tài)有點(diǎn)要崩了,怎么還沒(méi)到 Controller?OpenStack 框架的代碼繞來(lái)繞去的,沒(méi)有點(diǎn)耐心還真的很難讀下去。

既然已經(jīng)開(kāi)了頭,沒(méi)辦法還得硬著頭皮繼續(xù)讀了下去。

終于我發(fā)現(xiàn),在APIRouter初始化的時(shí)候,它會(huì)去注冊(cè)所有的 Resource,同時(shí)將這些 Resource 交由 routes.Mapper 來(lái)管理、創(chuàng)建路由映射,所以上面提到的 routes.middleware.RoutesMiddleware 才能根據(jù)url通過(guò) mapper.match 獲取到相應(yīng)的Resource。

從 Nova 代碼中看出每個(gè)Resource 對(duì)應(yīng)一個(gè) Controller 對(duì)象,因?yàn)?Controller 對(duì)象本身就是對(duì)一種資源的操作集合。

通過(guò)日志的打印,可以發(fā)現(xiàn) nova 管理的 Resource 對(duì)象有多么的多而雜

os-server-groups
os-keypairs
os-availability-zone
remote-consoles
os-simple-tenant-usage
os-instance-actions
os-migrations
os-hypervisors
diagnostics
os-agents
images
os-fixed-ips
os-networks
os-security-groups
os-security-groups
os-security-group-rules
flavors
os-floating-ips-bulk
os-console-auth-tokens
os-baremetal-nodes
os-cloudpipe
os-server-external-events
os-instance_usage_audit_log
os-floating-ips
os-security-group-default-rules
os-tenant-networks
os-certificates
os-quota-class-sets
os-floating-ip-pools
os-floating-ip-DNS
entries
os-aggregates
os-fping
os-server-password
os-flavor-access
consoles
os-extra_specs
os-interface
os-services
servers
extensions
metadata
metadata
limits
ips
os-cells
versions
tags
migrations
os-hosts
os-virtual-interfaces
os-assisted-volume-snapshots
os-quota-sets
os-volumes
os-volumes_boot
os-volume_attachments
os-snapshots
os-server-groups
os-keypairs
os-availability-zone
remote-consoles
os-simple-tenant-usage
os-instance-actions
os-migrations
os-hypervisors
diagnostics
os-agents
images
os-fixed-ips
os-networks
os-security-groups
os-security-groups
os-security-group-rules
flavors
os-floating-ips-bulk
os-console-auth-tokens
os-baremetal-nodes
os-cloudpipe
os-server-external-events
os-instance_usage_audit_log
os-floating-ips
os-security-group-default-rules
os-tenant-networks
os-certificates
os-quota-class-sets
os-floating-ip-pools
os-floating-ip-dns
entries
os-aggregates
os-fping
os-server-password
os-flavor-access
consoles
os-extra_specs
os-interface
os-services
servers
extensions
metadata
metadata
limits
ips
os-cells
versions
tags
migrations
os-hosts
os-virtual-interfaces
os-assisted-volume-snapshots
os-quota-sets
os-volumes
os-volumes_boot
os-volume_attachments
os-snapshots

你一定很好奇,這路由是如何創(chuàng)建的吧,關(guān)鍵代碼就是如下一行。如果你想要了解更多路由的創(chuàng)建過(guò)程,可以看一下這篇文章(Python Route總結(jié)),寫(xiě)得不錯(cuò)。

routes.mapper.connect("server",               
                "/{project_id}/servers/list_vm_state",
          controller=self.resources['servers'],
         action='list_vm_state',
         conditions={'list_vm_state': 'GET'})

歷盡了千辛萬(wàn)苦,我終于找到了 Controller 對(duì)象,知道了請(qǐng)求發(fā)出后,wsgi server是如何根據(jù)url找到對(duì)應(yīng)的Controller(根據(jù)routes.Mapper路由映射)。

但是很快,你又會(huì)問(wèn)。對(duì)于一個(gè)資源的操作(action),有很多,比如新增,刪除,更新等

不同的操作要執(zhí)行Controller 里不同的函數(shù)。

如果是新增資源,就調(diào)用 create()

如果是刪除資源,就調(diào)用 delete()

如果是更新資源,就調(diào)用 update()

那代碼如何怎樣知道要執(zhí)行哪個(gè)函數(shù)呢?

以/servers/xxx/action請(qǐng)求為例,請(qǐng)求調(diào)用的函數(shù)實(shí)際包含在請(qǐng)求的body中。

經(jīng)過(guò)routes.middleware.RoutesMiddleware的__call__函數(shù)解析后,此時(shí)即將調(diào)用的Resource已經(jīng)確定為哪個(gè)模塊中的Controller所構(gòu)建的Resource,而 action 參數(shù)為"action",接下來(lái)在Resource的__call__ 函數(shù)里面會(huì)因?yàn)閍ction=="action"從而開(kāi)始解析body的內(nèi)容,找出Controller中所對(duì)應(yīng)的方法。

Controller在構(gòu)建的過(guò)程中會(huì)由于MetaClass的影響將其所有action類(lèi)型的方法填入一個(gè)字典中,key由每個(gè)_action_xxx方法前的 @wsgi.action('xxx')裝飾函數(shù)給出,value為每個(gè)_action_xxx方法的名字(從中可以看出規(guī)律,在body里面請(qǐng)求的方法名前加上_aciton_即為Controller中對(duì)應(yīng)調(diào)用的方法)。

之后在使用Controller構(gòu)建Resource對(duì)象的過(guò)程中會(huì)向Resource注冊(cè)該Controller的這個(gè)字典中的內(nèi)容。這樣,只需在請(qǐng)求的body中給出調(diào)用方法的key,然后就可以找到這個(gè)key所映射的方法,最后在Resource的__call__函數(shù)中會(huì)調(diào)用Controller類(lèi)的這個(gè)函數(shù)!

其實(shí)我在上面我們打印 match 對(duì)象時(shí),就已經(jīng)將對(duì)應(yīng)的函數(shù)打印出來(lái)了。

這邊以 nova show(展示資源為例),來(lái)理解一下。

當(dāng)你調(diào)用 nova show [uuid] 命令,novaclient 就會(huì)給 nova-api 發(fā)送一個(gè)http的請(qǐng)求

nova show 1c250b15-a346-43c5-9b41-20767ec7c94b

通過(guò)打印得到的 match 對(duì)象如下

{'action': u'show', 'controller': <nova.api.openstack.wsgi.ResourceV21 object at 0x667bad0>, 'project_id': u'2ac17c7c792d45eaa764c30bac37fad9'}

其中 action 就是對(duì)應(yīng)的處理函數(shù),而controller 就對(duì)應(yīng)的 Resource 對(duì)象,project_id 是租戶id(你可以不理會(huì))。

繼續(xù)看 ResourceV21 類(lèi)里的 __call__ 函數(shù)的代碼。

圖示地方,會(huì)從 environ 里獲取中看到獲取 action 的具體代碼

我將這邊的 action_args打印出來(lái)

{'action': 'show', 'project_id': '2ac17c7c792d45eaa764c30bac37fad9', 'id': '1c250b15-a346-43c5-9b41-20767ec7c94b'}

其中 action 還是是函數(shù)名,id 是要操作的資源的唯一id標(biāo)識(shí)。

__call__ 的最后,會(huì) 調(diào)用 _process_stack 方法

Python web為什么離不開(kāi)WSGI

在圖標(biāo)處,get_method 會(huì)根據(jù) action(函數(shù)名) 取得處理函數(shù)對(duì)象。

meth :<bound method ServersController.show of <nova.api.openstack.compute.servers.ServersController object at 0x7be3750>>

最后,再執(zhí)行這個(gè)函數(shù),取得 action_result,在 _process_stack 會(huì)對(duì) response 進(jìn)行初步封裝。

Python web為什么離不開(kāi)WSGI

然后將 response 再返回到 wsgify ,由這個(gè)專(zhuān)業(yè)的工具函數(shù),進(jìn)行 response 的最后封裝和返回給客戶端。

至此,一個(gè)請(qǐng)求從發(fā)出到響應(yīng)就結(jié)束了。


看完了這篇文章,相信你對(duì)Python web為什么離不開(kāi)WSGI有了一定的了解,想了解更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!

本文題目:Pythonweb為什么離不開(kāi)WSGI
網(wǎng)頁(yè)地址:http://chinadenli.net/article36/gioesg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供小程序開(kāi)發(fā)定制網(wǎng)站、服務(wù)器托管企業(yè)網(wǎng)站制作、網(wǎng)站維護(hù)動(dòng)態(tài)網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都定制網(wǎng)站建設(shè)