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 {
39public:
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.
63 HeaderMap(std::initializer_list<std::pair<PredefinedHeader, std::string>> headers);
64 /// Constructor with capacity: preallocates `capacity` elements for internal
65 /// storage.
66 HeaderMap(std::size_t capacity);
67 /// Constructor from iterator pair:
68 /// `HeaderMap{key_value_pairs.begin(), key_value_pairs.end()}`.
69 /// Its unspecified which pair is inserted in case of names not being unique.
70 template <typename InputIt>
71 HeaderMap(InputIt first, InputIt last);
72
73 /// Destructor
74 ~HeaderMap();
75
76 /// Copy constructor
77 HeaderMap(const HeaderMap& other);
78 /// Move constructor
79 HeaderMap(HeaderMap&& other) noexcept;
80 /// Copy assignment operator
81 HeaderMap& operator=(const HeaderMap& other);
82 /// Move assignment operator
83 HeaderMap& operator=(HeaderMap&& other) noexcept;
84
85 /// Non-binding call to reserve `capacity` elements for internal storage.
86 void reserve(std::size_t capacity);
87 /// Returns the amount of name-value-pairs being stored.
88 std::size_t size() const noexcept;
89 /// Return true if no name-value-pairs are being stored, false otherwise.
90 bool empty() const noexcept;
91 /// Removes all the key-value-pairs being stored,
92 /// doesn't shrink underlying storage.
93 void clear();
94
95 /// Returns 1 if the key exists, 0 otherwise.
96 std::size_t count(std::string_view key) const noexcept;
97 /// @overload
98 std::size_t count(const PredefinedHeader& key) const noexcept;
99
100 template <std::size_t Size>
101 [[noreturn]] std::size_t count(const char (&)[Size]) const noexcept {
102 ReportMisuse<Size>();
103 }
104
105 /// Returns true if the key exists, false otherwise.
106 bool contains(std::string_view key) const noexcept;
107 /// @overload
108 bool contains(const PredefinedHeader& key) const noexcept;
109
110 template <std::size_t Size>
111 [[noreturn]] bool contains(const char (&)[Size]) const noexcept {
112 ReportMisuse<Size>();
113 }
114
115 /// If the key is present, returns reference to it's header value,
116 /// otherwise inserts a pair (key, "") and returns reference
117 /// to newly inserted empty string.
118 /// In an insertion took place, key is moved-out,
119 /// otherwise key is left unchanged.
120 std::string& operator[](std::string&& key);
121 /// If the key is present, returns reference to it's header value,
122 /// otherwise inserts a pair (key, "") and returns reference
123 /// to newly inserted empty string.
124 std::string& operator[](std::string_view key);
125 /// @overload
126 std::string& operator[](const PredefinedHeader& key);
127
128 template <std::size_t Size>
129 [[noreturn]] std::string& operator[](const char (&)[Size]) {
130 ReportMisuse<Size>();
131 }
132
133 /// If the key is present, returns iterator to its name-value pair,
134 /// otherwise return end().
135 Iterator find(std::string_view key) noexcept;
136 /// @overload
137 ConstIterator find(std::string_view key) const noexcept;
138
139 /// If the key is present, returns iterator to its name-value pair,
140 /// otherwise return end().
141 Iterator find(const PredefinedHeader& key) noexcept;
142 /// @overload
143 ConstIterator find(const PredefinedHeader& key) const noexcept;
144
145 template <std::size_t Size>
146 [[noreturn]] Iterator find(const char (&)[Size]) noexcept;
147
148 template <std::size_t Size>
149 [[noreturn]] ConstIterator find(const char (&)[Size]) const noexcept;
150
151 /// If the key is already present in the map, does nothing.
152 /// Otherwise inserts a pair of key and in-place constructed value.
153 template <typename... Args>
154 void emplace(std::string_view key, Args&&... args) {
155 Emplace(std::move(key), std::forward<Args>(args)...);
156 }
157
158 /// If the key is already present in the map, does nothing.
159 /// Otherwise inserts a pair of key and inplace constructed value.
160 template <typename... Args>
161 void emplace(std::string key, Args&&... args) {
162 Emplace(std::move(key), std::forward<Args>(args)...);
163 }
164
165 /// If the key is already present in the map, does nothing.
166 /// Otherwise inserts a pair of key and inplace constructed value.
167 template <typename... Args>
168 void try_emplace(std::string key, Args&&... args) {
169 Emplace(std::move(key), std::forward<Args>(args)...);
170 }
171
172 /// For every iterator it in [first, last) inserts *it.
173 /// Its unspecified which pair is inserted in case of names not being unique.
174 template <typename InputIt>
175 void insert(InputIt first, InputIt last);
176
177 /// If kvp.first is already present in the map, does nothing,
178 /// otherwise inserts the pair into container.
179 void insert(const std::pair<std::string, std::string>& kvp);
180 /// If kvp.first is already present in the map, does nothing,
181 /// otherwise inserts the pair into container.
182 void insert(std::pair<std::string, std::string>&& kvp);
183
184 /// If key is already present in the map, changes corresponding header value
185 /// to provided value, otherwise inserts the pair into container.
186 void insert_or_assign(std::string key, std::string value);
187 /// @overload
188 void insert_or_assign(const PredefinedHeader& key, std::string value);
189
190 /// If key is already present in the map, appends ",{value}" to the
191 /// corresponding header value, otherwise inserts the pair into container.
192 void InsertOrAppend(std::string key, std::string value);
193 /// @overload
194 void InsertOrAppend(const PredefinedHeader& key, std::string value);
195
196 /// Erases the pair to which the iterator points, returns iterator following
197 /// the last removed element.
198 /// Iterators/pointers to erased value and `begin` are invalidated.
199 Iterator erase(Iterator it);
200 /// Erases the pair to which the iterator points, returns iterator following
201 /// the last removed element.
202 /// Iterators/pointers to erased value and `begin` are invalidated.
203 Iterator erase(ConstIterator it);
204
205 /// If the key is present in container, behaves exactly like erase(Iterator).
206 /// Otherwise does nothing and returns `end()`.
207 Iterator erase(std::string_view key);
208 /// @overload
209 Iterator erase(const PredefinedHeader& key);
210
211 template <std::size_t Size>
212 [[noreturn]] Iterator erase(const char (&)[Size]);
213
214 /// If the key is present in container, returns reference to its header value,
215 /// otherwise throws std::out_of_range.
216 std::string& at(std::string_view key);
217 /// @overload
218 std::string& at(const PredefinedHeader& key);
219 /// If the key is present in container, returns reference to its header value,
220 /// otherwise throws std::out_of_range.
221 const std::string& at(std::string_view key) const;
222 /// @overload
223 const std::string& at(const PredefinedHeader& key) const;
224
225 template <std::size_t Size>
226 [[noreturn]] std::string& at(const char (&)[Size]) {
227 ReportMisuse<Size>();
228 }
229 template <std::size_t Size>
230 [[noreturn]] const std::string& at(const char (&)[Size]) const {
231 ReportMisuse<Size>();
232 }
233
234 /// Returns an iterator to the first name-value-pair being stored.
235 Iterator begin() noexcept;
236 /// Returns an iterator to the first name-value-pair being stored.
237 ConstIterator begin() const noexcept;
238 /// Returns an iterator to the first name-value-pair being stored.
239 ConstIterator cbegin() const noexcept;
240
241 /// Returns an iterator to the end (valid but not dereferenceable).
242 Iterator end() noexcept;
243 /// Returns an iterator to the end (valid but not dereferenceable).
244 ConstIterator end() const noexcept;
245 /// Returns an iterator to the end (valid but not dereferenceable).
246 ConstIterator cend() const noexcept;
247
248 /// Returns true if `other` contains exactly the same set of name-value-pairs,
249 /// false otherwise.
250 bool operator==(const HeaderMap& other) const noexcept;
251
252 /// Appends container content in http headers format to provided buffer,
253 /// that is appends
254 /// @code
255 /// header1: value1\r\n
256 /// header2: value2\r\n
257 /// ...
258 /// @endcode
259 /// resizing buffer as needed.
260 void OutputInHttpFormat(HeadersString& buffer) const;
261
262private:
263 friend class TestsHelper;
264
265 template <typename KeyType, typename... Args>
266 void Emplace(KeyType&& key, Args&&... args);
267
268 template <std::size_t Size>
269 [[noreturn]] static void ReportMisuse();
270
271 utils::FastPimpl<header_map::Map, 272, 8> impl_;
272};
273
274template <typename InputIt>
275HeaderMap::HeaderMap(InputIt first, InputIt last) : HeaderMap{} {
276 insert(first, last);
277}
278
279template <typename InputIt>
280void HeaderMap::insert(InputIt first, InputIt last) {
281 for (; first != last; ++first) {
282 insert(*first);
283 }
284}
285
286template <typename KeyType, typename... Args>
287void HeaderMap::Emplace(KeyType&& key, Args&&... args) {
288 static_assert(std::is_rvalue_reference_v<decltype(key)>);
289 // This is a private function, and we know what we are doing here.
290 // NOLINTNEXTLINE(bugprone-move-forwarding-reference)
291 auto& value = operator[](std::move(key));
292 if (value.empty()) {
293 value = std::string{std::forward<Args>(args)...};
294 }
295}
296
297template <std::size_t Size>
298void HeaderMap::ReportMisuse() {
299 static_assert(
300 !Size,
301 "Please create a 'constexpr PredefinedHeader' and use that "
302 "instead of passing header name as a string literal."
303 );
304}
305
306namespace header_map {
307
308class MapEntry final {
309public:
310 MapEntry();
311 ~MapEntry();
312
313 MapEntry(std::string&& key, std::string&& value);
314
315 MapEntry(const MapEntry& other);
316 MapEntry& operator=(const MapEntry& other);
317 MapEntry(MapEntry&& other) noexcept;
318 MapEntry& operator=(MapEntry&& other) noexcept;
319
320 std::pair<const std::string, std::string>& Get();
321 const std::pair<const std::string, std::string>& Get() const;
322
323 std::pair<std::string, std::string>& GetMutable();
324
325 bool operator==(const MapEntry& other) const;
326
327private:
328 // The interface requires std::pair<CONST std::string, std::string>, but we
329 // don't want to copy where move would do, so this.
330 // Only 'mutable_value' is ever the active member of 'Slot', but we still
331 // can access it through `value` due to
332 // https://eel.is/c++draft/class.union.general#note-1
333 // The idea was taken from abseil:
334 // https://github.com/abseil/abseil-cpp/blob/1ae9b71c474628d60eb251a3f62967fe64151bb2/absl/container/internal/container_memory.h#L302
335 union Slot {
336 Slot();
337 ~Slot();
338
339 std::pair<std::string, std::string> mutable_value;
340 std::pair<const std::string, std::string> value;
341 };
342
343 Slot slot_{};
344};
345
346} // namespace header_map
347
348class HeaderMap::Iterator final {
349public:
350 using iterator_category = std::forward_iterator_tag;
351 using difference_type = std::ptrdiff_t;
352 using value_type = std::pair<const std::string, std::string>;
353 using reference = value_type&;
354 using const_reference = const value_type&;
355 using pointer = value_type*;
356 using const_pointer = const value_type*;
357
358 // The underlying iterator is a reversed one to do not invalidate
359 // end() on erase - end() is actually an rbegin() of underlying storage and is
360 // only invalidated on reallocation.
361 using UnderlyingIterator = 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
385private:
386 friend class HeaderMap::ConstIterator;
387
388 UnderlyingIterator it_{};
389};
390
391class HeaderMap::ConstIterator final {
392public:
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 = std::vector<header_map::MapEntry>::const_reverse_iterator;
405
406 ConstIterator();
407 explicit ConstIterator(UnderlyingIterator it);
408 ~ConstIterator();
409
410 ConstIterator(const ConstIterator& other);
411 ConstIterator(ConstIterator&& other) noexcept;
412 ConstIterator& operator=(const ConstIterator& other);
413 ConstIterator& operator=(ConstIterator&& other) noexcept;
414
415 ConstIterator operator++(int);
416 ConstIterator& operator++();
417
418 reference operator*();
419 const_reference operator*() const;
420 pointer operator->();
421 const_pointer operator->() const;
422
423 bool operator==(const ConstIterator& other) const;
424 bool operator!=(const ConstIterator& other) const;
425
426 bool operator==(const Iterator& other) const;
427
428private:
429 friend class HeaderMap::Iterator;
430
431 UnderlyingIterator it_{};
432};
433
434template <std::size_t Size>
435HeaderMap::Iterator HeaderMap::find(const char (&)[Size]) noexcept {
436 ReportMisuse<Size>();
437}
438
439template <std::size_t Size>
440HeaderMap::ConstIterator HeaderMap::find(const char (&)[Size]) const noexcept {
441 ReportMisuse<Size>();
442}
443
444template <std::size_t Size>
445HeaderMap::Iterator HeaderMap::erase(const char (&)[Size]) {
446 ReportMisuse<Size>();
447}
448
449} // namespace http::headers
450
451USERVER_NAMESPACE_END