LCOV - code coverage report
Current view: top level - boost/beast2/server - http_stream.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 0.0 % 66 0
Test Date: 2026-01-15 20:49:55 Functions: 0.0 % 10 0

            Line data    Source code
       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/server/route_handler_corosio.hpp>
      18              : #include <boost/beast2/server/router_corosio.hpp>
      19              : #include <boost/beast2/detail/except.hpp>
      20              : #include <boost/capy/application.hpp>
      21              : #include <boost/capy/task.hpp>
      22              : #include <boost/capy/buffers.hpp>
      23              : #include <boost/corosio/socket.hpp>
      24              : #include <boost/corosio/io_result.hpp>
      25              : #include <boost/http/request_parser.hpp>
      26              : #include <boost/http/response.hpp>
      27              : #include <boost/http/serializer.hpp>
      28              : #include <boost/http/string_body.hpp>
      29              : #include <boost/http/server/basic_router.hpp>
      30              : #include <boost/http/error.hpp>
      31              : #include <boost/url/parse.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              :     This class provides a coroutine-based HTTP server session that reads
      41              :     HTTP requests, routes them to handlers installed in a router, and
      42              :     sends the HTTP response.
      43              : 
      44              :     The session runs as a coroutine task and uses Corosio for async I/O.
      45              : */
      46              : class http_stream
      47              : {
      48              : public:
      49              :     /** Constructor.
      50              :         
      51              :         Initializes a new HTTP connection object that operates on
      52              :         the given socket and uses the specified router to dispatch
      53              :         incoming requests.
      54              : 
      55              :         @param app The owning application, used to access shared services
      56              :             such as logging and protocol objects.
      57              :         @param sock The socket to read from and write to.
      58              :         @param routes The router used to dispatch incoming HTTP requests.
      59              :     */
      60              :     http_stream(
      61              :         capy::application& app,
      62              :         corosio::socket& sock,
      63              :         router_corosio routes);
      64              : 
      65              :     /** Run the HTTP session as a coroutine.
      66              : 
      67              :         Reads HTTP requests, dispatches them through the router,
      68              :         and writes responses until the connection closes or an
      69              :         error occurs.
      70              : 
      71              :         @param config The acceptor configuration for this connection.
      72              : 
      73              :         @return A task that completes when the session ends.
      74              :     */
      75              :     capy::task<void>
      76              :     run(http::acceptor_config const& config);
      77              : 
      78              : private:
      79              :     capy::task<corosio::io_result<std::size_t>>
      80              :     read_header();
      81              : 
      82              :     capy::task<corosio::io_result<std::size_t>>
      83              :     read_body();
      84              : 
      85              :     capy::task<corosio::io_result<std::size_t>>
      86              :     write_response();
      87              : 
      88              :     void on_headers();
      89              :     http::route_result do_dispatch();
      90              :     void do_respond(http::route_result rv);
      91              : 
      92            0 :     std::string id() const
      93              :     {
      94            0 :         return std::string("[") + std::to_string(id_) + "] ";
      95              :     }
      96              : 
      97              :     void clear() noexcept;
      98              : 
      99              :     section sect_;
     100              :     std::size_t id_ = 0;
     101              :     corosio::socket& sock_;
     102              :     router_corosio routes_;
     103              :     http::acceptor_config const* pconfig_ = nullptr;
     104              :     corosio_route_params rp_;
     105              : };
     106              : 
     107              : //------------------------------------------------
     108              : 
     109              : inline
     110            0 : http_stream::
     111              : http_stream(
     112              :     capy::application& app,
     113              :     corosio::socket& sock,
     114            0 :     router_corosio routes)
     115            0 :     : sect_(use_log_service(app).get_section("http_stream"))
     116            0 :     , id_(
     117            0 :         []() noexcept
     118              :         {
     119              :             static std::size_t n = 0;
     120            0 :             return ++n;
     121            0 :         }())
     122            0 :     , sock_(sock)
     123            0 :     , routes_(std::move(routes))
     124            0 :     , rp_(sock_)
     125              : {
     126            0 :     rp_.parser = http::request_parser(app);
     127            0 :     rp_.serializer = http::serializer(app);
     128              :     // Note: suspend mechanism removed - handlers must complete synchronously
     129            0 : }
     130              : 
     131              : inline
     132              : capy::task<void>
     133            0 : http_stream::
     134              : run(http::acceptor_config const& config)
     135              : {
     136              :     pconfig_ = &config;
     137              : 
     138              :     for (;;)
     139              :     {
     140              :         // Reset parser for new request
     141              :         rp_.parser.reset();
     142              :         rp_.session_data.clear();
     143              :         rp_.parser.start();
     144              : 
     145              :         // Read HTTP request header
     146              :         auto [ec, n] = co_await read_header();
     147              :         if (ec)
     148              :         {
     149              :             LOG_TRC(sect_)("{} read_header: {}", id(), ec.message());
     150              :             break;
     151              :         }
     152              : 
     153              :         LOG_TRC(sect_)("{} read_header bytes={}", id(), n);
     154              : 
     155              :         // Process headers and dispatch
     156              :         on_headers();
     157              : 
     158              :         auto rv = do_dispatch();
     159              :         do_respond(rv);
     160              : 
     161              :         // Write response
     162              :         if (!rp_.serializer.is_done())
     163              :         {
     164              :             auto [wec, wn] = co_await write_response();
     165              :             if (wec)
     166              :             {
     167              :                 LOG_TRC(sect_)("{} write_response: {}", id(), wec.message());
     168              :                 break;
     169              :             }
     170              :             LOG_TRC(sect_)("{} write_response bytes={}", id(), wn);
     171              :         }
     172              : 
     173              :         // Check keep-alive
     174              :         if (!rp_.res.keep_alive())
     175              :             break;
     176              :     }
     177              : 
     178              :     clear();
     179            0 : }
     180              : 
     181              : inline
     182              : capy::task<corosio::io_result<std::size_t>>
     183            0 : http_stream::
     184              : read_header()
     185              : {
     186              :     std::size_t total_bytes = 0;
     187              :     system::error_code ec;
     188              : 
     189              :     for (;;)
     190              :     {
     191              :         // Try to parse what we have
     192              :         rp_.parser.parse(ec);
     193              :         
     194              :         if (ec == http::condition::need_more_input)
     195              :         {
     196              :             // Need to read more data
     197              :             auto buf = rp_.parser.prepare();
     198              :             auto [read_ec, n] = co_await sock_.read_some(buf);
     199              :             
     200              :             if (read_ec)
     201              :             {
     202              :                 co_return {read_ec, total_bytes};
     203              :             }
     204              :             
     205              :             if (n == 0)
     206              :             {
     207              :                 // EOF
     208              :                 rp_.parser.commit_eof();
     209              :                 ec = {};
     210              :             }
     211              :             else
     212              :             {
     213              :                 rp_.parser.commit(n);
     214              :                 total_bytes += n;
     215              :             }
     216              :             continue;
     217              :         }
     218              : 
     219              :         if (ec.failed())
     220              :         {
     221              :             co_return {ec, total_bytes};
     222              :         }
     223              : 
     224              :         // Header complete
     225              :         if (rp_.parser.got_header())
     226              :         {
     227              :             co_return {{}, total_bytes};
     228              :         }
     229              :     }
     230            0 : }
     231              : 
     232              : inline
     233              : capy::task<corosio::io_result<std::size_t>>
     234              : http_stream::
     235              : read_body()
     236              : {
     237              :     std::size_t total_bytes = 0;
     238              :     system::error_code ec;
     239              : 
     240              :     while (!rp_.parser.is_complete())
     241              :     {
     242              :         rp_.parser.parse(ec);
     243              :         
     244              :         if (ec == http::condition::need_more_input)
     245              :         {
     246              :             auto buf = rp_.parser.prepare();
     247              :             auto [read_ec, n] = co_await sock_.read_some(buf);
     248              :             
     249              :             if (read_ec)
     250              :             {
     251              :                 co_return {read_ec, total_bytes};
     252              :             }
     253              :             
     254              :             if (n == 0)
     255              :             {
     256              :                 rp_.parser.commit_eof();
     257              :             }
     258              :             else
     259              :             {
     260              :                 rp_.parser.commit(n);
     261              :                 total_bytes += n;
     262              :             }
     263              :             continue;
     264              :         }
     265              : 
     266              :         if (ec.failed())
     267              :         {
     268              :             co_return {ec, total_bytes};
     269              :         }
     270              :     }
     271              : 
     272              :     co_return {{}, total_bytes};
     273              : }
     274              : 
     275              : inline
     276              : capy::task<corosio::io_result<std::size_t>>
     277            0 : http_stream::
     278              : write_response()
     279              : {
     280              :     std::size_t total_bytes = 0;
     281              : 
     282              :     while (!rp_.serializer.is_done())
     283              :     {
     284              :         auto rv = rp_.serializer.prepare();
     285              :         if (!rv)
     286              :         {
     287              :             co_return {rv.error(), total_bytes};
     288              :         }
     289              : 
     290              :         auto bufs = *rv;
     291              :         std::size_t buf_size = 0;
     292              :         for (auto const& buf : bufs)
     293              :             buf_size += buf.size();
     294              : 
     295              :         if (buf_size == 0)
     296              :         {
     297              :             // Serializer done
     298              :             break;
     299              :         }
     300              : 
     301              :         // Write buffers - we need to write them all
     302              :         std::size_t written = 0;
     303              :         for (auto const& buf : bufs)
     304              :         {
     305              :             auto [ec, n] = co_await sock_.write_some(
     306              :                 capy::const_buffer(buf.data(), buf.size()));
     307              :             if (ec)
     308              :             {
     309              :                 co_return {ec, total_bytes + written};
     310              :             }
     311              :             written += n;
     312              :         }
     313              : 
     314              :         rp_.serializer.consume(written);
     315              :         total_bytes += written;
     316              :     }
     317              : 
     318              :     co_return {{}, total_bytes};
     319            0 : }
     320              : 
     321              : inline
     322              : void
     323            0 : http_stream::
     324              : on_headers()
     325              : {
     326              :     // Set up Request and Response objects
     327            0 :     rp_.req = rp_.parser.get();
     328            0 :     rp_.route_data.clear();
     329            0 :     rp_.res.set_start_line(
     330              :         http::status::ok, rp_.req.version());
     331            0 :     rp_.res.set_keep_alive(rp_.req.keep_alive());
     332            0 :     rp_.serializer.reset();
     333              : 
     334              :     // Parse the URL
     335            0 :     auto rv = urls::parse_uri_reference(rp_.req.target());
     336            0 :     if (rv.has_error())
     337              :     {
     338            0 :         rp_.status(http::status::bad_request);
     339            0 :         rp_.set_body("Bad Request: " + rv.error().message());
     340            0 :         return;
     341              :     }
     342              : 
     343            0 :     rp_.url = rv.value();
     344              : }
     345              : 
     346              : inline
     347              : http::route_result
     348            0 : http_stream::
     349              : do_dispatch()
     350              : {
     351            0 :     return routes_.dispatch(
     352            0 :         rp_.req.method(), rp_.url, rp_);
     353              : }
     354              : 
     355              : inline
     356              : void
     357            0 : http_stream::
     358              : do_respond(http::route_result rv)
     359              : {
     360            0 :     if (rv == http::route::close)
     361              :     {
     362            0 :         rp_.res.set_keep_alive(false);
     363            0 :         return;
     364              :     }
     365              : 
     366            0 :     if (rv == http::route::complete)
     367              :     {
     368              :         // Handler sent the response directly
     369            0 :         return;
     370              :     }
     371              : 
     372            0 :     if (rv == http::route::suspend)
     373              :     {
     374              :         // Suspend not supported - treat as internal error
     375            0 :         rp_.status(http::status::internal_server_error);
     376            0 :         rp_.set_body("Handler suspend not supported");
     377            0 :         rp_.res.set_keep_alive(false);
     378            0 :         return;
     379              :     }
     380              : 
     381            0 :     if (rv == http::route::next)
     382              :     {
     383              :         // Unhandled request
     384            0 :         auto const status = http::status::not_found;
     385            0 :         rp_.status(status);
     386            0 :         rp_.set_body(http::to_string(status));
     387            0 :         return;
     388              :     }
     389              : 
     390            0 :     if (rv != http::route::send)
     391              :     {
     392              :         // Error message of last resort
     393            0 :         BOOST_ASSERT(rv.failed());
     394            0 :         BOOST_ASSERT(!http::is_route_result(rv));
     395            0 :         rp_.status(http::status::internal_server_error);
     396            0 :         std::string s;
     397            0 :         format_to(s, "An internal server error occurred: {}", rv.message());
     398            0 :         rp_.res.set_keep_alive(false);
     399            0 :         rp_.set_body(s);
     400            0 :     }
     401              : }
     402              : 
     403              : inline
     404              : void
     405            0 : http_stream::
     406              : clear() noexcept
     407              : {
     408            0 :     rp_.parser.reset();
     409            0 :     rp_.serializer.reset();
     410            0 :     rp_.res.clear();
     411            0 : }
     412              : 
     413              : } // beast2
     414              : } // boost
     415              : 
     416              : #endif
        

Generated by: LCOV version 2.3