2014-10-06

Nginx + luaで目指すリッチで高速なWebサーバー

こんにちは、SHUNです。 今回はnginxの拡張モジュールを使い、Webサーバーだけである程度の処理ができるように便利なtipsを集めました。 計測データの受け取りやリダイレクトなどアプリケーションサーバーを介さなくても済む処理はWebサーバー側で処理することで処理速度が圧倒的に早くなるので、なるべくWebサーバーでやりましょう、というコンセプトです。 基本的にnginxのインストールや設定はできていることが前提ですので、初めての方は以下も参照してみてください。

lua-nginx-moduleのインストール

nginxはモジュールをインストールするたびに毎回ビルドしなければいけません。 lua-nginx-moduleのインストールは以下のエントリーを参考にさせていただきました。

基本的には入れたいモジュールをダウンロードしてきて、nginxのconfigureをする際に–add-moduleでそのフォルダを指定していくだけ。後はmakeしてできたnginxをもとのところにあてがうことで反映できます。

以下がlua moduleの公式レファレンスなので、適宜ご参照ください。

luaで計算した値を変数にsetする

set_by_luaでluaのreturn結果をnginxの変数内に入れることができます。

location /test {
      set_by_lua $res '
        local a = 3
        local b = 2
        return a + b
      '
      return 200 $res;
}

/testにアクセスすると5という文字が表示されているはずです。

luaファイルを別ファイルに分ける

nginx.confに全て処理を書くと冗長になってしまう場合はファイルを分けると良いでしょう。この場合はset_by_lua_fileという関数を使います。

  • /etc/nginx/lua/calc.lua
local a = 3
local b = 2
return a + b
  • nginx.conf
location /test {
      set_by_lua_file $res '/etc/nginx/lua/calc.lua';
      return 200 $res;
}

例外処理

luaは非常に軽量な言語なので、try ~ catchなどの例外処理がデフォルトではありません。そのため、以下のようなtry ~ catchの構文を作ってしまいましょう。

  • try-catch.lua
function catch(what)
  return what[1]
end

function try(what)
  status, result = pcall(what[1])
  if not status then
    what[2](result)
  end
  return result
end

使うときは以下。

require "try-catch"
try {
  function()
    error('oops')
  end,

  catch {
    function(error)
      print('caught error: ' .. error)
    end
  }
}

参考

エンコードされた文字列のデコード

urlパラメータにエンコードされた文字列が入っているとき、以下のようにします。

location /test {
set_by_lua $dec_url '
  local hex_to_char = function(x)
      return string.char(tonumber(x, 16))
  end

  local unescape = function(url)
      return url:gsub("%%(%x%x)", hex_to_char)
  end

  return unescape(ngx.var.url);
  ';
return 200 $dec_url;
}

unescapeというローカルメソッドでデコードしています。

正規表現を用いた文字列の一致

ngx.re.matchという関数を使って文字列の一致を調べることができます。

location /a {
   content_by_lua '
      local regex = "\\\\d+"
      local m = ngx.re.match("hello, 1234", regex)
      if m then ngx.say(m[0]) else ngx.say("not matched!") end
  ';
}

日時の取得

os.dateで日時の取得ができます。フォーマットも指定することが可能です。

location /test {
      set_by_lua $created_at 'return os.date("!%Y-%m-%d %H:%M:%S %z")';
      return 200 $created_at;
}

シェルスクリプト実行

os.executeでシェルスクリプトを実行できます。

location /test {
  content_by_lua '
    os.execute("/home/example/sample.rb")
    ngx.say("hello world")
  ';
}

4kb制限

なんとcontent_by_lua内に4kb以上の文字列を並べるとエラーになる仕様があります。 そのため、content_by_luaにすべての処理を書くのではなく、別ファイルにしてset_by_lua_fileなどを用いて分けることで回避することができます。

参考 - nginx lua使用注意事项

luaじゃないけど便利なnginxコマンド集

else構文

luaを使えば問題は無いのですが、luaを使わない場合、nginxのみだとelseが使えません。そのときは以下のようにifだけでelseを実現させます。

location test{
  set $check 0;
  if ($arg_a ~* 'hello'){
     set $check 1;
     return 200;
  }
  if ($check = 0){
     rewrite ^ /test2; 
  }
}

この場合、$arg_a、つまりaというGETパラメータにhelloという文字列が入っている場合は200を返し、入っていない場合はtest2に飛ぶ、といった処理になります。

クッキー関係

  • クッキーの取り出し
if ($http_cookie ~* '(user_id)') {
   set $user_id $cookie_user_id;
}
  • クッキーの挿入
set $content "user_id=1; domain=.example.com;path=/; Max-Age=31536000;";
add_header Set-Cookie $user_id;

空のgifを返す

location test{
  empty_gif;
}

empty_gif;と書くだけで、空のgifファイルを返すようになります。imgタグを使ってピクセルタグを埋め込んでサーバーに通知するときなど便利! 注意点として、if構文の中でempty_gif;を呼び出したりするとエラーになってしまうので、その際はif内でrewriteをして別のlocationに移動してempty_gif;を呼び出すという手があります。

以上になります!

  1. uokada reblogged this from shun0750
  2. shun0750 posted this