GCC Code Coverage Report


Directory: ./
File: libs/beast2/include/boost/beast2/server/http_stream.hpp
Date: 2026-01-04 15:38:14
Exec Total Coverage
Lines: 0 150 0.0%
Functions: 0 21 0.0%
Branches: 0 112 0.0%

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_HTTP_STREAM_HPP
11 #define BOOST_BEAST2_SERVER_HTTP_STREAM_HPP
12
13 #include <boost/beast2/detail/config.hpp>
14 #include <boost/beast2/log_service.hpp>
15 #include <boost/beast2/error.hpp>
16 #include <boost/beast2/format.hpp>
17 #include <boost/beast2/read.hpp>
18 #include <boost/beast2/server/any_lambda.hpp>
19 #include <boost/beast2/server/route_handler_asio.hpp>
20 #include <boost/beast2/server/router_asio.hpp>
21 #include <boost/beast2/wrap_executor.hpp>
22 #include <boost/beast2/write.hpp>
23 #include <boost/beast2/detail/except.hpp>
24 #include <boost/capy/application.hpp>
25 #include <boost/http_proto/request_parser.hpp>
26 #include <boost/http_proto/response.hpp>
27 #include <boost/http_proto/serializer.hpp>
28 #include <boost/http_proto/string_body.hpp>
29 #include <boost/http_proto/server/basic_router.hpp>
30 #include <boost/url/parse.hpp>
31 #include <boost/asio/prepend.hpp>
32
33 namespace boost {
34 namespace beast2 {
35
36 //------------------------------------------------
37
38 /** An HTTP server stream which routes requests to handlers and sends responses.
39
40 An object of this type wraps an asynchronous Boost.ASIO stream and implements
41 a high level server connection which reads HTTP requests, routes them to
42 handlers installed in a router, and sends the HTTP response.
43
44 @par Requires
45 `AsyncStream` must satisfy <em>AsyncReadStream</em> and <em>AsyncWriteStream</em>
46
47 @tparam AsyncStream The type of asynchronous stream.
48 */
49 template<class AsyncStream>
50 class http_stream
51 : private http::suspender::owner
52 {
53 public:
54 /** Constructor.
55
56 This initializes a new HTTP connection object that operates on
57 the given stream, uses the specified router to dispatch incoming
58 requests, and calls the supplied completion function when the
59 connection closes or fails.
60
61 Construction does not start any I/O; call @ref on_stream_begin when
62 the stream is connected to the remote peer to begin reading
63 requests and processing them.
64
65 @param app The owning application, used to access shared services
66 such as logging and protocol objects.
67 @param stream The underlying asynchronous stream to read from
68 and write to. The caller is responsible for maintaining its
69 lifetime for the duration of the session.
70 @param routes The router used to dispatch incoming HTTP requests.
71 @param close_fn The function invoked when the connection is closed
72 or an unrecoverable error occurs.
73 */
74 http_stream(
75 capy::application& app,
76 AsyncStream& stream,
77 router_asio<AsyncStream&> routes,
78 any_lambda<void(system::error_code)> close_fn);
79
80 /** Called to start a new HTTP session
81
82 The stream must be in a connected,
83 correct state for a new session.
84 */
85 void on_stream_begin(http::acceptor_config const& config);
86
87 private:
88 void do_read();
89 void on_read(
90 system::error_code ec,
91 std::size_t bytes_transferred);
92 void on_headers();
93 void do_dispatch(http::route_result rv = {});
94 void do_read_body();
95 void on_read_body(
96 system::error_code ec,
97 std::size_t bytes_transferred);
98 void do_respond(http::route_result rv);
99 void do_write();
100 void on_write(
101 system::error_code const& ec,
102 std::size_t bytes_transferred);
103 void on_complete();
104 http::resumer do_suspend() override;
105 void do_resume(http::route_result const& rv) override;
106 void do_resume(std::exception_ptr ep) override;
107 void do_close();
108 void do_fail(core::string_view s,
109 system::error_code const& ec);
110 void clear() noexcept;
111
112 protected:
113 std::string id() const
114 {
115 return std::string("[") + std::to_string(id_) + "] ";
116 }
117
118 protected:
119 struct resetter;
120 section sect_;
121 std::size_t id_ = 0;
122 AsyncStream& stream_;
123 router_asio<AsyncStream&> routes_;
124 any_lambda<void(system::error_code)> close_;
125 http::acceptor_config const* pconfig_ = nullptr;
126
127 using work_guard = asio::executor_work_guard<decltype(
128 std::declval<AsyncStream&>().get_executor())>;
129 std::unique_ptr<work_guard> pwg_;
130 asio_route_params<AsyncStream&> rp_;
131 };
132
133 //------------------------------------------------
134
135 // for exception safety
136 template<class AsyncStream>
137 struct http_stream<AsyncStream>::
138 resetter
139 {
140 ~resetter()
141 {
142 if(clear_)
143 owner_.clear();
144 }
145
146 explicit resetter(
147 http_stream<AsyncStream>& owner) noexcept
148 : owner_(owner)
149 {
150 }
151
152 void accept()
153 {
154 clear_ = false;
155 }
156
157 private:
158 http_stream<AsyncStream>& owner_;
159 bool clear_ = true;
160 };
161
162 //------------------------------------------------
163
164 template<class AsyncStream>
165 http_stream<AsyncStream>::
166 http_stream(
167 capy::application& app,
168 AsyncStream& stream,
169 router_asio<AsyncStream&> routes,
170 any_lambda<void(system::error_code)> close)
171 : sect_(use_log_service(app).get_section("http_stream"))
172 , id_(
173 []() noexcept
174 {
175 static std::size_t n = 0;
176 return ++n;
177 }())
178 , stream_(stream)
179 , routes_(std::move(routes))
180 , close_(close)
181 , rp_(stream_)
182 {
183 rp_.parser = http::request_parser(app);
184
185 rp_.serializer = http::serializer(app);
186 rp_.suspend = http::suspender(*this);
187 rp_.ex = wrap_executor(stream_.get_executor());
188 }
189
190 // called to start a new HTTP session.
191 // the connection must be in the correct state already.
192 template<class AsyncStream>
193 void
194 http_stream<AsyncStream>::
195 on_stream_begin(
196 http::acceptor_config const& config)
197 {
198 pconfig_ = &config;
199
200 rp_.parser.reset();
201 rp_.session_data.clear();
202 do_read();
203 }
204
205 // begin reading the request
206 template<class AsyncStream>
207 void
208 http_stream<AsyncStream>::
209 do_read()
210 {
211 rp_.parser.start();
212
213 beast2::async_read_some(
214 stream_,
215 rp_.parser,
216 call_mf(&http_stream::on_read, this));
217 }
218
219 // called when the read operation completes
220 template<class AsyncStream>
221 void
222 http_stream<AsyncStream>::
223 on_read(
224 system::error_code ec,
225 std::size_t bytes_transferred)
226 {
227 (void)bytes_transferred;
228
229 if(ec.failed())
230 return do_fail("http_stream::on_read", ec);
231
232 LOG_TRC(this->sect_)(
233 "{} http_stream::on_read bytes={}",
234 this->id(), bytes_transferred);
235
236 on_headers();
237 }
238
239 // called to set up the response after reading the request
240 template<class AsyncStream>
241 void
242 http_stream<AsyncStream>::
243 on_headers()
244 {
245 // set up Request and Response objects
246 // VFALCO HACK for now we make a copy of the message
247 rp_.req = rp_.parser.get();
248 rp_.route_data.clear();
249 rp_.res.set_start_line( // VFALCO WTF
250 http::status::ok, rp_.req.version());
251 rp_.res.set_keep_alive(rp_.req.keep_alive());
252 rp_.serializer.reset();
253
254 // parse the URL
255 {
256 auto rv = urls::parse_uri_reference(rp_.req.target());
257 if(rv.has_error())
258 {
259 // error parsing URL
260 rp_.status(http::status::bad_request);
261 rp_.set_body("Bad Request: " + rv.error().message());
262 return do_respond(rv.error());
263 }
264
265 rp_.url = rv.value();
266 }
267
268 // invoke handlers for the route
269 do_dispatch();
270 }
271
272 // called to dispatch or resume the route
273 template<class AsyncStream>
274 void
275 http_stream<AsyncStream>::
276 do_dispatch(
277 http::route_result rv)
278 {
279 if(! rv.failed())
280 {
281 BOOST_ASSERT(! pwg_); // can't be suspended
282 rv = routes_.dispatch(
283 rp_.req.method(), rp_.url, rp_);
284 }
285 else
286 {
287 rv = routes_.resume(rp_, rv);
288 }
289
290 do_respond(rv);
291 }
292
293 // finish reading the body
294 template<class AsyncStream>
295 void
296 http_stream<AsyncStream>::
297 do_read_body()
298 {
299 beast2::async_read(
300 stream_,
301 rp_.parser,
302 call_mf(&http_stream::on_read_body, this));
303 }
304
305 // called repeatedly when reading the body
306 template<class AsyncStream>
307 void
308 http_stream<AsyncStream>::
309 on_read_body(
310 system::error_code ec,
311 std::size_t bytes_transferred)
312 {
313 if(ec.failed())
314 return do_fail("http_stream::on_read_body", ec);
315
316 LOG_TRC(this->sect_)(
317 "{} http_stream::on_read_body bytes={}",
318 this->id(), bytes_transferred);
319
320 BOOST_ASSERT(rp_.parser.is_complete());
321
322 rp_.do_finish();
323 }
324
325 // called after obtaining a route result
326 template<class AsyncStream>
327 void
328 http_stream<AsyncStream>::
329 do_respond(
330 http::route_result rv)
331 {
332 BOOST_ASSERT(rv != http::route::next_route);
333
334 if(rv == http::route::close)
335 {
336 return do_close();
337 }
338
339 if(rv == http::route::complete)
340 {
341 // VFALCO what if the connection was closed or keep-alive=false?
342 // handler sent the response?
343 BOOST_ASSERT(rp_.serializer.is_done());
344 return on_write(system::error_code(), 0);
345 }
346
347 if(rv == http::route::suspend)
348 {
349 // didn't call suspend()?
350 if(! pwg_)
351 detail::throw_logic_error();
352 if(rp_.parser.is_body_set())
353 return do_read_body();
354 return;
355 }
356
357 if(rv == http::route::next)
358 {
359 // unhandled request
360 auto const status = http::status::not_found;
361 rp_.status(status);
362 rp_.set_body(http::to_string(status));
363 }
364 else if(rv != http::route::send)
365 {
366 // error message of last resort
367 BOOST_ASSERT(rv.failed());
368 BOOST_ASSERT(! http::is_route_result(rv));
369 rp_.status(http::status::internal_server_error);
370 std::string s;
371 format_to(s, "An internal server error occurred: {}", rv.message());
372 rp_.res.set_keep_alive(false); // VFALCO?
373 rp_.set_body(s);
374 }
375
376 do_write();
377 }
378
379 // begin writing the response
380 template<class AsyncStream>
381 void
382 http_stream<AsyncStream>::
383 do_write()
384 {
385 BOOST_ASSERT(! rp_.serializer.is_done());
386 beast2::async_write(stream_, rp_.serializer,
387 call_mf(&http_stream::on_write, this));
388 }
389
390 // called when the write operation completes
391 template<class AsyncStream>
392 void
393 http_stream<AsyncStream>::
394 on_write(
395 system::error_code const& ec,
396 std::size_t bytes_transferred)
397 {
398 (void)bytes_transferred;
399
400 if(ec.failed())
401 return do_fail("http_stream::on_write", ec);
402
403 BOOST_ASSERT(rp_.serializer.is_done());
404
405 LOG_TRC(this->sect_)(
406 "{} http_stream::on_write bytes={}",
407 this->id(), bytes_transferred);
408
409 if(rp_.res.keep_alive())
410 return do_read();
411
412 do_close();
413 }
414
415 template<class AsyncStream>
416 auto
417 http_stream<AsyncStream>::
418 do_suspend() ->
419 http::resumer
420 {
421 BOOST_ASSERT(stream_.get_executor().running_in_this_thread());
422
423 // can't call twice
424 BOOST_ASSERT(! pwg_);
425 pwg_.reset(new work_guard(stream_.get_executor()));
426
427 // VFALCO cancel timer
428
429 return http::resumer(*this);
430 }
431
432 // called by resume(rv)
433 template<class AsyncStream>
434 void
435 http_stream<AsyncStream>::
436 do_resume(
437 http::route_result const& rv)
438 {
439 asio::dispatch(
440 stream_.get_executor(),
441 [this, rv]
442 {
443 BOOST_ASSERT(pwg_.get() != nullptr);
444 pwg_.reset();
445
446 do_dispatch(rv);
447 });
448 }
449
450 // called by resume(ep)
451 template<class AsyncStream>
452 void
453 http_stream<AsyncStream>::
454 do_resume(
455 std::exception_ptr ep)
456 {
457 asio::dispatch(
458 stream_.get_executor(),
459 [this, ep]
460 {
461 BOOST_ASSERT(pwg_.get() != nullptr);
462 pwg_.reset();
463
464 rp_.status(http::status::internal_server_error);
465 try
466 {
467 std::rethrow_exception(ep);
468 }
469 catch(std::exception const& e)
470 {
471 std::string s;
472 format_to(s, "An internal server error occurred: {}", e.what());
473 rp_.set_body(s);
474 }
475 catch(...)
476 {
477 rp_.set_body("An internal server error occurred");
478 }
479 rp_.res.set_keep_alive(false);
480 do_write();
481 });
482 }
483
484 // called when a non-recoverable error occurs
485 template<class AsyncStream>
486 void
487 http_stream<AsyncStream>::
488 do_fail(
489 core::string_view s, system::error_code const& ec)
490 {
491 LOG_TRC(this->sect_)("{}: {}", s, ec.message());
492
493 // tidy up lingering objects
494 rp_.parser.reset();
495 rp_.serializer.reset();
496
497 close_(ec);
498 }
499
500 // end the session
501 template<class AsyncStream>
502 void
503 http_stream<AsyncStream>::
504 do_close()
505 {
506 clear();
507 close_({});
508 }
509
510 // clear everything, releasing transient objects
511 template<class AsyncStream>
512 void
513 http_stream<AsyncStream>::
514 clear() noexcept
515 {
516 rp_.parser.reset();
517 rp_.serializer.reset();
518 rp_.res.clear();
519 }
520
521 } // beast2
522 } // boost
523
524 #endif
525