本記事はmod_mruby ngx_mruby advent calendar 2014 21日目の記事です。
機能20日目、私 @matsumotory による「mrubyのサーバアプリケーション組込みにおいて複数スクリプトでインタプリタを共有することのデメリットとその対策」でした。
9日のアドベントカレンダーでmod_mrubyインストール後入門という記事で、mod_mrubyでのRuby実装が裏ではApacheのモジュール実装とリンクしているという話を紹介しました。
で、同様にngx_mrubyでもインストール後入門の記事を書こうかと思ったのですが、ほとんどmod_mrubyの場合と同じ内容になってしまう可能性があったので、むしろ、mod_mrubyと決定的に違う所はどういう機能かを紹介しようと考えました。
そこで、今回はngx_mrubyの特徴的な機能である、nginx変数の扱いについて説明します。
nginxのインストールに関しては、アドベントカレンダー11日目の @takeswim さんのnginxにngx_mrubyをインストールするを読むのが良いでしょう。
そのまえに一応ngx_mrubyのインストール後入門概略
mod_mrubyとほとんど同じというのは、どちらかというとApacheモジュール開発の考え方が、nginxモジュール開発と非常に似ているからです。
ここで細かなモジュール開発の説明は避けますが、Apacheモジュールもnginxモジュールも、プロセス起動からリクエストを受けてレスポンスを返すまでに様々なフックフェイズがあって、そこにCの関数ポインタを登録しておくことで、リクエスト処理時にフックされる、というモジュールアーキテクチャをとっています。
詳しくは、mod_mrubyインストール後入門という記事を見て下さい。
ですので、ngx_mrubyもmod_mrubyと同様、各種フックフェイズをwrapしたnginxのディレクティブを用意して、そこにRubyコードを渡したり、Rubyファイルを渡したり、キャッシュオプションを指定したり、という仕組みもほぼ同様です。
設定の仕方も以下のようにほぼ同じになるように設計しています。
mod_mrubyの設定
# 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の設定
# 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というディレクティブがあって、それを使うと、
set $backend "http://192.168.0.100:8888/";
のように、$backend
変数にhttp://192.168.0.100:8888/
がセットされます。
さらに、それをngx_mruby上からより複雑な事ができるように、mruby_set
ディレクティブを用意しています。
mruby_set $backend /path/to/proxy.rb;
第一引数の変数に対して、第二引数のRubyスクリプトを実行した後にreturn
されるオブジェクトを文字列として代入することができます。
例えば、以下のように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
変数に入れられます。
この機能を利用して、
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を書いていたとします。
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、つまりレスポンスを生成する時のスクリプトが以下のようなコードだったとします。
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"
を返すことになります。
また、
r = Nginx::Request.new
r.var.hoge = "ngx_mruby"
# or
# r.var.set "hoge", "mod_mruby"
とすることにより、値を代入することもできます。もちろんこの値は、nginx.conf上でも反映されます。
nginxのコア変数を参照
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でアクセスするとします。
すると、
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 で可視化してみるが非常に面白いので、一度見てみると良いと思います。
また、connections_active
やconnections_reading
はnginx_stub_statusモジュールによる変数です。つまり、ngx_mruby以外にnginxのモジュールを組み込む事で、そのモジュール内で有効なnginx変数にもアクセスすることができます。例えば、nginxのGeoIPモジュールは地理情報をnginxの変数に登録しますが、それらもngx_mrubyによって、Nginx::Reuqest.new.var
あるいはNginx::Var.new
でオブジェクトとして取り出す事ができるでしょう。
その他の情報については、Wikiを参照して下さい。
まとめ
ということで、今日はmod_mrubyとngx_mrubyで決定的に違う、ngx_mrubyによるnginx変数の扱いについて解説しました。
この辺りの理解が深まると、ngx_mrubyを用いてより複雑な処理を簡潔にRubyで記述することができ、nginxの振る舞いをより自由に決定することができるでしょう。
明日22日は @takeswim さんによる「ngx_mrubyを使って簡易ファイル共有システムを作る」です!