GCC Code Coverage Report


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