Files
UnrealEngine/Engine/Source/Runtime/Online/HTTP/Private/HttpManager.cpp
2025-05-18 13:04:45 +08:00

812 lines
27 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "HttpManager.h"
#include "HttpModule.h"
#include "HAL/IConsoleManager.h"
#include "HAL/PlatformTime.h"
#include "HAL/PlatformProcess.h"
#include "Misc/ScopeLock.h"
#include "Http.h"
#include "Misc/App.h"
#include "Misc/Guid.h"
#include "Misc/Fork.h"
#include "HttpThread.h"
#include "GenericPlatform/HttpRequestCommon.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/CommandLine.h"
#include "ProfilingDebugging/CsvProfiler.h"
#include "Stats/Stats.h"
#include "Containers/BackgroundableTicker.h"
TAutoConsoleVariable<int32> CVarHttpEventLoopEnableChance(
TEXT("http.CurlEventLoopEnableChance"),
UE_HTTP_EVENT_LOOP_ENABLE_CHANCE_BY_DEFAULT,
TEXT("Enable chance of event loop, from 0 to 100"),
ECVF_SaveForNextBoot
);
TAutoConsoleVariable<FString> CVarHttpUrlPatternsToLogResponse(
TEXT("http.UrlPatternsToLogResponse"),
TEXT(""),
TEXT("List of url patterns to log headers and json content: \"epicgames.com unrealengine.com ...\""),
ECVF_SaveForNextBoot
);
TAutoConsoleVariable<FString> CVarHttpUrlPatternsToDisableFailedLog(
TEXT("http.UrlPatternsToDisableFailedLog"),
TEXT(""),
TEXT("List of url patterns to disable failed log: \"epicgames.com unrealengine.com ...\"")
);
TAutoConsoleVariable<FString> CVarHttpUrlPatternsToMockFailure(
TEXT("http.UrlPatternsToMockFailure"),
TEXT(""),
TEXT("List of url patterns to mock failure with response code, 0 indicates ConnectionError: \"epicgames.com->0 unrealengine.com->503 ...\""),
ECVF_SaveForNextBoot
);
TAutoConsoleVariable<bool> CVarHttpRemoveRequestUsingHttpThreadPolicyOnHttpThread(
TEXT("http.RemoveRequestUsingHttpThreadPolicyOnHttpThread"),
true,
TEXT("When enabled, if http request use CompleteOnHttpThread, once complete it will be removed from http manager on http thread"),
ECVF_SaveForNextBoot
);
// FHttpManager
const TCHAR* LexToString(const EHttpFlushReason& FlushReason)
{
switch (FlushReason)
{
case EHttpFlushReason::Default: return TEXT("Default");
PRAGMA_DISABLE_DEPRECATION_WARNINGS
case EHttpFlushReason::Background: return TEXT("Background");
PRAGMA_ENABLE_DEPRECATION_WARNINGS
case EHttpFlushReason::Shutdown: return TEXT("Shutdown");
case EHttpFlushReason::FullFlush: return TEXT("FullFlush");
}
checkNoEntry();
return TEXT("Invalid");
}
namespace
{
bool ShouldOutputHttpWarnings()
{
return !IsRunningCommandlet() && !FApp::IsUnattended();
}
}
CSV_DEFINE_CATEGORY(HttpManager, true);
CSV_DEFINE_STAT(HttpManager, RequestsInQueue);
CSV_DEFINE_STAT(HttpManager, MaxRequestsInQueue);
CSV_DEFINE_STAT(HttpManager, RequestsInFlight);
CSV_DEFINE_STAT(HttpManager, MaxRequestsInFlight);
CSV_DEFINE_STAT(HttpManager, MaxTimeToWaitInQueue);
CSV_DEFINE_STAT(HttpManager, DownloadedMB);
CSV_DEFINE_STAT(HttpManager, BandwidthMbps);
CSV_DEFINE_STAT(HttpManager, DurationMsAvg);
PRAGMA_DISABLE_DEPRECATION_WARNINGS
FHttpManager::FHttpManager()
: FTSTickerObjectBase(0.0f, FTSBackgroundableTicker::GetCoreTicker())
, Thread(nullptr)
, CorrelationIdMethod(FHttpManager::GetDefaultCorrelationIdMethod())
{
bFlushing = false;
}
FHttpManager::~FHttpManager()
{
if (Thread)
{
Thread->StopThread();
delete Thread;
}
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
void FHttpManager::Initialize()
{
if (!Thread)
{
bUseEventLoop = (FMath::RandRange(0, 99) < CVarHttpEventLoopEnableChance.GetValueOnGameThread());
// Also support to change it through runtime args.
// Can't set cvar CVarHttpEventLoopEnableChance through runtime args or .ini files because http module initialized too early
FParse::Bool(FCommandLine::Get(), TEXT("useeventloop="), bUseEventLoop);
Thread = CreateHttpThread();
Thread->StartThread();
}
UpdateConfigs();
UpdateUrlPatternsToLogResponse(CVarHttpUrlPatternsToLogResponse.AsVariable());
CVarHttpUrlPatternsToLogResponse.AsVariable()->OnChangedDelegate().AddRaw(this, &FHttpManager::UpdateUrlPatternsToLogResponse);
UpdateUrlPatternsToDisableFailedLog(CVarHttpUrlPatternsToDisableFailedLog.AsVariable());
CVarHttpUrlPatternsToDisableFailedLog.AsVariable()->OnChangedDelegate().AddRaw(this, &FHttpManager::UpdateUrlPatternsToDisableFailedLog);
UpdateUrlPatternsToMockFailure(CVarHttpUrlPatternsToMockFailure.AsVariable());
CVarHttpUrlPatternsToMockFailure.AsVariable()->OnChangedDelegate().AddRaw(this, &FHttpManager::UpdateUrlPatternsToMockFailure);
}
void FHttpManager::Shutdown()
{
CVarHttpUrlPatternsToLogResponse.AsVariable()->OnChangedDelegate().Clear();
CVarHttpUrlPatternsToDisableFailedLog.AsVariable()->OnChangedDelegate().Clear();
CVarHttpUrlPatternsToMockFailure.AsVariable()->OnChangedDelegate().Clear();
{
FScopeLock ScopeLock(&RequestLock);
// Don't emit these tracking logs in commandlet runs. Build system traps warnings during cook, and these are not truly fatal, but useful for tracking down shutdown issues.
UE_CLOG(ShouldOutputHttpWarnings() && Requests.Num(), LogHttp, Warning, TEXT("[FHttpManager::Shutdown] Unbinding delegates for %d outstanding Http Requests:"), Requests.Num());
// Clear delegates since they may point to deleted instances
for (TArray<FHttpRequestRef>::TIterator It(Requests); It; ++It)
{
TSharedPtr<IHttpRequest> Request = *It;
StaticCastSharedPtr<FHttpRequestImpl>(Request)->Shutdown();
// Don't emit these tracking logs in commandlet runs. Build system traps warnings during cook, and these are not truly fatal, but useful for tracking down shutdown issues.
UE_CLOG(ShouldOutputHttpWarnings(), LogHttp, Warning, TEXT(" verb=[%s] url=[%s] refs=[%d] status=%s"), *Request->GetVerb(), *Request->GetURL(), Request.GetSharedReferenceCount(), EHttpRequestStatus::ToString(Request->GetStatus()));
}
}
// Clear general delegates since they may point to deleted instances
RequestAddedDelegate.Unbind();
RequestCompletedDelegate.Unbind();
// Flush all requests
Flush(EHttpFlushReason::Shutdown);
}
bool FHttpManager::HasAnyBoundDelegate() const
{
FScopeLock ScopeLock(&RequestLock);
for (TArray<FHttpRequestRef>::TConstIterator It(Requests); It; ++It)
{
const FHttpRequestRef& Request = *It;
if (Request->OnProcessRequestComplete().IsBound())
{
return true;
}
}
if (RequestAddedDelegate.IsBound())
{
return true;
}
if (RequestCompletedDelegate.IsBound())
{
return true;
}
return false;
}
void FHttpManager::ReloadFlushTimeLimits()
{
FlushTimeLimitsMap.Reset();
//Save int values of Default and FullFlush?
for (EHttpFlushReason Reason : TEnumRange<EHttpFlushReason>())
{
double SoftLimitSeconds = 2.0;
double HardLimitSeconds = 4.0;
// We default the time limits to generous values, keeping the Hard limits always greater than the soft ones, and -1 for the unlimited
switch (Reason)
{
case EHttpFlushReason::Default:
GConfig->GetDouble(TEXT("HTTP"), TEXT("FlushSoftTimeLimitDefault"), SoftLimitSeconds, GEngineIni);
GConfig->GetDouble(TEXT("HTTP"), TEXT("FlushHardTimeLimitDefault"), HardLimitSeconds, GEngineIni);
break;
case EHttpFlushReason::Shutdown:
GConfig->GetDouble(TEXT("HTTP"), TEXT("FlushSoftTimeLimitShutdown"), SoftLimitSeconds, GEngineIni);
GConfig->GetDouble(TEXT("HTTP"), TEXT("FlushHardTimeLimitShutdown"), HardLimitSeconds, GEngineIni);
if ((HardLimitSeconds >= 0) && ((SoftLimitSeconds < 0) || (SoftLimitSeconds >= HardLimitSeconds)))
{
UE_CLOG(!IsRunningCommandlet(), LogHttp, Warning, TEXT("Soft limit[%.02f] is higher than the hard limit set[%.02f] in file [%s]. Please change the soft limit to a value lower than the hard limit for Flush to work correctly. - 1 is unlimited and therefore the highest possible value."), static_cast<float>(SoftLimitSeconds), static_cast<float>(HardLimitSeconds), *GEngineIni);
// we need to be absolutely sure that SoftLimitSeconds is always strictly less than HardLimitSeconds so remaining requests (if any) can be canceled before exiting
if (HardLimitSeconds > 0.0)
{
SoftLimitSeconds = HardLimitSeconds / 2.0; // clamping SoftLimitSeconds to a reasonable value
}
else
{
// HardLimitSeconds should never be 0.0 while shutting down otherwise we can't cancel the remaining requests
HardLimitSeconds = 0.05; // using a non zero value
SoftLimitSeconds = 0.0; // cancelling request immediately
}
}
break;
case EHttpFlushReason::FullFlush:
SoftLimitSeconds = -1.0;
HardLimitSeconds = -1.0;
GConfig->GetDouble(TEXT("HTTP"), TEXT("FlushSoftTimeLimitFullFlush"), SoftLimitSeconds, GEngineIni);
GConfig->GetDouble(TEXT("HTTP"), TEXT("FlushHardTimeLimitFullFlush"), HardLimitSeconds, GEngineIni);
break;
}
FHttpFlushTimeLimit TimeLimit(SoftLimitSeconds, HardLimitSeconds);
FlushTimeLimitsMap.Add(Reason, TimeLimit);
}
}
void FHttpManager::SetCorrelationIdMethod(TFunction<FString()> InCorrelationIdMethod)
{
check(InCorrelationIdMethod);
CorrelationIdMethod = MoveTemp(InCorrelationIdMethod);
}
FString FHttpManager::CreateCorrelationId() const
{
return CorrelationIdMethod();
}
#if !UE_BUILD_SHIPPING
namespace HttpManagerInternal
{
static void CheckNoDuplicatedProtocol(FStringView UrlStringView)
{
const FStringView Delimiter = TEXT("://");
const int32 FoundIndex = UE::String::FindFirst(UrlStringView, Delimiter);
if (FoundIndex != INDEX_NONE)
{
FStringView Domain = UrlStringView.RightChop(FoundIndex + Delimiter.Len());
UE_CLOG(Domain.Contains(Delimiter), LogHttp, Warning, TEXT("Duplicated delimiter '://' found in url %.*s, is that intended?"), UrlStringView.Len(), UrlStringView.GetData());
}
}
}
#endif // !UE_BUILD_SHIPPING
bool FHttpManager::IsDomainAllowed(const FString& Url) const
{
#if !UE_BUILD_SHIPPING
HttpManagerInternal::CheckNoDuplicatedProtocol(Url);
#endif // !UE_BUILD_SHIPPING
if (!URLRequestFilter.IsEmpty())
{
return URLRequestFilter.IsRequestAllowed(Url);
}
#if !UE_BUILD_SHIPPING
#if !(UE_GAME || UE_SERVER)
// Allowed domain filtering is opt-in in non-shipping non-game/server builds
static const bool bForceUseAllowList = FParse::Param(FCommandLine::Get(), TEXT("EnableHttpDomainRestrictions"));
if (!bForceUseAllowList)
{
return true;
}
#else
// The check is on by default but allow non-shipping game/server builds to disable the filtering
static const bool bIgnoreAllowList = FParse::Param(FCommandLine::Get(), TEXT("DisableHttpDomainRestrictions"));
if (bIgnoreAllowList)
{
return true;
}
#endif
#endif // !UE_BUILD_SHIPPING
return true;
}
/*static*/
TFunction<FString()> FHttpManager::GetDefaultCorrelationIdMethod()
{
return []{ return FGuid::NewGuid().ToString(); };
}
void FHttpManager::OnBeforeFork()
{
Flush(EHttpFlushReason::Default);
}
void FHttpManager::OnAfterFork()
{
}
void FHttpManager::OnEndFramePostFork()
{
// nothing
}
void FHttpManager::UpdateConfigs()
{
URLRequestFilter.UpdateConfig(TEXT("Online.HttpManager"), GEngineIni);
ReloadFlushTimeLimits();
if (Thread)
{
Thread->UpdateConfigs();
}
}
void FHttpManager::AddGameThreadTask(TFunction<void()>&& Task, float Delay)
{
if (Task)
{
GameThreadTicker.AddTicker(FTickerDelegate::CreateLambda([Task](float DeltaTime) {
Task();
return false;
}), Delay);
}
}
TSharedPtr<IHttpTaskTimerHandle> FHttpManager::AddHttpThreadTask(TFunction<void()>&& Task, float InDelay)
{
check(Thread);
return Thread->AddHttpThreadTask(MoveTemp(Task), InDelay);
}
void FHttpManager::RemoveHttpThreadTask(TSharedPtr<IHttpTaskTimerHandle> HttpTaskTimerHandle)
{
check(Thread);
HttpTaskTimerHandle->RemoveTaskFrom(Thread);
}
FHttpThreadBase* FHttpManager::CreateHttpThread()
{
return new FLegacyHttpThread();
}
void FHttpManager::Flush(EHttpFlushReason FlushReason)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_FHttpManager_Flush);
UE_CLOG(IsCurrentThreadCompletingRequest(), LogHttp, Warning, TEXT("It's not supported to call Flush during http request complete delegate!"));
checkf(FlushReason != EHttpFlushReason::Shutdown || !HasAnyBoundDelegate(), TEXT("Use Shutdown() instead of Flush(EHttpFlushReason::Shutdown) directly."));
// This variable is set to indicate that flush is happening.
// While flushing is in progress, the RequestLock is held and threads are blocked when trying to submit new requests.
bFlushing = true;
double FlushTimeSoftLimitSeconds = FlushTimeLimitsMap[FlushReason].SoftLimitSeconds;
double FlushTimeHardLimitSeconds = FlushTimeLimitsMap[FlushReason].HardLimitSeconds;
// this specifies how long to sleep between calls to tick.
// The smaller the value, the more quickly we may find out that all requests have completed, but the more work may be done in the meantime.
float SecondsToSleepForOutstandingThreadedRequests = 0.5f;
GConfig->GetFloat(TEXT("HTTP"), TEXT("RequestCleanupDelaySec"), SecondsToSleepForOutstandingThreadedRequests, GEngineIni);
UE_CLOG(!IsRunningCommandlet(), LogHttp, Verbose, TEXT("[FHttpManager::Flush] FlushReason [%s] FlushTimeSoftLimitSeconds [%.3fs] FlushTimeHardLimitSeconds [%.3fs] SecondsToSleepForOutstandingThreadedRequests [%.3fs]"), LexToString(FlushReason), FlushTimeSoftLimitSeconds, FlushTimeHardLimitSeconds, SecondsToSleepForOutstandingThreadedRequests);
uint32 RequestsNum = 0;
{
FScopeLock ScopeLock(&RequestLock);
RequestsNum = Requests.Num();
}
UE_CLOG(!IsRunningCommandlet() && RequestsNum, LogHttp, Verbose, TEXT("[FHttpManager::Flush] Cleanup starts for %d outstanding Http Requests."), RequestsNum);
double BeginWaitTime = FPlatformTime::Seconds();
double LastFlushTickTime = BeginWaitTime;
double StallWarnTime = BeginWaitTime + 0.5;
double AppTime = FPlatformTime::Seconds();
// For a duration equal to FlushTimeHardLimitSeconds, we wait for ongoing http requests to complete
while (RequestsNum > 0 && (FlushTimeHardLimitSeconds < 0 || (AppTime - BeginWaitTime < FlushTimeHardLimitSeconds)))
{
SCOPED_ENTER_BACKGROUND_EVENT(STAT_FHttpManager_Flush_Iteration);
// If time equal to FlushTimeSoftLimitSeconds has passed and there's still ongoing http requests, we cancel them (setting FlushTimeSoftLimitSeconds to 0 does this immediately)
if (FlushTimeSoftLimitSeconds >= 0 && (AppTime - BeginWaitTime >= FlushTimeSoftLimitSeconds))
{
// Don't emit these tracking logs in commandlet runs. Build system traps warnings during cook, and these are not truly fatal, but useful for tracking down shutdown issues.
UE_CLOG(ShouldOutputHttpWarnings(), LogHttp, Warning, TEXT("[FHttpManager::Flush] FlushTimeSoftLimitSeconds [%.3fs] exceeded. Cancelling %d outstanding HTTP requests:"), FlushTimeSoftLimitSeconds, RequestsNum);
{
TArray<FHttpRequestRef> RequestsToCancel;
{
FScopeLock ScopeLock(&RequestLock);
RequestsToCancel = Requests;
}
for (TArray<FHttpRequestRef>::TIterator It(RequestsToCancel); It; ++It)
{
FHttpRequestRef& Request = *It;
// Don't emit these tracking logs in commandlet runs. Build system traps warnings during cook, and these are not truly fatal, but useful for tracking down shutdown issues.
UE_CLOG(ShouldOutputHttpWarnings(), LogHttp, Warning, TEXT(" verb=[%s] url=[%s] refs=[%d] status=%s"), *Request->GetVerb(), *Request->GetURL(), Request.GetSharedReferenceCount(), EHttpRequestStatus::ToString(Request->GetStatus()));
FScopedEnterBackgroundEvent(*Request->GetURL());
Request->CancelRequest();
}
}
}
// Process ongoing Http Requests
FlushTick(AppTime - LastFlushTickTime);
LastFlushTickTime = AppTime;
{
FScopeLock ScopeLock(&RequestLock);
RequestsNum = Requests.Num();
}
// Process threaded Http Requests
if (RequestsNum > 0)
{
if (Thread)
{
if (Thread->NeedsSingleThreadTick())
{
if (AppTime >= StallWarnTime)
{
// Don't emit these tracking logs in commandlet runs. Build system traps warnings during cook, and these are not truly fatal, but useful for tracking down shutdown issues.
UE_CLOG(ShouldOutputHttpWarnings(), LogHttp, Warning, TEXT(" Ticking HTTPThread for %d outstanding Http requests."), RequestsNum);
StallWarnTime = AppTime + 0.5;
}
Thread->Tick();
}
else
{
// Don't emit these tracking logs in commandlet runs. Build system traps warnings during cook, and these are not truly fatal, but useful for tracking down shutdown issues.
UE_CLOG(ShouldOutputHttpWarnings(), LogHttp, Warning, TEXT(" Sleeping %.3fs to wait for %d outstanding Http Requests."), SecondsToSleepForOutstandingThreadedRequests, RequestsNum);
FPlatformProcess::Sleep(SecondsToSleepForOutstandingThreadedRequests);
}
}
}
AppTime = FPlatformTime::Seconds();
}
UE_CLOG(!IsRunningCommandlet(), LogHttp, Verbose, TEXT("[FHttpManager::Flush] Cleanup ended after %.3fs. %d outstanding Http Requests."), AppTime - BeginWaitTime, RequestsNum);
// Don't emit these tracking logs in commandlet runs. Build system traps warnings during cook, and these are not truly fatal, but useful for tracking down shutdown issues.
if (RequestsNum > 0 && (FlushTimeHardLimitSeconds > 0 && (AppTime - BeginWaitTime > FlushTimeHardLimitSeconds)) && ShouldOutputHttpWarnings())
{
UE_LOG(LogHttp, Warning, TEXT("[FHttpManager::Flush] FlushTimeHardLimitSeconds [%.3fs] exceeded. The following requests are being abandoned without being flushed:"), FlushTimeHardLimitSeconds);
FScopeLock ScopeLock(&RequestLock);
for (TArray<FHttpRequestRef>::TIterator It(Requests); It; ++It)
{
FHttpRequestRef& Request = *It;
//List the outstanding requests that are being abandoned without being canceled.
UE_LOG(LogHttp, Warning, TEXT(" verb=[%s] url=[%s] refs=[%d] status=%s"), *Request->GetVerb(), *Request->GetURL(), Request.GetSharedReferenceCount(), EHttpRequestStatus::ToString(Request->GetStatus()));
}
}
bFlushing = false;
}
bool FHttpManager::Tick(float DeltaSeconds)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_FHttpManager_Tick);
// Normally Tick() should only be called from game thread. But it's still possible Tick() be called
// from off-game thread when quit in purpose like GPU OOM, to flush remain HTTP analysis requests
// Run GameThread tasks
{
FScopeLock ScopeLock(&GameThreadTickerLock);
GameThreadTicker.Tick(DeltaSeconds);
}
if (Thread)
{
{
// Tick each active request
FScopeLock ScopeLock(&RequestLock);
for (const FHttpRequestRef& Request : Requests)
{
Request->Tick(DeltaSeconds);
}
}
TArray<FHttpRequestCommon*> CompletedThreadedRequests;
{
// Thread->GetCompletedRequests doesn't support multi-thread access
FScopeLock ScopeLock(&CompletedRequestLock);
Thread->GetCompletedRequests(CompletedThreadedRequests);
}
// Finish and remove any completed requests
for (FHttpRequestCommon* CompletedRequest : CompletedThreadedRequests)
{
FHttpRequestRef CompletedRequestRef = CompletedRequest->AsShared();
if (!CVarHttpRemoveRequestUsingHttpThreadPolicyOnHttpThread.GetValueOnAnyThread() || CompletedRequest->GetDelegateThreadPolicy() == EHttpRequestDelegateThreadPolicy::CompleteOnGameThread)
{
FScopeLock ScopeLock(&RequestLock);
Requests.Remove(CompletedRequestRef);
}
if (CompletedRequest->GetDelegateThreadPolicy() == EHttpRequestDelegateThreadPolicy::CompleteOnGameThread)
{
MarkCurrentThreadCompletingRequest(true);
CompletedRequest->FinishRequest();
// Can't move BroadcastHttpRequestCompleted to CompletedRequest->FinishRequest because
// FinishRequest can be called before adding into HttpManager when pre-check failed
BroadcastHttpRequestCompleted(CompletedRequestRef);
MarkCurrentThreadCompletingRequest(false);
}
}
}
// Report csv stats.
int32 TotalDownloadedMB = int32(HttpStats.TotalDownloadedBytes.load() >> 20);
CSV_CUSTOM_STAT_DEFINED(RequestsInQueue, HttpStats.RequestsInQueue.load(), ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT_DEFINED(MaxRequestsInQueue, int32(HttpStats.MaxRequestsInQueue.load()), ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT_DEFINED(RequestsInFlight, HttpStats.RequestsInFlight.load(), ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT_DEFINED(MaxRequestsInFlight, int32(HttpStats.MaxRequestsInFlight.load()), ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT_DEFINED(MaxTimeToWaitInQueue, int32(HttpStats.MaxTimeToWaitInQueue.load()), ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT_DEFINED(DownloadedMB, TotalDownloadedMB, ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT_DEFINED(BandwidthMbps, int32(HttpStats.BandwidthMbps.load()), ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT_DEFINED(DurationMsAvg, int32(HttpStats.HttpDurationMsAvg.load()), ECsvCustomStatOp::Set);
// keep ticking
return true;
}
void FHttpManager::FlushTick(float DeltaSeconds)
{
Tick(DeltaSeconds);
}
void FHttpManager::AddRequest(const FHttpRequestRef& Request)
{
{
FScopeLock ScopeLock(&RequestLock);
UE_CLOG(bFlushing, LogHttp, Warning, TEXT("Adding request %s to http manager while flushing"), *Request->GetURL());
Requests.Add(Request);
}
QUICK_SCOPE_CYCLE_COUNTER(STAT_FHttpManager_RequestAddedDelegate);
RequestAddedDelegate.ExecuteIfBound(Request);
}
void FHttpManager::RemoveRequest(const FHttpRequestRef& Request)
{
FScopeLock ScopeLock(&RequestLock);
Requests.Remove(Request);
}
void FHttpManager::AddThreadedRequest(const TSharedRef<FHttpRequestCommon, ESPMode::ThreadSafe>& Request)
{
check(Thread);
{
AddRequest(Request);
}
Thread->AddRequest(&Request.Get());
}
void FHttpManager::CancelThreadedRequest(const TSharedRef<FHttpRequestCommon, ESPMode::ThreadSafe>& Request)
{
check(Thread);
Thread->CancelRequest(&Request.Get());
}
bool FHttpManager::IsValidRequest(const IHttpRequest* RequestPtr) const
{
FScopeLock ScopeLock(&RequestLock);
bool bResult = false;
for (const FHttpRequestRef& Request : Requests)
{
if (&Request.Get() == RequestPtr)
{
bResult = true;
break;
}
}
return bResult;
}
void FHttpManager::SetRequestAddedDelegate(const FHttpManagerRequestAddedDelegate& Delegate)
{
RequestAddedDelegate = Delegate;
}
void FHttpManager::SetRequestCompletedDelegate(const FHttpManagerRequestCompletedDelegate& Delegate)
{
RequestCompletedDelegate = Delegate;
}
void FHttpManager::DumpRequests(FOutputDevice& Ar) const
{
FScopeLock ScopeLock(&RequestLock);
Ar.Logf(TEXT("------- (%d) Http Requests"), Requests.Num());
for (const FHttpRequestRef& Request : Requests)
{
Ar.Logf(TEXT(" verb=[%s] url=[%s] status=%s"),
*Request->GetVerb(), *Request->GetURL(), EHttpRequestStatus::ToString(Request->GetStatus()));
}
}
bool FHttpManager::SupportsDynamicProxy() const
{
return false;
}
void FHttpManager::BroadcastHttpRequestCompleted(const FHttpRequestRef& Request)
{
FHttpResponsePtr Response = Request->GetResponse();
if (Response.IsValid())
{
const int64 OldDuration = HttpStatsHistory.DurationMs[HttpStatsHistory.HistoryIndex];
const int64 NewDuration = FMath::RoundToInt64(Request->GetElapsedTime() * 1000.0f);
HttpStatsHistory.TotalDuration -= OldDuration;
HttpStatsHistory.TotalDuration += NewDuration;
HttpStatsHistory.DurationMs[HttpStatsHistory.HistoryIndex] = NewDuration;
const int64 SizeBytes = Response->GetContentLength();
HttpStats.TotalDownloadedBytes += SizeBytes;
HttpStatsHistory.TotalDownloadedBytes -= HttpStatsHistory.DownloadedBytes[HttpStatsHistory.HistoryIndex];
HttpStatsHistory.TotalDownloadedBytes += SizeBytes;
HttpStatsHistory.DownloadedBytes[HttpStatsHistory.HistoryIndex] = SizeBytes;
HttpStats.BandwidthMbps = ((HttpStatsHistory.TotalDownloadedBytes * 8) / (HttpStatsHistory.TotalDuration + 1) / 1000);
HttpStats.HttpDurationMsAvg = HttpStatsHistory.TotalDuration / FHttpStatsHistory::HttpHistoryCount;
// Increment index
HttpStatsHistory.HistoryIndex = (HttpStatsHistory.HistoryIndex + 1) % FHttpStatsHistory::HttpHistoryCount;
}
RequestCompletedDelegate.ExecuteIfBound(Request);
}
FHttpThreadBase* FHttpManager::GetThread()
{
return Thread;
}
void FHttpManager::RecordStatTimeToConnect(float Duration)
{
HttpStats.MaxTimeToConnect = FGenericPlatformMath::Max(Duration, HttpStats.MaxTimeToConnect.load());
}
void FHttpManager::RecordStatRequestsInFlight(uint32 RequestsInFlight)
{
HttpStats.RequestsInFlight = RequestsInFlight;
HttpStats.MaxRequestsInFlight = FGenericPlatformMath::Max(RequestsInFlight, HttpStats.MaxRequestsInFlight.load());
}
void FHttpManager::RecordStatRequestsInQueue(uint32 RequestsInQueue)
{
HttpStats.RequestsInQueue = RequestsInQueue;
HttpStats.MaxRequestsInQueue = FGenericPlatformMath::Max(RequestsInQueue, HttpStats.MaxRequestsInQueue.load());
}
void FHttpManager::RecordMaxTimeToWaitInQueue(float Duration)
{
HttpStats.MaxTimeToWaitInQueue = FGenericPlatformMath::Max(Duration, HttpStats.MaxTimeToWaitInQueue.load());
}
void FHttpManager::RecordPlatformStats(const FHttpStatsPlatform& PlatformStats)
{
HttpStats.PlatformStats = PlatformStats;
}
void FHttpManager::UpdateUrlPatternsToLogResponse(IConsoleVariable* CVar)
{
const FScopeLock CacheLock(&UrlPatternsToLogResponseCriticalSection);
const FString UrlPatternsToLogResponseStr = CVar->AsVariable()->GetString();
UrlPatternsToLogResponseStr.ParseIntoArray(UrlPatternsToLogResponse, TEXT(" "));
}
bool FHttpManager::ShouldLogResponse(FStringView Url) const
{
const FScopeLock CacheLock(&UrlPatternsToLogResponseCriticalSection);
for (const FString& UrlPatternToLogResponse : UrlPatternsToLogResponse)
{
if (Url.Contains(UrlPatternToLogResponse))
{
return true;
}
}
return false;
}
void FHttpManager::UpdateUrlPatternsToDisableFailedLog(IConsoleVariable* CVar)
{
const FScopeLock CacheLock(&UrlPatternsToDisableFailedLogCriticalSection);
const FString UrlPatternsToDisableFailedLogStr = CVar->AsVariable()->GetString();
UrlPatternsToDisableFailedLogStr.ParseIntoArray(UrlPatternsToDisableFailedLog, TEXT(" "));
}
bool FHttpManager::ShouldDisableFailedLog(FStringView Url) const
{
const FScopeLock CacheLock(&UrlPatternsToDisableFailedLogCriticalSection);
for (const FString& UrlPatternToDisableFailedLog : UrlPatternsToDisableFailedLog)
{
if (Url.Contains(UrlPatternToDisableFailedLog))
{
return true;
}
}
return false;
}
void FHttpManager::UpdateUrlPatternsToMockFailure(IConsoleVariable* CVar)
{
const FScopeLock CacheLock(&UrlPatternsToMockFailureCriticalSection);
const FString UrlPatternsToMockFailureStr = CVar->AsVariable()->GetString();
TArray<FString> UrlPatternsToMockFailureStrings;
UrlPatternsToMockFailureStr.ParseIntoArray(UrlPatternsToMockFailureStrings, TEXT(" "));
UrlPatternsToMockFailure.Empty();
for (const FString& UrlPatternToMockFailureString : UrlPatternsToMockFailureStrings)
{
TArray<FString> UrlPattern;
UrlPatternToMockFailureString.ParseIntoArray(UrlPattern, TEXT("->"));
if (UrlPattern.Num() == 2)
{
int32 ResponseCode = FCString::Atoi(*UrlPattern[1]);
UrlPatternsToMockFailure.Emplace(UrlPattern[0], ResponseCode);
}
}
}
TOptional<int32> FHttpManager::GetMockFailure(FStringView Url) const
{
TOptional<int32> Result;
const FScopeLock CacheLock(&UrlPatternsToMockFailureCriticalSection);
for (const TPair<FString, int32>& UrlPattern : UrlPatternsToMockFailure)
{
if (Url.Contains(UrlPattern.Key))
{
Result = UrlPattern.Value;
}
}
return Result;
}
void FHttpManager::MarkCurrentThreadCompletingRequest(bool bCompleting)
{
#if !UE_BUILD_SHIPPING
const FScopeLock CacheLock(&ThreadsCompletingRequestCriticalSection);
bool& ThreadMark = ThreadsCompletingRequest.FindOrAdd(FPlatformTLS::GetCurrentThreadId());
ThreadMark = bCompleting;
#endif
}
bool FHttpManager::IsCurrentThreadCompletingRequest() const
{
#if !UE_BUILD_SHIPPING
const FScopeLock CacheLock(&ThreadsCompletingRequestCriticalSection);
const bool* ThreadMark = ThreadsCompletingRequest.Find(FPlatformTLS::GetCurrentThreadId());
if (ThreadMark == nullptr)
{
return false;
}
return *ThreadMark;
#else
return false;
#endif
}