I am currently trying to download a File from a public git-Repository using curl in my Unreal C++ Project. Here is the code I'm trying to execute that I derived from the FTP-Example:
// This is in the .h file
struct FFtpFile {
FILE* File;
const char* Filename;
};
void FtpFetch(const std::string URL, const char* Filename) {
CURL* Curl = curl_easy_init();
const FFtpFile FtpFile {
nullptr,
Filename
};
if (!Curl) {
UE_LOG(LogTemp, Warning, TEXT("Error Initiating cURL"));
return;
}
curl_easy_setopt(Curl, CURLOPT_URL, URL.c_str());
curl_easy_setopt(Curl, CURLOPT_VERBOSE, 1);
curl_easy_setopt(Curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(Curl, CURLOPT_SSL_VERIFYPEER, false);
// Data Callback
const auto WriteCallback = +[](void* Contents, const size_t Size, const size_t NumMem, FFtpFile* FileStruct) -> size_t {
if (!FileStruct->File) {
fopen_s(&FileStruct->File, FileStruct->Filename, "wb");
if (!FileStruct->File) {
return CURLE_WRITE_ERROR;
}
}
return fwrite(Contents, Size, NumMem, FileStruct->File);
};
curl_easy_setopt(Curl, CURLOPT_WRITEFUNCTION, DownloadCallback);
curl_easy_setopt(Curl, CURLOPT_WRITEDATA, FtpFile);
const CURLcode Result = curl_easy_perform(Curl);
if (Result != CURLE_OK) {
const FString Message(curl_easy_strerror(Result));
UE_LOG(LogTemp, Warning, TEXT("Error Getting Content of the Model File: %s"), *Message);
return;
}
curl_easy_cleanup(Curl);
// Close the Stream after Cleanup
UE_LOG(LogTemp, Log, TEXT("Successfully Fetched FTP-File. Closing Write Stream"))
if (FtpFile.File) {
fclose(FtpFile.File);
}
}
Note that this is executed on a separate Thread using the Unreal Async function:
void AsyncFetchModelFile(const std::string URL) {
std::string Path = ...
TFunction<void()> Task = [Path, URL]() {
FtpFetch(URL, Path.c_str());
};
UE_LOG(LogTemp, Log, TEXT("Fetching FTP on Background Thread"))
Async(EAsyncExecution::Thread, Task, [](){UE_LOG(LogTemp, Warning, TEXT("Finishied FTP on Background Thread!"))});
}
I already removed the curl_global calls, as the documentation states those are not thread-safe. I also tried running the code on the main thread, but the same error happens there too.
To the error itself: The download runs almost flawlessly, but the downloaded file (in this case a .fbx file) always misses the last ~800 Bytes and is therefore incomplete. Also, the file keeps being open in Unreal, so I can't delete/move the file unless I close the Editor.
Before writing this Unreal code I tried running the same code in a pure C++ setting and there it worked flawlessly. But for some reason doing the same in Unreal doesn't work.
I also tried using a private method instead of the lambda-Function, but that didn't make any difference.
Any help would be appreciated
~Okaghana
I didn't manage to get it working in the end (I suspect it's an Unreal-Related Bug), but I found another way using the included Unreal HTTP Module:
FString Path = ...
// Create the Callback when the HTTP-Request has finished
auto OnRequestComplete = [Path](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) {
if (bWasSuccessful) {
FFileHelper::SaveArrayToFile(Response->GetContent(), *Path);
UE_LOG(LogTemp, Log, TEXT("Successfully downloaded the file to '%s'"), *Path)
} else {
UE_LOG(LogTemp, Warning, TEXT("Error downloading the file (See EHttpResponseCodes): %s"), Response->GetResponseCode())
}
};
// Create a HTTP-Request and Fetch the file
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = FHttpModule::Get().CreateRequest();
Request->SetVerb("GET");
Request->SetURL(URL);
Request->OnProcessRequestComplete().BindLambda(OnRequestComplete);
Request->ProcessRequest();
Related
My application uses libcurl to upload files to an SFTP server, and sometimes I need to close the application before the current upload is complete. I would like to abort the upload, and resume it later. However, when the upload is aborted there is corrupted data at the end of the partially uploaded file on the server. The amount various, up to about 10 bytes. This makes resuming the upload by appending data impossible.
See some sample code below. Press Ctrl-C to abort the upload, then manually download the partial file and compare it with the original file. The partial file will usually contain incorrect data at the end.
#include <stdio.h>
#include <signal.h>
#include "curl.h"
// Handle Ctrl-C to abort.
static bool s_bAborted = false;
static void __cdecl ctrlCHandler(int)
{
printf("Aborting...\n");
s_bAborted = true;
}
// File reading function.
static size_t readFunction(char * ptr, size_t size, size_t nmemb, void * stream)
{
FILE * pFile = (FILE *)stream;
if (ferror(pFile))
{
return CURL_READFUNC_ABORT;
}
return fread(ptr, size, nmemb, pFile);
}
// Progress function so transfers can be aborted.
static int progressFunction(void * clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{
if (s_bAborted)
{
return 1;
}
return CURL_PROGRESSFUNC_CONTINUE;
}
int main()
{
// Add your local file and server URL.
// Note: This problem was observed using an SFTP server.
const char * szLocalFile = "Test.bin";
const char * szRemoteUrl = "sftp://user:password#host/Test.bin";
curl_global_init(CURL_GLOBAL_ALL);
CURL * curl = curl_easy_init();
FILE * pFile = fopen(szLocalFile, "rb");
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(curl, CURLOPT_URL, szRemoteUrl);
curl_easy_setopt(curl, CURLOPT_READFUNCTION, readFunction);
curl_easy_setopt(curl, CURLOPT_READDATA, pFile);
curl_easy_setopt(curl, CURLOPT_APPEND, 0L);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progressFunction);
printf("Uploading... Press Ctrl-C to abort\n");
signal(SIGINT, ctrlCHandler);
CURLcode eResult = curl_easy_perform(curl);
fclose(pFile);
if (eResult == CURLE_OK)
{
printf("Uploaded OK\n");
}
else if (eResult == CURLE_ABORTED_BY_CALLBACK)
{
printf("Upload aborted\n");
}
else
{
printf("Error: Upload failed: %d\n", eResult);
}
curl_easy_cleanup(curl);
curl_global_cleanup();
// After aborting the upload, use something else to download the partial file.
// The last few bytes of the partial file are usually incorrect,
// i.e. different to the original file in that position.
// This makes resuming the upload impossible, since it will append after incorrect bytes.
}
Please can anyone help? Thank you.
Following the advice of rustyx above, I modified it to abort using the read function, and removed the progress callback. This appears to fix the problem (after testing about 15 times).
Note: This method means the partial file is always a multiple of 64 kB. When aborting from the progress callback it was any random size. Perhaps this has something to do with it.
See updated code below:
#include <stdio.h>
#include <signal.h>
#include "curl.h"
// Handle Ctrl-C to abort.
static bool s_bAborted = false;
static void __cdecl ctrlCHandler(int)
{
s_bAborted = true;
}
// File reading function.
static size_t readFunction(char * ptr, size_t size, size_t nmemb, void * stream)
{
FILE * pFile = (FILE *)stream;
if (s_bAborted || ferror(pFile))
{
return CURL_READFUNC_ABORT;
}
return fread(ptr, size, nmemb, pFile);
}
int main()
{
// Add your local file and server URL.
// Note: This problem was observed using an SFTP server.
const char * szLocalFile = "Test.bin";
const char * szRemoteUrl = "sftp://user:password#host/Test.bin";
curl_global_init(CURL_GLOBAL_ALL);
CURL * curl = curl_easy_init();
FILE * pFile = fopen(szLocalFile, "rb");
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(curl, CURLOPT_URL, szRemoteUrl);
curl_easy_setopt(curl, CURLOPT_READFUNCTION, readFunction);
curl_easy_setopt(curl, CURLOPT_READDATA, pFile);
curl_easy_setopt(curl, CURLOPT_APPEND, 0L);
printf("Uploading... Press Ctrl-C to abort\n");
signal(SIGINT, ctrlCHandler);
CURLcode eResult = curl_easy_perform(curl);
fclose(pFile);
if (eResult == CURLE_OK)
{
printf("Uploaded OK\n");
}
else if (eResult == CURLE_ABORTED_BY_CALLBACK)
{
printf("Upload aborted\n");
}
else
{
printf("Error: Upload failed: %d\n", eResult);
}
curl_easy_cleanup(curl);
curl_global_cleanup();
}
I'm writting a GUI application, that needs to periodically ask for data using requests, and then plot it in a widget. I created a wxThread which in Entry() ask for data using libcurl, then it shoots event to the main thread that activates plot updating function. But there is a strange issue with sending requests, always after a few iterations my request sending thread hangs, and when I kill it, then I got information about the error(Cannot connect to server.) Is there any way to make thread not to hang, but just ignore the error, and try to send request again? (This error only occours when server is online, when its offline I can get the error without hanging the thread.)
I thought it was due to some server error so I setted CURLOPT_TIMEOUT and CURLOPT_CONNECTIONTIMEOUT, but with no effect, just the name of the error changes after killing a thread to "Timeout error". Strangly enough when the colelcting data thread is restarted from GUI without closing the app it works perfectly. So It hangs only on the first run(exactly after 18-24 iterations) or I did not have opportunity to see it hangs again, but I did multiple tests.
Here is my code for sending, the app frezzes at curle_easy_perform(), it did not go into writting function.
struct stats
{
int y_data1;
int y_data2;
int x_data;
};
class OutputThread : public wxThread
{
public:
OutputThread(wxWindow* parent, std::string url) : wxThread(wxTHREAD_DETACHED)
{
_parentHandler = parent;
_url = url;
}
protected:
wxThread::ExitCode Entry()
{
while(!TestDestroy())
{
repidjson::Document response;
std::string raw_response = send_request(_url );
if(raw_response != "")
{
if(response.Parse(raw_response.c_str()).HasParseError())
{
std::cout << "Parse error!\n";
sleep(1);
continue;
}
else
{
stats new_stats;
new_stats.y_data1 = response["y_data1"].GetInt();
new_stats.y_data2 = response["y_data2"].GetInt();
new_stats.x_data = response["x_data"].GetInt();
wxCommandEvent event(wxEVT_COMMAND_OUTPUT_THREAD_UPDATE, wxID_ANY);
event.SetClientData(static_cast<void*>(&new_stats));
_parentHandler->GetEventHandler()->AddPendingEvent(event);
sleep(1);
}
}
}
}
private:
wxWindow* _parentHandler;
std::string _url;
};
std::string send_request(std::string url)
{
curl_global_init(CURL_GLOBAL_ALL);
auto handle = curl_easy_init();
std::stringstream returnData;
if(handle)
{
curl_easy_setopt(handle, CURLOPT_NOPROXY, "localhost");
curl_easy_setopt(handle, CURLOPT_URL, url.c_str());
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_data);
curl_easy_setopt(handle, CURLOPT_WRITEDATA, &returnData);
curl_easy_setopt(handle, CURLOPT_TIMEOUT, 1L);
curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT, 1L):
curl_easy_setopt(handle, CURLOPT_HTTPGET, 1);
auto ret = curl_easy_perform(handle); //here the thread hangs
if(ret != CURLE_OK)
{
std::cout << "curl_easy_perform() failed: " << curl_Easy_strerror(ret) << "\n";
returnData.clear();
returnData << "";
}
curl_easy_cleanup(handle);
}
curl_global_cleanup();
return returnData.str();
}
//Data writing function
size_t write_data(void* ptr, size_t size, size_t nmeb, void* stream)
{
std::string buf = std::string(static_cast<char*>(ptr), size * nmeb);
std::stringstream* response = static_cast<std::stringstream*>(stream);
response->write(buf.c_str(), (std::streamsize)buf.size());
return size*nmeb;
}
Parent is an wxPanel with graph and function that updates the plot.
wxEVT_COMMAND_OUTPUT_THREAD_UPDATE it is my custom event that activates plot updating function. I'm not sure if there is any point in reproducing the whole widget with graph so to make things short there is a button to start capturing data and to stop, what is equivalent of creating OutputThread and executing Entry() function until user press stop button.
Could it be a threading issue or is it just an error in sending function?
I have started working with libcurl, and simply tried running the basic code to get a file from an url. When I get this file using curl.exe, compiled with the same library, I get detect no random traffic on my localhost. However, when I run with my own executable, I get around 19 packets sent between two localhost ports.
I make sure to call curl_global_init(CURL_GLOBAL_WIN32) and curl_global_cleanup() after the method call.
What could be the cause of this traffic, and how could I make it go away?
int CurlFileDownloader::downloadSingleFile(const std::string& url, const std::string& destination) {
CURLcode res = CURLE_READ_ERROR;
mHandle = curl_easy_init();
if(mHandle) {
mData.destinationFolder = destination;
// Get the file name from the url
auto lastPos = url.find_last_of("/");
mData.fileName = url.substr(lastPos + 1);
curl_easy_setopt(mHandle, CURLOPT_URL, url.c_str());
/* Define our callback to get called when there's data to be written */
curl_easy_setopt(mHandle, CURLOPT_WRITEFUNCTION, &CurlFileDownloader::writeFileContent);
/* Set a pointer to our struct to pass to the callback */
curl_easy_setopt(mHandle, CURLOPT_WRITEDATA, &mData);
/* Switch on full protocol/debug output */
curl_easy_setopt(mHandle, CURLOPT_VERBOSE, 1L);
mLastError = curl_easy_perform(mHandle);
/* always cleanup */
curl_easy_cleanup(mHandle);
if (mData.fileStream.is_open()) {
mData.fileStream.close();
}
if(CURLE_OK != mLastError) {
std::cerr << "Curl error " << mLastError << std::endl;
}
}
return mLastError;
}
size_t CurlFileDownloader::writeFileContent(char *buffer, size_t size, size_t nmemb, void *cb_data) {
struct CurlCallbackData *data = (CurlCallbackData*)cb_data;
size_t written = 0;
if (data->fileStream.is_open()) {
data->fileStream.write(buffer, nmemb);
}
else {
/* listing output */
if (data->destinationFolder != "") {
data->fileStream.open(data->destinationFolder + "\\" + data->fileName, std::ios::out | std::ios::binary);
}
else {
data->fileStream.open(data->fileName, std::ios::out | std::ios::binary);
}
data->fileStream.write(buffer, nmemb);
}
return nmemb;
}
Here is a sample of what RawCap.exe is capturing.
The source of the localhost communication was the use of a socket pair for IPV4 Loopback. When removing the #USE_SOCKETPAIR from libCURL's socketpair.h, the issue went away.
My problem is that in one part of my program a double variable gets incorrectly set to 2.71179e-308 on one specific (virtual) computer (not any other). No crashes or anything like that. After much work I narrowed the problem down to a call to curl_easy_perform (the call has no other connection with said variable). If I fake the curl_easy_perform call and return before it executes the variable is not modified.
My first thought was that there was some problem with the callback write function, but having looked long and hard at it I can't find anything wrong, and replacing it with an empty function still didn't help the error.
My second thought was that perhaps my curl settings strings went out of scope before being used, but in my version of curl (7.43.0) they should be copied and stored by curl itself.
Now I'm at wits end and so turn to SO for some sort of hint at what could be wrong. This is the code for the class I use for all communication;
Header:
#include <string>
class HTTPClient
{
public:
enum Verb
{
V_GET,
V_POST,
V_PUT,
V_DELETE,
};
// Constructor is not thread safe. Make sure to initialize in main thread.
HTTPClient(const std::string& userAgent, const std::string& certPath, const std::string& cookieFile, const std::string& proxy = "");
~HTTPClient(void);
int MakeRequest(Verb verb, const std::string& url, int& responseCode, std::string& response);
private:
static size_t WriteFunction(void *ptr, size_t size, size_t nmemb, void *custom);
void* m_curl;
};
Source:
#include "HTTPClient.h"
#include <curl/curl.h>
HTTPClient::HTTPClient(const std::string& userAgent, const std::string& certPath, const std::string& cookieFile, const std::string& proxy)
{
curl_global_init(CURL_GLOBAL_DEFAULT);
m_curl = curl_easy_init();
if (m_curl)
{
// Indicate where the certificate is located
curl_easy_setopt(m_curl, CURLOPT_CAINFO, certPath.c_str());
// Indicate where the cookie file is located
curl_easy_setopt(m_curl, CURLOPT_COOKIEFILE, cookieFile.c_str());
curl_easy_setopt(m_curl, CURLOPT_COOKIEJAR, cookieFile.c_str());
// Set user agent
curl_easy_setopt(m_curl, CURLOPT_USERAGENT, userAgent.c_str());
// Set the response function
curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, &HTTPClient::WriteFunction);
// Set proxy if specified
if (!proxy.empty())
curl_easy_setopt(m_curl, CURLOPT_PROXY, proxy.c_str());
}
}
HTTPClient::~HTTPClient(void)
{
if (m_curl)
curl_easy_cleanup(m_curl);
curl_global_cleanup();
}
int HTTPClient::MakeRequest(Verb verb, const std::string& url, int& responseCode, std::string& response)
{
std::string protocol, server, path, parameters;
if (!m_curl)
return CURLE_FAILED_INIT;
// Set response data
response.clear();
curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &response);
switch (verb)
{
case V_GET:
curl_easy_setopt(m_curl, CURLOPT_CUSTOMREQUEST, "GET");
curl_easy_setopt(m_curl, CURLOPT_URL, url.c_str());
break;
// Other cases removed for brevity
}
// Execute command
CURLcode res = curl_easy_perform(m_curl); // <-- When this executes the variable gets damaged
// Get response code
long code;
curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &code);
responseCode = code;
return res;
}
size_t HTTPClient::WriteFunction(void *ptr, size_t size, size_t nmemb, void *custom)
{
size_t full_size = size * nmemb;
std::string* p_response = reinterpret_cast<std::string*>(custom);
p_response->reserve(full_size);
for (size_t i=0; i<full_size; ++i)
{
char c = reinterpret_cast<char*>(ptr)[i];
p_response->push_back(c);
}
return full_size;
}
Edit:
I have now run tests with WinDbg and have found out pretty much what happens (but not how to solve the problem).
The double that is trashed is in the release build put in the mmx7 register. It then sits there without being changed until curl_easy_perform is called but before the WriteFunction is called by curl_easy_perform.
Other than compiling my own version of curl so I can debug deeper I don't know how to get past this problem.
I’m writing the program sending POST-requests to the server and getting answers from it. (using curl library) (OS Linux, Red Hat Linux 3.2.2.-5). Sometimes I see , that response from server contains only second part of the message. ( I print _sResponse and sometimes I see full message(more often) and sometimes broken message(only last part of it)).
Class CurlSoapHandler
{
……..
static std::string _sResponse;
static std::string GetResponse() {return _sResponse;}
static size_t write_data(char *ptr, size_t size, size_t count, std::string *buffer)
{
int result = 0;
if (buffer != NULL)
{
std::string tmp_buffer(ptr, size * count);
_sResponse = tmp_buffer;
result = size * count;
}
else
{
std::cout<<"Buffer is not OK!"<<std::endl;
}
return result;
}
};
void CurlSoapHandler::DoRequest(const std::string& sRequest, std::string& sResponse)
{
CURL* _CURL;
CURLcode res;
struct curl_slist *headerlist=NULL;
headerlist = curl_slist_append(headerlist, "Content-Type:text/xml");
curl_global_init(CURL_GLOBAL_ALL);
_CURL = curl_easy_init();
if(_CURL)
{
curl_easy_setopt( _CURL, CURLOPT_URL, ttp://10.10.10.11:8083/Server/Server.asmx);
curl_easy_setopt( _CURL, CURLOPT_USERPWD, "user#password");
curl_easy_setopt( _CURL, CURLOPT_POSTFIELDS, sRequest.c_str());
curl_easy_setopt( _CURL, CURLOPT_HTTPHEADER, headerlist);
curl_easy_setopt( _CURL, CURLOPT_TIMEOUT, 20);
curl_easy_setopt( _CURL, CURLOPT_WRITEFUNCTION, CurlSoapHandler::write_data);
res = curl_easy_perform(_CURL);
sResponse = GetResponse();
if(res != CURLE_OK)
{
std::cerr<<"CURL message: "<<curl_easy_strerror(res)<<std::endl;
}
curl_easy_cleanup(_CURL);
}
else
{
std::cout<<"Curl initialization problem!"<<std::endl;
}
curl_global_cleanup();
curl_slist_free_all (headerlist);
}
I have no ideas why it occurs. Does someone have ideas? (May be it’s necessary to set some curl options, for example). How can I determine the reason for it and understand if it’s problem of my applications or not. I'd caught traffic using tcpdump utility (I run it on my Virtual Machine (VMWARe)) and saw full message in the dump. So, I think that server sends the correct response. Thanks in advance!
Just replace _sResponse = tmp_buffer; by _sResponse += tmp_buffer; shoulds work.