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 传输,减少进程切换和重复封装工作,并通过标准 CURLcode 与 easy_strerror 统一错误处理方式。
1.3 核心功能
- 全局初始化和清理:
global_init、global_cleanup。 - easy 句柄生命周期:
easy_init、easy_perform、easy_cleanup。 - 请求选项配置:URL、认证、HTTP 方法、POST 数据、超时、范围下载、网卡绑定、TLS 校验、证书/私钥、TFTP 块大小等。
- 数据处理:Lua 回调接收响应头/响应体,或将数据读写到 Lua 文件句柄。
- HTTP 头和 multipart:使用
slist_*构造请求头,使用mime_*构造表单和文件上传。 - 传输结果查询:HTTP 状态码、Content-Length、实际下载字节数。
- 错误描述:将 curl 返回码转换为可读字符串。
1.4 关键术语表
| 术语 | 解释 |
|---|---|
| lcurl | Lua 与 libcurl easy API 之间的绑定模块。 |
| easy handle | 一次传输及其选项、回调和结果的上下文。 |
| CURLcode | libcurl 返回码;0 表示成功。 |
| C 模块 | 可在 Lua 中通过 require 加载的共享库。 |
| slist / mime | HTTP 头链表和 multipart 表单构造对象。 |
1.5 外部交互边界图
2. API 使用说明与示例
2.1 模块加载、初始化与句柄生命周期
功能说明
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 线程中并发配置、执行或释放。
调试示例
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_url | CURLOPT_URL | string | 设置请求 URL。 |
easy_setopt_userpwd | CURLOPT_USERPWD | string | 设置 user:password Basic 认证。 |
easy_setopt_header | CURLOPT_HEADER | integer | 设为 1 时将响应头并入输出数据。 |
easy_setopt_nobody | CURLOPT_NOBODY | integer | 设为 1 时只获取响应头。 |
easy_setopt_verbose | CURLOPT_VERBOSE | integer | 设为 1 时输出 curl 详细传输信息到 stderr。 |
easy_setopt_connecttimeout | CURLOPT_CONNECTTIMEOUT | integer | 连接超时时间,单位为秒。 |
easy_setopt_timeout | CURLOPT_TIMEOUT | integer | 整个传输超时时间,单位为秒。 |
easy_setopt_range | CURLOPT_RANGE | string | 设置范围下载,如 "0-99"。 |
easy_setopt_upload | CURLOPT_UPLOAD | integer | 设为 1 开启上传模式。 |
easy_setopt_int_file_size | CURLOPT_INFILESIZE | integer | 设置上传数据大小,单位为字节。 |
easy_setopt_forbid_reuse | CURLOPT_FORBID_REUSE | integer | 设为 1 禁止连接复用。 |
easy_setopt_nosignal | CURLOPT_NOSIGNAL | integer | 设为 1 禁止使用信号;多线程场景建议开启。 |
easy_setopt_address_scope | CURLOPT_ADDRESS_SCOPE | integer | 设置 IPv6 地址 scope。 |
easy_setopt_customerquest | CURLOPT_CUSTOMREQUEST | string | 设置自定义方法,如 GET、DELETE。 |
easy_setopt_interface | CURLOPT_INTERFACE | string | 绑定本地网卡或本地 IP。 |
easy_setopt_postfields | CURLOPT_POSTFIELDS | string | 设置 POST 请求体。 |
easy_setopt_maxfilesize | CURLOPT_MAXFILESIZE | integer | 设置接收文件大小上限。 |
easy_setopt_tftp_blksize | CURLOPT_TFTP_BLKSIZE | integer | 设置 TFTP 块大小。 |
TLS 相关接口如下:
| Lua 函数 | 对应选项 | 参数类型 | 用途 |
|---|---|---|---|
easy_setopt_ssl_verifyhost | CURLOPT_SSL_VERIFYHOST | integer | HTTPS 主机名校验;常用值为 2(校验)或 0(关闭)。 |
easy_setopt_ssl_verifypeer | CURLOPT_SSL_VERIFYPEER | integer | HTTPS 对端证书校验;0 表示关闭。 |
easy_setopt_sslcert | CURLOPT_SSLCERT | string | 客户端证书文件路径。 |
easy_setopt_sslkey | CURLOPT_SSLKEY | string | 客户端私钥文件路径。 |
easy_setopt_cainfo | CURLOPT_CAINFO | string | CA 证书文件路径。 |
easy_setopt_crlfile | CURLOPT_CRLFILE | string | CRL 吊销列表文件路径。 |
easy_setopt_sslcert_blob | CURLOPT_SSLCERT_BLOB | string | 直接传入客户端证书内容。 |
easy_setopt_sslkey_blob | CURLOPT_SSLKEY_BLOB | string | 直接传入客户端私钥内容。 |
返回值与异常
成功通常返回 0,句柄无效返回 -1,其他值为 libcurl CURLcode。类型不匹配会由 Lua 参数检查直接抛出错误。
应用场景
用于配置 HTTP(S)、FTP、TFTP 等请求的连接、认证、超时、上传下载及 TLS 参数。
限制条件
TLS 校验、证书和私钥等安全相关选项必须与目标服务配置一致;生产环境不建议关闭证书或主机名校验。
调试示例
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) | handle | ret, code | 获取 HTTP 状态码。 |
easy_get_http_code(handle) | handle | ret, code | easy_getinfo_response_code 的别名。 |
easy_getinfo_content_length_download(handle) | handle | ret, length | 获取服务端声明的 Content-Length。 |
easy_getinfo_size_download(handle) | handle | ret, size | 获取实际下载字节数。 |
返回值与异常
设置接口返回 CURLcode,信息查询接口返回状态值和结果;句柄或参数无效时通常返回 -1。
应用场景
用于流式处理响应体、将下载内容写入文件,以及查询传输结果。
调试示例
回调由 C 层保存到 Lua 注册表,并在创建句柄时记录的主线程 lua_State 上执行。回调参数是一个字符串分块,不保证一次回调对应完整的 HTTP 消息。
以下示例用于将下载内容写入文件。
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 头
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、slist | CURLcode | 将头链表绑定到 easy 句柄。 |
返回值与异常
slist_append 和 mime_* 成功时返回对象或 CURLcode,无效参数通常返回 -1。
绑定后,easy 句柄在 easy_cleanup 中释放其记录的 curl_slist。当前没有独立的 slist_free 接口,未绑定到 easy 句柄的链表不应长期创建。
应用场景
用于 JSON API 请求头、文件上传和 multipart 表单提交。
限制条件
头链表不应长期脱离 easy handle;mime_free 必须由调用方显式执行。
调试示例
multipart 表单
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) | handle | mime 指针;无效 handle 返回 -1 | 创建 multipart 表单。 |
mime_addpart(mime) | mime | part 指针 | 新增一个表单字段。 |
mime_name(part, name) | part、字符串 | CURLcode | 设置字段名。 |
mime_data(part, data) | part、字符串 | CURLcode | 设置字符串字段内容。 |
mime_filedata(part, path) | part、字符串 | CURLcode | 设置文件字段内容。 |
easy_setopt_mimepost(handle, mime) | handle、mime | CURLcode | 将表单绑定到请求。 |
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、-1 或 CURLcode;参数类型错误由 Lua 直接抛出异常。
应用场景
用于调用方统一处理 lcurl 参数、返回值和错误信息。
限制条件
-1 表示绑定层参数错误,非零 CURLcode 表示 libcurl 传输层错误,二者不能混为同一错误码。
调试示例
local code = curl.easy_perform(h)
if code ~= 0 then
log:error('curl failed: %s', curl.easy_strerror(code))
end3. 组件扩展案例
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.c | 在 luaL_Reg l[] 中加入 Lua 名称到 C 函数的映射。 |
| 构建链接 | src/CMakeLists.txt | 仅在引入新库或新头文件时调整。 |
新增扩展后请同步更新 2.2 接口表、参数类型、用途、限制和示例。
3.3 二次开发指导
以新增 CURLOPT_FOLLOWLOCATION 为例:
/* 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.c 的 luaL_Reg l[] 检查文档覆盖情况。扩展代码应与 2.2 文档应在同一次变更中提交。
验证步骤:
- 使用仓库既有构建流程重新编译并安装
lcurl.so。 - 在 Lua 中
require('lcurl.core'),创建句柄并调用新接口。 - 根据真实场景判断记录日志,若还是抛错,再执行实际请求验证行为。
- 运行
test/unit/test_app.lua所需的单元测试环境,观察 ASAN/泄漏检查结果。
注意事项:参数类型、-1 无效句柄约定、资源释放和函数注册名必须与现有实现保持一致;涉及临时内存时应参考 easy_setopt_sslcert_blob 的 g_malloc0/g_free 用法。
4. 日志说明
4.1 一键日志收集
lcurl 是动态库,不创建独立日志文件,仓库未注册自定义 on_dump 回调。一键日志是否包含 lcurl 调用产生的信息,取决于宿主组件的日志配置。
4.2 关键日志信息
| code返回值解析 | 日志级别 | 含义解读 | 建议处理动作 |
|---|---|---|---|
code=0 | INFO | curl 传输成功。 | 无 |
code=7 / CURLE_COULDNT_CONNECT | ERROR | 无法建立连接。 | 检查目标地址、端口、防火墙和路由。 |
code=28 / CURLE_OPERATION_TIMEDOUT | ERROR | 连接或传输超时。 | 检查网络质量并调整超时设置。 |
code=60 / CURLE_SSL_CACERT | ERROR | CA 证书校验失败。 | 检查 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 错误码速查表
| 错误码 | 宏 | 含义 | 排查建议 |
|---|---|---|---|
-1 | lcurl 自定义 | 句柄、指针或文件参数无效。 | 排查传入的参数是否有误。 |
0 | CURLE_OK | 成功。 | 继续检查 HTTP 状态码和业务数据。 |
3 | CURLE_URL_MALFORMAT | URL 格式错误。 | 检查 scheme、主机名、端口和转义。 |
6 | CURLE_COULDNT_RESOLVE_HOST | 无法解析主机。 | 检查 DNS、主机名和网络配置。 |
7 | CURLE_COULDNT_CONNECT | 无法连接目标。 | 检查目标服务、端口、防火墙和路由。 |
22 | CURLE_HTTP_RETURNED_ERROR | HTTP 返回错误。 | 获取 HTTP 状态码,检查服务端响应。 |
28 | CURLE_OPERATION_TIMEDOUT | 操作超时。 | 调整连接/总超时并检查网络。 |
35 | CURLE_SSL_CONNECT_ERROR | SSL/TLS 连接错误。 | 检查协议、TLS 后端、证书和私钥。 |
60 | CURLE_SSL_CACERT | CA 证书校验失败。 | 检查 easy_setopt_cainfo 和证书链。 |
完整错误描述以当前构建版本 libcurl 的 easy_strerror 返回值为准。
5.3 最小化复现与证据收集
- 记录调用函数、参数类型、HTTP URL、handle 状态和
CURLcode。 - 使用
easy_strerror获取错误描述,并记录 HTTP 状态码。 - 检查
/opt/bmc/luaclib/lcurl.so、package.cpath、证书、DNS、路由和防火墙。 - 在宿主组件日志中保存完整调用栈和复现步骤。
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 参考资料
- libcurl easy API 文档:https://curl.se/libcurl/c/libcurl-easy.html
- Mulan PSL v2:
LICENSE。