文章目錄
- URL Template Considered Harmful
- 參考資料
關於REST, 經常容易引起困惑的一個問題是:在
Machine-to-machine 的 REST 應用中,
用戶端怎麼可能在沒有任何預Crowdsourced Security Testing識的情況下就能跟隨伺服器端返回的超文本來自適應的將應用程式邏輯進行下去?
答案是不可能
. Client端必須提前瞭解足夠的
server端返回的超文本的知識
, 才能跟隨其中的指示將邏輯進行下去
.
那跟
SOAP, WSDL之類的又有啥區別
? 答案在於
REST 要求的
“足夠知識
”要遠遠弱於
WSDL對服務介面定義的約束
,
其靈活性則高於
WSDL. 畢竟
, REST 架構風格著眼於生命週期較長的
, 不斷演化的
, 跨組織邊界的應用程式
. 可以用來滿足
HATEOAS這個
REST約束的武器就是
Media Type: 擴充已有的
Media Type, 發明新的
Media Type,
並在合適的範圍內標準化
來看兩個例子
.
Web的成功與其說是
HTTP的成功
, 不如說是
HTML的成功
,
或者說
text/html這種
Media Type的成功
.
HTML定義了一組有限的
,
標準化的
, 特定領域的標籤
.
所有的
HTML用戶端都能理解並按照自己的能力渲染出
Web伺服器返回的任何合法的
HTML. 從全功能的
Chrome/FireFox
/Safari/IE, 到只是顯示文本的
lynx, 到資源受限環境下如手機中的各種瀏覽器
, 都能將多姿多彩的
Web呈現給終端使用者
, 並引導他們進行下一步互動
最經常被拿來作為
machine-to-machine REST 應用成功案例的則是
RSS/ATOM. 這族標準定義了新的
Media
Type, application/rss+xml
, application/atom+xml
. 不出意外的
, 這組
Media
Types定義了一組有限的
, 標準化的
, 特定領域的標籤
. 每一個
RSS/ATOM用戶端應用都可以從某個資源的
application/rss
(atom)+xml的表述開始
, 順藤摸瓜的遍曆每個感興趣的資源
. 這些用戶端都理解每一個
RSS/ATOM定義的標籤
. 服務端可以自由的改變新資源的
URI/URL Template而不必擔心破壞現有的
Client, 因為這些
Client並不依賴於預先設定的
URL Template, 而僅僅依賴於一個
Root的表述
, 及標準的
link relations
回到我們的問題
. 那麼
REST要求的
“足夠知識
”要弱到什麼程度
,
才能既使用戶端能理解伺服器給出的線索
, 又不致於耦合的太緊以致伺服器和用戶端無法獨立演化
?
觀察上面兩個例子
, 它們有以下共同特點
:
1. Response是
Well-formed
,可以被用戶端解析
. 這是廢話
,
除了
AI應用
, 絕大部分網路應用都得有明確定義的協議
.
但這意味著
REST 應用中也必須明確定義用戶端和伺服器資料交換的格式
2. Response中包含了當前上下文
(
或資源
)相關的資訊
, 但對我們這個問題來說更重要的是包含了對其它資源進行訪問的線索
.
是的
, 它是用最最基本
, 最最普通的
link 來實現的
. 足夠弱
. 那語義是否足夠清晰到用戶端能理解每一個
link, 能夠選擇正確的
link並知道如何訪問呢
? 我們來看看
link上都能承載啥語義
(from
http://www.ietf.org/rfc/rfc5988.txt):
Link = "Link" ":" #link-value link-value = "<" URI-Reference ">" *( ";" link-param ) link-param = ( ( "rel" "=" relation-types ) | ( "anchor" "=" <"> URI-Reference <"> ) | ( "rev" "=" relation-types ) | ( "hreflang" "=" Language-Tag ) | ( "media" "=" ( MediaDesc | ( <"> MediaDesc <"> ) ) ) | ( "title" "=" quoted-string ) | ( "title*" "=" ext-value ) | ( "type" "=" ( media-type | quoted-mt ) ) | ( link-extension ) ) link-extension = ( parmname [ "=" ( ptoken | quoted-string ) ] ) | ( ext-name-star "=" ext-value ) ext-name-star = parmname "*" ; reserved for RFC2231-profiled ; extensions. Whitespace NOT ; allowed in between. ptoken = 1*ptokenchar ptokenchar = "!" | "#" | "$" | "%" | "&" | "'" | "(" | ")" | "*" | "+" | "-" | "." | "/" | DIGIT | ":" | "<" | "=" | ">" | "?" | "@" | ALPHA | "[" | "]" | "^" | "_" | "`" | "{" | "|" | "}" | "~" media-type = type-name "/" subtype-name quoted-mt = <"> media-type <"> relation-types = relation-type | <"> relation-type *( 1*SP relation-type ) <"> relation-type = reg-rel-type | ext-rel-type reg-rel-type = LOALPHA *( LOALPHA | DIGIT | "." | "-" ) ext-rel-type = URI
這裡我們重點考察一下
rel
和
media-type
屬性
在
ATOM(http://www.ietf.org/rfc/rfc4287.txt)裡
, 預設定義了
5種
link
relations, 分別是
alternate, related, self, enclosure和
via. 標準賦予它們明確的語義
, 比如
alternate表示
link所指向的目標是當前資源的備用版本
. 用戶端因此可以理解每個
link的含義
, 自動或引導使用者完成後面的操作
.
如何完成?則牽扯到
media-type
. 比如如果
media-type定義的是
audio/mp4,用戶端則可以顯示播放選項或自動播放或乾脆忽略
.
這裡
link以及
media-type等屬性協助形成了一個遞迴的過程
. 我們從某個
link開始
, 知曉它的
media-type(所謂知曉它的
media-type,
就是能理解這種
media-type定義的每一個元素的語義
), 我們就能夠得到這個
link所指向的資源以這種
media-type表現出來的一種表述
, 其中包含了其它可用的
link以及
media-type, 可以讓這個過程一直繼續下去
, 直到用戶端覺得可以了或者服務端返回了不包含任何其它
link的表述
.
然而初始的
media-type和
link
relations總是有限的
, 我們如何應對現有
media-type表達不了的語義?這就是
HTML和
RSS/ATOM例子包含的第三個要素
3. 定義領域相關的
media-type
. HTML的問題域是如何表達各種顯示效果
, RSS/ATOM則是如何發布資訊
. media-type以及
link
relations等都是可擴充的
. 我們要做的就是為我們的特定領域的應用定義擴充
, 如果現存的被標準化的元素不夠用的話
. 這裡有一個問題
, 就是
REST應用的範圍的問題
. 如果我們的應用是面向
internet的
, 面向無數已知的未知的用戶端應用的
, 則意味著我們必須儘可能標準化我們擴充的
media-type, 或者至少讓它廣泛接受
.
而對於企業內部以
REST架構的應用
, 我們同樣面臨標準化的問題
, 只不過範圍可能小一點
, 至少是企業內部需要有共同接受的
media-type
前面我們看到了良好定義的
media-type是如何協助客戶應用理解
server端的
response而又不致緊密耦合服務端的實現的
. 我們需要一個企業開發的例子來驗證一下我們的理解
.
比如要開發一個企業內部不同應用之間共用使用者資訊的應用
, 包括許可權資訊
,
當前可以執行的操作
, 可以訪問的應用
, 以及應用之間共用的
Preference設定等
. 我們可以定義一種叫
application/vnd.tw.account+xml
的
media-type 可以有以下的片段
<account><br /> <preference><br /> <link rel="preference" media-type="application/vnd.tw.account.preference+xml" href="http://xxx/preference" mce_href="http://xxx/preference" /><br /> <link rel="edit" media-type="text/html" href="http://xxx/preference/editForm" mce_href="http://xxx/preference/editForm"><br /> </preference><br /> <subscribed-services><br /> <subscribed-service><br /> <name>Taxes</name><br /> <link rel="subscription" media-type="text/html" href="http://taxes.xxx.com" mce_href="http://taxes.xxx.com" /><br /> </subscribed-service><br /> <subscribed-service><br /> <name>Audit</name><br /> <link rel="subscription" media-type="text/html" href="http://audit.xxx.com" mce_href="http://audit.xxx.com" /><br /> </subscribed-service><br /> </subscribed-services><br /> <contacts><br /> <contact><br /> <name>Tom</name><br /> <link rel="contact" media-type="application/xfn+xml" href="http://account.xxx.com/tom" mce_href="http://account.xxx.com/tom" /><br /> </contact><br /> </contacts><br /></account><br />這樣無論是互動式用戶端像瀏覽器還是自動化的後台應用
, 都可以
follow這段
media的指示進行自己感興趣的操作
URL Template Considered Harmful
如果這些都實現的話
, 就可以有一個推論:
URL
Template不是必須的,甚至是有害的
. 它限制了伺服器端的變化
.
客戶應用應總是從
Root Resource開始
, 在特定
Media Type的引導下解析出其它的
URI, 給予服務程式演化的靈活性而不是按照
URL Template這種預先的知識來推算
.
用REST做過很多項目的Xu Hao
對Url
template也有同樣的看法:"URL template在我看來是有害的,它是一種隱含的伺服器和用戶端約定。此外還有一點,由於大多數URL
template是用來表達state
transfer的URL的,比如/xxx/approve之類的,使得用戶端必須瞭解伺服器的狀態轉移的細節,這在我看來是一個更大的問題。這使得客戶
端和伺服器的耦合變得更加緊密,同時這種風格極度鼓勵伺服器藉由用戶端來維持狀態的一致性"
很多REST相關的文章都把大量的篇幅給了
HTTP, 比如
HTTP
Verbs POST/GET/PUT/DELETE, HTTP Status Code等
, 而
media
type著墨不多
. Xu Hao在社區中曾很多次的提起自訂Media Type, 對REST應用不多可能當時不太明白, 現在越來越多的人認識到擴充和標準化更多的
media type才能更多的發揮
REST的潛力
參考資料
http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
尤其後面的討論
http://www.iana.org/assignments/link-relations/link-relations.xml
http://microformats.org/wiki/existing-rel-values
http://www.w3.org/TR/html401/types.html#type-links