1 项目概述
1.1 背景介绍及目标
1.1.1 背景
web 框架的路由系统就是根据用户输入的 URL 的不同来返回不同的内容。
日常开发 web 程序的时候,一般是先编写逻辑函数,然后再配置路由,进而提供服务,当项目变大的时候,维护路由成本也会逐渐提高。
那是不是可以通过一种方式,让框架自身维护路由
1.1.2 目标
『基础』用户访问不同的 URL,框架路由到特定 handler 函数进行处理
『进阶』根据开发者 handler 函数,框架自动生成路由
1.2 名词说明
environ: WSGI 网关将 HTTP server 请求包封装的一个 dict 对象
handler: 代指 butterfly 处理函数
1.3 Roadmap
2 需求分析
2.1 功能需求
用户请求的 URL,会记录在 environ['PATH_INFO'] 中
解法
期望
2.2 非功能需求
2.3 调研
2.3.1 路由简单例子
请求 /home/index 时,返回 "Hello Home";请求 /login 时,返回 "welcome to login our site. "
这里只是一个简单的例子,我们需要更好的处理 environ['PATH_INFO']
2.3.2 Django
在 Django 中,路由是浏览器访问服务器时,先访问的项目中的 url,再由项目中的 url 找到应用中 url,这些 url 是放在一个列表里,遵从从前往后匹配的规则。
Django 支持动态路由即:
很多时候,我们需要获取 URL 中的一些片段,作为参数,传递给处理请求的视图函数。
例如访问网址 https://127.0.0.1:8585/user/4869
我们处理能匹配这个网址还要能将 4869 这串数字传递给函数,以便来查询用户。这个时候就是获取 url 中的一个片段作为参数传递给视图函数。
url 传递指定参数的语法为:
name 可以理解为所要传递的参数的名称,pattern 代表所要匹配的模式。例如,
路由系统会将正则部分匹配到的数据作为参数传递给 views.detail() 函数,views.detail() 函数也会多出一个参数,名为 userid。当然作为一个函数,userid 参数是可以提供默认值的。
Django2 中的路由参数传递
在 Django2 中路由参数传递改变了一点写法。
2.3.3 Tornado
通过正则进行匹配
2.3.4 Flask
在 Flask 中,路由是通过装饰器给每个视图函数提供的,而且根据请求方式的不同可以一个 url 用于不同的作用。
示例:
werkzeug 路由逻辑
事实上,flask 核心的路由逻辑是在 werkzeug 中实现的。我们先看一下 werkzeug 提供的路由功能。
上面的代码演示了 werkzeug 最核心的路由功能:
match 实现
werkzeug 中是怎么实现 match 方法的。Map 保存了 Rule 列表,match 的时候会依次调用其中的 rule.match 方法,如果匹配就找到了 match。Rule.match 方法的代码如下:
它的逻辑是这样的:用实现 compile 的正则表达式去匹配给出的真实路径信息,把所有的匹配组件转换成对应的值,保存在字典中(这就是传递给视图函数的参数列表)并返回。
3 总体设计
3.1 系统架构
3.2 设计与折衷
3.2.1 最多支持路由层次
目前看 2 层就可以,如下
3.2.2 是否支持动态路由
如 restful 风格
目前暂不支持
3.3 潜在风险
4 详细设计
4.1 URL PATH 与 handler 对应字典
通过一个 dict 存储对应关系,将路由放到 apicube 字典中
apicube demo
将路由写到 apicube 字典
根据请求路径在 apicube 字典中找到对应的处理 Handler,支持 1-2 级路由
4.2 自动生成路由
制定对应规则,路由通过自动导入 handlers package 以及其下的子 package 然后自动注册到 Web 路由中。
设置一个目录为 API 接口代码文件夹,如 handlers ,如:
其中 handlers 此文件夹下的各层 __init__.py
只能写接口 API 服务代码,这些接口 API 函数可以依赖本目录下的其他模块
4.2.1 路由自动映射规则
规则:项目文件夹 [...]/ 接口函数
示例:(如下 handlers/__init__.py::echo 标识 handlers 下的 __init__.py 中有个 echo 函数
)
4.2.2 handler 函数自动加载到路由中的条件
条件(第一个参数为 "req" 的非私有函数)
5 实现
5.1 自动生成路由
5.1.1 导入所有子 module/package
导入模块
如何导入所有子模块
如果要获取包里面的所有模块列表,不应该用 os.listdir(),而是 pkgutil 模块。
网上说函数 iter_modules() 和 walk_packages() 的区别在于:后者会迭代所有深度的子包。实际测试发现没有区别
示例
5.1.2 动态导入对象
根据不同的条件导入不同的包
目录结构
程序内容
执行
6 传送门
Last updated