puma-devがMacOSX上でどう動いているのかコードリーディングして調べてみた

## はじめに

Railsアプリケーションの開発用Webサーバとして、puma-devが便利ということで何気なく使っていました。しかしその仕組みについてはいまいちブラックボックス感が拭えませんでした。

そこで勉強も兼ねて、実際にpuma-devのコードを読んで仕組みを調べてみました。

## 調べたテーマ

コードを全て読み切るというのは大変だったので、puma-devについて自分が疑問に思っていたことを2つテーマとして設けて調べました。

1. puma-devの初期セットアップで何をしているか？
2. Webブラウザからリクエストを投げたときpuma-devは何をしているか？

## 前提

### 環境

puma-devはLinuxでも利用できるかと思いますが、本記事ではMac OS Xの環境で調べています。

* Mac OS X 10.13.3
* puma-dev v0.11

### コードリーディングの対象リポジトリ

2018/4/19時点で最新のreleaseであるv0.11のpuma-devをコードリーディングの対象としました。

https://github.com/puma/puma-dev/tree/0.11

puma-devというくらいなのでrubyかと思いましたが、golangで書かれていました。しかし割とシンプルなCLIツールの構成になっており、読みやすかった印象です。


## 調べたテーマ1: puma-devの初期セットアップで何をしているか？

まずはpuma-devを利用する前の初期設定で何をしているのかを調べました。

多分puma-devを使うときは、おおよそ下記のようなコマンドを実行するかと思います。

それぞれのコマンドで何をしているかざっくり確認します。

```
$ puma-dev -install -d test
$ sudo puma-dev -setup
$ cd /path/to/app
$ puma-dev link -n app_name
$ puma-dev -uninstall
```
### コマンド：puma-dev -install -d test

**コード該当箇所**

https://github.com/puma/puma-dev/blob/0.11/dev/setup.go#L98-L189

* LaunchAgentsの設定ファイル```~/Library/LaunchAgents/io.puma.dev.plist```を書き出している。```-d```で指定したドメイン名```test```もここで.plistファイルに反映される
* ```~/Library/Logs/puma-dev.log```をログファイルのパスに指定している
* ```launchctl load```で配置した.plistファイルをサービス起動している

puma-devはMacのLaunchAgentsを使って、プロセスは自動起動する挙動をとるようです。

インストール後、下記のようなコマンドでpuma-devのプロセスが存在することを確認できると思います。

```
$ ps x | grep puma-dev
1086   ??  S      1:14.41 /usr/local/Cellar/puma-dev/0.11/bin/puma-dev -launchd -dir ~/.puma-dev -d test -timeout 15m0s
```

### コマンド：puma-dev -setup

**コード該当箇所**

https://github.com/puma/puma-dev/blob/0.11/dev/setup.go#L16-L75

* ```etcDir```で指定されているディレクトリ```/etc/resolver```の所有者をpuma-devコマンドを実行しているユーザに変更する
* ```/etc/resolver```以下にDNS名前解決用のドメイン名（test）のファイルを作成する

resolverを使い、ローカルのMacにpuma-dev用のDNSサーバ設定を追加しているようです。下記のようなコマンドで確認できます。

```
$ cat /etc/resolver/test
# Generated by puma-dev
nameserver 127.0.0.1
port 9253
```

### コマンド：puma-dev -stop

**コード該当箇所**

https://github.com/puma/puma-dev/blob/0.11/dev/setup.go#L191-L198

* ```pkill -USR1 puma-dev```を実行し、puma-devプロセスを再起動する

```puma-dev -install```でLaunchAgentに常時起動プロセスとして登録されているので、プロセスは停止せず再起動する挙動をとるみたいです。

### コマンド：puma-dev link -n app_name 

**コード該当箇所**

https://github.com/puma/puma-dev/blob/0.11/cmd/puma-dev/command.go#L23-L88

* ```-n```オプションで指定したファイル名でシンボリックリンクを作成する
* シンボリックリンクは```~/.puma-dev/app_name```のように作成され、コマンド実行したディレクトリまたは```-n app_name```以降に設定したディレクトリパスを紐付ける

コードを読む限り、単純なシンボリックリンク作成のみしているようです。

### コマンド：puma-dev -uninstall

**コード該当箇所**

https://github.com/puma/puma-dev/blob/0.11/dev/setup.go#L200-L214

* ```launchctl unload```でpuma-devプロセスを停止する
* ```~/Library/LaunchAgents/io.puma.dev.plist```を削除する
* ```/etc/resolver```以下のファイルを削除する

アンインストールということもあり、puma-dev用の設定ファイルの削除まで実施するみたいです。

余談ですが、puma-devプロセスだけを停止したい場合は、下記コマンドを実行すれば良いということになりそうです。
(※<a href="https://github.com/puma/puma-dev/issues/111#issuecomment-341225395" target="_blank">puma-devのissue</a>にも書いてありました。)

```
$ launchctl unload ~/Library/LaunchAgents/io.puma.dev.plist
```

## 調べたテーマ2: Webブラウザからリクエストを投げたときpuma-devは何をしているか？

おおまかにpuma-devの設定で何をしているかは確認できたので、次は実際にpuma-devを利用するときの挙動を調べてみました。

### 概要図

まず、自分がpuma-devのコードやプロセスの挙動を調べて理解した内容を図にしてみました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/19f0587b-39f3-ae4c-90e0-4ba86b0dbb32.png)

1. Webブラウザから```https://XXX.test```というURLでリクエスト
2. ```/etc/resolver/test```で定義されている設定に基づき、puma-devプロセス上のDNSサーバ経由でURLの名前解決をリクエスト
3. puma-devプロセス上のDNSサーバで、HTTPSなら```localhost:443```に、HTTPなら```localhost:80```に名前解決
4. puma-devプロセス上のHTTP/HTTPSサーバから、UNIXドメインソケット通信を使ったpumaプロセスを起動・リクエスト
5. pumaがリクエストに応じてRailsアプリケーションにリクエストする

こちらを踏まえ、Webブラウザからリクエストが投げられた後の挙動をざっくり見ていこうと思います。

### 1 ~ 2までの挙動

Webブラウザから```https://XXX.test```というURLでリクエストすると、まずDNSの名前解決を試みます。

```puma-dev -install -d test```で作成されたファイルをおさらいすると、IPアドレス```127.0.0.1:9253```が指定されており、こちらに対して名前解決を試みます。

``` /etc/resolver/test
# Generated by puma-dev
nameserver 127.0.0.1
port 9253
```

ポート9253番はpuma-devのデフォルトのDNSサーバのポート番号として設定されています。

https://github.com/puma/puma-dev/blob/master/dev/dns.go#L11

### 2 ~ 3までの挙動

```127.0.0.1:9253``` に対するDNSの名前解決のリクエストは、LaunchAgentsによって常時起動しているpuma-devのプロセスに対して実行されます。
改めて、puma-devのプロセスが起動していることを確認します。

```
$ ps x | grep puma-dev
1086   ??  S      1:14.41 /usr/local/Cellar/puma-dev/0.11/bin/puma-dev -launchd -dir ~/.puma-dev -d test -timeout 15m0s
```

またpuma-devがLISTENしているポート番号を確認してみます。9253番とhttp/httpsのポートをLISTENしているのがわかります。(http/httpsについては後ほどまとめます)

つまり、ポート9253番への通信があるとpuma-devのプロセスに通信されることになります。
ここから、puma-devプロセスがDNSサーバとしてリクエストを受けることが予想できます。

```
$ lsof -i | grep puma-dev
puma-dev   1086 gotchane    5u  IPv4 0x7ab086f0e36ca1fd      0t0  TCP localhost:9253 (LISTEN)
puma-dev   1086 gotchane    7u  IPv4 0x7ab086f0cff974d5      0t0  UDP localhost:9253
puma-dev   1086 gotchane    8u  IPv4 0x7ab086f0e36991fd      0t0  TCP *:http (LISTEN)
puma-dev   1086 gotchane   11u  IPv4 0x7ab086f0e36925dd      0t0  TCP *:https (LISTEN)
```

実際のコードも読んでみると、抜粋した箇所でリクエストをLISTENする処理が書かれているようです。
puma-devはhttp/httpsのポートもLISTENしているので、リクエストはそのままpuma-devのhttp/httpsサーバに移る感じになると思います。

https://github.com/puma/puma-dev/blob/master/dev/dns.go#L61-L90

### 3 ~ 5の挙動

ここではhttp/httpsサーバからRailsアプリケーションがどう起動されるかに絞って調べてみました。
（※puma-devで複数アプリケーション登録した時の切り替え処理などもありましたが、その辺はちょっと複雑だったので割愛します。近々追記予定）

Railsアプリケーションが起動する処理は、下記抜粋箇所に実装されています。

https://github.com/puma/puma-dev/blob/master/dev/app.go#L231-L346

読んでいただくとわかるのですが、こちらのコード内にpumaをUNIXドメインソケット通信で起動するコマンドが定義されています。

下記にコマンドの部分だけ抜粋します。```-b unix:%s```と書いてあるので、UNIXドメインソケット通信のファイルパスにbindして起動していることがわかります。

```
const executionShell = `exec bash -c '
cd %s
if test -e ~/.powconfig; then
	source ~/.powconfig
fi
if test -e .env; then
	source .env
fi
if test -e .powrc; then
	source .powrc
fi
if test -e .powenv; then
	source .powenv
fi
if test -e Gemfile && bundle exec puma -V &>/dev/null; then
	exec bundle exec puma -C $CONFIG --tag puma-dev:%s -w $WORKERS -t 0:$THREADS -b unix:%s
fi
exec puma -C $CONFIG --tag puma-dev:%s -w $WORKERS -t 0:$THREADS -b unix:%s'
`
```

こちらを踏まえると、特に```rails s```や```bundle exec puma```などのコマンドを別途実行する必要はなく、puma-devで実行してくれるということがわかります。

pumaプロセスを確認してみると、UNIXドメインソケット通信のファイルを作ってpumaが起動していることがわかると思います。

```
$ ps x | grep puma  
 1086   ??  S      1:15.38 /usr/local/Cellar/puma-dev/0.11/bin/puma-dev -launchd -dir ~/.puma-dev -d test -timeout 15m0s
80819   ??  S      5:28.50 puma 3.6.2 (unix:/Users/gotchane/.puma-dev/sample_app/tmp/puma-dev-1086.sock) [puma-dev:sample_app]
```

ここでようやくRailsアプリへのリクエストまで疎通できるところまで辿ってこれました。

## 終わりに

だいぶ大まかに調べてきましたが、コードを読みつつ挙動を調べることでだいぶ理解が進んだと思いました。golangの勉強にもなってよかったです。