1、cookie和session
- cookie:在網站中,http請求是無狀態的。也就是說即使第一次和服務器連接後並且登錄成功後,第二次請求服務器依然不能知道當前請求是哪個用戶。cookie的出現就是爲了解決這個問題,第一次登錄後服務器返回一些數據(cookie)給瀏覽器,然後瀏覽器保存在本地,當該用戶發送第二次請求的時候,就會自動的把上次請求存儲的cookie數據自動的攜帶給服務器,服務器通過瀏覽器攜帶的數據就能判斷當前用戶是哪個了。cookie存儲的數據量有限,不同的瀏覽器有不同的存儲大小,但一般不超過4KB。因此使用cookie只能存儲一些小量的數據。
@app.route('/')
def index():
# return '123'
res = Response("首頁")
res.set_cookie('abc', '123', max_age=3) # key=abc, value=123, 3s後過期
''' set_cookie()源碼
def set_cookie(
self,
key,
value="",
max_age=None, # 失效時間
expires=None, # datetime形式的失效時間
path="/", # 在該路徑下才能設置cookie
domain=None, # 域名限制
secure=False, # 僅可通過HTTPS使用
httponly=False,
samesite=None,
):
:param key: the key (name) of the cookie to be set.
:param value: the value of the cookie.
:param max_age: should be a number of seconds, or `None` (default) if
the cookie should last only as long as the client's
browser session.
:param expires: should be a `datetime` object or UNIX timestamp.
:param path: limits the cookie to a given path, per default it will
span the whole domain.
:param domain: if you want to set a cross-domain cookie. For example,
``domain=".example.com"`` will set a cookie that is
readable by the domain ``www.example.com``,
``foo.example.com`` etc. Otherwise, a cookie will only
be readable by the domain that set it.
:param secure: If `True`, the cookie will only be available via HTTPS
'''
return res
-
session: session和cookie的作用有點類似,都是爲了存儲用戶相關的信息。不同的是,cookie是存儲在本地瀏覽器,session是一個思路、一個概念、一個服務器存儲授權信息的解決方案,不同的服務器,不同的框架,不同的語言有不同的實現。雖然實現不一樣,但是他們的目的都是服務器爲了方便存儲數據的。session的出現,是爲了解決cookie存儲數據不安全的問題的。
-
cookie和session結合使用:web開發發展至今,cookie和session的使用已經出現了一些非常成熟的方案。在如今的市場或者企業裏,一般有兩種存儲方式:
• 存儲在服務端:通過cookie存儲一個session_id,然後具體的數據則是保存在session中。如果用戶已經登錄,則服務器會在cookie中保存一個session_id,下次再次請求的時候,會把該session_id攜帶上來,服務器根據session_id在session庫中獲取用戶的session數據。就能知道該用戶到底是誰,以及之前保存的一些狀態信息。這種專業術語叫做server side session。存儲在服務器的數據會更加的安全,不容易被竊取。但存儲在服務器也有一定的弊端,就是會佔用服務器的資源,但現在服務器已經發展至今,一些session信息還是綽綽有餘的。(Django框架採用這種模式)。
• 將session數據加密,然後存儲在cookie中:這種專業術語叫做client side session。flask採用的就是這種方式,但是也可以替換成其他形式。
from flask import Flask, session
import os
print(os.urandom(24)) # 輸出的是24位隨機的字符串 當做secret_key使用
app = Flask(__name__)
# 加密session中的 secret_key
app.config['SECRET_KEY'] = os.urandom(24)
@app.route("/")
def index():
# session 字典類型
session['username'] = 'xxx'
session['user_id'] = 123
return "1231231"
if __name__ == '__main__':
app.run(debug=True)
flask中使用cookie和session
- cookies:在Flask中操作cookie,是通過response對象來操作,可以在response返回之前,通過response.set_cookie來設置,這個方法有以下幾個參數需要注意:
- key:設置的cookie的key。
- value:key對應的value。
- max_age:改cookie的過期時間,如果不設置,則瀏覽器關閉後就會自動過期。
- expires:過期時間,應該是一個datetime類型。
- domain:該cookie在哪個域名中有效。一般設置子域名,比如cms.example.com。
- path:該cookie在哪個路徑下有效。
- session:Flask中的session是通過from flask import session。然後添加值key和value進去即可。並且,Flask中的session機制是將session信息加密,然後存儲在cookie中。專業術語叫做client side session。
from flask import Flask, session
import os
from datetime import timedelta # 導入時間
# print(os.urandom(24)) # 輸出的是24位隨機的字符串 當做secret_key使用
app = Flask(__name__)
# 加密session中的 secret_key
app.config['SECRET_KEY'] = os.urandom(24)
# 持久化時間設置
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=2) # 2小時session過期
@app.route("/login/")
def login():
# session 字典類型 登錄操作,記錄信息傳輸到數據庫
session['username'] = 'xxxxxx'
session['user_id'] = 123
# 持久化 有效期
session.permanent = True
return "登錄界面"
@app.route("/")
def index():
username = session.get('username')
print(username)
return "首頁界面"
@app.route("/logout/")
def logout():
# 刪除session中的username
session.pop('username')
# 清空session中的所有數據
session.clear()
return "退出登錄"
if __name__ == '__main__':
app.run(debug=True)
2、Flask上下文
Flask項目中有兩個上下文,一個是應用上下文(app),另外一個是請求上下文(request)。請求上下文request和應用上下文current_app都是一個全局變量。所有請求都共享的。Flask有特殊的機制可以保證每次請求的數據都是隔離的,即A請求所產生的數據不會影響到B請求。所以可以直接導入request對象,也不會被一些髒數據影響了,並且不需要在每個函數中使用request的時候傳入request對象。這兩個上下文具體的實現方式和原理可以沒必要詳細瞭解。
請求上下文
- request:請求上下文上的對象。這個對象一般用來保存一些請求的變量。比如method、args、form等。
- session:請求上下文上的對象。這個對象一般用來保存一些會話信息。
應用上下文
- current_app:返回當前的app。
- g:應用上下文上的對象。處理請求時用作臨時存儲的對象。
# -*- encoding: utf-8 -*-
"""
@File : session_demo.py
@Time : 2020/5/8 9:57
@Author : chen
"""
from flask import Flask, session, request, current_app
import os
from datetime import timedelta # 導入時間
from utils import log_a, log_b # 導入記錄模塊
import config
# print(os.urandom(24)) # 輸出的是24位隨機的字符串 當做secret_key使用
app = Flask(__name__) # current_app代表app
# 加密session中的 secret_key
app.config['SECRET_KEY'] = os.urandom(24)
# 持久化時間設置
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=2) # 2小時session過期
@app.route("/login/")
def login():
# session 字典類型 登錄操作,記錄信息傳輸到數據庫
session['username'] = 'xxxxxx'
session['user_id'] = 123
# 持久化 有效期
session.permanent = True
return "登錄界面"
# 需要在視圖函數中調用 不能在視圖函數外使用
# print(current_app.name)
@app.route("/")
def index():
username = session.get('username')
# 採用g對象,每次刷新頁面,會被清空,臨時對象
g.username = username
log_a() # 這裏可以不用傳參 g對象是全局
log_b()
# print(username)
print(current_app.name) # 輸出當前app的名字 需要在視圖函數中調用
# 不是讀取config.py中的數據,查看的是app綁定的配置
# print(current_app.config['HOST'])
print(current_app.config['SECRET_KEY']) # 輸出配置中的數據
return "首頁界面"
@app.route("/logout/")
def logout():
# 刪除session中的username
session.pop('username')
# 清空session中的所有數據
session.clear()
return "退出登錄"
if __name__ == '__main__':
app.run(debug=True)
# -*- encoding: utf-8 -*-
"""
@File : utils.py
@Time : 2020/5/9 17:18
@Author : chen
"""
# utils.py 工具文件 發送驗證碼等,隨機生成字符串等
# 記錄登錄的信息
def log_a(username):
print("log a %s"% username)
def log_b(username):
print("log b %s" % username)
g對象的傳參
# -*- encoding: utf-8 -*-
"""
@File : utils.py
@Time : 2020/5/9 17:18
@Author : chen
"""
# utils.py 工具文件 發送驗證碼等,隨機生成字符串等
from flask import g # 導入g對象
# 記錄登錄的信息
def log_a():
print("log a %s"% g.username)
def log_b():
print("log b %s" % g.username)
常用的鉤子函數
- before_first_request:處理第一次請求之前執行。
@app.before_first_request
def first_request():
print 'first time request'
- before_request:在每次請求之前執行。通常可以用這個裝飾器來給視圖函數增加一些變量。
@app.before_request
def before_request():
if not hasattr(g,'user'):
setattr(g,'user','xxxx')
- teardown_appcontext:不管是否有異常,註冊的函數都會在每次請求之後執行。
@app.teardown_appcontext
def teardown(response):
print("teardown 被執行")
return respons
- context_processor:上下文處理器。返回的字典中的鍵可以在模板上下文中使用。
@app.context_processor
def context_processor():
return {'current_user':'xxx'}
- errorhandler:errorhandler接收狀態碼,可以自定義返回這種狀態碼的響應的處理方法。
@app.errorhandler(404)
def page_not_found(error):
return 'This page does not exist',404
鉤子函數整體代碼文件hook_demo.py
# -*- encoding: utf-8 -*-
"""
@File : hook_demo.py
@Time : 2020/5/9 20:27
@Author : chen
"""
from flask import Flask, render_template, abort
app = Flask(__name__)
@app.route("/")
def index():
# 主動拋出異常的同時需要errorhandler模塊捕獲到這個異常
abort(404) # 此時訪問首頁的狀態碼就是404了,直接運行errorhandler(404)中的方法
# 這種主動拋出異常方式無法被捕獲到
# raise IndexError(" index error!")
print("這是首頁")
# return "這是首頁"
return render_template("index.html")
# 處理第一次請求之前執行。
@app.before_first_request
def handel_first_request(): # 只執行一次,刷新後不會再執行
print("1 這是第一次請求之前執行的")
# before_request:在每次請求之前執行。通常可以用這個裝飾器來給視圖函數增加一些變量。
@app.before_request
def handel_before_request():
print("2 每次請求之前執行")
# after_request: 在每次請求之後執行
@app.after_request
def handel_after_request(response):
print("3 每次請求之後執行的")
return response
# teardown_appcontext:不管是否有異常,註冊的函數都會在每次請求之後執行。
@app.teardown_appcontext
def handel_teardown_appcontext(response):
print("4 註冊的函數都會在每次請求之後執行")
return response
# context_processor:上下文處理器。返回的字典中的鍵可以在模板上下文中使用。
@app.context_processor
def handel_context_processor():
print("5 上下文處理器。返回的字典中的鍵可以在模板上下文中使用。")
return {"username": "abcd"} # 將這個參數變量映射到模板html文件中,
# 錯誤異常的捕獲
# errorhandler:errorhandler接收狀態碼,可以自定義返回這種狀態碼的響應的處理方法。
@app.errorhandler(404)
def handel_errorhandler(error): # error必須傳遞
print("6 errorhandler接收狀態碼,可以自定義返回這種狀態碼的響應的處理方法。")
# return "頁面不存在", 404 # 404網頁訪問狀態碼
return render_template("404.html"), 404 # 404網頁訪問狀態碼
@app.errorhandler(500)
def server_error(error): # error必須傳遞
return "服務器錯誤!", 500 # 500 網頁響應狀態碼
if __name__ == '__main__':
# 項目上線之後 部署到服務器,debug模式需要關閉,因爲報錯信息不能給客戶看
app.run(debug=True, port=9999)
404.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>頁面不見了!!!!</p>
</body>
</html>
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- 使用context_processor:上下文處理器進行參數傳遞 -->
{{ username }}
</body>
</html>
3、Restful API規範
restful api是用於在前端與後臺進行通信的一套規範。使用這個規範可以讓前後端開發變得更加輕鬆。以下將討論這套規範的一些設計細節。
協議
採用http或者https協議。
數據傳輸格式
數據之間傳輸的格式應該都使用json,而不使用xml。
url鏈接
url鏈接中,不能有動詞,只能有名詞。並且對於一些名詞,如果出現複數,那麼應該在後面加s。
HTTP請求的方法
- GET:從服務器上獲取資源。
- POST:在服務器上新創建一個資源。
- PUT:在服務器上更新資源。(客戶端提供所有改變後的數據)
- PATCH:在服務器上更新資源。(客戶端只提供需要改變的屬性)
- DELETE:從服務器上刪除資源。
示例:
- GET /users/:獲取所有用戶。
- POST /user/:新建一個用戶。
- GET /user/id/:根據id獲取一個用戶。
- PUT /user/id/:更新某個id的用戶的信息(需要提供用戶的所有信息)。
- PATCH /user/id/:更新某個id的用戶信息(只需要提供需要改變的信息)。
- DELETE /user/id/:刪除一個用戶。
4、Flask-Restful插件
Flask-Restful是一個專門用來寫restful api的一個插件。使用他可以快速的集成restful api功能。在app的後臺以及純api的後臺中,這個插件可以幫助我們節省很多時間。當然,如果在普通的網站中,這個插件就顯得有些雞肋了,因爲在普通的網頁開發中,是需要去渲染HTML代碼的,而Flask-Restful在每個請求中都是返回json格式的數據。
安裝
Flask-Restful需要在Flask 0.8以上的版本,在Python2.6或者Python3.3上運行。通過pip install flask-restful即可安裝。
定義Restful的視圖
如果使用Flask-Restful,那麼定義視圖函數的時候,就要繼承自flask_restful.Resource類,然後再根據當前請求的method來定義相應的方法。比如期望客戶端是使用get方法發送過來的請求,那麼就定義一個get方法。類似於MethodView。
from flask import Flask,render_template,url_for
from flask_restful import Api, Resource
app = Flask(__name__)
# 用Api來綁定app
api = Api(app)
class IndexView(Resource): # 類視圖不同於之前繼承View
def get(self):
return {"username":"xxx"} # 返回數據
def post(self):
return {"info": "登陸成功"}
api.add_resource(IndexView, '/', endpoint='index') # 類視圖綁定
if __name__ == '__main__':
app.run(debug=True)
注意事項
- endpoint是用來給url_for反轉url的時候指定的。如果不寫endpoint,那麼將會使用視圖的名字的小寫來作爲endpoint。
- add_resource的第二個參數是訪問這個視圖函數的url,這個url可以跟之前的route一樣,可以傳遞參數。並且還有一點不同的是,這個方法可以傳遞多個url來指定這個視圖函數。
Postman軟件測試post方法傳遞參數是否連接成功
5、 參數解析
Flask-Restful插件提供了類似WTForms來驗證提交的數據是否合法的包,叫做reqparse。
add_argument可以指定這個字段的名字,這個字段的數據類型等。
- default:默認值,如果這個參數沒有值,那麼將使用這個參數指定的值。
- required:是否必須。默認爲False,如果設置爲True,那麼這個參數就必須提交上來。
- type:這個參數的數據類型,如果指定,那麼將使用指定的數據類型來強制轉換提交上來的值。
- choices:選項。提交上來的值只有滿足這個選項中的值才符合驗證通過,否則驗證不通過。
- help:錯誤信息。如果驗證失敗後,將會使用這個參數指定的值作爲錯誤信息。
- trim:是否要去掉前後的空格。
from flask import Flask, render_template, url_for
from flask_restful import Api, Resource, reqparse, inputs # reqparse 類似WTForms來驗證提交的數據是否合法 ,inputs驗證email,url等數據
app = Flask(__name__)
# 用Api來綁定app
api = Api(app)
class IndexView(Resource): # 類視圖不同於之前繼承View
def get(self):
return {"username": "xxx"} # 返回數據
def post(self):
# 類似WTForms來驗證提交的數據是否合法
parse = reqparse.RequestParser()
# 傳參 help=錯誤信息 required=True必須提供該參數
parse.add_argument('username', type=str, help='用戶名驗證錯誤', required=True)
parse.add_argument('password', type=str, help='用戶名密碼錯誤', required=True)
parse.add_argument('age', type=int, help='用戶年齡錯誤')
parse.add_argument('gender', type=str, help='用戶性別錯誤', choices=['male', 'female'])
# url驗證\郵箱驗證
parse.add_argument('homepage', type=inputs.url, help='網頁鏈接錯誤', required=True)
# regex 正則表達式 手機號碼驗證
parse.add_argument('phone', type=inputs.regex(r'1[3456]\d{9}'), help='手機號碼錯誤', required=True)
# 驗證參數是否傳遞成功
args = parse.parse_args()
print(args)
return {"info": "登陸成功"}
api.add_resource(IndexView, '/', endpoint='index') # 類視圖綁定
if __name__ == '__main__':
app.run(debug=True)
6、 輸出字段
對於一個視圖函數,你可以指定好一些字段用於返回。以後可以使用ORM模型或者自定義的模型的時候,他會自動的獲取模型中的相應的字段,生成json數據,然後再返回給客戶端。這其中需要導入flask_restful.marshal_with裝飾器。並且需要寫一個字典,來指示需要返回的字段,以及該字段的數據類型。
from flask import Flask, render_template, url_for
from flask_restful import Api, Resource, reqparse, inputs # reqparse 類似WTForms來驗證提交的數據是否合法 ,inputs驗證email,url等數據
from flask_restful import fields, marshal_with # fields用於輸出字段 marshal_with裝飾器關聯返回字段信息
app = Flask(__name__)
# 用Api來綁定app
api = Api(app)
class ArticleView(Resource): # 類視圖不同於之前繼承View,這裏需要繼承Resource
resource_fields = { # 返回字段信息
'title': fields.String,
'content': fields.String,
}
@marshal_with(resource_fields) # 關聯返回字段信息
def get(self):
return {'title': 'abcdefg'}
api.add_resource(ArticleView, '/article/', endpoint='article') # 類視圖綁定
if __name__ == '__main__':
app.run(debug=True)
重命名屬性
很多時候你面向公衆的字段名稱是不同於內部的屬性名。使用 attribute可以配置這種映射。比如現在想要返回user.school中的值,但是在返回給外面的時候,想以education返回回去,那麼可以這樣寫
resource_fields = {
'education': fields.String(attribute='school')
}
默認值
在返回一些字段的時候,有時候可能沒有值,那麼這時候可以在指定fields的時候給定一個默認值
resource_fields = {
'age': fields.Integer(default=18)
}
複雜結構
有時候想要在返回的數據格式中,形成比較複雜的結構。那麼可以使用一些特殊的字段來實現。比如要在一個字段中放置一個列表,那麼可以使用fields.List,比如在一個字段下面又是一個字典,那麼可以使用fields.Nested。
class ProfileView(Resource):
resource_fields = {
'username': fields.String,
'age': fields.Integer,
'school': fields.String,
'tags': fields.List(fields.String),
'more': fields.Nested({
'signature': fields.String
})
}
實例代碼如下
主體代碼文件: flask_restful_demo1.py
from flask import Flask, render_template, url_for
from flask_restful import Api, Resource, reqparse, inputs # reqparse 類似WTForms來驗證提交的數據是否合法 ,inputs驗證email,url等數據
from flask_restful import fields, marshal_with # fields用於輸出字段 marshal_with裝飾器關聯返回字段信息
import config # 導入配置文件
from exts import db # 第三方文件防止互相引用
from model import Article # 導入模型
app = Flask(__name__)
# 加載配置
app.config.from_object(config)
db.init_app(app) # 綁定app
# 用Api來綁定app
api = Api(app)
class ArticleView(Resource): # 類視圖不同於之前繼承View,這裏需要繼承Resource
resource_fields = { # 返回字段信息
# 'title': fields.String,
# 修改給前端返回的字段信息 原本的模型中的屬性或者叫字段爲title,現在返回給前端的是article_title
'article_title': fields.String(attribute='title'),
'content': fields.String,
# 'author': fields.String,
'author': fields.Nested({ # 一對多關係
"username": fields.String,
"email": fields.String,
}),
# 'tag': fields.String,
'tag': fields.List(fields.Nested({ # 多對多關係
"id": fields.Integer,
"name": fields.String,
})),
# 模型中沒有的字段名和設置默認值
'read_count': fields.Integer(default=0)
}
@marshal_with(resource_fields) # 關聯返回字段信息
def get(self, article_id): # 傳參
article = Article.query.get(article_id)
return article # 返回模型,返回的數據中包含模型中的所有字段
# return {'title': 'abcdefg'}
api.add_resource(IndexView, '/', endpoint='index') # 類視圖綁定
# api.add_resource(ArticleView, '/article/', endpoint='article') # 類視圖綁定
api.add_resource(ArticleView, '/article/<article_id>', endpoint='article') # 傳參的時候路由也一起改變
if __name__ == '__main__':
app.run(debug=True)
模型文件model.py
# -*- encoding: utf-8 -*-
"""
@File : model.py
@Time : 2020/5/10 17:09
@Author : chen
"""
from exts import db
# 用戶
# 文章 用戶與文章是一對多關係
# 標籤 文章與標籤是多對多關係 此時需要建立中間表
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50))
email = db.Column(db.String(50))
# 中間表的創建 創建article和tag的中間表
article_tag_table = db.Table(
'article_tag',
db.Column('article_id', db.Integer, db.ForeignKey('article.id')),
db.Column('tag_id', db.Integer, db.ForeignKey('tag.id')),
)
class Article(db.Model):
__tablename__ = 'article'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(50))
context = db.Column(db.String(50))
# 外鍵
author_id = db.Column(db.Integer, db.ForeignKey('user.id'))
# 反向引用
author = db.relationship('User', backref='articles')
# 中間表建立連接關係
tags = db.relationship('Tag', secondary=article_tag_table, backref='tags')
class Tag(db.Model):
__tablename__ = 'tag'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
配置文件config.py
# -*- encoding: utf-8 -*-
"""
@File : config.py
@Time : 2020/5/10 17:08
@Author : chen
"""
HOSTNAME = '127.0.0.1'
DATABASE = 'demo0510'
PORT = 3306
USERNAME = 'root'
PASSWORD = 'root'
DB_URL = 'mysql+mysqlconnector://{}:{}@{}/{}?charset=utf8'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)
# engine = create_engine(DB_URL)
SQLALCHEMY_DATABASE_URI = DB_URL
SQLALCHEMY_TRACK_MODIFICATIONS = False
第三方引用文件exts.py :防止互相引用報錯
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
映射到數據庫文件manage.py
# -*- encoding: utf-8 -*-
"""
@File : manage.py
@Time : 2020/5/10 17:36
@Author : chen
"""
from flask_script import Manager
from flask_restful_demo import app # 需要將當前文件夾設置爲當前根目錄,纔不會報錯
from flask_migrate import Migrate, MigrateCommand
from exts import db
# 導入模型 才能映射到數據庫
import model
manage = Manager(app)
Migrate(app, db)
manage.add_command('db', MigrateCommand)
if __name__ == '__main__':
manage.run()