LCOV - code coverage report
Current view: top level - source/extensions/http/cache/simple_http_cache - simple_http_cache.cc (source / functions) Hit Total Coverage
Test: coverage.dat Lines: 5 193 2.6 %
Date: 2024-01-05 06:35:25 Functions: 2 25 8.0 %

          Line data    Source code
       1             : #include "source/extensions/http/cache/simple_http_cache/simple_http_cache.h"
       2             : 
       3             : #include "envoy/extensions/http/cache/simple_http_cache/v3/config.pb.h"
       4             : #include "envoy/registry/registry.h"
       5             : 
       6             : #include "source/common/buffer/buffer_impl.h"
       7             : #include "source/common/http/header_map_impl.h"
       8             : 
       9             : namespace Envoy {
      10             : namespace Extensions {
      11             : namespace HttpFilters {
      12             : namespace Cache {
      13             : namespace {
      14             : 
      15             : // Returns a Key with the vary header added to custom_fields.
      16             : // It is an error to call this with headers that don't include vary.
      17             : // Returns nullopt if the vary headers in the response are not
      18             : // compatible with the VaryAllowList in the LookupRequest.
      19             : absl::optional<Key> variedRequestKey(const LookupRequest& request,
      20           0 :                                      const Http::ResponseHeaderMap& response_headers) {
      21           0 :   absl::btree_set<absl::string_view> vary_header_values =
      22           0 :       VaryHeaderUtils::getVaryValues(response_headers);
      23           0 :   ASSERT(!vary_header_values.empty());
      24           0 :   const absl::optional<std::string> vary_identifier = VaryHeaderUtils::createVaryIdentifier(
      25           0 :       request.varyAllowList(), vary_header_values, request.requestHeaders());
      26           0 :   if (!vary_identifier.has_value()) {
      27           0 :     return absl::nullopt;
      28           0 :   }
      29           0 :   Key varied_request_key = request.key();
      30           0 :   varied_request_key.add_custom_fields(vary_identifier.value());
      31           0 :   return varied_request_key;
      32           0 : }
      33             : 
      34             : class SimpleLookupContext : public LookupContext {
      35             : public:
      36             :   SimpleLookupContext(SimpleHttpCache& cache, LookupRequest&& request)
      37           0 :       : cache_(cache), request_(std::move(request)) {}
      38             : 
      39           0 :   void getHeaders(LookupHeadersCallback&& cb) override {
      40           0 :     auto entry = cache_.lookup(request_);
      41           0 :     body_ = std::move(entry.body_);
      42           0 :     trailers_ = std::move(entry.trailers_);
      43           0 :     cb(entry.response_headers_ ? request_.makeLookupResult(std::move(entry.response_headers_),
      44           0 :                                                            std::move(entry.metadata_), body_.size(),
      45           0 :                                                            trailers_ != nullptr)
      46           0 :                                : LookupResult{});
      47           0 :   }
      48             : 
      49           0 :   void getBody(const AdjustedByteRange& range, LookupBodyCallback&& cb) override {
      50           0 :     ASSERT(range.end() <= body_.length(), "Attempt to read past end of body.");
      51           0 :     cb(std::make_unique<Buffer::OwnedImpl>(&body_[range.begin()], range.length()));
      52           0 :   }
      53             : 
      54             :   // The cache must call cb with the cached trailers.
      55           0 :   void getTrailers(LookupTrailersCallback&& cb) override {
      56           0 :     ASSERT(trailers_);
      57           0 :     cb(std::move(trailers_));
      58           0 :   }
      59             : 
      60           0 :   const LookupRequest& request() const { return request_; }
      61           0 :   void onDestroy() override {}
      62             : 
      63             : private:
      64             :   SimpleHttpCache& cache_;
      65             :   const LookupRequest request_;
      66             :   std::string body_;
      67             :   Http::ResponseTrailerMapPtr trailers_;
      68             : };
      69             : 
      70             : class SimpleInsertContext : public InsertContext {
      71             : public:
      72             :   SimpleInsertContext(LookupContext& lookup_context, SimpleHttpCache& cache)
      73             :       : key_(dynamic_cast<SimpleLookupContext&>(lookup_context).request().key()),
      74             :         request_headers_(
      75             :             dynamic_cast<SimpleLookupContext&>(lookup_context).request().requestHeaders()),
      76             :         vary_allow_list_(
      77             :             dynamic_cast<SimpleLookupContext&>(lookup_context).request().varyAllowList()),
      78           0 :         cache_(cache) {}
      79             : 
      80             :   void insertHeaders(const Http::ResponseHeaderMap& response_headers,
      81             :                      const ResponseMetadata& metadata, InsertCallback insert_success,
      82           0 :                      bool end_stream) override {
      83           0 :     ASSERT(!committed_);
      84           0 :     response_headers_ = Http::createHeaderMap<Http::ResponseHeaderMapImpl>(response_headers);
      85           0 :     metadata_ = metadata;
      86           0 :     if (end_stream) {
      87           0 :       insert_success(commit());
      88           0 :     } else {
      89           0 :       insert_success(true);
      90           0 :     }
      91           0 :   }
      92             : 
      93             :   void insertBody(const Buffer::Instance& chunk, InsertCallback ready_for_next_chunk,
      94           0 :                   bool end_stream) override {
      95           0 :     ASSERT(!committed_);
      96           0 :     ASSERT(ready_for_next_chunk || end_stream);
      97             : 
      98           0 :     body_.add(chunk);
      99           0 :     if (end_stream) {
     100           0 :       ready_for_next_chunk(commit());
     101           0 :     } else {
     102           0 :       ready_for_next_chunk(true);
     103           0 :     }
     104           0 :   }
     105             : 
     106             :   void insertTrailers(const Http::ResponseTrailerMap& trailers,
     107           0 :                       InsertCallback insert_complete) override {
     108           0 :     ASSERT(!committed_);
     109           0 :     trailers_ = Http::createHeaderMap<Http::ResponseTrailerMapImpl>(trailers);
     110           0 :     insert_complete(commit());
     111           0 :   }
     112             : 
     113           0 :   void onDestroy() override {}
     114             : 
     115             : private:
     116           0 :   bool commit() {
     117           0 :     committed_ = true;
     118           0 :     if (VaryHeaderUtils::hasVary(*response_headers_)) {
     119           0 :       return cache_.varyInsert(key_, std::move(response_headers_), std::move(metadata_),
     120           0 :                                body_.toString(), request_headers_, vary_allow_list_,
     121           0 :                                std::move(trailers_));
     122           0 :     } else {
     123           0 :       return cache_.insert(key_, std::move(response_headers_), std::move(metadata_),
     124           0 :                            body_.toString(), std::move(trailers_));
     125           0 :     }
     126           0 :   }
     127             : 
     128             :   Key key_;
     129             :   const Http::RequestHeaderMap& request_headers_;
     130             :   const VaryAllowList& vary_allow_list_;
     131             :   Http::ResponseHeaderMapPtr response_headers_;
     132             :   ResponseMetadata metadata_;
     133             :   SimpleHttpCache& cache_;
     134             :   Buffer::OwnedImpl body_;
     135             :   bool committed_ = false;
     136             :   Http::ResponseTrailerMapPtr trailers_;
     137             : };
     138             : } // namespace
     139             : 
     140             : LookupContextPtr SimpleHttpCache::makeLookupContext(LookupRequest&& request,
     141           0 :                                                     Http::StreamDecoderFilterCallbacks&) {
     142           0 :   return std::make_unique<SimpleLookupContext>(*this, std::move(request));
     143           0 : }
     144             : 
     145             : void SimpleHttpCache::updateHeaders(const LookupContext& lookup_context,
     146             :                                     const Http::ResponseHeaderMap& response_headers,
     147             :                                     const ResponseMetadata& metadata,
     148           0 :                                     std::function<void(bool)> on_complete) {
     149           0 :   const auto& simple_lookup_context = static_cast<const SimpleLookupContext&>(lookup_context);
     150           0 :   const Key& key = simple_lookup_context.request().key();
     151           0 :   absl::WriterMutexLock lock(&mutex_);
     152           0 :   auto iter = map_.find(key);
     153           0 :   if (iter == map_.end() || !iter->second.response_headers_) {
     154           0 :     on_complete(false);
     155           0 :     return;
     156           0 :   }
     157           0 :   if (VaryHeaderUtils::hasVary(*iter->second.response_headers_)) {
     158           0 :     absl::optional<Key> varied_key =
     159           0 :         variedRequestKey(simple_lookup_context.request(), *iter->second.response_headers_);
     160           0 :     if (!varied_key.has_value()) {
     161           0 :       on_complete(false);
     162           0 :       return;
     163           0 :     }
     164           0 :     iter = map_.find(varied_key.value());
     165           0 :     if (iter == map_.end() || !iter->second.response_headers_) {
     166           0 :       on_complete(false);
     167           0 :       return;
     168           0 :     }
     169           0 :   }
     170           0 :   Entry& entry = iter->second;
     171             : 
     172           0 :   applyHeaderUpdate(response_headers, *entry.response_headers_);
     173           0 :   entry.metadata_ = metadata;
     174           0 :   on_complete(true);
     175           0 : }
     176             : 
     177           0 : SimpleHttpCache::Entry SimpleHttpCache::lookup(const LookupRequest& request) {
     178           0 :   absl::ReaderMutexLock lock(&mutex_);
     179           0 :   auto iter = map_.find(request.key());
     180           0 :   if (iter == map_.end()) {
     181           0 :     return Entry{};
     182           0 :   }
     183           0 :   ASSERT(iter->second.response_headers_);
     184             : 
     185           0 :   if (VaryHeaderUtils::hasVary(*iter->second.response_headers_)) {
     186           0 :     return varyLookup(request, iter->second.response_headers_);
     187           0 :   } else {
     188           0 :     Http::ResponseTrailerMapPtr trailers_map;
     189           0 :     if (iter->second.trailers_) {
     190           0 :       trailers_map = Http::createHeaderMap<Http::ResponseTrailerMapImpl>(*iter->second.trailers_);
     191           0 :     }
     192           0 :     return SimpleHttpCache::Entry{
     193           0 :         Http::createHeaderMap<Http::ResponseHeaderMapImpl>(*iter->second.response_headers_),
     194           0 :         iter->second.metadata_, iter->second.body_, std::move(trailers_map)};
     195           0 :   }
     196           0 : }
     197             : 
     198             : bool SimpleHttpCache::insert(const Key& key, Http::ResponseHeaderMapPtr&& response_headers,
     199             :                              ResponseMetadata&& metadata, std::string&& body,
     200           0 :                              Http::ResponseTrailerMapPtr&& trailers) {
     201           0 :   absl::WriterMutexLock lock(&mutex_);
     202           0 :   map_[key] = SimpleHttpCache::Entry{std::move(response_headers), std::move(metadata),
     203           0 :                                      std::move(body), std::move(trailers)};
     204           0 :   return true;
     205           0 : }
     206             : 
     207             : SimpleHttpCache::Entry
     208             : SimpleHttpCache::varyLookup(const LookupRequest& request,
     209           0 :                             const Http::ResponseHeaderMapPtr& response_headers) {
     210             :   // This method should be called from lookup, which holds the mutex for reading.
     211           0 :   mutex_.AssertReaderHeld();
     212             : 
     213           0 :   absl::optional<Key> varied_key = variedRequestKey(request, *response_headers);
     214           0 :   if (!varied_key.has_value()) {
     215           0 :     return SimpleHttpCache::Entry{};
     216           0 :   }
     217           0 :   Key& varied_request_key = varied_key.value();
     218             : 
     219           0 :   auto iter = map_.find(varied_request_key);
     220           0 :   if (iter == map_.end()) {
     221           0 :     return SimpleHttpCache::Entry{};
     222           0 :   }
     223           0 :   ASSERT(iter->second.response_headers_);
     224           0 :   Http::ResponseTrailerMapPtr trailers_map;
     225           0 :   if (iter->second.trailers_) {
     226           0 :     trailers_map = Http::createHeaderMap<Http::ResponseTrailerMapImpl>(*iter->second.trailers_);
     227           0 :   }
     228             : 
     229           0 :   return SimpleHttpCache::Entry{
     230           0 :       Http::createHeaderMap<Http::ResponseHeaderMapImpl>(*iter->second.response_headers_),
     231           0 :       iter->second.metadata_, iter->second.body_, std::move(trailers_map)};
     232           0 : }
     233             : 
     234             : bool SimpleHttpCache::varyInsert(const Key& request_key,
     235             :                                  Http::ResponseHeaderMapPtr&& response_headers,
     236             :                                  ResponseMetadata&& metadata, std::string&& body,
     237             :                                  const Http::RequestHeaderMap& request_headers,
     238             :                                  const VaryAllowList& vary_allow_list,
     239           0 :                                  Http::ResponseTrailerMapPtr&& trailers) {
     240           0 :   absl::WriterMutexLock lock(&mutex_);
     241             : 
     242           0 :   absl::btree_set<absl::string_view> vary_header_values =
     243           0 :       VaryHeaderUtils::getVaryValues(*response_headers);
     244           0 :   ASSERT(!vary_header_values.empty());
     245             : 
     246             :   // Insert the varied response.
     247           0 :   Key varied_request_key = request_key;
     248           0 :   const absl::optional<std::string> vary_identifier =
     249           0 :       VaryHeaderUtils::createVaryIdentifier(vary_allow_list, vary_header_values, request_headers);
     250           0 :   if (!vary_identifier.has_value()) {
     251             :     // Skip the insert if we are unable to create a vary key.
     252           0 :     return false;
     253           0 :   }
     254             : 
     255           0 :   varied_request_key.add_custom_fields(vary_identifier.value());
     256           0 :   map_[varied_request_key] = SimpleHttpCache::Entry{
     257           0 :       std::move(response_headers), std::move(metadata), std::move(body), std::move(trailers)};
     258             : 
     259             :   // Add a special entry to flag that this request generates varied responses.
     260           0 :   auto iter = map_.find(request_key);
     261           0 :   if (iter == map_.end()) {
     262           0 :     Envoy::Http::ResponseHeaderMapPtr vary_only_map =
     263           0 :         Envoy::Http::createHeaderMap<Envoy::Http::ResponseHeaderMapImpl>({});
     264           0 :     vary_only_map->setCopy(Envoy::Http::CustomHeaders::get().Vary,
     265           0 :                            absl::StrJoin(vary_header_values, ","));
     266             :     // TODO(cbdm): In a cache that evicts entries, we could maintain a list of the "varykey"s that
     267             :     // we have inserted as the body for this first lookup. This way, we would know which keys we
     268             :     // have inserted for that resource. For the first entry simply use vary_identifier as the
     269             :     // entry_list; for future entries append vary_identifier to existing list.
     270           0 :     std::string entry_list;
     271           0 :     map_[request_key] =
     272           0 :         SimpleHttpCache::Entry{std::move(vary_only_map), {}, std::move(entry_list), {}};
     273           0 :   }
     274           0 :   return true;
     275           0 : }
     276             : 
     277             : InsertContextPtr SimpleHttpCache::makeInsertContext(LookupContextPtr&& lookup_context,
     278           0 :                                                     Http::StreamEncoderFilterCallbacks&) {
     279           0 :   ASSERT(lookup_context != nullptr);
     280           0 :   return std::make_unique<SimpleInsertContext>(*lookup_context, *this);
     281           0 : }
     282             : 
     283             : constexpr absl::string_view Name = "envoy.extensions.http.cache.simple";
     284             : 
     285           0 : CacheInfo SimpleHttpCache::cacheInfo() const {
     286           0 :   CacheInfo cache_info;
     287           0 :   cache_info.name_ = Name;
     288           0 :   return cache_info;
     289           0 : }
     290             : 
     291             : SINGLETON_MANAGER_REGISTRATION(simple_http_cache_singleton);
     292             : 
     293             : class SimpleHttpCacheFactory : public HttpCacheFactory {
     294             : public:
     295             :   // From UntypedFactory
     296          38 :   std::string name() const override { return std::string(Name); }
     297             :   // From TypedFactory
     298           1 :   ProtobufTypes::MessagePtr createEmptyConfigProto() override {
     299           1 :     return std::make_unique<
     300           1 :         envoy::extensions::http::cache::simple_http_cache::v3::SimpleHttpCacheConfig>();
     301           1 :   }
     302             :   // From HttpCacheFactory
     303             :   std::shared_ptr<HttpCache>
     304             :   getCache(const envoy::extensions::filters::http::cache::v3::CacheConfig&,
     305           0 :            Server::Configuration::FactoryContext& context) override {
     306           0 :     return context.serverFactoryContext().singletonManager().getTyped<SimpleHttpCache>(
     307           0 :         SINGLETON_MANAGER_REGISTERED_NAME(simple_http_cache_singleton), &createCache);
     308           0 :   }
     309             : 
     310             : private:
     311           0 :   static std::shared_ptr<Singleton::Instance> createCache() {
     312           0 :     return std::make_shared<SimpleHttpCache>();
     313           0 :   }
     314             : };
     315             : 
     316             : static Registry::RegisterFactory<SimpleHttpCacheFactory, HttpCacheFactory> register_;
     317             : 
     318             : } // namespace Cache
     319             : } // namespace HttpFilters
     320             : } // namespace Extensions
     321             : } // namespace Envoy

Generated by: LCOV version 1.15