======
この記事はmod_mruby ngx_mruby Advent Calendar 2014の5日目の記事です(というか、2日目の記事の続きです)
昨日はhfmさんのngx_mruby(memcached付)をCentOS/SLでビルドする時のハマりどころと直し方についてでした。
今回は、ISUCON4予選問題をngx_mrubyだけで解いてみて分かった、細かいTipsやハマりどころ等を書きたいと思います。
Tips系ということで、2日目はブログに書きましたが、今回はQiitaにしてみました。
Tips
色々なリクエスト情報の取り方
GET
Nginxの組み込み変数である$args
から取れます。
r = Nginx::Request.new
Nginx.echo r.args #=> "id=hoge&name=fuga"
ちなみにargs
だと先頭の?
が付かなくて、query_string
だと先頭の?
が付きます。
なので、args
の方が若干扱いやすいかと思います。
2014/12/07追記:
ngx_mruby作者のmatsumotoryさんから補足を頂きました。
以下のようにarg_[key名]
でkeyを指定して取れるそうです。
断然コチラのほうが扱いやすいですね!
v = Nginx::Var.new
v.arg_name #=> "matsumotory"
v.arg_id #=> 0
POST
これも同じように$request_body
から取れる…と思いきや取れません。
ちょっと、どこに書いてあったか忘れたのですが、$request_body
が有効になるのはかなり後のフェーズらしく、Nginx自体がそのフェーズにフックできるようになっていないとかで標準では無理なんだそうな…
ログ出力は当然最後なので、ログに書き出すことは標準でもできますが、$request_body
を見てrewriteするとかはngx_mruby関係なくできません。
じゃあ、無理なの?っていうと、まあ一応やり方はありました。
だいぶダーティハックっぽいのですが、form-input-nginx-moduleなるNginxのサードパティモジュールを入れて、それを使おうとすると、$request_body
が扱えるようになります。
set_form_input $login;
set_form_input $password;
本来はこのような記述をすることで、$request_body
からそれぞれ変数名に対応したkeyに対するvalueを変数に格納してくれるような挙動をするのですが、それと同時に$request_body
を弄るモジュールの性質上、他からも$request_body
が参照可能となります。
r = Nginx::Request.new
Nginx.echo r.var.request_body #=> "login=hoge&password=fuga"
とまあ、かなりのダーティハックです。
この辺は他に良い方法をご存知な方がいらっしゃれば是非、補足いただけると嬉しいです。
Cookie
これについては、方法は2種類(?)あります。
- Nginxの組み込み変数
$http_cookie
から取る方法
r = Nginx::Request.new
Nginx.echo r.var.http_cookie #=> "login=hoge&password=fuga"
- リクエストヘッダから取る方法
r = Nginx::Request.new
Nginx.echo r.headers_in['Cookie'] #=> "login=hoge; notice=fuga"
Cookieを操作する
Cookieのセット
レスポンスヘッダのSet-Cookie
にセットします。
r = Nginx::Request.new
r.headers_out['Set-Cookie'] = "login=hoge; path=/"
Cookieの削除
Cookieを削除させることはできませんので、Cookieの有効期限を過去にすることで無効にさせます。
year = Time.now.year - 1
r = Nginx::Request.new
r.headers_out['Set-Cookie'] = "notice=+; expires=Mon, 25-Nov-#{year} 11:11:11 GMT; path=/"
遭遇したハマりどころ
デフォルトではPOSTが取れない
対処法(ダーティハック)は上記を参照ください。
これはngx_mrubyというよりもNginxの制約ではありますが…
Rubyの組み込みクラスと同じ名前であっても、メソッドも同じとは限らない
遭遇したのはTime
クラスでした。strftime
メソッドが無かったです。
Rubyならこう書く所を
p Time.now.strftime("%Y-%m-%d %H:%M:%S") #=> "2014-12-05 00:11:22"
こんな風に書いてみたり…w
year = Time.now.year
month = Time.now.month
day = Time.now.day
hour = Time.now.hour
min = Time.now.min
sec = Time.now.sec
month = month < 10 ? "0#{month.to_s}" : month.to_s
day = day < 10 ? "0#{day.to_s}" : day.to_s
hour = hour < 10 ? "0#{hour.to_s}" : hour.to_s
min = min < 10 ? "0#{min.to_s}" : min.to_s
sec = sec < 10 ? "0#{sec.to_s}" : sec.to_s
Nginx.echo "#{year}-#{month}-#{day} #{hour}:#{min}:#{sec}" #=> "2014-12-05 00:11:22"
まあ、これもngx_mrubyというよりはmrubyのハマりどころですね…
あとは、2日目に書いたのですが、原因はまだ切り分けてませんが、Nginx.redirect
が不正な形式のレスポンスを返しちゃうことですね。
とりあえずは、302リダイレクト時のレスポンスを模倣したレスポンスを返す形で以下のようにしました。
r = Nginx::Request.new
r.headers_out["Location"] = "http://#{r.var.http_host}/mypage"
Nginx.return Nginx::HTTP_MOVED_TEMPORARILY
こちらからは以上です。
明日はかっば(inokappa)さんのmod_mruby と mruby-aws-s3 を使って S3 上の HTML をコンテンツを表示させてみようなります!