GCC Code Coverage Report


Directory: ./
File: libs/beast2/include/boost/beast2/server/body_source.hpp
Date: 2025-12-24 17:07:59
Exec Total Coverage
Lines: 121 125 96.8%
Functions: 30 30 100.0%
Branches: 21 23 91.3%

Line Branch Exec Source
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot 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/beast2
8 //
9
10 #ifndef BOOST_BEAST2_SERVER_BODY_SOURCE_HPP
11 #define BOOST_BEAST2_SERVER_BODY_SOURCE_HPP
12
13 #include <boost/beast2/detail/config.hpp>
14 #include <boost/beast2/detail/except.hpp>
15 #include <boost/beast2/detail/type_traits.hpp>
16 #include <boost/http_proto/error.hpp>
17 #include <boost/buffers/buffer.hpp>
18 #include <boost/buffers/copy.hpp>
19 #include <boost/buffers/slice.hpp>
20 #include <boost/buffers/data_source.hpp>
21 #include <boost/buffers/read_source.hpp>
22 #include <boost/core/span.hpp>
23 #include <boost/system/error_code.hpp>
24
25 namespace boost {
26 namespace beast2 {
27
28 /** Tag for customizing body construction
29 */
30 struct construct_body_tag {};
31
32 //-----------------------------------------------
33
34 /** A type erased HTTP message body.
35
36 This type erases the specific body type used to represent the message body.
37 It provides a uniform interface for reading the body data regardless of
38 how the body is implemented. Accessing the bytes is achieved by calling
39 @ref read which reads data into a caller-provided buffer. Alternatively,
40 when @ref has_buffers returns `true` the body consists of buffers in memory,
41 and they can be accessed directly by calling @ref get_buffers.
42
43 Example bodies include:
44 - in-memory buffers
45 - file-backed bodies
46 - generated bodies
47
48 @note A body_source is movable but not copyable.
49
50 Type-erased bodies can always be rewound to the beginning by calling
51 @ref rewind. Therefore, bodies can be read multiple times.
52
53 @par Thread Safety
54 Unsafe.
55 */
56 class body_source
57 {
58 public:
59 /** Destructor.
60 */
61 BOOST_BEAST2_DECL ~body_source();
62
63 /** Constructor
64
65 Default-constructed bodies are empty.
66 */
67 body_source() = default;
68
69 /** Constructor
70
71 The content of @p other is transferred to `*this`. The
72 moved-from body is left empty, as if default-constructed.
73 */
74 2 body_source(body_source&& other) noexcept
75 2 : impl_(other.impl_)
76 {
77 2 other.impl_ = nullptr;
78 2 }
79
80 /** Assignment
81
82 The previous body, if any, is released and the
83 content of @p other is transferred to `*this`.
84 */
85 BOOST_BEAST2_DECL
86 body_source& operator=(body_source&& other) noexcept;
87
88 /** Construct a streaming body source.
89 */
90 template<class ReadSource
91 , typename std::enable_if<
92 ! std::is_same<typename std::decay<ReadSource>::type, body_source>::value &&
93 buffers::is_read_source<typename std::decay<ReadSource>::type>::value
94 , int>::type = 0>
95 body_source(ReadSource&& body);
96
97 /** Construct a streaming body source with a known size.
98 */
99 template<class ReadSource
100 , typename std::enable_if<
101 ! std::is_same<typename std::decay<ReadSource>::type, body_source>::value &&
102 buffers::is_read_source<typename std::decay<ReadSource>::type>::value
103 , int>::type = 0>
104 body_source(
105 std::size_t known_size,
106 ReadSource&& body);
107
108 /** Construct a buffers body source.
109 */
110 template<class DataSource
111 , typename std::enable_if<
112 ! std::is_same<typename std::decay<DataSource>::type, body_source>::value &&
113 buffers::is_data_source<typename std::decay<DataSource>::type>::value
114 , int>::type = 0>
115 body_source(DataSource&& body);
116
117 //template<class T>
118 //body(T&& t);
119
120 /** Return `true` if the size of the body is known.
121 */
122 10 bool has_size() const noexcept
123 {
124
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
10 if(impl_)
125 8 return impl_->has_size();
126 2 return true; // empty
127 }
128
129 /** Return `true` if the body consists of buffers in memory.
130 When the body consists of buffers in memory, they can
131 also be accessed directly using @ref get_buffers.
132 */
133 10 bool has_buffers() const noexcept
134 {
135
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
10 if(impl_)
136 8 return impl_->has_buffers();
137 2 return true; // empty
138 }
139
140 /** Return the size of the body, if available.
141 @throw std::invalid_argument if @ref has_size returns `false`.
142 @return The size of the body in bytes.
143 */
144 10 auto size() const -> std::size_t
145 {
146
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
10 if(impl_)
147 8 return impl_->size();
148 2 return 0; // empty
149 }
150
151 /** Return the buffers representing the body, if available.
152 @throw std::invalid_argument if @ref has_buffers returns `false`.
153 @return A span of buffers representing the body.
154 */
155 10 auto data() const ->
156 span<buffers::const_buffer const>
157 {
158
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
10 if(impl_)
159 8 return impl_->data();
160 2 return {}; // empty
161 }
162
163 /** Rewind the body to the beginning.
164 This allows the body to be accessed from the start when calling @read.
165 */
166 154 void rewind()
167 {
168
2/2
✓ Branch 0 taken 136 times.
✓ Branch 1 taken 18 times.
154 if(impl_)
169 136 return impl_->rewind();
170 // empty
171 }
172
173 /** Read from the body into a caller-provided buffer.
174
175 @param dest A pointer to the buffer to read into.
176 @param n The maximum number of bytes to read.
177 @param ec Set to the error, if any occurred.
178 @return The number of bytes read, which may be
179 less than the number requested. A return value of
180 zero indicates end-of-body.
181 */
182 auto
183 320 read(void* dest, std::size_t n,
184 system::error_code& ec) ->
185 std::size_t
186 {
187
2/2
✓ Branch 0 taken 304 times.
✓ Branch 1 taken 16 times.
320 if(impl_)
188 304 return impl_->read(dest, n, ec);
189 // empty
190 16 ec = http::error::end_of_stream;
191 16 return 0;
192 }
193
194 private:
195 struct impl
196 {
197 12 virtual ~impl() = default;
198 4 virtual bool has_size() const noexcept { return false; }
199 5 virtual bool has_buffers() const noexcept { return false; }
200 4 virtual std::size_t size() const
201 {
202 4 detail::throw_invalid_argument();
203 }
204 5 virtual auto data() const ->
205 span<buffers::const_buffer const>
206 {
207 5 detail::throw_invalid_argument();
208 }
209 virtual void rewind() = 0;
210 virtual std::size_t read(
211 void* dest, std::size_t n,
212 system::error_code& ec) = 0;
213 };
214
215 impl* impl_ = nullptr;
216 };
217
218 //-----------------------------------------------
219
220 template<class ReadSource
221 , typename std::enable_if<
222 ! std::is_same<typename std::decay<ReadSource>::type, body_source>::value &&
223 buffers::is_read_source<typename std::decay<ReadSource>::type>::value
224 , int>::type>
225 3 body_source::
226 body_source(
227 3 ReadSource&& body)
228 {
229 struct model : impl
230 {
231 system::error_code ec_;
232 typename std::decay<ReadSource>::type body_;
233
234 3 explicit model(ReadSource&& body)
235 3 : body_(std::forward<ReadSource>(body))
236 {
237 3 }
238
239 68 void rewind() override
240 {
241 68 ec_ = {};
242 68 body_.rewind();
243 68 }
244
245 154 std::size_t read(
246 void* dest,
247 std::size_t size,
248 system::error_code& ec) override
249 {
250
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 154 times.
154 if(ec_.failed())
251 {
252 ec = ec_;
253 return 0;
254 }
255 154 auto nread = body_.read(
256 154 buffers::mutable_buffer(dest, size), ec);
257 154 ec_ = ec;
258 154 return nread;
259 }
260 };
261
262 3 auto p = ::operator new(sizeof(model));
263 3 impl_ = ::new(p) model(
264 std::forward<ReadSource>(body));
265 3 }
266
267 /** Construct a streaming body source with a known size.
268 */
269 template<class ReadSource
270 , typename std::enable_if<
271 ! std::is_same<typename std::decay<ReadSource>::type, body_source>::value &&
272 buffers::is_read_source<typename std::decay<ReadSource>::type>::value
273 , int>::type>
274 1 body_source::
275 body_source(
276 std::size_t known_size,
277 1 ReadSource&& body)
278 {
279 struct model : impl
280 {
281 std::size_t size_;
282 system::error_code ec_;
283 typename std::decay<ReadSource>::type body_;
284
285 1 model(
286 ReadSource&& body,
287 std::size_t known_size)
288 1 : size_(known_size)
289 1 , body_(std::forward<ReadSource>(body))
290 {
291 1 }
292
293 1 bool has_size() const noexcept override
294 {
295 1 return true;
296 }
297
298 1 std::size_t size() const override
299 {
300 1 return size_;
301 }
302
303 17 void rewind() override
304 {
305 17 ec_ = {};
306 17 body_.rewind();
307 17 }
308
309 24 std::size_t read(
310 void* dest,
311 std::size_t size,
312 system::error_code& ec) override
313 {
314
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
24 if(ec_.failed())
315 {
316 ec = ec_;
317 return 0;
318 }
319 24 auto nread = body_.read(
320 24 buffers::mutable_buffer(dest, size), ec);
321 24 ec_ = ec;
322 24 return nread;
323 }
324 };
325
326 1 auto p = ::operator new(sizeof(model));
327 1 impl_ = ::new(p) model(
328 std::forward<ReadSource>(body), known_size);
329 1 }
330
331 /** Construct a buffers body source.
332 */
333 template<class DataSource
334 , typename std::enable_if<
335 ! std::is_same<typename std::decay<DataSource>::type, body_source>::value &&
336 buffers::is_data_source<typename std::decay<DataSource>::type>::value
337 , int>::type>
338 2 body_source::
339 body_source(
340 2 DataSource&& body)
341 {
342 struct model : impl
343 {
344 typename std::decay<DataSource>::type body_;
345 std::size_t size_ = 0;
346 span<boost::buffers::const_buffer> bs_;
347 std::size_t nread_ = 0;
348
349 2 explicit model(
350 DataSource&& body) noexcept
351 2 : body_(std::forward<DataSource>(body))
352 {
353 2 auto const& data = body_.data();
354 2 auto const& end = buffers::end(data);
355 2 auto p = reinterpret_cast<
356 buffers::const_buffer*>(this+1);
357 2 std::size_t length = 0;
358
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
4 for(auto it = buffers::begin(data); it != end; ++it)
359 {
360 2 boost::buffers::const_buffer cb(*it);
361 2 size_ += cb.size();
362 2 *p++ = cb;
363 2 ++length;
364 }
365 2 bs_ = { reinterpret_cast<
366 buffers::const_buffer*>(this + 1), length };
367 2 }
368
369 3 bool has_size() const noexcept override
370 {
371 3 return true;
372 }
373
374 3 bool has_buffers() const noexcept override
375 {
376 3 return true;
377 }
378
379 3 std::size_t size() const override
380 {
381 3 return size_;
382 }
383
384 span<buffers::const_buffer const>
385 3 data() const override
386 {
387 3 return bs_;
388 }
389
390 51 void rewind() override
391 {
392 51 nread_ = 0;
393 51 }
394
395 126 std::size_t read(
396 void* dest,
397 std::size_t n0,
398 system::error_code& ec) override
399 {
400 126 std::size_t n = buffers::copy(
401 126 buffers::mutable_buffer(dest, n0),
402 126 buffers::sans_prefix(bs_, nread_));
403 126 nread_ += n;
404
2/2
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 78 times.
126 if(nread_ >= size_)
405 48 ec = http::error::end_of_stream;
406 else
407 78 ec = {};
408 126 return n;
409 }
410 };
411
412 2 std::size_t length = 0;
413 2 auto const& data = body.data();
414 2 auto const& end = buffers::end(data);
415 2 for(auto it = buffers::begin(data);
416
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 it != end; ++it)
417 {
418 2 ++length;
419 }
420
421 // VFALCO this requires DataSource to be nothrow
422 // move constructible for strong exception safety.
423 4 auto p = ::operator new(sizeof(model) +
424
1/1
✓ Branch 1 taken 2 times.
2 length * sizeof(buffers::const_buffer));
425 2 impl_ = ::new(p) model(
426 std::forward<DataSource>(body));
427 2 }
428
429 } // beast2
430 } // boost
431
432 #endif
433