筆記-flask-原理及請求處理流程

筆記-flask-原理及請求處理流程

 

1.      服務器聲明及運行

最基本的flask項目代碼以下html

from flask import Flaskshell

app = Flask(__name__)flask

 

@app.route("/")服務器

def hello():app

    return "Hello World!"dom

 

if __name__ == "__main__":socket

    app.run()ide

 

簡單來講,聲明Flask對象,聲明view function,運行flask對象。函數

看一下後臺作了什麼。post

1.1.    __init__

看一下Flask對象的__init__方法:

         #: Holds the path to the instance folder.
        #: .. versionadded:: 0.8
       
self.instance_path = instance_path

        #: The configuration dictionary as :class:`Config`.  This behaves
        #: exactly like a regular dictionary but supports additional methods
        #: to load a config from files.
       
self.config = self.make_config(instance_relative_config)

        #: A dictionary of all view functions registered.
       
self.view_functions = {}

        #: A dictionary of all registered error handlers.
       
self.error_handler_spec = {}

        #: A list of functions that are called when:meth:`url_for`raises a
        #: :exc:`~werkzeug.routing.BuildError
       
self.url_build_error_handlers = []

        #: A dictionary with lists of functions that will be called at the
        #: beginning of each request.

         #use the :meth:`before_request`: decorator.
       
self.before_request_funcs = {}

        #: A list of functions that will be called at the beginning of the
        #: first request to this instance.
        #: :meth:`before_first_request` decorator.
       
self.before_first_request_funcs = []

        #: A dictionary with lists of functions that should be called after
        #: each request. 

        #: :meth:`after_request` decorator.
       
self.after_request_funcs = {}

        #: A dictionary with lists of functions that are called after
        #: each request, even if an exception has occurred.

        #: :meth:`teardown_request` decorator.
       
self.teardown_request_funcs = {}

        #: A list of functions that are called when the application context
        #: is destroyed.
       
self.teardown_appcontext_funcs = []

        #: A dictionary with lists of functions that are called before the
        #: :attr:`before_request_funcs` functions.
        #: :meth:`url_value_preprocessor`.
       
self.url_value_preprocessors = {}

        #: A dictionary with lists of functions that can be used as URL value
        #: preprocessors.
        #:meth:`url_defaults`
       
self.url_default_functions = {}

        #: A list of shell context processor functions that should be run
        #: when a shell context is created.
       
self.shell_context_processors = []

        #: all the attached blueprints in a dictionary by name.  Blueprints
        #: can be attached multiple times so this dictionary does not tell
        #: you how often they got attached.
        #:
        #: .. versionadded:: 0.7
       
self.blueprints = {}
        self._blueprint_order = []

 

     #: The :class:`~werkzeug.routing.Map` for this instance.  You can use

        #: this to change the routing converters after the class was created

        #: but before any routes are connected.

        self.url_map = Map()

 

        self.url_map.host_matching = host_matching

        self.subdomain_matching = subdomain_matching

 

 

把路徑及其它環境變量設置去掉之後,剩下的基本就這些了。

都是一些函數列表,用於在不一樣時機處理請求。

view_functions保存視圖函數,error_handlers保存的錯誤處理函數

url_map保存uri到視圖函數的映射。

 

1.1.1.   route裝飾器

順帶講一下route裝飾器:

def route(self, rule, **options):

        """A decorator that is used to register a view function for a

        given URL rule.  This does the same thing as :meth:`add_url_rule`

        but is intended for decorator usage::

 

            @app.route('/')

            def index():

                return 'Hello World'

 

        """

        def decorator(f):

            endpoint = options.pop('endpoint', None)

            self.add_url_rule(rule, endpoint, f, **options)

            return f

        return decorator

route裝飾器的做用是將視圖函數與url的對應聯繫裝入self.url_map。

 

1.2.    app.run()

上面說到的是初始化部分,下面看服務器運行部分,當執行app.run()時:

找到run()方法,它作的事情不多,只是設定的一些參數,而後調用了run_simple方法:

    def run(self, host=None, port=None, debug=None,

            load_dotenv=True, **options):

from werkzeug.serving import run_simple

        try:

            run_simple(host, port, self, **options)

        finally:

            # reset the first request information if the development server

            # reset normally.  This makes it possible to restart the server

            # without reloader and that stuff from an interactive shell.

            self._got_first_request = False

 

1.3.    run_simple()

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

這裏只列出了核心部分,前面有一些判斷處理語句,按序作了如下工做:

  1. 對debug模式以及靜態文件的包裝;
  2. ShareDataMiddleware就是一箇中間件,這裏是起到吧文件轉換爲服務器可接受的Response形式的做用。
  3. use_reloader 用於決定當app代碼改變時是否要重啓服務器,如果True,則他會創建一個socket,其中的can_open_by_fd由socket中是否由fromfd特徵決定,若是能夠就將fd儲存在環境變量中以便重啓後的複用,socket開始監聽,然後就調用run_with_reloader,它也接受了函數inner.
  4. 不管use_reloader是否是True時,都會調用函數內部的inner函數。inner函數內,在環境中WERKZEUG_SERVER_FD這個key儲存了能夠複用的socket,若沒有就設爲None,而後就調用函數make_server,這根據參數process和threads選擇合適的服務器,取得服務器對象後,就調用方法run_forever,這服務器也就啓動了。,werkzeug提供了多種可選的服務器,這裏是一個基本的單線程單進程服務器

 

1.4.    make_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)

基本上就是根據服務器狀況建立一個server instance。

繼續,選擇BaseWSGIServer()去看一下

1.5.    BaseWSGIServer

 

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, get_sockaddr(host, int(port),

                                               self.address_family), 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

 

2.      請求處理

上面的部分是服務器的聲明及運行,下面寫一下flask服務器是如何處理請求的。

WSGI部分暫且略過,具體可看:https://www.cnblogs.com/steinliber/p/5133386.html

總而言之,它經過application_iter = app(environ, start_response)將請求體傳給了flask的app。

 

2.1.1.   wsgi_app()

接下來,當http請求從server發送過來的時候,會調用__call__()方法,最後實際是調用了wsgi_app功能並傳入environ和start_response。

代碼以下:

   def wsgi_app(self, environ, start_response):

        """The actual WSGI application.

        :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)

        error = None

        try:

            try:

                ctx.push()

                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)

 

    def __call__(self, environ, start_response):

        """The WSGI server calls the Flask application object as the

        WSGI application. This calls :meth:`wsgi_app` which can be

        wrapped to applying middleware."""

        return self.wsgi_app(environ, start_response)

 

 

 

response是在這裏生成的。

上下文處理語句:

ctx = self.request_context(environ)

 

核心語句:

response = self.full_dispatch_request()

 

2.1.2.   full_dispatch_request

找到full_dispatch_request:

def full_dispatch_request(self):
    """Dispatches the request and on top of that performs request
    pre and postprocessing as well as HTTP exception catching and
    error handling.
    """
   
self.try_trigger_before_first_request_functions()
    try:
        request_started.send(self)
        rv = self.preprocess_request()
        if rv is None:
            rv = self.dispatch_request()
    except Exception as e:
        rv = self.handle_user_exception(e)
    return self.finalize_request(rv)

 

try_trigger_before_first_request_function()用於判斷是不是第一個請求,而後是否執行           

for func in self.before_first_request_funcs:

                func()

request_started是信號機制通知請求開始處理,preprocess_request會調用app中註冊的請求前函數,若函數的返回值不是None,response的內容就設爲該返回值。不然就調用dispatch_request來找到對應的視圖函數獲得返回值

 

preprocess_request()方法的話,主要是進行flask的hook鉤子, before_request功能的實現;

 

2.1.3.   dispatch_request()

下一句:

    try:
        request_started.send(self)
        rv = self.preprocess_request()
        if rv is None:
            rv = self.dispatch_request()

一個http請求到了這裏,實際上已經完成了從wsgi部分的過渡,進入到了尋找響應的階段了,一個請求經過url進來之後,app怎麼知道要如何響應呢?

就是經過dispatch_request方法來進行請求斷定和分發。

 

    def dispatch_request(self):

        """Does the request dispatching.  Matches the URL and returns the

        return value of the view or error handler.  This does not have to

        be a response object.  In order to convert the return value to a

        proper response object, call :func:`make_response`.

        """

        req = _request_ctx_stack.top.request

        if req.routing_exception is not None:

            self.raise_routing_exception(req)

        rule = req.url_rule

        # if we provide automatic options for this URL and the

        # request came with the OPTIONS method, reply automatically

        if getattr(rule, 'provide_automatic_options', False) \

           and req.method == 'OPTIONS':

            return self.make_default_options_response()

        # otherwise dispatch to the handler for that endpoint

        return self.view_functions[rule.endpoint](**req.view_args)

 

self.view_functions是經過路由模塊產生的endpoint與視圖函數相對應的字典。這個就能返回視圖函數要返回的值。

 

2.1.4.   finalize_request()

接下來返回full_dispatch_request

return self.finalize_request(rv)

對響應進行處理,主要是標準化,經過make_response來將其轉化爲response的對象

def finalize_request(self, rv, from_error_handler=False):

        """Given the return value from a view function this finalizes

        the request by converting it into a response and invoking the

        postprocessing functions.  This is invoked for both normal

        request dispatching as well as error handlers.

 

        Because this means that it might be called as a result of a

        failure a special safe mode is available which can be enabled

        with the `from_error_handler` flag.  If enabled, failures in

        response processing will be logged and otherwise ignored.

 

        :internal:

        """

        response = self.make_response(rv)

        try:

            response = self.process_response(response)

            request_finished.send(self, response=response)

        except Exception:

            if not from_error_handler:

                raise

            self.logger.exception('Request finalizing failed with an '

                                  'error while handling an error')

        return response

 

 

3.      總結

對flask程序結構有了初步瞭解,理解了從請求到WSGI到flask的處理流程。

從請求到響應的流程圖:

 

 

4. 參考文檔

https://www.jianshu.com/p/2a2407f66438

https://blog.csdn.net/bestallen/article/details/54342120

相關文章
相關標籤/搜索