目录

 一、init_by_lua

二、init_worker_by_lua

三、set_by_lua

四、rewrite_by_lua

五、access_by_lua

六、content_by_lua

七、header_filter_by_lua

八、body_filter_by_lua

九、log_by_lua

十、balancer_by_lua_block

一、init_by_lua

init_by_lua 在每次 Nginx 重新加载配置时执行,可用来完成一些耗时模块的加载,或者初始化一些全局配置。在管理进程创建工作进程时,指令中的全局变量会复制到工作进程中。

1)在 nginx.conf 的 http 模块中写入

# 设置全局共享变量(字典),名字为 shared_data 共享内存的大小是1M

lua_shared_dict shared_data 1m;

init_by_lua_file /usr/example/lua/init.lua                     # 初始化的lua代码位置

2)编写 init.lua

# 加载一些耗时模块

local redis = require "resty.redis"

local cjson = require "cjson"

-- 设置全局变量,不推荐

count = 1

-- 设置共享内存

local shared_data = ngx.shared.shared_data      # 获取到 http 模块配置的 shared_data 共享内存

shared_data:set("count", 1)                     # 在共享内存中设置一个变量 count 值为 1

3)编写 test.lua

-- 对全局变量进行+1操作

count = count + 1

-- 输出全局变量的值

ngx.say("global variable : ", count)

-- 获取共享内存的值

local shared_data = ngx.shared.shared_data

ngx.say(", shared memory : ", shared.data.get("count"))

-- 对共享内存的值 + 1 操作

shared_data:incr("count", 1)

4)增加 location 测试

location /lua {
    default_type "application/json";

    content_by_lua_file "test.lua"
}

// 启动 Nginx ,访问 /lua 可见全局变量一直保持不变,共享内存中的变量每次访问依次 + 1,因为共享内存的变量只在 Nginx 退出后才被收回,而每个 worker 接受 /lua 请求后都能获取共享变量并执行 test.lua 文件

二、init_worker_by_lua

用于启动一些定时任务,比如心跳检查,定时拉取服务器配置等。有多少个工作进程,该定时任务就会启动多少个,一个工作进程拥有一个定时器

1)在 nginx.conf 的 http 模块中增加

init_worker_by_lua_file /usr/example/lua/init_worker.lua;

2)编写 init_worker.lua

#!/usr/local/bin/lua

local count = 0
local delayInSeconds = 3
local heartBeatCheck = nil

heartBeatCheck = function()
    count = count + 1

    # 写入到error日志中
    ngx.log(ngx.ERR, "do check", count)

    local ok, err = ngx.timer.at(delayInSeconds, heartBeatCheck)

    # 每隔delayInSeconds时间触发heartBeatCheck回调函数,每次回调函数的执行不会在当前请求中执行,是在一个轻量级线程中执行。

    if not ok then
        ngx.log(ngx.ERR, "failed to startup heartbeart worker", err)
    end
end

heartBeatCheck()


-- 根据实际情况可设置

lua_max_pending_timers 1024;            -- 指定最大的等待任务数(定时器)
lua_max_running_timers 256;             -- 最大同时运行任务数(定时器)

3)保存重启 nginx ,可见每隔3秒在 error.log 会出现 do check 1, 2, 3... 的信息

三、set_by_lua

用于设置 Nginx 变量,用于处理一些特定的情况

1、例子:参数相加

1)在 nginx.conf 中添加

location /lua_set_1 {
    default_type "text/html";
    set_by_lua_file $num /usr/example/lua/test_set_1.lua

    # 语法 : set_by_lua_file $var lua_file arg1 arg2 ...
    # 该指令可以在 Lua 代码中实现所有复杂的逻辑,但执行速度要快,不要阻塞

    echo $num;                          # 在页面中输出该变量
}

2)编写 test_set_1.lua

#!/usr/local/bin/lua

local uri_args = ngx.req.get_uri_args()            -- 获取 http request 的参数

local i = uri_args["i"] or 0                        

local j = uri_args["j"] or 0

return i + j

3)访问 /lua_set_1?i=100&j=200 输出 300

2、例子:不同域名响应不同的内容

1)在 nginx 的 conf 文件中添加

#map主机
map $host $host_var {                -- 根据域名确定 $host_var 的值是1或是0
    default 0;
    2.test.com 1;
}

2)修改 /etc/hosts

127.0.0.1 test.com 2.test.com

3)在 nginx.conf 的 server 模块中添加

set_by_lua $host_jump '
    local var = ngx.var.host_var
    if var == "1" then
        return 1
    else 
        return 0
    end
';

location /test {

    echo $host_jump;

    if ($host_jump) {
        proxy_pass http://localhost/$host_jump.html;
        break;
    }

    if ($host_jump = 0) {
        proxy_pass http://localhost/$host_jump.html;
        break;
    }              
}

4)发起 curl 请求测试

curl http://test.com:9999/test             -- 响应 localhost/0.html
    
curl http://2.test.com:9999/test           -- 响应 localhost/1.html

四、rewrite_by_lua

用于执行内部 URL 重写或外部重定向,典型的如伪静态化 url 重写,本阶段在 rewrite 处理阶段的最后默认执行。

1、rewrite 阶段实现新老页面跳转

1)修改 nginx.conf 添加配置

location /lua_rewrite_1 {
    default_type "text/html";
    rewrite_by_lua_file /usr/example/lua/test.lua;
    echo "no rewrite";
}

2)编写 test.lua

if ngx.req.get_uri_args["jump"] == "1" then
    return ngx.redirect("http://jd.com", 302)
end

-- 301 或者 302 可根据自己需求

3)当请求 /lua_rewrite_1?jump=1 发现页面已被跳转到京东首页,而如果没有 jump=1 参数会输出 no rewrite

2、使用 set_uri_args 重写请求内部实现重新发起定位

1)修改 nginx.conf 增加配置

location /lua_rewrite_2 {
    default_type "text/html";
    rewrite_by_lua_file /usr/example/lua/test.lua;
    echo "rewrite2 uri : $uri, a : $arg_a";

    # $uri 获取当前请求的uri地址 $arg_a 能直接获取GET参数的a的值
}

2)编写 test.lua

if ngx.req.get_uri_args()["jump"] == "1" then
    ngx.req.set_uri("/lua_rewrite_3", false)         -- 设置uri不发生重定向,当第二个参数为true时,会发生重定向,而不会执行后面的代码
    ngx.req.set_uri("/lua_rewrite_4", false)
    ngx.req.set_uri_args({a = 1, b = 2})             -- 设置uri参数
end

4)发送 curl 请求查看

curl http://localhost:9999/lua_rewrite_2

# 输出 uri /lua_rewrite_2,a = 

curl http://localhost:9999/lua_rewrite_2?jump=1

# 输出 uri /lua_rewrite_4,不会跳转与重定向,地址不发生改变,只设置uri。输出 a = 1

五、access_by_lua

用于访问控制,如限定某个指定token的用户才能访问页面

1)修改 nginx.conf

location /lua_access {
    default_type "text/html";
    access_by_lua_file /root/access.lua;
    echo "access";
}

2)修改 access.lua

if ngx.req.get_uri_args()["token"] ~= "123" then
    ngx.exit(403)
end

3)发起 curl 测试

curl http://localhost:9999/lua_access                 # 403 Forbidden

curl http://localhost:9999/lua_access?token=123       # access

六、content_by_lua

lua 应用最多的阶段,正式处理内容基本都在这阶段

1)修改 nginx.conf 增加

# 定义一个返回get参数的api

location /lua_content_testapi {
    default_type "text/html";
    echo $args;
}


# 使用 content_by_lua

location /lua_content_test {
    default_type "application/json";              -- 响应格式为json 
    # lua_need_request_body on;                     -- 确定在运行rewrite / access / access_by_lua *之前是否强制读取请求正文数据。
    # client_max_body_size 50k;                
    # client_body_buffer_size 50k;

    ## 要读取$ request_body变量中的请求正文数据,client_body_buffer_size必须与client_max_body_size具有相同的值

    content_by_lua '
        local vdata = {}
        local cjson = require "cjson"
        vdata["name"] = "pan"
        vdata["age"] = 12

        local jsonRequest = cjson.encode(vdata)   -- 将table转为json对象,并发给上面的api接口

        local resp = ngx.location.capture("/lua_content_testapi", {method = ngx.HTTP_GET, args = jsonRequest})
        
        -- 返回res.status, res.header, res.body, res.truncated.
        -- 使用uri发出同步但仍无阻塞的Nginx子请求 
        -- 子请求只是模仿HTTP接口,但没有额外的HTTP / TCP流量,也没有涉及IPC。

        if resp.status == ngx.HTTP_OK and resp.body then
            ngx.print(resp.body)                             -- 打印子请求访问的内容主体
        end
    ';
}

2)发起 curl 测试

curl http://localhost:9999/lua_content_test

# 响应:{"age":12,"name":"pan"}

七、header_filter_by_lua

设置应答消息的头部信息,即 response header

修改 nginx.conf

location / {
    default_type "text/html";
    header_filter_by_lua 'ngx.header.Foo = "panguangyu"';
    echo $uri;
}

-- 刷新请求,可见Response Header中出现 Foo = panguangyu

八、body_filter_by_lua

用于修改应答body的内容

在 nginx.conf 中添加

# body_filter_by_lua 测试
location /lua_body {
    default_type "text/html";
    echo a;
    echo b;
    echo c;
    echo d;

    body_filter_by_lua '
        local chunk = ngx.arg[1];

        if (string.match(chunk, "c")) then        -- 如果遇到 c ,将下一个输出流该为f
            ngx.arg[1] = "f"                      -- flag                    
        end

    ';
}

// 输出 a b cf ,ngx.arg[1] 代表每次处理的数据流块,如 a b c d nginx会将其划分为 chunk 数据流作为输出。当设置 ngx.arg[2] = true 时,会截断后面所有的数据流chunk,不会被输出。如在flag行指定 ngx.arg[2] = true 将只输出 a b c

九、log_by_lua

用于log请求处理阶段,用lua处理日志,但并不替换原有log处理。下面以简单统计页面PV为例

1)在 nginx.conf 设置

# 在 server 外层定义
lua_shared_dict log_dict 5M;

# log_by_lua 测试
location /lua_log_count {
    default_type "text/html";
    log_by_lua '
        local log_dict = ngx.shared.log_dict
        local newvalue, err = log_dict:incr("pv", 1)
        if not newvalue and err == "not found" then
            log_dict:add("pv", 0)
            log_dict:incr("pv", 1)
        end
    ';

    content_by_lua '
        ngx.say("success")
    ';
}

location /lua_log_see {
    default_type "text/html";
    content_by_lua '
        local log_dict = ngx.shared.log_dict
        local pv = log_dict:get("pv")

        if pv then
            ngx.say("total pv : ", pv)
        end
    ';
}

2)依次请求 /lua_log_count,然后请求 /lua_log_see 查看 PV

十、balancer_by_lua_block

可作为上游服务器的负载均衡器

### 在两个端口创建服务,根据请求参数除2作为hash值,取出对应的端口作为服务

upstream backend {
    server 0.0.0.0;
    balancer_by_lua_block {
        local balancer = require "ngx.balancer"
        local port = {8088, 8089}
        
        local param = ngx.req.get_uri_args()["server"] or 0

        local hash = (param % 2) + 1
        
        local ok, err = balancer.set_current_peer("127.0.0.1", port[hash])

        if not ok then
            return ngx.exit(500)
        end
    }
}