flask context management & source code analysis

Basic Process Overview

 

copy code
- are two different implementations compared to django.
    - django/tornado is implemented by passing parameters
    - Flask is managed through context, both can be implemented, but the way of implementation is different.
    - Context management:
        - Before talking about context management, we must first mention threadinglocal, which opens up a separate space for each thread, but Flask does not use it to do it, it implements a local class by itself
        - A dictionary is created to save the data. The key of this dictionary is the unique identifier of the thread. If there is a unique identifier obtained by the coroutine with greelet, it can be the thread or support the coroutine, followed by the accessed data { Greenlet as a unique identifier: save data} This ensures data isolation
        - When a request comes in:
            - Encapsulates all data (request, session) related to the request into the RequestContext.
            - Then add the RequestContext object to Local through the LocalStack class (call the push method)
        - When used, call request
            - Calling such methods request.method, print(request), request+xxx will execute the corresponding magic method in LocalProxy
            - Call the _get_current_object function in the magic method
            - Through the top method in LocalStack, go to Local to get the value, which is the last value of the list.
        - when the request terminates
            - Or pop the value in the Local from the list through the pop method of LocalStack.
copy code

 

copy code
Built-in session process

When the request just comes in, it will encapsulate the request-related and session into the RequestContext object, and the RequestCcontext object will then put the object into a Flask-specific object through the push method in it, similar to the theadingLocal object. 
In push, the open_session method in the session will be called. This method helps us to obtain the user's original session information. If there is, it will be obtained. If not, it will return an empty dictionary-like data structure and assign it to the session in the object. When used, trigger the magic method in the LocalProxy object, then call get_current_obj, and use the method in Localstark to obtain the data from Local through the partial function. After use, call the save_session method of the session object to encrypt the data and write it to the user's cookie. This operation is after after_request
copy code

 

 

 

 request 与 session

To call the request object of the current request in flask, it needs to be imported using from flask import reuqest. When we import the request global object, it is essentially the request_context_obj.request object encapsulated in the RequestContext corresponding to this request thread in Local. 
In addition to the request object, session, g and current_app are all based on this principle

 

 

LocalStack class and Local class

 

Local class

 

  It is a local thread implemented by flask imitating threading.Local. The internal self.__storage__ encapsulates a dictionary to store the private data data of the thread corresponding to each request, ensuring data isolation between each request.

  His structure is like this

  •  The RequestContext object encapsulates the request object and session object of the current request
self.__storage__ = {
    The unique identifier obtained by greenlet: {"stack": [RequestContext]},
    The unique identifier obtained by greenlet: {"stack": [RequestContext]},
    The unique identifier obtained by greenlet: {"stack": [RequestContext]},
}

 

__storage__ is a dictionary, but methods such as __setattr__ and __getattr__ are also defined in Local, which means that we can operate the __storage__ dictionary in the way of operating the properties of the Local object
copy code
try:
    from greenlet import getcurrent as get_ident # The unique identifier of the coroutine
except ImportError:
    try:
        from thread import get_ident # The unique identifier of the thread
    except ImportError:
        from _thread import get_ident


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

    def __init__(self):
        object.__setattr__(self, '__storage__', {}) # self.__storage__ = {} cannot be used directly here, because setattr will be triggered
        object.__setattr__(self, '__ident_func__', get_ident)


    def __release_local__(self):
        self.__storage__.pop(self.__ident_func__(), None) # Delete thread-related data stored in __storage__ (requestcontext)

    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)
copy code

 

 

LocalStack class

 

The role of the Local class is only to store data, while LocalStack provides pop, top, and push methods to operate the data in Local. That is, the data in Local is operated through LocalStack.

  • The push method puts a RequestContext into the list corresponding to stack in the local.__storage__ dictionary
  • The pop method takes the RequestContext from the list and removes it from the list
  • The top method is used to obtain the RequestContext object and will not delete the object

Note: The request, session and other flask global objects imported in the life cycle of each request point to the same reference, which means that operating the session elsewhere is the same session object.

copy code
class LocalStack(object):

    def __init__(self):
        self._local = Local()

    def push(self, obj):
        rv = getattr(self._local, 'stack', None)
        if rv is None:
            self._local.stack = rv = []
        rv.append(obj)
        return rv

    def pop(self):
        stack = getattr(self._local, 'stack', None)
        if stack is None:
            return None
        elif len (stack) == 1:
            release_local(self._local)
            return stack[-1]
        else:
            return stack.pop()

    @property
    def top(self):
        """The topmost item on the stack.  If the stack is empty,
        `None` is returned.
        """
        try:
            return self._local.stack[-1]
        except (AttributeError, IndexError):
            return None
copy code

 

 

Source code process analysis

 

1. After the request comes in, the __call__ method of the app will be executed first. The app.wsgi_app() method is actually called inside this method.

2. The ctx = self.request_context(environ) method essentially instantiates a RequestContext object and encapsulates all the relevant information of the request (WSGI environment) into the RequestContext object. When instantiating the RequestContext object, its __init__ method Will do the following things

  •  2.1 Initialize RequestContext, initialize app, request, session and other attributes
  •  2.2 Call requestcontext_obj.match_request(), which will match url_rule internally
copy code
class Flask:
    def wsgi_app(self, environ, start_response):

        ctx = self.request_context(environ)
        ctx.push()

        # omit part of the content in the middle

    def request_context(self, environ):
        return RequestContext(self, environ)


class RequestContext:
    def __init__(self, app, environ, request=None):
        self.app = app
        if request is None:
            request = app.request_class(environ)
        self.request = request
        self.url_adapter = app.create_url_adapter(self.request)
        self.flashes = None
        self.session = None # Assigned in self.push()

        self.match_request()

    def match_request(self):
        try:
            url_rule, self.request.view_args = \
                self.url_adapter.match(return_rule=True)
            self.request.url_rule = url_rule
        except HTTPException as e:
            self.request.routing_exception = e
copy code

 

 

2.3 request = app.request_class(environ) will package all the relevant information of the request into a Request object and bind it to the request attribute of the RequestContext object

class Flask:
    request_class = Request # app.request_class is actually Request

 

 

3. ctx.push(), this method will push the RequestContext object to the list of Local.__storage__ dictionary through the LocalStack class,

  Get the session data corresponding to the current request and bind it to the session attribute of the RequestContext object

copy code
# globals.py

_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'
copy code
copy code
class RequestContext:
    def push(self):
        # omit part

        _request_ctx_stack.push(self) # save the requestcontext to the list

        self.session = self.app.open_session(self.request) # Get session data and bind it to the session attribute of the requestcontext object
        if self.session is None:
            self.session = self.app.make_null_session()
copy code

 

 

4. When we use from flask import request, session, it will trigger the corresponding magic method in the LocalProxy class, call the _get_current_object() function, call _lookup_req_object in the function, and pass LocalStack to Local to __storage__ dictionary

  The request attribute of the RequestContext object in the corresponding list and the Request object and SecureCookieSession object corresponding to the session attribute are taken out

copy code
request = LocalProxy(partial(_lookup_req_object, 'request'))    # 获取requestcontext_obj.request
session = LocalProxy(partial(_lookup_req_object, 'session'))    # 获取requestcontext_obj.session


def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name) # Use reflection to get the attribute value of the RequestContext object
copy code

 

 

5. At the end of the request, call the pop method in RequestContext, and pop the data in the __storage__ dictionary in Local through Localstack

copy code
class LocalStack():  

    """"省略"""
    def pop(self):
        """Removes the topmost item from the stack, will return the
        old value or `None` if the stack was already empty.
        """
        stack = getattr(self._local, 'stack', None)
        if stack is None:
            return None
        elif len (stack) == 1:
            release_local(self._local)
            return stack[-1]
        else:
            return stack.pop()
copy code
copy code
class Local(object):
    __slots__ = ('__storage__', '__ident_func__')

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

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

 

application context

 

The principle of application context is basically the same as the process and management context. They create their own Local class respectively, and create a class-independent data storage space for each thread in their own Local class.

Context global variables current_app, g, request, session, etc. are all objects. We can bind some data to these objects through attributes, and then take out these data for operation when needed.

copy code
from flask import Flask,request,g,current_app,

app = Flask(__name__)

@app.before_request
def before():
    g.permission_code_list = ['list','add']
    current_app.x = 123
    request.name = "zhou"



@app.route('/',methods=['GET',"POST"])
def index():
    print(g.permission_code_list)
    print(current_app.x)
    print(request.name)
    # ['list', 'add']
    # 123
    # zhou
    return "index"


if __name__ == '__main__':
    app.run()
copy code

 

source code

copy code
def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name)


def _lookup_app_object(name):
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return getattr(top, name)


def _find_app():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return top.app


# context locals
_request_ctx_stack = LocalStack() #Request context
_app_ctx_stack = LocalStack() #Application context
current_app = LocalProxy(_find_app)
g = LocalProxy(partial(_lookup_app_object, 'g'))
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324963844&siteId=291194637