(WIP)
- Table of Contents
nginx version: nginx/1.10.1
built by gcc 4.4.7 20120313 (Red Hat 4.4.7-4) (GCC)
configure arguments: --with-openssl=/usr/local/src/openssl --with-ld-opt=-Wl,-E,-rpath,/usr/local/lib --add-module=/usr/local/src/ngx_devel_kit --add-module=/usr/local/src/lua-nginx-module
https://github.com/openresty/lua-nginx-module.git 7d242ff79a63bae15c8f5f68999dfe5f4da7cf67 (v0.10.6)
https://github.com/simpl/ngx_devel_kit.git e443262071e759c047492be60ec7e2d73c5b57ec
要make install
http://luajit.org/git/luajit-2.0.git 3e4a196777450f7db11067e93a17655ba3ee0d53 (tag: v2.1.0-beta2)
何をされてもhello worldで応答するhttpサーバー。
http {
server {
listen 80;
location / {
content_by_lua_block {
ngx.say("hello, world!")
}
}
}
}
error.log
にnoticeレベルで出力するにはprintを使用する。
print("hello, world!")
個別にログレベルを指定したい場合はngx.logを使用する。
ngx.log(ngx.INFO, "foobar")
第一引数に渡すログレベルには以下のものが用意されている。
ngx.STDERR
ngx.EMERG
ngx.ALERT
ngx.CRIT
ngx.ERR
ngx.WARN
ngx.NOTICE
ngx.INFO
ngx.DEBUG
(出典: https://github.com/openresty/lua-nginx-module#nginx-log-level-constants)
ngx.varを経由してnginx.confで使用出来る変数にアクセスすることが出来る。
...
ngx.say(ngx.var.http_host)
...
hostname/location?xxx=yyy
といったクエリパラメータに依存する動的な変数も同様にngx.varから参照することが出来る。
ngx.var.arg_xxx
ngx.req.get_headers
を使用する
...
ngx.say(ngx.req.get_headers()["Host"])
...
ngx.req.get_body_data()
を叩く前にはngx.req.read_body()
を一度実行する必要がある。
それをしないとnilしか返ってこない。
...
content_by_lua_block {
ngx.req.read_body()
ngx.print(ngx.req.get_body_data())
}
...
次のような外部luaファイルをnginxから呼び出してみる。
-- testlib.lua
local _M = {}
function _M:foo()
return "bar"
end
return _M
nginx.conf
からライブラリへのパスを通し、requireから使用する。
http {
lua_package_path '/absolute/path/to/lua/scripts/?.lua;;'
...
content_by_lua_block {
local lib = require("testlib")
ngx.say(lib:foo())
}
...
}
一度require
したスクリプトの内容はlua_code_cache
を切らない限りキャッシュされるので、その後該当ファイルを編集しても変更は反映されない。lua_package_path
はrequire
呼び出し時のPATHとして設定されているため、外部モジュールの中でサブディレクトリにあるスクリプトを使う場合は単にrequire("foo/bar")
などで、スラッシュ区切りでモジュールを呼び出せば良い。
redisと同じようなインターフェースを持つshared dictionaryを定義してワーカー間でデータを同期して共有することが出来る。
http {
lua_shared_dict dic 10m; # 名前'dic'のshared dictionaryを10mbyteのサイズで作成
...
content_by_lua_block {
local cnt = ngx.shared.dic:get("cnt") or 0
ngx.say(cnt)
ngx.shared.dic:set("cnt", cnt + 1)
}
...
}
shared dictionaryの中身はnginxをrestartするまで保持される(reloadでは消えない)。
require
で読み出したluaモジュールはワーカープロセスにキャッシュされる。このため、モジュールの内部で定義されている変数をワーカー内部での共有データとして使うことができる。ワーカー同士でデータを共有することはできないので、read onlyのデータに対して使用するといいだろう。(参考: Data Sharing within an Nginx Worker)
TBD. 基本的にはluajitのluacでバイトコードを使って*_by_file
で使用すればよい
lua_code_cacheを切ることで外部ファイルで記述されたluaスクリプトをリクエスト単位で読み込み直すようになる。
nginx.conf
に直接記述されたスクリプトに関してはreload
が必要。
http {
lua_code_cache off;
...
}
公式ドキュメントより、以下の図を参照。 (出典: https://github.com/openresty/lua-nginx-module#directives )
body_filter_by_luaの中でngx.arg[1]
にオリジナルのbodyが入っているので、変更を加えて代入することで内容を変更することが出来る。
...
location / {
proxy_pass http://mybackend;
body_filter_by_lua_block {
ngx.arg[1] = string.upper(ngx.arg[1])
}
}
...
lua-resty-websocketを利用してluaレベルでwebsocketサーバーを構築して中継を行う。以下はupstreamにwebsocketを設定してリバースプロクシを構成するサンプル。
...
location / {
proxy_pass http://websocket;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
content_by_lua_block {
local server = require "resty/websocket/server"
local client = require "resty/websocket/client"
local protocol = require "resty/websocket/protocol"
-- websocket server
local sock_server, err = server:new()
if not sock_server then
ngx.log(ngx.ERR, "failed to new websocket: ", err)
return ngx.exit(444)
end
local sock_client, err = client:new()
-- local uri = "ws://" .. ngx.var.http_host .. ngx.var.uri .. ngx.var.is_args .. ngx.var.args
local uri = "ws://192.168.56.102:3000" .. ngx.var.uri .. ngx.var.is_args .. ngx.var.args
local ok, err = sock_client:connect(uri)
if not ok then
ngx.say("failed to connect remote: " .. err)
ngx.exit(404)
end
local function ws_proxy(sock_from, sock_to, flip_masking)
local opcode_mapper = {
["continuation"] = 0x0,
["text"] = 0x1,
["binary"] = 0x2,
["close"] = 0x8,
["ping"] = 0x9,
["pong"] = 0x9,
}
while true do
local data, typ, err = sock_from:recv_frame(flip_masking)
if data == nil then
-- socket already closed
sock_to:send_close()
break
else
ngx.log(ngx.INFO, data .. " (" .. typ .. ")")
local fin = (typ ~= "continuation")
if typ == "close" then
sock_from:send_close()
end
local bytes, err = sock_to:send_frame(fin, opcode_mapper[typ], data, flip_masking)
if bytes == nil then
sock_from:send_close()
break
end
end
end
end
local s2c = ngx.thread.spawn(ws_proxy, sock_client, sock_server, false)
local c2s = ngx.thread.spawn(ws_proxy, sock_server, sock_client, true)
if not ngx.thread.wait(s2c) then
ngx.log(ngx.ERR, "s2c wait failed")
end
if not ngx.thread.wait(c2s) then
ngx.log(ngx.ERR, "c2s wait failed")
end
}
}
...
ngx.exitを使用して任意のステータスコードを返して処理を終了することができる。
ngx.exit(200)
サポートされている定数のリストはこちら。
ノンブロッキングなスリープで通信遅延をエミュレートするにはngx.sleepを秒数指定で使用する。ミリ秒精度までサポートされている。
...
location / {
access_by_lua_block {
ngx.sleep(3.000) -- 3秒待機
}
}
...