python 全棧開發,Day139(websocket原理,flask之請求上下文)

昨日內容回顧

flask和django對比

flask和django本質是同樣的,都是web框架。html

可是django自帶了一些組件,flask雖然自帶的組件比較少,可是它有不少的第三方插件。

mysql

那麼在什麼狀況下,使用flask呢?web

好比讓flask寫一個大型項目,它須要不少第三方插件。
那麼堆着堆着,就和django同樣了!面試

 

總結:sql

若是一個項目須要的插件比較少,可使用flask。
若是須要的插件比較多,使用django更加方便。數據庫

 

flask知識點

裝飾器

在flask中,裝飾器用的是比較多的。看下面一段代碼django

from flask import Flask

app = Flask(__name__)

@app.route('/index')
def index():
    return 'index'

if __name__ == '__main__':
    app.run()

如今有一個裝飾器函數xxx,若是須要在每次請求index頁面時,作一些操做。json

那麼裝飾器,應該加在哪裏呢?flask

 

這樣?瀏覽器

@xxx
@app.route('/index')

仍是這樣呢?

@app.route('/index')
@xxx

 

答案是,必須在@app.route('/index')下面才行。爲何呢?

由於若是加在@app.route上面,那麼執行@xxx以後,那麼就直接走視圖函數了。已經沒有意義了!

而若是在@app.route下面,那麼執行到路由後,就會先執行@xxx,再執行視圖函數!

 

裝飾器的順序

看下面一段代碼,index視圖函數,加了一個裝飾器xxxx

from flask import Flask

app = Flask(__name__)

def xxxx(func):
    def inner(*args,**kwargs):
        print('before')
        return func(*args,**kwargs)

    return inner

@app.route('/index')
@xxxx
def index():
    return 'index'

if __name__ == '__main__':
    app.run()
View Code

啓動程序,訪問首頁

http://127.0.0.1:5000/index

查看Pycharm控制檯輸出:  before

 

若是再加視圖函數home,並應用xxxx裝飾器

from flask import Flask

app = Flask(__name__)

def xxxx(func):
    def inner(*args,**kwargs):
        print('before')
        return func(*args,**kwargs)

    return inner

@app.route('/index')
@xxxx
def index():
    return 'index'

@app.route('/home')
@xxxx
def home():
    return 'home'

if __name__ == '__main__':
    app.run()
View Code

啓動以後,會直接報錯

AssertionError: View function mapping is overwriting an existing endpoint function: inner

爲何呢?因爲代碼是從上至下執行的。視圖函數執行xxxx裝飾器以後,使用__name__方法獲取函數名時,名字是inner

那麼所以執行到home時,函數名也是inner。那麼flask就會拋出異常,inner函數重複了!

 

如何解決呢?使用functools就能夠了!它會保留原函數信息,包括函數名!

from flask import Flask
import functools

app = Flask(__name__)

def xxxx(func):
    @functools.wraps(func)
    def inner(*args,**kwargs):
        print('before')
        return func(*args,**kwargs)

    return inner

@app.route('/index')
@xxxx
def index():
    return 'index'

@app.route('/home')
@xxxx
def home():
    return 'home'

if __name__ == '__main__':
    app.run()
View Code

再次執行,就不會報錯了。

 

所以,之後爲了裝飾器不出問題,必定要加functools

 

before_request/after_request

看下面的代碼,b1和b2誰會先執行?

# import pymysql
# from DBUtils.PooledDB import PooledDB, SharedDBConnection
# POOL = PooledDB(
#     creator=pymysql,  # 使用連接數據庫的模塊
#     maxconnections=6,  # 鏈接池容許的最大鏈接數,0和None表示不限制鏈接數
#     mincached=2,  # 初始化時,連接池中至少建立的空閒的連接,0表示不建立
#     maxcached=5,  # 連接池中最多閒置的連接,0和None不限制
#     maxshared=3,  # 連接池中最多共享的連接數量,0和None表示所有共享。PS: 無用,由於pymysql和MySQLdb等模塊的 threadsafety都爲1,全部值不管設置爲多少,_maxcached永遠爲0,因此永遠是全部連接都共享。
#     blocking=True,  # 鏈接池中若是沒有可用鏈接後,是否阻塞等待。True,等待;False,不等待而後報錯
#     maxusage=None,  # 一個連接最多被重複使用的次數,None表示無限制
#     setsession=[],  # 開始會話前執行的命令列表。如:["set datestyle to ...", "set time zone ..."]
#     ping=0,
#     # ping MySQL服務端,檢查是否服務可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always
#     host='127.0.0.1',
#     port=3306,
#     user='root',
#     password='123',
#     database='pooldb',
#     charset='utf8'
# )
'''
1.Flask路由
    1.endpoint="user" # 反向url地址
    2.url_address = url_for("user")
    3.methods = ["GET","POST"] # 容許請求進入視圖函數的方式
    4.redirect_to # 在進入視圖函數以前重定向
    5./index/<nid> # 動態參數路由 <int:nid> def index(nid)
    6.strict_slashes # 是否嚴格要求路由地址 /
    7.defaults={"nid":1} # def index(nid)

2.Flask初始化配置(實例化):
    1.template_folder # 指定模板路徑
    2.static_url_path # 指定靜態文件目錄的URL地址
    3.static_folder # 指定靜態文件目錄路徑

3.Flask對象配置
    1.DEBUG #開發模式的調試功能 True False
    2.app.config.from_object(class) # 經過對象的方式導入配置
    3.secret_key # 開啓session功能的時候須要添加的配置

4.Blueprint
    1.將功能和主程序分離,註冊
    2.bl = Blueprint("dongdong",__name__)
    3.註冊 register_blueprint(bl)

5.send_file jsonify
    1.send_file # 打開並返回文件 content-type:文件類型
    2.jsonify # 將一個字符串 轉爲JSON格式 加入 content-type:application/json 頭

6.特殊的裝飾器:
    1.before_request # 在請求進入視圖函數以前執行的函數(登陸認證)
    2.after_request # 在請求響應回瀏覽器以前執行的函數
    3.before_first_request # 在第一次請求進入視圖函數以前執行的函數
    4.errorheader(404) # 當遇到此類錯誤響應的時候(自定義錯誤頁面)

7.flash
    1.flash("msg","tag") # 閃現存儲
    2.get_flashed_messages(category_filter=["tag"]) # 閃現取值
    只要用到了get_flashed_messages就必定清空flash



1.DButils 數據庫鏈接池
    建立鏈接池同時建立鏈接
    用到鏈接時從鏈接池中抽取一個鏈接
    釋放鏈接時將鏈接放回鏈接池中
    節省與mysql的通信次數和時長

2.Websocket 通信協議

 Web + socket
 QQ 即時通信軟件 97

 初期輪詢:
    QQ 聯衆 軟件不斷的循環訪問服務器問它有沒有給我發送的消息
    優勢:響應及時
    缺點:浪費CPU資源,浪費帶寬

 長輪詢:
    當客戶端發起詢問,服務器說你等着1分鐘以後,你再來問我
    斷開再次發起鏈接,服務器幫你輪詢
    優勢:響應及時
    缺點:用戶一旦造成規模,服務器消耗是致命的

 新的協議 websocket
    規定了一個數據格式
    收發數據
    該收就收
    該發就發

3.羣聊

4.私聊

'''
# from flask import Flask,request,redirect,session
#
# app = Flask(__name__)
# app.secret_key = "DragonFire"
#
#
# @app.before_request
# def is_login():  # 判斷是否登陸
#     # 白名單設置,判斷爲登陸頁面時
#     if request.path == "/login":
#         # 跳過處理
#         return None
#     # 判斷session是不存在時
#     if not session.get("user"):
#         # 重定向到登陸頁面
#         return redirect("/login")
#
# @app.after_request
# def foot_log(environ):  # 記錄訪問日誌
#     print(environ)  # 響應信息
#     # 判斷請求路徑不是登陸頁面
#     if request.path != "/login":
#         # 打印訪問路徑
#         print("有客人訪問了",request.path)
#
#     return environ
#
# @app.route("/login",methods=["POST","GET"])
# def login():
#     if request.method == "GET":
#         return "Login"
#
#     user = request.form["username"]  # form表單獲取
#     pwd = request.form["password"]  # form表單獲取
#     # 判斷form表示數據和 後臺數據庫匹配
#     # models.UserInfo.objects.filter(username=user,password=pwd).first()
#     if user == 'xiao' and pwd == '123':
#         # 設置session
#         session["user"] = user
#         # 跳轉首頁
#         return redirect("/index")
#
#
# @app.route("/index")
# def index():
#     return "Index"
#
# @app.route("/home")
# def home():
#     return "Home"
#
# if __name__ == '__main__':
#     app.run("0.0.0.0", 5000)

'''

1.玩具開機提示語
剛剛開機的時候:
    1.受權問題(MD5受權碼)提示語 : 請聯繫玩具廠商
    2.綁定問題 提示語 : 快給我找一個小主人
    3.成功 提示語:歡迎使用 

    
2.爲多個玩具發送點播:
    mpop 彈出菜單 


3.聊天界面:
    <div class="leftd">
        <img src="avatar/girl.jpg" class="leftd_h" />
        <div class="speech left">點擊播放</div>
    </div>
    <div class="rightd">
        <img src="avatar/girl.jpg" class="rightd_h" />
        <div class="speech right">點擊播放</div>
    </div>
    
    按住錄音:
        hold: 按住事件 開始錄音(回調函數)
        release: 鬆開事件 結束錄音 執行錄音中的回調函數
    
4.app錄音:
    var rec = plus.audio.getRcorder()
    rec.record(
        {filename:"_doc/audio/",format:"amr"},
        function(success){ success //錄音文件保存路徑 },
        function(error){}
    )
    
    rec.stop()
    
5.app與服務器端文件傳輸(ws傳輸):
    1.app使用dataURL方式打開錄音文件 : base64 文件
    2.經過某個函數 將 Base64 格式的文件 轉爲 Blob 用於 websocket傳輸
    3.將Blob對象使用Ws發送至服務端
    4.服務端保存文件(amr)
    5.將amr 轉換爲 mp3  使用 ffmpeg -i xxx.amr xxx.mp3
    

6.簡單的對話(app向玩具(web)發起):
    app:    1.發起兩次 ws.send({to_user:}) 告訴服務端我要發給誰消息
            2. ws.send(blob) app與服務器端文件傳輸
    
    websocket服務:
        0.建立兩個變量,用於接收to_user 和 blob對象
        1.收到用戶的JSON字符串,to_user
            獲取對方的Websocket,用戶send
        2.收到用戶的Blob對象,語音文件
            保存成amr文件,轉換成mp3
            注意保存文件的路徑
            
        3.將轉換完成的文件發送給 to_user
        
        4.兩個變量置空

'''

from flask import Flask
import functools

app = Flask(__name__)

@app.before_request
def b1():
    print('b1')

@app.before_request
def b2():
    print('b2')

def xxxx(func):
    @functools.wraps(func)
    def inner(*args,**kwargs):
        print('before')
        return func(*args,**kwargs)

    return inner

@app.route('/index')
@xxxx
def index():
    return 'index'

@app.route('/home')
@xxxx
def home():
    return 'home'

if __name__ == '__main__':
    app.run(debug=True)
View Code

啓動程序,訪問index頁面

http://127.0.0.1:5000/index

查看Pycharm控制檯輸出:

b1
b2
before

能夠發現,b1先執行。爲何呢?由於代碼是從上至下執行的,因此誰先加載,誰就先執行!

 

 

關於before_request源碼分析,請參考連接:

https://blog.csdn.net/slamx/article/details/50491192

 

舉例:

from flask import Flask

app = Flask(__name__)


@app.before_request
def b1():
    print('b1')

@app.after_request
def a1(environ):
    print('a1')
    return environ


@app.route('/index')
def hello_world():
    return 'Hello World!'


if __name__ == '__main__':
    app.run()
View Code

訪問首頁:http://127.0.0.1:5000/index,效果以下:

 

Pycharm輸出:

b1
a1

 

總結:before_request其實是將視圖函數,append到一個列表中。after_request也是將視圖函數append到一個列表中,可是它對列表作了reverse操做!具體,能夠看源碼。

 

endpoint

endpoint主要是作反向解析的,使用url_for模塊,就能夠反向生成url

from flask import Flask,url_for

app = Flask(__name__)

@app.route('/index',endpoint='n1')
def index():
    print(url_for('n1'))
    return 'index'


if __name__ == '__main__':
    app.run(debug=True)
View Code

 

訪問url:

http://127.0.0.1:5000/index

執行輸出:

/index

 

flask內置session

flask的session默認存儲在哪裏呢?在django中,session默認是保存在表裏面的。

那麼flask的session實際上是保存在 用戶瀏覽器的cookie中

 

它是如何存儲的呢?看下面一段代碼

from flask import Flask,request,session

app = Flask(__name__)
app.secret_key = 'fdsa'  # 必需要指定這個參數

@app.route('/login')
def login():
    #認證過程省略...
    # 設置session
    session['user_info'] = 'xiao'
    return '123'

if __name__ == '__main__':
    app.run(debug=True)
View Code

訪問登陸頁面,效果以下:

查看請求, 發現一個Set-Cookie。這個cookie的key就是session,值爲一堆字符串。它是已經加密過的!

 

那麼它是如何實現的呢?看這一行代碼

session['user_info'] = 'xiao'

它在內存中,維護了一個空間,這個空間是一個字典。因爲服務端是單進程,單線程。

全部請求過來時,會排隊。這個字典,會放一個key,這個key就是程序的線程id,value存放用戶信息。

而value是一個字典,好比:{'user_info':'xiao'}

假設有100個用戶,那麼有100個值。大概是這樣的樣子:

{
    "線程id": {
        "user_info": "xiao"
    },
    "線程id": {
        "user_info": "zhang"
    },
    ...
}

返回給瀏覽器時,將內存中的字典作序列化,並作了加密
加完密以後,在cookie中寫了一點數據
key是隨機的,可是vlaue纔是真正的數據

 

這個時候,flask字典,就清空了。
用戶瀏覽器cookie中就有數據了,可是flask中的數據已經沒有了!
這個時候,若是再來一用戶,也是執行上面的流程。


總之,做爲服務器,我不存儲數據。
那麼問題來了,flask如何作session驗證?

若是以前的用戶來了,它會攜帶cookie。
flask會讀取cookie值,若是發現有,進行解密。若是解密成功,那麼就是已經登陸過了,不然沒有登陸過。

解密以後,它會將數據放到字典中!
那麼讀取時,它會直接從內存中讀取。

 

關於flask的源碼分析,請參考連接:

http://www.javashuo.com/article/p-yesdfxfy-bc.html

 

1、websocket原理

因爲時間關係,步驟略...

 

關於websocket原理,請參考連接:

http://www.javashuo.com/article/p-kaadeqnc-e.html

 

2、flask之請求上下文

flask上下文管理,主要分爲2類:

請求上下文管理

應用上下文管理

 

因爲時間關係,步驟略...

草稿圖

 

 

 

關於flask上下文管理,請參考連接:

https://www.cnblogs.com/zhaopanpan/p/9457343.html 

 

https://blog.csdn.net/bestallen/article/details/54429629

 

關於flask面試題,請參考連接:

https://www.cnblogs.com/caochao-/articles/8963610.html

 

今日內容總結:

內容詳細:
    1. websocket原理
        a. websocket是一個協議。
            websocket解決了一個問題:服務端能夠向客戶端推送消息。
            
            
            http協議規定:
                - 請求體請求體
                - 一次請求一次響應(無狀態短連接)
            websocket協議規定:
                - 握手
                    - base64(sha1(key + magic string ))
                - 收發數據(加密)
                    -  =127
                    -  =126 
                    -  <=125
                - 鏈接建立不斷開(持久鏈接)
                
        b. 使用 
            - flask: werkzurg / geventwebsocket
            - django: wsgiref / channel 
            - tornado: 本身寫全支持:http和ws
            
    
    2. flask上下文管理 

        前戲:
            a. threading.local 
            
                # 建立threading.local對象 
                val = threading.local()

                def task(arg):
                    # threading.local對象.xxx = 123
                    # 內部,獲取當前線程ID
                    #   {
                    #         7800:{'x1':1}
                    #         7180:{'x1':2}
                    #   }
                    val.x1 = arg
                    

                for i in range(10):
                    t = threading.Thread(target=task,args=(i,))
                    t.start()
                
                # ####### flask中搞了一個升級版的threading.local() #######
                # 建立threading.local對象 
                val = threading.local()

                def task(arg):
                    # threading.local對象.xxx = 123
                    # 內部,獲取當前協程ID
                    #   {
                    #         7800:{'x1':1}
                    #         7180:{'x1':2}
                    #   }
                    val.x1 = arg
                    

                for i in range(10):
                    t = threading.Thread(target=task,args=(i,))
                    t.start()
            b. 棧 
                後進先出的數據結構
                
            c. 偏函數 
                保留已知參數 
                
            d. 全局變量,flask程序啓動只有一份數據
                _request_ctx_stack = LocalStack()
                _app_ctx_stack = LocalStack()
                current_app = LocalProxy(_find_app)
                request = LocalProxy(partial(_lookup_req_object, 'request'))
                session = LocalProxy(partial(_lookup_req_object, 'session'))
                g = LocalProxy(partial(_lookup_app_object, 'g'))

        正文:圖
        
        
重點總結:
    1. flask路由:裝飾器   *****
    2. flask的session,默認寫在瀏覽器cookie中。 ***
    3. websocket協議       *****
    4. flask請求上下文管理 *****
    

做業:
    請求上下文類關係圖
View Code

 

未完待續...

相關文章
相關標籤/搜索