Python Web Flask source Interpretation (II) - routing principle

About
program ape a thoughtful, lifelong learning practitioners, is currently in a start-up team any team lead, technology stack involves Android, Python, Java, and Go, this is the main technology stack our team.
Github: https: //github.com/hylinux1024
micro-channel public number: Lifetime developer (angrycode)

Take on a topic, continue reading Flaskthe source code, look at this framework routing principle .

0x00 routing principle

First look Flaskeasy usage

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return f'Hello, World!'

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

In Flaskthe use @app.routeof this decorator is achieved urland the method of mapping between.

Flask.route

Open routemethod

def route(self, rule, **options):
    """这个方法的注释非常详细,为了避免代码篇幅过长,这里省略注释"""
    def decorator(f):
        self.add_url_rule(rule, f.__name__, **options)
        self.view_functions[f.__name__] = f
        return f

    return decorator

In the routetwo methods of parameters ruleand options. ruleAre urlrules, optionsparameters are mainly werkzeug.routing.Ruleclass uses. The method also defines an internal decoratormethod, the urlpath rules, and a method name saved correspondence relationship, a dictionary is then stored into a function name and method also function object corresponding.

Flask.add_url_rule
def add_url_rule(self, rule, endpoint, **options):
    options['endpoint'] = endpoint
    options.setdefault('methods', ('GET',))
    self.url_map.add(Rule(rule, **options))

Notes This method is also very detailed, probably it means if you define a method

@app.route('/')
def index():
    pass

Equivalent to

def index():
    pass
app.add_url_rule('index', '/')
app.view_functions['index'] = index

The last call url_map.addmethod ruleand optionstructure to Ruleadd to a Mapobject.

Rule

RuleRepresentation urlrule, it is werkzeugdefined in the class library.

url_mapIs a self-defined Mapobjects. Its purpose is to realize urlthe mapping between the methods.

Map.add
def add(self, rulefactory):
    """Add a new rule or factory to the map and bind it.  Requires that the
    rule is not bound to another map.

    :param rulefactory: a :class:`Rule` or :class:`RuleFactory`
    """
    for rule in rulefactory.get_rules(self):
        rule.bind(self)
        self._rules.append(rule)
        self._rules_by_endpoint.setdefault(rule.endpoint, []).append(rule)
    self._remap = True

In the addmethod to call rulethe bindmethod, here is the real logic binding.

Rule.bind
def bind(self, map, rebind=False):
    """Bind the url to a map and create a regular expression based on
    the information from the rule itself and the defaults from the map.

    :internal:
    """
    if self.map is not None and not rebind:
        raise RuntimeError('url rule %r already bound to map %r' %
                           (self, self.map))
    # 将url与map对应起来,即将map保存在rule对象自身的map属性上
    self.map = map
    if self.strict_slashes is None:
        self.strict_slashes = map.strict_slashes
    if self.subdomain is None:
        self.subdomain = map.default_subdomain

    rule = self.subdomain + '|' + (self.is_leaf and self.rule or self.rule.rstrip('/'))

    self._trace = []
    self._converters = {}
    self._weights = []

    regex_parts = []
    for converter, arguments, variable in parse_rule(rule):
        if converter is None:
            regex_parts.append(re.escape(variable))
            self._trace.append((False, variable))
            self._weights.append(len(variable))
        else:
            convobj = get_converter(map, converter, arguments)
            regex_parts.append('(?P<%s>%s)' % (variable, convobj.regex))
            self._converters[variable] = convobj
            self._trace.append((True, variable))
            self._weights.append(convobj.weight)
            self.arguments.add(str(variable))
            if convobj.is_greedy:
                self.greediness += 1
    if not self.is_leaf:
        self._trace.append((False, '/'))

    if not self.build_only:
        regex = r'^%s%s$' % (
            u''.join(regex_parts),
            (not self.is_leaf or not self.strict_slashes) and \
                '(?<!/)(?P<__suffix__>/?)' or ''
        )
        self._regex = re.compile(regex, re.UNICODE)

In the bindmethod forcalled loop parse_urlapproach, which is a generator function, which is performed using the positive and yieldback to a tuple. The details of this method is still a lot, but here we seize the main vein, first find out the overall process.

In the Flaskstartup decorator from the routevery beginning will urlfunction approach and response association.

Call logic

Flask.route -> Flask.add_url_rule -> Map.add -> Rule.bind

Response request 0x01

When the service starts, Flaskit will open a default Webserver for easy development and debugging, and the actual environment might use nginx+gunicornother tools for deployment. Since the deployment is not the subject of this section, we focus on how the client requests a response.

In the one we know Flaskthrough the Werkzeuglibrary in the run_simplemethod of the service started.

When a client sends a request to this method is performed

Flask.wsgi_app
def wsgi_app(self, environ, start_response):
    """The actual WSGI application.  This is not implemented in
    `__call__` so that middlewares can be applied:

        app.wsgi_app = MyMiddleware(app.wsgi_app)

    :param environ: a WSGI environment
    :param start_response: a callable accepting a status code, a list of headers and an optional
    exception context to start the 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)

environIs a Webparameter passed over the server, request_context(environ)creates a request context instance, by pre-treating preprocess_requestand then will enter the distribution request dispatch_request, then the response is performed make_responseand process_response, finally returned response.

Here we focus on dispatch_request.

Flask.dispatch_request
def dispatch_request(self):
    """Does the request dispatching.  Matches the URL and returns the
    return value of the view or error handler.  This does not have to
    be a response object.  In order to convert the return value to a
    proper response object, call :func:`make_response`.
    """
    try:
        endpoint, values = self.match_request()
        return self.view_functions[endpoint](**values)
    except HTTPException as e:
        handler = self.error_handlers.get(e.code)
        if handler is None:
            return e
        return handler(e)
    except Exception as e:
        handler = self.error_handlers.get(500)
        if self.debug or handler is None:
            raise
        return handler(e)

The core of this method is that match_request, by matching the client request urlto find the corresponding function method rules.

Flask.match_request
def match_request(self):
    """Matches the current request against the URL map and also
    stores the endpoint and view arguments on the request object
    is successful, otherwise the exception is stored.
    """
    rv = _request_ctx_stack.top.url_adapter.match()
    request.endpoint, request.view_args = rv
    return rv

After completion of matching is called self.view_functions[endpoint](**values)to perform the corresponding function method, and returns the value of the function.

If these dispatch_requestdo not match the urlrule, it performs error_handlersan error code corresponding to the dictionary to find the execution handlermethod.

At this point urlrouting rule matching process is complete.

0x02 summarize

In Flaskwill start after routethe decorator parsed, the urlrules and methods corresponding to functions stored.
When a client requests, Flask.wsgi_appthe method will be executed, and start matching urlfinds the corresponding method, after the execution result returned.

0x03 learning materials

  • https://werkzeug.palletsprojects.com/en/0.15.x/
  • https://palletsprojects.com/p/flask/
  • https://docs.python.org/3/library/http.server.html#module-http.server

Guess you like

Origin www.cnblogs.com/angrycode/p/11445163.html