HTTPは本来ステートレス

最近、Play Frameworkに触っている。Webで調べ物をすると、よく、「PlayのControllerはステートレスだ」とか、「セッションスコープじゃない」から不便だと言われる。その様なことを言及しているブログではJavaでPlayを使っているから、おそらく、Java由来のフレームワークを使っている経験のある人がPlayを触った結果そのような感想を持つのだと思う。

また、うちの新人はいまだにこの点を理解しておらず、クライアントブラウザとサーバの間には見えないヒモが存在し、任意のタイミングでどちらからもイベントを開始して自由に情報をやり取りできると思っているようで、何度丁寧に説明しても理解していないようだ。

Web開発をやっている人の殆どは「何を今さら」と思うだろうが、初学者のために簡単に説明を書いてみたいと思う。

本来HTTPはステートレス

HTTPプロトコルというのは非常に古いプロトコルである。大昔のWebというのは、サーバにある資源(ファイル、画像、文書)を取ってくるという仕事がメインだった。だから、サーバと、その中にある資源を一意に表すことのできるURLを解釈して、それに応じた資源をクライアントに返すという仕事で事足りていた。

たとえば、http://xxx.com/welcome.pngにアクセスしたら必ずwelcome.pngが返ってくる。http://xxx.com/index.htmlにアクセスしたら必ずindex.htmlが返ってくる。何度アクセスしても、サーバの資源が変更されない限りはその内容は同じものだ。別のクライアントからアクセスしてもそれは同じである。

ちなみに、その後HTTPは資源を保存したり削除したりする命令(PUT, DELETEなど)をも受け付けるよう機能拡張されたが、主にセキュリティ上の問題からこれらはあまり実際のアプリケーションには実装されなかった。

ショッピングカート

過去のWebでは要求されたURLに対応するものを愚直に返却するのがサーバの仕事であったが、現代では同じURLを要求しても違う結果が返ってくることが必要とされる場面が多々ある。代表例が通販サイトのショッピングカートだろう。

たとえば、http://xxxx.com/shopping/cartがショッピングカートのURLだったとする。たとえばユーザーAからこのURLが要求された場合はユーザーAのカートの中身を表示させなければならないし、ユーザーBであったらユーザーBのカートの中身を表示させなければならない。また、ログインしていないユーザーであればログインページを表示するということになるだろう。

こうした必要性が生じたときに昔のNetscape Communicationsという会社が提案した方法はCookieという仕組みであった。これはサーバから送信したデータをクライアント側のブラウザで保存できる仕組みであり、その後ほとんどのブラウザに実装された。Cookieはキーと値のペアからなるシンプルな形式のデータからなり、それを受け取ったクライアントは次回以降、同じサーバにリクエストを発行するときは必ずこのキー/値の組を送信する。

ショッピングカートを実装する仕組みはこうだ。サーバはユーザーが正しいユーザーIDとパスワードを送り、サーバがそれを検証して正しいという判断を下すと、クライアントに対してCookieを発行する。これに含まれるデータはふつう、ある程度の長さをもった乱数である。クライアントはCookieを受け取ったら次回以降のリクエストには必ずこのデータを付与してリクエストを発行するので、この乱数は毎回サーバ側に送信される。

ユーザーAに発行された乱数がRa、ユーザーBに発行された乱数がRbだったとしよう。ユーザーAがhttp://xxxx.com/shopping/cartにアクセスした時には乱数Raも同様にサーバに対して送信する。サーバ側は発行した乱数とユーザーの組を何らかの仕組みで記憶しておく。その記憶をたどり、乱数Raに対応するのがユーザーAであると判明すれば、ユーザーAのカートの中身を表示するためのhtmlデータをクライアントに対して返却する。同様に、乱数Rbが送られればユーザーBのカートの中身を表示する。

鋭い観察眼を持った人は、ここで「もしユーザーAが乱数Rbを送ったら、ユーザーAはユーザーBのカートの中身を見ることができてしまうのでは?」と思うだろう。答えはイエスである。これはセッションハイジャックと呼ばれる攻撃手法だ(セッションハイジャックにはTCPセッションのハイジャックというのもあるが、ここではHTTPセッションを指す)。だから、他のユーザーの乱数が容易に想像できないように、十分な長さの乱数を用いることになる。たとえば、3ケタの数字の乱数であれば1000回程度試せばすべてのユーザーのセッションが判明してしまうが、これがたとえば128bit(2^128通り)などというオーダーになったら推測は困難だろう。

しかしそれでも、SSHなどといったプロトコルに比べれば脆弱であることに変わりはなく、HTTPセッションをプロトコルのレベルで強固に担保するような方法がずいぶんと前から望まれているが、一度広まってしまった規格を変えるのはなかなか難しく、実現には至っていない。

サーバ側処理の隠ぺい

ショッピングカートの例を再度考えよう。サーバ側がやらなくてはならない処理の一つは、各URLが要求された時にユーザーに応じて別の表示状態を構成しなければならないということであった。先に挙げた例はショッピングカートの表示であったが、ショッピングカートだけで構成されるWebサイトというのは無い。多くの場合、トップページにはその人の過去の購入履歴から判断した嗜好に応じた商品が表示されるし、商品の購入を考えても住所を記入するページ、支払方法を選択するページ、最終確認のページ…とさまざまな遷移がある。それらすべての状態をサーバ側で管理する方法を実装するのはなかなか大変である。

大変であるが、多くの場面で必要とされる方法には、普通はライブラリやフレームワークのサポートが備わっている。Strutsとその派生のJava由来フレームワーク、またはASP.NETなどではサーバ側にセッション(クライアント)ごとに状態を保持させる方法が取られることが多い。あるユーザーと、ユーザーに対応するデータの取得処理は完全に隠ぺいされ、まるで単一ユーザーによって使用されるクライアントアプリケーションを作成するかのごとき考え方で開発可能だ。

この方法はサーバ側にユーザーの状態を多数保持させるステートフルな方法である。このような方法は直観的でわかり易いが、いっぽうで初学者にはまるでサーバの状態とクライアントでの表示内容が密接に結び付き、お互いがお互いの状態を動的かつ容易に変更し合えるという錯覚を生じさせる。

しかしながら、先に述べたようなHTTPの制限があるためにそれは幻想であり、根底にはステートレスなHTTPプロトコルを用いることによる制限は避けられない。(AjaxやWebSocketを使えばまた話は別だが)

RESTful

サーバー側がステートフルであると、URLにはあまり意味がなくなってくる。極端な事を言えば、http://xxxx.com/shoppingというアドレスにすべてのページをマッピングし、クライアントから渡されるセッションIDによってあらゆる画面遷移を実現するということも不可能ではない。実際にそのような方法を取ることは無いものの、方向性は同じような方角を指し示しているのは間違いない。あるページにブックマークをしたが、次にそのページを開いたら同じ内容が表示されなかったとか、ブラウザで戻るとか進むボタンを使ったら最初からやり直せと言われたりだとか、そういう経験は誰にでもおありだろう。サーバ側が手綱を握っているので、クライアント側には状態を復元するための情報は開示されないのだ。

ところが最近ではREST(Representational State Transfer)というキーワードをよく耳にするようになった。これはその名の通り、要求リクエストに結果を表示させるために必要なすべての具体的な情報を包含させるべき、という考え方である。原点回帰ともいえるかもしれない。RESTに従ったアプリケーション(RESTfulなアプリケーション)ではURLは非常に説明的だ。たとえば、

  1. GET http://xxxxx.com/users/withpop
  2. DELETE http://xxxxx.com/users/withpop
  3. GET http://xxxxx.com/carlist/page/2

などといったURLが思いつく。RESTfulなアプリケーションでは全ての資源に一意に対応するURLを持つという原則を持っている。1番目はwithpopというユーザーのページ。2番目はwithpopというユーザーを消去するという処理(HTTPのDELETEメソッドを利用する)。3番目は車のリストの2ページ目を表示する。1番目にブックマークを行えば、どの環境からでも常にwithpopというユーザーのページが表示されることが期待できる。

実際にはまだユーザー認証などの仕組みではCookieに頼る場面が多々あるが、しかしこのような思想はインタフェースの簡略化という点で非常に良いことだと私は思う。はっきりと言えるのは、いかなるシステムでも、インタフェースは簡素であるほうが偉いということである状態が一つ増えるたびにそれを管理する手間と解釈の誤りが混入する危険性が増えていく。

また、個人的な意見ではあるが、先に述べたようなステートフルなフレームワークは過度に隠ぺいしすぎであると思う。状態をプログラム内部にたくさん持つとスケーラビリティを確保できないし、W3Cの仕様からかけ離れた開発方法(HTTPを知らなくても開発できる方法)をとるというのは、初学者に対して先に述べたような誤解を生じさせるし、そのフレームワークを使用する過程で得た知識のほとんどは他の技術では利用できず無駄になる可能性が高いという点も気がかりだ。

もっとも、初学者に対しての誤解という点に関しては「HTTPの基礎くらい勉強してこい」と言いたくはなるのだが、ITドカタと貶されているこの状況でさらに門戸を狭めるのは良くないだろう。