Skip to main content

Ch3: Design Response Data

discussion

要使用什麼樣的數據格式,html? xml? json? or...?

3.1 數據格式

  • JSON 已成為世上 API 數據格式的標準
  • 越來越多 API 只支持 JSON 一種格式

在線服務的數據格式

ServicesData Format
TwitterJSON
FacebookJSON(FQL 支持 XML)
FoursquareJSON
GitHubJSON
TumblrJSON
FlickrJSON、XML
AmazonXML
Open WeatherMapXML、JSON
Yahoo! JAPANXML、JSON、PHP serialize
RakutenXML、JSON
tip

Amazon 還只支持 XML ?????

  • JSON Api & XML ApiGoogle Trends 比較 api_format_trends

  • AJAX 曾經就是基於 XML 運行的

  • JSON 比 XML 應用更為廣泛?

    JSON 更簡單地表示相同數據、佔用空間小、更好的兼容 JavaScript

tip

! ! ! 越簡單易懂的東西越容易普及 ! ! !

其他數據格式

  • MessagePack
    • 使用二進位序列化組成
    • 一個類似 JSON 但比 JSON 更快、更小的數據格式(還不流行)

數據格式的指定方法

  • 使用查詢參數的方法
    • e.g. https://{FQDN}/v1/users?format=xml
  • 使用擴展名的方法
    • e.g. https://{FQDN}/v1/users.json
  • 使用在請求 Header 指定媒體類型的方法
    • e.g.
      GET /v1/users
      Host: api.example.com
      Accept: application/json
    • Accept & Content-Type
      • 客戶端可以使用 Accept Header field 來告知伺服器可接受的 response 媒體類型
      • Content-Type Header field 表示相關的媒體類型
      • 參考資料(以下重點)
        • The Accept header always indicates what kind of response from the server a client can accept.
        • Content-type is about the content of the current request or response, depending on which kind of HTTP message it is applied.

在線服務的數據格式

Service指定方法查詢參數名HTTP Header
Youtube查詢參數alt-
Flickr查詢參數format-
Twilio擴展名--
Linkedin查詢參數/請求 Headerformatx-li-format
Yahoo! JAPAN查詢參數output-
Rakuten查詢參數format-
Vimeo查詢參數output-
GitHub請求 Header-Accept

3.2 使用 JSONP

JSONP (JSON with Padding) 是一種將 JSON 傳遞給瀏覽器的方法

你看不到...你看不到...你看不到...(其實沒有很懂 JSONP 的用途 XD)

形式如下

callback({ id: 123, name: "Saeed" });
  • 在 JSON 加入填充信息
discussion

在 script 元素中的 callback 定義是一份 js 的資源?如何運作? (P.S. 看了 3.2.1 好像懂了那麼一點點)

<script src="https://api.example.com/v1/users?callback=callback">

  • XHTTPRequest 有同源策略的限制
  • script 元素不屬於同源策略限制範疇

3.2.1 支持 JSONP 的操作方法

  • 使用全局變量保存的方法
    var apidata = { id: 123, name: "Saeed" };
    • Web 頁面在讀取數據之前必須知道全局變量的名稱
    • 沒有使用 callback function 方便

取得 JSONP 的方式

Service查詢參數名取得 JSONP 的方法
Foursquarecallback透過 callback 查詢參數判斷
Linkedincallback指定 JSONP 作為數據格式
Instagramcallback透過 callback 查詢參數判斷
Twittercallback透過 callback 查詢參數判斷
Facebookcallback透過 callback 查詢參數判斷
GitHubcallback透過 callback 查詢參數判斷
Flickrjsoncallback返回標準的 JSONP
note

JSONP 是 javascript 不是 JSON,在設置 HTTP Content-Type Header 不能使用 application/json,而要使用 application/javascript

注意

在 Header 設置 X-Content-Type-Options: nosniff,在較新的瀏覽器中會發生錯誤,還有可能會引發安全漏洞等問題 ❗❗❗

3.2.2 JSONP 與錯誤處理

  • 在服務器返回錯誤狀態 (4XX、5XX) 時,script 元素會終止腳本載入,客戶端將無法知道發生什麼了。

  • 使用 JSONP 時,若發生錯誤,服務器依然必須返回 200,但須回應具體的錯誤內容。

    {
    status_code: 404,
    error_message: "Not Found"
    }

Foursquare 範例

{
"meta": {
"code": 200,
...meta信息
},
"response": {
實際數據
}
}

Linkedin 採用指定錯誤 callback 函數 (error-callback) 的方式

  • http://api.linkedin.com/v1/people/~:(id)?callback=firstNameResponse&error-callback=firstNameError
discussion

應該支持 JSONP?

3.3 數據內部結構與思考方法

如何盡可能減少 API 訪問次數

  • API 訪問次數增加的缺點
    • 會使用戶困擾(網頁載入慢 ex. teams......噓 🤫)
    • 增加 HTTP 負載
    • 降低應用程序速度
    • 加大服務器負擔
  • API 會被如何使用
  • 設計更加易用的 API
tip

Users 會以你想不到的方式來使用這些 API

想不到吧~~~ 誒~我就這樣亂打亂試

3.3.1 讓用戶來選擇 Response 內容

  • 盡可能返回更多的數據?

  • 數據量越小越好?

  • API 調用時透過查詢參數來指定所需字段

    http://api.example.com/v1/users/1234?fields=name,age

    通過字段名來指定所需資料

    Service查詢參數名示例省略時的行為
    bit.lyfieldscities, lang, url, referrer返回所有資料
    Etsyfields--
  • 指定組合名稱。(Amazon Product Advertising API 稱為 Response Group)

3.3.2 封裝是否必要

統一的結構將所有資料包起來

{
"header": {
"status": "success",
"errorCode": 0
},
"response": {
...實際數據
}
}
  • header 保存 API 狀態
  • response 保存有效數據(客戶端所需的數據)

優缺點

  • 更容易進行抽象處理
  • 可能會使資料變得冗長

    例如上述範例裡的 header,實際上 HTTP 已經完成封裝了

tip

使用 JSONP 時例外,封裝會更加便利,讀取 JSONP 數據時無法取得 HTTP 等資訊

3.3.3 數據是否應該扁平化

在對象中嵌套對象

  • 使用階層形式的資料

    {
    "id": 41234,
    "message": "Hello World!",
    "sender": {
    "id": 51245,
    "name": "Jay"
    },
    "receiver": {
    "id": 62124,
    "name": "Mech"
    },
    ......
    }
  • 使用扁平化形式的資料

    {
    "id": 41234,
    "message": "Hello World!",
    "sender_id": 51245,
    "sender_name": "Jay",
    "receiver_id": 62124,
    "receiver_name": "Mech",
    ......
    }

單純多項彙整

  • 使用階層形式的資料

    {
    "id": 52352,
    "name": "Jay",
    "profile": {
    "birthday": 1111,
    "gender": "male",
    "languages": ["zh", "en"]
    },
    ...
    }
  • 使用扁平化形式的資料

    {
    "id": 52352,
    "name": "Jay",
    "birthday": 1111,
    "gender": "male",
    "languages": ["zh", "en"],
    ...
    }

3.3.4 序列與格式

返回序列

[
{
"id": 51235,
"name": "Jay",
"profileIcon": "<url>",
...
},
{
"id": 42351,
"name": "Tom",
"profileIcon": "<url>",
...
}
]

作為對象返回

{
"friends": [
{
"id": 51235,
"name": "Jay",
"profileIcon": "<url>",
...
},
{
"id": 42351,
"name": "Tom",
"profileIcon": "<url>",
...
}
]
}

優點

  • 更容易理解序列數據表示的是什麼
  • 數據透過對象封裝實現了結構統一
  • 可以避免安全方面的風險
    • e.g. Facebook api_response_format

3.3.5 如何返回序列的個數以及是否還有後續數據

這小節個人覺得比較沒有特別重點

discussion

大家對於 如何返回數據數量及後續是否還有數據 有什麼想法嗎?

3.4 各個數據的格式

3.4.1 各個數據的名稱

Keys 命名要點

  1. 使用多數 API 中用來表示相同含義的單字
    • 不要賦予給一些常用單字不符合它所表達的含義 (e.g. user_id 存 user 登入時間)
  2. 盡可能簡短命名
    • e.g. user_registration_datetime -> registered_at or created_at
  3. key 包含多個單字時,API 中連接單字的方式要統一
  4. 盡可能不用奇怪的縮寫
    • e.g.
      • timeline -> tl
      • timezone -> tz
      • location -> lctn
  5. 注意單複數命名
    • e.g. 取得好友列表應該是 friends 而不是 friend
info

設計 API keys name 可以參考 ProgrammableWeb

3.4.2 如何表示性別數據

兩種主流方式

  1. male or female
  2. 1 表示男性,2 表示女性
tip

多數情況會以字串方式來描述性別。

sex基於生物學意義上的性別,gender基於社會、文化意義上的性別。

3.4.3 日期格式

format nameexample
RFC 822(RFC 1123 中修正)Sun, 06 Nov 1994 08:49:37 GMT
RFC 850(RFC 1036 中修正)Sunday, 06-Nov-94 08:49:37 GMT
ANSI C asctime() formatSun Nov 6 08:49:37 1994
RFC 33392015-10-12T11:30:22+09:00
Unix timestamp (epoch second)1396821803
  • epoch second: 紀錄 1970年01月01日 00點00分00秒(UTC) 到當前時間的秒數
RFC 3339

UTC 可以用 Z 標記

  • 2015-11-02T13:00:12+00:00
  • 2015-11-02T13:00:12+0000
  • 2015-11-02T13:00:12Z

有些 API 會在 Header 填入 Unix timestamp (Ch4 會討論)

個人經驗...

  • 不要自幹處理時間,盡量使用多人用的時間處理套件 😆
  • Backend 盡量使用 UTC 時間處理
tip

目前大多數都使用 RFC 3339 格式

-00:00 表示時區不明

3.4.4 大整數(bigint) 與 JSON

  • 64bit 整數當 32bit 處理會導致溢位
  • JavaScript 將所有數值都當作 IEEE 754 標準的 64bit 浮點數處理
var bitInt = 462781738297483264
console.log(bitInt)

# > 462781738297483260
note

Twitter API 同時將 ID & ID 的字串類型返回

{
"id": 266031293949698048,
"id_str": "266031293949698048"
}

3.5 Response 數據的設計

  • API 沒有必要如實反映 DB 的 table 結構
    • 若好友列表的 table 裡可能只包含 User Id,也不代表好友列表只能返回 User Id
  • 定義特定的數據結構,在 Client-side 就可以採用相同的程式碼處理
本節重點

必須仔細思考 API 的使用情境,設計出用戶使用起來易懂、方便的資料結構

3.6 錯誤訊息的表示

3.6.1 透過狀態碼來表示錯誤訊息

  • 選擇合適的 HTTP status code
HTTP/1.1 200 OK
Server: GitHub.com
Date: sun, 04 May 2014 22:25:56 GMT
Content-Type: application/json; charset=utf-8
......
status codemeaning
1XXInformational
2XXSuccessful
3XXRedirects
4XXClient errors
5XXServer errors
tip

找不到合適的 status code 時,使用"200" "400" "500"等...以"00"結尾的 status code

3.6.2 向 Client-side 返回詳細的錯誤訊息

  1. 返回在 Response Headers(定義私有 Header)
X-MYNAME-ERROR-CODE: 2013
X-MYNAME-ERROR-MESSAGE: Bad authentication token
X-MYNAME-ERROR-INFO: http://docs.exa,ple.com/api/v1/authentication
  1. 返回在 Response Data 中(JSON、XML 等)
{
"error": {
"code": 2013,
"message": "Bad authentication token",
"info": "http://docs.exa,ple.com/api/v1/authentication"
}
}

Twitter & GitHub Example

  • Twitter

    {
    "errors": [
    {
    "message": "Bad authentication data",
    "code": 215
    }
    ]
    }
  • GitHub

    {
    "message": "Not Found",
    "documentation_url": "https://developer.github.com/v3"
    }

3.6.3 如何返回詳細的錯誤訊息

  • 返回錯誤訊息、錯誤碼、錯誤連結等,具體且有效訊息
  • 錯誤碼應和 API 文檔一起提供
  • 錯誤訊息中可包含提供給開發人員與非開發人員的訊息
    {
    "error": {
    "developerMessage": "...for developer message...",
    "userMessage": "...for user message...",
    "code": 2013,
    "info": "http://docs.example.com/api/v1/authentication"
    }
    }

3.6.4 發生錯誤時防止返回 HTML

某些 API 發生錯誤時會 Response HTML (500, 503, 404 較常見),為了讓 Clinet-side 有更好的用戶體驗,我們需要多加注意 API Response 格式

3.6.5 維護與狀態碼

API 服務若要停止運行進行維護作業,需要提供用戶一些資訊

  • 503 status code
  • headers 帶入 Retry-After (Retry-After可以是具體日期或可以正常訪問的秒數)
tip

維護時間需要考慮一些預期以外的事情發生,進而準備充沛的時間處理

Space: the final frontier. These are the voyages of the starship Enterprise.

3.6.6 需返回意義不明確的訊息時

依業務場景及安全性考量,有時會並非壹定需要返回正確訊息

3.7 小結

Good ✅
  • 使用 JSON 或和目的一致的數據格式
  • 不要做多餘的封裝
  • Response data 盡可能使用扁平化結構
  • 數據名稱應簡潔易懂,並適當地使用單複數
  • 錯誤格式統一,使 Client-side 能方便的理解錯誤的詳細訊息