1. OpenResty配置基础

1.1 配置文件结构

# nginx.conf 主配置文件
worker_processes auto;
error_log logs/error.log;

events {
    worker_connections 1024;
}

http {
    # Lua相关配置
    lua_package_path "/usr/local/openresty/lualib/?.lua;;";
    lua_package_cpath "/usr/local/openresty/lualib/?.so;;";
    
    # 共享内存配置
    lua_shared_dict my_cache 10m;
    lua_shared_dict my_locks 1m;
    
    # 初始化脚本
    init_by_lua_file /path/to/init.lua;
    init_worker_by_lua_file /path/to/init_worker.lua;
    
    server {
        listen 80;
        server_name localhost;
        
        # 各种Lua指令的使用
        location / {
            content_by_lua_file /path/to/content.lua;
        }
    }
}

1.2 Lua指令概览

# 服务器启动时执行一次
init_by_lua_block { ... }
init_by_lua_file /path/to/init.lua;

# 每个worker进程启动时执行
init_worker_by_lua_block { ... }
init_worker_by_lua_file /path/to/init_worker.lua;

# 设置变量
set_by_lua_block $var { ... }
set_by_lua_file $var /path/to/set.lua;

# 重写阶段
rewrite_by_lua_block { ... }
rewrite_by_lua_file /path/to/rewrite.lua;

# 访问控制阶段
access_by_lua_block { ... }
access_by_lua_file /path/to/access.lua;

# 内容生成阶段
content_by_lua_block { ... }
content_by_lua_file /path/to/content.lua;

# 响应头过滤
header_filter_by_lua_block { ... }
header_filter_by_lua_file /path/to/header_filter.lua;

# 响应体过滤
body_filter_by_lua_block { ... }
body_filter_by_lua_file /path/to/body_filter.lua;

# 日志阶段
log_by_lua_block { ... }
log_by_lua_file /path/to/log.lua;

2. Nginx处理阶段详解

2.1 请求处理流程

┌─────────────────┐
│   接收请求       │
└─────────────────┘
          │
┌─────────────────┐
│   post-read     │  ← realip模块
└─────────────────┘
          │
┌─────────────────┐
│   server-rewrite│  ← rewrite模块(server级别)
└─────────────────┘
          │
┌─────────────────┐
│   find-config   │  ← 查找location
└─────────────────┘
          │
┌─────────────────┐
│   rewrite       │  ← rewrite_by_lua*
└─────────────────┘
          │
┌─────────────────┐
│   post-rewrite  │  ← rewrite模块(location级别)
└─────────────────┘
          │
┌─────────────────┐
│   preaccess     │  ← limit_req, limit_conn
└─────────────────┘
          │
┌─────────────────┐
│   access        │  ← access_by_lua*, auth模块
└─────────────────┘
          │
┌─────────────────┐
│   post-access   │  ← satisfy指令
└─────────────────┘
          │
┌─────────────────┐
│   try-files     │  ← try_files指令
└─────────────────┘
          │
┌─────────────────┐
│   content       │  ← content_by_lua*, proxy_pass
└─────────────────┘
          │
┌─────────────────┐
│   log           │  ← log_by_lua*, access_log
└─────────────────┘

2.2 各阶段的作用

init_by_lua*

# 服务器启动时执行,用于初始化全局配置
init_by_lua_block {
    -- 加载配置
    local config = require "config"
    config.load("/etc/app/config.json")
    
    -- 初始化全局变量
    _G.app_version = "1.0.0"
    _G.start_time = ngx.time()
    
    -- 预编译正则表达式
    local ngx_re = require "ngx.re"
    _G.email_pattern = ngx_re.compile(r"[\w.-]+@[\w.-]+\.\w+")
}

init_worker_by_lua*

# 每个worker进程启动时执行
init_worker_by_lua_block {
    -- 初始化定时器
    local function cleanup_cache()
        local cache = ngx.shared.my_cache
        cache:flush_expired()
    end
    
    -- 每60秒清理一次过期缓存
    local ok, err = ngx.timer.every(60, cleanup_cache)
    if not ok then
        ngx.log(ngx.ERR, "failed to create timer: ", err)
    end
    
    -- 初始化worker级别的资源
    local redis = require "resty.redis"
    _G.redis_pool = redis:new()
}

set_by_lua*

# 设置Nginx变量
location /api {
    set_by_lua_block $user_id {
        local token = ngx.var.http_authorization
        if not token then
            return ""
        end
        
        -- 解析JWT token
        local jwt = require "resty.jwt"
        local payload = jwt:verify("secret", token)
        
        return payload and payload.user_id or ""
    }
    
    # 将user_id传递给后端
    proxy_set_header X-User-ID $user_id;
    proxy_pass http://backend;
}

rewrite_by_lua*

# URL重写和请求预处理
location /old-api {
    rewrite_by_lua_block {
        -- URL重写
        local uri = ngx.var.uri
        local new_uri = string.gsub(uri, "^/old%-api", "/api/v1")
        ngx.req.set_uri(new_uri)
        
        -- 添加版本头
        ngx.req.set_header("API-Version", "v1")
        
        -- 记录重写日志
        ngx.log(ngx.INFO, "Rewritten ", uri, " to ", new_uri)
    }
    
    proxy_pass http://backend;
}

access_by_lua*

# 访问控制和认证
location /protected {
    access_by_lua_block {
        -- IP白名单检查
        local allowed_ips = {"127.0.0.1", "192.168.1.0/24"}
        local client_ip = ngx.var.remote_addr
        
        local function is_ip_allowed(ip)
            for _, allowed in ipairs(allowed_ips) do
                if ip == allowed then
                    return true
                end
            end
            return false
        end
        
        if not is_ip_allowed(client_ip) then
            ngx.status = 403
            ngx.say("Access denied")
            ngx.exit(403)
        end
        
        -- 认证检查
        local auth_header = ngx.var.http_authorization
        if not auth_header then
            ngx.status = 401
            ngx.header.www_authenticate = 'Bearer realm="API"'
            ngx.say("Authentication required")
            ngx.exit(401)
        end
        
        -- 验证token
        local token = string.match(auth_header, "Bearer%s+(.+)")
        if not token or not validate_token(token) then
            ngx.status = 401
            ngx.say("Invalid token")
            ngx.exit(401)
        end
    }
    
    proxy_pass http://backend;
}

content_by_lua*

# 内容生成
location /api/users {
    content_by_lua_block {
        -- 解析请求参数
        ngx.req.read_body()
        local method = ngx.var.request_method
        local args = ngx.req.get_uri_args()
        
        if method == "GET" then
            -- 获取用户列表
            local page = tonumber(args.page) or 1
            local limit = tonumber(args.limit) or 10
            
            local users = get_users(page, limit)
            
            ngx.header.content_type = "application/json"
            ngx.say(require("cjson").encode({
                data = users,
                page = page,
                limit = limit
            }))
        elseif method == "POST" then
            -- 创建用户
            local body = ngx.req.get_body_data()
            local user_data = require("cjson").decode(body)
            
            local user_id = create_user(user_data)
            
            ngx.status = 201
            ngx.header.content_type = "application/json"
            ngx.say(require("cjson").encode({
                id = user_id,
                message = "User created successfully"
            }))
        else
            ngx.status = 405
            ngx.say("Method not allowed")
        end
    }
}

header_filter_by_lua*

# 响应头过滤
location / {
    proxy_pass http://backend;
    
    header_filter_by_lua_block {
        -- 添加安全头
        ngx.header["X-Frame-Options"] = "DENY"
        ngx.header["X-Content-Type-Options"] = "nosniff"
        ngx.header["X-XSS-Protection"] = "1; mode=block"
        
        -- 移除敏感头
        ngx.header["Server"] = nil
        ngx.header["X-Powered-By"] = nil
        
        -- 添加CORS头
        local origin = ngx.var.http_origin
        if origin then
            ngx.header["Access-Control-Allow-Origin"] = origin
            ngx.header["Access-Control-Allow-Credentials"] = "true"
        end
        
        -- 缓存控制
        if ngx.var.uri:match("\.css$") or ngx.var.uri:match("\.js$") then
            ngx.header["Cache-Control"] = "public, max-age=31536000"
        end
    }
}

body_filter_by_lua*

# 响应体过滤
location /api {
    proxy_pass http://backend;
    
    body_filter_by_lua_block {
        -- 获取响应体
        local chunk = ngx.arg[1]
        local eof = ngx.arg[2]
        
        if not chunk then
            return
        end
        
        -- 响应体压缩
        if ngx.header.content_type and 
           ngx.header.content_type:match("application/json") then
            
            -- 移除响应中的敏感信息
            local filtered = string.gsub(chunk, '"password":"[^"]*"', '"password":"***"')
            ngx.arg[1] = filtered
        end
        
        -- 添加水印
        if eof and ngx.header.content_type and 
           ngx.header.content_type:match("text/html") then
            local watermark = "\n<!-- Powered by OpenResty -->"
            ngx.arg[1] = chunk .. watermark
        end
    }
}

log_by_lua*

# 日志记录
location / {
    proxy_pass http://backend;
    
    log_by_lua_block {
        -- 记录详细的访问日志
        local log_data = {
            timestamp = ngx.time(),
            method = ngx.var.request_method,
            uri = ngx.var.uri,
            args = ngx.var.args,
            status = ngx.var.status,
            body_bytes_sent = ngx.var.body_bytes_sent,
            request_time = ngx.var.request_time,
            upstream_response_time = ngx.var.upstream_response_time,
            remote_addr = ngx.var.remote_addr,
            user_agent = ngx.var.http_user_agent,
            referer = ngx.var.http_referer
        }
        
        -- 发送到日志系统
        local logger = require "logger"
        logger.info(require("cjson").encode(log_data))
        
        -- 统计API调用次数
        local cache = ngx.shared.my_cache
        local key = "api_count:" .. ngx.var.uri
        local count = cache:get(key) or 0
        cache:set(key, count + 1, 3600)
        
        -- 错误告警
        if tonumber(ngx.var.status) >= 500 then
            local alert = require "alert"
            alert.send("API Error: " .. ngx.var.uri .. " returned " .. ngx.var.status)
        end
    }
}

3. 共享内存和缓存

3.1 lua_shared_dict配置

http {
    # 定义共享内存区域
    lua_shared_dict my_cache 100m;      # 缓存数据
    lua_shared_dict my_locks 10m;       # 分布式锁
    lua_shared_dict my_stats 50m;       # 统计数据
    lua_shared_dict rate_limit 10m;     # 限流计数器
}

3.2 共享内存操作

-- 获取共享内存对象
local cache = ngx.shared.my_cache

-- 基本操作
cache:set("key", "value", 3600)  -- 设置,过期时间3600秒
local value = cache:get("key")   -- 获取
cache:delete("key")              -- 删除

-- 原子操作
local newval, err = cache:incr("counter", 1, 0)  -- 原子递增
local success, err, forcible = cache:add("lock", 1, 10)  -- 原子添加

-- 批量操作
local keys = cache:get_keys(100)  -- 获取最多100个key
cache:flush_all()                 -- 清空所有数据
cache:flush_expired()             -- 清空过期数据

-- 容量管理
local capacity = cache:capacity()  -- 总容量
local free = cache:free_space()    -- 剩余空间

3.3 缓存模式实现

-- 缓存穿透保护
local function get_user_with_cache(user_id)
    local cache = ngx.shared.my_cache
    local cache_key = "user:" .. user_id
    
    -- 尝试从缓存获取
    local cached_user = cache:get(cache_key)
    if cached_user then
        if cached_user == "NULL" then
            return nil  -- 缓存的空值
        end
        return require("cjson").decode(cached_user)
    end
    
    -- 从数据库获取
    local user = get_user_from_db(user_id)
    
    -- 缓存结果(包括空值)
    if user then
        cache:set(cache_key, require("cjson").encode(user), 3600)
    else
        cache:set(cache_key, "NULL", 300)  -- 空值缓存较短时间
    end
    
    return user
end

-- 分布式锁实现
local function acquire_lock(key, timeout)
    local locks = ngx.shared.my_locks
    local lock_key = "lock:" .. key
    
    -- 尝试获取锁
    local success, err, forcible = locks:add(lock_key, 1, timeout or 10)
    return success
end

local function release_lock(key)
    local locks = ngx.shared.my_locks
    local lock_key = "lock:" .. key
    locks:delete(lock_key)
end

-- 使用锁保护临界区
local function update_counter_safely(key)
    if acquire_lock(key) then
        local cache = ngx.shared.my_cache
        local current = cache:get(key) or 0
        cache:set(key, current + 1, 3600)
        release_lock(key)
        return true
    else
        return false, "Failed to acquire lock"
    end
end

4. 变量和上下文

4.1 Nginx变量访问

-- 读取Nginx变量
local method = ngx.var.request_method
local uri = ngx.var.uri
local args = ngx.var.args
local remote_addr = ngx.var.remote_addr
local user_agent = ngx.var.http_user_agent

-- 设置Nginx变量
ngx.var.my_var = "custom_value"

-- 请求参数
local arg_name = ngx.var.arg_name  -- GET参数
local cookie_session = ngx.var.cookie_session  -- Cookie值

-- 上游服务器信息
local upstream_addr = ngx.var.upstream_addr
local upstream_status = ngx.var.upstream_status
local upstream_response_time = ngx.var.upstream_response_time

4.2 请求上下文

-- 请求头操作
local headers = ngx.req.get_headers()
for name, value in pairs(headers) do
    ngx.log(ngx.INFO, name, ": ", value)
end

-- 设置请求头
ngx.req.set_header("X-Custom-Header", "value")
ngx.req.clear_header("Unwanted-Header")

-- 请求体操作
ngx.req.read_body()
local body_data = ngx.req.get_body_data()
local body_file = ngx.req.get_body_file()

-- URI和参数操作
local uri_args = ngx.req.get_uri_args()
local post_args = ngx.req.get_post_args()

-- 修改请求
ngx.req.set_uri("/new/path")
ngx.req.set_uri_args({page = 1, limit = 10})

4.3 响应上下文

-- 响应头操作
ngx.header.content_type = "application/json"
ngx.header["Cache-Control"] = "no-cache"
ngx.header["Set-Cookie"] = {"session=abc123", "lang=en"}

-- 响应状态
ngx.status = 200

-- 响应体
ngx.say("Hello, World!")  -- 输出并添加换行
ngx.print("Hello")        -- 输出不添加换行
ngx.flush()               -- 刷新输出缓冲区

-- 结束请求
ngx.exit(200)  -- 立即结束请求
ngx.eof()      -- 结束响应体输出

5. 配置最佳实践

5.1 性能优化配置

http {
    # Lua代码缓存
    lua_code_cache on;  # 生产环境必须开启
    
    # 正则表达式缓存
    lua_regex_cache_max_entries 8192;
    
    # 共享内存优化
    lua_shared_dict cache 1000m;
    
    # SSL优化
    lua_ssl_verify_depth 2;
    lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
    
    # 连接池配置
    upstream backend {
        server 127.0.0.1:8080;
        keepalive 32;
    }
    
    server {
        # 连接保持
        keepalive_timeout 65;
        keepalive_requests 100;
        
        # 缓冲区优化
        client_body_buffer_size 128k;
        client_max_body_size 10m;
        
        location / {
            # 上游连接保持
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            
            # 超时设置
            proxy_connect_timeout 5s;
            proxy_send_timeout 10s;
            proxy_read_timeout 10s;
            
            proxy_pass http://backend;
        }
    }
}

5.2 安全配置

http {
    # 隐藏版本信息
    server_tokens off;
    
    # 限制请求大小
    client_max_body_size 1m;
    
    # 限制请求频率
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
    
    server {
        # 安全头
        add_header X-Frame-Options DENY;
        add_header X-Content-Type-Options nosniff;
        add_header X-XSS-Protection "1; mode=block";
        
        # 限流
        limit_req zone=api burst=20 nodelay;
        
        # SSL配置
        listen 443 ssl http2;
        ssl_certificate /path/to/cert.pem;
        ssl_certificate_key /path/to/key.pem;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256;
        
        location / {
            # IP访问控制
            access_by_lua_block {
                local ip = ngx.var.remote_addr
                if ip == "192.168.1.100" then
                    ngx.exit(403)
                end
            }
            
            proxy_pass http://backend;
        }
    }
}

5.3 监控和日志配置

http {
    # 日志格式
    log_format json_log escape=json
        '{'
        '"time":"$time_iso8601",'  
        '"remote_addr":"$remote_addr",'  
        '"method":"$request_method",'  
        '"uri":"$uri",'  
        '"status":$status,'  
        '"body_bytes_sent":$body_bytes_sent,'  
        '"request_time":$request_time,'  
        '"upstream_response_time":"$upstream_response_time",'  
        '"user_agent":"$http_user_agent"'  
        '}';
    
    access_log logs/access.log json_log;
    
    # 状态监控
    server {
        listen 8080;
        server_name localhost;
        
        location /status {
            stub_status on;
            access_log off;
            allow 127.0.0.1;
            deny all;
        }
        
        location /lua-status {
            content_by_lua_block {
                local cache = ngx.shared.my_cache
                local stats = {
                    capacity = cache:capacity(),
                    free_space = cache:free_space(),
                    keys_count = #cache:get_keys(0)
                }
                
                ngx.header.content_type = "application/json"
                ngx.say(require("cjson").encode(stats))
            }
        }
    }
}

6. 总结

Nginx与Lua的集成是OpenResty的核心特性,通过合理配置各个处理阶段的Lua脚本,我们可以实现强大的Web应用功能。理解各个阶段的执行顺序和作用,掌握共享内存、变量操作等核心概念,是开发高性能OpenResty应用的基础。

7. 参考资料