1. 程式人生 > >乾貨來啦。Flask框架看這一篇就夠了,關注不迷路,Jeff帶你看原始碼。開發技術時時更新

乾貨來啦。Flask框架看這一篇就夠了,關注不迷路,Jeff帶你看原始碼。開發技術時時更新

[TOC] # 一、初識Flask ## 1.1 什麼是flask? ![img](https://img2018.cnblogs.com/blog/1825659/201910/1825659-20191009213327205-1932317939.png) Flask 本是作者 Armin Ronacher在2010年4月1日的一個愚人節玩笑 ,不過後來大受歡迎,進而成為一個正式的python編寫的web框架 Flask是一個Python編寫的Web 微框架,讓我們可以使用Python語言快速實現一個網站或Web服務,在介紹Flask之前首先來聊下它和Django的聯絡以及區別,django個大而全的web框架,它內建許多模組,flask是一個小而精的輕量級框架,Django功能大而全,Flask只包含基本的配置, Django的一站式解決的思路,能讓開發者不用在開發之前就在選擇應用的基礎設施上花費大量時間。Django有模板,表單,路由,基本的資料庫管理等等內建功能。與之相反,Flask只是一個核心,預設依賴於2個外部庫: Jinja2 模板引擎和 WSGI工具集--Werkzeug , flask的使用特點是基本所有的工具使用都依賴於匯入的形式去擴充套件,flask只保留了web開發的核心功能。 ![img](https://img2018.cnblogs.com/blog/1825659/201910/1825659-20191009213820982-2905688.jpg) *WSGI(web伺服器閘道器介面)是python中用來規定web伺服器如何與python Web伺服器如何與Python Web程式進行溝通的標準,本質上就是一個socket服務端。而 Werkzeug模組 就是WSGI一個具體的實現* **關鍵詞**:一個Python編寫微web框架 一個核心兩個庫( Jinja2 模板引擎 和 WSGI工具集) ## 1.2 為什麼要有flask? flask效能上基本滿足一般web開發的需求, 並且靈活性以及可擴充套件性上要優於其他web框架, 對各種資料庫的契合度都非常高 **關鍵詞**:1. 效能基本滿足需求 2 .靈活性可拓展性強 3. 對各種資料庫的契合度都比較高。 4.在真實的生產環境下,小專案開發快,大專案設計靈活 # 二、Flask快速啟動 ```python ''' pip install flask ''' # 1 匯入flask,我們要用flask,就必須匯入Flask from flask import Flask # 2 生成一個Flask物件,__name__表示當前檔案的名字 app = Flask(__name__) # 3 新增路由,flask用的是裝飾器的模式 #註冊路由,並寫響應函式index @app.route("/") def index(): return "Hello flask" if __name__ == '__main__': #4 啟動flask #run裡面是執行了run_simple(host,port,self=app,也就是flask物件) app.run() ``` # 三、Flask四劍客 ```python ''' 響應字串 響應html頁面 跳轉頁面 返回json字串 ''' from flask import Flask, render_template, redirect, jsonify app = Flask(__name__) @app.route("/index") def index(): # 1.返回字串 # return "hello word 啊啊啊" # 2.返回html頁面 # 返回html,從flask裡面匯入render_template # return render_template("index.html") # 3.跳轉路由 # return redirect('/login') # 4.返回資料轉json返回,從flask中匯入jsonify # data = {"name": "jeff", "age": "18"} # return jsonify(data) pass @app.route("/login") def login(): return "我是login頁面" if __name__ == '__main__': app.run() ``` # 三、flask的配置檔案 ```python ''' 四種配置flask方法配置 1.直接給app物件賦值屬性 2.以字典的形式,給flask配置檔案做配置 3.以檔案的形式給flask做配置(django就是這種) 4.以類的形式,給flask做配置(推薦使用) ''' from flask import Flask app = Flask(__name__) # 方式1(不推薦),因為只能配置兩項。debug和secret_key # app.debug = True # 預設false,自動重啟 # 方式2 字典的形式 # app.config["DEBUG"] = True # 方式3 以檔案的形式,在from_pyfile裡傳遞路徑 # app.config.from_pyfile("settings.py") # 方式4 以類的形式,一個檔案多個套配置,減少測試與更改(推薦使用) # app.config.from_object("setobj.settings") @app.route("/") def index(): return "json 是炮王" if __name__ == '__main__': app.run() ``` 方式三的配置檔案: 類似Django一樣,一個專門的配置檔案 ``` DEBUG = True ``` 方式四的配置類: 優點:一個檔案,多套配置。不同的類不同的配置,減少了測試與上線的更改的配置項 ``` class settings(): DEBUG = True ``` ## 可以配置的屬性 ```python flask中的配置檔案是一個flask.config.Config物件(繼承字典),預設配置為: ''' default_config = ImmutableDict( { "ENV": None, "DEBUG": None, "TESTING": False, "PROPAGATE_EXCEPTIONS": None, "PRESERVE_CONTEXT_ON_EXCEPTION": None, "SECRET_KEY": None, "PERMANENT_SESSION_LIFETIME": timedelta(days=31), "USE_X_SENDFILE": False, "SERVER_NAME": None, "APPLICATION_ROOT": "/", "SESSION_COOKIE_NAME": "session", "SESSION_COOKIE_DOMAIN": None, "SESSION_COOKIE_PATH": None, "SESSION_COOKIE_HTTPONLY": True, "SESSION_COOKIE_SECURE": False, "SESSION_COOKIE_SAMESITE": None, "SESSION_REFRESH_EACH_REQUEST": True, "MAX_CONTENT_LENGTH": None, "SEND_FILE_MAX_AGE_DEFAULT": timedelta(hours=12), "TRAP_BAD_REQUEST_ERRORS": None, "TRAP_HTTP_EXCEPTIONS": False, "EXPLAIN_TEMPLATE_LOADING": False, "PREFERRED_URL_SCHEME": "http", "JSON_AS_ASCII": True, "JSON_SORT_KEYS": True, "JSONIFY_PRETTYPRINT_REGULAR": False, "JSONIFY_MIMETYPE": "application/json", "TEMPLATES_AUTO_RELOAD": None, "MAX_COOKIE_SIZE": 4093, } ) ''' ``` # 四、flask路由 ## 4.1 原始碼分析 ```python # 原始碼分析: ''' self.add_url_rule(rule, endpoint, f, **options) def add_url_rule( self, # app物件 rule, # url路由 endpoint=None, # 路由別名 view_func=None, # 響應的函式名 provide_automatic_options=None, **options ): methods :['POST','GET'] —》控制請求方法,不傳預設只能GET方法 ''' # @app.route的本質就在執行add_url_rule # rule是路由,endpoint是路由別名,view_func是響應函式 # 如果endpoint不傳就是響應的函式名 ``` ## 4.2 使用1:起別名 ```python from flask import Flask , url_for, redirect app = Flask(__name__) def index(): print() return "我是index" # @app.route('/detail/',methods=['GET','POST'],endpoint='detail') # 典型寫法 @app.route('/login') def login(): print(url_for("index1")) # 走的別名,如果別名沒有寫,預設函式名 return redirect(url_for("index1")) # 走的別名,如果別名沒有寫,預設函式名 # url_for: # 用endpoint獲取路由要用url_for 在flask中匯入,也就是反向解析 # 路由 app.add_url_rule('/', endpoint='index1', view_func=index) # endpoint 別名 # view_func 響應的函式名 if __name__ == '__main__': app.run() ``` ## 4.3 使用2:有名分組 ```python from flask import Flask , url_for, redirect app = Flask(__name__) # 必須接收分組名字一樣的 nid def index(nid): print(nid) return "我是index" # 路由:有名分組 app.add_url_rule('/index/', endpoint='index1', view_func=index, methods=['POST','GET']) app.add_url_rule('/index/', endpoint='index', view_func=index, methods=['POST','GET']) # 瀏覽器:http://127.0.0.1:5000/index/asdfas # string、int 規定接收的型別 if __name__ == '__main__': app.run() ``` ## 4.4 路由小總結 ```python 總結: 1 @app.route("/login") 的本質app.add_url_rule("/login",view_func=login),所以我們就可以用這兩個方式來新增路由 2 路由的引數, 2.1 endpoint,做是反向解析,如果上面新增路由的時候,沒有傳遞endpoint就是使用響應函式的函式名,反向解析用url_for(),做解析,這個url_for必須在flask裡面匯入 2.2 methods=["POST","GET"],該引數控制路由允許哪些請求方法訪問,如果不傳,預設只能GET方法 2.3 路由以及路由路由轉化器。"/index/",<引數的型別:用哪個變數來接收>,響應函式中的形參的名字必須轉化器中一致。 ``` ## 4.5 預設轉化器 ```python DEFAULT_CONVERTERS = { 'default': UnicodeConverter, 'string': UnicodeConverter, 'any': AnyConverter, 'path': PathConverter, 'int': IntegerConverter, 'float': FloatConverter, 'uuid': UUIDConverter, } ``` ## 4.6 自定義轉換器 ```python #1 寫類,繼承BaseConverter #2 註冊:app.url_map.converters['regex'] = RegexConverter # 3 使用:@app.route('/index/') 正則表示式會當作第二個引數傳遞到類中 from flask import Flask, views, url_for from werkzeug.routing import BaseConverter app = Flask(import_name=__name__) # 1.寫類 class RegexConverter(BaseConverter): """ 自定義URL匹配正則表示式 """ def __init__(self, map, regex): super(RegexConverter, self).__init__(map) self.regex = regex def to_python(self, value): """ 路由匹配時,匹配成功後傳遞給檢視函式中引數的值 """ return int(value) def to_url(self, value): """ 使用url_for反向生成URL時,傳遞的引數經過該方法處理,返回的值用於生成URL中的引數 """ val = super(RegexConverter, self).to_url(value) return val # 2.註冊:新增到flask中 app.url_map.converters['regex'] = RegexConverter # 正則匹配處理結果,要交給to_python,to_python函式可以對匹配處理結果做處理 # 3.使用: @app.route('/index/') def index(nid): print(url_for('index', nid='888')) return 'Index' if __name__ == '__main__': app.run() ``` ## 4.7 自定義轉換器總結 ```python 1 匯入from werkzeug.routing import BaseConverter 2 我寫個繼承BaseConverter。實現3個方法,def __init__ , def to_python , def to_url 3 將上面的類註冊到app.url_map.converters['regex'] = RegexConverter中 4 然後就可以在路由轉化器中使用3中的regex("傳正則") 5 當路由被訪問以後。regex("傳正則")會匹配結果,把結果傳遞to_python,我們可以進行再次處理,to_python處理好的結果,會傳遞給響應函式的形參 6 當用url做反向解析的時候,傳遞給路由轉化器的引數,會經過to_url,進行處理。處理以後,在拼接到路由。 ``` # 五、flask模板渲染 py檔案: ```python from flask import Flask,render_template,Markup app = Flask(__name__) app.debug = True USERS = { 1:{'name':'張三','age':18,'gender':'男','text':"道路千萬條"}, 2:{'name':'李四','age':28,'gender':'男','text':"安全第一條"}, 3:{'name':'王五','age':18,'gender':'女','text':"行車不規範"}, } def func1(arg,tank): return Markup(f"

餅哥正帥,{arg} is sb {tank} is same as {arg}

") @app.route("/") def index(): # data = { # "user" :USERS, # "name": "jason" # } return render_template("index.html",user = USERS,name="jason",ht1 = func1,ht="

餅哥正帥

") #return render_template("index.html",**data) if __name__ == '__main__': app.run() ``` ## 1.變數的迴圈 ```html Title

我是html

{% for k,v in user.items() %} {% endfor %} ``` ## 2.邏輯判斷 ```html Title

使用者列表

{{ k }} {{ v.name }} {{ v['name'] }} {{ v.get('name') }} {{url_for("index")}}
{% if name %}

Hello {{ name }}!

{% else %}

Hello World!

{% endif %}
``` ## 3.執行函式,傳引數 **比django中多可以加括號,執行函式,傳引數** ```python from flask import Flask,render_template,Markup app = Flask(__name__) app.debug = True def func1(arg,tank): return Markup(f"

餅哥正帥,{arg} is sb {tank} is same as {arg}

") @app.route("/") def index(): # data = { # "user" :USERS, # "name": "jason" # } return render_template("index.html",ht1 = func1,ht="

餅哥正帥

") if __name__ == '__main__': app.run() ``` html檔案 ```html Title {{ ht|safe}} {{ht1("jaosn","tank")}} // 傳引數 ``` # 六、flask的請求與響應 ```python from flask import Flask, request, make_response app = Flask(__name__) app.debug = True @app.route('/', methods=['POST', 'GET']) def index(): # print('請求方法',request.method) # 請求方法 # print('get請求的引數',request.args) # get 請求的引數 # print('post請求的引數',request.form) # post請求的引數 # print('請求的cookies',request.cookies) # 請求的cookies # print('post與get的所有引數',request.values) # post與get的所有引數 # 響應頭,新增make_response response = make_response('ok') #response = make_response(render_template("login.html")) # 設定cookie response.set_cookie("key","val") return 'OK' if __name__ == '__main__': app.run() ``` **請求相關的資訊** ```python 請求相關的資訊 print("請求方法",request.method)#請求方法 print("get請求的引數",request.args)# get請求的引數 print("post請求的引數",request.form) #post請求的引數 print("post,與get的所有引數",request.values)#post,與get的所有引數 print("請求的cookies",request.cookies)#請求的cookies 請求相關資訊 request.method 提交的方法 request.args get請求提及的資料 request.form post請求提交的資料 request.values post和get提交的資料總和 request.cookies 客戶端所帶的cookie request.headers 請求頭 request.path 不帶域名,請求路徑 request.full_path 不帶域名,帶引數的請求路徑 request.script_root request.url 帶域名帶引數的請求路徑 request.base_url 帶域名請求路徑 request.url_root 域名 request.host_url 域名 request.host 127.0.0.1:500 ``` # 七、設定cookies ```python from flask import Flask, make_response app = Flask(__name__) app.debug = True @app.route('/', methods=['POST', 'GET']) def index(): # 響應頭,新增make_response response = make_response('ok') # 設定cookies response.set_cookie('key', 'val') # 刪除cookies response.delete_cookie("key") # 設定響應頭 response.headers["x-somexx"] = "A SB" return response if __name__ == '__main__': app.run() ``` ## 設定cookie的引數 ```python key, 鍵 value='', 值 max_age=None, 超時時間 cookie需要延續的時間(以秒為單位)如果引數是\ None`` ,這個cookie會延續到瀏覽器關閉為止 expires=None, 超時時間(IE requires expires, so set it if hasn't been already.) path='/', Cookie生效的路徑,/ 表示根路徑,特殊的:根路徑的cookie可以被任何url的頁面訪問,瀏覽器只會把cookie回傳給帶有該路徑的頁面,這樣可以避免將cookie傳給站點中的其他的應用。 domain=None, Cookie生效的域名 你可用這個引數來構造一個跨站cookie。如, domain=".example.com"所構造的cookie對下面這些站點都是可讀的:www.example.com 、 www2.example.com 和an.other.sub.domain.example.com 。如果該引數設定為 None ,cookie只能由設定它的站點讀取 secure=False, 瀏覽器將通過HTTPS來回傳cookie httponly=False 只能http協議傳輸,無法被JavaScript獲取(不是絕對,底層抓包可以獲取到也可以被覆蓋) ``` # 八、flask的session cookie:存放在客戶端的鍵值對 session:存放在客戶端的鍵值對 token:存放在客戶端,通過演算法來校驗 ## 8.1 設定session(使用版) **在使用session之前必須現在設定一下金鑰** ``` app.secret_key="asdas" # 值隨便 # app.config['SECRET_KEY'] = os.urandom(24) # 配置session使用的祕鑰 ``` ```python 設定:session['username'] = 'xxx' # 在django中發什麼三件事,1,生成一個隨機的字串 2 往資料庫存 3 寫入cookie返回瀏覽器 # 在flask中他沒有資料庫,但session是怎樣實現的? # 生成一個金鑰寫入這個cookie,然後下次請求的時候,通過這個cookie解密,然後賦值給session # 我們通過app.session_interface來檢視 刪除:session.pop('username', None) ``` Flask提供了session物件用來將cookie加密儲存,session通過祕鑰對資料進行簽名以加密資料。 ```python from flask import Flask, session import os app = Flask(__name__) app.config['SECRET_KEY'] = os.urandom(24) # 配置session使用的祕鑰 @app.route('/') def set_session_info(): session['username'] = 'mark' # 使用使用者資訊配置sesion資訊作為cookie,並新增到響應體中 return '設定session資訊' ``` session物件像可以字典一樣操作,內部是把字典的資訊進行加密操作然後新增到相應體中作為cookie,響應的時候會自動返回給瀏覽器。 ```python session['username'] = 'mark' session['userphone'] = '123456' # 可以指定多條session資訊,統一放到響應的cookie中返回給瀏覽器 ``` ## 8.2 設定session(分析版) ```python from flask import Flask,session app = Flask(__name__) # 要用session,必須app配置一個金鑰 app.secret_key = "asdasdihasdiuh" # 設定session的名字,預設配置檔案中為:session app.config['SESSION_COOKIE_NAME']="python13session" # app.session_interface #app.session_interface實現了兩個方法,一個叫save_session,一個open_session, # save_session 存session執行,加密設定cookies # open_session 取session執行,解密大字典,拿到session @app.route("/",) def index(): #如何設定sessoion # 1 匯入session # 2 給sessoion設定值 session['name'] = "egon" return "ok" @app.route("/login") def login(): print(session["name"]) return "login" if __name__ == '__main__': app.run() ``` ## 8.3 設定session有效期 ```python 後端Flask跟瀏覽器互動預設情況下,session cookie會在使用者關閉瀏覽器時清除。通過將session.permanent屬性設為True可以將session的有效期延長為31天,也可以通過操作app的配置PERMANENT_SESSION_LIFETIME來設定session過期時間。 案例 3.3.2.1:開啟指定session過期時間模式 from flask import Flask, session import os app = Flask(__name__) app.config['SECRET_KEY'] = os.urandom(24) @app.route('/') def set_session_info(): session['username'] = 'mark' session['userphone'] = '123456' session.permanent = True # 開啟設定有效期,預設為31天后過期 return 'Hello World!' ... ``` **設定自定義過期時間** ```python # 通過設定PERMANENT_SESSION_LIFETIME指定具體的過期時間 from datetime import timedelta app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=1) # 設定為1小時候過期 ``` ## 8.4 獲取sessoin 在Flask中獲取設定的session資訊通過session物件獲取,session物件是繼承了字典類,所以獲取的時候是字典的取值方式。其內部會把瀏覽器傳過來的session資訊解密。 ```python @app.route('/get_session/') def get_session(): username = session.get('username') userphone = session.get('userphone') if username or userphone: return "{},{}".format(username, userphone) return "session為空" ``` ![1550930691065](https://img2018.cnblogs.com/blog/1825659/201910/1825659-20191012155137512-1371028199..png) ## 8.5 刪除session `session`物件呼叫`pop()`可以根據具體的`session`的key清除掉指定的session資訊。 session物件呼叫`clear()`可以清除此次請求的瀏覽器關於本域名的所有session資訊 ```python @app.route('/del_session/') def del_session(): session.pop('username') # session.clear() return '刪除成功' ``` ## 8.6 原始碼分析 **session原始碼的執行流程** ``` -save_seesion -響應的時候,把session中的值加密序列化放大到了cookie中,返回到瀏覽器中 -open_session -請求來了,從cookie中取出值,反解,生成session物件,以後再檢視函式中直接用sessoin就可以了。 ``` **原始碼分析** ```python # app.session_interface 點進去 class SecureCookieSessionInterface(SessionInterface): salt = "cookie-session" digest_method = staticmethod(hashlib.sha1) key_derivation = "hmac" serializer = session_json_serializer session_class = SecureCookieSession def get_signing_serializer(self, app): if not app.secret_key: return None signer_kwargs = dict( key_derivation=self.key_derivation, digest_method=self.digest_method ) return URLSafeTimedSerializer( app.secret_key, salt=self.salt, serializer=self.serializer, signer_kwargs=signer_kwargs, ) # 取session的時候執行的 def open_session(self, app, request): s = self.get_signing_serializer(app) if s is None: return None ##cookie鍵是SESSION_COOKIE_NAME"=session val = request.cookies.get(app.session_cookie_name) print("open_session.session_cookie_name,", app.session_cookie_name, ) if not val: return self.session_class() max_age = total_seconds(app.permanent_session_lifetime) try: data = s.loads(val, max_age=max_age) print("self.session_class(data)", self.session_class(data) ) return self.session_class(data) except BadSignature: return self.session_class() #存session的時候執行的 def save_session(self, app, session, response): domain = self.get_cookie_domain(app) path = self.get_cookie_path(app) # If the session is modified to be empty, remove the cookie. # If the session is empty, return without setting the cookie. if not session: if session.modified: response.delete_cookie( app.session_cookie_name, domain=domain, path=path ) return # Add a "Vary: Cookie" header if the session was accessed at all. if session.accessed: response.vary.add("Cookie") if not self.should_set_cookie(app, session): return httponly = self.get_cookie_httponly(app) secure = self.get_cookie_secure(app) samesite = self.get_cookie_samesite(app) expires = self.get_expiration_time(app, session) # 把session做了一個加密,把整個session的key--》val,全部加密,的到一個value值, #session是一個大字典, val = self.get_signing_serializer(app).dumps(dict(session)) # 他把session加密後得到的val存到cookie裡面了 #cookie鍵是SESSION_COOKIE_NAME"=session print("原始碼中的session",dict(session)) print("app.session_cookie_name,",app.session_cookie_name,) response.set_cookie( app.session_cookie_name, val, expires=expires, httponly=httponly, domain=domain, path=path, secure=secure, samesite=samesite, ) ``` # 九、閃現 ## 9.1 什麼是閃現? ![1554289488442](https://img2018.cnblogs.com/blog/1825659/201910/1825659-20191012155137970-224747758..png) ```python -設定:flash('aaa') -取值:get_flashed_message() - -假設在a頁面操作出錯,跳轉到b頁面,在b頁面顯示a頁面的錯誤資訊 ``` ```python from flask import Flask,flash,get_flashed_messages app = Flask(__name__) #app.session_interface app.secret_key ="sdasd" # 什麼閃現:就像session一樣,也是一個頁面設定,另一個頁面使用,我不管你在哪個頁面呼叫的 # 只要呼叫一次,就清空了, # 閃現的作用,一般用資訊處理。假設使用者,a頁面做操作,產生了資訊。我希望在b頁面內獲取。 # 但是我不知道使用者在什麼時候,訪問b頁面,但是隻要使用者一旦訪問頁面就把資訊顯示出來。 # 同一頁面,同次請求是可以拿多次的 @app.route("/") def index(): #產生資訊,message設定訊息的,category給訊息分類,如果不傳預設用”message“ flash("你錯過了我") flash(message="你再次錯過我",category="渣男") return "index" @app.route("/login") def login(): #(with_categories=True,訊息是否要帶上分類資訊,category_filter=["渣男"]對訊息進行過濾,取指定的分類訊息 print(get_flashed_messages(with_categories=True,category_filter=["渣男"])) print(get_flashed_messages()) return "login" @app.route("/test") def test(): print(get_flashed_messages()) return "test" if __name__ == '__main__': app.ru