人間とウェブの未来

「ウェブの歴史は人類の歴史の繰り返し」という観点から色々勉強しています。

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

本記事は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_setmruby_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.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のコア変数を参照

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