Files
UnrealEngine/Engine/Source/Developer/Zen/Internal/ZenServerHttp.h
2025-05-18 13:04:45 +08:00

337 lines
9.1 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Containers/StaticArray.h"
#include "Containers/StringView.h"
#include "Containers/UnrealString.h"
#include "Experimental/ZenGlobals.h"
#include "Memory/MemoryFwd.h"
// if there is a platform-specific include then it must be used in the header file in case it defines CURL_STRICTER
#if defined(PLATFORM_CURL_INCLUDE)
#if PLATFORM_MICROSOFT
#include "Microsoft/AllowMicrosoftPlatformTypes.h"
#endif
#include PLATFORM_CURL_INCLUDE
#if PLATFORM_MICROSOFT
#include "Microsoft/HideMicrosoftPlatformTypes.h"
#endif
#endif //defined(PLATFORM_CURL_INCLUDE)
class FCompositeBuffer;
class FCbObjectView;
class FCbPackage;
#if UE_WITH_ZEN
namespace UE::Zen {
class FZenServiceEndpoint;
static bool IsSuccessCode(int ResponseCode)
{
return 200 <= ResponseCode && ResponseCode < 300;
}
enum class EContentType
{
Binary = 0,
Text = 1,
JSON = 2,
CbObject = 3,
CbPackage = 4,
YAML = 5,
CbPackageOffer = 6,
CompressedBinary = 7,
UnknownContentType = 8,
Count
};
inline FStringView GetMimeType(EContentType Type)
{
switch (Type)
{
case EContentType::Binary:
return TEXTVIEW("application/octet-stream");
case EContentType::Text:
return TEXTVIEW("text/plain");
case EContentType::JSON:
return TEXTVIEW("application/json");
case EContentType::CbObject:
return TEXTVIEW("application/x-ue-cb");
case EContentType::CbPackage:
return TEXTVIEW("application/x-ue-cbpkg");
case EContentType::YAML:
return TEXTVIEW("text/yaml");
case EContentType::CbPackageOffer:
return TEXTVIEW("application/x-ue-offer");
case EContentType::CompressedBinary:
return TEXTVIEW("application/x-ue-comp");
default:
return TEXTVIEW("unknown");
}
}
/** 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 of this module
*/
class FZenHttpRequest
{
public:
ZEN_API FZenHttpRequest(const FZenServiceEndpoint& InDomain, bool bInLogErrors, uint32 ConnectTimeoutMS = 0);
ZEN_API ~FZenHttpRequest();
/**
* Resets all options on the request except those that should always be set.
*/
ZEN_API void Reset();
/**
* Initializes a previously-allocated FZenHttpRequest with the options that can vary between requests
*/
ZEN_API void Initialize(bool bInLogErrors);
/** Returns the HTTP response code.*/
inline const int GetResponseCode() const
{
return int(ResponseCode);
}
ZEN_API FAnsiStringView GetError() const;
inline const bool GetResponseFormatValid() const
{
return bResponseFormatValid;
}
/** Returns the number of bytes sent during this request (headers withstanding). */
inline const size_t GetBytesSent() const
{
return BytesSent;
}
/**
* Convenience result type interpreted from HTTP response code.
*/
enum class Result
{
Success,
Failed
};
/**
* Upload buffer using the request, using PUT verb
* @param Uri Url to use.
* @param Buffer Data to upload
* @param ContentType The content MIME type.
* @return Result of the request
*/
ZEN_API Result PerformBlockingPut(const TCHAR* Uri, const FCompositeBuffer& Buffer, EContentType ContentType);
/**
* 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 this is null then
* downloaded data will be stored in an internal buffer and accessed via GetResponseAsString
* @param ContentType The MIME type to accept.
* @return Result of the request
*/
ZEN_API Result PerformBlockingDownload(FStringView Uri, TArray64<uint8>* Buffer, EContentType AcceptType);
/**
* Download an url into a buffer using the request.
* @param Uri Url to use.
* @param OutPackage Package instance which will receive the data
* @result Request success/failure status
*/
ZEN_API Result PerformBlockingDownload(const TCHAR* Uri, FCbPackage& OutPackage);
/**
* Query an url using the request. Queries can use either "Head" or "Delete" verbs.
* @param Uri Url to use.
* @param ContentType The MIME type to accept.
* @return Result of the request
*/
ZEN_API Result PerformBlockingHead(FStringView Uri, EContentType AcceptType);
/**
* Query an url using the request. Queries can use either "Head" or "Delete" verbs.
* @param Uri Url to use.
* @return Result of the request
*/
ZEN_API Result PerformBlockingDelete(FStringView Uri);
ZEN_API Result PerformBlockingPostPackage(FStringView Uri, const FCbPackage& Package,
EContentType AcceptType = EContentType::UnknownContentType);
ZEN_API Result PerformBlockingPost(FStringView Uri, FCbObjectView Obj,
EContentType AcceptType = EContentType::UnknownContentType);
ZEN_API Result PerformBlockingPost(FStringView Uri, FMemoryView Payload,
EContentType ContentType = EContentType::Binary, EContentType AcceptType = EContentType::UnknownContentType);
ZEN_API Result PerformRpc(FStringView Uri, FCbObjectView Request, FCbPackage &OutResponse);
ZEN_API Result PerformRpc(FStringView Uri, const FCbPackage& Request, FCbPackage& OutResponse);
/** Returns the response buffer as a string. Note that if the request is performed
with an external buffer as target buffer this string will be empty.
*/
ZEN_API FString GetResponseAsString() const;
/** Returns the response buffer. Note that if the request is performed
* with an external buffer as target buffer this will be empty.
*/
inline const TArray64<uint8>& GetResponseBuffer() const
{
return ResponseBuffer;
}
ZEN_API FCbObjectView GetResponseAsObject() const;
ZEN_API FCbPackage GetResponseAsPackage() const;
ZEN_API bool GetHeader(const ANSICHAR* Header, FString& OutValue) const;
ZEN_API static FAnsiStringView LexResponseCodeToString(int HttpCode);
private:
#if defined(CURL_STRICTER)
CURL* Curl = nullptr;
#else
void* /* CURL */ Curl = nullptr;
#endif
long /* CURLCode */ CurlResult;
long ResponseCode = 0;
size_t BytesSent = 0;
size_t BytesReceived = 0;
bool bLogErrors = false;
bool bResponseFormatValid = false;
const FCompositeBuffer* ReadDataView = nullptr;
TArray64<uint8>* WriteDataBufferPtr = nullptr;
FCbPackage* WriteDataPackage = nullptr;
TArray64<uint8>* WriteHeaderBufferPtr = nullptr;
TArray64<uint8> ResponseHeader;
TArray64<uint8> ResponseBuffer; // If no other response buffer is set, this is where the response payload goes
TArray<FString> Headers;
FString Domain;
const FZenServiceEndpoint& Endpoint;
void AddHeader(FStringView Header, FStringView Value);
/**
* Supported request verb
*/
enum class RequestVerb
{
Get,
Put,
Post,
Delete,
Head
};
void LogResult(long /*CURLcode*/ Result, const TCHAR* Uri, RequestVerb Verb) const;
/**
* Performs the request, blocking until finished.
* @param Uri Address on the domain to query
* @param Verb HTTP verb to use
* @param Buffer Optional buffer to directly receive the result of the request.
* If unset the response body will be stored in the request.
*/
Result PerformBlocking(FStringView Uri, RequestVerb Verb, uint64 ContentLength);
FZenHttpRequest::Result ParseRpcResponse(FZenHttpRequest::Result ResultFromPost, FCbPackage& OutResponse);
static FString GetAnsiBufferAsString(const TArray64<uint8>& Buffer);
struct FStatics;
};
/**
* Pool which manages a fixed set of requests. Users are required to release requests that have been
* acquired.
*
* Intended to be used with \ref FScopedRequestPtr which handles lifetime management transparently
*/
struct FZenHttpRequestPool
{
ZEN_API explicit FZenHttpRequestPool(const FZenServiceEndpoint& Endpoint, uint32 PoolEntryCount = 16);
ZEN_API ~FZenHttpRequestPool();
/** 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.
*/
ZEN_API FZenHttpRequest* WaitForFreeRequest();
/** Release request to the pool.
* @param Request Request that should be freed. Note that any buffer owned by the request can now be reset.
*/
ZEN_API void ReleaseRequestToPool(FZenHttpRequest* Request);
private:
struct FEntry
{
std::atomic<uint8> IsAllocated;
FZenHttpRequest* Request;
};
TArray<FEntry> Pool;
};
/**
* Utility class to manage requesting and releasing requests from the \ref FRequestPool.
*/
struct FZenScopedRequestPtr
{
public:
FZenScopedRequestPtr(FZenHttpRequestPool* InPool, bool bLogErrors=true)
: Request(InPool->WaitForFreeRequest())
, Pool(InPool)
{
Request->Initialize(bLogErrors);
}
~FZenScopedRequestPtr()
{
Pool->ReleaseRequestToPool(Request);
}
inline bool IsValid() const
{
return Request != nullptr;
}
inline operator bool() const { return IsValid(); }
FZenHttpRequest* operator->()
{
check(IsValid());
return Request;
}
FZenHttpRequest& operator*()
{
check(IsValid());
return *Request;
}
private:
FZenHttpRequest* Request;
FZenHttpRequestPool* Pool;
};
} // namespace UE::Zen
#endif // UE_WITH_ZEN