You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
468 lines
12 KiB
C++
468 lines
12 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#if WITH_WINHTTP
|
|
|
|
#include "WinHttp/WinHttpHttpRequest.h"
|
|
#include "WinHttp/WinHttpHttpManager.h"
|
|
#include "WinHttp/WinHttpHttpResponse.h"
|
|
#include "WinHttp/Support/WinHttpConnectionHttp.h"
|
|
#include "GenericPlatform/HttpRequestPayload.h"
|
|
#include "Http.h"
|
|
#include "HttpModule.h"
|
|
|
|
#include "HAL/PlatformTime.h"
|
|
#include "Containers/StringView.h"
|
|
#include "HAL/FileManager.h"
|
|
|
|
FWinHttpHttpRequest::FWinHttpHttpRequest()
|
|
{
|
|
|
|
}
|
|
|
|
FWinHttpHttpRequest::~FWinHttpHttpRequest()
|
|
{
|
|
// Make sure we either didn't start, or we finished before destructing
|
|
check(!RequestStartTimeSeconds.IsSet() || RequestFinishTimeSeconds.IsSet());
|
|
}
|
|
|
|
FString FWinHttpHttpRequest::GetURL() const
|
|
{
|
|
return RequestData.Url;
|
|
}
|
|
|
|
FString FWinHttpHttpRequest::GetURLParameter(const FString& ParameterName) const
|
|
{
|
|
FString ReturnValue;
|
|
|
|
if (TOptional<FString> OptionalParameterValue = FGenericPlatformHttp::GetUrlParameter(RequestData.Url, ParameterName))
|
|
{
|
|
ReturnValue = MoveTemp(OptionalParameterValue.GetValue());
|
|
}
|
|
|
|
return ReturnValue;
|
|
}
|
|
|
|
FString FWinHttpHttpRequest::GetHeader(const FString& HeaderName) const
|
|
{
|
|
const FString* const ExistingHeader = RequestData.Headers.Find(HeaderName);
|
|
return ExistingHeader ? *ExistingHeader : FString();
|
|
}
|
|
|
|
TArray<FString> FWinHttpHttpRequest::GetAllHeaders() const
|
|
{
|
|
TArray<FString> AllHeaders;
|
|
|
|
for (const TPair<FString, FString>& Header : RequestData.Headers)
|
|
{
|
|
AllHeaders.Add(FString::Printf(TEXT("%s: %s"), *Header.Key, *Header.Value));
|
|
}
|
|
|
|
return AllHeaders;
|
|
}
|
|
|
|
FString FWinHttpHttpRequest::GetContentType() const
|
|
{
|
|
return GetHeader(TEXT("Content-Type"));
|
|
}
|
|
|
|
int32 FWinHttpHttpRequest::GetContentLength() const
|
|
{
|
|
return RequestData.Payload.IsValid() ? RequestData.Payload->GetContentLength() : 0;
|
|
}
|
|
|
|
const TArray<uint8>& FWinHttpHttpRequest::GetContent() const
|
|
{
|
|
static const TArray<uint8> EmptyContent;
|
|
return RequestData.Payload.IsValid() ? RequestData.Payload->GetContent() : EmptyContent;
|
|
}
|
|
|
|
FString FWinHttpHttpRequest::GetVerb() const
|
|
{
|
|
return RequestData.Verb;
|
|
}
|
|
|
|
void FWinHttpHttpRequest::SetVerb(const FString& InVerb)
|
|
{
|
|
if (State == EHttpRequestStatus::Processing)
|
|
{
|
|
UE_LOG(LogHttp, Warning, TEXT("Attempted to set verb on a request that is inflight"));
|
|
return;
|
|
}
|
|
|
|
RequestData.Verb = InVerb.ToUpper();
|
|
}
|
|
|
|
void FWinHttpHttpRequest::SetURL(const FString& InURL)
|
|
{
|
|
if (State == EHttpRequestStatus::Processing)
|
|
{
|
|
UE_LOG(LogHttp, Warning, TEXT("Attempted to set URL on a request that is inflight"));
|
|
return;
|
|
}
|
|
|
|
RequestData.Url = InURL;
|
|
}
|
|
|
|
void FWinHttpHttpRequest::SetContent(const TArray<uint8>& ContentPayload)
|
|
{
|
|
SetContent(CopyTemp(ContentPayload));
|
|
}
|
|
|
|
void FWinHttpHttpRequest::SetContent(TArray<uint8>&& ContentPayload)
|
|
{
|
|
if (State == EHttpRequestStatus::Processing)
|
|
{
|
|
UE_LOG(LogHttp, Warning, TEXT("Attempted to set content on a request that is inflight"));
|
|
return;
|
|
}
|
|
|
|
RequestData.Payload = MakeShared<FRequestPayloadInMemory, ESPMode::ThreadSafe>(MoveTemp(ContentPayload));
|
|
}
|
|
|
|
void FWinHttpHttpRequest::SetContentAsString(const FString& ContentString)
|
|
{
|
|
if (State == EHttpRequestStatus::Processing)
|
|
{
|
|
UE_LOG(LogHttp, Warning, TEXT("Attempted to set content on a request that is inflight"));
|
|
return;
|
|
}
|
|
|
|
const FTCHARToUTF8 Converter(*ContentString, ContentString.Len());
|
|
|
|
TArray<uint8> Content;
|
|
Content.Append(reinterpret_cast<const uint8*>(Converter.Get()), Converter.Length());
|
|
|
|
RequestData.Payload = MakeShared<FRequestPayloadInMemory, ESPMode::ThreadSafe>(MoveTemp(Content));
|
|
}
|
|
|
|
bool FWinHttpHttpRequest::SetContentAsStreamedFile(const FString& Filename)
|
|
{
|
|
if (State == EHttpRequestStatus::Processing)
|
|
{
|
|
UE_LOG(LogHttp, Warning, TEXT("Attempted to set content on a request that is inflight"));
|
|
return false;
|
|
}
|
|
|
|
if (FArchive* File = IFileManager::Get().CreateFileReader(*Filename))
|
|
{
|
|
RequestData.Payload = MakeShared<FRequestPayloadInFileStream, ESPMode::ThreadSafe>(MakeShareable(File));
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogHttp, Warning, TEXT("Failed to open '%s' for reading"), *Filename);
|
|
RequestData.Payload.Reset();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool FWinHttpHttpRequest::SetContentFromStream(TSharedRef<FArchive, ESPMode::ThreadSafe> Stream)
|
|
{
|
|
if (State == EHttpRequestStatus::Processing)
|
|
{
|
|
UE_LOG(LogHttp, Warning, TEXT("Attempted to set content on a request that is inflight"));
|
|
return false;
|
|
}
|
|
|
|
RequestData.Payload = MakeShared<FRequestPayloadInFileStream, ESPMode::ThreadSafe>(Stream);
|
|
return true;
|
|
}
|
|
|
|
void FWinHttpHttpRequest::SetHeader(const FString& HeaderName, const FString& HeaderValue)
|
|
{
|
|
if (State == EHttpRequestStatus::Processing)
|
|
{
|
|
UE_LOG(LogHttp, Warning, TEXT("Attempted to set a header on a request that is inflight"));
|
|
return;
|
|
}
|
|
|
|
if (HeaderName.IsEmpty())
|
|
{
|
|
UE_LOG(LogHttp, Warning, TEXT("Attempted to set an empty header name"));
|
|
return;
|
|
}
|
|
|
|
RequestData.Headers.Add(HeaderName, HeaderValue);
|
|
}
|
|
|
|
void FWinHttpHttpRequest::AppendToHeader(const FString& HeaderName, const FString& AdditionalHeaderValue)
|
|
{
|
|
if (State == EHttpRequestStatus::Processing)
|
|
{
|
|
UE_LOG(LogHttp, Warning, TEXT("Attempted to append a header on a request that is inflight"));
|
|
return;
|
|
}
|
|
|
|
if (HeaderName.IsEmpty())
|
|
{
|
|
UE_LOG(LogHttp, Warning, TEXT("Attempted to append an empty header name"));
|
|
return;
|
|
}
|
|
|
|
if (const FString* ExistingHeaderValue = RequestData.Headers.Find(HeaderName))
|
|
{
|
|
RequestData.Headers.Add(HeaderName, FString::Printf(TEXT("%s, %s"), **ExistingHeaderValue, *AdditionalHeaderValue));
|
|
}
|
|
else
|
|
{
|
|
RequestData.Headers.Add(HeaderName, AdditionalHeaderValue);
|
|
}
|
|
}
|
|
|
|
bool FWinHttpHttpRequest::ProcessRequest()
|
|
{
|
|
UE_LOG(LogHttp, Verbose, TEXT("FWinHttpHttpRequest::ProcessRequest() FWinHttpHttpRequest=[%p]"), this);
|
|
|
|
if (State == EHttpRequestStatus::Processing)
|
|
{
|
|
UE_LOG(LogHttp, Warning, TEXT("Attempted to start request while it is still in inflight"));
|
|
return false;
|
|
}
|
|
|
|
FWinHttpHttpManager* HttpManager = FWinHttpHttpManager::GetManager();
|
|
if (!HttpManager)
|
|
{
|
|
UE_LOG(LogHttp, Warning, TEXT("Attempted to start request with no HTTP manager"));
|
|
return false;
|
|
}
|
|
|
|
Response.Reset();
|
|
TotalBytesSent = 0;
|
|
TotalBytesReceived = 0;
|
|
RequestStartTimeSeconds.Reset();
|
|
RequestFinishTimeSeconds.Reset();
|
|
bRequestCancelled = false;
|
|
|
|
State = EHttpRequestStatus::Processing;
|
|
|
|
TSharedRef<FWinHttpHttpRequest, ESPMode::ThreadSafe> LocalStrongThis = StaticCastSharedRef<FWinHttpHttpRequest>(AsShared());
|
|
HttpManager->QuerySessionForUrl(RequestData.Url, FWinHttpQuerySessionComplete::CreateLambda([LocalWeakThis = TWeakPtr<FWinHttpHttpRequest, ESPMode::ThreadSafe>(LocalStrongThis)](FWinHttpSession* SessionPtr)
|
|
{
|
|
// Validate state
|
|
TSharedPtr<FWinHttpHttpRequest, ESPMode::ThreadSafe> StrongThis = LocalWeakThis.Pin();
|
|
if (!StrongThis.IsValid())
|
|
{
|
|
// We went away
|
|
return;
|
|
}
|
|
if (StrongThis->bRequestCancelled)
|
|
{
|
|
// We were cancelled
|
|
return;
|
|
}
|
|
if (!SessionPtr)
|
|
{
|
|
// Could not create session
|
|
UE_LOG(LogHttp, Warning, TEXT("Unable to create WinHttp Session, failing request"));
|
|
StrongThis->FinishRequest();
|
|
return;
|
|
}
|
|
|
|
FWinHttpHttpRequestData& RequestData = StrongThis->RequestData;
|
|
|
|
// Create connection object
|
|
TSharedPtr<FWinHttpConnectionHttp, ESPMode::ThreadSafe> Connection = FWinHttpConnectionHttp::CreateHttpConnection(*SessionPtr, RequestData.Verb, RequestData.Url, RequestData.Headers, RequestData.Payload);
|
|
if (!Connection.IsValid())
|
|
{
|
|
UE_LOG(LogHttp, Warning, TEXT("Unable to create WinHttp Connection, failing request"));
|
|
StrongThis->FinishRequest();
|
|
return;
|
|
}
|
|
|
|
// Bind listeners
|
|
TSharedRef<FWinHttpHttpRequest, ESPMode::ThreadSafe> StrongThisRef = StrongThis.ToSharedRef();
|
|
Connection->SetDataTransferredHandler(FWinHttpConnectionHttpOnDataTransferred::CreateThreadSafeSP(StrongThisRef, &FWinHttpHttpRequest::HandleDataTransferred));
|
|
Connection->SetHeaderReceivedHandler(FWinHttpConnectionHttpOnHeaderReceived::CreateThreadSafeSP(StrongThisRef, &FWinHttpHttpRequest::HandleHeaderReceived));
|
|
Connection->SetRequestCompletedHandler(FWinHttpConnectionHttpOnRequestComplete::CreateThreadSafeSP(StrongThisRef, &FWinHttpHttpRequest::HandleRequestComplete));
|
|
|
|
// Start request!
|
|
StrongThisRef->RequestStartTimeSeconds = FPlatformTime::Seconds();
|
|
if (!Connection->StartRequest())
|
|
{
|
|
UE_LOG(LogHttp, Warning, TEXT("Unable to start WinHttp Connection, failing request"));
|
|
StrongThisRef->FinishRequest();
|
|
return;
|
|
}
|
|
|
|
// Save object
|
|
StrongThisRef->Connection = MoveTemp(Connection);
|
|
}));
|
|
|
|
// Store our request so it doesn't die if the requester doesn't store it (common use case)
|
|
FHttpModule::Get().GetHttpManager().AddRequest(LocalStrongThis);
|
|
return true;
|
|
}
|
|
|
|
void FWinHttpHttpRequest::CancelRequest()
|
|
{
|
|
UE_LOG(LogHttp, Log, TEXT("FWinHttpHttpRequest::CancelRequest() FWinHttpHttpRequest=[%p]"), this);
|
|
|
|
if (EHttpRequestStatus::IsFinished(State))
|
|
{
|
|
UE_LOG(LogHttp, Warning, TEXT("Attempted to cancel a request that was already finished"));
|
|
return;
|
|
}
|
|
if (bRequestCancelled)
|
|
{
|
|
UE_LOG(LogHttp, Warning, TEXT("Attempted to cancel a request that was already cancelled"));
|
|
return;
|
|
}
|
|
|
|
bRequestCancelled = true;
|
|
|
|
|
|
// FinishRequest will cleanup connection
|
|
if (!IsInGameThread())
|
|
{
|
|
// Always finish on the game thread
|
|
FHttpModule::Get().GetHttpManager().AddGameThreadTask([StrongThis = StaticCastSharedRef<FWinHttpHttpRequest>(AsShared())]()
|
|
{
|
|
StrongThis->FinishRequest();
|
|
});
|
|
}
|
|
else
|
|
{
|
|
FinishRequest();
|
|
}
|
|
}
|
|
|
|
EHttpRequestStatus::Type FWinHttpHttpRequest::GetStatus() const
|
|
{
|
|
return State;
|
|
}
|
|
|
|
const FHttpResponsePtr FWinHttpHttpRequest::GetResponse() const
|
|
{
|
|
return Response;
|
|
}
|
|
|
|
void FWinHttpHttpRequest::Tick(float DeltaSeconds)
|
|
{
|
|
if (Connection.IsValid())
|
|
{
|
|
Connection->PumpMessages();
|
|
// Connection is not guaranteed to be valid anymore here, be sure to check again if it gets used again below
|
|
}
|
|
}
|
|
|
|
float FWinHttpHttpRequest::GetElapsedTime() const
|
|
{
|
|
if (!RequestStartTimeSeconds.IsSet())
|
|
{
|
|
// Request hasn't started
|
|
return 0.0f;
|
|
}
|
|
|
|
if (RequestFinishTimeSeconds.IsSet())
|
|
{
|
|
// Request finished
|
|
return RequestFinishTimeSeconds.GetValue() - RequestStartTimeSeconds.GetValue();
|
|
}
|
|
|
|
// Request still in progress
|
|
return FPlatformTime::Seconds() - RequestStartTimeSeconds.GetValue();
|
|
}
|
|
|
|
void FWinHttpHttpRequest::HandleDataTransferred(int32 BytesSent, int32 BytesReceived)
|
|
{
|
|
check(IsInGameThread());
|
|
|
|
if (BytesSent > 0 || BytesReceived > 0)
|
|
{
|
|
if (BytesReceived > 0)
|
|
{
|
|
UpdateResponseBody();
|
|
}
|
|
TotalBytesSent += BytesSent;
|
|
TotalBytesReceived += BytesReceived;
|
|
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> KeepAlive = AsShared();
|
|
OnRequestProgress().ExecuteIfBound(AsShared(), TotalBytesSent, TotalBytesReceived);
|
|
}
|
|
}
|
|
|
|
void FWinHttpHttpRequest::HandleHeaderReceived(const FString& HeaderKey, const FString& HeaderValue)
|
|
{
|
|
check(IsInGameThread());
|
|
|
|
if (Response.IsValid())
|
|
{
|
|
Response->AppendHeader(HeaderKey, HeaderValue);
|
|
}
|
|
else if (Connection.IsValid())
|
|
{
|
|
Response = MakeShared<FWinHttpHttpResponse, ESPMode::ThreadSafe>(RequestData.Url, Connection->GetResponseCode(), CopyTemp(Connection->GetHeadersReceived()), TArray<uint8>());
|
|
}
|
|
|
|
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> KeepAlive = AsShared();
|
|
OnHeaderReceived().ExecuteIfBound(AsShared(), HeaderKey, HeaderValue);
|
|
}
|
|
|
|
void FWinHttpHttpRequest::HandleRequestComplete(EHttpRequestStatus::Type CompletionStatus)
|
|
{
|
|
check(IsInGameThread());
|
|
check(EHttpRequestStatus::IsFinished(CompletionStatus));
|
|
|
|
State = CompletionStatus;
|
|
|
|
if (CompletionStatus == EHttpRequestStatus::Succeeded)
|
|
{
|
|
UpdateResponseBody(true);
|
|
}
|
|
|
|
FinishRequest();
|
|
}
|
|
|
|
void FWinHttpHttpRequest::UpdateResponseBody(bool bForceResponseExist)
|
|
{
|
|
if (Connection.IsValid())
|
|
{
|
|
TArray<uint8> NewChunk(MoveTemp(Connection->GetLastChunk()));
|
|
if (NewChunk.Num() > 0 || bForceResponseExist)
|
|
{
|
|
if (Response.IsValid())
|
|
{
|
|
if (NewChunk.Num() > 0)
|
|
{
|
|
Response->AppendPayload(NewChunk);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Response = MakeShared<FWinHttpHttpResponse, ESPMode::ThreadSafe>(RequestData.Url, Connection->GetResponseCode(), CopyTemp(Connection->GetHeadersReceived()), MoveTemp(NewChunk));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FWinHttpHttpRequest::FinishRequest()
|
|
{
|
|
if (RequestFinishTimeSeconds.IsSet())
|
|
{
|
|
// Already finished
|
|
return;
|
|
}
|
|
RequestFinishTimeSeconds = FPlatformTime::Seconds();
|
|
|
|
// Set our final state if it's not set yet
|
|
if (!EHttpRequestStatus::IsFinished(State))
|
|
{
|
|
State = EHttpRequestStatus::Failed;
|
|
}
|
|
|
|
// Shutdown our connection
|
|
if (Connection.IsValid())
|
|
{
|
|
if (!Connection->IsComplete())
|
|
{
|
|
Connection->CancelRequest();
|
|
}
|
|
Connection.Reset();
|
|
}
|
|
|
|
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> KeepAlive = AsShared();
|
|
FHttpModule::Get().GetHttpManager().RemoveRequest(KeepAlive);
|
|
OnProcessRequestComplete().ExecuteIfBound(KeepAlive, Response, Response.IsValid());
|
|
}
|
|
|
|
#endif // WITH_WINHTTP
|