C++ cUrl 发送 multipart/form-data 文件到 Web 服务器

6

正如标题所说,我想在C++程序中使用cUrl库将文件发送到Web服务器。

我从服务器获取了URL和它需要HTTP POST方法以及mutipart/form-data的信息。此外,它还需要一个名为file的参数来传递数据。

我也有一个可以在curl控制台中使用的cUrl调用:

curl -X POST -H "Cache-Control: no-cache" -H "Content-Type: multipart/form-data " -F "file=@test.txt" "url"

如果我运行下面的代码,服务器会返回没有上传或没有multipart表单的答案。

现在是我的代码:

//## File stuff
struct stat file_info;
FILE *fd;

fopen_s(&fd,"file.txt", "rb"); /* open file to upload */
if (!fd) {

    return 1; /* can't continue */
}

/* to get the file size */
if (fstat(_fileno(fd), &file_info) != 0) {

    return 1; /* can't continue */
}
//##

CURL *hnd = curl_easy_init();

curl_easy_setopt(hnd, CURLOPT_CUSTOMREQUEST, "POST");
curl_easy_setopt(hnd, CURLOPT_URL, "url");

struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "cache-control: no-cache");
headers = curl_slist_append(headers, "content-type: multipart / form-data");
curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, headers);

curl_easy_setopt(hnd, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(hnd, CURLOPT_READDATA, fd);

curl_easy_setopt(hnd, CURLOPT_INFILESIZE_LARGE,(curl_off_t)file_info.st_size);

CURLcode ret = curl_easy_perform(hnd);

编辑

使用缓冲区中的文件添加新代码:

std::string contents;
std::ifstream in("empty.txt", std::ios::in | std::ios::binary);

if (in)
{
    in.seekg(0, std::ios::end);
    contents.resize(in.tellg());
    in.seekg(0, std::ios::beg);
    in.read(&contents[0], contents.size());
    in.close();
}

CURL *hnd = curl_easy_init();

uint32_t size = contents.size();

//struct curl_slist *headers = NULL;
//headers = curl_slist_append(headers, "cache-control: no-cache");
//headers = curl_slist_append(headers, "content-type: multipart/form-data");
//curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, headers);

curl_easy_setopt(hnd, CURLOPT_URL, "url");
curl_easy_setopt(hnd, CURLOPT_POST, 1);
curl_easy_setopt(hnd, CURLOPT_POSTFIELDS, contents.data());
curl_easy_setopt(hnd, CURLOPT_POSTFIELDSIZE, size);

CURLcode ret = curl_easy_perform(hnd);

现在服务器响应没有文件数据。我是否还需要说明它是multipart/form-data?但是,如果我取消注释标题设置,响应会提示关于多行表单的错误。

另外,我对所需的“file”参数也感到困惑。我是否必须将其添加到CURLOPT_POSTFIELDS中?

希望你们中的其中一个可以帮助我,谢谢!

最终编辑

我明白了。以下代码适用于给出的服务器设置。

std::string contents;
std::ifstream in("file.txt", std::ios::in | std::ios::binary);

if (in)
{
    in.seekg(0, std::ios::end);
    contents.resize(in.tellg());
    in.seekg(0, std::ios::beg);
    in.read(&contents[0], contents.size());
    in.close();
}
//##


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);

// set up the header
curl_formadd(&formpost,
    &lastptr,
    CURLFORM_COPYNAME, "cache-control:",
    CURLFORM_COPYCONTENTS, "no-cache",
    CURLFORM_END);

curl_formadd(&formpost,
    &lastptr,
    CURLFORM_COPYNAME, "content-type:",
    CURLFORM_COPYCONTENTS, "multipart/form-data",
    CURLFORM_END);

curl_formadd(&formpost, &lastptr,
    CURLFORM_COPYNAME, "file",  // <--- the (in this case) wanted file-Tag!
    CURLFORM_BUFFER, "data",
    CURLFORM_BUFFERPTR, contents.data(),
    CURLFORM_BUFFERLENGTH, contents.size(),
    CURLFORM_END);

curl = curl_easy_init();

headerlist = curl_slist_append(headerlist, buf);
if (curl) {

    curl_easy_setopt(curl, CURLOPT_URL, "url");

    curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
    //curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
    //curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
    //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);

    res = curl_easy_perform(curl);
    /* Check for errors */
    if (res != CURLE_OK)
        fprintf(stderr, "curl_easy_perform() failed: %s\n",
            curl_easy_strerror(res));


    curl_easy_cleanup(curl);


    curl_formfree(formpost);

    curl_slist_free_all(headerlist);
}

https://curl.haxx.se/libcurl/c/postit2.html - Arunmu
还有这个例子,我收到了“没有数据文件”的消息。我所做的唯一更改是将“postit2.c”更改为“file.txt”。 - HEvTH
2个回答

1
您没有正确格式化您的多部分数据。您可以使用netcat实用程序查看应该是什么样子:

在终端1中:

$ nc -lk 8080 # listen for TCP connection on port 8080

在第二个终端中:
$ curl -X POST -F "file=@test.txt" localhost:8080

通过检查终端1,您会看到curl发送了类似以下请求的请求:
POST / HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.43.0
Accept: */*
Content-Length: 193
Expect: 100-continue
Content-Type: multipart/form-data; boundary=------------------------a975b09b8e15800c

--------------------------a975b09b8e15800c
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain

qwerty

--------------------------a975b09b8e15800c--

现在,通过让C++代码将数据发送到netcat(localhost:8080)来调试它,并修复代码,直到其请求类似于curl发送的请求。
一个通用的建议-不要盲目调试。您无法控制的服务器的响应不提供有关问题的足够信息。如果可能,请在允许您查看每个数据位并验证数据是否符合规范和/或您的期望的环境中进行调试。

好的,谢谢。但我认为我的技能还不够高。我下载了Windows版的Netcat并进入控制台,输入“nc -l 8080”(k不存在),但它无法找到主机。 - HEvTH
@HEvTH curl可执行文件会自动添加边界和Content-Disposition数据。 - Leon
@HEvTH nc 报告的错误确切文本是什么? - Leon
nc:转发主机查找失败:h_errno 11001:HOST_NOT_FOUND - HEvTH
谢谢你的建议,如果我深入研究这个主题,我会记住它。但现在我已经解决了当前的问题(请参见我的帖子中的最终编辑)。但是我真的很感激你的提示。 - HEvTH
显示剩余3条评论

0

你的命令行使用了POST,但是你的代码使用了CURLOPT_UPLOAD,这显然是不同的。

你应该使用CURLOPT_POST,并且你应该将你的文件读取(或映射)到一个缓冲区中:

// Assume you read or map your file into 
// `uint8_t* buffer`, with `uint32_t size`
...
CURL *hnd = curl_easy_init();
curl_easy_setopt(hnd, CURLOPT_URL, "url");
curl_easy_setopt(hnd, CURLOPT_POST, 1);
curl_easy_setopt(hnd, CURLOPT_POSTFIELDS, buffer);
curl_easy_setopt(hnd, CURLOPT_POSTFIELDSIZE, size);

谢谢你的回答。我尝试了一下,现在卡在另一个点上了。我根据你的答案编辑了我的帖子,希望你有时间看一下,谢谢。 - HEvTH
尝试在CURLOPT_POSTFIELDS的内容中添加"file="。当然,如果您的服务器需要额外的头文件,您也应该添加Cache-Control: no-cache头文件。 - Mine
谢谢,但我认为边界也存在问题。 - HEvTH

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接