GCC Code Coverage Report


Directory: ./
File: libs/beast2/include/boost/beast2/server/http_stream.hpp
Date: 2025-12-03 02:12:11
Exec Total Coverage
Lines: 0 120 0.0%
Functions: 0 17 0.0%
Branches: 0 91 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_proto::detacher::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_proto::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_proto::route_result rv = {});
93 void do_respond(http_proto::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_proto::resumer do_detach() override;
100 void do_resume(http_proto::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_proto::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 http_proto::Request req_;
125 ResponseAsio<AsyncStream&> res_;
126 };
127
128 //------------------------------------------------
129
130 // for exception safety
131 template<class AsyncStream>
132 struct http_stream<AsyncStream>::
133 resetter
134 {
135 ~resetter()
136 {
137 if(clear_)
138 owner_.clear();
139 }
140
141 explicit resetter(
142 http_stream<AsyncStream>& owner) noexcept
143 : owner_(owner)
144 {
145 }
146
147 void accept()
148 {
149 clear_ = false;
150 }
151
152 private:
153 http_stream<AsyncStream>& owner_;
154 bool clear_ = true;
155 };
156
157 //------------------------------------------------
158
159 template<class AsyncStream>
160 http_stream<AsyncStream>::
161 http_stream(
162 capy::application& app,
163 AsyncStream& stream,
164 router_asio<AsyncStream&> routes,
165 any_lambda<void(system::error_code)> close)
166 : sect_(use_log_service(app).get_section("http_stream"))
167 , id_(
168 []() noexcept
169 {
170 static std::size_t n = 0;
171 return ++n;
172 }())
173 , stream_(stream)
174 , routes_(std::move(routes))
175 , close_(close)
176 , res_(stream_)
177 {
178 req_.parser = http_proto::request_parser(app);
179
180 res_.serializer = http_proto::serializer(app);
181 res_.detach = http_proto::detacher(*this);
182 }
183
184 // called to start a new HTTP session.
185 // the connection must be in the correct state already.
186 template<class AsyncStream>
187 void
188 http_stream<AsyncStream>::
189 on_stream_begin(
190 http_proto::acceptor_config const& config)
191 {
192 pconfig_ = &config;
193
194 req_.parser.reset();
195 res_.data.clear();
196 do_read();
197 }
198
199 // begin reading the request
200 template<class AsyncStream>
201 void
202 http_stream<AsyncStream>::
203 do_read()
204 {
205 req_.parser.start();
206
207 beast2::async_read(stream_, req_.parser,
208 call_mf(&http_stream::on_read, this));
209 }
210
211 // called when the read operation completes
212 template<class AsyncStream>
213 void
214 http_stream<AsyncStream>::
215 on_read(
216 system::error_code ec,
217 std::size_t bytes_transferred)
218 {
219 (void)bytes_transferred;
220
221 if(ec.failed())
222 return do_fail("http_stream::on_read", ec);
223
224 LOG_TRC(this->sect_)(
225 "{} http_stream::on_read bytes={}",
226 this->id(), bytes_transferred);
227
228 BOOST_ASSERT(req_.parser.is_complete());
229
230 on_headers();
231 }
232
233 // called to set up the response after reading the request
234 template<class AsyncStream>
235 void
236 http_stream<AsyncStream>::
237 on_headers()
238 {
239 // set up Request and Response objects
240 res_.serializer.reset();
241 // VFALCO HACK for now we make a copy of the message
242 req_.message = req_.parser.get();
243 //res_.message.set_version(req_.message.version());
244 res_.message.set_start_line( // VFALCO WTF
245 http_proto::status::ok, req_.message.version());
246 res_.message.set_keep_alive(req_.message.keep_alive());
247 res_.data.clear();
248
249 // parse the URL
250 {
251 auto rv = urls::parse_uri_reference(req_.message.target());
252 if(rv.has_error())
253 {
254 // error parsing URL
255 res_.status(http_proto::status::bad_request);
256 res_.set_body("Bad Request: " + rv.error().message());
257 return do_respond(rv.error());
258 }
259
260 req_.url = rv.value();
261 }
262
263 // invoke handlers for the route
264 do_dispatch();
265 }
266
267 // called to dispatch or resume the route
268 template<class AsyncStream>
269 void
270 http_stream<AsyncStream>::
271 do_dispatch(
272 http_proto::route_result rv)
273 {
274 if(! rv.failed())
275 {
276 BOOST_ASSERT(! pwg_); // can't be detached
277 rv = routes_.dispatch(
278 req_.message.method(), req_.url, req_, res_);
279 }
280 else
281 {
282 rv = routes_.resume(req_, res_, rv);
283 }
284
285 do_respond(rv);
286 }
287
288 // called after obtaining a route result
289 template<class AsyncStream>
290 void
291 http_stream<AsyncStream>::
292 do_respond(
293 http_proto::route_result rv)
294 {
295 BOOST_ASSERT(rv != http_proto::route::next_route);
296
297 if(rv == http_proto::route::close)
298 {
299 return do_close();
300 }
301
302 if(rv == http_proto::route::complete)
303 {
304 // VFALCO what if the connection was closed or keep-alive=false?
305 // handler sendt the response?
306 BOOST_ASSERT(res_.serializer.is_done());
307 return on_write(system::error_code(), 0);
308 }
309
310 if(rv == http_proto::route::detach)
311 {
312 // didn't call res.detach()?
313 if(! pwg_)
314 detail::throw_logic_error();
315 return;
316 }
317
318 if(rv == http_proto::route::next)
319 {
320 // unhandled request
321 auto const status = http_proto::status::not_found;
322 res_.status(status);
323 //res_.message.set_keep_alive(false); // VFALCO?
324 res_.set_body(http_proto::to_string(status));
325 }
326 else if(rv != http_proto::route::send)
327 {
328 // error message of last resort
329 BOOST_ASSERT(rv.failed());
330 BOOST_ASSERT(! http_proto::is_route_result(rv));
331 res_.status(http_proto::status::internal_server_error);
332 std::string s;
333 format_to(s, "An internal server error occurred: {}", rv.message());
334 res_.message.set_keep_alive(false); // VFALCO?
335 res_.set_body(s);
336 }
337
338 do_write();
339 }
340
341 // begin writing the response
342 template<class AsyncStream>
343 void
344 http_stream<AsyncStream>::
345 do_write()
346 {
347 BOOST_ASSERT(! res_.serializer.is_done());
348 beast2::async_write(stream_, res_.serializer,
349 call_mf(&http_stream::on_write, this));
350 }
351
352 // called when the write operation completes
353 template<class AsyncStream>
354 void
355 http_stream<AsyncStream>::
356 on_write(
357 system::error_code const& ec,
358 std::size_t bytes_transferred)
359 {
360 (void)bytes_transferred;
361
362 if(ec.failed())
363 return do_fail("http_stream::on_write", ec);
364
365 BOOST_ASSERT(res_.serializer.is_done());
366
367 LOG_TRC(this->sect_)(
368 "{} http_stream::on_write bytes={}",
369 this->id(), bytes_transferred);
370
371 if(res_.message.keep_alive())
372 return do_read();
373
374 do_close();
375 }
376
377 template<class AsyncStream>
378 auto
379 http_stream<AsyncStream>::
380 do_detach() ->
381 http_proto::resumer
382 {
383 BOOST_ASSERT(stream_.get_executor().running_in_this_thread());
384
385 // can't call twice
386 BOOST_ASSERT(! pwg_);
387 pwg_.reset(new work_guard(stream_.get_executor()));
388
389 // VFALCO cancel timer
390
391 return http_proto::resumer(*this);
392 }
393
394 // called by resume(rv)
395 template<class AsyncStream>
396 void
397 http_stream<AsyncStream>::
398 do_resume(
399 http_proto::route_result const& rv)
400 {
401 asio::dispatch(
402 stream_.get_executor(),
403 [this, rv]
404 {
405 BOOST_ASSERT(pwg_.get() != nullptr);
406 pwg_.reset();
407
408 do_dispatch(rv);
409 });
410 }
411
412 // called when a non-recoverable error occurs
413 template<class AsyncStream>
414 void
415 http_stream<AsyncStream>::
416 do_fail(
417 core::string_view s, system::error_code const& ec)
418 {
419 LOG_TRC(this->sect_)("{}: {}", s, ec.message());
420
421 // tidy up lingering objects
422 req_.parser.reset();
423 res_.serializer.reset();
424
425 close_(ec);
426 }
427
428 // end the session
429 template<class AsyncStream>
430 void
431 http_stream<AsyncStream>::
432 do_close()
433 {
434 clear();
435 close_({});
436 }
437
438 // clear everything, releasing transient objects
439 template<class AsyncStream>
440 void
441 http_stream<AsyncStream>::
442 clear() noexcept
443 {
444 req_.parser.reset();
445 res_.serializer.reset();
446 res_.message.clear();
447 }
448
449 } // beast2
450 } // boost
451
452 #endif
453