MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python Flask中的路由与请求处理详解

2021-07-115.2k 阅读

Flask 路由基础

在 Flask 应用中,路由(Routing)是指将不同的 URL 映射到相应的处理函数上。简单来说,当用户在浏览器中输入一个 URL 访问网站时,Flask 应用需要知道该用哪个函数来处理这个请求,这就是路由的作用。

基本路由定义

定义一个基本路由非常简单,通过 @app.route 装饰器来实现。以下是一个简单的 Flask 应用示例:

from flask import Flask

app = Flask(__name__)


@app.route('/')
def index():
    return "这是首页"


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

在上述代码中,@app.route('/') 表示将根 URL(即 '/')映射到 index 函数。当用户访问应用的根 URL 时,index 函数会被调用,并返回字符串 "这是首页" 作为响应。

路由中的变量规则

有时候,我们需要在 URL 中传递一些动态信息,例如用户 ID、文章标题等。Flask 允许在路由中定义变量,语法为 <变量名>。示例如下:

from flask import Flask

app = Flask(__name__)


@app.route('/user/<username>')
def show_user_profile(username):
    return f"用户 {username} 的资料"


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

在这个例子中,/user/<username> 路由表示匹配以 /user/ 开头,后面跟着任意字符串的 URL。<username> 是一个变量,这个变量的值会作为参数传递给 show_user_profile 函数。比如访问 /user/johnshow_user_profile 函数接收到的 username 参数值就是 john,并返回 "用户 john 的资料"。

变量还可以指定类型,语法为 <类型:变量名>。常见的类型有 string(默认类型,匹配除 / 之外的任何文本)、int(匹配整数)、float(匹配浮点数)、path(匹配包含 / 的文本)等。以下是一个匹配整数类型变量的示例:

from flask import Flask

app = Flask(__name__)


@app.route('/post/<int:post_id>')
def show_post(post_id):
    return f"文章 ID 为 {post_id}"


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

这里 /post/<int:post_id> 只匹配 post_id 为整数的 URL,如 /post/123,如果访问 /post/abc 则会返回 404 错误,因为 abc 不是整数。

路由的高级特性

多路由绑定到同一函数

在实际开发中,可能会有多个不同的 URL 都需要执行同一个处理逻辑,这时可以将多个路由绑定到同一个函数上。示例如下:

from flask import Flask

app = Flask(__name__)


@app.route('/home')
@app.route('/')
def home():
    return "欢迎来到主页"


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

在这个例子中,/home/ 这两个路由都绑定到了 home 函数,用户访问这两个 URL 中的任意一个,都会执行 home 函数并返回 "欢迎来到主页"。

反向构建 URL

在 Flask 中,不仅可以根据 URL 找到对应的处理函数,还可以根据函数名反向构建出对应的 URL。这在生成 HTML 链接、重定向等场景中非常有用。通过 url_for 函数来实现反向构建 URL。示例如下:

from flask import Flask, url_for

app = Flask(__name__)


@app.route('/user/<username>')
def show_user_profile(username):
    return f"用户 {username} 的资料"


with app.test_request_context():
    print(url_for('show_user_profile', username='john'))

在上述代码中,url_for('show_user_profile', username='john') 会返回 /user/john,即根据 show_user_profile 函数名和传入的参数 username='john' 反向构建出对应的 URL。with app.test_request_context() 是为了在测试环境中模拟请求上下文,使得 url_for 函数能够正常工作。在实际应用中,url_for 通常在视图函数或模板中使用。

自定义 URL 转换器

虽然 Flask 提供了几种常见的 URL 变量类型,但在某些特定场景下,可能需要自定义 URL 转换器。要自定义 URL 转换器,需要继承 BaseConverter 类,并实现 to_pythonto_url 方法。以下是一个简单的自定义 URL 转换器示例,用于匹配十六进制颜色代码:

from flask import Flask
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.url_map.converters['regex'] = RegexConverter


@app.route('/color/<regex("[0 - 9a - fA - F]{6}"):color_code>')
def show_color(color_code):
    return f"颜色代码: {color_code}"


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

在这个例子中,定义了一个 RegexConverter 类,它继承自 BaseConverter。通过 app.url_map.converters['regex'] = RegexConverter 将自定义的转换器注册到 Flask 应用中,名称为 regex。在路由定义中,/<regex("[0 - 9a - fA - F]{6}"):color_code> 表示使用自定义的 regex 转换器来匹配长度为 6 的十六进制颜色代码,to_python 方法会将匹配到的字符串转换为 Python 对象(这里就是字符串本身)传递给视图函数,to_url 方法则用于反向构建 URL 时将对象转换为字符串。

Flask 请求处理

当 Flask 应用接收到一个 HTTP 请求时,需要对请求进行处理,包括获取请求中的数据、处理请求头、根据请求方法执行不同的逻辑等。

请求对象

Flask 通过 request 对象来获取客户端发送的请求信息。在使用 request 对象之前,需要先从 flask 模块中导入它。示例如下:

from flask import Flask, request

app = Flask(__name__)


@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        return f"登录成功,用户名: {username},密码: {password}"
    else:
        return "请使用 POST 方法提交表单"


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

在上述代码中,request.method 用于获取请求的方法(如 GETPOST 等)。当请求方法为 POST 时,通过 request.form.get('username')request.form.get('password') 从表单数据中获取 usernamepassword 字段的值。request.form 是一个类似字典的对象,用于存储表单数据。

获取不同类型的请求数据

  1. 获取 URL 参数(GET 请求) 对于 GET 请求,参数通常包含在 URL 中,形如 ?key1=value1&key2=value2。可以通过 request.args 来获取这些参数,request.args 也是一个类似字典的对象。示例如下:
from flask import Flask, request

app = Flask(__name__)


@app.route('/search')
def search():
    keyword = request.args.get('keyword')
    return f"搜索关键词: {keyword}"


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

当访问 http://127.0.0.1:5000/search?keyword=python 时,request.args.get('keyword') 会获取到 python 并返回 "搜索关键词: python"。

  1. 获取 JSON 数据 如果客户端发送的是 JSON 格式的数据,例如在使用 AJAX 进行 POST 请求并传递 JSON 数据时,可以通过 request.get_json() 方法来获取。示例如下:
from flask import Flask, request

app = Flask(__name__)


@app.route('/api/data', methods=['POST'])
def receive_json():
    data = request.get_json()
    if data:
        return f"接收到的 JSON 数据: {data}"
    else:
        return "未接收到有效的 JSON 数据"


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

使用工具(如 Postman)发送一个 POST 请求到 /api/data,并在请求体中设置 JSON 数据,如 {"name": "John", "age": 30},Flask 应用就能通过 request.get_json() 获取到这个 JSON 数据并进行处理。

  1. 获取请求头 通过 request.headers 可以获取请求头信息,request.headers 也是一个类似字典的对象。示例如下:
from flask import Flask, request

app = Flask(__name__)


@app.route('/headers')
def show_headers():
    headers = request.headers
    result = ""
    for key, value in headers.items():
        result += f"{key}: {value}<br>"
    return result


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

访问 /headers 时,会返回请求头中的所有信息,每个键值对占一行显示。

请求方法处理

Flask 支持多种 HTTP 请求方法,如 GETPOSTPUTDELETE 等。在定义路由时,可以通过 methods 参数指定该路由支持的请求方法。示例如下:

from flask import Flask, request

app = Flask(__name__)


@app.route('/method', methods=['GET', 'POST'])
def handle_method():
    if request.method == 'GET':
        return "这是 GET 请求"
    elif request.method == 'POST':
        return "这是 POST 请求"


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

在上述代码中,/method 路由支持 GETPOST 两种请求方法,根据不同的请求方法返回不同的响应。如果客户端以其他方法(如 PUT)访问该路由,Flask 会返回 405 Method Not Allowed 错误。

请求钩子

请求钩子(Request Hooks)是 Flask 提供的一种机制,允许在请求处理的不同阶段执行一些通用的代码逻辑,例如在请求处理前进行身份验证、日志记录,在请求处理后进行资源清理等。Flask 提供了以下几种请求钩子:

before_request

before_request 装饰的函数会在每个请求之前执行。如果这个函数返回了一个响应对象,Flask 会直接将这个响应返回给客户端,而不再执行视图函数。示例如下:

from flask import Flask, request, abort

app = Flask(__name__)


@app.before_request
def check_authentication():
    if not request.headers.get('Authorization'):
        abort(401)


@app.route('/protected')
def protected():
    return "这是一个受保护的路由"


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

在上述代码中,check_authentication 函数使用 before_request 装饰,它检查请求头中是否包含 Authorization 字段,如果没有则返回 401 Unauthorized 错误,阻止对 protected 路由的访问。

after_request

after_request 装饰的函数会在视图函数执行之后,响应返回给客户端之前执行。这个函数接收一个响应对象作为参数,并必须返回一个响应对象。可以利用这个钩子来添加响应头、记录日志等。示例如下:

from flask import Flask, request, make_response

app = Flask(__name__)


@app.after_request
def add_header(response):
    response.headers['X - Powered - By'] = 'Flask'
    return response


@app.route('/')
def index():
    return "首页"


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

在这个例子中,add_header 函数使用 after_request 装饰,它在响应对象上添加了一个自定义的响应头 X - Powered - By: Flask

teardown_request

teardown_request 装饰的函数会在请求处理完成后执行,无论请求处理过程中是否发生异常。这个函数接收一个异常对象(如果有异常发生)作为参数。可以用于清理资源,如关闭数据库连接等。示例如下:

import sqlite3
from flask import Flask, g

app = Flask(__name__)


def get_db():
    if 'db' not in g:
        g.db = sqlite3.connect('example.db')
    return g.db


@app.teardown_request
def close_db(error):
    db = g.pop('db', None)
    if db is not None:
        db.close()


@app.route('/')
def index():
    db = get_db()
    cursor = db.cursor()
    cursor.execute('SELECT * FROM users')
    results = cursor.fetchall()
    return str(results)


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

在上述代码中,close_db 函数使用 teardown_request 装饰,它在请求处理完成后关闭数据库连接,无论请求过程中是否发生异常。g 是 Flask 提供的一个全局对象,用于在请求处理过程中存储数据,这里用于存储数据库连接对象。

错误处理

在 Flask 应用中,不可避免地会遇到各种错误,如 404 页面未找到、500 服务器内部错误等。Flask 提供了方便的错误处理机制,可以自定义错误页面和错误处理逻辑。

处理常见错误

  1. 404 错误处理 通过 @app.errorhandler(404) 装饰器可以定义 404 错误的处理函数。示例如下:
from flask import Flask, render_template

app = Flask(__name__)


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


@app.route('/')
def index():
    return "首页"


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

在这个例子中,当发生 404 错误时,会调用 page_not_found 函数,它渲染一个名为 404.html 的模板,并返回 404 状态码。404.html 模板可以根据项目需求自定义页面内容,展示友好的错误提示信息。

  1. 500 错误处理 类似地,通过 @app.errorhandler(500) 装饰器可以定义 500 错误的处理函数。示例如下:
from flask import Flask, render_template

app = Flask(__name__)


@app.errorhandler(500)
def internal_error(error):
    return render_template('500.html'), 500


@app.route('/error')
def trigger_error():
    raise Exception("模拟服务器内部错误")


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

在上述代码中,/error 路由故意引发一个异常,触发 500 错误。internal_error 函数会捕获这个错误,渲染 500.html 模板并返回 500 状态码,给用户展示服务器内部错误的提示信息。

自定义错误处理

除了处理常见的 HTTP 错误码,还可以自定义错误类型和处理逻辑。首先定义一个自定义的错误类,然后使用 @app.errorhandler 装饰器来处理这个自定义错误。示例如下:

from flask import Flask, abort

app = Flask(__name__)


class MyCustomError(Exception):
    pass


@app.errorhandler(MyCustomError)
def handle_custom_error(error):
    return "发生了自定义错误", 500


@app.route('/custom_error')
def trigger_custom_error():
    raise MyCustomError()


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

在这个例子中,定义了 MyCustomError 自定义错误类。当访问 /custom_error 路由时,会引发 MyCustomError 错误,handle_custom_error 函数会捕获并处理这个错误,返回 "发生了自定义错误" 和 500 状态码。

通过合理运用 Flask 的路由与请求处理机制,包括路由的定义、请求数据的获取与处理、请求钩子的使用以及错误处理等,可以构建出功能丰富、健壮的 Web 应用程序。这些特性使得 Flask 在 Web 开发领域具有很高的灵活性和实用性,无论是开发小型的个人项目还是大型的企业级应用,都能发挥出强大的作用。同时,深入理解这些底层原理,有助于开发者更好地优化和扩展应用,提高代码的质量和可维护性。在实际项目中,还需要结合具体的业务需求,灵活运用这些技术,以实现高效、稳定且用户体验良好的 Web 应用。