userver: userver/http/header_map.hpp Source File
Loading...
Searching...
No Matches
header_map.hpp
1#pragma once
2
3#include <initializer_list>
4#include <iterator>
5#include <string>
6#include <vector>
7
8#include <userver/formats/json_fwd.hpp>
9#include <userver/formats/parse/to.hpp>
10#include <userver/http/predefined_header.hpp>
11#include <userver/utils/fast_pimpl.hpp>
12
13USERVER_NAMESPACE_BEGIN
14
15namespace http::headers {
16
17class TestsHelper;
18
19namespace header_map {
20class Map;
21}
22
23/// @ingroup userver_universal userver_containers
24///
25/// @brief Container that maps case-insensitive header name into header value.
26///
27/// Allows storing up to 24576 name->value pairs, after that an attempt to
28/// insert a new pair will throw.
29///
30/// Has an anti-hashdos collisions resolution built-in, and the capacity limit
31/// might be lowered in case of an attack being detected.
32///
33/// Iterators/pointers invalidation loosely matches that of std::vector:
34/// * if an insertion took place, iterators/pointers are invalidated;
35/// * successful erase invalidates the iterator being erased and `begin` (it's
36/// implemented via swap and pop_back idiom, and `begin` is actually an `rbegin`
37/// of underlying vector).
38class HeaderMap final {
39 public:
40 /// Iterator
41 class Iterator;
42 /// Const iterator
43 class ConstIterator;
44
45 using iterator = Iterator;
46 using const_iterator = ConstIterator;
47
48 using key_type = std::string;
49 using mapped_type = std::string;
50
51 /// The exception being thrown in case of capacity overflow
52 class TooManyHeadersException final : public std::runtime_error {
53 using std::runtime_error::runtime_error;
54 };
55
56 /// Default constructor.
57 HeaderMap();
58 /// Constructor from initializer list: `HeaderMap({{"a", "b"}, {"c", "d"}})`.
59 /// Its unspecified which pair is inserted in case of names not being unique.
60 HeaderMap(std::initializer_list<std::pair<std::string, std::string>> headers);
61 /// Constructor from initializer list: `HeaderMap({{"a", "b"}, {"c", "d"}})`.
62 /// Its unspecified which pair is inserted in case of names not being unique.
64 std::initializer_list<std::pair<PredefinedHeader, std::string>> headers);
65 /// Constructor with capacity: preallocates `capacity` elements for internal
66 /// storage.
67 HeaderMap(std::size_t capacity);
68 /// Constructor from iterator pair:
69 /// `HeaderMap{key_value_pairs.begin(), key_value_pairs.end()}`.
70 /// Its unspecified which pair is inserted in case of names not being unique.
71 template <typename InputIt>
72 HeaderMap(InputIt first, InputIt last);
73
74 /// Destructor
75 ~HeaderMap();
76
77 /// Copy constructor
78 HeaderMap(const HeaderMap& other);
79 /// Move constructor
80 HeaderMap(HeaderMap&& other) noexcept;
81 /// Copy assignment operator
82 HeaderMap& operator=(const HeaderMap& other);
83 /// Move assignment operator
84 HeaderMap& operator=(HeaderMap&& other) noexcept;
85
86 /// Non-binding call to reserve `capacity` elements for internal storage.
87 void reserve(std::size_t capacity);
88 /// Returns the amount of name-value-pairs being stored.
89 std::size_t size() const noexcept;
90 /// Return true if no name-value-pairs are being stored, false otherwise.
91 bool empty() const noexcept;
92 /// Removes all the key-value-pairs being stored,
93 /// doesn't shrink underlying storage.
94 void clear();
95
96 /// Returns 1 if the key exists, 0 otherwise.
97 std::size_t count(std::string_view key) const noexcept;
98 /// @overload
99 std::size_t count(const PredefinedHeader& key) const noexcept;
100
101 template <std::size_t Size>
102 [[noreturn]] std::size_t count(const char (&)[Size]) const noexcept {
103 ReportMisuse<Size>();
104 }
105
106 /// Returns true if the key exists, false otherwise.
107 bool contains(std::string_view key) const noexcept;
108 /// @overload
109 bool contains(const PredefinedHeader& key) const noexcept;
110
111 template <std::size_t Size>
112 [[noreturn]] bool contains(const char (&)[Size]) const noexcept {
113 ReportMisuse<Size>();
114 }
115
116 /// If the key is present, returns reference to it's header value,
117 /// otherwise inserts a pair (key, "") and returns reference
118 /// to newly inserted empty string.
119 /// In an insertion took place, key is moved-out,
120 /// otherwise key is left unchanged.
121 std::string& operator[](std::string&& key);
122 /// If the key is present, returns reference to it's header value,
123 /// otherwise inserts a pair (key, "") and returns reference
124 /// to newly inserted empty string.
125 std::string& operator[](std::string_view key);
126 /// @overload
127 std::string& operator[](const PredefinedHeader& key);
128
129 template <std::size_t Size>
130 [[noreturn]] std::string& operator[](const char (&)[Size]) {
131 ReportMisuse<Size>();
132 }
133
134 /// If the key is present, returns iterator to its name-value pair,
135 /// otherwise return end().
136 Iterator find(std::string_view key) noexcept;
137 /// @overload
138 ConstIterator find(std::string_view key) const noexcept;
139
140 /// If the key is present, returns iterator to its name-value pair,
141 /// otherwise return end().
142 Iterator find(const PredefinedHeader& key) noexcept;
143 /// @overload
144 ConstIterator find(const PredefinedHeader& key) const noexcept;
145
146 template <std::size_t Size>
147 [[noreturn]] Iterator find(const char (&)[Size]) noexcept;
148
149 template <std::size_t Size>
150 [[noreturn]] ConstIterator find(const char (&)[Size]) const noexcept;
151
152 /// If the key is already present in the map, does nothing.
153 /// Otherwise inserts a pair of key and in-place constructed value.
154 template <typename... Args>
155 void emplace(std::string_view key, Args&&... args) {
156 Emplace(std::move(key), std::forward<Args>(args)...);
157 }
158
159 /// If the key is already present in the map, does nothing.
160 /// Otherwise inserts a pair of key and inplace constructed value.
161 template <typename... Args>
162 void emplace(std::string key, Args&&... args) {
163 Emplace(std::move(key), std::forward<Args>(args)...);
164 }
165
166 /// If the key is already present in the map, does nothing.
167 /// Otherwise inserts a pair of key and inplace constructed value.
168 template <typename... Args>
169 void try_emplace(std::string key, Args&&... args) {
170 Emplace(std::move(key), std::forward<Args>(args)...);
171 }
172
173 /// For every iterator it in [first, last) inserts *it.
174 /// Its unspecified which pair is inserted in case of names not being unique.
175 template <typename InputIt>
176 void insert(InputIt first, InputIt last);
177
178 /// If kvp.first is already present in the map, does nothing,
179 /// otherwise inserts the pair into container.
180 void insert(const std::pair<std::string, std::string>& kvp);
181 /// If kvp.first is already present in the map, does nothing,
182 /// otherwise inserts the pair into container.
183 void insert(std::pair<std::string, std::string>&& kvp);
184
185 /// If key is already present in the map, changes corresponding header value
186 /// to provided value, otherwise inserts the pair into container.
187 void insert_or_assign(std::string key, std::string value);
188 /// @overload
189 void insert_or_assign(const PredefinedHeader& key, std::string value);
190
191 /// If key is already present in the map, appends ",{value}" to the
192 /// corresponding header value, otherwise inserts the pair into container.
193 void InsertOrAppend(std::string key, std::string value);
194 /// @overload
195 void InsertOrAppend(const PredefinedHeader& key, std::string value);
196
197 /// Erases the pair to which the iterator points, returns iterator following
198 /// the last removed element.
199 /// Iterators/pointers to erased value and `begin` are invalidated.
200 Iterator erase(Iterator it);
201 /// Erases the pair to which the iterator points, returns iterator following
202 /// the last removed element.
203 /// Iterators/pointers to erased value and `begin` are invalidated.
204 Iterator erase(ConstIterator it);
205
206 /// If the key is present in container, behaves exactly like erase(Iterator).
207 /// Otherwise does nothing and returns `end()`.
208 Iterator erase(std::string_view key);
209 /// @overload
210 Iterator erase(const PredefinedHeader& key);
211
212 template <std::size_t Size>
213 [[noreturn]] Iterator erase(const char (&)[Size]);
214
215 /// If the key is present in container, returns reference to its header value,
216 /// otherwise throws std::out_of_range.
217 std::string& at(std::string_view key);
218 /// @overload
219 std::string& at(const PredefinedHeader& key);
220 /// If the key is present in container, returns reference to its header value,
221 /// otherwise throws std::out_of_range.
222 const std::string& at(std::string_view key) const;
223 /// @overload
224 const std::string& at(const PredefinedHeader& key) const;
225
226 template <std::size_t Size>
227 [[noreturn]] std::string& at(const char (&)[Size]) {
228 ReportMisuse<Size>();
229 }
230 template <std::size_t Size>
231 [[noreturn]] const std::string& at(const char (&)[Size]) const {
232 ReportMisuse<Size>();
233 }
234
235 /// Returns an iterator to the first name-value-pair being stored.
236 Iterator begin() noexcept;
237 /// Returns an iterator to the first name-value-pair being stored.
238 ConstIterator begin() const noexcept;
239 /// Returns an iterator to the first name-value-pair being stored.
240 ConstIterator cbegin() const noexcept;
241
242 /// Returns an iterator to the end (valid but not dereferenceable).
243 Iterator end() noexcept;
244 /// Returns an iterator to the end (valid but not dereferenceable).
245 ConstIterator end() const noexcept;
246 /// Returns an iterator to the end (valid but not dereferenceable).
247 ConstIterator cend() const noexcept;
248
249 /// Returns true if `other` contains exactly the same set of name-value-pairs,
250 /// false otherwise.
251 bool operator==(const HeaderMap& other) const noexcept;
252
253 /// Appends container content in http headers format to provided buffer,
254 /// that is appends
255 /// @code
256 /// header1: value1\r\n
257 /// header2: value2\r\n
258 /// ...
259 /// @endcode
260 /// resizing buffer as needed.
261 void OutputInHttpFormat(HeadersString& buffer) const;
262
263 private:
264 friend class TestsHelper;
265
266 template <typename KeyType, typename... Args>
267 void Emplace(KeyType&& key, Args&&... args);
268
269 template <std::size_t Size>
270 [[noreturn]] static void ReportMisuse();
271
272 utils::FastPimpl<header_map::Map, 272, 8> impl_;
273};
274
275template <typename InputIt>
276HeaderMap::HeaderMap(InputIt first, InputIt last) : HeaderMap{} {
277 insert(first, last);
278}
279
280template <typename InputIt>
281void HeaderMap::insert(InputIt first, InputIt last) {
282 for (; first != last; ++first) {
283 insert(*first);
284 }
285}
286
287template <typename KeyType, typename... Args>
288void HeaderMap::Emplace(KeyType&& key, Args&&... args) {
289 static_assert(std::is_rvalue_reference_v<decltype(key)>);
290 // This is a private function, and we know what we are doing here.
291 // NOLINTNEXTLINE(bugprone-move-forwarding-reference)
292 auto& value = operator[](std::move(key));
293 if (value.empty()) {
294 value = std::string{std::forward<Args>(args)...};
295 }
296}
297
298template <std::size_t Size>
299void HeaderMap::ReportMisuse() {
300 static_assert(!Size,
301 "Please create a 'constexpr PredefinedHeader' and use that "
302 "instead of passing header name as a string literal.");
303}
304
305namespace header_map {
306
307class MapEntry final {
308 public:
309 MapEntry();
310 ~MapEntry();
311
312 MapEntry(std::string&& key, std::string&& value);
313
314 MapEntry(const MapEntry& other);
315 MapEntry& operator=(const MapEntry& other);
316 MapEntry(MapEntry&& other) noexcept;
317 MapEntry& operator=(MapEntry&& other) noexcept;
318
319 std::pair<const std::string, std::string>& Get();
320 const std::pair<const std::string, std::string>& Get() const;
321
322 std::pair<std::string, std::string>& GetMutable();
323
324 bool operator==(const MapEntry& other) const;
325
326 private:
327 // The interface requires std::pair<CONST std::string, std::string>, but we
328 // don't want to copy where move would do, so this.
329 // Only 'mutable_value' is ever the active member of 'Slot', but we still
330 // can access it through `value` due to
331 // https://eel.is/c++draft/class.union.general#note-1
332 // The idea was taken from abseil:
333 // https://github.com/abseil/abseil-cpp/blob/1ae9b71c474628d60eb251a3f62967fe64151bb2/absl/container/internal/container_memory.h#L302
334 union Slot {
335 Slot();
336 ~Slot();
337
338 std::pair<std::string, std::string> mutable_value;
339 std::pair<const std::string, std::string> value;
340 };
341
342 Slot slot_{};
343};
344
345} // namespace header_map
346
347class HeaderMap::Iterator final {
348 public:
349 using iterator_category = std::forward_iterator_tag;
350 using difference_type = std::ptrdiff_t;
351 using value_type = std::pair<const std::string, std::string>;
352 using reference = value_type&;
353 using const_reference = const value_type&;
354 using pointer = value_type*;
355 using const_pointer = const value_type*;
356
357 // The underlying iterator is a reversed one to do not invalidate
358 // end() on erase - end() is actually an rbegin() of underlying storage and is
359 // only invalidated on reallocation.
360 using UnderlyingIterator =
361 std::vector<header_map::MapEntry>::reverse_iterator;
362
363 Iterator();
364 explicit Iterator(UnderlyingIterator it);
365 ~Iterator();
366
367 Iterator(const Iterator& other);
368 Iterator(Iterator&& other) noexcept;
369 Iterator& operator=(const Iterator& other);
370 Iterator& operator=(Iterator&& other) noexcept;
371
372 Iterator operator++(int);
373 Iterator& operator++();
374
375 reference operator*();
376 const_reference operator*() const;
377 pointer operator->();
378 const_pointer operator->() const;
379
380 bool operator==(const Iterator& other) const;
381 bool operator!=(const Iterator& other) const;
382
383 bool operator==(const ConstIterator& other) const;
384
385 private:
386 friend class HeaderMap::ConstIterator;
387
388 UnderlyingIterator it_{};
389};
390
391class HeaderMap::ConstIterator final {
392 public:
393 using iterator_category = std::forward_iterator_tag;
394 using difference_type = std::ptrdiff_t;
395 using value_type = std::pair<const std::string, std::string>;
396 using reference = const value_type&;
397 using const_reference = const value_type&;
398 using pointer = const value_type*;
399 using const_pointer = const value_type*;
400
401 // The underlying iterator is a reversed one to do not invalidate
402 // end() on erase - end() is actually an rbegin() of underlying storage and is
403 // only invalidated on reallocation.
404 using UnderlyingIterator =
405 std::vector<header_map::MapEntry>::const_reverse_iterator;
406
407 ConstIterator();
408 explicit ConstIterator(UnderlyingIterator it);
409 ~ConstIterator();
410
411 ConstIterator(const ConstIterator& other);
412 ConstIterator(ConstIterator&& other) noexcept;
413 ConstIterator& operator=(const ConstIterator& other);
414 ConstIterator& operator=(ConstIterator&& other) noexcept;
415
416 ConstIterator operator++(int);
417 ConstIterator& operator++();
418
419 reference operator*();
420 const_reference operator*() const;
421 pointer operator->();
422 const_pointer operator->() const;
423
424 bool operator==(const ConstIterator& other) const;
425 bool operator!=(const ConstIterator& other) const;
426
427 bool operator==(const Iterator& other) const;
428
429 private:
430 friend class HeaderMap::Iterator;
431
432 UnderlyingIterator it_{};
433};
434
435template <std::size_t Size>
436HeaderMap::Iterator HeaderMap::find(const char (&)[Size]) noexcept {
437 ReportMisuse<Size>();
438}
439
440template <std::size_t Size>
441HeaderMap::ConstIterator HeaderMap::find(const char (&)[Size]) const noexcept {
442 ReportMisuse<Size>();
443}
444
445template <std::size_t Size>
446HeaderMap::Iterator HeaderMap::erase(const char (&)[Size]) {
447 ReportMisuse<Size>();
448}
449
450} // namespace http::headers
451
452USERVER_NAMESPACE_END