【问题标题】:libcURL POST multipart upload (with buffered image) returning HTTP 400libcURL POST 分段上传(带缓冲图像)返回 HTTP 400
【发布时间】:2016-05-13 17:03:24
【问题描述】:

我正在向 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 文档,但我无法弄清楚为什么这些请求会被拒绝。

【问题讨论】:

  • 没有语言“C/C++”。只有两种不同的语言 C 和 C++。
  • 使用数据包嗅探器(如 Wireshark)查看 Chrome 实际发布的内容,然后将其与 libcurl 发布的内容进行比较。这将帮助您查明 libcurl 调用中的错误/缺失。
  • 不需要 CURLOPT_HTTPHEADER,当您使用 CURLOPT_HTTPPOST 时,libcurl 会自行设置正确的内容类型。
  • 除了@RemyLebeau 的评论,您可以只使用浏览器的开发工具,Chrome 和Firefox 都有内置工具,可以让您查看它发送的POST 请求。将其与您的 libcurl 帖子发送的内容进行比较。相应调整。

标签: c++ opencv libcurl


【解决方案1】:

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

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,您也会首先获得一个正文,以及一个数据写入回调(查看this example)。如果您这样做了,您会注意到 API 发送的错误是 {"error":{"message":"Invalid image file"}},这已经为您提供了线索。

【讨论】:

  • 干杯,非常感谢。关于垃圾指针的解释本身就是值得的。从你的帖子中学到了很多。我很好奇字符串描述符到底是什么以及为什么它指向垃圾而不是字符串数据。
  • 那是因为它包含更多信息,主要是字符串的长度(因为在 std:string 中,您还可以存储包含空字符的字符串,否则会终止纯 C 字符串)和字符串缓冲区的容量(因为库将更智能地管理内存,例如一次分配更多容量,以防止每次添加内容时都必须重新分配)。
  • 如果您有兴趣深入研究:info.prelert.com/blog/cpp-stdstring-implementations
  • 谢谢!我不是,没有兴趣深入研究这个:)
【解决方案2】:

你已经搞砸了兄弟。这是示例代码,请您重构代码。最大的问题是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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-12-08
    • 2015-09-27
    • 1970-01-01
    • 1970-01-01
    • 2014-10-26
    • 2021-01-13
    • 1970-01-01
    • 2014-02-26
    相关资源
    最近更新 更多