昨日書いた「HTTPメソッド、URL、そして標準化された動詞」、どうも詰めが甘い記事だったので、少し訂正と補足をします。以下で、HTTPクライアントとしては、現存するブラウザを想定しています。
([追記]総括を「そろそろ決着、HTTPメソッド、URL、そして標準化された動詞」に書きました。[/追記])
動詞としてのpost
動詞(あるいは抽象メソッド)とは、具体的なHTTP通信とは離れて、リクエストの意図・意味を記述する記号です。最初に、この動詞を整理して、それをHTTPを使って表現する方法は後から考える段取りにします。しかし、もともとのHTTPメソッドも、その本来の意図・意味を動詞として定義しておきます。混乱を避けるために、現実のHTTPメソッドは大文字、抽象化された動詞は小文字で書くことにします。
HTTPメソッド | 意味/使い方 | 対応する動詞 |
---|---|---|
GET | リソース(の表現)を取得する | view |
PUT | 新しいリソースを作る/既存リリースを上書き変更する | put |
DELETE | リソースを削除する | delete |
HEAD | リーソースのメタデータを取得する | (今回は考えない) |
POST | 新しいリソースをサーバー側で決める名前で作る | post |
昨日の記事では、HTTPメソッドの本来の意味に対応する動詞postを忘れていました。動詞postを入れると; view, viw-as-*, download, edit, process, create, update, append, put, post, delete, exec が典型的動詞です。動詞をこれらに限定する気はありませんが、多くの場面で共通的に使える動詞を確認しておきたいのです。
execとprocess
動詞execと動詞processの区別が曖昧だったので、もう少しハッキリさせます。
- exec :サーバー側にあるプログラムを起動する。このプログラム起動自体は、サーバー側での副作用を起こさない。URLは実行すべきプログラムを指し示す。プログラムの実体が何であるかは問わない。例えば、URLが指し示す“プログラム”がHTMLファイルであってもよい。プログラムにパラメータを渡してもよいが、入力データは渡さない。
- process :サーバー側の副作用を期待するプログラムを(サーバー側で)実行する。URLは実行すべきプログラムを指し示す。プログラムの実体が何であるかは問わない。プログラムに入力データは必ず渡す(空なデータのときもある)。パラメータを渡してもよい。
execは、ネットワーク越しにブラウザに“プログラムをロードする”感じです。ですから、フォームやJavaScriptを含んだHTMLファイルを“実行する”と言ってもかまいません。一方のprocessは、ブラウザから送ったデータをサーバー側で処理することを依頼します。
こう定義してみると、昨日の表の一部は訂正する必要があります。
動詞 | GETメソッド | POSTメソッド | リソースは存在すべきか |
---|---|---|---|
デフォルト | view, exec | process, exec | - |
exec | ○ | ○ | 存在 |
execが、GETメソッドでもPOSTメソッドでも使えるのは変なので、次のようにします。
動詞 | GETメソッド | POSTメソッド | リソースは存在すべきか |
---|---|---|---|
デフォルト | view, exec | process | - |
exec | ○ | × | 存在 |
viewとexec
execの意味を上記のように定義すると、「プログラムの実体が何であるかは問わない」ので、単なるHTMLファイルのロードもexecと解釈できます。となると、viewとの区別が極めて曖昧になります。viewとexecを区別するのはもともと困難なのかも知れません。
Windowsデスクトップで、ファイルをクリック(あるいはダブルクリック)すると、そのファイル(リソース)に対してデフォルトの動詞が実行されますが、デフォルト動詞の名前はopenで、意味的にはviewとexecを一緒にしたようなものです。例えば、memo.txt というファイルをクリックすると、(通常の設定では)notepad.exeが起動してmemo.txtを表示します。この事象を、memo.txtを「viewしたのか/execしたのか/editしたのか」と議論してみても、どうも埒が明かない気がします。
現実的には、viewとexecの区別は曖昧なままにしておくか、open, getのようなどっちとも取れる動詞に統合するかでしょう。
view-as-* と追加パラメータ
view-as-htmlのような動詞を使う代わりに、表示フォーマットを別なパラメータに指定する方法もあります。例えば、http://example.jp/samples/hello.java?_verb=view&format=html、 あるいは、動詞viewはデフォルトですから http://example.jp/samples/hello.java?format=html のように。これは、動詞の一部にパラメータを埋め込むか、それとも独立したパラメータにするか、という問題で、あまり決定的な優劣はありません。
Catyの場合でいえば、動詞によるディスパッチはフレームワーク側で処理され、追加のパラメータによる振る舞いの変更は http://example.jp/samples/hello.java に結びつけられたプログラム側で行うという違いがあります。しかし、これはあくまで個別固有の事例に過ぎないので、一般論としては、?_verb=view-as-html と ?_verb=view&format=html の使い分けの基準はハッキリしません。
editに副作用はないのか?
id:yssk22さんのブックマークコメントに「(editには)ロックする、という副作用があるように見える」とありました。確かに、動詞editを受け取ったサーバーは、指定のリソースを編集する準備をします。リソースそのものがただちに変更はされなくても、サーバー側システムの状態は変更されます。
この意味で、「editに副作用はない」と言い切るのは問題ありです。しかし、「editにより、リソースの変更は生じない」とは言えます。さて、このような影響を持つ動詞はGETに乗せるべきかPOSTに乗せるべきか? どちらかを選べと言われれば、僕はGETを選びますが、POSTを否定する理由も見当たりません。editの送信方式は、GETでもPOSTでもいいということになりそうです。
processの曖昧性
動詞processは、HTTPメソッドPOSTにより起動されるサーバ側処理を総称的に表しています。そのため、POSTの曖昧性はそのままprocessの曖昧性に引き継がれ、「processしろ」という動詞を使ったリクエストは具体性がありません。
昨日の記事では:
processはデフォルト動詞であり、createの意味を含む可能性があるので ...
と書いたのですが、これはさすがに拡大解釈のし過ぎで、processを送る先のURLは、既存の処理システム(プロセッサ)を表すと考えた方がいいと思い直しました。動詞がprocessの場合は、目的語はURLではなく、むしろPOSTで送るデータ(エンティティボディ)のほうです。URLは処理の手段なので、process Data with URL って感じでしょうか。
putとpostとappend
「HTTPメソッドの正統的使い方と現実的対処法」で紹介した書籍『RESTful Webサービス』の第4章に、HTTPメソッドPUTとPOSTの違いが詳述されています。
PUTもPOSTもリソースの生成に使えます。違いは、PUTが目的のリソースを表すURLをクライアントが指定するのに対して、POSTに指定するURLが「親」または「ファクトリー」を表すことです。http://example.jp/blog/2010-01-13.entry?_verb=put は、URLで指定されたリソースが生成または上書きされます。ファイルシステムに例えるなら、open, write, closeすることですね。一方、POSTでは、指定するURLは(例えば)http://example.jp/blog となります。http://example.jp/blog?_verb=post とすると、サーバー側が適当に(この場合は日付を使って)リソースを生成します。
appendという動詞は、POSTの用法を細分したものです。appendは、既存リソースの変更をしますが、putのように全部置き換えるのではなくて、既存リソースに追加をします。ただし、追加も実は置換えで、「たまたま以前の内容も含む新データで置き換えられた」とも言えるので、putとappendの区別が明確なわけではありません。[追記]いやっ、後述のベキ等性により区別が付きます。[/追記]
また、http://example.jp/blog をブログエントリーの集まりというコレクション・リソースと考えれば、http://example.jp/blog?_verb=append によって新しいエントリーが「追加」されるのだとも解釈できます。つまり、postとappendの境界も曖昧です。『RESTful Webサービス』には、POSTのほとんどの使用例はappendであると記されています。
createとgenerate
PUTとPOSTでは、指定するURLの意味が変わると言いました。新規リソースを作成する動詞createにも同じ議論が通用します。createの目的語は、新規リソースの名前そのものなのか、それともファクトリーを表すのでしょうか?
仮にgenerateという動詞を導入して、事情を整理しましょう。
動詞 | URLは何を表すか | リソースは存在すべきか | 内容データ |
---|---|---|---|
put | 上書き変更するリソース | 存在しても不在でもよい | 必要 |
post | ファクトリー・リソース | ファクトリーは存在、リソースは不在 | 必要 |
create | 新規作成するリソース | 不在 | 不要 |
generate | ファクトリー・リソース | ファクトリーは存在、リソースは不在 | 不要 |
こうして見ると、generateは、空データを伴ったpostで代用できます。(createを空データを伴ったputで代用すると、既存リソースを意図せず消去してしまう危険があります。)post+空データを特にgenerateと名付けてもいいですが、さほどの必要性は感じません。
ベキ等性の考察
『RESTful Webサービス』にベキ等性(冪等性、べき等性)という概念が出てきます。これは、「同じリクエストを続けて2回行っても1回のリクエストと変わらない」ということです。ただし、「変わらない」のはサーバー側のリソース状態のことです。処理システムの状態やクライアント側まで考えれば、ベキ等性はほとんど期待できません。
- サーバー側システムがリクエストの回数を勘定していれば、カウンタ値の状態がベキ等になるわけがない。
- 同じHTMLファイルを2回リクエストすると、ブラウザのタブ/ウィンドウが増えるかもしれない。
- 同じファイルを2回ダウンロードすると、ローカルファイルシステムに同じ内容のファイルが2つできるかもしれない。
- ブログエントリーの編集中に、もう一度編集(edit)リクエストすると失敗するかもしれない。
今列挙したようなことは無視して、サーバー側に保持されているリソースが変化するかどうかだけを問題にしてベキ等性を考えます。すると、post, append, generateはベキ等になりません。リクエストのたびに、リソースが追加更新されるからです。
リソース状態に関するベキ等性は、整数変数への代入(破壊的代入)とインクリメントを思い浮かべると理解しやすいでしょう。n = 3; という代入文を2回行っても1回と同じです。n++; というインクリメントを2回行うと1回とは違う結果になります。
まとめ
今回の内容をまとめた表は次のとおりです。「△」が付いている動詞は、意味や用途が曖昧なものです。
動詞 | GETメソッド | POSTメソッド | リソースは存在すべきか | 内容データ | ベキ等か |
---|---|---|---|---|---|
デフォルト | view, exec | process | - | - | - |
view△ | ○ | × | 存在 | なし | ○ |
view-as-* | ○ | × | 存在 | なし | ○ |
exec△ | ○ | × | 存在 | なし | ○ |
download | ○ | × | 存在 | なし | ○ |
edit | ○ | ○ | 存在 | なし | ○ |
process△ | × | ○ | プログラムとして存在 | 必要 | 状況による |
create | × | ○ | 不在 | 不要 | ○ |
update | × | ○ | 存在 | 必要 | ○ |
append△ | × | ○ | 存在 | 必要 | × |
put | × | ○ | どちらでもよい | 必要 | ○ |
post△ | × | ○ | ファクトリーは存在、リソースは不在 | 必要 | × |
generate△ | × | ○ | ファクトリーは存在、リソースは不在 | 不要 | × |
delete | × | ○ | 存在 | 不要 | ○ |