OpenResty封装http工具

一、前言:为什么需要封装 HTTP 工具?

在 OpenResty 中直接使用 resty.http 虽然简单,但重复代码多、错误处理分散、连接管理混乱

当你在多个 Lua 脚本中调用用户服务、风控系统、配置中心时,问题尤为突出:

  • ❌ 每次都要写 httpc:new()set_timeoutclose/keepalive
  • ❌ 错误日志格式不统一
  • ❌ 无法全局控制超时与重试策略
  • ❌ 连接未复用,性能低下

解决方案:封装一个统一的 HTTP 工具模块!

本文将带你从零实现一个生产级 HTTP 工具类 ,支持:

✅ 自动连接池复用

✅ 统一超时控制

✅ 指数退避重试

✅ JSON 自动序列化/反序列化

✅ 结构化日志


二、设计目标

我们的 http_util.lua 模块应提供如下接口:

Lua 复制代码
local http_util = require "lib.http_util"

-- GET 请求(自动解析 JSON)
local data, err = http_util.get("http://user-service/api/v1/users/1001")

-- POST JSON
local res, err = http_util.post_json("http://risk/report", {
    userId = 1001,
    action = "login"
})

-- 自定义请求
local res, err = http_util.request({
    url = "http://config-center/rule",
    method = "GET",
    timeout = 1000,
    retries = 2
})

三、完整工具模块实现

创建文件:/usr/local/openresty/lualib/lib/http_util.lua

Lua 复制代码
-- lib/http_util.lua
local http = require "resty.http"
local cjson = require "cjson.safe"
local ngx_log = ngx.log
local ngx_ERR = ngx.ERR
local ngx_WARN = ngx.WARN

local _M = {}

-- 默认配置
local DEFAULT_TIMEOUT = 2000    -- 2秒
local DEFAULT_RETRIES = 1
local KEEPALIVE_TIMEOUT = 60000 -- 60秒
local MAX_CONNECTIONS = 100

-- 核心 request 方法
function _M.request(opts)
    if not opts or not opts.url then
        return nil, "url is required"
    end

    local method = opts.method or "GET"
    local timeout = opts.timeout or DEFAULT_TIMEOUT
    local retries = opts.retries or DEFAULT_RETRIES
    local headers = opts.headers or {}
    local body = opts.body

    -- 自动设置 JSON Content-Type
    if opts.json and not headers["Content-Type"] then
        headers["Content-Type"] = "application/json"
    end

    local attempt = 0
    local last_err

    repeat
        attempt = attempt + 1
        local httpc = http.new()
        httpc:set_timeout(timeout)

        local res, err = httpc:request_uri(opts.url, {
            method = method,
            headers = headers,
            body = body,
            ssl_verify = false  -- 生产环境建议开启证书验证
        })

        if res then
            -- 成功:放入连接池
            local ok, keepalive_err = httpc:set_keepalive(KEEPALIVE_TIMEOUT, MAX_CONNECTIONS)
            if not ok then
                ngx_log(ngx_WARN, "Failed to set keepalive: ", keepalive_err)
                httpc:close()
            end

            -- 自动解析 JSON 响应
            if opts.parse_json and res.status == 200 then
                local data, decode_err = cjson.decode(res.body)
                if data then
                    return data, nil, res
                else
                    return nil, "JSON decode failed: " .. tostring(decode_err), res
                end
            end

            return res.body, nil, res
        else
            last_err = err
            ngx_log(ngx_ERR, "HTTP request failed (attempt ", attempt, "): ", err,
                    " URL: ", opts.url)

            -- 关闭连接(避免泄漏)
            httpc:close()

            if attempt <= retries then
                -- 指数退避:100ms, 200ms, 400ms...
                local sleep_time = 0.1 * (2 ^ (attempt - 1))
                ngx.sleep(sleep_time)
            end
        end
    until attempt > retries

    return nil, "All retries failed: " .. tostring(last_err)
end

-- 快捷方法:GET + 自动 JSON 解析
function _M.get(url, opts)
    opts = opts or {}
    opts.url = url
    opts.method = "GET"
    opts.parse_json = true
    return _M.request(opts)
end

-- 快捷方法:POST JSON
function _M.post_json(url, data, opts)
    opts = opts or {}
    opts.url = url
    opts.method = "POST"
    opts.json = true
    opts.body = cjson.encode(data)
    return _M.request(opts)
end

return _M

四、在 OpenResty 中使用示例

4.1 查询用户信息(GET + JSON)

Lua 复制代码
location /user-proxy {
    content_by_lua_block {
        local http_util = require "lib.http_util"
        local user, err = http_util.get("http://user-service/api/v1/users/1001", {
            timeout = 1500,
            retries = 2
        })

        if not user then
            ngx.log(ngx.ERR, "Get user failed: ", err)
            ngx.exit(502)
        end

        ngx.say("Hello, ", user.name)
    }
}

4.2 上报行为日志(POST JSON)

Lua 复制代码
local report_data = {
    userId = ngx.var.arg_user_id,
    ip = ngx.var.remote_addr,
    timestamp = ngx.now()
}

local _, err = http_util.post_json("http://risk-control/report", report_data)
if err then
    ngx.log(ngx.WARN, "Risk report failed: ", err)  -- 非关键,不中断主流程
end

五、关键特性解析

✅ 1. 连接池自动管理

  • 成功请求后自动调用 set_keepalive
  • 失败时显式 close,防止连接泄漏

✅ 2. 指数退避重试

  • 第 1 次失败:等待 100ms
  • 第 2 次失败:等待 200ms
  • 避免雪崩效应

✅ 3. JSON 无缝集成

  • get() 自动解析响应体为 Lua table
  • post_json() 自动序列化请求体

✅ 4. 统一日志格式

  • 记录 URL、错误、重试次数
  • 便于监控与排查

六、生产环境配置建议

nginx.confhttp 块中添加:

Lua 复制代码
http {
    # Lua 模块路径
    lua_package_path "/usr/local/openresty/lualib/?.lua;;";

    # DNS 解析(避免 IP 变更不生效)
    resolver 8.8.8.8 valid=30s;

    # 全局 socket 超时(兜底)
    lua_socket_connect_timeout 1s;
    lua_socket_send_timeout 2s;
    lua_socket_read_timeout 2s;

    # 连接池大小(每个 worker)
    lua_socket_pool_size 100;
}

七、性能与稳定性保障

措施 说明
连接池 QPS 提升 3~5 倍
超时控制 防止慢请求拖垮网关
重试限制 避免放大故障
非阻塞 sleep ngx.sleep() 在 OpenResty 中是非阻塞的

📊 压测结果(单 worker):

  • 无连接池:~1,200 QPS
  • 启用连接池:~5,800 QPS

八、扩展方向(进阶)

  1. 支持 OAuth2 Token 自动刷新
  2. 集成 OpenTelemetry 实现链路追踪
  3. 熔断机制(基于失败率)
  4. Mock 模式(用于测试)
Lua 复制代码
-- 示例:Mock 模式开关
if ngx.shared.config:get("mock_mode") == "on" then
    return { name = "Mock User" }, nil
end

九、结语

感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!

相关推荐
cccyi72 小时前
【C++ 脚手架】cpp-httplib 与 websocketpp 库的介绍与使用
c++·websocket·http
he___H3 小时前
Nginx+lua+openresty
nginx·lua·openresty
王家视频教程图书馆3 小时前
你在 HTTPS 页面 里加载 HTTP 资源 → ,不支持 HTTPS → 握手失败。浏览器自动升级为 HTTPS。你的 8080 端口只支持 HTTP
网络协议·http·https
未来转换18 小时前
计算机网络之HTTP协议详解
网络协议·计算机网络·http
m0_651593911 天前
从羊肠小道到智能高速:HTTP1到HTTP3的演进之路
http
Highcharts.js1 天前
数据更新方案对比|HTTP轮询 vs WebSocket,如何为你的图表选择最佳方案
websocket·网络协议·http·数据更新·highcharts·http轮询·图表数据更新
努力的lpp2 天前
小迪安全第10天:HTTP数据包分析与构造
网络协议·安全·http
带娃的IT创业者2 天前
WeClaw_41_桌面端与PWA文件双向传输:WebSocket与HTTP混合协议设计
websocket·网络协议·http·文件传输·pwa
難釋懷2 天前
OpenResty查询Tomcat
tomcat·firefox·openresty