wsgiref 源代碼分析

wsgiref 源代碼分析

wsgiref

wsgiref 是Python標準庫給出的 WSGI 的參考實現。

WSGI是Python Web 開發中爲服務器程序和應用程序設定的標準,滿足這一標準的服務器程序和應用程序可以配合使用。我在上一篇博文《WSGI簡介》中對此有詳細的介紹。在閱讀wsgiref源代碼之前,一定要對WSGI有一定了解。

WSGI 在 PEP 333 中描述,但是隻靠閱讀PEP 333 可能在理解上還是不夠深入,所以閱讀官方給出的參考實現是很有必要的。閱讀完這份源代碼後,不僅有利於對WSGI的理解。而且會讓你對服務端程序如何對客戶端請求有一個直觀的理解,從相對底層的socket監聽請求,到上層對HTTP請求的處理。

當然,這只是對WSGI的參考實現,目的是爲了描述清楚WSGI,而不是真正用在產品中。如果想對Python Web開發中服務器端的實現有更廣泛,更深入的理解,需要進一步閱讀Python常用框架的源代碼。

wsgiref 源代碼分析

wsgiref 源代碼可以在 pypi wsgiref 0.1.2 下載。另外,我在閱讀的過程中作了大量註釋,包含模塊介紹,調用層次關係,demo的運行結果,等 等,還包含了閱讀過程中製作的思維導圖。GitHub地址註釋版wsgiref

結構


上圖描述了wsgiref的所有模塊及模塊間的調用關係,可以看出,wsgiref有以下模塊:

  • simple_server
    這一模塊實現了一個簡單的 HTTP 服務器,並給出了一個簡單的 demo,運行:

    python simple_server.py
    

會啓動這個demo,運行一次請求,並把這次請求中涉及到的環境變量在瀏覽器中顯示出來。

  • handlers
    simple_server模塊將HTTP服務器分成了 Server 部分和Handler部分,前者負責接收請求,後者負責具體的處理, 其中Handler部分主要在handlers中實現。
  • headers
    這一模塊主要是爲HTTP協議中header部分建立數據結構。
  • util
    這一模塊包含了一些工具函數,主要用於對環境變量,URL的處理。
  • validate
    這一模塊提供了一個驗證工具,可以用於驗證你的實現是否符合WSGI標準。

simple_server


可以看出,simple_server 模塊主要有兩部分內容

  • 應用程序
    函數demo_app是應用程序部分
  • 服務器程序
    服務器程序主要分成Server 和 Handler兩部分,另外還有一個函數 make_server 用來生成一個服務器實例

我們先看應用程序部分。

注意:以 M:開始的都是我自己添加的註釋,不包含在最初的源代碼中。

def demo_app(environ,start_response):
    # M: StringIO reads and writes a string buffer (also known as memory files).

    from StringIO import StringIO
    stdout = StringIO()
    print >> stdout, "Hello world!"
    print >> stdout

    h = environ.items()
    h.sort()
    for k,v in h:
        print >> stdout, k,'=',`v`

    start_response("200 OK", [('Content-Type','text/plain')])

    return [stdout.getvalue()]

這裏可以看出,這個 demo_app 是如何展示WSGI中對應用程序的要求的:

  • demo_app 有兩個參數
  • 第一個參數 environ是一個字典
  • 第二個參數 start_response是一個可調用函數
  • demo_app 返回一個可迭代對象
  • demo_app 需要調用 start_response

另外,可以看出,返回的內容就是環境變量當前的內容,這一點可以運行

python simple_server.py

在瀏覽器中看到的內容,就是上述源代碼的for循環中輸出的。

這裏,使用了 StringIO ,可以看出,所有輸出的內容都先存儲在其實例中,最後返回的時候一起在可迭代對象中返回。

接下來,我們看服務器程序。

先從 make_server 看起,它是用來生成一個server實例的:

def make_server(
    host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
):
    """Create a new WSGI server listening on `host` and `port` for `app`"""

    # M: -> HTTPServer.__init__
    #    -> TCPServer.__init__
    #       -> TCPServer.server_bind
    #           -> TCPServer.socket.bind
    #       -> TCPServer.server_activate
    #           -> TCPServer.socket.listen
    server = server_class((host, port), handler_class)

    # M: conresponding to WSGIRequestHandler.handle()
    #    -> handler.run(self.server.get_app())
    server.set_app(app)

    return server

雖然代碼只有三行,但是可以看出生成一個 server 都需要些什麼:

  • (host, port)
    主機名和端口號
  • handler_class
    用於處理請求的handler類
  • app 服務器程序在處理時,一定會調用我們之前寫好的應用程序,這樣他們才能配合起來爲客戶端面服務,所以,你看到了那個 set_app 調用。

另外,在註釋部分,你可以看到那代碼背後都發生了什麼。

生成 server 實例時,默認的 server_class 是 WSGIServer,它是HTTPServer的子類,後者又是TCPServer的子類,所以初始化 server 時,會沿着類的繼承關係執行下去,最終,生成 server 實例的過程,其實是最底層的 TCPServer 在初始化時,完成了對socket的bind和listen。

後面的 set_app 設置了 app,它會在 handler_class (默認爲WSGIRequestHandler)的handle函數中被取出來,然後交給 handler 的 run 函數運行。

好,現在我們開始介紹Server部分的主要內容,即WSGIServer和WSGIRequestHandler,首先,我們看一下二者的繼承體系。

  • WSGIServer
# M:
#         +------------+
#         | BaseServer |
#         +------------+
#               |
#               V
#         +------------+
#         | TCPServer  |
#         +------------+
#               |
#               V
#         +------------+
#         | HTTPServer |
#         +------------+
#               |
#               V
#         +------------+
#         | WSGIServer |
#         +------------+

可以看出 WSGIServer 來自 HTTPServer,後者來自 Python 標準庫中的BaseHTTPServer模塊,更上層的TCPServer和BaseServer來自 Python 標準庫中的 SocketServer 模塊。

  • WSGIRequestHandler
# M:
#         +--------------------+
#         | BaseRequestHandler |
#         +--------------------+
#                   |
#                   V
#         +-----------------------+
#         | StreamRequestHandler  |
#         +-----------------------+
#                   |
#                   V
#         +------------------------+
#         | BaseHTTPRequestHandler |
#         +------------------------+
#                   |
#                   V
#         +--------------------+
#         | WSGIRequestHandler |
#         +--------------------+

可以看出 WSGIRequestHandler 來自 BaseHTTPRequestHandler,後者來自 Python 標準庫中的BaseHTTPServer模塊,更上層的StreamRequestHandler和BaseRequestHandler來自 Python 標準庫中的 SocketServer 模塊。

這時候,三個模塊之間的層次關係我們可以理清楚了。

# M:
#        +-----------------------------------------------+
#        | simple_server: WSGIServer, WSGIRequestHandler |   
#        |                                               |
#        +-----------------------------------------------+
#                               |
#                               V
#       +----------------------------------------------------+
#       | BaseHTTPServer: HTTPServer, BaseHTTPRequestHandler |
#       +----------------------------------------------------+
#                               |
#                               V
#       +----------------------------------------------------+
#       | SocketServer: TCPServer,BaseSErver;                |
#       | StreamRequestHandler,BaseRequestHandler            |
#       +----------------------------------------------------+
# 

另外,這一模塊中還有一個類,叫ServerHandler,它繼承自 handlers 模塊中的 SimpleHandler,我們再看看它的繼承體系:

# M:
#        +-------------+
#        | BaseHandler |  
#        +-------------+
#               |
#               V
#       +----------------+
#       | SimpleHandler  |
#       +----------------+
#               |
#               V
#       +---------------+
#       | ServerHandler |
#       +---------------+
# 

好了,現在這個模塊中的繼承結構都弄清楚了,現在我們看看他們如何配合起來完成對客戶端請求的處理。

首先,回顧simple_server涉及的模塊:

  • WSGIServer
  • WSGIRequestHandler
  • ServerHandler
  • demo_app
  • make_server

然後,把大腦清空,想一下要對客戶端請求進行處理需要做什麼

  • 啓動服務器,監聽客戶端請求
  • 請求來臨,處理用戶請求

我們看看這幾個模塊是如何配合完成這兩個功能的

先看看 simple_server 模塊的 main 部分,即執行

python simple_server.py

時執行的內容:

    httpd = make_server('', 8000, demo_app)
    sa = httpd.socket.getsockname()
    print "Serving HTTP on", sa[0], "port", sa[1], "..."

    # M: webbrowser provides a high-level interface to allow displaying Web-based documents 
    # to users. Under most circumstances
    import webbrowser
    webbrowser.open('http://localhost:8000/xyz?abc')

    httpd.handle_request()  # serve one request, then exit

可以看出,主要完成的功能是:

  • 啓動服務器
  • 模塊用戶請求
  • 處理用戶請求

那麼,我們主要關心的就是 make_server 和 handle_request 背後都發生了什麼。

  • make_server


上圖可以看出函數之間的調用關係,也可以看出 make_server 到 使用 socket 監聽用戶請求的過程。

  • handle_request

handle_request 的過程真正將各個模塊聯繫起來了。


上圖很清楚地說明了 由handle_request到demo_app的執行過程,把這個模塊的各個部分聯繫起來。相信無需多言了。

handlers


從圖中可以看出handler模塊中的四部分,它們其實是四個類,從基類到子類依次爲:

  • BaseHandler
  • SimpleHandler
  • BaseCGIHandler
  • CGIHandler

最主要的實現在 BaseHandler中,其它幾個類都是在基類基礎上做了簡單的實現。BaseHandler是不能直接使用的,因爲有幾個關鍵的地方沒有實現,SimpleHandler是一個可以使用的簡單實現。simple_server中的 ServerHandler類就是這個模塊中SimpleHandler的子類。

在 BaseHandler中,最重要的是 run 函數:

def run(self, application):
        """Invoke the application"""
        # Note to self: don't move the close()!  Asynchronous servers shouldn't
        # call close() from finish_response(), so if you close() anywhere but
        # the double-error branch here, you'll break asynchronous servers by
        # prematurely closing.  Async servers must return from 'run()' without
        # closing if there might still be output to iterate over.
        try:
            self.setup_environ()
            self.result = application(self.environ, self.start_response)
            self.finish_response()
        except:
            try:
                self.handle_error()
            except:
                # If we get an error handling an error, just give up already!
                self.close()
                raise   # ...and let the actual server figure it out.

它先設置好環境變量,再調用我們的 demo_app, 並通過 finish_response 將調用結果傳送給客戶端。如果處理過程遇到錯誤,轉入 handle_error 處理。

此外,BaseHandler中還包含了 WSGI 中多次提到的 start_response,start_response 在 demo_app 中被調用,我們看看它的實現:

    def start_response(self, status, headers,exc_info=None):
        """'start_response()' callable as specified by PEP 333"""

        # M:
        # exc_info:
        #    The exc_info argument, if supplied, must be a Python sys.exc_info()
        #    tuple. This argument should be supplied by the application only if
        #    start_response is being called by an error handler.

        #    exc_info is the most recent exception catch in except clause

        #    in error_output:
        #        start_response(
        #             self.error_status,self.error_headers[:],sys.exc_info())

        # headers_sent:
        #    when send_headers is invoked, headers_sent = True
        #    when close is invoked, headers_sent = False

        if exc_info:
            try:
                if self.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 self.headers is not None:
            raise AssertionError("Headers already set!")

        assert type(status) is StringType,"Status must be a string"
        assert len(status)>=4,"Status must be at least 4 characters"
        assert int(status[:3]),"Status message must begin w/3-digit code"
        assert status[3]==" ", "Status message must have a space after code"
        if __debug__:
            for name,val in headers:
                assert type(name) is StringType,"Header names must be strings"
                assert type(val) is StringType,"Header values must be strings"
                assert not is_hop_by_hop(name),"Hop-by-hop headers not allowed"

        # M: set status and headers

        self.status = status

        # M:
        #    headers_class is Headers in module headers
        self.headers = self.headers_class(headers)

        return self.write

可以看出,它先對參數進行了檢查,然後再將headers 存儲在成員變量中,這兩點 WSGI標準中都有明確說明,需要檢查參數,並且這一步只能將 headers存儲起來,不能直接發送給客戶端。也就是說,這個 start_response 還沒有真正 response。

其實剛剛介紹 run 函數的時候已經提到了,真正的 response 在 finish_response 函數中:

    def finish_response(self):
        """Send any iterable data, then close self and the iterable

        Subclasses intended for use in asynchronous servers will
        want to redefine this method, such that it sets up callbacks
        in the event loop to iterate over the data, and to call
        'self.close()' once the response is finished.
        """

        # M:
        #    result_is_file: 
        #       True if 'self.result' is an instance of 'self.wsgi_file_wrapper'
        #    finish_content:
        #       Ensure headers and content have both been sent
        #    close:
        #       Close the iterable (if needed) and reset all instance vars
        if not self.result_is_file() or not self.sendfile():
            for data in self.result:
                self.write(data) # send data by self.write
            self.finish_content()
        self.close()

另外一個需要注意的地方是錯誤處理,在 run 函數中通過 try/except 捕獲錯誤,錯誤處理使用了 handle_error 函數,WSGI中提到,start_response 函數的第三個參數 exc_info 會在錯誤處理的時候使用,我們來看看它是如何被使用的:

    def handle_error(self):
        """Log current error, and send error output to client if possible"""
        self.log_exception(sys.exc_info())
        if not self.headers_sent:
            self.result = self.error_output(self.environ, self.start_response)
            self.finish_response()
        # XXX else: attempt advanced recovery techniques for HTML or text?

    def error_output(self, environ, start_response):
        """WSGI mini-app to create error output

        By default, this just uses the 'error_status', 'error_headers',
        and 'error_body' attributes to generate an output page.  It can
        be overridden in a subclass to dynamically generate diagnostics,
        choose an appropriate message for the user's preferred language, etc.

        Note, however, that it's not recommended from a security perspective to
        spit out diagnostics to any old user; ideally, you should have to do
        something special to enable diagnostic output, which is why we don't
        include any here!
        """

        # M:
        # sys.exc_info():
        #    Return information about the most recent exception caught by an except
        #    clause in the current stack frame or in an older stack frame.

        start_response(self.error_status,self.error_headers[:],sys.exc_info())
        return [self.error_body]

看到了吧,handle_error 又調用了 error_output ,後者調用 start_response,並將其第三個參數設置爲 sys.exc_info(),這一點在 WSGI 中也有說明。

好了,這一部分我們就介紹到這裏,不再深入過多的細節,畢竟源代碼還是要自己親自閱讀的。剩下的三個部分不是核心問題,就是一些數據結構和工具函數,我們簡單說一下。

headers


這個模塊是對HTTP 響應部分的頭部設立的數據結構,實現了一個類似Python 中 dict的數據結構。可以看出,它實現了一些函數來支持一些運算符,例如 __len____setitem____getitem____delitem____str__, 另外,還實現了 dict 操作中的getkeysvalues函數。

util


這個模塊主要就是一些有用的函數,用於處理URL, 環境變量。

validate


這個模塊主要是檢查你對WSGI的實現,是否滿足標準,包含三個部分:

  • validator
  • Wrapper
  • Check

validator 調用後面兩個部分來完成驗證工作,可以看出Check部分對WSGI中規定的各個部分進行了檢查。

好了,就介紹到這裏,這只是一個總結和導讀,再次強調:

源代碼還是要自己親自閱讀的
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章