paulwong

          RESTful 最佳實踐

          除了傳統對于遠程調用的需求,近來移動開發對于api的規范化需要,restful作為一個流行的接口調用方式,值得深入了解。

          聲明 本文屬于轉載:原文

          此文為實踐總結,是自己在實踐過程中積累的經驗和"哲學"。部分內容參考相關資料,參考內容請看尾頁。建議對RESTful有一定了解者閱讀!

          "哲學"

          • 不要為了RESTful而RESTful

          • 在能表達清楚的情況下,簡單就是美

          接口路徑設計

          接口設計原則

          URI指向的是唯一的資源對象
          示例: 指向ID為cloud.mario的Account對象

          GET http://~/$version/accounts/cloud.mario

          URI可以隱式指向唯一的集合列表

          示例: 隱式地指向trades list 集合

          GET http://~/$version/trades/(list)

          等同于

          GET http://~/$version/trades

          聚合資源必須通過父級資源操作

          示例ProfileUser的聚合資源,User有一個唯一且私有的Profile資源,只能通過User操作Profile。

          更新user_id為123456的Profile資源 PUT http://~/$version/users/123456/profiles Request Body:{       "full_name": "cloud.mario",       "state": "Beijing",       "title": "一個開發者"   }

          Http Methods

          HTTP OperationDescription
          GET獲取,查找
          POST新增創建
          PUT更新
          PATCH部分更新
          DELETE刪除

          URL組成

          1. 網絡協議(HTTP, HTTPS)

          2. 服務器地址

          3. 版本

          4. 接口名稱

          5. ?參數列表

          GET https://github.com/v1/trades

          為什么需要版本?

          當服務被更多其他系統使用的時候,服務的可用性和上下兼容變得至關重要。被外部系統依賴的服務在升級時是一個非常麻煩的事情,既要發布新的接口,又要保留舊的接口留出時間讓調用者去升級。在URL中加入Version標示能很好地解決上下兼容(新老版本共存)問題。

          示例1: URL中新增了Path parameter

          v1版本

          GET http://~/v1/trades?user_id=123456

          v2版本

          GET http://~/v2/:user_id/trades

          示例1中的user_id參數在v2版本被加入到path parameter中,使用$version保證了v1v2接口的共存。

          示例2: 數據接口發生變化

          v1版本

          GET http://~/v1/accounts/cloud.mario Response Body: {       "user_name": "cloud.mario",       "e_mail": "cloud.mario@gmail.com",       "state": "Beijign",       "title": "一個開發者" }

          v2版本

          GET http://~/v2/accounts/cloud.mario Response Body: {           "user_name": "cloud.mario",           "e_mail": "cloud.mario@gmail.com",           "profile":      {                   "state": "Beijign",                   "title": "一個開發者"              } }

          示例2中的接口返回數據結構已經發生了變化。使用$version保證了v1v2接口的共存。

          URL定義限制

          1. 不使用大寫字母

          2. 使用中線-代替下劃線_

          3. 參數列表應該被encode過

          接口分類

          資源對象的CURD操作

          GET http://~/$version/trades                 #獲取trades列表 GET http://~/$version/trades/:id            #根據id獲取單個trade POST http://~/$version/trades               #創建trade PUT http://~/$version/trades/:id             #根據id更新trade PATCH http://~/$version/trades/:id        #根據id部分更新trade DELETE http://~/$version/trades/:id      #根據id刪除trade

          系統設置

          使用settings標識,根據服務的屬性選擇http方法。

          http://~/settings/$version/server-name

          示例1: 搜索

          GET http://~/services/$version/search?q=filter&category=file

          示例2: 任務隊列操作

          PUT http://~/services/$version/queued/jobs          往任務隊列里面添加一個新的任務 DELETE http://~/services/$version/queued/jobs/:id   根據id刪除任務

          示例3: 更改界面語言環境

          PUT http://~/settings/$version/gui/lang {    "lang": "zh-CN"}

          為什么需要區分?

          1. Microservices

          Microservices是一個全新的概念,它主要的觀點是將一個大型的服務系統分解成多個微型系統。每個微型系統都能獨立工作,并且提供各種不同的服務。獨立運行的特點使微型系統之間不會產生相互影響,其中的一個微型系統宕機并不會牽連到其他的微型系統。這種架構使分布式系統的節點數量大大提升。因為RESTful服務是無狀態的,所以這種分解并不會帶來狀態共享的問題。

          1. 路由規則(邏輯)

          當我們需要對不同屬性的接口做路由規則的時候,按功能劃分接口是一個很好的方案。例如:我們要對系統設置接口設置增加更嚴格的調用限制。

          緩存

          網絡接口相對于堆棧接口來說數據傳輸極其不穩定,盡可能地減少數據傳輸不僅能控制這種風險還能減少流量。使用緩存還能有效地提高后臺的吞吐量。

          后臺在響應請求時使用響應頭E-TagLast-Modified來標記數據的版本,前臺在發送請求時將數據版本通過請求頭If-Match幫助后臺判斷緩存的使用。

          Request Header

           If-Match: 2390239059405940

          Response Header

          E-Tag: 2390239059405940Last-Modified: 1403183502701

          Bookmarker

          在實際的環境中,有大量的查詢需求是相同的。將這些搜索需求標簽化能降低使用難度也可以達到重用的目的。

          示例: 查找狀態為關閉的訂單

          普通方式
          GET http://~/$version/trades?status=closed&sorting=-created_at

          Bookmarker
          GET http://~/$version/trades#recently_closed

          GET http://~/$version/trades/recently_closed

          HATEOAS

          HATEOAS通過Web Linking的方式來描述程序的狀態信息

          Link 主要包含以下屬性:

          PropertyDescription
          rel關聯內容
          hrefURL
          type媒體類型
          methodHttp Method
          title標題
          arguments參數列表
          value返回值

          Rel 可能為以下值:

          ValueDescription
          next下一步
          prev上一步
          first第一步,最前
          last最后一步,最后
          source來源
          self資源自身,相對于this

          Web Linking 可以通過兩種方式傳遞至客戶端:

          Http Header

          Link: <http://~/$version/trades?page_no=10>; rel="next", <http://~/$version/trades?page_no=19>; rel="last"

          Http JSON Body

          {     "links": [         {             "rel": "next",             "href": "http://~/$version/trades?page_no=1"         },         {             "rel": "last",             "href": "http://~/$version/trades?page_no=19"         }     ] }

          示例1: 用戶注冊業務

          1. 用戶填寫E-Mail與密碼

          2. 完善用戶資料

          Register Request

          POST http://~/$version/accountsHeaders:     Accept: application/json         Content-Type: application/json;charset=utf-8Body:     {             "username": "cloud.mario@gmail.com",             "e_mail": "cloud.mario@gmail.com",             "password": "balabala"     }

          Register Response

          Headers:     Content-Type: application/json;charset=utf-8 Status: 201 Created Body:     {              "uri": "http://~/$version/accounts/cloud.mario",             "identity": "cloud.mario",             "created_at": 1403535668653,             "links": [             {                             "rel": "next",                             "href": "http://~/$version/accounts/cloud.mario/profiles",                             "method": "POST",                             "title": "Editing Profiles",                             "arguments": "status=editing"             }         ]     }

          Profile Request

          POST http://~/$version/accounts/cloud.mario/profilesHeaders:     Accept: application/json         Content-Type: application/json;charset=utf-8Body:     {             "full_name": "cloud.mario",             "state": "Beijing",             "title": "一個開發者"     }

          Profile Response

          Headers:     Content-Type: application/json;charset=utf-8Status: 201 Created Body:     {             "uri": "http://~/$version/accounts/cloud.mario/profiles",             "identity": "cloud.mario",            "created_at": 1403535668653     }

          示例2: 請看下節<分頁>

          HATEOAS在解決什么問題?

          HATEOAS是Hypermedia as the Engine of Application State的縮寫形式,中文意思為:超媒體應用狀態引擎。它的核心思想是使用超媒體表達應用狀態,與hypertext-driven思想是一致的。在此之前,我們大多數的程序業務控制在前臺完成。例如:我們會在前臺做注冊流程,我們在前臺判定下一步應該做什么,可以做什么。當使用HATEOAS時,這些狀態流程控制都在應用程序的后臺完成。我們使用超媒體來表達前臺做完某一步驟之后可以做哪些? 這樣一來,前臺的任務就變得相當簡單了,前臺需要處理的是理解狀態表述,數據收集和結果顯示。

          思考

          HATEOAS會帶來怎樣的改變? 使用它的意義在哪?

          分頁

          Request

          GET http://~/$version/trades?page=10&pre_page=100

          Response

          Link Header

          Link: <http://~/$version/trades?page=11&pre_page=100>; rel="next", <http://~/$version/trades?page=19&pre_page=100>; rel="last"

          JSON Body

          {     "links": [         {             "rel": "next",             "href": "http://~/$version/trades?page=11&pre_page=100"         },         {             "rel": "last",             "href": "http://~/$version/trades?page=19&pre_page=100"         }     ] }

          安全

          調用限制

          為保證服務的可用性應對服務進行調用過載保護

          Response Headers

          X-RateLimit-Limit: 3000             調用量的最大限制 X-RateLimit-Reset: 1403162176516    調用限制重置時間 X-RateLimit-Remaining: 299          剩余的調用量

          安全驗證

          RESTful服務使用Oauth2的方式進行調用授權,使用http請求頭Authorization設置授權碼; 必須使用User-Agent設置客戶端信息, 無User-Agent請求頭的請求應該被拒絕訪問。

          Request Header

          User-Agent: Data-Server-Client Authorzation: Bearer 383w9JKJLJFw4ewpie2wefmjdlJLDJF

          為什么建議使用Oauth2授權?

          Oauth2的參與者為:客戶端,資源所有者,授權服務器,資源服務器??蛻舳讼葟馁Y源所有者得到授權碼之后使用授權碼從授權服務器得到token,再使用token調用資源服務器獲取經過資源所有者授權使用的資源。這種授權方式的特點有:

          1. 資源所有者可以隨時撤銷授權許可

          2. 可以通過撤銷token拒絕客戶端的調用

          3. 資源服務器可以拒絕客戶端的調用

          通過這三種方式可以做到對資源的嚴格保護。資源的訪問權限也把握在資源所有者的手中,而不是資源服務器。
          當然,Oauth2授權框架也允許受信任的客戶端直接使用token調用資源服務器獲取資源。這種靈活性完全取決于客戶端類型和對資源的保護程度。

          為什么授權碼要放在Http Header中?

          1. WEB服務器對訪問做記錄已經成為了行業的一個標準,訪問記錄不僅可以用來做訪問量統計還能用來做訪問特征分析?;ヂ摼W廣告平臺就是利用訪問記錄來做精準營銷的。如果token(授權碼)包含在URL中就有很大的安全風險。

          2. 包含在URL中的token串可能被進行重定向傳遞。通過這兩種方式入侵者可以不通過授權而使用泄漏的授權碼訪問那些受保護的數據,會造成數據泄漏的風險。

          以Apache為例,訪問日志為:

          127.0.0.1 - - [24/Jun/2014:14:38:04 +0800] "GET /v1/accounts/cloud.mario?token=dgdreLJLJLER798989erJKJK HTTPS/1.1" 200 343

          通過對訪問日志的提取,很容易得到token信息。

          數據設計

          交互原則

          1. 查詢,過濾條件使用query string。

          2. 用來描述數據或者請求的元數據放Header中,例如 X-Result-Fields。

          3. Content body 僅僅用來傳輸數據。

          4. 數據要做到拿來就可用的原則,不需要“拆箱”的過程。

          結構

          使用JSON格式傳輸數據,在http請求頭和響應頭申明Content-Type。返回的數據結構應該做到盡可能簡單,不要過于包裝。響應狀態應該包含在響應頭中!

          Request

          Accept: application/json Content-Type: application/json;charset=UTF-8

          Response

          Content-Type: application/json;charset=UTF-8

          錯誤的做法

          {     "status": 200,     "data": {         "trade_id": 1234,         "trade_name": "Bala bala"     } }

          正確的做法

          Response Headers:     Status: 200     Response Body:     {             "trade_id": 1234,             "trade_name": "Bala bala"     }

          示例1: 創建User對象

          POST http://~/$version/users Request     headers:         Accept: application/json                Content-Type: application/json;charset=UTF-8     body:         {                     "user_name": "Cloud Mario"         } Response     status: 201 Created     headers:         Content-Type: application/json;charset=UTF-8     body:         {                     "uri": "http://~/$version/users/1234",                     "identity": 1234,                     "created_on": "Date()",                     "links": [                 {                                     "rel": "next",                                     "href": "http://~/gui/users/1234"                 }             ]         }

          為什么是JSON?

          JSON 是一種可以跨平臺高擴展的輕量級的數據交換格式。易于人閱讀和編寫,同時也易于機器解析和生成。

          屬性定義限制

          1. 不能使用大寫(大小寫友好)

          2. 使用下劃線_命名(連接兩個單詞)

          3. 屬性和字符串值必須使用雙引號""

          提取部分字段

          無狀態服務器應該允許客戶端對數據按需提取。在請求頭使用X-Result-Fields指定數據返回的字段集合。

          例如:trade 有trade_id, trade_namecreated_at 三個屬性,客戶端只需其中的trade_idtrade_name屬性。

          Request Header

          X-Result-Fields: trade_id,trade_name

          子對象描述

          數據里面的子對象使用URI描述不應該被提取,除非用戶指定需要提取子對象

          示例trade里面的order對象

          錯誤的做法

          {     "trade_id": "123456789",     "full_path": null,     "order": {         "order_id": "987654321"     } }

          正確的做法

          {     "trade_id": "123456789",     "order": "http://~/$version/orders/987654321" }

          應用指定提取子對象,需要在請求頭聲明X-Expansion-Fields

          Request

          X-Expansion-Fields: true

          為什么要客戶端指定提取子對象時才提取?

          懶模式服務能夠最大程度地節省運算資源。雖然與客戶端交互的次數有所增加,但是能做到按需提取,按需響應,這也是響應式設計的一大特點。客戶端的用戶行為模式無法真實地模擬,也就無法確定哪些資源需要做到一次性推送,讓客戶端按需使用是一個不錯的方式。

          關于空字段

          應該在返回結果里面剔除空字段,因為null值傳輸到客戶端并沒有實際的含義,反而增加了占用空間。

          Tips

          使用HTTP Header時,優先使用合適的標準頭屬性。用X-作為前綴自定義一個頭屬性,例如: X-Result-Fields。

          狀態碼&錯誤處理

          應用狀態碼

          CodeHTTP OperationBody ContentsDescription
          200GET,PUT資源操作成功
          201POST資源,元數據對象創建成功
          202POST,PUT,DELETE,PATCHN/A請求已經被接受
          204DELETE,PUT,PATCHN/A操作已經執行成功,但是沒有返回數據
          301GETlink資源已被移除
          303GETlink重定向
          304GETN/A資源沒有被修改
          400GET,PSOT,PUT,DELETE,PATCH錯誤提示(消息)參數列表錯誤(缺少,格式不匹配)
          401GET,PSOT,PUT,DELETE,PATCH錯誤提示(消息)未授權
          403GET,PSOT,PUT,DELETE,PATCH錯誤提示(消息)訪問受限,授權過期
          404GET,PSOT,PUT,DELETE,PATCH錯誤提示(消息)資源,服務未找到
          405GET,PSOT,PUT,DELETE,PATCH錯誤提示(消息)不允許的http方法
          409GET,PSOT,PUT,DELETE,PATCH錯誤提示(消息)資源沖突,或者資源被鎖定
          415GET,PSOT,PUT,DELETE,PATCH錯誤提示(消息)不支持的數據(媒體)類型
          429GET,PSOT,PUT,DELETE,PATCH錯誤提示(消息)請求過多被限制
          500GET,PSOT,PUT,DELETE,PATCH錯誤提示(消息)系統內部錯誤
          501GET,PSOT,PUT,DELETE,PATCH錯誤提示(消息)接口未實現

          容器狀態碼

          容器狀態碼是指http容器的狀態碼,應用不應該使用或限制使用

          CodeHTTP OperationBody ContentsDescription
          303GETlink靜態資源被移除,應用限制使用
          503GET,PSOT,PUT,DELETE,PATCHtext body服務器宕機

          Tips

          4開頭的錯誤用來表達來自于客戶端的錯誤,例如: 未授權,參數缺失。5開頭的錯誤用來表達服務端的錯誤,例如: 在連接外部系統(DB)發生的IO錯誤。

          錯誤信息格式

          錯誤信息應該包含下列內容:

          1. 錯誤標題 message, 必須

          2. 錯誤代碼 error code, 必須

          3. 錯誤信息 error message, 必須

          4. 資源 resource, 可選

          5. 屬性 field, 可選

          6. 文檔地址 document, 可選

          Tips

          Error Code 盡可能做到簡潔明了,提取異常的關鍵字并且使用下劃線_把它們連接起來。

          示例: 調用頻率超過限制,Response:

              Headers:         Content-Type: application/json;charset=UTF-8         X-RateLimit-Limit: 3000         X-RateLimit-Reset: 1403162176516         X-RateLimit-Remaining: 0     {             "message": "Message title",              "errors": [             {                              "code": "rate_limit_exceeded",                             "message": "Too Many Requests. API rate limit exceeded",                             "document": "https://developer.github.com/v3/gists/"             }         ]     }

          錦上添花

          1. 格式化(Pettyprint)JSON數據(返回結果)并且使用gzip壓縮,Pettyprint易于閱讀,多余的空格在經過gzip壓縮之后占用空間比壓縮之前更小。

          2. 重寫Server

          3. 返回X-Powered-By

          Response Headers

          X-Pretty-Print: true Content-Encoding: gzip Server: cloud.mario@sina.com X-Powered-By: cloud.mario;email=cloud.mario@gmail.com

          資源福利

          框架&工具

          參考資料

          posted on 2014-08-18 08:47 paulwong 閱讀(6476) 評論(0)  編輯  收藏 所屬分類: RESTFUL-API


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 当阳市| 乌鲁木齐县| 襄樊市| 阜南县| 德保县| 大邑县| 怀宁县| 南澳县| 辰溪县| 大荔县| 土默特左旗| 县级市| 乡宁县| 新闻| 珠海市| 龙山县| 吉木乃县| 阳泉市| 嵊州市| 小金县| 珠海市| 丹东市| 新乐市| 剑阁县| 綦江县| 乌拉特前旗| 阳原县| 固原市| 西峡县| 方正县| 托里县| 博兴县| 东乌| 舒城县| 稻城县| 大厂| 临汾市| 泰州市| 蕲春县| 日喀则市| 新疆|