框架之路由系统

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

导入模块

importlib example

如何导入所有子模块

如果要获取包里面的所有模块列表,不应该用 os.listdir(),而是 pkgutil 模块。

网上说函数 iter_modules() 和 walk_packages() 的区别在于:后者会迭代所有深度的子包。实际测试发现没有区别

示例

5.1.2 动态导入对象

根据不同的条件导入不同的包

目录结构

程序内容

执行

6 传送门

插件化思维及实现

Last updated