Flask學習-Wsgiref庫

1、前言

前面在Flask學習-Flask基礎之WSGI中提到了WerkZeug,咱們知道,WerkZeug是一個支持WSGI協議的Server,其實還有不少其餘支持WSGI協議的Server。http://wsgi.readthedocs.io/en/latest/servers.html,這裏能夠看到有uwsgi、werkzeug.servingwsgirefpython-fastcgi等等幾十個。wsgiref是官方給出的一個實現了WSGI標準用於演示用的簡單Python內置庫,它實現了一個簡單的WSGI Server和WSGI Application(在simple_server模塊中),主要分爲五個模塊:simple_server, util, headers, handlers, validate。 wsgiref源碼地址:https://pypi.python.org/pypi/wsgiref。經過學習Wsgiref,我相信可以更加清晰的瞭解Web框架的實現。 html

2、例子

wsgiref 是 PEP 333 定義的 wsgi 規範的範例實現,裏面的功能包括了:python

  • 操做 wsgi 的環境變量
  • 應答頭部的處理
  • 實現簡單的 HTTP server
  • 簡單的對程序端和服務器端校驗函數

因爲simple_server中已經實現了一個簡單的WSGI Server和WSGI Application,咱們只要直接運行就能夠了。git

simple_server.py:web

 

if __name__ == '__main__':
    httpd = make_server('', 8000, demo_app)
    sa = httpd.socket.getsockname()
    print "Serving HTTP on", sa[0], "port", sa[1], "..."
    import webbrowser
    webbrowser.open('http://localhost:8000/xyz?abc')
    httpd.handle_request()  # serve one request, then exit
    httpd.server_close()

 

  

 

  

而後將app運行起來後,結果以下:flask

 

3、過程分析

一、Server端啓動流程

make_server('localhost', 8000, application)服務器

先查看make_server在wsgiref中的定義:數據結構

 

def make_server(host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler):
    """Create a new WSGI server listening on `host` and `port` for `app`"""
   #  -> HTTPServer.__init__
    #    -> TCPServer.__init__
    #       -> TCPServer.server_bind
    #           -> TCPServer.socket.bind
    #       -> TCPServer.server_activate
    #           -> TCPServer.socket.listen
  server = server_class((host, port), handler_class) 
  server.set_app(app) 
  return server

 

  

 

  

雖然代碼只有三行,可是能夠看出生成一個 server 都須要些什麼:app

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

另外,在註釋部分,你能夠看到那代碼背後都發生了什麼。框架

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

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

上圖能夠看出函數之間的調用關係,也能夠看出 make_server 到 使用 socket 監聽用戶請求的過程。至此,Server端已經起來了。

二、Client端請求過程

httpd.handle_request() 

整個過程能夠看出,HTTP創建於TCP之上。

稍微難理解一點的地方是:

一、BaseServer.finish_requset()後,就到了WSGIRequestHandler初始化這一步。

二、BaseRequestHandler初始化時,會調用handle()

BaseServer最須要注意的是在構造的時候設置了請求處理類RequestHandlerClass

class BaseServer:
      def __init__(self, server_address, RequestHandlerClass):
        """Constructor.  May be extended, do not override."""
        self.server_address = server_address
        self.RequestHandlerClass = RequestHandlerClass
        self.__is_shut_down = threading.Event()
        self.__shutdown_request = False

  

先回顧一下流程,當請求走到TCPServer.handle_request,過程以下:

  • 首先一個鏈接進來,BaseServer監聽到鏈接,調用self._handle_request_noblock()處理
  • self._handle_request_noblock()最終找到finish_request方法
  • finish_request方法實例化請求處理類RequestHandlerClass
RequestHandlerClass是由調用make_server()時傳入的handler_class
def make_server(
    host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
):
    """Create a new WSGI server listening on `host` and `port` for `app`"""
    server = server_class((host, port), handler_class)  #WSGIServer((host,port), WSGIRequestHandler)
  server.set_app(app) return server

  

WSGIServer的初始化最終會調用BaseServer的__init__()函數。這裏的RequestHandlerClass其實就是make_server傳入的WSGIRequestHandler。

 

4、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 中也有說明。

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

5、headers

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

6、util

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

7、validate

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

  • validator
  • Wrapper
  • Check

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

相關文章
相關標籤/搜索