Flask 請求處理流程 && 多線程模型 && context

flask

  • handler的處理流程.(路由)
  • ThreadedWSGIServer.(多線程非阻塞服務器)
  • 多線程如何保證請求安全.(ctx)

1.flask handler 執行流程:

# 1.wsgi階段
WSGI -> Flask().__call__

# 2.Flask階段
wsgi_app() -> full_dispatch_request() -> dispatch_request() -> view_handler()

full_dispatch_request: 框架實現. 
1.請求封裝, 請求ctx入棧
2.觸發鉤子函數.(try_trigger_before_first_request_functions) 
3.發送請求開始信號.(request_started.send(self))
4.觸發dispatch_request. 調用到用戶的handler

view_handler: 用戶編寫, 實現業務

1.1 WSGI: 實現網關協議的接口

  • call(environ, start_response))

flask.app.py 實現了wsgi接口.(上線可以託管在uwsgi/gunicorn後面)

class Flask(_PackageBoundObject):
    def dispatch_request(self):
        # 1.從_request_ctx_stack棧頂獲取請求.
        req = _request_ctx_stack.top.request
        if req.routing_exception is not None:
            self.raise_routing_exception(req)
        # 2.Rule對象包含了url到viewhandler的映射
        rule = req.url_rule
        # ...
        # 3.取出handler並執行
        return self.view_functions[rule.endpoint](**req.view_args)

    def full_dispatch_request(self):
        # 1.觸發first_request_functions
        self.try_trigger_before_first_request_functions()
        try:
            # 2.發送request_started信號, 觸發所有註冊了信號的函數
            request_started.send(self)
            # 3.預處理請求
            rv = self.preprocess_request()
            if rv is None:
                # 4.處理請求,調用到用戶註冊的對應方法
                rv = self.dispatch_request()
        except Exception as e:
            rv = self.handle_user_exception(e)
        # 5.結束請求
        return self.finalize_request(rv)
    def wsgi_app(self, environ, start_response):
      
        # 1.請求封裝ctx
        ctx = self.request_context(environ)
        error = None
        try:
            try:
                # 2.ctx入棧
                ctx.push()
                response = self.full_dispatch_request()
            except Exception as e:
                error = e
                response = self.handle_exception(e)
            except:  # noqa: B001
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            # 3.ctx自動出棧
            ctx.auto_pop(error)

    def __call__(self, environ, start_response):
        return self.wsgi_app(environ, start_response)

1.2 View CBV: 實現dispatch接口. 完成http方法到CBV.meth的調用.

  • View: 基視圖類.
    • 實現as_view
    • dispatch_request: 子類實現
  • MethodViewType:方法視圖元類.
    • cls.methods = methods 綁定方法集到類屬性
  • MethodView(with_metaclass(MethodViewType, View)): CBV

flask.views.py

http_method_funcs = frozenset(
    ["get", "post", "head", "options", "delete", "put", "trace", "patch"]
)

class View(object):
    """
        class MyView(View):
            methods = ['GET']

            def dispatch_request(self, name):
                return 'Hello %s!' % name

        app.add_url_rule('/hello/<name>', view_func=MyView.as_view('myview'))
    """
    methods = None
    provide_automatic_options = None
    decorators = ()

    def dispatch_request(self):
        raise NotImplementedError()

    @classmethod
    def as_view(cls, name, *class_args, **class_kwargs):
        def view(*args, **kwargs):
            # 1.實例化視圖類
            self = view.view_class(*class_args, **class_kwargs)
            # 2.觸發dispatch_request
            return self.dispatch_request(*args, **kwargs)

        # 註冊裝飾器
        if cls.decorators:
            view.__name__ = name
            view.__module__ = cls.__module__
            for decorator in cls.decorators:
                view = decorator(view)
        view.view_class = cls
        view.__name__ = name
        view.__doc__ = cls.__doc__
        view.__module__ = cls.__module__
        view.methods = cls.methods
        view.provide_automatic_options = cls.provide_automatic_options
        # 路由註冊的函數. 與url形成映射. 記錄在Flask().views_functions
        return view


class MethodViewType(type):
    def __init__(cls, name, bases, d):
        super(MethodViewType, cls).__init__(name, bases, d)
        # 1.cls的屬性中沒有methods時
        if "methods" not in d:
            methods = set()
            for base in bases:
                if getattr(base, "methods", None):
                    methods.update(base.methods)
            for key in http_method_funcs:
                if hasattr(cls, key):
                    methods.add(key.upper())
            if methods:
                # 2.綁定methods屬性到當前cls
                cls.methods = methods

class MethodView(with_metaclass(MethodViewType, View)):
    """A class-based view that dispatches request methods to the corresponding
    class methods. For example, if you implement a ``get`` method, it will be
    used to handle ``GET`` requests. ::

        class CounterAPI(MethodView):
            def get(self):
                return session.get('counter', 0)

            def post(self):
                session['counter'] = session.get('counter', 0) + 1
                return 'OK'

        app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
    """

    def dispatch_request(self, *args, **kwargs):
        # 1.獲取視圖實例的meth
        meth = getattr(self, request.method.lower(), None)
        if meth is None and request.method == "HEAD":
            meth = getattr(self, "get", None)

        assert meth is not None, "Unimplemented method %r" % request.method
        # 2.執行methd
        return meth(*args, **kwargs)

1.3 Blueprint 藍圖

  • 實現路由.

auth_view.py

auth = Blueprint('auth', __name__)
# 綁定 /api/auth/login 到login函數上, 支持GET,POST
@auth.route('/login', methods=['GET', 'POST'])
def login():
    pass

# CBV
class Files(views.MethodView):
    methods = ['POST', 'GET', 'PUT']
    decorators = [auth_decorator]

    def get(self, *args, **kwargs):
        pass

    def post(self, *args, **kwargs):
        pass

    def put(self, *args, **kwargs):
        pass

auth.add_url_rule('/files', view_func=Files.as_view(name='files'))

main.py

from auth_view import auth

app = Flask(__name__)
app.register_blueprint(auth, url_prefix='/api/auth')

2.flask server

開發環境的服務器.(默認: ThreadedWSGIServer)

  • 多線程服務器啓動.(每個建立一個連接,創建一個線程處理請求).
  • WSGIRequestHandler: 處理請求,封裝(environ, start_response)
  • Flask().call(environ, start_response): 被執行

2.1 開發服務器啓動流程.

app.run -> run_simple -> inner() -> ThreadedWSGIServer().serve_forever()
                                 -> BaseWSGIServer().serve_forever()
                                 -> HTTPServer().serve_forever()
                                 -> TCPServer().serve_forever()
                                 -> BaseServer().serve_forever()

flask.server.py

# app.run()
class Flask(_PackageBoundObject):
    def run(self, host=None, port=None, debug=None, load_dotenv=True, **options):
        options.setdefault("use_reloader", self.debug)
        options.setdefault("use_debugger", self.debug)
        options.setdefault("threaded", True)
        try:
            run_simple(host, port, self, **options)
        finally:
            self._got_first_request = False

SocketServer.py: 標準庫實現

  • serve_forever: 服務器啓動,等待請求
  • process_request: 處理請求
class BaseServer:
    def _handle_request_noblock(self):
        """Handle one request, without blocking.

        I assume that select.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 socket.error:
            return
        if self.verify_request(request, client_address):
            try:
                # 調用ThreadingMixIn().process_request處理請求.
                self.process_request(request, client_address)
            except:
                self.handle_error(request, client_address)
                self.shutdown_request(request)

    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:
            while not self.__shutdown_request:
                r, w, e = _eintr_retry(select.select, [self], [], [],
                                       poll_interval)
                if self in r:
                    self._handle_request_noblock()
        finally:
            self.__shutdown_request = False
            self.__is_shut_down.set()

class TCPServer(BaseServer):
    pass

class HTTPServer(SocketServer.TCPServer):
    pass

2.2 WSGIRequestHandler 處理請求

werkzeug.serving.py: 實現處理wsgi協議的請求

{
    "wsgi.multiprocess": False,
    "HTTP_COOKIE": "csrftoken=; gfsessionid=",
    "SERVER_SOFTWARE": "Werkzeug/0.16.0",
    "SCRIPT_NAME": "",
    "REQUEST_METHOD": "GET",
    "PATH_INFO": "/api/auth/login",
    "SERVER_PROTOCOL": "HTTP/1.1",
    "werkzeug.server.shutdown": <function shutdown_server at 0x10d050050>,
    "HTTP_CONNECTION": "keep-alive",
    "werkzeug.request": <BaseRequest "http://localhost:8065/api/auth/login?username=test&password=test" [GET
    ]>,
    "wsgi.input": <open file "<socket>", mode "rb" at 0x10d04e030>,
    "wsgi.multithread": True,
    "REQUEST_URI": "/api/auth/login?username=test&password=test",
    "wsgi.version": "(1, 0)",
    "REMOTE_ADDR": "127.0.0.1",
    "HTTP_ACCEPT_ENCODING": "gzip, deflate"
    ...
}
class WSGIRequestHandler(BaseHTTPRequestHandler, object):
    def run_wsgi(self):
        # 1.讀取socket的數據,封裝成wsgi的env
        self.environ = environ = self.make_environ()
        def execute(app):
            # 2.調用Flask().__call__() 觸發請求流程
            application_iter = app(environ, start_response)
            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 (_ConnectionError, socket.timeout) as e:
            self.connection_dropped(e, environ)
        except Exception:
            if self.server.passthrough_errors:
                raise
    pass

class BaseWSGIServer(HTTPServer, object):
    """
    單線程,單進程 wsgi server
    """
    def init():
        handler = WSGIRequestHandler
        #給http服務器綁定了wsgi請求的handler
        HTTPServer.__init__(self, server_address, handler)

class ThreadedWSGIServer(ThreadingMixIn, BaseWSGIServer):

    """A WSGI server that does threading."""

    multithread = True
    daemon_threads = True

def make_server():
    return ThreadedWSGIServer(
            host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd
        )

def inner(...):
    srv = make_server()
    # 啓動服務器
    srv.serve_forever()

app.run流程:

-> srv.serve_forever()
    -> ThreadedWSGIServer().serve_forever()
        -> BaseServer().serve_forever()

2.3 ThreadingMixIn 多線程處理請求

ThreadingMixIn: 實現了process_request接口. 啓動子線程處理當前請求

class ThreadingMixIn:
    """Mix-in class to handle each request in a new thread."""
    daemon_threads = False

    def process_request_thread(self, request, client_address):
        try:
            self.finish_request(request, client_address)
            self.shutdown_request(request)
        except:
            self.handle_error(request, client_address)
            self.shutdown_request(request)

    def process_request(self, request, client_address):
        t = threading.Thread(target = self.process_request_thread,
                             args = (request, client_address))
        t.daemon = self.daemon_threads
        t.start()

serve_forever 中使用非阻塞處理請求,一旦請求可處理,執行self.process_request, 調用ThreadingMixIn().process_request()

  • 啓動新的線程處理請求.
  • 主線程繼續執行serve_forever()
  • 新線程的啓動執行流程.(觸發Flask().call())
1.process_request_thread()
2.self.finish_request(request, client_address)
    -> BaseServer().finish_request(self, request, client_address):
        -> self.RequestHandlerClass(request, client_address, self)
            ->  WSGIRequestHandler().handle()
                ->  WSGIRequestHandler().run_wsgi()
                    -> app(environ, start_response) # Flask().__call__()
3.self.shutdown_request(request)

5.ctx local

參考 流程

werkzeug.local.py

  • Local
  • LocalProxy
  • LocalStack
  • LocalManager 當使用線程模型時,get_ident獲取線程標識 當使用greenlet協程時, get_ident獲取到協程標識
try:
    from greenlet import getcurrent as get_ident
except ImportError:
    try:
        from thread import get_ident
    except ImportError:
        from _thread import get_ident

class Local(object):
    __slots__ = ("__storage__", "__ident_func__")

    def __init__(self):
        object.__setattr__(self, "__storage__", {})
        object.__setattr__(self, "__ident_func__", get_ident)

    def __iter__(self):
        return iter(self.__storage__.items())

    def __call__(self, proxy):
        """Create a proxy for a name."""
        return LocalProxy(self, proxy)

    def __release_local__(self):
        self.__storage__.pop(self.__ident_func__(), None)

    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        ident = self.__ident_func__()
        storage = self.__storage__
        try:
            storage[ident][name] = value
        except KeyError:
            storage[ident] = {name: value}

    def __delattr__(self, name):
        try:
            del self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

flask.globals.py

  • _request_ctx_stack: 請求上下文棧
  • _app_ctx_stack: app上下文棧
  • current_app: 當前請求,同時啓動多個Flask()
  • request
  • session
  • g
# context locals
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, "request"))
session = LocalProxy(partial(_lookup_req_object, "session"))
g = LocalProxy(partial(_lookup_app_object, "g"))

py3 context是否可以替換: context

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