關於wsgi協議的理解

不·一發表於2019-02-15

基礎概念

首先要了解 WSGI 規範的概念,WSGI(Web Server Gateway Interface)規範描述了web server(Gunicorn,uWSGI等)如何與web application(flask, django等)互動、web application如何處理請求,定義在 pep 3333。正是有了 WSGI 規範,我們才能在任意 web server 上跑各種 web 應用。WSGI API 定義看起來很簡單:

def application(environ, start_response)
複製程式碼
  • application 就是 WSGI app,一個可呼叫物件

  • 引數:

    • environ: 一個包含 WSGI 環境資訊的字典,由 WSGI 伺服器提供,常見的 key 有 PATH_INFO,QUERY_STRING 等
    • start_response: 生成 WSGI 響應的回撥函式,接收兩個引數,status 和 headers
  • 函式返回值為響應體的迭代器 ###簡單舉例 下面舉個簡單的例子,比如一個返回 hello world 的應用:

def application(environ, start_response):
    status = '200 OK'
    headers = [('Content-Type', 'text/html; charset=utf8')]
    start_response(status, headers)
    return  [b"<h1>Hello, World!</h1>"]
複製程式碼

werkzeug相關

werkzeug是Python實現的WSGI規範的使用函式庫。 正如werkzeug官網Werkzeug上所說,werkzeug使用起來非常簡單,但是卻非常強大。關於使用簡單的這個特性,官網給了一段示例程式碼。

from werkzeug.wrappers import Request, Response
@Request.application
def application(request):
    return Response('Hello World!')
if __name__ == '__main__':
    from werkzeug.serving import run_simple
    run_simple('localhost', 4000, application)
複製程式碼

###簡單小結 關於上面的程式碼我做一下總結: application--可呼叫物件,wsig模組中加括號括號執行 application的返回值--Response物件,wsgi中會對該物件加括號執行其__call__方法 一次成功的訪問,由以下幾步完成

  1. 瀏覽器(client)傳送一個請求(request
  2. 伺服器(server)接收到請求
  3. 伺服器處理請求
  4. 返回處理的結果(response
  5. 瀏覽器處理返回的結果,顯示出來。

Detail

具體來說:

  1. wigi相關模組通過建立socket拿到客戶端傳送的資料,然後進行解析,然後封裝到environ中
  2. web框架比如flask,他拿到environ,執行其內部各種呼叫函式,檢視函式,然後返回Response物件
  3. wigi相關模組拿到相應的Response物件,執行其__call__方法拿到app_iter物件,進行for迴圈進行socket.sendall(data)方法進行資料傳送 ###原始碼 現在我們開始看一下原始碼:
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):
    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()

    inner()
複製程式碼

執行inner方法 然後執行make_server方法拿到其返回值並賦值給srv

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)
複製程式碼

以BaseWSGIServer類為例,將其例項化就是執行其__init__方法 因為類的各種繼承,我就不一一細說了: 總的來說:

就是建立socket和定義處理request的類RequestHandleClass
其為:WSGIRequestHandler,自己看一下原始碼找一下吧
複製程式碼

然後執行srv.server_forver srvBaseWSGIServer的例項,根據類的繼承,去查詢各種方法. 記住一點就是查詢方法優先從自己的類定義中找,如果沒有就去父類中找.時刻謹記self是誰

#BaseWSGIServer中定義
    def serve_forever(self):
        self.shutdown_signal = False
        try:
            HTTPServer.serve_forever(self)
        except KeyboardInterrupt:
            pass
        finally:
            self.server_close()
複製程式碼
###BaseServer
 def serve_forever(self, poll_interval=0.5):
        """Handle one request at a time until shutdown.

        Polls for shutdown every poll_interval seconds. Ignores
        self.timeout. If you need to do periodic tasks, do them in
        another thread.
        """
        self.__is_shut_down.clear()
        try:
            # XXX: Consider using another file descriptor or connecting to the
            # socket to wake this up instead of polling. Polling reduces our
            # responsiveness to a shutdown request and wastes cpu at all other
            # times.
            with _ServerSelector() as selector:
                selector.register(self, selectors.EVENT_READ)

                while not self.__shutdown_request:
                    ready = selector.select(poll_interval)
                    if ready:
                        self._handle_request_noblock()

                    self.service_actions()
        finally:
            self.__shutdown_request = False
            self.__is_shut_down.set()
複製程式碼
# BaseServer
    def _handle_request_noblock(self):
        """Handle one request, without blocking.

        I assume that selector.select() has returned that the socket is
        readable before this function was called, so there should be no risk of
        blocking in get_request().
        """
        try:
            request, client_address = self.get_request()
        except OSError:
            return
        if self.verify_request(request, client_address):
            try:
                self.process_request(request, client_address)
            except Exception:
                self.handle_error(request, client_address)
                self.shutdown_request(request)
            except:
                self.shutdown_request(request)
                raise
        else:
            self.shutdown_request(request)
複製程式碼

執行process_request方法

    def process_request(self, request, client_address):
        """Call finish_request.
        Overridden by ForkingMixIn and ThreadingMixIn.
        """
        self.finish_request(request, client_address)
        self.shutdown_request(request)
複製程式碼

Next

    def finish_request(self, request, client_address):
        """Finish one request by instantiating RequestHandlerClass."""
        self.RequestHandlerClass(request, client_address, self)
複製程式碼

執行RequestHandlerClass類的例項化

執行BaseHTTPRequestHandler的handle方法 WSGIRequestHandler.handle_one_request

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()
複製程式碼
    def run_wsgi(self):
        if self.headers.get('Expect', '').lower().strip() == '100-continue':
            self.wfile.write(b'HTTP/1.1 100 Continue\r\n\r\n')

        self.environ = environ = self.make_environ()
        headers_set = []
        headers_sent = []

        def write(data):
            assert headers_set, 'write() before start_response'
            if not headers_sent:
                status, response_headers = headers_sent[:] = headers_set
                try:
                    code, msg = status.split(None, 1)
                except ValueError:
                    code, msg = status, ""
                code = int(code)
                self.send_response(code, msg)
                header_keys = set()
                for key, value in response_headers:
                    self.send_header(key, value)
                    key = key.lower()
                    header_keys.add(key)
                if not ('content-length' in header_keys or
                        environ['REQUEST_METHOD'] == 'HEAD' or
                        code < 200 or code in (204, 304)):
                    self.close_connection = True
                    self.send_header('Connection', 'close')
                if 'server' not in header_keys:
                    self.send_header('Server', self.version_string())
                if 'date' not in header_keys:
                    self.send_header('Date', self.date_time_string())
                self.end_headers()

            assert isinstance(data, bytes), 'applications must write bytes'
            self.wfile.write(data)
            self.wfile.flush()

        def start_response(status, response_headers, exc_info=None):
            if exc_info:
                try:
                    if headers_sent:
                        reraise(*exc_info)
                finally:
                    exc_info = None
            elif headers_set:
                raise AssertionError('Headers already set')
            headers_set[:] = [status, response_headers]
            return write

        def execute(app): # app_iter物件 包含了需要返回的各項資料
            application_iter = app(environ, start_response)  # Flask例項的call方法返回的的response物件的__call__方法返回的東西
            try:
                for data in application_iter:
                    write(data)
                if not headers_sent:
                    write(b'')
            finally:
                if hasattr(application_iter, 'close'):
                    application_iter.close()
                application_iter = None

        try:
            execute(self.server.app)
        except (socket.error, socket.timeout) as e:
            self.connection_dropped(e, environ)
        except Exception:
            if self.server.passthrough_errors:
                raise
            from werkzeug.debug.tbtools import get_current_traceback
            traceback = get_current_traceback(ignore_system_exceptions=True)
            try:
                # if we haven't yet sent the headers but they are set
                # we roll back to be able to set them again.
                if not headers_sent:
                    del headers_set[:]
                execute(InternalServerError())
            except Exception:
                pass
            self.server.log('error', 'Error on request:\n%s',
                            traceback.plaintext)
複製程式碼

通過這個程式碼,我們拿到了app執行後拿到的可迭代物件 application_iter = app(environ, start_response) # Flask例項的call方法返回的的response物件的__call__方法返回的可迭代物件

END

最終for迴圈這個物件傳送了資料

for data in application_iter:
                    write(data)
複製程式碼

參考

相關文章