ngx_mrubyインストール後入門 - ngx_mrubyによるnginx変数の扱い


本記事は[mod_mruby ngx_mruby advent calendar 2014](http://qiita.com/advent-calendar/2014/mod-ngx-mruby) 21日目の記事です。

機能20日目、私 @matsumotory による「[mrubyのサーバアプリケーション組込みにおいて複数スクリプトでインタプリタを共有することのデメリットとその対策](http://qiita.com/matsumotory/items/9b1215236c7145c07271)」でした。

9日のアドベントカレンダーで[mod_mrubyインストール後入門という記事](http://qiita.com/matsumotory/items/7121d6cce728125e5ce2)で、mod_mrubyでのRuby実装が裏ではApacheのモジュール実装とリンクしているという話を紹介しました。

で、同様にngx_mrubyでもインストール後入門の記事を書こうかと思ったのですが、ほとんどmod_mrubyの場合と同じ内容になってしまう可能性があったので、むしろ、mod_mrubyと決定的に違う所はどういう機能かを紹介しようと考えました。

そこで、今回はngx_mrubyの特徴的な機能である、nginx変数の扱いについて説明します。

nginxのインストールに関しては、アドベントカレンダー11日目の @takeswim さんの[nginxにngx_mrubyをインストールする](http://qiita.com/takeswim/items/d80dcf9865d06571cc5e)を読むのが良いでしょう。

## そのまえに一応ngx_mrubyのインストール後入門概略

mod_mrubyとほとんど同じというのは、どちらかというとApacheモジュール開発の考え方が、nginxモジュール開発と非常に似ているからです。

ここで細かなモジュール開発の説明は避けますが、Apacheモジュールもnginxモジュールも、プロセス起動からリクエストを受けてレスポンスを返すまでに様々なフックフェイズがあって、そこにCの関数ポインタを登録しておくことで、リクエスト処理時にフックされる、というモジュールアーキテクチャをとっています。

詳しくは、[mod_mrubyインストール後入門という記事](http://qiita.com/matsumotory/items/7121d6cce728125e5ce2)を見て下さい。

ですので、ngx_mrubyもmod_mrubyと同様、各種フックフェイズをwrapしたnginxのディレクティブを用意して、そこにRubyコードを渡したり、Rubyファイルを渡したり、キャッシュオプションを指定したり、という仕組みもほぼ同様です。

設定の仕方も以下のようにほぼ同じになるように設計しています。

### mod_mrubyの設定

```apache
# Normal hook
<Location /mruby-test>
    mrubyHandlerMiddle /path/to/test.rb
</Location>

# ByteCode Caching at Start up
<Location /mruby-test-cache>
    mrubyHandlerMiddle /path/to/test.rb cache
</Location>
```

### ngx_mrubyの設定
```nginx
# Normal hook
location /mruby-test {
    mruby_content_handler /path/to/test.rb;
}

# ByteCode Caching at Start up
location /mruby-test-cache {
    mruby_content_handler /path/to/test.rb cache;
}
```

mod_mrubyを使うことでApacheモジュールをRubyで書けるのと同様、ngx_mrubyを使うことでnginxモジュールをRubyで書ける、というイメージを掴んで頂けたでしょうか。

## ngx_mrubyによるnginx変数の扱い

ここからが本題です。nginxは設定内に、ある種のプログラミング言語のように変数を扱う事ができます。ここでnginx変数の細かい説明は省略します。ググると沢山でてくると思います。

このnginxの変数は、設定を書く上では非常に重要な役割を担っており、変数をうまく使う事によって動的な設定を定義することができます。ですが、nginxのプログラミング言語っぽい設定は、いくつかハマりどころがあったり、本来のPerlやRubyといったプログラミング言語と比べると書きにくいと感じています。

そうであれば、もちろんngx_mrubyでもnginx変数の値を操作できるようにして、より書きやすいRubyスクリプト内で変数を動的に扱えれば、nginxの設定言語で変数を制御するよりも書きやすくなるのではないかと考えました。

というわけで、ngx_mrubyによるnginx変数の扱いを幾つか紹介します。

### 自分で変数をnginx.conf上に定義する場合

nginxでは元から定義されている各種変数の他に、自分で変数を定義することができます。

例えば、nginxのsetというディレクティブがあって、それを使うと、

```nginx
set $backend "http://192.168.0.100:8888/";
```

のように、``$backend``変数に``http://192.168.0.100:8888/``がセットされます。

さらに、それをngx_mruby上からより複雑な事ができるように、``mruby_set``ディレクティブを用意しています。

```nginx
mruby_set $backend /path/to/proxy.rb;
```

第一引数の変数に対して、第二引数のRubyスクリプトを実行した後に``return``されるオブジェクトを文字列として代入することができます。

例えば、以下のようにproxy.rbを書くと、

```ruby:proxy.rb
backends = [
    "http://192.168.0.101:8888/",
    "http://192.168.0.102:8888/",
    "http://192.168.0.103:8888/",
]

backends[rand(backends.length)]
```

``backends``配列からランダムで一つ、``backend``のURLがオブジェクトとしてreturnされます。それが、``mruby_set``の第一引数である``$backend``変数に入れられます。

この機能を利用して、

```nginx
location /proxy {
  mruby_set $backend /path/to/proxy.rb;
  proxy_pass http://$backend;
}
```

とかくと、リバースプロキシ先のサーバURLの選択アルゴリズムをRubyスクリプト内で動的に記述することができます。

簡単かつ強力ですね。

これによって、例えばnginx.confのproxy_passはホスト名で書くと、それに紐づくIPを静的にロードしてしまい、nginxを再起動しないと新しく名前解決はしませんが、そういう処理もmrubyのリゾルバのようなmgemを作れば実現できそうですね。

### Rubyスクリプト内で変数を操作する場合

ngx_mrubyでは、Rubyスクリプト内でnginxの変数を呼び出し、値を操作することができます。

例えば、以下のようなnginx.confを書いていたとします。

```nginx
location /vars {
  set $hoge ”100";

  mruby_set_code $foo ‘
    r = Nginx::Request.new
    r.var.hoge.to_i * 2     #=> 200
  ’;

  mruby_content_handler /path/to/var.rb;
}
```

この場合、まずsetディレクティブによって``$hoge``に100がセットされます。

続いて、先ほど説明したngx_mrubyの``mruby_set``ディレクティブのインラインコード版である``mruby_set_code``ディレクティブを使って、Rubyスクリプト内で``Nginx::Reuqest.new.var.hoge``により``$hoge``変数を取り出し、その値を倍にして``$foo``変数にセットします。値を操作するメソッドはまとめて後述しますので、ここでは雰囲気だけ見ておいて下さい。

これで、nginx上の``$foo``変数には200が入る事になりますね。

このように、

- nginx変数初期化時にRubyを使って値を生成する
- nginx変数をRubyスクリプト内部で直接操作する

を組み合わせると、nginxの変数をかなり自由度高く操作できます。``mruby_set``や``mruby_set_code``はまさにその組み合わせのディレクティブととらえることができますね。

さらに、``mruby_content_handler``で呼び出すvar.rb、つまりレスポンスを生成する時のスクリプトが以下のようなコードだったとします。

```ruby:var.rb
r = Nginx::Request.new

Nginx.echo "hoge: #{r.var.hoge}"     #=> "100"
Nginx.echo "foo : #{r.var.foo}"      #=> "200"

```

Rubyスクリプト内でのnginx変数の制御は、``Nginx::Reuqest.new.var``あるいは``Nginx::Var.new``でオブジェクトとして呼び出す事ができます。

例えば、上記のsetディレクティブやmruby_set_codeディレクティブで設定した$hogeや$foo等のnginxの変数は、``Nginx::Reuqest.new.var.hoge``や``Nginx::Reuqest.new.var.foo``、あるいは、``Nginx::Var.new.hoge``や``Nginx::Var.new.foo``で参照することができます。

上記のvar.rbでは、レスポンスとして``hoge: "100"``かつ``foo : "200"``を返すことになります。

また、

```ruby
r = Nginx::Request.new

r.var.hoge = "ngx_mruby"

# or
# r.var.set "hoge", "mod_mruby"
```

とすることにより、値を代入することもできます。もちろんこの値は、nginx.conf上でも反映されます。

### nginxのコア変数を参照

```ruby:var.rb
r = Nginx::Request.new


Nginx.echo "name: #{r.var.arg_name}"
Nginx.echo "uri : #{r.var.uri}"
```

var.rbが上記のようなコードであった場合に、上述のnginx.confの``Location /var``に対して、

```
http://example.com/vars?name=matsumotory
```

というURLでアクセスするとします。

すると、

```ruby
Nginx.echo "name: #{r.var.arg_name}" #=> "matsumotory"
Nginx.echo "uri : #{r.var.uri}"      #=> "/vars"
```

で、``arg_{キー名}``や``uriを``変数名として指定すると、nginxに予め予約されているnginxのコア変数を参照することができます。

この変数は、他に以下のような変数があります。

variables| | | |
---------|---------------|------------|--------
http_host|http_user_agent|http_referer|http_via
http_x_forwarded_for|http_cookie|content_length|content_type
host|binary_remote_addr|remote_addr|remote_port
server_addr|server_port|server_protocol|scheme
https|request_uri|uri|document_uri
request|document_root|realpath_root|query_string
args|is_args|request_filename|server_name
request_method|remote_user|bytes_sent|body_bytes_sent
pipe|request_completion|request_body|request_body_file
request_length|request_time|status|sent_http_content_type
sent_http_content_length|sent_http_location|sent_http_last_modified|sent_http_connection
sent_http_keep_alive|sent_http_transfer_encoding|sent_http_cache_control|limit_rate
connection|connection_requests|nginx_version|hostname
pid|msec|time_iso8601|time_local
tcpinfo_rtt|tcpinfo_rttvar|tcpinfo_snd_cwnd|tcpinfo_rcv_space
connections_active|connections_reading|connections_writing|connections_waiting

nginxのコア変数をRubyスクリプト上で取得することにより、nginxの様々なパラメータ値を利用した処理をRubyスクリプトに書けます。

この機能を上手く利用した例として、19日担当の @inokappa さんの[ngx_mruby で Nginx への接続数等の内部情報を取得して InfluxDB と Tasseo で可視化してみる](http://qiita.com/inokappa/items/e50d312c50efa51b5dd9)が非常に面白いので、一度見てみると良いと思います。

また、``connections_active``や``connections_reading``はnginx_stub_statusモジュールによる変数です。つまり、ngx_mruby以外にnginxのモジュールを組み込む事で、そのモジュール内で有効なnginx変数にもアクセスすることができます。例えば、nginxのGeoIPモジュールは地理情報をnginxの変数に登録しますが、それらもngx_mrubyによって、``Nginx::Reuqest.new.var``あるいは``Nginx::Var.new``でオブジェクトとして取り出す事ができるでしょう。

その他の情報については、[Wikiを参照して下さい。](https://github.com/matsumoto-r/ngx_mruby/wiki/Class-and-Method#nginxvar-class)

## まとめ

ということで、今日はmod_mrubyとngx_mrubyで決定的に違う、ngx_mrubyによるnginx変数の扱いについて解説しました。

この辺りの理解が深まると、ngx_mrubyを用いてより複雑な処理を簡潔にRubyで記述することができ、nginxの振る舞いをより自由に決定することができるでしょう。

明日22日は @takeswim さんによる「[ngx_mrubyを使って簡易ファイル共有システムを作る](http://qiita.com/advent-calendar/2014/mod-ngx-mruby)」です！