作者选择了自由与开源基金会作为写作捐赠计划的一部分接受捐赠。
引言
Flask是一个轻量级的Python Web框架,为使用Python语言创建Web应用程序提供了有用的工具和特性。
在开发Web应用程序时,您难免会遇到应用程序行为与预期不符的情况。您可能会拼错变量、误用for
循环,或以引发Python异常的方式构建if
语句,比如在声明函数之前调用它,或者尝试访问一个不存在的页面。如果您学会如何妥善处理错误和异常,将会更容易、更顺畅地开发Flask应用程序。
在本教程中,您将构建一个小型Web应用程序,展示如何处理开发Web应用程序时常见的错误。您将创建自定义错误页面,使用Flask调试器排查异常,并使用日志记录来跟踪应用程序中的事件。
前提条件
-
本地Python 3编程环境。您可以根据您的发行版遵循如何安装和配置Python 3的本地编程环境系列教程。在本教程中,我们将项目目录命名为
flask_app
。 -
对Flask基本概念的理解,如路由、视图函数和模板。如果您不熟悉Flask,请查阅如何使用Flask和Python创建您的第一个Web应用以及如何在Flask应用中使用模板。
-
掌握基本的HTML概念。你可以回顾我们的如何使用HTML构建网站教程系列以获取背景知识。
步骤1 — 使用Flask调试器
在这一步中,你将创建一个包含若干错误的应用,首先在不启用调试模式下运行,观察应用的响应情况。然后开启调试模式,利用调试器来排查应用错误。
在激活编程环境并安装 Flask 后,打开位于你的 `flask_app` 目录中的名为 `app.py` 的文件进行编辑:
在 `app.py` 文件中添加以下代码:
在上面的代码中,你首先从 `flask` 包中导入了 `Flask` 类。然后创建了一个名为 `app` 的 Flask 应用程序实例。你使用 `@app.route()` 装饰器创建了一个名为 `index()` 的视图函数,该函数调用 `render_template()` 函数作为返回值,进而渲染一个名为 `index.html` 的模板。这段代码中有两个错误:第一个是你没有导入 `render_template()` 函数,第二个是 `index.html` 模板文件不存在。
保存并关闭文件。
接下来,使用以下命令通过 `FLASK_APP` 环境变量告知 Flask 应用程序的位置(在 Windows 上,使用 `set` 代替 `export`):
然后使用 `flask run` 命令运行应用程序服务器:
你将在终端中看到以下信息:
Output * Serving Flask app 'app' (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
此输出提供了以下信息:
-
正在服务的 Flask 应用程序(在本例中为 `app.py`)
-
这里的环境设置为
production
。警告信息强调此服务器不适用于生产部署。您当前使用的是开发服务器,因此可以忽略此警告,但如需更多信息,请参阅Flask文档中的部署选项页面。您还可以查看这篇关于使用Gunicorn部署Flask的教程,或者使用uWSGI的教程,或者按照如何在App Platform上使用Gunicorn部署Flask应用的步骤,通过DigitalOcean App Platform部署您的Flask应用。 -
调试模式已关闭,这意味着Flask调试器未运行,您在应用程序中将不会收到有用的错误消息。在生产环境中,显示详细错误信息会使您的应用程序面临安全漏洞的风险。
-
服务器正在运行在
http://127.0.0.1:5000/
网址上。要停止服务器,请使用CTRL+C
,但现在还不要这样做。
现在,使用浏览器访问主页:
http://127.0.0.1:5000/
您将看到类似以下内容的消息:
OutputInternal Server Error
The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.
这是 500 内部服务器错误,这是一个服务器错误响应,表示服务器在应用程序代码中遇到了内部错误。
在终端中,您会看到以下输出:
Output[2021-09-12 15:16:56,441] ERROR in app: Exception on / [GET]
Traceback (most recent call last):
File "/home/abd/.local/lib/python3.9/site-packages/flask/app.py", line 2070, in wsgi_app
response = self.full_dispatch_request()
File "/home/abd/.local/lib/python3.9/site-packages/flask/app.py", line 1515, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/home/abd/.local/lib/python3.9/site-packages/flask/app.py", line 1513, in full_dispatch_request
rv = self.dispatch_request()
File "/home/abd/.local/lib/python3.9/site-packages/flask/app.py", line 1499, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
File "/home/abd/python/flask/series03/flask_app/app.py", line 8, in index
return render_template('index.html')
NameError: name 'render_template' is not defined
127.0.0.1 - - [12/Sep/2021 15:16:56] "GET / HTTP/1.1" 500 -
上面的回溯信息展示了触发内部服务器错误的代码路径。其中,NameError: name 'render_template' is not defined
指出了问题的根源:render_template()
函数未被导入。
如您所见,您需要转到终端来排查错误,这不太方便。
您可以通过在开发服务器中启用调试模式来获得更好的故障排查体验。为此,使用 CTRL+C
停止服务器,并将环境变量 FLASK_ENV
设置为 development
,以便您可以在开发模式下运行应用程序(这将启用调试器),使用以下命令(在 Windows 上,使用 set
代替 export
):
运行开发服务器:
您将在终端中看到类似如下的输出:
Output * Serving Flask app 'app' (lazy loading)
* Environment: development
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 120-484-907
在这里,您可以看到环境现在是 development
,调试模式已开启,并且调试器处于活动状态。Debugger PIN
是一个PIN码,您需要用它来解锁浏览器中的控制台(一个可以通过点击下方图片中圈出的终端图标访问的交互式Python shell)。
刷新浏览器中的索引页面,您将看到以下页面:
在这里,您会看到错误信息以更易于理解的方式显示。第一个标题告诉您导致问题的Python异常名称(本例中为 NameError
)。第二行给出了直接原因(render_template()
未定义,这意味着在此情况下未导入该函数)。接下来,您会看到执行过程中涉及的Flask内部代码的追溯路径。从底部向上阅读追溯路径,因为追溯路径的最后一行通常包含最有用的信息。
注意:
圈出的终端图标允许您在浏览器的不同帧中运行Python代码。这对于像在Python交互式shell中那样检查变量值非常有用。当您点击终端图标时,需要输入运行服务器时获得的Debugger PIN码。在本教程中,您不需要使用这个交互式shell。
要解决这个 NameError
问题,保持服务器运行,打开一个新的终端窗口,激活你的环境,并打开你的 app.py
文件:
修改文件如下所示:
保存并关闭文件。
这里你导入了之前缺失的 render_template()
函数。
在开发服务器运行的情况下,刷新浏览器中的索引页面。
这次你会看到一个错误页面,信息如下:
Outputjinja2.exceptions.TemplateNotFound
jinja2.exceptions.TemplateNotFound: index.html
这个错误消息表明 index.html
模板不存在。
为了修复这个问题,你需要创建一个 base.html
模板文件,其他模板将从该模板继承以避免代码重复,然后再创建一个扩展基础模板的 index.html
模板。
创建 templates
目录,这是 Flask 查找模板文件的目录。然后使用你喜欢的编辑器打开一个 base.html
文件:
向你的 base.html
文件添加以下代码:
保存并关闭文件。
这个基础模板包含了你将在其他模板中重用的所有 HTML 样板代码。title
块将被替换以设置每个页面的标题,content
块将被替换为每个页面的内容。导航栏有两个链接,一个是索引页面,你使用 url_for()
辅助函数链接到 index()
视图函数,另一个是关于页面,如果你选择在你的应用程序中包含的话。
接下来,打开一个名为 index.html
的模板文件,该文件将从基础模板继承。
在其中添加以下代码:
保存并关闭文件。
在上面的代码中,你扩展了基础模板并重写了 content
块。随后,你设置了页面标题,并使用 title
块在 H1
标题中显示它,同时在 H2
标题中显示问候语。
在开发服务器运行的情况下,刷新浏览器中的索引页面。
你会看到应用程序不再显示错误,索引页面按预期显示。
现在,你已经使用了调试模式并了解了如何处理错误信息。接下来,你将中止请求以响应你选择的错误信息,并了解如何响应自定义错误页面。
步骤 2 — 创建自定义错误页面
在这一步骤中,你将学习如何中止请求并响应 404 HTTP 错误信息,当用户请求服务器上不存在的数据时。你还将学习如何为常见的 HTTP 错误创建自定义错误页面,例如 404 Not Found
错误和 500 Internal Server Error
错误。
为了演示如何中止请求并返回自定义的404 HTTP错误页面,您将创建一个显示几条消息的页面。如果请求的消息不存在,您将返回一个404错误。
首先,打开您的app.py
文件,为消息页面添加一个新的路由:
在文件末尾添加以下路由:
保存并关闭文件。
在上面的路由中,您有一个URL变量idx
。这个索引将决定显示哪条消息。例如,如果URL是/messages/0
,将显示第一条消息(Message Zero
)。您使用int
转换器来仅接受正整数,因为URL变量默认情况下是字符串值。
在message()
视图函数内部,您有一个名为messages
的常规Python列表,包含三条消息。(在现实场景中,这些消息会来自数据库、API或其他外部数据源。)该函数返回对render_template()
函数的调用,带有两个参数,message.html
作为模板文件,以及一个将传递给模板的message
变量。这个变量将根据URL中idx
变量的值,从messages
列表中获取一个列表项。
接下来,打开一个新的message.html
模板文件:
向其中添加以下代码:
保存并关闭文件。
在上面的代码中,你扩展了基础模板并重写了content
块。你添加了一个标题(Messages
)在H1标题中,并在H2标题中显示message
变量的值。
在开发服务器运行的情况下,访问浏览器中的以下URL:
http://127.0.0.1:5000/messages/0
http://127.0.0.1:5000/messages/1
http://127.0.0.1:5000/messages/2
http://127.0.0.1:5000/messages/3
你会看到在前三个URL中,H2
分别包含文本Message Zero
、Message One
或Message Two
。然而,在第四个URL上,服务器将响应一个IndexError: list index out of range
错误消息。在生产环境中,响应本应是一个500 Internal Server Error
,但这里的正确响应应是一个404 Not Found
,以表明服务器找不到索引为3
的消息。
你可以使用Flask的abort()
辅助函数来响应404
错误。为此,打开app.py
文件:
编辑第一行以导入abort()
函数。然后通过添加一个try ... except
子句来编辑message()
视图函数,如下所示:
保存并关闭文件。
在上述代码中,你导入了abort()
函数,用于中止请求并返回错误响应。在message()
视图函数中,你使用了一个try ... except
语句块来包裹函数。首先,你尝试返回与URL中的索引相对应的消息的messages
模板。如果该索引没有对应的消息,则会引发IndexError
异常。接着,你使用except
子句捕获该错误,并通过abort(404)
中止请求,返回一个404 Not Found
的HTTP错误。
启动开发服务器后,使用浏览器重新访问之前返回IndexError
的URL(或访问任何索引大于2的URL):
http://127.0.0.1:5000/messages/3
你将看到以下响应:
Not Found
The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
现在,你有了一个更好的错误消息,表明服务器找不到所请求的消息。
接下来,你将为404错误页面和500错误页面创建模板。
首先,你将使用特殊的@app.errorhandler()
装饰器注册一个函数作为404
错误的处理程序。打开app.py
文件进行编辑:
nano app.py
按如下方式添加高亮部分以编辑文件:
保存并关闭文件。
在这里,您使用 `@app.errorhandler()
` 装饰器将函数 `page_not_found()
` 注册为自定义错误处理程序。该函数以错误作为参数,并返回对 `render_template()
` 函数的调用,使用名为 `404.html
` 的模板。稍后您将创建此模板,如果愿意,可以使用其他名称。在 `render_template()
` 调用之后,您还返回整数 `404
`。这告诉 Flask 响应中的状态码应为 `404
`。如果您不添加它,默认的状态码响应将是 `200
`,这意味着请求已成功。
接下来,打开一个新的 `404.html
` 模板:
向其中添加以下代码:
保存并关闭文件。
就像任何其他模板一样,您扩展了基础模板,替换了 `content
` 和 `title
` 块的内容,并添加了您自己的 HTML 代码。这里有一个 `<h1>
` 标题作为标题,一个 `<p>
` 标签带有自定义错误消息,告诉用户页面未找到,以及对可能手动输入 URL 的用户的有用提示。
您可以在错误页面中使用任何 HTML、CSS 和 JavaScript,就像在其他模板中一样。
在开发服务器运行的情况下,使用浏览器重新访问以下 URL:
http://127.0.0.1:5000/messages/3
您会看到页面现在具有基础模板中的导航栏和自定义错误消息。
同样,你可以为500 Internal Server Error
错误添加一个自定义错误页面。打开app.py
文件:
在404
错误处理程序下方添加以下错误处理程序:
这里你使用了与404
错误处理程序相同的模式。你使用app.errorhandler()
装饰器并传入500
参数,将名为internal_error()
的函数转化为错误处理程序。你渲染一个名为500.html
的模板,并以500
状态码响应。
接下来,为了演示自定义错误页面的呈现方式,在文件末尾添加一个返回500
HTTP错误的路由。无论调试器是否运行,该路由始终会返回500 Internal Server Error
:
这里你创建了一个/500
路由,并使用abort()
函数返回500
HTTP错误。
保存并关闭文件。
然后,打开新的500.html
模板:
向其中添加以下代码:
保存并关闭文件。
在这里,你做了与404.html
模板相同的事情。你扩展了基础模板,并用标题和两条自定义消息替换了内容块,告知用户发生了内部服务器错误。
在开发服务器运行的情况下,访问返回 500
错误的路线:
http://127.0.0.1:5000/500
您的自定义页面将出现,而不是通用的错误页面。
现在您知道如何在 Flask 应用程序中为 HTTP 错误使用自定义错误页面了。接下来,您将学习如何使用日志记录来跟踪应用程序中的事件。跟踪事件有助于您了解代码的行为,进而有助于开发和故障排除。
步骤 3 — 使用日志记录跟踪应用程序中的事件
在这一步中,您将使用日志记录来跟踪服务器运行和应用程序使用时发生的事件,这有助于您查看应用程序代码中的情况,从而更容易排查错误。
您已经见过每当开发服务器运行时的日志,通常看起来像这样:
127.0.0.1 - - [21/Sep/2021 14:36:45] "GET /messages/1 HTTP/1.1" 200 -
127.0.0.1 - - [21/Sep/2021 14:36:52] "GET /messages/2 HTTP/1.1" 200 -
127.0.0.1 - - [21/Sep/2021 14:36:54] "GET /messages/3 HTTP/1.1" 404 -
在这些日志中,您可以看到以下信息:
127.0.0.1
:服务器运行的主机。[21/Sep/2021 14:36:45]
:请求的日期和时间。GET
:HTTP 请求方法。在这种情况下,GET
用于检索数据。/messages/2
: 用户请求的路径。HTTP/1.1
: HTTP版本。200
或404
: 响应的状态码。
这些日志帮助你诊断应用程序中出现的问题。你可以使用Flask提供的app.logger
记录器,在需要了解某些请求的更多细节时记录更多信息。
通过日志记录,你可以使用不同的函数在不同的日志级别上报信息。每个级别表示发生了某个严重程度的事件。可以使用以下函数:
app.logger.debug()
: 用于记录事件的详细信息。app.logger.info()
: 确认事情按预期工作。app.logger.warning()
: 指示发生了意外情况(如“磁盘空间不足”),但应用程序仍在正常工作。app.logger.error()
: 应用程序的某个部分发生了错误。app.logger.critical()
: 发生了严重错误;整个应用程序可能停止工作。
为了演示如何使用Flask记录器,打开你的app.py
文件进行编辑,以记录几个事件:
编辑message()
视图函数,使其如下所示:
保存并关闭文件。
在这里,你记录了几个不同级别的事件。你使用 app.logger.info()
来记录一个按预期工作的事件(这是一个 INFO
级别)。你使用 app.logger.debug()
来记录详细信息(DEBUG
级别),提到应用程序现在正在获取一个具有特定索引的消息。然后你使用 app.logger.error()
来记录一个 IndexError
异常已被引发的事实,以及导致问题的特定索引(ERROR
级别,因为发生了错误)。
访问以下 URL:
http://127.0.0.1:5000/messages/1
你将在运行服务器的终端中看到以下信息:
Output
[2021-09-21 15:17:02,625] INFO in app: Building the messages list...
[2021-09-21 15:17:02,626] DEBUG in app: Get message with index: 1
127.0.0.1 - - [21/Sep/2021 15:17:02] "GET /messages/1 HTTP/1.1" 200 -
在这里,你看到了 app.logger.info()
记录的 INFO
消息,以及使用 app.logger.debug()
记录的带有索引号的 DEBUG
消息。
现在访问一个不存在的消息的 URL:
http://127.0.0.1:5000/messages/3
你将在终端中看到以下信息:
Output[2021-09-21 15:33:43,899] INFO in app: Building the messages list...
[2021-09-21 15:33:43,899] DEBUG in app: Get message with index: 3
[2021-09-21 15:33:43,900] ERROR in app: Index 3 is causing an IndexError
127.0.0.1 - - [21/Sep/2021 15:33:43] "GET /messages/3 HTTP/1.1" 404 -
如你所见,你有之前看到的 INFO
和 DEBUG
日志,以及一个新的 ERROR
日志,因为索引为 3
的消息不存在。
记录事件、详细信息和错误有助于你识别哪里出了问题,并使故障排除更加容易。
你已在这一步中学会了如何使用 Flask 的日志记录器。若想更深入了解日志记录,请参阅如何在 Python 3 中使用日志记录。如需详尽探讨,可查阅Flask 日志记录文档及Python 日志记录文档。
总结
现在,你已掌握如何在 Flask 中启用调试模式,以及如何排查和修复开发 Flask 网页应用时可能遇到的一些常见错误。此外,你还为常见的 HTTP 错误创建了自定义错误页面,并利用 Flask 的日志记录器追踪应用中的事件,以帮助你检查并理解应用的行为。
若想进一步了解 Flask,请访问Flask 主题页面。
Source:
https://www.digitalocean.com/community/tutorials/how-to-handle-errors-in-a-flask-application