深入理解Flask路由的实现机制

本系列共三篇文章:

深入理解Flask路由的实现机制
深入理解Flask路由(2)- werkzeug 路由系统
深入理解Flask路由 (3) - 动态 url 及转换器

本篇介绍 Flask 路由的基本用法,并且通过部分源代码深入浅出阐述 Flask 路由的实现机制。

路由的基本用法

我们先编写一段简单代码,代码包括两个视图函数。

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return 'Index Page'

@app.route('/about')
def about():
    return 'About Page'

if __name__ == '__main__':
    print(app.url_map)
    app.run()

所谓路由,就是 Flask 根据客户端 request 的 URL,查找对应的视图函数 (view function),由视图函数进行处理后,返回 response 到客户端。Flask application 有两个属性来保存与路由相关的信息:

  • url_map: 储存 url 和 endpoint 的映射(url_map 的数据类型是 werkzeug.routing.Map
  • view_functions: 储存 endpoint 和 view function 的映射 (dict 类型)

就上面的例子来说,当客户端请求的 URL 中,path 为 /,Flask 就用 index 视图函数进行处理,当客户端请求的 URL 中,path 为 /about, Flask 就用 about 视图函数进行处理。运行程序,在 IDE 中打印了如下信息:

Map([<Rule '/about' (GET, OPTIONS, HEAD) -> about>,
 <Rule '/' (GET, OPTIONS, HEAD) -> index>,
 <Rule '/static/<filename>' (GET, OPTIONS, HEAD) -> static>])

endpoint 的作用

这里有两个知识点:第一个是 url 到视图函数的映射以 endpoint 来作为中介 (url -> endpoint -> view function)。为什么从 url 到视图函数的映射使用 endpoint 作为中介呢?如果不用 blueprint,endpoint 是没什么作用的。使用 blueprint 后,endpoint 就允许通过 blueprint 来进行区分。

接下来说明 endpoint 的作用,创建一个新的 Flask 工程,工程文件的结构如下:

flask-route-logic /
	admin /
		__init__.py
	user /
		__init__.py
	app.py

admin 和 user 作为两个蓝图 (blueprint),用于模块化组织代码。

admin/__init__.py 的代码如下:

# admin/__init__.py

from flask import Blueprint

adminbp = Blueprint('adminbp', __name__, url_prefix='/admin')

@adminbp.route('/')
def index():
    return 'Admin blueprint, index page'

user/__init__.py 的代码如下:

# user/__init__.py

from flask import Blueprint

userbp = Blueprint('userbp', __name__, url_prefix='/user')

@userbp.route('/')
def index():
    return 'User blueprint, index page'

app 主文件的代码如下:

from flask import Flask, url_for
from user import userbp
from admin import adminbp

app = Flask(__name__)
app.register_blueprint(userbp)
app.register_blueprint(adminbp)

@app.route('/', endpoint='index')
def index():
    return 'Index Page'

if __name__ == '__main__':
    print(app.url_map)
    app.run()

在代码中,一共定义了 3 个名称都为 index 的视图函数。运行后,打印的 url_map 信息如下:

Map([<Rule '/admin/' (OPTIONS, HEAD, GET) -> adminbp.index>,
 <Rule '/user/' (OPTIONS, HEAD, GET) -> userbp.index>,
 <Rule '/' (OPTIONS, HEAD, GET) -> index>,
 <Rule '/static/<filename>' (OPTIONS, HEAD, GET) -> static>])

可以看到,使用 blueprint 后,3 个 index 函数,endpoint 的名称分别为 index, adminbp.index 和 userbp.index,这样使用 url_for() 函数的时候就能区分,进行反向解析了。

定位 static 文件:static endpoint

第二个知识点,在本篇第一段代码中,我们定义了两个路由,为什么打印出来的 url_map 却有 3 个 rule (/, /aboutstatic) 呢?这是因为 Flask 在代码中添加了一个名为 static 的 endpoint,用于 url_for() 函数定位 static文件, 比如 css, images 等等。为了便于理解,我们用示例代码来说明。我们搭建一个如下所示的工程文件结构:

flask-route-logic /
	static /
		images /
			demo.png
	templates /
		index.html
	app2.py

在 static/images 文件夹下有一个图片文件,我们在 index.html 中,将使用 url_for 函数来构建一个 url,指向 demo.png 图像文件。

index.html 文件代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>url_for 函数示例</title>
</head>
<body>
    <p1>Below is a plus size model:</p1><br/>
    <img src="{{ url_for('static', filename='images/demo.png') }}"/>
</body>
</html>

app2.py 的代码:

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

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

这里,之所以能用 url_for('static', filename='images/demo.png) ,static 作为 endpoint, 就是因为 Flask 为了处理静态文件而在代码中增加的一个 endpoint ( static) 的路由匹配规则。在 Flask 的源代码 __init__() 方法中,我们可以看到这样一段代码(不同版本可能稍有出入):

if self.has_static_folder:
   # ...(省略无关代码)
    self.add_url_rule(
        self.static_url_path + "/<path:filename>",
        endpoint="static",
        host=static_host,
        view_func=self.send_static_file,
    )

这段代码用硬编码的方式添加了 static 这个 endpoint

Flask 的路由通过 @route 装饰器实现,本质上是调用 add_url_rule() 方法实现的,相关代码如下:

# flask/app.py

class Flask(_PackageBoundObject):
	# ...
	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

add_url_rule() 函数的 3 个参数是 rule, endpoint 和 view functions,其核心代码如下(我省略了无关代码和部分细节代码):

def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
	# 如果没有指定endpoint,则默认为view functon的函数名
	if endpoint is None:
	    endpoint = _endpoint_from_view_func(view_func)	
	# 将 endpoint 加入到 options(dict)
	options["endpoint"] = endpoint
	
	# methods: GET, HEAD, OPTIONS等
	methods = options.pop("methods", None)	
	# 从view function获取method,没有则为GET
	if methods is None:
        methods = getattr(view_func, "methods", None) or ("GET",)   
    # 将 methods改变为set类型
    methods = set(item.upper() for item in methods)
	
	# 将rule添加到url_map
	rule = self.url_rule_class(rule, methods=methods, **options)
    self.url_map.add(rule)
	
	# 添加view functions
	if view_func is not None:	   
	   self.view_functions[endpoint] = view_func

根据上面的核心代码,我们知道,add_url_rule() 最主要做了 4 件事:

  • 处理 endpoint: 由函数参数提供,或者默认为函数名称
  • 处理 methods (GET, HEAD, OPTIONS, POST 等)
  • 将每个匹配规则作为 rule 添加到 url_map
  • 将 endpoint 和 view function 的映射添加到 url_functions (dict)

既然 route 装饰器的本质是调用 add_url_rule(),我们的代码也可以这样写:

from flask import Flask

app = Flask(__name__)

def index():
    return 'Index Page'

def about():
    return 'About Page'

if __name__ == '__main__':
    app.add_url_rule('/', 'index', index)
    app.add_url_rule('/about', 'about', about)

    print(app.url_map)
    app.run()

另外,说明一下,Flask 路由的数据结构、路由匹配规则等,是由 werkzeug 实现的,Flask 只是使用者而已。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在 Flask实现 Token 机制,可以使用 JWT(JSON Web Tokens)库。下面是一个简单的示例代码: ```python from flask import Flask, request from flask_jwt import JWT, jwt_required, current_identity from werkzeug.security import safe_str_cmp app = Flask(__name__) app.config['SECRET_KEY'] = 'super-secret' app.config['JWT_EXPIRATION_DELTA'] = timedelta(days=1) class User: def __init__(self, id, username, password): self.id = id self.username = username self.password = password def __str__(self): return f"User(id='{self.id}')" users = [ User(1, 'user1', 'password1'), User(2, 'user2', 'password2'), ] def authenticate(username, password): user = next((user for user in users if user.username == username), None) if user and safe_str_cmp(user.password.encode('utf-8'), password.encode('utf-8')): return user def identity(payload): user_id = payload['identity'] return next((user for user in users if user.id == user_id), None) jwt = JWT(app, authenticate, identity) @app.route('/protected') @jwt_required() def protected(): return f'Hello, {current_identity}!' if __name__ == '__main__': app.run() ``` 在上面的代码中,我们首先定义了一个 `User` 类来存储用户信息。然后定义了一个 `authenticate` 函数来用于用户认证,它会查找用户列表中是否存在指定的用户名和密码。如果存在,则返回该用户对象。 接下来,我们定义了一个 `identity` 函数,它会根据 JWT 中存储的用户 ID 来查找用户对象。最后,我们创建了一个 `jwt` 对象,并将其绑定到 Flask 中。 最后,我们定义了一个受保护的路由 `/protected`,并使用 `@jwt_required()` 装饰器来保护它。这意味着在访问该路由时,用户必须先提供有效的 JWT 才能继续访问。 当用户成功提供有效的 JWT 且通过身份验证时,`current_identity` 会返回该用户对象,我们可以在响应中使用它。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值