文章目錄
君子之交淡如水,小人之交甘若醴。
君子之間的交情,並不因利益驅使而怎樣,小人之間的交往,卻多因利益驅使,利益過後,人與人如過眼雲煙。君子之間,不因無利益不互相關心,小人之間,卻多因利益關係而互相勾結。
一、Cookie的使用
1.Cookie的基本概念
在網站中,http請求是無狀態的,也就是說即使第一次和服務器連接後並且登錄成功後,第二次請求服務器依然不能知道當前請求是哪個用戶。
cookie的出現就是爲了解決這個問題,類型爲小型文本文件,是某些網站爲了辨別用戶身份,進行Session跟蹤而儲存在用戶本地終端上的數據(通常經過加密),由用戶客戶端計算機暫時或永久保存的信息。
第一次登錄後服務器返回一些數據(cookie)給瀏覽器,然後瀏覽器保存在本地,當該用戶發送第二次請求的時候,就會自動將上次請求存儲的cookie數據自動傳送給服務器,服務器通過瀏覽器攜帶的數據來判斷用戶。
cookie存儲的數據量有限,不同的瀏覽器有不同的存儲大小,但一般不超過4KB,因此使用cookie只能存儲一些小量的數據。
2.Flask中使用Cookie
在Flask中操作cookie,是通過Response對象來操作,可以在response返回之前,通過response.set_cookie()
方法來設置,這個方法有以下幾個參數:
- key
cookie的鍵 - value
cookie的鍵對應的值。 - max_age
cookie的過期時間,如果不設置,則瀏覽器關閉後就會自動過期。 - expires
過期時間,時間戳的形式(1970到現在的時間)。 - domain
該cookie在哪個域名中有效,一般設置子域名,比如cms.example.com。 - path
該cookie在哪個路徑下有效,即當前主域名。
cookie簡單測試:
flask_app.py如下:
from flask import Flask, Response
app = Flask(__name__)
app.config['TEMPLATE_AUTO_RELOAD'] = True
@app.route('/')
def index():
return '首頁'
@app.route('/cookietest/')
def cookietest():
res = Response('Hello World!!')
res.set_cookie('username', value='corley', max_age=3)
return res
if __name__ == '__main__':
app.run(debug=True)
顯示:
二、Flask-session的介紹和使用
1.Session的基本概念
session和cookie的作用有點類似,都是爲了存儲用戶相關的信息;
session可以類比字典,每個鍵對應着指定的值。
不同的是,cookie存儲在本地瀏覽器,session是一個思路、一個概念、一個服務器存儲授權信息的解決方案,不同的服務器、不同的框架、不同的語言有不同的實現。雖然實現不一樣,但是目的都是爲了在服務器更方便地存儲數據。
session是爲了解決cookie存儲數據不安全的問題。
在現實中,cookie和session是可以結合使用的,有兩種方式:
- 存儲在服務端
通過cookie存儲一個session_id,具體的數據則是保存在session中。如果用戶已經登錄,則服務器會在cookie中保存一個session_id,下次再次請求的時候,會攜帶該session_id,服務器根據session_id在session庫中獲取用戶的session數據,就能判斷該用戶到底是誰,以及之前保存的一些狀態信息,這稱爲server side session。
數據存儲在服務器會更加安全,不容易被竊取,包括Django在內的很多框架都是採用的這種形式。
但存儲在服務器也有一定的弊端,就是會佔用一定的服務器資源,但現在服務器存儲量一般較大,存儲session信息是不成問題的。 - 存儲在客戶端
將session數據加密,然後存儲在cookie中,這種稱爲client side session。
flask採用的就是這種方式,但是也可以替換成其他形式。
2.Flask中使用Session
Flask中中使用session需要先通過from flask import session
語句導入,再添加key和value。
Flask中的session機制是將session信息加密,然後存儲在cookie中,因此需要通過配置信息app.config['SECRET_KEY']
設置加密密鑰。
session的基本使用測試如下:
from flask import Flask, session
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24) # os.urandom()生成指定字節數的隨機字符串
@app.route('/')
def index():
session['username'] = 'Corley'
session['user_id'] = 1
return '這是首頁'
if __name__ == '__main__':
app.run(debug=True)
顯示:
顯然,上傳了session的一些信息;
其中,os.urandom()
用於生成指定字節數的隨機字符串,用於表示密鑰;
同時可以看到,到期時間是瀏覽器會話結束時。
可以指定會話到期時間,如下:
from flask import Flask, session
from datetime import timedelta
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
# 自定義過期時間
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=2)
@app.route('/')
def index():
session['username'] = 'Corley'
session['user_id'] = 1
# 設置持久化
session.permanent = True
return '這是首頁'
if __name__ == '__main__':
app.run(debug=True)
顯示:
顯然,此時session的過期時間爲2個小時。
如果想自定義過期時間,需要進行兩方面配置:
(1)設置持久化
session.permanent = True
設置了持久化後,默認的過期時間爲1個月,如果想要自定義過期時間,還需要進行第二步配置。
(2)自定義過期時間
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=2)
還可以獲取session值,測試如下:
from flask import Flask, session
from datetime import timedelta
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=2)
@app.route('/')
def index():
username = session.get('username')
return '這是首頁' + str(username)
@app.route('/login/')
def login():
session['username'] = 'Corley'
session['user_id'] = 1
session.permanent = True
return '登錄頁面'
if __name__ == '__main__':
app.run(debug=True)
顯示:
顯然,獲取到了session的信息。
可以刪除session,如下:
from flask import Flask, session
from datetime import timedelta
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=2)
@app.route('/')
def index():
username = session.get('username')
return '這是首頁' + str(username)
@app.route('/login/')
def login():
session['username'] = 'Corley'
session['user_id'] = 1
session.permanent = True
return '登錄頁面'
@app.route('/logout/')
def logout():
# 刪除session
session.clear()
return '退出登錄'+ str(session.get('username'))
if __name__ == '__main__':
app.run(debug=True)
顯示:
顯然,在訪問http://127.0.0.1:5000/logout/後,session已經清空;
除了session.clear()
,還可以指定key刪除session,如刪除username可以用session.pop('username')
。
三、Flask上下文
Flask項目中上下文包括兩類:
- 應用上下文
用於記錄和應用相關的數據,包括current_app和g。 - 請求上下文
用於記錄和請求相關的數據,包括request和session。
請求上下文request和應用上下文current_app都是全局變量,所有請求都是共享的。
Flask中使用特殊的機制來保證每次請求的數據都是隔離的,即用戶A請求所產生的數據不會影響到用戶B的請求,所以直接導入request對象也不會被一些髒數據影響,並且不需要像Django一樣在每個函數中使用request的時候傳入request參數。
4類上下文對象如下:
- request
請求上下文對象,一般用來保存一些請求的變量,如method、args、form等。 - session
請求上下文對象,一般用來保存一些會話信息。 - current_app
應用上下文對象,返回當前的app。 - g
應用上下文對象,處理請求時用於臨時存儲的對象。
current_app使用示意如下:
from flask import Flask, current_app
app = Flask(__name__)
@app.route('/')
def index():
return '這是首頁'
@app.route('/login/')
def login():
ca = current_app.name
cfg = current_app.config['DEBUG']
return '登錄頁面:' + ca + '-' + str(cfg)
if __name__ == '__main__':
app.run(debug=True)
顯示:
顯然,得到了當前所處的app,即初始化Flask對象時傳入的參數__name__
,也就是當前Python文件名;
除了獲取當前app名稱,還可以獲取綁定到app中的配置參數。
注意:current_app只能在視圖函數中使用,在視圖函數之外使用會報錯。
一般情況測試:
新建utils.py如下:
'''
工具文件
'''
def log_a(username):
print("Log A %s" % username)
def log_b(username):
print("Log B %s" % username)
主程序flask_context.py如下:
from flask import Flask, session
from utils import log_a, log_b
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
@app.route('/')
def index():
username = session.get('username')
log_a(username)
log_b(username)
return '這是首頁'
@app.route('/login/')
def login():
session['username'] = 'Corley'
session['user_id'] = 1
return '登錄頁面'
if __name__ == '__main__':
app.run(debug=True)
運行後,先訪問http://127.0.0.1:5000/,再訪問http://127.0.0.1:5000/login/,再訪問http://127.0.0.1:5000/,控制檯打印如下:
127.0.0.1 - - [13/May/2020 11:57:59] "GET / HTTP/1.1" 200 -
Log A None
Log B None
127.0.0.1 - - [13/May/2020 11:58:14] "GET /login/ HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2020 11:58:23] "GET / HTTP/1.1" 200 -
Log A Corley
Log B Corley
顯然,在訪問登錄頁面之後,控制檯打印出了username信息。
此時使用g對象進行改進:
主程序文件修改如下:
from flask import Flask, current_app, g, session
from utils import log_a, log_b
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
@app.route('/')
def index():
g.username = session.get('username')
log_a()
log_b()
return '這是首頁'
@app.route('/login/')
def login():
session['username'] = 'Corley'
session['user_id'] = 1
return '登錄頁面'
if __name__ == '__main__':
app.run(debug=True)
utils.py修改如下:
'''
工具文件
'''
from flask import g
def log_a():
print("Log A %s" % g.username)
def log_b():
print("Log B %s" % g.username)
運行之後,按之前的順序訪問,控制檯打印:
127.0.0.1 - - [13/May/2020 12:04:34] "GET / HTTP/1.1" 200 -
Log A None
Log B None
127.0.0.1 - - [13/May/2020 12:04:42] "GET /login/ HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2020 12:04:52] "GET / HTTP/1.1" 200 -
Log A Corley
Log B Corley
顯然,達到了同樣的效果,並且此時不需要再在log_a()
和log_b()
函數中傳遞參數;
每次刷新頁面後,g對象都會被重置,是臨時存儲的對象;
g對象一般用於解決頻繁傳參的問題。
可以在一個視圖函數中實現調用另一個函數,如下:
from flask import Flask, current_app, g, session
from utils import log_a, log_b
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
@app.route('/')
def index():
g.username = session.get('username')
log_a()
log_b()
hello()
return '這是首頁'
def hello():
print('Hello %s' % g.username)
@app.route('/login/')
def login():
session['username'] = 'Corley'
session['user_id'] = 1
return '登錄頁面'
if __name__ == '__main__':
app.run(debug=True)
按之前的順序訪問,控制檯打印如下:
127.0.0.1 - - [13/May/2020 12:13:41] "GET / HTTP/1.1" 200 -
Log A None
Log B None
Hello None
127.0.0.1 - - [13/May/2020 12:13:48] "GET /login/ HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2020 12:13:52] "GET / HTTP/1.1" 200 -
Log A Corley
Log B Corley
Hello Corley
四、常用的鉤子函數
鉤子函數是指在執行函數和目標函數之間掛載的函數,框架開發者給調用方提供一個point即掛載點,至於掛載什麼函數由調用方決定, 從而大大提高了靈活性。
1.before_first_request
第一次請求之前執行。
練習如下:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
print('這是首頁')
return '這是首頁'
@app.before_first_request
def handle_first_request():
print('在第一次請求之前執行')
if __name__ == '__main__':
app.run(debug=True)
運行後,訪問http://127.0.0.1:5000/,控制檯打印:
127.0.0.1 - - [13/May/2020 12:39:01] "GET / HTTP/1.1" 200 -
在第一次請求之前執行
這是首頁
2.before_request
在每次請求之前執行,通常可以用這個裝飾器來給視圖函數增加一些變量。
練習如下:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
print('這是首頁')
return '這是首頁'
@app.before_first_request
def handle_first_request():
print('在第一次請求之前執行')
@app.before_request
def handle_before():
print("在每一次請求之前執行")
if __name__ == '__main__':
app.run(debug=True)
運行後,多次訪問http://127.0.0.1:5000/,控制檯打印:
在第一次請求之前執行
在每一次請求之前執行
這是首頁
127.0.0.1 - - [13/May/2020 12:41:36] "GET / HTTP/1.1" 200 -
在每一次請求之前執行
這是首頁
127.0.0.1 - - [13/May/2020 12:41:38] "GET / HTTP/1.1" 200 -
在每一次請求之前執行
這是首頁
127.0.0.1 - - [13/May/2020 12:41:39] "GET / HTTP/1.1" 200 -
在每一次請求之前執行
這是首頁
127.0.0.1 - - [13/May/2020 12:41:40] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [13/May/2020 12:41:41] "GET / HTTP/1.1" 200 -
在每一次請求之前執行
這是首頁
3.after_request
在每次請求之後執行。
練習如下:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
print('這是首頁')
return '這是首頁'
@app.before_first_request
def handle_first_request():
print('在第一次請求之前執行')
@app.before_request
def handle_before():
print("在每一次請求之前執行")
@app.after_request
def handle_after(response):
print("在每一次請求之後執行")
return response
if __name__ == '__main__':
app.run(debug=True)
運行後,多次訪問http://127.0.0.1:5000/,控制檯打印:
127.0.0.1 - - [13/May/2020 12:51:05] "GET / HTTP/1.1" 200 -
在第一次請求之前執行
在每一次請求之前執行
這是首頁
在每一次請求之後執行
在每一次請求之前執行
這是首頁
在每一次請求之後執行
127.0.0.1 - - [13/May/2020 12:51:06] "GET / HTTP/1.1" 200 -
在每一次請求之前執行
這是首頁
在每一次請求之後執行
127.0.0.1 - - [13/May/2020 12:51:07] "GET / HTTP/1.1" 200 -
在每一次請求之前執行
這是首頁
在每一次請求之後執行
127.0.0.1 - - [13/May/2020 12:51:08] "GET / HTTP/1.1" 200 -
在每一次請求之前執行
這是首頁
在每一次請求之後執行
127.0.0.1 - - [13/May/2020 12:51:09] "GET / HTTP/1.1" 200 -
4.teardown_appcontext
不管是否有異常,註冊的函數都會在每次請求之後執行。
練習如下:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
1/0
print('這是首頁')
return '這是首頁'
@app.before_first_request
def handle_first_request():
print('在第一次請求之前執行')
@app.before_request
def handle_before():
print("在每一次請求之前執行")
@app.after_request
def handle_after(response):
print("在每一次請求之後執行")
return response
@app.teardown_appcontext
def handle_teardown(response):
print('teardown被執行')
return response
if __name__ == '__main__':
app.run()
運行後,訪問http://127.0.0.1:5000/,顯示:
控制檯打印:
在第一次請求之前執行
在每一次請求之前執行
在每一次請求之後執行
teardown被執行
[2020-05-13 12:58:48,206] ERROR in app: Exception on / [GET]
Traceback (most recent call last):
File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 2447, in wsgi_app
response = self.full_dispatch_request()
File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1952, in full_dispatch_request
rv = self.handle_user_exception(e)
File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1821, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "XXX\Test-gftU5mTd\lib\site-packages\flask\_compat.py", line 39, in reraise
raise value
File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1950, in full_dispatch_request
rv = self.dispatch_request()
File "XXX\Test-gftU5mTd\lib\site-packages\flask\app.py", line 1936, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "XXXX\flask_hook.py", line 8, in index
1/0
ZeroDivisionError: division by zero
127.0.0.1 - - [13/May/2020 12:58:48] "GET / HTTP/1.1" 500 -
顯然, 在出現異常的情況下也執行了handle_teardown()
函數;
虛造關閉Debug模式纔能有明顯的現象,在Debug模式下是不能執行handle_teardown()
函數的。
5.context_processor
上下文處理器,返回的字典中的鍵可以在模板上下文中使用。
假如在多個視圖函數中渲染模板時都需要傳入同樣的參數,flask_hook.py如下:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html', username='Corley')
@app.route('/list/')
def list():
return render_template('list.html', username='Corley')
if __name__ == '__main__':
app.run(debug=True)
在模板目錄templates中新建index.html和list.html,index.html如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首頁</title>
</head>
<body>
<p>這是首頁</p>
<p>{{ username }}</p>
</body>
</html>
list.html如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>列表</title>
</head>
<body>
<p>這是列表</p>
<p>{{ username }}</p>
</body>
</html>
顯示:
顯然,達到了效果,但是在每個視圖函數中都要傳入參數,很麻煩冗餘,可以用context_processor裝飾器定義鉤子函數傳遞公共參數:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/list/')
def list():
return render_template('list.html')
@app.context_processor
def context_process():
return {'username': 'Corley'}
if __name__ == '__main__':
app.run(debug=True)
效果與之前一樣;
此時在每個視圖函數中不需要再傳遞參數,而是在context_processor裝飾器定義的鉤子函數中返回參數。
6.errorhandler
errorhandler接收狀態碼作爲參數,可以自定義處理返回這個狀態碼的響應的方法。
測試如下:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
1/0
return render_template('index.html')
@app.route('/list/')
def list():
return render_template('list.html')
@app.context_processor
def context_process():
return {'username': 'Corley'}
@app.errorhandler(404)
def page_not_found(error):
return render_template('404.html'), 404
@app.errorhandler(500)
def server_error(error):
return '<p>服務器內部錯誤...</p>', 500
if __name__ == '__main__':
app.run()
新建404.html如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>頁面404</title>
</head>
<body>
<p>頁面跑到火星去了...</p>
</body>
</html>
顯示:
顯然,捕獲到了404和500錯誤。
還可以在視圖函數中用abort()
方法主動拋出異常,如下:
from flask import Flask, render_template, abort
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/list/')
def list():
abort(404)
return render_template('list.html')
@app.context_processor
def context_process():
return {'username': 'Corley'}
@app.errorhandler(404)
def page_not_found(error):
return render_template('404.html'), 404
@app.errorhandler(500)
def server_error(error):
return '<p>服務器內部錯誤...</p>', 500
if __name__ == '__main__':
app.run()
顯示:
顯然,此時再訪問http://127.0.0.1:5000/list/,就會報錯;
因爲在list()
視圖函數中使用sort()
方法拋出404錯誤,被鉤子函數page_not_found()
捕獲到,因此訪問會出現404錯誤。
五、Flask-Restful
1.Restful API規範
Restful API是用於在前端與後臺進行通信的一套規範,使用這個規範可以讓前後端開發變得更加簡單。
協議
採用http或者https協議。
數據傳輸格式
數據之間傳輸的格式應該使用json形式,而不是xml。
url鏈接
url鏈接中不能有動詞,只能有名詞;
對於一些名詞,如果出現複數,應該在後面加s。
HTTP請求的方法
方法 | 含義 | 舉例 |
---|---|---|
GET | 從服務器上獲取資源 | /users/ 獲取所有用戶 |
… | … | /user/id/ 根據id獲取一個用戶 |
POST | 在服務器上新創建一個資源 | /user/ 新建一個用戶 |
PUT | 在服務器上更新資源(客戶端提供所有改變後的數據) | /user/id/ 更新某個id的用戶的信息(需要提供用戶的所有信息) |
PATCH | 在服務器上更新資源(客戶端只提供需要改變的屬性) | /user/id/ 更新某個id的用戶信息(只需要提供需要改變的信息) |
DELETE | 從服務器上刪除資源 | /user/id/ 刪除一個用戶 |
狀態碼
狀態碼 | 描述 | 含義 |
---|---|---|
200 | OK | 服務器成功響應客戶端的請求。 |
400 | INVALID REQUEST | 用戶發出的請求有錯誤,服務器沒有進行新建或修改數據的操作 |
401 | Unauthorized | 用戶沒有權限訪問這個請求 |
403 | Forbidden | 因爲某些原因禁止訪問這個請求 |
404 | NOT FOUND | 用戶發送的請求的url不存在 |
406 | NOT Acceptable | 用戶請求不被服務器接收(比如服務器期望客戶端發送某個字段,但是沒有發送) |
500 | Internal server error | 服務器內部錯誤,比如出現了bug |
2.Flask-Restful插件
基本概念
Flask-Restful是Flask中專門用來寫restful api的一個插件,使用它可以快速集成restful api功能。
在app的後臺以及純API的後臺中,這個插件可以幫助我們節省很多時間;
在普通的網站中,這個插件顯得有些雞肋,因爲在普通的網頁開發中,是需要去渲染HTML代碼的,而Flask-Restful在每個請求中都返回json格式的數據。
安裝
Flask-Restful的環境要求:
- Flask版本>=0.80.8
- Python版本爲2.6、2.7,或3.3及以上。
在虛擬環境中使用pip install flask-restful
命令安裝。
基本使用
使用Flask-Restful定義視圖函數時,要繼承自flask_restful.Resource
類,再根據具體的請求的方法來定義相應的方法。
例如,如果期望客戶端使用get方法發送請求,那麼就定義一個get方法,類似於MethodView。
簡單使用測試如下:
from flask import Flask
from flask_restful import Api, Resource
app = Flask(__name__)
api = Api(app)
class IndexView(Resource):
def get(self):
return {'username': 'Corley'}
def post(self):
return {'info': 'Login Successfully!!'}
api.add_resource(IndexView, '/', endpoint='index')
if __name__ == '__main__':
app.run(debug=True)
運行之後,使用Postman模擬請求如下:
顯然,用get方法和post方法請求得到的結果是不一樣的。
說明:
- endpoint是用來給
url_for()
方法反轉url的時候指定的;如果不設置endpoint參數,會使用視圖的名字的小寫作爲endpoint的值。 add_resource()
方法的第二個參數是視圖函數的路由地址,這個地址與視圖函數的route一樣,可以傳遞參數;有一點不同的是,這個方法可以傳遞多個url來指定視圖函數。