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 Flask
the source code, look at this framework routing principle .
0x00 routing principle
First look Flask
easy usage
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return f'Hello, World!'
if __name__ == '__main__':
app.run()
In Flask
the use @app.route
of this decorator is achieved url
and the method of mapping between.
Flask.route
Open route
method
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 route
two methods of parameters rule
and options
. rule
Are url
rules, options
parameters are mainly werkzeug.routing.Rule
class uses. The method also defines an internal decorator
method, the url
path 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.add
method rule
and option
structure to Rule
add to a Map
object.
Rule
Rule
Representation url
rule, it is werkzeug
defined in the class library.
url_map
Is a self-defined Map
objects. Its purpose is to realize url
the 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 add
method to call rule
the bind
method, 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 bind
method for
called loop parse_url
approach, which is a generator function, which is performed using the positive and yield
back 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 Flask
startup decorator from the route
very beginning will url
function approach and response association.
Call logic
Flask.route -> Flask.add_url_rule -> Map.add -> Rule.bind
Response request 0x01
When the service starts, Flask
it will open a default Web
server for easy development and debugging, and the actual environment might use nginx+gunicorn
other 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 Flask
through the Werkzeug
library in the run_simple
method 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)
environ
Is a Web
parameter passed over the server, request_context(environ)
creates a request context instance, by pre-treating preprocess_request
and then will enter the distribution request dispatch_request
, then the response is performed make_response
and 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 url
to 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_request
do not match the url
rule, it performs error_handlers
an error code corresponding to the dictionary to find the execution handler
method.
At this point url
routing rule matching process is complete.
0x02 summarize
In Flask
will start after route
the decorator parsed, the url
rules and methods corresponding to functions stored.
When a client requests, Flask.wsgi_app
the method will be executed, and start matching url
finds 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