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"
# 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-30 19:32:59 -04:00
UE : : FHttpRequest : : RequestVerb 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-30 19:32:59 -04:00
const size_t MaxReadSize = SizeInBlocks * BlockSizeInBytes ;
const FMemoryView SourceView = Request - > ReadDataView . Mid ( Request - > BytesSent , MaxReadSize ) ;
MakeMemoryView ( Ptr , MaxReadSize ) . CopyFrom ( SourceView ) ;
Request - > BytesSent + = SourceView . GetSize ( ) ;
return SourceView . GetSize ( ) ;
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-30 19:32:59 -04:00
TArray < 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-30 19:32:59 -04:00
TArray < 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-30 19:32:59 -04:00
case SEEK_END : NewPosition = Request - > ReadDataView . GetSize ( ) + Offset ; break ;
2022-05-26 18:07:41 -04:00
}
// Make sure we don't seek outside of the buffer
2022-05-30 19:32:59 -04:00
if ( NewPosition < 0 | | NewPosition > = Request - > ReadDataView . 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-30 19:32:59 -04:00
FHttpRequest : : FHttpRequest ( const TCHAR * InDomain , const TCHAR * 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-30 19:32:59 -04:00
ReadDataView = FMemoryView ( ) ;
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-30 19:32:59 -04:00
// Allow compressed data
curl_easy_setopt ( LocalCurl , CURLOPT_ACCEPT_ENCODING , " gzip " ) ;
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-30 19:32:59 -04:00
FHttpRequest : : Result FHttpRequest : : PerformBlockingDownload ( const TCHAR * Uri , TArray < uint8 > * Buffer , 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-30 19:32:59 -04:00
return PerformBlocking ( Uri , Get , 0u , ExpectedErrorCodes ) ;
2022-05-18 09:58:59 -04:00
}
2022-05-30 19:32:59 -04:00
void FHttpRequest : : EnqueueAsyncDownload ( UE : : DerivedData : : IRequestOwner & Owner , FHttpRequestPool * Pool , const TCHAR * Uri , FOnHttpRequestComplete & & OnComplete , 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-30 19:32:59 -04:00
return EnqueueAsync ( Owner , Pool , Uri , Get , 0u , MoveTemp ( OnComplete ) , ExpectedErrorCodes ) ;
2022-05-18 09:58:59 -04:00
}
2022-05-30 19:32:59 -04:00
void FHttpRequest : : SetHeader ( const TCHAR * Header , const TCHAR * 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-30 19:32:59 -04:00
Headers . Add ( FString : : Printf ( TEXT ( " %s: %s " ) , Header , Value ) ) ;
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-30 19:32:59 -04:00
FHttpRequest : : Result HttpResult ;
2022-05-26 18:07:41 -04:00
switch ( ResultCode )
2022-05-18 09:58:59 -04:00
{
case CURLE_OK :
2022-05-30 19:32:59 -04:00
HttpResult = Success ;
2022-05-18 09:58:59 -04:00
break ;
case CURLE_OPERATION_TIMEDOUT :
2022-05-30 19:32:59 -04:00
HttpResult = FailedTimeout ;
2022-05-18 09:58:59 -04:00
break ;
default :
2022-05-30 19:32:59 -04:00
HttpResult = 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-30 19:32:59 -04:00
curl_slist * FHttpRequest : : PrepareToIssueRequest ( const TCHAR * Uri , uint64 ContentLength )
2022-05-18 09:58:59 -04:00
{
2022-05-30 19:32:59 -04:00
static const char * CommonHeaders [ ] = {
2022-05-18 09:58:59 -04:00
" User-Agent: Unreal Engine " ,
nullptr
} ;
// Setup request options
2022-05-30 19:32:59 -04:00
FString Url = FString : : Printf ( TEXT ( " %s/%s " ) , * EffectiveDomain , Uri ) ;
curl_easy_setopt ( static_cast < CURL * > ( Curl ) , CURLOPT_URL , TCHAR_TO_ANSI ( * Url ) ) ;
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-30 19:32:59 -04:00
// Content-Length should always be set
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-30 19:32:59 -04:00
FHttpRequest : : Result FHttpRequest : : PerformBlocking ( const TCHAR * Uri , RequestVerb 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-30 19:32:59 -04:00
curl_slist * CurlHeaders = PrepareToIssueRequest ( Uri , 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-30 19:32:59 -04:00
return ResultCode = = CURLE_OK ? Success : Failed ;
2022-05-18 09:58:59 -04:00
}
2022-05-30 19:32:59 -04:00
void FHttpRequest : : EnqueueAsync ( UE : : DerivedData : : IRequestOwner & Owner , FHttpRequestPool * Pool , const TCHAR * Uri , RequestVerb 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-30 19:32:59 -04:00
AsyncData - > CurlHeaders = PrepareToIssueRequest ( Uri , 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-30 19:32:59 -04:00
void FHttpRequest : : LogResult ( FResultCode Result , const TCHAR * Uri , RequestVerb 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-30 19:32:59 -04:00
case Head :
2022-05-18 09:58:59 -04:00
bSuccess = ( ExpectedErrorCodes . Contains ( ResponseCode ) | | IsSuccessResponse ( ResponseCode ) ) ;
VerbStr = TEXT ( " querying " ) ;
break ;
2022-05-30 19:32:59 -04:00
case 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-30 19:32:59 -04:00
case Put :
case PutCompactBinary :
case PutCompressedBlob :
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-30 19:32:59 -04:00
case Post :
case PostCompactBinary :
case PostJson :
2022-05-18 09:58:59 -04:00
bSuccess = ( ExpectedErrorCodes . Contains ( ResponseCode ) | | IsSuccessResponse ( ResponseCode ) ) ;
VerbStr = TEXT ( " posting " ) ;
break ;
2022-05-30 19:32:59 -04:00
case 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-30 19:32:59 -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-30 19:32:59 -04:00
Uri ,
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-30 19:32:59 -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-30 19:32:59 -04:00
Uri ,
2022-05-18 09:58:59 -04:00
* Response
) ;
}
else
{
UE_LOG (
LogDerivedDataCache ,
Display ,
2022-05-30 19:32:59 -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-30 19:32:59 -04:00
Uri ,
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-30 19:32:59 -04:00
template < FHttpRequest : : RequestVerb V >
FHttpRequest : : Result FHttpRequest : : PerformBlockingUpload ( const TCHAR * Uri , TArrayView < const uint8 > Buffer , TConstArrayView < long > ExpectedErrorCodes )
2022-05-18 09:58:59 -04:00
{
2022-05-30 19:32:59 -04:00
static_assert ( V = = Put | | V = = PutCompactBinary | | V = = PutCompressedBlob | | V = = Post | | V = = PostCompactBinary | | V = = PostJson , " Upload should use either Put or Post verbs. " ) ;
2022-05-26 18:07:41 -04:00
uint64 ContentLength = 0u ;
CURL * LocalCurl = static_cast < CURL * > ( Curl ) ;
2022-05-30 19:32:59 -04:00
if constexpr ( V = = Put | | V = = PutCompactBinary | | V = = PutCompressedBlob )
{
curl_easy_setopt ( LocalCurl , CURLOPT_UPLOAD , 1L ) ;
curl_easy_setopt ( LocalCurl , CURLOPT_INFILESIZE , Buffer . Num ( ) ) ;
curl_easy_setopt ( LocalCurl , CURLOPT_READDATA , this ) ;
curl_easy_setopt ( LocalCurl , CURLOPT_READFUNCTION , Http : : Private : : FHttpRequestStatics : : StaticReadFn ) ;
if constexpr ( V = = PutCompactBinary )
{
Headers . Add ( FString ( TEXT ( " Content-Type: application/x-ue-cb " ) ) ) ;
}
else if constexpr ( V = = PutCompressedBlob )
{
Headers . Add ( FString ( TEXT ( " Content-Type: application/x-ue-comp " ) ) ) ;
}
else
{
Headers . Add ( FString ( TEXT ( " Content-Type: application/octet-stream " ) ) ) ;
}
ContentLength = Buffer . Num ( ) ;
ReadDataView = MakeMemoryView ( Buffer ) ;
}
else if constexpr ( V = = Post | | V = = PostCompactBinary | | V = = PostJson )
{
curl_easy_setopt ( LocalCurl , CURLOPT_POST , 1L ) ;
curl_easy_setopt ( LocalCurl , CURLOPT_INFILESIZE , Buffer . Num ( ) ) ;
curl_easy_setopt ( LocalCurl , CURLOPT_READDATA , this ) ;
curl_easy_setopt ( LocalCurl , CURLOPT_READFUNCTION , Http : : Private : : FHttpRequestStatics : : StaticReadFn ) ;
if constexpr ( V = = PostCompactBinary )
{
Headers . Add ( FString ( TEXT ( " Content-Type: application/x-ue-cb " ) ) ) ;
}
else if constexpr ( V = = PostJson )
{
Headers . Add ( FString ( TEXT ( " Content-Type: application/json " ) ) ) ;
}
else
{
Headers . Add ( FString ( TEXT ( " Content-Type: application/x-www-form-urlencoded " ) ) ) ;
}
ContentLength = Buffer . Num ( ) ;
ReadDataView = MakeMemoryView ( Buffer ) ;
}
2022-05-26 18:07:41 -04:00
2022-05-30 19:32:59 -04:00
return PerformBlocking ( Uri , V , ContentLength , ExpectedErrorCodes ) ;
2022-05-18 09:58:59 -04:00
}
2022-05-30 19:32:59 -04:00
template < FHttpRequest : : RequestVerb V >
void FHttpRequest : : EnqueueAsyncUpload ( UE : : DerivedData : : IRequestOwner & Owner , FHttpRequestPool * Pool , const TCHAR * Uri , FSharedBuffer Buffer , FOnHttpRequestComplete & & OnComplete , TConstArrayView < long > ExpectedErrorCodes )
2022-05-18 09:58:59 -04:00
{
2022-05-30 19:32:59 -04:00
static_assert ( V = = Put | | V = = PutCompactBinary | | V = = PutCompressedBlob | | V = = Post | | V = = PostCompactBinary | | V = = PostJson , " Upload should use either Put or Post verbs. " ) ;
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-30 19:32:59 -04:00
if constexpr ( V = = Put | | V = = PutCompactBinary | | V = = PutCompressedBlob )
{
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 ) ;
if constexpr ( V = = PutCompactBinary )
{
Headers . Add ( FString ( TEXT ( " Content-Type: application/x-ue-cb " ) ) ) ;
}
else if constexpr ( V = = PutCompressedBlob )
{
Headers . Add ( FString ( TEXT ( " Content-Type: application/x-ue-comp " ) ) ) ;
}
else
{
Headers . Add ( FString ( TEXT ( " Content-Type: application/octet-stream " ) ) ) ;
}
ReadSharedBuffer = Buffer ;
ContentLength = Buffer . GetSize ( ) ;
ReadDataView = Buffer . GetView ( ) ;
}
else if constexpr ( V = = Post | | V = = PostCompactBinary | | V = = PostJson )
{
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 ) ;
if constexpr ( V = = PostCompactBinary )
{
Headers . Add ( FString ( TEXT ( " Content-Type: application/x-ue-cb " ) ) ) ;
}
else if constexpr ( V = = PostJson )
{
Headers . Add ( FString ( TEXT ( " Content-Type: application/json " ) ) ) ;
}
else
{
Headers . Add ( FString ( TEXT ( " Content-Type: application/x-www-form-urlencoded " ) ) ) ;
}
ReadSharedBuffer = Buffer ;
ContentLength = Buffer . GetSize ( ) ;
ReadDataView = Buffer . GetView ( ) ;
}
2022-05-18 09:58:59 -04:00
2022-05-30 19:32:59 -04:00
return EnqueueAsync ( Owner , Pool , Uri , V , ContentLength , MoveTemp ( OnComplete ) , ExpectedErrorCodes ) ;
2022-05-18 09:58:59 -04:00
}
2022-05-30 19:32:59 -04:00
template < FHttpRequest : : RequestVerb V >
FHttpRequest : : Result FHttpRequest : : PerformBlockingQuery ( const TCHAR * Uri , TConstArrayView < long > ExpectedErrorCodes )
2022-05-26 18:07:41 -04:00
{
2022-05-30 19:32:59 -04:00
static_assert ( V = = Head | | V = = Delete , " Queries should use either Head or Delete verbs. " ) ;
2022-05-26 18:07:41 -04:00
2022-05-30 19:32:59 -04:00
if ( V = = Delete )
{
curl_easy_setopt ( static_cast < CURL * > ( Curl ) , CURLOPT_CUSTOMREQUEST , " DELETE " ) ;
}
else if ( V = = Head )
{
curl_easy_setopt ( static_cast < CURL * > ( Curl ) , CURLOPT_NOBODY , 1L ) ;
}
2022-05-26 18:07:41 -04:00
2022-05-30 19:32:59 -04:00
return PerformBlocking ( Uri , V , 0u , ExpectedErrorCodes ) ;
2022-05-26 18:07:41 -04:00
}
2022-05-30 19:32:59 -04:00
template < FHttpRequest : : RequestVerb V >
void FHttpRequest : : EnqueueAsyncQuery ( UE : : DerivedData : : IRequestOwner & Owner , FHttpRequestPool * Pool , const TCHAR * Uri , FOnHttpRequestComplete & & OnComplete , TConstArrayView < long > ExpectedErrorCodes )
2022-05-26 18:07:41 -04:00
{
2022-05-30 19:32:59 -04:00
static_assert ( V = = Head | | V = = Delete , " Queries should use either Head or Delete verbs. " ) ;
2022-05-26 18:07:41 -04:00
2022-05-30 19:32:59 -04:00
if ( V = = Delete )
{
curl_easy_setopt ( static_cast < CURL * > ( Curl ) , CURLOPT_CUSTOMREQUEST , " DELETE " ) ;
}
else if ( V = = Head )
{
curl_easy_setopt ( static_cast < CURL * > ( Curl ) , CURLOPT_NOBODY , 1L ) ;
}
2022-05-26 18:07:41 -04:00
2022-05-30 19:32:59 -04:00
return EnqueueAsync ( Owner , Pool , Uri , V , 0u , MoveTemp ( OnComplete ) , ExpectedErrorCodes ) ;
2022-05-26 18:07:41 -04:00
}
2022-05-30 19:32:59 -04:00
# define UE_HTTP_DEFINE_EXTERN_TEMPLATE(Verb) template FHttpRequest::Result FHttpRequest::PerformBlockingUpload<Verb>(const TCHAR* Uri, TArrayView<const uint8> Buffer, TConstArrayView<long> ExpectedErrorCodes);
UE_HTTP_FOREACH_UPLOAD_VERB ( UE_HTTP_DEFINE_EXTERN_TEMPLATE )
# undef UE_HTTP_DEFINE_EXTERN_TEMPLATE
2022-05-26 18:07:41 -04:00
2022-05-30 19:32:59 -04:00
# define UE_HTTP_DEFINE_EXTERN_TEMPLATE(Verb) template void FHttpRequest::EnqueueAsyncUpload<Verb>(UE::DerivedData::IRequestOwner& Owner, FHttpRequestPool* Pool, const TCHAR* Uri, FSharedBuffer Buffer, FOnHttpRequestComplete&& OnComplete, TConstArrayView<long> ExpectedErrorCodes);
UE_HTTP_FOREACH_UPLOAD_VERB ( UE_HTTP_DEFINE_EXTERN_TEMPLATE )
# undef UE_HTTP_DEFINE_EXTERN_TEMPLATE
2022-05-26 18:07:41 -04:00
2022-05-30 19:32:59 -04:00
# define UE_HTTP_DEFINE_EXTERN_TEMPLATE(Verb) template FHttpRequest::Result FHttpRequest::PerformBlockingQuery<Verb>(const TCHAR* Uri, TConstArrayView<long> ExpectedErrorCodes);
UE_HTTP_FOREACH_QUERY_VERB ( UE_HTTP_DEFINE_EXTERN_TEMPLATE )
# undef UE_HTTP_DEFINE_EXTERN_TEMPLATE
# define UE_HTTP_DEFINE_EXTERN_TEMPLATE(Verb) template void FHttpRequest::EnqueueAsyncQuery<Verb>(UE::DerivedData::IRequestOwner& Owner, FHttpRequestPool* Pool, const TCHAR* Uri, FOnHttpRequestComplete&& OnComplete, TConstArrayView<long> ExpectedErrorCodes);
UE_HTTP_FOREACH_QUERY_VERB ( UE_HTTP_DEFINE_EXTERN_TEMPLATE )
# undef UE_HTTP_DEFINE_EXTERN_TEMPLATE
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