Files
UnrealEngineUWP/Engine/Source/Runtime/Online/HTTP/Private/GenericPlatform/HttpRequestCommon.cpp
lorry li 3139b7bf9c Log warning instead of check when http activity timer has been started.
[REVIEW] [at]michael.kirzinger [at]michael.atchison [at]rafa.lecina
#rb michael.atchison, Michael.Kirzinger

[CL 32193587 by lorry li in ue5-main branch]
2024-03-12 17:33:11 -04:00

494 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "GenericPlatform/HttpRequestCommon.h"
#include "GenericPlatform/HttpResponseCommon.h"
#include "HAL/Event.h"
#include "Http.h"
#include "HttpManager.h"
#include "Misc/CommandLine.h"
#include "Stats/Stats.h"
FHttpRequestCommon::FHttpRequestCommon()
: RequestStartTimeAbsoluteSeconds(FPlatformTime::Seconds())
, ActivityTimeoutAt(0.0)
{
}
FString FHttpRequestCommon::GetURLParameter(const FString& ParameterName) const
{
FString ReturnValue;
if (TOptional<FString> OptionalParameterValue = FGenericPlatformHttp::GetUrlParameter(GetURL(), ParameterName))
{
ReturnValue = MoveTemp(OptionalParameterValue.GetValue());
}
return ReturnValue;
}
EHttpRequestStatus::Type FHttpRequestCommon::GetStatus() const
{
return CompletionStatus;
}
const FString& FHttpRequestCommon::GetEffectiveURL() const
{
return EffectiveURL;
}
EHttpFailureReason FHttpRequestCommon::GetFailureReason() const
{
return FailureReason;
}
bool FHttpRequestCommon::PreCheck() const
{
// Disabled http request processing
if (!FHttpModule::Get().IsHttpEnabled())
{
UE_LOG(LogHttp, Verbose, TEXT("Http disabled. Skipping request. url=%s"), *GetURL());
return false;
}
// Prevent overlapped requests using the same instance
if (CompletionStatus == EHttpRequestStatus::Processing)
{
UE_LOG(LogHttp, Warning, TEXT("ProcessRequest failed. Still processing last request."));
return false;
}
// Nothing to do without a valid URL
if (GetURL().IsEmpty())
{
UE_LOG(LogHttp, Warning, TEXT("ProcessRequest failed. No URL was specified."));
return false;
}
if (GetVerb().IsEmpty())
{
UE_LOG(LogHttp, Warning, TEXT("ProcessRequest failed. No Verb was specified."));
return false;
}
if (!FHttpModule::Get().GetHttpManager().IsDomainAllowed(GetURL()))
{
UE_LOG(LogHttp, Warning, TEXT("ProcessRequest failed. URL '%s' is not using an allowed domain."), *GetURL());
return false;
}
if (bTimedOut)
{
UE_LOG(LogHttp, Warning, TEXT("ProcessRequest failed. Request with URL '%s' already timed out."), *GetURL());
return false;
}
return true;
}
bool FHttpRequestCommon::PreProcess()
{
ClearInCaseOfRetry();
if (!PreCheck() || !SetupRequest())
{
FinishRequestNotInHttpManager();
return false;
}
StartTotalTimeoutTimer();
UE_LOG(LogHttp, Verbose, TEXT("%p: Verb='%s' URL='%s'"), this, *GetVerb(), *GetURL());
return true;
}
void FHttpRequestCommon::PostProcess()
{
CleanupRequest();
}
void FHttpRequestCommon::ClearInCaseOfRetry()
{
bActivityTimedOut = false;
FailureReason = EHttpFailureReason::None;
bCanceled = false;
EffectiveURL = GetURL();
ResponseCommon.Reset();
}
void FHttpRequestCommon::FinishRequestNotInHttpManager()
{
if (IsInGameThread())
{
if (DelegateThreadPolicy == EHttpRequestDelegateThreadPolicy::CompleteOnGameThread)
{
FinishRequest();
}
else
{
FHttpModule::Get().GetHttpManager().AddHttpThreadTask([StrongThis = StaticCastSharedRef<FHttpRequestCommon>(AsShared())]()
{
StrongThis->FinishRequest();
});
}
}
else
{
if (DelegateThreadPolicy == EHttpRequestDelegateThreadPolicy::CompleteOnHttpThread)
{
FinishRequest();
}
else
{
FHttpModule::Get().GetHttpManager().AddGameThreadTask([StrongThis = StaticCastSharedRef<FHttpRequestCommon>(AsShared())]()
{
StrongThis->FinishRequest();
});
}
}
}
void FHttpRequestCommon::SetDelegateThreadPolicy(EHttpRequestDelegateThreadPolicy InDelegateThreadPolicy)
{
DelegateThreadPolicy = InDelegateThreadPolicy;
}
EHttpRequestDelegateThreadPolicy FHttpRequestCommon::GetDelegateThreadPolicy() const
{
return DelegateThreadPolicy;
}
void FHttpRequestCommon::HandleRequestSucceed(TSharedPtr<IHttpResponse> InResponse)
{
SetStatus(EHttpRequestStatus::Succeeded);
OnProcessRequestComplete().ExecuteIfBound(SharedThis(this), InResponse, true);
FHttpModule::Get().GetHttpManager().RecordStatTimeToConnect(ConnectTime);
}
void FHttpRequestCommon::SetStatus(EHttpRequestStatus::Type InCompletionStatus)
{
CompletionStatus = InCompletionStatus;
if (ResponseCommon)
{
ResponseCommon->SetRequestStatus(InCompletionStatus);
}
}
void FHttpRequestCommon::SetFailureReason(EHttpFailureReason InFailureReason)
{
UE_CLOG(FailureReason != EHttpFailureReason::None, LogHttp, Warning, TEXT("FailureReason had been set to %s, now setting to %s"), LexToString(FailureReason), LexToString(InFailureReason));
FailureReason = InFailureReason;
if (ResponseCommon)
{
ResponseCommon->SetRequestFailureReason(InFailureReason);
}
}
void FHttpRequestCommon::SetTimeout(float InTimeoutSecs)
{
TimeoutSecs = InTimeoutSecs;
}
void FHttpRequestCommon::ClearTimeout()
{
TimeoutSecs.Reset();
StopTotalTimeoutTimer();
}
TOptional<float> FHttpRequestCommon::GetTimeout() const
{
return TimeoutSecs;
}
float FHttpRequestCommon::GetTimeoutOrDefault() const
{
return GetTimeout().Get(FHttpModule::Get().GetHttpTotalTimeout());
}
const FHttpResponsePtr FHttpRequestCommon::GetResponse() const
{
return ResponseCommon;
}
void FHttpRequestCommon::CancelRequest()
{
bool bWasCanceled = bCanceled.exchange(true);
if (bWasCanceled)
{
return;
}
StopActivityTimeoutTimer();
StopPassingReceivedData();
UE_LOG(LogHttp, Verbose, TEXT("HTTP request canceled. URL=%s"), *GetURL());
FHttpModule::Get().GetHttpManager().AddHttpThreadTask([StrongThis = StaticCastSharedRef<FHttpRequestCommon>(AsShared())]()
{
// Run AbortRequest in HTTP thread to avoid potential concurrency issue
QUICK_SCOPE_CYCLE_COUNTER(STAT_FHttpRequestCommon_AbortRequest);
StrongThis->AbortRequest();
});
}
void FHttpRequestCommon::StartActivityTimeoutTimer()
{
const FScopeLock CacheLock(&HttpTaskTimerHandleCriticalSection);
if (bUsePlatformActivityTimeout)
{
return;
}
#if !UE_BUILD_SHIPPING
static const bool bNoTimeouts = FParse::Param(FCommandLine::Get(), TEXT("NoTimeouts"));
if (bNoTimeouts)
{
return;
}
#endif
if (bActivityTimedOut)
{
return;
}
float HttpActivityTimeout = FHttpModule::Get().GetHttpActivityTimeout();
if (HttpActivityTimeout == 0)
{
return;
}
StartActivityTimeoutTimerBy(HttpActivityTimeout);
ResetActivityTimeoutTimer(TEXTVIEW("Connected"));
}
void FHttpRequestCommon::StartActivityTimeoutTimerBy(double DelayToTrigger)
{
if (ActivityTimeoutHttpTaskTimerHandle != nullptr)
{
UE_LOG(LogHttp, Warning, TEXT("Request %p already started activity timeout timer"), this);
return;
}
TWeakPtr<IHttpRequest> RequestWeakPtr(AsShared());
ActivityTimeoutHttpTaskTimerHandle = FHttpModule::Get().GetHttpManager().AddHttpThreadTask([RequestWeakPtr]() {
if (TSharedPtr<IHttpRequest> RequestPtr = RequestWeakPtr.Pin())
{
TSharedPtr<FHttpRequestCommon> RequestCommonPtr = StaticCastSharedPtr<FHttpRequestCommon>(RequestPtr);
RequestCommonPtr->OnActivityTimeoutTimerTaskTrigger();
}
}, DelayToTrigger + 0.05);
}
void FHttpRequestCommon::OnActivityTimeoutTimerTaskTrigger()
{
const FScopeLock CacheLock(&HttpTaskTimerHandleCriticalSection);
ActivityTimeoutHttpTaskTimerHandle.Reset();
if (EHttpRequestStatus::IsFinished(GetStatus()))
{
UE_LOG(LogHttp, Warning, TEXT("Request %p had finished when activity timeout timer trigger at [%s]"), this, *FDateTime::Now().ToString(TEXT("%H:%M:%S:%s")));
return;
}
if (FPlatformTime::Seconds() < ActivityTimeoutAt)
{
// Check back later
UE_LOG(LogHttp, VeryVerbose, TEXT("Request %p check response timeout at [%s], will check again in %.5f seconds"), this, *FDateTime::Now().ToString(TEXT("%H:%M:%S:%s")), ActivityTimeoutAt - FPlatformTime::Seconds());
StartActivityTimeoutTimerBy(ActivityTimeoutAt - FPlatformTime::Seconds());
return;
}
QUICK_SCOPE_CYCLE_COUNTER(STAT_FHttpRequestCommon_AbortRequest);
bActivityTimedOut = true;
AbortRequest();
UE_LOG(LogHttp, Log, TEXT("Request [%s] timed out at [%s] because of no responding for %0.2f seconds"), *GetURL(), *FDateTime::Now().ToString(TEXT("%H:%M:%S:%s")), FHttpModule::Get().GetHttpActivityTimeout());
}
void FHttpRequestCommon::ResetActivityTimeoutTimer(FStringView Reason)
{
const FScopeLock CacheLock(&HttpTaskTimerHandleCriticalSection);
if (bUsePlatformActivityTimeout)
{
return;
}
if (!ActivityTimeoutHttpTaskTimerHandle)
{
return;
}
ActivityTimeoutAt = FPlatformTime::Seconds() + FHttpModule::Get().GetHttpActivityTimeout();
UE_LOG(LogHttp, VeryVerbose, TEXT("Request [%p] reset response timeout timer at %s: %s"), this, *FDateTime::Now().ToString(TEXT("%H:%M:%S:%s")), Reason.GetData());
}
void FHttpRequestCommon::StopActivityTimeoutTimer()
{
const FScopeLock CacheLock(&HttpTaskTimerHandleCriticalSection);
if (bUsePlatformActivityTimeout)
{
return;
}
if (!ActivityTimeoutHttpTaskTimerHandle)
{
return;
}
FHttpModule::Get().GetHttpManager().RemoveHttpThreadTask(ActivityTimeoutHttpTaskTimerHandle);
ActivityTimeoutHttpTaskTimerHandle.Reset();
}
void FHttpRequestCommon::StartTotalTimeoutTimer()
{
const FScopeLock CacheLock(&HttpTaskTimerHandleCriticalSection);
#if !UE_BUILD_SHIPPING
static const bool bNoTimeouts = FParse::Param(FCommandLine::Get(), TEXT("NoTimeouts"));
if (bNoTimeouts)
{
return;
}
#endif
float TimeoutOrDefault = GetTimeoutOrDefault();
if (TimeoutOrDefault == 0)
{
return;
}
if (bTimedOut)
{
return;
}
// Timeout include retries, so if it's already started before, check this to prevent from adding timer multiple times
if (TotalTimeoutHttpTaskTimerHandle)
{
return;
}
TWeakPtr<IHttpRequest> RequestWeakPtr(AsShared());
TotalTimeoutHttpTaskTimerHandle = FHttpModule::Get().GetHttpManager().AddHttpThreadTask([RequestWeakPtr]() {
if (TSharedPtr<IHttpRequest> RequestPtr = RequestWeakPtr.Pin())
{
TSharedPtr<FHttpRequestCommon> RequestCommonPtr = StaticCastSharedPtr<FHttpRequestCommon>(RequestPtr);
RequestCommonPtr->OnTotalTimeoutTimerTaskTrigger();
}
}, TimeoutOrDefault);
}
void FHttpRequestCommon::OnTotalTimeoutTimerTaskTrigger()
{
const FScopeLock CacheLock(&HttpTaskTimerHandleCriticalSection);
bTimedOut = true;
if (EHttpRequestStatus::IsFinished(GetStatus()))
{
return;
}
StopActivityTimeoutTimer();
QUICK_SCOPE_CYCLE_COUNTER(STAT_FHttpRequestCommon_AbortRequest);
UE_LOG(LogHttp, Warning, TEXT("HTTP request timed out after %0.2f seconds URL=%s"), GetTimeoutOrDefault(), *GetURL());
AbortRequest();
}
void FHttpRequestCommon::StopTotalTimeoutTimer()
{
const FScopeLock CacheLock(&HttpTaskTimerHandleCriticalSection);
if (TotalTimeoutHttpTaskTimerHandle)
{
FHttpModule::Get().GetHttpManager().RemoveHttpThreadTask(TotalTimeoutHttpTaskTimerHandle);
TotalTimeoutHttpTaskTimerHandle.Reset();
}
}
void FHttpRequestCommon::Shutdown()
{
FHttpRequestImpl::Shutdown();
StopPassingReceivedData();
StopActivityTimeoutTimer();
StopTotalTimeoutTimer();
}
void FHttpRequestCommon::ProcessRequestUntilComplete()
{
checkf(!OnProcessRequestComplete().IsBound(), TEXT("OnProcessRequestComplete is not supported for sync call"));
SetDelegateThreadPolicy(EHttpRequestDelegateThreadPolicy::CompleteOnHttpThread);
FEvent* Event = FPlatformProcess::GetSynchEventFromPool(true);
OnProcessRequestComplete().BindLambda([Event](FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, bool bSucceeded) {
Event->Trigger();
});
ProcessRequest();
Event->Wait();
FPlatformProcess::ReturnSynchEventToPool(Event);
}
void FHttpRequestCommon::TriggerStatusCodeReceivedDelegate(int32 StatusCode)
{
if (DelegateThreadPolicy == EHttpRequestDelegateThreadPolicy::CompleteOnHttpThread)
{
OnStatusCodeReceived().ExecuteIfBound(SharedThis(this), StatusCode);
}
else if (OnStatusCodeReceived().IsBound())
{
FHttpModule::Get().GetHttpManager().AddGameThreadTask([StrongThis = AsShared(), StatusCode]()
{
StrongThis->OnStatusCodeReceived().ExecuteIfBound(StrongThis, StatusCode);
});
}
}
void FHttpRequestCommon::SetEffectiveURL(const FString& InEffectiveURL)
{
EffectiveURL = InEffectiveURL;
if (ResponseCommon)
{
ResponseCommon->SetEffectiveURL(EffectiveURL);
}
}
bool FHttpRequestCommon::SetResponseBodyReceiveStream(TSharedRef<FArchive> Stream)
{
const FScopeLock StreamLock(&ResponseBodyReceiveStreamCriticalSection);
ResponseBodyReceiveStream = Stream;
bInitializedWithValidStream = true;
return true;
}
bool FHttpRequestCommon::PassReceivedDataToStream(void* Ptr, int64 Length)
{
const FScopeLock StreamLock(&ResponseBodyReceiveStreamCriticalSection);
if (!ResponseBodyReceiveStream)
{
return false;
}
ResponseBodyReceiveStream->Serialize(Ptr, Length);
return !ResponseBodyReceiveStream->GetError();
}
void FHttpRequestCommon::StopPassingReceivedData()
{
const FScopeLock StreamLock(&ResponseBodyReceiveStreamCriticalSection);
ResponseBodyReceiveStream = nullptr;
}