1. 程式人生 > >應用程式設計介面(API)

應用程式設計介面(API)

Web API,具體說就是為我們寫好的網站內容開發出可供第三方使用的API介面。書中提到了一個概念:REST(Representational State Transfer)——表現層狀態轉移。這是一種Web服務架構。它具有6個特徵:

  • 客戶端-伺服器
  • 無狀態
  • 快取
  • 介面統一
  • 系統分層
  • 按需程式碼

1. REST API

1.1 資源

資源是 REST 架構方式的核心概念。在 REST 架構中,資源是程式中你要著重關注的事物。例如,在部落格程式中,使用者、部落格文章和評論都是資源。

每個資源都要使用唯一的 URL 表示。
還是以部落格程式為例,一篇部落格文章可以使用 URL /api/posts/12345 表示,其中 12345 是這篇文章的唯一識別符號,使用文章在資料庫中的主鍵表示。


URL 的格式或內容無關緊要,只要資源的 URL 只表示唯一的一個資源即可。
某一類資源的集合也要有一個 URL。部落格文章集合的 URL 可以是 /api/posts/,評論集合的URL 可以是 /api/comments/。
API 還可以為某一類資源的邏輯子集定義集合 URL。例如,編號為 12345 的部落格文章,其中的所有評論可以使用 URL /api/posts/12345/comments/ 表示。

1.2 請求方法

技術分享

 如果資源不支援客戶端使用的請求方法,響應的狀態碼為 405,返回“不允許使用的方法”。Flask 會自動處理這種錯誤。

1.3 請求和響應主體

一篇部落格文章對應的資源可以使用如下的JSON表示:

{
    
"url": "http://www.example.com/api/posts/12345", "title": "Writing RESTful APIs in Python", "author": "http://www.example.com/api/users/2", "body": "... text of the article here ...", "comments": "http://www.example.com/api/posts/12345/comments" }

在這篇部落格文章中,url、author 和 comments 欄位都是完整的資源 URL。這是很重要的表示方法,因為客戶端可以通過這些 URL 發掘新資源。

在設計良好的 REST API 中,客戶端只需知道幾個頂級資源的 URL,其他資源的 URL 則從響應中包含的連結上發掘。

1.4 版本

版本區分 Web 服務所處理的 URL。例如,首次釋出的部落格 Web 服務可以通過 /api/v1.0/posts/ 提供部落格文章的集合。

2. 使用Flask提供 REST Web 服務

使用 Flask 建立 REST Web 服務很簡單。
使用熟悉的 route() 修飾器及其 methods 可選引數可以宣告服務所提供資源 URL 的路由。
處理 JSON 資料同樣簡單,因為請求中包含的JSON 資料可通過 request.json 這個 Python 字典獲取,並且需要包含 JSON 的響應可以使用 Flask 提供的輔助函式 jsonify() 從 Python 字典中生成。

2.1 建立API藍本

REST API 相關的路由是一個自成一體的程式子集,所以為了更好地組織程式碼,最好把這些路由放到獨立的藍本中。


API 藍本的結構

|-flasky
    |-app/
    |-api_1_0
        |-__init__.py
        |-users.py
        |-posts.py
        |-comments.py
        |-authentication.py
        |-errors.py
        |-decorators.py

API 包的名字中有一個版本號。如果需要建立一個向前相容的 API 版本,可以新增一個版本號不同的包,讓程式同時支援兩個版本的 API。

app/api_1_0/__init__.py:API 藍本的構造檔案

from flask import Blueprint

api = Blueprint(api, __name__)

from . import authentication, posts, users, comments, errors

app/__init__.py: 註冊API藍本

def create_app(config_name):
    # ...
    form .api_1_0 import api as api_1_0_blueprint 
    app.register_blueprint(api_1_0_blueprint, url_prefix=/api/v1.0)
    # ...

2.2 錯誤處理

處理 404 和 500 狀態碼時會有點小麻煩,因為這兩個錯誤是由 Flask 自己生成的,而且一般會返回 HTML 響應,這很可能會讓 API 客戶端困惑。為所有客戶端生成適當響應的一種方法是,在錯誤處理程式中根據客戶端請求的格式改寫響應,這種技術稱為內容協商。改進後的 404 錯誤處理程式,它向 Web 服務客戶端傳送 JSON 格式響應,除此之外都發送 HTML 格式響應。

app/main/errors.py: 使用HTTP內容協商處理錯誤

@main.app_errorhandler(404)
def page_not_found(e):
    if request.accept_mimetypes.accept_json and not request.accept_mimetypes.accept_html:
        res = jsonify({error: not found})
        res.status_code = 404
        return res
    return render_template(404.html), 404

這個新版錯誤處理程式檢查 Accept 請求首部(Werkzeug 將其解碼為 request.accept_mimetypes),根據首部的值決定客戶端期望接收的響應格式。

其他狀態碼都由 Web 服務生成,因此可在藍本的 errors.py 模組作為輔助函式實現。

app/api_1_0/errors.py: API藍本中403狀態碼的錯誤處理

def forbidden(message):
    response = jsonify({error: forbidden, message: message})
    response.status_code = 403
    return response

2.3 使用Flask-HTTPAuth認證使用者

和普通的 Web 程式一樣,Web 服務也需要保護資訊,確保未經授權的使用者無法訪問。為此,RIA 必須詢問使用者的登入密令,並將其傳給伺服器進行驗證。
REST Web 服務的特徵之一是無狀態,即伺服器在兩次請求之間不能“記住”客戶端的任何資訊。客戶端必須在發出的請求中包含所有必要資訊,因此所有請求都必須包含使用者密令。

但在 RESTWeb 服務中使用 cookie 有點不現實,因為 Web 瀏覽器之外的客戶端很難提供對 cookie 的支援。鑑於此,使用 cookie 並不是一個很好的設計選擇。

因為 REST 架構基於 HTTP 協議,所以傳送密令的最佳方式是使用 HTTP 認證,基本認證和摘要認證都可以。在 HTTP 認證中,使用者密令包含在請求的 Authorization 首部中。

HTTP 認證協議很簡單,可以直接實現,不過 Flask-HTTPAuth 擴充套件提供了一個便利的包裝,可以把協議的細節隱藏在修飾器之中,類似於 Flask-Login 提供的 login_required 修飾器。

Flask-HTTPAuth 使用 pip 安裝

(venv) $ pip install flask-httpauth

app/api_1_0/authentication.py: 初始化Flask-HTTPAuth

from flask.ext.httpauth import HTTPBasicAuth
auth = HTTPBasicAuth()

@auth.verify_password
def verify_password(email, password):
    if email == ‘‘:
        g.current_user = AnonymousUser()
        return True
    user = User.query.filter_by(email=email).first()
    if not user:
        return False
    g.current_user = user
    return user.verify_password(password)

由於這種使用者認證方法只在API藍本中使用,所以Flask-HTTPAuth只在藍本包中初始化,而不是像其他擴充套件那樣在app包中初始化。
把通過認證的使用者儲存在 Flask 的全域性物件 g 中,如此一來,檢視函式便能進行訪問。

如果認證密令不正確,伺服器向客戶端返回 401 錯誤。預設情況下,Flask-HTTPAuth 自動生成這個狀態碼,但為了和 API 返回的其他錯誤保持一致,我們可以自定義這個錯誤響應。

app/api_1_0/authentication.py:Flask-HTTPAuth 錯誤處理程式

@auth.error_handler
def auth_error():
    return unauthorized(Invalid credentials)

這個藍本中的所有路由都要進行保護,所以我們可以在 before_request 處理程式中使用一次 login_required 修飾器,應用到整個藍本。

app/api_1_0/authentication.py:在 before_request 處理程式中進行認證

from .errors import forbidden_error

@api.before_request
@auth.login_required
def before_request():
    if not g.current_user.is_annoymous and not g.current_user.confirmed:
        return forbidden(Unconfirmed accout)

2.4 基於token的認證

每次請求時,客戶端都要傳送認證密令。為了避免總是傳送敏感資訊,我們可以提供一種基於token的認證方案。

app/models.py:支援基於令牌的認證

class User(db.Model):
    # ...
    def generate_auth_token(self, expiration):
        s = Serializer(current_app.config[SECRET_KEY],
            expires_in=expiration)
        return s.dumps({id: self.id})

    @staticmethod
    def verify_auth_token(token):
        s = Serializer(current_app.config[SECRET_KEY])
        try:
            data = s.loads(token)
        except:
            return None
        return User.query.get(data[id])

修改之前的verify_password函式

app/api_1_0/authentication.py: 支援 token

@auth.verify_password
def verify_password(email_or_token, password):
    if email_or_token == ‘‘:
        g.current_user = AnonymousUser()
        return True
    if password == ‘‘:
        g.current_user = User.verify_auth_token(email_or_token)
        g.token_used = True
        return g.current_user is not None
    user = User.query.filter_by(email=email_or_token).first()
    if not user:
        return False
    g.current_user = user
    g.token_used = False
    return user.verify_password(password)

api/api_1_0/authentication.py: 生成認證 token

@api.route(/token)
def get_token():
    if g.current_user.is_annoymous() or g.token_used:
        return return unauthorized(Invalid credentials)
    return jsonify({token: g.current_user.generate_auth_token(
        expiration=3600), expiration: 3600})

2.5 資源和 json 轉化

app/models.py:把文章轉換成 JSON 格式的序列化字典

class Post(db.Model):
    # ...
    def to_json(self):
        json_post = {
            url: url_for(api.get_post, id=self.id, _external=True),
            body: self.body,
            body_html: self.body_html,
            timestamp: self.timestamp,
            author: url_for(api.get_user, id=self.author_id, _external=True),
            comments: url_for(api.get_post_comments, id=self.id, _external=True),
            comment_count: self.comments.count()
        }
    return json_post

url、author 和 comments 欄位要分別返回各自資源的 URL,因此它們使用 url_for() 生成,所呼叫的路由即將在 API 藍本中定義。注意,所有 url_for() 方法都指定了引數 _external=True,這麼做是為了生成完整的 URL,而不是生成傳統 Web 程式中經常使用的相對 URL。
這段程式碼還說明表示資源時可以使用虛構的屬性。comment_count 欄位是部落格文章的評論數量,並不是模型的真實屬性,它之所以包含在這個資源中是為了便於客戶端使用。

把 JSON 轉換成模型時面臨的問題是,客戶端提供的資料可能無效、錯誤或者多餘。

app/models.py:從 JSON 格式資料建立一篇部落格文章

from app.exceptions import ValidationError

class Post(db.Model):
    # ...
    @staticmethod
    def from_json(json_post):
        body = json_post.get(body)
        if body is None or body == ‘‘:
            raise ValidationError(post does not have a body)
        return Post(body=body)

上述程式碼在實現過程中只選擇使用 JSON 字典中的 body 屬性,而把 body_html屬性忽略了,因為只要 body 屬性的值發生變化,就會觸發一個 SQLAlchemy 事件,自動在伺服器端渲染 Markdown。除非允許客戶端倒填日期(這個程式並不提供此功能),否則無需指定 timestamp 屬性。由於客戶端無權選擇部落格文章的作者,所以沒有使用 author 欄位。

注意上面如何檢查錯誤
在這種情況下,丟擲異常才是處理錯誤的正確方式,因為 from_json()方法並沒有掌握處理問題的足夠資訊,唯有把錯誤交給呼叫者,由上層程式碼處理這個錯誤。

ValidationError 類是 Python 中 ValueError 類的簡單子類。

app/exceptions.py:ValidationError 異常

class ValidationError(ValueError):
    pass

現在,程式需要向客戶端提供適當的響應以處理這個異常。為了避免在檢視函式中編寫捕獲異常的程式碼,我們可建立一個全域性異常處理程式。

api/api_1_0/errors.py: API 中 ValidationError 異常的處理程式

@api.errorhandler(ValidationError)
def validation_error(e):
    return bad_request(e.args[0])

這裡使用的 errorhandler 修飾器和註冊 HTTP 狀態碼處理程式時使用的是同一個,只不過此時接收的引數是 Exception 類,只要丟擲了指定類的異常,就會呼叫被修飾的函式。

注意,這個修飾器從API 藍本中呼叫,所以只有當處理藍本中的路由時丟擲了異常才會呼叫這個處理程式。

使用這個技術時,檢視函式中得程式碼可以寫得十分簡潔明,而且無需檢查錯誤。

2.6 實現資源端點

app/api_1_0/posts.py: 文章資源GET請求的處理程式

@api.route(/posts/)
@auth.login_required
def get_posts():
    posts = Post.query.all()
    return jsonify({posts: [post.to_json() for post in posts]})

@api.route(/posts/<int:id>)
@auth.login_required
def get_post(id):
    post = Post.get_or_404(id)
    return jsonify(post.to_json)

ps:404 錯誤的處理程式在程式層定義,如果客戶端請求 JSON 格式,就要返回JSON 格式響應。如果要根據 Web 服務定製響應內容,也可在 API 藍本中重新定義 404 錯誤處理程式。

部落格文章資源的 POST 請求處理程式把一篇新部落格文章插入資料庫。

app/api_1_0/posts.py:文章資源 POST 請求的處理程式

@api.route(/posts/, methods=[POST])
@permission_required(Permission.WRITE_ARTICLES)
def new_post():
    post = Post.from_json(request.json)
    post.author = g.current_user
    db.session.add(post)
    db.session.commit()
    return jsonify(post.to_json()), 201,             {Location: url_for(api.get_post, id=post.id, _external=True)}

得益於前面實現的錯誤處理程式,建立部落格文章的過程變得很直觀。

部落格文章從 JSON 資料中建立,其作者就是通過認證的使用者。這個模型寫入資料庫之後,會返回 201 狀態碼,並把 Location 首部的值設為剛建立的這個資源的 URL。
注意,為便於客戶端操作,響應的主體中包含了新建的資源( Location 首部)。如此一來,客戶端就無需在建立資源後再立即發起一個 GET 請求以獲取資源。

用來防止未授權使用者建立新部落格文章的 permission_required 修飾器和程式中使用的類似,但會針對 API 藍本進行自定義。

app/api_1_0/decorators.py:permission_required 修飾器

def permission_required(permission):
    def decorator(f):
        @wraps(f)
            def decorated_function(*args, **kwargs):
            if not g.current_user.can(permission):
                return forbidden(Insufficient permissions)
            return f(*args, **kwargs)
        return decorated_function
    return decorator

2.7 分頁大型資源集合

和Web程式一樣,Web服務也可以對集合進行分頁。

app/api_1_0/posts.py:分頁文章資源

@api.route(/posts/)
    def get_posts():
    page = request.args.get(page
            
           

相關推薦

應用程式設計介面API

Web API,具體說就是為我們寫好的網站內容開發出可供第三方使用的API介面。書中提到了一個概念:REST(Representational State Transfer)——表現層狀態轉移。這是一種Web服務架構。它具有6個特徵:客戶端-伺服器無狀態快取介面統一系統分層按

23 Flask mega-tutorial 第23章應用程式程式設計介面API

如需轉載請註明出處。 win10 64位、Python 3.6.3、Notepad++、Chrome 67.0.3396.99(正式版本)(64 位) 注:作者編寫時間2018-05-09,linux、python 3.5.2 以下內容均是加入自己的理解與增刪,

微服務介面API的過去,現在與未來

API的過去,現在與未來 隨著微服務架構的流行,貌似我們已經聊了很多關於現在的API的設計與規範,不過能夠暢想的未來的API又是怎樣的模式呢?首先,我們需要回顧下API的過去與現在。 過去 土耳其機器人:The Turk 我們可以追溯到1770年匈牙利帝國時代的哈

linux應用程式設計筆記1gdb除錯方法及如何找出dbg.c程式中的錯誤

摘要: 介紹了gdb偵錯程式以及其使用流程,總結了常用的幾個gdb除錯命令,最後使用這些方法找出dgb.c檔案中的錯誤。 一、gdb簡介     gdb是gnu釋出的一款功能強大的程式除錯工具,其主

MFC介面程式設計基礎28:直接訪問MySql API 連線 mysql

直接訪問MySql API 連線 mysql 第一步: 安裝MySql 第二步: 在工程檔案中新增mysql 標頭檔案 #include mysql.h 第三步: 在VS屬性中做如下配置: ①:附加包含標頭檔案mysql.h的目錄,即安裝目錄下的

MFC介面程式設計基礎24:建立資料庫應用框架

上一篇:MFC介面程式設計基礎(23):建立並註冊資料來源 下一篇:MFC介面程式設計基礎(25):設計記錄操作介面 使用MFC類嚮導可以方便地得到一個數據庫應用程式的框架,建立一個MFC單文件EXE應用程式Exa

MFC介面程式設計基礎19:文件檢視結構應用程式例子Editor

上一篇:MFC介面程式設計基礎(18):文件檢視結構應用程式例子(Ex_DocView) 下一篇:MFC介面程式設計基礎(20):一個簡單的文件序列化示例(Ex_SDIArchive) 建立單文件應用程式Edito

MFC介面程式設計基礎18:文件檢視結構應用程式例子Ex_DocView

上一篇:MFC介面程式設計基礎(17):文件序列化 下一篇:MFC介面程式設計基礎(19):文件檢視結構應用程式例子(Editor) 1.建立基於CFormView類的多文件應用程式 用MFC 應用程式嚮導建立

MFC介面程式設計基礎10:基於對話方塊的MFC應用程式

上一篇:MFC介面程式設計基礎(09):選單(二) 下一篇:MFC介面程式設計基礎(11):靜態文字框、命令按鈕和編輯框 MFC程式設計 MFC 是 Visual C++ 的核心。雖然在 Windows 應用程

0、計算機語言分類和應用程序接口API

常見應用程序接口0、計算機語言 三種語言1、機器語言2、匯編語言3、高級語言 1、機器語言(01代碼):為了加快開發人員的開發,於是出現了微碼(匯編語言)—>編譯器(匯編語言是通過編譯器,可以讓微碼轉變成01代碼)2、匯編語言:盡管人類可以識別,但是匯編語言跟芯片(CPU)的結合程度也是很高的,不僅開發

java 介面interface應用詳解

java 介面(interface)的應用詳解 1.java 介面(interface) 2.java 介面(interface)使用的知識點 3.介面使用總結 4.結果的應用和 實現介面的匿名類物件 1.

C程式設計語言UNIX系統介面

系統介面和標準的C庫,是不同的兩個範疇。 標準C庫,各個平臺的C編譯器都應該支援,具有跨平臺的性質。 系統介面則是系統相關的,UNIX的系統介面,在Win上就肯定不能用。 那麼兩者是不是還有聯絡呢,其實是的。標準C庫,跟系統相關的部分,不也是通過系統呼叫/系統介面實現的麼。所以系

Matlab第六課:圖形介面GUI程式設計

目標: Graphical User Interface 一、MATLAB GUI Programs 開始完成一個GUI程式: 1.建立一個資料夾,儲存程式 2.使用 guide 命令在命令列建立一個matlab GUI 介面 3.可以拖動元件,建立GUI

java-圖形使用者介面GUI之AWT程式設計-整體思路與程式碼架構

1、整體思路   任何視窗都可以被分解成一個空的容器,容器中盛放了大量的基本元件,通過設定這些基本元件的大小、位置等屬性,就可以將該空的容器和基本元件組成一個整體的視窗。具體實現思路:   1)建立一個Frame頂層視窗   2)設定頂層視窗的佈局(如果需要的話):f.setLayout(new xxx

UEFI應用程式設計--SMM

13.4.2 SMRAM快取 在進入SMM之前或者在退出SMM之前,IA-32處理器不會自動的回寫(write back)或著使它的快取失效。因為這行為,必須小心謹慎系統記憶體中的SMRAM和SMRAM的快取,以防止快取不一致當在SMM和受保護模式操作之間來回切換時。下面三個

C#程式設計學習06:使用百度API進行路徑規劃

官方demo連線:http://lbsyun.baidu.com/jsdemo.htm#i5_9 在C#中使用webbroswer進行地圖顯示,並利用百度地圖API進行路徑規劃;先上效果圖 1 HTML檔案的編寫 <!DOCTYPE html> <html>

JavaWeb--應用程式設計模型JSP model2 ,MVC,三層結構

一、MVC MVC並非是java特有,幾乎所有B/S結構專案都用它 M----->Model模型 V------>view檢視     C----->controller控制層  二、JSP model2

Android開發中實用API介面

1、Throwable介面中的getStackTrace()方法(或者Thread類的getStackTrace()方法),根據這個方法可以得到函式的逐層呼叫地址,其返回值為StackTraceElement[]; 2、StackTraceElement類,其中四個方法getClassName(),g

Android開發者編寫自己的API介面

序 作為一名Android開發人員,想要實現對一些資料的操作和展示,可以通過一些提供Api介面的網站去獲取,雖然Api市場上種類繁多,不過別人提供的介面未必就是自己想要的,到最後,還是得自己去實現Api介面。 畢竟,最瞭解自己需求的人,還得是自己。 準

網路程式設計教程Linux網路程式設計基礎API

        首先介紹Linux下整個的網路程式設計流程: 一、socket地址API 1.主機位元組序和網路位元組序         位元組序分為大端位元組序(big endian)和小端位元組序(little endian)。大端位元組序是指一個整數的搞我位元