Line data Source code
1 : #include "source/common/http/header_map_impl.h"
2 :
3 : #include <cstdint>
4 : #include <list>
5 : #include <memory>
6 : #include <string>
7 :
8 : #include "envoy/http/header_map.h"
9 :
10 : #include "source/common/common/assert.h"
11 : #include "source/common/common/dump_state_utils.h"
12 : #include "source/common/common/empty_string.h"
13 : #include "source/common/runtime/runtime_features.h"
14 : #include "source/common/singleton/const_singleton.h"
15 :
16 : #include "absl/strings/match.h"
17 :
18 : namespace Envoy {
19 : namespace Http {
20 :
21 : bool HeaderStringValidator::disable_validation_for_tests_ = false;
22 :
23 : namespace {
24 :
25 : constexpr absl::string_view DelimiterForInlineHeaders{","};
26 : constexpr absl::string_view DelimiterForInlineCookies{"; "};
27 : const static int kMinHeadersForLazyMap = 3; // Optimal hard-coded value based on benchmarks.
28 :
29 1385 : absl::string_view delimiterByHeader(const LowerCaseString& key) {
30 1385 : if (key == Http::Headers::get().Cookie) {
31 0 : return DelimiterForInlineCookies;
32 0 : }
33 1385 : return DelimiterForInlineHeaders;
34 1385 : }
35 :
36 : } // namespace
37 :
38 : // Initialize as a Type::Reference
39 : HeaderString::HeaderString(const LowerCaseString& ref_value) noexcept
40 19455 : : UnionStringBase(absl::string_view(ref_value.get().c_str(), ref_value.get().size())) {
41 19455 : ASSERT(valid());
42 19455 : }
43 :
44 0 : HeaderString::HeaderString(UnionString&& move_value) noexcept {
45 0 : buffer_ = std::move(move_value.storage());
46 0 : move_value.clear();
47 0 : ASSERT(valid());
48 0 : }
49 :
50 : // Specialization needed for HeaderMapImpl::HeaderList::insert() when key is LowerCaseString.
51 : // A fully specialized template must be defined once in the program, hence this may not be in
52 : // a header file.
53 17569 : template <> bool HeaderMapImpl::HeaderList::isPseudoHeader(const LowerCaseString& key) {
54 17569 : return key.get().c_str()[0] == ':';
55 17569 : }
56 :
57 2633 : bool HeaderMapImpl::HeaderList::maybeMakeMap() {
58 2633 : if (lazy_map_.empty()) {
59 2174 : if (headers_.size() < kMinHeadersForLazyMap) {
60 1370 : return false;
61 1370 : }
62 : // Add all entries from the list into the map.
63 70166 : for (auto node = headers_.begin(); node != headers_.end(); ++node) {
64 69362 : HeaderNodeVector& v = lazy_map_[node->key().getStringView()];
65 69362 : v.push_back(node);
66 69362 : }
67 804 : }
68 1263 : return true;
69 2633 : }
70 :
71 660 : size_t HeaderMapImpl::HeaderList::remove(absl::string_view key) {
72 660 : size_t removed_bytes = 0;
73 660 : if (maybeMakeMap()) {
74 156 : auto iter = lazy_map_.find(key);
75 156 : if (iter != lazy_map_.end()) {
76 : // Erase from the map, and all same key entries from the list.
77 76 : HeaderNodeVector header_nodes = std::move(iter->second);
78 76 : lazy_map_.erase(iter);
79 113 : for (const HeaderNode& node : header_nodes) {
80 113 : ASSERT(node->key() == key);
81 113 : removed_bytes += node->key().size() + node->value().size();
82 113 : erase(node, false /* remove_from_map */);
83 113 : }
84 76 : }
85 544 : } else {
86 : // Erase all same key entries from the list.
87 1051 : for (auto i = headers_.begin(); i != headers_.end();) {
88 547 : if (i->key() == key) {
89 128 : removed_bytes += i->key().size() + i->value().size();
90 128 : i = erase(i, false /* remove_from_map */);
91 420 : } else {
92 419 : ++i;
93 419 : }
94 547 : }
95 504 : }
96 660 : return removed_bytes;
97 660 : }
98 :
99 7590 : HeaderMapImpl::HeaderEntryImpl::HeaderEntryImpl(const LowerCaseString& key) : key_(key) {}
100 :
101 : HeaderMapImpl::HeaderEntryImpl::HeaderEntryImpl(const LowerCaseString& key, HeaderString&& value)
102 9979 : : key_(key), value_(std::move(value)) {}
103 :
104 : HeaderMapImpl::HeaderEntryImpl::HeaderEntryImpl(HeaderString&& key, HeaderString&& value)
105 73632 : : key_(std::move(key)), value_(std::move(value)) {}
106 :
107 0 : void HeaderMapImpl::HeaderEntryImpl::value(absl::string_view value) { value_.setCopy(value); }
108 :
109 0 : void HeaderMapImpl::HeaderEntryImpl::value(uint64_t value) { value_.setInteger(value); }
110 :
111 0 : void HeaderMapImpl::HeaderEntryImpl::value(const HeaderEntry& header) {
112 0 : value(header.value().getStringView());
113 0 : }
114 :
115 33 : template <> HeaderMapImpl::StaticLookupTable<RequestHeaderMap>::StaticLookupTable() {
116 33 : #define REGISTER_DEFAULT_REQUEST_HEADER(name) \
117 1584 : CustomInlineHeaderRegistry::registerInlineHeader<RequestHeaderMap::header_map_type>( \
118 1584 : Headers::get().name);
119 1188 : INLINE_REQ_HEADERS(REGISTER_DEFAULT_REQUEST_HEADER)
120 396 : INLINE_REQ_RESP_HEADERS(REGISTER_DEFAULT_REQUEST_HEADER)
121 :
122 33 : finalizeTable();
123 :
124 : // Special case where we map a legacy host header to :authority.
125 33 : const auto handle =
126 33 : CustomInlineHeaderRegistry::getInlineHeader<RequestHeaderMap::header_map_type>(
127 33 : Headers::get().Host);
128 355 : add(Headers::get().HostLegacy.get().c_str(), [handle](HeaderMapImpl& h) -> StaticLookupResponse {
129 335 : return {&h.inlineHeaders()[handle.value().it_->second], &handle.value().it_->first};
130 335 : });
131 33 : }
132 :
133 19 : template <> HeaderMapImpl::StaticLookupTable<RequestTrailerMap>::StaticLookupTable() {
134 19 : finalizeTable();
135 19 : }
136 :
137 26 : template <> HeaderMapImpl::StaticLookupTable<ResponseHeaderMap>::StaticLookupTable() {
138 26 : #define REGISTER_RESPONSE_HEADER(name) \
139 624 : CustomInlineHeaderRegistry::registerInlineHeader<ResponseHeaderMap::header_map_type>( \
140 624 : Headers::get().name);
141 260 : INLINE_RESP_HEADERS(REGISTER_RESPONSE_HEADER)
142 312 : INLINE_REQ_RESP_HEADERS(REGISTER_RESPONSE_HEADER)
143 52 : INLINE_RESP_HEADERS_TRAILERS(REGISTER_RESPONSE_HEADER)
144 :
145 26 : finalizeTable();
146 26 : }
147 :
148 23 : template <> HeaderMapImpl::StaticLookupTable<ResponseTrailerMap>::StaticLookupTable() {
149 23 : #define REGISTER_RESPONSE_TRAILER(name) \
150 46 : CustomInlineHeaderRegistry::registerInlineHeader<ResponseTrailerMap::header_map_type>( \
151 46 : Headers::get().name);
152 46 : INLINE_RESP_HEADERS_TRAILERS(REGISTER_RESPONSE_TRAILER)
153 :
154 23 : finalizeTable();
155 23 : }
156 :
157 : uint64_t HeaderMapImpl::appendToHeader(HeaderString& header, absl::string_view data,
158 1574 : absl::string_view delimiter) {
159 1574 : if (data.empty()) {
160 952 : return 0;
161 952 : }
162 622 : uint64_t byte_size = 0;
163 622 : if (!header.empty()) {
164 422 : header.append(delimiter.data(), delimiter.size());
165 422 : byte_size += delimiter.size();
166 422 : }
167 622 : header.append(data.data(), data.size());
168 622 : return data.size() + byte_size;
169 1574 : }
170 :
171 7908 : void HeaderMapImpl::updateSize(uint64_t from_size, uint64_t to_size) {
172 7908 : ASSERT(cached_byte_size_ >= from_size);
173 7908 : cached_byte_size_ -= from_size;
174 7908 : cached_byte_size_ += to_size;
175 7908 : }
176 :
177 93592 : void HeaderMapImpl::addSize(uint64_t size) { cached_byte_size_ += size; }
178 :
179 1762 : void HeaderMapImpl::subtractSize(uint64_t size) {
180 1762 : ASSERT(cached_byte_size_ >= size);
181 1762 : cached_byte_size_ -= size;
182 1762 : }
183 :
184 1150 : void HeaderMapImpl::copyFrom(HeaderMap& lhs, const HeaderMap& header_map) {
185 3756 : header_map.iterate([&lhs](const HeaderEntry& header) -> HeaderMap::Iterate {
186 : // TODO(mattklein123) PERF: Avoid copying here if not necessary.
187 3626 : HeaderString key_string;
188 3626 : key_string.setCopy(header.key().getStringView());
189 3626 : HeaderString value_string;
190 3626 : value_string.setCopy(header.value().getStringView());
191 :
192 3626 : lhs.addViaMove(std::move(key_string), std::move(value_string));
193 3626 : return HeaderMap::Iterate::Continue;
194 3626 : });
195 1150 : }
196 :
197 : namespace {
198 :
199 : // This is currently only used in tests and is not optimized for performance.
200 : HeaderMap::ConstIterateCb
201 0 : collectAllHeaders(std::vector<std::pair<absl::string_view, absl::string_view>>* dest) {
202 0 : return [dest](const HeaderEntry& header) -> HeaderMap::Iterate {
203 0 : dest->push_back(std::make_pair(header.key().getStringView(), header.value().getStringView()));
204 0 : return HeaderMap::Iterate::Continue;
205 0 : };
206 0 : };
207 :
208 : } // namespace
209 :
210 : // This is currently only used in tests and is not optimized for performance.
211 0 : bool HeaderMapImpl::operator==(const HeaderMap& rhs) const {
212 0 : if (size() != rhs.size()) {
213 0 : return false;
214 0 : }
215 :
216 0 : std::vector<std::pair<absl::string_view, absl::string_view>> rhs_headers;
217 0 : rhs_headers.reserve(rhs.size());
218 0 : rhs.iterate(collectAllHeaders(&rhs_headers));
219 :
220 0 : auto i = headers_.begin();
221 0 : auto j = rhs_headers.begin();
222 0 : for (; i != headers_.end(); ++i, ++j) {
223 0 : if (i->key() != j->first || i->value() != j->second) {
224 0 : return false;
225 0 : }
226 0 : }
227 :
228 0 : return true;
229 0 : }
230 :
231 0 : bool HeaderMapImpl::operator!=(const HeaderMap& rhs) const { return !operator==(rhs); }
232 :
233 84965 : void HeaderMapImpl::insertByKey(HeaderString&& key, HeaderString&& value) {
234 84965 : auto lookup = staticLookup(key.getStringView());
235 84965 : if (lookup.has_value()) {
236 11333 : key.clear();
237 11333 : if (*lookup.value().entry_ == nullptr) {
238 9979 : maybeCreateInline(lookup.value().entry_, *lookup.value().key_, std::move(value));
239 10868 : } else {
240 1354 : const auto delimiter = delimiterByHeader(*lookup.value().key_);
241 1354 : const uint64_t added_size =
242 1354 : appendToHeader((*lookup.value().entry_)->value(), value.getStringView(), delimiter);
243 1354 : addSize(added_size);
244 1354 : value.clear();
245 1354 : }
246 80284 : } else {
247 73632 : addSize(key.size() + value.size());
248 73632 : HeaderNode i = headers_.insert(std::move(key), std::move(value));
249 73632 : i->entry_ = i;
250 73632 : }
251 84965 : }
252 :
253 13308 : void HeaderMapImpl::addViaMove(HeaderString&& key, HeaderString&& value) {
254 13308 : insertByKey(std::move(key), std::move(value));
255 13308 : }
256 :
257 810 : void HeaderMapImpl::addReference(const LowerCaseString& key, absl::string_view value) {
258 810 : HeaderString ref_key(key);
259 810 : HeaderString ref_value(value);
260 810 : insertByKey(std::move(ref_key), std::move(ref_value));
261 810 : }
262 :
263 48 : void HeaderMapImpl::addReferenceKey(const LowerCaseString& key, uint64_t value) {
264 48 : HeaderString ref_key(key);
265 48 : HeaderString new_value;
266 48 : new_value.setInteger(value);
267 48 : insertByKey(std::move(ref_key), std::move(new_value));
268 48 : ASSERT(new_value.empty()); // NOLINT(bugprone-use-after-move)
269 48 : }
270 :
271 574 : void HeaderMapImpl::addReferenceKey(const LowerCaseString& key, absl::string_view value) {
272 574 : HeaderString ref_key(key);
273 574 : HeaderString new_value;
274 574 : new_value.setCopy(value);
275 574 : insertByKey(std::move(ref_key), std::move(new_value));
276 574 : ASSERT(new_value.empty()); // NOLINT(bugprone-use-after-move)
277 574 : }
278 :
279 33 : void HeaderMapImpl::addCopy(const LowerCaseString& key, uint64_t value) {
280 : // In the case that the header is appended, we will perform a needless copy of the key and value.
281 : // This is done on purpose to keep the code simple and should be rare.
282 33 : HeaderString new_key;
283 33 : new_key.setCopy(key.get());
284 33 : HeaderString new_value;
285 33 : new_value.setInteger(value);
286 33 : insertByKey(std::move(new_key), std::move(new_value));
287 33 : ASSERT(new_key.empty()); // NOLINT(bugprone-use-after-move)
288 33 : ASSERT(new_value.empty()); // NOLINT(bugprone-use-after-move)
289 33 : }
290 :
291 70192 : void HeaderMapImpl::addCopy(const LowerCaseString& key, absl::string_view value) {
292 : // In the case that the header is appended, we will perform a needless copy of the key and value.
293 : // This is done on purpose to keep the code simple and should be rare.
294 70192 : HeaderString new_key;
295 70192 : new_key.setCopy(key.get());
296 70192 : HeaderString new_value;
297 70192 : new_value.setCopy(value);
298 70192 : insertByKey(std::move(new_key), std::move(new_value));
299 70192 : ASSERT(new_key.empty()); // NOLINT(bugprone-use-after-move)
300 70192 : ASSERT(new_value.empty()); // NOLINT(bugprone-use-after-move)
301 70192 : }
302 :
303 103 : void HeaderMapImpl::appendCopy(const LowerCaseString& key, absl::string_view value) {
304 : // TODO(#9221): converge on and document a policy for coalescing multiple headers.
305 103 : auto entry = getExisting(key);
306 103 : if (!entry.empty()) {
307 31 : const auto delimiter = delimiterByHeader(key);
308 31 : const uint64_t added_size = appendToHeader(entry[0]->value(), value, delimiter);
309 31 : addSize(added_size);
310 72 : } else {
311 72 : addCopy(key, value);
312 72 : }
313 103 : }
314 :
315 401 : void HeaderMapImpl::setReference(const LowerCaseString& key, absl::string_view value) {
316 401 : remove(key);
317 401 : addReference(key, value);
318 401 : }
319 :
320 239 : void HeaderMapImpl::setReferenceKey(const LowerCaseString& key, absl::string_view value) {
321 239 : remove(key);
322 239 : addReferenceKey(key, value);
323 239 : }
324 :
325 5 : void HeaderMapImpl::setCopy(const LowerCaseString& key, absl::string_view value) {
326 5 : remove(key);
327 5 : addCopy(key, value);
328 5 : }
329 :
330 38057 : uint64_t HeaderMapImpl::byteSize() const { return cached_byte_size_; }
331 :
332 8648 : void HeaderMapImpl::verifyByteSizeInternalForTest() const {
333 : // Computes the total byte size by summing the byte size of the keys and values.
334 8648 : uint64_t byte_size = 0;
335 23281 : for (const HeaderEntryImpl& header : headers_) {
336 23281 : byte_size += header.key().size();
337 23281 : byte_size += header.value().size();
338 23281 : }
339 8648 : ASSERT(cached_byte_size_ == byte_size);
340 8648 : }
341 :
342 6772 : HeaderMap::GetResult HeaderMapImpl::get(const LowerCaseString& key) const {
343 6772 : return HeaderMap::GetResult(const_cast<HeaderMapImpl*>(this)->getExisting(key));
344 6772 : }
345 :
346 6875 : HeaderMap::NonConstGetResult HeaderMapImpl::getExisting(absl::string_view key) {
347 : // Attempt a trie lookup first to see if the user is requesting an O(1) header. This may be
348 : // relatively common in certain header matching / routing patterns.
349 : // TODO(mattklein123): Add inline handle support directly to the header matcher code to support
350 : // this use case more directly.
351 6875 : HeaderMap::NonConstGetResult ret;
352 6875 : auto lookup = staticLookup(key);
353 6875 : if (lookup.has_value()) {
354 4902 : if (*lookup.value().entry_ != nullptr) {
355 2210 : ret.push_back(*lookup.value().entry_);
356 2210 : }
357 4902 : return ret;
358 4902 : }
359 :
360 : // If the requested header is not an O(1) header try using the lazy map to
361 : // search for it instead of iterating the headers list.
362 1973 : if (headers_.maybeMakeMap()) {
363 1107 : HeaderList::HeaderLazyMap::iterator iter = headers_.mapFind(key);
364 1107 : if (iter != headers_.mapEnd()) {
365 157 : const HeaderList::HeaderNodeVector& v = iter->second;
366 157 : ASSERT(!v.empty()); // It's impossible to have a map entry with an empty vector as its value.
367 66224 : for (const auto& values_it : v) {
368 : // Convert the iterated value to a HeaderEntry*.
369 66224 : ret.push_back(&(*values_it));
370 66224 : }
371 157 : }
372 1107 : return ret;
373 1107 : }
374 :
375 : // If the requested header is not an O(1) header and the lazy map is not in use, we do a full
376 : // scan. Doing the trie lookup is wasteful in the miss case, but is present for code consistency
377 : // with other functions that do similar things.
378 974 : for (HeaderEntryImpl& header : headers_) {
379 386 : if (header.key() == key) {
380 103 : ret.push_back(&header);
381 103 : }
382 386 : }
383 :
384 866 : return ret;
385 1973 : }
386 :
387 10098 : void HeaderMapImpl::iterate(HeaderMap::ConstIterateCb cb) const {
388 30026 : for (const HeaderEntryImpl& header : headers_) {
389 30024 : if (cb(header) == HeaderMap::Iterate::Break) {
390 15 : break;
391 15 : }
392 30024 : }
393 10098 : }
394 :
395 6737 : void HeaderMapImpl::iterateReverse(HeaderMap::ConstIterateCb cb) const {
396 21719 : for (auto it = headers_.rbegin(); it != headers_.rend(); it++) {
397 14982 : if (cb(*it) == HeaderMap::Iterate::Break) {
398 0 : break;
399 0 : }
400 14982 : }
401 6737 : }
402 :
403 1199 : void HeaderMapImpl::clear() {
404 1199 : clearInline();
405 1199 : headers_.clear();
406 1199 : cached_byte_size_ = 0;
407 1199 : }
408 :
409 67 : size_t HeaderMapImpl::removeIf(const HeaderMap::HeaderMatchPredicate& predicate) {
410 67 : const size_t old_size = headers_.size();
411 101 : headers_.removeIf([&predicate, this](const HeaderEntryImpl& entry) {
412 101 : const bool to_remove = predicate(entry);
413 101 : if (to_remove) {
414 : // If this header should be removed, make sure any references in the
415 : // static lookup table are cleared as well.
416 25 : auto lookup = staticLookup(entry.key().getStringView());
417 25 : if (lookup.has_value()) {
418 2 : if (lookup.value().entry_) {
419 2 : const uint32_t key_value_size =
420 2 : (*lookup.value().entry_)->key().size() + (*lookup.value().entry_)->value().size();
421 2 : subtractSize(key_value_size);
422 2 : *lookup.value().entry_ = nullptr;
423 2 : }
424 23 : } else {
425 23 : subtractSize(entry.key().size() + entry.value().size());
426 23 : }
427 25 : }
428 101 : return to_remove;
429 101 : });
430 67 : return old_size - headers_.size();
431 67 : }
432 :
433 774 : size_t HeaderMapImpl::remove(const LowerCaseString& key) { return removeExisting(key); }
434 :
435 67 : size_t HeaderMapImpl::removePrefix(const LowerCaseString& prefix) {
436 101 : return HeaderMapImpl::removeIf([&prefix](const HeaderEntry& entry) -> bool {
437 101 : return absl::StartsWith(entry.key().getStringView(), prefix.get());
438 101 : });
439 67 : }
440 :
441 0 : void HeaderMapImpl::dumpState(std::ostream& os, int indent_level) const {
442 0 : iterate([&os,
443 0 : spaces = spacesForLevel(indent_level)](const HeaderEntry& header) -> HeaderMap::Iterate {
444 0 : os << spaces << "'" << header.key().getStringView() << "', '" << header.value().getStringView()
445 0 : << "'\n";
446 0 : return HeaderMap::Iterate::Continue;
447 0 : });
448 0 : }
449 :
450 : HeaderMapImpl::HeaderEntryImpl& HeaderMapImpl::maybeCreateInline(HeaderEntryImpl** entry,
451 8914 : const LowerCaseString& key) {
452 8914 : if (*entry) {
453 1324 : return **entry;
454 1324 : }
455 :
456 7590 : addSize(key.get().size());
457 7590 : HeaderNode i = headers_.insert(key);
458 7590 : i->entry_ = i;
459 7590 : *entry = &(*i);
460 7590 : return **entry;
461 8914 : }
462 :
463 : HeaderMapImpl::HeaderEntryImpl& HeaderMapImpl::maybeCreateInline(HeaderEntryImpl** entry,
464 : const LowerCaseString& key,
465 9979 : HeaderString&& value) {
466 9979 : if (*entry) {
467 0 : value.clear();
468 0 : return **entry;
469 0 : }
470 :
471 9979 : addSize(key.get().size() + value.size());
472 9979 : HeaderNode i = headers_.insert(key, std::move(value));
473 9979 : i->entry_ = i;
474 9979 : *entry = &(*i);
475 9979 : return **entry;
476 9979 : }
477 :
478 774 : size_t HeaderMapImpl::removeExisting(absl::string_view key) {
479 774 : const size_t old_size = headers_.size();
480 774 : auto lookup = staticLookup(key);
481 774 : if (lookup.has_value()) {
482 114 : removeInline(lookup.value().entry_);
483 747 : } else {
484 660 : subtractSize(headers_.remove(key));
485 660 : }
486 774 : return old_size - headers_.size();
487 774 : }
488 :
489 14174 : size_t HeaderMapImpl::removeInline(HeaderEntryImpl** ptr_to_entry) {
490 14174 : if (!*ptr_to_entry) {
491 13914 : return 0;
492 13914 : }
493 :
494 260 : HeaderEntryImpl* entry = *ptr_to_entry;
495 260 : const uint64_t size_to_subtract = entry->entry_->key().size() + entry->entry_->value().size();
496 260 : subtractSize(size_to_subtract);
497 260 : *ptr_to_entry = nullptr;
498 260 : headers_.erase(entry->entry_, true);
499 260 : return 1;
500 14174 : }
501 :
502 : namespace {
503 : template <class T>
504 536 : HeaderMapImplUtility::HeaderMapImplInfo makeHeaderMapImplInfo(absl::string_view name) {
505 : // Constructing a header map implementation will force the custom headers and sizing to be
506 : // finalized, so do that first.
507 536 : auto header_map = T::create();
508 :
509 536 : HeaderMapImplUtility::HeaderMapImplInfo info;
510 536 : info.name_ = std::string(name);
511 536 : info.size_ = T::inlineHeadersSize() + sizeof(T);
512 11140 : for (const auto& header : CustomInlineHeaderRegistry::headers<T::header_map_type>()) {
513 11140 : info.registered_headers_.push_back(header.first.get());
514 11140 : }
515 536 : return info;
516 536 : }
517 : } // namespace
518 :
519 : std::vector<HeaderMapImplUtility::HeaderMapImplInfo>
520 134 : HeaderMapImplUtility::getAllHeaderMapImplInfo() {
521 134 : std::vector<HeaderMapImplUtility::HeaderMapImplInfo> ret;
522 134 : ret.push_back(makeHeaderMapImplInfo<RequestHeaderMapImpl>("request header map"));
523 134 : ret.push_back(makeHeaderMapImplInfo<RequestTrailerMapImpl>("request trailer map"));
524 134 : ret.push_back(makeHeaderMapImplInfo<ResponseHeaderMapImpl>("response header map"));
525 134 : ret.push_back(makeHeaderMapImplInfo<ResponseTrailerMapImpl>("response trailer map"));
526 134 : return ret;
527 134 : }
528 :
529 : } // namespace Http
530 : } // namespace Envoy
|