Я пишу код для загрузки файла на файловый сервер вместе с двумя другими строковыми переменными как часть почтового запроса HTTP.
Идея использования мультиинтерфейса здесь заключается в том, чтобы в будущем загружать несколько файлов.
Здесь используется версия Libcurl: 7.44.
Вот моя программа:
#include <iostream>
#include <string>
#include <curl/curl.h>
const auto TimeoutInMS = 1000;
const auto FileDescriptorZero = 0;
bool HTTPPostSuccessful(long httpResponseCode)
{
bool httpRequestSuccessful = false;
if (httpResponseCode == 200)
{
httpRequestSuccessful = true;
}
return httpRequestSuccessful;
}
size_t WriteCallback(void * buffer, size_t size, size_t count, void * userp)
{
size_t numBytes = size * count;
static_cast<std::string*>(userp)->append(static_cast<char*>(buffer), numBytes);
return numBytes;
}
void HTTPPost(const std::string& value1, const std::string& value2, const std::string& filePath)
{
struct curl_slist *pHTTPRequestHeaders = nullptr;
struct curl_httppost* pFormpost = nullptr;
struct curl_httppost* pLastptr = nullptr;
uint16_t httpResponseCode = 0;
int stillSendingFile = 0;
CURL* pCurlEasyHandle = curl_easy_init();
CURLM *pCurlMultiHandle = curl_multi_init();
std::string responseData{};
if (pCurlEasyHandle && pCurlMultiHandle)
{
pHTTPRequestHeaders = curl_slist_append(pHTTPRequestHeaders, "Content-Type: multipart/form-data");
curl_easy_setopt(pCurlEasyHandle, CURLOPT_HTTPHEADER, pHTTPRequestHeaders);
curl_easy_setopt(pCurlEasyHandle, CURLOPT_URL, "https://http_.org/logs/readers");
curl_easy_setopt(pCurlEasyHandle, CURLOPT_VERBOSE, 1L);
curl_formadd(&pFormpost, &pLastptr,
CURLFORM_COPYNAME, "fileName",
CURLFORM_FILE, filePath.c_str(),
CURLFORM_CONTENTTYPE, "text/csv",
CURLFORM_END);
curl_formadd(&pFormpost, &pLastptr,
CURLFORM_COPYNAME, "Value1",
CURLFORM_COPYCONTENTS, value1.c_str(),
CURLFORM_END);
curl_formadd(&pFormpost, &pLastptr,
CURLFORM_COPYNAME, "Value2",
CURLFORM_COPYCONTENTS, value2.c_str(),
CURLFORM_END);
curl_easy_setopt(pCurlEasyHandle, CURLOPT_HTTPPOST, pFormpost);
curl_easy_setopt(pCurlEasyHandle, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(pCurlEasyHandle, CURLOPT_WRITEDATA, &responseData);
curl_multi_add_handle(pCurlMultiHandle, pCurlEasyHandle);
do
{
curl_multi_perform(pCurlMultiHandle, &stillSendingFile);
if (stillSendingFile)
{
curl_multi_wait(pCurlMultiHandle, nullptr, FileDescriptorZero, TimeoutInMS, nullptr);
}
}
while(stillSendingFile);
CURLcode res = curl_easy_getinfo(pCurlEasyHandle, CURLINFO_RESPONSE_CODE, &httpResponseCode);
if (HTTPPostSuccessful(httpResponseCode) && res == CURLE_OK)
{
std::cout << "File sent Successfully, HTTP response code: " << httpResponseCode << ", ResponseData: "<< responseData<< std::endl;
}
else
{
std::cerr << "Error during request: " << curl_easy_strerror(res) << ", Failure HTTP response code: " << httpResponseCode << std::endl;
}
if (pCurlMultiHandle && pCurlEasyHandle)
{
std::cout << "Clean up for curl_multi_remove_handle " << std::endl;
curl_multi_remove_handle(pCurlMultiHandle, pCurlEasyHandle);
}
if (pCurlEasyHandle)
{
std::cout << "Clean up for pCurlEasyHandle " << std::endl;
curl_easy_cleanup(pCurlEasyHandle);
pCurlEasyHandle = nullptr;
}
if (pCurlMultiHandle)
{
std::cout << "Clean up for pCurlMultiHandle " << std::endl;
curl_multi_cleanup(pCurlMultiHandle);
pCurlMultiHandle = nullptr;
}
if (pHTTPRequestHeaders)
{
std::cout << "Clean up pHTTPRequestHeaders " << std::endl;
curl_slist_free_all(pHTTPRequestHeaders);
pHTTPRequestHeaders = nullptr;
}
if (pFormpost)
{
std::cout << "Clean up pFormpost " << std::endl;
curl_formfree(pFormpost);
pFormpost = nullptr;
pLastptr = nullptr;
}
}
}
bool UploadFile(const std::string& value1, const std::string& value2, const std::string& filePath)
{
curl_global_init(CURL_GLOBAL_ALL);
HTTPPost(value1, value2, filePath);
curl_global_cleanup();
return true;
}
int main ()
{
UploadFile("1", "1", "/tmp/UploadFIle/testDoc.txt");
}
приведенный выше код отлично работает на моей машине, когда я фиксирую его на сервере сборки Jenkins, я получаю следующую ошибку:
7: HTTPClient Initialization is successful.
7: * Could not resolve host: http_.org
7: * Closing connection 0
7: Error during request: No error, Failure HTTP response code: 0
7: Clean up for curl_multi_remove_handle
7: Clean up for pCurlEasyHandle
7: Clean up for pCurlMultiHandle
7: Clean up pHTTPRequestHeaders
7: Clean up pFormpost
7/30 Test #7: **** ....................................***Exception: SegFault 0.29 sec
В некоторых случаях также наблюдается следующая ошибка:
8: HTTPClient Initialization is successful.
8: * Could not resolve host: http_.org
8: * Closing connection 0
8: Error during request: No error, Failure HTTP response code: 0
8: Clean up for curl_multi_remove_handle
8: Clean up for pCurlEasyHandle
8: Clean up for pCurlMultiHandle
8: Clean up pHTTPRequestHeaders
8: Clean up pFormpost
8: ==11267== Invalid read of size 8
8: ==11267== at 0x4E48018: curl_formfree (in /usr/lib/x86_64-linux-gnu/libcurl.so.4.4.0)
Может кто-нибудь, пожалуйста, сообщите мне, что здесь не так, новичок в Curl.
Отмечен Valgrind, но нет вывода Valgrind?
Привет @ 273K, Мы должны использовать версию curl 7.44, поэтому, пожалуйста, дайте мне знать, есть ли какой-либо другой способ в этой версии libcurl.
Вероятно, это не связано с вашей проблемой, но в целом неплохо уничтожать вещи в порядке, обратном порядку их создания.
Привет @ 273K, Обновил код, как вы предложили, теперь вместо недействительного бесплатного я вижу ошибку сегмента.
@273K, да в одной строке с curl_formfree. В настоящее время наш сервер не готов, поэтому можно тестировать только отрицательные случаи на сервере jenkins, эта ошибка, которую я получаю в случае, когда URI сервера недействителен, а файл пуст, я также протестировал тот же код с URI: «httpbin.org /post" на моей машине с пустым файлом, который отлично работает. Я получаю сообщение об ошибке только на сервере сборки jenkins, когда я фиксирую свой код.





Первая проблема, приводящая к UB, находится в WriteCallback:
static_cast<std::string*>(userp)->append(static_cast<char*>(buffer), 0, numBytes);
Вы выбрали перегруженную функцию-член
basic_string& append( const basic_string& str,
size_type pos, size_type count )
который создает std::string из данных, не заканчивающихся нулем, в buffer.
Вы должны использовать другую перегруженную функцию-член
basic_string& append( const CharT* s, size_type count )
поэтому правильный вызов
static_cast<std::string*>(userp)->append(static_cast<char*>(buffer), numBytes);
Вторая проблема — это uint16_t httpResponseCode, в то время как CURLINFO_RESPONSE_CODE требует указатель на длинное значение, вы передаете &httpResponseCode указатель на короткое значение, это еще один UB. В частности портит данные в локальных переменных, записывает нули в 2 или 6 байт вне httpResponseCode хранилища, возможно в pFormpost байтах. Должен быть
long httpResponseCode = 0;
Спасибо @ 273K, я проверю это и сообщу вам результат.
Почему вы используете устаревший API?