From 174da599941dc75bfa87700cbfbc61c568ff3127 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 18 Jan 2026 20:01:05 -0800 Subject: [PATCH 01/17] Improved route handling API --- example/server/main.cpp | 3 +- .../beast2/server/route_handler_corosio.hpp | 56 ++++++++++++++++++- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/example/server/main.cpp b/example/server/main.cpp index 6c1d0f7e..7da8982b 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -178,8 +178,7 @@ int server_main( int argc, char* argv[] ) rr.use( [](http::route_params& rp) -> capy::task { - (void)rp; - co_return http::route::next; + co_return co_await rp.send( "Hello, coworld!" ); }); http_server hsrv(ioc, std::atoi(argv[4]), std::move(rr)); auto ec = hsrv.bind(ep); diff --git a/include/boost/beast2/server/route_handler_corosio.hpp b/include/boost/beast2/server/route_handler_corosio.hpp index dcda4305..93ae1ee7 100644 --- a/include/boost/beast2/server/route_handler_corosio.hpp +++ b/include/boost/beast2/server/route_handler_corosio.hpp @@ -12,7 +12,10 @@ #include #include +#include +#include #include +#include namespace boost { namespace beast2 { @@ -30,9 +33,60 @@ class corosio_route_params explicit corosio_route_params( corosio::socket& s) - : stream(s) + : stream( s ) { } + + http::route_task + send( std::string_view body ) override + { + // Auto-detect Content-Type if not already set + if( ! res.exists( http::field::content_type ) ) + { + if( ! body.empty() && body[0] == '<' ) + res.set( http::field::content_type, + "text/html; charset=utf-8" ); + else + res.set( http::field::content_type, + "text/plain; charset=utf-8" ); + } + + // Set Content-Length if not already set + if( ! res.exists( http::field::content_length ) ) + res.set_payload_size( body.size() ); + + // Start the serializer with the response and body + serializer.start( res, + http::string_body( std::string( body ) ) ); + + // Write the response to the socket + while( ! serializer.is_done() ) + { + auto rv = serializer.prepare(); + if( ! rv ) + co_return rv.error(); + + auto bufs = *rv; + + // Write all buffers + for( auto const& buf : bufs ) + { + auto [ec, n] = co_await stream.write_some( + capy::const_buffer( buf.data(), buf.size() ) ); + if( ec ) + co_return ec; + } + + // Calculate total size written + std::size_t written = 0; + for( auto const& buf : bufs ) + written += buf.size(); + + serializer.consume( written ); + } + + co_return {}; + } }; } // beast2 From c4e736e158ea377fb008d0ecf7f40a478f863a04 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 18 Jan 2026 20:55:50 -0800 Subject: [PATCH 02/17] Functionality for serve_static --- example/server/main.cpp | 3 + .../beast2/server/route_handler_corosio.hpp | 44 ++++ include/boost/beast2/server/serve_static.hpp | 178 ------------- src/server/serve_static.cpp | 236 ------------------ test/unit/server/serve_static.cpp | 11 - 5 files changed, 47 insertions(+), 425 deletions(-) delete mode 100644 include/boost/beast2/server/serve_static.hpp delete mode 100644 src/server/serve_static.cpp delete mode 100644 test/unit/server/serve_static.cpp diff --git a/example/server/main.cpp b/example/server/main.cpp index 7da8982b..1214b7b6 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -175,11 +175,14 @@ int server_main( int argc, char* argv[] ) http::router rr; + rr.use( "/", serve_static( argv[3] ) ); +#if 0 rr.use( [](http::route_params& rp) -> capy::task { co_return co_await rp.send( "Hello, coworld!" ); }); +#endif http_server hsrv(ioc, std::atoi(argv[4]), std::move(rr)); auto ec = hsrv.bind(ep); if(ec) diff --git a/include/boost/beast2/server/route_handler_corosio.hpp b/include/boost/beast2/server/route_handler_corosio.hpp index 93ae1ee7..4d7b532f 100644 --- a/include/boost/beast2/server/route_handler_corosio.hpp +++ b/include/boost/beast2/server/route_handler_corosio.hpp @@ -87,6 +87,50 @@ class corosio_route_params co_return {}; } + + http::route_task + end() override + { + // For chunked encoding, send the terminating chunk + // Currently assumes headers have already been sent + // and any final data has been written via write() + + // If using chunked encoding, send "0\r\n\r\n" + if( res.value_or( + http::field::transfer_encoding, "" ) == "chunked" ) + { + static constexpr char terminator[] = "0\r\n\r\n"; + auto [ec, n] = co_await stream.write_some( + capy::const_buffer( terminator, 5 ) ); + if( ec ) + co_return ec; + } + + co_return {}; + } + +protected: + http::route_task + write_impl( http::detail::any_bufref buffers ) override + { + // Extract buffers from type-erased wrapper + constexpr std::size_t max_bufs = 16; + capy::mutable_buffer bufs[max_bufs]; + auto const n = buffers.copy_to( bufs, max_bufs ); + + // Write all buffers + for( std::size_t i = 0; i < n; ++i ) + { + auto [ec, written] = co_await stream.write_some( + capy::const_buffer( + bufs[i].data(), + bufs[i].size() ) ); + if( ec ) + co_return ec; + } + + co_return {}; + } }; } // beast2 diff --git a/include/boost/beast2/server/serve_static.hpp b/include/boost/beast2/server/serve_static.hpp deleted file mode 100644 index abe09da5..00000000 --- a/include/boost/beast2/server/serve_static.hpp +++ /dev/null @@ -1,178 +0,0 @@ -// -// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/beast2 -// - -#ifndef BOOST_BEAST2_SERVER_SERVE_STATIC_HPP -#define BOOST_BEAST2_SERVER_SERVE_STATIC_HPP - -#include -#include - -namespace boost { -namespace beast2 { - -/** A route handler which serves static files from a document root - - This handler operates similary to the npm package serve-static. -*/ -struct serve_static -{ - /** Policy for handling dotfiles - */ - enum class dotfiles_policy - { - allow, - deny, - ignore - }; - - /** Options for the static file server - */ - struct options - { - /** How to handle dotfiles - - Dotfiles are files or directories whose names begin with a dot (.) - The default is to ignore dotfiles. - */ - dotfiles_policy dotfiles = dotfiles_policy::ignore; - - // VFALCO extensions fallbacks vector - - // VFALCO vector of index file names - - /** Maximum cache age in milliseconds - */ - // VFALCO - // std::chrono::duration max_age = std::chrono::milliseconds(0); ? - std::size_t max_age_ms = 0; - - // VFALCO set_headers callback - - /** Enable accepting range requests. - - When this is false, the "Accept-Ranges" field will not be - sent, and any "Range" field in the request will be ignored. - */ - bool accept_ranges = true; - - /** Enable sending cache-control headers. - - When this is set to `false`, the @ref immutable - and @ref max_age options are ignored. - */ - bool cache_control = true; - - /** Enable etag header generation. - */ - bool etag = true; - - /** Treat client errors as unhandled requests. - - When this value is `true`, all error codes will be - treated as if unhandled. Otherwise, errors (including - file not found) will go through the error handling routes. - - Typically true is desired such that multiple physical - directories can be mapped to the same web address or for - routes to fill in non-existent files. - - The value false can be used if this handler is mounted - at a path that is designed to be strictly a single file system - directory, which allows for short-circuiting 404s for less - overhead. This handler will also reply to all methods. - - @note This handler replies to all HTTP methods. - */ - bool fallthrough = true; - - /** Enable the immutable directive in cache control headers. - - When this is true, the "immutable" directive will be - added to the "Cache-Control" field. - This indicates to clients that the resource will not - change during its freshness lifetime. - This is typically used when the filenames contain - a hash of the content, such as when using a build - tool which fingerprints static assets. - The @ref max_age value must also be set to a non-zero value. - */ - bool immutable = false; - - /** Enable a default index file for directory requests. - When a request is made for a directory path, such as - "/docs/", the file "index.html" will be served if it - exists within that directory. - */ - bool index = true; // "index.html" default - - /** Enable the "Last-Modified" header. - - The file system's last modified value is used. - */ - bool last_modified = true; - - /** Enable redirection for directories missing a trailing slash. - - When a request is made for a directory path without a trailing - slash, the client is redirected to the same path with the slash - appended. This is useful for relative links to work correctly - in browsers. - For example, a request for `/docs` when `/docs/index.html` exists - will be redirected to `/docs/`. - @note This requires that the client accepts redirections. - */ - bool redirect = true; - }; - - BOOST_BEAST2_DECL - ~serve_static(); - - /** Constructor - @param path The document root path - @param options The options to use - */ - BOOST_BEAST2_DECL - serve_static( - core::string_view path, - options const& opt); - - /** Constructor - @param path The document root path - */ - explicit - serve_static( - core::string_view path) - : serve_static(path, options{}) - { - } - - /** Constructor - */ - BOOST_BEAST2_DECL - serve_static(serve_static&&) noexcept; - - /** Handle a request - @param req The request - @param res The response - @return `true` if the request was handled, `false` to - indicate the request was not handled. - */ - BOOST_BEAST2_DECL - system::error_code operator()( - http::route_params&) const; - -private: - struct impl; - impl* impl_; -}; - -} // beast2 -} // boost - -#endif diff --git a/src/server/serve_static.cpp b/src/server/serve_static.cpp deleted file mode 100644 index 96616a2c..00000000 --- a/src/server/serve_static.cpp +++ /dev/null @@ -1,236 +0,0 @@ -// -// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/beast2 -// - -#include -#include -#include -#include -#include - -namespace boost { -namespace beast2 { - -//------------------------------------------------ - -// Return a reasonable mime type based on the extension of a file. -static -core::string_view -get_extension( - core::string_view path) noexcept -{ - auto const pos = path.rfind("."); - if( pos == core::string_view::npos) - return core::string_view(); - return path.substr(pos); -} - -static -core::string_view -mime_type( - core::string_view path) -{ - using urls::grammar::ci_is_equal; - auto ext = get_extension(path); - if(ci_is_equal(ext, ".htm")) return "text/html"; - if(ci_is_equal(ext, ".html")) return "text/html"; - if(ci_is_equal(ext, ".php")) return "text/html"; - if(ci_is_equal(ext, ".css")) return "text/css"; - if(ci_is_equal(ext, ".txt")) return "text/plain"; - if(ci_is_equal(ext, ".js")) return "application/javascript"; - if(ci_is_equal(ext, ".json")) return "application/json"; - if(ci_is_equal(ext, ".xml")) return "application/xml"; - if(ci_is_equal(ext, ".swf")) return "application/x-shockwave-flash"; - if(ci_is_equal(ext, ".flv")) return "video/x-flv"; - if(ci_is_equal(ext, ".png")) return "image/png"; - if(ci_is_equal(ext, ".jpe")) return "image/jpeg"; - if(ci_is_equal(ext, ".jpeg")) return "image/jpeg"; - if(ci_is_equal(ext, ".jpg")) return "image/jpeg"; - if(ci_is_equal(ext, ".gif")) return "image/gif"; - if(ci_is_equal(ext, ".bmp")) return "image/bmp"; - if(ci_is_equal(ext, ".ico")) return "image/vnd.microsoft.icon"; - if(ci_is_equal(ext, ".tiff")) return "image/tiff"; - if(ci_is_equal(ext, ".tif")) return "image/tiff"; - if(ci_is_equal(ext, ".svg")) return "image/svg+xml"; - if(ci_is_equal(ext, ".svgz")) return "image/svg+xml"; - return "application/text"; -} - -#if 0 -// Append an HTTP rel-path to a local filesystem path. -// The returned path is normalized for the platform. -static -void -path_cat( - std::string& result, - core::string_view prefix, - urls::segments_view suffix) -{ - result = prefix; - -#ifdef BOOST_MSVC - char constexpr path_separator = '\\'; -#else - char constexpr path_separator = '/'; -#endif - if( result.back() == path_separator) - result.resize(result.size() - 1); // remove trailing -#ifdef BOOST_MSVC - for(auto& c : result) - if( c == '/') - c = path_separator; -#endif - for(auto const& seg : suffix) - { - result.push_back(path_separator); - result.append(seg); - } -} -#endif - -// Append an HTTP rel-path to a local filesystem path. -// The returned path is normalized for the platform. -static -void -path_cat( - std::string& result, - core::string_view prefix, - core::string_view suffix) -{ - result = prefix; - -#ifdef BOOST_MSVC - char constexpr path_separator = '\\'; -#else - char constexpr path_separator = '/'; -#endif - if( result.back() == path_separator) - result.resize(result.size() - 1); // remove trailing -#ifdef BOOST_MSVC - for(auto& c : result) - if( c == '/') - c = path_separator; -#endif - for(auto const& c : suffix) - { - if(c == '/') - result.push_back(path_separator); - else - result.push_back(c); - } -} - -//------------------------------------------------ - -// serve-static -// -// https://www.npmjs.com/package/serve-static - -struct serve_static::impl -{ - impl( - core::string_view path_, - options const& opt_) - : path(path_) - , opt(opt_) - { - } - - std::string path; - options opt; -}; - -serve_static:: -~serve_static() -{ - if(impl_) - delete impl_; -} - -serve_static:: -serve_static(serve_static&& other) noexcept - : impl_(other.impl_) -{ - other.impl_ = nullptr; -} - -serve_static:: -serve_static( - core::string_view path, - options const& opt) - : impl_(new impl(path, opt)) -{ -} - -auto -serve_static:: -operator()( - http::route_params&) const -> - http::route_result -{ - return {}; -#if 0 - // Allow: GET, HEAD - if( p.req.method() != http::method::get && - p.req.method() != http::method::head) - { - if(impl_->opt.fallthrough) - return http::route::next; - - p.res.set_status( - http::status::method_not_allowed); - p.res.set(http::field::allow, "GET, HEAD"); - p.set_body(""); - return http::route::send; - } - - // Build the path to the requested file - std::string path; - path_cat(path, impl_->path, p.path); - if(p.parser.get().target().back() == '/') - { - path.push_back('/'); - path.append("index.html"); - } - - // Attempt to open the file - system::error_code ec; - capy::file f; - std::uint64_t size = 0; - f.open(path.c_str(), capy::file_mode::scan, ec); - if(! ec.failed()) - size = f.size(ec); - if(! ec.failed()) - { - p.res.set_start_line( - http::status::ok, - p.req.version()); - p.res.set_payload_size(size); - - auto mt = mime_type(get_extension(path)); - p.res.append( - http::field::content_type, mt); - - // send file - p.serializer.start( - p.res, std::move(f), size); - return http::route::send; - } - - if( ec == system::errc::no_such_file_or_directory && - ! impl_->opt.fallthrough) - return http::route::next; - - BOOST_ASSERT(ec.failed()); - return ec; -#endif -} - -} // beast2 -} // boost - diff --git a/test/unit/server/serve_static.cpp b/test/unit/server/serve_static.cpp deleted file mode 100644 index e9f2ba8f..00000000 --- a/test/unit/server/serve_static.cpp +++ /dev/null @@ -1,11 +0,0 @@ -// -// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/beast2 -// - -// Test that header file is self-contained. -#include From 31d052d54c77afd5ecf2366d1c4df3aa84d47659 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 18 Jan 2026 21:19:33 -0800 Subject: [PATCH 03/17] Use http::serve_static --- example/server/main.cpp | 4 +- .../beast2/server/route_handler_corosio.hpp | 57 ++++++++++--------- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/example/server/main.cpp b/example/server/main.cpp index 1214b7b6..76ab40c6 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -11,7 +11,7 @@ //#include #include #include -#include +#include #include #include #include @@ -175,7 +175,7 @@ int server_main( int argc, char* argv[] ) http::router rr; - rr.use( "/", serve_static( argv[3] ) ); + rr.use( "/", http::serve_static( argv[3] ) ); #if 0 rr.use( [](http::route_params& rp) -> capy::task diff --git a/include/boost/beast2/server/route_handler_corosio.hpp b/include/boost/beast2/server/route_handler_corosio.hpp index 4d7b532f..a8141e43 100644 --- a/include/boost/beast2/server/route_handler_corosio.hpp +++ b/include/boost/beast2/server/route_handler_corosio.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include namespace boost { @@ -33,56 +34,56 @@ class corosio_route_params explicit corosio_route_params( corosio::socket& s) - : stream( s ) + : stream(s) { } http::route_task - send( std::string_view body ) override + send(std::string_view body) override { // Auto-detect Content-Type if not already set - if( ! res.exists( http::field::content_type ) ) + if(! res.exists(http::field::content_type)) { - if( ! body.empty() && body[0] == '<' ) - res.set( http::field::content_type, - "text/html; charset=utf-8" ); + if(! body.empty() && body[0] == '<') + res.set(http::field::content_type, + "text/html; charset=utf-8"); else - res.set( http::field::content_type, - "text/plain; charset=utf-8" ); + res.set(http::field::content_type, + "text/plain; charset=utf-8"); } // Set Content-Length if not already set - if( ! res.exists( http::field::content_length ) ) - res.set_payload_size( body.size() ); + if(! res.exists(http::field::content_length)) + res.set_payload_size(body.size()); // Start the serializer with the response and body - serializer.start( res, - http::string_body( std::string( body ) ) ); + serializer.start(res, + http::string_body(std::string(body))); // Write the response to the socket - while( ! serializer.is_done() ) + while(! serializer.is_done()) { auto rv = serializer.prepare(); - if( ! rv ) + if(! rv) co_return rv.error(); auto bufs = *rv; // Write all buffers - for( auto const& buf : bufs ) + for(auto const& buf : bufs) { auto [ec, n] = co_await stream.write_some( - capy::const_buffer( buf.data(), buf.size() ) ); - if( ec ) + capy::const_buffer(buf.data(), buf.size())); + if(ec) co_return ec; } // Calculate total size written std::size_t written = 0; - for( auto const& buf : bufs ) + for(auto const& buf : bufs) written += buf.size(); - serializer.consume( written ); + serializer.consume(written); } co_return {}; @@ -96,13 +97,13 @@ class corosio_route_params // and any final data has been written via write() // If using chunked encoding, send "0\r\n\r\n" - if( res.value_or( - http::field::transfer_encoding, "" ) == "chunked" ) + if(res.value_or( + http::field::transfer_encoding, "") == "chunked") { static constexpr char terminator[] = "0\r\n\r\n"; auto [ec, n] = co_await stream.write_some( - capy::const_buffer( terminator, 5 ) ); - if( ec ) + capy::const_buffer(terminator, 5)); + if(ec) co_return ec; } @@ -111,21 +112,21 @@ class corosio_route_params protected: http::route_task - write_impl( http::detail::any_bufref buffers ) override + write_impl(capy::any_bufref buffers) override { // Extract buffers from type-erased wrapper constexpr std::size_t max_bufs = 16; capy::mutable_buffer bufs[max_bufs]; - auto const n = buffers.copy_to( bufs, max_bufs ); + auto const n = buffers.copy_to(bufs, max_bufs); // Write all buffers - for( std::size_t i = 0; i < n; ++i ) + for(std::size_t i = 0; i < n; ++i) { auto [ec, written] = co_await stream.write_some( capy::const_buffer( bufs[i].data(), - bufs[i].size() ) ); - if( ec ) + bufs[i].size())); + if(ec) co_return ec; } From 1783f3dd60fb8eb0e25ea1bd9eb431bab09661c6 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Tue, 20 Jan 2026 02:55:34 -0800 Subject: [PATCH 04/17] Remove body_source --- example/server/main.cpp | 1 - include/boost/beast2/server/body_source.hpp | 432 -------------------- src/http_server.cpp | 1 + src/server/body_source.cpp | 41 -- test/unit/server/body_source.cpp | 266 ------------ 5 files changed, 1 insertion(+), 740 deletions(-) delete mode 100644 include/boost/beast2/server/body_source.hpp delete mode 100644 src/server/body_source.cpp delete mode 100644 test/unit/server/body_source.cpp diff --git a/example/server/main.cpp b/example/server/main.cpp index 76ab40c6..4a141d4e 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -8,7 +8,6 @@ // #include "serve_log_admin.hpp" -//#include #include #include #include diff --git a/include/boost/beast2/server/body_source.hpp b/include/boost/beast2/server/body_source.hpp deleted file mode 100644 index 09cc805c..00000000 --- a/include/boost/beast2/server/body_source.hpp +++ /dev/null @@ -1,432 +0,0 @@ -// -// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/beast2 -// - -#ifndef BOOST_BEAST2_SERVER_BODY_SOURCE_HPP -#define BOOST_BEAST2_SERVER_BODY_SOURCE_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace boost { -namespace beast2 { - -/** Tag for customizing body construction -*/ -struct construct_body_tag {}; - -//----------------------------------------------- - -/** A type erased HTTP message body. - - This type erases the specific body type used to represent the message body. - It provides a uniform interface for reading the body data regardless of - how the body is implemented. Accessing the bytes is achieved by calling - @ref read which reads data into a caller-provided buffer. Alternatively, - when @ref has_buffers returns `true` the body consists of buffers in memory, - and they can be accessed directly by calling @ref get_buffers. - - Example bodies include: - - in-memory buffers - - file-backed bodies - - generated bodies - - @note A body_source is movable but not copyable. - - Type-erased bodies can always be rewound to the beginning by calling - @ref rewind. Therefore, bodies can be read multiple times. - - @par Thread Safety - Unsafe. -*/ -class body_source -{ -public: - /** Destructor. - */ - BOOST_BEAST2_DECL ~body_source(); - - /** Constructor - - Default-constructed bodies are empty. - */ - body_source() = default; - - /** Constructor - - The content of @p other is transferred to `*this`. The - moved-from body is left empty, as if default-constructed. - */ - body_source(body_source&& other) noexcept - : impl_(other.impl_) - { - other.impl_ = nullptr; - } - - /** Assignment - - The previous body, if any, is released and the - content of @p other is transferred to `*this`. - */ - BOOST_BEAST2_DECL - body_source& operator=(body_source&& other) noexcept; - - /** Construct a streaming body source. - */ - template::type, body_source>::value && - capy::is_read_source::type>::value - , int>::type = 0> - body_source(ReadSource&& body); - - /** Construct a streaming body source with a known size. - */ - template::type, body_source>::value && - capy::is_read_source::type>::value - , int>::type = 0> - body_source( - std::size_t known_size, - ReadSource&& body); - - /** Construct a buffers body source. - */ - template::type, body_source>::value && - capy::is_data_source::type>::value - , int>::type = 0> - body_source(DataSource&& body); - - //template - //body(T&& t); - - /** Return `true` if the size of the body is known. - */ - bool has_size() const noexcept - { - if(impl_) - return impl_->has_size(); - return true; // empty - } - - /** Return `true` if the body consists of buffers in memory. - When the body consists of buffers in memory, they can - also be accessed directly using @ref get_buffers. - */ - bool has_buffers() const noexcept - { - if(impl_) - return impl_->has_buffers(); - return true; // empty - } - - /** Return the size of the body, if available. - @throw std::invalid_argument if @ref has_size returns `false`. - @return The size of the body in bytes. - */ - auto size() const -> std::size_t - { - if(impl_) - return impl_->size(); - return 0; // empty - } - - /** Return the buffers representing the body, if available. - @throw std::invalid_argument if @ref has_buffers returns `false`. - @return A span of buffers representing the body. - */ - auto data() const -> - span - { - if(impl_) - return impl_->data(); - return {}; // empty - } - - /** Rewind the body to the beginning. - This allows the body to be accessed from the start when calling @read. - */ - void rewind() - { - if(impl_) - return impl_->rewind(); - // empty - } - - /** Read from the body into a caller-provided buffer. - - @param dest A pointer to the buffer to read into. - @param n The maximum number of bytes to read. - @param ec Set to the error, if any occurred. - @return The number of bytes read, which may be - less than the number requested. A return value of - zero indicates end-of-body. - */ - auto - read(void* dest, std::size_t n, - system::error_code& ec) -> - std::size_t - { - if(impl_) - return impl_->read(dest, n, ec); - // empty - ec = http::error::end_of_stream; - return 0; - } - -private: - struct impl - { - virtual ~impl() = default; - virtual bool has_size() const noexcept { return false; } - virtual bool has_buffers() const noexcept { return false; } - virtual std::size_t size() const - { - detail::throw_invalid_argument(); - } - virtual auto data() const -> - span - { - detail::throw_invalid_argument(); - } - virtual void rewind() = 0; - virtual std::size_t read( - void* dest, std::size_t n, - system::error_code& ec) = 0; - }; - - impl* impl_ = nullptr; -}; - -//----------------------------------------------- - -template::type, body_source>::value && - capy::is_read_source::type>::value - , int>::type> -body_source:: -body_source( - ReadSource&& body) -{ - struct model : impl - { - system::error_code ec_; - typename std::decay::type body_; - - explicit model(ReadSource&& body) - : body_(std::forward(body)) - { - } - - void rewind() override - { - ec_ = {}; - body_.rewind(); - } - - std::size_t read( - void* dest, - std::size_t size, - system::error_code& ec) override - { - if(ec_.failed()) - { - ec = ec_; - return 0; - } - auto nread = body_.read( - capy::mutable_buffer(dest, size), ec); - ec_ = ec; - return nread; - } - }; - - auto p = ::operator new(sizeof(model)); - impl_ = ::new(p) model( - std::forward(body)); -} - -/** Construct a streaming body source with a known size. -*/ -template::type, body_source>::value && - capy::is_read_source::type>::value - , int>::type> -body_source:: -body_source( - std::size_t known_size, - ReadSource&& body) -{ - struct model : impl - { - std::size_t size_; - system::error_code ec_; - typename std::decay::type body_; - - model( - ReadSource&& body, - std::size_t known_size) - : size_(known_size) - , body_(std::forward(body)) - { - } - - bool has_size() const noexcept override - { - return true; - } - - std::size_t size() const override - { - return size_; - } - - void rewind() override - { - ec_ = {}; - body_.rewind(); - } - - std::size_t read( - void* dest, - std::size_t size, - system::error_code& ec) override - { - if(ec_.failed()) - { - ec = ec_; - return 0; - } - auto nread = body_.read( - capy::mutable_buffer(dest, size), ec); - ec_ = ec; - return nread; - } - }; - - auto p = ::operator new(sizeof(model)); - impl_ = ::new(p) model( - std::forward(body), known_size); -} - -/** Construct a buffers body source. -*/ -template::type, body_source>::value && - capy::is_data_source::type>::value - , int>::type> -body_source:: -body_source( - DataSource&& body) -{ - struct model : impl - { - typename std::decay::type body_; - std::size_t size_ = 0; - span bs_; - std::size_t nread_ = 0; - - explicit model( - DataSource&& body) noexcept - : body_(std::forward(body)) - { - auto const& data = body_.data(); - auto const& end = capy::end(data); - auto p = reinterpret_cast< - capy::const_buffer*>(this+1); - std::size_t length = 0; - for(auto it = capy::begin(data); it != end; ++it) - { - boost::capy::const_buffer cb(*it); - size_ += cb.size(); - *p++ = cb; - ++length; - } - bs_ = { reinterpret_cast< - capy::const_buffer*>(this + 1), length }; - } - - bool has_size() const noexcept override - { - return true; - } - - bool has_buffers() const noexcept override - { - return true; - } - - std::size_t size() const override - { - return size_; - } - - span - data() const override - { - return bs_; - } - - void rewind() override - { - nread_ = 0; - } - - std::size_t read( - void* dest, - std::size_t n0, - system::error_code& ec) override - { - std::size_t n = capy::copy( - capy::mutable_buffer(dest, n0), - capy::sans_prefix(bs_, nread_)); - nread_ += n; - if(nread_ >= size_) - ec = http::error::end_of_stream; - else - ec = {}; - return n; - } - }; - - std::size_t length = 0; - auto const& data = body.data(); - auto const& end = capy::end(data); - for(auto it = capy::begin(data); - it != end; ++it) - { - ++length; - } - - // VFALCO this requires DataSource to be nothrow - // move constructible for strong exception safety. - auto p = ::operator new(sizeof(model) + - length * sizeof(capy::const_buffer)); - impl_ = ::new(p) model( - std::forward(body)); -} - -} // beast2 -} // boost - -#endif diff --git a/src/http_server.cpp b/src/http_server.cpp index 9bacd76c..461f4609 100644 --- a/src/http_server.cpp +++ b/src/http_server.cpp @@ -205,6 +205,7 @@ do_session( auto rv = co_await impl_->router.dispatch( w.rp.req.method(), w.rp.url, w.rp); + (void)rv; #if 0 do_respond(rv); diff --git a/src/server/body_source.cpp b/src/server/body_source.cpp deleted file mode 100644 index cfd9449c..00000000 --- a/src/server/body_source.cpp +++ /dev/null @@ -1,41 +0,0 @@ -// -// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/beast2 -// - -#include - -namespace boost { -namespace beast2 { - -body_source:: -~body_source() -{ - if(! impl_) - return; - impl_->~impl(); - ::operator delete(impl_); -} - -body_source& -body_source:: -operator=(body_source&& other) noexcept -{ - if(&other == this) - return *this; - if(impl_) - { - impl_->~impl(); - ::operator delete(impl_); - } - impl_ = other.impl_; - other.impl_ = nullptr; - return *this; -} - -} // beast2 -} // boost diff --git a/test/unit/server/body_source.cpp b/test/unit/server/body_source.cpp deleted file mode 100644 index ea0f0f27..00000000 --- a/test/unit/server/body_source.cpp +++ /dev/null @@ -1,266 +0,0 @@ -// -// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/beast2 -// - -// Test that header file is self-contained. -#include - -#include -#include - -#include "test_suite.hpp" - -namespace boost { -namespace beast2 { - -namespace { - -struct data_source -{ - core::string_view s_; - - data_source( - core::string_view s) - : s_(s) - { - } - - capy::const_buffer - data() const - { - return { s_.data(), s_.size() }; - } -}; - -struct read_source -{ - core::string_view s_; - std::size_t nread_ = 0; - system::error_code ec_; - - explicit read_source( - core::string_view s, - system::error_code ec = {}) - : s_(s) - , ec_(ec) - { - } - - void rewind() - { - nread_ = 0; - } - - template - std::size_t read( - MutableBufferSequence const& dest, - system::error_code& ec) - { - if( nread_ > 0 && - ec_.failed()) - { - // fail on second read - ec = ec_; - return 0; - } - auto n = capy::copy( - dest, - capy::const_buffer( - s_.data() + nread_, - s_.size() - nread_)); - nread_ += n; - if(nread_ >= s_.size()) - { - if(ec_.failed()) - ec = ec_; - else - ec = http::error::end_of_stream; - } - else - { - ec = {}; - } - return n; - } -}; - -} // (anon) - -#if 0 -BOOST_CORE_STATIC_ASSERT( - std::is_move_constructible::value); -BOOST_CORE_STATIC_ASSERT( - ! std::is_copy_constructible::value); -BOOST_CORE_STATIC_ASSERT( - ! std::is_constructible::value); -BOOST_CORE_STATIC_ASSERT( - std::is_constructible::value); -BOOST_CORE_STATIC_ASSERT( - std::is_constructible::value); -BOOST_CORE_STATIC_ASSERT( - std::is_move_assignable::value); -BOOST_CORE_STATIC_ASSERT( - ! std::is_copy_assignable::value); -BOOST_CORE_STATIC_ASSERT( - std::is_assignable::value); -BOOST_CORE_STATIC_ASSERT( - ! std::is_assignable::value); -BOOST_CORE_STATIC_ASSERT( - std::is_assignable::value); -BOOST_CORE_STATIC_ASSERT( - std::is_assignable::value); -#endif - -struct body_source_test -{ - void grind( - body_source& b, - core::string_view s0, - system::error_code fec = {}) - { - char buf[16]; - for(std::size_t n = 1; n <= sizeof(buf); ++n) - { - std::string s; - system::error_code ec; - b.rewind(); - for(;;) - { - auto nread = b.read(buf, n, ec); - s.append(buf, nread); - if(ec == http::error::end_of_stream) - { - BOOST_TEST(! fec.failed()); - BOOST_TEST_EQ(s, s0); - break; - } - if(ec.failed()) - { - BOOST_TEST_EQ(ec, fec); - break; - } - BOOST_TEST_GT(nread, 0); - } - } - } - - void testEmpty() - { - body_source b; - BOOST_TEST_EQ(b.has_size(), true); - BOOST_TEST_EQ(b.size(), 0); - BOOST_TEST_EQ(b.has_buffers(), true); - BOOST_TEST_EQ(capy::buffer_size(b.data()), 0); - BOOST_TEST_NO_THROW(b.rewind()); - grind(b, ""); - } - - void testBuffers() - { - core::string_view s1("Hello, world!"); - core::string_view s2("Boost"); - - body_source b1((data_source(s1))); - BOOST_TEST_EQ(b1.has_size(), true); - BOOST_TEST_EQ(b1.size(), s1.size()); - BOOST_TEST_EQ(b1.has_buffers(), true); - BOOST_TEST_EQ(capy::buffer_size(b1.data()), s1.size()); - BOOST_TEST_NO_THROW(b1.rewind()); - grind(b1, s1); - - body_source b2 = std::move(b1); - BOOST_TEST_EQ(b2.has_size(), true); - BOOST_TEST_EQ(b2.size(), s1.size()); - BOOST_TEST_EQ(b2.has_buffers(), true); - BOOST_TEST_EQ(capy::buffer_size(b2.data()), s1.size()); - BOOST_TEST_NO_THROW(b2.rewind()); - grind(b2, s1); - - b1 = data_source(s2); - BOOST_TEST_EQ(b1.has_size(), true); - BOOST_TEST_EQ(b1.size(), s2.size()); - BOOST_TEST_EQ(b1.has_buffers(), true); - BOOST_TEST_EQ(capy::buffer_size(b1.data()), s2.size()); - BOOST_TEST_NO_THROW(b1.rewind()); - grind(b1, s2); - } - - void testStream() - { - core::string_view s1("Hello, world!"); - core::string_view s2("Boost"); - - body_source b1((read_source(s1))); - BOOST_TEST_EQ(b1.has_size(), false); - BOOST_TEST_EQ(b1.has_buffers(), false); - BOOST_TEST_THROWS(b1.size(), std::invalid_argument); - BOOST_TEST_THROWS(b1.data(), std::invalid_argument); - BOOST_TEST_NO_THROW(b1.rewind()); - grind(b1, s1); - - body_source b2 = std::move(b1); - BOOST_TEST_EQ(b2.has_size(), false); - BOOST_TEST_EQ(b2.has_buffers(), false); - BOOST_TEST_THROWS(b2.size(), std::invalid_argument); - BOOST_TEST_THROWS(b2.data(), std::invalid_argument); - BOOST_TEST_NO_THROW(b2.rewind()); - BOOST_TEST_EQ(b1.has_size(), true); - BOOST_TEST_EQ(b1.size(), 0); - BOOST_TEST_EQ(b1.has_buffers(), true); - BOOST_TEST_EQ(capy::buffer_size(b1.data()), 0); - BOOST_TEST_NO_THROW(b1.rewind()); - grind(b2, s1); - - b1 = read_source(s2); - BOOST_TEST_EQ(b1.has_size(), false); - BOOST_TEST_EQ(b1.has_buffers(), false); - BOOST_TEST_THROWS(b1.size(), std::invalid_argument); - BOOST_TEST_THROWS(b1.data(), std::invalid_argument); - BOOST_TEST_NO_THROW(b1.rewind()); - grind(b1, s2); - - // sized source - b2 = body_source(s2.size(), read_source(s2)); - BOOST_TEST_EQ(b2.has_size(), true); - BOOST_TEST_EQ(b2.has_buffers(), false); - BOOST_TEST_EQ(b2.size(), s2.size()); - BOOST_TEST_THROWS(b1.data(), std::invalid_argument); - BOOST_TEST_NO_THROW(b2.rewind()); - grind(b2, s2); - } - - void testFail() - { - core::string_view s1("Hello, world!"); - system::error_code fec = - http::error::bad_connection; - - body_source b1((read_source(s1, fec))); - BOOST_TEST_EQ(b1.has_size(), false); - BOOST_TEST_EQ(b1.has_buffers(), false); - BOOST_TEST_THROWS(b1.size(), std::invalid_argument); - BOOST_TEST_THROWS(b1.data(), std::invalid_argument); - BOOST_TEST_NO_THROW(b1.rewind()); - grind(b1, s1, fec); - } - - void run() - { - testEmpty(); - testBuffers(); - testStream(); - testFail(); - } -}; - -TEST_SUITE( - body_source_test, - "boost.beast2.body_source"); - -} // beast2 -} // boost From 2c08daf3c455afcdb0c5238dd86e692caea48622 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Tue, 20 Jan 2026 17:14:04 -0800 Subject: [PATCH 05/17] buffer_param usage --- include/boost/beast2/server/route_handler_corosio.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/boost/beast2/server/route_handler_corosio.hpp b/include/boost/beast2/server/route_handler_corosio.hpp index a8141e43..b2cb2419 100644 --- a/include/boost/beast2/server/route_handler_corosio.hpp +++ b/include/boost/beast2/server/route_handler_corosio.hpp @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include namespace boost { @@ -112,7 +112,7 @@ class corosio_route_params protected: http::route_task - write_impl(capy::any_bufref buffers) override + write_impl(capy::buffer_param buffers) override { // Extract buffers from type-erased wrapper constexpr std::size_t max_bufs = 16; From 49302343fecc57aef7ad49531e81ca487637ffab Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Tue, 20 Jan 2026 18:49:13 -0800 Subject: [PATCH 06/17] io_buffer_param is in Boost.Corosio --- include/boost/beast2/server/route_handler_corosio.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/boost/beast2/server/route_handler_corosio.hpp b/include/boost/beast2/server/route_handler_corosio.hpp index b2cb2419..47fa2cfa 100644 --- a/include/boost/beast2/server/route_handler_corosio.hpp +++ b/include/boost/beast2/server/route_handler_corosio.hpp @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include namespace boost { @@ -112,7 +112,7 @@ class corosio_route_params protected: http::route_task - write_impl(capy::buffer_param buffers) override + write_impl(corosio::io_buffer_param buffers) override { // Extract buffers from type-erased wrapper constexpr std::size_t max_bufs = 16; From 57c0538dfaab55566b6325f9eeb53cf113fe32ea Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Wed, 21 Jan 2026 12:28:28 -0800 Subject: [PATCH 07/17] Add .gitattributes --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..6313b56c --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf From 02b8647fe298ea7fb18b295a02b4bce6e95a1609 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Thu, 22 Jan 2026 07:48:45 -0800 Subject: [PATCH 08/17] Add CMakePresets.json --- CMakePresets.json | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index ca4abfb1..5aa94bfe 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -1,16 +1,10 @@ { - "version": 8, - "configurePresets": [ - { - "name": "Custom configure preset", - "displayName": "Custom configure preset", - "description": "Sets Ninja generator, build and install directory", - "generator": "Ninja", - "binaryDir": "${sourceDir}/out/build/${presetName}", - "cacheVariables": { - "CMAKE_BUILD_TYPE": "Debug", - "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}" - } - } - ] -} \ No newline at end of file + "version": 6, + "cmakeMinimumRequired": { + "major": 3, + "minor": 25, + "patch": 0 + }, + "configurePresets": [], + "buildPresets": [] +} From 998f8cbab3fc668ef4fee9fc1a4ded57bc56eb10 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Thu, 22 Jan 2026 21:58:06 -0800 Subject: [PATCH 09/17] refactor body stream writing in route handler --- .../beast2/server/route_handler_corosio.hpp | 72 +++++++++++++------ 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/include/boost/beast2/server/route_handler_corosio.hpp b/include/boost/beast2/server/route_handler_corosio.hpp index 47fa2cfa..81f1110d 100644 --- a/include/boost/beast2/server/route_handler_corosio.hpp +++ b/include/boost/beast2/server/route_handler_corosio.hpp @@ -15,8 +15,9 @@ #include #include #include -#include #include +#include +#include namespace boost { namespace beast2 { @@ -26,6 +27,8 @@ namespace beast2 { class corosio_route_params : public http::route_params { + http::serializer::stream srs_; + public: using stream_type = corosio::socket; @@ -92,19 +95,31 @@ class corosio_route_params http::route_task end() override { - // For chunked encoding, send the terminating chunk - // Currently assumes headers have already been sent - // and any final data has been written via write() + // Close the serializer stream if active + if(srs_.is_open()) + srs_.close(); - // If using chunked encoding, send "0\r\n\r\n" - if(res.value_or( - http::field::transfer_encoding, "") == "chunked") + // Drain all remaining serializer output + while(!serializer.is_done()) { - static constexpr char terminator[] = "0\r\n\r\n"; - auto [ec, n] = co_await stream.write_some( - capy::const_buffer(terminator, 5)); + auto cbs = serializer.prepare(); + if(cbs.has_error()) + { + if(cbs.error() == http::error::need_data) + continue; + co_return cbs.error(); + } + + if(capy::buffer_size(*cbs) == 0) + { + serializer.consume(0); + continue; + } + + auto [ec, n] = co_await capy::write(stream, *cbs); if(ec) co_return ec; + serializer.consume(capy::buffer_size(*cbs)); } co_return {}; @@ -112,22 +127,37 @@ class corosio_route_params protected: http::route_task - write_impl(corosio::io_buffer_param buffers) override + write_impl(capy::const_buffer_param buffers) override { - // Extract buffers from type-erased wrapper - constexpr std::size_t max_bufs = 16; - capy::mutable_buffer bufs[max_bufs]; - auto const n = buffers.copy_to(bufs, max_bufs); + // Initialize streaming on first call + if(!srs_.is_open()) + srs_ = serializer.start_stream(res); - // Write all buffers - for(std::size_t i = 0; i < n; ++i) + // Loop until all input buffers are consumed + while(true) { - auto [ec, written] = co_await stream.write_some( - capy::const_buffer( - bufs[i].data(), - bufs[i].size())); + auto bufs = buffers.data(); + if(bufs.empty()) + break; + + // Copy data to serializer stream + std::size_t bytes = capy::copy(srs_.prepare(), bufs); + srs_.commit(bytes); + buffers.consume(bytes); + + // Write serializer output to socket + auto cbs = serializer.prepare(); + if(cbs.has_error()) + { + if(cbs.error() != http::error::need_data) + co_return cbs.error(); + continue; + } + + auto [ec, n] = co_await capy::write(stream, *cbs); if(ec) co_return ec; + serializer.consume(capy::buffer_size(*cbs)); } co_return {}; From 0ea6d155caf2123f28b5ae51d2cce9d16470fbe1 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Thu, 22 Jan 2026 22:09:34 -0800 Subject: [PATCH 10/17] Update for moved files and build scripts --- example/client/burl/CMakeLists.txt | 8 +++---- example/client/burl/connect.cpp | 4 ++-- example/client/burl/connect.hpp | 6 ++--- example/client/burl/main.cpp | 16 ++++++------- example/client/get/CMakeLists.txt | 8 +++---- example/client/get/main.cpp | 22 ++++++++--------- example/client/jsonrpc/CMakeLists.txt | 8 +++---- example/client/jsonrpc/jsonrpc/client.cpp | 14 +++++------ example/client/jsonrpc/jsonrpc/client.hpp | 6 ++--- example/client/jsonrpc/main.cpp | 18 +++++++------- example/client/visit/main.cpp | 6 ++--- example/server/CMakeLists.txt | 8 +++---- example/server/main.cpp | 26 ++++++++++----------- example/server/serve_log_admin.cpp | 6 ++--- example/server/serve_log_admin.hpp | 4 ++-- include/boost/beast2/log_service.hpp | 4 ++-- include/boost/beast2/server/http_stream.hpp | 6 ++--- src/http_server.cpp | 6 ++--- src/log_service.cpp | 2 +- 19 files changed, 89 insertions(+), 89 deletions(-) diff --git a/example/client/burl/CMakeLists.txt b/example/client/burl/CMakeLists.txt index 921ecea6..8c5956e0 100644 --- a/example/client/burl/CMakeLists.txt +++ b/example/client/burl/CMakeLists.txt @@ -35,12 +35,12 @@ if (WIN32) target_link_libraries(beast2_example_client_burl crypt32) endif() -if (TARGET Boost::capy_zlib) - target_link_libraries(beast2_example_client_burl Boost::capy_zlib) +if (TARGET Boost::http_zlib) + target_link_libraries(beast2_example_client_burl Boost::http_zlib) endif() -if (TARGET Boost::capy_brotli) - target_link_libraries(beast2_example_client_burl Boost::capy_brotli) +if (TARGET Boost::http_brotli) + target_link_libraries(beast2_example_client_burl Boost::http_brotli) endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") diff --git a/example/client/burl/connect.cpp b/example/client/burl/connect.cpp index b9855d17..8913d492 100644 --- a/example/client/burl/connect.cpp +++ b/example/client/burl/connect.cpp @@ -153,7 +153,7 @@ connect_socks5_proxy( asio::awaitable connect_http_proxy( const operation_config& oc, - capy::polystore& capy_ctx, + http::polystore& capy_ctx, asio::ip::tcp::socket& stream, const urls::url_view& url, const urls::url_view& proxy) @@ -225,7 +225,7 @@ asio::awaitable connect( const operation_config& oc, ssl::context& ssl_ctx, - capy::polystore& capy_ctx, + http::polystore& capy_ctx, any_stream& stream, urls::url url) { diff --git a/example/client/burl/connect.hpp b/example/client/burl/connect.hpp index ca4aefb5..eddcea76 100644 --- a/example/client/burl/connect.hpp +++ b/example/client/burl/connect.hpp @@ -15,11 +15,11 @@ #include #include -#include +#include #include namespace asio = boost::asio; -namespace capy = boost::capy; +namespace http = boost::http; namespace ssl = boost::asio::ssl; namespace urls = boost::urls; @@ -27,7 +27,7 @@ asio::awaitable connect( const operation_config& oc, ssl::context& ssl_ctx, - capy::polystore& capy_ctx, + http::polystore& capy_ctx, any_stream& stream, urls::url url); diff --git a/example/client/burl/main.cpp b/example/client/burl/main.cpp index 1f318507..e17f8bef 100644 --- a/example/client/burl/main.cpp +++ b/example/client/burl/main.cpp @@ -31,8 +31,8 @@ #include #include #include -#include -#include +#include +#include #include #include #include @@ -45,13 +45,13 @@ namespace capy = boost::capy; namespace scope = boost::scope; using system_error = boost::system::system_error; -#ifdef BOOST_CAPY_HAS_ZLIB +#ifdef BOOST_HTTP_HAS_ZLIB constexpr bool capy_has_zlib = true; #else constexpr bool capy_has_zlib = false; #endif -#ifdef BOOST_CAPY_HAS_BROTLI +#ifdef BOOST_HTTP_HAS_BROTLI constexpr bool capy_has_brotli = true; #else constexpr bool capy_has_brotli = false; @@ -345,7 +345,7 @@ perform_request( boost::optional& cookie_jar, core::string_view exp_cookies, ssl::context& ssl_ctx, - capy::polystore& capy_ctx, + http::polystore& capy_ctx, message msg, request_opt request_opt) { @@ -759,7 +759,7 @@ co_main(int argc, char* argv[]) auto executor = co_await asio::this_coro::executor; auto task_group = ::task_group{ executor, oc.parallel_max }; - auto capy_ctx = capy::polystore{}; + auto capy_ctx = http::polystore{}; auto cookie_jar = boost::optional<::cookie_jar>{}; auto header_output = boost::optional{}; auto exp_cookies = std::string{}; @@ -778,13 +778,13 @@ co_main(int argc, char* argv[]) if constexpr(capy_has_brotli) { cfg.apply_brotli_decoder = true; - capy::brotli::install_decode_service(capy_ctx); + http::brotli::install_decode_service(capy_ctx); } if constexpr(capy_has_zlib) { cfg.apply_deflate_decoder = true; cfg.apply_gzip_decoder = true; - capy::zlib::install_inflate_service(capy_ctx); + http::zlib::install_inflate_service(capy_ctx); } http::install_parser_service(capy_ctx, cfg); } diff --git a/example/client/get/CMakeLists.txt b/example/client/get/CMakeLists.txt index 9e296095..93aa4c14 100644 --- a/example/client/get/CMakeLists.txt +++ b/example/client/get/CMakeLists.txt @@ -33,10 +33,10 @@ if (WIN32) target_link_libraries(beast2_example_client_get crypt32) endif() -if (TARGET Boost::capy_zlib) - target_link_libraries(beast2_example_client_get Boost::capy_zlib) +if (TARGET Boost::http_zlib) + target_link_libraries(beast2_example_client_get Boost::http_zlib) endif() -if (TARGET Boost::capy_brotli) - target_link_libraries(beast2_example_client_get Boost::capy_brotli) +if (TARGET Boost::http_brotli) + target_link_libraries(beast2_example_client_get Boost::http_brotli) endif() diff --git a/example/client/get/main.cpp b/example/client/get/main.cpp index a9bca59b..849c1113 100644 --- a/example/client/get/main.cpp +++ b/example/client/get/main.cpp @@ -17,9 +17,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include #include #include @@ -50,7 +50,7 @@ class session session( asio::io_context& ioc, asio::ssl::context& ssl_ctx, - capy::polystore& capy_ctx) + http::polystore& capy_ctx) : ssl_ctx_(ssl_ctx) , stream_(ioc, ssl_ctx) , resolver_(ioc) @@ -74,10 +74,10 @@ class session url.authority().encoded_host_and_port().decode()); // Enable compression - #ifdef BOOST_CAPY_HAS_BROTLI + #ifdef BOOST_HTTP_HAS_BROTLI req_.append(field::accept_encoding, "br"); #endif - #ifdef BOOST_CAPY_HAS_ZLIB + #ifdef BOOST_HTTP_HAS_ZLIB req_.append(field::accept_encoding, "deflate, gzip"); #endif @@ -442,21 +442,21 @@ main(int argc, char* argv[]) // holds optional deflate and // required configuration services - capy::polystore capy_ctx; + http::polystore capy_ctx; // Install parser service { http::response_parser::config cfg; cfg.body_limit = std::uint64_t(-1); cfg.min_buffer = 64 * 1024; - #ifdef BOOST_CAPY_HAS_BROTLI + #ifdef BOOST_HTTP_HAS_BROTLI cfg.apply_brotli_decoder = true; - capy::brotli::install_decode_service(capy_ctx); + http::brotli::install_decode_service(capy_ctx); #endif - #ifdef BOOST_CAPY_HAS_ZLIB + #ifdef BOOST_HTTP_HAS_ZLIB cfg.apply_deflate_decoder = true; cfg.apply_gzip_decoder = true; - capy::zlib::install_inflate_service(capy_ctx); + http::zlib::install_inflate_service(capy_ctx); #endif http::install_parser_service(capy_ctx, cfg); } diff --git a/example/client/jsonrpc/CMakeLists.txt b/example/client/jsonrpc/CMakeLists.txt index 998f4895..e596fb74 100644 --- a/example/client/jsonrpc/CMakeLists.txt +++ b/example/client/jsonrpc/CMakeLists.txt @@ -43,9 +43,9 @@ set_property(TARGET beast2_example_client_jsonrpc target_link_libraries(beast2_example_client_jsonrpc PRIVATE beast2_example_client_jsonrpc_lib) -if (TARGET Boost::capy_zlib) - target_link_libraries(beast2_example_client_jsonrpc PRIVATE Boost::capy_zlib) +if (TARGET Boost::http_zlib) + target_link_libraries(beast2_example_client_jsonrpc PRIVATE Boost::http_zlib) endif() -if (TARGET Boost::capy_brotli) - target_link_libraries(beast2_example_client_jsonrpc PRIVATE Boost::capy_brotli) +if (TARGET Boost::http_brotli) + target_link_libraries(beast2_example_client_jsonrpc PRIVATE Boost::http_brotli) endif() diff --git a/example/client/jsonrpc/jsonrpc/client.cpp b/example/client/jsonrpc/jsonrpc/client.cpp index 5823c29a..a828b299 100644 --- a/example/client/jsonrpc/jsonrpc/client.cpp +++ b/example/client/jsonrpc/jsonrpc/client.cpp @@ -20,9 +20,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include #include using namespace boost; @@ -385,7 +385,7 @@ class client::request_op client:: client( urls::url endpoint, - capy::polystore& capy_ctx, + http::polystore& capy_ctx, asio::any_io_executor exec, asio::ssl::context& ssl_ctx) : client( @@ -398,7 +398,7 @@ client( client:: client( urls::url endpoint, - capy::polystore& capy_ctx, + http::polystore& capy_ctx, std::unique_ptr stream) : stream_(std::move(stream)) , endpoint_(std::move(endpoint)) @@ -416,10 +416,10 @@ client( req_.append(field::accept, "application/json"); req_.append(field::user_agent, "Boost.Http.Io"); - if(capy_ctx.find() != nullptr) + if(capy_ctx.find() != nullptr) req_.append(field::accept_encoding, "br"); - if(capy_ctx.find() != nullptr) + if(capy_ctx.find() != nullptr) req_.append(field::accept_encoding, "deflate, gzip"); pr_.reset(); diff --git a/example/client/jsonrpc/jsonrpc/client.hpp b/example/client/jsonrpc/jsonrpc/client.hpp index 4265cfd4..38f6bf24 100644 --- a/example/client/jsonrpc/jsonrpc/client.hpp +++ b/example/client/jsonrpc/jsonrpc/client.hpp @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include namespace jsonrpc { @@ -56,7 +56,7 @@ class client */ client( boost::urls::url endpoint, - boost::capy::polystore& capy_ctx, + boost::http::polystore& capy_ctx, boost::asio::any_io_executor exec, boost::asio::ssl::context& ssl_ctx); @@ -67,7 +67,7 @@ class client */ client( boost::urls::url endpoint, - boost::capy::polystore& capy_ctx, + boost::http::polystore& capy_ctx, std::unique_ptr stream); /// Get the executor associated with the object. diff --git a/example/client/jsonrpc/main.cpp b/example/client/jsonrpc/main.cpp index 0b7c9f54..33b11833 100644 --- a/example/client/jsonrpc/main.cpp +++ b/example/client/jsonrpc/main.cpp @@ -16,9 +16,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include #include @@ -27,7 +27,7 @@ using namespace boost; asio::awaitable co_main( asio::ssl::context& ssl_ctx, - capy::polystore& capy_ctx) + http::polystore& capy_ctx) { using dec_float = multiprecision::cpp_dec_float_50; const auto to_int = [](std::string_view s) @@ -147,20 +147,20 @@ main(int, char*[]) // CAPY context holds optional deflate and // required configuration services - capy::polystore capy_ctx; + http::polystore capy_ctx; // Install parser service { http::response_parser::config cfg; cfg.min_buffer = 64 * 1024; - #ifdef BOOST_CAPY_HAS_BROTLI + #ifdef BOOST_HTTP_HAS_BROTLI cfg.apply_brotli_decoder = true; - capy::brotli::install_decode_service(capy_ctx); + http::brotli::install_decode_service(capy_ctx); #endif - #ifdef BOOST_CAPY_HAS_ZLIB + #ifdef BOOST_HTTP_HAS_ZLIB cfg.apply_deflate_decoder = true; cfg.apply_gzip_decoder = true; - capy::zlib::install_inflate_service(capy_ctx); + http::zlib::install_inflate_service(capy_ctx); #endif http::install_parser_service(capy_ctx, cfg); } diff --git a/example/client/visit/main.cpp b/example/client/visit/main.cpp index c5f3c15e..7810f707 100644 --- a/example/client/visit/main.cpp +++ b/example/client/visit/main.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include //------------------------------------------------ @@ -202,7 +202,7 @@ struct worker explicit worker( executor_type ex, - boost::capy::polystore& ctx) + boost::http::polystore& ctx) : sock(ex) , pr(ctx) { @@ -276,7 +276,7 @@ main(int argc, char* argv[]) (void)argc; (void)argv; - boost::capy::polystore ctx; + boost::http::polystore ctx; boost::http::parser::config_base cfg; boost::http::install_parser_service(ctx, cfg); diff --git a/example/server/CMakeLists.txt b/example/server/CMakeLists.txt index f2f1508b..cf7aae0b 100644 --- a/example/server/CMakeLists.txt +++ b/example/server/CMakeLists.txt @@ -26,10 +26,10 @@ target_link_libraries( Boost::url ) -if (TARGET Boost::capy_zlib) - target_link_libraries(beast2_server_example Boost::capy_zlib) +if (TARGET Boost::http_zlib) + target_link_libraries(beast2_server_example Boost::http_zlib) endif() -if (TARGET Boost::capy_brotli) - target_link_libraries(beast2_server_example Boost::capy_brotli) +if (TARGET Boost::http_brotli) + target_link_libraries(beast2_server_example Boost::http_brotli) endif() diff --git a/example/server/main.cpp b/example/server/main.cpp index 4a141d4e..02fde4f3 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -12,17 +12,17 @@ #include #include #include -#include +#include #include #include #include #include #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include @@ -34,16 +34,16 @@ namespace beast2 { capy::thread_pool g_tp; -void install_services(capy::application& app) +void install_services(http::application& app) { -#ifdef BOOST_CAPY_HAS_BROTLI - capy::brotli::install_decode_service(app); - capy::brotli::install_encode_service(app); +#ifdef BOOST_HTTP_HAS_BROTLI + http::brotli::install_decode_service(app); + http::brotli::install_encode_service(app); #endif -#ifdef BOOST_CAPY_HAS_ZLIB - capy::zlib::install_deflate_service(app); - capy::zlib::install_inflate_service(app); +#ifdef BOOST_HTTP_HAS_ZLIB + http::zlib::install_deflate_service(app); + http::zlib::install_inflate_service(app); #endif // VFALCO These ugly incantations are needed for http and will hopefully go away soon. @@ -158,7 +158,7 @@ int server_main( int argc, char* argv[] ) return EXIT_FAILURE; } - capy::application app; + http::application app; corosio::io_context ioc; install_services(app); diff --git a/example/server/serve_log_admin.cpp b/example/server/serve_log_admin.cpp index 743d27da..5aba7bea 100644 --- a/example/server/serve_log_admin.cpp +++ b/example/server/serve_log_admin.cpp @@ -23,7 +23,7 @@ class serve_log_page { public: serve_log_page( - capy::polystore& ps) + http::polystore& ps) : ls_(use_log_service(ps)) { } @@ -92,7 +92,7 @@ class handle_submit { public: handle_submit( - capy::polystore& ps) + http::polystore& ps) : ls_(use_log_service(ps)) { } @@ -121,7 +121,7 @@ class handle_submit #if 0 router serve_log_admin( - capy::polystore& ps) + http::polystore& ps) { router r; r.add(http::method::get, "/", serve_log_page(ps)); diff --git a/example/server/serve_log_admin.hpp b/example/server/serve_log_admin.hpp index 1851833c..ccf9356f 100644 --- a/example/server/serve_log_admin.hpp +++ b/example/server/serve_log_admin.hpp @@ -12,14 +12,14 @@ #include #include -#include +#include namespace boost { namespace beast2 { router serve_log_admin( - capy::polystore& ps); + http::polystore& ps); } // beast2 } // boost diff --git a/include/boost/beast2/log_service.hpp b/include/boost/beast2/log_service.hpp index 9a975330..efae4c41 100644 --- a/include/boost/beast2/log_service.hpp +++ b/include/boost/beast2/log_service.hpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include @@ -47,7 +47,7 @@ class BOOST_SYMBOL_VISIBLE BOOST_BEAST2_DECL log_service& use_log_service( - capy::polystore& ps); + http::polystore& ps); } // beast2 } // boost diff --git a/include/boost/beast2/server/http_stream.hpp b/include/boost/beast2/server/http_stream.hpp index f48cf853..211203f4 100644 --- a/include/boost/beast2/server/http_stream.hpp +++ b/include/boost/beast2/server/http_stream.hpp @@ -19,7 +19,7 @@ #include #include #include -#include +#include #include #include #include @@ -61,7 +61,7 @@ class http_stream @param routes The router used to dispatch incoming HTTP requests. */ http_stream( - capy::application& app, + http::application& app, corosio::socket& sock, http::flat_router& routes); @@ -112,7 +112,7 @@ class http_stream inline http_stream:: http_stream( - capy::application& app, + http::application& app, corosio::socket& sock, http::flat_router& /*routes*/) : sect_(use_log_service(app).get_section("http_stream")) diff --git a/src/http_server.cpp b/src/http_server.cpp index 461f4609..5cc4073b 100644 --- a/src/http_server.cpp +++ b/src/http_server.cpp @@ -21,7 +21,7 @@ #include #include -#include +#include namespace boost { namespace beast2 { @@ -29,7 +29,7 @@ namespace beast2 { struct http_server::impl { http::flat_router router; - capy::application app; + http::application app; impl(http::flat_router r) : router(std::move(r)) @@ -76,7 +76,7 @@ struct http_server:: launch(ctx.get_executor(), srv->do_session(*this)); } - capy::task> + capy::task> read_header() { std::size_t total_bytes = 0; diff --git a/src/log_service.cpp b/src/log_service.cpp index 90776b2e..a497eba1 100644 --- a/src/log_service.cpp +++ b/src/log_service.cpp @@ -43,7 +43,7 @@ class log_service_impl log_service& use_log_service( - capy::polystore& ps) + http::polystore& ps) { return ps.try_emplace(); } From fc568586d1f5c3f407b509d5c6542a08ab64ff86 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 23 Jan 2026 06:56:58 -0800 Subject: [PATCH 11/17] route_params::send is in Boost.Http --- .../beast2/server/route_handler_corosio.hpp | 51 ------------------- 1 file changed, 51 deletions(-) diff --git a/include/boost/beast2/server/route_handler_corosio.hpp b/include/boost/beast2/server/route_handler_corosio.hpp index 81f1110d..714788c3 100644 --- a/include/boost/beast2/server/route_handler_corosio.hpp +++ b/include/boost/beast2/server/route_handler_corosio.hpp @@ -41,57 +41,6 @@ class corosio_route_params { } - http::route_task - send(std::string_view body) override - { - // Auto-detect Content-Type if not already set - if(! res.exists(http::field::content_type)) - { - if(! body.empty() && body[0] == '<') - res.set(http::field::content_type, - "text/html; charset=utf-8"); - else - res.set(http::field::content_type, - "text/plain; charset=utf-8"); - } - - // Set Content-Length if not already set - if(! res.exists(http::field::content_length)) - res.set_payload_size(body.size()); - - // Start the serializer with the response and body - serializer.start(res, - http::string_body(std::string(body))); - - // Write the response to the socket - while(! serializer.is_done()) - { - auto rv = serializer.prepare(); - if(! rv) - co_return rv.error(); - - auto bufs = *rv; - - // Write all buffers - for(auto const& buf : bufs) - { - auto [ec, n] = co_await stream.write_some( - capy::const_buffer(buf.data(), buf.size())); - if(ec) - co_return ec; - } - - // Calculate total size written - std::size_t written = 0; - for(auto const& buf : bufs) - written += buf.size(); - - serializer.consume(written); - } - - co_return {}; - } - http::route_task end() override { From 249160a54de7f48c2c347f972ee443b06ccff849 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 23 Jan 2026 07:38:52 -0800 Subject: [PATCH 12/17] Remove serializer Source API --- example/client/burl/main.cpp | 21 +- example/client/burl/message.cpp | 103 +------- example/client/burl/message.hpp | 51 +--- example/client/burl/multipart_form.cpp | 305 ---------------------- example/client/burl/multipart_form.hpp | 85 ------ example/client/burl/options.cpp | 52 +--- example/client/jsonrpc/jsonrpc/client.cpp | 36 +-- 7 files changed, 15 insertions(+), 638 deletions(-) delete mode 100644 example/client/burl/multipart_form.cpp delete mode 100644 example/client/burl/multipart_form.hpp diff --git a/example/client/burl/main.cpp b/example/client/burl/main.cpp index e17f8bef..6f624fa8 100644 --- a/example/client/burl/main.cpp +++ b/example/client/burl/main.cpp @@ -376,26 +376,7 @@ perform_request( if(!request_opt.input.empty()) { - msg = [&]() -> message - { - if(request_opt.input == "-") - return stdin_body{}; - - auto path = request_opt.input; - - // Append filename to URL if missing - auto segs = url.encoded_segments(); - if(segs.empty()) - { - segs.push_back(path.filename().string()); - } - else if(auto back = --segs.end(); back->empty()) - { - segs.replace(back, path.filename().string()); - } - - return file_body{ path.string() }; - }(); + throw std::runtime_error{ "File upload is not available" }; } fs::path output_path = [&]() diff --git a/example/client/burl/message.cpp b/example/client/burl/message.cpp index d9601dc6..ddeff2c8 100644 --- a/example/client/burl/message.cpp +++ b/example/client/burl/message.cpp @@ -8,18 +8,10 @@ // #include "message.hpp" -#include "mime_type.hpp" -#include #include -#include -#include -#include - -namespace capy = boost::capy; -namespace fs = std::filesystem; -using system_error = boost::system::system_error; +namespace capy = boost::capy; string_body::string_body(std::string body, std::string content_type) : body_{ std::move(body) } @@ -53,79 +45,6 @@ string_body::body() const noexcept // ----------------------------------------------------------------------------- -file_body::file_body(std::string path) - : path_{ std::move(path) } -{ -} - -http::method -file_body::method() const noexcept -{ - return http::method::put; -} - -core::string_view -file_body::content_type() const noexcept -{ - return mime_type(path_); -} - -std::uint64_t -file_body::content_length() const -{ - return fs::file_size(path_); -} - -http::file_source -file_body::body() const -{ - boost::capy::file file; - error_code ec; - file.open(path_.c_str(), boost::capy::file_mode::read, ec); - if(ec) - throw system_error{ ec }; - - return http::file_source{ std::move(file), content_length() }; -} - -// ----------------------------------------------------------------------------- - -boost::http::source::results -stdin_body::source::on_read(capy::mutable_buffer mb) -{ - std::cin.read(static_cast(mb.data()), mb.size()); - - return { .ec = {}, - .bytes = static_cast(std::cin.gcount()), - .finished = std::cin.eof() }; -} - -http::method -stdin_body::method() const noexcept -{ - return http::method::put; -} - -core::string_view -stdin_body::content_type() const noexcept -{ - return "application/octet-stream"; -} - -boost::optional -stdin_body::content_length() const noexcept -{ - return boost::none; -} - -stdin_body::source -stdin_body::body() const -{ - return {}; -} - -// ----------------------------------------------------------------------------- - void message::set_headers(http::request& request) const { @@ -138,20 +57,11 @@ message::set_headers(http::request& request) const request.set_method(f.method()); request.set(field::content_type, f.content_type()); - boost::optional content_length = - f.content_length(); - if(content_length.has_value()) - { - request.set_content_length(content_length.value()); - if(content_length.value() >= 1024 * 1024 && - request.version() == http::version::http_1_1) - request.set(field::expect, "100-continue"); - } - else - { - request.set_chunked(true); + std::size_t content_length = f.content_length(); + request.set_content_length(content_length); + if(content_length >= 1024 * 1024 && + request.version() == http::version::http_1_1) request.set(field::expect, "100-continue"); - } } }, body_); @@ -167,8 +77,7 @@ message::start_serializer( { if constexpr(!std::is_same_v) { - serializer.start>( - request, f.body()); + serializer.start(request, f.body()); } else { diff --git a/example/client/burl/message.hpp b/example/client/burl/message.hpp index 3d3caef4..1e5387d3 100644 --- a/example/client/burl/message.hpp +++ b/example/client/burl/message.hpp @@ -10,9 +10,6 @@ #ifndef BURL_MESSAGE_HPP #define BURL_MESSAGE_HPP -#include "multipart_form.hpp" - -#include #include #include @@ -41,57 +38,11 @@ class string_body body() const noexcept; }; -class file_body -{ - std::string path_; - -public: - file_body(std::string path); - - http::method - method() const noexcept; - - core::string_view - content_type() const noexcept; - - std::uint64_t - content_length() const; - - http::file_source - body() const; -}; - -class stdin_body -{ -public: - class source : public http::source - { - public: - results - on_read(capy::mutable_buffer mb) override; - }; - - http::method - method() const noexcept; - - core::string_view - content_type() const noexcept; - - boost::optional - content_length() const noexcept; - - source - body() const; -}; - class message { std::variant< std::monostate, - string_body, - multipart_form, - file_body, - stdin_body> + string_body> body_; public: diff --git a/example/client/burl/multipart_form.cpp b/example/client/burl/multipart_form.cpp deleted file mode 100644 index 16c0edab..00000000 --- a/example/client/burl/multipart_form.cpp +++ /dev/null @@ -1,305 +0,0 @@ -// -// Copyright (c) 2024 Mohammad Nejati -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/beast2 -// - -#include "multipart_form.hpp" - -#include -#include -#include -#include - -#include -#include - -namespace capy = boost::capy; -namespace core = boost::core; -namespace fs = std::filesystem; -using system_error = boost::system::system_error; - -namespace -{ -std::array -generate_boundary() -{ - std::array rs; - constexpr static char chars[] = - "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - static std::random_device rd; - std::uniform_int_distribution dist{ 0, sizeof(chars) - 2 }; - std::fill(rs.begin(), rs.end(), '-'); - std::generate( - rs.begin() + 2 + 24, rs.end() - 2, [&] { return chars[dist(rd)]; }); - return rs; -} - -std::string -serialize_headers(std::vector& headers) -{ - std::string rs; - for(const auto& h : headers) - { - rs.append("\r\n"); - rs.append(h); - } - return rs; -} - -core::string_view content_disposition_ = - "\r\nContent-Disposition: form-data; name=\""; -core::string_view filename_ = "; filename=\""; -core::string_view content_type_ = "\r\nContent-Type: "; -} // namespace - -// ----------------------------------------------------------------------------- - -multipart_form::multipart_form() - : storage_{ generate_boundary() } -{ -} - -void -multipart_form::append( - bool is_file, - std::string name, - std::string value, - boost::optional filename, - boost::optional content_type, - std::vector headers) -{ - auto size = is_file ? fs::file_size(value) : value.size(); - - pacapy_.push_back( - { is_file, - std::move(name), - std::move(value), - size, - std::move(filename), - std::move(content_type), - serialize_headers(headers) }); -} - -http::method -multipart_form::method() const noexcept -{ - return http::method::post; -} - -std::string -multipart_form::content_type() const -{ - std::string res = "multipart/form-data; boundary="; - // append boundary - res.append(storage_.begin() + 2, storage_.end() - 2); - return res; -} - -std::uint64_t -multipart_form::content_length() const noexcept -{ - auto rs = std::uint64_t{}; - for(const auto& part : pacapy_) - { - rs += storage_.size() - 2; // --boundary - rs += content_disposition_.size(); - rs += part.name.size(); - rs += 1; // closing double quote - - if(part.content_type) - { - rs += content_type_.size(); - rs += part.content_type->size(); - } - - if(part.filename) - { - rs += filename_.size(); - rs += part.filename->size(); - rs += 1; // closing double quote - } - - rs += part.headers.size(); - - rs += 4; // after headers - rs += part.size; - rs += 2; // after content - } - rs += storage_.size(); // --boundary-- - rs += 2; // - return rs; -} - -multipart_form::source -multipart_form::body() const -{ - return source{ this }; -} - -// ----------------------------------------------------------------------------- - -multipart_form::source::source(const multipart_form* form) noexcept - : form_{ form } -{ -} - -multipart_form::source::results -multipart_form::source::on_read(capy::mutable_buffer mb) -{ - auto rs = results{}; - - auto copy = [&](core::string_view sv) - { - capy::const_buffer source(sv.data(), sv.size()); - capy::remove_prefix(mb, static_cast(skip_)); - - auto copied = capy::copy(mb, source); - - capy::remove_prefix(mb, copied); - rs.bytes += copied; - skip_ += copied; - - if(skip_ != sv.size()) - return false; - - skip_ = 0; - return true; - }; - - auto read = [&](const std::string& path, uint64_t size) - { - capy::file file; - - file.open(path.c_str(), capy::file_mode::read, rs.ec); - if(rs.ec) - return false; - - file.seek(skip_, rs.ec); - if(rs.ec) - return false; - - auto read = file.read( - mb.data(), - (std::min)(static_cast(mb.size()), size), - rs.ec); - if(rs.ec) - return false; - - capy::remove_prefix(mb, read); - rs.bytes += read; - skip_ += read; - - if(skip_ != size) - return false; - - skip_ = 0; - return true; - }; - - while(it_ != form_->pacapy_.end()) - { - switch(step_) - { - case 0: - // --boundary - if(!copy({ form_->storage_.data(), form_->storage_.size() - 2 })) - return rs; - step_ = 1; - BOOST_FALLTHROUGH; - case 1: - if(!copy(content_disposition_)) - return rs; - step_ = 2; - BOOST_FALLTHROUGH; - case 2: - if(!copy(it_->name)) - return rs; - step_ = 3; - BOOST_FALLTHROUGH; - case 3: - if(!copy("\"")) - return rs; - step_ = 4; - BOOST_FALLTHROUGH; - case 4: - if(!it_->filename) - goto content_type; - if(!copy(filename_)) - return rs; - step_ = 5; - BOOST_FALLTHROUGH; - case 5: - if(!copy(it_->filename.value())) - return rs; - step_ = 6; - BOOST_FALLTHROUGH; - case 6: - if(!copy("\"")) - return rs; - step_ = 7; - BOOST_FALLTHROUGH; - case 7: - content_type: - if(!it_->content_type) - goto headers; - if(!copy(content_type_)) - return rs; - step_ = 8; - BOOST_FALLTHROUGH; - case 8: - if(!copy(it_->content_type.value())) - return rs; - step_ = 9; - BOOST_FALLTHROUGH; - case 9: - headers: - if(!copy(it_->headers)) - return rs; - step_ = 10; - BOOST_FALLTHROUGH; - case 10: - if(!copy("\r\n\r\n")) - return rs; - step_ = 11; - BOOST_FALLTHROUGH; - case 11: - if(it_->is_file) - { - if(!read(it_->value, it_->size)) - return rs; - } - else - { - if(!copy(it_->value)) - return rs; - } - step_ = 12; - BOOST_FALLTHROUGH; - case 12: - if(!copy("\r\n")) - return rs; - ++it_; - step_ = 0; - } - } - - switch(step_) - { - case 0: - // --boundary-- - if(!copy({ form_->storage_.data(), form_->storage_.size() })) - return rs; - step_ = 1; - BOOST_FALLTHROUGH; - case 1: - if(!copy("\r\n")) - return rs; - } - - rs.finished = true; - return rs; -} diff --git a/example/client/burl/multipart_form.hpp b/example/client/burl/multipart_form.hpp deleted file mode 100644 index 09a0068f..00000000 --- a/example/client/burl/multipart_form.hpp +++ /dev/null @@ -1,85 +0,0 @@ -// -// Copyright (c) 2024 Mohammad Nejati -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -// Official repository: https://github.com/cppalliance/beast2 -// - -#ifndef BURL_MULTIPART_FORM_HPP -#define BURL_MULTIPART_FORM_HPP - -#include -#include -#include -#include -#include -#include - -#include -#include - -namespace capy = boost::capy; -namespace http = boost::http; -using error_code = boost::system::error_code; - -class multipart_form -{ - struct part - { - bool is_file = false; - std::string name; - std::string value; - std::uint64_t size; - boost::optional filename; - boost::optional content_type; - std::string headers; - }; - - // boundary with extra "--" prefix and postfix. - std::array storage_; - std::vector pacapy_; - -public: - class source; - - multipart_form(); - - void - append( - bool is_file, - std::string name, - std::string value, - boost::optional filename = {}, - boost::optional content_type = {}, - std::vector headers = {}); - - http::method - method() const noexcept; - - std::string - content_type() const; - - std::uint64_t - content_length() const noexcept; - - source - body() const; -}; - -class multipart_form::source : public http::source -{ - const multipart_form* form_; - std::vector::const_iterator it_{ form_->pacapy_.begin() }; - int step_ = 0; - std::uint64_t skip_ = 0; - -public: - explicit source(const multipart_form* form) noexcept; - - results - on_read(capy::mutable_buffer mb) override; -}; - -#endif diff --git a/example/client/burl/options.cpp b/example/client/burl/options.cpp index f0dc12fa..3371afc7 100644 --- a/example/client/burl/options.cpp +++ b/example/client/burl/options.cpp @@ -984,57 +984,7 @@ parse_args(int argc, char* argv[]) if(vm.contains("form") || vm.contains("form-string")) { - auto form = multipart_form{}; - if(vm.contains("form")) - { - for(core::string_view sv : - vm.at("form").as>()) - { - auto [name, prefix, value, filename, type, headers] = - parse_form_option(sv); - - auto is_file = false; - - if(prefix == '@' || prefix == '<') - { - is_file = true; - - if(!filename && prefix != '<') - filename = fs::path{ value }.filename().string(); - - if(value == "-") - { - value.clear(); - any_istream{ core::string_view{ "-" } }.append_to( - value); - is_file = false; - } - else if(!type) - { - type = ::mime_type(value); - } - } - form.append( - is_file, - std::move(name), - std::move(value), - std::move(filename), - std::move(type), - std::move(headers)); - } - } - if(vm.contains("form-string")) - { - for(core::string_view sv : - vm.at("form-string").as>()) - { - if(auto pos = sv.find('='); pos != sv.npos) - form.append(false, sv.substr(0, pos), sv.substr(pos + 1)); - else - throw std::runtime_error("Illegally formatted input field"); - } - } - oc.msg = std::move(form); + throw std::runtime_error{ "Multipart form support is not available" }; } if(vm.contains("json")) diff --git a/example/client/jsonrpc/jsonrpc/client.cpp b/example/client/jsonrpc/jsonrpc/client.cpp index a828b299..29cc3651 100644 --- a/example/client/jsonrpc/jsonrpc/client.cpp +++ b/example/client/jsonrpc/jsonrpc/client.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -250,33 +251,6 @@ class json_sink : public http::sink } }; -class json_source : public http::source -{ - json::serializer& jsr_; - json::value v_; - -public: - json_source(json::serializer& jsr, json::value v) - : jsr_(jsr) - , v_(std::move(v)) - { - jsr_.reset(&v_); - } - -private: - results - on_read( - capy::mutable_buffer b) override - { - results ret; - ret.bytes = jsr_.read( - static_cast(b.data()), - b.size()).size(); - ret.finished = jsr_.done(); - return ret; - } -}; - } //namespace // ---------------------------------------------- @@ -286,6 +260,7 @@ class client::request_op { client& client_; std::uint64_t id_; + std::string body_; public: request_op(client& c) noexcept @@ -310,9 +285,10 @@ class client::request_op { "id", id_ } }, sp); - client_.req_.set_chunked(true); - client_.sr_.start( - client_.req_, client_.jsr_, std::move(value)); + body_ = json::serialize(value); + client_.req_.set_content_length(body_.size()); + client_.sr_.start( + client_.req_, http::string_body(std::move(body_))); beast2::async_write( *client_.stream_, client_.sr_, std::move(self)); From e2e576d28cd12e5737d62945be2e00074e086111 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 23 Jan 2026 19:46:06 -0800 Subject: [PATCH 13/17] buffer_copy is the name --- include/boost/beast2/server/route_handler_corosio.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/boost/beast2/server/route_handler_corosio.hpp b/include/boost/beast2/server/route_handler_corosio.hpp index 714788c3..8346ecc6 100644 --- a/include/boost/beast2/server/route_handler_corosio.hpp +++ b/include/boost/beast2/server/route_handler_corosio.hpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include namespace boost { @@ -90,7 +90,7 @@ class corosio_route_params break; // Copy data to serializer stream - std::size_t bytes = capy::copy(srs_.prepare(), bufs); + std::size_t bytes = capy::buffer_copy(srs_.prepare(), bufs); srs_.commit(bytes); buffers.consume(bytes); From c73f392c17610ce7a39b6abe7e05c9d66bdde623 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 23 Jan 2026 23:04:35 -0800 Subject: [PATCH 14/17] use buffer_empty --- include/boost/beast2/server/route_handler_corosio.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/beast2/server/route_handler_corosio.hpp b/include/boost/beast2/server/route_handler_corosio.hpp index 8346ecc6..6b6c21f2 100644 --- a/include/boost/beast2/server/route_handler_corosio.hpp +++ b/include/boost/beast2/server/route_handler_corosio.hpp @@ -59,7 +59,7 @@ class corosio_route_params co_return cbs.error(); } - if(capy::buffer_size(*cbs) == 0) + if(capy::buffer_empty(*cbs)) { serializer.consume(0); continue; From 6195d0c4938658f10c3c90c86886b3c4f7f63c07 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sat, 24 Jan 2026 07:41:26 -0800 Subject: [PATCH 15/17] Use system context --- example/client/burl/connect.cpp | 8 +++----- example/client/burl/connect.hpp | 2 -- example/client/burl/main.cpp | 17 +++++++---------- example/client/get/main.cpp | 20 ++++++-------------- example/client/jsonrpc/jsonrpc/client.cpp | 12 ++++-------- example/client/jsonrpc/jsonrpc/client.hpp | 3 --- example/client/jsonrpc/main.cpp | 19 ++++++------------- example/client/visit/main.cpp | 10 +++------- example/server/main.cpp | 17 ++++++++--------- example/server/serve_log_admin.cpp | 17 +++++++---------- example/server/serve_log_admin.hpp | 4 +--- include/boost/beast2/log_service.hpp | 11 +++++------ src/log_service.cpp | 14 +++++++++++--- 13 files changed, 61 insertions(+), 93 deletions(-) diff --git a/example/client/burl/connect.cpp b/example/client/burl/connect.cpp index 8913d492..836bb7c2 100644 --- a/example/client/burl/connect.cpp +++ b/example/client/burl/connect.cpp @@ -153,7 +153,6 @@ connect_socks5_proxy( asio::awaitable connect_http_proxy( const operation_config& oc, - http::polystore& capy_ctx, asio::ip::tcp::socket& stream, const urls::url_view& url, const urls::url_view& proxy) @@ -190,8 +189,8 @@ connect_http_proxy( request.set(field::proxy_authorization, basic_auth); } - auto serializer = http::serializer{ capy_ctx }; - auto parser = http::response_parser{ capy_ctx }; + auto serializer = http::serializer{}; + auto parser = http::response_parser{}; serializer.start(request); co_await beast2::async_write(stream, serializer); @@ -225,7 +224,6 @@ asio::awaitable connect( const operation_config& oc, ssl::context& ssl_ctx, - http::polystore& capy_ctx, any_stream& stream, urls::url url) { @@ -261,7 +259,7 @@ connect( { if(oc.proxy.scheme() == "http") { - co_await connect_http_proxy(oc, capy_ctx, socket, url, oc.proxy); + co_await connect_http_proxy(oc, socket, url, oc.proxy); } else if(oc.proxy.scheme() == "socks5") { diff --git a/example/client/burl/connect.hpp b/example/client/burl/connect.hpp index eddcea76..f51416ea 100644 --- a/example/client/burl/connect.hpp +++ b/example/client/burl/connect.hpp @@ -15,7 +15,6 @@ #include #include -#include #include namespace asio = boost::asio; @@ -27,7 +26,6 @@ asio::awaitable connect( const operation_config& oc, ssl::context& ssl_ctx, - http::polystore& capy_ctx, any_stream& stream, urls::url url); diff --git a/example/client/burl/main.cpp b/example/client/burl/main.cpp index 6f624fa8..98cffcce 100644 --- a/example/client/burl/main.cpp +++ b/example/client/burl/main.cpp @@ -345,15 +345,14 @@ perform_request( boost::optional& cookie_jar, core::string_view exp_cookies, ssl::context& ssl_ctx, - http::polystore& capy_ctx, message msg, request_opt request_opt) { using field = http::field; auto executor = co_await asio::this_coro::executor; auto stream = any_stream{ asio::ip::tcp::socket{ executor } }; - auto parser = http::response_parser{ capy_ctx }; - auto serializer = http::serializer{ capy_ctx }; + auto parser = http::response_parser{}; + auto serializer = http::serializer{}; urls::url url = [&]() { @@ -446,7 +445,7 @@ perform_request( co_await asio::co_spawn( executor, - connect(oc, ssl_ctx, capy_ctx, stream, url), + connect(oc, ssl_ctx, stream, url), asio::cancel_after(oc.connect_timeout)); if(oc.recvpersecond) @@ -740,7 +739,6 @@ co_main(int argc, char* argv[]) auto executor = co_await asio::this_coro::executor; auto task_group = ::task_group{ executor, oc.parallel_max }; - auto capy_ctx = http::polystore{}; auto cookie_jar = boost::optional<::cookie_jar>{}; auto header_output = boost::optional{}; auto exp_cookies = std::string{}; @@ -759,22 +757,22 @@ co_main(int argc, char* argv[]) if constexpr(capy_has_brotli) { cfg.apply_brotli_decoder = true; - http::brotli::install_decode_service(capy_ctx); + http::brotli::install_decode_service(); } if constexpr(capy_has_zlib) { cfg.apply_deflate_decoder = true; cfg.apply_gzip_decoder = true; - http::zlib::install_inflate_service(capy_ctx); + http::zlib::install_inflate_service(); } - http::install_parser_service(capy_ctx, cfg); + http::install_parser_service(cfg); } // serializer service { http::serializer::config cfg; cfg.payload_buffer = 1024 * 1024; - http::install_serializer_service(capy_ctx, cfg); + http::install_serializer_service(cfg); } if(!oc.headerfile.empty()) @@ -812,7 +810,6 @@ co_main(int argc, char* argv[]) cookie_jar, exp_cookies, ssl_ctx, - capy_ctx, oc.msg, ropt.value()); }; diff --git a/example/client/get/main.cpp b/example/client/get/main.cpp index 849c1113..59d4e93a 100644 --- a/example/client/get/main.cpp +++ b/example/client/get/main.cpp @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -49,13 +48,10 @@ class session public: session( asio::io_context& ioc, - asio::ssl::context& ssl_ctx, - http::polystore& capy_ctx) + asio::ssl::context& ssl_ctx) : ssl_ctx_(ssl_ctx) , stream_(ioc, ssl_ctx) , resolver_(ioc) - , sr_(capy_ctx) - , pr_(capy_ctx) { } @@ -440,10 +436,6 @@ main(int argc, char* argv[]) // The SSL context is required, and holds certificates asio::ssl::context ssl_ctx(asio::ssl::context::tls_client); - // holds optional deflate and - // required configuration services - http::polystore capy_ctx; - // Install parser service { http::response_parser::config cfg; @@ -451,18 +443,18 @@ main(int argc, char* argv[]) cfg.min_buffer = 64 * 1024; #ifdef BOOST_HTTP_HAS_BROTLI cfg.apply_brotli_decoder = true; - http::brotli::install_decode_service(capy_ctx); + http::brotli::install_decode_service(); #endif #ifdef BOOST_HTTP_HAS_ZLIB cfg.apply_deflate_decoder = true; cfg.apply_gzip_decoder = true; - http::zlib::install_inflate_service(capy_ctx); + http::zlib::install_inflate_service(); #endif - http::install_parser_service(capy_ctx, cfg); + http::install_parser_service(cfg); } // Install serializer service with default configuration - http::install_serializer_service(capy_ctx, {}); + http::install_serializer_service({}); // Root certificates used for verification ssl_ctx.set_default_verify_paths(); @@ -476,7 +468,7 @@ main(int argc, char* argv[]) if(!url.has_authority()) goto help; - session s(ioc, ssl_ctx, capy_ctx); + session s(ioc, ssl_ctx); s.run(url); ioc.run(); diff --git a/example/client/jsonrpc/jsonrpc/client.cpp b/example/client/jsonrpc/jsonrpc/client.cpp index 29cc3651..c4f5529c 100644 --- a/example/client/jsonrpc/jsonrpc/client.cpp +++ b/example/client/jsonrpc/jsonrpc/client.cpp @@ -18,11 +18,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include @@ -361,12 +361,10 @@ class client::request_op client:: client( urls::url endpoint, - http::polystore& capy_ctx, asio::any_io_executor exec, asio::ssl::context& ssl_ctx) : client( std::move(endpoint), - capy_ctx, std::unique_ptr(new stream_impl(exec, ssl_ctx))) { } @@ -374,12 +372,9 @@ client( client:: client( urls::url endpoint, - http::polystore& capy_ctx, std::unique_ptr stream) : stream_(std::move(stream)) , endpoint_(std::move(endpoint)) - , sr_(capy_ctx) - , pr_(capy_ctx) , req_(http::method::post, "/") { using field = http::field; @@ -392,10 +387,11 @@ client( req_.append(field::accept, "application/json"); req_.append(field::user_agent, "Boost.Http.Io"); - if(capy_ctx.find() != nullptr) + auto& ctx = capy::get_system_context(); + if(ctx.find_service() != nullptr) req_.append(field::accept_encoding, "br"); - if(capy_ctx.find() != nullptr) + if(ctx.find_service() != nullptr) req_.append(field::accept_encoding, "deflate, gzip"); pr_.reset(); diff --git a/example/client/jsonrpc/jsonrpc/client.hpp b/example/client/jsonrpc/jsonrpc/client.hpp index 38f6bf24..7bd9a322 100644 --- a/example/client/jsonrpc/jsonrpc/client.hpp +++ b/example/client/jsonrpc/jsonrpc/client.hpp @@ -25,7 +25,6 @@ #include #include #include -#include #include namespace jsonrpc { @@ -56,7 +55,6 @@ class client */ client( boost::urls::url endpoint, - boost::http::polystore& capy_ctx, boost::asio::any_io_executor exec, boost::asio::ssl::context& ssl_ctx); @@ -67,7 +65,6 @@ class client */ client( boost::urls::url endpoint, - boost::http::polystore& capy_ctx, std::unique_ptr stream); /// Get the executor associated with the object. diff --git a/example/client/jsonrpc/main.cpp b/example/client/jsonrpc/main.cpp index 33b11833..b2d406f7 100644 --- a/example/client/jsonrpc/main.cpp +++ b/example/client/jsonrpc/main.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include @@ -26,8 +25,7 @@ using namespace boost; asio::awaitable co_main( - asio::ssl::context& ssl_ctx, - http::polystore& capy_ctx) + asio::ssl::context& ssl_ctx) { using dec_float = multiprecision::cpp_dec_float_50; const auto to_int = [](std::string_view s) @@ -37,7 +35,6 @@ co_main( jsonrpc::client client( urls::url("https://ethereum.publicnode.com"), - capy_ctx, co_await asio::this_coro::executor, ssl_ctx); @@ -145,28 +142,24 @@ main(int, char*[]) // The SSL context is required, and holds certificates asio::ssl::context ssl_ctx(asio::ssl::context::tls_client); - // CAPY context holds optional deflate and - // required configuration services - http::polystore capy_ctx; - // Install parser service { http::response_parser::config cfg; cfg.min_buffer = 64 * 1024; #ifdef BOOST_HTTP_HAS_BROTLI cfg.apply_brotli_decoder = true; - http::brotli::install_decode_service(capy_ctx); + http::brotli::install_decode_service(); #endif #ifdef BOOST_HTTP_HAS_ZLIB cfg.apply_deflate_decoder = true; cfg.apply_gzip_decoder = true; - http::zlib::install_inflate_service(capy_ctx); + http::zlib::install_inflate_service(); #endif - http::install_parser_service(capy_ctx, cfg); + http::install_parser_service(cfg); } // Install serializer service with default configuration - http::install_serializer_service(capy_ctx, {}); + http::install_serializer_service({}); // Root certificates used for verification ssl_ctx.set_default_verify_paths(); @@ -176,7 +169,7 @@ main(int, char*[]) asio::co_spawn( ioc, - co_main(ssl_ctx, capy_ctx), + co_main(ssl_ctx), [](auto eptr) { if(eptr) diff --git a/example/client/visit/main.cpp b/example/client/visit/main.cpp index 7810f707..ee2ae215 100644 --- a/example/client/visit/main.cpp +++ b/example/client/visit/main.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include //------------------------------------------------ @@ -201,10 +200,8 @@ struct worker explicit worker( - executor_type ex, - boost::http::polystore& ctx) + executor_type ex) : sock(ex) - , pr(ctx) { sock.open(boost::asio::ip::tcp::v4()); } @@ -276,13 +273,12 @@ main(int argc, char* argv[]) (void)argc; (void)argv; - boost::http::polystore ctx; boost::http::parser::config_base cfg; - boost::http::install_parser_service(ctx, cfg); + boost::http::install_parser_service(cfg); boost::asio::io_context ioc; - worker w(ioc.get_executor(), ctx); + worker w(ioc.get_executor()); w.do_next(); diff --git a/example/server/main.cpp b/example/server/main.cpp index 02fde4f3..071c26c5 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -34,22 +34,21 @@ namespace beast2 { capy::thread_pool g_tp; -void install_services(http::application& app) +void install_services() { #ifdef BOOST_HTTP_HAS_BROTLI - http::brotli::install_decode_service(app); - http::brotli::install_encode_service(app); + http::brotli::install_decode_service(); + http::brotli::install_encode_service(); #endif #ifdef BOOST_HTTP_HAS_ZLIB - http::zlib::install_deflate_service(app); - http::zlib::install_inflate_service(app); + http::zlib::install_deflate_service(); + http::zlib::install_inflate_service(); #endif - // VFALCO These ugly incantations are needed for http and will hopefully go away soon. - http::install_parser_service(app, + http::install_parser_service( http::request_parser::config()); - http::install_serializer_service(app, + http::install_serializer_service( http::serializer::config()); } @@ -161,7 +160,7 @@ int server_main( int argc, char* argv[] ) http::application app; corosio::io_context ioc; - install_services(app); + install_services(); auto addr = urls::parse_ipv4_address(argv[1]); if(addr.has_error()) diff --git a/example/server/serve_log_admin.cpp b/example/server/serve_log_admin.cpp index 5aba7bea..9980c008 100644 --- a/example/server/serve_log_admin.cpp +++ b/example/server/serve_log_admin.cpp @@ -22,9 +22,8 @@ namespace { class serve_log_page { public: - serve_log_page( - http::polystore& ps) - : ls_(use_log_service(ps)) + serve_log_page() + : ls_(use_log_service()) { } @@ -91,9 +90,8 @@ class serve_log_page class handle_submit { public: - handle_submit( - http::polystore& ps) - : ls_(use_log_service(ps)) + handle_submit() + : ls_(use_log_service()) { } @@ -120,12 +118,11 @@ class handle_submit #if 0 router -serve_log_admin( - http::polystore& ps) +serve_log_admin() { router r; - r.add(http::method::get, "/", serve_log_page(ps)); - r.add(http::method::get, "/submit", handle_submit(ps)); + r.add(http::method::get, "/", serve_log_page()); + r.add(http::method::get, "/submit", handle_submit()); return r; } #endif diff --git a/example/server/serve_log_admin.hpp b/example/server/serve_log_admin.hpp index ccf9356f..cdad56e4 100644 --- a/example/server/serve_log_admin.hpp +++ b/example/server/serve_log_admin.hpp @@ -12,14 +12,12 @@ #include #include -#include namespace boost { namespace beast2 { router -serve_log_admin( - http::polystore& ps); +serve_log_admin(); } // beast2 } // boost diff --git a/include/boost/beast2/log_service.hpp b/include/boost/beast2/log_service.hpp index efae4c41..f847c421 100644 --- a/include/boost/beast2/log_service.hpp +++ b/include/boost/beast2/log_service.hpp @@ -12,7 +12,6 @@ #include #include -#include #include #include @@ -38,16 +37,16 @@ class BOOST_SYMBOL_VISIBLE std::vector
= 0; }; -/** Return the log service attached to the polystore - If the polystore does not already contain the +/** Return the log service from the system context + + If the system context does not already contain the service, it is created. - @param ps The polystore. + @return The log service. */ BOOST_BEAST2_DECL log_service& -use_log_service( - http::polystore& ps); +use_log_service(); } // beast2 } // boost diff --git a/src/log_service.cpp b/src/log_service.cpp index a497eba1..c8260da5 100644 --- a/src/log_service.cpp +++ b/src/log_service.cpp @@ -9,6 +9,7 @@ #include #include +#include namespace boost { namespace beast2 { @@ -17,10 +18,16 @@ namespace { class log_service_impl : public log_service + , public capy::execution_context::service { public: using key_type = log_service; + explicit + log_service_impl(capy::execution_context&) noexcept + { + } + section get_section( core::string_view name) override @@ -35,6 +42,8 @@ class log_service_impl return ls_.get_sections(); } + void shutdown() override {} + private: log_sections ls_; }; @@ -42,10 +51,9 @@ class log_service_impl } // (anon) log_service& -use_log_service( - http::polystore& ps) +use_log_service() { - return ps.try_emplace(); + return capy::get_system_context().use_service(); } } // beast2 From d48d3a101554bc26b5e522cd67d7e5346489c4da Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sat, 24 Jan 2026 09:22:16 -0800 Subject: [PATCH 16/17] config is shared --- example/server/main.cpp | 21 +++++++++++---------- include/boost/beast2/http_server.hpp | 6 +++++- src/http_server.cpp | 18 +++++++++--------- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/example/server/main.cpp b/example/server/main.cpp index 071c26c5..1d09d0a4 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -37,19 +37,12 @@ capy::thread_pool g_tp; void install_services() { #ifdef BOOST_HTTP_HAS_BROTLI - http::brotli::install_decode_service(); - http::brotli::install_encode_service(); + http::brotli::install_brotli_service(); #endif #ifdef BOOST_HTTP_HAS_ZLIB - http::zlib::install_deflate_service(); - http::zlib::install_inflate_service(); + http::zlib::install_zlib_service(); #endif - - http::install_parser_service( - http::request_parser::config()); - http::install_serializer_service( - http::serializer::config()); } #if 0 @@ -181,7 +174,15 @@ int server_main( int argc, char* argv[] ) co_return co_await rp.send( "Hello, coworld!" ); }); #endif - http_server hsrv(ioc, std::atoi(argv[4]), std::move(rr)); + auto parser_cfg = http::make_parser_config(http::parser_config(true)); + auto serializer_cfg = http::make_serializer_config(http::serializer_config()); + + http_server hsrv( + ioc, + std::atoi(argv[4]), + std::move(rr), + std::move(parser_cfg), + std::move(serializer_cfg)); auto ec = hsrv.bind(ep); if(ec) { diff --git a/include/boost/beast2/http_server.hpp b/include/boost/beast2/http_server.hpp index 303f347f..b338efcd 100644 --- a/include/boost/beast2/http_server.hpp +++ b/include/boost/beast2/http_server.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace boost { @@ -31,7 +32,10 @@ class BOOST_BEAST2_DECL http_server( corosio::io_context& ctx, std::size_t num_workers, - http::flat_router router); + http::flat_router router, + http::shared_parser_config parser_cfg, + http::shared_serializer_config serializer_cfg); + private: struct worker; diff --git a/src/http_server.cpp b/src/http_server.cpp index 5cc4073b..8cc0dfe6 100644 --- a/src/http_server.cpp +++ b/src/http_server.cpp @@ -30,16 +30,12 @@ struct http_server::impl { http::flat_router router; http::application app; + http::shared_parser_config parser_cfg; + http::shared_serializer_config serializer_cfg; impl(http::flat_router r) : router(std::move(r)) { - // VFALCO These ugly incantations are needed - // for http and will hopefully go away soon. - http::install_parser_service(app, - http::request_parser::config()); - http::install_serializer_service(app, - http::serializer::config()); } }; @@ -62,8 +58,8 @@ struct http_server:: , rp(sock) { sock.open(); - rp.parser = http::request_parser(srv->impl_->app); - rp.serializer = http::serializer(srv->impl_->app); + rp.parser = http::request_parser(srv->impl_->parser_cfg); + rp.serializer = http::serializer(srv->impl_->serializer_cfg); } corosio::socket& socket() override @@ -151,10 +147,14 @@ http_server:: http_server( corosio::io_context& ctx, std::size_t num_workers, - http::flat_router router) + http::flat_router router, + http::shared_parser_config parser_cfg, + http::shared_serializer_config serializer_cfg) : tcp_server(ctx, ctx.get_executor()) , impl_(new impl(std::move(router))) { + impl_->parser_cfg = std::move(parser_cfg); + impl_->serializer_cfg = std::move(serializer_cfg); wv_.reserve(num_workers); for(std::size_t i = 0; i < num_workers; ++i) wv_.emplace(ctx, this); From 44db682ecb3aa50a12acd3c55f23dd45c70a7051 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sat, 24 Jan 2026 09:55:26 -0800 Subject: [PATCH 17/17] Refactor router types and includes --- doc/tagfiles/boost-http-doxygen.tag.xml | 6 +-- example/server/main.cpp | 2 +- include/boost/beast2/http_server.hpp | 40 ++++++++++++++++++- include/boost/beast2/server/http_stream.hpp | 2 +- .../beast2/server/route_handler_corosio.hpp | 2 +- include/boost/beast2/server/router.hpp | 4 +- .../boost/beast2/server/router_corosio.hpp | 4 +- src/http_server.cpp | 4 +- 8 files changed, 52 insertions(+), 12 deletions(-) diff --git a/doc/tagfiles/boost-http-doxygen.tag.xml b/doc/tagfiles/boost-http-doxygen.tag.xml index 48273eab..b0d10ebc 100644 --- a/doc/tagfiles/boost-http-doxygen.tag.xml +++ b/doc/tagfiles/boost-http-doxygen.tag.xml @@ -7,7 +7,7 @@ boost::http::is_sink boost::http::is_source boost::http::route_result - boost::http::router + boost::http::basic_router boost::http::acceptor_config boost::http::any_router boost::http::basic_router @@ -149,8 +149,8 @@ boost/http/route_result.adoc - boost::http::router - boost/http/router.adoc + boost::http::basic_router + boost/http/basic_router.adoc boost::http::acceptor_config diff --git a/example/server/main.cpp b/example/server/main.cpp index 1d09d0a4..22fb912a 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -164,7 +164,7 @@ int server_main( int argc, char* argv[] ) auto port = static_cast(std::atoi(argv[2])); corosio::endpoint ep(addr.value(), port); - http::router rr; + http::basic_router rr; rr.use( "/", http::serve_static( argv[3] ) ); #if 0 diff --git a/include/boost/beast2/http_server.hpp b/include/boost/beast2/http_server.hpp index b338efcd..450f0d28 100644 --- a/include/boost/beast2/http_server.hpp +++ b/include/boost/beast2/http_server.hpp @@ -20,6 +20,34 @@ namespace boost { namespace http { class flat_router; } namespace beast2 { +/** An HTTP server for handling requests with coroutine-based I/O. + + This class provides a complete HTTP server implementation that + accepts connections, parses HTTP requests, and dispatches them + through a router. Each connection is handled by a worker that + processes requests using coroutines. + + @par Thread Safety + Distinct objects: Safe. + Shared objects: Unsafe. + + @par Example + @code + corosio::io_context ctx; + http::flat_router router; + router.add( http::verb::get, "/", my_handler ); + + http_server srv( + ctx, + 4, // workers + std::move( router ), + http::shared_parser_config::make(), + http::shared_serializer_config::make() ); + + srv.listen( "0.0.0.0", 8080 ); + ctx.run(); + @endcode +*/ class BOOST_BEAST2_DECL http_server : public corosio::tcp_server { @@ -27,8 +55,19 @@ class BOOST_BEAST2_DECL impl* impl_; public: + /// Destroy the server. ~http_server(); + /** Construct an HTTP server. + + @param ctx The I/O context for asynchronous operations. + @param num_workers Number of worker objects for handling + connections concurrently. + @param router The router for dispatching requests to handlers. + @param parser_cfg Shared configuration for request parsers. + @param serializer_cfg Shared configuration for response + serializers. + */ http_server( corosio::io_context& ctx, std::size_t num_workers, @@ -36,7 +75,6 @@ class BOOST_BEAST2_DECL http::shared_parser_config parser_cfg, http::shared_serializer_config serializer_cfg); - private: struct worker; diff --git a/include/boost/beast2/server/http_stream.hpp b/include/boost/beast2/server/http_stream.hpp index 211203f4..1b1a2c70 100644 --- a/include/boost/beast2/server/http_stream.hpp +++ b/include/boost/beast2/server/http_stream.hpp @@ -29,7 +29,7 @@ #include #include #include -#include +#include #include #include diff --git a/include/boost/beast2/server/route_handler_corosio.hpp b/include/boost/beast2/server/route_handler_corosio.hpp index 6b6c21f2..8e6e84c4 100644 --- a/include/boost/beast2/server/route_handler_corosio.hpp +++ b/include/boost/beast2/server/route_handler_corosio.hpp @@ -11,7 +11,7 @@ #define BOOST_BEAST2_SERVER_ROUTE_HANDLER_COROSIO_HPP #include -#include +#include #include #include #include diff --git a/include/boost/beast2/server/router.hpp b/include/boost/beast2/server/router.hpp index ed94d146..91caaaa2 100644 --- a/include/boost/beast2/server/router.hpp +++ b/include/boost/beast2/server/router.hpp @@ -11,15 +11,15 @@ #define BOOST_BEAST2_SERVER_ROUTER_HPP #include +#include #include -#include namespace boost { namespace beast2 { /** The sans-IO router type */ -using router = http::router; +using router = http::basic_router; } // beast2 } // boost diff --git a/include/boost/beast2/server/router_corosio.hpp b/include/boost/beast2/server/router_corosio.hpp index d091128d..c0d89e4a 100644 --- a/include/boost/beast2/server/router_corosio.hpp +++ b/include/boost/beast2/server/router_corosio.hpp @@ -11,7 +11,7 @@ #define BOOST_BEAST2_SERVER_ROUTER_COROSIO_HPP #include -#include +#include #include namespace boost { @@ -19,7 +19,7 @@ namespace beast2 { /** The Corosio-aware router type */ -using router_corosio = http::router; +using router_corosio = http::basic_router; } // beast2 } // boost diff --git a/src/http_server.cpp b/src/http_server.cpp index 8cc0dfe6..7835e5ea 100644 --- a/src/http_server.cpp +++ b/src/http_server.cpp @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include #include @@ -39,6 +39,8 @@ struct http_server::impl } }; +// Each worker owns its own socket and parser/serializer state, +// allowing concurrent connection handling without synchronization. struct http_server:: worker : tcp_server::worker_base {