ISUCON4予選問題をngx_mrubyだけで解いてみた（ノウハウ編）

======

この記事は[mod_mruby ngx_mruby Advent Calendar 2014](http://qiita.com/advent-calendar/2014/mod-ngx-mruby)の5日目の記事です（というか、[2日目の記事](http://marcy.hatenablog.com/entry/2014/12/02/013243)の続きです）  

昨日は[hfm](http://qiita.com/hfm)さんの[ngx_mruby(memcached付)をCentOS/SLでビルドする時のハマりどころと直し方について](http://blog.hifumi.info/2014/12/04/ngx_mruby-memcached-in-rhel/)でした。

今回は、[ISUCON4予選問題](http://isucon.net/archives/40724693.html)をngx_mrubyだけで解いてみて分かった、細かいTipsやハマりどころ等を書きたいと思います。
Tips系ということで、2日目はブログに書きましたが、今回はQiitaにしてみました。

# Tips

## 色々なリクエスト情報の取り方

### GET
Nginxの組み込み変数である```$args```から取れます。

```rb:example1.rb
r = Nginx::Request.new
Nginx.echo r.args #=> "id=hoge&name=fuga"
```

ちなみに```args```だと先頭の```?```が付かなくて、```query_string```だと先頭の```?```が付きます。
なので、```args```の方が若干扱いやすいかと思います。

##### 2014/12/07追記：
ngx_mruby作者の[matsumotory](http://qiita.com/matsumotory)さんから補足を頂きました。
以下のように```arg_[key名]```でkeyを指定して取れるそうです。
断然コチラのほうが扱いやすいですね！

```ruby
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**](https://github.com/calio/form-input-nginx-module)なるNginxのサードパティモジュールを入れて、それを使おうとすると、```$request_body```が扱えるようになります。

```conf:nginx.conf
set_form_input $login;
set_form_input $password;
```

本来はこのような記述をすることで、```$request_body```からそれぞれ変数名に対応したkeyに対するvalueを変数に格納してくれるような挙動をするのですが、それと同時に```$request_body```を弄るモジュールの性質上、他からも```$request_body```が参照可能となります。

```rb:example2.rb
r = Nginx::Request.new
Nginx.echo r.var.request_body #=> "login=hoge&password=fuga"
```

とまあ、かなりのダーティハックです。
この辺は他に良い方法をご存知な方がいらっしゃれば是非、補足いただけると嬉しいです。

### Cookie

これについては、方法は2種類（？）あります。

- Nginxの組み込み変数```$http_cookie```から取る方法

```rb:example3.rb
r = Nginx::Request.new
Nginx.echo r.var.http_cookie #=> "login=hoge&password=fuga"
```

-  リクエストヘッダから取る方法

```rb:example4.rb
r = Nginx::Request.new
Nginx.echo r.headers_in['Cookie'] #=> "login=hoge; notice=fuga"
```

## Cookieを操作する

### Cookieのセット

レスポンスヘッダの```Set-Cookie```にセットします。

```rb:example4.rb
r = Nginx::Request.new
r.headers_out['Set-Cookie'] = "login=hoge; path=/"
```

### Cookieの削除

Cookieを削除させることはできませんので、Cookieの有効期限を過去にすることで無効にさせます。

```rb:example4.rb
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ならこう書く所を

```rb:example5.rb
p Time.now.strftime("%Y-%m-%d %H:%M:%S") #=> "2014-12-05 00:11:22" 
```

こんな風に書いてみたり…ｗ

```rb:example6.rb
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日目](http://marcy.hatenablog.com/entry/2014/12/02/013243)に書いたのですが、原因はまだ切り分けてませんが、```Nginx.redirect```が不正な形式のレスポンスを返しちゃうことですね。

とりあえずは、302リダイレクト時のレスポンスを模倣したレスポンスを返す形で以下のようにしました。

```rb:example7.rb
r = Nginx::Request.new
r.headers_out["Location"] = "http://#{r.var.http_host}/mypage"
Nginx.return Nginx::HTTP_MOVED_TEMPORARILY
```
  
こちらからは以上です。
明日は[かっば(inokappa)](http://qiita.com/inokappa)さんの[mod_mruby と mruby-aws-s3 を使って S3 上の HTML をコンテンツを表示させてみよう](http://qiita.com/advent-calendar/2014/mod-ngx-mruby)なります！