libcURL POST 分段上传(带有缓冲图像)返回 HTTP 400

libcURL POST multipart upload (with buffered image) returning HTTP 400

本文关键字:图像 缓冲 返回 HTTP 分段 POST libcURL      更新时间:2023-10-16

我正在向Cloudinary API发送一个POST多部分,以上传我使用openCV捕获和缓冲的图像。Cloudinary给了我一个HTTP 400。我已经测试过使用 chrome 应用程序上传图像PostMan并且有效,因此我的 POST 请求中一定有错误或缓冲数据的方式有问题。

以下是他们描述对 API 的调用的方式:http://cloudinary.com/documentation/upload_images#uploading_with_a_direct_call_to_the_api

我已经在这里待了好几天了,任何帮助将不胜感激。一旦弄清楚了这一点,我将把这段代码提交给Cloudinary,这样其他摆弄C/C++的用户可以轻松地上传到他们的平台,而不会像我所经历的那样痛苦。

//Clone frame and encode into buffer as JPEG
cv::Mat hand_roi = frame.clone();
std::vector<unsigned char> imgBuffer; 
imencode(".jpg", hand_roi, imgBuffer);
//Buffer to String with pointer/length
std::string imgStrBuff = std::string(imgBuffer.begin(), imgBuffer.end());
char *imgBuffPtr = (char *)&imgStrBuff;
long imgBuffLength = static_cast<long>(imgStrBuff.size());
//Unix time in (s)
std::time_t t = std::time(0);
std::string unixTime = std::to_string(t);
//API_Secret:
std::string APISecret = "XXXXXXXXXXXXXXXXXXXXX";
//Create SHA1 signature required by cloudinary
std::string hashString = "timestamp=" + unixTime + APISecret;
std::string authSig = sha1(hashString);
//POST URL
std::string url = "https://api.cloudinary.com/v1_1/xxxxxxxxxxx/image/upload";
/*-----CURL SETUP-----*/
//Set headers as null
struct curl_slist *headers = NULL;
struct curl_httppost *formpost = NULL;
struct curl_httppost *lastptr = NULL;
//Data type 
curl_slist_append(headers, "Content-Type: multipart/form-data");
curl_global_init(CURL_GLOBAL_ALL);
curl_formadd(&formpost,
             &lastptr,
             CURLFORM_COPYNAME, "api_key",
             CURLFORM_COPYCONTENTS, "xxxAPIKeyxxx",
             CURLFORM_END);
curl_formadd(&formpost,
             &lastptr,
             CURLFORM_COPYNAME, "timestamp",
             CURLFORM_COPYCONTENTS, unixT.c_str(),
             CURLFORM_END);
curl_formadd(&formpost,
             &lastptr,
             CURLFORM_COPYNAME, "signature",
             CURLFORM_COPYCONTENTS, authSig.c_str(),
             CURLFORM_END);
curl_formadd(&formpost,
             &lastptr,
             CURLFORM_COPYNAME, "file",
             CURLFORM_BUFFER, "random_img_name.jpg",
             CURLFORM_BUFFERPTR, imgBuffPtr,
             CURLFORM_BUFFERLENGTH, imgBuffLength,
             CURLFORM_END);
//init easy_curl
CURL* curl = curl_easy_init();
if(!curl)
    return false;
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
res = curl_easy_perform(curl);

我基本上已经阅读了所有的libcurl文档,但我只是无法弄清楚为什么这些请求被拒绝。

您的代码有几个问题,我将逐一解决它们,从导致操作实际失败的最关键问题开始。

1(您正在向CURL提供垃圾指针作为文件缓冲区

问题出在这一部分:

std::string imgStrBuff = std::string(imgBuffer.begin(), imgBuffer.end());
char *imgBuffPtr = (char *)&imgStrBuff;
long imgBuffLength = static_cast<long>(imgStrBuff.size());

这样做:它将imgBuffer填充到C++字符串imgStrBuff中,然后将指向字符串描述符的指针误解为指向实际数据的指针,并将其存储在imgBuffPtr中。数据的(正确(长度存储在 imgBuffLength 中。

因此,再往下,您要求 CURL 从地址 imgBuffPtr 开始从内存中读取 imgBuffLength 个字节......而且你很幸运,你的程序不只是崩溃,因为它现在将读取字符串描述符和任何垃圾,而不是实际数据!

我想你真正想做的是char *imgBuffPtr = imgStrBuff.c_str();,但有一个更好的方法,中间没有任何字符串:

char *imgBuffPtr = &imgBuffer[0];
long imgBuffLength = imgBuffer.size();

该标准要求std::vector将其数据存储在连续内存中,因此将其保存为仅使用指向其第一个元素的指针。

2(您没有在正确的时间打电话给curl_global_init

curl_global_init应在任何其他 CURL 函数之前调用,但只需调用一次。因此,在此特定代码中,它必须高于对curl_slist_append的调用,但实际上您只需要在程序的启动例程中的某个地方使用它。

3( 您没有将curl_slist_append的结果分配给headers

此行实际上什么都不做(除了创建列表并泄漏它(:

curl_slist_append(headers, "Content-Type: multipart/form-data");

这是因为您必须将其返回值赋回变量 headers ,如下所示:

headers = curl_slist_append(headers, "Content-Type: multipart/form-data");

4( Content-Type标题是不必要的

正如评论者Daniel Stenberg已经指出的那样,没有必要将内容类型设置为标题,因为CURL_HTTPPOST已经为您执行此操作 - 实际上,它必须这样做,因为它还必须定义多部分边界。

5(您正在泄漏数据结构

以下内容仅适用于代码中没有此功能的情况:

您正在创建一个简单的会话变量curl,一个curl_slist headers(尽管#4已过时(和curl_httppost结构formpost

完成后,必须使用以下代码再次释放它们:

curl_easy_cleanup(curl);
curl_formfree(formpost);
curl_slist_free_all(headers);

为将来提供一些调试帮助

下次遇到此类问题时,您应该明确执行两件事:

  1. 正如评论者Remy Lebeau所建议的那样,使用数据包嗅探器(如Wireshark或SmartSniff(或分析代理(如Fiddler(来查看实际发送或接收的内容。您可能需要将协议更改为http://才能正常工作(之后将其更改回来!(,否则您将只能看到加密的SSL数据。通过这种方式,您可以了解真正越界的情况以及问题可能出在哪里。然后,您还可以比较数据的两个版本,一个有效(例如从命令行或浏览器(,另一个版本不起作用。

  2. 检查从 API 返回的实际响应正文。您可能需要curl_easy_setopt(curl, CURLOPT_FAILONERROR, 0);以便即使状态代码为>=400,您也会首先获得一个正文,以及一个数据写入回调(查看此示例(。如果您这样做,您会注意到 API 发送的错误是{"error":{"message":"Invalid image file"}}这已经为您提供了线索。

兄弟,你把一切都搞砸了。下面是示例代码,请你重构代码。最大的问题是curl_global_init它必须是第一个,然后curl_slist_append应该接受新的指针。

以下面的示例为例,开始逐个更新代码,并且应该全部设置

#include <stdio.h>
#include <string.h>
#include <curl/curl.h>
int main(int argc, char *argv[])
{
  CURL *curl;
  CURLcode res;
  struct curl_httppost *formpost=NULL;
  struct curl_httppost *lastptr=NULL;
  struct curl_slist *headerlist=NULL;
  static const char buf[] = "Expect:";
  curl_global_init(CURL_GLOBAL_ALL);
  /* Fill in the file upload field */
  curl_formadd(&formpost,
               &lastptr,
               CURLFORM_COPYNAME, "sendfile",
               CURLFORM_FILE, "postit2.c",
               CURLFORM_END);
  /* Fill in the filename field */
  curl_formadd(&formpost,
               &lastptr,
               CURLFORM_COPYNAME, "filename",
               CURLFORM_COPYCONTENTS, "postit2.c",
               CURLFORM_END);

  /* Fill in the submit field too, even if this is rarely needed */
  curl_formadd(&formpost,
               &lastptr,
               CURLFORM_COPYNAME, "submit",
               CURLFORM_COPYCONTENTS, "send",
               CURLFORM_END);
  curl = curl_easy_init();
  /* initalize custom header list (stating that Expect: 100-continue is not
     wanted */
  headerlist = curl_slist_append(headerlist, buf);
  if(curl) {
    /* what URL that receives this POST */
    curl_easy_setopt(curl, CURLOPT_URL, "http://example.com/examplepost.cgi");
    if ( (argc == 2) && (!strcmp(argv[1], "noexpectheader")) )
      /* only disable 100-continue header if explicitly requested */
      curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
    curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
    /* Perform the request, res will get the return code */
    res = curl_easy_perform(curl);
    /* Check for errors */
    if(res != CURLE_OK)
      fprintf(stderr, "curl_easy_perform() failed: %sn",
              curl_easy_strerror(res));
    /* always cleanup */
    curl_easy_cleanup(curl);
    /* then cleanup the formpost chain */
    curl_formfree(formpost);
    /* free slist */
    curl_slist_free_all (headerlist);
  }
  return 0;
}