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:

  1. The applications developed using the Flask framework are Web applications. Because Python usesWSGIGateway, so this application can also be calledWSGIApplication;
  2. The design of servers and Web applications should follow some specifications of gateway interfaces. forWSGIGateway, which requires a Web application to implement a function or a callable objectwebapp(environ, start_response). To be defined in server or gatewaystart_responseFunction and call the Web application. For this section, please refer to:Wsgiref package — WSGI Compliant Web Services Implementation (PART 1).
  3. Flask relies on the underlying librarieswerkzeug. 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:

  1. Flask uses request and response processingwerkzeugIn the libraryRequestClasses andResponseClass. For these two classes, please refer to:The Werkzeug library — Wrappers module.
  2. Flask application is used for PROCESSING URL patternswerkzeugIn the libraryMapClasses andRuleClass, one for each URL patternRuleExamples, theseRuleThe instance is eventually passed as a parameter toMapClass to construct a “map” containing all URL patterns. This map can be used to match the URL information in the request aboutMapClasses andRuleClass for more information, please refer to:Werkzeug library — Routing module.
  3. When instantiating a Flask applicationappFlask 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 aRuleInstance, and then addMapIn the example. When accessing'/'Is executedindex(). For Flask matching urls, see a follow-up article.

  4. When the Flask application is instantiated, one is createdJinjaFlask environment, which is a template engine that comes with Flask. You can viewJinja document, the relevant introduction will not be made here.
  5. The instantiated Flask application is a callable object. As mentioned earlier, Web applications followWSGIThe 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 executeswsgi_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.

  6. 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 accessappThe request;
  • If you visit/app1Is triggeredapp1(environ, start_response)To process the accessapp1The request. access/app2In 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 objectappThe Flask property is the current Flask application;
  • url_adapter— of the context objecturl_adapterThe properties are applied through FlaskMapThe instance is constructed into oneMapAdapterInstance, whose main function is to combine the URLS in the request andMapURL rules in the instance to match;
  • request— of the context objectrequestAttributes are passed throughRequestClass to reflect the requested information;
  • session— of the context objectsessionProperty to store requested session information;
  • g— of the context objectgProperty 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,LocalStackThe 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 exampleapp,request,session,gAnd so on;
  • When the request ends and the context exits,LocalStackCleans 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:

  1. Actions, called before the request is formally processedpreprocess_request()Methods, such as opening a database connection;
  2. Formally process the request. This procedure calldispatch_request()Method, which calls the relevant view function based on the URL match;
  3. Converts the value returned from the view function to oneResponseObject;
  4. Before the response is sent toWSGIServer before callingprocess_response(response)Do some follow-up;
  5. callresponse(environ, start_response)Method sends the response backWSGIThe server. For the use of this method, please refer to:The Werkzeug library — Wrappers module;
  6. When exiting the context,LocalStackData (request context objects) generated by the current thread/coroutine is cleaned up.