Nginx Lua Module 개발 노트

Mar 13, 2017

Nginx Lua Module을 개발하면서 필요한 내용을 정리했다.

기본

nil(Null) 체크

Lua nil은 값이 없다는 것을 의미한다.

if keys ~= nil then
    ngx.log(ngx.INFO, "nil이 아니다")
end

if keys == nil then
    ngx.log(ngx.INFO, "nil이다")
end

타입 변환

tonumber("10") -- 10
tostring(10) -- "10"

타입 구하기

공유 메모리의 키를 가져오는데, 어떤 형태인지 알아보려고 사용했었다.

-- 출력 : table
ngx.log(ngx.INFO, type(ngx.shared.shared_dict:get_keys()))

For Loop

공유메모리의 키를 모두 가져와서 For Loop을 돌릴 수 있다.

-- k : 인덱스, v : 키 문자열
for k, v in pairs(ngx.shared.shared_dict:get_keys()) do
    ngx.log(ngx.INFO, v) -- 각 키값들이 출력됨.
end

문자열

string.len("abcdefg") -- 7

-- sprintf처럼 포맷 지정
string.format('%s/AAA', 'BBB') -- BBB/AAA

-- 문자열 찾기
local str = "This is a string."
if string.match(str, "This") then
    -- 찾음
end

-- 문자열 치환
string.gsub(ngx.var.request_uri, "?.*", "") -- ?부터 제거

날짜/시간

os.time() -- 1490167184
os.date("%c", os.time()) -- Wed Mar 22 16:20:06 2017 (KST 또는 시스템 설정에 따름)
os.date("!%c", os.time()) -- Wed Mar 22 07:20:47 2017 (UTC)

Table

-- 카운팅
table.getn(ngx.shared.shared_dict)

Logging

Nginx 로그파일에 로그를 단계별로 기록할 수 있다.

ngx.log(ngx.INFO, "Log")
ngx.log(ngx.DEBUG, "Log")
ngx.log(ngx.WARN, "Log")
ngx.log(ngx.ERR, "Log")

모듈화

모듈화를 위해서 다음과 같은 형태로 파일을 만들어서 사용할 수 있다.

-- mymodule.lua
local _M = {}

function _M.main(ctx)
    ctx.foo = "bar"
end

return _M

이렇게 작성한 모듈은 다음과 같이 사용할 수 있다.

local Mymodule = require 'mymodule';

Mymodule.main({foo: "foo"})

스크립트 경로 가져오기

script_path()를 호출하는 파일의 경로를 구할 수 있다. 해당 파일을 기준으로 다른 파일 경로를 가져올 때 유용하다.

function script_path()
    local str = debug.getinfo(2, "S").source:sub(2)
    return str:match("(.*/)")
end

script_path() -- /home/luascript/access.lua

동적으로 모듈 호출

local pcall = pcall
local ok, upstream = pcall(require, "ngx.upstream")
if not ok then
     error("ngx_upstream_lua module required")
end

Nginx Lua Module 전용

ngx_http_lua_module

Nginx에 LUA 코드를 끼워 넣어 사용할 수 있는 모듈로 기본적으로 Nginx에서 제공되지 않고, 직접 설치해야 한다.

공식사이트

출력

원하는 단계에 넣으면 중간에 다른 결과를 출력할 수 있다.

ngx.header.content_type = 'text/html';
ngx.print("hello")
ngx.exit(200)

버전 정보

ngx.config.nginx_version -- nginx 버전 
ngx.config.ngx_lua_version -- nginx lua 버전 9015

http 요청

ngx.location.capture는 nginx 워커에서 특정 URI에 추가 요청을 할 수 있다.

local res = ngx.location.capture("/auth")

if res then
    ngx.say("status: ", res.status)
    ngx.say("body:")
    ngx.print(res.body)
end

redirect

특정 페이지로 리다이렉트할 수 있다.

-- redirect
ngx.redirect("http://"..ngx.req.get_headers()["Host"].."/page")

-- exec
ngx.exec("@bar", "a=goodbye");

exec는 redirect와 다르게, 내부 리다이렉트를 하고, 새로운 외부 HTTP 트래픽과 관련이 없다.

Header 관련

-- upstream으로 전달
ngx.req.set_header("X-Epoch", ngx.time())

-- 응답 헤더에 추가한다. phase간 값을 공유할 수도 있다. 예 : access phse -> header_filter phase
ngx.header['X-Epoch'] = ngx.time()
ngx.header['Content-Type'] = "text/html"
ngx.header['Set-Cookie'] = 'Foo=abc; path=/'

Nginx에서 설정한 변수 사용하기

nginx.conf:

map "${http_host}${request_uri}" $value_in_lua {
    default "default_value";

    # 정규식 사용 가능. ~ : case-sensitive, ~* : case-insensitive
  ~*^(.+\.)?hostname/path "value";
}

주소가 설정한 값과 일치하면 그 값을 LUA에서 다음과 같이 사용할 수 있다.

ngx.var.value_in_lua -- value

3rd party module

LIP - ini Parser

LIP는 LUA INI Parser다. ini 파일을 불러오고, 저장할 수 있다.

-- ini 불러오기
local LIP = require 'LIP';
local config = LIP.load('/home/luascript/config.ini');

CJSON

CJSON은 LUA에서 JSON을 지원하는 모듈이다.

설치하기

다음 페이지 다른 JSON 모듈을 비교한다.

JSON 모듈 비교

에러

a temporary file while reading upstream

LUA에서 다음 에러가 발생한다.

a temporary file /path/file while reading upstream, client: 000.000.000.000, server: localhost, request: "GET /file.php HTTP/1.1", upstream: "http://127.0.0.1:10000/file.php", host: "domain"

문제 생기는 부분을 확인하니 다음과 같이 이미지를 base64로 출력하는 부분의 크기가 설정한 값보다 커서 생긴 문제였다. 이미지 크기에 맞게 프록시 버퍼의 크기를 늘려줬다.

<?php
<img src='data:image/jpeg;base64, <?php echo $image?>' width="100%">

Nginx 설정을 변경했다.

proxy_buffers 500 8k;

예제 프로그램이라서 base64로 이미지를 출력했지만, 버퍼를 늘리는 것보다 이미지는 static 서버를 통해 제공하는 것이 좋다.