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