GCC Code Coverage Report


Directory: libs/http_proto/
File: include/boost/http_proto/serializer.hpp
Date: 2025-06-14 21:08:33
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 1348 is_done() const noexcept
194 {
195 1348 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 34 construct_source(Args&&... args)
239 {
240 34 return ws_.emplace<Source>(
241 34 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 22 stream(
434 serializer& sr) noexcept
435 22 : sr_(&sr)
436 {
437 22 }
438
439 serializer* sr_ = nullptr;
440 };
441
442 //---------------------------------------------------------
443
444 class serializer::const_buf_gen_base
445 {
446 public:
447 // Next non-empty buffer
448 virtual
449 buffers::const_buffer
450 operator()() = 0;
451
452 // Size of remaining buffers
453 virtual
454 std::size_t
455 size() const = 0;
456
457 // Count of remaining non-empty buffers
458 virtual
459 std::size_t
460 count() const = 0;
461
462 // Returns true when there is no buffer or
463 // the remaining buffers are empty
464 virtual
465 bool
466 is_empty() const = 0;
467 };
468
469 template<class ConstBufferSequence>
470 class serializer::const_buf_gen
471 : public const_buf_gen_base
472 {
473 using it_t = decltype(buffers::begin(
474 std::declval<ConstBufferSequence>()));
475
476 ConstBufferSequence cbs_;
477 it_t current_;
478 public:
479 using const_buffer =
480 buffers::const_buffer;
481
482 explicit
483 48 const_buf_gen(ConstBufferSequence cbs)
484 96 : cbs_(std::move(cbs))
485 48 , current_(buffers::begin(cbs_))
486 {
487 48 }
488
489 const_buffer
490 866 operator()() override
491 {
492
2/2
✓ Branch 1 taken 431 times.
✓ Branch 2 taken 2 times.
866 while(current_ != buffers::end(cbs_))
493 {
494 862 const_buffer buf = *current_++;
495
1/2
✓ Branch 1 taken 431 times.
✗ Branch 2 not taken.
862 if(buf.size() != 0)
496 862 return buf;
497 }
498 4 return {};
499 }
500
501 std::size_t
502 2 size() const override
503 {
504 4 return std::accumulate(
505 2 current_,
506 2 buffers::end(cbs_),
507 std::size_t{},
508 23 [](std::size_t sum, const_buffer cb) {
509 25 return sum + cb.size(); });
510 }
511
512 std::size_t
513 48 count() const override
514 {
515 144 return std::count_if(
516 48 current_,
517 48 buffers::end(cbs_),
518 458 [](const_buffer cb) {
519 506 return cb.size() != 0; });
520 }
521
522 bool
523 824 is_empty() const override
524 {
525 2472 return std::all_of(
526 824 current_,
527 824 buffers::end(cbs_),
528 415 [](const_buffer cb) {
529 1239 return cb.size() == 0; });
530 }
531 };
532
533 //---------------------------------------------------------
534
535 template<
536 class ConstBufferSequence,
537 class>
538 void
539 48 serializer::
540 start(
541 message_view_base const& m,
542 ConstBufferSequence&& cbs)
543 {
544 static_assert(buffers::is_const_buffer_sequence<
545 ConstBufferSequence>::value,
546 "ConstBufferSequence type requirements not met");
547
548 48 start_init(m);
549 96 buf_gen_ = std::addressof(
550 ws_.emplace<const_buf_gen<typename
551 48 std::decay<ConstBufferSequence>::type>>(
552 std::forward<ConstBufferSequence>(cbs)));
553 48 start_buffers(m);
554 48 }
555
556 template<
557 class Source,
558 class... Args,
559 class>
560 Source&
561 25 serializer::
562 start(
563 message_view_base const& m,
564 Args&&... args)
565 {
566 static_assert(
567 !std::is_abstract<Source>::value, "");
568 static_assert(
569 std::is_constructible<Source, Args...>::value ||
570 std::is_constructible<Source, detail::workspace&, Args...>::value,
571 "The Source cannot be constructed with the given arguments");
572
573 25 start_init(m);
574 25 auto& src = construct_source<Source>(
575 std::forward<Args>(args)...);
576 25 source_ = std::addressof(src);
577 25 start_source(m);
578 25 return src;
579 }
580
581 } // http_proto
582 } // boost
583
584 #endif
585