PEP333翻譯

轉自:http://angeloce.iteye.com/blog/519286

英文原文:http://www.python.org/dev/peps/pep-0333/


摘要

       爲促進python的web應用在各類web服務器上的移植, 本文檔指定了web服務器和python的web框架間的標準接口.

原理和目標

       python目前擁有了大量的web框架,例如Zope, Quixote, Webware, SkunkWeb, PSO, Twisted Web.(想看更多點擊http://wiki.python.org/moin/WebFrameworks .). 從如此多的框架中選擇對剛剛進入python的新人來說是個問題,一般來說, 選擇一個web框架限制了對web服務器的選擇,反過來也一樣.相比之下,儘管 Java也有衆多的web框架, Java的 servlet  API使得程序員可以使用任何Java web框架編寫在能運行在任何web服務器上的程序,只要他們都支持servlet API。

概述

        WSGI接口有兩個方面, server 或 gateway 服務端,  以及 application 或 framework 應用端. 服務端會調用一個應用端的可調用對象, 如何傳遞可調用對象的細節由服務端決定.有可能一些服務器要求應用程序的開發者寫一個腳本來創建服務器/網關的一個實例來獲得應用程序;另一些服務器可能使用配置文件或者其他的機制來制定哪裏有一個需要被import/ obtain 的應用程序.

              除了這些服務器,網管,應用程序和框架外,也可以創建了實現WSGI隨意一側接口的中間件組件.這些組件從服務器看就像應用,從應用程序看就像服務器,利用這樣的組建可以提供更多的接口, 內容轉換, 導航等等其他有用的功能。

             在整個概述中,我們使用術語"callable"來表示一個函數,方法, 類或者一個實現了__call__方法的實例.可調用對象的實現要根據服務端和應用端的需求來選擇合適的方法.相反, 服務端和應用端調用這個可調用對象的時候並不用去關心到底是誰提供給它的, 僅僅是調用它們而已, 而不會對它們內省.

應用端

             應用程序對象是一個需要接受兩個參數的可調用對象. 這個術語"object(對象)"不應該被誤解爲需要一個對象實例: 函數,方法, 類, 實現了__call__方法的實例都可以作爲一個應用程序對象來使用.應用程序對象必須能夠被多次調用, 幾乎所有的服務器/網關(除了CGI)都會製造重複請求.(注意: 儘管我們一直說應用程序對象,  這不應該被理解爲, 假設應用開發者使用已存在的高等web框架服務來開發他們的應用, 就必須使用WSGI作爲web編程的API!WSGI對於框架和服務器開發者僅僅是一個工具,並沒想要直接支持應用開發人員.)這有兩個例子,一個是函數,另一個是類:

python代碼:

def simple_app(environ, start_response):
    """Simplest possible application object"""
    status = '200 OK'
    response_headers = [('Content-type','text/plain')]
    start_response(status, response_headers)
    return ['Hello world!\n']
class AppClass:
    """Produce the same output, but using a class

    (Note: 'AppClass' is the "application" here, so calling it
    returns an instance of 'AppClass', which is then the iterable
    return value of the "application callable" as required by
    the spec.

    If we wanted to use *instances* of 'AppClass' as application
    objects instead, we would have to implement a '__call__'
    method, which would be invoked to execute the application,
    and we would need to create an instance for use by the
    server or gateway.
    """

    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"

服務端

              服務器和網關在得到每一個http客戶端的請求後都會調用一次可調用的應用對象, 請求直接用於應用.爲了說明,這有一個簡單的CGI網關,實現了一個獲得了應用對象的函數.注意這個簡單的例子已經限制了錯誤處理, 因爲默認情況下一個未被捕捉到的異常會被髮送到sys.stderr並在web服務器上記錄下來.

import os, sys

def 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()

中間件

              請注意一個單獨的對象可能對於應用端扮演服務器的角色,對於服務端又扮演應用程序的角色. 這樣的中間件可以實現這件功能:

              1. 根據目標地址將請求發送給不同的應用程序對象,並在之後重寫環境變量.

              2. Allowing multiple applications or frameworks to run side-by-side in the same process 

                  Load balancing and remote processing, by forwarding requests and responses over a network

                  Perform content postprocessing, such as applying XSL stylesheets

from piglatin import piglatin

class LatinIter:

    """Transform iterated output to piglatin, if it's okay to do so

    Note that the "okayness" can change until the application yields
    its first non-empty string, so 'transform_ok' has to be a mutable
    truth value."""

    def __init__(self,result,transform_ok):
        if hasattr(result,'close'):
            self.close = result.close
        self._next = iter(result).next
        self.transform_ok = transform_ok

    def __iter__(self):
        return self

    def next(self):
        if self.transform_ok:
            return piglatin(self._next())
        else:
            return self._next()

class Latinator:

    # by default, don't transform output
    transform = False

    def __init__(self, application):
        self.application = application

    def __call__(self, environ, start_response):

        transform_ok = []

        def start_latin(status,response_headers,exc_info=None):

            # Reset ok flag, in case this is a repeat call
            transform_ok[:]=[]

            for name,value in response_headers:
                if name.lower()=='content-type' and value=='text/plain':
                    transform_ok.append(True)
                    # Strip content-length if present, else it'll be wrong
                    response_headers = [(name,value)
                        for name,value in response_headers
                            if name.lower()<>'content-length'
                    ]
                    break

            write = start_response(status,response_headers,exc_info)

            if transform_ok:
                def write_latin(data):
                    write(piglatin(data))
                return write_latin
            else:
                return write

        return LatinIter(self.application(environ,start_latin),transform_ok)


# Run foo_app under a Latinator's control, using the example CGI gateway
from foo_app import foo_app
run_with_cgi(Latinator(foo_app))

詳細說明

              應用程序對象必須接受兩個位置參數.爲了更好的解釋,我們叫他們一個爲environ一個爲start_response( = res),但他們並不要求一定是這個名字.服務端必須使用位置參數(而不是關鍵字參數)來調用應用程序對象.例如得調用 result = application(environ, start_response)

              environ是一個字典, 包括了CGI環境變量.environ必須是 python內建的字典對象dict(不是其子類, 用戶字典或其他模擬的字典), 應用程序可以以任何方式改變environ. environ必須包含來一些WSGI必需的變量(在下一章節說明),和服務器專有的變量, 按照慣例命名方式見下文

            res參數是一個包含了兩個位置參數,一個可選參數的可調用對象. 爲了說明, 我們叫這三個參數分別爲 status, response_headers 和 exc_info, 當然他們也不是必需使用這個名字.應用程序必須使用位置參數來調用他們.

                status參數是一個類似"999 Message here"格式的狀態字符串, reponse_headers是一個包含描述HTTP響應頭的(header_name, header_value)元祖的列表, 可選參數exc_info 僅僅被使用在應用程序捕獲到錯誤並試圖把錯誤信息顯示到瀏覽器的時候.

                res對象必須返回一個接受一個位參(一個寫成HTTP響應體的字符串)的可寫可調用對象.(注意: write()現在僅提供給一些已存在的框架來支持其需要的輸出接口.在編寫新的應用或框架時如果可以避免就不要使用它.更多細節可參考Buffering and Streaming 部分)

               當服務端調用時, 應用程序對象必須返回一個可迭代的對象以生成零到多個字符串.這可以以多種方式實現,比如返回一個字符串列表,或者製作一個生成器函數來生成字符串(yield), 或者返回一個可迭代的實例.不論如何實現,應用程序對象必須返回一個可迭代輸出零到多個字符串的迭代器.

               服務端必須把生成的字符串以無緩衝的方式傳遞給客戶端, 在另一個請求到來前完成每個字符串的傳輸.參見下面的Buffering and Streaming章節瞭解關於應用程序如何處理輸出.

               服務端應該將這些字符串視爲二進制字節序列,尤其要確保行結束符沒有改動.應用程序有責任確定字符串格式適用於客戶端.(服務端可能會隊HTTP傳輸編碼,或者爲了實現服務器特性執行其他轉換例如字節範圍內傳輸.更多細節參見下面Other HTTP Features章節.)

              如果對於返回的迭代器求長(len)成功了,服務端應該相信這個結果是精確的.就是說,如果從應用程序返回的迭代器提供了__len__()方法, 它肯定會返回一個精確地結果.(參看如何處理Content-Length Header章節會知道要使用這個結果.)

              如果迭代器還提供了close()方法,服務端必須在當前請求完成時調用該方法,無論該請求是正常的結束了還是由於錯誤提早終止了.(這是爲了能釋放應用程序資源. 該協議試圖完成PEP325的生成器支持, 和其他平常的帶有close()f方法的迭代器.)

              注意: 應用程序必須在該迭代器生成第一個字符串前調用start_response對象, 以使得服務端能在發送任何具體內容前發送頭內容.然而, 這個調用可以再迭代器首次迭代前被執行, 所以服務端必須假定start_response()在迭代前被調用了.

              最後, 服務端不應該使用迭代器的任何其他屬性, 除非它對服務端是一個專門制定的實例,例如由wsgi.file_wrapper返回的"file_wrapper"(請看可選的特定平臺文件處理Platform-Specific File Handling). 一般情況下, 只是用這裏指定的屬性, 或者是PEP234規定的迭代器接口.


environ變量

              environ字典需要包含這些CGI環境變量, 作爲對通用接口規範的定義.下面這些變量必須存在在字典中, 除非它們的值是空字符串, 它們被忽略掉.

              REQUEST_METHOD HTTP請求方法,諸如 GET 或 POST.不能寫成空字符串,當然也是必需的.

              SCRIPT_NAME 相對於應用對象請求的URL地址的初始部分, 以使得應用程序知道請求的虛擬地址.這個可以是空字符串, 如果應用位置在服務端的根目錄地址.

              PATH_INFO 請求URL地址的剩餘部分, 指示請求應用目標的虛擬位置. 這個可以是空字符串, 如果請求目標位應用的根目錄並且結尾沒有反斜槓.

              QUERY_STRING 在"?"後面的請求URL部分.可以爲空或者省略掉.

              CONTENT_TYPEHTTP請求中所有Content-Type字段的內容. 可以爲空或省略.

              CONTENT_LENGTH HTTP請求中所有Content-Length 字段的內容. 可以爲空或省略.

              SERVER_NAME, SERVER_PORT在連接SCRIPT_NAME 和PATH_INFO時,這兩個變量可以用來完成整個URL. 請注意, 如果HTTP_HOST存在, 應該優先於使用它而不是SERVER_NAME來重建URL. 更多細節請參見下面的 URL Reconstruction 章節. 這兩個變量都不能爲空, 當然也是必需的.

             SERVER_PROTOCOL客戶端發送請求使用的協議版本. 通常這是些類似 "HTTP/1.0" 或 "HTTP/1.1"的東西, 可能會使應用程序來決定如果處理每個HTTP請求頭.(這個變量或許應該叫做REQUEST_PROTOCOL, 因爲它指的是請求的協議, 不一定代表服務端響應的協議. 然而爲了兼容CGI我們必須保留這個名字.)

             HTTP_ Variables與客戶端支持的HTTP請求頭一致的變量.(也就是以"HTTP_"開頭命名的變量.)這些變量是否出現都要與HTTP請求頭中的變量保持一致.

            

服務端應該嘗試提供其他合適的CGI變量.如果使用了SSL, 也應該提供合適的Apache SSL環境變量, 例如 HTTPS=on 和 SSL_PROTOCOL. 然而請注意, 使用了除上述之外其他CGI兩邊的應用程序對於不支持相關擴展的web服務器來說是不可移植過來的.(例如, 一個不能發佈文件的web服務器不可能提供有意義的DOCUMENT_ROOT 或 PATH_TRANSLATED 變量.)

 

一個遵守WSGI協議的服務端應該記錄它會提供什麼變量. 應用程序應該檢查它需要的變量是否都存在, 如果缺少了某個變量應該有 後備的 方法.

 

注意: 缺失的變量(例如沒有發生認證的REMOTE_USER變量)應該從字典中忽略掉. 而且要注意 CGI定義的變量如果存在就一定是個字符串. 它違反了 CGI變量可以是任何類型(不一定要是字符串)的規定.

          除了CGI定義的變量外, environ字典可以包括操作系統的環境變量, 但必須包括下列WSGI定義的變量:

          wsgi.version元祖 (1,0), 表示WSGI的版本是1.0

          wsgi.url_scheme表示哪個應用會被調用在URL的 "scheme"部分的字符串.通常,  應該是"http" 或"https".

          wsgi.input一個HTTP請求體能讀出的輸入流(類文件對象).(服務端可能等待應用程序請求再執行讀出, 或者提前讀出並緩存在內存或硬盤上, 或使用其它技術來提供類似的輸入流.)

          wsgi.errors

一個輸出流(類文件對象), 錯誤輸出可以使用寫人到 記錄程序或者其它標準和可能的中央位置錯誤.(-_-#).這應該是個文本模式流; 例如, 應用程序應該使用"\n"作爲行結尾, 並且可以假設它會被服務端轉換爲正確的行結尾.

 

對於大多數服務器, wsgi.errors是服務器主要的異常日誌. 或者可以是sys.stderr或其他種類的log文件. 服務器文檔應該包括如何配置這個或在哪裏找到記錄輸出的說明.如果需要, 服務端應該提供不同的異常流到不同的應用中.

          wsgi.multithread如果應用程序可以同時被相同進程的不同現成調用, 這個值應該寫爲True, 否則寫爲False.

          wsgi.multiprocess如果同一個應用程序可以同時被不同進程調用, 寫爲True, 否則寫爲False.

          wsgi.run_once如果服務端期望(但不保證)應用程序在該進程期內僅被調用一次, 該值寫爲True.通常,對於建在CGI(或類似)的網關該值總爲True.

          最後, environ字典可以包含某些服務器自定義的變量. 這些變量應該使用lower-case形式命名,包括字符,數字,小數點和下劃線,並且應該使用能唯一定義服務端的名字作爲前綴.例如,mod_python可以定義一個像 mod_python.some_variable名字的變量.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章