以前に有名となった「Apache vs Yawsのグラフ」(source)を見て、あなたもまたYawsを使うべきだと思ったでしょうか? 一見すると、そのグラフは、Yawsに対する信じられないくらい大きなスケーラビリティの優位性があるように見えます。Apacheが4000のパラレル接続でダウンしたのに対し、Yawsは80,000を超えるスケール能力を持っています。このグラフに対する反応は大きく二極化する傾向にあります。「これらのグラフは正確な方法で行われたものではなかった」あるいは「Apacheの設定ミスに違いない」というものと、それとは反対に「ワオ!Yawsを利用する価値がある」というものです。
Yawsの比較グラフを信じるかどうかに関係なく、Yaws(サイト・英語)は動的コンテンツを提供するための確かなWebサーバーです。Claes Wikstrom氏は、Yawsを「もう一つのWebサーバー」として、Erlang(サイト・英語)で実装しました。それは、長時間稼動、並列性、高信頼性分散システムのサポートに特化して作られたプログラミング言語です。(Erlangについてもっと勉強したければ、言語作成者であるJoe Armstrong氏によって書かれたすばらしい書籍Programming Erlangbook(source)を参照してください) Erlangのいくつかの独自機能と相まったYawsの柔軟性は、RESTful Webサービスプラットフォームとして説得力のある組み合わせとなります。もし静的なページを提供したいのであれば、代わりにlighttpd(サイト・英語)やnginx(サイト・英語)を入手してください。しかし、動的なRESTful Webサービスを書いているのであれば、Yawsは間違いなく調査する価値があります。本稿では、Webサービス開発にYawsとErlangを使用した私の経験のいくつかを説明します。
Yaws基礎
Yawsは、動的なWebコンテンツの提供やRESTful Webサービスのサポートに関していくつかの方法を提供します。:
-
静的ページ内へのErlangコードの埋め込み。このアプローチでは、静的コンテンツの中に直接erlタグを書き、その中に書く
out/1
という関数にErlangのコードを埋め込みます。ファイルは、.yaws
という拡張子を持ち、そのファイルはYawsによって処理され、その中に含まれるであろうout/1
関数の実行結果と共にerlタグが置き換えられます。Erlangの表記では、out/1
はarityが1の関数ということです。即ち、関数が1つの引数をとることを意味します。その引数は、Yawsのarg
レコードとして期待され、コードでそれらをハンドリングすることを目的に、受け取った入力リクエストに対する詳細を伝えるためにYawsが利用するデータ構造です。例えば、arg レコードは、リクエストURI、ヘッダー、POST
データなどといった情報を提供します。 -
アプリケーションモジュール(appmods)。Yawsのappmod機能は、アプリケーションコードがURIをコントロールするためのものです。前述したアプローチでは、Erlangのコードは、静的ファイル内に組み込まれ、そのURIはWebサーバーのドキュメントルートの相対パスによって決定されます。しかし、appmodと共にアプリケーションがURIの意味をコントロールします。そして、そのURIは通常、どのファイルシステムにあるものとも一致しません。基本的に、Appmodsは
out/1
関数を利用したErlangのモジュールです。そういったモジュールは、URIパス要素に対応づけるためのYawsの設定ファイル内に書かれています。リクエストが登録されたappmodと関連づけられたパス要素を含んでいる場合、Yawsはそのモジュールのout/1
関数を呼び出し、それをarg レコードに渡します。appmodのout/1
関数は、的確なリソースを確定するために残りのURIを検査することができます。そのリソースは、受け取ったリクエストのターゲットであり、それ相応に反応します。 -
Yawsアプリケーション(yapps)。通常、単一のErlangモジュールであるappmodsとは異なり、Yawsのyapps は成熟したアプリケーションです。それぞれのyapp には自身のドキュメントルートを持ち、それぞれが専用のappmodのセットを持つことができます。特に、yappsはErlang/OTPのアプリケーションです。OTPとは、「Open Telecom Platform」のことで、強力な機能を持つErlangアプリケーションを提供するための、十分に実績のあるライブラリとフレームワークのセットです。OTPは、分散、イベントハンドリング、高信頼性といった多くのことをおこなうためのイディオムやアプローチを隠蔽します。Erlang/OTPは、いろいろなテレコムシステムの中で、実世界の現場での利用実績があります。例えば、1年を通じてたった数ミリ秒のダウンタイムをマークしています。
これら3つのアプローチ全てが、YawsのWebサイト(サイト・英語) で詳しく述べられており、RESTful Webサービスに役立つように応用させることも可能で、サービスそれ自身の特異性に依存しています。しかしながら、私の経験では、Webアプリケーションに対する最高の制御を提供するので、yappsとappmodsはベストに機能します。
RESTfulな設計
私たちはRESTful Webサービスを開発したいので、「Representational State Transfer(表現の状態遷移)」として表されるRESTに関して、いくつか細かい部分を見ることにしましょう。Webのような大規模な分散システムに適したアーキテクチャスタイルを表現するために、Roy T. Fielding は、自身の論文の中で「REST」という言葉を使いました(source)。本質的には、HTTPはRESTの実装です。「Representational State Transfer」という言葉は、リクエストとレスポンスの中で、リソース状態の表現のやりとりを通してRESTfulシステムが動作するという事実を表しています。例えば、HTTPのGETで得られた典型的なWebページは、GETを対象としたURIによって指定されるWebリソースのHTML表現です。
RESTfulなWebサービスを開発するとき、以下の事項は注意すべき重要事項です。
-
リソースとリソース識別子
-
それぞれのリソースに対応したメソッド
-
クライアントとサーバー間のデータ交換フォーマット
-
ステータスコード
-
リクエストとレスポンスそれぞれに対する適切なHTTPヘッダ
YawsとErlangを背景として、これらの分野をそれぞれ検討しましょう。
リソース識別子
RESTful なWebサービスを設計するときには、サービスが何のリソースで構成されているのか、それらを特定する一番の方法は何か、お互いがどのように関連しているのかといったことについて考える必要があります。RESTfulなリソースはURIによって識別されます。通常、関連するリソースにはURIがあり、それ自身が関連していて、共通のパス要素を共有しています。例えば、Webベースのバグトラッキングシステムでは、架空のプロジェクト「フェニックス」に対する全てのバグが、URI http://www.example.com/projects/Phoenix/bugs/
によって見ることができます。それに対して、特定のバグ番号12345は、http://www.example.com/projects/Phoenix/bugs/12345/
によって見ることができます。RESTfulなリソースは、自身の状態表現の中で他のリソースに対するURIを提供する傾向もあります。それにより、Web アプリケーション全体の一部をナビゲートするための状態表現の中に返されたURIを使用するために、クライアントが特定のリソースの状態を取得することができます。
Yawsでは、arg
レコードがリクエストURIを示します。そして、yaws_api
モジュールがそれを簡単に取得するためのrequest_url
関数を提供します。
out(Arg) ->
Uri = yaws_api:request_url(Arg),
Path = string:tokens(Uri#url.path, "/"),
一度、リクエストURIを持てば、上記に示すように、/(スラッシュ)で区切ることでリクエストパスを分割することが便利であると分かります。その結果は、appmodと関連のあるURIポイントで始まるパス要素のリストとなります。例えば、URI http://www.example.com/projects/
の「projects」パス要素上にappmodを関連づけたとします。あるリクエストが、プレフィックスとしてそのURIを含む全てのURIで構成されているのならば、appmodのout/1 関数は、リクエストのターゲットリソースを表している分割されたパス要素のリストとなります。例えば、URI http://www.example.com/projects/Phoenix/bugs/
のリクエストは、上記で示したコードを実行した後で、Pathの値の中にあるErlangのパス要素のリストになります。:
["projects", "Phoenix", "bugs"]
URIを分割することの効用として、Erlangのパターンマッチにより、ディスパッチがとても簡単になります。例えば、このような関数の先頭を定義することにより、特定のURIを扱うための分割した関数を書くことができます。それをout/2と呼ぶことにしましょう。:
out(Arg, ["projects", Project, "bugs"]) ->
% code to handle this URI goes here.
この out/2
f関数は、値 Project
, によって、私たちが知っている全てのプロジェクトのバグリストに対する全てのリクエストを操作することができます。そして、それは関数本体を利用することができ、リクエストされている特定のプロジェクト名にセットされます。サポートしている追加のURIは単純です。それは、out/2
関数の値を追加するだけです。それらはYawsのフレームワークから直接呼ばれてないので、もし望むのであれば、これらの関数に対して out
以外の名前を自由に使用することもできます。
留意すべき点は、リソースURIを適切に定義することで、大きなメリットを生み出すということです。appmodsやyappsによって、リッチなURIの部分をもつことが非常に簡単です。何故なら異なるパス要素上の異なるappmodsの紐づきの単純化と、ディスパッチの容易さがあるからです。 Erlangのパターンマッチングは、異なるURIに対するリクエストハンドリングを容易にします。これを、全てのサービスが同じURIで与えらていて、 RESTfulでないサービス定義を伝統的に利用しているプアなものと比較してください。通常、このURIはスクリプトを示しています。リクエスト本体の中、あるいは、実際にリクエストをディスパッチするための場所を特定するURIのクエリー文字を通して提供された情報を使用するためのスクリプトを示しています。上で示すようなErlang/Yawsのディスパッチ技術によるURIは、伝統的なアプローチによる終わりがないかのようなパラメーターリストによる過剰なURIよりもはるかにすっきりとしています。
リソースメソッド
WebクライアントがWebリソース上で呼び出すことができる方法は、HTTPの動詞となるGET、PUT、POST、DELETEによって定義されます。しかし、個々のリソースはこれらの操作のサブセットのみをサポートする傾向にあります。あなたがWebサービスを設計するとき、それぞれのリソースがどの方法をサポートするのかを決め、RFC 2616(source)で定義されているそれぞれのHTTPの操作に期待されている動作に注意する必要があります。
Yawsでは、リクエストの方法は http_request
レコードにあり、argレコードを通してアクセス可能です。
Method = (Arg#arg.req)#http_request.method
これは、Erlangのatomを表現しているリクエストメソッドを返し、パターンマッチングのディスパッチアプローチに加えることができます。リクエストメソッドを含めるために、out
関数に新しいパラメータを加え、out/3
に変えることができます。
out(Arg, 'GET', ["projects", Project, "bugs"]) ->
% code to handle GET for this URI goes here.
この関数は、プロジェクトごとのバグリストに対する GET
リクエストだけを扱います。おそらく、リストに新しいバグを追加するために、別の変数として POST
を扱うかもしれません。GET
や POST
だけでなく、他の全ての操作を扱わないようにするには、同じURIに対して全てを補足する関数を記述するだけです。
out(Arg, 'GET', ["projects", Project, "bugs"]) ->
% code to handle GET for this URI goes here;
out(Arg, 'POST', ["projects", Project, "bugs"]) ->
% code to handle POST for this URI goes here;
out(Arg, _Method, ["projects", _Project, "bugs"]) ->
[{status, 405}].
ここで、GET
と POST
以外の方法は、3つめにあたります。それは、HTTPステータス405を返し、「メソッドが許可されていない」ことを意味します。MethodとProjectの先頭にあるアンダースコアは、それらを使用しないことに対して、コンパイラの警告を出力しないようにするものです。
ちょうどURIのディスパッチのように、Erlangのパターンマッチングは、別々のHTTP操作を取り扱うために別々の関数にディスパッチすることを容易にします。
表現形式
RESTfulなWebサービスを設計するとき、それぞれのリソースがどんな表現(群)をサポートするのかを考える必要があります。例えば、Webサービスのリソースは、通常XMLやJSON表現をサポートします。Erlangでは、XMLの読み込みと生成のためにxmerlライブラリ(source)を提供しており、YawsはわかりやすいJSONモジュールを提供しています。共に、とてもよく機能します。
クライアントがどんな表現(群)を選ぶのかを決めるために、送られてきたリクエストの Accept
header ヘッダーにアクセスすることができます。このヘッダーは、 headers
レコードの中で利用することができ、arg
レコードを通して利用することもできます。
Accept_hdr = (Arg#arg.headers)#headers.accept
あなたのリソースが複数の表現をサポートする場合、どちらの表現が好ましいかをクライアントが示したかどうかを見るために、このヘッダをチェックすることができます。クライアントが Accept
ヘッダーを送信していないならば、上で示した Accept_hdr
変数は、atomの undefined
がセットされるでしょう。そして、あなたのリソースはそれがベストであると考える表現であればなんでも提供することができます。さもなければ、あなたのサービスは、どの表現が送信されたのかを判断するために、Accept_hdr
の値をパースすることができます。クライアントが、リソースが実行できないようなリクエスト表現を要求するなら、何のフォーマットが受け入れ可能かを示しているボディ部と共に、「受け入れられない」を意味するHTTPステータス406を返します。
case Accept_hdr of
undefined ->
% return default representation;
"application/xml" ->
% return XML representation;
"application/json" ->
% return JSON representation;?
_Other ->
Msg = "Accept: application/xml, application/json",
Error = "Error 406",
[{status, 406},
{header, {content_type, "text/html"}},
{ehtml,
[{head, [], [{title, [], Error}]},
{body, [],
[{h1, [], Error},
{p, [], Msg}]}]}]
end.
上のErlangのコードは、application/xml かapplication/jsonのどちらであるかを見るためにAccept_hdr の値をチェックしています。いずれかであれば、そのリソースは適切な表現を返しますが、そうでなければ、上記コードはリソースが提供しようとしている表現を示しているHTMLドキュメントに加えて、HTTPステータス406を返します。
求める表現を取り扱うもう一つの方法は、あなたが想像するように、もう一つのパラメータとしてout ハンドラ関数にそれを加えることです。この方法では、Erlangのパターンマッチによって、リクエストがリクエストURI/メソッド/表現の組み合わせに対して適切なハンドラにディスパッチされることを保証します。これは、上記のようにケース文でハンドラが分散することを防ぎます。
ところで、この例ではYawsのehtml タイプも示しています。それはHTMLを一連のErlangの言葉として表現する方法です。私は、ehtmlが書くのに非常に直感的であると思います。何故なら、それが直接HTMLの構造に従いながら、とてもコンパクトで、文字通りのHTMLを書くときに直面するタグの記述ミスや記述の面倒さを取り除いているからです。
ステータスコード
RFC 2616によって示されるように、RESTfulなWebサービスは適切なHTTPのステータスコードを返さなければなりません。適切なステータスを返すことは、Yawsにとって簡単なことです。単に、out/1関数の結果に、statusのタプルを含めます。適切なステータスコードを返す例として、上記のケース文を参照してください。もし、コードが明確にステータスをセットしていない場合は、Yawsは、成功を表すステータス200をセットします。
HTTPヘッダー
Yawsでのリクエストヘッダーの取得とリプライヘッダーの設定も簡単です。既に、ヘッダーレコードからAcceptヘッダーを取得する例を見てきました。他のリクエストヘッダーは、同様の方法で取得することができます。以下のように、リプライヘッダーをセットするには、単に返すリプライのheaderタプルをセットする必要があります。
{header, {content_type, "text/html"}}
この例では、Content-type ヘッダーに「text/html」をセットしています。同様にして、「メソッドが許可されていない」エラーを表すステータス405を返している先の例では、以下のヘッダーも含める必要があります。
{header, {"Allow", "GET, POST"}}
AppmodsあるいはYapps?
ここまで、YawsとErlangが、RESTfulなWebサービスに対して、最も重要な関心事の多くを扱うことをとても簡単にするための方法を見てきました。一つ残る疑問は、appmods対yappsの選択についてです。そして、その答えはあなたのサービスが何をするかに依存します。あなたが実装しているWebサービスが、他のバックエンドサービスと相互作用する必要があるならば、おそらくyappsがベストな選択でしょう。それらが本格的な Erlang/OTPアプリケーションならば、それらは一般的に初期化と終了の関数があり、バックエンドへのコネクションの生成と切断がおこなわれるでしょう。もし、あなたのyappがErlang/OTPの gen_server
ならば、例えば、init/1
関数は、gen_server
フレームワークが提供する状態を確立し、変更することが可能で、サーバーに入ってくる呼び出しによって、いつでもコールバックします。加えて、yappsを使用することは、同じようにappmodsを使用することができることを意味するので、別のものを選択するという問題ではありません。最後に、yapps は、Erlang/OTPの管理ツリーに加わることが可能です。そこで、スーパーバイザープロセスがyappsを監視し、それらが機能しなくなれば再起動することができます。スーパーバイザーツリーは、Erlangシステムの長期稼動において重要な役割を果たしています。
本稿は、リレーショナルデータベース以外のバックエンドをベースとしたRESTfulなWebサービスについて適合させました。データーベースと従来のWebサーバーで実装しているならば、Erlyweb(サイト・英語)を調べる必要があります。それは、そのようなWebサービスのためのフレームワークであり、それもまたYawsとErlangをベースにしています。
結論
RESTful なWebサービスを実装することの重要な側面は、適切なプログラミング言語を選ぶことです。私たちは、様々なプログラミング言語の中にある多くのサービスフレームワークが、長年にわたって行ったり来たりするのを見てきました。そして、そのほとんどが、問題に適していなかったというだけの理由で失敗でした。 YawsとErlangは、特別なRESTful Webサービスフレームワークを提供しませんが、それでも、それらが提供するものは、RESTful開発を目的として特別に作られたその他多くの言語フレームワークよりも、RESTful開発により適しています。
この種の記事は、必ずしもYaws、Erlang、RESTful Webサービスの詳細に深く踏み込んでいるわけではありません。願わくは、簡単なコードの例を通して主要なトピックに触れ、それらを扱うための考え方を蓄えてくれることを望みます。私の経験では、YawsとErlangでRESTfulなWebアプリケーションを構築することは、とても簡単で、結果のコードは読むのが容易で、保守が容易で、拡張が容易です。
略歴
Steve Vinoski氏は、Westford, MA, USAのVerivueのテクニカルスタッフのメンバーです。彼は、IEEEの上級メンバーでACMのメンバーでもあります。彼は、過去20年以上にわたり、分散コンピューティングや統合に関する80を超える記事、コラム、書籍の著者あるいは共著をおこなっています。さらに、過去6年にわたり、「Toward Integration」のコラムをIEEEのインターネットコンピュータマガジンに掲載しつづけています。Steveとは、 vinoski at ieee.org で連絡を取ることができます。また、彼のブログ http://steve.vinoski.net/blog/にアクセスしてください。