Restful API規範
restful API是用於在前端與後臺進行通信的一套規範,使用這個規範可以讓前後端開發變得更加輕鬆:
1. 協議:http或者https
2. 數據傳輸格式:json
3. url鏈接:url鏈接中,不能有動詞,只能有名詞,並且對於一些名詞,如果出現複數,就用複數的形式
4. 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/:刪除一個用戶
狀態碼:
狀態碼 | 原生描述 | 描述 |
200 | OK | 服務器成功響應客戶端請求 |
400 | INVALID REQUEST | 用戶發出的請求有誤,服務器沒有進行新建或者修改數據 |
401 | Unauthorized | 用戶沒有權限訪問這個請求 |
403 | Forbidden | 因爲某些原因禁止訪問這個請求 |
404 | NOT FOUND | 用戶請求的url不存在 |
406 | NOT Acceptable | 用戶請求不被服務器接受,例如服務器期望客戶端發送某個字段,但是沒有發送 |
500 | Internal Server error | 服務器內部錯誤,比如出現了bug |
Restful插件:
通過 pip install flask-restful安裝flask-restful插件
定義restful視圖函數:如果使用flask-restful,那麼定義視圖函數的時候,需要繼承自flask-restful.Resource這個類,然後再根據當前請求的method來定義響應的方法。類似於類視圖的功能,可以分別定義get方法與post方法。
工具:postman做接口測試
from flask import Flask, render_template, request
from flask_restful import Api, Resource
import config
app = Flask(__name__)
app.config.from_object(config)
api = Api(app=app)
class LoginView(Resource):
def post(self):
return {"username": "tom"}
api.add_resource(LoginView, '/login/', endpoint='login')
@app.route('/')
def index():
return render_template("html/index.html")
if __name__ == '__main__':
app.run()
注意事項:
1. endpoint是用來給url_for反轉url的時候使用的。如果不寫endpoint,那麼將會使用視圖的名字的小寫來作爲endpoint。
2. add_resource的第二個參數是訪問這個視圖函數的url,這個url和之前的route一樣,也可以傳遞參數,並且這個方法還可以傳遞多個url來指定這個視圖函數。
class LoginView(Resource):
# 如果add_resource綁定了多個url,如果沒有傳參,可以將視圖函數中的參數設置爲None
# 此時即使url中沒有傳遞參數,也不會報錯
def post(self, username=None):
return {"username": username}
api.add_resource(LoginView, '/login/<username>/', '/register/', endpoint='login')
注意事項:
1. 如果向返回json數據,那麼就使用flask-restful,如果向渲染模板,那麼好是採用視圖函數或者類視圖的方法。
2. url還是和之前的一樣,可以傳遞參數。不同的是視圖函數的是,可以綁定多個視圖函數。
flask-restful參數
flask-restful插件提供了類似於WTForms來驗證提交的數據是否合法,叫做reparse
class LoginView(Resource):
# 如果add_resource綁定了多個url,如果沒有傳參,可以將視圖函數中的參數設置爲None
# 此時即使url中沒有傳遞參數,也不會報錯
def post(self):
parse = reqparse.RequestParser()
parse.add_argument("username", type=str, help="用戶名驗證錯誤")
parse.add_argument("password", type=str, help="密碼驗證錯誤")
args = parse.parse_args()
return {"username": args.get("username"), "password": args.get("password")}
api.add_resource(LoginView, '/login/', endpoint='login')
在不傳參數的時候,返回的結果:
在post方法中傳遞參數之後,可以看到返回的結果:
add_argument可以指定的字段:
1. default: 當沒有傳遞相應參數的時候,設置這個參數的默認值
2. required:是否必須傳遞這一參數, 布爾值
3. type:指定提交的參數的類型,如果指定,那麼將使用指定的數據類型來強制轉換提交上來的值。
4. choices:選項,提交上來的值只有滿足這個選項中的值才能驗證通過
5. help:錯誤信息,如果驗證失敗,將會使用這個參數指定的值作爲錯誤信息
6. trim:是否要取出前後的空格
type字段的值,可以使用python內置的數據類型,也可以使用flask-restful.input中的類型,例如:
url:判斷傳入的參數是否爲url
regex:正則表達式
date:將這個字符串轉換爲datetime.date,如果轉換失敗,則會拋出異常
from flask import Flask, render_template, request
from flask_restful import Api, Resource, reqparse, inputs
import config
app = Flask(__name__)
app.config.from_object(config)
api = Api(app=app)
class LoginView(Resource):
# 如果add_resource綁定了多個url,如果沒有傳參,可以將視圖函數中的參數設置爲None
# 此時即使url中沒有傳遞參數,也不會報錯
def post(self):
parse = reqparse.RequestParser()
parse.add_argument("username", type=str, help="用戶名驗證錯誤", trim=True)
parse.add_argument("password", type=str, help="密碼驗證錯誤", trim=True)
parse.add_argument("age", type=int, help="年齡驗證錯誤", trim=True)
parse.add_argument("render", type=str, choices=["female", "male"], trim=True, help="性別填寫錯誤")
parse.add_argument("link", type=inputs.url, trim=True, help="url填寫錯誤")
parse.add_argument("phone", type=inputs.regex(r"1[3578]\d{9}"), trim=True, help="電話號碼格式錯誤")
args = parse.parse_args()
return {"username": args.get("username"),
"password": args.get("password"),
"age": args.get("age"),
"render": args.get("render")}
api.add_resource(LoginView, '/login/', endpoint='login')
@app.route('/')
def index():
return render_template("html/index.html")
if __name__ == '__main__':
app.run()
Flask-restful標準:
輸出字段:
對於一個視圖函數,可以指定好一些字段用於返回。在可以使用ORM模型或者自定義模型的時候,它會自動獲取模型中的相應字段,生成json數據,然後再返回給客戶端。這其中需要導入flask_restful.marshal_with裝飾器,並且需要寫一個字典,來指示需要返回的字段,以及該字段的數據類型。
from flask import Flask, render_template, request
from flask_restful import Api, Resource, fields, marshal_with
import config
app = Flask(__name__)
app.config.from_object(config)
api = Api(app=app)
class ArticleView(Resource):
resource_fields = {
"title": fields.String,
"content": fields.String
} # 指定需要返回的字段
@marshal_with(resource_fields)
def get(self):
return {"title": "xxx", "content": "yyy"}
api.add_resource(ArticleView, '/articles/', endpoint="articles")
@app.route('/')
def index():
return render_template("html/index.html")
if __name__ == '__main__':
app.run()
有的字段沒有值,在return的字典中沒有寫,也會返回爲null值:
class ArticleView(Resource):
resource_fields = {
"title": fields.String,
"content": fields.String
} # 指定需要返回的字段
@marshal_with(resource_fields)
def get(self):
return {"title": "xxx"} # 即使此時content字段沒有值,也會返回爲null,這樣就保證了接口比較規範
api.add_resource(ArticleView, '/articles/', endpoint="articles")
返回的結果:
這種方式還有一個好處,就是如果相應的數據是一個模型,則可以直接返回模型的對象即可,而不用構建字典
from flask import Flask, render_template, request
from flask_restful import Api, Resource, fields, marshal_with
import config
app = Flask(__name__)
app.config.from_object(config)
api = Api(app=app)
class Article(object):
def __init__(self, title, content):
self.title = title
self.content = content
article = Article(title="gone with wind", content="xsddkkjsdv")
class ArticleView(Resource):
resource_fields = {
"title": fields.String,
"content": fields.String
} # 指定需要返回的字段
@marshal_with(resource_fields)
def get(self):
return article # 可以根據article對象中的屬性自動構造字典,返回字典
api.add_resource(ArticleView, '/articles/', endpoint="articles")
@app.route('/')
def index():
return render_template("html/index.html")
if __name__ == '__main__':
app.run()
複雜結構:
返回的字段的值任然是一個模型,則需要使用Nested()方法再定義一個字典,返回相應的字段
# 定義一個restful視圖
class ArticleView(Resource):
resource_field = {
"title": fields.String,
"content": fields.String,
"author": fields.Nested({ # User類型
"username": fields.String,
"email": fields.String
}),
"tags": fields.Nested({
"name": fields.String
})
}
@marshal_with(resource_field)
def get(self, article_id):
article = db.session.query(Article).filter(Article.id == article_id).first()
return article
api.add_resource(ArticleView, '/articles/<article_id>/', endpoint="article")
使用field.Nested()之後,返回的的數據如下所示:
重命名屬性:
在讀取模型中的某個字段之後,需要用一個新的名字將其返回,可以使用attribute屬性實現:
例如,需要將article中的title屬性返回,並賦予一個新的屬性名:
# 定義一個restful視圖
class ArticleView(Resource):
resource_field = {
"article_title": fields.String(attribute="title"), # 改變屬性的名字
"article_content": fields.String(attribute="content"),
"article_author": fields.Nested({ # User類型
"username": fields.String,
"email": fields.String
}, attribute="author"),
"article_tags": fields.Nested({
"name": fields.String
}, attribute="tags")
}
@marshal_with(resource_field)
def get(self, article_id):
article = db.session.query(Article).filter(Article.id == article_id).first()
return article
api.add_resource(ArticleView, '/articles/<article_id>/', endpoint="article")
返回結果:
默認值:
在resource_field中定義的一些字段,如果模型中沒有與之對應的字段,則該字段的值會被設置爲null返回。除此之外,還可以給沒有的字段設置默認值,最後返回的該字段的值就是默認值,而不是null.
class ArticleView(Resource):
resource_field = {
"article_title": fields.String(attribute="title"), # 改變屬性的名字
"article_content": fields.String(attribute="content"),
"article_author": fields.Nested({ # User類型
"username": fields.String,
"email": fields.String
}, attribute="author"),
"article_tags": fields.Nested({
"name": fields.String
}, attribute="tags"),
"read_cnt": fields.Integer(default=100)
}
@marshal_with(resource_field)
def get(self, article_id):
article = db.session.query(Article).filter(Article.id == article_id).first()
return article
api.add_resource(ArticleView, '/articles/<article_id>/', endpoint="article")
此時返回的read_cnt字段的值就是默認值100:
Flask-restful細節:
1. flask-restful結合藍圖使用
在flask-restful中使用藍圖,需要將藍圖定義到一個單獨的文件即可,此時在定義api的時候,以藍圖爲參數,而不用再以app作爲定義api的參數,在主app主註冊藍圖:
例如,定義藍圖的文件,articleViews.py
# -*- coding: utf-8 -*-
from flask import Blueprint
from flask_restful import Resource, fields, marshal_with, Api
from models import User, Article, Tag
from exts import db
article_bp = Blueprint("article", __name__, url_prefix="/article")
api = Api(article_bp) # api
# 定義一個restful視圖
class ArticleView(Resource):
resource_field = {
"article_title": fields.String(attribute="title"), # 改變屬性的名字
"article_content": fields.String(attribute="content"),
"article_author": fields.Nested({ # User類型
"username": fields.String,
"email": fields.String
}, attribute="author"),
"article_tags": fields.Nested({
"name": fields.String
}, attribute="tags"),
"read_cnt": fields.Integer(default=100)
}
@marshal_with(resource_field)
def get(self, article_id):
article = db.session.query(Article).filter(Article.id == article_id).first()
return article
api.add_resource(ArticleView, "/<article_id>/", endpoint="article")
在主app.py文件中註冊藍圖:
from flask import Flask, render_template, request
import config
from exts import db
from models import User, Article, Tag
from articleViews import article_bp
app = Flask(__name__)
app.config.from_object(config)
db.init_app(app=app)
app.register_blueprint(article_bp) # 註冊藍圖
@app.route('/')
def index():
user = User(username="kong", email="[email protected]")
article = Article(title="gone", content="hover")
article.author = user
tag1 = Tag(name="java")
tag2 = Tag(name="python")
article.tags.extend([tag1, tag2])
db.session.add(article)
db.session.commit()
return "Hello"
if __name__ == '__main__':
app.run()
其中models文件爲定義ORM模型的文件:models.py
# -*- coding: utf-8 -*-
from exts import db
class User(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(50), nullable=False)
email = db.Column(db.String(50), nullable=False)
# tag和article之間是多對多的關係
article_tag = db.Table("article_tag",
db.Column("article_id", db.Integer, db.ForeignKey("article.id"), primary_key=True),
db.Column("tag_id", db.Integer, db.ForeignKey("tag.id"), primary_key=True),
)
class Article(db.Model):
__tablename__ = "article"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text)
author_id = db.Column(db.Integer, db.ForeignKey("user.id"))
author = db.relationship("User", backref="articles")
tags = db.relationship("Tag", secondary=article_tag, backref="articles")
class Tag(db.Model):
__tablename__ = "tag"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
2. flask-restful渲染模板
falsk-restful規定的數據交互的形式是JSON,所以即使返回渲染的模板(及render_templlate(html文件)),此時在瀏覽器中顯示的還是字符串形式的,而不會當作html代碼被瀏覽器解析:此時需要藉助一個裝飾器,@api.representation("text/html")來定義一個函數,在這個函數中對html代碼進行封裝,再進行返回
# -*- coding: utf-8 -*-
from flask import Blueprint, render_template, make_response
from flask_restful import Resource, fields, marshal_with, Api
from models import User, Article, Tag
from exts import db
article_bp = Blueprint("article", __name__, url_prefix="/article")
api = Api(article_bp) # api
@api.representation("text/html") # 保證restful也能夠通過render_template渲染模板
def output_html(data, code, headers):
# data是字符串形式的html文本
resp = make_response(data) # 構造Response對象
return resp
# 定義一個restful視圖
class ArticleView(Resource):
resource_field = {
"article_title": fields.String(attribute="title"), # 改變屬性的名字
"article_content": fields.String(attribute="content"),
"article_author": fields.Nested({ # User類型
"username": fields.String,
"email": fields.String
}, attribute="author"),
"article_tags": fields.Nested({
"name": fields.String
}, attribute="tags"),
"read_cnt": fields.Integer(default=100)
}
@marshal_with(resource_field)
def get(self, article_id):
article = db.session.query(Article).filter(Article.id == article_id).first()
return article
class ArticleListView(Resource):
def get(self):
return render_template("html/list.html")
# 此時,restful返回的依然是字符串形式的html文本,不能被瀏覽器解析
api.add_resource(ArticleView, "/<article_id>/", endpoint="article")
api.add_resource(ArticleListView, '/list/', endpoint="list")