flask中響應(yīng)錯(cuò)誤的處理及errorhandler的應(yīng)用方式
flask響應(yīng)錯(cuò)誤處理及errorhandler應(yīng)用
@app.errorhandler(404) def page_not_found(error): return render_template('404.html'),404
則當(dāng)互出現(xiàn)請(qǐng)求錯(cuò)誤時(shí),并不一定需要設(shè)置重定向路由,僅定義一個(gè)errorhandler對(duì)應(yīng)的錯(cuò)誤頁(yè)面即可
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>404</h1> <h1>很抱歉!</h1> <p>您訪問(wèn)的頁(yè)面不存在</p> </body> </html>!
flask學(xué)習(xí)筆記:錯(cuò)誤處理
1. 做好準(zhǔn)備工作
- 進(jìn)入項(xiàng)目主目錄
- 激活虛擬環(huán)境
2. Flask中的錯(cuò)誤處理
登陸賬號(hào),點(diǎn)開編輯資料頁(yè)面,試著將用戶名改為一個(gè)已經(jīng)存在的用戶名,然后,你會(huì)看到屏幕顯示“Internal Server Error”。
現(xiàn)在,看看命令行終端,你能看到錯(cuò)誤堆棧跟蹤,堆棧跟蹤在錯(cuò)誤調(diào)試時(shí)非常有用,因?yàn)樗鼈冿@示該堆棧中的調(diào)用序列,一直到產(chǎn)生錯(cuò)誤的那行:
(venv) $ flask run
* Serving Flask app "microblog"
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
[2017-09-14 22:40:02,027] ERROR in app: Exception on /edit_profile [POST]
Traceback (most recent call last):
File "/home/miguel/microblog/venv/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1182, in _execute_context
context)
File "/home/miguel/microblog/venv/lib/python3.6/site-packages/sqlalchemy/engine/default.py", line 470, in do_execute
cursor.execute(statement, parameters)
sqlite3.IntegrityError: UNIQUE constraint failed: user.username
堆棧跟蹤指出bug出在哪,應(yīng)用允許用戶更改用戶名,但是并不會(huì)驗(yàn)證用戶選擇的新用戶名是否與系統(tǒng)中已有的用戶發(fā)生沖突。該錯(cuò)誤來(lái)自SQLAlchemy,它試圖將新用戶名寫入數(shù)據(jù)庫(kù),但由于用戶名字段設(shè)置了 unique=True,所以寫入被拒絕。
現(xiàn)在默認(rèn)的錯(cuò)誤頁(yè)面很丑,與整個(gè)應(yīng)用的布局不符。
3. 調(diào)試模式
在生產(chǎn)服務(wù)器中,上面那樣的處理錯(cuò)誤的方式很好,如果出現(xiàn)了錯(cuò)誤,用戶將看到一個(gè)籠統(tǒng)的錯(cuò)誤頁(yè)面,而詳細(xì)的錯(cuò)誤詳細(xì)輸出到服務(wù)器進(jìn)程或者日志文件中。
但當(dāng)你在開發(fā)應(yīng)用時(shí),你可以開啟調(diào)試模式,F(xiàn)lask會(huì)在瀏覽器中輸出非常好的調(diào)試器。先關(guān)閉應(yīng)用,設(shè)置以下環(huán)境變量即可激活調(diào)試模式:
(venv) $ export FLASK_DEBUG=1
Windows中使用 set 來(lái)設(shè)置。
設(shè)置完 FLASK_DEBUG 后,重啟應(yīng)用,終端輸出與之前看到的就不同了:
(venv) microblog2 $ flask run ?* Serving Flask app "microblog" ?* Forcing debug mode on ?* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) ?* Restarting with stat ?* Debugger is active! ?* Debugger PIN: 177-562-960
現(xiàn)在,再試試讓程序奔潰,看看瀏覽器中的交互式調(diào)試器。
調(diào)試器允許你展開每個(gè)堆棧幀并查看響應(yīng)的源碼,你也可以在任何幀上打開Python提示符并執(zhí)行任何有效的Python表達(dá)式,例如檢查變量的值。
調(diào)試器允許用戶遠(yuǎn)程執(zhí)行服務(wù)器中的代碼,因此對(duì)于想要滲入應(yīng)用的而已用戶來(lái)說(shuō)有利。作為附加的安全措施,在瀏覽器中允許的調(diào)試器剛開始是鎖定的,在第一次使用時(shí),會(huì)要求輸入PIN碼,你可以在flask run命令的輸出中看到。
另外,調(diào)試模式還有個(gè)很重要的特性,就是重新載入(reloader),這是個(gè)很有用的開發(fā)特性,如果你在開啟調(diào)試模式時(shí)運(yùn)行 flask run,修改了源文件并保存,應(yīng)用就會(huì)自動(dòng)重新載入。
4. 自定義錯(cuò)誤頁(yè)面
Flask提供了一種機(jī)制使應(yīng)用可以安裝自己的錯(cuò)誤頁(yè)面,而不是默認(rèn)的無(wú)聊頁(yè)面。比如,我們來(lái)定義404錯(cuò)誤頁(yè)面和500錯(cuò)誤頁(yè)面,這兩種錯(cuò)誤是最常見的。定義其他錯(cuò)誤頁(yè)面同理。
用 @errorhandler 裝飾器可以自定義錯(cuò)誤管理器。新建 app/errors.py 模塊。
app/routes.py
from flask import render_template from app import app, db ? @app.errorhandler(404) def not_found_error(error): ? ? return render_template('404.html'), 404 ? @app.errorhandler(500) def internal_error(error): ? ? db.session.rollback() ? ? return render_template('500.html'), 500
錯(cuò)誤函數(shù)的工作方式和視圖函數(shù)類似。這兩個(gè)錯(cuò)誤會(huì)返回各自模板的內(nèi)容。注意這兩個(gè)函數(shù)都返回了除模板外的第二個(gè)值,也就是錯(cuò)誤代碼。在之前創(chuàng)建的視圖函數(shù)中,我都沒有顯式的定義第二個(gè)值,因?yàn)槟J(rèn)值是200(代表成功響應(yīng))。而這兩個(gè)是錯(cuò)誤頁(yè)面,所以我希望在響應(yīng)時(shí)能反映這一點(diǎn)。
數(shù)據(jù)庫(kù)錯(cuò)誤會(huì)調(diào)用500錯(cuò)誤。本例中的用戶名重名會(huì)造成這個(gè)錯(cuò)誤。為了確保數(shù)據(jù)庫(kù)事務(wù)錯(cuò)誤不會(huì)干擾模板觸發(fā)的數(shù)據(jù)庫(kù)訪問(wèn),程序會(huì)執(zhí)行事務(wù)回滾。
下面是404錯(cuò)誤的模板:
app/templates/404.html
{% extends "base.html" %} ? {% block content %} ? ? <h1>File Not Found</h1> ? ? <p><a href="{{ url_for('index') }}" rel="external nofollow" rel="external nofollow" >Back</a></p> {% endblock %}
這是500錯(cuò)誤的:
app/templates/500.html
{% extends "base.html" %} ? {% block content %} ? ? <h1>An unexpected error has occurred</h1> ? ? <p>The administrator has been notified. Sorry for the inconvenience!</p> ? ? <p><a href="{{ url_for('index') }}" rel="external nofollow" rel="external nofollow" >Back</a></p> {% endblock %}
兩個(gè)模板都繼承自 base.html 模板。
在 app/errors.py 模塊導(dǎo)入到 __init__.py 文件中應(yīng)用實(shí)例的后面。
app/__init__.py
# ... ? from app import routes, models, errors
在終端設(shè)置 FLASK_DEBUG=0 ,然后試著再次觸發(fā)用戶名重名錯(cuò)誤,你就能看到更友好的錯(cuò)誤頁(yè)面了。
5. 通過(guò)郵件發(fā)送錯(cuò)誤
Flask提供的錯(cuò)誤處理機(jī)制有個(gè)問(wèn)題,就是沒有提示,只會(huì)在終端輸出堆棧跟蹤,開發(fā)時(shí)倒是沒事,但如果應(yīng)用部署到了生產(chǎn)服務(wù)器,沒人會(huì)盯著輸出看,所以要想個(gè)別的辦法。
第一種解決方案是觸發(fā)錯(cuò)誤后發(fā)郵件通知管理員,郵件正文包含堆棧跟蹤。
第一步,將郵件服務(wù)信息添加到配置文件:
config.py
class Config(object): ? ? # ... ? ? MAIL_SERVER = os.environ.get('MAIL_SERVER') ? ? MAIL_PORT = int(os.environ.get('MAIL_PORT') or 25) ? ? MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS') is not None ? ? MAIL_USERNAME = os.environ.get('MAIL_USERNAME') ? ? MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD') ? ? ADMINS = ['your-email@example.com']
郵件配置變量包含服務(wù)器和端口,用于啟用加密連接的布爾值,以及可選的用戶名和密碼。這五個(gè)配置變量都從環(huán)境變量中加載。如果環(huán)境中沒有設(shè)置郵件服務(wù)器,我就會(huì)禁用郵件錯(cuò)誤提示。郵件端口也可以在環(huán)境變量中設(shè)置,如果沒有設(shè)置,就用25端口。ADMIN 配置變量代表用于接收錯(cuò)誤報(bào)告的郵箱地址。
Flask用Python的 logging 包來(lái)寫入日志,這個(gè)包可用于用郵件發(fā)送日志。我要做的就是添加 SMTPHandler 實(shí)例到Flask記錄器對(duì)象,也就是 app.logger:
app/__init__.py
import logging from logging.handlers import SMTPHandler ? # ... ? if not app.debug: ? ? if app.config['MAIL_SERVER']: ? ? ? ? auth = None ? ? ? ? if app.config['MAIL_USERNAME'] or app.config['MAIL_PASSWORD']: ? ? ? ? ? ? auth = (app.config['MAIL_USERNAME'], app.config['MAIL_PASSWORD']) ? ? ? ? secure = None ? ? ? ? if app.config['MAIL_USE_TLS']: ? ? ? ? ? ? secure = () ? ? ? ? mail_handler = SMTPHandler( ? ? ? ? ? ? mailhost=(app.config['MAIL_SERVER'], app.config['MAIL_PORT']), ? ? ? ? ? ? fromaddr='no-reply@' + app.config['MAIL_SERVER'], ? ? ? ? ? ? toaddrs=app.config['ADMINS'], subject='Microblog Failure', ? ? ? ? ? ? credentials=auth, secure=secure) ? ? ? ? mail_handler.setLevel(logging.ERROR) ? ? ? ? app.logger.addHandler(mail_handler)
以上程序只在調(diào)試模式為開啟時(shí)才會(huì)開啟郵件記錄器功能,也就是當(dāng) app.debug 為 True 以及郵件服務(wù)可用時(shí)。
設(shè)置郵件記錄器有些繁瑣,因?yàn)楸仨氁幚碓S多郵件服務(wù)的安全可選項(xiàng)。但實(shí)際上,上面的代碼創(chuàng)建了 SMTPHandler 實(shí)例。設(shè)置其級(jí)別,使其只報(bào)告錯(cuò)誤信息。
測(cè)試此功能的方法有兩種。簡(jiǎn)單的這種是使用 Python 中的 SMTP 調(diào)試服務(wù),接收郵件而不是發(fā)送郵件,并將其打印大盤控制臺(tái)。再打開一個(gè)終端會(huì)話并運(yùn)行以下命令:
(venv) $ python -m smtpd -n -c DebuggingServer localhost:8025
設(shè)置 MAIL_SERVER=localhost 和 MAIL_PORT=8025。如果用的是 Linux 或者 Mac OS 系統(tǒng),命令前要加上 sudo 前綴。如果用的是 Windows 系統(tǒng),要以管理員權(quán)限打開終端。此命令需要管理員權(quán)限,1024端口以下是是只有管理員才能使用的端口?;蛘吣阋部梢愿臑楦叩亩丝谔?hào),比如5025,并將 MAIL_PORT 變量設(shè)置為環(huán)境中選擇的端口,這樣就不需要管理員權(quán)限了。
第二個(gè)終端的SMTP調(diào)試服務(wù)保持運(yùn)行,在第一個(gè)終端環(huán)境中設(shè)置 export MAIL_SERVER=localhost 和 MAIL_PORT=8025(Windows中用set)。由于在調(diào)試模式中應(yīng)用不會(huì)發(fā)送郵件,所以將FLASK_DEBUG 變量設(shè)置為 0 或者不用設(shè)置。運(yùn)行應(yīng)用,再觸發(fā)一次 SQLAlchemy 錯(cuò)誤,在終端就能看到含有堆棧錯(cuò)誤的郵件了。
第二種測(cè)試方式是配置真正的郵件服務(wù)。下面是如何使用 Gmail 的郵件服務(wù):
export MAIL_SERVER=smtp.googlemail.com export MAIL_PORT=587 export MAIL_USE_TLS=1 export MAIL_USERNAME=<your-gmail-username> export MAIL_PASSWORD=<your-gmail-password>
在 Windows 系統(tǒng)種用 set 而不是 export 。
6. 記錄到文件
app/__init__.py
# ... from logging.handlers import RotatingFileHandler import os ? # ... ? if not app.debug: ? ? # ... ? ? ? if not os.path.exists('logs'): ? ? ? ? os.mkdir('logs') ? ? file_handler = RotatingFileHandler('logs/microblog.log', maxBytes=10240, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?backupCount=10) ? ? file_handler.setFormatter(logging.Formatter( ? ? ? ? '%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]')) ? ? file_handler.setLevel(logging.INFO) ? ? app.logger.addHandler(file_handler) ? ? ? app.logger.setLevel(logging.INFO) ? ? app.logger.info('Microblog startup')
我將文件名為 microblog.log 的日志文件寫入 logs 文件夾。
RotatingFileHandler 類確保日志文件不會(huì)太大。在本例中我將日志文件的大小限制為 10KB,并且將最后10個(gè)日志文件保留為備份。
logging.Formatter 類為日志消息提供自定義格式。由于這些消息會(huì)被記錄到文件,所以格式中包含時(shí)間戳,日志級(jí)別,消息,日志項(xiàng)起始位置的源文件和行號(hào)。
我將應(yīng)用記錄器和文件記錄器處理器的日志等級(jí)都設(shè)置為 INFO。(日志級(jí)別按照嚴(yán)重程度分別是:DEBUG,INFO,WARNING,ERROR,CRITICAL)。
每次服務(wù)器啟動(dòng)時(shí)都會(huì)往日志寫入行,當(dāng)這個(gè)應(yīng)用在生產(chǎn)服務(wù)器中運(yùn)行時(shí),能看出服務(wù)器上面時(shí)候重啟。
7. 修復(fù)用戶名重名漏洞
RegistrationForm 已經(jīng)實(shí)現(xiàn)了用戶名驗(yàn)證,但編輯表單并沒有。
app/forms.py
class EditProfileForm(FlaskForm): ? ? username = StringField('Username', validators=[DataRequired()]) ? ? about_me = TextAreaField('About me', validators=[Length(min=0, max=140)]) ? ? submit = SubmitField('Submit') ? ? ? def __init__(self, original_username, *args, **kwargs): ? ? ? ? super(EditProfileForm, self).__init__(*args, **kwargs) ? ? ? ? self.original_username = original_username ? ? ? def validate_username(self, username): ? ? ? ? if username.data != self.original_username: ? ? ? ? ? ? user = User.query.filter_by(username=self.username.data).first() ? ? ? ? ? ? if user is not None: ? ? ? ? ? ? ? ? raise ValidationError('Please use a different username.')
通過(guò)自定義驗(yàn)證方法可以實(shí)現(xiàn)這個(gè)功能,但這里還有個(gè)重載構(gòu)造函數(shù),它接受原始用戶名作為參數(shù)。此用戶名被保存為實(shí)例變量,然后用validate_username()方法檢查。如果表單中的用戶名與原始用戶名相同,就不需要檢查數(shù)據(jù)庫(kù)中環(huán)有沒有重復(fù)項(xiàng)了。
將原始用戶名作為參數(shù)加入到視圖函數(shù)。
app/routes.py
@app.route('/edit_profile', methods=['GET', 'POST']) @login_required def edit_profile(): ? ? form = EditProfileForm(current_user.username) ? ? # ...
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
PyQt5實(shí)現(xiàn)從主窗口打開子窗口的方法
今天小編就為大家分享一篇PyQt5實(shí)現(xiàn)從主窗口打開子窗口的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-06-06Python3開發(fā)實(shí)例之非關(guān)系型圖數(shù)據(jù)庫(kù)Neo4j安裝方法及Python3連接操作Neo4j方法實(shí)例
這篇文章主要介紹了Python3開發(fā)實(shí)例之非關(guān)系型圖數(shù)據(jù)庫(kù)Neo4j安裝方法及Python3連接操作Neo4j方法實(shí)例,需要的朋友可以參考下2020-03-03Django?CSRF驗(yàn)證失敗請(qǐng)求被中斷的問(wèn)題
這篇文章主要介紹了Django?CSRF驗(yàn)證失敗請(qǐng)求被中斷的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09解決win64 Python下安裝PIL出錯(cuò)問(wèn)題(圖解)
這篇文章主要介紹了解決win64 Python下安裝PIL出錯(cuò)問(wèn)題,文中的解決方法也很簡(jiǎn)單,需要的朋友參考下吧2018-09-09python數(shù)據(jù)結(jié)構(gòu)之二叉樹的統(tǒng)計(jì)與轉(zhuǎn)換實(shí)例
這篇文章主要介紹了python數(shù)據(jù)結(jié)構(gòu)之二叉樹的統(tǒng)計(jì)與轉(zhuǎn)換實(shí)例,例如統(tǒng)計(jì)二叉樹的葉子、分支節(jié)點(diǎn),以及二叉樹的左右兩樹互換等,需要的朋友可以參考下2014-04-04Python?Pygame實(shí)現(xiàn)可控制的煙花游戲
大家好,本篇文章主要講的是Python?Pygame實(shí)現(xiàn)可控制的煙花游戲,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下2022-01-01基于Python實(shí)現(xiàn)主機(jī)遠(yuǎn)程控制
這篇文章主要介紹了基于Python實(shí)現(xiàn)主機(jī)遠(yuǎn)程控制,本文為?HITwh?網(wǎng)絡(luò)空間安全專業(yè)網(wǎng)絡(luò)空間安全設(shè)計(jì)與實(shí)踐選題,主要實(shí)現(xiàn)了遠(yuǎn)程監(jiān)控局域網(wǎng)內(nèi)的主機(jī)桌面與網(wǎng)絡(luò)情況、簡(jiǎn)單鍵鼠控制、遠(yuǎn)程斷網(wǎng)(ARP?攻擊)、數(shù)據(jù)加密傳輸?shù)裙δ埽旅鎭?lái)看看具體實(shí)現(xiàn)過(guò)程吧2022-01-01Python graphlib庫(kù)輕松創(chuàng)建操作分析圖形對(duì)象
Python中的graphlib庫(kù)是一個(gè)功能強(qiáng)大且易于使用的工具,graphlib提供了許多功能,可以幫助您創(chuàng)建、操作和分析圖形對(duì)象,本文將介紹graphlib庫(kù)的主要用法,并提供一些示例代碼和輸出來(lái)幫助您入門2024-01-01