lcurl

版本信息

项目内容
组件版本1.130.3
首发版本1.130.1
文档作者openUBMC 社区
最后更新2026-09-13

1. 组件概述

1.1 组件简介

lcurl 是 openUBMC 中的 Lua 动态库组件,将 libcurl easy API 封装为 Lua 模块 lcurl.core。Lua 业务进程通过 require('lcurl.core') 加载 /opt/bmc/luaclib/lcurl.so,即可在进程内完成 HTTP(S)、FTP、TFTP 等传输,以及文件上传、下载、HTTP 头、multipart 表单和 TLS 证书配置。

组件不启动独立服务进程、不注册 D-Bus 资源协作接口,也不提供 IPMI 命令。它只负责 Lua 与 curl C API 之间的参数转换、句柄管理以及回调转发。

1.2 解决什么问题

openUBMC 的业务代码大量使用 Lua,而网络请求通常需要调用 C 扩展或外部命令。lcurl 提供稳定的 Lua 函数接口,使业务代码可以直接配置并执行 curl 传输,减少进程切换和重复封装工作,并通过标准 CURLcodeeasy_strerror 统一错误处理方式。

1.3 核心功能

  • 全局初始化和清理:global_initglobal_cleanup
  • easy 句柄生命周期:easy_initeasy_performeasy_cleanup
  • 请求选项配置:URL、认证、HTTP 方法、POST 数据、超时、范围下载、网卡绑定、TLS 校验、证书/私钥、TFTP 块大小等。
  • 数据处理:Lua 回调接收响应头/响应体,或将数据读写到 Lua 文件句柄。
  • HTTP 头和 multipart:使用 slist_* 构造请求头,使用 mime_* 构造表单和文件上传。
  • 传输结果查询:HTTP 状态码、Content-Length、实际下载字节数。
  • 错误描述:将 curl 返回码转换为可读字符串。

1.4 关键术语表

术语解释
lcurlLua 与 libcurl easy API 之间的绑定模块。
easy handle一次传输及其选项、回调和结果的上下文。
CURLcodelibcurl 返回码;0 表示成功。
C 模块可在 Lua 中通过 require 加载的共享库。
slist / mimeHTTP 头链表和 multipart 表单构造对象。

1.5 外部交互边界图

2. API 使用说明与示例

2.1 模块加载、初始化与句柄生命周期

功能说明

lua
local curl_handle = curl.easy_init()
if curl_handle == nil then
    log:error('get curl handler failed')
    return '{}'
end
return curl_handle

参数说明

函数参数返回值说明
easy_init()handle 或 nil成功返回 easy userdata;底层 curl_easy_init 失败时无返回值,在 Lua 中表现为 nil

返回值与异常

初始化成功返回 easy handle;失败通常表现为 nil,调用方应检查返回值并记录错误。

应用场景

用于创建、使用和释放独立传输句柄,是其他 lcurl API 的前置步骤。

限制条件

同一 handle 不应在多个 Lua 线程中并发配置、执行或释放。

调试示例

lua
assert(curl.global_init() == 0)
local h = assert(curl.easy_init())
-- 配置并执行请求
curl.easy_cleanup(h)
curl.global_cleanup()

2.2 请求选项接口

功能说明

提供 URL、认证、HTTP 方法、超时、上传下载和 TLS 等请求选项的 Lua 封装。

参数说明

所有 easy_setopt_* 函数的第一个参数是 easy handle,第二个参数是选项值。常用接口如下。

Lua 函数对应选项参数类型用途
easy_setopt_urlCURLOPT_URLstring设置请求 URL。
easy_setopt_userpwdCURLOPT_USERPWDstring设置 user:password Basic 认证。
easy_setopt_headerCURLOPT_HEADERinteger设为 1 时将响应头并入输出数据。
easy_setopt_nobodyCURLOPT_NOBODYinteger设为 1 时只获取响应头。
easy_setopt_verboseCURLOPT_VERBOSEinteger设为 1 时输出 curl 详细传输信息到 stderr。
easy_setopt_connecttimeoutCURLOPT_CONNECTTIMEOUTinteger连接超时时间,单位为秒。
easy_setopt_timeoutCURLOPT_TIMEOUTinteger整个传输超时时间,单位为秒。
easy_setopt_rangeCURLOPT_RANGEstring设置范围下载,如 "0-99"
easy_setopt_uploadCURLOPT_UPLOADinteger设为 1 开启上传模式。
easy_setopt_int_file_sizeCURLOPT_INFILESIZEinteger设置上传数据大小,单位为字节。
easy_setopt_forbid_reuseCURLOPT_FORBID_REUSEinteger设为 1 禁止连接复用。
easy_setopt_nosignalCURLOPT_NOSIGNALinteger设为 1 禁止使用信号;多线程场景建议开启。
easy_setopt_address_scopeCURLOPT_ADDRESS_SCOPEinteger设置 IPv6 地址 scope。
easy_setopt_customerquestCURLOPT_CUSTOMREQUESTstring设置自定义方法,如 GETDELETE
easy_setopt_interfaceCURLOPT_INTERFACEstring绑定本地网卡或本地 IP。
easy_setopt_postfieldsCURLOPT_POSTFIELDSstring设置 POST 请求体。
easy_setopt_maxfilesizeCURLOPT_MAXFILESIZEinteger设置接收文件大小上限。
easy_setopt_tftp_blksizeCURLOPT_TFTP_BLKSIZEinteger设置 TFTP 块大小。

TLS 相关接口如下:

Lua 函数对应选项参数类型用途
easy_setopt_ssl_verifyhostCURLOPT_SSL_VERIFYHOSTintegerHTTPS 主机名校验;常用值为 2(校验)或 0(关闭)。
easy_setopt_ssl_verifypeerCURLOPT_SSL_VERIFYPEERintegerHTTPS 对端证书校验;0 表示关闭。
easy_setopt_sslcertCURLOPT_SSLCERTstring客户端证书文件路径。
easy_setopt_sslkeyCURLOPT_SSLKEYstring客户端私钥文件路径。
easy_setopt_cainfoCURLOPT_CAINFOstringCA 证书文件路径。
easy_setopt_crlfileCURLOPT_CRLFILEstringCRL 吊销列表文件路径。
easy_setopt_sslcert_blobCURLOPT_SSLCERT_BLOBstring直接传入客户端证书内容。
easy_setopt_sslkey_blobCURLOPT_SSLKEY_BLOBstring直接传入客户端私钥内容。

返回值与异常

成功通常返回 0,句柄无效返回 -1,其他值为 libcurl CURLcode。类型不匹配会由 Lua 参数检查直接抛出错误。

应用场景

用于配置 HTTP(S)、FTP、TFTP 等请求的连接、认证、超时、上传下载及 TLS 参数。

限制条件

TLS 校验、证书和私钥等安全相关选项必须与目标服务配置一致;生产环境不建议关闭证书或主机名校验。

调试示例

lua
local curl = require('lcurl.core')
// 检查 curl.global_init() 是否为0
local h = assert(curl.easy_init())
// 检查 curl.easy_setopt_url(h, 'https://example.com/api') 是否为0
// 检查 curl.easy_setopt_connecttimeout(h, 5) 是否为0
// 检查 curl.easy_setopt_timeout(h, 30) 是否为0
// 检查 curl.easy_setopt_nosignal(h, 1) 是否为0
local code = curl.easy_perform(h)
if code ~= 0 then
    error(curl.easy_strerror(code))
end
curl.easy_cleanup(h)
curl.global_cleanup()

2.3 回调、文件读写与结果查询

功能说明

提供响应回调和文件写入绑定,以及 HTTP 状态码、Content-Length 和下载大小查询。

参数说明

Lua 函数参数返回值说明
easy_setopt_writefunction(handle, fn)handle、Lua 函数CURLcode每收到一段响应体即调用 fn(data)
easy_setopt_headerfunction(handle, fn)handle、Lua 函数CURLcode每收到一段响应头即调用 fn(data)
easy_setopt_read_data(handle, file)handle、Lua 文件句柄CURLcode将上传数据源设置为 io.open 返回的文件。
easy_setopt_write_data(handle, file)handle、Lua 文件句柄CURLcode将响应写入 Lua 文件句柄。
easy_getinfo_response_code(handle)handleret, code获取 HTTP 状态码。
easy_get_http_code(handle)handleret, codeeasy_getinfo_response_code 的别名。
easy_getinfo_content_length_download(handle)handleret, length获取服务端声明的 Content-Length。
easy_getinfo_size_download(handle)handleret, size获取实际下载字节数。

返回值与异常

设置接口返回 CURLcode,信息查询接口返回状态值和结果;句柄或参数无效时通常返回 -1

应用场景

用于流式处理响应体、将下载内容写入文件,以及查询传输结果。

调试示例

回调由 C 层保存到 Lua 注册表,并在创建句柄时记录的主线程 lua_State 上执行。回调参数是一个字符串分块,不保证一次回调对应完整的 HTTP 消息。

以下示例用于将下载内容写入文件。

lua
lCurl *c = (lCurl *)lua_touserdata(L, 1);
if (c == NULL) {
  lua_pushinteger(L, -1);
  return 1;
}
luaL_Stream *v = (luaL_Stream *)lua_touserdata(L, 2);
if (v == NULL) {
  lua_pushinteger(L, -1);
  return 1;
}
FILE *fp = v->f;
CURLcode ret = curl_easy_setopt(c->curl, CURLOPT_WRITEDATA, fp);
lua_pushinteger(L, ret);
return 1;

限制条件

  • lua_touserdata 只判 NULL,不校验 userdata 类型。传入其他类型的完整 userdata 或 light userdata 时返回 NULL,代码会静默 push -1,而不是 luaL_error,掩盖错误。
  • CURLOPT_WRITEDATA必须与 CURLOPT_WRITEFUNCTION 配对,若不设置 WRITEFUNCTION=fwrite,WRITEDATA 只会作为回调参数传入,不会真正生效。
  • fp 必须以写模式(wb)打开;只读打开时 fwrite 会失败,fp 必须存活到 curl_easy_perform 结束。该绑定未登记所有权,Lua 侧若先 GC/关闭该 stream 再 perform,会 use-after-free。
  • CURLcode 是枚举:CURLE_OK=0 表示成功,非 0 为错误码。Lua 侧须判断 CURLcode 是否为0。

2.4 HTTP 头与 multipart/form-data

功能说明

提供 HTTP 头链表和 multipart 表单构造能力,用于设置请求头和文件上传表单。

参数说明

HTTP 头
lua
local headers = curl.slist_init()
headers = curl.slist_append(headers, 'Content-Type: application/json')
headers = curl.slist_append(headers, 'Accept: application/json')
// 检查 curl.easy_setopt_httpheader(h, headers) 是否为0
函数参数返回值说明
slist_init()slist userdata创建空的 HTTP 头链表。
slist_append(slist, value)slist、字符串slist 指针;无效参数返回 -1追加一项 Header: value。返回值应继续保存并传给 easy_setopt_httpheader
easy_setopt_httpheader(handle, slist)handle、slistCURLcode将头链表绑定到 easy 句柄。

返回值与异常

slist_appendmime_* 成功时返回对象或 CURLcode,无效参数通常返回 -1

绑定后,easy 句柄在 easy_cleanup 中释放其记录的 curl_slist。当前没有独立的 slist_free 接口,未绑定到 easy 句柄的链表不应长期创建。

应用场景

用于 JSON API 请求头、文件上传和 multipart 表单提交。

限制条件

头链表不应长期脱离 easy handle;mime_free 必须由调用方显式执行。

调试示例

multipart 表单
lua
local mime = curl.mime_init(h)
local part = curl.mime_addpart(mime)
// 检查 curl.mime_name(part, 'file') 是否为0
// 检查 curl.mime_filedata(part, '/tmp/test.bin') 是否为0
// 检查 curl.easy_setopt_mimepost(h, mime) 是否为0
local ret = curl.easy_perform(h)
curl.mime_free(mime)
// 检查 ret 是否为 0, 若为0,则抛出 curl.easy_strerror(ret)
函数参数返回值说明
mime_init(handle)handlemime 指针;无效 handle 返回 -1创建 multipart 表单。
mime_addpart(mime)mimepart 指针新增一个表单字段。
mime_name(part, name)part、字符串CURLcode设置字段名。
mime_data(part, data)part、字符串CURLcode设置字符串字段内容。
mime_filedata(part, path)part、字符串CURLcode设置文件字段内容。
easy_setopt_mimepost(handle, mime)handle、mimeCURLcode将表单绑定到请求。
mime_free(mime)mime释放表单;必须由调用方显式调用。

2.5 参数校验、返回值与异常

功能说明

统一说明 Lua 参数校验、句柄错误和 CURLcode 返回约定。

参数说明

  • 句柄或指针无效时,接口通常返回自定义错误码 -1;信息查询接口此时只返回一个 -1
  • 字符串参数使用 luaL_checkstring,整数参数使用 luaL_checkinteger。类型不匹配会抛出 Lua 错误,而不是返回 CURLcode,例如:bad argument #2 to 'easy_setopt_url' (string expected, got nil)
  • 设置和执行接口的非零返回值均应通过 easy_strerror(code) 转换后记录。

返回值与异常

设置/执行接口返回 0-1CURLcode;参数类型错误由 Lua 直接抛出异常。

应用场景

用于调用方统一处理 lcurl 参数、返回值和错误信息。

限制条件

-1 表示绑定层参数错误,非零 CURLcode 表示 libcurl 传输层错误,二者不能混为同一错误码。

调试示例

lua
local code = curl.easy_perform(h)
if code ~= 0 then
    log:error('curl failed: %s', curl.easy_strerror(code))
end

3. 组件扩展案例

3.1 拓展能力概述

lcurl 没有插件加载机制;扩展方式是在 C 层为 libcurl 新增选项或信息接口,再注册为 Lua 函数。扩展不会改变现有函数的返回约定。

3.2 扩展点说明

扩展点文件说明
函数实现src/l_curl_easy.c新增 int l_xxx(lua_State *L),完成参数检查和 curl 调用。
函数声明src/l_curl_easy.h增加函数原型。
函数注册src/l_curl.cluaL_Reg l[] 中加入 Lua 名称到 C 函数的映射。
构建链接src/CMakeLists.txt仅在引入新库或新头文件时调整。

新增扩展后请同步更新 2.2 接口表、参数类型、用途、限制和示例。

3.3 二次开发指导

以新增 CURLOPT_FOLLOWLOCATION 为例:

c
/* src/l_curl_easy.h */
int l_easy_setopt_followlocation(lua_State *L);

/* src/l_curl_easy.c */
int l_easy_setopt_followlocation(lua_State *L)
{
    lCurl *c = (lCurl *)lua_touserdata(L, 1);
    if (c == NULL) {
        lua_pushinteger(L, -1);
        return 1;
    }
    const glong flag = (glong)luaL_checkinteger(L, 2);
    CURLcode ret = curl_easy_setopt(c->curl, CURLOPT_FOLLOWLOCATION, flag);
    lua_pushinteger(L, ret);
    return 1;
}

/* src/l_curl.c 的 l[] 表 */
{"easy_setopt_followlocation", l_easy_setopt_followlocation},

请更新 2.2 请求选项接口,对照 src/l_curl.cluaL_Reg l[] 检查文档覆盖情况。扩展代码应与 2.2 文档应在同一次变更中提交。

验证步骤:

  1. 使用仓库既有构建流程重新编译并安装 lcurl.so
  2. 在 Lua 中 require('lcurl.core'),创建句柄并调用新接口。
  3. 根据真实场景判断记录日志,若还是抛错,再执行实际请求验证行为。
  4. 运行 test/unit/test_app.lua 所需的单元测试环境,观察 ASAN/泄漏检查结果。

注意事项:参数类型、-1 无效句柄约定、资源释放和函数注册名必须与现有实现保持一致;涉及临时内存时应参考 easy_setopt_sslcert_blobg_malloc0/g_free 用法。

4. 日志说明

4.1 一键日志收集

lcurl 是动态库,不创建独立日志文件,仓库未注册自定义 on_dump 回调。一键日志是否包含 lcurl 调用产生的信息,取决于宿主组件的日志配置。

4.2 关键日志信息

code返回值解析日志级别含义解读建议处理动作
code=0INFOcurl 传输成功。
code=7 / CURLE_COULDNT_CONNECTERROR无法建立连接。检查目标地址、端口、防火墙和路由。
code=28 / CURLE_OPERATION_TIMEDOUTERROR连接或传输超时。检查网络质量并调整超时设置。
code=60 / CURLE_SSL_CACERTERRORCA 证书校验失败。检查 cainfo、证书有效期和主机名。

5. 问题定界指南

5.1 典型问题定界

问题描述是否为本组件问题判断依据关键证据收集方法
require('lcurl.core') 找不到模块可能是通常与 lcurl.so 部署或 package.cpath 有关。检查 /opt/bmc/luaclib/lcurl.so 是否存在,在 Lua内打印加载路径 package.cpath确认是否包含安装目录。
easy_init() 返回 nil可能是可能是 调用curl_easy_init初始化失败。确认进程首次使用前已调用 global_init()且 返回值为 0,使用 free -m检查内存是否耗尽。
easy_perform() 返回非 0通常不是返回码来自 libcurl 传输层,需区分网络、协议、TLS 和参数问题。记录返回码和错误具体描述easy_strerror,参考 5.2 错误码速查表定位具体原因。
HTTP 状态码为 401/403/404通常不是连接已完成,可能原因有认证失败、URL路径错误、资源不存在、权限不足。easy_get_http_code 获取状态码并核对请求配置。

5.2 错误码速查表

错误码含义排查建议
-1lcurl 自定义句柄、指针或文件参数无效。排查传入的参数是否有误。
0CURLE_OK成功。继续检查 HTTP 状态码和业务数据。
3CURLE_URL_MALFORMATURL 格式错误。检查 scheme、主机名、端口和转义。
6CURLE_COULDNT_RESOLVE_HOST无法解析主机。检查 DNS、主机名和网络配置。
7CURLE_COULDNT_CONNECT无法连接目标。检查目标服务、端口、防火墙和路由。
22CURLE_HTTP_RETURNED_ERRORHTTP 返回错误。获取 HTTP 状态码,检查服务端响应。
28CURLE_OPERATION_TIMEDOUT操作超时。调整连接/总超时并检查网络。
35CURLE_SSL_CONNECT_ERRORSSL/TLS 连接错误。检查协议、TLS 后端、证书和私钥。
60CURLE_SSL_CACERTCA 证书校验失败。检查 easy_setopt_cainfo 和证书链。

完整错误描述以当前构建版本 libcurl 的 easy_strerror 返回值为准。

5.3 最小化复现与证据收集

  1. 记录调用函数、参数类型、HTTP URL、handle 状态和 CURLcode
  2. 使用 easy_strerror 获取错误描述,并记录 HTTP 状态码。
  3. 检查 /opt/bmc/luaclib/lcurl.sopackage.cpath、证书、DNS、路由和防火墙。
  4. 在宿主组件日志中保存完整调用栈和复现步骤。

5.4 调试方法

  • 单元测试可运行 test/unit/test_app.lua,并结合 ASAN 检查资源释放。
  • 开启客户端的详细日志,记录请求参数和返回值。
  • 对 TLS 问题分别验证 CA、证书、私钥和主机名,避免直接关闭校验作为长期方案。

6. 常见问题解答

Q1:require('lcurl.core') 失败怎么办?

  • 问题描述:require('lcurl.core') 失败,Lua 报 module not found。
  • 一句话答案:检查共享库部署路径和 Lua C 模块搜索路径。
  • 根因说明:共享库未部署、路径不对或模块名不是 lcurl.core
  • 解决方案:确认 /opt/bmc/luaclib/lcurl.so 存在,并在 package.cpath 中包含 /opt/bmc/luaclib/?.so
  • 规避方案:通过组件包和正式构建流程部署,不要只复制单个共享库。
  • 适用版本:1.130.2。

Q2:easy_init() 返回 nil 的原因是什么?

  • 问题描述:创建 easy 句柄没有返回 userdata。
  • 一句话答案:通常是 global_init() 未成功或底层 curl_easy_init() 失败。
  • 根因说明:底层 curl_easy_init() 返回空指针,或依赖和内存不足。
  • 解决方案:确认 global_init() == 0,检查 libcurl 依赖是否完整及系统内存是否充足。
  • 规避方案:在组件启动阶段完成全局初始化,并为失败路径记录明确错误。
  • 适用版本:1.130.2。

Q3:多线程使用 lcurl 要注意什么?

  • 问题描述:多线程请求出现超时、回调异常或不稳定。
  • 一句话答案:一个 handle 的配置、执行、回调和清理必须保持在线程内串行完成。
  • 根因说明:回调绑定到创建句柄时记录的主线程 lua_State,libcurl 的信号行为也可能影响多线程。
  • 解决方案:让句柄的配置、执行和清理在同一线程完成,按请求单独创建句柄。
  • 规避方案:启用 easy_setopt_nosignal,不要跨线程共享或释放同一个 handle。
  • 适用版本:1.130.2。

附录

附录 A 参考资料