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

时间:2016-05-06 23:10:31

标签: c++ opencv libcurl

我发送POST多部分到Cloudinary API上传我已经使用openCV捕获和缓冲的图像。 Cloudinary正在给我一个HTTP 400.我已经测试了使用chrome app 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文档,但我无法弄清楚这些请求被拒绝的原因。

2 个答案:

答案 0 :(得分:5)

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

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从地址imgBuffLength开始读取内存中的imgBuffPtr个字节...你很幸运你的程序没有崩溃,因为它现在将读取字符串描述符和任何垃圾偶然位于内存后面而不是实际数据!

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

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

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

2)您未在合适的时间致电curl_global_init

应该在任何其他CURL函数之前调用

curl_global_init,但是您只需要调用它一次。所以在这个特定的代码中,它必须高于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)您正在泄露数据结构

以下内容仅适用于您尚未在代码中的某个位置进行此操作的情况:

您正在创建一个简单的会话变量curlcurl_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);,因此即使状态代码为&gt; = 400,您也会首先获得正文,并且数据写回调(查看this example)。如果你这样做了,你会注意到API发送的错误是{"error":{"message":"Invalid image file"}},这本来就给你一个线索。

答案 1 :(得分:1)

你已经把它搞砸了兄弟。这是示例代码,请求您重构代码。最大的问题是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: %s\n",
              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;
}