GCC Code Coverage Report


Directory: libs/http_proto/
File: include/boost/http_proto/serializer.hpp
Date: 2025-06-18 09:40:27
Exec Total Coverage
Lines: 51 51 100.0%
Functions: 23 26 88.5%
Branches: 3 4 75.0%

Line Branch Exec Source
1 //
2 // Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com)
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/http_proto
8 //
9
10 #ifndef BOOST_HTTP_PROTO_SERIALIZER_HPP
11 #define BOOST_HTTP_PROTO_SERIALIZER_HPP
12
13 #include <boost/http_proto/context.hpp>
14 #include <boost/http_proto/detail/array_of_const_buffers.hpp>
15 #include <boost/http_proto/detail/config.hpp>
16 #include <boost/http_proto/detail/except.hpp>
17 #include <boost/http_proto/detail/header.hpp>
18 #include <boost/http_proto/detail/workspace.hpp>
19 #include <boost/http_proto/source.hpp>
20
21 #include <boost/buffers/circular_buffer.hpp>
22 #include <boost/buffers/const_buffer_span.hpp>
23 #include <boost/buffers/range.hpp>
24 #include <boost/buffers/type_traits.hpp>
25 #include <boost/system/result.hpp>
26
27 #include <memory>
28 #include <numeric>
29 #include <type_traits>
30 #include <utility>
31
32 namespace boost {
33 namespace http_proto {
34
35 #ifndef BOOST_HTTP_PROTO_DOCS
36 class message_view_base;
37 namespace detail {
38 class filter;
39 } // namespace detail
40 #endif
41
42 /** A serializer for HTTP/1 messages
43
44 This is used to serialize one or more complete
45 HTTP/1 messages. Each message consists of a
46 required header followed by an optional body.
47
48 Objects of this type operate using an "input area" and an
49 "output area". Callers provide data to the input area
50 using one of the @ref start or @ref start_stream member
51 functions. After input is provided, serialized data
52 becomes available in the serializer's output area in the
53 form of a constant buffer sequence.
54
55 Callers alternate between filling the input area and
56 consuming the output area until all the input has been
57 provided and all the output data has been consumed, or
58 an error occurs.
59
60 After calling @ref start, the caller must ensure that the
61 contents of the associated message are not changed or
62 destroyed until @ref is_done returns true, @ref reset is
63 called, or the serializer is destroyed, otherwise the
64 behavior is undefined.
65 */
66 class serializer
67 {
68 public:
69 using const_buffers_type =
70 buffers::const_buffer_span;
71
72 struct stream;
73
74 /** Destructor
75 */
76 BOOST_HTTP_PROTO_DECL
77 ~serializer();
78
79 /** Constructor
80 */
81 BOOST_HTTP_PROTO_DECL
82 serializer(
83 serializer&&) noexcept;
84
85 /** Constructor
86
87 @param ctx The serializer will access services
88 registered with this context.
89 */
90 BOOST_HTTP_PROTO_DECL
91 serializer(
92 context& ctx);
93
94 /** Constructor
95 */
96 BOOST_HTTP_PROTO_DECL
97 serializer(
98 context& ctx,
99 std::size_t buffer_size);
100
101 //--------------------------------------------
102
103 /** Prepare the serializer for a new stream
104 */
105 BOOST_HTTP_PROTO_DECL
106 void
107 reset() noexcept;
108
109 /** Prepare the serializer for a new message
110
111 The message will not contain a body.
112 Changing the contents of the message
113 after calling this function and before
114 @ref is_done returns `true` results in
115 undefined behavior.
116 */
117 void
118 4 start(
119 message_view_base const& m)
120 {
121 4 start_empty(m);
122 4 }
123
124 /** Prepare the serializer for a new message
125
126 Changing the contents of the message
127 after calling this function and before
128 @ref is_done returns `true` results in
129 undefined behavior.
130
131 @par Constraints
132 @code
133 is_const_buffers< ConstBuffers >::value == true
134 @endcode
135 */
136 template<
137 class ConstBufferSequence
138 #ifndef BOOST_HTTP_PROTO_DOCS
139 ,class = typename
140 std::enable_if<
141 buffers::is_const_buffer_sequence<
142 ConstBufferSequence>::value
143 >::type
144 #endif
145 >
146 void
147 start(
148 message_view_base const& m,
149 ConstBufferSequence&& body);
150
151 /** Prepare the serializer for a new message
152
153 Changing the contents of the message
154 after calling this function and before
155 @ref is_done returns `true` results in
156 undefined behavior.
157 */
158 template<
159 class Source,
160 class... Args
161 #ifndef BOOST_HTTP_PROTO_DOCS
162 ,class = typename std::enable_if<
163 is_source<Source>::value>::type
164 #endif
165 >
166 Source&
167 start(
168 message_view_base const& m,
169 Args&&... args);
170
171 //--------------------------------------------
172
173 /** Return a new stream for this serializer.
174
175 After the serializer is destroyed, @ref reset is called,
176 or @ref is_done returns true, the only valid operation
177 on the stream is destruction.
178
179 A stream may be used to invert the flow of control
180 when the caller is supplying body data as a series
181 of buffers.
182 */
183 BOOST_HTTP_PROTO_DECL
184 stream
185 start_stream(
186 message_view_base const& m);
187
188 //--------------------------------------------
189
190 /** Return true if serialization is complete.
191 */
192 bool
193 1400 is_done() const noexcept
194 {
195 1400 return is_done_;
196 }
197
198 /** Return the output area.
199
200 This function will serialize some or
201 all of the content and return the
202 corresponding output buffers.
203
204 @par Preconditions
205 @code
206 this->is_done() == false
207 @endcode
208 */
209 BOOST_HTTP_PROTO_DECL
210 auto
211 prepare() ->
212 system::result<
213 const_buffers_type>;
214
215 /** Consume bytes from the output area.
216 */
217 BOOST_HTTP_PROTO_DECL
218 void
219 consume(std::size_t n);
220
221 private:
222 class const_buf_gen_base;
223
224 template<class>
225 class const_buf_gen;
226
227 detail::array_of_const_buffers
228 make_array(std::size_t n);
229
230 template<
231 class Source,
232 class... Args,
233 typename std::enable_if<
234 std::is_constructible<
235 Source,
236 Args...>::value>::type* = nullptr>
237 Source&
238 42 construct_source(Args&&... args)
239 {
240 42 return ws_.emplace<Source>(
241 42 std::forward<Args>(args)...);
242 }
243
244 template<
245 class Source,
246 class... Args,
247 typename std::enable_if<
248 std::is_constructible<
249 Source,
250 detail::workspace&,
251 Args...>::value>::type* = nullptr>
252 Source&
253 construct_source(Args&&... args)
254 {
255 return ws_.emplace<Source>(
256 ws_, std::forward<Args>(args)...);
257 }
258
259 BOOST_HTTP_PROTO_DECL
260 void
261 start_init(
262 message_view_base const&);
263
264 BOOST_HTTP_PROTO_DECL
265 void
266 start_empty(
267 message_view_base const&);
268
269 BOOST_HTTP_PROTO_DECL
270 void
271 start_buffers(
272 message_view_base const&);
273
274 BOOST_HTTP_PROTO_DECL
275 void
276 start_source(
277 message_view_base const&);
278
279 enum class style
280 {
281 empty,
282 buffers,
283 source,
284 stream
285 };
286
287 context& ctx_;
288 detail::workspace ws_;
289
290 const_buf_gen_base* buf_gen_;
291 detail::filter* filter_;
292 source* source_;
293
294 buffers::circular_buffer cb0_;
295 buffers::circular_buffer cb1_;
296 detail::array_of_const_buffers prepped_;
297 buffers::const_buffer tmp_;
298
299 style st_;
300 bool more_input_;
301 bool is_done_;
302 bool is_header_done_;
303 bool is_chunked_;
304 bool needs_exp100_continue_;
305 bool filter_done_;
306 };
307
308 //------------------------------------------------
309
310 /** The type used for caller-provided body data during
311 serialization.
312
313 @code{.cpp}
314 http_proto::serializer sr(128);
315
316 http_proto::request req;
317 auto stream = sr.start_stream(req);
318
319 std::string_view msg = "Hello, world!";
320 auto n = buffers::copy(
321 stream.prepare(),
322 buffers::make_buffer(
323 msg.data(), msg.size()));
324
325 stream.commit(n);
326
327 auto cbs = sr.prepare().value();
328 (void)cbs;
329 @endcode
330 */
331 struct serializer::stream
332 {
333 /** Constructor.
334
335 The only valid operations on default constructed
336 streams are assignment and destruction.
337 */
338 stream() = default;
339
340 /** Constructor.
341
342 The constructed stream will share the same
343 serializer as `other`.
344 */
345 stream(stream const& other) = default;
346
347 /** Assignment.
348
349 The current stream will share the same serializer
350 as `other`.
351 */
352 stream& operator= (
353 stream const& other) = default;
354
355 /** A MutableBufferSequence consisting of a buffer pair.
356 */
357 using buffers_type =
358 buffers::mutable_buffer_pair;
359
360 /** Returns the remaining available capacity.
361
362 The returned value represents the available free
363 space in the backing fixed-sized buffers used by the
364 serializer associated with this stream.
365
366 The capacity is absolute and does not do any
367 accounting for any octets required by a chunked
368 transfer encoding.
369 */
370 BOOST_HTTP_PROTO_DECL
371 std::size_t
372 capacity() const noexcept;
373
374 /** Return true if the stream cannot currently hold
375 additional output data.
376
377 The fixed-sized buffers maintained by the associated
378 serializer can be sufficiently full from previous
379 calls to @ref stream::commit.
380
381 This function can be called to determine if the caller
382 should drain the serializer via @ref serializer::consume calls
383 before attempting to fill the buffer sequence
384 returned from @ref stream::prepare.
385 */
386 BOOST_HTTP_PROTO_DECL
387 bool
388 is_full() const noexcept;
389
390 /** Returns a MutableBufferSequence for storing
391 serializer input. If `n` bytes are written to the
392 buffer sequence, @ref stream::commit must be called
393 with `n` to update the backing serializer's buffers.
394
395 The returned buffer sequence is as wide as is
396 possible.
397
398 @exception std::length_error Thrown if the stream
399 has insufficient capacity and a chunked transfer
400 encoding is being used
401 */
402 BOOST_HTTP_PROTO_DECL
403 buffers_type
404 prepare() const;
405
406 /** Make `n` bytes available to the serializer.
407
408 Once the buffer sequence returned from @ref stream::prepare
409 has been filled, the input can be marked as ready
410 for serialization by using this function.
411
412 @exception std::logic_error Thrown if `commit` is
413 called with 0.
414 */
415 BOOST_HTTP_PROTO_DECL
416 void
417 commit(std::size_t n) const;
418
419 /** Indicate that no more data is coming and that the
420 body should be treated as complete.
421
422 @excpeption std::logic_error Thrown if the stream
423 has been previously closed.
424 */
425 BOOST_HTTP_PROTO_DECL
426 void
427 close() const;
428
429 private:
430 friend class serializer;
431
432 explicit
433 30 stream(
434 serializer& sr) noexcept
435 30 : sr_(&sr)
436 {
437 30 }
438
439 serializer* sr_ = nullptr;
440 };
441
442 //---------------------------------------------------------
443
444 class serializer::const_buf_gen_base
445 {
446 public:
447 // Returns the next non-empty buffer,
448 // or an empty buffer if none remain.
449 virtual
450 buffers::const_buffer
451 next() = 0;
452
453 // Size of remaining buffers
454 virtual
455 std::size_t
456 size() const = 0;
457
458 // Count of remaining non-empty buffers
459 virtual
460 std::size_t
461 count() const = 0;
462
463 // Returns true when there is no buffer or
464 // the remaining buffers are empty
465 virtual
466 bool
467 is_empty() const = 0;
468 };
469
470 template<class ConstBufferSequence>
471 class serializer::const_buf_gen
472 : public const_buf_gen_base
473 {
474 using it_t = decltype(buffers::begin(
475 std::declval<ConstBufferSequence>()));
476
477 ConstBufferSequence cbs_;
478 it_t current_;
479 public:
480 using const_buffer =
481 buffers::const_buffer;
482
483 explicit
484 64 const_buf_gen(ConstBufferSequence cbs)
485 128 : cbs_(std::move(cbs))
486 64 , current_(buffers::begin(cbs_))
487 {
488 64 }
489
490 const_buffer
491 898 next() override
492 {
493
2/2
✓ Branch 1 taken 431 times.
✓ Branch 2 taken 18 times.
898 while(current_ != buffers::end(cbs_))
494 {
495 862 const_buffer buf = *current_++;
496
1/2
✓ Branch 1 taken 431 times.
✗ Branch 2 not taken.
862 if(buf.size() != 0)
497 862 return buf;
498 }
499 36 return {};
500 }
501
502 std::size_t
503 2 size() const override
504 {
505 4 return std::accumulate(
506 2 current_,
507 2 buffers::end(cbs_),
508 std::size_t{},
509 23 [](std::size_t sum, const_buffer cb) {
510 25 return sum + cb.size(); });
511 }
512
513 std::size_t
514 64 count() const override
515 {
516 192 return std::count_if(
517 64 current_,
518 64 buffers::end(cbs_),
519 642 [](const_buffer cb) {
520 706 return cb.size() != 0; });
521 }
522
523 bool
524 70 is_empty() const override
525 {
526 210 return std::all_of(
527 70 current_,
528 70 buffers::end(cbs_),
529 231 [](const_buffer cb) {
530 301 return cb.size() == 0; });
531 }
532 };
533
534 //---------------------------------------------------------
535
536 template<
537 class ConstBufferSequence,
538 class>
539 void
540 64 serializer::
541 start(
542 message_view_base const& m,
543 ConstBufferSequence&& cbs)
544 {
545 static_assert(buffers::is_const_buffer_sequence<
546 ConstBufferSequence>::value,
547 "ConstBufferSequence type requirements not met");
548
549 64 start_init(m);
550 128 buf_gen_ = std::addressof(
551 ws_.emplace<const_buf_gen<typename
552 64 std::decay<ConstBufferSequence>::type>>(
553 std::forward<ConstBufferSequence>(cbs)));
554 64 start_buffers(m);
555 64 }
556
557 template<
558 class Source,
559 class... Args,
560 class>
561 Source&
562 33 serializer::
563 start(
564 message_view_base const& m,
565 Args&&... args)
566 {
567 static_assert(
568 !std::is_abstract<Source>::value, "");
569 static_assert(
570 std::is_constructible<Source, Args...>::value ||
571 std::is_constructible<Source, detail::workspace&, Args...>::value,
572 "The Source cannot be constructed with the given arguments");
573
574 33 start_init(m);
575 33 auto& src = construct_source<Source>(
576 std::forward<Args>(args)...);
577 33 source_ = std::addressof(src);
578 33 start_source(m);
579 33 return src;
580 }
581
582 } // http_proto
583 } // boost
584
585 #endif
586