Nginx Lua扩展 #

一、OpenResty概述 #

1.1 什么是OpenResty #

OpenResty是一个基于Nginx的Web平台,集成了大量精良的Lua库,使得Nginx可以作为一个强大的Web应用服务器。

1.2 OpenResty特点 #

  • 基于Nginx
  • 内置LuaJIT
  • 丰富的Lua库
  • 非阻塞I/O
  • 高性能

1.3 安装OpenResty #

Ubuntu/Debian:

bash
wget -qO - https://openresty.org/package/pubkey.gpg | sudo apt-key add -
sudo apt-get -y install software-properties-common
sudo add-apt-repository -y "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main"
sudo apt-get update
sudo apt-get install -y openresty

CentOS/RHEL:

bash
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
sudo yum install -y openresty

Docker:

bash
docker pull openresty/openresty

二、基本Lua脚本 #

2.1 Hello World #

nginx
location /hello {
    default_type text/html;
    content_by_lua_block {
        ngx.say("Hello, OpenResty!")
    }
}

2.2 外部Lua文件 #

nginx
location /hello {
    default_type text/html;
    content_by_lua_file /path/to/hello.lua;
}

hello.lua:

lua
ngx.say("Hello from external file!")

2.3 Lua代码位置 #

指令 执行阶段
init_by_lua Nginx初始化
init_worker_by_lua Worker初始化
set_by_lua 设置变量
rewrite_by_lua 重写阶段
access_by_lua 访问控制
content_by_lua 内容生成
header_filter_by_lua 响应头过滤
body_filter_by_lua 响应体过滤
log_by_lua 日志阶段

三、ngx库 #

3.1 输出内容 #

nginx
location /output {
    content_by_lua_block {
        ngx.say("Hello")
        ngx.print("World")
        ngx.exit(200)
    }
}

3.2 获取请求信息 #

nginx
location /request {
    content_by_lua_block {
        ngx.say("Method: ", ngx.req.get_method())
        ngx.say("URI: ", ngx.var.uri)
        ngx.say("Args: ", ngx.var.args)
        ngx.say("Host: ", ngx.var.host)
        ngx.say("Remote Addr: ", ngx.var.remote_addr)
        ngx.say("User Agent: ", ngx.var.http_user_agent)
    }
}

3.3 获取请求参数 #

nginx
location /args {
    content_by_lua_block {
        local args = ngx.req.get_uri_args()
        for k, v in pairs(args) do
            ngx.say(k, ": ", v)
        end
    }
}

3.4 获取请求体 #

nginx
location /body {
    content_by_lua_block {
        ngx.req.read_body()
        local body = ngx.req.get_body_data()
        ngx.say("Body: ", body)
    }
}

3.5 获取请求头 #

nginx
location /headers {
    content_by_lua_block {
        local headers = ngx.req.get_headers()
        for k, v in pairs(headers) do
            ngx.say(k, ": ", v)
        end
    }
}

四、响应操作 #

4.1 设置响应头 #

nginx
location /set_header {
    content_by_lua_block {
        ngx.header["X-Custom-Header"] = "Custom Value"
        ngx.header["Content-Type"] = "application/json"
        ngx.say('{"status": "ok"}')
    }
}

4.2 设置状态码 #

nginx
location /status {
    content_by_lua_block {
        ngx.status = 201
        ngx.say("Created")
    }
}

4.3 重定向 #

nginx
location /redirect {
    content_by_lua_block {
        ngx.redirect("https://example.com", 301)
    }
}

五、访问控制 #

5.1 IP白名单 #

nginx
location /admin {
    access_by_lua_block {
        local ip = ngx.var.remote_addr
        local whitelist = {
            "192.168.1.0/24",
            "10.0.0.0/8"
        }
        
        local function ip_in_cidr(ip, cidr)
            -- 实现CIDR匹配
            return true
        end
        
        local allowed = false
        for _, cidr in ipairs(whitelist) do
            if ip_in_cidr(ip, cidr) then
                allowed = true
                break
            end
        end
        
        if not allowed then
            ngx.exit(403)
        end
    }
    
    proxy_pass http://backend;
}

5.2 JWT验证 #

nginx
location /api {
    access_by_lua_block {
        local jwt = require "resty.jwt"
        local token = ngx.var.http_authorization
        
        if not token then
            ngx.exit(401)
        end
        
        local jwt_obj = jwt:verify("secret_key", token)
        if not jwt_obj.verified then
            ngx.exit(401)
        end
        
        ngx.req.set_header("X-User-ID", jwt_obj.payload.sub)
    }
    
    proxy_pass http://backend;
}

5.3 请求签名验证 #

nginx
location /api {
    access_by_lua_block {
        local resty_hmac = require "resty.hmac"
        
        local timestamp = ngx.var.http_x_timestamp
        local signature = ngx.var.http_x_signature
        local secret = "your_secret_key"
        
        local hmac = resty_hmac:new(secret, resty_hmac.ALGOS.SHA256)
        local expected = hmac:final(timestamp)
        
        if signature ~= expected then
            ngx.exit(401)
        end
    }
    
    proxy_pass http://backend;
}

六、动态配置 #

6.1 共享内存 #

nginx
lua_shared_dict config 10m;

location /config {
    content_by_lua_block {
        local config = ngx.shared.config
        
        config:set("api_key", "12345")
        
        local value = config:get("api_key")
        ngx.say("API Key: ", value)
    }
}

6.2 动态upstream #

nginx
lua_shared_dict upstreams 10m;

init_worker_by_lua_block {
    local upstreams = ngx.shared.upstreams
    upstreams:set("backend1", "192.168.1.10:8080")
    upstreams:set("backend2", "192.168.1.11:8080")
}

location / {
    content_by_lua_block {
        local upstreams = ngx.shared.upstreams
        local server = upstreams:get("backend1")
        
        local res = ngx.location.capture("/proxy/" .. server .. ngx.var.uri)
        ngx.say(res.body)
    }
}

6.3 配置热更新 #

nginx
lua_shared_dict dynamic_config 10m;

location /admin/reload {
    content_by_lua_block {
        local config = ngx.shared.dynamic_config
        
        local new_config = ngx.req.get_body_data()
        config:set("rules", new_config)
        
        ngx.say("Config reloaded")
    }
}

七、数据库访问 #

7.1 MySQL #

nginx
location /mysql {
    content_by_lua_block {
        local mysql = require "resty.mysql"
        local db, err = mysql:new()
        
        db:set_timeout(1000)
        
        local ok, err, errcode, sqlstate = db:connect{
            host = "127.0.0.1",
            port = 3306,
            database = "test",
            user = "root",
            password = "password"
        }
        
        local res, err, errcode, sqlstate = db:query("SELECT * FROM users")
        
        local cjson = require "cjson"
        ngx.say(cjson.encode(res))
        
        db:close()
    }
}

7.2 Redis #

nginx
location /redis {
    content_by_lua_block {
        local redis = require "resty.redis"
        local red = redis:new()
        
        red:set_timeout(1000)
        
        local ok, err = red:connect("127.0.0.1", 6379)
        
        local res, err = red:get("key")
        
        ngx.say("Value: ", res)
        
        red:close()
    }
}

7.3 连接池 #

nginx
location /redis {
    content_by_lua_block {
        local redis = require "resty.redis"
        local red = redis:new()
        
        red:connect("127.0.0.1", 6379)
        
        local res, err = red:get("key")
        ngx.say(res)
        
        red:set_keepalive(10000, 100)
    }
}

八、HTTP请求 #

8.1 内部请求 #

nginx
location /internal {
    content_by_lua_block {
        local res = ngx.location.capture("/api/data")
        
        if res.status == 200 then
            ngx.say(res.body)
        else
            ngx.exit(res.status)
        end
    }
}

location /api/data {
    internal;
    proxy_pass http://backend;
}

8.2 外部请求 #

nginx
location /external {
    content_by_lua_block {
        local http = require "resty.http"
        local httpc = http.new()
        
        local res, err = httpc:request_uri("http://example.com/api", {
            method = "GET",
            headers = {
                ["Content-Type"] = "application/json"
            }
        })
        
        if res then
            ngx.say(res.body)
        end
    }
}

九、API网关示例 #

9.1 完整网关配置 #

nginx
lua_shared_dict api_cache 10m;
lua_shared_dict rate_limit 10m;

init_by_lua_block {
    local api_config = {
        ["/api/user"] = {
            upstream = "user-service",
            rate_limit = 100,
            auth = true
        },
        ["/api/order"] = {
            upstream = "order-service",
            rate_limit = 50,
            auth = true
        },
        ["/api/public"] = {
            upstream = "public-service",
            rate_limit = 200,
            auth = false
        }
    }
    
    package.loaded.api_config = api_config
}

server {
    listen 80;
    
    location /api {
        access_by_lua_block {
            local api_config = package.loaded.api_config
            
            local path = ngx.var.uri
            local config = api_config[path]
            
            if not config then
                ngx.exit(404)
            end
            
            if config.auth then
                local token = ngx.var.http_authorization
                if not token then
                    ngx.exit(401)
                end
                
                local jwt = require "resty.jwt"
                local jwt_obj = jwt:verify("secret", token)
                if not jwt_obj.verified then
                    ngx.exit(401)
                end
            end
            
            local rate_limit = ngx.shared.rate_limit
            local key = ngx.var.remote_addr .. ":" .. path
            local count = rate_limit:incr(key, 1, 0, 60)
            
            if count > config.rate_limit then
                ngx.exit(429)
            end
        }
        
        content_by_lua_block {
            local api_config = package.loaded.api_config
            local config = api_config[ngx.var.uri]
            
            local res = ngx.location.capture("/proxy/" .. config.upstream .. ngx.var.request_uri)
            
            ngx.status = res.status
            for k, v in pairs(res.header) do
                ngx.header[k] = v
            end
            ngx.say(res.body)
        }
    }
    
    location ~ ^/proxy/([^/]+)(.*) {
        internal;
        set $upstream $1;
        set $path $2;
        proxy_pass http://$upstream$path;
    }
}

十、性能优化 #

10.1 Lua代码缓存 #

nginx
lua_code_cache on;

生产环境必须开启。

10.2 JIT优化 #

nginx
lua_jit_enable on;

10.3 共享内存 #

nginx
lua_shared_dict cache 100m;

10.4 连接池 #

lua
local ok, err = red:set_keepalive(10000, 100)

十一、调试技巧 #

11.1 日志输出 #

lua
ngx.log(ngx.ERR, "Error message")
ngx.log(ngx.WARN, "Warning message")
ngx.log(ngx.INFO, "Info message")
ngx.log(ngx.DEBUG, "Debug message")

11.2 调试模式 #

nginx
lua_code_cache off;
error_log logs/error.log debug;

十二、总结 #

本章我们学习了:

  1. OpenResty基础:安装和基本概念
  2. Lua脚本:基本语法和ngx库
  3. 请求处理:获取参数、请求体、请求头
  4. 响应操作:设置头、状态码、重定向
  5. 访问控制:IP白名单、JWT验证、签名验证
  6. 动态配置:共享内存、动态upstream
  7. 数据库访问:MySQL、Redis、连接池
  8. HTTP请求:内部请求和外部请求
  9. API网关:完整网关实现
  10. 性能优化:代码缓存、JIT、连接池

掌握Lua扩展后,让我们进入下一章,学习健康检查!

最后更新:2026-03-27