lua-nginx-module を使いこなす


----
（2016.3追記）
２年ほど前に書いたエントリで、当時と比べて nginx のバージョンもあがっていますが、いまでも時々ストックしていただいているようです。Nginx Lua API にもいろいろ加わったものがあるようですが、大きく書き換える必要はなさそう。ただし、一部、リンク先のドキュメントがなくなってしまっているところがあったり、記載していたAPIの一部が非推奨になっているようなので、少しアップデートしました。

----
lua-nginx-module は、nginx を使っているヒトには、いろんなところで、ものすごく便利に使える仕組みなんだけど、日本語の情報は 意外と少ないですね。

* http://wiki.nginx.org/HttpLuaModule
* https://github.com/openresty/lua-nginx-module

インストールして使ってみる といったエントリは見かけるのですが、本格的に使おうと思ったときに必要となる情報があまり見当たりません。
ちょっと調べた範囲では、下記あたりが貴重な日本語の情報源でした。

* [[nginx] lua-nginx-module の紹介 ならびに Nginx+Lua+Redisによる動的なリバースプロキシの実装案](http://d.hatena.ne.jp/hiboma/20120205/1328448746)
* [nginxとLuaの話](http://www.slideshare.net/moriyoshi/nginx-lua)
* [Using ngx_lua / lua-nginx-module in pixiv](http://www.slideshare.net/harukayon/ngx-lua-public)

今回は、ガッツリ使ってみようというヒト向けに、少し詳しく書いてみようと思います。
＃その分、インストールとかは省略…



## nginx の HTTPリクエスト処理

lua-nginx-module は、nginx のリクエスト処理の各フェーズで呼び出されるという構成になっているため、まずは nginx のHTTPリクエストの処理について簡単に見てみましょう。

nginx では、Listen しているポートへのアクセスを受けると、下記のようなフェーズでリクエストを処理します。

|Phase|概要|
|:---|:----|
|server selection|nginx.conf の中の server を選択|
|post read|リクエストヘッダからクライアントのIPアドレスを取得|
|server rewrite|サーバレベルの URI の rewrite処理|
|location selection|location を選択|
|location rewrite|location レベルの URI の rewrite処理|
|preaccess|access処理で必要な情報の取得|
|access|IPアドレス制限や Basic認証等、アクセスの認可チェック|
|try files|try_files の処理 ([参考](http://heartbeats.jp/hbblog/2012/04/nginx05.html))|
|content|メインのコンテンツの処理。生成したり proxy として働いたり。|
|log|ログを残す|
|post action|post_action（リクエスト完了後）の処理|



参考

* http://wiki.nginx.org/Phases

（2016.3追記）上記のURLは ページがなくなっていました。残念ながらオフィシャルページで似た記載が見当たりません。下記あたりを参照してください。

* http://www.aosabook.org/en/nginx.html#sec.nginx.internals
* http://www.nginxguts.com/2011/01/phases/
* http://www.programering.com/a/MzN4MzMwATY.html 
* http://www.slideshare.net/joshzhu/nginx-internals/37



## lua-nginx-module がフックする処理

lua-nginx-module は、上記の nginx の Phase のうち、Rewrite, Access, Content, Log のフェーズに対してフックする仕組みを提供しています。各フェーズごとに、lua-nginx-module が提供する主なフックの仕組み（nginx のディレクティブ）を以下にリストしておきます。

(2016.3追記) 下記では xxx_by_lua という directive を紹介していますが、これは非推奨になり、xxx_by_lua_block を使う事が推奨されているようです。ほとんど同じですが、文字列ではなく、{} で括った block として定義します。以下、xxx_by_lua は xxx_by_lua_block として読み替えてください。


* Rewrite Phase
  * set_by_lua：変数の設定、ヘッダの操作、リダイレクト等が可能
  * rewrite_by_lua：Rewrite Phase の最後で実施され、自由度の高い rewrite処理を実現可能
* Access Phase
  * access_by_lua：自由度の高い認可処理を実現可能
* Content Phase
  * content_by_lua：コンテンツの生成
  * header_filter_by_lua：コンテンツ生成後、header に対するフィルタ処理（書き換えや追加など）に対応
  * body_filter_by_lua：コンテンツ生成後、body に対するフィルタ処理
* Log Phase
  * log_by_lua：ログ処理のタイミングで動作。ここでリクエストの情報を変数にためておくことで、nginx + lua だけで集計の仕組みを作ることも可能


各ディレクティブの名前に、_file をつけると、nginx.conf の中に直接 Lua のコードを書くかわりに、外部ファイルで定義することができます。少し大きなコードを書く場合は、わけて管理したほうが良いですよね。

ファイルをわけて書く場合、開発中は、lua_code_cache を OFF にしておくと、_file で読み込む際にキャッシュせず毎回読み込み、いちいち nginx を reload しなくても済むので便利です。

init_by_lua および init_by_lua_file は、上記のフェーズではなく、nginx のリロード時などの初期化の際に動作するので、変数などの初期化で利用できます。

ちなみに、Luaスクリプトからは、ngx.get_phase を利用して、現在のフェーズを取得することができます。

参考

* https://github.com/openresty/lua-nginx-module#directives
(2016.3 リンク修正)

(2016.3 追記）
上記で紹介した以外にも、使えそうな directives が増えていました。例えば、balancer_by_lua_block や ssl_certificate_by_lua_block など… 詳しくは上記URLを参照してください。


## lua-nginx-module で利用できる Nginx API

上記の各種フック（*_by_lua）から 呼び出された Lua のコードでは、ヘッダや リクエスト内容を見たり、nginx の worker プロセス間で設定や変数を共有して、なんらかの処理をします。そのような処理を柔軟に行うために、lua-nginx-module には様々な Nginx Lua API が用意されています。

\*_by_lua から呼び出された Lua のコードは、*_by_lua の context の中で動作しますが、各 context 毎に 利用出来る Nginx Lua API には制限があります。例えば、ngx.location.caputure という API は、rewrite_by_lua*, access_by_lua*, content_by_lua* の中でしか利用出来ません。

ここでは、主な Nginx Lua API を紹介します。

### [ngx.var.VARIABLE](http://wiki.nginx.org/HttpLuaModule#ngx.var.VARIABLE)

これを使うと、nginx の変数として、nginx.conf の中で set された（宣言された）変数にアクセスできます。

この変数はリクエストが終わるたびにメモリから解放されるので、リクエストをまたがって保持したいものは、ngx.shared.DICT に保存しましょう。


### [ngx.ctx](http://wiki.nginx.org/HttpLuaModule#ngx.ctx)

ngx.var.VARIABLE と同様に、リクエスト内で共有する変数のように利用することができますが、nginx の変数ではないため、nginx.conf の中で あらかじめ宣言しておく必要はありません。

ただし、リクエスト内で共有といっても、後述のサブリクエストの中では別のscopeとして処理されてしまいます。


### [ngx.shared.DICT](http://wiki.nginx.org/HttpLuaModule#ngx.shared.DICT)

リクエストや worker プロセスを越えて、nginx の中で共有できる変数で、Key Value Store的に利用できます。
get, set, add, replace, incr, delete, flush_all, flush_expired, get_keys 等のメソッドが利用できる優れもの。
expire などもつけることができるので、冗長構成でなければ、memcache の代わりに使うことも出来ますね。

ただし、あらかじめ lua_shared_dict で宣言しておくことが必要です。この際、サイズを宣言するので、最大サイズに想定をつけておく必要があります。

この変数は、nginx の configを reload しても保持されますが、restart したり SIGHUP を受けるとクリアされます。


### [ngx.location.capture](http://wiki.nginx.org/HttpLuaModule#ngx.location.capture)

これを使うと、サブリクエストを飛ばすことができます。ただし、任意のURLへのリクエストができるわけではなく、nginx 内のインターナルなリクエストしか出来ません。

といっても、例えば、下記のように nginxに設定しておくと、インターナルな /auth へのリクエストが proxy されて、外部へのリクエストとして処理されるため、自由度の高いインタフェースとして利用することができます。

```
       location /auth {
           proxy_pass http://example.com/auth/;
           proxy_redirect default;
       }

		location /foo {
			access_by_lua '
               res = ngx.location.capture("/auth", { share_all_vars = true });	
                ...
			';
		}
```

ngx.location.capture では、method, body, args を指定できるほか、subrequest 側の ngx.ctx や ngx.vars も設定可能なので、様々なデータを引き渡すこともできます。
返り値は、status, header, body の３つからなるテーブルになっているので、これを適宜処理することで、コンテンツを作ったり、アクセスの認可チェックを行うことができます。

[ngx.location.capture_multi](http://wiki.nginx.org/HttpLuaModule#ngx.location.capture_multi) を使うと、複数のリクエストをパラレルに投げることも可能です。

ngx.is_subrequest を使うと、それが subrequest かどうかチェックすることもできるので、内部用の path に対する外部からのリクエストをはじく　ということも出来ます。

URIのescape/unescape や パラメータの encode/decode 用の関数も用意されていて、それほど苦労することなく HTTPリクエストを組み立てることが出来ます。

* [ngx.excape_uri/unescape_uri](http://wiki.nginx.org/HttpLuaModule#ngx.escape_uri)
* [ngx.encode_args/decode_args](http://wiki.nginx.org/HttpLuaModule#ngx.encode_args)

### [ngx.header.HEADER](http://wiki.nginx.org/HttpLuaModule#ngx.header.HEADER)

リクエストに対するレスポンスヘッダーを保持しています。ここを書き換えたり追加することも可能なので、Webアプリの前段に置けば、Webアプリを変更することなく、CORS対応なども出来ますね。

リクエストヘッダについては、ngx.req.get_headers を使ってアクセスします。

### [ngx.req.xxx（主なもの）](http://wiki.nginx.org/HttpLuaModule#ngx.req.start_time)

* ngx.req.start_time: リクエストスタート時間
* ngx.req.get_method: GETとか POSTとか
* ngx.req.get_uri_args: query パラメータの取得
* ngx.req.get_post_args: POSTのパラメータの取得
* ngx.req.get_headers: リクエストヘッダの取得
* ngx.req.read_body: Body の取得

get だけでなく、set もできるので、いろいろな書き換えも可能です。
他にも多数あります。

### その他いろいろ

#### [ngx.exec](http://wiki.nginx.org/HttpLuaModule#ngx.exec)

内部の location にリダイレクトします。リダイレクトといっても、302 などのようにブラウザまで帰るわけではなく、nginx 内の処理としてのリダイレクトになります。

#### [ngx.redirect](http://wiki.nginx.org/HttpLuaModule#ngx.redirect)

普通に 301 や 302 でリダイレクトするときはこれを使います。

#### [ngx.print / ngx.say](http://wiki.nginx.org/HttpLuaModule#ngx.print)

レスポンスボディに文字列を書きだします。

#### [ngx.log](http://wiki.nginx.org/HttpLuaModule#ngx.log)

ログを error.log に書き出します。error.log 以外にも書けるといいんだけど...

#### [ngx.exit](http://wiki.nginx.org/HttpLuaModule#ngx.exit)

処理を終了し、レスポンスコードをつけてレスポンスを返します。

#### [時間関係](http://wiki.nginx.org/HttpLuaModule#ngx.today)

いろいろありますね..

* ngx.today
* ngx.time
* ngx.now
* ngx.localtime
* ngx.utctime
* ngx.cookietime : Cookie の Expire 用
* ngx.http_time : HTTPヘッダのフォーマットに変換
* ngx.parse_http_time : HTTPヘッダのフォーマットをParse


#### [文字列関連](http://wiki.nginx.org/HttpLuaModule#ngx.re.match)

正規表現のライブラリを利用できるようになっていて、Lua本体にはない 下記の関数が利用可能です。

* ngx.re.match
* ngx.re.gmatch
* ngx.re.find
* ngx.re.sub
* ngx.re.gsub


#### [ngx.socket.udp, ngx.socket.tcp](http://wiki.nginx.org/HttpLuaModule#ngx.socket.udp)

UDP/TCPレベルのソケット通信がいろいろできるみたいです。
これを使って、[memcache や mysql と通信するモジュール](https://github.com/openresty/lua-nginx-module#description)が作られているのでしょう。

ngx.shared.DICTは、同じ nginx上でしかシェアされないので、冗長構成の場合は、これらの仕組みを使ってセッション管理などが出来そうですね。

#### [ngx.thread](http://wiki.nginx.org/HttpLuaModule#ngx.thread.spawn)

スレッド生成もできるようです。

#### [coroutine](http://wiki.nginx.org/HttpLuaModule#coroutine.create)

[Lua の coroutine](http://torus.jp/memo/x200907/lua-coroutine.rd.html) を作れるみたいです。


まとめ
-----

いろいろ便利に使えるAPI が用意されていて、様々な応用が出来ると思います。

例えば 既存のWebアプリの前段に入れることで、Single Sign On を実現したり、CORS対応することも出来るでしょう。また、一部のリクエストだけ部分的に Body に変更を加えて A/Bテストするとか、API の呼び出し回数の上限管理をするといったことも出来そうです。

このレイヤーで実現すれば、後段のWebアプリが Java でも Ruby でも PHPでも同じように使えるところも嬉しいところ。
機会があれば、ぜひお試しを...