06-Flask之REST&API設計

一涎嚼、REST

  • 問題

網絡應用程序阱州,分為前端和后端兩個部分。當前的發(fā)展趨勢法梯,就是前端設備層出不窮(手機苔货、平板、桌面電腦立哑、其他專用設備......)夜惭。

因此,必須有一種統(tǒng)一的機制铛绰,方便不同的前端設備與后端進行通信诈茧。這致使API構架的流行。

  • 基本概念

REST是"Representational State Transfer"縮寫捂掰,即是"表現層狀態(tài)轉化"敢会。而"表現層"其實指的是"資源(Resource)"的表現層曾沈。

一種軟件架構風格、設計風格鸥昏、而不是標準塞俱,只是提供了一組設計原則和約束條件。

它主要用于客戶端和服務器交互類的軟件互广×搽纾基于這個風格設計的軟件可以更簡潔,更有層次惫皱,更易于實現緩存機制等像樊。

REST其實是一種組織Web服務的架構,而并不是我們想象的那樣是實現Web服務的一種新的技術旅敷,更沒有要求一定要使用HTTP生棍。其目標是為了創(chuàng)建具有良好擴展性的分布式系統(tǒng)。

1. 資源(Resource)
    就是網絡上的一個實體媳谁,或是網絡上的具體信息涂滴,可以是一段文本、一張圖片晴音、一首歌曲柔纵、一部電影。
    每個資源都會對應的URL锤躁,且是唯一的標識符搁料,想要獲取資源只需要調用對應URL。
2. 表現層(Representation)
    "資源"是一種信息實體系羞,它可以有多種外在表現形式郭计。
    而"資源"具體呈現出來的形式,就叫它的"表現層"椒振。
3. 狀態(tài)轉換(State Transfer)
    訪問一個網站昭伸,就是客戶端和服務端的交互過程,這個過程中就會涉及到數據和狀態(tài)的變化澎迎。
    互聯(lián)網通信協(xié)議HTPP庐杨,是無狀態(tài)協(xié)議。即所有狀態(tài)都保存在服務端夹供。
    客戶端要操作服務端必須通過某種方式辑莫,讓服務端發(fā)生"狀態(tài)轉換",而這轉換是建立在表現層之上的罩引,所以就是"表現層狀態(tài)轉換"。
    客戶端用到的手段只能是HTTP協(xié)議枝笨,在操作方式的動詞:     GET/POST/PUT/DELETE袁铐。
    對應GET獲取資源揭蜒,POST新建資源(或更新資源),PUT更新資源剔桨,DELETE刪除資源屉更。
  • 架構級約束
1.使用客戶/服務器模型∪髯海客戶和服務器之間通過一個統(tǒng)一的接口來互相通訊
2.層次化的系統(tǒng)瑰谜。在一個REST系統(tǒng)中,客戶端并不會固定地與一個服務器打交道
3.無狀態(tài)树绩。在一個REST系統(tǒng)中萨脑,服務端并不會保存有關客戶的任何狀態(tài)。也就是說饺饭,客戶端自身負責用戶狀態(tài)的維持渤早,并在每次發(fā)送請求時都需要提供足夠的信息
4.可緩存。REST系統(tǒng)需要能夠恰當地緩存請求瘫俊,以盡量減少服務端和客戶端之間的信息傳輸鹊杖,以提高性能
5.統(tǒng)一的接口。一個REST系統(tǒng)需要使用一個統(tǒng)一的接口來完成子系統(tǒng)之間以及服務與用戶之間的交互扛芽。這使得REST系統(tǒng)中的各個子系統(tǒng)可以獨自完成演化

一個系統(tǒng)滿足了上面所列出的五條約束骂蓖,那么該系統(tǒng)就被稱為是RESTful。

  • 什么是RESTful框架
1. 每一個URL代表一種資源
2. 客戶端和服務器之間川尖,傳遞這種資源的某種表現層
3. 客戶端通過四個HTTP動詞登下,對服務端進行操作,實現"表現層狀態(tài)轉換"

RESTful API是目前比較成熟的一套互聯(lián)網應用程序的API設計理論空厌。

二庐船、返回JSON格式數據

天氣API: https://www.sojson.com/api/weather.html

  • jsonify序列化操作
# 返回json格式數據
@blue.route('/getjson/')
def getjson():
    data = {
        'data': 'hello flask',
        'status': 200
    }
    return jsonify(data)

實現前后端的分離,后臺人員只需要提供API接口接口嘲更。

  • 后臺人員工作職責
- 定制接口
- 模型定制
- 面向接口編程
    只關注請求地址
    值關注請求結果格式
  • 示例
- 在app/views.py中添加獲取商品列表數據API 【后臺人員】
    # 學生成績API
    @blue.route('/getscore/')
    def getscore():
        data = {
            'msg':'學生成績列表',
            'status': '200',
            'content': [20,32,53,90,43,67,99,89]
        }

        return jsonify(data)
    
    備注: 獲取數據 http://127.0.0.1:8000/getscore/

- 在app/static/html/testlist.html       【前端人員】
    # 發(fā)起Ajax請求筐钟,獲取對應學生成績列表json數據,解析json數據赋朦,并渲染到頁面中
    <script>
        $(function () {
            $.getJSON("http://127.0.0.1:8000/getscore/", function(json){
                if (json.status == 200){
                    $('body').append($('<h3></h3>').html(json.msg))

                    for(var i=0; i<json.content.length; i++){
                        $('body').append($('<h3></h3>').html('成績:'+json.content[i]))
                    }
                }
                else{
                    $('body').append($('<h3></h3>').html('哥們篓冲,你錯了!'))
                }
            });
        })
    </script>
    
    
    備注: 靜態(tài)頁面在瀏覽器中打開 http://127.0.0.1:8000/static/html/scorelist.html

書寫靜態(tài)頁面時,要注意是web相關的內容宠哄,而不是模板壹将,所以注釋等方式是會不一樣的!C怠诽俯!

三、RESTful API設計

  • 協(xié)議
API與用戶的通信協(xié)議承粤,通常使用HTTP(S)協(xié)議暴区。
  • 域名
應該盡量將API部署在專用域名之下闯团。
    如: http://api.zyz.com

如果確定API很簡單,不會有大規(guī)模擴從仙粱,可以考慮放在主域名之下房交。
    如: http://www.zyz.com/api/
  • 版本
應該將API的版本號放入URL。
    如: http://api.zyz.com/v1/

也有將版本號放在HTTP的頭信息中伐割,但不如放在URL中方便直觀候味,Github就是這么做的。

  • 路徑
路徑又稱"終點"(endpoint)隔心,表示API的具體網址白群。
在RESTful架構中,每個網址代表一種資源济炎,所以網址不能有動詞川抡,只能有名詞。
而所用名詞往往與數據庫表單名對應须尚。
  • HTTP動詞
對于資源的具體操作類型崖堤,由HTTP動詞表示。
HTTP常用動詞:
    - GET(SELECT) 從服務器取資源
    - POST(CREATE or UPDATE) 服務器中創(chuàng)建資源或更新資源
    - PUT(UPDATE) 在服務器更新資源(客戶端提供改變后的完整資源)
    - PATCH(UPDATE) 在服務器更新資源(客戶端提供改變的屬性)
    - DELETE(DELETE) 從服務器刪除資源
    - HEAD 獲取資源的元數據
    - OPTHONS 獲取信息耐床,關于資源的那些屬性是客戶端可以改變的

例如:
    - GET  /students  獲取所有學生
    - POST /student   新建學生
    - GET  /students/id 獲取某一個學生
    - PUT  /students/id 更新某個學生的信息(需要提供學生的全部信息)
    - PATHC /students/id 更新某個學生的信息(需要提供學生變更信息)
    - DELETE /students/id 刪除某個學生
  • 過濾信息
當記錄數量過多密幔,服務器不可能將它們返回給用戶。APIT應該提供參數撩轰,過濾返回結果胯甩。
    ?limit=10
    ?offset=10
    ?page=2&per_page=10
    ?sortby=name&order=desc
    ?student_id=id
  • 狀態(tài)碼
服務器向用戶返回的狀態(tài)碼和提示信息。
    200 OK - [GET]:服務器成功返回用戶請求的數據
    201 CREATED -[POST/PUT/PATCH]:用戶新建或修改數據成功
    202 Accepted - [*] :表示一個請求已經進入后臺排隊(異步任務)
    204 NO CONTENT - [DELETE]:表示數據刪除成功
    400 INVALID REQUEST - [POST/PUT/PATCH]:用戶發(fā)出的請求有錯誤
    401 Unauthorized - [*] :表示用戶沒有權限(令牌堪嫂,用戶名偎箫,密碼錯誤)
    403 Forbidden - [*]:表示用戶得到授權,但是訪問是被禁止的
    404 NOT FOUND - [*]:用戶發(fā)出的請求針對的是不存在的記錄
    406 Not Acceptable - [*]:用戶請求格式不可得
    410 Gone - [GET] :用戶請求的資源被永久移除皆串,且不會再得到的
    422 Unprocesable entity -[POST/PUT/PATCH]:當創(chuàng)建一個對象時淹办,發(fā)生一個驗證錯誤
    500 INTERNAL SERVER EROR - [*] :服務器內部發(fā)生錯誤
    
    
    2xx —— 正確的響應
    3xx —— 重定向
    4xx —— 客戶端錯誤
    5xx —— 服務端錯誤
  • 錯誤處理
如果狀態(tài)碼是4xx,就應該向用戶返回出錯信息恶复。一般來說怜森,返回的信息中將error做為鍵名
  • 返回結果
針對不同操作,服務器想用戶返回的結果應該符合以下規(guī)范:
    GET /collection:返回資源對象的列表(數組谤牡,集合)
    GET /collection/id:返回單個資源對象
    POST /collection:返回新生成的資源對象
    PUT /collection/id:返回完整的資源對象
    PATCH /collection/id:返回完整的資源對象
    DELETE /collection/id:返回一個空文檔
  • 核心點
RESTfull 的核心是 resource 副硅,對一個 resource ,用不同的 HTTP verb 做增刪查改翅萤。
  • 其他
服務器返回的數據格式恐疲,應該盡量使用JSON
  • 原生實現
@blue.route('/api/v1/users/',methods=['GET','POST','DELETE','PUT','PATCH'])
def users():
    if request.method == 'GET': # 獲取用戶
        pass
    elif request.method == 'POST':  # 更新或創(chuàng)建用戶密碼
        # 獲取數據
        username = request.form.get('username')
        password = request.form.get('password')

        data = {
            'msg':'ok',
            'status': 201
        }

        # 不為空
        if not username or not password:
            data['msg'] = '參數不正確'
            data['status'] = 422
            return jsonify(data)

        user = User()
        user.u_name = username
        user.u_passwd = generate_passwd(password)

        try:
            db.session.add(user)
            db.session.commit()
        except Exception as e:
            data['msg'] = '用戶已存在'
            data['status'] = 423
            return jsonify(data)

        return jsonify(data)

    elif request.method == 'DELETE':    # 刪除用戶
        pass
    elif request.method == 'PUT':   # 更新賬和密碼
        pass
    elif request.method == 'PATCH':  # 修改密碼
        pass
    else:
        abort(405)

# 密碼加密處理
def generate_passwd(passwd):
    hash = hashlib.md5()
    hash.update(passwd.encode('utf-8'))
    return hash.hexdigest()

jsonify: json序列化

四、Flask-RESTful插件

Flask-RESTful添加快速構建REST API的支持,也是一個能夠和現有ORM庫協(xié)同工作的輕量級的擴展培己。Flask-RESTful鼓勵以最小的設置的最佳實踐糜烹。

中文文檔: http://www.pythondoc.com/Flask-RESTful/

  • 基本使用
- 安裝
    pip install flask-restful
    
- 配置
    # ext.py文件中
    from flask_restful import Api
    api = Api()
    api.init_app(app)
    
- 使用(項目拆分)
    # 定義一個資源
    # views.py已經可以替換為apis.py,之前的路由功能換種寫法
    class HelloWorld(Resource):
        def get(self):  # get請求
            return {'msg': 'hello world'}

        def post(self): # post請求
            return {'msg': '你好!'}
           
    # 添加一個資源
    # add_resource 注冊路由到框架上
    # 如果沒有指定 endpoint漱凝,Flask-RESTful 會根據類名生成一個
    # 但有時候如 url_for 需要 endpoint,因此最好明確給 endpoint 賦值
    api.add_resource(HelloWorld, '/hello/',endpoint='hello')

Flask-Rest-JSONAPI插件诸迟、Flask-Restless插件

  • 項目拆分
備注:
    之前路由的操作都是在views.py中茸炒,現在只需要提供API接口,所以功能就會不一樣阵苇;
    在項目進行完基本拆分之后壁公,可以將views.py改為apis.py;
    資源不止一個绅项,而不能都只是有一個紊册,而是有多個,為了方便管理快耿,可以將apis變?yōu)榘男问侥叶福奖愎芾?

# App/apis/__init__.py
  api = Api()
  def init_api(app):
        api.init_app(api)

# App/__init_.py [之前init_blue 換為 init_api即可]
  init_api(app)

# App/apis/HelloResource.py 定義資源
    class HelloWorld(Resource):
        def get(self):  # get請求
            return {'msg': 'hello world'}

# App/apis/__init__.py 添加資源
api.add_resource(HelloResource, '/hello/', endpoint='hello')
  • 帶參數操作
# App/apis/UserApi.py文件
class UserResource(Resource):
    def get(self,id):
        str = '(get)userid: %d' % id
        return {'msg':str}

    def post(self,id):
        str = '(post)userid: %d' % id
        return {'msg': str}
        
# App/apis/__init__.py文件
from App.apis import UserAPI
api.add_resource(UserResource, '/user/<int:id>/', endpoint='user')

Flask-RESTful 提供的最主要的基礎就是資源(resources)。資源(Resources)是構建在 Flask視圖 之上掀亥,只要在你的資源(resource)上定義方法就能夠容易地訪問多個 HTTP 方法撞反。[無需原生操作,因為一個資源而進行不同的判斷操作處理]

  • 端點操作(Endpoints)
很多時候在一個 API 中搪花,你的資源可以通過多個 URL 訪問遏片。
你可以把多個 URL 傳給 Api 對象的 Api.add_resource() 方法。每一個 URL 都能訪問到你的 Resource撮竿。
api.add_resource(HelloWorld, 
                 '/hello/',
                 '/haha/',
                 '/hehe/')
  • 輸出格式定制
默認情況下吮便,在你的返回迭代中所有字段將會原樣呈現。
實際更多的需要一個字典類型數據幢踏,之后通過JSON序列化即可髓需。
Flask-RESTful 提供了 fields 模塊和 marshal_with() 裝飾器來進行數據格式化。

# @marshal_with(需要返回的數據格式)
    如果返回的數據惑折,在預定義的結構中不存在授账,數據會被自動濾掉;
    如果返回的數據惨驶,在預定的結構中存在白热,數據會正常返回;
    如果返回的數據比預定的結構字段少粗卜,預定義的字段會顯示默認值屋确;
    
# 支持類型
    - 常用基本類型
        String
        Integer
    - 列表類型
        List
    - 級聯(lián)類型
        Nested
    - 結構嵌套
        fields.List(fields.Nested())
        
        
# 示例1
    """ 獲取一只貓的數據基本結構
        {
            'msg':'ok',
            'status':200,
            'data': {
                'id': 1,
                'name': 'TOM',
                'color': '紅色'
            }
        }
    """
    catmodel_fields = {
        'id': fields.Integer,
        'name': fields.String,
        'color': fields.String
    }
    
    onecat_fields = {
        'msg': fields.String(default='ok'),
        'status': fields.Integer(default=200),
        'data': fields.Nested(catmodel_fields)  # 嵌套
    }
    
    class OneCatResource(Resource):
        @marshal_with(onecat_fields)
        def get(self):
            cat = Cat.query.first()
            data = {
                # msg 使用默認值,可以省略不寫
                # status 使用默認只,也可以不寫
                'data': cat
            }
            return data
        
# 示例2
    """ 獲取所有貓的數據結構
        {
            'msg':'ok',
            'status': 200,
            'data': [
                {
                    'id': 1,
                    'name': 'TOM1',
                    'color': '紅色'
                },
                {
                    'id': 2,
                    'name': 'TOM2',
                    'color': '紅色'
                },
                {
                    'id': 3,
                    'name': 'TOM3',
                    'color': '紅色'
                },
                ...
                
            ]
        }
    """
    catmodel_fields = {
        'id': fields.Integer,
        'name': fields.String,
        'color': fields.String
    }
    
    cats_fields = {
        'msg': fields.String(default='ok'),
        'status': fields.Integer(default=200),
        'data': fields.List(fields.Nested(catmodel_fields))
    }

    class CatResource(Resource):
        @marshal_with(cats_fields)
        def get(self):
            cats = Cat.query.all()
            data = {
                'msg': 'ok!',
                'status': 200,
                'data': cats
            }
            return data
  • 請求參數解析
- 基本使用
    # https://127.0.0.1/showview/?page=1
    parser = reqparse.RequestParser()
    # 接受參數page,類型是str,錯誤提示help
    parser.add_argument('page', type=str, help='請輸入頁碼')
    
    class ShowView(Resource):
        def get(self):
            parse = parser.parse_args()
            c_page = parse.get('page') or 1
            
        def post(self):
            parse = parser.parse_args()
            c_page = parse.get('page') or 1
            
    
    
    
- 必須參數 required=True
    # 該參數必須傳入
    parser.add_argument('page', type=int, help='請輸入頁碼', required=True)

- 多參數(列表)
    # 如果要接受一個鍵有多個值的話攻臀,可以傳入 action='append'
    # https://127.0.0.1/showview/?name='liming'&page=1&name='zhangsan'
    parser.add_argument('name', type=str, action='append')

- 參數位置
    參數位置: form焕数、args、headers刨啸、cookies堡赔、files(上傳文件)
    
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市设联,隨后出現的幾起案子善已,更是在濱河造成了極大的恐慌,老刑警劉巖离例,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件换团,死亡現場離奇詭異,居然都是意外死亡宫蛆,警方通過查閱死者的電腦和手機艘包,發(fā)現死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耀盗,“玉大人想虎,你說我怎么就攤上這事∨劾洌” “怎么了磷醋?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長胡诗。 經常有香客問我邓线,道長,這世上最難降的妖魔是什么煌恢? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任骇陈,我火速辦了婚禮,結果婚禮上瑰抵,老公的妹妹穿的比我還像新娘你雌。我一直安慰自己,他們只是感情好二汛,可當我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布婿崭。 她就那樣靜靜地躺著,像睡著了一般肴颊。 火紅的嫁衣襯著肌膚如雪氓栈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天婿着,我揣著相機與錄音授瘦,去河邊找鬼醋界。 笑死,一個胖子當著我的面吹牛提完,可吹牛的內容都是我干的形纺。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼徒欣,長吁一口氣:“原來是場噩夢啊……” “哼逐样!你這毒婦竟也來了?” 一聲冷哼從身側響起打肝,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤官研,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后闯睹,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡担神,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年楼吃,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妄讯。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡孩锡,死狀恐怖,靈堂內的尸體忽然破棺而出亥贸,到底是詐尸還是另有隱情躬窜,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布炕置,位于F島的核電站荣挨,受9級特大地震影響,放射性物質發(fā)生泄漏朴摊。R本人自食惡果不足惜默垄,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望甚纲。 院中可真熱鬧口锭,春花似錦、人聲如沸介杆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽春哨。三九已至荆隘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間悲靴,已是汗流浹背臭胜。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工莫其, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人耸三。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓乱陡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親仪壮。 傳聞我的和親對象是個殘疾皇子憨颠,可洞房花燭夜當晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內容