home ホーム search 検索 -  login ログイン  | reload edit datainfo version cmd icon diff delete  | help ヘルプ

技術/HTTP/REST設計思想の "Stateless" との付き合い方

技術/HTTP/REST設計思想の "Stateless" との付き合い方

技術 / HTTP / REST設計思想の "Stateless" との付き合い方
id: 1350 所有者: msakamoto-sf    作成日: 2015-02-11 21:34:52
カテゴリ: HTTP プログラミング 

RESTfulなAPIやWebアプリケーションを開発する際に、一つの疑問が生じる。
RESTでは「ステートレス」を重視して、サーバサイドでのセッション管理ではなく、クライアント側で認証情報や状態を保持して、サーバに都度送る方式を唱えている。これはHTTPで実装するなら、Cookieを使ったセッション管理ではなく、BasicやDigest認証など、HTTP認証を使うことになる。
しかし現実問題として、モダンなWebサイトでBasic/Digest認証を扱うことはなく、サーバサイドのCookieを使ったセッション管理を使うことになる。

RESTにおいて、ステートレスという特性と、現実のセッション管理をどうバランシングすれば良いのか?

うまく整理しきれていないが、結論を三行に濃縮してみる:

  1. RESTは2000年前後のWebに対するカウンターパンチとして提唱されたため、恐らく意図的に極端な主張となっている。
  2. RESTは設計思想であり、現場で吟味した上での「おいしいとこどり」でも構わない。
  3. RESTの下に自分たちが開発するWebアプリがあるのではなく、Webアプリ開発を支える設計思想の一つに、RESTの一部を取り込めればそれで良い。

以下、自分なりにRESTについて調べ、上記観点についてまとめた内容となる。


RESTの出自

RESTは "Architectual Style" であり、分散ネットワークシステムにおけるアーキテクチャのパターンの一つであるとされる。1990年代後半~2000年初頭にかけて、HTTPの策定やApache Webサーバの開発に関わった Roy T. Fielding 氏が提唱し、Paul Prescod 氏などにより広められ、その後のWeb開発で重要な考え方の一つになった。

see:

以下のような設計原則を提唱している。 ( see: http://ja.wikipedia.org/wiki/REST )

  1. ステートレスなクライアント/サーバプロトコル
  2. すべての情報 (リソース) に適用できる「よく定義された操作」のセット
  3. リソースを一意に識別する「汎用的な構文」
  4. アプリケーションの情報と状態遷移の両方を扱うことができる「ハイパーメディアの使用」

2015年を生きる今の自分たちから見ると、ステートレスを除いては「何を今更」と云いたくなるほど普通に見られる原則である。

今、普遍的に見られる原則が、過去、わざわざ提唱されたということは、当時は、そうでなかったことが推測される。

当時のWebがどんな世界だったのか、以下の記事にその一端を伺うことが出来る。(こういう古い記事のURLがちゃんと残ってて参照できるのはスバラスィ! ITmediaに感謝!)

ポイントとしては、WebブラウザやWebサイト主体の最初の黎明期~発展期だった点。まだ Web 2.0 が出てくる前という点も覚えておきたい。
当時は初期のWebアプリの開発ツールなどで、POSTだらけの遷移だったり、出来立てのSOAPだったりが乱立していたらしい。
そのため、ハイパーテキストの思想やURIの考え方が蔑ろにされ、ブラウザに表示されるURLを送っただけでは同じ情報が見れなかったり(POST後の遷移)、リンクでつながるのではなくボタンクリックによるPOST遷移で画面が切り替わるなど、今らすればずいぶんと使いづらい世界が広がっていたようだ。

そのような状況に対して、ネットワークベースの分散アプリケーションの設計思想の一つとして、提唱されたのががREST, Representational State Transfer になる。
RESTはネットワークベースのクライアント - サーバ分散システムを想定し、scalabiliry, reliability, cache などの制約を考慮した場合の設計思想として整理されている。
HTTPを前提としてはいないが、一番実装しやすい世界がHTTPのWebの世界となっている。

推測だが、HTTP/URIの策定に関わり、Apache Webサーバの開発にも関わっていた Roy T. Fielding 氏にとって、当時の状況に対して忸怩たる思いや、強い「コレジャナイ」「どうしてこうなった」感を抱いていたのではないだろうか。

RESTは当時のWeb開発者たちに広く受け入れられ、日本でも広がっていった。
URIの組み立て方や、HTTPで定義済みのVerb(GET, POST, PUT, DELETE)をリソースの操作にマッピングする考え方は、自分にとっても直観的で分かりやすく、納得できる。
レスポンスにハイパーリンクを含めることでリソースをリンクさせていく考え方も、WebページやAPIレスポンスの作り方として、自分も違和感なく受け入れられる。
だが「ステートレス」については、その前提や目的が軽視され、「ステートレスでなければならない」というお題目だけが広まってしまった印象を感じており、「ステート」の定義の曖昧さも感じているため、強い違和感を感じている。

「Stateless」とセッションの混乱

自分の力量不足で、これについては全く整理できていない。つまりそれだけ、RESTが唱える「Stateless」の「State」と、Web開発で扱う「状態」の関係がよくわからない状態になっている。

RESTの「Stateless」の解説としてよく見かけるのが、その目的として「Scalability」があるという点。つまり、サーバサイドで状態を管理しているとサーバ台数を増やした場合の同期処理などのためパフォーマンスやスケーラビリティで問題が出てくる。またLoad Balancerでのsticky sessionの扱いも出てくる。これを解決するため、クライアントとサーバのコミュニケーションをStatelessにする、というのがRESTの考え方となるらしい。

see:

自分は、Web開発で扱う「状態」には以下の種類があると考えている。

  1. WebブラウザなどのUserAgentの認証状態(ログインしたか、してないか)
  2. 複数の遷移にまたがるトランザクションの状態
    1. 代表例1 : ショッピングカート
    2. 代表例2 : 複数画面にまたがるユーザ登録画面やアンケートフォーム
  3. 今その瞬間のWeb画面の表示状態
    1. 分かりづらいので代表例その1 : 検索一覧結果のページネーションで、「最初に検索した時点での一覧」の中をページネーションするのか、ページネーションで遷移する都度、検索しなおしてその中の何番目~という扱いにするのか。「戻る」や「進む」ボタンをクリックした時に、本当にユーザが一つ前に見ていた内容を表示するのか、検索条件は同じで、検索しなおした結果(=もしその間に他のユーザが削除や追加をしていたら、一覧が変わる可能性もある)を表示するのか。
    2. 代表例その2:HTML5 history APIを使い、location.hashを元にDOM構造を一意に扱うような場合、どうするか?

以下、つらつらと思った内容。

  • yohei-y:weblog: ステートレスとは何か
    • http://yohei-y.blogspot.jp/2007/10/blog-post.html
    • → この「客」と「店員」は確かにステートレスだが、Web開発では具体的にどんなシーンに相当するのか?ショッピングカートか?認証に関わる部分は含まれるのか?
    • → ショッピングカート的な仕組みであれば、現在ならsessionStorageなどと組み合わせて実現できそうだが。
  • yohei-y:weblog: REST 入門(その8) REST でないもの
    • http://yohei-y.blogspot.jp/2005/05/rest-8-rest.html
    • →「HTTP をステートフルにする代表格はクッキーを使ったセッション管理です。 REST アーキテクチャスタイルの視点からみると、 クッキーを使ったセッション管理は間違った HTTP の拡張、ということになります。」
    • → WEB+DB PRESS Vol.32 , 「REST アーキテクチャスタイル入門」にも同じ内容で説明してくれている・・・が・・・
    • 強い違和感を感じたのが「間違ったHTTPの拡張」という単語。これは逆に言えば「正しいHTTPの拡張」というのが存在するということ。では、HTTPの拡張の「正しい」「間違っている」は何を元に判断されるのか?それの妥当性を検討するのは/担保するのは/保証するのは誰か?とか考え始めるとどんどん分からなくなってくる。
  • RESTアンチパターン
    • http://www.infoq.com/jp/articles/rest-anti-patterns
    • →「cookie が、認証トークンといった何らかの情報を格納するために利用されているならば、サーバーはセッション状態への依存なしに正当性を確認することができるので、cookieは申し分ないRESTfulです。」具体的にどんな実装になるのか、例が全く思い浮かばず=理解できてない。
  • REST でよくある間違い
    • http://www.geocities.jp/yamamotoyohei/rest/mistakes.html
    • →「クライアントが「ログインする」必要はないはずだし、「接続を始める(start a connection)」必要もないはずだ。 」この一文、そもそも「間違い」の例なのか、RESTだとこうなる、という正しい方の考えなのかが分からない。
    • →意図的に極論的な書き方にしているんだろうが、疑問が出てくるばかり・・・。

結局 state というのが、各開発者が実現しようと考えている「セッション管理」のどの部分にあたるのか、REST主要メンバーの間でも議論中であり、機械的に判別できるような状況ではないことが分かる。

セッション管理をClient側に持たせてstateless化を試みた例

Railsではサーバサイドではなく、クライアントサイド=Cookieに、セッションIDではなく直接セッション情報を格納する方式を使えるらしい。もちろん暗号化や改ざん対策は可能。

ただ、他の言語やFWとか見てみて、この方式が広まっているとは思えない・・・。

セッションをサーバサイドで無効化することも可能という点で、結局、どこかしらサーバ側でクライアントとの間でやりとりするトークンIDなどの最低限度の値は管理できるようにしておかないと、セキュリティ面で色々、自力で考えなくてはならなくなる面が多そう。
普通に、これまでのサーバサイドのセッション管理機構(言語それ自体のものだったり、FWが提供してたり)をそのまま使えば、最小限の労力でセキュアなセッション管理を行える。
RESTを意識するあまり、セキュリティ面まで普段と違う作りにして、自力で色々実装したり、あるいはFW側の機能を検証する必要があるか?どうか?

OWASPの"REST Security Cheat Sheet"ではただ一言。「セッションベースのユーザ認証を使え」と書いてある。

「そういえばOWASPではRESTアーキテクチャスタイルについて何か推奨とか出してるかな?」と思い、探したら以下のページが見つかった。

ストレートに一言。

RESTful web services should use session-based authentication,
either by establishing a session token via a POST or by using an API key as a POST body argument or as a cookie.
Usernames, passwords, session tokens, and API keys should not appear in the URL,
as this can be captured in web server logs, which makes them intrinsically valuable.

ユーザ認証にはセッション管理を使え、とあり、さらに、セッションIDやAPIキー、ログインIDとパスワード、トークンなどはURLに含めるな、ともある。

他にもこのページにはRESTスタイルで開発するときのセキュリティ上の注意点が解説されている。英語になるが、内容的には普通のWebサイトと同じように入力チェック+出力時のJSON/XMLに合わせたエンコーディング、CSRF対策などをしてください、という流れ。

また以下のページは、逆にRESTなWeb APIを診断する、pentester向けのガイドとなっている。クロールできたURLだけでは、全部のREST APIの機能を見れたことにはならない可能性があるので、ソースコード診断(ホワイトボックステスト)も可能なら実施したい、などとあり、参考になる。

その他の参考資料

RESTの難しさの感想

RESTは設計パターンであるが、「HTTPを使った分散C/Sシステムは "どうあるべきか?" 」という思想になってしまってるのが難しさの原因だと思う。

英語の博士論文を読み込み、実際の開発に落とし込んだ時のギャップとか曖昧さについて最先端のメンバーと英語で会話してディスカッションして「どうあるべきか?」に一つ一つ答えを出していくのは、非常に限定された少人数でしかできない。

大多数の開発者は、そのような哲学的な思想やHTTPの将来には興味がなく、迫り来るスケジュールの中、数年先位までのメンテナンス性とセキュリティと顧客要求を両立させるので大変で、設計思想の妥当性の検証や現場でのfit & gapを検証してディスカッションする役目は負っていない。

単に「より分かりやすい・扱いやすいWebシステムを作るためのテクニック」として道具に落とし込んで、「こういうURL構成にすると分かりやすい、便利」とか「HTTPのリクエストメソッドを実際のリソース操作にマッピングすると、スゴイ自然に見えて、メンテナンス性もあがる」とかにしてくれてれば、ユーザとして気軽に扱えたかもしれない。

設計思想として「かくあるべし」というものが出来上がってしまったので、開発者たちは自分の作ろうとしているのがそれに沿ったものなのか、不安にかられ、「正しいのか、間違っているのか」というお墨付きを得ようとする。

これはちょうど、OOPが流行りだした後、「こういうプログラミングはOOPに従っているのか?」と不安に思うのと似てる。

他人が作った思想に沿った作りになっているのか、いないのか、考えたり、不安に思って調べるのは、個人的にはもうたくさんだ。

Roy T. Fielding 氏はあなたのWeb開発を設計してくれはしない。

あなた自身を含めた、チームメンバーが納得できる設計方式でWeb開発するのが一番大事なのではないか?

その上で、REST設計スタイルで取り込みたい部分があれば取り込めば良いし、取り込みづらい部分があれば取り込まなくても良い。

仮に「これはRESTではない」という批判が上がったところで、我々はRESTのためにWeb開発をするのではない。
ユーザにとって利用価値があり、分かりやすく、メンテナンスを容易にするために、あれこれ頭をひねって設計して、Webを開発している。

RESTが我々を支配してはならない。我々がRESTを支配しなければならない。

RESTじゃないんじゃないか?とビクビクするより、そもそもこうした設計思想はそういう不安を駆り立てる構造を持ってるんだから、細かいこと気にせず作ればいいんじゃないかな?

英語の論文バリバリ読み込んだり記事書いたりしてる人をもって「RESTは難しい」と言わしめてる。そこまで「正しく」扱うのが難しくて勉強が必要な設計思想、現場の開発者たちが「正しく理解して、正しく扱う」ことに期待してもしょうがないし、期待されてもできないと思う。

とくにstatelessについては、以下の記事にあるように、無理してクライアント側Cookieに持たせるよりは、従来通りのサーバサイドのセッション管理にした方が良かった、というのがスゴイ生々しいので、あんまり気にする必要無いんじゃないか。

本当に "stateless" にしないと scalability は確保できないのか? -> 「お前がそう思うんならそうなんだろう お前ん中ではな」

結局ここに帰ってくる。
最大の違和感は、本当に "stateless" にしないと scalability & reliability は確保できないのか?という点だ。
特にWebシステムであれば、言語/FW/ミドルウェア/サーバのどこかしらに、セッション情報のクラスタリングや共有・同期機構があるはずだ。
もちろんそれがパフォーマンス上の問題になったり、同期タイミングの問題だとか、実際の開発で検証すべき部分はある。
しかしサーバサイドのセッション管理自体はWeb開発においても枯れた方式であり、セキュアに開発するノウハウも蓄積されているし、大抵のWeb開発言語やFWにはサーバサイドでのセッション管理方式が実装されている。
それらを無視してまで、scalabilityとreliabilityを優先した "stateless" なクライアントサイドの状態管理を導入する必要があるのか?

よほどクリティカルなシステムでも無い限り、例えばサーバの片系が落ちて、数秒程度のセッション情報の同期失敗が発生しても、利用者はほとんど影響を受けないのではないか?
2015年現在ではIn Memoryかディスク書き込みかに関わらず、分散KVSのソリューションも出揃い、サーバサイドのセッション情報をそれらで管理することで冗長性とスケーラビリティをある程度確保することも可能だ。
そうした現実を踏まえた上で、なお、"stateless"にしないとscalabilityとreliabilityは確保できないのか?

また、状態管理が必要になる機能は、本当にURIで一意に識別してアクセスできなければならない、つまりRESTに従わなければならないのか?

ショッピングカートでアイテムをバスケットに入れる一連の遷移は、本当に各遷移でRESTに従わなければならないのか?

3画面以上に分割された複雑なユーザ登録の画面遷移の途中、一画面ごとに、一意なURLでRESTにしたがってアクセスできなければならないのか?

そもそも状態遷移の管理が必要な機能は、URLで一意にアクセスできる必要は無いのではないか?厳密にRESTに従う必要は無いのではないか?

RESTは誰のためのものか?

RESTはWeb開発者が従わなければならない法律なのか?教義なのか?

従っていないと罰せられるのか?

そんな訳は無い。

scalabilityについてどう対処するか、reliabilityについてどこまで担保するか、そんなものは我々開発者がそれぞれのアプリ要件に応じて吟味するものであり、「RESTだからscalabilityを確保するために○○しなければならない」という思考はナンセンスだ。

・・・だが、色々ググってると、どうも皆そうしたメンタリティに陥ってるような気がするんだよなー。自分もそうなんですが。

なので、結論として、RESTだからscalability確保のために"stateless"にしなければならない、サーバサイドのセッション管理は使ってはならない、ということは無いんです。

多分、2000年初頭のWebの状況に対して、 "stateless" の部分が若干攻撃的に主張され、それがやたら強調されてるだけの気がします。

だって、色々ぐぐっても、「scalabilityやreliabilityを加味して、無理して "stateless" にしなくてもいいんじゃね?」っていう論調、見かけないし・・・。

だから "stateless" という主張だけがひとり歩きして、お題目化して、一周遅れでRESTを取り込んだJavaEE6-7辺りで、今頃になって「JerseyはRESTfulなAPI作るためのFWだから、セッション管理使えないの?」みたいな疑問が出てくるんじゃないかなーと。

でもソフトウェア開発の設計思想とか、開発思想って大なり小なりそういう、当初のバックグラウンドの文脈がロストされたため、数年後、変にお題目だけがひとり歩きしてややこしい状況になるって普通にあると思うんですよ。製品開発でのマーケティングとかに巻き込まれて、本来とは違う主旨でバズワード化することもあるでしょうし。

なので、なので、なので、「そのAPIってRESTじゃないよね?」とか言ってくる輩にはこの画像を返せば良いと思います。

「お前がそう思うんならそうなんだろう お前ん中ではな」
(from http://matome.naver.jp/odai/2137161402956130101/2137208074866804503)


2015-02-15 追記

沢山はてブしてもらいまして、さらにいくつか勉強になるコメントも頂けました。
山本陽平様直々に、「ちなみに2008年からステートレスの優先度は低いよと主張してます」とスライド資料をブクマコメントで教えていただき、大変参考になりました。

白状しますと、実はこの記事書いた数日前まで以下の書籍をまともに読んでおらず、Web上に散らばったFielding氏の論文とかRESTに関するblog記事を漁っていました。その中でも一次リソースやそれに近いもの、RESTの啓蒙活動で著名な山本陽平様の記事をなるべく参照するようにはしていたのですが・・・今日、丸善でだーっと立ち読みしてきまして、誤解してた部分ですとか、それでもやっぱりちょっと気になった部分などありました。

誤解してたところ

RESTが登場した背景について、自分勝手に「当時のWebは○○な感じで使いづらかったからかな~」と想像していましたが、どちらかというとSOAPやCORBA、RPCなどを意識していたようですね。
今更ですがInfoQで "REST" で記事を検索してみますと、延々と終わることのない SOAP陣営と REST陣営の、お互いに譲れない、もはや何の意味があるのか傍目からは分からないような論争が続いてます。

それでもやっぱり「ここはちょっとな・・・」と気になった部分

RESTがSOAPやCORBA, RPCなどを意識してたとなりますと、やはり「Webサイト」というよりは「Webサービス」寄りのアーキテクチャスタイルに思います。
当時の「Webサービス」というのは「プログラマブルなWeb」みたいな表現が前掲の書籍のどこかにありましたが、サービスを提供するサーバと、そのサービスを利用するクライアントソフトウェアに分かれてて、ここでいうクライアントソフトウェアというのはWebブラウザというよりはビジネスドメインに特化したソフトウェアのように思います。
どちらかというと古き良きサーバ <> サーバ間のWebAPIの方がREST/SOAPがターゲットとしている世界観に近いと感じました。
だからこそ、認証の話でこうも簡単にHTTP認証が登場したり、OAuthの話が出てくるのだと思います。

自分もSOAPベースのWebシステム構築を少しだけ手伝ったことがありまして、大量のXMLとか、SOAPのお約束・お作法にがんじがらめの開発に辟易したことがあります。
その体験からも、SOAPの複雑さに対する、HTTPやURIが持っている特性とパワーを素直に活用したシンプルなRESTは、合理的であるし直感的で、納得度の高い考え方です。
そしてサーバ <> サーバ間、あるいはスマホアプリ全盛の昨今であれば、ネイティブアプリ <> サーバ間のAPIということであれば、確かにセッションをなくしたstatelessでも、自分は納得できます。
HTTP認証やOAuthを使ったトークンで、認証状態を「アプリケーションのトランザクション」ではなく「認証リソース」として表現する、という考え方かなーと。

しかし、ユーザが直接触るUIとしては、ユーザに対しては状態を表現しなければ非常に使いづらいと考えます。「ステートレス」の説明でファストフードの店員とお客の会話の例が出てきますが、実際にステートレスに注文をやりとりしてしまうのは不便に感じるお客の方が多いと思います。
現実世界の我々からすれば、「状態」がある方が自然です。
ネイティブアプリであればUIコンポーネントなどがその点を制御できるので、サーバサイドのRESTと綺麗に分離できます。
しかしWebブラウザ上で動作するWebアプリの場合、状態を管理するのが結局Web画面を生成するサーバサイドの役割になってきます。これが、RESTとのギャップが発生する原因に思います。
scalabilityを確保するためにサーバサイドに状態を持ち込まず、クライアント側にアプリケーション状態を管理してもらうのがRESTの特徴でしたが、クライアント側もWebサービスと同じくWebアプリケーションとして提供される場合に、クライアントとしてのWebにRESTを適用しようとして、それがギャップになるのではないかと・・・。
エンドユーザが触れるフロント部分のWebサイト開発では「状態」を扱って人間の思考スタイルに沿った見え方にして、裏側でサービス層とやりとりする場合はHTTPとURIのパワーを活かしたシンプルなRESTでまとめる。
そういう住み分けがいいんじゃないかなー、と思いました。

最近はHTML5 + JavaScriptによるSPA(Single Page Application)で構築するケースも出てきてますので、それであればアプリケーション状態をブラウザ側で管理でき、サービスとなるAPIだけをXHRで適宜呼び出す形になります。ただその場合でも、WebフロントとWebサービスAPIが同じレイヤーで動作するため、認証状態やトランザクション状態など双方でどうハンドリングするのか調整が必要になってくると思います。
そのような場合に、「RESTだから~~~しなければならない」ではなく、現実で必要な要件に照らしあわせて、RESTでうまくフィットする部分を見極めて設計に反映していくような取捨選択をしていくのが現実的だと思います。



プレーンテキスト形式でダウンロード
現在のバージョン : 2
更新者: msakamoto-sf
更新日: 2015-02-15 20:03:11
md5:cb5ec4ae6db370f004f134ddfa6d907a
sha1:3509e86e85b44388ec11a7921808546f89a844a6
コメント
コメントを投稿するにはログインして下さい。