Goran Lazarevski 669cf5383b Lucky World source code
2025-03-18 19:25:25 +01:00

644 lines
16 KiB
C++

// Copyright 2014-2019 Vladimir Alyamkin. All Rights Reserved.
#include "VaRestRequestJSON.h"
#include "VaRestDefines.h"
#include "VaRestJsonObject.h"
#include "VaRestJsonValue.h"
#include "VaRestLibrary.h"
#include "VaRestSettings.h"
#include "Engine/Engine.h"
#include "Engine/LatentActionManager.h"
#include "Engine/World.h"
#include "Interfaces/IHttpResponse.h"
#include "Serialization/JsonSerializer.h"
#include "Serialization/JsonWriter.h"
FString UVaRestRequestJSON::DeprecatedResponseString(TEXT("DEPRECATED: Please use GetResponseContentAsString() instead"));
template <class T>
void FVaRestLatentAction<T>::Cancel()
{
UObject* Obj = Request.Get();
if (Obj != nullptr)
{
((UVaRestRequestJSON*)Obj)->Cancel();
}
}
UVaRestRequestJSON::UVaRestRequestJSON(const class FObjectInitializer& PCIP)
: Super(PCIP)
, BinaryContentType(TEXT("application/octet-stream"))
{
ContinueAction = nullptr;
RequestVerb = EVaRestRequestVerb::GET;
RequestContentType = EVaRestRequestContentType::x_www_form_urlencoded_url;
ResetData();
}
void UVaRestRequestJSON::SetVerb(EVaRestRequestVerb Verb)
{
RequestVerb = Verb;
}
void UVaRestRequestJSON::SetCustomVerb(FString Verb)
{
CustomVerb = Verb;
}
void UVaRestRequestJSON::SetContentType(EVaRestRequestContentType ContentType)
{
RequestContentType = ContentType;
}
void UVaRestRequestJSON::SetBinaryContentType(const FString& ContentType)
{
BinaryContentType = ContentType;
}
void UVaRestRequestJSON::SetBinaryRequestContent(const TArray<uint8>& Bytes)
{
RequestBytes = Bytes;
}
void UVaRestRequestJSON::SetStringRequestContent(const FString& Content)
{
StringRequestContent = Content;
}
void UVaRestRequestJSON::SetHeader(const FString& HeaderName, const FString& HeaderValue)
{
RequestHeaders.Add(HeaderName, HeaderValue);
}
//////////////////////////////////////////////////////////////////////////
// Destruction and reset
void UVaRestRequestJSON::ResetData()
{
ResetRequestData();
ResetResponseData();
}
void UVaRestRequestJSON::ResetRequestData()
{
if (RequestJsonObj != nullptr)
{
RequestJsonObj->Reset();
}
else
{
RequestJsonObj = NewObject<UVaRestJsonObject>();
}
// See issue #90
// HttpRequest = FHttpModule::Get().CreateRequest();
RequestBytes.Empty();
StringRequestContent.Empty();
}
void UVaRestRequestJSON::ResetResponseData()
{
if (ResponseJsonObj != nullptr)
{
ResponseJsonObj->Reset();
}
else
{
ResponseJsonObj = NewObject<UVaRestJsonObject>();
}
if (ResponseJsonValue != nullptr)
{
ResponseJsonValue->Reset();
}
else
{
ResponseJsonValue = NewObject<UVaRestJsonValue>();
}
ResponseHeaders.Empty();
ResponseCode = -1;
ResponseSize = 0;
bIsValidJsonResponse = false;
// #127 Reset string to deprecated state
ResponseContent = DeprecatedResponseString;
ResponseBytes.Empty();
ResponseContentLength = 0;
}
void UVaRestRequestJSON::Cancel()
{
ContinueAction = nullptr;
ResetResponseData();
}
//////////////////////////////////////////////////////////////////////////
// JSON data accessors
UVaRestJsonObject* UVaRestRequestJSON::GetRequestObject() const
{
check(RequestJsonObj);
return RequestJsonObj;
}
void UVaRestRequestJSON::SetRequestObject(UVaRestJsonObject* JsonObject)
{
if (JsonObject == nullptr)
{
UE_LOG(LogVaRest, Error, TEXT("%s: Provided JsonObject is nullptr"), *VA_FUNC_LINE);
return;
}
RequestJsonObj = JsonObject;
}
UVaRestJsonObject* UVaRestRequestJSON::GetResponseObject() const
{
check(ResponseJsonObj);
return ResponseJsonObj;
}
void UVaRestRequestJSON::SetResponseObject(UVaRestJsonObject* JsonObject)
{
if (JsonObject == nullptr)
{
UE_LOG(LogVaRest, Error, TEXT("%s: Provided JsonObject is nullptr"), *VA_FUNC_LINE);
return;
}
ResponseJsonObj = JsonObject;
}
UVaRestJsonValue* UVaRestRequestJSON::GetResponseValue() const
{
check(ResponseJsonValue);
return ResponseJsonValue;
}
///////////////////////////////////////////////////////////////////////////
// Response data access
FString UVaRestRequestJSON::GetURL() const
{
return HttpRequest->GetURL();
}
EVaRestRequestVerb UVaRestRequestJSON::GetVerb() const
{
return RequestVerb;
}
EVaRestRequestStatus UVaRestRequestJSON::GetStatus() const
{
return EVaRestRequestStatus((uint8)HttpRequest->GetStatus());
}
int32 UVaRestRequestJSON::GetResponseCode() const
{
return ResponseCode;
}
FString UVaRestRequestJSON::GetResponseHeader(const FString& HeaderName)
{
FString Result;
FString* Header = ResponseHeaders.Find(HeaderName);
if (Header != nullptr)
{
Result = *Header;
}
return Result;
}
TArray<FString> UVaRestRequestJSON::GetAllResponseHeaders() const
{
TArray<FString> Result;
for (TMap<FString, FString>::TConstIterator It(ResponseHeaders); It; ++It)
{
Result.Add(It.Key() + TEXT(": ") + It.Value());
}
return Result;
}
int32 UVaRestRequestJSON::GetResponseContentLength() const
{
return ResponseContentLength;
}
const TArray<uint8>& UVaRestRequestJSON::GetResponseContent() const
{
return ResponseBytes;
}
//////////////////////////////////////////////////////////////////////////
// URL processing
void UVaRestRequestJSON::SetURL(const FString& Url)
{
// Be sure to trim URL because it can break links on iOS
FString TrimmedUrl = Url;
TrimmedUrl.TrimStartInline();
TrimmedUrl.TrimEndInline();
HttpRequest->SetURL(TrimmedUrl);
}
void UVaRestRequestJSON::ProcessURL(const FString& Url)
{
SetURL(Url);
ProcessRequest();
}
void UVaRestRequestJSON::ApplyURL(const FString& Url, UVaRestJsonObject*& Result, UObject* WorldContextObject, FLatentActionInfo LatentInfo)
{
// Be sure to trim URL because it can break links on iOS
FString TrimmedUrl = Url;
TrimmedUrl.TrimStartInline();
TrimmedUrl.TrimEndInline();
HttpRequest->SetURL(TrimmedUrl);
// Prepare latent action
if (UWorld* World = GEngine->GetWorldFromContextObjectChecked(WorldContextObject))
{
FLatentActionManager& LatentActionManager = World->GetLatentActionManager();
FVaRestLatentAction<UVaRestJsonObject*>* Kont = LatentActionManager.FindExistingAction<FVaRestLatentAction<UVaRestJsonObject*>>(LatentInfo.CallbackTarget, LatentInfo.UUID);
if (Kont != nullptr)
{
Kont->Cancel();
LatentActionManager.RemoveActionsForObject(LatentInfo.CallbackTarget);
}
LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, ContinueAction = new FVaRestLatentAction<UVaRestJsonObject*>(this, Result, LatentInfo));
}
ProcessRequest();
}
void UVaRestRequestJSON::ExecuteProcessRequest()
{
if (HttpRequest->GetURL().Len() == 0)
{
UE_LOG(LogVaRest, Error, TEXT("Request execution attempt with empty URL"));
return;
}
ProcessRequest();
}
void UVaRestRequestJSON::ProcessRequest()
{
// Force add to root once request is launched
AddToRoot();
// Set verb
switch (RequestVerb)
{
case EVaRestRequestVerb::GET:
HttpRequest->SetVerb(TEXT("GET"));
break;
case EVaRestRequestVerb::POST:
HttpRequest->SetVerb(TEXT("POST"));
break;
case EVaRestRequestVerb::PUT:
HttpRequest->SetVerb(TEXT("PUT"));
break;
case EVaRestRequestVerb::DEL:
HttpRequest->SetVerb(TEXT("DELETE"));
break;
case EVaRestRequestVerb::CUSTOM:
HttpRequest->SetVerb(CustomVerb);
break;
default:
break;
}
// Set content-type
switch (RequestContentType)
{
case EVaRestRequestContentType::x_www_form_urlencoded_url:
{
HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/x-www-form-urlencoded"));
FString UrlParams = "";
uint16 ParamIdx = 0;
// Loop through all the values and prepare additional url part
for (auto RequestIt = RequestJsonObj->GetRootObject()->Values.CreateIterator(); RequestIt; ++RequestIt)
{
FString Key = RequestIt.Key();
FString Value = RequestIt.Value().Get()->AsString();
if (!Key.IsEmpty() && !Value.IsEmpty())
{
UrlParams += ParamIdx == 0 ? "?" : "&";
UrlParams += UVaRestLibrary::PercentEncode(Key) + "=" + UVaRestLibrary::PercentEncode(Value);
}
ParamIdx++;
}
// Apply params
HttpRequest->SetURL(HttpRequest->GetURL() + UrlParams);
// Add optional string content
if (!StringRequestContent.IsEmpty())
{
HttpRequest->SetContentAsString(StringRequestContent);
}
// Check extended log to avoid security vulnerability (#133)
if (UVaRestLibrary::GetVaRestSettings()->bExtendedLog)
{
UE_LOG(LogVaRest, Log, TEXT("%s: Request (urlencoded): %s %s %s %s"), *VA_FUNC_LINE, *HttpRequest->GetVerb(), *HttpRequest->GetURL(), *UrlParams, *StringRequestContent);
}
else
{
UE_LOG(LogVaRest, Log, TEXT("%s: Request (urlencoded): %s %s (check bExtendedLog for additional data)"), *VA_FUNC_LINE, *HttpRequest->GetVerb(), *HttpRequest->GetURL());
}
break;
}
case EVaRestRequestContentType::x_www_form_urlencoded_body:
{
HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/x-www-form-urlencoded"));
FString UrlParams = "";
uint16 ParamIdx = 0;
// Add optional string content
if (!StringRequestContent.IsEmpty())
{
UrlParams = StringRequestContent;
}
else
{
// Loop through all the values and prepare additional url part
for (auto RequestIt = RequestJsonObj->GetRootObject()->Values.CreateIterator(); RequestIt; ++RequestIt)
{
FString Key = RequestIt.Key();
FString Value = RequestIt.Value().Get()->AsString();
if (!Key.IsEmpty() && !Value.IsEmpty())
{
UrlParams += ParamIdx == 0 ? "" : "&";
UrlParams += UVaRestLibrary::PercentEncode(Key) + "=" + UVaRestLibrary::PercentEncode(Value);
}
ParamIdx++;
}
}
// Apply params
HttpRequest->SetContentAsString(UrlParams);
// Check extended log to avoid security vulnerability (#133)
if (UVaRestLibrary::GetVaRestSettings()->bExtendedLog)
{
UE_LOG(LogVaRest, Log, TEXT("%s: Request (url body): %s %s %s"), *VA_FUNC_LINE, *HttpRequest->GetVerb(), *HttpRequest->GetURL(), *UrlParams);
}
else
{
UE_LOG(LogVaRest, Log, TEXT("%s: Request (url body): %s %s (check bExtendedLog for additional data)"), *VA_FUNC_LINE, *HttpRequest->GetVerb(), *HttpRequest->GetURL());
}
break;
}
case EVaRestRequestContentType::binary:
{
HttpRequest->SetHeader(TEXT("Content-Type"), BinaryContentType);
HttpRequest->SetContent(RequestBytes);
UE_LOG(LogVaRest, Log, TEXT("Request (binary): %s %s"), *HttpRequest->GetVerb(), *HttpRequest->GetURL());
break;
}
case EVaRestRequestContentType::json:
{
HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
// Body is ignored for get requests, so we shouldn't place json into it even if it's empty
if (RequestVerb == EVaRestRequestVerb::GET)
{
break;
}
// Serialize data to json string
FString OutputString;
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString);
FJsonSerializer::Serialize(RequestJsonObj->GetRootObject(), Writer);
// Set Json content
HttpRequest->SetContentAsString(OutputString);
if (UVaRestLibrary::GetVaRestSettings()->bExtendedLog)
{
UE_LOG(LogVaRest, Log, TEXT("Request (json): %s %s %sJSON(%s%s%s)JSON"), *HttpRequest->GetVerb(), *HttpRequest->GetURL(), LINE_TERMINATOR, LINE_TERMINATOR, *OutputString, LINE_TERMINATOR);
}
else
{
UE_LOG(LogVaRest, Log, TEXT("Request (json): %s %s (check bExtendedLog for additional data)"), *HttpRequest->GetVerb(), *HttpRequest->GetURL());
}
break;
}
default:
break;
}
// Apply additional headers
for (TMap<FString, FString>::TConstIterator It(RequestHeaders); It; ++It)
{
HttpRequest->SetHeader(It.Key(), It.Value());
}
// Bind event
HttpRequest->OnProcessRequestComplete().BindUObject(this, &UVaRestRequestJSON::OnProcessRequestComplete);
// Execute the request
HttpRequest->ProcessRequest();
}
//////////////////////////////////////////////////////////////////////////
// Request callbacks
void UVaRestRequestJSON::OnProcessRequestComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
// Remove from root on completion
RemoveFromRoot();
// Be sure that we have no data from previous response
ResetResponseData();
// Check we have a response and save response code as int32
if (Response.IsValid())
{
ResponseCode = Response->GetResponseCode();
}
// Check we have result to process futher
if (!bWasSuccessful || !Response.IsValid())
{
UE_LOG(LogVaRest, Error, TEXT("Request failed (%d): %s"), ResponseCode, *Request->GetURL());
// Broadcast the result event
OnRequestFail.Broadcast(this);
OnStaticRequestFail.Broadcast(this);
return;
}
#if PLATFORM_DESKTOP
// Log response state
UE_LOG(LogVaRest, Log, TEXT("Response (%d): %sJSON(%s%s%s)JSON"), ResponseCode, LINE_TERMINATOR, LINE_TERMINATOR, *Response->GetContentAsString(), LINE_TERMINATOR);
#endif
// Process response headers
TArray<FString> Headers = Response->GetAllHeaders();
for (FString Header : Headers)
{
FString Key;
FString Value;
if (Header.Split(TEXT(": "), &Key, &Value))
{
ResponseHeaders.Add(Key, Value);
}
}
if (UVaRestLibrary::GetVaRestSettings()->bUseChunkedParser)
{
// Try to deserialize data to JSON
const TArray<uint8>& Bytes = Response->GetContent();
ResponseSize = ResponseJsonObj->DeserializeFromUTF8Bytes((const ANSICHAR*)Bytes.GetData(), Bytes.Num());
// Log errors
if (ResponseSize == 0)
{
// As we assume it's recommended way to use current class, but not the only one,
// it will be the warning instead of error
UE_LOG(LogVaRest, Warning, TEXT("JSON could not be decoded!"));
}
}
else
{
// Use default unreal one
const TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(*Response->GetContentAsString());
TSharedPtr<FJsonValue> OutJsonValue;
if (FJsonSerializer::Deserialize(Reader, OutJsonValue))
{
ResponseJsonValue->SetRootValue(OutJsonValue);
if (ResponseJsonValue->GetType() == EVaJson::Object)
{
ResponseJsonObj->SetRootObject(ResponseJsonValue->GetRootValue()->AsObject());
ResponseSize = Response->GetContentLength();
}
}
}
// Decide whether the request was successful
bIsValidJsonResponse = bWasSuccessful && (ResponseSize > 0);
if (!bIsValidJsonResponse)
{
// Save response data as a string
ResponseContent = Response->GetContentAsString();
ResponseSize = ResponseContent.GetAllocatedSize();
ResponseBytes = Response->GetContent();
ResponseContentLength = Response->GetContentLength();
}
// Broadcast the result events on next tick
OnRequestComplete.Broadcast(this);
OnStaticRequestComplete.Broadcast(this);
// Finish the latent action
if (ContinueAction)
{
FVaRestLatentAction<UVaRestJsonObject*>* K = ContinueAction;
ContinueAction = nullptr;
K->Call(ResponseJsonObj);
}
}
//////////////////////////////////////////////////////////////////////////
// Tags
void UVaRestRequestJSON::AddTag(FName Tag)
{
if (Tag != NAME_None)
{
Tags.AddUnique(Tag);
}
}
int32 UVaRestRequestJSON::RemoveTag(FName Tag)
{
return Tags.Remove(Tag);
}
bool UVaRestRequestJSON::HasTag(FName Tag) const
{
return (Tag != NAME_None) && Tags.Contains(Tag);
}
//////////////////////////////////////////////////////////////////////////
// Data
FString UVaRestRequestJSON::GetResponseContentAsString(bool bCacheResponseContent)
{
// Check we have valid json response
if (!bIsValidJsonResponse)
{
// We've cached response content in OnProcessRequestComplete()
return ResponseContent;
}
// Check we have valid response object
if (!ResponseJsonObj || !ResponseJsonObj->IsValidLowLevel())
{
// Discard previous cached string if we had one
ResponseContent = DeprecatedResponseString;
return TEXT("Invalid response");
}
// Check if we should re-genetate it in runtime
if (!bCacheResponseContent)
{
UE_LOG(LogVaRest, Warning, TEXT("%s: Use of uncashed getter could be slow"), *VA_FUNC_LINE);
return ResponseJsonObj->EncodeJson();
}
// Check that we haven't cached content yet
if (ResponseContent == DeprecatedResponseString)
{
UE_LOG(LogVaRest, Warning, TEXT("%s: Response content string is cached"), *VA_FUNC_LINE);
ResponseContent = ResponseJsonObj->EncodeJson();
}
// Return previously cached content now
return ResponseContent;
}