HTTP

HTTP URI 設計 (URI Design)

統一資源識別符 (URI) 是 Web 架構中最簡單的元素,也是最重要的 !
 
良好的 URI 設計,並非只有『好看』而已,
往往牽涉到開發的彈性、可擴充性。
 


 

特性

(2-2) 資源、表示、URI:

URI 對映的是一個 資源資源 可以是任何東西,
資源 (resource),是對一實體集合的『概念』映射 (而非特定檔案)。

 
這種對於 資源 (resource) 的 抽象定義 (abstract definition),
是實現 Web 架構的 關鍵特性 (Key Features):
 
 

靜態 (static)

許多 Web 頁面、應用 依賴此特性,
使 URI 的引用,能保持 靜態 (static)
且訪問的結果,能 隨時間而變化
 
例如,此 button 將導向至 https://crazy.ck101.com/news/
而非 每個星期,都要 更改 一次 URI (不計重新導向):


 
又例如,隨著使用者人數成長,某公司欲部署/搬移 新 Server,
過程中可能涉及 儲存硬碟、Web Server 的更換 (e.g., Apache -> Nginx),
甚至是全新的 作業系統 (Windows -> Linux)…。
 
 

盡可能少更改

然而 HTTP 語意並不會變
倘若當初 URI 的設計良好,新環境僅需保持映射語意,
Client 仍藉由相同的方式與之互動,
除了 Server 提升健壯性、效能…,沒有其他變化發生。
 
再次,實現的關鍵在於 — (1-3) 統一介面 (Uniform Interface)

HTTP 透過向 客戶端 (Client) 提供 與資源種類無關的 統一介面 (uniform interface)
藉此 隱藏服務實現的細節

 
由於 URI 的 映射語意 (semantics of the mapping) 是 區別資源的關鍵 !
於是,一個簡單的 URI 設計原則:

URI 應 盡可能少地更改
(儘管 URI 是對映靜態文件)

 

通用性 (generality)

資源 (resource)表示 (representation)元資料 (metadata),包含了許多資訊來源,
而不需人為地以 種類 或 實作方式 加以區分,
進而提供 資源 的 通用性 (generality)
 
 

延遲綁定 (late binding)

代表允許 資源 延遲綁定 (late binding) 到 表示 (representation) 的參考,
並能基於請求訊息的性質,進行 內容協商 (Content Negotiation)
 
 

基於概念 (Based on Concept)

因為背後的資源可以是 任意東西 (e.g., 服務、運算、檔案),
若 部署、快取 的 安全性 與 彈性 允許,
應以 基於概念 (Based on Concept) 的 URI 映射 為主。
 
例如,對可能有多種 表示 (representation) 的資料 「使用者 9487」,
應使用 表頭欄位 (Header) 或 其他方式 進行 內容協商 尤佳,而非特定檔案 :

https://example.com/users/9487/ (O) https://example.com/users/9487.json (X)

 
然而,這仍須取決所使用的 快取策略。
 
若為不易改變的靜態資源 (某些 css、js),則較無影響 :

https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js (O)

 


 

名詞 vs. 動詞

HTTP 其中一個設計目標是:

分離『 資源辨識 (resource identification) 』 與 『 請求語意 (request semantics)

 
兩者的關係,就像是:

  • 目標(目標的) 用途
  • 名詞動詞
  • 工具使用方式

 
BUT 現實是殘酷的…😢
 
過去的十幾年,這個理念被忘的一乾二淨,
許多應用將一坨拉庫 『動詞』的 請求語意,
塞到『名詞』的 URI 之中。
 
以新增一個 id 為 9487 的使用者為範例:
 
 

錯誤示範

我要資源:『新增使用者』(X)
編號為: 9487 (O)

https://example.org/createUser?id=9487

沒道理…

 


 
若這樣改呢 ?
 
我要資源: 『動作』是『新增』的使用者 (X)
編號為: 9487 (O)

https://example.org/user?action=create&id=9487

也沒好到哪去 😐

 


 

正確示範

我要資源:『使用者』(O)
編號為: 9487 (O)

https://example.org/user?id=9487

 


 
或者將 query 置於路徑中:
 
我要資源:『使用者』(O)
編號為: 9487 (O)

https://example.org/user/9487/

 


 

可預見性

URI 時常具有 階層 (hierachy) 的觀念,
設計時,應優先考慮『可預見性』,
提供良好的邏輯,讓人們能直覺的猜到資源的位置與內容。
 
在 URL 中,即是以「 / 」(slash) 表達階層概念,例如:

https://example.org/users/Jason/ 使用者 Jason https://example.org/users/Jason/articles/20 使用者 Jason 的 20 號文章

 


 

善用子網域

(2-1) 統一資源識別符 (URI) 所述:
網域名稱 中,也包含了能依 網域擁有者 自行設定的 子網域 (Subdomain)
如最常見的 www (唸法: triple w)、本站的 blog
內容傳遞網路 常使用的 cdn、或其他的 apimailtw …等等:
 
Ex:

https://api.github.com https://www.github.com

 
與 『路徑』相比,兩者皆能實作負載平衡,並沒有絕對的好壞,
然而,個人認為 子網域 (Subdomain) 獨立部署、組織調校 上相對便利,故偏好之,
實務上仍應考量自身需求為主:

https://blog.jason.party https://jason.party/blog

 


 

總結

幾個常見的 URI 設計原則:

  • 使用名詞 而非動詞 (如上述範例)
  • 依 集合 與 個體,使用 複數單數 單字
  • 根據資源的關聯性,區分先後 順序
  • 視需求 使用『/』或『?
  • 單字間空白以 『』或『_』連接 (ex: 訂單列表 order-list)
  • 相同的資源可能有多個 URI

 
Ex:

https://example.org/users 使用者列表   https://example.org/users/Jason/ 使用者 Jason https://example.org/users?name=Jason 使用者 Jason   https://example.org/users/Jason/articles 使用者 Jason 的 文章列表   https://example.org/users/Jason/articles/20 使用者 Jason 的 20 號文章 https://example.org/users/Jason/articles?id=20 使用者 Jason 的 20 號文章

 
 

API

URI 實務上有非常多種風格,可多參考知名 API 實作。
 
例如:
GitHub API: https://api.github.com/
Facebook 的 Graph API: https://developers.facebook.com/docs/graph-api

 
 

作者: 鄭中勝
喜愛音樂,但不知為何總在打程式 ? 期許能重新審視、整理自身所學,幫助有需要的人。

發表迴響