// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include #include #include #include "cachestore.h" #include "structuredcache.h" #include "upstream/jupiter.h" #include #include namespace zen { using namespace std::literals; HttpStructuredCacheService::HttpStructuredCacheService(std::filesystem::path RootPath, zen::CasStore& InStore) : m_CasStore(InStore) , m_CacheStore(InStore, RootPath) { spdlog::info("initializing structured cache at '{}'", RootPath); m_Cloud = new CloudCacheClient("https://jupiter.devtools.epicgames.com"sv, "ue4.ddc"sv /* namespace */, "https://epicgames.okta.com/oauth2/auso645ojjWVdRI3d0x7/v1/token"sv /* provider */, "0oao91lrhqPiAlaGD0x7"sv /* client id */, "-GBWjjenhCgOwhxL5yBKNJECVIoDPH0MK4RDuN7d"sv /* oauth secret */); } HttpStructuredCacheService::~HttpStructuredCacheService() { spdlog::info("closing structured cache"); } const char* HttpStructuredCacheService::BaseUri() const { return "/z$/"; } void HttpStructuredCacheService::HandleRequest(zen::HttpServerRequest& Request) { CacheRef Ref; if (!ValidateUri(Request, /* out */ Ref)) { return Request.WriteResponse(zen::HttpResponse::BadRequest); // invalid URL } switch (auto Verb = Request.RequestVerb()) { using enum zen::HttpVerb; case kHead: case kGet: { CacheValue Value; bool Success = m_CacheStore.Get(Ref.BucketSegment, Ref.HashKey, /* out */ Value); if (!Success) { Request.WriteResponse(zen::HttpResponse::NotFound); } else { if (Verb == kHead) { Request.SetSuppressResponseBody(); Request.WriteResponse(zen::HttpResponse::OK, zen::HttpContentType::kBinary, Value.Value); } else { Request.WriteResponse(zen::HttpResponse::OK, zen::HttpContentType::kBinary, Value.Value); } } } break; case kPut: { if (zen::IoBuffer Body = Request.ReadPayload()) { CacheValue Value; Value.Value = Body; HttpContentType ContentType = Request.RequestContentType(); switch (ContentType) { case HttpContentType::kUnknownContentType: case HttpContentType::kBinary: Value.IsCompactBinary = false; break; case HttpContentType::kCbObject: Value.IsCompactBinary = true; break; default: return Request.WriteResponse(zen::HttpResponse::BadRequest); } m_CacheStore.Put(Ref.BucketSegment, Ref.HashKey, Value); // This is currently synchronous for simplicity and debuggability but should be // made asynchronous if (m_Cloud) { CloudCacheSession Session(m_Cloud); zen::Stopwatch Timer; if (Session.Put(Ref.BucketSegment, Ref.HashKey)) { spdlog::debug("upstream PUT succeeded after {:5}! {}", zen::NiceTimeSpanMs(Timer.getElapsedTimeMs()), Ref.HashKey); } else { spdlog::debug("upstream PUT failed after {:5}! {}", zen::NiceTimeSpanMs(Timer.getElapsedTimeMs()), Ref.HashKey); } } return Request.WriteResponse(zen::HttpResponse::Created); } else { return; } } break; case kPost: break; default: break; } } [[nodiscard]] bool HttpStructuredCacheService::ValidateUri(zen::HttpServerRequest& Request, CacheRef& OutRef) { std::string_view Key = Request.RelativeUri(); std::string_view::size_type BucketSplitOffset = Key.find_last_of('/'); if (BucketSplitOffset == std::string_view::npos) { return false; } OutRef.BucketSegment = Key.substr(0, BucketSplitOffset); std::string_view HashSegment = Key.substr(BucketSplitOffset + 1); if (HashSegment.size() != (2 * sizeof OutRef.HashKey.Hash)) { return false; } bool IsOk = zen::ParseHexBytes(HashSegment.data(), HashSegment.size(), OutRef.HashKey.Hash); if (!IsOk) { return false; } return true; } } // namespace zen