はじめに
MochiWebはErlangで書かれた軽量HTTPサーバーです。
Erlang使いを志すならMochiWebのコードを読むと良いらしいので読んでみます。
MochiWebはMITライセンスのようです。
HTTPサーバーの起動はmochiweb_httpモジュールのstart関数により行います。
なので、mochiweb_httpから順に流れを追ってみます。
mochiweb_http.erlは単体テストを入れて250行程度。
ファイルの先頭部分
参考
- http://www.erlang.org/doc/reference_manual/modules.html
- http://www.erlang.org/doc/reference_manual/macros.html
- http://d.hatena.ne.jp/m-hiyama/20070922/1190432025
%% @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を読む