You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Switch Zen cache store to use the HTTP request codepath in the Jupiter cache store while taking some of the improvements that had been made when that code was branched off to make the Zen HTTP codepath. This is a precursor for getting Zen cache store using non-blocking operations like Jupiter cache store - this has started to be stubbed in with the EnqueueAsyncRpc methods that are stubbed in but not used from the Zen cache store. Eventually I'd like this HTTP code to move out of the DerivedDataCache module and be usable elsewhere and supplant the HTTP code currently in the Zen module, but this will require some decisions about whether IRequestOwner should move out of the DerivedDataCache module or not. Tested Zen operation using ShooterGame editor. Tested Jupiter operation using CitySample editor (empty local cache). #rb devin.doucette #fyi stefan.boberg #preflight 62953e65401169f78c57c298 #preflight 629642a1be0ae0b33a46d012 [CL 20438549 by Zousar Shaker in ue5-main branch]
559 lines
15 KiB
C++
559 lines
15 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#pragma once
|
|
|
|
#include "HAL/Platform.h"
|
|
#define UE_WITH_HTTP_CLIENT PLATFORM_DESKTOP
|
|
|
|
#if UE_WITH_HTTP_CLIENT
|
|
#include "CoreTypes.h"
|
|
#include "Containers/Array.h"
|
|
#include "Containers/ArrayView.h"
|
|
#include "Containers/DepletableMpscQueue.h"
|
|
#include "Containers/UnrealString.h"
|
|
#include "DerivedDataRequest.h"
|
|
#include "DerivedDataRequestOwner.h"
|
|
#include "DerivedDataRequestTypes.h"
|
|
#include "Dom/JsonObject.h"
|
|
#include "Experimental/Async/LazyEvent.h"
|
|
#include "Experimental/Containers/FAAArrayQueue.h"
|
|
#include "HAL/CriticalSection.h"
|
|
#include "HAL/PlatformProcess.h"
|
|
#include "HAL/Thread.h"
|
|
#include "Memory/CompositeBuffer.h"
|
|
#include "Memory/MemoryView.h"
|
|
#include "Templates/PimplPtr.h"
|
|
#include "Templates/SharedPointer.h"
|
|
#include <atomic>
|
|
|
|
|
|
struct curl_slist;
|
|
namespace UE::Http::Private { struct FAsyncRequestData; }
|
|
namespace UE::Http::Private { struct FHttpRequestStatics; }
|
|
namespace UE::Http::Private { struct FHttpSharedDataInternals; }
|
|
namespace UE::Http::Private { struct FHttpSharedDataStatics; }
|
|
|
|
namespace UE
|
|
{
|
|
|
|
class FHttpRequestPool;
|
|
|
|
enum class EHttpContentType : int16
|
|
{
|
|
// Negative integers reserved for future custom string content types
|
|
UnspecifiedContentType = 0,
|
|
AnyContentType,
|
|
Binary,
|
|
Text,
|
|
JSON,
|
|
CbObject,
|
|
CbPackage,
|
|
YAML,
|
|
CbPackageOffer,
|
|
CompressedBinary,
|
|
FormUrlEncoded,
|
|
Count
|
|
};
|
|
|
|
inline FStringView GetHttpMimeType(EHttpContentType Type)
|
|
{
|
|
switch (Type)
|
|
{
|
|
case EHttpContentType::UnspecifiedContentType:
|
|
checkNoEntry();
|
|
return FStringView();
|
|
case EHttpContentType::AnyContentType:
|
|
return TEXTVIEW("*/*");
|
|
case EHttpContentType::Binary:
|
|
return TEXTVIEW("application/octet-stream");
|
|
case EHttpContentType::Text:
|
|
return TEXTVIEW("text/plain");
|
|
case EHttpContentType::JSON:
|
|
return TEXTVIEW("application/json");
|
|
case EHttpContentType::CbObject:
|
|
return TEXTVIEW("application/x-ue-cb");
|
|
case EHttpContentType::CbPackage:
|
|
return TEXTVIEW("application/x-ue-cbpkg");
|
|
case EHttpContentType::YAML:
|
|
return TEXTVIEW("text/yaml");
|
|
case EHttpContentType::CbPackageOffer:
|
|
return TEXTVIEW("application/x-ue-offer");
|
|
case EHttpContentType::CompressedBinary:
|
|
return TEXTVIEW("application/x-ue-comp");
|
|
case EHttpContentType::FormUrlEncoded:
|
|
return TEXTVIEW("application/x-www-form-urlencoded");
|
|
default:
|
|
return TEXTVIEW("unknown");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Encapsulation for access token shared by all requests.
|
|
*/
|
|
class FHttpAccessToken
|
|
{
|
|
public:
|
|
FHttpAccessToken() = default;
|
|
FString GetHeader();
|
|
void SetHeader(const TCHAR*);
|
|
uint32 GetSerial() const;
|
|
private:
|
|
FRWLock Lock;
|
|
FString Token;
|
|
uint32 Serial;
|
|
};
|
|
|
|
class FHttpSharedData
|
|
{
|
|
public:
|
|
FHttpSharedData();
|
|
~FHttpSharedData();
|
|
private:
|
|
friend class FHttpRequest;
|
|
friend struct Http::Private::FHttpSharedDataStatics;
|
|
|
|
UE::FLazyEvent PendingRequestEvent;
|
|
FThread AsyncServiceThread;
|
|
std::atomic<bool> bAsyncThreadStarting = false;
|
|
std::atomic<bool> bAsyncThreadShutdownRequested = false;
|
|
|
|
TPimplPtr<Http::Private::FHttpSharedDataInternals> Internals;
|
|
|
|
void ProcessAsyncRequests();
|
|
void AddRequest(void* Curl);
|
|
void* GetCurlShare() const;
|
|
};
|
|
|
|
|
|
/**
|
|
* Minimal HTTP request type wrapping CURL without the need for managers. This request
|
|
* is written to allow reuse of request objects, in order to allow connections to be reused.
|
|
*
|
|
* CURL has a global library initialization (curl_global_init). We rely on this happening in
|
|
* the Online/HTTP library which is a dependency on this module.
|
|
*/
|
|
class FHttpRequest
|
|
{
|
|
public:
|
|
|
|
/**
|
|
* Convenience result type interpreted from HTTP response code.
|
|
*/
|
|
enum class EResult
|
|
{
|
|
Success,
|
|
Failed,
|
|
FailedTimeout
|
|
};
|
|
|
|
enum class ECompletionBehavior : uint8
|
|
{
|
|
Done,
|
|
Retry
|
|
};
|
|
|
|
/**
|
|
* Supported request verbs
|
|
*/
|
|
enum class ERequestVerb
|
|
{
|
|
Get,
|
|
Put,
|
|
Post,
|
|
Delete,
|
|
Head
|
|
};
|
|
|
|
using FResultCode = int;
|
|
|
|
using FOnHttpRequestComplete = TUniqueFunction<ECompletionBehavior(EResult HttpResult, FHttpRequest* Request)>;
|
|
|
|
FHttpRequest(FStringView InDomain, FStringView InEffectiveDomain, FHttpAccessToken* InAuthorizationToken, FHttpSharedData* InSharedData, bool bInLogErrors);
|
|
~FHttpRequest();
|
|
|
|
/**
|
|
* Resets all options on the request except those that should always be set.
|
|
*/
|
|
void Reset();
|
|
|
|
void PrepareToRetry();
|
|
|
|
/** Gets the domain name for this request */
|
|
const FString& GetName() const
|
|
{
|
|
return Domain;
|
|
}
|
|
|
|
/** Gets the domain name for this request */
|
|
const FString& GetDomain() const
|
|
{
|
|
return Domain;
|
|
}
|
|
|
|
/** Gets the effective domain name for this request */
|
|
const FString& GetEffectiveDomain() const
|
|
{
|
|
return EffectiveDomain;
|
|
}
|
|
|
|
/** Returns the HTTP response code.*/
|
|
const int64 GetResponseCode() const
|
|
{
|
|
return ResponseCode;
|
|
}
|
|
|
|
/** Returns the number of bytes received this request (headers withstanding). */
|
|
const size_t GetBytesReceived() const
|
|
{
|
|
return BytesReceived;
|
|
}
|
|
|
|
/** Returns the number of attempts we've made issuing this request (currently tracked for async requests only). */
|
|
const size_t GetAttempts() const
|
|
{
|
|
return Attempts;
|
|
}
|
|
|
|
/** Returns the number of bytes sent during this request (headers withstanding). */
|
|
const size_t GetBytesSent() const
|
|
{
|
|
return BytesSent;
|
|
}
|
|
|
|
/**
|
|
* Upload buffer using the request, using either "Put" or "Post" verbs.
|
|
* @param Uri Url to use.
|
|
* @param Buffer Data to upload
|
|
* @return Result of the request
|
|
*/
|
|
EResult PerformBlockingPut(FStringView Uri,
|
|
FCompositeBuffer Buffer,
|
|
EHttpContentType ContentType = EHttpContentType::UnspecifiedContentType,
|
|
TConstArrayView<long> ExpectedErrorCodes = {});
|
|
EResult PerformBlockingPost(FStringView Uri,
|
|
FCompositeBuffer Buffer,
|
|
EHttpContentType ContentType = EHttpContentType::UnspecifiedContentType,
|
|
EHttpContentType AcceptType = EHttpContentType::UnspecifiedContentType,
|
|
TConstArrayView<long> ExpectedErrorCodes = {});
|
|
void EnqueueAsyncPut(UE::DerivedData::IRequestOwner& Owner,
|
|
FHttpRequestPool* Pool,
|
|
FStringView Uri,
|
|
FCompositeBuffer Buffer,
|
|
FOnHttpRequestComplete&& OnComplete,
|
|
EHttpContentType ContentType = EHttpContentType::UnspecifiedContentType,
|
|
TConstArrayView<long> ExpectedErrorCodes = {});
|
|
void EnqueueAsyncPost(UE::DerivedData::IRequestOwner& Owner,
|
|
FHttpRequestPool* Pool,
|
|
FStringView Uri,
|
|
FCompositeBuffer Buffer,
|
|
FOnHttpRequestComplete&& OnComplete,
|
|
EHttpContentType ContentType = EHttpContentType::UnspecifiedContentType,
|
|
EHttpContentType AcceptType = EHttpContentType::UnspecifiedContentType,
|
|
TConstArrayView<long> ExpectedErrorCodes = {});
|
|
|
|
/**
|
|
* Download an url into a buffer using the request.
|
|
* @param Uri Url to use.
|
|
* @param Buffer Optional buffer where data should be downloaded to. If empty downloaded data will
|
|
* be stored in an internal buffer and accessed GetResponse* methods.
|
|
* @return Result of the request
|
|
*/
|
|
EResult PerformBlockingDownload(FStringView Uri,
|
|
TArray64<uint8>* Buffer,
|
|
EHttpContentType AcceptType = EHttpContentType::UnspecifiedContentType,
|
|
TConstArrayView<long> ExpectedErrorCodes = {400});
|
|
void EnqueueAsyncDownload(UE::DerivedData::IRequestOwner& Owner,
|
|
FHttpRequestPool* Pool,
|
|
FStringView Uri,
|
|
FOnHttpRequestComplete&& OnComplete,
|
|
EHttpContentType AcceptType = EHttpContentType::UnspecifiedContentType,
|
|
TConstArrayView<long> ExpectedErrorCodes = {400});
|
|
|
|
/**
|
|
* Query an url using the request. Queries can use either "Head" or "Delete" verbs.
|
|
* @param Uri Url to use.
|
|
* @return Result of the request
|
|
*/
|
|
EResult PerformBlockingHead(FStringView Uri,
|
|
EHttpContentType AcceptType = EHttpContentType::UnspecifiedContentType,
|
|
TConstArrayView<long> ExpectedErrorCodes = {400});
|
|
EResult PerformBlockingDelete(FStringView Uri,
|
|
TConstArrayView<long> ExpectedErrorCodes = {400});
|
|
void EnqueueAsyncHead(UE::DerivedData::IRequestOwner& Owner,
|
|
FHttpRequestPool* Pool,
|
|
FStringView Uri,
|
|
FOnHttpRequestComplete&& OnComplete,
|
|
EHttpContentType AcceptType = EHttpContentType::UnspecifiedContentType,
|
|
TConstArrayView<long> ExpectedErrorCodes = {400});
|
|
void EnqueueAsyncDelete(UE::DerivedData::IRequestOwner& Owner,
|
|
FHttpRequestPool* Pool,
|
|
FStringView Uri,
|
|
FOnHttpRequestComplete&& OnComplete,
|
|
TConstArrayView<long> ExpectedErrorCodes = {400});
|
|
|
|
/**
|
|
* Set a header to send with the request.
|
|
*/
|
|
void AddHeader(FStringView Header, FStringView Value);
|
|
|
|
/**
|
|
* Attempts to find the header from the response. Returns false if header is not present.
|
|
*/
|
|
bool GetHeader(const ANSICHAR* Header, FString& OutValue) const;
|
|
|
|
/**
|
|
* Returns the response buffer. Note that is the request is performed
|
|
* with an external buffer as target buffer this string will be empty.
|
|
*/
|
|
const TArray64<uint8>& GetResponseBuffer() const
|
|
{
|
|
return ResponseBuffer;
|
|
}
|
|
|
|
FSharedBuffer MoveResponseBufferToShared()
|
|
{
|
|
return MakeSharedBufferFromArray(MoveTemp(ResponseBuffer));
|
|
}
|
|
|
|
/**
|
|
* Returns the response buffer as a string. Note that is the request is performed
|
|
* with an external buffer as target buffer this string will be empty.
|
|
*/
|
|
FString GetResponseAsString() const
|
|
{
|
|
return GetAnsiBufferAsString(ResponseBuffer);
|
|
}
|
|
|
|
/**
|
|
* Returns the response header as a string.
|
|
*/
|
|
FString GetResponseHeaderAsString()
|
|
{
|
|
return GetAnsiBufferAsString(ResponseHeader);
|
|
}
|
|
|
|
/**
|
|
* Tries to parse the response buffer as a JsonObject. Return empty pointer if
|
|
* parse error occurs.
|
|
*/
|
|
TSharedPtr<FJsonObject> GetResponseAsJsonObject() const;
|
|
|
|
/**
|
|
* Tries to parse the response buffer as a JsonArray. Return empty array if
|
|
* parse error occurs.
|
|
*/
|
|
TArray<TSharedPtr<FJsonValue>> GetResponseAsJsonArray() const;
|
|
|
|
void CompleteAsync(FResultCode Result);
|
|
|
|
/** Will return true if the response code is considered a success */
|
|
static bool IsSuccessResponse(long ResponseCode)
|
|
{
|
|
// We consider anything in the 1XX or 2XX range a success
|
|
return ResponseCode >= 100 && ResponseCode < 300;
|
|
}
|
|
|
|
static bool AllowAsync();
|
|
|
|
private:
|
|
friend struct Http::Private::FAsyncRequestData;
|
|
friend struct Http::Private::FHttpRequestStatics;
|
|
|
|
void* Curl;
|
|
FResultCode ResultCode;
|
|
FHttpSharedData* SharedData;
|
|
Http::Private::FAsyncRequestData* AsyncData;
|
|
long ResponseCode;
|
|
size_t BytesSent;
|
|
size_t BytesReceived;
|
|
size_t Attempts;
|
|
bool bLogErrors;
|
|
|
|
FCompositeBuffer ReadCompositeBuffer;
|
|
TArray64<uint8>* WriteDataBufferPtr;
|
|
TArray64<uint8>* WriteHeaderBufferPtr;
|
|
|
|
TArray64<uint8> ResponseHeader;
|
|
TArray64<uint8> ResponseBuffer;
|
|
TArray<FString> Headers;
|
|
FString Domain;
|
|
FString EffectiveDomain;
|
|
FHttpAccessToken* AuthorizationToken;
|
|
|
|
void AddContentTypeHeader(FStringView Header, EHttpContentType Type);
|
|
curl_slist* PrepareToIssueRequest(FStringView Uri, ERequestVerb Verb, uint64 ContentLength);
|
|
|
|
/**
|
|
* Performs the request, blocking until finished.
|
|
* @param Uri Address on the domain to query
|
|
* @param Verb HTTP verb to use
|
|
* @param ContentLength The number of bytes being uploaded for the body of this request.
|
|
* @param ExpectedErrorCodes An array of expected return codes outside of the success range that should NOT be logged as an abnormal/exceptional outcome.
|
|
* If unset the response body will be stored in the request.
|
|
*/
|
|
EResult PerformBlocking(FStringView Uri, ERequestVerb Verb, uint64 ContentLength, TConstArrayView<long> ExpectedErrorCodes);
|
|
|
|
void EnqueueAsync(UE::DerivedData::IRequestOwner& Owner, FHttpRequestPool* Pool, FStringView Uri, ERequestVerb Verb, uint64 ContentLength, FOnHttpRequestComplete&& OnComplete, TConstArrayView<long> ExpectedErrorCodes);
|
|
|
|
void LogResult(FResultCode EResult, FStringView Uri, ERequestVerb Verb, TConstArrayView<long> ExpectedErrorCodes) const;
|
|
|
|
FString GetAnsiBufferAsString(const TArray64<uint8>& Buffer) const
|
|
{
|
|
// Content is NOT null-terminated; we need to specify lengths here
|
|
FUTF8ToTCHAR TCHARData(reinterpret_cast<const ANSICHAR*>(Buffer.GetData()), Buffer.Num());
|
|
return FString(TCHARData.Length(), TCHARData.Get());
|
|
}
|
|
|
|
};
|
|
|
|
|
|
/**
|
|
* Pool that manages a fixed set of requests. Users are required to release requests that have been
|
|
* acquired. Usable with \ref FScopedRequestPtr which handles this automatically.
|
|
*/
|
|
class FHttpRequestPool
|
|
{
|
|
public:
|
|
FHttpRequestPool(FStringView InServiceUrl, FStringView InEffectiveServiceUrl, FHttpAccessToken* InAuthorizationToken, FHttpSharedData* InSharedData, uint32 PoolSize, uint32 InOverflowLimit = 0);
|
|
~FHttpRequestPool();
|
|
|
|
/**
|
|
* Attempts to get a request is free. Once a request has been returned it is
|
|
* "owned by the caller and need to release it to the pool when work has been completed.
|
|
* @return Usable request instance if one is available, otherwise null.
|
|
*/
|
|
FHttpRequest* GetFreeRequest(bool bUnboundedOverflow = false);
|
|
|
|
class FWaiter : public FThreadSafeRefCountedObject
|
|
{
|
|
public:
|
|
std::atomic<FHttpRequest*> Request{ nullptr };
|
|
|
|
FWaiter(FHttpRequestPool* InPool)
|
|
: Event(FPlatformProcess::GetSynchEventFromPool(true))
|
|
, Pool(InPool)
|
|
{
|
|
}
|
|
|
|
bool Wait(uint32 TimeMS)
|
|
{
|
|
return Event->Wait(TimeMS);
|
|
}
|
|
|
|
void Trigger()
|
|
{
|
|
Event->Trigger();
|
|
}
|
|
private:
|
|
~FWaiter()
|
|
{
|
|
FPlatformProcess::ReturnSynchEventToPool(Event);
|
|
|
|
if (Request)
|
|
{
|
|
Pool->ReleaseRequestToPool(Request.exchange(nullptr));
|
|
}
|
|
}
|
|
|
|
FEvent* Event;
|
|
FHttpRequestPool* Pool;
|
|
};
|
|
|
|
/**
|
|
* Block until a request is free. Once a request has been returned it is
|
|
* "owned by the caller and need to release it to the pool when work has been completed.
|
|
* @return Usable request instance.
|
|
*/
|
|
FHttpRequest* WaitForFreeRequest(bool bUnboundedOverflow = false);
|
|
|
|
/**
|
|
* Release request to the pool.
|
|
* @param Request Request that should be freed. Note that any buffer owened by the request can now be reset.
|
|
*/
|
|
void ReleaseRequestToPool(FHttpRequest* Request);
|
|
|
|
/**
|
|
* While holding a request, make it shared across many users.
|
|
*/
|
|
void MakeRequestShared(FHttpRequest* Request, uint8 Users);
|
|
|
|
private:
|
|
|
|
struct FEntry
|
|
{
|
|
std::atomic<uint8> Usage;
|
|
FHttpRequest* Request;
|
|
};
|
|
|
|
TArray<FEntry> Pool;
|
|
TArray<FHttpRequest> Requests;
|
|
FAAArrayQueue<FWaiter> Waiters;
|
|
std::atomic<uint32> ActiveOverflowRequests;
|
|
struct FInitData
|
|
{
|
|
FString ServiceUrl;
|
|
FString EffectiveServiceUrl;
|
|
FHttpAccessToken* AccessToken;
|
|
FHttpSharedData* SharedData;
|
|
|
|
FInitData(FStringView InServiceUrl, FStringView InEffectiveServiceUrl, FHttpAccessToken* InAccessToken, FHttpSharedData* InSharedData)
|
|
: ServiceUrl(InServiceUrl)
|
|
, EffectiveServiceUrl(InEffectiveServiceUrl)
|
|
, AccessToken(InAccessToken)
|
|
, SharedData(InSharedData)
|
|
{
|
|
}
|
|
};
|
|
TUniquePtr<const FInitData> InitData;
|
|
const uint32 OverflowLimit;
|
|
|
|
FHttpRequestPool() = delete;
|
|
};
|
|
|
|
/**
|
|
* Utility class to manage requesting and releasing requests from the \ref FHttpRequestPool.
|
|
*/
|
|
class FScopedHttpPoolRequestPtr
|
|
{
|
|
public:
|
|
UE_NONCOPYABLE(FScopedHttpPoolRequestPtr)
|
|
|
|
explicit FScopedHttpPoolRequestPtr(FHttpRequestPool* InPool)
|
|
: Request(InPool->WaitForFreeRequest())
|
|
, Pool(InPool)
|
|
{
|
|
}
|
|
|
|
~FScopedHttpPoolRequestPtr()
|
|
{
|
|
Pool->ReleaseRequestToPool(Request);
|
|
}
|
|
|
|
bool IsValid() const
|
|
{
|
|
return Request != nullptr;
|
|
}
|
|
|
|
inline explicit operator bool() const { return IsValid(); }
|
|
|
|
FHttpRequest* Get() const
|
|
{
|
|
check(IsValid());
|
|
return Request;
|
|
}
|
|
|
|
FHttpRequest* operator->()
|
|
{
|
|
check(IsValid());
|
|
return Request;
|
|
}
|
|
|
|
private:
|
|
FHttpRequest* Request;
|
|
FHttpRequestPool* Pool;
|
|
};
|
|
|
|
} // UE
|
|
|
|
#endif // UE_WITH_HTTP_CLIENT
|