The introduction

This paper mainly combs the design idea of route in Flask source code. Firstly, the function of flask route is introduced from the perspective of WSGI protocol. Secondly, it explains how to use Map and Rule of Werkzeug library to realize route. Finally, the complete process of route in a complete HTTP request is combed out.

Flask Route design idea

Source code Version Description

This article refers to the Flask 0.5 version of the code. Flask 0.1 version of the code is very short, just over 600 lines, but this version lacks the Blueprint mechanism. Therefore, I refer to version 0.5.

Flask route sample

Use the flask example directly from the official documentation

from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello World! ' @app.route('/post/') def show_post(post_id): # show the post with the given id, the id is an integer return 'Post %d' % post_id if __name__ == '__main__': app.run()Copy the code

In this example, the app.route decorator is used to route the following two urls and handlers:

{ 
    '/': hello_world, 
    '/post/' : show_post
}Copy the code

Flask calls hello_world when the HTTP request URL is ‘/’; Flask calls show_post when the HTTP request url is ‘/ POST /< some integer value >’ (for example, /post/32).

What the Flask Route does

The flask route is used to map urls to processing functions.

The WSGI protocol divides the components that handle requests into three categories based on function and invocation relationship: Server, Middleware, and Application. Server can call Middleware and Application, and Middleware can call Application.

A WSGi-compliant framework reads and parses an HTTP request, generates environ and start_Response, and calls Middleware. After middleware completes its processing, it can call the next middleware or Application to form a complete chain of requests. The application is at the last level of the request chain and its role is to generate the final response.

HTTP servers (e.g., Nginx)--> WSGI Server (e.g., Gunicorn, SimpleHttpServer)--> Middleware --> Middleware -->... -->applicationCopy the code

Anyone exposed to Java Web development will immediately notice that this is exactly the same middleware mechanism found in servlets.

Of particular importance:

In the example in the previous section, App = Flask(__name__) creates a middleware whose core function is request dispatch.

The above sentence is very important, please repeat it 100 times in your mind. The above sentence is very important, please repeat it 100 times in your mind. The above sentence is very important, please repeat it 100 times in your mind.

The premise of request forwarding is to establish the mapping relationship between URL and processing function, namely route function. So in Flask, route is a decorator for the Flask class.

Flask Route implementation idea

From the previous section, we know the following two points:

  1. Flask route is the mapping between URL and processing function.

  2. Flask is the middleware that calls url handlers during HTTP requests.

If we implement route ourselves, the idea is simple:

  1. Set up a Flask class, which is a Middleware class with a typical one-word member variable url_map;

  2. url_map = {url : function}

  3. When an HTTP request is made, request Dispatch: find function from url_map based on the URL and call function;

  4. Call the middleware or Application that follows and pass the result of function.

Flask does the same thing.

class Flask(object): def __init__(self): Self. url_map = {} # def __call__(self, environ, start_response): # According to the WSGI protocol, Middleware must be a core function of callable self.dispatch_request() # Flask request Dispatch Return Application (environ, Def def route(self, rule): = Flask: = Flask: = Flask: = Flask: = Flask: = Flask Self. Url_map [rule] = f return f return decorator def dispath_request(self): Url = get_url_from_environ() # return self.url_map[url]() # find the corresponding handler from url_map and call itCopy the code

At this point, a simple skeleton of Flaskmiddleware is complete. The main features of the Flask class above are:

  1. Wsgi-compliant Middleware: callable and application can be called

  2. The ability to save mapping information between URLS and processing functions

  3. Being able to find a handler based on the URL and call it (that is, request Dispatch)

In practice, of course, it can’t be that simple, but the basic idea is the same.

Application of Map and Rule in The Werkzeug library in Flask

It should be noted that the simplest Flask class implemented above has a number of problems. For example, how to handle the same URL in an HTTP request with different request methods, such as GET and POST, if they correspond to different processing functions?

Flask uses maps and rules from the WerkZeug library to manage the MAPPING between urls and processing functions.

In Werkzeug, the main function of Rule is to save a set of URL, endpoint, and methods: Each (URL, endpoint, and methods) has a corresponding Rule object: its implementation is as follows:

class Rule(object):
    def __init__(self, url, endpoint, methods):
        self.rule = url
        self.endpoint = endpoint
        self.methods = methodsCopy the code

{url: function}} {url: function}

Flask implements a mediation endpoint in the middle, so that the URL maps to the handler function like this:

Function {url: endpoint} function {url: endpoint} Function} # save the relationship between function and endpointCopy the code

The simple flask skeleton {url: function} dictionary is changed to {url: function}, and {url: endpoint} is mapped using the Map and Rule classes.

You can see that the endpoint is an intermediary in the mapping between urls and processing functions, so it can be any value that can be used as a dictionary key, such as a string. However, in actual use, the endpoint is usually a string, and by default:

  1. If the mapping is established through the flask. route decorator, then the endpoint is the name of the processing function.

  2. If the mapping is established through Blueprint, the endpoint is the Blueprint name. Handler function name;

Because a Rule object is created for each URL ->endpoint- >function relationship, there are many Rule objects. The Map is used to save all Rule objects. Therefore, in general, Map is used as follows:


    m = Map([
            Rule('/', endpoint='index'),
            Rule('/downloads/', endpoint='downloads/index'),
            Rule('/downloads/', endpoint='downloads/show')
           ])
Copy the code

In Flask’s source code

class Flask(object): def __init__(self): Self.url_map = Map() # url_map Map self.view_functions = {} # view_functions save the endpoint-->functionCopy the code
  1. The member variable url_map holds all (URL, endpoint, method) relationships

  2. The member variable view_functions holds all {endpoint, function} relationships

So, for a URL, as long as it can find (URL,endpoint,method), it can find the corresponding function according to the endpoint.

The complete flow of route

First, establish the Flask object:

app = Flask(__name__)Copy the code

Then, establish the mapping between URL and function:

@app.route('/') def hello_world(): return 'Hello World! 'Copy the code

In the decorator route, create two mappings (URL, endpoint, method) and {endpoint: function} :

if endpoint is None: Self.url_map. add(Rule(url, endpoint, method)) # save (url, endpoint, Self. view_functions[endpoint] = view_func # Save the {endpoint: function} mappingCopy the code

This completes the mapping between the URL and the response function.

Next, call WSGI Server in response to the HTTP request, using the example at the beginning of this article:

app.run()Copy the code

Call WSGI Server provided by the Python standard library, which in real life might be Gunicorn or UWSGi.

Flask.__call__ will eventually be called, regardless of what the server is. This function completes the request Dispatch task.

For Request Dispatch, the request parses environ, gets the URL, and then calls the map. match function, which finally finds the pre-saved Map (URL, endpoint, method), We then return (endpoint, url request parameter), and since we have the endpoint, we can then fetch the corresponding response function directly from flask. view_functions, so we can call the function directly

Self.view_functions [endpoint](URL request parameters)Copy the code

At this point, the complete route is complete.

conclusion

  1. Flask’s Flask class is WSGI’s Dispatch Middleware;

  2. Flask’s URl_map stores all maps (URL, endpoint, method);

  3. Flask view_functions holds all {endpoint: function} mappings;

  4. Dispath Request is the process of finding the endpoint based on the URL, then finding function based on the endpoint, and finally calling function