// 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); #if 0 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 */); #endif } 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: { ZenCacheValue 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()) { ZenCacheValue 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; try { Session.Put(Ref.BucketSegment, Ref.HashKey, Value); spdlog::debug("upstream PUT ({}) succeeded after {:5}!", Ref.HashKey, zen::NiceTimeSpanMs(Timer.getElapsedTimeMs())); } catch (std::exception& e) { spdlog::debug("upstream PUT ({}) failed after {:5}: '{}'", Ref.HashKey, zen::NiceTimeSpanMs(Timer.getElapsedTimeMs()), e.what()); throw; } } 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_first_of('/'); if (BucketSplitOffset == std::string_view::npos) { return false; } OutRef.BucketSegment = Key.substr(0, BucketSplitOffset); std::string_view HashSegment; std::string_view PayloadSegment; std::string_view::size_type PayloadSplitOffset = Key.find_last_of('/'); // We know there is a slash so no need to check for npos return if (PayloadSplitOffset == BucketSplitOffset) { // Basic cache record lookup HashSegment = Key.substr(BucketSplitOffset + 1); } else { // Cache record + payload lookup HashSegment = Key.substr(BucketSplitOffset + 1, PayloadSplitOffset - BucketSplitOffset - 1); PayloadSegment = Key.substr(PayloadSplitOffset + 1); } if (HashSegment.size() != (2 * sizeof OutRef.HashKey.Hash)) { return false; } if (!PayloadSegment.empty() && PayloadSegment.size() != 24) { OutRef.PayloadId = zen::Oid::FromHexString(PayloadSegment); if (!OutRef.PayloadId) { return false; } } const bool IsOk = zen::ParseHexBytes(HashSegment.data(), HashSegment.size(), OutRef.HashKey.Hash); if (!IsOk) { return false; } return true; } } // namespace zen