I have been studying flask recently. Since the HTTP interface provided by gfirefly uses flask, it used to write some simple operations in the game. Recently, many flask operations have been involved, so I studied it carefully. I have some experience with the request context and app context of flask, so I would like to share with my friends the request principles of flask.
To use request in our view, you only need to use from flask import request.
I'm curious about how to ensure that there is no confusion in the request in a multi-threaded environment.
In flask. globals. py
def _lookup_req_object(name): top = _request_ctx_stack.top if top is None: raise RuntimeError(‘working outside of request context‘) return getattr(top, name)_request_ctx_stack = LocalStack()request = LocalProxy(partial(_lookup_req_object, ‘request‘))session = LocalProxy(partial(_lookup_req_object, ‘session‘))
In fact, no matter whether the request or session is obtained through getattr (top, name), there must be a context
The object maintains both request and session. We only need to import the request in one place, and the request can be used in any view function,
The key is that each request object is a different one, indicating that obtaining the request object must be a dynamic operation. Otherwise, all requests must be the same. The magic here is the combination of the _ lookup_req_object function and localproxy.
Localproxy is a proxy object defined in werkzeug. Local. py. It is used to send all requests to internal _ local objects.
Class localproxy (object): def _ init _ (self, local, name = none): # I simplified the code of localproxy. The local here is not necessarily local. the local thread object defined in Py can also be any callable object # What is passed in our request is the _ lookup_req_object function object. _ setattr _ (self, '_ localproxy _ local', local) object. _ setattr _ (self, '_ name _', name) def _ get_current_object (Self): # obviously, the _ lookup_req_object function does not have _ release_local _ If not hasattr (self. _ local, '_ release_local _'): return self. _ local () Try: Return getattr (self. _ local, self. _ name _) doesn t attributeerror: Raise runtimeerror ('no object bound to % s' % self. _ name _) def _ getattr _ (self, name): Return getattr (self. _ get_current_object (), name)
When we call request. method, _ lookup_req_object is called. Any call to the request is a call to the returned object of _ lookup_req_object.
Since each request is different, either top = _ request_ctx_stack.top is different or top. different request attributes have different top attributes returned each time in flask, so the attributes of the request are changed.
Now let's take a look at _ request_ctx_stack = localstack (). localstack is actually a simple simulation of the basic stack operations, push, top, pop, the local variables of the Internally stored thread are the key to the non-confusion of requests in multiple threads.
class Local(object): __slots__ = (‘__storage__‘, ‘__ident_func__‘) def __init__(self): object.__setattr__(self, ‘__storage__‘, {}) object.__setattr__(self, ‘__ident_func__‘, get_ident) def __getattr__(self, name): return self.__storage__[self.__ident_func__()][name]
Let's take a brief look at the local code __storage _. The key is thread. get_ident, that is, the corresponding value is returned Based on the thread identifier.
Next let's take a look at the entire interaction process. Where is the _ request_ctx_stack stack set push? The push should be the object that we mentioned above has both the request and session attributes, so what is this guy?
Flask starts with app. Run ().
class Flask(_PackageBoundObject): def run(self, host=None, port=None, debug=None, **options): from werkzeug.serving import run_simple run_simple(host, port, self, **options)
The run_simple of werkzeug is used. According to wsgi specifications, an app is an interface and two parameters are accepted, namely, application (Environ, start_response)
In run_wsgi's run_wsgi, we can clearly see the call process.
Def run_wsgi (Self): Environ = self. make_environ () 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): application_iter = app (Environ, start_response) # environ is used to send a request to the request # start_response mainly increases the response header and status code. At last, werkzeug needs to send the request try: for data in application_iter: # according to wsgi specifications, the APP returns a sequence of write (data) # The sending result if not headers_sent: Write (B '') Finally: If hasattr (application_iter, 'close'): application_iter.close () application_iter = none try: Execute (self. server. APP) socket T (socket. error, socket. timeout) as E: Pass
Use the _ call _ method in flask to adapt to the wsgi specification.
Class flask (_ packageboundobject): def _ call _ (self, Environ, start_response): "" shortcut for: ATTR: 'wsgi _ app '. "" return self. wsgi_app (Environ, start_response) def wsgi_app (self, Environ, start_response): CTX = self. request_context (Environ) # This CTX is the context CTX with both request and session attributes. push () error = none try: Try: Response = self. full_dispatch_request () failed t exception as E: Error = E response = self. make_response (self. handle_exception (E) return response (Environ, start_response) Finally: If self. should_ignore_error (error): Error = none CTX. auto_pop (error) def request_context (self, Environ): Return requestcontext (self, Environ)
Haha, I finally found the mysterious person. requestcontext is to keep the context variable of a request. Our _ request_ctx_stack has always been empty. When a request comes, we call CTX. push () will push CTX to _ request_ctx_stack.
Let's take a look at CTX. Push.
Class requestcontext (object): def _ init _ (self, app, Environ, request = none): Self. APP = app if request is none: Request = app. request_class (Environ) # CREATE request self based on environment variables. request = request self. session = none def push (Self): _ request_ctx_stack.push (Self) # Push CTX into _ request_ctx_stack # Open the session at the moment that the request context is # available. this allows a custom open_session method to use the # request context (e.g. code that access database information # stored on 'G' instead of the appcontext ). self. session = self. app. open_session (self. request) If self. session is none: Self. session = self. app. make_null_session () def POP (self, exc = none): Rv = _ request_ctx_stack.pop () def auto_pop (self, exc): If self. request. environ. get ('flask. _ preserve_context ') or (EXC is not none and self. app. preserve_context_on_exception): Self. preserved = true self. _ preserved_exc = exc else: Self. pop (EXC)
We can see that the CTX. Push operation pushes CTX to _ request_ctx_stack, so when we call request. method, we will call _ lookup_req_object. Top is the CTX context object, while getattr (top, "request") returns the CTX request, this request is created based on the environment variable in _ init _ of CTX.
Haha, you understand. Before each view function operation is called, flask puts the created CTX in the Thread Local, which can be obtained based on the thread ID. In the finally of wsgi_app, CTX. auto_pop (error) is called. The system determines whether to clear CTX in _ request_ctx_stack.
The above is my simplified code. In fact, in requestcontext push, _ app_ctx_stack = localstack () is none, and the app is also pushed. The corresponding app context object is appcontext.
We know that flask also has a mysterious object g, flask starting from 0.10g is bound together with the app (http://flask.pocoo.org/docs/0.10/api/#flask.g), G is a member variable of appcontext. Although G is bound with the app, The appcontext of different requests is different, so g is still different. That is to say, you cannot set G. Name in another view, and then use g. Name in another view. The attributeerror message is displayed.
At this point, I don't know if you have any questions about requestcontext. Here, we have only one request. Why should we use the stack for the request instead of directly saving the current request instance?
In fact, this is mainly for the coexistence of multiple apps. Since we generally only have one app, the top of the stack must be the current request object. If it is multiple apps, at the top of the stack, the current active request is stored, that is, the stack is used to obtain the current active request object.
The following uses gtwisted to simulate multiple apps. Because gtwisted uses greenlet, multiple apps. Run will not be blocked. Of course, you can also use a thread to simulate it.
from flask import Flask, url_forfrom gtwisted.core import reactorapp1 = Flask("app1")app2 = Flask("app2")@app1.route("/index1")def index1(): return "app1"@app1.route("/home")def home(): return "app1home"@app2.route("/index2")def index2(): return "app2" # reactor.listenWSGI(8000, app1)# reactor.listenWSGI(8001, app2)# reactor.run()with app1.test_request_context(): print url_for(‘index1‘) with app2.test_request_context(): print url_for(‘index2‘) print url_for(‘home‘)
If you remove the three lines in the middle, you can access multiple apps through the browser 127.0.0.1: 8000/index1, 127.0.0.1: 80001/index2
Output result:
When app1.test _ request_context () is used, the context corresponding to app1 is pushed to the stack. Currently, the active app context is app1, so url_for uses the context of app1, when with app2.test _ request_context (), the context corresponding to app2 is pushed to the stack. Currently, the active app context is app2. When with is out of scope, the context of app2 is displayed, app1 is active at this time, which is why context uses stacks instead of instance variables.
All the friends here have understood it. flask has provided us with great convenience in an elegant way and has done a lot of work on our own. This is a contemporary living Lei Feng !!!
How flask request, G, and session are implemented