前言

Flask是目前为止我最喜欢的一个Python Web框架了,为了更好的掌握其内部实现机制,这两天准备学习下Flask的源码,将由浅入深跟大家分享下,其中Flask版本为1.1.1。

上次了解了Flask服务的启动流程,今天我们来看下路由的内部实现机理。

Flask系列文章:

  1. Flask开发初探
  2. Flask源码分析一:服务启动

关于路由

所谓路由,就是处理请求URL和函数之间关系的程序。

Flask中也是对URL规则进行统一管理的,创建URL规则有两种方式:

  1. 使用@app.route修饰器,并传入URL规则作为参数,将函数绑定到URL,这个过程便将一个函数注册为路由,这个函数则被称为视图函数。
  2. 使用app.add_url_rule()。

在开始阅读源码之前,我是有这几点疑问的?

  1. 注册路由的过程是什么?
  2. Flask内部是如何进行URL规则管理的?
  3. 一个视图函数绑定多个URL内部是如何实现的?
  4. 动态URL是如何进行视图函数匹配的呢?
  5. 匹配路由的过程是怎样的呢?

那就让我们带着这几点疑问一起去学习源码吧!

正文

注册路由

首先,route()装饰器:

def route(self, rule, **options):       

        def decorator(f):
endpoint = options.pop("endpoint", None)
self.add_url_rule(rule, endpoint, f, **options)
return f return decorator

route()有两个参数,rule表示url规则。该函数对参数进行处理之后,调用方法add_url_role(),这里也就验证了两种注册路由的方法等价。我们来看下代码:

def add_url_rule(
self,
rule,
endpoint=None,
view_func=None,
provide_automatic_options=None,
**options
): if endpoint is None:
endpoint = _endpoint_from_view_func(view_func)
options["endpoint"] = endpoint
methods = options.pop("methods", None) # if the methods are not given and the view_func object knows its
# methods we can use that instead. If neither exists, we go with
# a tuple of only ``GET`` as default.
if methods is None:
methods = getattr(view_func, "methods", None) or ("GET",)
if isinstance(methods, string_types):
raise TypeError(
"Allowed methods have to be iterables of strings, "
'for example: @app.route(..., methods=["POST"])'
)
methods = set(item.upper() for item in methods) # Methods that should always be added
required_methods = set(getattr(view_func, "required_methods", ())) # starting with Flask 0.8 the view_func object can disable and
# force-enable the automatic options handling.
if provide_automatic_options is None:
provide_automatic_options = getattr(
view_func, "provide_automatic_options", None
) if provide_automatic_options is None:
if "OPTIONS" not in methods:
provide_automatic_options = True
required_methods.add("OPTIONS")
else:
provide_automatic_options = False # Add the required methods now.
methods |= required_methods rule = self.url_rule_class(rule, methods=methods, **options)
rule.provide_automatic_options = provide_automatic_options self.url_map.add(rule)
if view_func is not None:
old_func = self.view_functions.get(endpoint)
if old_func is not None and old_func != view_func:
raise AssertionError(
"View function mapping is overwriting an "
"existing endpoint function: %s" % endpoint
)
self.view_functions[endpoint] = view_func

入参包括:

  1. rule: url规则
  2. endpoint : 要注册规则的endpoint,默认是视图函数的名儿
  3. view_func: 视图函数
  4. provide_automatic_options: 请求方法是否添加OPTIONS方法的一个标志
  5. options: 关于请求处理的一些方法等

可以看到,add_url_rule()首先进行参数处理,包括:

  1. endpoint默认为视图函数的name
  2. url请求的方法默认为GET
  3. 若请求方法中没有设置OPTIONS,添加该方法。

在处理完所有的参数后,将该URL规则写入url_map(创建好Rule对象,并添加到Map对象中),将视图函数写入view_function字典中。

其中,url_map 是werkzeug.routing:Map 类的对象,rule是 werkzeug.routing:Rule 类的对象,也就是Flask的核心路由逻辑是在werkzeug中实现的

werkzeug

werkzeug是使用Python编写的一个WSGI工具集,werkzeug.routing模块主要用于url解析。

Rule类

Rule类继承自RuleFactory类,一个Rule实例代表一个URL模式,一个WSGI应用会处理很多个不同的URL模式,与此同时产生很多个Rule实例,这些实例将作为参数传给Map类。

Map类

Map类构造的实例存储所有的url规则,解析并匹配请求对应的视图函数。

路由匹配

在应用初始化的过程中,会注册所有的路由规则,可以调用(app.url_map)查看,当服务收到URL请求时,就需要进行路由匹配,以找到对应的视图函数,对应的流程和原理是什么呢?

当用户请求进入Flask应用时,调用Flask类的wsgi_app方法:

def wsgi_app(self, environ, start_response):

    ctx = self.request_context(environ)
error = None
try:
try:
ctx.push()
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.handle_exception(e)
except: # noqa: B001
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)

该函数的处理过程包括:

  1. 创建RequestContext对象,在对象初始化的过程中调用app.create_url_adapter()方法,将请求参数environ传给Map对象创建MapAdapter对象,保存在url_adapter字段中
  2. 将RequestContext对象推入_request_ctx_stack栈中
  3. 通过RequestContext的match_request方法,调用MapAdapter对象的match方法找到匹配的Rule并解析出参数,保存在request的url_rule和view_args字段中
  4. 调用full_dispatch_request()

接下来我们看下full_dispatch_request方法:

def full_dispatch_request(self):
self.try_trigger_before_first_request_functions()
try:
request_started.send(self)
rv = self.preprocess_request()
if rv is None:
rv = self.dispatch_request()
except Exception as e:
rv = self.handle_user_exception(e)
return self.finalize_request(rv)

可以看到,重点执行dispatch_request():

def dispatch_request(self):
req = _request_ctx_stack.top.request
if req.routing_exception is not None:
self.raise_routing_exception(req)
rule = req.url_rule
# if we provide automatic options for this URL and the
# request came with the OPTIONS method, reply automatically
if (
getattr(rule, "provide_automatic_options", False)
and req.method == "OPTIONS"
):
return self.make_default_options_response()
# otherwise dispatch to the handler for that endpoint
return self.view_functions[rule.endpoint](**req.view_args)

处理的过程是:获取请求对象的request,找到对应的endpoint,继而从view_functions中找到对应的视图函数,传递请求参数,视图函数处理内部逻辑并返回,完成一次请求分发。

以上,就是Flask路由的内部实现原理。

Flask源码分析二:路由内部实现原理的更多相关文章

  1. Python Web Flask源码解读(二)——路由原理

    关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...

  2. flask源码分析

    本flask源码分析不间断更新 而且我分析的源码全是我个人觉得是很beautiful的 1 flask-login 1.1 flask.ext.login.login_required(func),下 ...

  3. Tomcat源码分析二:先看看Tomcat的整体架构

    Tomcat源码分析二:先看看Tomcat的整体架构 Tomcat架构图 我们先来看一张比较经典的Tomcat架构图: 从这张图中,我们可以看出Tomcat中含有Server.Service.Conn ...

  4. 十、Spring之BeanFactory源码分析(二)

    Spring之BeanFactory源码分析(二) 前言 在前面我们简单的分析了BeanFactory的结构,ListableBeanFactory,HierarchicalBeanFactory,A ...

  5. Vue源码分析(二) : Vue实例挂载

    Vue源码分析(二) : Vue实例挂载 author: @TiffanysBear 实例挂载主要是 $mount 方法的实现,在 src/platforms/web/entry-runtime-wi ...

  6. Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题

    4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...

  7. 框架-springmvc源码分析(二)

    框架-springmvc源码分析(二) 参考: http://www.cnblogs.com/leftthen/p/5207787.html http://www.cnblogs.com/leftth ...

  8. 多线程之美8一 AbstractQueuedSynchronizer源码分析<二>

    目录 AQS的源码分析 该篇主要分析AQS的ConditionObject,是AQS的内部类,实现等待通知机制. 1.条件队列 条件队列与AQS中的同步队列有所不同,结构图如下: 两者区别: 1.链表 ...

  9. 并发编程学习笔记(9)----AQS的共享模式源码分析及CountDownLatch使用及原理

    1. AQS共享模式 前面已经说过了AQS的原理及独享模式的源码分析,今天就来学习共享模式下的AQS的几个接口的源码. 首先还是从顶级接口acquireShared()方法入手: public fin ...

随机推荐

  1. Linux之修改系统密码

    目录 Linux之修改系统密码 参考 RHEL6修改系统密码 RHEL7修改系统密码 Linux之修改系统密码

  2. 基本IO操作--字节流

    一.InputStream与OutputStream1. 输入与输出 我们编写的程序除了自身会定义一些数据信息外,经常还会引用外界的数据,或是将自身的数据发送到外界.比如,我们编写的程序想读取一个文本 ...

  3. Java 学习笔记之 线程安全

    线程安全: 线程安全的方法一定是排队运行的. public class SyncObject { synchronized public void methodA() { try { System.o ...

  4. node的重点学习笔记(1)————node

    node的重点学习笔记(1)----node 提到node就必须提一下他的npm了,npm是世界上最大的开放源代码的生态系统.通俗来说这就如同亚马逊丛林,要啥物种有啥物种,一个巨大的生态圈,里面有一堆 ...

  5. sudo 提示 'xxx is not in the sudoers file.This incident will be reported.的解决方法'

    在使用 Linux 的过程中,有时候需要临时获取 root 权限来执行命令时,一般通过在命令前添加 sudo 来解决. 但是第一次使用 sudo 时,有可能会得到这样一个错误提示 xxx is not ...

  6. conversion function——转换函数

    类型转换函数 与 explicit关键字 1.类型转换函数 在C++中,可以使用构造函数将一个指定类型的数据转换为类的对象,也可以使用类型转换函数 (type conversion function) ...

  7. 终端 10X 工作法(一)

    目录 1. Terminal 2. Grep 3. Sed 4. Awk 5. Xargs 6. Find 在 github 上面有一个 700 多人 star 的 repo 叫做 Bash-Onel ...

  8. maven更新慢,改用国内镜像地址

    方法很简单: 在 maven根目录 > conf > settings.xml 中 <mirrors>里添加以下子节点: <mirror> <id>al ...

  9. 移动端网页常用meta

    今天在对前公司的某直播室前端进行改版时,整理了一下平时移动端页面开发时,最常用的meta.如下: <!--定义页面制作者,可以留姓名,也可以留联系方式--> <meta name=& ...

  10. Python:numpy中shape和reshape的用法

    >>> w=np.zeros((5,6))>>> warray([[ 0.,  0.,  0.,  0.,  0.,  0.],       [ 0.,  0.,  ...