Hatena::ブログ(Diary)

Yet Another Hackadelic

2012-02-11

PATCH メソッド、新しい HTTP Status Code

Spec はあまりミーハーに追いかけても後で痛い目にあったりするもんですが、久しぶりに面白いなーと思ったのでちょっと取り上げてみます。

ちなみに斜め読みなので記事の正確性についてはあまり保証しませんw

PATCH Method for HTTP

RFC 5789 にある PATCH Method for HTTP ですが、RESTful API の致命的な弱点でもある PUT がリソースの完全なる置き換えなのに対して、PATCH は差分適用である所が中々面白いです。

2.1. A Simple PATCH Example のサンプルを見てみます。

PATCH /file.txt HTTP/1.1
Host: www.example.com
Content-Type: application/example
If-Match: "e0023aa4e"
Content-Length: 100

[description of changes]

Content-Type では元のリソースに対して何らかのパッチ形式を定義して、If-Match ヘッダでは該当するバージョンみたいな物を指定してるんでしょうね。

例えば SQL ならば、

UPDATE resources SET /* description of changes */ WHERE id = 'file.txt' AND version = 'e0023aa4e';

みたいなニュアンスなんでしょうね。3.1 The Accept-Patch Header で OPTIONS メソッドとして受け入れられる PATCH メソッドの本文形式を照会出来ると、まぁ良く出来てますねーって印象です。

OpenSocial の RESTful API だと、9.3 Partial Updates でなんとなーく規定されているように、

GET /appdata/@me/@self/@app

200 OK
Content-Type: application/json; charset=utf-8
ETag: "akb1"

{
 "pokes" : 3,
 "last_poke" : "2008-02-13T18:30:02Z"
}   

みたいなエントリがあったとして、

PUT /appdata/@me/@self/@app?fields=pokes
Content-Type: application/json; charset=utf-8
If-Match: "akb1"

{
  "pokes": 4
}

とかが Partial Updates ですね。そういう訳で PUT メソッドと fields パラメータにより結構強引に差分更新を表現していた所が、より明確な意味での実現が提供されてるって感じです。

Additional HTTP Status Codes

Additional HTTP Status Codes draft-nottingham-http-new-status-04 で定義されている幾つかの HTTP Status Code は少なすぎるレスポンスコードの言葉足らずな所を補う意味で良いんじゃないかなーと思ってます。

まだドラフトなんでアレですけど、今後期待したいなと。

3. 428 Precondition Required

3. 428 Precondition Required はさっきの PATCH メソッドによるリクエストの際に、If-Match によるバージョン指定をクライアントに要求とかそういう事が出来るっぽぃですね。

4. 429 Too Many Requests

429 Too Many Requests は現実的に使いたいシーンはあるだろうなーと。

今、こういうのを表現する際に、10.5.4 503 Service Unavailable を使ったりしてたんだけど、予めリクエスト数の上限についてガイドラインを提示しているのであれば、これはクライアント側の問題に帰着させるのが正しいと思うので4xxで表現するのが妥当だよなーなんて思う次第であります。

See Also

2011-02-13 心変わり

JSON-RPC, RESTful API とクエリパラメータ

OpenSocial の JSON-RPC, RESTful API の設計についてのよもやま話です。

JSON-RPC とクエリパラメータ

OpenSocial Core API Server Specification 1.1URL Addression と言うセクションがあります。

これは JSON-RPC を http GET で呼び出す際に params の部分など構造化されたデータをどうやって渡すのって際の仕様になります。

JSON Object URL Parameter
{ "field" : "value" } field=value
{ "field" : [1,2,3,4,5]} field=1,2,3,4,5
{ "field" : "12" } field='12'
{ "field" : [identifier,anotheridentifier]} field=identifier,anotheridentifier
{ "field" : ["value","another value"]} field=value,"another value"
{ "field" : ['value','another value']} field=value,'another value'
{ "field" : { "nested" : "value" }} field.nested=value
{ "field" : [{ "nested1" : "value1" }, { "nested2" : "value2" }]} field(0).nested1=value1&field(1).nested2=value2

まぁシングルクオートとダブルクオートを意図的に使い分けたいユースケースがさっぱり意味が分からない上に、identifier とか意味分かんないんでこの辺り謎過ぎるのですが、そういう細かい突っ込みはとりあえず置いておきます。

ところで JSON-RPC 2.0 の extension として定義されている JSON-RPC over HTTP によれば、GET の時の挙動は3.5 GET に書いてあり、params の部分はエンコードしろと書いてあります。どういう風にエンコードするかと言えば Base64 して URL Encode しろと言う感じです。

Pre-Encoded Params:
http://<end point>?method=sum&params={"a":3,"b":4}&id=2
http://<end point>?method=sum&params=[3,4]&id=1
Encoded Request:
http://<end point>?method=sum&params=eyJhIjozLCJiIjo0fQ%3D%3D&id=2
http://<end point>?method=sum&params=WzMsNF0%3D&id=1

って感じ。これはこれで単純明快ですね。

ちなみに先に JSON-RPC に対して結論を言っておくと、誰が楽しくて GET で叩くのか僕には意味が分かりません!*1大人しく POST 使いましょう。

RESTful API とクエリパラメータ

それでもやっぱり RESTful API が好き!と言う方も多いでしょう。僕もそうです。何度痛い目に合っても何故か好き。

まぁそんなことはどうでも良いのですが、OpenSocial RESTful API は中々面白い機能が幾つかあって、

  • Partial Update
  • Filter & Sort

辺りが挙げられます。Partial Update は冷静に考えると利便性の観点から出来て然るべきなんですが、ここでは触れない事にします。

Filter & Sort の辺りは Open Search 辺りからやってきた概念でしょう。

Filter や Sort なんですけど、例えばこんな風に書きます。

http://example.com/people/@me/@friends?filterBy=displayName&filterOp=startsWith&filterValue=zigo&sortBy=id&sortOrder=descending&count=50&startIndex=1

まぁ特に説明しなくても理解出来るとは思いますけど。

現時点の RESTful API の仕様では filter をもっと複雑に記述するなんて事は出来ません。と言うのも AND なのか OR なのかを指定する仕組みが無いからだと思うのですが、まぁ OR は要らないすよね。

そこで冒頭の JSON-RPC の URL Addressing から Syntax を借りてくると、こんな風に書けるかもしれません。

http://example.com/people/@me/@friends?filterBy(0)=displayName&filterOp(0)=startsWith&filterValue(0)=zigo,kaz,hid&filterBy(1)=gender&filterOp(1)=equals&filterValue(1)=male

この部分をデータ構造にすると、

{
  "filterBy": ["displayName", "gender"],
  "filterOp": ["startsWith", "equals"],
  "filterValue": [ ["zigo", "kaz", "hid"], "male" ]
}

みたいな感じとなり、SQL で表現すれば、

WHERE ( displayName LIKE 'zigo%' OR displayName LIKE 'kaz%' OR displayName LIKE 'hid%' ) AND gender = 'male';

のようになると。

実は某プラットフォームの API v2 ではこの記法が使えるらしいのですが、とりあえず undocument & no supports です。

雑感

とりあえず JSON-RPC に関して仮に GET をサポートするのであれば本家の方の仕様( Base64 )の方がすっきりしていて良いかなと思います。あと、一般論としてクエリパラメータとして何か構造化されたデータを埋め込む場合も、これと同様の手法の方が便利だろうなと思います。

RESTful API と filter の議論ですけど、こういうのもありかなとは思います。

*1:監視用途とかなら意味があるかもしれない