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
9
                                     const Http::ResponseHeaderMap& response_headers) {
21
9
  absl::btree_set<absl::string_view> vary_header_values =
22
9
      VaryHeaderUtils::getVaryValues(response_headers);
23
9
  ASSERT(!vary_header_values.empty());
24
9
  const absl::optional<std::string> vary_identifier = VaryHeaderUtils::createVaryIdentifier(
25
9
      request.varyAllowList(), vary_header_values, request.requestHeaders());
26
9
  if (!vary_identifier.has_value()) {
27
1
    return absl::nullopt;
28
1
  }
29
8
  Key varied_request_key = request.key();
30
8
  varied_request_key.add_custom_fields(vary_identifier.value());
31
8
  return varied_request_key;
32
9
}
33

            
34
class SimpleLookupContext : public LookupContext {
35
public:
36
  SimpleLookupContext(Event::Dispatcher& dispatcher, SimpleHttpCache& cache,
37
                      LookupRequest&& request)
38
216
      : dispatcher_(dispatcher), cache_(cache), request_(std::move(request)) {}
39

            
40
216
  void getHeaders(LookupHeadersCallback&& cb) override {
41
216
    auto entry = cache_.lookup(request_);
42
216
    body_ = std::move(entry.body_);
43
216
    trailers_ = std::move(entry.trailers_);
44
216
    LookupResult result = entry.response_headers_
45
216
                              ? request_.makeLookupResult(std::move(entry.response_headers_),
46
96
                                                          std::move(entry.metadata_), body_.size())
47
216
                              : LookupResult{};
48
216
    bool end_stream = body_.empty() && trailers_ == nullptr;
49
216
    dispatcher_.post([result = std::move(result), cb = std::move(cb), end_stream,
50
216
                      cancelled = cancelled_]() mutable {
51
216
      if (!*cancelled) {
52
212
        std::move(cb)(std::move(result), end_stream);
53
212
      }
54
216
    });
55
216
  }
56

            
57
43
  void getBody(const AdjustedByteRange& range, LookupBodyCallback&& cb) override {
58
43
    ASSERT(range.end() <= body_.length(), "Attempt to read past end of body.");
59
43
    auto result = std::make_unique<Buffer::OwnedImpl>(&body_[range.begin()], range.length());
60
43
    bool end_stream = trailers_ == nullptr && range.end() == body_.length();
61
43
    dispatcher_.post([result = std::move(result), cb = std::move(cb), end_stream,
62
43
                      cancelled = cancelled_]() mutable {
63
43
      if (!*cancelled) {
64
42
        std::move(cb)(std::move(result), end_stream);
65
42
      }
66
43
    });
67
43
  }
68

            
69
  // The cache must call cb with the cached trailers.
70
11
  void getTrailers(LookupTrailersCallback&& cb) override {
71
11
    ASSERT(trailers_);
72
11
    dispatcher_.post(
73
11
        [cb = std::move(cb), trailers = std::move(trailers_), cancelled = cancelled_]() mutable {
74
11
          if (!*cancelled) {
75
10
            std::move(cb)(std::move(trailers));
76
10
          }
77
11
        });
78
11
  }
79

            
80
321
  const LookupRequest& request() const { return request_; }
81
189
  void onDestroy() override { *cancelled_ = true; }
82
120
  Event::Dispatcher& dispatcher() const { return dispatcher_; }
83

            
84
private:
85
  Event::Dispatcher& dispatcher_;
86
  std::shared_ptr<bool> cancelled_ = std::make_shared<bool>(false);
87
  SimpleHttpCache& cache_;
88
  const LookupRequest request_;
89
  std::string body_;
90
  Http::ResponseTrailerMapPtr trailers_;
91
};
92

            
93
class SimpleInsertContext : public InsertContext {
94
public:
95
  SimpleInsertContext(SimpleLookupContext& lookup_context, SimpleHttpCache& cache)
96
100
      : dispatcher_(lookup_context.dispatcher()), key_(lookup_context.request().key()),
97
100
        request_headers_(lookup_context.request().requestHeaders()),
98
100
        vary_allow_list_(lookup_context.request().varyAllowList()), cache_(cache) {}
99

            
100
251
  void post(InsertCallback cb, bool result) {
101
251
    dispatcher_.post([cb = std::move(cb), result = result, cancelled = cancelled_]() mutable {
102
251
      if (!*cancelled) {
103
251
        std::move(cb)(result);
104
251
      }
105
251
    });
106
251
  }
107

            
108
  void insertHeaders(const Http::ResponseHeaderMap& response_headers,
109
                     const ResponseMetadata& metadata, InsertCallback insert_success,
110
100
                     bool end_stream) override {
111
100
    ASSERT(!committed_);
112
100
    response_headers_ = Http::createHeaderMap<Http::ResponseHeaderMapImpl>(response_headers);
113
100
    metadata_ = metadata;
114
100
    if (end_stream) {
115
13
      post(std::move(insert_success), commit());
116
87
    } else {
117
87
      post(std::move(insert_success), true);
118
87
    }
119
100
  }
120

            
121
  void insertBody(const Buffer::Instance& chunk, InsertCallback ready_for_next_chunk,
122
135
                  bool end_stream) override {
123
135
    ASSERT(!committed_);
124
135
    ASSERT(ready_for_next_chunk || end_stream);
125

            
126
135
    body_.add(chunk);
127
135
    if (end_stream) {
128
70
      post(std::move(ready_for_next_chunk), commit());
129
76
    } else {
130
65
      post(std::move(ready_for_next_chunk), true);
131
65
    }
132
135
  }
133

            
134
  void insertTrailers(const Http::ResponseTrailerMap& trailers,
135
16
                      InsertCallback insert_complete) override {
136
16
    ASSERT(!committed_);
137
16
    trailers_ = Http::createHeaderMap<Http::ResponseTrailerMapImpl>(trailers);
138
16
    post(std::move(insert_complete), commit());
139
16
  }
140

            
141
100
  void onDestroy() override { *cancelled_ = true; }
142

            
143
private:
144
99
  bool commit() {
145
99
    committed_ = true;
146
99
    if (VaryHeaderUtils::hasVary(*response_headers_)) {
147
4
      return cache_.varyInsert(key_, std::move(response_headers_), std::move(metadata_),
148
4
                               body_.toString(), request_headers_, vary_allow_list_,
149
4
                               std::move(trailers_));
150
95
    } else {
151
95
      return cache_.insert(key_, std::move(response_headers_), std::move(metadata_),
152
95
                           body_.toString(), std::move(trailers_));
153
95
    }
154
99
  }
155

            
156
  Event::Dispatcher& dispatcher_;
157
  std::shared_ptr<bool> cancelled_ = std::make_shared<bool>(false);
158
  Key key_;
159
  const Http::RequestHeaderMap& request_headers_;
160
  const VaryAllowList& vary_allow_list_;
161
  Http::ResponseHeaderMapPtr response_headers_;
162
  ResponseMetadata metadata_;
163
  SimpleHttpCache& cache_;
164
  Buffer::OwnedImpl body_;
165
  bool committed_ = false;
166
  Http::ResponseTrailerMapPtr trailers_;
167
};
168
} // namespace
169

            
170
LookupContextPtr SimpleHttpCache::makeLookupContext(LookupRequest&& request,
171
216
                                                    Http::StreamFilterCallbacks& callbacks) {
172
216
  return std::make_unique<SimpleLookupContext>(callbacks.dispatcher(), *this, std::move(request));
173
216
}
174

            
175
void SimpleHttpCache::updateHeaders(const LookupContext& lookup_context,
176
                                    const Http::ResponseHeaderMap& response_headers,
177
                                    const ResponseMetadata& metadata,
178
20
                                    UpdateHeadersCallback on_complete) {
179
20
  const auto& simple_lookup_context = static_cast<const SimpleLookupContext&>(lookup_context);
180
20
  const Key& key = simple_lookup_context.request().key();
181
20
  absl::WriterMutexLock lock(mutex_);
182
20
  auto iter = map_.find(key);
183
20
  auto post_complete = [on_complete = std::move(on_complete),
184
20
                        &dispatcher = simple_lookup_context.dispatcher()](bool result) mutable {
185
20
    dispatcher.post([on_complete = std::move(on_complete), result]() mutable {
186
20
      std::move(on_complete)(result);
187
20
    });
188
20
  };
189
20
  if (iter == map_.end() || !iter->second.response_headers_) {
190
1
    std::move(post_complete)(false);
191
1
    return;
192
1
  }
193
19
  if (VaryHeaderUtils::hasVary(*iter->second.response_headers_)) {
194
1
    absl::optional<Key> varied_key =
195
1
        variedRequestKey(simple_lookup_context.request(), *iter->second.response_headers_);
196
1
    if (!varied_key.has_value()) {
197
      std::move(post_complete)(false);
198
      return;
199
    }
200
1
    iter = map_.find(varied_key.value());
201
1
    if (iter == map_.end() || !iter->second.response_headers_) {
202
      std::move(post_complete)(false);
203
      return;
204
    }
205
1
  }
206
19
  Entry& entry = iter->second;
207

            
208
19
  applyHeaderUpdate(response_headers, *entry.response_headers_);
209
19
  entry.metadata_ = metadata;
210
19
  std::move(post_complete)(true);
211
19
}
212

            
213
216
SimpleHttpCache::Entry SimpleHttpCache::lookup(const LookupRequest& request) {
214
216
  absl::ReaderMutexLock lock(mutex_);
215
216
  auto iter = map_.find(request.key());
216
216
  if (iter == map_.end()) {
217
118
    return Entry{};
218
118
  }
219
98
  ASSERT(iter->second.response_headers_);
220

            
221
98
  if (VaryHeaderUtils::hasVary(*iter->second.response_headers_)) {
222
8
    return varyLookup(request, iter->second.response_headers_);
223
90
  } else {
224
90
    Http::ResponseTrailerMapPtr trailers_map;
225
90
    if (iter->second.trailers_) {
226
11
      trailers_map = Http::createHeaderMap<Http::ResponseTrailerMapImpl>(*iter->second.trailers_);
227
11
    }
228
90
    return SimpleHttpCache::Entry{
229
90
        Http::createHeaderMap<Http::ResponseHeaderMapImpl>(*iter->second.response_headers_),
230
90
        iter->second.metadata_, iter->second.body_, std::move(trailers_map)};
231
90
  }
232
98
}
233

            
234
bool SimpleHttpCache::insert(const Key& key, Http::ResponseHeaderMapPtr&& response_headers,
235
                             ResponseMetadata&& metadata, std::string&& body,
236
95
                             Http::ResponseTrailerMapPtr&& trailers) {
237
95
  absl::WriterMutexLock lock(mutex_);
238
95
  map_[key] = SimpleHttpCache::Entry{std::move(response_headers), std::move(metadata),
239
95
                                     std::move(body), std::move(trailers)};
240
95
  return true;
241
95
}
242

            
243
SimpleHttpCache::Entry
244
SimpleHttpCache::varyLookup(const LookupRequest& request,
245
8
                            const Http::ResponseHeaderMapPtr& response_headers) {
246
  // This method should be called from lookup, which holds the mutex for reading.
247
8
  mutex_.AssertReaderHeld();
248

            
249
8
  absl::optional<Key> varied_key = variedRequestKey(request, *response_headers);
250
8
  if (!varied_key.has_value()) {
251
1
    return SimpleHttpCache::Entry{};
252
1
  }
253
7
  Key& varied_request_key = varied_key.value();
254

            
255
7
  auto iter = map_.find(varied_request_key);
256
7
  if (iter == map_.end()) {
257
1
    return SimpleHttpCache::Entry{};
258
1
  }
259
6
  ASSERT(iter->second.response_headers_);
260
6
  Http::ResponseTrailerMapPtr trailers_map;
261
6
  if (iter->second.trailers_) {
262
2
    trailers_map = Http::createHeaderMap<Http::ResponseTrailerMapImpl>(*iter->second.trailers_);
263
2
  }
264

            
265
6
  return SimpleHttpCache::Entry{
266
6
      Http::createHeaderMap<Http::ResponseHeaderMapImpl>(*iter->second.response_headers_),
267
6
      iter->second.metadata_, iter->second.body_, std::move(trailers_map)};
268
7
}
269

            
270
bool SimpleHttpCache::varyInsert(const Key& request_key,
271
                                 Http::ResponseHeaderMapPtr&& response_headers,
272
                                 ResponseMetadata&& metadata, std::string&& body,
273
                                 const Http::RequestHeaderMap& request_headers,
274
                                 const VaryAllowList& vary_allow_list,
275
4
                                 Http::ResponseTrailerMapPtr&& trailers) {
276
4
  absl::WriterMutexLock lock(mutex_);
277

            
278
4
  absl::btree_set<absl::string_view> vary_header_values =
279
4
      VaryHeaderUtils::getVaryValues(*response_headers);
280
4
  ASSERT(!vary_header_values.empty());
281

            
282
  // Insert the varied response.
283
4
  Key varied_request_key = request_key;
284
4
  const absl::optional<std::string> vary_identifier =
285
4
      VaryHeaderUtils::createVaryIdentifier(vary_allow_list, vary_header_values, request_headers);
286
4
  if (!vary_identifier.has_value()) {
287
    // Skip the insert if we are unable to create a vary key.
288
1
    return false;
289
1
  }
290

            
291
3
  varied_request_key.add_custom_fields(vary_identifier.value());
292
3
  map_[varied_request_key] = SimpleHttpCache::Entry{
293
3
      std::move(response_headers), std::move(metadata), std::move(body), std::move(trailers)};
294

            
295
  // Add a special entry to flag that this request generates varied responses.
296
3
  auto iter = map_.find(request_key);
297
3
  if (iter == map_.end()) {
298
2
    Envoy::Http::ResponseHeaderMapPtr vary_only_map =
299
2
        Envoy::Http::createHeaderMap<Envoy::Http::ResponseHeaderMapImpl>({});
300
2
    vary_only_map->setCopy(Envoy::Http::CustomHeaders::get().Vary,
301
2
                           absl::StrJoin(vary_header_values, ","));
302
    // TODO(cbdm): In a cache that evicts entries, we could maintain a list of the "varykey"s that
303
    // we have inserted as the body for this first lookup. This way, we would know which keys we
304
    // have inserted for that resource. For the first entry simply use vary_identifier as the
305
    // entry_list; for future entries append vary_identifier to existing list.
306
2
    std::string entry_list;
307
2
    map_[request_key] =
308
2
        SimpleHttpCache::Entry{std::move(vary_only_map), {}, std::move(entry_list), {}};
309
2
  }
310
3
  return true;
311
4
}
312

            
313
InsertContextPtr SimpleHttpCache::makeInsertContext(LookupContextPtr&& lookup_context,
314
100
                                                    Http::StreamFilterCallbacks&) {
315
100
  ASSERT(lookup_context != nullptr);
316
100
  auto ret = std::make_unique<SimpleInsertContext>(
317
100
      dynamic_cast<SimpleLookupContext&>(*lookup_context), *this);
318
100
  lookup_context->onDestroy();
319
100
  return ret;
320
100
}
321

            
322
constexpr absl::string_view Name = "envoy.extensions.http.cache.simple";
323

            
324
1
CacheInfo SimpleHttpCache::cacheInfo() const {
325
1
  CacheInfo cache_info;
326
1
  cache_info.name_ = Name;
327
1
  return cache_info;
328
1
}
329

            
330
SINGLETON_MANAGER_REGISTRATION(simple_http_cache_singleton);
331

            
332
class SimpleHttpCacheFactory : public HttpCacheFactory {
333
public:
334
  // From UntypedFactory
335
69
  std::string name() const override { return std::string(Name); }
336
  // From TypedFactory
337
5
  ProtobufTypes::MessagePtr createEmptyConfigProto() override {
338
5
    return std::make_unique<
339
5
        envoy::extensions::http::cache::simple_http_cache::v3::SimpleHttpCacheConfig>();
340
5
  }
341
  // From HttpCacheFactory
342
  std::shared_ptr<HttpCache>
343
  getCache(const envoy::extensions::filters::http::cache::v3::CacheConfig&,
344
62
           Server::Configuration::FactoryContext& context) override {
345
62
    return context.serverFactoryContext().singletonManager().getTyped<SimpleHttpCache>(
346
62
        SINGLETON_MANAGER_REGISTERED_NAME(simple_http_cache_singleton), &createCache);
347
62
  }
348

            
349
private:
350
62
  static std::shared_ptr<Singleton::Instance> createCache() {
351
62
    return std::make_shared<SimpleHttpCache>();
352
62
  }
353
};
354

            
355
static Registry::RegisterFactory<SimpleHttpCacheFactory, HttpCacheFactory> register_;
356

            
357
} // namespace Cache
358
} // namespace HttpFilters
359
} // namespace Extensions
360
} // namespace Envoy