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
|