Web應用程式是使用HTTP協定傳輸資料的。HTTP協定是無狀態的協定。一旦資料交換完畢,使用者端與伺服器端的連線就會關閉。再次交換資料需要建立新的連線,這就意味著伺服器無法從連線上跟蹤對談。
Cookie就是這樣的一種機制。它可以彌補HTTP協定無狀態的不足。在Session出現之前,基本上所有的網站都採用Cookie來跟蹤對談。
Cookie實際上是一小段的文字資訊。使用者端請求伺服器,如果伺服器需要記錄該使用者狀態,就使用response向用戶端瀏覽器頒發一個Cookie。使用者端瀏覽器會把Cookie儲存起來。
當瀏覽器再請求該網站時,瀏覽器把請求的網址連同該Cookie一同提交給伺服器。伺服器檢查該Cookie,以此來辨認使用者狀態。伺服器還可以根據需要修改Cookie的內容。
注意
from flask import Flask,make_response
# 建立物件
app = Flask(__name__)
# 路由地址
@app.route("/")
def index():
return "pandeng"
@app.route('/set_cookie/')
def set_cookie():
resp = make_response('設定了一個Cookie資訊')
resp.set_cookie('uname','pd')
return resp
if __name__ == "__main__":
app.config.from_pyfile("./setting.py")
app.run()
@app.route('/get_cookie/')
def get_cookie():
uname = request.cookies.get('uname')
return f'Cookie裡面,uname的內容是{uname}'
@app.route('/del_cookie/')
def del_cookie():
resp = make_response('刪除了一個Cookie資訊')
resp.delete_cookie('uname')
return resp
max_age
:以秒為單位,距離現在多少秒後cookie會過期。expires
:為datetime型別。這個時間需要設定為格林尼治時間,相對北京時間來說 會自動+8小時如果max_age
和expires
都設定了,那麼這時候以max_age
為標準。
注意
@app.route('/set_cookie/')
def set_cookie():
resp = make_response('設定了一個Cookie資訊')
# temp_time = datetime(2022,1,12,hour=18,minute=0,second=0)
age = 60*60*2 # 設定兩個小時後到期
# resp.set_cookie('uname','pd',expires=temp_time)
resp.set_cookie('uname','pd',max_age=age)
return resp
由於datetime
也可以這樣操作
@app.route('/set_cookie1/')
def set_cookie1():
resp = make_response('設定了一個Cookie1資訊')
# 設定標準時間的 兩個小時後到期(就是10個小時後)
temp_time = datetime.now() + timedelta(hours=2)
resp.set_cookie('uname','pd',expires=temp_time)
return resp
Session
和Cookie
的作用有點類似,都是為了儲存使用者相關的資訊,都是為了解決http協定無狀態的這個特點。
不同的是Cookie
儲存在使用者端瀏覽器中,而Session
儲存在伺服器上。
使用者端瀏覽器存取伺服器的時候,伺服器把使用者端資訊以某種形式記錄在伺服器上。使用者端瀏覽器再次存取時只需要從該Session
中查詢該客戶的狀態就可以了。
注意
不同的語言,不同的框架,有不同的實現。雖然底層的實現不完全一樣,但目的都是讓伺服器端能方便的儲存資料而產生的。
Session的出現,是為了解決cookie儲存資料不安全的問題的。
Cookie
機制是通過檢查客戶身上的「通行證」來確定客戶身份的話Session
機制就是通過檢查伺服器上的「客戶明細表」來確認客戶身份。Flask框架中,session的跟蹤機制跟Cookie有關,這也就意味著脫離了Cookie,session就不好使了。
因為session跟蹤機制跟cookie有關,所以,要分伺服器端和使用者端分別起到什麼功能來理解。
答:URL地址攜帶SessionID
from os import urandom
# 直接設定 內容隨便設定
app.secret_key = "houwehvowv"
'''
# 類方式
class DefaultConfig:
SERECT_KEY = urandom(24) # 設定長度為24的字串
app.config.from_object(DefaultConfig)
'''
# 記得先設定Session的鹽
@app.route("/set_session/")
def set_session():
session['uname'] = 'pandeng'
return '設定了一個session物件'
@app.route("/get_session/")
def get_session():
# 能查當然能改
uname = session.get('uname')
# 根據uame值,從資料庫中查詢
return f'session裡面,uname的內容是{uname}'
@app.route("/del_session/")
def del_session():
# pop刪除一個key
session.pop('uname')
# 可以用clear刪除全部資訊
# session.clear()
return '刪除了一個session物件'
如果沒有設定session的有效期。那麼預設就是瀏覽器關閉後過期。
如果設定session.permanent=True
,那麼就會預設在31天后過期。
如果不想在31天后過期,按如下步驟操作:
session.permanent=True
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hour=2)
在兩個小時後過期。app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=2) # 設定session兩天後過期
@app.route("/set_session/")
def set_session():
# 設定session的持久化
session.permanent=True
session['uname'] = 'pandeng'
return '設定了一個session物件'
在app.secret_key
隨機的情況下,先進入http://127.0.0.1:5000/set_session/
, 再進入http://127.0.0.1:5000/get_session/
,能正常顯示;關掉程序,重新開啟,直接進入http://127.0.0.1:5000/get_session/
,返回的session值為None
在app.secret_key
寫死的情況下,先進入http://127.0.0.1:5000/set_session/
, 再進入http://127.0.0.1:5000/get_session/
,能正常顯示;關掉程序,重新開啟,直接進入http://127.0.0.1:5000/get_session/
,也能正常顯示;
核心理解就是: session其實就是保留的,但是金鑰的伺服器獨有的,session是伺服器頒發的唯一的,只要用伺服器的金鑰解不開,就是None;所以也可以理解為金鑰寫死下,重新啟動伺服器,Session不會過期;隨機下,重新啟動伺服器就會過期(實際沒過期,只是伺服器看不懂了)
拿之前的程式碼改吧改吧就成了!!
<div class="zhuce_box">
<div class="beijing_box"></div>
<div class="zhuce_con_box">
<div class="zhuce_nav clearfix">
<a href="/" class="zhuce_logo_box fl"><img src="../../static/img/logo.png" alt=""></a>
<p class="phone_zhuce_box fr"><img src="../../static/img/zhucephone.png" alt="">聯絡電話:18514000360</p>
</div>
<div class="zhuce_info_box denglu_info_box clearfix">
<div class="zhuce_txt_box fl">
<h2>百戰 老師好!!</h2>
<p>讓人人享有高品質教育</p>
</div>
<div class="user_in_con_b fr">
<div class="user_in_info">
<ul class="login_btn clearfix">
<li class="user_li_on"><a href="{{ url_for('login') }}">登入</a></li>
<li class="user_li_on1"><a href="{{ url_for('register') }}">註冊</a></li>
</ul>
<div class="user_dengzhu_box">
<div class="user_dengzhu_info">
<form method="post" action="/login/">
<div class="user_input_info">
<input type="text" placeholder="使用者名稱" name="uname" class="login_phone">
<p class="info_error error_login_phone"></p>
</div>
<div class="captcha">
<div id="your-dom-id" class="nc-container"></div>
<input type="hidden" name="captcha" value="0">
</div>
<div class="phone_code">
<div class="phone_code_info">
<input type="text" placeholder="密碼" name="pwd"
class="dxcode login_code">
<input type="button" value="獲取驗證碼" class="get_dxcode get_login_code">
</div>
<p class="info_error error_login_code">{{ msg | default('',boolean=True) }}</p>
</div>
<div class="zhuce_btn">
<input type="submit" class="submit" value="登入">
</div>
</form>
<div class="user_zhu_txt">手機號不能用?<a href="">通過申訴更改手機號</a></div>
<a href="" class="weixin_button"><span
class="icon iconfont icon-weixin6"></span>微信掃碼一鍵登入</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% block script %}
<script></script>
{% endblock %}
{% extends "./10_類檢視的使用/login.html" %}
{% block title %}
註冊頁面
{% endblock %}
{% block script %}
<script>
var login = document.querySelector(".user_li_on");
var register = document.querySelector(".user_li_on1");
login.setAttribute("style", "border-bottom: none;color:black;")
register.setAttribute("style", "border-bottom: 5px solid #00b683;color:#00b683;")
</script>
{% endblock %}
./06Jinja2模板/index1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>使用者主頁</title>
<style>
a{
float:right;
}
</style>
</head>
<body>
<a href="{{ url_for('index') }}">回到首頁</a>
<h1>{{ uname }}: {{ age }}</h1>
<p>最喜歡的金融課目是{{ hobby.finance }}</p>
<p>最喜歡的體育專案是{{ hobby.sports }}</p>
<p>最喜歡的程式語言是{{ hobby.it }}</p>
</body>
</html>
from flask import Flask, session,views,render_template,request,redirect,url_for
app = Flask(__name__)
app.secret_key = 'vbdibv'
@app.route('/')
def index():
return "Hello"
class LoginView(views.MethodView):
def _jump(self,msg=None):
return render_template('./10_類檢視的使用/login.html',msg=msg)
def get(self):
msg = request.args.get('msg')
return self._jump(msg=msg)
def post(self):
uname = request.form.get('uname')
pwd = request.form.get('pwd')
if uname == 'pandeng' and pwd == '123':
# 驗證使用者資訊
session['uname'] = uname
return redirect(url_for('user'))
return self._jump(msg="使用者名稱密碼錯誤")
app.add_url_rule('/login/',view_func=LoginView.as_view('login'))
@app.route('/register/')
def register():
return render_template('./10_類檢視的使用/register.html')
@app.route("/user/")
def user():
uname = session.get('uname')
if uname:
# 就從資料庫中查hobby, 這裡寫死了
hobby = {
"finance":"quantify",
"sports":"riding",
"it":"Python",
}
return render_template('./06Jinja2模板/index1.html',uname=uname,hobby=hobby)
else:
return '請先' + '<a href="/login/">登入</a>'
if __name__ == '__main__':
app.run(debug=True)
最終效果
http://127.0.0.1:5000/user/
http://127.0.0.1:5000/user/
,就無需登入,不會再跳轉去登入頁面了需求
複習程序,執行緒,協程
錯誤想法
正確想法
Local物件
在Flask中,類似於 request
物件,其實是繫結到了一個 werkzeug.local.Local
物件上。
這樣,即使是同一個物件,那麼在多個執行緒中都是隔離的。類似的物件還有 session
物件。
flask = werkzeug + sqlalchemy + jinja2
Python提供了 ThreadLocal
變數,它本身是一個全域性變數,但是每個執行緒卻可以利用它來儲存屬於自己的私有資料,這些私有資料對其他執行緒也是不可見的。
from threading import Thread,local
local = local()
local.request = '這個是請求的資料1'
class MyThread(Thread):
def run(self):
local.request = '我是pd'
print('子執行緒:', local.request)
my_thread = MyThread()
my_thread.start()
my_thread.join()
print('主執行緒:', local.request)
可以看到主執行緒與子執行緒是相互分離,互不影響的
from threading import Thread
from werkzeug.local import Local
local = Local()
local.request = '這個是請求的資料1'
class MyThread(Thread):
def run(self):
local.request = '我是pd'
print('子執行緒:', local.request)
my_thread = MyThread()
my_thread.start()
my_thread.join()
print('主執行緒:', local.request)
這個的結果與上面的是一致的。
總結
只要滿足繫結到"local"或"Local"物件上的屬性,在每個執行緒中都是隔離的,那麼他就叫做 ThreadLocal
物件,也叫’ThreadLocal’變數。
上下文(感性的理解)
每一段程式都有很多外部變數,只有像add這種簡單的函數才是沒有外部變數的。 一旦一段程式有了外部變數,這段程式就不完整,不能獨立執行。為了能讓這段程式可以執行,就要給所有的外部變數一個一個設定一些值。就些值所在的集合就是叫上下文。
並且上下文這一概念在中斷任務的場景下具有重大意義,其中任務在被中斷後,處理器儲存上下文並提供中斷處理,因些在這之後,任務可以在同一個地方繼續執行。(上下文越小,延遲越小)
應用上下文是存放到一個 LocalStack
的棧中。和應用app相關的操作就必須要用到應用上下文
那到底是不是這樣呢? 去看原始碼!!!
from flask import Flask,current_app
app = Flask(__name__)
# 建立應用上下文
app_ctx = app.app_context()
app_ctx.push()
print(current_app.name)
'''
# 方法2
with app.app_context():
print(current_app.name)
'''
@app.route('/')
def index():
return f'Hello,這是一個{current_app.name}應用'
if __name__ == '__main__':
app.run(debug=True)
上下文的一個典型應用場景就是用來快取一些我們需要在發生請求之前或者要使用的資源。舉個例子,比如資料庫連線。當我們在應用上下文中來儲存東西的時候你得選擇一個唯一的名字,這是因為應用上下文為 Flask 應用和擴充套件所共用。
注意
在檢視函數中,不用擔心應用上下文的問題。因為檢視函數要執行,那麼肯定是通過存取url的方式執行的,
那麼這種情況下,Flask底層就已經自動的幫我們把應用上下文都推入到了相應的棧中。
如果想要在檢視函數外 則執行相關的操作,
請求上下文
請求上下文也是存放到一個 LocalStack
的棧中。和請求相關的操作就必須用到請求上下文,比如使用 url_for
反轉檢視函數;
from flask import Flask,current_app,url_for
app = Flask(__name__)
@app.route('/')
def index():
return f'Hello,這是一個{current_app.name}應用'
@app.route('/test/')
def test():
url = url_for('index')
return f'這個是一個小測試-{url}'
url = url_for('index')
print(url)
if __name__ == '__main__':
app.run(debug=True)
RuntimeError: Attempted to generate a URL without the application context being pushed. This has to be executed when application context is available.
原因就是因為沒有手動推入請求上下文;
就算手動設定了:
with app.app_context():
url = url_for('index')
print(url)
RuntimeError: Application was not able to create a URL adapter for request independent URL generation. You might be able to fix this by setting the SERVER_NAME config variable.
怎麼辦? 回去康康原始碼!! 點進去url_for
,發現其實不僅需要LocalStack
的棧,還需要request
的棧
解決方案:
with app.test_request_context():
url = url_for('index')
print(url)
為什麼上下文需要放在棧中?
1.應用上下文:Flask底層是基於werkzeug,werkzeug是可以包含多個app的,所以這時候用一個棧來儲存。
如果你在使用app1,那麼app1應該是要在棧的頂部,如果用完了app1,那麼app1應該從棧中刪除。方便其他程式碼使用下面的app。
2.如果在寫測試程式碼,或者離線指令碼的時候,我們有時候可能需要建立多個請求上下文,這時候就需要存放到一個棧中了。
使用哪個請求上下文的時候,就把對應的請求上下文放到棧的頂部,用完了就要把這個請求上下文從棧中移除掉。(所以這也是使用with的原因)
g物件是在整個Flask應用執行期間都是可以使用的。並且也跟request一樣,是執行緒隔離的。
這個物件是專門用來儲存開發者自己定義的一些資料,方便在整個Flask程式中都可以使用。
在主邏輯下,新建一個檔案utils.py
裡面放處理業務的函數
from flask import g
def func_a():
return f"最喜歡的金融課目是:{g.hobby['finance']}"
def func_b():
return f"最喜歡的體育專案是:{g.hobby['sports']}"
def func_c():
return f"最喜歡的程式語言是:{g.hobby['it']}"
回到主邏輯中
from flask import g
from utils import *
@app.route('/user/')
def user():
hobby = {
"finance":"quantify",
"sports":"riding",
"it":"Python",
}
g.hobby = hobby
a = func_a()
b = func_b()
c = func_c()
return f'PD <br> {a} <br> {b} <br> {c}'
還是那個熟悉的頁面:
最後來梳理一遍上面的過程,先是進入user()
函數,然後資料賦給G物件,然後呼叫函數時,參照的g物件也是賦值後的g物件,可以正常執行函數