"phx.server"コマンドの実装を追い掛ける
この記事は、「fukuoka.ex Elixir/Phoenix Advent Calendar 2021」の20日目の記事です。
遅刻してすみません…!
phx.serverへの関心
ElixirのWebアプリケーションフレームワークの Phoenix Framework にて、サーバを起動するコマンドにphx.server
があります。
Phoenixサーバを起動するためによく使う割にはどんな働きをしているのか私は知りませんでした。
そこで、今回はphx.server
の実装を追い掛けてみます。
対象バージョンと前提
今回の対象バージョンは下記の通りになります。
Elixir: 1.13
Phoenix: 1.6.5
前提は、次の2点とします。
- 今回の検証で用いるプロジェクトはコマンド
$ mix phx.new firstphoenix
を実行した直後とします。 - 他にPhoenixサーバが起動していないものとします。
実行コマンド
$ mix.phx.server
オプション等は設定していないものとします。
phx.serverの処理の流れ
処理の大まかな流れは大まかに下記のようになります。
細かい関数呼び出しやエラーハンドリング等の分岐は省いて正常にサーバが起動した場合の処理のみ追い掛けます。
- Mix.Tasks.Phx.Server.run/1
- Mix.Tasks.Run.run/1
- Mix.Tasks.Run.run/5
- Mix.Task.run/2
- Mix.Task.run_task/3
- Mix.TasksServer.run/1
次節以降、それぞれの処理を下記の3点で見ていきます。
- 各関数が定義されているファイルのURL
-
$ mix phx.server
を実行した際に各関数に渡される実引数 - 関数がやっていること
1. Mix.Tasks.Phx.Server.run/1
実引数
args = []
やっていること
- 環境変数
phoenix.serve_endpoints
への設定 - コマンドライン引数を渡して
Mix.Tasks.Run.run/1
を呼び出す
mix phx.server
を実行すると、この関数が呼ばれます。2行だけの関数です。
Application.put_env/4
で環境変数を設定したら、Mix.Tasks.Run.run/1
を呼び出します。
2. Mix.Tasks.Run.run/1
実引数
args = []
やっていること
- コマンドライン引数からオプションと引数をパース
- コマンドライン引数やオプションを渡して
Mix.Tasks.Run.run/5
呼び出す
まず始めにOptionParser.parse_head!/2
を呼び出します。parse_head!/2
は、コマンド引数から有効なオプションをopts
変数として取り出し、それ以外をhead
変数に格納します。
コマンド引数のパースが終わったら、opts
とhead
などを渡してMix.Tasks.Run.run/5
を呼び出します。
3. Mix.Tasks.Run.run/5
実引数
args = [],
opts = [],
head = [],
expr_evaluator = &Code.eval_string/1,
file_evaluator = &Code.require_file/1
やっていること
- オプションから値の取り出しや置換
- コマンドライン引数の上書き
- コンパイル設定の書き換え
-
Mix.Task.run/2
を呼び出す(4番で説明後述) - コマンドライン引数として渡されたオプションに指定されたファイルの読み込みや処理の実行(今回はオプションは空なので処理無し)
Mix.Tasks.Run.run/5
は、まずopts
に対して値の置き換えや特定の値の取り出しを行います。
続いて、System.argv/1
でコマンドライン引数を上書き、Mix.Tasks.Run.process_config/1
でコンパイル時の設定を書き換えます。
その後、Mix.Task.run/2
を呼び出します。
4. Mix.Task.run/2
実引数
task = "app.start",
args = []
やっていること
-
task
をaliasとして呼び出すべきか、そのままタスクとして呼び出すかを判別 -
mix phx.new
を実行した直後の状態なのでMix.Task.run_task/3
を呼び出す
Mix.Task.run/2
は2つ定義されていますが、第1引数に渡される値は文字列"app.start"ですのでis_binary/1
のガード節がtrueになるdef run(task, args) when is_binary(task)
の方が呼ばれます。
関数内では、mix.exsに記述したaliasの設定やサーバの起動状態によって呼ばれる処理が変わります。
mix phx.new
を実行した直後は
-
Mix.Project.config()[:aliases][String.to_atom("app.start")]
の結果がnil
- 前提として他のPhoenixサーバが起動していないため
!Mix.TasksServer.get({:task, task, proj})
がtrue
上記2点の結果Mix.Task.run_task/3
が呼ばれます。
5. Mix.Task.run_task/3
実引数
proj = Firstphoenix.MixProject,
task = "app.start",
args = []
やっていること
※ すみません、この関数については詳しく調べられておらず振る舞い、コメントや関数名からの推測が多いです。
- タスクが有効かどうかをチェックし、タスクが定義されているモジュールを取得
- タスクを再帰的に実行する必要があるかどうかを判定
- タスクの再帰実行の有無やプロジェクトに積まれているタスクの有無をチェックし、
Mix.TasksServer.run/1
を呼び出す
引数のタスクが実行できるか、再帰実行すべきものか、プロジェクトに積まれているタスクがあるか、といったことを評価します。
実際の振る舞いを観測できていませんが、ここではMix.TasksServer.run/1
を呼び出したものとして進めます。
6. Mix.TasksServer.run/1
実引数
tuple = {
:task,
"app.start",
Firstphoenix.MixProject
}
やっていること
-
Agent.get_and_update/3
を呼び出す
Agent.get_and_update/3
はGenServer.call/3
を呼び出すので、ここでサーバが起動されます。
Agent.get_and_update/3
のソースコード
おわりに
mix phx.server
の実装を追い掛けました。
本当は6. Mix.TasksServer.run/1
の後でいくつかの処理を呼び出していますが、実行コマンドにオプションが設定されていないため何も処理を行われないので省略しました。
私の独自調査のため間違っている箇所もあるかと思いますが、何かの参考になれば幸いです。
Discussion