mruby
ngx_mruby
mod_mruby

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

More than 3 years have passed since last update.

本記事は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を書くと、


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_setmruby_set_codeはまさにその組み合わせのディレクティブととらえることができますね。

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


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.hogeNginx::Reuqest.new.var.foo、あるいは、Nginx::Var.new.hogeNginx::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のコア変数を参照


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でアクセスするとします。

すると、

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_activeconnections_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を使って簡易ファイル共有システムを作る」です!