2011年11月16日水曜日

MochiWebで学ぶErlang(mochiweb_http)

はじめに



MochiWebはErlangで書かれた軽量HTTPサーバーです。
Erlang使いを志すならMochiWebのコードを読むと良いらしいので読んでみます。



MochiWebはMITライセンスのようです。



HTTPサーバーの起動はmochiweb_httpモジュールのstart関数により行います。
なので、mochiweb_httpから順に流れを追ってみます。



mochiweb_http.erlは単体テストを入れて250行程度。




ファイルの先頭部分



参考






%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2007 Mochi Media, Inc.

%% @doc HTTP server.


Erlangの一行コメントは「%」です。



「@xxx」はErlang/OTP付属のドキュメントジェネレータであるEDocのタグです。




-module(mochiweb_http).
-author('bob@mochimedia.com').
-export([start/1, start_link/1, stop/0, stop/1]).
-export([loop/2]).
-export([after_response/2, reentry/1]).
-export([parse_range_request/1, range_skip_length/2]).

-define(REQUEST_RECV_TIMEOUT, 300000). %% timeout waiting for request line
-define(HEADERS_RECV_TIMEOUT, 30000). %% timeout waiting for headers

-define(MAX_HEADERS, 1000).
-define(DEFAULTS, [{name, ?MODULE},
{port, 8888}]).


シングルクオートで囲まれた値は文字列ではなくアトムです。



「-」から始まるものにはModule Attribute、Preprocessor、レコード定義、
定義済みマクロ(?FILEおよび?LINE)の変更、型や関数の仕様記述があります。



Module Attributeはユーザが自由に定義でき、以下の関数でそのモジュールに定義された
値の一覧を取得できます。




Module:module_info(attributes)


定義済みのModule Attributeその他は以下のとおり。




  • module : モジュール名定義

  • export : 関数のエクスポート

  • import : 関数のインポート

  • compile : コンパイラオプション

  • vsn : モジュールのバージョン

  • behaviour : ビヘイビアのコールバックモジュールであると宣言

  • record : レコード定義

  • include : ファイルインクルード。主にヘッダに対して使う

  • include_lib : includeとほぼ同じ。探索パスが異なる

  • define : マクロ定義

  • undef : マクロを未定義状態にする

  • ifdef : マクロが定義されている場合のマクロ制御フロー

  • ifndef : マクロが未定義である場合のマクロ制御フロー

  • else : ifdef/ifndefとあわせて利用する。ifdef/ifndefでない場合

  • endif : ifdef/ifndef/elseの終端

  • file : 定義済みマクロ?FILEおよび?LINEの値を変更する

  • spec : 関数の仕様。EDocなど以外に影響はない(たぶん)

  • type : 型の仕様。EDocなど以外に影響はない(たぶん)



マクロの定義と使用法は以下のとおり。




%% 定義
-define(Const, Replacement).
%%% 引数を文字列に展開するには「??Arg」を使う
-define(TEST(Exp), io:format("~s : ~p~n", [??Exp, Exp])).
%% 使用
?Const.
?TEST(1 + 2).
%% => "1 + 2 : 3"と表示される


定義済みマクロは以下。




  • ?MODULE : モジュール名(アトム)

  • ?MODULE_STRING : モジュール名(文字列)

  • ?FILE : ファイル名

  • ?LINE : 行番号

  • ?MACHINE : 'BEAM'



include対象のパスに環境変数を利用することができます。




%% $PROJECT_ROOTはos:getenv("PROJ_ROOT")の戻り値に展開される
-include("$PROJ_ROOT/path/lib.hrl").



mochiweb_httpの関数



関数の処理や初めて知ったor気になった点など。




  • parse_options


    • start関数に渡されたオプションをパースして返す

    • オプションは属性リスト(proplists)

    • mochilists:set_defaults関数で未定義オプションにデフォルト値を設定(ポート番号)

    • 戻り値のloopオプション = {?MODULE, loop, [もともと指定されていたloop用関数]}


  • stop


    • mochiweb_socket_server:stopを呼び出す


  • start


    • mochiweb_socket_server:startを呼び出す

    • サーバーの処理はmochiweb_socket_serverに記述されている


  • loop


    • mochiweb_socket:setoptsでソケットに{packet, http}をセットしてrequestを呼ぶ

    • {packet, http}とするとパケット受信時にHTTPヘッダをパースしたものを取得できる

    • mochiweb_socketモジュールはSSL対応か否かによりgen_tcpとsslを使い分けるラッパー


  • request


    • HTTPリクエストの先頭行を読み込む

    • ソケットのオプション{active, once}を指定すると1度だけパケットをメッセージとして受信する


  • headers


    • HTTPリクエストのヘッダを受信する

    • {packet, httph}としているとヘッダの終端まで読み込んだ場合、http_eohを受信する


  • call_body


    • 引数として渡された関数を呼び出す


  • handle_invalid_request


    • 400 Bad Requestを返す

    • Req:respond(...)は、parameterized moduleを利用した表記


  • new_request


    • リクエスト(parameterized module)を作成する


  • after_response


    • mochiweb_requestのshould_closeを呼び出し、ソケットをクローズするかどうかを決定する

    • HTTPのバージョンやKeep-Aliveによって判別するっぽい

    • erlang:garbage_collect()によりガーベージコレクションを明示的に実行できる


  • parse_range_request


    • Rangeヘッダの値をパースする

    • 文字列はリストなので ++ で連結できる

    • 文字列を区切ってリストにするにはstring:tokensを使う

    • 文字列->整数の変換はBIFのlist_to_integerで行える


  • range_skip_length


    • 部分レスポンスの範囲を返す

    • Sizeは対象とするレスポンス(ファイル)のサイズだと思う





Misc



モジュール定義時に、以下のように宣言するとParameterized Moduleになるらしいです。




-module(module_name, [Vars]).


オブジェクト指向チックな書き方ができるようになるようですが、当然変数への代入は1度きりのまま。
mochiwebではプロセス辞書(erlang:put,erlang:getなど)を利用してリクエストの状態を保存しているようです。



mochiweb_requestを読む際にもう少し調べます。




テスト



ifdefにより、TESTマクロが定義されている場合のみ、EUnitを利用したテスト関数が
定義されます。



EUnitでは関数名末尾が「_test」で終わるものは試験ケースとして扱われるようです。



eunit.hrlをインクルードすると、アサーション用マクロが読み込まれます。





TODO




  • EDocについて調べる

  • EUnitについて調べる

  • mochiweb_requestおよびmochiweb_responseを読む

  • mochiweb_socket_serverを読む




0 件のコメント:

コメントを投稿