// Copyright Epic Games, Inc. All Rights Reserved. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "clients/httpclientcommon.h" #if ZEN_WITH_TESTS # include # include #endif // ZEN_WITH_TESTS namespace zen { extern HttpClientBase* CreateCprHttpClient(std::string_view BaseUri, const HttpClientSettings& ConnectionSettings); using namespace std::literals; ////////////////////////////////////////////////////////////////////////// HttpClientBase::HttpClientBase(std::string_view BaseUri, const HttpClientSettings& ConnectionSettings) : m_Log(zen::logging::Get(ConnectionSettings.LogCategory)) , m_BaseUri(BaseUri) , m_ConnectionSettings(ConnectionSettings) { if (ConnectionSettings.SessionId == Oid::Zero) { m_SessionId = GetSessionIdString(); } else { m_SessionId = ConnectionSettings.SessionId.ToString(); } } HttpClientBase::~HttpClientBase() { } bool HttpClientBase::Authenticate() { ZEN_TRACE_CPU("HttpClientBase::Authenticate"); std::optional Token = GetAccessToken(); if (!Token) { return false; } return Token->IsValid(); } const std::optional HttpClientBase::GetAccessToken() { ZEN_TRACE_CPU("HttpClientBase::GetAccessToken"); if (!m_ConnectionSettings.AccessTokenProvider.has_value()) { return {}; } { RwLock::SharedLockScope _(m_AccessTokenLock); if (m_CachedAccessToken.IsValid()) { return m_CachedAccessToken; } } RwLock::ExclusiveLockScope _(m_AccessTokenLock); if (m_CachedAccessToken.IsValid()) { return m_CachedAccessToken; } m_CachedAccessToken = m_ConnectionSettings.AccessTokenProvider.value()(); return m_CachedAccessToken; } ////////////////////////////////////////////////////////////////////////// CbObject HttpClient::Response::AsObject() const { if (ResponsePayload) { CbValidateError ValidationError = CbValidateError::None; if (CbObject ResponseObject = ValidateAndReadCompactBinaryObject(IoBuffer(ResponsePayload), ValidationError); ValidationError == CbValidateError::None) { return ResponseObject; } } return {}; } CbPackage HttpClient::Response::AsPackage() const { // TODO: sanity checks and error handling if (ResponsePayload) { return ParsePackageMessage(ResponsePayload); } return {}; } std::string_view HttpClient::Response::AsText() const { if (ResponsePayload) { return std::string_view(reinterpret_cast(ResponsePayload.GetData()), ResponsePayload.GetSize()); } return {}; } std::string HttpClient::Response::ToText() const { if (!ResponsePayload) return {}; switch (ResponsePayload.GetContentType()) { case ZenContentType::kCbObject: { zen::ExtendableStringBuilder<1024> ObjStr; zen::CbObject Object{SharedBuffer(ResponsePayload)}; zen::CompactBinaryToJson(Object, ObjStr); return ObjStr.ToString(); } break; case ZenContentType::kCSS: case ZenContentType::kHTML: case ZenContentType::kJavaScript: case ZenContentType::kJSON: case ZenContentType::kText: case ZenContentType::kYAML: return std::string{AsText()}; default: return ""; } } bool HttpClient::Response::IsSuccess() const noexcept { return !Error && IsHttpSuccessCode(StatusCode); } std::string HttpClient::Response::ErrorMessage(std::string_view Prefix) const { if (Error.has_value()) { return fmt::format("{}: {}", Prefix, Error->ErrorMessage); } else if (StatusCode != HttpResponseCode::ImATeapot && (int)StatusCode) { std::string TextResponse = ToText(); return fmt::format("{}{}HTTP error {} {}{}", Prefix, Prefix.empty() ? ""sv : ": "sv, (int)StatusCode, zen::ToString(StatusCode), TextResponse.empty() ? ""sv : fmt::format(" ({})", TextResponse)); } else { return fmt::format("{}{}unknown error", Prefix, Prefix.empty() ? ""sv : ": "sv); } } void HttpClient::Response::ThrowError(std::string_view ErrorPrefix) { if (!IsSuccess()) { throw HttpClientError(ErrorMessage(ErrorPrefix), Error.has_value() ? Error.value().ErrorCode : 0, StatusCode); } } ////////////////////////////////////////////////////////////////////////// HttpClient::HttpClient(std::string_view BaseUri, const HttpClientSettings& ConnectionSettings) : m_BaseUri(BaseUri) , m_ConnectionSettings(ConnectionSettings) { m_SessionId = GetSessionIdString(); m_Inner = CreateCprHttpClient(BaseUri, ConnectionSettings); } HttpClient::~HttpClient() { delete m_Inner; } void HttpClient::SetSessionId(const Oid& SessionId) { if (SessionId == Oid::Zero) { m_SessionId = GetSessionIdString(); } else { m_SessionId = SessionId.ToString(); } } HttpClient::Response HttpClient::Put(std::string_view Url, const IoBuffer& Payload, const HttpClient::KeyValueMap& AdditionalHeader) { return m_Inner->Put(Url, Payload, AdditionalHeader); } HttpClient::Response HttpClient::Put(std::string_view Url, const HttpClient::KeyValueMap& Parameters) { return m_Inner->Put(Url, Parameters); } HttpClient::Response HttpClient::Get(std::string_view Url, const HttpClient::KeyValueMap& AdditionalHeader, const HttpClient::KeyValueMap& Parameters) { return m_Inner->Get(Url, AdditionalHeader, Parameters); } HttpClient::Response HttpClient::Head(std::string_view Url, const HttpClient::KeyValueMap& AdditionalHeader) { return m_Inner->Head(Url, AdditionalHeader); } HttpClient::Response HttpClient::Delete(std::string_view Url, const HttpClient::KeyValueMap& AdditionalHeader) { return m_Inner->Delete(Url, AdditionalHeader); } HttpClient::Response HttpClient::Post(std::string_view Url, const HttpClient::KeyValueMap& AdditionalHeader, const HttpClient::KeyValueMap& Parameters) { return m_Inner->Post(Url, AdditionalHeader, Parameters); } HttpClient::Response HttpClient::Post(std::string_view Url, const IoBuffer& Payload, const HttpClient::KeyValueMap& AdditionalHeader) { return m_Inner->Post(Url, Payload, AdditionalHeader); } HttpClient::Response HttpClient::Post(std::string_view Url, const IoBuffer& Payload, ZenContentType ContentType, const HttpClient::KeyValueMap& AdditionalHeader) { return m_Inner->Post(Url, Payload, ContentType, AdditionalHeader); } HttpClient::Response HttpClient::Post(std::string_view Url, CbObject Payload, const HttpClient::KeyValueMap& AdditionalHeader) { return m_Inner->Post(Url, Payload, AdditionalHeader); } HttpClient::Response HttpClient::Post(std::string_view Url, CbPackage Payload, const HttpClient::KeyValueMap& AdditionalHeader) { return m_Inner->Post(Url, Payload, AdditionalHeader); } HttpClient::Response HttpClient::Post(std::string_view Url, const CompositeBuffer& Payload, ZenContentType ContentType, const HttpClient::KeyValueMap& AdditionalHeader) { return m_Inner->Post(Url, Payload, ContentType, AdditionalHeader); } HttpClient::Response HttpClient::Upload(std::string_view Url, const IoBuffer& Payload, const HttpClient::KeyValueMap& AdditionalHeader) { return m_Inner->Upload(Url, Payload, AdditionalHeader); } HttpClient::Response HttpClient::Upload(std::string_view Url, const CompositeBuffer& Payload, ZenContentType ContentType, const HttpClient::KeyValueMap& AdditionalHeader) { return m_Inner->Upload(Url, Payload, ContentType, AdditionalHeader); } HttpClient::Response HttpClient::Download(std::string_view Url, const std::filesystem::path& TempFolderPath, const HttpClient::KeyValueMap& AdditionalHeader) { return m_Inner->Download(Url, TempFolderPath, AdditionalHeader); } HttpClient::Response HttpClient::TransactPackage(std::string_view Url, CbPackage Package, const HttpClient::KeyValueMap& AdditionalHeader) { return m_Inner->TransactPackage(Url, Package, AdditionalHeader); } bool HttpClient::Authenticate() { return m_Inner->Authenticate(); } ////////////////////////////////////////////////////////////////////////// #if ZEN_WITH_TESTS TEST_CASE("responseformat") { using namespace std::literals; SUBCASE("identity") { BodyLogFormatter _{"abcd"}; CHECK_EQ(_.GetText(), "abcd"sv); } SUBCASE("very long") { std::string_view LongView = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"; BodyLogFormatter _{LongView}; CHECK(_.GetText().size() < LongView.size()); CHECK(_.GetText().starts_with("[truncated"sv)); } SUBCASE("invalid text") { std::string_view BadText = "totobaba\xff\xfe"; BodyLogFormatter _{BadText}; CHECK_EQ(_.GetText(), "totobaba"); } } TEST_CASE("httpclient") { using namespace std::literals; SUBCASE("client") {} } void httpclient_forcelink() { } #endif } // namespace zen