WSGI 初探

WSGI是什麼?

WSGI的官方定義是,the Python Web Server Gateway Interface。從名字就可以看出來,這東西是一個Gateway,也就是網關。網關的作用就是在協議之間進行轉換。

下面對本文出現的一些名詞做定義。 wsgi app ,又稱應用,就是一個WSGI application,wsgi container ,又稱容器,雖然這個部分常常被稱為handler,不過我個人認為handler容易和app混淆,所以我稱之為容器,wsgi_middleware ,又稱中間件 。一種特殊類型的程序,專門負責在容器和應用之間。

理解的WSGI架構圖吧

WSGI應用

WSGI應用其實就是一個callable的對象。舉一個最簡單的例子,假設存在如下的一個應用:

Python代码  
def application(environ, start_response):  
  status = '200 OK'  
  output = 'World!'  
  response_headers = [('Content-type', 'text/plain'),  
                      ('Content-Length', str(12)]  
  write = start_response(status, response_headers)  
  write('Hello ')  
  return [output]  

這個WSGI應用簡單的可以用簡陋來形容,但是他的確是一個功能完整的WSGI應用。只不過給人留下了太多的疑點,environ是什麼? start_response是什麼?為什麼可以同時用write和return來返回內容?

對於這些疑問,不妨自己猜測一下他的作用。聯想到CGI,那麼environ可能就是一系列的環境變量,用來表示HTTP請求的信息,比如說method之類的。 start_response,可能是接受HTTP response頭信息,然後返回一個write函數,這個write函數可以把HTTP response的body返回給客戶端。 return自然是將HTTP response的body信息返回。不過這裡的write和函數返回有什麼區別?會不會是其實外圍默認調用write對應用返回值進行處理?而且為什麼應用的返回值是一個列表呢?說明肯定存在一個對應用執行結果的迭代輸出過程。難道說他隱含的支持iterator或者generator嗎?

等等,應用執行結果?一個應用既然是一個函數,說明肯定有一個對象去執行它,並且可以猜到,這個對象把environ和start_response傳給應用,將應用的返回結果輸出給客戶端。那麼這個對像是什麼呢?自然就是WSGI容器了。

WSGI容器

先說說WSGI容器的來源,其實這是我自己編造出來的一個概念。來源就是JavaServlet容器。我個人理解兩者有相似的地方,就順手拿過來用了。

WSGI容器的作用,就是構建一個讓WSGI應用成功執行的環境。成功執行,意味著需要傳入正確的參數,以及正確處理返回的結果,還得把結果返回給客戶端。

一般來說,WSGI容器必須依附於現有的webserver的技術才能實現,比如說CGI,FastCGI,或者是embed的模式。

下面利用CGI的方式編寫一個最簡單的WSGI容器。關於WSGI容器的協議官方文檔並沒有具體的說如何實現,只是介紹了一些需要約束的東西。具體內容​​看PEP3333中的協議。


Python代码  
#!/usr/bin/python  
#encoding:utf8  
  
import cgi  
import cgitb  
import sys  
import os  
  
#Make the environ argument  
environ = {}  
environ['REQUEST_METHOD'] = os.environ['REQUEST_METHOD']  
environ['SCRIPT_NAME'] = os.environ['SCRIPT_NAME']  
environ['PATH_INFO'] = os.environ['PATH_INFO']  
environ['QUERY_STRING'] = os.environ['QUERY_STRING']  
environ['CONTENT_TYPE'] = os.environ['CONTENT_TYPE']  
environ['CONTENT_LENGTH'] = os.environ['CONTENT_LENGTH']  
environ['SERVER_NAME'] = os.environ['SERVER_NAME']  
environ['SERVER_PORT'] = os.environ['SERVER_PORT']  
environ['SERVER_PROTOCOL'] = os.environ['SERVER_PROTOCOL']  
environ['wsgi.version'] = (1, 0)  
environ['wsgi.url_scheme'] = 'http'  
environ['wsgi.input']        = sys.stdin  
environ['wsgi.errors']       = sys.stderr  
environ['wsgi.multithread']  = False  
environ['wsgi.multiprocess'] = True  
environ['wsgi.run_once']     = True  
  
  
#make the start_response argument  
#注意,WSGI协议规定,如果没有body内容,是不能返回http response头信息的。  
sent_header = False  
res_status = None  
res_headers = None  
  
def write(body):  
    global sent_header  
    if sent_header:  
        sys.stdout.write(body)  
    else:  
        print res_status  
        for k, v in res_headers:  
            print k + ': ' + v  
        print   
        sys.stdout.write(body)  
        sent_header = True  
  
def start_response(status, response_headers):  
    global res_status  
    global res_headers  
    res_status = status  
    res_headers = response_headers  
    return write  
  
#here is the application  
  def application(environ, start_response):  
    status = '200 OK'  
    output = 'World!'  
    response_headers = [('Content-type', 'text/plain'),  
                        ('Content-Length', str(12)]  
    write = start_response(status, response_headers)  
    write('Hello ')  
    return [output]  
  
#here run the application  
result = application(environ, start_response)  
for value in result:   
    write(value)  

不過我從WSGI容器的設計中可以看出WSGI的應用設計上面存在著一個重大的問題就是:為什麼要提供兩種方式返回數據?明明只有一個write函數,卻既可以在application裡面調用,又可以在容器中傳輸應用的返回值來調用。如果說讓我來設計的話,直接把start_response給去掉了。就用application(environ)這個接口。傳一個方法,然後返回值就是status, response_headers和一個字符串的列表。實際傳輸的方法全部隱藏了。用戶只需要從environ中讀取數據處理就行了。 。

Middleware中間件

中間件是一類特殊的程序,可以在容器和應用之間乾一些壞事。 。其實熟悉python的decorator的人就會發現,這和decoraotr沒什麼區別。

下面來實現一個route的簡單middleware。


Python代码  
class Router(object):  
    def __init__(self):  
        self.path_info = {}  
    def route(self, environ, start_response):  
        application = self.path_info[environ['PATH_INFO']]  
        return application(environ, start_response)  
    def __call__(self, path):  
        def wrapper(application):  
            self.path_info[path] = application  
        return wrapper 
這就是一個很簡單的路由功能的middleware。將上面那段wsgi容器的代碼裡面的應用修改成如下:


Python代码  
router = Router()  
  
#here is the application  
@router('/hello')  
def hello(environ, start_response):  
    status = '200 OK'  
    output = 'Hello'  
    response_headers = [('Content-type', 'text/plain'),  
                        ('Content-Length', str(len(output)))]  
    write = start_response(status, response_headers)  
    return [output]  
 
@router('/world')  
def world(environ, start_response):  
    status = '200 OK'  
    output = 'World!'  
    response_headers = [('Content-type', 'text/plain'),  
                        ('Content-Length', str(len(output)))]  
    write = start_response(status, response_headers)  
    return [output]  
#here run the application  
result = router.route(environ, start_response)  
for value in result:   
    write(value)  
這樣,容器就會自動的根據訪問的地址找到對應的app執行了。

留言

這個網誌中的熱門文章

Json概述以及python對json的相關操作

Docker容器日誌查看與清理

利用 Keepalived 提供 VIP