使用curl mime API将数组加密数据转为multipart/form-data 解决本地数据读取失败问题
libcurl mime接口构造multipart/form-data报错解决方法
报错根因
你触发错误的核心原因是接口使用错误:curl_mime_filedata接口的入参要求是本地文件的路径,调用后libcurl会自动读取路径指向的文件内容作为表单字段值。你当前传入的是内存中的加密二进制数据,libcurl会把你传入的数据内容当作文件路径去读取,自然找不到对应文件,抛出「Failed to open/read local data from file/application」错误。
你查阅到的curl_formadd是旧版libcurl的表单操作接口,你当前使用的curl_mime系列是官方推荐的新版替代接口,不需要切换到旧接口,新版接口有对应的内存数据传入方法。
需要修改的代码点
- 上传内存二进制数据作为文件字段:使用
curl_mime_data传入内存数据指针和长度,搭配curl_mime_filename指定上传的文件名(根据后端接口要求调整) - 普通文本字段直接使用
curl_mime_data传入内容即可,不要调用curl_mime_filedata - 你当前的回调函数返回值为0,会导致返回的响应头被截断,如果需要正常接收返回结果需要同步修正回调逻辑
- 额外注意:你当前取cmdData前10个字节作为认证头的逻辑没有判断cmdData长度,当传入的数组长度小于10时会发生内存越界,建议补充长度校验
修正后完整代码
#include <string> #include <vector> #include <iostream> #include <cstring> #include <curl/curl.h> #include <curl/easy.h> #define SYSLOG_INFO std::clog struct Post { std::string url, chunk; static constexpr auto HeaderSize = 10; // 修正回调函数,正常写入返回头 size_t static callBackFunk(char* buffer, size_t size, size_t nmemb, void* userdata) { size_t real_size = size * nmemb; std::string* resp_header = static_cast<std::string*>(userdata); resp_header->append(buffer, real_size); SYSLOG_INFO << __PRETTY_FUNCTION__ << " received " << real_size << " bytes header\n"; return real_size; } std::string libcurl(const std::vector<uint8_t>& cmdData) { // 补充长度校验,避免越界 if (cmdData.size() < HeaderSize) { SYSLOG_INFO << "cmdData length is less than required HeaderSize " << HeaderSize << "\n"; return ""; } CURL* curl{nullptr}; CURLcode res; curl_mime* form = NULL; curl_mimepart* field = NULL; struct curl_slist* headers = NULL; static const char buf[] = "Expect:"; curl = curl_easy_init(); if (!curl) { SYSLOG_INFO << "curl_easy_init failed\n"; return ""; } curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); form = curl_mime_init(curl); // 第一个字段upfile:传入内存二进制数据作为文件内容 field = curl_mime_addpart(form); curl_mime_name(field, "upfile"); // 第三个参数用CURL_BINARY指明是二进制数据,避免libcurl自动处理转义 curl_mime_data(field, reinterpret_cast<const char*>(cmdData.data()), cmdData.size(), CURL_BINARY); // 如果后端要求指定上传文件名,可保留这一行,文件名自行修改 curl_mime_filename(field, "upload.bin"); // 第二个字段filename:如果是传文件名文本就直接传文本内容,可按需求调整值 field = curl_mime_addpart(form); curl_mime_name(field, "filename"); curl_mime_data(field, "upload.bin", CURL_ZERO_TERMINATED); std::string header = std::string(cmdData.begin(), cmdData.begin() + HeaderSize); SYSLOG_INFO << "header = " << header << std::endl; std::string headerAuth = "SCPv2: " + header; headers = curl_slist_append(headers, headerAuth.c_str()); headers = curl_slist_append(headers, buf); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_MIMEPOST, form); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); curl_easy_setopt(curl, CURLOPT_HEADERDATA, &chunk); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, callBackFunk); // 如果需要接收响应体,可以额外加CURLOPT_WRITEDATA和CURLOPT_WRITEFUNCTION配置 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); res = curl_easy_perform(curl); if (res != CURLE_OK) { std::cerr << " curl_easy_perform() failed " << curl_easy_strerror(res) << std::endl; } curl_easy_cleanup(curl); curl_mime_free(form); curl_slist_free_all(headers); return chunk; } }; int main() { // 测试用的cmdData长度要大于等于10,这里补全到10字节避免校验失败 Post post {"http://www.example.com", "hello world"}; post.libcurl({1,2,3,4,5,6,7,8,9,10}); return 0; }
内容的提问来源于stack exchange,提问作者Shanmukha Raju
相关产品推荐
相关产品推荐

