源码解析flask的路由系统

简介: 当我们新建一个flask项目时,pycharm通常已经为项目定义了一个基本路由@app.route('/')def hello_world(): return 'Hello World!'此时在浏览器中输入地址http://127.

当我们新建一个flask项目时,pycharm通常已经为项目定义了一个基本路由

@app.route('/')
def hello_world():
    return 'Hello World!'

此时在浏览器中输入地址http://127.0.0.1:5000,页面会显示出"Hello World!"的字样

如下图所示

img_f45d1d4b72ac0a3b35a1ff56acfa72f1.png

那么此时在flask后台程序中,到底发生了什么事情呢??

在上面的例子中,可以看到对hello_world视图函数被app.route这个有参装假器装饰

来看下app.route这个有参装饰器的内部实现原理

app是Flask主程序的类实例化本项目名得到的一个对象

app = Flask(__name__)

然后调用app对象的route方法来装饰hello_world视图函数

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

在用app.route装饰hello_world视图函数的时候,实际上app.route中还可以添加一些参数。

比如指定请求的方法的变量:methods=["GET","POST"]以及指定视图函数的endpoint,相当于Django中视图函数的别名等

在这里,rule参数相当于hello_world视图函数中的"/"路径,options参数中包含methods和endpoint等

在route装饰器里,返回decorator闭包函数。

在decorator闭包函数中,先从options中获取endpoint的值,endpoint的值默认为None

然后调用self.add_url_rule内部方法处理传递的参数rule,endpoint,f等,在这里self指的是app这个对象

查看app对象中的add_url_rule方法:

    @setupmethod
    def add_url_rule(self, rule, endpoint=None, view_func=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.
        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

可以看到,当在视图函数中没有指定endpoint时,程序会调用_endpoint_from_view_func方法为endpoint赋值

def _endpoint_from_view_func(view_func):
    assert view_func is not None, 'expected view func if endpoint ' \
                                  'is not provided.'
    return view_func.__name__

可以看出,_endpoint_from_view_func实际上返回的就是view_func函数的函数名。

在上面的例子中,view_func指的是hello_world这个视图函数

所以此时,在options这个字典中有一个键为endpoint,对应的值为view_func函数的函数名

接着,程序从options字典中弹出"methods"的值,并对methods中的每个方法转换为大写,如果methods没有从程序中获取,则默认为"GET"

接着,程序从函数中获取"required_methods"的值,并进行去重,默认得到一个空集合

再对methods和required_methods进行"|="操作,也就是按位或运算

    |=(按位或)运算
    
    >>> a = 15
    >>> bin(a)
    '0b1111'
    >>> b = 100
    >>> bin(b)
    '0b1100100'
    >>> a |= b
    >>> a
    111
    >>> bin(a)
    '0b1101111'
    >>> 0b1101111
    111
    
    先把a这个十进制数转换成二进制,得到1111
    再把b这个十进制数转换成二进制,得到1100100
    
    对a和b的二进制格式进行按位或运算
    
    a   000  0111
    b   110  0100
        110  0111
    
    因为a转换成二进制只有四位,如果要和b的二进制格式做位运算,则必须在头部填充0到和b的二进制相同的长度,得到"0000111"
    
    或运算中,只要有一个数为1,则这一位上做或运算的结果就为1
    所以上面两个数做或运算得到的二进制数为"0b1100111"
    把这个二进制数转换成十进制,则为111,把111这个十进制数赋值给a

对methods和required_methods进行按位或运算,实际上就是把required_methods的值添加到methods方法集合里

接着程序调用self.url_rule_class方法处理rule(也就是"/"),methods和options字典

得到rule这个对象,在这里self同样指的是app这个对象

可以看到,url_rule_class指向的是Rule这个类的内存地址

url_rule_class = Rule

然后用Map类实例化得到self.url_map对象,调用self.url_map对象中的add方法处理rule这个对象

self.url_map = Map()

分析了上面的app.route的流程,知道使用app对象的route方法装饰rule,实际上就是执行了add_url_rule这个方法

那如果定义一个视图函数,调用app对象中的add_url_rule方法来处理对应的rule,是不是也可以完成route的装饰器功能呢

    from flask import Flask
    app = Flask(__name__)
    
    @app.route('/')
    def hello_world():
        return 'Hello World!'
    
    def login():
        return "登录成功!!"
    
    app.add_url_rule("/login",endpoint=None,view_func=login,methods=["GET"])

启动这个项目,在浏览器中打开"http://127.0.0.1:5000/login"地址,

得到的效果如下

img_24eadb00065ce6bd0a90ef8a587adc69.png

由些我们可以知道,虽然flask的路由实现表面上是使用了route这个装饰器,实际上内部也是调用app对象中的add_url_rule方法来实现,类似于Django中中路由的用法

多个路由指向同一个视图函数

使用多个路由指向同一个视图函数

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

def login():
    return "登录成功!!"

@app.route("/index1/")
@app.route("/index2/")
def index():
    return "This is the index page!!"

app.add_url_rule("/login",endpoint=None,view_func=login,methods=["GET"])

启动项目,在浏览器中分别打开http://127.0.0.1:5000/index1/http://127.0.0.1:5000/index2/,可以看到前端页面指向同一个页面

img_738524e0623e4e425f593a5bfbdd3d3a.png

img_643e793995e0a7e64655e3d3c608faf7.png

使用正则表达式进行路由匹配

在Django中,可以有路由系统中调用正则表达式进行路由规则匹配,在flask中调用werkzeug插件也可以实现这个功能

from flask import Flask, render_template, redirect
from werkzeug.routing import BaseConverter

class RegexConverter(BaseConverter):
    def __init__(self,url_map,*items):
        super(RegexConverter,self).__init__(url_map)
        self.regex = items[0]

app = Flask(__name__)
app.debug = True
app.url_map.converters['regex'] = RegexConverter

@app.route('/')
def hello_world():
    return 'Hello World!'

@app.route('/user/<regex("[0-9]{4}"):user_id>')
def user1(user_id):
    return "User %s" % user_id

@app.route('/user/<regex("[a-z]{4,8}"):user_id>')
def user2(user_id):
    return "User %s" % user_id

@app.route("/index1/")
@app.route("/index2/")
def index():
    return "This is the index page!!"

@app.errorhandler(404)
def page_not_found(error):
    return render_template('404.html'),404

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

启动项目,就可以使用正则表达式进行用户名的规则匹配了

在上面的例子里,用户名可以由4位数字或4到8位的小写字母组成,还实现了404页面的路由

当用户输入的路由返回状态码为404时,执行page_not_found这个视图函数

用浏览器分别打开包含不同类型用户名的URL,可以看到实现了正则表达式进行URL匹配

例1:输入3位数字的用户名

img_43cbaa0a2d2e0b7540e74b2d5b250cb4.png

例2:输入4位数字的用户名

img_ab7b925a451304f17b5167917f0f17a9.png

例3:输入5位数字的用户名

img_d1fa45e8b900d20e0a7a4d0632271bea.png

例4:输入3位小写字母的用户名

img_0f30d9a5694cff0e0c022c5e3f7a1131.png

例5:输入4到8位小写字母的用户名

img_70d316213f3324d242778352c0eb742a.png

img_73fb2f8d8fd9c2d16184c0458fe84d4c.png

例6:输入9位小写字母的用户名

img_3ac4514c3ee99cd615200713049bd20a.png

由此可以实现在路由中完成正则表达式的规则匹配了

目录
相关文章
|
12月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
1117 29
|
12月前
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
469 4
|
12月前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
12月前
|
移动开发 前端开发 JavaScript
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。
|
12月前
|
存储 前端开发 JavaScript
在线教育网课系统源码开发指南:功能设计与技术实现深度解析
在线教育网课系统是近年来发展迅猛的教育形式的核心载体,具备用户管理、课程管理、教学互动、学习评估等功能。本文从功能和技术两方面解析其源码开发,涵盖前端(HTML5、CSS3、JavaScript等)、后端(Java、Python等)、流媒体及云计算技术,并强调安全性、稳定性和用户体验的重要性。
|
机器学习/深度学习 自然语言处理 算法
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
3351 1
|
12月前
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
455 2
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
1084 2
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
1264 1
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析

推荐镜像

更多
  • DNS