2022-05-18 09:58:59 -04:00
|
|
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
|
|
|
|
#include "HttpClient.h"
|
|
|
|
|
|
2022-05-26 18:07:41 -04:00
|
|
|
#if UE_WITH_HTTP_CLIENT
|
2022-05-18 09:58:59 -04:00
|
|
|
#include "DerivedDataLegacyCacheStore.h"
|
2022-05-18 16:21:02 -04:00
|
|
|
#include "HAL/IConsoleManager.h"
|
2022-05-18 09:58:59 -04:00
|
|
|
#include "Logging/LogMacros.h"
|
2022-05-31 12:42:17 -04:00
|
|
|
#include "Serialization/CompactBinary.h"
|
2022-05-18 09:58:59 -04:00
|
|
|
#include "Serialization/JsonReader.h"
|
|
|
|
|
#include "Serialization/JsonSerializer.h"
|
|
|
|
|
|
2022-05-26 18:07:41 -04:00
|
|
|
#if WITH_SSL
|
|
|
|
|
#include "Ssl.h"
|
|
|
|
|
#include <openssl/ssl.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#define CURL_NO_OLDIES
|
|
|
|
|
#if PLATFORM_MICROSOFT
|
|
|
|
|
#include "Microsoft/AllowMicrosoftPlatformTypes.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#if defined(PLATFORM_CURL_INCLUDE)
|
|
|
|
|
#include PLATFORM_CURL_INCLUDE
|
|
|
|
|
#else
|
|
|
|
|
#include "curl/curl.h"
|
|
|
|
|
#endif //defined(PLATFORM_CURL_INCLUDE)
|
|
|
|
|
|
|
|
|
|
#if PLATFORM_MICROSOFT
|
|
|
|
|
#include "Microsoft/HideMicrosoftPlatformTypes.h"
|
|
|
|
|
#endif
|
2022-05-18 09:58:59 -04:00
|
|
|
|
|
|
|
|
namespace UE
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
static bool bHttpEnableAsync = true;
|
|
|
|
|
static FAutoConsoleVariableRef CVarHttpEnableAsync(
|
|
|
|
|
TEXT("DDC.Http.EnableAsync"),
|
|
|
|
|
bHttpEnableAsync,
|
|
|
|
|
TEXT("If true, async operations are permitted, otherwise all operations are forced to be synchronous."),
|
|
|
|
|
ECVF_Default);
|
|
|
|
|
|
2022-05-26 18:07:41 -04:00
|
|
|
namespace Http::Private
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
static constexpr bool RequestDebugEnabled = false;
|
|
|
|
|
static constexpr bool RequestTimeoutEnabled = true;
|
|
|
|
|
static constexpr long RequestTimeoutSeconds = 30L;
|
|
|
|
|
static constexpr size_t RequestMaxBufferReserve = 104857600u;
|
|
|
|
|
|
|
|
|
|
struct FAsyncRequestData final : public DerivedData::FRequestBase
|
|
|
|
|
{
|
|
|
|
|
UE::DerivedData::IRequestOwner* Owner = nullptr;
|
|
|
|
|
UE::FHttpRequestPool* Pool = nullptr;
|
|
|
|
|
curl_slist* CurlHeaders = nullptr;
|
|
|
|
|
FString Uri;
|
2022-05-31 12:42:17 -04:00
|
|
|
UE::FHttpRequest::ERequestVerb Verb;
|
2022-05-26 18:07:41 -04:00
|
|
|
TArray<long, TInlineAllocator<4>> ExpectedErrorCodes;
|
|
|
|
|
UE::FHttpRequest::FOnHttpRequestComplete OnComplete;
|
|
|
|
|
UE::FLazyEvent Event {EEventMode::ManualReset};
|
|
|
|
|
|
|
|
|
|
void Reset()
|
|
|
|
|
{
|
|
|
|
|
if (CurlHeaders)
|
|
|
|
|
{
|
|
|
|
|
curl_slist_free_all(CurlHeaders);
|
|
|
|
|
CurlHeaders = nullptr;
|
|
|
|
|
}
|
|
|
|
|
Uri.Empty();
|
|
|
|
|
ExpectedErrorCodes.Empty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetPriority(UE::DerivedData::EPriority Priority) final {}
|
|
|
|
|
|
|
|
|
|
void Cancel() final
|
|
|
|
|
{
|
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(HttpDDC_Cancel);
|
|
|
|
|
Event.Wait();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Wait() final
|
|
|
|
|
{
|
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(HttpDDC_Wait);
|
|
|
|
|
Event.Wait();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct FHttpRequestStatics
|
|
|
|
|
{
|
|
|
|
|
#if WITH_SSL
|
|
|
|
|
static int SslCertVerify(int PreverifyOk, X509_STORE_CTX* Context)
|
|
|
|
|
{
|
|
|
|
|
if (PreverifyOk == 1)
|
|
|
|
|
{
|
|
|
|
|
SSL* Handle = static_cast<SSL*>(X509_STORE_CTX_get_ex_data(Context, SSL_get_ex_data_X509_STORE_CTX_idx()));
|
|
|
|
|
check(Handle);
|
|
|
|
|
|
|
|
|
|
SSL_CTX* SslContext = SSL_get_SSL_CTX(Handle);
|
|
|
|
|
check(SslContext);
|
|
|
|
|
|
|
|
|
|
FHttpRequest* Request = static_cast<FHttpRequest*>(SSL_CTX_get_app_data(SslContext));
|
|
|
|
|
check(Request);
|
|
|
|
|
|
|
|
|
|
const FString& Domain = Request->GetDomain();
|
|
|
|
|
|
|
|
|
|
if (!FSslModule::Get().GetCertificateManager().VerifySslCertificates(Context, Domain))
|
|
|
|
|
{
|
|
|
|
|
PreverifyOk = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return PreverifyOk;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static UE::FHttpRequest::FResultCode StaticSSLCTXFn(CURL* curl, void* sslctx, void* parm)
|
|
|
|
|
{
|
|
|
|
|
SSL_CTX* Context = static_cast<SSL_CTX*>(sslctx);
|
|
|
|
|
const ISslCertificateManager& CertificateManager = FSslModule::Get().GetCertificateManager();
|
|
|
|
|
|
|
|
|
|
CertificateManager.AddCertificatesToSslContext(Context);
|
|
|
|
|
SSL_CTX_set_verify(Context, SSL_CTX_get_verify_mode(Context), SslCertVerify);
|
|
|
|
|
SSL_CTX_set_app_data(Context, parm);
|
|
|
|
|
|
|
|
|
|
/* all set to go */
|
|
|
|
|
return CURLE_OK;
|
|
|
|
|
}
|
|
|
|
|
#endif //#if WITH_SSL
|
|
|
|
|
|
|
|
|
|
static size_t StaticDebugCallback(CURL * Handle, curl_infotype DebugInfoType, char * DebugInfo, size_t DebugInfoSize, void* UserData)
|
|
|
|
|
{
|
|
|
|
|
FHttpRequest* Request = static_cast<FHttpRequest*>(UserData);
|
|
|
|
|
|
|
|
|
|
switch (DebugInfoType)
|
|
|
|
|
{
|
|
|
|
|
case CURLINFO_TEXT:
|
|
|
|
|
{
|
|
|
|
|
// Truncate at 1023 characters. This is just an arbitrary number based on a buffer size seen in
|
|
|
|
|
// the libcurl code.
|
|
|
|
|
DebugInfoSize = FMath::Min(DebugInfoSize, (size_t)1023);
|
|
|
|
|
|
|
|
|
|
// Calculate the actual length of the string due to incorrect use of snprintf() in lib/vtls/openssl.c.
|
|
|
|
|
char* FoundNulPtr = (char*)memchr(DebugInfo, 0, DebugInfoSize);
|
|
|
|
|
int CalculatedSize = FoundNulPtr != nullptr ? FoundNulPtr - DebugInfo : DebugInfoSize;
|
|
|
|
|
|
|
|
|
|
auto ConvertedString = StringCast<TCHAR>(static_cast<const ANSICHAR*>(DebugInfo), CalculatedSize);
|
|
|
|
|
FString DebugText(ConvertedString.Length(), ConvertedString.Get());
|
|
|
|
|
DebugText.ReplaceInline(TEXT("\n"), TEXT(""), ESearchCase::CaseSensitive);
|
|
|
|
|
DebugText.ReplaceInline(TEXT("\r"), TEXT(""), ESearchCase::CaseSensitive);
|
|
|
|
|
UE_LOG(LogDerivedDataCache, VeryVerbose, TEXT("%s: %p: '%s'"), *Request->GetName(), Request, *DebugText);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case CURLINFO_HEADER_IN:
|
|
|
|
|
UE_LOG(LogDerivedDataCache, VeryVerbose, TEXT("%s: %p: Received header (%d bytes)"), *Request->GetName(), Request, DebugInfoSize);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case CURLINFO_DATA_IN:
|
|
|
|
|
UE_LOG(LogDerivedDataCache, VeryVerbose, TEXT("%s: %p: Received data (%d bytes)"), *Request->GetName(), Request, DebugInfoSize);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case CURLINFO_DATA_OUT:
|
|
|
|
|
UE_LOG(LogDerivedDataCache, VeryVerbose, TEXT("%s: %p: Sent data (%d bytes)"), *Request->GetName(), Request, DebugInfoSize);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case CURLINFO_SSL_DATA_IN:
|
|
|
|
|
UE_LOG(LogDerivedDataCache, VeryVerbose, TEXT("%s: %p: Received SSL data (%d bytes)"), *Request->GetName(), Request, DebugInfoSize);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case CURLINFO_SSL_DATA_OUT:
|
|
|
|
|
UE_LOG(LogDerivedDataCache, VeryVerbose, TEXT("%s: %p: Sent SSL data (%d bytes)"), *Request->GetName(), Request, DebugInfoSize);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static size_t StaticReadFn(void* Ptr, size_t SizeInBlocks, size_t BlockSizeInBytes, void* UserData)
|
|
|
|
|
{
|
|
|
|
|
FHttpRequest* Request = static_cast<FHttpRequest*>(UserData);
|
2022-05-31 12:42:17 -04:00
|
|
|
const size_t Offset = Request->BytesSent;
|
|
|
|
|
const size_t ReadSize = FMath::Min((size_t)Request->ReadCompositeBuffer.GetSize() - Offset, SizeInBlocks * BlockSizeInBytes);
|
|
|
|
|
Request->ReadCompositeBuffer.CopyTo(MakeMemoryView(Ptr, ReadSize), Request->BytesSent);
|
|
|
|
|
Request->BytesSent += ReadSize;
|
|
|
|
|
return ReadSize;
|
2022-05-26 18:07:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static size_t StaticWriteHeaderFn(void* Ptr, size_t SizeInBlocks, size_t BlockSizeInBytes, void* UserData)
|
|
|
|
|
{
|
|
|
|
|
FHttpRequest* Request = static_cast<FHttpRequest*>(UserData);
|
|
|
|
|
const size_t WriteSize = SizeInBlocks * BlockSizeInBytes;
|
2022-05-31 12:42:17 -04:00
|
|
|
TArray64<uint8>* WriteHeaderBufferPtr = Request->WriteHeaderBufferPtr;
|
2022-05-26 18:07:41 -04:00
|
|
|
if (WriteHeaderBufferPtr && WriteSize > 0)
|
|
|
|
|
{
|
|
|
|
|
const size_t CurrentBufferLength = WriteHeaderBufferPtr->Num();
|
|
|
|
|
if (CurrentBufferLength > 0)
|
|
|
|
|
{
|
|
|
|
|
// Remove the previous zero termination
|
|
|
|
|
(*WriteHeaderBufferPtr)[CurrentBufferLength-1] = ' ';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write the header
|
|
|
|
|
WriteHeaderBufferPtr->Append((const uint8*)Ptr, WriteSize + 1);
|
|
|
|
|
(*WriteHeaderBufferPtr)[WriteHeaderBufferPtr->Num()-1] = 0; // Zero terminate string
|
|
|
|
|
return WriteSize;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static size_t StaticWriteBodyFn(void* Ptr, size_t SizeInBlocks, size_t BlockSizeInBytes, void* UserData)
|
|
|
|
|
{
|
|
|
|
|
FHttpRequest* Request = static_cast<FHttpRequest*>(UserData);
|
|
|
|
|
const size_t WriteSize = SizeInBlocks * BlockSizeInBytes;
|
2022-05-31 12:42:17 -04:00
|
|
|
TArray64<uint8>* WriteDataBufferPtr = Request->WriteDataBufferPtr;
|
2022-05-26 18:07:41 -04:00
|
|
|
|
|
|
|
|
if (WriteDataBufferPtr && WriteSize > 0)
|
|
|
|
|
{
|
|
|
|
|
// If this is the first part of the body being received, try to reserve
|
|
|
|
|
// memory if content length is defined in the header.
|
|
|
|
|
if (Request->BytesReceived == 0 && Request->WriteHeaderBufferPtr)
|
|
|
|
|
{
|
|
|
|
|
static const ANSICHAR* ContentLengthHeaderStr = "Content-Length: ";
|
|
|
|
|
const ANSICHAR* Header = (const ANSICHAR*)Request->WriteHeaderBufferPtr->GetData();
|
|
|
|
|
|
|
|
|
|
if (const ANSICHAR* ContentLengthHeader = FCStringAnsi::Strstr(Header, ContentLengthHeaderStr))
|
|
|
|
|
{
|
|
|
|
|
size_t ContentLength = (size_t)FCStringAnsi::Atoi64(ContentLengthHeader + strlen(ContentLengthHeaderStr));
|
|
|
|
|
if (ContentLength > 0u && ContentLength < Http::Private::RequestMaxBufferReserve)
|
|
|
|
|
{
|
|
|
|
|
WriteDataBufferPtr->Reserve(ContentLength);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write to the target buffer
|
|
|
|
|
WriteDataBufferPtr->Append((const uint8*)Ptr, WriteSize);
|
|
|
|
|
Request->BytesReceived += WriteSize;
|
|
|
|
|
return WriteSize;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static size_t StaticSeekFn(void* UserData, curl_off_t Offset, int Origin)
|
|
|
|
|
{
|
|
|
|
|
FHttpRequest* Request = static_cast<FHttpRequest*>(UserData);
|
|
|
|
|
size_t NewPosition = 0;
|
|
|
|
|
|
|
|
|
|
switch (Origin)
|
|
|
|
|
{
|
|
|
|
|
case SEEK_SET: NewPosition = Offset; break;
|
|
|
|
|
case SEEK_CUR: NewPosition = Request->BytesSent + Offset; break;
|
2022-05-31 12:42:17 -04:00
|
|
|
case SEEK_END: NewPosition = Request->ReadCompositeBuffer.GetSize() + Offset; break;
|
2022-05-26 18:07:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Make sure we don't seek outside of the buffer
|
2022-05-31 12:42:17 -04:00
|
|
|
if (NewPosition < 0 || NewPosition >= Request->ReadCompositeBuffer.GetSize())
|
2022-05-26 18:07:41 -04:00
|
|
|
{
|
|
|
|
|
return CURL_SEEKFUNC_FAIL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update the used offset
|
|
|
|
|
Request->BytesSent = NewPosition;
|
|
|
|
|
return CURL_SEEKFUNC_OK;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
FHttpRequest::FHttpRequest(FStringView InDomain, FStringView InEffectiveDomain, FHttpAccessToken* InAuthorizationToken, FHttpSharedData* InSharedData, bool bInLogErrors)
|
2022-05-18 09:58:59 -04:00
|
|
|
: SharedData(InSharedData)
|
|
|
|
|
, AsyncData(nullptr)
|
|
|
|
|
, bLogErrors(bInLogErrors)
|
|
|
|
|
, Domain(InDomain)
|
|
|
|
|
, EffectiveDomain(InEffectiveDomain)
|
|
|
|
|
, AuthorizationToken(InAuthorizationToken)
|
|
|
|
|
{
|
|
|
|
|
Curl = curl_easy_init();
|
|
|
|
|
Reset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FHttpRequest::~FHttpRequest()
|
|
|
|
|
{
|
2022-05-26 18:07:41 -04:00
|
|
|
curl_easy_cleanup(static_cast<CURL*>(Curl));
|
2022-05-18 09:58:59 -04:00
|
|
|
check(!AsyncData);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FHttpRequest::Reset()
|
|
|
|
|
{
|
|
|
|
|
Headers.Reset();
|
|
|
|
|
ResponseHeader.Reset();
|
|
|
|
|
ResponseBuffer.Reset();
|
|
|
|
|
ResponseCode = 0;
|
2022-05-31 12:42:17 -04:00
|
|
|
ReadCompositeBuffer.Reset();
|
2022-05-18 09:58:59 -04:00
|
|
|
WriteDataBufferPtr = nullptr;
|
|
|
|
|
WriteHeaderBufferPtr = nullptr;
|
|
|
|
|
BytesSent = 0;
|
|
|
|
|
BytesReceived = 0;
|
|
|
|
|
Attempts = 0;
|
2022-05-26 18:07:41 -04:00
|
|
|
ResultCode = CURL_LAST;
|
2022-05-18 09:58:59 -04:00
|
|
|
|
2022-05-26 18:07:41 -04:00
|
|
|
CURL* LocalCurl = static_cast<CURL*>(Curl);
|
|
|
|
|
curl_easy_reset(LocalCurl);
|
2022-05-18 09:58:59 -04:00
|
|
|
check(!AsyncData);
|
|
|
|
|
|
|
|
|
|
// Options that are always set for all connections.
|
2022-05-26 18:07:41 -04:00
|
|
|
if constexpr(Http::Private::RequestTimeoutEnabled)
|
|
|
|
|
{
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_CONNECTTIMEOUT, Http::Private::RequestTimeoutSeconds);
|
|
|
|
|
}
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_NOSIGNAL, 1L);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_DNS_CACHE_TIMEOUT, 300L); // Don't re-resolve every minute
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_SHARE, SharedData ? SharedData->GetCurlShare() : nullptr);
|
2022-05-18 09:58:59 -04:00
|
|
|
// Response functions
|
2022-05-26 18:07:41 -04:00
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_HEADERDATA, this);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_HEADERFUNCTION, Http::Private::FHttpRequestStatics::StaticWriteHeaderFn);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_WRITEDATA, this);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_WRITEFUNCTION, Http::Private::FHttpRequestStatics::StaticWriteBodyFn);
|
2022-05-19 12:54:26 -04:00
|
|
|
#if WITH_SSL
|
|
|
|
|
// SSL options
|
2022-05-26 18:07:41 -04:00
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_USE_SSL, CURLUSESSL_ALL);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_SSL_VERIFYPEER, 1);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_SSL_VERIFYHOST, 1);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_SSLCERTTYPE, "PEM");
|
2022-05-18 09:58:59 -04:00
|
|
|
// SSL certification verification
|
2022-05-26 18:07:41 -04:00
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_CAINFO, nullptr);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_SSL_CTX_FUNCTION, Http::Private::FHttpRequestStatics::StaticSSLCTXFn);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_SSL_CTX_DATA, this);
|
2022-05-19 12:54:26 -04:00
|
|
|
#endif //#if WITH_SSL
|
2022-05-18 09:58:59 -04:00
|
|
|
// Rewind method, handle special error case where request need to rewind data stream
|
2022-05-26 18:07:41 -04:00
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_SEEKFUNCTION, Http::Private::FHttpRequestStatics::StaticSeekFn);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_SEEKDATA, this);
|
2022-05-18 09:58:59 -04:00
|
|
|
// Set minimum speed behavior to allow operations to abort if the transfer speed is poor for the given duration (1kbps over a 30 second span)
|
2022-05-26 18:07:41 -04:00
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_LOW_SPEED_TIME, 30L);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_LOW_SPEED_LIMIT, 1024L);
|
2022-05-18 09:58:59 -04:00
|
|
|
// Debug hooks
|
2022-05-26 18:07:41 -04:00
|
|
|
if constexpr(Http::Private::RequestDebugEnabled)
|
|
|
|
|
{
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_DEBUGDATA, this);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_DEBUGFUNCTION, Http::Private::FHttpRequestStatics::StaticDebugCallback);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_VERBOSE, 1L);
|
|
|
|
|
}
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_PRIVATE, this);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
|
2022-05-18 09:58:59 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FHttpRequest::PrepareToRetry()
|
|
|
|
|
{
|
|
|
|
|
ResponseHeader.Reset();
|
|
|
|
|
ResponseBuffer.Reset();
|
|
|
|
|
ResponseCode = 0;
|
|
|
|
|
BytesSent = 0;
|
|
|
|
|
BytesReceived = 0;
|
2022-05-26 18:07:41 -04:00
|
|
|
ResultCode = CURL_LAST;
|
2022-05-18 09:58:59 -04:00
|
|
|
++Attempts;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
FHttpRequest::EResult FHttpRequest::PerformBlockingDownload(FStringView Uri,
|
|
|
|
|
TArray64<uint8>* Buffer,
|
|
|
|
|
EHttpContentType AcceptType,
|
|
|
|
|
TConstArrayView<long> ExpectedErrorCodes)
|
2022-05-18 09:58:59 -04:00
|
|
|
{
|
2022-05-26 18:07:41 -04:00
|
|
|
curl_easy_setopt(static_cast<CURL*>(Curl), CURLOPT_HTTPGET, 1L);
|
2022-05-18 09:58:59 -04:00
|
|
|
WriteDataBufferPtr = Buffer;
|
2022-05-31 12:42:17 -04:00
|
|
|
|
|
|
|
|
AddContentTypeHeader(TEXTVIEW("Accept"), AcceptType);
|
2022-05-18 09:58:59 -04:00
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
return PerformBlocking(Uri, ERequestVerb::Get, 0u, ExpectedErrorCodes);
|
2022-05-18 09:58:59 -04:00
|
|
|
}
|
|
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
void FHttpRequest::EnqueueAsyncDownload(UE::DerivedData::IRequestOwner& Owner,
|
|
|
|
|
FHttpRequestPool* Pool,
|
|
|
|
|
FStringView Uri,
|
|
|
|
|
FOnHttpRequestComplete&& OnComplete,
|
|
|
|
|
EHttpContentType AcceptType,
|
|
|
|
|
TConstArrayView<long> ExpectedErrorCodes)
|
2022-05-18 09:58:59 -04:00
|
|
|
{
|
2022-05-26 18:07:41 -04:00
|
|
|
curl_easy_setopt(static_cast<CURL*>(Curl), CURLOPT_HTTPGET, 1L);
|
2022-05-18 09:58:59 -04:00
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
return EnqueueAsync(Owner, Pool, Uri, ERequestVerb::Get, 0u, MoveTemp(OnComplete), ExpectedErrorCodes);
|
2022-05-18 09:58:59 -04:00
|
|
|
}
|
|
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
void FHttpRequest::AddHeader(FStringView Header, FStringView Value)
|
2022-05-18 09:58:59 -04:00
|
|
|
{
|
2022-05-26 18:07:41 -04:00
|
|
|
check(ResultCode == CURL_LAST); // Cannot set header after request is sent
|
2022-05-31 12:42:17 -04:00
|
|
|
TStringBuilder<128> Sb;
|
|
|
|
|
Sb << Header << TEXTVIEW(": ") << Value;
|
|
|
|
|
Headers.Emplace(Sb);
|
2022-05-18 09:58:59 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FHttpRequest::GetHeader(const ANSICHAR* Header, FString& OutValue) const
|
|
|
|
|
{
|
2022-05-26 18:07:41 -04:00
|
|
|
check(ResultCode != CURL_LAST); // Cannot query headers before request is sent
|
2022-05-18 09:58:59 -04:00
|
|
|
|
|
|
|
|
const ANSICHAR* HeadersBuffer = (const ANSICHAR*) ResponseHeader.GetData();
|
|
|
|
|
size_t HeaderLen = strlen(Header);
|
|
|
|
|
|
|
|
|
|
// Find the header key in the (ANSI) response buffer. If not found we can exist immediately
|
|
|
|
|
if (const ANSICHAR* Found = FCStringAnsi::Stristr(HeadersBuffer, Header))
|
|
|
|
|
{
|
|
|
|
|
const ANSICHAR* Linebreak = strchr(Found, '\r');
|
|
|
|
|
const ANSICHAR* ValueStart = Found + HeaderLen + 2; //colon and space
|
|
|
|
|
const size_t ValueSize = Linebreak - ValueStart;
|
|
|
|
|
FUTF8ToTCHAR TCHARData(ValueStart, ValueSize);
|
|
|
|
|
OutValue = FString(TCHARData.Length(), TCHARData.Get());
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TSharedPtr<FJsonObject> FHttpRequest::GetResponseAsJsonObject() const
|
|
|
|
|
{
|
|
|
|
|
FString Response = GetAnsiBufferAsString(ResponseBuffer);
|
|
|
|
|
|
|
|
|
|
TSharedPtr<FJsonObject> JsonObject;
|
|
|
|
|
TSharedRef<TJsonReader<> > JsonReader = TJsonReaderFactory<>::Create(Response);
|
|
|
|
|
if (!FJsonSerializer::Deserialize(JsonReader, JsonObject) || !JsonObject.IsValid())
|
|
|
|
|
{
|
|
|
|
|
return TSharedPtr<FJsonObject>(nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return JsonObject;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TArray<TSharedPtr<FJsonValue>> FHttpRequest::GetResponseAsJsonArray() const
|
|
|
|
|
{
|
|
|
|
|
FString Response = GetAnsiBufferAsString(ResponseBuffer);
|
|
|
|
|
|
|
|
|
|
TArray<TSharedPtr<FJsonValue>> JsonArray;
|
|
|
|
|
TSharedRef<TJsonReader<>> JsonReader = TJsonReaderFactory<>::Create(Response);
|
|
|
|
|
FJsonSerializer::Deserialize(JsonReader, JsonArray);
|
|
|
|
|
return JsonArray;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FHttpRequest::AllowAsync()
|
|
|
|
|
{
|
|
|
|
|
if (!FGenericPlatformProcess::SupportsMultithreading() || !bHttpEnableAsync)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-26 18:07:41 -04:00
|
|
|
void FHttpRequest::CompleteAsync(FResultCode Result)
|
2022-05-18 09:58:59 -04:00
|
|
|
{
|
2022-05-26 18:07:41 -04:00
|
|
|
ResultCode = Result;
|
2022-05-18 09:58:59 -04:00
|
|
|
|
|
|
|
|
// Get response code
|
2022-05-26 18:07:41 -04:00
|
|
|
curl_easy_getinfo(static_cast<CURL*>(Curl), CURLINFO_RESPONSE_CODE, &ResponseCode);
|
2022-05-18 09:58:59 -04:00
|
|
|
|
|
|
|
|
LogResult(Result, *AsyncData->Uri, AsyncData->Verb, AsyncData->ExpectedErrorCodes);
|
|
|
|
|
|
|
|
|
|
ECompletionBehavior Behavior;
|
|
|
|
|
{
|
|
|
|
|
UE::DerivedData::FRequestBarrier Barrier(*AsyncData->Owner, UE::DerivedData::ERequestBarrierFlags::Priority);
|
2022-05-31 12:42:17 -04:00
|
|
|
FHttpRequest::EResult HttpResult;
|
2022-05-26 18:07:41 -04:00
|
|
|
switch (ResultCode)
|
2022-05-18 09:58:59 -04:00
|
|
|
{
|
|
|
|
|
case CURLE_OK:
|
2022-05-31 12:42:17 -04:00
|
|
|
HttpResult = EResult::Success;
|
2022-05-18 09:58:59 -04:00
|
|
|
break;
|
|
|
|
|
case CURLE_OPERATION_TIMEDOUT:
|
2022-05-31 12:42:17 -04:00
|
|
|
HttpResult = EResult::FailedTimeout;
|
2022-05-18 09:58:59 -04:00
|
|
|
break;
|
|
|
|
|
default:
|
2022-05-31 12:42:17 -04:00
|
|
|
HttpResult = EResult::Failed;
|
2022-05-18 09:58:59 -04:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
Behavior = AsyncData->OnComplete(HttpResult, this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Behavior == ECompletionBehavior::Retry)
|
|
|
|
|
{
|
|
|
|
|
PrepareToRetry();
|
|
|
|
|
SharedData->AddRequest(Curl);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Clean up
|
|
|
|
|
curl_slist_free_all(AsyncData->CurlHeaders);
|
|
|
|
|
|
|
|
|
|
AsyncData->Owner->End(AsyncData, [this, Behavior]
|
|
|
|
|
{
|
|
|
|
|
AsyncData->Event.Trigger();
|
|
|
|
|
FHttpRequestPool* Pool = AsyncData->Pool;
|
|
|
|
|
AsyncData = nullptr;
|
|
|
|
|
if (Pool)
|
|
|
|
|
{
|
|
|
|
|
Pool->ReleaseRequestToPool(this);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
void FHttpRequest::AddContentTypeHeader(FStringView Header, EHttpContentType Type)
|
2022-05-18 09:58:59 -04:00
|
|
|
{
|
2022-05-31 12:42:17 -04:00
|
|
|
if (Type != EHttpContentType::UnspecifiedContentType)
|
|
|
|
|
{
|
|
|
|
|
AddHeader(Header, GetHttpMimeType(Type));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const char* GetSessionIdHeader()
|
|
|
|
|
{
|
|
|
|
|
static FCbObjectId SessionId = FCbObjectId::NewObjectId();
|
|
|
|
|
|
|
|
|
|
static const char* HeaderString = []
|
|
|
|
|
{
|
|
|
|
|
static TAnsiStringBuilder<64> SessionIdHeader;
|
|
|
|
|
SessionIdHeader << "UE-Session: " << SessionId;
|
|
|
|
|
return SessionIdHeader.GetData();
|
|
|
|
|
}();
|
|
|
|
|
|
|
|
|
|
return HeaderString;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static std::atomic<int> GRequestId{1};
|
|
|
|
|
|
|
|
|
|
curl_slist* FHttpRequest::PrepareToIssueRequest(FStringView Uri, ERequestVerb Verb, uint64 ContentLength)
|
|
|
|
|
{
|
|
|
|
|
// Strip any leading slashes because we compose the prefix and the suffix with a separating slash below
|
|
|
|
|
while (Uri.StartsWith(TEXT('/')))
|
|
|
|
|
{
|
|
|
|
|
Uri.RightChopInline(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TAnsiStringBuilder<32> RequestIdHeader;
|
|
|
|
|
RequestIdHeader << "UE-Request: " << GRequestId.fetch_add(1, std::memory_order_relaxed);
|
|
|
|
|
|
|
|
|
|
const char* CommonHeaders[] =
|
|
|
|
|
{
|
|
|
|
|
GetSessionIdHeader(),
|
|
|
|
|
RequestIdHeader.GetData(),
|
2022-05-18 09:58:59 -04:00
|
|
|
"User-Agent: Unreal Engine",
|
2022-05-31 12:42:17 -04:00
|
|
|
// Strip any Expect: 100-Continue header since this just introduces latency
|
|
|
|
|
"Expect:",
|
2022-05-18 09:58:59 -04:00
|
|
|
nullptr
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Setup request options
|
2022-05-31 12:42:17 -04:00
|
|
|
curl_easy_setopt(static_cast<CURL*>(Curl), CURLOPT_URL, *WriteToUtf8String<512>(EffectiveDomain, '/', Uri));
|
2022-05-18 09:58:59 -04:00
|
|
|
|
|
|
|
|
// Setup response header buffer. If caller has not setup a response data buffer, use internal.
|
|
|
|
|
WriteHeaderBufferPtr = &ResponseHeader;
|
|
|
|
|
if (WriteDataBufferPtr == nullptr)
|
|
|
|
|
{
|
|
|
|
|
WriteDataBufferPtr = &ResponseBuffer;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
if ((Verb != ERequestVerb::Delete) && (Verb != ERequestVerb::Get))
|
|
|
|
|
{
|
|
|
|
|
Headers.Add(FString::Printf(TEXT("Content-Length: %d"), ContentLength));
|
|
|
|
|
}
|
2022-05-18 09:58:59 -04:00
|
|
|
|
|
|
|
|
// And auth token if it's set
|
|
|
|
|
if (AuthorizationToken)
|
|
|
|
|
{
|
|
|
|
|
Headers.Add(AuthorizationToken->GetHeader());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build headers list
|
|
|
|
|
curl_slist* CurlHeaders = nullptr;
|
|
|
|
|
// Add common headers
|
|
|
|
|
for (uint8 i = 0; CommonHeaders[i] != nullptr; ++i)
|
|
|
|
|
{
|
|
|
|
|
CurlHeaders = curl_slist_append(CurlHeaders, CommonHeaders[i]);
|
|
|
|
|
}
|
|
|
|
|
// Setup added headers
|
|
|
|
|
for (const FString& Header : Headers)
|
|
|
|
|
{
|
|
|
|
|
CurlHeaders = curl_slist_append(CurlHeaders, TCHAR_TO_ANSI(*Header));
|
|
|
|
|
}
|
2022-05-26 18:07:41 -04:00
|
|
|
curl_easy_setopt(static_cast<CURL*>(Curl), CURLOPT_HTTPHEADER, CurlHeaders);
|
2022-05-18 09:58:59 -04:00
|
|
|
return CurlHeaders;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
FHttpRequest::EResult FHttpRequest::PerformBlocking(FStringView Uri, ERequestVerb Verb, uint64 ContentLength, TConstArrayView<long> ExpectedErrorCodes)
|
2022-05-18 09:58:59 -04:00
|
|
|
{
|
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(HttpDDC_CurlPerform);
|
|
|
|
|
|
|
|
|
|
// Build headers list
|
2022-05-31 12:42:17 -04:00
|
|
|
curl_slist* CurlHeaders = PrepareToIssueRequest(Uri, Verb, ContentLength);
|
2022-05-18 09:58:59 -04:00
|
|
|
|
|
|
|
|
// Shots fired!
|
2022-05-26 18:07:41 -04:00
|
|
|
ResultCode = curl_easy_perform(static_cast<CURL*>(Curl));
|
2022-05-18 09:58:59 -04:00
|
|
|
|
|
|
|
|
// Get response code
|
2022-05-26 18:07:41 -04:00
|
|
|
curl_easy_getinfo(static_cast<CURL*>(Curl), CURLINFO_RESPONSE_CODE, &ResponseCode);
|
2022-05-18 09:58:59 -04:00
|
|
|
|
2022-05-26 18:07:41 -04:00
|
|
|
LogResult(ResultCode, Uri, Verb, ExpectedErrorCodes);
|
2022-05-18 09:58:59 -04:00
|
|
|
|
|
|
|
|
// Clean up
|
|
|
|
|
curl_slist_free_all(CurlHeaders);
|
|
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
return ResultCode == CURLE_OK ? EResult::Success : EResult::Failed;
|
2022-05-18 09:58:59 -04:00
|
|
|
}
|
|
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
void FHttpRequest::EnqueueAsync(UE::DerivedData::IRequestOwner& Owner, FHttpRequestPool* Pool, FStringView Uri, ERequestVerb Verb, uint64 ContentLength, FOnHttpRequestComplete&& OnComplete, TConstArrayView<long> ExpectedErrorCodes)
|
2022-05-18 09:58:59 -04:00
|
|
|
{
|
|
|
|
|
if (!AllowAsync())
|
|
|
|
|
{
|
|
|
|
|
while (OnComplete(PerformBlocking(Uri, Verb, ContentLength, ExpectedErrorCodes), this) == ECompletionBehavior::Retry)
|
|
|
|
|
{
|
|
|
|
|
PrepareToRetry();
|
|
|
|
|
}
|
|
|
|
|
if (Pool)
|
|
|
|
|
{
|
|
|
|
|
Pool->ReleaseRequestToPool(this);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(HttpDDC_CurlEnqueueAsync);
|
2022-05-26 18:07:41 -04:00
|
|
|
AsyncData = new Http::Private::FAsyncRequestData;
|
2022-05-18 09:58:59 -04:00
|
|
|
AsyncData->Owner = &Owner;
|
|
|
|
|
AsyncData->Pool = Pool;
|
2022-05-31 12:42:17 -04:00
|
|
|
AsyncData->CurlHeaders = PrepareToIssueRequest(Uri, Verb, ContentLength);
|
2022-05-18 09:58:59 -04:00
|
|
|
AsyncData->Uri = Uri;
|
|
|
|
|
AsyncData->Verb = Verb;
|
|
|
|
|
AsyncData->ExpectedErrorCodes = ExpectedErrorCodes;
|
|
|
|
|
AsyncData->OnComplete = MoveTemp(OnComplete);
|
|
|
|
|
AsyncData->Owner->Begin(AsyncData);
|
|
|
|
|
|
|
|
|
|
SharedData->AddRequest(Curl);
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
void FHttpRequest::LogResult(FResultCode Result, FStringView Uri, ERequestVerb Verb, TConstArrayView<long> ExpectedErrorCodes) const
|
2022-05-18 09:58:59 -04:00
|
|
|
{
|
|
|
|
|
if (Result == CURLE_OK)
|
|
|
|
|
{
|
|
|
|
|
bool bSuccess = false;
|
|
|
|
|
const TCHAR* VerbStr = nullptr;
|
|
|
|
|
FString AdditionalInfo;
|
|
|
|
|
|
|
|
|
|
switch (Verb)
|
|
|
|
|
{
|
2022-05-31 12:42:17 -04:00
|
|
|
case ERequestVerb::Head:
|
2022-05-18 09:58:59 -04:00
|
|
|
bSuccess = (ExpectedErrorCodes.Contains(ResponseCode) || IsSuccessResponse(ResponseCode));
|
|
|
|
|
VerbStr = TEXT("querying");
|
|
|
|
|
break;
|
2022-05-31 12:42:17 -04:00
|
|
|
case ERequestVerb::Get:
|
2022-05-18 09:58:59 -04:00
|
|
|
bSuccess = (ExpectedErrorCodes.Contains(ResponseCode) || IsSuccessResponse(ResponseCode));
|
|
|
|
|
VerbStr = TEXT("fetching");
|
|
|
|
|
AdditionalInfo = FString::Printf(TEXT("Received: %d bytes."), BytesReceived);
|
|
|
|
|
break;
|
2022-05-31 12:42:17 -04:00
|
|
|
case ERequestVerb::Put:
|
2022-05-18 09:58:59 -04:00
|
|
|
bSuccess = (ExpectedErrorCodes.Contains(ResponseCode) || IsSuccessResponse(ResponseCode));
|
|
|
|
|
VerbStr = TEXT("updating");
|
|
|
|
|
AdditionalInfo = FString::Printf(TEXT("Sent: %d bytes."), BytesSent);
|
|
|
|
|
break;
|
2022-05-31 12:42:17 -04:00
|
|
|
case ERequestVerb::Post:
|
2022-05-18 09:58:59 -04:00
|
|
|
bSuccess = (ExpectedErrorCodes.Contains(ResponseCode) || IsSuccessResponse(ResponseCode));
|
|
|
|
|
VerbStr = TEXT("posting");
|
|
|
|
|
break;
|
2022-05-31 12:42:17 -04:00
|
|
|
case ERequestVerb::Delete:
|
2022-05-18 09:58:59 -04:00
|
|
|
bSuccess = (ExpectedErrorCodes.Contains(ResponseCode) || IsSuccessResponse(ResponseCode));
|
|
|
|
|
VerbStr = TEXT("deleting");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (bSuccess)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(
|
|
|
|
|
LogDerivedDataCache,
|
|
|
|
|
Verbose,
|
2022-05-31 12:42:17 -04:00
|
|
|
TEXT("%s: Finished %s HTTP cache entry (response %d) from %.*s. %s"),
|
2022-05-18 09:58:59 -04:00
|
|
|
*GetName(),
|
|
|
|
|
VerbStr,
|
|
|
|
|
ResponseCode,
|
2022-05-31 12:42:17 -04:00
|
|
|
Uri.Len(), Uri.GetData(),
|
2022-05-18 09:58:59 -04:00
|
|
|
*AdditionalInfo
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
else if(bLogErrors)
|
|
|
|
|
{
|
|
|
|
|
// Print the response body if we got one, otherwise print header.
|
|
|
|
|
FString Response = GetAnsiBufferAsString(ResponseBuffer.Num() > 0 ? ResponseBuffer : ResponseHeader);
|
|
|
|
|
Response.ReplaceCharInline(TEXT('\n'), TEXT(' '));
|
|
|
|
|
Response.ReplaceCharInline(TEXT('\r'), TEXT(' '));
|
|
|
|
|
// Dont log access denied as error, since tokens can expire mid session
|
|
|
|
|
if (ResponseCode == 401)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(
|
|
|
|
|
LogDerivedDataCache,
|
|
|
|
|
Verbose,
|
2022-05-31 12:42:17 -04:00
|
|
|
TEXT("%s: Failed %s HTTP cache entry (response %d) from %.*s. Response: %s"),
|
2022-05-18 09:58:59 -04:00
|
|
|
*GetName(),
|
|
|
|
|
VerbStr,
|
|
|
|
|
ResponseCode,
|
2022-05-31 12:42:17 -04:00
|
|
|
Uri.Len(), Uri.GetData(),
|
2022-05-18 09:58:59 -04:00
|
|
|
*Response
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(
|
|
|
|
|
LogDerivedDataCache,
|
|
|
|
|
Display,
|
2022-05-31 12:42:17 -04:00
|
|
|
TEXT("%s: Failed %s HTTP cache entry (response %d) from %.*s. Response: %s"),
|
2022-05-18 09:58:59 -04:00
|
|
|
*GetName(),
|
|
|
|
|
VerbStr,
|
|
|
|
|
ResponseCode,
|
2022-05-31 12:42:17 -04:00
|
|
|
Uri.Len(), Uri.GetData(),
|
2022-05-18 09:58:59 -04:00
|
|
|
*Response
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if(bLogErrors)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(
|
|
|
|
|
LogDerivedDataCache,
|
|
|
|
|
Display,
|
|
|
|
|
TEXT("%s: Error while connecting to %s: %s"),
|
|
|
|
|
*GetName(),
|
|
|
|
|
*EffectiveDomain,
|
2022-05-26 18:07:41 -04:00
|
|
|
ANSI_TO_TCHAR(curl_easy_strerror(static_cast<CURLcode>(Result)))
|
2022-05-18 09:58:59 -04:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
FHttpRequest::EResult FHttpRequest::PerformBlockingPut(FStringView Uri,
|
|
|
|
|
FCompositeBuffer Buffer,
|
|
|
|
|
EHttpContentType ContentType,
|
|
|
|
|
TConstArrayView<long> ExpectedErrorCodes)
|
2022-05-18 09:58:59 -04:00
|
|
|
{
|
2022-05-26 18:07:41 -04:00
|
|
|
uint64 ContentLength = 0u;
|
|
|
|
|
|
|
|
|
|
CURL* LocalCurl = static_cast<CURL*>(Curl);
|
2022-05-31 12:42:17 -04:00
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_UPLOAD, 1L);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_INFILESIZE, Buffer.GetSize());
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_READDATA, this);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_READFUNCTION, Http::Private::FHttpRequestStatics::StaticReadFn);
|
|
|
|
|
AddContentTypeHeader(TEXTVIEW("Content-Type"), ContentType);
|
|
|
|
|
ContentLength = Buffer.GetSize();
|
|
|
|
|
ReadCompositeBuffer = Buffer;
|
2022-05-26 18:07:41 -04:00
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
return PerformBlocking(Uri, ERequestVerb::Put, ContentLength, ExpectedErrorCodes);
|
2022-05-18 09:58:59 -04:00
|
|
|
}
|
|
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
FHttpRequest::EResult FHttpRequest::PerformBlockingPost(FStringView Uri,
|
|
|
|
|
FCompositeBuffer Buffer,
|
|
|
|
|
EHttpContentType ContentType,
|
|
|
|
|
EHttpContentType AcceptType,
|
|
|
|
|
TConstArrayView<long> ExpectedErrorCodes)
|
2022-05-18 09:58:59 -04:00
|
|
|
{
|
2022-05-26 18:07:41 -04:00
|
|
|
uint64 ContentLength = 0u;
|
2022-05-18 09:58:59 -04:00
|
|
|
|
2022-05-26 18:07:41 -04:00
|
|
|
CURL* LocalCurl = static_cast<CURL*>(Curl);
|
2022-05-31 12:42:17 -04:00
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_POST, 1L);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_POSTFIELDSIZE, Buffer.GetSize());
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_READDATA, this);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_READFUNCTION, Http::Private::FHttpRequestStatics::StaticReadFn);
|
|
|
|
|
AddContentTypeHeader(TEXTVIEW("Content-Type"), ContentType);
|
|
|
|
|
AddContentTypeHeader(TEXTVIEW("Accept"), AcceptType);
|
|
|
|
|
ContentLength = Buffer.GetSize();
|
|
|
|
|
ReadCompositeBuffer = Buffer;
|
2022-05-18 09:58:59 -04:00
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
return PerformBlocking(Uri, ERequestVerb::Post, ContentLength, ExpectedErrorCodes);
|
2022-05-18 09:58:59 -04:00
|
|
|
}
|
|
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
void FHttpRequest::EnqueueAsyncPut(UE::DerivedData::IRequestOwner& Owner,
|
|
|
|
|
FHttpRequestPool* Pool,
|
|
|
|
|
FStringView Uri,
|
|
|
|
|
FCompositeBuffer Buffer,
|
|
|
|
|
FOnHttpRequestComplete&& OnComplete,
|
|
|
|
|
EHttpContentType ContentType,
|
|
|
|
|
TConstArrayView<long> ExpectedErrorCodes)
|
2022-05-26 18:07:41 -04:00
|
|
|
{
|
2022-05-31 12:42:17 -04:00
|
|
|
uint64 ContentLength = 0u;
|
2022-05-26 18:07:41 -04:00
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
CURL* LocalCurl = static_cast<CURL*>(Curl);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_UPLOAD, 1L);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_INFILESIZE, Buffer.GetSize());
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_READDATA, this);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_READFUNCTION, Http::Private::FHttpRequestStatics::StaticReadFn);
|
|
|
|
|
AddContentTypeHeader(TEXTVIEW("Content-Type"), ContentType);
|
|
|
|
|
ReadCompositeBuffer = Buffer;
|
|
|
|
|
ContentLength = Buffer.GetSize();
|
2022-05-26 18:07:41 -04:00
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
return EnqueueAsync(Owner, Pool, Uri, ERequestVerb::Put, ContentLength, MoveTemp(OnComplete), ExpectedErrorCodes);
|
2022-05-26 18:07:41 -04:00
|
|
|
}
|
|
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
void FHttpRequest::EnqueueAsyncPost(UE::DerivedData::IRequestOwner& Owner,
|
|
|
|
|
FHttpRequestPool* Pool,
|
|
|
|
|
FStringView Uri,
|
|
|
|
|
FCompositeBuffer Buffer,
|
|
|
|
|
FOnHttpRequestComplete&& OnComplete,
|
|
|
|
|
EHttpContentType ContentType,
|
|
|
|
|
EHttpContentType AcceptType,
|
|
|
|
|
TConstArrayView<long> ExpectedErrorCodes)
|
2022-05-26 18:07:41 -04:00
|
|
|
{
|
2022-05-31 12:42:17 -04:00
|
|
|
uint64 ContentLength = 0u;
|
2022-05-26 18:07:41 -04:00
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
CURL* LocalCurl = static_cast<CURL*>(Curl);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_POST, 1L);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_INFILESIZE, Buffer.GetSize());
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_READDATA, this);
|
|
|
|
|
curl_easy_setopt(LocalCurl, CURLOPT_READFUNCTION, Http::Private::FHttpRequestStatics::StaticReadFn);
|
|
|
|
|
AddContentTypeHeader(TEXTVIEW("Content-Type"), ContentType);
|
|
|
|
|
AddContentTypeHeader(TEXTVIEW("Accept"), AcceptType);
|
|
|
|
|
ReadCompositeBuffer = Buffer;
|
|
|
|
|
ContentLength = Buffer.GetSize();
|
2022-05-26 18:07:41 -04:00
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
return EnqueueAsync(Owner, Pool, Uri, ERequestVerb::Post, ContentLength, MoveTemp(OnComplete), ExpectedErrorCodes);
|
2022-05-26 18:07:41 -04:00
|
|
|
}
|
|
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
FHttpRequest::EResult FHttpRequest::PerformBlockingHead(FStringView Uri,
|
|
|
|
|
EHttpContentType AcceptType,
|
|
|
|
|
TConstArrayView<long> ExpectedErrorCodes)
|
|
|
|
|
{
|
|
|
|
|
curl_easy_setopt(static_cast<CURL*>(Curl), CURLOPT_NOBODY, 1L);
|
|
|
|
|
AddContentTypeHeader(TEXTVIEW("Accept"), AcceptType);
|
|
|
|
|
return PerformBlocking(Uri, ERequestVerb::Head, 0u, ExpectedErrorCodes);
|
|
|
|
|
}
|
2022-05-26 18:07:41 -04:00
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
FHttpRequest::EResult FHttpRequest::PerformBlockingDelete(FStringView Uri,
|
|
|
|
|
TConstArrayView<long> ExpectedErrorCodes)
|
|
|
|
|
{
|
|
|
|
|
curl_easy_setopt(static_cast<CURL*>(Curl), CURLOPT_CUSTOMREQUEST, "DELETE");
|
|
|
|
|
return PerformBlocking(Uri, ERequestVerb::Delete, 0u, ExpectedErrorCodes);
|
|
|
|
|
}
|
2022-05-26 18:07:41 -04:00
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
void FHttpRequest::EnqueueAsyncHead(UE::DerivedData::IRequestOwner& Owner,
|
|
|
|
|
FHttpRequestPool* Pool,
|
|
|
|
|
FStringView Uri,
|
|
|
|
|
FOnHttpRequestComplete&& OnComplete,
|
|
|
|
|
EHttpContentType AcceptType,
|
|
|
|
|
TConstArrayView<long> ExpectedErrorCodes)
|
|
|
|
|
{
|
|
|
|
|
curl_easy_setopt(static_cast<CURL*>(Curl), CURLOPT_NOBODY, 1L);
|
|
|
|
|
AddContentTypeHeader(TEXTVIEW("Accept"), AcceptType);
|
|
|
|
|
return EnqueueAsync(Owner, Pool, Uri, ERequestVerb::Head, 0u, MoveTemp(OnComplete), ExpectedErrorCodes);
|
|
|
|
|
}
|
2022-05-26 18:07:41 -04:00
|
|
|
|
2022-05-31 12:42:17 -04:00
|
|
|
void FHttpRequest::EnqueueAsyncDelete(UE::DerivedData::IRequestOwner& Owner,
|
|
|
|
|
FHttpRequestPool* Pool,
|
|
|
|
|
FStringView Uri,
|
|
|
|
|
FOnHttpRequestComplete&& OnComplete,
|
|
|
|
|
TConstArrayView<long> ExpectedErrorCodes)
|
|
|
|
|
{
|
|
|
|
|
curl_easy_setopt(static_cast<CURL*>(Curl), CURLOPT_CUSTOMREQUEST, "DELETE");
|
|
|
|
|
return EnqueueAsync(Owner, Pool, Uri, ERequestVerb::Delete, 0u, MoveTemp(OnComplete), ExpectedErrorCodes);
|
|
|
|
|
}
|
2022-05-26 18:07:41 -04:00
|
|
|
|
2022-05-18 09:58:59 -04:00
|
|
|
} // UE
|
|
|
|
|
|
2022-05-26 18:07:41 -04:00
|
|
|
#endif // UE_WITH_HTTP_CLIENT
|