2023-05-25 17:56:06 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "CoreMinimal.h"
2023-06-02 12:09:58 -04:00
# include "HAL/FileManager.h"
2023-05-25 17:56:06 -04:00
# include "HAL/IConsoleManager.h"
# include "HAL/PlatformProcess.h"
2023-06-08 14:54:12 -04:00
# include "HAL/Runnable.h"
# include "HAL/RunnableThread.h"
2023-06-02 16:59:56 -04:00
# include "HttpManager.h"
2023-08-30 17:27:56 -04:00
# include "HttpRetrySystem.h"
2023-06-02 16:59:56 -04:00
# include "Http.h"
2023-05-25 17:56:06 -04:00
# include "Misc/CommandLine.h"
# include "TestHarness.h"
2023-10-04 15:14:01 -04:00
# include "Serialization/JsonSerializerMacros.h"
2023-05-25 17:56:06 -04:00
/**
* HTTP Tests
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
* PURPOSE :
*
* Integration Tests to make sure all kinds of HTTP client features in C + + work well on different platforms ,
* including but not limited to error handing , retrying , threading , streaming , SSL and profiling .
*
* Refer to WebTests / README . md for more info about how to run these tests
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
# define HTTP_TAG "[HTTP]"
# define HTTP_TIME_DIFF_TOLERANCE 0.5f
2023-09-18 16:36:57 -04:00
extern TAutoConsoleVariable < bool > CVarHttpInsecureProtocolEnabled ;
2023-11-13 17:55:00 -05:00
extern TAutoConsoleVariable < bool > CVarHttpRetrySystemNonGameThreadSupportEnabled ;
2023-09-18 16:36:57 -04:00
2023-05-25 17:56:06 -04:00
class FHttpModuleTestFixture
{
public :
FHttpModuleTestFixture ( )
: WebServerIp ( TEXT ( " 127.0.0.1 " ) )
2023-08-17 18:35:29 -04:00
, WebServerHttpPort ( 8000 )
2023-08-01 11:06:27 -04:00
, bRunHeavyTests ( false )
2023-11-13 17:55:00 -05:00
, bRetryEnabled ( true )
2023-08-30 21:20:37 -04:00
, OldVerbosity ( LogHttp . GetVerbosity ( ) )
2023-05-25 17:56:06 -04:00
{
ParseSettingsFromCommandLine ( ) ;
2023-11-13 17:55:00 -05:00
bRetryEnabled & = CVarHttpRetrySystemNonGameThreadSupportEnabled . GetValueOnAnyThread ( ) ;
2023-05-25 17:56:06 -04:00
HttpModule = new FHttpModule ( ) ;
IModuleInterface * Module = HttpModule ;
Module - > StartupModule ( ) ;
2023-09-18 16:36:57 -04:00
CVarHttpInsecureProtocolEnabled - > Set ( true ) ;
2023-05-25 17:56:06 -04:00
}
virtual ~ FHttpModuleTestFixture ( )
{
IModuleInterface * Module = HttpModule ;
Module - > ShutdownModule ( ) ;
delete Module ;
2023-08-30 21:20:37 -04:00
if ( OldVerbosity ! = LogHttp . GetVerbosity ( ) )
{
LogHttp . SetVerbosity ( OldVerbosity ) ;
}
2023-05-25 17:56:06 -04:00
}
void ParseSettingsFromCommandLine ( )
{
FParse : : Value ( FCommandLine : : Get ( ) , TEXT ( " web_server_ip " ) , WebServerIp ) ;
2023-08-01 11:06:27 -04:00
FParse : : Bool ( FCommandLine : : Get ( ) , TEXT ( " run_heavy_tests " ) , bRunHeavyTests ) ;
2023-10-27 08:42:11 -04:00
FParse : : Bool ( FCommandLine : : Get ( ) , TEXT ( " retry_enabled " ) , bRetryEnabled ) ;
2023-05-25 17:56:06 -04:00
}
2023-08-30 21:20:37 -04:00
void DisableWarningsInThisTest ( )
{
LogHttp . SetVerbosity ( ELogVerbosity : : Error ) ;
}
2023-08-15 16:35:29 -04:00
const FString UrlWithInvalidPortToTestConnectTimeout ( ) const { return FString : : Format ( TEXT ( " http://{0}:{1} " ) , { * WebServerIp , 8765 } ) ; }
2023-08-17 18:35:29 -04:00
const FString UrlBase ( ) const { return FString : : Format ( TEXT ( " http://{0}:{1} " ) , { * WebServerIp , WebServerHttpPort } ) ; }
2023-05-25 17:56:06 -04:00
const FString UrlHttpTests ( ) const { return FString : : Format ( TEXT ( " {0}/webtests/httptests " ) , { * UrlBase ( ) } ) ; }
const FString UrlToTestMethods ( ) const { return FString : : Format ( TEXT ( " {0}/methods " ) , { * UrlHttpTests ( ) } ) ; }
2023-06-15 09:11:36 -04:00
const FString UrlStreamDownload ( uint32 Chunks , uint32 ChunkSize ) { return FString : : Format ( TEXT ( " {0}/streaming_download/{1}/{2}/ " ) , { * UrlHttpTests ( ) , Chunks , ChunkSize } ) ; }
2023-05-25 17:56:06 -04:00
FString WebServerIp ;
2023-08-17 18:35:29 -04:00
uint32 WebServerHttpPort ;
2023-05-25 17:56:06 -04:00
FHttpModule * HttpModule ;
2023-08-15 16:35:29 -04:00
2023-08-01 11:06:27 -04:00
bool bRunHeavyTests ;
2023-10-27 08:42:11 -04:00
bool bRetryEnabled ;
2023-08-30 21:20:37 -04:00
ELogVerbosity : : Type OldVerbosity ;
2023-05-25 17:56:06 -04:00
} ;
TEST_CASE_METHOD ( FHttpModuleTestFixture , " Shutdown http module without issue when there are ongoing http requests. " , HTTP_TAG )
{
2023-08-30 21:20:37 -04:00
DisableWarningsInThisTest ( ) ;
2023-05-25 17:56:06 -04:00
uint32 ChunkSize = 1024 * 1024 ;
TArray < uint8 > DataChunk ;
DataChunk . SetNum ( ChunkSize ) ;
FMemory : : Memset ( DataChunk . GetData ( ) , ' d ' , ChunkSize ) ;
for ( int32 i = 0 ; i < 10 ; + + i )
{
IHttpRequest * LeakingHttpRequest = FPlatformHttp : : ConstructRequest ( ) ; // Leaking in purpose to make sure it's ok
2023-06-15 09:11:36 -04:00
TSharedRef < IHttpRequest > HttpRequest = HttpModule - > CreateRequest ( ) ;
2023-05-25 17:56:06 -04:00
HttpRequest - > SetURL ( UrlToTestMethods ( ) ) ;
HttpRequest - > SetVerb ( TEXT ( " PUT " ) ) ;
// TODO: Use some shared data, like cookie, openssl session etc.
HttpRequest - > SetContent ( DataChunk ) ;
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
CHECK ( bSucceeded ) ;
} ) ;
HttpRequest - > ProcessRequest ( ) ;
}
HttpModule - > GetHttpManager ( ) . Tick ( 0.0f ) ;
}
2023-10-27 08:42:11 -04:00
class FMockRetryManager : public FHttpRetrySystem : : FManager
2023-06-13 11:00:18 -04:00
{
public :
2023-10-27 08:42:11 -04:00
using FHttpRetrySystem : : FManager : : FManager ;
using FHttpRetrySystem : : FManager : : RequestList ;
using FHttpRetrySystem : : FManager : : FHttpRetryRequestEntry ;
using FHttpRetrySystem : : FManager : : RetryTimeoutRelativeSecondsDefault ;
2023-11-13 17:55:00 -05:00
bool IsEmpty ( )
{
FScopeLock ScopeLock ( & RequestListLock ) ;
return RequestList . IsEmpty ( ) ;
}
2023-06-13 11:00:18 -04:00
} ;
2023-05-25 17:56:06 -04:00
class FWaitUntilCompleteHttpFixture : public FHttpModuleTestFixture
{
public :
FWaitUntilCompleteHttpFixture ( )
{
HttpModule - > GetHttpManager ( ) . SetRequestAddedDelegate ( FHttpManagerRequestAddedDelegate : : CreateRaw ( this , & FWaitUntilCompleteHttpFixture : : OnRequestAdded ) ) ;
HttpModule - > GetHttpManager ( ) . SetRequestCompletedDelegate ( FHttpManagerRequestCompletedDelegate : : CreateRaw ( this , & FWaitUntilCompleteHttpFixture : : OnRequestCompleted ) ) ;
2023-10-27 08:42:11 -04:00
if ( bRetryEnabled )
{
HttpRetryManager = MakeShared < FMockRetryManager > ( FHttpRetrySystem : : FRetryLimitCountSetting ( RetryLimitCount ) , FHttpRetrySystem : : FRetryTimeoutRelativeSecondsSetting ( /*RetryTimeoutRelativeSeconds*/ ) ) ;
HttpRetryManager - > RetryTimeoutRelativeSecondsDefault = 2.0f ; // Value is set so that retry system will use it at high level, will no longer wait for ever for HTTP code
}
2023-05-25 17:56:06 -04:00
}
~ FWaitUntilCompleteHttpFixture ( )
{
WaitUntilAllHttpRequestsComplete ( ) ;
HttpModule - > GetHttpManager ( ) . SetRequestAddedDelegate ( FHttpManagerRequestAddedDelegate ( ) ) ;
HttpModule - > GetHttpManager ( ) . SetRequestCompletedDelegate ( FHttpManagerRequestCompletedDelegate ( ) ) ;
}
void OnRequestAdded ( const FHttpRequestRef & Request )
{
+ + OngoingRequests ;
}
void OnRequestCompleted ( const FHttpRequestRef & Request )
{
2023-08-30 18:56:04 -04:00
ensure ( - - OngoingRequests > = 0 ) ;
2023-05-25 17:56:06 -04:00
}
void WaitUntilAllHttpRequestsComplete ( )
{
2023-11-13 17:55:00 -05:00
while ( OngoingRequests ! = 0 | | ( bRetryEnabled & & ! HttpRetryManager - > IsEmpty ( ) ) )
2023-05-25 17:56:06 -04:00
{
HttpModule - > GetHttpManager ( ) . Tick ( TickFrequency ) ;
FPlatformProcess : : Sleep ( TickFrequency ) ;
}
2023-09-01 10:24:00 -04:00
// In case in http thread the http request complete and set OngoingRequests to 0, http manager never
// had chance to Tick and remove the request
HttpModule - > GetHttpManager ( ) . Tick ( TickFrequency ) ;
2023-05-25 17:56:06 -04:00
}
2023-10-27 08:42:11 -04:00
TSharedRef < IHttpRequest > CreateRequest ( )
{
return bRetryEnabled ? HttpRetryManager - > CreateRequest ( ) : HttpModule - > CreateRequest ( ) ;
}
2023-08-30 18:56:04 -04:00
std : : atomic < int32 > OngoingRequests = 0 ;
2023-05-25 17:56:06 -04:00
float TickFrequency = 1.0f / 60 ; /*60 FPS*/ ;
2023-10-27 08:42:11 -04:00
2023-11-13 17:55:00 -05:00
uint32 RetryLimitCount = 0 ;
2023-10-27 08:42:11 -04:00
TSharedPtr < FMockRetryManager > HttpRetryManager ;
2023-05-25 17:56:06 -04:00
} ;
2023-06-02 16:59:56 -04:00
TEST_CASE_METHOD ( FWaitUntilCompleteHttpFixture , " Http Methods " , HTTP_TAG )
2023-05-25 17:56:06 -04:00
{
2023-10-27 08:42:11 -04:00
TSharedRef < IHttpRequest > HttpRequest = CreateRequest ( ) ;
2023-10-04 15:14:01 -04:00
CHECK ( HttpRequest - > GetVerb ( ) = = TEXT ( " GET " ) ) ;
2023-05-25 17:56:06 -04:00
HttpRequest - > SetURL ( UrlToTestMethods ( ) ) ;
2023-06-02 16:59:56 -04:00
2023-10-04 15:14:01 -04:00
SECTION ( " Default GET " )
{
}
2023-06-02 16:59:56 -04:00
SECTION ( " GET " )
{
HttpRequest - > SetVerb ( TEXT ( " GET " ) ) ;
}
SECTION ( " POST " )
{
HttpRequest - > SetVerb ( TEXT ( " POST " ) ) ;
}
SECTION ( " PUT " )
{
HttpRequest - > SetVerb ( TEXT ( " PUT " ) ) ;
}
SECTION ( " DELETE " )
{
HttpRequest - > SetVerb ( TEXT ( " DELETE " ) ) ;
}
2023-05-25 17:56:06 -04:00
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
CHECK ( bSucceeded ) ;
2023-06-14 04:02:22 -04:00
REQUIRE ( HttpResponse ! = nullptr ) ;
2023-05-25 17:56:06 -04:00
CHECK ( HttpResponse - > GetResponseCode ( ) = = 200 ) ;
} ) ;
HttpRequest - > ProcessRequest ( ) ;
}
2023-06-02 16:59:56 -04:00
TEST_CASE_METHOD ( FWaitUntilCompleteHttpFixture , " Get large response content without chunks " , HTTP_TAG )
2023-05-25 17:56:06 -04:00
{
2023-10-27 08:42:11 -04:00
TSharedRef < IHttpRequest > HttpRequest = CreateRequest ( ) ;
2023-05-25 17:56:06 -04:00
HttpRequest - > SetURL ( FString : : Format ( TEXT ( " {0}/get_large_response_without_chunks/{1}/ " ) , { * UrlHttpTests ( ) , 1024 * 1024 /*bytes_number*/ } ) ) ;
HttpRequest - > SetVerb ( TEXT ( " GET " ) ) ;
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
CHECK ( bSucceeded ) ;
2023-06-14 04:02:22 -04:00
REQUIRE ( HttpResponse ! = nullptr ) ;
2023-05-25 17:56:06 -04:00
CHECK ( HttpResponse - > GetResponseCode ( ) = = 200 ) ;
} ) ;
HttpRequest - > ProcessRequest ( ) ;
}
2023-08-01 11:06:27 -04:00
// TODO: Enable this after finding a more reliable way of simulate timeout
//TEST_CASE_METHOD(FWaitUntilCompleteHttpFixture, "Http request connect timeout", HTTP_TAG)
//{
2023-10-27 08:42:11 -04:00
// TSharedRef<IHttpRequest> HttpRequest = CreateRequest();
2023-08-01 11:06:27 -04:00
// HttpRequest->SetURL(UrlWithInvalidPortToTestConnectTimeout());
// HttpRequest->SetVerb(TEXT("GET"));
// HttpRequest->SetTimeout(7);
// FDateTime StartTime = FDateTime::Now();
// HttpRequest->OnProcessRequestComplete().BindLambda([StartTime](FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, bool bSucceeded) {
// CHECK(!bSucceeded);
// CHECK(HttpResponse == nullptr);
// // TODO: For now curl impl is using customized timeout instead of relying on native http timeout,
// // which doesn't get CURLE_COULDNT_CONNECT. Enable this after switching to native http timeout
// //CHECK(HttpRequest->GetStatus() == EHttpRequestStatus::Failed_ConnectionError);
// FTimespan Timespan = FDateTime::Now() - StartTime;
// float DurationInSeconds = Timespan.GetTotalSeconds();
// CHECK(FMath::IsNearlyEqual(DurationInSeconds, 7, HTTP_TIME_DIFF_TOLERANCE));
// });
// HttpRequest->ProcessRequest();
//}
2023-05-25 17:56:06 -04:00
2023-06-02 12:09:58 -04:00
TEST_CASE_METHOD ( FWaitUntilCompleteHttpFixture , " Streaming http download " , HTTP_TAG )
2023-05-25 17:56:06 -04:00
{
2023-06-13 11:00:18 -04:00
uint32 Chunks = 3 ;
2023-05-25 17:56:06 -04:00
uint32 ChunkSize = 1024 * 1024 ;
2023-10-27 08:42:11 -04:00
TSharedRef < IHttpRequest > HttpRequest = CreateRequest ( ) ;
2023-06-15 09:11:36 -04:00
HttpRequest - > SetURL ( UrlStreamDownload ( Chunks , ChunkSize ) ) ;
2023-05-25 17:56:06 -04:00
HttpRequest - > SetVerb ( TEXT ( " GET " ) ) ;
2023-06-02 12:09:58 -04:00
TSharedRef < int64 > TotalBytesReceived = MakeShared < int64 > ( 0 ) ;
SECTION ( " Success without stream provided " )
{
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ Chunks , ChunkSize ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
CHECK ( bSucceeded ) ;
2023-06-14 04:02:22 -04:00
REQUIRE ( HttpResponse ! = nullptr ) ;
2023-06-02 12:09:58 -04:00
CHECK ( HttpResponse - > GetResponseCode ( ) = = 200 ) ;
2023-08-30 17:27:56 -04:00
CHECK ( ! HttpResponse - > GetAllHeaders ( ) . IsEmpty ( ) ) ;
2023-06-14 04:02:22 -04:00
CHECK ( HttpResponse - > GetContentLength ( ) = = Chunks * ChunkSize ) ;
2023-06-02 12:09:58 -04:00
} ) ;
}
SECTION ( " Success with customized stream " )
{
class FTestHttpReceiveStream final : public FArchive
{
public :
FTestHttpReceiveStream ( TSharedRef < int64 > InTotalBytesReceived )
: TotalBytesReceived ( InTotalBytesReceived )
{
}
virtual void Serialize ( void * V , int64 Length ) override
{
* TotalBytesReceived + = Length ;
}
TSharedRef < int64 > TotalBytesReceived ;
} ;
TSharedRef < FTestHttpReceiveStream > Stream = MakeShared < FTestHttpReceiveStream > ( TotalBytesReceived ) ;
CHECK ( HttpRequest - > SetResponseBodyReceiveStream ( Stream ) ) ;
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ Chunks , ChunkSize , TotalBytesReceived ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
2023-06-14 04:02:22 -04:00
CHECK ( bSucceeded ) ;
REQUIRE ( HttpResponse ! = nullptr ) ;
CHECK ( HttpResponse - > GetResponseCode ( ) = = 200 ) ;
2023-08-30 17:27:56 -04:00
CHECK ( ! HttpResponse - > GetAllHeaders ( ) . IsEmpty ( ) ) ;
2023-06-02 12:09:58 -04:00
CHECK ( HttpResponse - > GetContentLength ( ) = = Chunks * ChunkSize ) ;
CHECK ( HttpResponse - > GetContent ( ) . IsEmpty ( ) ) ;
CHECK ( * TotalBytesReceived = = Chunks * ChunkSize ) ;
} ) ;
}
SECTION ( " Success with customized stream delegate " )
{
FHttpRequestStreamDelegate Delegate ;
Delegate . BindLambda ( [ TotalBytesReceived ] ( void * Ptr , int64 Length ) {
* TotalBytesReceived + = Length ;
return true ;
} ) ;
CHECK ( HttpRequest - > SetResponseBodyReceiveStreamDelegate ( Delegate ) ) ;
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ Chunks , ChunkSize , TotalBytesReceived ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
2023-06-14 04:02:22 -04:00
CHECK ( bSucceeded ) ;
REQUIRE ( HttpResponse ! = nullptr ) ;
CHECK ( HttpResponse - > GetResponseCode ( ) = = 200 ) ;
2023-08-30 17:27:56 -04:00
CHECK ( ! HttpResponse - > GetAllHeaders ( ) . IsEmpty ( ) ) ;
2023-06-02 12:09:58 -04:00
CHECK ( HttpResponse - > GetContentLength ( ) = = Chunks * ChunkSize ) ;
CHECK ( HttpResponse - > GetContent ( ) . IsEmpty ( ) ) ;
CHECK ( * TotalBytesReceived = = Chunks * ChunkSize ) ;
} ) ;
}
SECTION ( " Use customized stream to receive response body but failed when serialize " )
{
2023-08-30 21:20:37 -04:00
DisableWarningsInThisTest ( ) ;
2023-06-02 12:09:58 -04:00
class FTestHttpReceiveStream final : public FArchive
{
public :
FTestHttpReceiveStream ( TSharedRef < int64 > InTotalBytesReceived )
: TotalBytesReceived ( InTotalBytesReceived )
{
}
virtual void Serialize ( void * V , int64 Length ) override
{
* TotalBytesReceived + = Length ;
SetError ( ) ;
}
TSharedRef < int64 > TotalBytesReceived ;
} ;
TSharedRef < FTestHttpReceiveStream > Stream = MakeShared < FTestHttpReceiveStream > ( TotalBytesReceived ) ;
CHECK ( HttpRequest - > SetResponseBodyReceiveStream ( Stream ) ) ;
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ ChunkSize , TotalBytesReceived ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
CHECK ( ! bSucceeded ) ;
CHECK ( HttpResponse ! = nullptr ) ;
CHECK ( * TotalBytesReceived < = ChunkSize ) ;
} ) ;
}
SECTION ( " Use customized stream delegate to receive response body but failed when call " )
{
2023-08-30 21:20:37 -04:00
DisableWarningsInThisTest ( ) ;
2023-06-02 12:09:58 -04:00
FHttpRequestStreamDelegate Delegate ;
Delegate . BindLambda ( [ TotalBytesReceived ] ( void * Ptr , int64 Length ) {
* TotalBytesReceived + = Length ;
return false ;
} ) ;
CHECK ( HttpRequest - > SetResponseBodyReceiveStreamDelegate ( Delegate ) ) ;
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ ChunkSize , TotalBytesReceived ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
CHECK ( ! bSucceeded ) ;
CHECK ( HttpResponse ! = nullptr ) ;
CHECK ( * TotalBytesReceived < = ChunkSize ) ;
} ) ;
}
SECTION ( " Success with file stream to receive response body " )
{
FString Filename = FString ( FPlatformProcess : : UserSettingsDir ( ) ) / TEXT ( " TestStreamDownload.dat " ) ;
FArchive * RawFile = IFileManager : : Get ( ) . CreateFileWriter ( * Filename ) ;
CHECK ( RawFile ! = nullptr ) ;
TSharedRef < FArchive > FileToWrite = MakeShareable ( RawFile ) ;
CHECK ( HttpRequest - > SetResponseBodyReceiveStream ( FileToWrite ) ) ;
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ Chunks , ChunkSize , Filename , FileToWrite ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
2023-06-14 04:02:22 -04:00
CHECK ( bSucceeded ) ;
REQUIRE ( HttpResponse ! = nullptr ) ;
2023-06-02 12:09:58 -04:00
CHECK ( HttpResponse - > GetContentLength ( ) = = Chunks * ChunkSize ) ;
CHECK ( HttpResponse - > GetContent ( ) . IsEmpty ( ) ) ;
CHECK ( HttpResponse - > GetResponseCode ( ) = = 200 ) ;
2023-08-30 17:27:56 -04:00
CHECK ( ! HttpResponse - > GetAllHeaders ( ) . IsEmpty ( ) ) ;
2023-06-02 12:09:58 -04:00
FileToWrite - > FlushCache ( ) ;
FileToWrite - > Close ( ) ;
2023-06-15 09:11:36 -04:00
TSharedRef < FArchive > FileToRead = MakeShareable ( IFileManager : : Get ( ) . CreateFileReader ( * Filename ) ) ;
2023-06-02 12:09:58 -04:00
CHECK ( FileToRead - > TotalSize ( ) = = Chunks * ChunkSize ) ;
2023-08-30 21:20:37 -04:00
FileToRead - > Close ( ) ;
2023-06-02 12:09:58 -04:00
IFileManager : : Get ( ) . Delete ( * Filename ) ;
} ) ;
}
2023-05-25 17:56:06 -04:00
HttpRequest - > ProcessRequest ( ) ;
}
2023-06-13 11:00:18 -04:00
TEST_CASE_METHOD ( FWaitUntilCompleteHttpFixture , " Can run parallel stream download requests " , HTTP_TAG )
{
uint32 Chunks = 5 ;
uint32 ChunkSize = 1024 * 1024 ;
for ( int i = 0 ; i < 3 ; + + i )
{
2023-10-27 08:42:11 -04:00
TSharedRef < IHttpRequest > HttpRequest = CreateRequest ( ) ;
2023-06-15 09:11:36 -04:00
HttpRequest - > SetURL ( UrlStreamDownload ( Chunks , ChunkSize ) ) ;
2023-06-13 11:00:18 -04:00
HttpRequest - > SetVerb ( TEXT ( " GET " ) ) ;
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ Chunks , ChunkSize ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
CHECK ( HttpResponse - > GetContentLength ( ) = = Chunks * ChunkSize ) ;
CHECK ( bSucceeded ) ;
CHECK ( HttpResponse - > GetResponseCode ( ) = = 200 ) ;
} ) ;
HttpRequest - > ProcessRequest ( ) ;
}
}
2023-06-16 10:53:42 -04:00
TEST_CASE_METHOD ( FWaitUntilCompleteHttpFixture , " Can download big file exceeds 32 bits " , HTTP_TAG )
{
2023-08-01 11:06:27 -04:00
if ( ! bRunHeavyTests )
{
return ;
}
2023-06-16 10:53:42 -04:00
// 5 * 1024 * 1024 * 1024 BYTES = 5368709120 BYTES = 5 GB
uint64 Chunks = 5 * 1024 ;
uint64 ChunkSize = 1024 * 1024 ;
2023-10-27 08:42:11 -04:00
TSharedRef < IHttpRequest > HttpRequest = CreateRequest ( ) ;
2023-06-16 10:53:42 -04:00
HttpRequest - > SetURL ( UrlStreamDownload ( Chunks , ChunkSize ) ) ;
HttpRequest - > SetVerb ( TEXT ( " GET " ) ) ;
TSharedRef < int64 > TotalBytesReceived = MakeShared < int64 > ( 0 ) ;
FHttpRequestStreamDelegate Delegate ;
Delegate . BindLambda ( [ TotalBytesReceived ] ( void * Ptr , int64 Length ) {
* TotalBytesReceived + = Length ;
return true ;
} ) ;
HttpRequest - > SetResponseBodyReceiveStreamDelegate ( Delegate ) ;
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ Chunks , ChunkSize , TotalBytesReceived ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
CHECK ( bSucceeded ) ;
REQUIRE ( HttpResponse ! = nullptr ) ;
CHECK ( HttpResponse - > GetContentLength ( ) = = Chunks * ChunkSize ) ;
CHECK ( HttpResponse - > GetContent ( ) . IsEmpty ( ) ) ;
CHECK ( * TotalBytesReceived = = Chunks * ChunkSize ) ;
CHECK ( HttpResponse - > GetResponseCode ( ) = = 200 ) ;
} ) ;
HttpRequest - > ProcessRequest ( ) ;
}
2023-06-15 09:11:36 -04:00
TEST_CASE_METHOD ( FWaitUntilCompleteHttpFixture , " Streaming http upload from memory " , HTTP_TAG )
2023-05-25 17:56:06 -04:00
{
2023-10-27 08:42:11 -04:00
TSharedRef < IHttpRequest > HttpRequest = CreateRequest ( ) ;
2023-06-15 09:11:36 -04:00
HttpRequest - > SetURL ( FString : : Format ( TEXT ( " {0}/streaming_upload_post " ) , { * UrlHttpTests ( ) } ) ) ;
2023-05-25 17:56:06 -04:00
HttpRequest - > SetVerb ( TEXT ( " POST " ) ) ;
const char * BoundaryLabel = " test_http_boundary " ;
HttpRequest - > SetHeader ( TEXT ( " Content-Type " ) , FString : : Format ( TEXT ( " multipart/form-data; boundary={0} " ) , { BoundaryLabel } ) ) ;
2023-06-15 09:11:36 -04:00
// Data will be sent by chunks in http request
2023-05-25 17:56:06 -04:00
const uint32 FileSize = 10 * 1024 * 1024 ;
char * FileData = ( char * ) FMemory : : Malloc ( FileSize + 1 ) ;
FMemory : : Memset ( FileData , ' d ' , FileSize ) ;
FileData [ FileSize + 1 ] = ' \0 ' ;
TArray < uint8 > ContentData ;
const int32 ContentMaxSize = FileSize + 256 /*max length of format string*/ ;
ContentData . Reserve ( ContentMaxSize ) ;
const int32 ContentLength = FCStringAnsi : : Snprintf (
( char * ) ContentData . GetData ( ) ,
ContentMaxSize ,
" --%s \r \n "
" Content-Disposition: form-data; name= \" file \" ; filename= \" bigfile.zip \" \r \n "
" Content-Type: application/octet-stream \r \n \r \n "
" %s \r \n "
" --%s-- " ,
BoundaryLabel , FileData , BoundaryLabel ) ;
FMemory : : Free ( FileData ) ;
CHECK ( ContentLength > 0 ) ;
CHECK ( ContentLength < ContentMaxSize ) ;
ContentData . SetNumUninitialized ( ContentLength ) ;
HttpRequest - > SetContent ( MoveTemp ( ContentData ) ) ;
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
CHECK ( bSucceeded ) ;
2023-06-14 04:02:22 -04:00
REQUIRE ( HttpResponse ! = nullptr ) ;
2023-05-25 17:56:06 -04:00
CHECK ( HttpResponse - > GetResponseCode ( ) = = 200 ) ;
} ) ;
HttpRequest - > ProcessRequest ( ) ;
}
2023-06-02 20:28:07 -04:00
2023-06-16 10:53:42 -04:00
class FTestHttpUploadStream final : public FArchive
{
public :
FTestHttpUploadStream ( uint64 InTotalSize )
: FakeTotalSize ( InTotalSize )
{
}
virtual void Serialize ( void * V , int64 Length ) override
{
for ( int64 i = 0 ; i < Length ; + + i )
{
( ( char * ) V ) [ i ] = ' d ' ;
}
CurrentPos + = Length ;
}
virtual int64 TotalSize ( ) override
{
return FakeTotalSize ;
}
virtual void Seek ( int64 InPos )
{
CurrentPos = InPos ;
}
virtual int64 Tell ( ) override
{
return CurrentPos ;
}
uint64 FakeTotalSize ;
uint64 CurrentPos = 0 ;
} ;
TEST_CASE_METHOD ( FWaitUntilCompleteHttpFixture , " Can upload big file exceeds 32 bits " , HTTP_TAG )
{
2023-08-01 11:06:27 -04:00
if ( ! bRunHeavyTests )
{
return ;
}
2023-06-16 10:53:42 -04:00
// TODO: Back to check later. xCurl 2206.4.0.0 doesn't work with file bigger than 32 bits
// 5 * 1024 * 1024 * 1024 BYTES = 5368709120 BYTES = 5 GB
//const uint64 TotalSize = 5368709120;
//const uint64 TotalSize = 4294967296;
//const uint64 TotalSize = 4294967295;
//const uint64 TotalSize = 2147483648;
const uint64 TotalSize = 2147483647 ;
TSharedRef < FTestHttpUploadStream > Stream = MakeShared < FTestHttpUploadStream > ( TotalSize ) ;
2023-10-27 08:42:11 -04:00
TSharedRef < IHttpRequest > HttpRequest = CreateRequest ( ) ;
2023-06-16 10:53:42 -04:00
HttpRequest - > SetURL ( FString : : Format ( TEXT ( " {0}/streaming_upload_put " ) , { * UrlHttpTests ( ) } ) ) ;
HttpRequest - > SetVerb ( TEXT ( " PUT " ) ) ;
HttpRequest - > SetContentFromStream ( Stream ) ;
HttpRequest - > SetHeader ( TEXT ( " Content-Disposition " ) , TEXT ( " attachment;filename=TestStreamUpload.dat " ) ) ;
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ Stream , TotalSize ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
CHECK ( bSucceeded ) ;
REQUIRE ( HttpResponse ! = nullptr ) ;
CHECK ( HttpResponse - > GetResponseCode ( ) = = 200 ) ;
CHECK ( Stream - > CurrentPos = = TotalSize ) ;
} ) ;
HttpRequest - > ProcessRequest ( ) ;
}
2023-11-20 09:55:01 -05:00
namespace UE
{
namespace TestHttp
2023-06-15 09:11:36 -04:00
{
2023-11-20 09:55:01 -05:00
void WriteTestFile ( const FString & TestFileName , uint64 TestFileSize )
{
FArchive * RawFile = IFileManager : : Get ( ) . CreateFileWriter ( * TestFileName ) ;
2023-06-15 09:11:36 -04:00
CHECK ( RawFile ! = nullptr ) ;
TSharedRef < FArchive > FileToWrite = MakeShareable ( RawFile ) ;
2023-11-20 09:55:01 -05:00
char * FileData = ( char * ) FMemory : : Malloc ( TestFileSize ) ;
FMemory : : Memset ( FileData , ' d ' , TestFileSize ) ;
FileToWrite - > Serialize ( FileData , TestFileSize ) ;
2023-06-15 09:11:36 -04:00
FileToWrite - > FlushCache ( ) ;
FileToWrite - > Close ( ) ;
FMemory : : Free ( FileData ) ;
2023-11-20 09:55:01 -05:00
}
}
}
TEST_CASE_METHOD ( FWaitUntilCompleteHttpFixture , " Streaming http upload from file by PUT can work well " , HTTP_TAG )
{
FString Filename = FString ( FPlatformProcess : : UserSettingsDir ( ) ) / TEXT ( " TestStreamUpload.dat " ) ;
UE : : TestHttp : : WriteTestFile ( Filename , 5 * 1024 * 1024 /*5MB*/ ) ;
2023-06-15 09:11:36 -04:00
2023-10-27 08:42:11 -04:00
TSharedRef < IHttpRequest > HttpRequest = CreateRequest ( ) ;
2023-06-15 09:11:36 -04:00
HttpRequest - > SetURL ( FString : : Format ( TEXT ( " {0}/streaming_upload_put " ) , { * UrlHttpTests ( ) } ) ) ;
HttpRequest - > SetVerb ( TEXT ( " PUT " ) ) ;
HttpRequest - > SetHeader ( TEXT ( " Content-Disposition " ) , TEXT ( " attachment;filename=TestStreamUpload.dat " ) ) ;
HttpRequest - > SetContentAsStreamedFile ( Filename ) ;
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ Filename ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
CHECK ( bSucceeded ) ;
CHECK ( HttpResponse - > GetResponseCode ( ) = = 200 ) ;
IFileManager : : Get ( ) . Delete ( * Filename ) ;
} ) ;
HttpRequest - > ProcessRequest ( ) ;
}
TEST_CASE_METHOD ( FWaitUntilCompleteHttpFixture , " Redirect enabled by default and can work well " , HTTP_TAG )
{
2023-10-27 08:42:11 -04:00
TSharedRef < IHttpRequest > HttpRequest = CreateRequest ( ) ;
2023-06-15 09:11:36 -04:00
HttpRequest - > SetURL ( FString : : Format ( TEXT ( " {0}/redirect_from " ) , { * UrlHttpTests ( ) } ) ) ;
HttpRequest - > SetVerb ( TEXT ( " GET " ) ) ;
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
CHECK ( bSucceeded ) ;
CHECK ( HttpResponse - > GetResponseCode ( ) = = 200 ) ;
} ) ;
HttpRequest - > ProcessRequest ( ) ;
}
2023-10-27 08:42:11 -04:00
class FWaitUntilQuitFromTestFixture : public FWaitUntilCompleteHttpFixture
{
public :
FWaitUntilQuitFromTestFixture ( )
{
}
~ FWaitUntilQuitFromTestFixture ( )
{
WaitUntilQuitFromTest ( ) ;
}
void WaitUntilQuitFromTest ( )
{
while ( ! bQuitRequested )
{
HttpModule - > GetHttpManager ( ) . Tick ( TickFrequency ) ;
FPlatformProcess : : Sleep ( TickFrequency ) ;
}
}
bool bQuitRequested = false ;
} ;
TEST_CASE_METHOD ( FWaitUntilQuitFromTestFixture , " Http request can be reused " , HTTP_TAG )
{
TSharedRef < IHttpRequest > HttpRequest = CreateRequest ( ) ;
HttpRequest - > SetURL ( UrlToTestMethods ( ) ) ;
HttpRequest - > SetVerb ( TEXT ( " POST " ) ) ;
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ this ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
CHECK ( bSucceeded ) ;
CHECK ( HttpResponse - > GetResponseCode ( ) = = 200 ) ;
uint32 Chunks = 3 ;
uint32 ChunkSize = 1024 ;
HttpRequest - > SetURL ( UrlStreamDownload ( Chunks , ChunkSize ) ) ;
HttpRequest - > SetVerb ( TEXT ( " GET " ) ) ;
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ this , Chunks , ChunkSize ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
CHECK ( bSucceeded ) ;
REQUIRE ( HttpResponse ! = nullptr ) ;
CHECK ( HttpResponse - > GetResponseCode ( ) = = 200 ) ;
CHECK ( HttpResponse - > GetContentLength ( ) = = Chunks * ChunkSize ) ;
bQuitRequested = true ;
} ) ;
HttpRequest - > ProcessRequest ( ) ;
} ) ;
HttpRequest - > ProcessRequest ( ) ;
}
2023-10-04 15:14:01 -04:00
// Response shared ptr should be able to be kept by user code and valid to access without http request
class FValidateResponseDependencyFixture : public FWaitUntilCompleteHttpFixture
{
public :
DECLARE_DELEGATE ( FValidateResponseDependencyDelegate ) ;
~ FValidateResponseDependencyFixture ( )
{
WaitUntilAllHttpRequestsComplete ( ) ;
ValidateResponseDependencyDelegate . ExecuteIfBound ( ) ;
}
FValidateResponseDependencyDelegate ValidateResponseDependencyDelegate ;
} ;
TEST_CASE_METHOD ( FValidateResponseDependencyFixture , " Http query with parameters " , HTTP_TAG )
{
struct FQueryWithParamsResponse : public FJsonSerializable
{
int32 VarInt ;
FString VarStr ;
BEGIN_JSON_SERIALIZER
JSON_SERIALIZE ( " var_int " , VarInt ) ;
JSON_SERIALIZE ( " var_str " , VarStr ) ;
END_JSON_SERIALIZER
} ;
TSharedRef < IHttpRequest > HttpRequest = HttpModule - > CreateRequest ( ) ;
FString UrlQueryWithParams = FString : : Format ( TEXT ( " {0}/query_with_params/?var_int=3&var_str=abc " ) , { * UrlHttpTests ( ) } ) ;
HttpRequest - > SetURL ( UrlQueryWithParams ) ;
HttpRequest - > SetVerb ( TEXT ( " GET " ) ) ;
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ this , UrlQueryWithParams ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
CHECK ( bSucceeded ) ;
REQUIRE ( HttpResponse ! = nullptr ) ;
CHECK ( HttpResponse - > GetResponseCode ( ) = = 200 ) ;
CHECK ( HttpRequest - > GetURL ( ) = = UrlQueryWithParams ) ;
FQueryWithParamsResponse QueryWithParamsResponse ;
REQUIRE ( QueryWithParamsResponse . FromJson ( HttpResponse - > GetContentAsString ( ) ) ) ;
CHECK ( FString : : FromInt ( QueryWithParamsResponse . VarInt ) = = HttpRequest - > GetURLParameter ( TEXT ( " var_int " ) ) ) ;
CHECK ( QueryWithParamsResponse . VarStr = = HttpRequest - > GetURLParameter ( TEXT ( " var_str " ) ) ) ;
CHECK ( FString : : FromInt ( QueryWithParamsResponse . VarInt ) = = HttpResponse - > GetURLParameter ( TEXT ( " var_int " ) ) ) ;
CHECK ( QueryWithParamsResponse . VarStr = = HttpResponse - > GetURLParameter ( TEXT ( " var_str " ) ) ) ;
ValidateResponseDependencyDelegate . BindLambda ( [ HttpResponse , UrlQueryWithParams , QueryWithParamsResponse ] ( ) {
// Validate all interfaces of http response can be called without accessing the destroyed http request
CHECK ( HttpResponse - > GetResponseCode ( ) = = 200 ) ;
CHECK ( ! HttpResponse - > GetContent ( ) . IsEmpty ( ) ) ;
CHECK ( ! HttpResponse - > GetContentAsString ( ) . IsEmpty ( ) ) ;
CHECK ( HttpResponse - > GetContentType ( ) = = TEXT ( " application/json " ) ) ;
CHECK ( HttpResponse - > GetHeader ( " Content-Type " ) = = TEXT ( " application/json " ) ) ;
CHECK ( ! HttpResponse - > GetAllHeaders ( ) . IsEmpty ( ) ) ;
CHECK ( HttpResponse - > GetURL ( ) = = UrlQueryWithParams ) ;
CHECK ( HttpResponse - > GetURLParameter ( TEXT ( " var_int " ) ) = = FString : : FromInt ( QueryWithParamsResponse . VarInt ) ) ;
CHECK ( HttpResponse - > GetURLParameter ( TEXT ( " var_str " ) ) = = QueryWithParamsResponse . VarStr ) ;
} ) ;
} ) ;
HttpRequest - > ProcessRequest ( ) ;
}
2023-08-30 17:27:56 -04:00
class FThreadedHttpRunnable : public FRunnable
2023-06-08 14:54:12 -04:00
{
public :
DECLARE_DELEGATE ( FRunActualTestCodeDelegate ) ;
2023-08-30 17:27:56 -04:00
FRunActualTestCodeDelegate & OnRunFromThread ( )
2023-06-08 14:54:12 -04:00
{
2023-08-30 17:27:56 -04:00
return ThreadCallback ;
2023-06-08 14:54:12 -04:00
}
// FRunnable interface
virtual uint32 Run ( ) override
{
ThreadCallback . ExecuteIfBound ( ) ;
return 0 ;
}
2023-08-30 17:27:56 -04:00
void StartTestHttpThread ( bool bBlockGameThread )
2023-06-08 14:54:12 -04:00
{
2023-08-30 17:27:56 -04:00
bBlockingGameThreadTick = bBlockGameThread ;
2023-06-08 14:54:12 -04:00
RunnableThread = TSharedPtr < FRunnableThread > ( FRunnableThread : : Create ( this , TEXT ( " Test Http Thread " ) ) ) ;
2023-08-30 17:27:56 -04:00
while ( bBlockingGameThreadTick )
{
float TickFrequency = 1.0f / 60 ; /*60 FPS*/ ;
FPlatformProcess : : Sleep ( TickFrequency ) ;
}
2023-06-08 14:54:12 -04:00
}
2023-08-30 17:27:56 -04:00
void UnblockGameThread ( )
{
bBlockingGameThreadTick = false ;
}
private :
2023-06-08 14:54:12 -04:00
FRunActualTestCodeDelegate ThreadCallback ;
TSharedPtr < FRunnableThread > RunnableThread ;
2023-08-30 17:27:56 -04:00
std : : atomic < bool > bBlockingGameThreadTick = true ;
} ;
class FWaitThreadedHttpFixture : public FWaitUntilCompleteHttpFixture
{
public :
~ FWaitThreadedHttpFixture ( )
{
WaitUntilAllHttpRequestsComplete ( ) ;
}
FThreadedHttpRunnable ThreadedHttpRunnable ;
2023-06-08 14:54:12 -04:00
} ;
TEST_CASE_METHOD ( FWaitThreadedHttpFixture , " Http streaming download request can work in non game thread " , HTTP_TAG )
{
2023-08-30 17:27:56 -04:00
ThreadedHttpRunnable . OnRunFromThread ( ) . BindLambda ( [ this ] ( ) {
2023-10-27 08:42:11 -04:00
TSharedRef < IHttpRequest > HttpRequest = CreateRequest ( ) ;
2023-06-15 09:11:36 -04:00
HttpRequest - > SetURL ( UrlStreamDownload ( 3 /*Chunks*/ , 1024 /*ChunkSize*/ ) ) ;
2023-06-08 14:54:12 -04:00
HttpRequest - > SetVerb ( TEXT ( " GET " ) ) ;
HttpRequest - > SetDelegateThreadPolicy ( EHttpRequestDelegateThreadPolicy : : CompleteOnHttpThread ) ;
class FTestHttpReceiveStream final : public FArchive
{
public :
virtual void Serialize ( void * V , int64 Length ) override
{
// No matter what's the thread policy, Serialize always get called in http thread.
CHECK ( ! IsInGameThread ( ) ) ;
}
} ;
CHECK ( HttpRequest - > SetResponseBodyReceiveStream ( MakeShared < FTestHttpReceiveStream > ( ) ) ) ;
2023-08-30 17:27:56 -04:00
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ this ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
2023-06-08 14:54:12 -04:00
// EHttpRequestDelegateThreadPolicy::CompleteOnHttpThread was used, so not in game thread here
CHECK ( ! IsInGameThread ( ) ) ;
CHECK ( bSucceeded ) ;
2023-08-30 17:27:56 -04:00
REQUIRE ( HttpResponse ! = nullptr ) ;
2023-06-08 14:54:12 -04:00
CHECK ( HttpResponse - > GetResponseCode ( ) = = 200 ) ;
2023-08-30 17:27:56 -04:00
CHECK ( ! HttpResponse - > GetAllHeaders ( ) . IsEmpty ( ) ) ;
ThreadedHttpRunnable . UnblockGameThread ( ) ;
2023-06-08 14:54:12 -04:00
} ) ;
HttpRequest - > ProcessRequest ( ) ;
} ) ;
2023-08-30 17:27:56 -04:00
ThreadedHttpRunnable . StartTestHttpThread ( true /*bBlockGameThread*/ ) ;
2023-06-08 14:54:12 -04:00
}
2023-08-30 18:56:04 -04:00
TEST_CASE_METHOD ( FWaitThreadedHttpFixture , " Http download request progress callback can be received in http thread " , HTTP_TAG )
{
std : : atomic < bool > bRequestProgressTriggered = false ;
ThreadedHttpRunnable . OnRunFromThread ( ) . BindLambda ( [ this , & bRequestProgressTriggered ] ( ) {
2023-10-27 08:42:11 -04:00
TSharedRef < IHttpRequest > HttpRequest = CreateRequest ( ) ;
2023-08-30 18:56:04 -04:00
HttpRequest - > SetURL ( UrlStreamDownload ( 10 /*Chunks*/ , 1024 * 1024 /*ChunkSize*/ ) ) ;
HttpRequest - > SetVerb ( TEXT ( " GET " ) ) ;
HttpRequest - > SetDelegateThreadPolicy ( EHttpRequestDelegateThreadPolicy : : CompleteOnHttpThread ) ;
HttpRequest - > OnRequestProgress64 ( ) . BindLambda ( [ this , & bRequestProgressTriggered ] ( FHttpRequestPtr Request , uint64 /*BytesSent*/ , uint64 BytesReceived ) {
if ( ! bRequestProgressTriggered )
{
// Only do these checks once, because when http request complete, this callback also get triggered
CHECK ( BytesReceived > 0 ) ;
CHECK ( BytesReceived < 10 /*Chunks*/ * 1024 * 1024 /*ChunkSize*/ ) ;
CHECK ( ! IsInGameThread ( ) ) ;
CHECK ( Request - > GetStatus ( ) = = EHttpRequestStatus : : Processing ) ;
bRequestProgressTriggered = true ;
}
} ) ;
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ this ] ( FHttpRequestPtr /*HttpRequest*/ , FHttpResponsePtr /*HttpResponse */ , bool bSucceeded ) {
CHECK ( bSucceeded ) ;
ThreadedHttpRunnable . UnblockGameThread ( ) ;
} ) ;
HttpRequest - > ProcessRequest ( ) ;
} ) ;
ThreadedHttpRunnable . StartTestHttpThread ( true /*bBlockGameThread*/ ) ;
CHECK ( bRequestProgressTriggered ) ;
}
2023-10-04 15:14:01 -04:00
TEST_CASE_METHOD ( FWaitUntilCompleteHttpFixture , " Http request pre check will fail " , HTTP_TAG )
{
DisableWarningsInThisTest ( ) ;
TSharedRef < IHttpRequest > HttpRequest = HttpModule - > CreateRequest ( ) ;
SECTION ( " when verb was set to empty " )
{
HttpRequest - > SetURL ( UrlToTestMethods ( ) ) ;
HttpRequest - > SetVerb ( TEXT ( " " ) ) ;
}
SECTION ( " when url protocol is not valid " )
{
HttpRequest - > SetURL ( " http_abc://www.epicgames.com " ) ;
HttpRequest - > SetVerb ( TEXT ( " GET " ) ) ;
}
SECTION ( " when url was not set " )
{
HttpRequest - > SetVerb ( TEXT ( " GET " ) ) ;
}
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
CHECK ( ! bSucceeded ) ;
} ) ;
HttpRequest - > ProcessRequest ( ) ;
}
2023-06-08 14:54:12 -04:00
namespace UE
{
namespace TestHttp
{
void SetupURLRequestFilter ( FHttpModule * HttpModule )
2023-06-02 20:28:07 -04:00
{
// Pre check will fail when domain is not allowed
UE : : Core : : FURLRequestFilter : : FRequestMap SchemeMap ;
SchemeMap . Add ( TEXT ( " http " ) , TArray < FString > { TEXT ( " epicgames.com " ) } ) ;
UE : : Core : : FURLRequestFilter Filter { SchemeMap } ;
HttpModule - > GetHttpManager ( ) . SetURLRequestFilter ( Filter ) ;
2023-06-08 14:54:12 -04:00
}
}
}
2023-10-27 08:42:11 -04:00
// Pre-check failed requests won't be added into http manager, so it can't rely on the requested added/completed callback in FWaitUntilCompleteHttpFixture
TEST_CASE_METHOD ( FWaitUntilQuitFromTestFixture , " Http request pre check will fail by thread policy " , HTTP_TAG )
2023-06-08 14:54:12 -04:00
{
2023-08-30 21:20:37 -04:00
DisableWarningsInThisTest ( ) ;
2023-06-08 14:54:12 -04:00
// Pre check will fail when domain is not allowed
UE : : TestHttp : : SetupURLRequestFilter ( HttpModule ) ;
2023-06-02 20:28:07 -04:00
2023-10-27 08:42:11 -04:00
TSharedRef < IHttpRequest > HttpRequest = CreateRequest ( ) ;
2023-06-02 20:28:07 -04:00
HttpRequest - > SetVerb ( TEXT ( " GET " ) ) ;
2023-06-08 14:54:12 -04:00
HttpRequest - > SetURL ( UrlToTestMethods ( ) ) ;
SECTION ( " on game thread " )
{
2023-10-27 08:42:11 -04:00
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ this ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
2023-06-08 14:54:12 -04:00
CHECK ( IsInGameThread ( ) ) ;
CHECK ( ! bSucceeded ) ;
2023-10-27 08:42:11 -04:00
bQuitRequested = true ;
2023-06-08 14:54:12 -04:00
} ) ;
}
SECTION ( " on http thread " )
{
HttpRequest - > SetDelegateThreadPolicy ( EHttpRequestDelegateThreadPolicy : : CompleteOnHttpThread ) ;
2023-10-27 08:42:11 -04:00
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ this ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
2023-06-08 14:54:12 -04:00
CHECK ( ! IsInGameThread ( ) ) ;
CHECK ( ! bSucceeded ) ;
2023-10-27 08:42:11 -04:00
bQuitRequested = true ;
2023-06-08 14:54:12 -04:00
} ) ;
}
2023-06-02 20:28:07 -04:00
HttpRequest - > ProcessRequest ( ) ;
}
2023-10-27 08:42:11 -04:00
class FWaitUnitilQuitFromTestThreadedFixture : public FWaitUntilQuitFromTestFixture
{
public :
~ FWaitUnitilQuitFromTestThreadedFixture ( )
{
WaitUntilQuitFromTest ( ) ;
}
FThreadedHttpRunnable ThreadedHttpRunnable ;
} ;
// Pre-check failed requests won't be added into http manager, so it can't rely on the requested added/completed callback in FWaitUntilCompleteHttpFixture
TEST_CASE_METHOD ( FWaitUnitilQuitFromTestThreadedFixture , " Threaded http request pre check will fail by thread policy " , HTTP_TAG )
2023-06-08 14:54:12 -04:00
{
2023-08-30 21:20:37 -04:00
DisableWarningsInThisTest ( ) ;
2023-08-30 17:27:56 -04:00
ThreadedHttpRunnable . OnRunFromThread ( ) . BindLambda ( [ this ] ( ) {
2023-06-08 14:54:12 -04:00
// Pre check will fail when domain is not allowed
UE : : TestHttp : : SetupURLRequestFilter ( HttpModule ) ;
2023-10-27 08:42:11 -04:00
TSharedRef < IHttpRequest > HttpRequest = CreateRequest ( ) ;
2023-06-08 14:54:12 -04:00
HttpRequest - > SetVerb ( TEXT ( " GET " ) ) ;
HttpRequest - > SetURL ( UrlToTestMethods ( ) ) ;
SECTION ( " on game thread " )
{
2023-10-27 08:42:11 -04:00
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ this ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
2023-06-08 14:54:12 -04:00
CHECK ( IsInGameThread ( ) ) ;
CHECK ( ! bSucceeded ) ;
2023-10-27 08:42:11 -04:00
bQuitRequested = true ;
2023-06-08 14:54:12 -04:00
} ) ;
}
SECTION ( " on http thread " )
{
HttpRequest - > SetDelegateThreadPolicy ( EHttpRequestDelegateThreadPolicy : : CompleteOnHttpThread ) ;
2023-10-27 08:42:11 -04:00
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ this ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
2023-06-08 14:54:12 -04:00
CHECK ( ! IsInGameThread ( ) ) ;
CHECK ( ! bSucceeded ) ;
2023-10-27 08:42:11 -04:00
bQuitRequested = true ;
2023-06-08 14:54:12 -04:00
} ) ;
}
HttpRequest - > ProcessRequest ( ) ;
} ) ;
2023-08-30 17:27:56 -04:00
ThreadedHttpRunnable . StartTestHttpThread ( false /*bBlockGameThread*/ ) ;
2023-06-08 14:54:12 -04:00
}
2023-06-14 04:02:22 -04:00
TEST_CASE_METHOD ( FWaitUntilCompleteHttpFixture , " Cancel http request connect before timeout " , HTTP_TAG )
{
2023-09-06 10:45:24 -04:00
DisableWarningsInThisTest ( ) ;
2023-10-27 08:42:11 -04:00
TSharedRef < IHttpRequest , ESPMode : : ThreadSafe > HttpRequest = CreateRequest ( ) ;
2023-06-14 04:02:22 -04:00
HttpRequest - > SetURL ( UrlWithInvalidPortToTestConnectTimeout ( ) ) ;
HttpRequest - > SetVerb ( TEXT ( " GET " ) ) ;
HttpRequest - > SetTimeout ( 7 ) ;
FDateTime StartTime = FDateTime : : Now ( ) ;
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ StartTime ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
CHECK ( ! bSucceeded ) ;
FTimespan Timespan = FDateTime : : Now ( ) - StartTime ;
float DurationInSeconds = Timespan . GetTotalSeconds ( ) ;
CHECK ( DurationInSeconds < 2 ) ;
} ) ;
HttpRequest - > ProcessRequest ( ) ;
FPlatformProcess : : Sleep ( 0.5 ) ;
HttpRequest - > CancelRequest ( ) ;
}
2023-08-30 17:27:56 -04:00
2023-10-27 08:42:11 -04:00
class FThreadedBatchRequestsFixture : public FWaitThreadedHttpFixture
2023-08-30 17:27:56 -04:00
{
public :
2023-10-27 08:42:11 -04:00
void LaunchBatchRequests ( uint32 BatchSize )
2023-08-30 17:27:56 -04:00
{
2023-10-27 08:42:11 -04:00
for ( uint32 i = 0 ; i < BatchSize ; + + i )
2023-08-30 17:27:56 -04:00
{
2023-10-27 08:42:11 -04:00
TSharedRef < IHttpRequest > HttpRequest = CreateRequest ( ) ;
2023-08-30 17:27:56 -04:00
HttpRequest - > SetURL ( UrlStreamDownload ( 3 , 1024 * 1024 ) ) ;
HttpRequest - > SetVerb ( TEXT ( " GET " ) ) ;
HttpRequest - > ProcessRequest ( ) ;
}
}
2023-10-27 08:42:11 -04:00
void BlockUntilFlushed ( )
{
if ( bRetryEnabled )
{
HttpRetryManager - > BlockUntilFlushed ( 5.0 ) ;
}
else
{
HttpModule - > GetHttpManager ( ) . Flush ( EHttpFlushReason : : Default ) ;
}
}
2023-08-30 17:27:56 -04:00
} ;
2023-10-27 08:42:11 -04:00
TEST_CASE_METHOD ( FThreadedBatchRequestsFixture , " Retry manager and http manager is thread safe " , HTTP_TAG )
2023-08-30 17:27:56 -04:00
{
2023-09-01 10:24:00 -04:00
DisableWarningsInThisTest ( ) ;
2023-08-30 17:27:56 -04:00
ThreadedHttpRunnable . OnRunFromThread ( ) . BindLambda ( [ this ] ( ) {
2023-10-27 08:42:11 -04:00
LaunchBatchRequests ( 10 ) ;
BlockUntilFlushed ( ) ;
2023-08-30 17:27:56 -04:00
} ) ;
ThreadedHttpRunnable . StartTestHttpThread ( false /*bBlockGameThread*/ ) ;
2023-10-27 08:42:11 -04:00
LaunchBatchRequests ( 10 ) ;
BlockUntilFlushed ( ) ;
2023-08-30 17:27:56 -04:00
}
2023-11-20 09:55:01 -05:00
# if (PLATFORM_WINDOWS && !WITH_CURL_XCURL) || PLATFORM_MAC || PLATFORM_UNIX
TEST_CASE_METHOD ( FWaitUntilCompleteHttpFixture , " Scheme besides http and https can work if allowed by settings " , HTTP_TAG )
{
bool bShouldSucceed = false ;
SECTION ( " when allowed " )
{
bShouldSucceed = true ;
}
SECTION ( " when not allowed " )
{
DisableWarningsInThisTest ( ) ;
// Pre check will fail when scheme is not listed
UE : : TestHttp : : SetupURLRequestFilter ( HttpModule ) ;
}
FString Filename = FString ( FPlatformProcess : : UserSettingsDir ( ) ) / TEXT ( " TestProtocolAllowed.dat " ) ;
UE : : TestHttp : : WriteTestFile ( Filename , 10 /*Bytes*/ ) ;
TSharedRef < IHttpRequest , ESPMode : : ThreadSafe > HttpRequest = HttpModule - > CreateRequest ( ) ;
HttpRequest - > SetURL ( FString ( TEXT ( " file:// " ) ) + Filename . Replace ( TEXT ( " " ) , TEXT ( " %20 " ) ) ) ;
HttpRequest - > SetVerb ( TEXT ( " GET " ) ) ;
HttpRequest - > OnProcessRequestComplete ( ) . BindLambda ( [ Filename , bShouldSucceed ] ( FHttpRequestPtr HttpRequest , FHttpResponsePtr HttpResponse , bool bSucceeded ) {
CHECK ( bSucceeded = = bShouldSucceed ) ;
IFileManager : : Get ( ) . Delete ( * Filename ) ;
} ) ;
HttpRequest - > ProcessRequest ( ) ;
}
# endif
2023-08-30 17:27:56 -04:00
2023-06-08 14:54:12 -04:00
// TODO: Add cancel test, with multiple cancel calls
2023-08-30 18:56:04 -04:00
// TODO: Add test case to validate header received callback can be received in http/game thread, and can be received before the request complete