ぶていのログでぶログ

思い出したが吉日

Nginxの真偽値とmruby_setの戻り値について

ngx_mrubyを使ってNginxを制御するときに、 mruby_setif を使うことが多いと思います。

例えばiPhoneからのアクセスの場合のみ特定の処理をしたいとします。 その場合以下の様なコード(nginx.conf)になると思います。

mruby_set_code $is_iphone '
  hin = Nginx::Headers_in.new

  # 判定分が雑だけど例題なのでご容赦・・・
  /iPhone/ === hin['User-Agent']
';

if($is_iphone) {
  # iPhoneの時だけ行なう処理
}

User-AgentiPhoneという文字列が含まれていれば true を返し、そうでなければ falseを返却し、その結果を $is_iphone 変数に格納します。 そして、この変数が真の時だけ特定の処理を行います。

これは意図した動作を行なうのでしょうか?

一見問題なさそうに見えますが、 これは意図した動作をしません。 なぜなら、 iPhone以外のアクセスの時($is_iphoneがfalseの時)もif分の中の処理を実行してしまいます

これはなぜかというと、 ngx_mrubyが*1終値をStringに変換してNginxの変数に格納しているのです。 つまり $is_iphoneにはtrueまたはfalseという文字列が格納されています

Nginxにおける if は、空文字または0の場合は偽として処理されますが、それ以外は真として処理されます。

a variable name; false if the value of a variable is an empty string or “0”; http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#if

よって、意図した動作をさせるためにはif分を以下のように書き換えます。

if($is_iphone = ture) {
  # iPhoneの時だけ行なう処理
}

これで期待する動作を行うようになりました!

2016/08/01追記

id:matsumoto_r さんによって、Nginx::TRUENginx::FALSE という定数が追加されました!

この定数を使って、以下のように書きなおすことができ、if ディレクティブで特別意識する必要がなくなりました!

mruby_set_code $is_iphone '
  hin = Nginx::Headers_in.new
  /iPhone/ === hin['User-Agent'] ? Nginx::TRUE : Nginx::FALSE
';

if($is_iphone) {
  # iPhoneの時だけ行なう処理
}

*1:ngx_mrubyがやっているのだろうと思ってソースを読んだのですが確信はないです。。間違っているかも。。