yohei-y:weblog

XML と REST/Web サービス関連の話題が中心の weblog です

2007-06-18

HTTP ステータスコードを正しく使おう

先月、ぐるなび API がリリースされていました。 ぐるなびさんの持っている膨大なデータベースに Web API を通して気軽にア クセスできるようになったのは、非常に喜ばしいし、その英断に感謝したいと 思います。

しかし、Web API 仕様書、特にエラー仕様を見てちょっとがっかりしました。 もう少し上手にデザインすれば、もっとよかったのに…、という思いです。

一度出してしまった API はそう簡単に変えられないと思いますが、 参考までに僕だったらどうするか、を書いてみます。

この仕様の一番の問題はエラーコードです。 以下は 2-2 のエラー仕様に記述されているサンプルです。

<?xml version="1.0" encoding="UTF-8"?>
<gnavi>
 <error>
   <code>602</code>
 </error>
</gnavi>

タグが三つ(gnavi, error, code)出てきます。 重要なのは code だけで、ここにエラーコードが入ります。 602 というコードは Invalid Shop Number を示します。 エラーコード一覧を見ると、現在五つのコードが定義されていることがわかり ます。

よくない点を一言で言うと、エラーコードを再発明してしまっているということです。 たとえば 604 は "Internal Server Error" なんですが、 このフレーズに覚えがありませんか? そう HTTP の 500 Internal Server Error と同じです。HTTP で 500 を返せばいいところを、 独自 XML 形式でしかも 604 という独自のエラーコードを再発明しています。

HTTP/1.1 200 OK
Content-Type: application/xml

<?xml version="1.0" encoding="UTF-8"?>
<gnavi>
 <error>
   <code>604</code>
 </error>
</gnavi>

本来はこうあるべきです。

HTTP/1.1 500 Internal Server Error
Content-Type: text/plain; charset=utf-8

処理中にエラーが発生しました。

なぜエラーコードの再発明は駄目なのでしょうか。それは専用のクライアントが必要になる からです。単なる HTTP クライアントではなく、ぐるなびのエラーコードを実 装した専用クライアントが必要になってしまうからです。専用クライアントが 必要なので、その分余計なコードが必要となって、障害が発生する確立も上り ます。

参考までに、ぐるなび API のエラーコードを HTTP のステータスコードにマッピングしてみました。

gnavi エラーコードHTTP ステータスコード
600 NoShop404 Not Found
601 Invalid Access403 Forbiddden
602 Invalid Shop Number400 Bad Request
603 Invalid Type400 Bad Request
604 Internal Server Error500 Internal Server Error

601 は通常は 401 Unauthorized にしたいところですが、 ぐるなび API は api key 方式を採用しているので 403 にしてみました。 また、この対応により 602 と 603 が 400 にまとめられてしまっていますが、 両者の違いは HTTP のレスポンスボディで記述すればいいでしょう。 もし、この二つを区別する理由が、エラーメッセージを出すためだけであれば、 メッセージそのものをプレーンテキストで返せばいいのです。

HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=utf-8

指定された店舗の情報が存在しません。
HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=utf-8

不正なぐるなび店舗IDパラメータが指定されました。

WEB+DB Press の今月号には、まさにこの話を書きました。 本文とコラムが同じくらいのページ数という、一時期の JavaWorld の檜山さ んみたいな構成ですが、 普段なにげなく使っている HTTP のステータスコードは Web API を作る上でどうあるべきか、という話です。

photo
WEB+DB PRESS Vol.39
WEB+DB PRESS編集部
技術評論社 2007-06-22

ラベル: , ,

2005-05-22

REST 入門(補足2) POST と PUT

» REST 入門 目次

kwatch さんから以下のようなコメントをもらいました。

POSTとPUTの説明が逆では?たしかPUTが新規作成であり、POSTは送ったリソースの処理を指定したURIに任せるということだったと思いますが。

コメント欄では返答が書ききれなかったので、補足として新しいポストを追加します。

RFC2616 では PUT の動作が二つ規定されています。

指定した URI がすでに存在している場合
PUT はその URI のリソースを修正(更新)する
指定した URI が存在しない場合
PUT はその URI のリソースを新規作成する

PUT を規定している 9.6 節には POST と PUT の違いとして以下の記述があります。

The fundamental difference between the POST and PUT requests is reflected in the different meaning of the Request-URI. The URI in a POST request identifies the resource that will handle the enclosed entity. That resource might be a data-accepting process, a gateway to some other protocol, or a separate entity that accepts annotations. In contrast, the URI in a PUT request identifies the entity enclosed with the request -- the user agent knows what URI is intended and the server MUST NOT attempt to apply the request to some other resource. If the server desires that the request be applied to a different URI, it MUST send a 301 (Moved Permanently) response; the user agent MAY then make its own decision regarding whether or not to redirect the request.

つまり PUT でも POST でもリソースを新規作成することができるのですが、以下の違いがあるということです。

PUT でリソースを新規作成する場合
クライアントが指定した URI のリソースが新規作成される
POST でリソースを新規作成する場合
クライアントが指定する URI はリソースを新規作成するリソースの URI。新規作成されたリソースの URI はサーバが決定する

ここまでは HTTP 1.1 の規定の話です。 論点は、リソースの URI を PUT でクライアントが作るのか POST でサーバが作るのか、ということです。 REST では URI はクライアントにとって不透明(opaque)であるべき、という原則があります。 そのココロはクライアントとサーバの結びつきをなるべく減らす、というものです。 PUT でリソースを新規作成する場合、クライアントはそのサーバが提供する URI 空間の管理方法を別途知らなければなりません。 このような密結合は REST あるいは Web サービスの世界では厳禁事項の一つです。

たとえば現状の Atom Publishing API では、 新しいリソースの作成はコレクション URI に POST することになっています。

REST 入門の一連のポストこのような理由から、リソースの新規作成は POST で行う、と説明しています。

ラベル: ,

2005-04-29

REST 入門(その5) 四つの動詞 -- GET, POST, PUT, DELETE

» REST 入門 目次

前回、URI で特定できるリソースに HTTP の GET という動詞を適用して、 ある時点・条件での状態の表現を転送するのが REST だという説明をしました。 URI (名詞)に適用できる動詞は GET だけではありません。

今回は GET 以外の三つの動詞を紹介します。

前提知識

ここでは実際に稼動している REST 実装の例としてはてなブックマーク AtomAPI を使います。 はてなブックマークそのものの説明はしませんので、あらかじめご了承ください。

はてなブックマークに登録したひとつのブックマークを考えてみてください。 このひとつのブックマークエントリが、対象のリソースになります。 たとえば REST 入門の目次をブックマークしたとします。 このブックマークの URI は http://b.hatena.ne.jp/atom/edit/175062 です。 まずは GET してみましょう。

GET /atom/edit/175062 HTTP/1.1
Host: b.hatena.ne.jp
X-WSSE: UsernameToken Username="yohei", ...

HTTP/1.1 200 OK
Content-Type: application/x.atom+xml

<entry xmlns="http://purl.org/atom/ns#">
  <title>傭兵日記: REST 入門</title>
  <link rel="related" type="text/html"
      href="http://yohei-y.blogspot.com/2005/04/rest_23.html" />
  <link rel="alternate" type="text/html"
      href="http://b.hatena.ne.jp/yohei/20050424#175062" />
  <link rel="service.edit" type="application/x.atom+xml"
    href="http://b.hatena.ne.jp/atom/edit/175062" title="傭兵日記: REST 入門" />
  <author>
    <name>yohei</name>
  </author>
  <generator url="http://b.hatena.ne.jp/" version="0.1"
    >Hatena::Bookmark</generator>
  <issued>2005-04-24T:14:46:00+9:00</issued>
  <id>tag:hatena.ne.jp,2005:bookmark-sample-175062</id>
  <summary type="text/plain">REST の入門</summary>
</entry>

実際にはてなブックマークから GET するには認証用の X-WSSE ヘッダが必要なことに注意してください。 また、レスポンスに含まれるのは HTML ではなく XML 形式のブックマークとなります。 XML に含まれる情報で重要なのは title 要素と summary 要素になります。 summary 要素はいわゆる「コメント」です。

以下では、このブックマークエントリ(リソース)の URI に、 GET 以外の HTTP メソッドを適用してみます。

リソースを更新 -- PUT

一度は登録したけれど、つけたコメントが気に入らないので修正したいとします。 こんなときは HTTP の PUT メソッドを使います。PUT リクエストはこのようになります。

PUT /atom/edit/175062 HTTP/1.1
Host: b.hatena.ne.jp
Content-Type: application/x.atom+xml
X-WSSE: UsernameToken Username="yohei", ...

<entry xmlns="http://purl.org/atom/ns#">
  <summary type="text/plain"
    >REST(Representational State Transfer) の入門</summary>
</entry>

PUT のレスポンスはこのようになります。

HTTP/1.1 200 OK

今、更新した URI を再度 GET することができます。

GET /atom/edit/175062 HTTP/1.1
Host: b.hatena.ne.jp
X-WSSE: UsernameToken Username="yohei", ...

取得される XML は、コメントが修正されたものになっているはずです。

HTTP/1.1 200 OK
Content-Type: application/x.atom+xml

<entry xmlns="http://purl.org/atom/ns#">
  <title>傭兵日記: REST 入門</title>
  <link rel="related" type="text/html"
      href="http://yohei-y.blogspot.com/2005/04/rest_23.html" />
  <link rel="alternate" type="text/html"
      href="http://b.hatena.ne.jp/yohei/20050424#175062" />
  <link rel="service.edit" type="application/x.atom+xml"
    href="http://b.hatena.ne.jp/atom/edit/175062" title="傭兵日記: REST 入門" />
  <author>
    <name>yohei</name>
  </author>
  <generator url="http://b.hatena.ne.jp/" version="0.1"
    >Hatena::Bookmark</generator>
  <issued>2005-04-24T:14:46:00+9:00</issued>
  <id>tag:hatena.ne.jp,2005:bookmark-sample-175062</id>
  <summary type="text/plain"
    >REST(Representational State Transfer) の入門</summary>
</entry>

リソースを削除 -- DELETE

ブックマークを削除したい場合は DELETE メソッドを使います。

DELETE /atom/edit/175062 HTTP/1.1
Host: b.hatena.ne.jp
X-WSSE: UsernameToken Username="yohei", ...

HTTP/1.1 200 OK

DELETE が成功すると、同じ URI を再度 GET することはできません。

GET /atom/edit/175062 HTTP/1.1
Host: b.hatena.ne.jp
X-WSSE: UsernameToken Username="yohei", ...

レスポンスは HTTP エラーになります。

HTTP/1.1 404 Not Found

リソースを新規作成 -- POST

先ほどのブックマーク、消してしまったけれどやっぱり元に戻したいとします。 そんなときは POST でブックマークリソースを新規に作成します。 でも、どの URI に POST したらよいのでしょうか。 リソースを新規に作成するわけですから、リソース自体がまだ存在せず、 したがって URI もありません。

こういう時はリソースを新規作成するためのリソースを用意します。 はてなブックマークではこのリソースの URI を PostURI と呼んでいます。 それでは POST してみましょう。

POST /atom/post HTTP/1.1
Host: b.hatena.ne.jp
X-WSSE: UsernameToken Username="yohei", ...

<entry xmlns="http://purl.org/atom/ns#">
  <link rel="related" type="text/html"
      href="http://yohei-y.blogspot.com/2005/04/rest_23.html" />
  <summary type="text/plain"
    >REST(Representational State Transfer) の入門</summary>
</entry>

正常に受け付けられると以下のようなレスポンスが返ってきます。

HTTP/1.1 201 OK
Content-Type: application/x.atom+xml
Location: http://b.hatena.ne.jp/atom/edit/xxxx

<entry xmlns="http://purl.org/atom/ns#">
  <title>傭兵日記: REST 入門</title>
  <link rel="related" type="text/html"
      href="http://yohei-y.blogspot.com/2005/04/rest_23.html" />
  <link rel="alternate" type="text/html"
      href="http://b.hatena.ne.jp/yohei/20050424#xxxx" />
  <link rel="service.edit" type="application/x.atom+xml"
    href="http://b.hatena.ne.jp/atom/edit/xxxx" title="傭兵日記: REST 入門" />
  <author>
    <name>yohei</name>
  </author>
  <generator url="http://b.hatena.ne.jp/" version="0.1"
    >Hatena::Bookmark</generator>
  <issued>2005-04-24T:17:46:00+9:00</issued>
  <id>tag:hatena.ne.jp,2005:bookmark-sample-xxxx</id>
  <summary type="text/plain"
    >REST(Representational State Transfer) の入門</summary>
</entry>

レスポンスには Location ヘッダがあります。 これが新規作成されたリソースの URI になります。

POST メソッドは、その自由度の高さから、濫用される傾向にあります。 実際には GET, PUT, DELETE で行えることも、POST でできてしまうのです。 しかし、HTTP メソッド本来の意味を考えて POST メソッドを利用するのが正しいのは言うまでもありません。 POST を使うときの経験則として、POST をリソースの新規作成にだけ使う、 というものがあります。新しいリソースを POST する、と覚えましょう。 Paul Prescod の "Common REST Mistakes" の2番がこれに該当します。

まとめ

今回の POST, PUT, DELETE と、前回の GET はある点で大きく異なります。 それは GET がリソースの状態に影響を与えないのに対して、 それ以外の三つの動詞がリソースの状態に何らかの影響を与える可能性がある点です。 このような性質を副作用(side effect)といいます。 副作用のない GET はキャッシュという大きな可能性を持つことになります。 キャッシュについては機会があれば説明しようと思います。

これまでの内容をまとめると以下のようになります。

  • GET はリソースを取得するメソッド
  • PUT はリソースを更新するメソッド
  • DELETE はリソースを削除するメソッド
  • POST はリソースを新規作成するメソッド
  • GET はリソースに副作用を与えない

REST では、リソースを操作するインターフェースにはこの四つのメソッドしかありません。 getXx() も setXx() も startXx() も createXx() もないのです。 これは REST アーキテクチャスタイルの持つ大きな制約のひとつ、 統一インターフェース(uniform interface)です。

次回は、Web の最大の特徴であるリンクと REST の関係について解説したいと思います。

[update 2005-05-22] POST と PUT についての記事を追加しました。

[update 2007-09-21] 2007年7月に Atom Publishing Protocol は標準化作業が終了しました。また、その技術解説を WEB+DB PRESS Vol.40 に書いています。

ラベル: ,

2005-04-24

REST 入門(その4) HTTP GET -- その絶大な効果

» REST 入門 目次

前回は、リソースの特徴について解説しました。 しかし、なぜリソースが重要なのかはまだわからないと思います。 今回はその一部を紹介します。

まず、おさらいしましょう。 僕たちは以下のリソースを例に使っています。

  • 東京の天気予報
  • 2005年8月24日のスケジュール
  • 新花巻駅の写真
  • Dijkstra 著 "Go To Statement Considered Harmful"
  • 僕の最近のブックマーク

そして、それぞれのリソースの識別子(URI)は以下のようになります。

  • http://weather.yahoo.co.jp/weather/jp/13/4410.html
  • https://example.com/schedule/20050824
  • http://www.flickr.com/photos/60043209@N00/6337155/
  • http://www.acm.org/classics/oct95/
  • http://del.icio.us/yohei

ここで質問です。URI を与えられたら、あなたは何をしますか?

ここで答を直接聞けないのは残念ですが、大多数の人は、URI をコピーして Web ブラウザの URI 欄(IEだったらアドレス欄)に貼り付け、 そしてリターンキーか「移動」ボタンを押すのではないでしょうか。 上記の URI は、スケジュール以外は全部実際にブラウザで見ることができるURL ばかりです。 ぜひコピー&ペーストしてみてください。

URL 貼り付けというなにげない日常的な行為、これが REST では非常に重要なのです。 ブラウザに URI を入力してリターン、するとブラウザはURI に示された Web サーバに対して HTTP の GET メソッドを発行します。

GET /weather/jp/13/4410.html HTTP/1.1
Host: weather.yahoo.co.jp

Web サーバは結果(その時点での東京の天気予報の HTML ページ)をブラウザに返します。

HTTP/1.1 200 OK
Content-Type: text/html; charset=euc-jp

実際の HTML がここに入る

世界中の人々が日々行っているこのなにげない作業を REST 流に説明すると、 名詞(リソース)に動詞(HTTP GET)を適用した、ことになります。

ナンジャソリャ? と思った人もいるかもしれませんが、これはいわゆる「抽象化」です。 現実の世界で起きている Yahoo の東京の天気予報のページをブラウザに表示する、 という行為から個別の事情を取り払い、 なるべく汎用的に使える言葉で説明する(抽象化する)と 「Yahooの東京の天気予報」というリソースを取得(ゲット、GET、HTTP で GET)する(取得する」という動詞を適用)、 となるのです。

この HTTP GET という抽象化は絶大な効力を発揮しています。 あるリソースを識別する URI さえ与えられれば、 ブラウザを使って HTTP GET を適用するだけで、 そのリソースのある時点での表現を取得できるのです。 それが HTML 文書だろうと、PNG 画像だろうと、.swf ファイルだろうと、Javascript だろうと関係ありません。

ここで「リソースのある時点での表現」という言葉が出てきました。 これは REST の名前の元になっている Representational state のことです。 Representational state とは、あるリソースのある時点・条件での状態の表現を指します。 今日の時点での天気予報リソースと明日の時点での天気予報リソースは、 リソース自体の状態が異なるので取得できる表現も違います。 また、その表現は HTML かもしれないし、XML かもしれないし、PDF かもしれないし、はたまた何か別の形式かもしれません。 この表現をネットワークを介して転送する(Transfer)のが Representational State Transfer (REST) です。

今回はここで終了です。 次回は GET 以外の動詞を見ていこうと思います。

ラベル: ,