如果問你 WSGI 是什麼?0歲Web後端開發必看

Python之禪發表於2018-09-19

640?wx_fmt=jpeg
題圖:Photo by Jaredd Craig on Unsplash

寫了幾年的 Python Web 程式,說不出 WSGI 是什麼? 說不出 Web 程式是如何跑起來的? 說不出一個Web核心框架應該包含哪些東西? 這估計是工資漲不上去的原因之一。讀完這篇文章,幫你瞭解 WSGI 是什麼。


背景

Python Web 開發中,服務端程式可以分為兩個部分,一是伺服器程式,二是應用程式。前者負責把客戶端請求接收,整理,後者負責具體的邏輯處理。為了方便應用程式的開發,我們把常用的功能封裝起來,成為各種Web開發框架,例如 Django, Flask, Tornado。不同的框架有不同的開發方式,但是無論如何,開發出的應用程式都要和伺服器程式配合,才能為使用者提供服務。這樣,伺服器程式就需要為不同的框架提供不同的支援。這樣混亂的局面無論對於伺服器還是框架,都是不好的。對伺服器來說,需要支援各種不同框架,對框架來說,只有支援它的伺服器才能被開發出的應用使用。

這時候,標準化就變得尤為重要。我們可以設立一個標準,只要伺服器程式支援這個標準,框架也支援這個標準,那麼他們就可以配合使用。一旦標準確定,雙方各自實現。這樣,伺服器可以支援更多支援標準的框架,框架也可以使用更多支援標準的伺服器。

640?wx_fmt=jpeg
幫助理解,畫了個圖


Python Web開發中,這個標準就是 The Web Server Gateway Interface, 即 WSGI. 這個標準在PEP 333中描述,後來,為了支援 Python 3.x, 並且修正一些問題,新的版本在PEP 3333中描述。

WSGI 是什麼

WSGI 是伺服器程式與應用程式的一個約定,它規定了雙方各自需要實現什麼介面,提供什麼功能,以便二者能夠配合使用。

WSGI 不能規定的太複雜,否則對已有的伺服器來說,實現起來會困難,不利於WSGI的普及。同時WSGI也不能規定的太多,例如cookie處理就沒有在WSGI中規定,這是為了給框架最大的靈活性。要知道WSGI最終的目的是為了方便伺服器與應用程式配合使用,而不是成為一個Web框架的標準。

另一方面,WSGI需要使得middleware(是中介軟體麼?)易於實現。middleware處於伺服器程式與應用程式之間,對伺服器程式來說,它相當於應用程式,對應用程式來說,它相當於伺服器程式。這樣,對使用者請求的處理,可以變成多個 middleware 疊加在一起,每個middleware實現不同的功能。請求從伺服器來的時候,依次通過middleware,響應從應用程式返回的時候,反向通過層層middleware。我們可以方便地新增,替換middleware,以便對使用者請求作出不同的處理。

WSGI主要是對應用程式與伺服器端的一些規定,所以,它的主要內容就分為兩個部分。

應用程式

WSGI規定:

1. 應用程式需要是一個可呼叫的物件

在Python中:

  • 可以是函式

  • 可以是一個例項,它的類實現了call方法

  • 可以是一個類,這時候,用這個類生成例項的過程就相當於呼叫這個類


同時,WSGI規定:

2. 可呼叫物件接收兩個引數

這樣,如果這個物件是函式的話,它看起來要是這個樣子:

# callable function
def application(environ, start_response):
   pass

如果這個物件是一個類的話,它看起來是這個樣子:

# callable class
class Application:
   def __init__(self, environ, start_response):
       pass

如果這個物件是一個類的例項,那麼,這個類看起來是這個樣子:

# callable object
class ApplicationObj:
   def __call__(self, environ, start_response):
       pass

最後,WSGI還規定:

3.可呼叫物件要返回一個值,這個值是可迭代的。

這樣的話,前面的三個例子就變成:

HELLO_WORLD = b"Hello world!\n"


# callable function
def application(environ, start_response):
   return [HELLO_WORLD]


# callable class
class Application:
   def __init__(self, environ, start_response):
       pass

   def __iter__(self):
       yield HELLO_WORLD


# callable object
class ApplicationObj:
   def __call__(self, environ, start_response):
       return [HELLO_WORLD]

你可能會說,不是啊,我們平時寫的web程式不是這樣啊。 比如如果使用web.py框架的話,一個典型的應用可能是這樣的:

class hello:
   def GET(self):
       return 'Hello, world!'

這是由於框架已經把WSGI中規定的一些東西封裝起來了,我們平時用框架時,看不到這些東西,只需要直接實現我們的邏輯,再返回一個值就好了。其它的東西框架幫我們做好了。這也是框架的價值所在,把常用的東西封裝起來,讓使用者只需要關注最重要的東西。

當然,WSGI關於應用程式的規定不只這些,但是現在,我們只需要知道這些就足夠了。下面,再介紹伺服器程式。

伺服器程式

伺服器程式會在每次客戶端的請求傳來時,呼叫我們寫好的應用程式,並將處理好的結果返回給客戶端。

WSGI規定:

4.伺服器程式需要呼叫應用程式

伺服器程式看起來大概是這個樣子的:

def run(application):
   environ = {}

   def start_response(status, response_headers, exc_info=None):
       pass

   result = application(environ, start_response)

   def write(data):
       pass

   for data in result:
       write(data)

這裡可以看出伺服器程式是如何與應用程式配合完成使用者請求的。

WSGI規定了應用程式需要一個可呼叫物件,有兩個引數,返回一個可迭代物件。在伺服器 程式中,針對這幾個規定,做了以下幾件事:

  1. 把應用程式需要的兩個引數設定好

  2. 呼叫應用程式

  3. 迭代訪問應用程式的返回結果,並將其傳回客戶端

你可以從中發現,應用程式需要的兩個引數,一個是一個dict物件,一個是函式。它們到底有什麼用呢?這都不是我們現在應該關心的,現在只需要知道,伺服器程式大概做了什麼事情就好了,後面,我們會深入討論這些細節。

middleware

另外,有些功能可能介於伺服器程式和應用程式之間,例如,伺服器拿到了客戶端請求的URL, 不同的URL需要交由不同的函式處理,這個功能叫做 URL Routing,這個功能就可以放在二者中間實現,這個中間層就是 middleware。

middleware對伺服器程式和應用是透明的,也就是說,伺服器程式以為它就是應用程式,而應用程式以為它就是伺服器。這就告訴我們,middleware需要把自己偽裝成一個伺服器,接受應用程式,呼叫它,同時middleware還需要把自己偽裝成一個應用程式,傳給伺服器程式。

其實無論是伺服器程式,middleware 還是應用程式,都在服務端,為客戶端提供服務,之所以把他們抽象成不同層,就是為了控制複雜度,使得每一次都不太複雜,各司其職。

640?wx_fmt=png

下面,我們看看middleware大概是什麼樣子的。

# URL Routing middleware
def urlrouting(url_app_mapping):
   def midware_app(environ, start_response):
       url = environ['PATH_INFO']
       app = url_app_mapping[url]

       result = app(environ, start_response)

       return result

   return midware_app

函式 midware_app就是一個簡單的middleware:對伺服器而言,它是一個應用程式,是一個可呼叫物件, 有兩個引數,返回一個可呼叫物件。對應用程式而言,它是一個伺服器,為應用程式提供了引數,並且呼叫了應用程式。

另外,這裡的urlrouting函式,相當於一個函式生成器,你給它不同的 url-app 對映關係,它會生成相應的具有 url routing功能的 middleware。

如果你僅僅想簡單瞭解一下WSGI是什麼,相信到這裡,你差不多明白了,下面會介紹WSGI的細節,這些細節來自 PEP3333。

Python之禪注:

其實在 Python 中,已經內建了一個實現 WSGI 的伺服器程式

from wsgiref.simple_server import make_server

def application(environ, start_response):
   start_response('200 OK', [('Content-Type', 'text/html')])
   return [b'Hello, World']

if __name__ == '__main__':
   server = make_server('', 8888, application)
   print("server running on port 8888")
   server.serve_forever()

這個Web程式包含了兩部分,application是應用程式部分,make_server會建立一個伺服器程式,伺服器程式啟動時,監聽8888埠,瀏覽器訪問 localhost:8888 時,會顯示Hello, World。 這裡的 application可以用你的 Django 程式來代替。


作者:minixalpha
原文:https://blog.csdn.net/on_1y/article/details/18803563

640?

推薦閱讀

相關文章