From 1b235fc947589dfbac0d09024947c37171c5dc7f Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Thu, 12 May 2022 13:04:59 +0200 Subject: Add support for /api/v2/ URI requests with namespace support --- zenserver/cache/structuredcache.cpp | 177 +++++++++++++++++++++++++++++++----- 1 file changed, 152 insertions(+), 25 deletions(-) (limited to 'zenserver/cache/structuredcache.cpp') diff --git a/zenserver/cache/structuredcache.cpp b/zenserver/cache/structuredcache.cpp index eed7a4420..691da36fa 100644 --- a/zenserver/cache/structuredcache.cpp +++ b/zenserver/cache/structuredcache.cpp @@ -121,47 +121,148 @@ HttpStructuredCacheService::Scrub(ScrubContext& Ctx) m_CacheStore.Scrub(Ctx); } -void -HttpStructuredCacheService::HandleRequest(HttpServerRequest& Request) +static constexpr std::string_view HttpZCacheAPIV2Prefix = "api/v2/"sv; +static constexpr std::string_view HttpZCacheRPCPrefix = "$rpc"sv; + +struct HttpRequestData { - CacheRef Ref; + std::optional Namespace; + std::optional Bucket; + std::optional HashKey; + std::optional ValueContentId; +}; - metrics::OperationTiming::Scope $(m_HttpRequests); +static bool +HttpRequestParseRelativeUri(std::string_view Key, HttpRequestData& Data) +{ + std::string_view Namespace = ZenCacheStore::DefaultNamespace; + if (Key.starts_with(HttpZCacheAPIV2Prefix)) + { + std::string_view::size_type NamespaceSplitOffset = Key.find_first_of('/', HttpZCacheAPIV2Prefix.length()); + if (NamespaceSplitOffset == std::string_view::npos) + { + // Namespace reference + if (!std::all_of(begin(Key), end(Key), [](const char c) { return std::isalnum(c); })) + { + return false; + } + Data.Namespace = ToLower(Key); + return true; + } + Data.Namespace = Key.substr(0, NamespaceSplitOffset); + Key = Key.substr(NamespaceSplitOffset + 1); + } + + std::string_view::size_type BucketSplitOffset = Key.find_first_of('/'); + if (BucketSplitOffset == std::string_view::npos) + { + if (!std::all_of(begin(Key), end(Key), [](const char c) { return std::isalnum(c); })) + { + return false; + } + Data.Bucket = ToLower(Key); + return true; + } + + std::string_view HashSegment; + std::string_view ValueSegment; + + std::string_view::size_type ValueSplitOffset = Key.find_last_of('/'); + if (ValueSplitOffset == BucketSplitOffset) + { + // Basic cache record lookup + HashSegment = Key.substr(BucketSplitOffset + 1); + } + else + { + // Cache record + valueid lookup + HashSegment = Key.substr(BucketSplitOffset + 1, ValueSplitOffset - BucketSplitOffset - 1); + ValueSegment = Key.substr(ValueSplitOffset + 1); + } - if (!ValidateKeyUri(Request, /* out */ Ref)) + if (HashSegment.size() != IoHash::StringLength) { - std::string_view Key = Request.RelativeUri(); + return false; + } + + IoHash KeyHash; + if (!ParseHexBytes(HashSegment.data(), HashSegment.size(), KeyHash.Hash)) + { + return false; + } - if (Key == "$rpc") + Data.HashKey = KeyHash; + + if (!ValueSegment.empty()) + { + if (ValueSegment.size() != IoHash::StringLength) { - return HandleRpcRequest(Request); + return false; } - if (std::all_of(begin(Key), end(Key), [](const char c) { return std::isalnum(c); })) + IoHash ValueHash; + if (!ParseHexBytes(ValueSegment.data(), ValueSegment.size(), ValueHash.Hash)) { - // Bucket reference - return HandleCacheBucketRequest(Request, Key); + return false; } + Data.ValueContentId = ValueHash; + } + + return true; +} + +void +HttpStructuredCacheService::HandleRequest(HttpServerRequest& Request) +{ + metrics::OperationTiming::Scope $(m_HttpRequests); + + std::string_view Key = Request.RelativeUri(); + if (Key == HttpZCacheRPCPrefix) + { + return HandleRpcRequest(Request); + } + HttpRequestData RequestData; + if (!HttpRequestParseRelativeUri(Key, RequestData)) + { return Request.WriteResponse(HttpResponseCode::BadRequest); // invalid URL } - CachePolicy PolicyFromURL = ParseCachePolicy(Request.GetQueryParams()); + if (RequestData.ValueContentId.has_value()) + { + ZEN_ASSERT(RequestData.Namespace.has_value()); + ZEN_ASSERT(RequestData.Bucket.has_value()); + ZEN_ASSERT(RequestData.HashKey.has_value()); + CacheRef Ref = {.Namespace = RequestData.Namespace.value(), + .BucketSegment = RequestData.Bucket.value(), + .HashKey = RequestData.HashKey.value(), + .ValueContentId = RequestData.ValueContentId.value()}; + return HandleCacheValueRequest(Request, Ref, ParseCachePolicy(Request.GetQueryParams())); + } - if (Ref.ValueContentId == IoHash::Zero) + if (RequestData.HashKey.has_value()) { - return HandleCacheRecordRequest(Request, Ref, PolicyFromURL); + ZEN_ASSERT(RequestData.Namespace.has_value()); + ZEN_ASSERT(RequestData.Bucket.has_value()); + CacheRef Ref = {.Namespace = RequestData.Namespace.value(), + .BucketSegment = RequestData.Bucket.value(), + .HashKey = RequestData.HashKey.value(), + .ValueContentId = IoHash::Zero}; + return HandleCacheRecordRequest(Request, Ref, ParseCachePolicy(Request.GetQueryParams())); } - else + + if (RequestData.Bucket.has_value()) { - return HandleCacheValueRequest(Request, Ref, PolicyFromURL); + ZEN_ASSERT(RequestData.Namespace.has_value()); + return HandleCacheBucketRequest(Request, RequestData.Namespace.value(), RequestData.Bucket.value()); } - return; + ZEN_ASSERT(RequestData.Namespace.has_value()); + return HandleCacheNamespaceRequest(Request, RequestData.Namespace.value()); } void -HttpStructuredCacheService::HandleCacheBucketRequest(HttpServerRequest& Request, std::string_view Bucket) +HttpStructuredCacheService::HandleCacheNamespaceRequest(zen::HttpServerRequest& Request, std::string_view) { switch (Request.RequestVerb()) { @@ -173,11 +274,39 @@ HttpStructuredCacheService::HandleCacheBucketRequest(HttpServerRequest& Request, break; case HttpVerb::kDelete: - // Drop bucket + // Drop namespace { - // TODO: Should add namespace to URI and handle if the namespace is missing for backwards compatability - std::string_view Namespace = ZenCacheStore::DefaultNamespace; + // if (m_CacheStore.DropNamespace(Namespace)) + // { + // return Request.WriteResponse(HttpResponseCode::OK); + // } + // else + // { + // return Request.WriteResponse(HttpResponseCode::NotFound); + // } + } + break; + default: + break; + } +} + +void +HttpStructuredCacheService::HandleCacheBucketRequest(HttpServerRequest& Request, std::string_view Namespace, std::string_view Bucket) +{ + switch (Request.RequestVerb()) + { + case HttpVerb::kHead: + case HttpVerb::kGet: + { + // Query stats + } + break; + + case HttpVerb::kDelete: + // Drop bucket + { if (m_CacheStore.DropBucket(Namespace, Bucket)) { return Request.WriteResponse(HttpResponseCode::OK); @@ -783,9 +912,8 @@ HttpStructuredCacheService::HandlePutCacheValue(zen::HttpServerRequest& Request, } bool -HttpStructuredCacheService::ValidateKeyUri(HttpServerRequest& Request, CacheRef& OutRef) +HttpStructuredCacheService::ValidateKeyUri(std::string_view Namespace, std::string_view Key, CacheRef& OutRef) { - std::string_view Key = Request.RelativeUri(); std::string_view::size_type BucketSplitOffset = Key.find_first_of('/'); if (BucketSplitOffset == std::string_view::npos) @@ -793,8 +921,7 @@ HttpStructuredCacheService::ValidateKeyUri(HttpServerRequest& Request, CacheRef& return false; } - OutRef.Namespace = ToLower(ZenCacheStore::DefaultNamespace); // TODO: Should add namespace to URI and handle if the namespace is - // missing for backwards compatability + OutRef.Namespace = ToLower(Namespace); OutRef.BucketSegment = ToLower(Key.substr(0, BucketSplitOffset)); if (!std::all_of(begin(OutRef.Namespace), end(OutRef.Namespace), [](const char c) { return std::isalnum(c); })) -- cgit v1.2.3