WSGI、Flask及Werkzeug三者之間的關係

目錄

一、WSGI是什麼?

二、Werkzeug是什麼

三、Flask的WSGI實現


一、WSGI是什麼?

WSGI是一套接口規範。一個WSGI程序用以接受客戶端請求,傳遞給應用,再返回服務器的響應給客戶端。WSGI程序通常被定義成一個函數,當然你也可以使用類實例來實現。

下圖顯示了python中客戶端、服務器、WSGI、應用之間的關係: 

從下往上開始介紹:

客戶端:瀏覽器或者app。

web服務器:Web服務器是指駐留於因特網上某種類型計算機的程序。當Web瀏覽器(客戶端)連到服務器上並請求文件時,服務器將處理該請求並將文件發送到該瀏覽器上,附帶的信息會告訴瀏覽器如何查看該文件(即文件類型)。服務器使用HTTP(超文本傳輸協議)進行信息交流,這就是人們常把它們稱爲HTTP服務器的原因。

嚴格意義上Web服務器只負責處理HTTP協議,用於處理靜態頁面的內容。而動態內容需要通過WSGI接口交給應用服務器去處理。
Web服務器包括Nginx,Apache,IIS等。而優秀的web服務器在接收http請求時,還可以做負載均衡和反向代理等工作。

WSGI容器:常見的WSGI容器有Gunicorn,uWSGI等。web框架和web服務器之間需要通信,這時候需要設計一套雙方都遵守的接口。PEP3333指出,WSGI(Web Server Gateway Interface)是WEB服務器和web框架或web應用之間建立的一種簡單通用的接口規範。有了wsgi這份接口規範,在web開發的過程中,能更加自由的選擇服務器端和框架;在服務器端和框架的開發過程能夠分離開來,不用過多的考慮雙方具體的實現,使得服務器端和框架開發者能夠專心自己領域的開發工作。而WSGI容器就是根據這套接口寫出來的。WSGI容器的作用就是根據web服務器傳遞而來的參數構建一個讓WSGI應用成功執行的環境,例如request,而且還得把WSGI應用上處理好的結果返回給web服務器。此外WSGI容器也叫應用服務器。

注: 一般應用服務器都集成了web服務器,主要是爲了調試方便,出於性能和穩定性考慮,並不能在生產環境中使用。

web框架:作用主要是方便我們開發 web應用程序,HTTP請求的動態數據就是由 web框架層來提供的。常見的 web框架有Flask,Django,Tornado等,而Tornado不光是一個web框架,還實現了WSGI容器的功能。

二、Werkzeug是什麼

都知道Flask是一個web框架,而且Flask是基於werkzeug開發的,那werkzeug是什麼呢?

Werkzeug是一個WSGI工具包,他可以作爲一個Web框架的底層庫。這裏稍微說一下, werkzeug 不是一個web服務器,也不是一個web框架,而是一個工具包,官方的介紹說是一個 WSGI 工具包,它可以作爲一個 Web 框架的底層庫,因爲它封裝好了很多 Web 框架的東西,例如 Request,Response 等等。使用它可以減輕web框架開發工作量。我看過werkzeug的源碼後發現,werkzeug也實現了WSGI容器的功能,而且利用python/http/server.py庫實現了一個簡易的http服務器。因此在調試的時候可以直接使用app.run()把服務器給運行起來。

WSGI簡化了編寫Web app的複雜度,使程序員不必關注底層的數據傳輸而專注於Web本身。框架則基於WSGI進一步抽象,用一個函數處理一個URL。而URL與函數的綁定,稱爲路由(route),而這些就交給Web框架來做了。Python Flask的路由,是由裝飾器實現的。

三、Flask的WSGI實現

有了上面的知識,從最簡單的這個flask程序來看WSGI的實現。

  1. 使用app.run()方法來啓動flask應用(app.run()代碼隱藏着創建一個服務器),app應用本身會作爲參數傳遞到WSGI服務器中。
  2. 在客戶端(這裏就是瀏覽器)輸入網址(發送一個請求),服務器使用WSGI 中間件來處理這個請求。
  3. WSGI 處理請求對應着wsgi_app(self, environ, start_response)方法,self參數對應着app,即flask程序;environ和 start_response由服務器提供。
  4. wsgi_app()作用就是調用各種請求處理函數來處理請求,然後返回處理結果。即用戶輸入網址後,看到了網頁響應。
from flask import Flask
 
app = Flask(__name__)  
#生成app實例,傳遞 __name__參數,__name__ 就是當前模塊名字。
 
@app.route("/")
def index():
    return "2017-08-21"
 
if __name__ == '__main__':
    app.run(debug=True)

1. 首先 app.run()方法開始。 看run()方法的定義,調用了werkzeug庫中的一個run_simple()方法,最後啓動了BaseWSGIServer 服務器。 運行run()方法是隻傳遞了debug=True參數。 看run() 方法內部:

  1. 第一個 if 語句設置默認host參數值爲 127.0.0.1
  2. 第二個 if 語句設置默認port參數值爲5000
  3. 第三個 if 語句中傳遞了debug 參數值爲 True
  4. the options to be forwarded to the underlying Werkzeug server. 這裏把debug狀態傳遞到底層的Werkzeug server。即use_reloader=True ; use_debugger=True
  5. 最後調用werkzeug庫中的一個run_simple()方法。同時,傳遞了剛剛設置的幾個參數
def run(self, host=None, port=None, debug=None, **options):
        """Runs the application on a local development server.
        ...
        """
        from werkzeug.serving import run_simple
        if host is None:
            host = '127.0.0.1'
        if port is None:
            server_name = self.config['SERVER_NAME']
            if server_name and ':' in server_name:
                port = int(server_name.rsplit(':', 1)[1])
            else:
                port = 5000
        if debug is not None:
            self.debug = bool(debug)
        options.setdefault('use_reloader', self.debug)
        options.setdefault('use_debugger', self.debug)
        try:
            run_simple(host, port, self, **options)
        finally:
            self._got_first_request = False

2. 看run_simple()方法

  1. hostname, port, application 對應着剛纔run()方法中傳遞過來的host, port, self 參數。(這裏self 就是Flask實例化了的app)
  2. 同時run()方法中還傳遞了user_debugger=True;user_loader=True 。剩餘的參數使用初始值。
  3. 根據上面 user_loader=True,第一個if語句成立,調用了werkzeug.debug模塊中的 DebuggedApplication類來對應用程序包裝一次。傳入了application參數和use_evalex參數,調用run_simple()方法時設置了use_evalex=True。DebuggedApplication類的簡單說明:Enables debugging support for a given application
  4. 第二個if條件語句不成立,不執行之後的代碼。
  5. 定義了log_startup函數和inner()函數,使用的時候再看具體實現了什麼。
  6. if use_reloader: 成立,會執行之後的代碼。最關鍵的一行代碼:run_with_reloader(inner, extra_files, reloader_interval, reloader_type)調用了run_with_reloader方法,inner作爲run_with_reloader方法中的main_func 參數;run_simple()方法設置了extra_files=None ,reloader_interval=1,reloader_type='auto' 同時作爲參數傳遞到run_with_reloader方法中。
  7. 然後,inner()方法中關鍵語句:make_server()創建http服務器; server_forever()讓服務器不要關閉,一直等待下一個請求。
def run_simple(hostname, port, application, use_reloader=False,
               use_debugger=False, use_evalex=True,
               extra_files=None, reloader_interval=1,
               reloader_type='auto', threaded=False,
               processes=1, request_handler=None, static_files=None,
               passthrough_errors=False, ssl_context=None):
    """Start a WSGI application. Optional features include a reloader,
    multithreading and fork support.
    :param hostname: The host for the .  eg: ``'localhost'``
    :param port: The port for the server.  eg: ``8080``
    :param application: the WSGI application to execute
    ...
    (省略了其餘的參數介紹。)
    """
    if use_debugger:
        from werkzeug.debug import DebuggedApplication
        application = DebuggedApplication(application, use_evalex)
    if static_files:
        from werkzeug.wsgi import SharedDataMiddleware
        application = SharedDataMiddleware(application, static_files)
 
    def log_startup(sock):
        display_hostname = hostname not in ('', '*') and hostname or 'localhost'
        if ':' in display_hostname:
            display_hostname = '[%s]' % display_hostname
        quit_msg = '(Press CTRL+C to quit)'
        port = sock.getsockname()[1]
        _log('info', ' * Running on %s://%s:%d/ %s',
             ssl_context is None and 'http' or 'https',
             display_hostname, port, quit_msg)
 
    def inner():
        try:
            fd = int(os.environ['WERKZEUG_SERVER_FD'])
        except (LookupError, ValueError):
            fd = None
        srv = make_server(hostname, port, application, threaded,
                          processes, request_handler,
                          passthrough_errors, ssl_context,
                          fd=fd)
        if fd is None:
            log_startup(srv.socket)
        srv.serve_forever()
 
    if use_reloader:
        # If we're not running already in the subprocess that is the
        # reloader we want to open up a socket early to make sure the
        # port is actually available.
        if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
            if port == 0 and not can_open_by_fd:
                raise ValueError('Cannot bind to a random port with enabled '
                                 'reloader if the Python interpreter does '
                                 'not support socket opening by fd.')
 
            # Create and destroy a socket so that any exceptions are
            # raised before we spawn a separate Python interpreter and
            # lose this ability.
            address_family = select_ip_version(hostname, port)
            s = socket.socket(address_family, socket.SOCK_STREAM)
            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            s.bind((hostname, port))
            if hasattr(s, 'set_inheritable'):
                s.set_inheritable(True)
 
            # If we can open the socket by file descriptor, then we can just
            # reuse this one and our socket will survive the restarts.
            if can_open_by_fd:
                os.environ['WERKZEUG_SERVER_FD'] = str(s.fileno())
                s.listen(LISTEN_QUEUE)
                log_startup(s)
            else:
                s.close()
 
        # Do not use relative imports, otherwise "python -m werkzeug.serving"
        # breaks.
        from werkzeug._reloader import run_with_reloader
        run_with_reloader(inner, extra_files, reloader_interval,
                          reloader_type)
    else:
        inner()

3. 然後,makeserver()方法:

  1. inner()方法中調用makeserver()方法時傳遞了所有需要的參數;hostname = 127.0.0.1,port = 5000,app 在這裏就是flask 程序,thread-線程。 processes = 1 單進程 …
  2. 根據判斷條件,make_server()方法最後會返回BaseWSGIServer(host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd) 來啓動BaseWSGIServer,創造一個單線程,單進程的WSGI server。
def make_server(host=None, port=None, app=None, threaded=False, processes=1,
                request_handler=None, passthrough_errors=False,
                ssl_context=None, fd=None):
    """Create a new server instance that is either threaded, or forks
    or just processes one request after another.
    """
    if threaded and processes > 1:
        raise ValueError("cannot have a multithreaded and "
                         "multi process server.")
    elif threaded:
        return ThreadedWSGIServer(host, port, app, request_handler,
                                  passthrough_errors, ssl_context, fd=fd)
    elif processes > 1:
        return ForkingWSGIServer(host, port, app, processes, request_handler,
                                 passthrough_errors, ssl_context, fd=fd)
    else:
        return BaseWSGIServer(host, port, app, request_handler,
                              passthrough_errors, ssl_context, fd=fd)

4. class BaseWSGIServer(HTTPServer, object)
BaseWSGIServer類繼承自HTTPServer類,最後詳細講。

class BaseWSGIServer(HTTPServer, object):
 
    """Simple single-threaded, single-process WSGI server."""
    multithread = False
    multiprocess = False
    request_queue_size = LISTEN_QUEUE
 
    def __init__(self, host, port, app, handler=None,
                 passthrough_errors=False, ssl_context=None, fd=None):
        if handler is None:
            handler = WSGIRequestHandler
 
        self.address_family = select_ip_version(host, port)
 
        if fd is not None:
            real_sock = socket.fromfd(fd, self.address_family,
                                      socket.SOCK_STREAM)
            port = 0
        HTTPServer.__init__(self, (host, int(port)), handler)
        self.app = app
        self.passthrough_errors = passthrough_errors
        self.shutdown_signal = False
        self.host = host
        self.port = self.socket.getsockname()[1]
 
        # Patch in the original socket.
        if fd is not None:
            self.socket.close()
            self.socket = real_sock
            self.server_address = self.socket.getsockname()
 
        if ssl_context is not None:
            if isinstance(ssl_context, tuple):
                ssl_context = load_ssl_context(*ssl_context)
            if ssl_context == 'adhoc':
                ssl_context = generate_adhoc_ssl_context()
            # If we are on Python 2 the return value from socket.fromfd
            # is an internal socket object but what we need for ssl wrap
            # is the wrapper around it :(
            sock = self.socket
            if PY2 and not isinstance(sock, socket.socket):
                sock = socket.socket(sock.family, sock.type, sock.proto, sock)
            self.socket = ssl_context.wrap_socket(sock, server_side=True)
            self.ssl_context = ssl_context
        else:
            self.ssl_context = None
 
    def log(self, type, message, *args):
        _log(type, message, *args)
 
    def serve_forever(self):
        self.shutdown_signal = False
        try:
            HTTPServer.serve_forever(self)
        except KeyboardInterrupt:
            pass
        finally:
            self.server_close()
 
    def handle_error(self, request, client_address):
        if self.passthrough_errors:
            raise
        return HTTPServer.handle_error(self, request, client_address)
 
    def get_request(self):
        con, info = self.socket.accept()
        return con, info

5. 找到HTTPServer類:

  1. HTTPServer類在python安裝路徑的Lib/http/server.py 模塊中。
  2. HTTPServer類實現了一個server_bind()方法用來綁定服務器地址和端口。
  3. class HTTPServer(socketserver.TCPServer) HTTPServer類繼承了socketserver模塊中的TCPServer類。
class HTTPServer(socketserver.TCPServer):
 
    allow_reuse_address = 1    # Seems to make sense in testing environment
 
    def server_bind(self):
        """Override server_bind to store the server name."""
        socketserver.TCPServer.server_bind(self)
        host, port = self.server_address[:2]
        self.server_name = socket.getfqdn(host)
        self.server_port = port

6. 找到 TCPServer類:

  1. TCPServer在socketserver模塊中。首先,socket 介紹: A network socket is an internal endpoint for sending or receiving data at a single node in a computer network. 。
  2. TCPServer類中的介紹:Base class for various socket-based server classes. Defaults to synchronous IP stream (i.e., TCP).
  3. TCPServer類繼承了BaseServer基類。都是定義了服務器端的基本屬性。後面的繼承類可以重寫這裏的一部分屬性。

7. 再回到BaseWSGIServer 類: 
BaseWSGIServer類重寫了一大堆東西,首先 __init__ 初始化類中的變量,供類中其他函數調用。

  1. __init__ 函數首先定義了handler = WSGIRequestHandler 來處理請求。
  2. WSGIRequestHandler簡單介紹:A request handler that implements WSGI dispatching.
  3. class WSGIRequestHandler(BaseHTTPRequestHandler, object) WSGIRequestHandler 繼承自 BaseHTTPRequestHandler。
  4. class BaseHTTPRequestHandler(socketserver.StreamRequestHandler)BaseHTTPRequestHandler繼承自socketserver模塊中的StreamRequestHandler
  5. class StreamRequestHandler(BaseRequestHandler) StreamRequestHandler繼承自BaseRequestHandler,就是用來處理請求的類。
  6. 最初的BaseRequestHandler基類定義的屬性很少,一層層的重寫,到了WSGIRequestHandler類,越來越完善了。
  7. 最重要的要知道,這個WSGIRequestHandler類是用來處理請求的。
def __init__(self, host, port, app, handler=None,
                 passthrough_errors=False, ssl_context=None, fd=None):
        if handler is None:
            handler = WSGIRequestHandler
            ...

8. WSGIRequestHandler類中定義了很多方法。因爲WSGI 是單線程、單進程的server,來看這個handle_one_request(self)方法,用來處理一個請求。

 def handle_one_request(self):
        """Handle a single HTTP request."""
        self.raw_requestline = self.rfile.readline()
        if not self.raw_requestline:
            self.close_connection = 1
        elif self.parse_request():
            return self.run_wsgi()

9. 調用了run_wsgi()方法,run_wsgi()方法方法好長,重點看這句execute(self.server.app) ,在這兒處理請求使用Flask中的__call__ 方法。。app(Flask實例對象)作爲參數在make_server()時已經傳遞到服務器中了。

def run_wsgi(self):
       ...
 
        def write(data):
            ...
 
        def start_response(status, response_headers, exc_info=None):
            ...
            return write
 
        def execute(app):
            ...
 
        try:
            execute(self.server.app)
        except ...
        ...

10. 服務器收到http請求,去調用app的時候,實際上是用了Flask 的 __call__方法,會調用wsgi_app()方法。

def __call__(self, environ, start_response):
        """Shortcut for :attr:`wsgi_app`."""
        return self.wsgi_app(environ, start_response)

11. 到了wsgi_app(),就和上一篇筆記的WSGI介紹聯繫起來了。這裏wsgi_app作爲中間件的存在,連接着服務器和應用程序。對服務器來說wsgi_app是應用程序;對應用程序來說,wsgi_app是服務器。

wsgi_app(self, environ, start_response)需要三個參數,self即需要運行的flask 應用程序,在創建服務器時傳遞到了 WSGI server。environ, start_response由服務器提供,wsgi_app的功能就是根據請求查找各種請求處理函數,然後返回請求處理結果到服務器。

def wsgi_app(self, environ, start_response):
        """The actual WSGI application.  This is not implemented in
        `__call__` so that middlewares can be applied without losing a
        reference to the class.  So instead of doing this::
            app = MyMiddleware(app)
        :param environ: a WSGI environment
        :param start_response: a callable accepting a status code,
                               a list of headers and an optional
                               exception context to start the response
        """
        ctx = self.request_context(environ)
        ctx.push()
        error = None
        try:
            try:
                response = self.full_dispatch_request()
            except Exception as e:
                error = e
                response = self.handle_exception(e)
            except:
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            ctx.auto_pop(error)

參考博客:

https://blog.csdn.net/sodawaterer/article/details/71497086

https://blog.csdn.net/sinat_36651044/article/details/77462831

https://www.jianshu.com/p/265aa5d0f22d
 

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