OpenResty中lua-resty-http库的HTTPS请求实战与性能调优

📅 2026/7/14 15:40:30
OpenResty中lua-resty-http库的HTTPS请求实战与性能调优
1. 项目概述为什么我们需要lua-resty-http在OpenResty的生态里发起HTTP请求是再常见不过的需求了。无论是作为API网关去调用后端微服务还是作为反向代理去聚合多个上游接口甚至是实现一个简单的爬虫或Webhook推送都离不开HTTP客户端。你可能会问OpenResty不是基于Nginx的吗直接用Nginx的proxy_pass不就行了确实对于简单的反向代理proxy_pass是首选。但当你需要动态决定请求的目标、在请求前后执行复杂的Lua逻辑、处理响应体、或者实现请求的流水线化时原生的Nginx指令就显得力不从心了。这时候Lua代码的灵活性和强大就体现出来了。OpenResty提供了ngx.socket.tcp和ngx.socket.ssl这样的cosocket接口理论上你可以用它们从零开始拼装一个HTTP客户端。但相信我除非是教学目的否则没人愿意每次都去手动处理HTTP协议的状态机、分块传输编码、连接复用这些繁琐的细节。这正是lua-resty-http库存在的意义——它封装了底层的cosocket操作提供了一个符合直觉、功能完备的HTTP客户端让你能像在Node.js或Python里使用axios、requests一样在OpenResty中优雅地发起HTTP/HTTPS请求。这个库由ledgetech团队维护在GitHub上拥有超过2k的Star是OpenResty社区里最成熟、最受信赖的HTTP客户端库没有之一。它支持HTTP/1.0和1.1、完整的SSL/TLS包括mTLS、流式响应体处理、连接池与长连接、请求流水线、以及通过代理发起请求等高级特性。今天我们就来深入探讨如何利用lua-resty-http模块特别是针对HTTPS请求构建高效、可靠的网络通信逻辑。我会结合我多年在网关和微服务架构中的实战经验分享从基础使用到高级调优再到避坑指南的全套心得。2. 核心设计两种模式与连接管理哲学lua-resty-http的设计非常清晰它提供了两种不同抽象层次的API对应着两种编程模式简单单次请求和流式请求。理解这两种模式及其背后的连接管理哲学是高效使用这个库的关键。2.1 简单单次请求模式request_uri这种模式对应httpc:request_uri(uri, params)方法。它的特点是“一站式”服务你给它一个完整的URI和一些参数它帮你完成DNS解析、建立连接包括TCP和SSL握手、发送请求、接收响应包括状态行、头部和整个响应体最后根据情况关闭连接或将其放回连接池。整个过程中响应体会被完整地读取并缓存在内存中以字符串形式返回。适用场景请求响应体不大比如几KB到几百KB且逻辑简单不需要精细控制连接生命周期和内存使用的场景。例如调用一个返回JSON配置的服务、验证一个Token、或者发送一个简单的监控心跳。核心优势代码极其简洁无需关心连接的建立和关闭细节。库内部会智能地处理HTTP协议语义比如根据Connection头决定是否保持连接并利用OpenResty的连接池。local httpc require(“resty.http”).new() local res, err httpc:request_uri(“https://api.example.com/v1/user, { method “GET”, headers { [“Authorization”] “Bearer your_token_here”, [“Content-Type”] “application/json”, }, ssl_verify false, -- 注意生产环境不建议关闭验证 keepalive_timeout 60000, -- 连接池中保持60秒 }) if not res then ngx.log(ngx.ERR, “请求失败: “, err) return end -- res.body 已经是完整的响应字符串 local user_data cjson.decode(res.body)注意request_uri会缓冲整个响应体。如果响应体非常大比如几十MB的文件下载会瞬间消耗大量内存可能导致OpenResty工作进程内存暴涨甚至被OOM Killer终止。务必评估响应体大小。2.2 流式请求模式connectrequest这种模式将连接建立、请求发送、响应读取、连接关闭/复用这几个步骤解耦给了开发者最大的控制权。你需要先调用httpc:connect(options)建立连接包含可能的代理连接和SSL握手然后使用httpc:request(params)发送请求再通过res.body_reader迭代器流式读取响应体最后手动调用httpc:set_keepalive()或httpc:close()管理连接。适用场景处理大响应体比如下载文件、处理流式API如Server-Sent Events的初步读取、或者你只想读取响应头部就丢弃Body。请求流水线在同一连接上连续发送多个请求HTTP Pipelining减少TCP和SSL握手的开销。精细的超时控制可以为连接、发送、接收分别设置不同的超时时间。复用SSL会话通过ssl_session参数复用TLS会话加速后续的HTTPS连接建立。核心优势内存使用可控、性能潜力更高连接复用、流水线、灵活性极强。local httpc require(“resty.http”).new() httpc:set_timeouts(5000, 5000, 30000) -- 连接5s发送5s读取30s -- 1. 建立连接包含SSL握手 local ok, err, ssl_session httpc:connect({ scheme “https”, host “api.example.com”, port 443, ssl_verify true, }) if not ok then ngx.log(ngx.ERR, “连接失败: “, err) return end -- 2. 发送请求 local res, err httpc:request({ path “/v1/large_file”, method “GET”, headers { [“Host”] “api.example.com”, }, }) if not res then ngx.log(ngx.ERR, “请求失败: “, err) httpc:close() return end -- 3. 流式读取响应体 if res.status 200 then local reader res.body_reader local buffer_size 16384 -- 16KB缓冲区 repeat local chunk, err reader(buffer_size) if err then ngx.log(ngx.ERR, “读取响应体错误: “, err) break end if chunk then -- 处理每一块数据例如写入临时文件或进行流式处理 ngx.ctx.file_handle:write(chunk) end until not chunk -- chunk为nil时表示读取完毕 else -- 非200响应直接读取整个body通常不大 local body, err res:read_body() ngx.log(ngx.WARN, “请求失败状态码:”, res.status, “, 响应:”, body) end -- 4. 将连接放回连接池以供复用 local ok, err httpc:set_keepalive() if not ok then ngx.log(ngx.ERR, “设置keepalive失败: “, err) -- 可以选择关闭连接 httpc:close() end连接池的智能管理这是lua-resty-http特别是新版connect方法的一大亮点。传统的connect(host, port)方法生成的连接池键名只包含host:port。这在纯HTTP下没问题但在HTTPS或通过代理访问时就有大问题。想象一下你通过一个HTTP代理访问https://bank.com和https://evil.com如果它们都解析到同一个代理服务器的IP:Port那么连接池可能会错误地复用这两个完全不同的安全会话新版connect(options)方法通过将scheme协议、host、port、proxy_opts代理配置等信息共同哈希生成一个唯一的连接池名称从根本上杜绝了这种不安全复用这也是官方推荐使用新API的重要原因。3. HTTPS请求实战证书、验证与性能调优发起一个简单的HTTPS请求不难但要确保其安全、高效、稳定就需要关注很多细节。下面我们围绕HTTPS拆解几个核心实战要点。3.1 SSL证书验证安全的第一道防线默认情况下lua-resty-http的ssl_verify参数是true这意味着它会尝试验证对端服务器的证书。验证失败连接就会中止。这是保证通信安全、防止中间人攻击的关键。常见错误与排查 你在热词里看到的unexpected status 404 not found: unknown error, url: https://api.deepseek.com...这种错误表面是404但根源可能是SSL握手失败。OpenResty的Lua环境可能没有配置正确的CA证书包。解决方案指定CA证书路径在Nginx配置或connect参数中通过lua_ssl_trusted_certificate指令或ssl_ca_cert参数如果底层cosocket支持指定受信任的CA证书文件。最可靠的方法是在nginx.conf的http块中全局配置http { lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt; # 系统CA证书路径 lua_ssl_verify_depth 3; # ... 其他配置 }对于lua-resty-http你可以在connect的options里设置需要OpenResty版本支持local ok, err httpc:connect({ scheme “https”, host “api.example.com”, port 443, ssl_verify true, ssl_server_name “api.example.com”, -- SNI扩展必须与证书域名匹配 -- 注意lua-resty-http 当前版本connect参数未直接暴露ssl_ca_cert -- 依赖全局的lua_ssl_trusted_certificate配置。 })生产环境切勿关闭验证为了方便测试很多人会设置ssl_verify false。这在生产环境是极其危险的因为它使你的应用完全暴露在中间人攻击之下。测试环境可以使用自签证书并让OpenResty信任你的自签CA。自签证书场景如果你在内网使用自签证书需要将自签证书的CA公钥添加到信任链中。可以将CA公钥内容追加到lua_ssl_trusted_certificate指向的文件末尾。3.2 超时设置避免僵尸请求网络是不稳定的。没有合理的超时设置一个慢速的上游服务可能会拖死你所有的OpenResty工作进程。lua-resty-http提供了两套超时设置API。set_timeout(time)设置统一的超时适用于所有socket操作连接、发送、接收。简单但不够精细。set_timeouts(connect_timeout, send_timeout, read_timeout)强烈推荐使用这个。你可以为不同阶段设置不同的超时。connect_timeoutTCP连接和SSL握手阶段。对于HTTPSSSL握手可能较慢尤其是在会话不复用的情况下可以设置稍长如30003秒。send_timeout发送请求头和数据的时间。通常很快20002秒足够。read_timeout从服务器读取响应数据包括头部和body的时间。这个值需要根据业务调整。如果下载大文件需要设置得很长如3000005分钟如果只是调用一个快速API50005秒即可。local httpc require(“resty.http”).new() -- 精细化的超时控制 httpc:set_timeouts(3000, 2000, 10000) -- 连接3秒发送2秒读取10秒 local res, err httpc:request_uri(“https://api.slow.com/data”, { method “GET”, }) if not res then -- 错误可能是超时也可能是网络错误 ngx.log(ngx.ERR, “请求失败可能原因:”, err) -- 可以根据err信息判断例如”timeout” if string.find(err, “timeout”) then -- 处理超时逻辑如重试或返回降级内容 ngx.status 504 ngx.say(“{“code”: 504, “msg”: “Upstream timeout”}“) return end end3.3 连接复用与性能优化HTTP/1.1的Keep-Alive和连接池是提升性能的利器。lua-resty-http完美地集成了OpenResty的cosocket连接池。关键参数pool自定义连接池名称。除非有特殊需求如按用户隔离连接否则让库自动生成是最安全的选择特别是对于HTTPS。pool_size连接池的大小。默认是lua_socket_pool_size通常为30。这个值决定了每个工作进程对每个目标scheme://host:port 代理等组合最多保持多少个空闲连接。设置太小无法充分利用长连接设置太大浪费内存。需要根据实际并发度和上游服务情况调整。keepalive_timeout连接在池中空闲的最大时间毫秒。超过这个时间连接会被关闭。默认是lua_socket_keepalive_timeout。最佳实践总是尝试复用连接在流式请求模式的最后使用set_keepalive()而不是close()。理解set_keepalive的返回值它可能返回1成功放入池中、nil出错、或者2因为协议要求必须关闭连接如HTTP/1.0请求没有Connection: keep-alive头。对于返回值2这是正常行为不是错误。监控连接池可以通过OpenResty的ngx.status模块或定时打印日志来观察连接池的使用情况避免连接泄漏即创建了连接但未关闭或放回池中。连接泄漏排查如果你发现工作进程的socket数量持续增长很可能是连接泄漏。确保在所有代码路径正常、异常、提前返回上都正确调用了set_keepalive或close。可以使用get_reused_times()方法检查当前连接被复用的次数如果一直是0说明都是新建连接可能复用没生效。4. 高级特性与实战场景解析掌握了基础我们来看看lua-resty-http的一些高级玩法这些特性能在特定场景下发挥巨大威力。4.1 通过HTTP/HTTPS代理发起请求在企业网络环境中出站流量经常需要经过代理服务器。lua-resty-http通过set_proxy_options方法提供了原生支持。local httpc require(“resty.http”).new() -- 设置代理配置 httpc:set_proxy_options({ http_proxy “http://proxy.corp.com:8080”, -- HTTP请求使用的代理 https_proxy “http://proxy.corp.com:8080”, -- HTTPS请求使用的代理CONNECT隧道 https_proxy_authorization “Basic ” .. ngx.encode_base64(“user:pass”), -- 代理认证 no_proxy “localhost,127.0.0.1,internal.api.corp.com”, -- 绕过代理的列表 }) -- 后续的connect和request会自动使用代理 local ok, err httpc:connect({ scheme “https”, host “external.api.com”, port 443, }) -- 此时实际连接会先建立到 proxy.corp.com:8080然后发起CONNECT请求建立到external.api.com:443的隧道重要提示代理功能仅在使用了新的connect(options)方法时才生效。使用旧的connect(host, port)签名无法自动启用代理。4.2 请求流水线HTTP Pipelining允许在同一个连接上在前一个请求的响应被完全接收之前就发送下一个请求。这可以减少延迟特别是在高延迟网络上。lua-resty-http通过request_pipeline方法支持。local httpc require(“resty.http”).new() local ok, err httpc:connect({ scheme “https”, host “api.example.com”, port 443, }) if not ok then ngx.log(ngx.ERR, “连接失败:”, err) return end -- 一次性发送三个请求 local responses, err httpc:request_pipeline({ { path “/api/users/1”, method “GET” }, { path “/api/users/2”, method “GET” }, { path “/api/users/3”, method “GET” }, }) if not responses then ngx.log(ngx.ERR, “流水线请求失败:”, err) httpc:close() return end -- 必须按顺序读取响应 for i, res in ipairs(responses) do if not res.status then ngx.log(ngx.ERR, “第”, i, “个请求读取失败可能socket错误”) break -- 后续响应也无法读取了 end ngx.log(ngx.INFO, “用户”, i, “状态码:”, res.status) local body, err res:read_body() -- 读取整个body -- 处理body... end httpc:set_keepalive()注意事项服务器必须支持HTTP Pipelining。虽然HTTP/1.1规范支持但很多服务器和代理实现得不好或者默认关闭。使用前需要测试。响应必须严格按照请求发送的顺序读取。你不能跳过第一个响应直接读第二个。如果某个请求的响应体没有被完整读取比如只读了头部那么该连接将处于不可用状态无法用于后续请求。务必确保每个响应的body都被消费掉。4.3 流式上传与下载我们之前展示了流式下载。流式上传同样重要特别是上传大文件时可以避免在内存中组装整个请求体。-- 假设我们有一个生成大文件内容的迭代器函数 local function large_file_reader(chunk_size) local offset 0 local total_size 1024 * 1024 * 100 -- 100MB return function() if offset total_size then return nil end local chunk string.rep(“A”, math.min(chunk_size, total_size - offset)) offset offset #chunk return chunk end end local httpc require(“resty.http”).new() httpc:set_timeouts(5000, 30000, 30000) -- 发送和读取超时设长 local ok, err httpc:connect({ scheme “https”, host “upload.example.com”, port 443, }) if not ok then ngx.log(ngx.ERR, “连接失败:”, err) return end -- 使用迭代器作为body实现流式上传 local res, err httpc:request({ path “/upload”, method “POST”, headers { [“Host”] “upload.example.com”, [“Content-Type”] “application/octet-stream”, [“Transfer-Encoding”] “chunked”, -- 必须使用分块编码 }, body large_file_reader(8192), -- 8KB每块 }) if not res then ngx.log(ngx.ERR, “上传请求失败:”, err) httpc:close() return end -- 读取响应... local body, err res:read_body() httpc:set_keepalive()关键点当使用迭代器作为body时必须设置Transfer-Encoding: chunked请求头因为库无法预先知道body的总长度。服务器需要支持分块传输编码。4.4 处理重定向lua-resty-http本身不自动处理HTTP重定向3xx状态码。这是一个设计上的考量因为自动重定向可能带来循环重定向、POST请求被错误地转为GET等问题。你需要手动实现。local function request_with_redirect(httpc, uri, params, max_redirects) max_redirects max_redirects or 5 local redirect_count 0 while redirect_count max_redirects do local res, err httpc:request_uri(uri, params) if not res then return nil, err end if res.status 300 and res.status 400 then local location res.headers[“Location”] or res.headers[“location”] if not location then return nil, “redirect with no location header” end -- 处理相对路径和绝对路径 if not string.find(location, “^https?://”) then -- 简单的相对路径处理实际应用可能需要更复杂的URL拼接 local parsed httpc:parse_uri(uri) if string.find(location, “^/”) then location parsed[1] .. “://” .. parsed[2] .. location else -- 处理相对路径这里简化处理 return nil, “relative redirect not fully supported” end end ngx.log(ngx.INFO, “Following redirect to: “, location) uri location -- 对于POST等非幂等方法重定向时应转为GET根据RFC if params.method and params.method ~ “GET” and params.method ~ “HEAD” then if res.status 301 or res.status 302 or res.status 303 then params.method “GET” params.body nil -- 移除请求体 if params.headers then params.headers[“Content-Length”] nil end end end redirect_count redirect_count 1 else -- 不是重定向返回结果 return res end end return nil, “too many redirects” end -- 使用示例 local httpc require(“resty.http”).new() local res, err request_with_redirect(httpc, “https://short.url/abc”, {method “GET”})5. 生产环境避坑指南与性能调优纸上得来终觉浅绝知此事要躬行。下面这些坑都是我或者我的团队在实际项目中踩过的希望能帮你绕过去。5.1 DNS解析问题OpenResty的cosocket默认是阻塞式解析DNS的并且不缓存DNS结果。这意味着每次connect如果host是域名都可能触发一次DNS查询在高并发下会对DNS服务器造成压力并增加请求延迟。解决方案使用OpenResty的resty.dns.resolver模块这是一个非阻塞的DNS解析器支持缓存。你可以先解析出IP再用IP去连接。但这增加了代码复杂度且需要处理DNS TTL过期和多个IP的负载均衡。在Nginx层面配置resolver指令并开启缓存这是更推荐的方式。在nginx.conf的http、server或location块中配置http { resolver 8.8.8.8 114.114.114.114 valid300s; # 指定DNS服务器缓存300秒 resolver_timeout 5s; # ... 其他配置 }配置后cosocket会使用Nginx的异步DNS解析器并享受缓存。valid300s表示缓存有效期300秒。直接使用IP地址对于内部服务可以直接配置IP绕过DNS。但失去了灵活性且需要自己处理服务发现和负载均衡。5.2 连接池竞争与“连接风暴”假设你有一个突发流量场景瞬间有1000个请求需要访问同一个上游HTTPS服务。如果连接池大小pool_size是30那么前30个请求会创建新连接并放入池中。后面的970个请求会尝试从池中获取空闲连接。如果前面的请求处理很慢连接没有及时释放那么大量请求会等待获取连接导致排队延迟甚至超时。优化策略合理设置pool_size不要盲目设大。需要根据上游服务的并发处理能力和网络状况来定。可以通过压测找到一个平衡点。通常建议设置为每个工作进程允许的最大并发上游请求数。设置backlog参数在connect的options中可以设置backlog。当连接池满且无空闲连接时新的connect调用会等待直到有连接释放或超时。backlog指定了等待队列的长度。超过长度connect会立即返回nil和错误connection pool is full。local ok, err httpc:connect({ scheme “https”, host “api.example.com”, port 443, pool_size 50, backlog 100, -- 等待队列长度 })实现请求队列或降级在应用层当检测到上游连接池满或请求超时率升高时可以实施限流、快速失败或返回缓存数据等降级策略。5.3 响应头大小限制OpenResty对单个请求或响应的头部大小有默认限制通过lua_socket_buffer_size配置默认是4k或8k。如果上游服务器返回的响应头非常大比如Set-Cookie很多可能会导致头部读取不完整解析错误。解决方案在nginx.conf中适当调大lua_socket_buffer_size。http { lua_socket_buffer_size 128k; # 根据实际情况调整 # ... 其他配置 }5.4 错误处理与重试机制网络请求天生不可靠。完善的错误处理和重试机制是保障系统韧性的关键。常见错误类型连接错误connect失败错误信息可能是connection refused,timeout,ssl handshake failed等。发送错误request失败可能是网络中断。读取错误body_reader或read_body失败可能是连接被对端重置。协议错误如无效的HTTP响应。一个简单的带重试的封装函数local function request_with_retry(httpc, uri, params, max_retries) max_retries max_retries or 3 local last_err for i 1, max_retries do local res, err httpc:request_uri(uri, params) if res then return res -- 成功直接返回 end last_err err ngx.log(ngx.WARN, “请求失败(尝试”, i, “/”, max_retries, “): “, err) -- 判断错误类型决定是否重试 -- 例如连接被拒绝、超时、SSL握手失败可以重试 -- 但像”host not found”这种DNS错误重试可能没用 if string.find(err, “timeout”) or string.find(err, “connection refused”) or string.find(err, “ssl handshake”) then if i max_retries then ngx.sleep(0.1 * i) -- 指数退避 end else -- 其他错误不重试 break end end return nil, “all retries failed: “ .. (last_err or “unknown”) end更高级的策略可以考虑结合断路器模式如lua-resty-circuitbreaker当上游服务失败率达到阈值时自动熔断直接返回降级内容避免雪崩。5.5 日志与监控在生产环境必须对HTTP客户端的调用情况进行监控。日志记录记录关键信息如请求耗时、状态码、响应大小。可以使用ngx.now()计算耗时。local start_time ngx.now() local res, err httpc:request_uri(“https://api.example.com”) local end_time ngx.now() local cost (end_time - start_time) * 1000 -- 毫秒 ngx.log(ngx.INFO, “上游调用: uri”, uri, “, status”, (res and res.status or “nil”), “, cost”, cost, “ms, err”, (err or “nil”))指标上报将耗时、状态码等信息上报到监控系统如Prometheus。可以定义几个关键的指标upstream_request_duration_seconds直方图、upstream_request_total计数器按状态码分类、upstream_request_failed_total计数器。链路追踪在微服务架构中将OpenResty发起的请求也纳入分布式追踪如Jaeger、SkyWalking注入Trace ID。6. 综合案例构建一个健壮的上游API调用模块最后我们整合以上所有知识点编写一个用于生产环境的、健壮的上游API调用模块。-- file: lib/resty/upstream_client.lua local http require “resty.http” local cjson require “cjson.safe” local _M { _VERSION ‘0.1’ } local mt { __index _M } function _M.new(self, opts) opts opts or {} local client { timeout_connect opts.timeout_connect or 3000, timeout_send opts.timeout_send or 5000, timeout_read opts.timeout_read or 10000, max_retries opts.max_retries or 2, keepalive_timeout opts.keepalive_timeout or 60000, keepalive_pool opts.keepalive_pool or 30, ssl_verify opts.ssl_verify, proxy_opts opts.proxy_opts, } return setmetatable(client, mt) end function _M.request(self, method, url, req_body, req_headers) local httpc http.new() if not httpc then return nil, “failed to create http client” end -- 设置超时 httpc:set_timeouts(self.timeout_connect, self.timeout_send, self.timeout_read) -- 设置代理如果有 if self.proxy_opts then httpc:set_proxy_options(self.proxy_opts) end local parsed httpc:parse_uri(url) if not parsed then return nil, “invalid url: “ .. url end local scheme, host, port, path_with_query unpack(parsed) local params { method method, headers req_headers or {}, keepalive_timeout self.keepalive_timeout, keepalive_pool self.keepalive_pool, } if req_body then if type(req_body) “table” then params.body cjson.encode(req_body) params.headers[“Content-Type”] “application/json” else params.body req_body end params.headers[“Content-Length”] #params.body end -- 合并SSL验证配置 local connect_opts { scheme scheme, host host, port port, ssl_verify self.ssl_verify, } local last_err for retry 1, self.max_retries do local res, req_err httpc:request_uri(url, params) if res then -- 成功处理响应 local ok, json_body if res.headers[“Content-Type”] and string.find(res.headers[“Content-Type”], “application/json”) then json_body, err cjson.decode(res.body) if not json_body then ngx.log(ngx.ERR, “failed to decode json response: “, err, “ body: “, string.sub(res.body, 1, 500)) end end return { status res.status, headers res.headers, body res.body, json json_body, } else last_err req_err ngx.log(ngx.WARN, string.format(“upstream request failed (retry %d/%d): %s”, retry, self.max_retries, req_err)) -- 判断是否可重试的错误 if self:_should_retry(req_err) and retry self.max_retries then ngx.sleep(0.05 * (2 ^ (retry - 1))) -- 指数退避 else break end end end return nil, “request failed after retries: “ .. (last_err or “unknown”) end function _M._should_retry(self, err) -- 定义哪些错误需要重试 if not err then return false end if string.find(err, “timeout”) then return true end if string.find(err, “connection refused”) then return true end if string.find(err, “closed”) then return true end -- SSL握手错误可能重试也无用但可以尝试一次 if string.find(err, “handshake”) and not string.find(err, “certificate”) then return true end return false end return _M使用示例local upstream_client require “resty.upstream_client” local client upstream_client:new({ timeout_connect 2000, timeout_read 8000, max_retries 1, ssl_verify false, -- 测试环境 }) local res, err client:request(“POST”, “https://api.service.com/data”, { key “value” }, { [“X-Api-Key”] “your-api-key”, }) if not res then ngx.log(ngx.ERR, “调用上游服务失败: “, err) ngx.status 502 ngx.say(“{“error”: “upstream_unavailable”}“) return end if res.status 200 and res.status 300 then ngx.say(cjson.encode({ success true, data res.json })) else ngx.log(ngx.WARN, “上游服务返回错误: “, res.status, “ body: “, res.body) ngx.status res.status ngx.say(res.body) end这个模块封装了超时、重试、JSON编解码、错误分类等通用逻辑可以作为项目中进行上游HTTP调用的基础组件。当然根据实际需求你还可以为其增加熔断器、指标上报、更精细的日志等特性。