I believe that many new Flask students (including myself), when reading official documents or Flask learning materials, start to understand Flask from the following code:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return "Hello World!"
if __name__ == '__main__':
app.run()Copy the code
Run the code above and visit http://localhost:5000/ in your browser to see Hello World! Appeared. This is a simple Flask application.
However, how does this code work? What’s the logic behind how a Flask app works? If you only care about Web applications, you can ignore these questions, but they make a lot of sense from a Web programming perspective. This article will briefly analyze the operation process of a Flask application, and some specific problems of the Flask framework will be analyzed in the following articles.
In order to facilitate analysis, this paper uses the source code of Flask 0.1 version to explore related problems.
Some preparation
Before formally analyzing Flask, there are a few things you need to know to prepare for it:
- The applications developed using the Flask framework are Web applications. Because Python uses
WSGI
Gateway, so this application can also be calledWSGI
Application; - The design of servers and Web applications should follow some specifications of gateway interfaces. for
WSGI
Gateway, which requires a Web application to implement a function or a callable objectwebapp(environ, start_response)
. To be defined in server or gatewaystart_response
Function and call the Web application. For this section, please refer to:Wsgiref package — WSGI Compliant Web Services Implementation (PART 1). - Flask relies on the underlying libraries
werkzeug
. Relevant content can be referred to:Werkzeug library.
This article won’t go into the specifics of servers or gateways for the moment, just to get an idea of how servers, gateways, and Web applications relate to each other and how they call each other.
A Flask application runs the process
1. Instantiate a Flask application
A Flask application can be instantiated using app = Flask(__name__). There are a few points or features to note about instantiated Flask applications:
- Flask uses request and response processing
werkzeug
In the libraryRequest
Classes andResponse
Class. For these two classes, please refer to:The Werkzeug library — Wrappers module. - Flask application is used for PROCESSING URL patterns
werkzeug
In the libraryMap
Classes andRule
Class, one for each URL patternRule
Examples, theseRule
The instance is eventually passed as a parameter toMap
Class to construct a “map” containing all URL patterns. This map can be used to match the URL information in the request aboutMap
Classes andRule
Class for more information, please refer to:Werkzeug library — Routing module. - When instantiating a Flask application
app
Flask has since adopted a more elegant way of adding URL modes, which can be compared to Django. Flask takes the decorator approach and writes URL rules together with view functions, the main of which areroute
. In the example above:
@app.route('/') def index(): passCopy the code
Writing the view function this way, it’s going to change
'/'
The URL rule and the view functionindex()
And it will form aRule
Instance, and then addMap
In the example. When accessing'/'
Is executedindex()
. For Flask matching urls, see a follow-up article. - When the Flask application is instantiated, one is created
Jinja
Flask environment, which is a template engine that comes with Flask. You can viewJinja document, the relevant introduction will not be made here. - The instantiated Flask application is a callable object. As mentioned earlier, Web applications follow
WSGI
The specification is to implement a function or callable objectwebapp(environ, start_response)
To facilitate server or gateway calls. Flask application passed__call__(environ, start_response)
Method can be invoked by the server or gateway.
def __call__(self, environ, start_response): """Shortcut for :attr:`wsgi_app`""" return self.wsgi_app(environ, start_response)Copy the code
Notice that calling the method executes
wsgi_app(environ, start_response)
Method, which is designed to change the characteristics of Flask applications by loading some “middleware” before the application formally processes the request. More on this later. - The Flask application has several other properties or methods that are used throughout the request and response process.
2. What happens when the Flask application is called
The above section analyzes what an instantiated Flask application looks like. Once a full Flask application is instantiated, it can be run by calling the app.run() method.
Flask’s run() method calls the run_simple method in the WerkZeug. Serving module. This method creates a local test server where the Flask application is run. The creation of the server is not explained here; see the documentation for the werkzeug.serving module.
The __call__(environ, start_Response) methods of the Flask application are triggered when the server starts calling the Flask application. Environ is generated by the server, and start_Response is defined on the server.
The wsGI_app (environ, start_Response) method is executed when Flask applications are called. As you can see, WSGI_app is the actual WSGI application being invoked, designed so that wsGI_app can be decorated with some “middleware” to handle some operations before the application actually processes the request. To make it easier to understand, here are two examples.
Example 1:Middleware SharedDataMiddleware
Middleware SharedDataMiddleware is a class in the WerkZeug.wsGi module. This class provides static content support for Web applications. Such as:
import os
from werkzeug.wsgi import SharedDataMiddleware
app = SharedDataMiddleware(app, {
'/shared': os.path.join(os.path.dirname(__file__), 'shared')
})Copy the code
Flask application through the above code, the app will be a SharedDataMiddleware instance, then can access the contents of the Shared folder in the http://example.com/shared/.
For the middleware SharedData Amiddleware, the Flask application is applied at the time of initial instantiation. It contains this code:
self.wsgi_app = SharedDataMiddleware(self.wsgi_app, {
self.static_path: target
})Copy the code
This code obviously turns wsGI_app into a SharedDataMiddleware object, which provides a static folder /static for Flask applications. This way, self.wsGI_app (environ, start_Response) is executed when the entire Flask application is called. Since self.wsgi_app is a SharedDataMiddleware object, the __call__(environ, start_Response) method of the SharedDataMiddleware object is triggered first. If the request is to access the /static folder, the SharedDataMiddleware object returns the response directly; If not, the wsGI_app (envion.start_Response) method of the Flask application is called to continue processing the request.
Example 2:Middleware DispatcherMiddleware
DispatcherMiddleware is also a class in the Werkzeug. wsGi module. This class can “merge” different applications. Here is an example of using DispatcherMiddleware.
from flask import Flask
from werkzeug import DispatcherMiddleware
app1 = Flask(__name__)
app2 = Flask(__name__)
app = Flask(__name__)
@app1.route('/')
def index():
return "This is app1!"
@app2.route('/')
def index():
return "This is app2!"
@app.route('/')
def index():
return "This is app!"
app = DispatcherMiddleware(app, {
'/app1': app1,
'/app2': app2
})
if __name__ == '__main__':
from werkzeug.serving import run_simple
run_simple('localhost', 5000, app)Copy the code
In the above example, we first create three different Flask applications and create a view function for each application. However, we used DispatcherMiddleware to combine app1, App2, and app. This makes the app a DispatcherMiddleware object.
When an app is called from the server, its __call__(environ, start_Response) method is triggered first because it is a DispatcherMiddleware object. It then determines which application to invoke based on the information in the request URL. Such as:
- If you visit
/
Is triggeredapp(environ, start_response)
(Note:The app is then a Flask object), which handles the need to accessapp
The request; - If you visit
/app1
Is triggeredapp1(environ, start_response)
To process the accessapp1
The request. access/app2
In the same way.
3. Context objects related to request processing
Wsgi_app (environ, start_Response) is called when the Flask application actually processes the request. This function works as follows:
def wsgi_app(environ, start_response):
with self.request_context(environ):
...Copy the code
Request context
As you can see, the Flask application constructs a context object when it processes a request. All request processing takes place in this context object. This context object is an instance of the _RequestContext class.
Flask v0.1 class _RequestContext(Object): """The request context contains all request relevant information. It is created at the beginning of the request and pushed to the `_request_ctx_stack` and removed at the end of it. It will create the URL adapter and request object for the WSGI environment provided. """ def __init__(self, app, environ): self.app = app self.url_adapter = app.url_map.bind_to_environ(environ) self.request = app.request_class(environ) self.session = app.open_session(self.request) self.g = _RequestGlobals() self.flashes = None def __enter__(self): _request_ctx_stack.push(self) def __exit__(self, exc_type, exc_value, tb): # do not pop the request stack if we are in debug mode and an # exception happened. This will allow the debugger to still # access the request object in the interactive shell. if tb is None or not self.app.debug: _request_ctx_stack.pop()Copy the code
The _RequestContext object is constructed with a number of attributes related to the Flask application:
app
— of the context objectapp
The Flask property is the current Flask application;url_adapter
— of the context objecturl_adapter
The properties are applied through FlaskMap
The instance is constructed into oneMapAdapter
Instance, whose main function is to combine the URLS in the request andMap
URL rules in the instance to match;request
— of the context objectrequest
Attributes are passed throughRequest
Class to reflect the requested information;session
— of the context objectsession
Property to store requested session information;g
— of the context objectg
Property can store global variables.flashes
— Message flash information.
LocalStack and some “global variables”
Note: when entering this context object, _request_CTx_stack.push (self) is triggered. Note here that Flask uses LocalStack, a data structure defined in the WerkZeug library.
_request_ctx_stack = LocalStack()Copy the code
For LocalStack, see: Werkzeug library – Local module. LocalStack is the stack structure into which the request context object _RequestContext is placed each time a request is processed. The data is stored in the stack as follows:
{880: {'stack': [<flask._RequestContext object>]}, 13232: {'stack': [<flask._RequestContext object>]}}Copy the code
This is a dictionary-style structure where the key represents the identity value of the current thread/coroutine and the value represents the variables stored by the current thread/coroutine. The werkzeug. Local module constructs this structure, easy to achieve thread/coroutine separation. It is this feature that makes it possible to access the following “global variables” in Flask:
current_app = LocalProxy(lambda: _request_ctx_stack.top.app)
request = LocalProxy(lambda: _request_ctx_stack.top.request)
session = LocalProxy(lambda: _request_ctx_stack.top.session)
g = LocalProxy(lambda: _request_ctx_stack.top.g)Copy the code
Where _request_CTx_stack. top always points to the “request context” stored in the current thread/coroutine, so that things like APP, Request, session, g, etc. can exist as “global”. “Global” here means in the current thread or coroutine.
From this we can see that when processing a request:
- First, a request context object is generated that contains information about the request. And when you enter the context,
LocalStack
The context object is pushed into the stack structure to store the object; - Request processing can take place in this context, which is described later. However, variables in context objects can be accessed in a “global” way, for example
app
,request
,session
,g
And so on; - When the request ends and the context exits,
LocalStack
Cleans up data generated by the current thread/coroutine (request context objects); - Flask 0.1 only had the concept of “request context”, and in Flask 0.9 the concept of “application context” was added. More on “application context” later.
4. Process the request in context
The procedure for handling a request is defined in the wsGI_app method as follows:
def wsgi_app(environ, start_response):
with self.request_context(environ):
rv = self.preprocess_request()
if rv is None:
rv = self.dispatch_request()
response = self.make_response(rv)
response = self.process_response(response)
return response(environ, start_response)Copy the code
As you can see from the code, the process of processing a request in a context object is divided into the following steps:
- Actions, called before the request is formally processed
preprocess_request()
Methods, such as opening a database connection; - Formally process the request. This procedure call
dispatch_request()
Method, which calls the relevant view function based on the URL match; - Converts the value returned from the view function to one
Response
Object; - Before the response is sent to
WSGI
Server before callingprocess_response(response)
Do some follow-up; - call
response(environ, start_response)
Method sends the response backWSGI
The server. For the use of this method, please refer to:The Werkzeug library — Wrappers module; - When exiting the context,
LocalStack
Data (request context objects) generated by the current thread/coroutine is cleaned up.