Python 中的“CGI” 介面——WSGI

來源:互聯網
上載者:User

標籤:

今天在 git.oschina 的首頁上看到他們推出示範平台,其中,Python 的示範平台支援 WSGI 介面的應用。雖然,這個示範平台連它自己提供的樣本都跑不起來,但是,它還是成功的勾起了我對 WSGI 的強烈好奇心。一番瞭解,對該機制的認識,總結如下。如有不妥,還望斧正。

 

為什麼是 WSGI?

寫過網頁應用的各位親,應該對 CGI 有瞭解,我們知道,CGI 的全程是“Common Gateway Interface”,即通用 Gateway Interface。沒錯,這裡的 WSGI,就是只針對 Python的網頁應用介面“Python Web Server Gateway Interface”。通過這樣的類比,想必大家對他的地位就有所瞭解了。

它只是一個介面定義:它不負責伺服器的實現,也不負責網頁應用的實現,它只是一個兩邊介面方式的約定。所以,它並不是另一個 WEB 應用程式框架,而通常意義上的 WEB 應用程式框架,也只相當於 WSGI 網頁應用端的一種實現。

這樣做的好處是?PEP 0333 中的解釋是,為了實現一個類似於 Java Servelet 的 API,使得遵循該介面的應用擁有更廣泛的適用性。是的,有了該介面,你就不用去考慮,伺服器對 Python 的支援到底是如何?——直接用 Python 實現的伺服器?伺服器嵌入 Python?或者是通過網關介面(CGI, Fastcgi...)——應用程式都有很好的適用性。就像是今天故事的開始,我們遇到了雲平台,它提供了對 WSGI 介面的支援,那麼,只要應用是基於該 WSGI 的,那麼應用就可以直接跑起來。

此外,WSGI 的設計,也提供了另外一種可能性,那就是中介軟體(middleware)。或者說,我們可以寫一些對 server 和 application 都相容的模組,我們可以把他們部署在 Server 端,也可以部署在 Application 端,完成比如緩衝、字元編碼轉換、根據 url 做應用 routing 等功能。這種設計模式,是 WSGI 降低了 server 和 application 耦合度之後的產物,同時,它從另一個角度大大提升了設計的靈活性。

 

WSGI 實施概略

上一小節,簡要對 WSGI 做了介紹。這裡從 application、server、middleware 三個角度對 WSGI 稍微進行深入,使我們對它有一個更具體的印象。

1)Application 端

WSGI 要求,應用端必須提供一個可被調用的實體(PEP 0333 使用的是 Object,文檔還特別解釋這有別於Object instance),該實體可以是:一個函數(function)、一個方法(method)、一個類(class)、或者是有__call__方法的對象(Object instance)。

這裡有兩個網頁應用端的實現樣本,一個 function,一個 class:

def simple_app(environ, start_response):    status = ‘200 OK‘    response_headers = [(‘Content-type‘, ‘text/plain‘)]    start_response(status, response_headers)    return [‘Hello world!\n‘]

上面的 function 並沒有處理來自 server 端的 environ,只是直接對請求直接做了 “200 ok” 回應;需要注意的是,server 對他進行調用(call)後,它返回一個 list(用“[]”包含在內)以保證結果的 iterable。下面的 class 功能類似。下面的 AppClass 作為應用實體的情況下,在具體實施時,server 對類進行調用(call),其實是對他進行了例化(可以參考後面 server 端的實現代碼),正如我們看到,這次調用(call)的傳回值也是可迭代的——雖然只迭代一次。

class AppClass:    def __init__(self, environ, start_response):        self.environ = environ        self.start = start_response    def __iter__(self):        status = ‘200 OK‘        response_headers = [(‘Content-type‘, ‘text/plain‘)]        self.start(status, response_headers)        yield "Hello world!\n"        """ In fact, the interator ‘ends‘ here because of no more yield field"""

使用對象作為應用實體時,可參考上面使用 function 作為實體的方法:為類添加 __call__ 方法,同時,其傳回值需為 iterable(比如 return [ something ])。

給 application 的兩個參數,是兩個位置相關的參數(不是具名引數),分別是:一個存放了 CGI 環境變數的 dictionary object,和一個可調用實體(需要給它三個位置相關的參數,兩個必須,一個可選)。

其中,可調用實體(前例中的 start_response)必須調用一次,兩個必須的參數分別為“ HTTP Response的狀態(str 類型)“ 和 “HTTP Response Header(list of tuples)“;一個可選的參數 exc_info,必須是 Python sys.exc_info() tuple,只有在出錯需要顯示錯誤資訊時使用。完整調用:start_response(status, response_headers,exc_info).

 

2)Server 端

下面是從 PEP 0333 拿來的一個簡單的 WSGI 容器,適用於 Python 作為 CGI 的應用程式框架。

import os, sysdef run_with_cgi(application):    environ = dict(os.environ.items())    environ[‘wsgi.input‘]        = sys.stdin    environ[‘wsgi.errors‘]       = sys.stderr    environ[‘wsgi.version‘]      = (1, 0)    environ[‘wsgi.multithread‘]  = False    environ[‘wsgi.multiprocess‘] = True    environ[‘wsgi.run_once‘]     = True    if environ.get(‘HTTPS‘, ‘off‘) in (‘on‘, ‘1‘):        environ[‘wsgi.url_scheme‘] = ‘https‘    else:        environ[‘wsgi.url_scheme‘] = ‘http‘    headers_set = []    headers_sent = []    def write(data):        if not headers_set:             raise AssertionError("write() before start_response()")        elif not headers_sent:             # Before the first output, send the stored headers             status, response_headers = headers_sent[:] = headers_set             sys.stdout.write(‘Status: %s\r\n‘ % status)             for header in response_headers:                 sys.stdout.write(‘%s: %s\r\n‘ % header)             sys.stdout.write(‘\r\n‘)        sys.stdout.write(data)        sys.stdout.flush()    def start_response(status, response_headers, exc_info=None):        if exc_info:            try:                if headers_sent:                    # Re-raise original exception if headers sent                    raise exc_info[0], exc_info[1], exc_info[2]            finally:                exc_info = None     # avoid dangling circular ref        elif headers_set:            raise AssertionError("Headers already set!")        headers_set[:] = [status, response_headers]        return write    result = application(environ, start_response)
try: for data in result: if data: # don‘t send headers until body appears write(data) if not headers_sent: write(‘‘) # send headers now if body was empty finally: if hasattr(result, ‘close‘): result.close()

上面的容器,大概實現了:a)將 CGI 環境變數放入 dictionary object (environ)中,供 Application 實體使用;b)定義了 start_response 方法,供 Application 實體調用;c)調用 application 實體,對 web 請求進行處理;d)將 application 的返回結果,以及通過 start_response 設定的 HTTP Response HEADER,寫到 stdout ——像其他 CGI 一樣,實際上是被發往網頁。

 

3) 作為 middleware

因為 WSGI 的寬鬆耦合的特性,我們可以輕鬆的在 Application 和 Server 之前插入任何的中間外掛程式,在不需要改動 Server 和 Application 的前提下,實現一些特殊功能。但是,這种放在 Server 和 Application “中間”的模組,並不是這裡要講的 middleware ;或者,這隻能算是一種特殊的 middleware,因為它僅僅是實現了 PEP 0333 中 middleware 定義的 Application 側的功能。這種僅實施在一側的 middleware,需要在發布時,特別的聲明。

PEP 0333 中約定,中介軟體是一些即可以在 Server 端實施,又可以在 Application 端實施的模組。所以,在設計的時候,對兩邊的特性都要做適當考慮。幸好,WSGI 介面設計的足夠簡單。

class Router():        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
""" The above is the middleware"""
router = Router()@router(‘/world‘)def world(environ, start_response): status = ‘200 OK‘ output = ‘World!‘start_response(status, response_headers) return [output] @router(‘/hello‘) def hello(environ, start_response): status = ‘200 OK‘ output = ‘Hello‘ response_headers = [(‘Content-type‘, ‘text/plain‘), (‘Content-Length‘, str(len(output)))] start_response(status, response_headers) return [output]

簡單解釋一下:

- 作為 Application 時,我們用 Router 執行個體化一個對象。然後對 “ PATH-APP “ 進行註冊,根據不同的 PATH,我們要進一步選擇哪個 App。接著,就是把 router.route() 餵給 Server ,作為 Application 側的可調用實體。有請求到來時,根據已經註冊的 “PATH-APP” 對選擇應用並執行。

- Server 端類似,我們要先執行個體化並完成註冊。然後,比如,拿我們上一小節實現的 WSGI 容器為例,我們需要修改 result = router.route(environ, start_response),同樣完成了router的功能。

下面是另外一個,實現了 postprocessor 的一個例子,在 Application 返回的 HTTP Header 裡面再加一個 Header。

def myapp(environ, start_response):    response_headers = [(‘content-type‘, ‘text/plain‘)]    start_response(‘200 OK‘, response_headers)    return [‘Check the headers!‘]class Middleware:    def __init__(self, app):        self.wrapped_app = app    def __call__(self, environ, start_response):        def custom_start_response(status, headers, exc_info=None):            headers.append((‘X-A-SIMPLE-TOKEN‘, "1234567890"))            return start_response(status, headers, exc_info)        return self.wrapped_app(environ, custom_start_response)app = Middleware(myapp)

這裡通過改寫傳遞給 Application 的實體,實現了 postprocess 的目的。

 

其他資源:

 - WSGI 的一些詳細資料,包括應用列表什麼的:http://wsgi.readthedocs.org/en/latest/

 - 支援 WSGI 的多線程 WEB 伺服器,基於SimpleHttpServer:http://www.owlfish.com/software/wsgiutils/

 - Paste 為構建以 WSGI 為基礎的 WEB 應用程式或架構提供一個良好的基礎

- 官方的 WSGI 實現參考:https://pypi.python.org/pypi/wsgiref

- 啄木鳥社區的 WSGI 中文 wiki:http://wiki.woodpecker.org.cn/moin/WSGI

- 和 Paste 一樣有名的基本架構:https://pypi.python.org/pypi/Pylons/1.0

- 目前 Python 比較流行的三大 WEB 架構:TurboGears,Django,web2py。+1,代碼在 K 層級的服務小架構:webpy。

Python 中的“CGI” 介面——WSGI

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.