前言
2023-8-11
以前對(duì)網(wǎng)站開(kāi)發(fā)萌生了想法,又有些急于求成,在B站照著視頻敲了一個(gè)基于flask的博客系統(tǒng)。但對(duì)于程序的代碼難免有些囫圇吞棗,存在許多模糊或不太理解的地方,只會(huì)照葫蘆畫(huà)瓢。
而當(dāng)自己想開(kāi)發(fā)一個(gè)什么網(wǎng)站的時(shí),就如同摸著石頭過(guò)河,常在許多小問(wèn)題上卡住,不知怎么實(shí)現(xiàn),也不知道需要去學(xué)習(xí)什么才能實(shí)現(xiàn)。例如,要做一個(gè)聊天室程序,我卻不知道在一方發(fā)出消息時(shí),如何在另一方實(shí)時(shí)地顯示出來(lái),思慮許久而終歸于放棄。
學(xué)習(xí)系統(tǒng)且詳細(xì)的知識(shí)有其好處,它可以沖退我那種徒手在黑暗中摸索的感覺(jué)。讀了“狼書(shū)”的兩個(gè)章節(jié),自覺(jué)很有收獲,此前的許多疑惑也得到了解答。然凡事各有弊益,啃書(shū)不是易事,還需下苦功夫。
曾有人對(duì)我說(shuō),看視頻會(huì)更有效率。也許他是對(duì)的?但我好像更喜歡書(shū)籍給我的感覺(jué)。但有些諷刺的是,大學(xué)至今,我卻也沒(méi)看下來(lái)多少書(shū),而時(shí)間在我休息的時(shí)候,它仍努力奔跑,兩載一晃而過(guò)。而不少人,也是這相似的困境吧?
回到本文,它是一篇讀書(shū)筆記,參雜少許個(gè)人想法但含量不高,所記零散,主要是作為個(gè)人提綱備忘,或許對(duì)諸位而言閱讀價(jià)值不高。若想學(xué)習(xí) Flask 框架,我還是很推薦去讀“狼書(shū)”原著的。
開(kāi)始
5個(gè)難度遞增的案例:留言板SayHello,個(gè)人博客Bluelog,圖片社交網(wǎng)站Albumy,待辦事項(xiàng)程序Todoism,聊天室CatChat。
前端學(xué)習(xí):
Web很多程序離不開(kāi)javascript,它可以方便、簡(jiǎn)潔地實(shí)現(xiàn)很多頁(yè)面按邏輯和功能。
了解Git:https://try.github.io/
文本編輯器:
作者的博客:http://greyli.com
Git使用
克隆本書(shū)代碼倉(cāng)庫(kù):
git clone https://github.com/greyli/helloflask.git
查看當(dāng)前項(xiàng)目倉(cāng)庫(kù)中包含的所有標(biāo)簽:
git tag -n
簽出對(duì)應(yīng)標(biāo)簽版本的代碼:
git checkout foo
簽出前對(duì)文件做了修改,需要撤銷:
git reset --hard
使用diff命令比較兩個(gè)標(biāo)簽對(duì)應(yīng)版本之間的變化:
git diff foo bar
使用git客戶端直觀查看版本變化:
gitk
定期使用git fetch命令來(lái)更新本地倉(cāng)庫(kù):
git fetch --all
git fetch --tags
git reset --hard origin/master
在本地復(fù)制新的派生倉(cāng)庫(kù),后可以在本地自由修改其中的代碼:
git clone https://github.com/你的用戶名/helloflask.git
1 初識(shí)Flask
Web框架可以讓我們不用關(guān)心底層的請(qǐng)求響應(yīng)處理,更方便地編寫(xiě)Web程序。
兩個(gè)主要依賴:(p3)
- WSGI(Web Server Gateway Interface,Web服務(wù)器網(wǎng)關(guān)接口)的工具集——Werkzeug(http://werkzeug.pocoo.org/)
- Jinja2模板引擎
1.1 搭建開(kāi)發(fā)環(huán)境
Pipenv:pip的加強(qiáng)版,讓包安裝、包依賴管理、虛擬環(huán)境管理更加方便。
創(chuàng)建虛擬環(huán)境:在項(xiàng)目根目錄(即helloflask文件夾中),使用pipenv install
命令。Pipfile
文件列出的依賴包也會(huì)一并被安裝。
顯示激活虛擬環(huán)境:Pipenv會(huì)自動(dòng)從項(xiàng)目目錄下的.env
文件中加載環(huán)境變量。
# 激活
pipenv shell
# 退出
exit
臨時(shí)使用虛擬環(huán)境中的python解釋器:(更推薦)
pipenv run python hello.py
查看當(dāng)前環(huán)境下的依賴情況:
pipenv graph
關(guān)于Pipfile項(xiàng)目的更多情況,請(qǐng)?jiān)L問(wèn)其主頁(yè)。
使用
pipenv install
命令安裝包時(shí),都是安裝到虛擬環(huán)境中。相當(dāng)于使用pip在激活虛擬環(huán)境的情況下安裝包。
更新flask版本:(書(shū)中為flask-1.0.2)
pipenv update flask
集成開(kāi)發(fā)環(huán)境:pycharm專業(yè)版提供了更多針對(duì)Flask開(kāi)發(fā)的功能,比如創(chuàng)建Flask項(xiàng)目模板,Jinja2語(yǔ)法高亮,與Flask命令行功能集成等。
設(shè)置Python解釋器(好像已經(jīng)不需要了):因?yàn)镻yCharm未集成支持Pipenv。(p10)
1.2 簡(jiǎn)單示例
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return '<h1>Hello Flask!</h1>'
Flask類表示一個(gè)Flask程序,實(shí)例化這個(gè)類就得到了我們的程序?qū)嵗齛pp。__name__
對(duì)于文件app.py而言,值即為“app”,它幫助Flask在相應(yīng)的文件夾里找到需要的資源,比如模板和靜態(tài)文件。(p12)
注冊(cè)路由:路由負(fù)責(zé)管理URL和函數(shù)之間的映射。
- 一個(gè)視圖函數(shù)可以綁定多個(gè)路由。
- 動(dòng)態(tài)路由:可以傳遞參數(shù),并可設(shè)置默認(rèn)參數(shù)。
注:Flask內(nèi)置一個(gè)開(kāi)發(fā)服務(wù)器,但在實(shí)際生產(chǎn)環(huán)境中需要使用性能更好的生產(chǎn)服務(wù)器。
啟動(dòng)開(kāi)發(fā)服務(wù)器:
# 未啟動(dòng)虛擬環(huán)境下使用
pipenv run flask run
# 在虛擬環(huán)境中
flask run
# 命令未找到
python -m flask run
app.run()
方法也可啟動(dòng)開(kāi)發(fā)服務(wù)器,已經(jīng)不推薦使用。
flask尋找程序?qū)嵗?/strong>:
- 在當(dāng)前目錄下,名為
app.py
或wsgi.py
的文件中尋找名為app
的程序?qū)嵗?/li> - 根據(jù)環(huán)境變量FLASK_APP尋找。
# 在linux
$ export FLASK_APP=hello
# 在windows
> set FLASK_APP=hello
管理環(huán)境變量:使用python-dotenv
包,從.env
或.flaskenv
文件加載。其中.env
文件存放一些敏感數(shù)據(jù)。
pipenv install python-dotenv
可在.flaskenv
寫(xiě)入:
# 默認(rèn)為production(生產(chǎn)環(huán)境),開(kāi)發(fā)模式將打開(kāi)調(diào)試器和重載器。
FLASK_ENV=development
使用Pycharm的運(yùn)行配置(而不使用命令行):在Run --> Edit Configurations(p19)
使服務(wù)器外部可見(jiàn):讓局域網(wǎng)用戶可以通過(guò)你的內(nèi)網(wǎng)IP進(jìn)行訪問(wèn)。想要公網(wǎng)訪問(wèn),可以考慮內(nèi)網(wǎng)穿透工具、端口轉(zhuǎn)發(fā)工具等,如 ngrok、Localtunnel。
flask run --host=0.0.0.0
flask的環(huán)境變量:可通過(guò)FLASK_<COMMAND>_<OPTION>
設(shè)置各種選項(xiàng)。
重載器:安裝 Watchdog。Werkzeug內(nèi)置有stat重載器,但耗電嚴(yán)重且準(zhǔn)確性一般。
# dev: 開(kāi)發(fā)依賴的包
pipenv install watchdog --dev
打開(kāi)PythonShell:使用flask打開(kāi)的shell自動(dòng)包含程序上下文,并且已經(jīng)導(dǎo)入了app實(shí)例。
flask shell
Flask擴(kuò)展:使用Flask提供的接口編寫(xiě)的Python庫(kù)。擴(kuò)展可以加速開(kāi)發(fā),但也會(huì)降低靈活性,并可能存在bug。
Flask項(xiàng)目配置:可能用到Flask提供的配置、擴(kuò)展提供的、程序特定的配置。它們用Flask對(duì)象的app.config屬性作為統(tǒng)一的接口。
- Flask配置章節(jié):https://flask.pocoo.org/docs/latest/config/
app.config['ADMIN_NAME'] = 'Peter'
# 一次加載多個(gè)值
app.config.update()方法
URL:使用url_for()
,方便url規(guī)則的修改。
- 相對(duì)url與絕對(duì)url(p24)
自定義Flask命令:
- Click官方文檔(自定義命令):http://click.pocoo.org/6/
@app.cli.command()
def hello():
click.echo('Hello, Human!')
> flask hello
Hello, Human!
視圖函數(shù)之名:可以溯源至MVC架構(gòu),即”模型 - 視圖 - 控制器”。但flask并不是MVC架構(gòu)的框架,因?yàn)闆](méi)有內(nèi)置數(shù)據(jù)模型的功能(需使用擴(kuò)展),視圖函數(shù)成為控制器函數(shù)才更加合適。(p28)
2 Flask與HTTP
request對(duì)象常用的屬性和方法:(p43)
Response類常用屬性和方法:(p48)
查看路由列表:這個(gè)列表由app.url_map
解析得到。其中static為Flask添加的特殊路由,用來(lái)訪問(wèn)靜態(tài)文件。
> flask routes
Flask內(nèi)置的URL變量轉(zhuǎn)換器:(p37)
URL規(guī)則中的轉(zhuǎn)換器:<轉(zhuǎn)換器:變量名>
,
@app.route('goback/<int:year>')
def go_back(year):
return '<p>Welcome to %d!</p>' % (2018 - year)
請(qǐng)求鉤子:也稱回調(diào)函數(shù),可以用來(lái)注冊(cè)在請(qǐng)求處理的不同階段執(zhí)行的處理函數(shù),如預(yù)處理、后處理,它們使用裝飾器 實(shí)現(xiàn)。(p58)
響應(yīng):大多數(shù)情況下,我們只負(fù)責(zé)返回響應(yīng)的主體內(nèi)容(而不負(fù)責(zé)首部及各種字段)。Flask會(huì)調(diào)用make_response()
方法將視圖函數(shù)返回值轉(zhuǎn)換為響應(yīng)對(duì)象。當(dāng)然,響應(yīng)也可以包含響應(yīng)主體、狀態(tài)碼、首部字段 三個(gè)部分內(nèi)容。
可使用redirect(<url字符串>)
方法重定向。
@app.route('/')
def hello_flask():
return '', 302, {'Location':'https://www.baidu.com'}
注:狀態(tài)碼不可兒戲,如將上面的
302
改為202
,則重定向會(huì)失效。
錯(cuò)誤響應(yīng):在視圖函數(shù)中使用abort(<狀態(tài)碼>)
,例如:
@app.route('/404')
def not_found():
abort(404)
響應(yīng)格式:在 HTTP 響應(yīng)中,數(shù)據(jù)可以通過(guò)多種格式傳輸,默認(rèn)為 HTML??梢栽O(shè)置不同的 MIME 類型來(lái)標(biāo)識(shí)不同的數(shù)據(jù)格式,MIME 類型在 Content-Type 字段中定義。
# method 1 - 修改響應(yīng)對(duì)象的屬性
# @plain 純文本
from flask import make_response
...
response = make_response("hello")
response.mimetype = 'text/plain'
# method 2 - 設(shè)置首部字段
response.headers['Content-Type'] = 'text/html; charset=utf-8'
-
XML:
application/xml
,一般作為 AJAX 請(qǐng)求的響應(yīng)格式,或是 Web API 的響應(yīng)格式。 -
JSON:
application/json
,指 JavaScript Object Notation(JavaScript對(duì)象表示法),更輕量、易解析。json模塊的dumps()方法,可以將python中的字典、列表、元組數(shù)據(jù)序列化為json字符串。
# 1 - python標(biāo)準(zhǔn)庫(kù)的json模塊
response = make_response(json.dumps(data))
response.mimetype = 'application/json'
return response
# 2 - 使用flask包裝的jsonify()函數(shù)
return jsonify(data)
Cookie:HTTP 是無(wú)狀態(tài)協(xié)議。Cookie是保存在瀏覽器上的小型文本數(shù)據(jù),保存一定時(shí)間,在下一次向同一個(gè)服務(wù)器發(fā)送請(qǐng)求時(shí)附帶這些數(shù)據(jù)。但明文存儲(chǔ)存在安全隱患。
使用set_cookie()
方法設(shè)置(參數(shù)見(jiàn)p68),從cookies
屬性獲取。
Session:在Flask中,session對(duì)象用來(lái)存儲(chǔ)加密的cookie。
-
設(shè)置程序密鑰:通過(guò)
Flask.secret_key
屬性;或環(huán)境變量SECRET_KEY
(可保存在.env
文件),在腳本中通過(guò)getenv()
方法獲取。
import os
app.secret_key = os.getenv('SECRET_KEY', 'secret string')
疑問(wèn):寫(xiě)進(jìn)了環(huán)境變量還需再腳本中手動(dòng)獲???那我隨便用個(gè)環(huán)境變量名稱是不是也可以?
疑問(wèn):看不懂:使用session對(duì)象存儲(chǔ)的Cookie,用戶可以看到其加密后的值,但無(wú)法修改它。因?yàn)閟ession中的內(nèi)容使用密鑰進(jìn)行簽名,一旦數(shù)據(jù)被修改,簽名的值也會(huì)變化。這樣再讀取時(shí),就會(huì)驗(yàn)證失敗,對(duì)應(yīng)的session值也會(huì)失效。 (p51)
- session cookie的保存時(shí)間:
上下文:Flask中有兩種上下文:程序上下文 和請(qǐng)求上下文 。
兩種上下文在視圖函數(shù)中都會(huì)自動(dòng)激活,這也意味折一些依賴于上下文的函數(shù)只能在視圖函數(shù)中使用,如url_for()
、jsonify()
等。
也可手動(dòng)激活程序上下文:
>>> from app import app
>>> from flask import current_app
# 方法1
>>> with app.app_context():
... current_app.name
# 方法2
>>> app_ctx = app.app_context()
>>> app_ctx.push()
>>> current_app.name
>>> app_ctx.pop()
# 激活請(qǐng)求上下文類似
>>> from app import app
>>> from flask import request
>>> with app.test_request_context('/hello'):
...
疑惑:g、request等對(duì)象如何區(qū)分不同的客戶端?
上下文鉤子:使用它注冊(cè)的回調(diào)函數(shù)會(huì)在程序上下文被銷毀時(shí)調(diào)用。
@app.teardown_appcontext
def teardown_db(exception):
...
db.close()
2.1 重定向回上一個(gè)頁(yè)面
利用referrer或URL的查詢參數(shù)。(p59)
referrer:即訪問(wèn)來(lái)源。當(dāng)用戶在某個(gè)站點(diǎn)單擊鏈接,瀏覽器向新鏈接所在的服務(wù)器發(fā)起請(qǐng)求,請(qǐng)求的數(shù)據(jù)中包含的HTTP_REFERER字段記錄了用戶所在的原站點(diǎn)URL。
疑惑:書(shū)中判斷url是否安全的代碼(如下)使我困惑了許久:既然
test_url
中也與request.host_url
做了拼接,那最后的netloc
不是必然相同嗎?后來(lái)我查找了
urljoin(base, url)
函數(shù)的處理機(jī)制:
- 如果
url
是一個(gè)相對(duì)URL,那么urljoin
會(huì)從url
中獲取路徑部分,并于base
中獲取的部分合并;- 如果
url
是一個(gè)絕對(duì)URL,則urljoin
會(huì)直接返回url
。那么在什么情況下,
is_safe_url
函數(shù)的返回值才為False
呢?
- 首先,
target
是一個(gè)絕對(duì)URL。- 同時(shí),該絕對(duì)URL的協(xié)議或主機(jī)不是本機(jī)。
綜上,還是感覺(jué)該函數(shù)的邏輯寫(xiě)得有些隱晦了,不便于理解(肯定不能是我太笨)。
def is_safe_url(target):
ref_url = urlparse(request.host_url)
test_url = urlparse(urljoin(request.host_url, target))
return test_url.scheme in ('http', 'https') and \
ref_url.netloc == test_url.netloc
2.2 使用AJAX技術(shù)發(fā)送異步請(qǐng)求
jQuery中和AJAX相關(guān)的方法和具體用法:http://api.jquery.com/category/ajax/
前言
在傳統(tǒng)的Web應(yīng)用中,程序的操作都是基于請(qǐng)求響應(yīng)循環(huán)來(lái)實(shí)現(xiàn)的。每當(dāng)頁(yè)面狀態(tài)需要變動(dòng),或是需要更新數(shù)據(jù)時(shí),都伴隨折一個(gè)發(fā)向服務(wù)器的請(qǐng)求。當(dāng)服務(wù)器響應(yīng)時(shí),整個(gè)頁(yè)面會(huì)重載,并渲染新頁(yè)面。
頻繁更新頁(yè)面會(huì)犧牲性能,且影響用戶體驗(yàn)。
AJAX是指異步Javascript和XML(Asynchronous JavaScript And XML),是一系列技術(shù)的組合體,如XMLHttpRequest、JavaScript、DOM。它讓W(xué)eb程序更像是程序,而非一堆用鏈接和按鈕鏈接起來(lái)的網(wǎng)頁(yè)資源。
可以使用 jQuery 實(shí)現(xiàn)AJAX操作:函數(shù)ajax()
可以發(fā)送AJAX請(qǐng)求。
2.3 HTTP服務(wù)器推送
推送技術(shù)對(duì)比:https://stackoverflow.com/a/12855533/5511489
- 傳統(tǒng)輪詢
- 長(zhǎng)輪詢
- SSE(Server-Sent Events)
- Websocket
2.4 Web安全防范
OWASP(Open Web Application Security Project,開(kāi)放式Web程序安全項(xiàng)目):https://www.owasp.org 。(p66)
常見(jiàn)攻擊方式:
- 注入攻擊
- XSS攻擊(Cross-Site Scripting,跨站腳本):將代碼注入被攻擊者的網(wǎng)站
- CSRF攻擊:(Cross Site Request Forgery,跨站請(qǐng)求偽造):偽造用戶的登陸狀態(tài)。
提示:雖然在實(shí)際開(kāi)發(fā)中,通過(guò)在”刪除“按鈕中加入鏈接來(lái)刪除資源非常方便,但安全問(wèn)題應(yīng)該作為編寫(xiě)代碼時(shí)的第一考量,應(yīng)該將這些按鈕內(nèi)嵌在使用了POST方法的form元素中。攻擊者就無(wú)法通過(guò)GET請(qǐng)求來(lái)修改用戶的數(shù)據(jù)。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-652900.html
疑惑:未理解csrf攻擊的防御原理。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-652900.html
到了這里,關(guān)于Flask Web開(kāi)發(fā)實(shí)戰(zhàn)(狼書(shū))| 筆記第1、2章的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!