From f7ed2edb679027b2662baf84bb8f803686797532 Mon Sep 17 00:00:00 2001 From: Amlal El Mahrouss Date: Sun, 30 Nov 2025 00:15:40 -0500 Subject: [PATCH 01/40] chore: improved CLAUDE.md and documentation for basic_router. Signed-off-by: Amlal El Mahrouss --- CLAUDE.md | 5 +++++ include/boost/beast2/server/basic_router.hpp | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 85909ef4..b961be36 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -57,3 +57,8 @@ T default_value(); - Concise, dry answers - Full files, not diffs - Accurate, compiling C++ code + +## Preconditions and Postconditions + +- The caller of any function returning a route_result must immediately return the route_result and cannot perform other actions +- route handlers which return a route_result must never return a system::error_code which would return false from failed() \ No newline at end of file diff --git a/include/boost/beast2/server/basic_router.hpp b/include/boost/beast2/server/basic_router.hpp index c58b56a3..11b670cd 100644 --- a/include/boost/beast2/server/basic_router.hpp +++ b/include/boost/beast2/server/basic_router.hpp @@ -214,7 +214,7 @@ class any_router { res.status(http_proto::status::ok); res.set_body("Hello, world!"); - return system::error_code{}; + return route::send; }); @endcode From 925b95d1029b5fcffabaa3daeead5e2510d6b665 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 30 Nov 2025 08:50:52 -0800 Subject: [PATCH 02/40] fix: any_router is public for now --- include/boost/beast2/server/basic_router.hpp | 8 ++++---- include/boost/beast2/server/router_types.hpp | 4 ++-- src/server/basic_router.cpp | 10 ++++++---- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/include/boost/beast2/server/basic_router.hpp b/include/boost/beast2/server/basic_router.hpp index 11b670cd..b5c7fc8a 100644 --- a/include/boost/beast2/server/basic_router.hpp +++ b/include/boost/beast2/server/basic_router.hpp @@ -128,8 +128,8 @@ struct router_options }; //----------------------------------------------- - -namespace detail { + +//namespace detail { class any_router; @@ -189,7 +189,7 @@ class any_router impl* impl_ = nullptr; }; -} // detail +//} // detail //----------------------------------------------- @@ -292,7 +292,7 @@ class any_router @tparam Res The type of response object. */ template -class basic_router : public detail::any_router +class basic_router : public /*detail::*/any_router { // Req must be publicly derived from basic_request BOOST_CORE_STATIC_ASSERT( diff --git a/include/boost/beast2/server/router_types.hpp b/include/boost/beast2/server/router_types.hpp index 3ee8df91..c146e18b 100644 --- a/include/boost/beast2/server/router_types.hpp +++ b/include/boost/beast2/server/router_types.hpp @@ -290,7 +290,7 @@ class basic_request core::string_view path; private: - friend class detail::any_router; + friend class /*detail::*/any_router; struct match_result; http_proto::method verb_ = http_proto::method::unknown; @@ -311,7 +311,7 @@ class basic_request class basic_response { private: - friend class detail::any_router; + friend class /*detail::*/any_router; template friend class basic_router; diff --git a/src/server/basic_router.cpp b/src/server/basic_router.cpp index ab515e9c..7406243b 100644 --- a/src/server/basic_router.cpp +++ b/src/server/basic_router.cpp @@ -22,7 +22,7 @@ namespace boost { namespace beast2 { -namespace detail { +//namespace detail { // VFALCO Temporarily here until Boost.URL merges the fix static @@ -80,6 +80,7 @@ pattern target path(use) path(get) //------------------------------------------------ + any_router::any_handler::~any_handler() = default; //------------------------------------------------ @@ -201,7 +202,7 @@ pct_decode_path( //------------------------------------------------ -} // detail +//} // detail struct basic_request:: match_result @@ -255,7 +256,7 @@ struct basic_request:: //------------------------------------------------ -namespace detail { +//namespace detail { // Matches a path against a pattern struct any_router::matcher @@ -867,6 +868,7 @@ do_dispatch( return res.ec_; } -} // detail +//} // detail + } // beast2 } // boost From 33a834448e63f1acf7aee49111220e22c20758ca Mon Sep 17 00:00:00 2001 From: Mohammad Nejati Date: Mon, 1 Dec 2025 11:02:17 +0000 Subject: [PATCH 03/40] chore: fix MinGW builds --- src/server/basic_router.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/server/basic_router.cpp b/src/server/basic_router.cpp index 7406243b..365c1519 100644 --- a/src/server/basic_router.cpp +++ b/src/server/basic_router.cpp @@ -80,8 +80,7 @@ pattern target path(use) path(get) //------------------------------------------------ - -any_router::any_handler::~any_handler() = default; +any_router::any_handler::~any_handler() {} //------------------------------------------------ From 433e6105c3b0183ee4e7a0468d4bc0eb1077d5e6 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 30 Nov 2025 17:31:36 -0800 Subject: [PATCH 04/40] feat: body_source --- include/boost/beast2/server/body_source.hpp | 432 ++++++++++++++++++++ src/server/body_source.cpp | 41 ++ test/unit/server/body_source.cpp | 264 ++++++++++++ 3 files changed, 737 insertions(+) create mode 100644 include/boost/beast2/server/body_source.hpp create mode 100644 src/server/body_source.cpp create mode 100644 test/unit/server/body_source.cpp diff --git a/include/boost/beast2/server/body_source.hpp b/include/boost/beast2/server/body_source.hpp new file mode 100644 index 00000000..2d485325 --- /dev/null +++ b/include/boost/beast2/server/body_source.hpp @@ -0,0 +1,432 @@ +// +// 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 && + buffers::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 && + buffers::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 && + buffers::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_proto::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 && + buffers::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( + buffers::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 && + buffers::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( + buffers::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 && + buffers::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 = buffers::end(data); + auto p = reinterpret_cast< + buffers::const_buffer*>(this+1); + std::size_t length = 0; + for(auto it = buffers::begin(data); it != end; ++it) + { + boost::buffers::const_buffer cb(*it); + size_ += cb.size(); + *p++ = cb; + ++length; + } + bs_ = { reinterpret_cast< + buffers::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 = buffers::copy( + buffers::mutable_buffer(dest, n0), + buffers::sans_prefix(bs_, nread_)); + nread_ += n; + if(nread_ >= size_) + ec = http_proto::error::end_of_stream; + else + ec = {}; + return n; + } + }; + + std::size_t length = 0; + auto const& data = body.data(); + auto const& end = buffers::end(data); + for(auto it = buffers::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(buffers::const_buffer)); + impl_ = ::new(p) model( + std::forward(body)); +} + +} // beast2 +} // boost + +#endif diff --git a/src/server/body_source.cpp b/src/server/body_source.cpp new file mode 100644 index 00000000..cfd9449c --- /dev/null +++ b/src/server/body_source.cpp @@ -0,0 +1,41 @@ +// +// 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 new file mode 100644 index 00000000..f287195c --- /dev/null +++ b/test/unit/server/body_source.cpp @@ -0,0 +1,264 @@ +// +// 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) + { + } + + buffers::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 = buffers::copy( + dest, + buffers::const_buffer( + s_.data() + nread_, + s_.size() - nread_)); + nread_ += n; + if(nread_ >= s_.size()) + { + if(ec_.failed()) + ec = ec_; + else + ec = http_proto::error::end_of_stream; + } + else + { + ec = {}; + } + return n; + } +}; + +} // (anon) + +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); + +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_proto::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(buffers::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(buffers::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(buffers::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(buffers::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(buffers::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_proto::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 1a62e26d099c650b76bf6a856404a07ac1a7aa26 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 30 Nov 2025 17:33:11 -0800 Subject: [PATCH 05/40] feat: post and http_stream refactor --- example/post-test.html | 59 ++++ example/server/main.cpp | 11 +- example/server/post_work.cpp | 73 +++++ example/server/post_work.hpp | 30 ++ include/boost/beast2/server/basic_router.hpp | 7 +- include/boost/beast2/server/http_stream.hpp | 288 +++++++++++------- include/boost/beast2/server/route_handler.hpp | 186 +++++++++-- .../beast2/server/route_handler_asio.hpp | 24 +- include/boost/beast2/server/router_types.hpp | 58 ++-- src/server/basic_router.cpp | 37 +-- src/server/route_handler.cpp | 17 +- src/server/router_types.cpp | 8 + test/unit/server/body_source.cpp | 2 + test/unit/server/route_handler_asio.cpp | 6 + test/unit/server/router_asio.cpp | 6 + 15 files changed, 623 insertions(+), 189 deletions(-) create mode 100644 example/post-test.html create mode 100644 example/server/post_work.cpp create mode 100644 example/server/post_work.hpp diff --git a/example/post-test.html b/example/post-test.html new file mode 100644 index 00000000..13c86ee8 --- /dev/null +++ b/example/post-test.html @@ -0,0 +1,59 @@ + + + + POST Test + + + + + +

+
+    
+
+
\ No newline at end of file
diff --git a/example/server/main.cpp b/example/server/main.cpp
index af0b1543..ceb38bf6 100644
--- a/example/server/main.cpp
+++ b/example/server/main.cpp
@@ -8,6 +8,7 @@
 //
 
 #include "certificate.hpp"
+#include "post_work.hpp"
 #include "serve_detached.hpp"
 #include "serve_log_admin.hpp"
 #include 
@@ -69,7 +70,15 @@ int server_main( int argc, char* argv[] )
 
         //srv.wwwroot.use("/log", serve_log_admin(app));
         //srv.wwwroot.use("/alt", serve_static( argv[3] ));
-        srv.wwwroot.use("/detach", serve_detached());
+        //srv.wwwroot.use("/detach", serve_detached());
+        srv.wwwroot.use(post_work());
+        srv.wwwroot.use(
+            []( Request& req,
+                Response& res) ->
+                    route_result
+            {
+                return route::next;
+            });
         srv.wwwroot.use("/", serve_static( argv[3] ));
 
         app.start();
diff --git a/example/server/post_work.cpp b/example/server/post_work.cpp
new file mode 100644
index 00000000..dc5c7d2d
--- /dev/null
+++ b/example/server/post_work.cpp
@@ -0,0 +1,73 @@
+//
+// Copyright (c) 2022 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 "post_work.hpp"
+
+namespace boost {
+namespace beast2 {
+
+/*
+struct etag
+{
+    Request& req;
+    Response& res;
+    sha1_state digest;
+
+    void operator()( resumer resume )
+    {
+        char buf[8192];
+        system::error_code ec;
+        auto nread = res.body.read(
+            buffers::make_buffer(buf), ec);
+        digest.update( buf, nread );
+        if(ec == error::eof)
+        {
+            res.body.rewind();
+            res.set_header(
+                http::field::etag,
+                to_hex(digest.finalize()) );
+            return resume( route::next );
+        }
+        if( ec.failed() )
+            return resume( ec );
+        // we will get called again
+    }
+};
+*/
+
+namespace {
+
+struct task
+{
+    std::size_t i = 10;
+
+    void
+    operator()(resumer resume)
+    {
+        if(i--)
+            return;
+        resume(route::next);
+    }
+};
+
+} // (anon)
+
+//------------------------------------------------
+
+route_result
+post_work::
+operator()(
+    Request&,
+    Response& res) const
+{
+    return res.post(task());
+}
+
+} // beast2
+} // boost
diff --git a/example/server/post_work.hpp b/example/server/post_work.hpp
new file mode 100644
index 00000000..46558772
--- /dev/null
+++ b/example/server/post_work.hpp
@@ -0,0 +1,30 @@
+//
+// 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_POST_WORK_HPP
+#define BOOST_BEAST2_SERVER_POST_WORK_HPP
+
+#include 
+#include 
+
+namespace boost {
+namespace beast2 {
+
+struct post_work
+{
+    system::error_code
+    operator()(
+        Request&,
+        Response& res) const;
+};
+
+} // beast2
+} // boost
+
+#endif
diff --git a/include/boost/beast2/server/basic_router.hpp b/include/boost/beast2/server/basic_router.hpp
index b5c7fc8a..1ef80990 100644
--- a/include/boost/beast2/server/basic_router.hpp
+++ b/include/boost/beast2/server/basic_router.hpp
@@ -178,7 +178,7 @@ class any_router
     BOOST_BEAST2_DECL void add_impl(layer&,
         core::string_view, handler_list const&);
     BOOST_BEAST2_DECL route_result resume_impl(
-        basic_request&, basic_response&, route_result const& ec) const;
+        basic_request&, basic_response&, route_result ec) const;
     BOOST_BEAST2_DECL route_result dispatch_impl(http_proto::method,
         core::string_view, urls::url_view const&,
             basic_request&, basic_response&) const;
@@ -733,7 +733,7 @@ class basic_router : public /*detail::*/any_router
         route_result const& rv) const ->
             route_result
     {
-        return resume_impl(req, res, rv);
+         return resume_impl(req, res, rv);
     }
 
 private:
@@ -833,7 +833,8 @@ class basic_router : public /*detail::*/any_router
         {
             auto const& ec = static_cast<
                 basic_response const&>(res).ec_;
-            if(! ec.failed())
+            if( res.resume_ > 0 ||
+                ! ec.failed())
                 return h.dispatch_impl(req, res);
             return beast2::route::next;
         }
diff --git a/include/boost/beast2/server/http_stream.hpp b/include/boost/beast2/server/http_stream.hpp
index e6448040..9e765e77 100644
--- a/include/boost/beast2/server/http_stream.hpp
+++ b/include/boost/beast2/server/http_stream.hpp
@@ -34,7 +34,7 @@ namespace beast2 {
 
 //------------------------------------------------
 
-/** An HTTP server stream which routes requests to handlers and sends reesponses.
+/** An HTTP server stream which routes requests to handlers and sends responses.
 
     An object of this type wraps an asynchronous Boost.ASIO stream and implements
     a high level server connection which reads HTTP requests, routes them to
@@ -85,23 +85,23 @@ class http_stream
 
 private:
     void do_read();
-
     void on_read(
         system::error_code ec,
         std::size_t bytes_transferred);
-
+    void on_headers();
+    void do_dispatch(route_result rv = {});
+    void do_respond(route_result rv);
+    void do_write();
     void on_write(
         system::error_code const& ec,
         std::size_t bytes_transferred);
-
+    void on_complete();
+    resumer do_detach() override;
+    void do_resume(route_result const& ec) override;
+    void do_close();
     void do_fail(core::string_view s,
         system::error_code const& ec);
-
-    resumer do_detach() override;
-
-    void do_resume(system::error_code const& ec) override;
-
-    void do_resume2(system::error_code ec);
+    void clear() noexcept;
 
 protected:
     std::string id() const
@@ -110,6 +110,7 @@ class http_stream
     }
 
 protected:
+    struct resetter;
     section sect_;
     std::size_t id_ = 0;
     AsyncStream& stream_;
@@ -126,6 +127,35 @@ class http_stream
 
 //------------------------------------------------
 
+// for exception safety
+template
+struct http_stream::
+    resetter
+{
+    ~resetter()
+    {
+        if(clear_)
+            owner_.clear();
+    }
+
+    explicit resetter(
+        http_stream& owner) noexcept
+        : owner_(owner)
+    {
+    }
+
+    void accept()
+    {
+        clear_ = false;
+    }
+
+private:
+    http_stream& owner_;
+    bool clear_ = true;
+};
+
+//------------------------------------------------
+
 template
 http_stream::
 http_stream(
@@ -151,11 +181,8 @@ http_stream(
     res_.detach = detacher(*this);
 }
 
-/** Called to start a new HTTP session
-
-    The stream must be in a connected,
-    correct state for a new session.
-*/
+// called to start a new HTTP session.
+// the connection must be in the correct state already.
 template
 void
 http_stream::
@@ -163,21 +190,25 @@ on_stream_begin(
     acceptor_config const& config)
 {
     pconfig_ = &config;
+
     req_.parser.reset();
+    res_.data.clear();
     do_read();
 }
 
+// begin reading the request
 template
 void
 http_stream::
 do_read()
 {
     req_.parser.start();
-    res_.serializer.reset();
+
     beast2::async_read(stream_, req_.parser,
         call_mf(&http_stream::on_read, this));
 }
 
+// called when the read operation completes
 template
 void 
 http_stream::
@@ -196,87 +227,128 @@ on_read(
 
     BOOST_ASSERT(req_.parser.is_complete());
 
-    //----------------------------------------
-    //
-    // set up Request and Response objects
-    //
+    on_headers();
+}
 
+// called to set up the response after reading the request
+template
+void 
+http_stream::
+on_headers()
+{
+    // set up Request and Response objects
+    res_.serializer.reset();
     // VFALCO HACK for now we make a copy of the message
     req_.message = req_.parser.get();
-
-    // copy version
-    res_.message.set_version(req_.message.version());
-
-    // copy keep-alive setting
-    res_.message.set_start_line(
-        http_proto::status::ok, req_.parser.get().version());
-    res_.message.set_keep_alive(req_.parser.get().keep_alive());
+    //res_.message.set_version(req_.message.version());
+    res_.message.set_start_line( // VFALCO WTF
+        http_proto::status::ok, req_.message.version());
+    res_.message.set_keep_alive(req_.message.keep_alive());
+    res_.data.clear();
 
     // parse the URL
     {
-        auto rv = urls::parse_uri_reference(req_.parser.get().target());
-        if(rv.has_value())
-        {
-            req_.url = rv.value();
-            req_.base_path = "";
-            req_.path = std::string(rv->encoded_path());
-        }
-        else
+        auto rv = urls::parse_uri_reference(req_.message.target());
+        if(rv.has_error())
         {
             // error parsing URL
-            res_.status(
-                http_proto::status::bad_request);
-            res_.set_body(
-                "Bad Request: " + rv.error().message());
-            goto do_write;
+            res_.status(http_proto::status::bad_request);
+            res_.set_body("Bad Request: " + rv.error().message());
+            return do_respond(rv.error());
         }
+
+        req_.url = rv.value();
     }
 
     // invoke handlers for the route
-    BOOST_ASSERT(! pwg_);
-    ec = routes_.dispatch(req_.message.method(), req_.url, req_, res_);
-    if(ec == route::send)
-        goto do_write;
+    do_dispatch();
+}
 
-    if(ec == route::next)
+// called to dispatch or resume the route
+template
+void 
+http_stream::
+do_dispatch(route_result rv)
+{
+    if(! rv.failed())
     {
-        // unhandled
-        res_.status(http_proto::status::not_found);
-        std::string s;
-        format_to(s, "The requested URL {} was not found on this server.", req_.url);
-        //res_.message.set_keep_alive(false); // VFALCO?
-        res_.set_body(s);
-        goto do_write;
+        BOOST_ASSERT(! pwg_); // can't be detached
+        rv = routes_.dispatch(
+            req_.message.method(), req_.url, req_, res_);
     }
+    else
+    {
+        rv = routes_.resume(req_, res_, rv);
+    }
+
+    do_respond(rv);
+}
+
+// called after obtaining a route result
+template
+void 
+http_stream::
+do_respond(
+    route_result rv)
+{
+    BOOST_ASSERT(rv != route::next_route);
 
-    if(ec == route::detach)
+    if(rv == route::close)
     {
-        // make sure they called detach()
-        BOOST_ASSERT(pwg_);
+        return do_close();
+    }
+
+    if(rv == route::complete)
+    {
+        // VFALCO what if the connection was closed or keep-alive=false?
+        // handler sendt the response?
+        BOOST_ASSERT(res_.serializer.is_done());
+        return on_write(system::error_code(), 0);
+    }
+
+    if(rv == route::detach)
+    {
+        // didn't call res.detach()?
+        if(! pwg_)
+            detail::throw_logic_error();
         return;
     }
 
-    // error message of last resort
+    if(rv == route::next)
+    {
+        // unhandled request
+        auto const status = http_proto::status::not_found;
+        res_.status(status);
+        //res_.message.set_keep_alive(false); // VFALCO?
+        res_.set_body(http_proto::to_string(status));
+    }
+    else if(rv != route::send)
     {
-        BOOST_ASSERT(ec.failed());
+        // error message of last resort
+        BOOST_ASSERT(rv.failed());
+        BOOST_ASSERT(! is_route_result(rv));
         res_.status(http_proto::status::internal_server_error);
         std::string s;
-        format_to(s, "An internal server error occurred: {}", ec.message());
-        //res_.message.set_keep_alive(false); // VFALCO?
+        format_to(s, "An internal server error occurred: {}", rv.message());
+        res_.message.set_keep_alive(false); // VFALCO?
         res_.set_body(s);
     }
 
-do_write:
-    if(res_.serializer.is_done())
-    {
-        // happens when the handler sends the response
-        return on_write(system::error_code(), 0);
-    }
+    do_write();
+}
 
+// begin writing the response
+template
+void 
+http_stream::
+do_write()
+{
+    BOOST_ASSERT(! res_.serializer.is_done());
     beast2::async_write(stream_, res_.serializer,
         call_mf(&http_stream::on_write, this));
 }
 
+// called when the write operation completes
 template
 void 
 http_stream::
@@ -298,29 +370,7 @@ on_write(
     if(res_.message.keep_alive())
         return do_read();
 
-    // tidy up lingering objects
-    req_.parser.reset();
-    res_.serializer.reset();
-    res_.message.clear();
-
-    close_({});
-}
-
-template
-void 
-http_stream::
-do_fail(
-    core::string_view s, system::error_code const& ec)
-{
-    LOG_TRC(this->sect_)("{}: {}", s, ec.message());
-
-    // tidy up lingering objects
-    req_.parser.reset();
-    res_.serializer.reset();
-    //res_.clear();
-    //preq_.reset();
-
-    close_(ec);
+    do_close();
 }
 
 template
@@ -340,44 +390,58 @@ do_detach() ->
     return resumer(*this);
 }
 
+// called by resume(rv)
 template
 void
 http_stream::
-do_resume(system::error_code const& ec)
+do_resume(route_result const& rv)
 {
     asio::dispatch(
         stream_.get_executor(),
-        asio::prepend(call_mf(
-            &http_stream::do_resume2, this), ec));
+        [this, rv]
+        {
+            BOOST_ASSERT(pwg_.get() != nullptr);
+            pwg_.reset();
+
+            do_dispatch(rv);
+        });
 }
 
+// called when a non-recoverable error occurs
 template
-void
+void 
 http_stream::
-do_resume2(system::error_code ec)
+do_fail(
+    core::string_view s, system::error_code const& ec)
 {
-    BOOST_ASSERT(stream_.get_executor().running_in_this_thread());
+    LOG_TRC(this->sect_)("{}: {}", s, ec.message());
 
-    BOOST_ASSERT(pwg_.get() != nullptr);
-    pwg_.reset();
+    // tidy up lingering objects
+    req_.parser.reset();
+    res_.serializer.reset();
 
-    // invoke handlers for the route
-    BOOST_ASSERT(! pwg_);
-    ec = routes_.resume(req_, res_, ec);
+    close_(ec);
+}
 
-    if(ec == route::detach)
-    {
-        // make sure they called detach()
-        BOOST_ASSERT(pwg_);
-        return;
-    }
+// end the session
+template
+void
+http_stream::
+do_close()
+{
+    clear();
+    close_({});
+}
 
-    if(ec.failed())
-    {
-        // give a default error response?
-    }
-    beast2::async_write(stream_, res_.serializer,
-        call_mf(&http_stream::on_write, this));
+// clear everything, releasing transient objects
+template
+void
+http_stream::
+clear() noexcept
+{
+    req_.parser.reset();
+    res_.serializer.reset();
+    res_.message.clear();
 }
 
 } // beast2
diff --git a/include/boost/beast2/server/route_handler.hpp b/include/boost/beast2/server/route_handler.hpp
index 1bb8d382..2b1be58f 100644
--- a/include/boost/beast2/server/route_handler.hpp
+++ b/include/boost/beast2/server/route_handler.hpp
@@ -12,13 +12,14 @@
 
 #include 
 #include 
-#include 
+#include 
 #include   // VFALCO forward declare?
 #include   // VFALCO forward declare?
 #include         // VFALCO forward declare?
 #include       // VFALCO forward declare?
 #include 
 #include 
+#include 
 
 namespace boost {
 namespace beast2 {
@@ -29,6 +30,8 @@ struct acceptor_config
     bool is_admin;
 };
 
+//-----------------------------------------------
+
 /** Request object for HTTP route handlers
 */
 struct Request : basic_request
@@ -53,14 +56,15 @@ struct Request : basic_request
     /** A container for storing arbitrary data associated with the request.
         This starts out empty for each new request.
     */
-    capy::polystore data;
+    capy::datastore data;
 };
 
 //-----------------------------------------------
 
 /** Response object for HTTP route handlers
 */
-struct Response : basic_response
+struct BOOST_SYMBOL_VISIBLE
+    Response : basic_response
 {
     /** The HTTP response message
     */
@@ -81,38 +85,18 @@ struct Response : basic_response
 
         This starts out empty for each new session.
     */
-    capy::polystore data;
-
-    route_result close() const noexcept
-    {
-        return route::close;
-    }
-
-    route_result complete() const noexcept
-    {
-        return route::complete;
-    }
-
-    route_result next() const noexcept
-    {
-        return route::next;
-    }
-
-    route_result next_route() const noexcept
-    {
-        return route::next_route;
-    }
-
-    route_result send() const noexcept
-    {
-        return route::send;
-    }
+    capy::datastore data;
 
-    BOOST_BEAST2_DECL
-    route_result
-    fail(system::error_code const& ec);
+    /** Destructor.
+    */
+    BOOST_BEAST2_DECL virtual ~Response();
 
-    // route_result send(core::string_view);
+    /** Reset the object for a new request.
+        This clears any state associated with
+        the previous request, preparing the object
+        for use with a new request.
+    */
+    BOOST_BEAST2_DECL void reset();
 
     /** Set the status code of the response.
         @par Example
@@ -129,8 +113,144 @@ struct Response : basic_response
     BOOST_BEAST2_DECL
     Response&
     set_body(std::string s);
+
+    // VFALCO this doc isn't quite right because it doesn't explain
+    // the possibility that post will return the final result immediately,
+    // and it needs to remind the user that calling a function which
+    // returns route_result means they have to return the value right away
+    // without doing anything else.
+    //
+    // VFALCO we have to detect calls to detach inside `f` and throw
+    //
+    /** Submit cooperative work.
+
+        This function detaches the current handler from the session,
+        and immediately invokes the specified function object @p f.
+        When the function returns normally, the function object is
+        placed into an implementation-defined work queue to be invoked
+        again. Otherwise, if the function calls `resume(rv)` then the
+        session is resumed and behaves as if the original route handler
+        had returned the value `rv`.
+
+        When the function object is invoked, it runs in the same context
+        as the original handler invocation. If the function object
+        attempts to call @ref post again, or attempts to call @ref detach,
+        an exception is thrown.
+
+        The function object @p f must have this equivalent signature:
+        @code
+        void ( resumer resume );
+        @endcode
+
+        @param f The function object to invoke.
+        @param c The continuation function to be invoked when f finishes.
+    */
+    template
+    auto
+    post(F&& f) -> route_result;
+
+protected:
+    /** A task to be invoked later
+    */
+    struct task
+    {
+        virtual ~task() = default;
+
+        /** Invoke the task.
+
+            @return true if the task resumed the session.
+        */
+        virtual bool invoke() = 0;
+    };
+
+    /** Post task_ to be invoked later
+
+        Subclasses must schedule task_ to be invoked at an unspecified
+        point in the future.
+    */
+    BOOST_BEAST2_DECL
+    virtual void do_post();
+
+    std::unique_ptr task_;
 };
 
+//-----------------------------------------------
+
+template
+auto
+Response::
+post(F&& f) -> route_result
+{
+    // task already posted
+    if(task_)
+        detail::throw_invalid_argument();
+
+    struct BOOST_SYMBOL_VISIBLE
+        immediate : detacher::owner
+    {
+        route_result rv;
+        bool set = false;
+        void do_resume(
+            route_result const& rv_) override
+        {
+            rv = rv_;
+            set = true;
+        }
+    };
+
+    class BOOST_SYMBOL_VISIBLE model
+        : public task
+        , public detacher::owner
+    {
+    public:
+        model(Response& res,
+            F&& f, resumer resume)
+            : res_(res)
+            , f_(std::forward(f))
+            , resume_(resume)
+        {
+        }
+
+        bool invoke() override
+        {
+            resumed_ = false;
+            // VFALCO analyze exception safety
+            f_(resumer(*this));
+            return resumed_;
+        }
+
+        void do_resume(
+            route_result const& rv) override
+        {
+            resumed_ = true;
+            resumer resume(resume_);
+            res_.task_.reset(); // destroys *this
+            resume(rv);
+        }
+
+    private:
+        Response& res_;
+        typename std::decay::type f_;
+        resumer resume_;
+        bool resumed_;
+    };
+
+    // first call
+    immediate impl;
+    f(resumer(impl));
+    if(impl.set)
+        return impl.rv;
+
+    return detach(
+        [&](resumer resume)
+        {
+            task_ = std::unique_ptr(new model(
+                *this, std::forward(f), resume));
+            do_post();
+        });
+}
+
+
 } // beast2
 } // boost
 
diff --git a/include/boost/beast2/server/route_handler_asio.hpp b/include/boost/beast2/server/route_handler_asio.hpp
index 793240ac..6a119e0d 100644
--- a/include/boost/beast2/server/route_handler_asio.hpp
+++ b/include/boost/beast2/server/route_handler_asio.hpp
@@ -12,6 +12,7 @@
 
 #include 
 #include 
+#include 
 #include 
 
 namespace boost {
@@ -20,8 +21,9 @@ namespace beast2 {
 /** Response object for Asio HTTP route handlers
 */
 template
-struct ResponseAsio : Response
+class ResponseAsio : public Response
 {
+public:
     using stream_type = typename std::decay::type;
 
     AsyncStream stream;
@@ -33,8 +35,28 @@ struct ResponseAsio : Response
         : stream(std::forward(args)...)
     {
     }
+
+private:
+    void do_post() override;
 };
 
+//-----------------------------------------------
+
+template
+void
+ResponseAsio::
+do_post()
+{
+    asio::post(
+        stream.get_executor(),
+        [&]
+        {
+            if(task_->invoke())
+                return;
+            do_post();
+        });
+}
+
 } // beast2
 } // boost
 
diff --git a/include/boost/beast2/server/router_types.hpp b/include/boost/beast2/server/router_types.hpp
index c146e18b..49f846fc 100644
--- a/include/boost/beast2/server/router_types.hpp
+++ b/include/boost/beast2/server/router_types.hpp
@@ -113,15 +113,10 @@ namespace detail {
 struct BOOST_SYMBOL_VISIBLE route_cat_type
     : system::error_category
 {
-    BOOST_BEAST2_DECL const char* name(
-        ) const noexcept override;
-    BOOST_BEAST2_DECL std::string message(
-        int) const override;
+    BOOST_BEAST2_DECL const char* name() const noexcept override;
+    BOOST_BEAST2_DECL std::string message(int) const override;
     BOOST_BEAST2_DECL char const* message(
-        int, char*, std::size_t
-            ) const noexcept override;
-    bool failed( int ) const noexcept override
-         { return false; }
+        int, char*, std::size_t) const noexcept override;
     BOOST_SYSTEM_CONSTEXPR route_cat_type()
         : error_category(0x51c90d393754ecdf )
     {
@@ -140,6 +135,18 @@ make_error_code(route ev) noexcept
         detail::route_cat};
 }
 
+/** Return true if `rv` is a route result.
+
+    A @ref route_result can hold any error code,
+    and this function returns `true` only if `rv`
+    holds a value from the @ref route enumeration.
+*/
+inline bool is_route_result(
+    route_result rv) noexcept
+{
+    return &rv.category() == &detail::route_cat;
+}
+
 //------------------------------------------------
 
 class resumer;
@@ -154,9 +161,10 @@ class detacher
 public:
     /** Base class of the implementation
     */
-    struct owner
+    struct BOOST_SYMBOL_VISIBLE
+        owner
     {
-        virtual resumer do_detach() = 0;
+        BOOST_BEAST2_DECL virtual resumer do_detach();
         virtual void do_resume(route_result const&) = 0;
     };
 
@@ -186,7 +194,13 @@ class detacher
 
 private:
     friend resumer;
-    owner* p_ = nullptr;
+    // Clang doesn't consider uninstantiated templates
+    // when checking for unused private fields.
+    owner* p_
+    #if defined(__clang__)
+        __attribute__((unused))
+    #endif
+        = nullptr; 
 };
 
 //------------------------------------------------
@@ -203,8 +217,8 @@ class resumer
     /** Constructor
 
         Default constructed resume functions will
-        be empty. Invoking an empty resume function
-        yields undefined behavior.
+        be empty. An exception is thrown when
+        attempting to invoke an empty object.
     */
     resumer() = default;
 
@@ -234,18 +248,28 @@ class resumer
     /** Resume the session
 
         A session is resumed as if the detached
-        handler returned the error code in `ec`.
+        handler returned the route result in @p rv.
 
         @param ec The error code to resume with.
+
+        @throw std::invalid_argument If the object is empty.
     */
     void operator()(
-        system::error_code const& ec = {}) const
+        route_result const& rv) const
     {
-        p_->do_resume(ec);
+        if(! p_)
+            detail::throw_invalid_argument();
+        p_->do_resume(rv);
     }
 
 private:
-    detacher::owner* p_ = nullptr;
+    // Clang doesn't consider uninstantiated templates
+    // when checking for unused private fields.
+    detacher::owner* p_
+    #if defined(__clang__)
+        __attribute__((unused))
+    #endif
+        = nullptr; 
 };
 
 template
diff --git a/src/server/basic_router.cpp b/src/server/basic_router.cpp
index 365c1519..bfe79b10 100644
--- a/src/server/basic_router.cpp
+++ b/src/server/basic_router.cpp
@@ -593,7 +593,7 @@ auto
 any_router::
 resume_impl(
     basic_request& req, basic_response& res,
-    route_result const& ec) const ->
+    route_result ec) const ->
         route_result
 {
     BOOST_ASSERT(res.resume_ > 0);
@@ -601,7 +601,7 @@ resume_impl(
         ec == route::close ||
         ec == route::complete)
         return ec;
-    if(&ec.category() != &detail::route_cat)
+    if(! is_route_result(ec))
     {
         // must indicate failure
         if(! ec.failed())
@@ -719,6 +719,9 @@ dispatch_impl(
     else if((impl_->opt & 16) != 0)
         req.strict = false;
 
+    // nested routers count as 1 call
+    //++res.pos_;
+
     match_result mr;
     for(auto const& i : impl_->layers)
     {
@@ -809,17 +812,6 @@ dispatch_impl(
                 rv == route::complete ||
                 rv == route::close)
                 return rv;
-            if(rv.failed())
-            {
-                // error handling mode
-                res.ec_ = rv;
-                if(! i.match.end)
-                    continue; // next entry
-                // routes don't have error handlers
-                while(++it != i.entries.end())
-                    res.pos_ += it->handler->count();
-                break; // skip remaining entries
-            }
             if(rv == route::next)
                 continue; // next entry
             if(rv == route::next_route)
@@ -832,9 +824,20 @@ dispatch_impl(
                 break; // skip remaining entries
             }
             // we must handle all route enums
-            BOOST_ASSERT(&rv.category() != &detail::route_cat);
-            // handler must return non-successful error_code
-            detail::throw_invalid_argument();
+            BOOST_ASSERT(! is_route_result(rv));
+            if(! rv.failed())
+            {
+                // handler must return non-successful error_code
+                detail::throw_invalid_argument();
+            }
+            // error handling mode
+            res.ec_ = rv;
+            if(! i.match.end)
+                continue; // next entry
+            // routes don't have error handlers
+            while(++it != i.entries.end())
+                res.pos_ += it->handler->count();
+            break; // skip remaining entries
         }
 
         mr.restore_path(req);
@@ -850,7 +853,7 @@ do_dispatch(
     basic_response& res) const
 {
     auto rv = dispatch_impl(req, res);
-    BOOST_ASSERT(&rv.category() == &detail::route_cat);
+    BOOST_ASSERT(is_route_result(rv));
     BOOST_ASSERT(rv != route::next_route);
     if(rv != route::next)
     {
diff --git a/src/server/route_handler.cpp b/src/server/route_handler.cpp
index 2d548bc9..0a74531a 100644
--- a/src/server/route_handler.cpp
+++ b/src/server/route_handler.cpp
@@ -15,13 +15,9 @@
 namespace boost {
 namespace beast2 {
 
-route_result
 Response::
-fail(system::error_code const& ec)
+~Response()
 {
-    if(! ec.failed())
-        detail::throw_invalid_argument();
-    return ec;
 }
 
 Response&
@@ -55,5 +51,16 @@ set_body(std::string s)
     return *this;
 }
 
+void
+Response::
+do_post()
+{
+    BOOST_ASSERT(task_);
+    // invoke until task resumes
+    for(;;)
+        if(task_->invoke())
+            break;
+}
+
 } // beast2
 } // boost
diff --git a/src/server/router_types.cpp b/src/server/router_types.cpp
index f66981bb..5c27e849 100644
--- a/src/server/router_types.cpp
+++ b/src/server/router_types.cpp
@@ -71,5 +71,13 @@ route_cat_type route_cat;
 
 } // detail
 
+resumer
+detacher::
+owner::
+do_detach()
+{
+    detail::throw_logic_error();
+}
+
 } // beast2
 } // boost
diff --git a/test/unit/server/body_source.cpp b/test/unit/server/body_source.cpp
index f287195c..5fad4d15 100644
--- a/test/unit/server/body_source.cpp
+++ b/test/unit/server/body_source.cpp
@@ -91,6 +91,7 @@ struct read_source
 
 } // (anon)
 
+#if 0
 BOOST_CORE_STATIC_ASSERT(
     std::is_move_constructible::value);
 BOOST_CORE_STATIC_ASSERT(
@@ -113,6 +114,7 @@ BOOST_CORE_STATIC_ASSERT(
     std::is_assignable::value);
 BOOST_CORE_STATIC_ASSERT(
     std::is_assignable::value);
+#endif
 
 struct body_source_test
 {
diff --git a/test/unit/server/route_handler_asio.cpp b/test/unit/server/route_handler_asio.cpp
index 723e855a..bea47575 100644
--- a/test/unit/server/route_handler_asio.cpp
+++ b/test/unit/server/route_handler_asio.cpp
@@ -12,6 +12,7 @@
 
 #include 
 #include 
+#include 
 
 #include "test_suite.hpp"
 
@@ -22,6 +23,11 @@ struct route_handler_asio_test
 {
     struct stream
     {
+        asio::any_io_executor
+        get_executor() const
+        {
+            return {};
+        }
     };
 
     using test_router = basic_router<
diff --git a/test/unit/server/router_asio.cpp b/test/unit/server/router_asio.cpp
index 1ab4b4d8..98b34980 100644
--- a/test/unit/server/router_asio.cpp
+++ b/test/unit/server/router_asio.cpp
@@ -12,6 +12,7 @@
 
 #include 
 #include 
+#include 
 
 #include "test_suite.hpp"
 
@@ -22,6 +23,11 @@ namespace {
 
 struct Stream
 {
+    asio::any_io_executor
+    get_executor() const
+    {
+        return {};
+    }
 };
 
 BOOST_CORE_STATIC_ASSERT(

From 2d7f0c2e3149365198da22edd7d48f3f7e838df5 Mon Sep 17 00:00:00 2001
From: Vinnie Falco 
Date: Sun, 30 Nov 2025 19:25:48 -0800
Subject: [PATCH 06/40] chore: use http_proto

---
 example/server/main.cpp                       |    8 +-
 example/server/post_work.cpp                  |   10 +-
 example/server/post_work.hpp                  |    6 +-
 example/server/serve_detached.hpp             |   10 +-
 example/server/serve_log_admin.cpp            |   12 +-
 include/boost/beast2.hpp                      |    3 -
 include/boost/beast2/server/basic_router.hpp  | 1015 -----------
 include/boost/beast2/server/http_stream.hpp   |   48 +-
 include/boost/beast2/server/plain_worker.hpp  |    2 +-
 include/boost/beast2/server/route_handler.hpp |  257 ---
 .../beast2/server/route_handler_asio.hpp      |    4 +-
 include/boost/beast2/server/router.hpp        |    6 +-
 include/boost/beast2/server/router_asio.hpp   |    5 +-
 include/boost/beast2/server/router_types.hpp  |  351 ----
 .../boost/beast2/server/serve_redirect.hpp    |    8 +-
 include/boost/beast2/server/serve_static.hpp  |    6 +-
 src/server/basic_router.cpp                   |  876 ----------
 src/server/http_server.cpp                    |    2 +-
 src/server/route_handler.cpp                  |   66 -
 src/server/router_types.cpp                   |   83 -
 src/server/serve_redirect.cpp                 |    8 +-
 src/server/serve_static.cpp                   |   14 +-
 test/unit/server/basic_router.cpp             | 1499 -----------------
 test/unit/server/route_handler.cpp            |  104 --
 test/unit/server/route_handler_asio.cpp       |   16 +-
 test/unit/server/router_types.cpp             |   30 -
 26 files changed, 86 insertions(+), 4363 deletions(-)
 delete mode 100644 include/boost/beast2/server/basic_router.hpp
 delete mode 100644 include/boost/beast2/server/route_handler.hpp
 delete mode 100644 include/boost/beast2/server/router_types.hpp
 delete mode 100644 src/server/basic_router.cpp
 delete mode 100644 src/server/route_handler.cpp
 delete mode 100644 src/server/router_types.cpp
 delete mode 100644 test/unit/server/basic_router.cpp
 delete mode 100644 test/unit/server/route_handler.cpp
 delete mode 100644 test/unit/server/router_types.cpp

diff --git a/example/server/main.cpp b/example/server/main.cpp
index ceb38bf6..89dbfc4a 100644
--- a/example/server/main.cpp
+++ b/example/server/main.cpp
@@ -73,11 +73,11 @@ int server_main( int argc, char* argv[] )
         //srv.wwwroot.use("/detach", serve_detached());
         srv.wwwroot.use(post_work());
         srv.wwwroot.use(
-            []( Request& req,
-                Response& res) ->
-                    route_result
+            []( http_proto::Request& req,
+                http_proto::Response& res) ->
+                    http_proto::route_result
             {
-                return route::next;
+                return http_proto::route::next;
             });
         srv.wwwroot.use("/", serve_static( argv[3] ));
 
diff --git a/example/server/post_work.cpp b/example/server/post_work.cpp
index dc5c7d2d..5420aad0 100644
--- a/example/server/post_work.cpp
+++ b/example/server/post_work.cpp
@@ -48,11 +48,11 @@ struct task
     std::size_t i = 10;
 
     void
-    operator()(resumer resume)
+    operator()(http_proto::resumer resume)
     {
         if(i--)
             return;
-        resume(route::next);
+        resume(http_proto::route::next);
     }
 };
 
@@ -60,11 +60,11 @@ struct task
 
 //------------------------------------------------
 
-route_result
+http_proto::route_result
 post_work::
 operator()(
-    Request&,
-    Response& res) const
+    http_proto::Request&,
+    http_proto::Response& res) const
 {
     return res.post(task());
 }
diff --git a/example/server/post_work.hpp b/example/server/post_work.hpp
index 46558772..ca097e87 100644
--- a/example/server/post_work.hpp
+++ b/example/server/post_work.hpp
@@ -11,7 +11,7 @@
 #define BOOST_BEAST2_SERVER_POST_WORK_HPP
 
 #include 
-#include 
+#include 
 
 namespace boost {
 namespace beast2 {
@@ -20,8 +20,8 @@ struct post_work
 {
     system::error_code
     operator()(
-        Request&,
-        Response& res) const;
+        http_proto::Request&,
+        http_proto::Response& res) const;
 };
 
 } // beast2
diff --git a/example/server/serve_detached.hpp b/example/server/serve_detached.hpp
index 70d74785..d11dae09 100644
--- a/example/server/serve_detached.hpp
+++ b/example/server/serve_detached.hpp
@@ -12,7 +12,7 @@
 
 #include 
 #include 
-#include 
+#include 
 #include 
 #include 
 #include 
@@ -39,11 +39,11 @@ class serve_detached
 
     system::error_code
     operator()(
-        Request&,
-        Response& res) const
+        http_proto::Request&,
+        http_proto::Response& res) const
     {
         return res.detach(
-            [&](resumer resume)
+            [&](http_proto::resumer resume)
             {
                 asio::post(*tp_,
                     [&, resume]()
@@ -52,7 +52,7 @@ class serve_detached
                         std::this_thread::sleep_for(std::chrono::seconds(1));
                         res.status(http_proto::status::ok);
                         res.set_body("Hello from serve_detached!\n");
-                        resume(route::send);
+                        resume(http_proto::route::send);
                         // resume( res.send("Hello from serve_detached!\n") );
                     });
             });
diff --git a/example/server/serve_log_admin.cpp b/example/server/serve_log_admin.cpp
index 6c2a2525..93ab8263 100644
--- a/example/server/serve_log_admin.cpp
+++ b/example/server/serve_log_admin.cpp
@@ -30,8 +30,8 @@ class serve_log_page
 
     system::error_code
     operator()(
-        Request&,
-        Response& res) const
+        http_proto::Request&,
+        http_proto::Response& res) const
     {
         auto const v = ls_.get_sections();
         std::string s;
@@ -75,7 +75,7 @@ class serve_log_page
         res.status(http_proto::status::ok);
         res.message.set(http_proto::field::content_type, "text/html; charset=UTF-8");
         res.set_body(std::move(s));
-        return error::success;
+        return http_proto::route::send;
     }
 
 private:
@@ -97,13 +97,13 @@ class handle_submit
 
     system::error_code
     operator()(
-        Request&,
-        Response& res) const
+        http_proto::Request&,
+        http_proto::Response& res) const
     {
         res.status(http_proto::status::ok);
         res.message.set(http_proto::field::content_type, "plain/text; charset=UTF-8");
         res.set_body("submit");
-        return error::success;
+        return http_proto::route::send;
     }
 
 private:
diff --git a/include/boost/beast2.hpp b/include/boost/beast2.hpp
index c19ea89f..08e75724 100644
--- a/include/boost/beast2.hpp
+++ b/include/boost/beast2.hpp
@@ -11,18 +11,15 @@
 #define BOOST_BEAST2_HPP
 
 #include 
-#include 
 #include 
 #include 
 #include 
 #include 
 //#include 
 #include 
-#include 
 #include 
 #include 
 #include 
-#include 
 #include 
 #include 
 //#include 
diff --git a/include/boost/beast2/server/basic_router.hpp b/include/boost/beast2/server/basic_router.hpp
deleted file mode 100644
index 1ef80990..00000000
--- a/include/boost/beast2/server/basic_router.hpp
+++ /dev/null
@@ -1,1015 +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_BASIC_ROUTER_HPP
-#define BOOST_BEAST2_SERVER_BASIC_ROUTER_HPP
-
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-#include 
-
-namespace boost {
-namespace beast2 {
-
-template
-class basic_router;
-
-/** Configuration options for routers.
-*/
-struct router_options
-{
-    /** Constructor
-
-        Routers constructed with default options inherit the values of
-        @ref case_sensitive and @ref strict from the parent router.
-        If there is no parent, both default to `false`.
-        The value of @ref merge_params always defaults to `false`
-        and is never inherited.
-    */
-    router_options() = default;
-
-    /** Set whether to merge parameters from parent routers.
-
-        This setting controls whether route parameters defined on parent
-        routers are made available in nested routers. It is not inherited
-        and always defaults to `false`.
-
-        @par Example
-        @code
-        router r(router_options()
-            .merge_params(true)
-            .case_sensitive(true)
-            .strict(false));
-        @endcode
-
-        @param value `true` to merge parameters from parent routers.
-        @return A reference to `*this` for chaining.
-    */
-    router_options&
-    merge_params(
-        bool value) noexcept
-    {
-        v_ = (v_ & ~1) | (value ? 1 : 0);
-        return *this;
-    }
-
-    /** Set whether pattern matching is case-sensitive.
-
-        When this option is not set explicitly, the value is inherited
-        from the parent router or defaults to `false` if there is no parent.
-
-        @par Example
-        @code
-        router r(router_options()
-            .case_sensitive(true)
-            .strict(true));
-        @endcode
-
-        @param value `true` to perform case-sensitive path matching.
-        @return A reference to `*this` for chaining.
-    */
-    router_options&
-    case_sensitive(
-        bool value) noexcept
-    {
-        if(value)
-            v_ = (v_ & ~6) | 2;
-        else
-            v_ = (v_ & ~6) | 4;
-        return *this;
-    }
-
-    /** Set whether pattern matching is strict.
-
-        When this option is not set explicitly, the value is inherited
-        from the parent router or defaults to `false` if there is no parent.
-        Strict matching treats a trailing slash as significant:
-        the pattern `"/api"` matches `"/api"` but not `"/api/"`.
-        When strict matching is disabled, these paths are treated
-        as equivalent.
-
-        @par Example
-        @code
-        router r(router_options()
-            .strict(true)
-            .case_sensitive(false));
-        @endcode
-
-        @param value `true` to enable strict path matching.
-        @return A reference to `*this` for chaining.
-    */
-    router_options&
-    strict(
-        bool value) noexcept
-    {
-        if(value)
-            v_ = (v_ & ~24) | 8;
-        else
-            v_ = (v_ & ~24) | 16;
-        return *this;
-    }
-
-private:
-    template friend class basic_router;
-    unsigned int v_ = 0;
-};
-
-//-----------------------------------------------
- 
-//namespace detail {
-
-class any_router;
-
-//-----------------------------------------------
-
-// implementation for all routers
-class any_router
-{
-private:
-    template
-    friend class beast2::basic_router;
-    using opt_flags = unsigned int;
-
-    struct BOOST_SYMBOL_VISIBLE any_handler
-    {
-        BOOST_BEAST2_DECL virtual ~any_handler();
-        virtual std::size_t count() const noexcept = 0;
-        virtual route_result invoke(
-            basic_request&, basic_response&) const = 0;
-    };
-
-    using handler_ptr = std::unique_ptr;
-
-    struct handler_list
-    {
-        std::size_t n;
-        handler_ptr* p;
-    };
-
-    using match_result = basic_request::match_result;
-    struct matcher;
-    struct layer;
-    struct impl;
-
-    BOOST_BEAST2_DECL ~any_router();
-    BOOST_BEAST2_DECL any_router(opt_flags);
-    BOOST_BEAST2_DECL any_router(any_router&&) noexcept;
-    BOOST_BEAST2_DECL any_router(any_router const&) noexcept;
-    BOOST_BEAST2_DECL any_router& operator=(any_router&&) noexcept;
-    BOOST_BEAST2_DECL any_router& operator=(any_router const&) noexcept;
-    BOOST_BEAST2_DECL std::size_t count() const noexcept;
-    BOOST_BEAST2_DECL layer& new_layer(core::string_view pattern);
-    BOOST_BEAST2_DECL void add_impl(core::string_view, handler_list const&);
-    BOOST_BEAST2_DECL void add_impl(layer&,
-        http_proto::method, handler_list const&);
-    BOOST_BEAST2_DECL void add_impl(layer&,
-        core::string_view, handler_list const&);
-    BOOST_BEAST2_DECL route_result resume_impl(
-        basic_request&, basic_response&, route_result ec) const;
-    BOOST_BEAST2_DECL route_result dispatch_impl(http_proto::method,
-        core::string_view, urls::url_view const&,
-            basic_request&, basic_response&) const;
-    BOOST_BEAST2_DECL route_result dispatch_impl(
-        basic_request&, basic_response&) const;
-    route_result do_dispatch(basic_request&, basic_response&) const;
-
-    impl* impl_ = nullptr;
-};
-
-//} // detail
-
-//-----------------------------------------------
-
-/** A container for HTTP route handlers.
-
-    `basic_router` objects store and dispatch route handlers based on the
-    HTTP method and path of an incoming request. Routes are added with a
-    path pattern and an associated handler, and the router is then used to
-    dispatch the appropriate handler.
-
-    Patterns used to create route definitions have percent-decoding applied
-    when handlers are mounted. A literal "%2F" in the pattern string is
-    indistinguishable from a literal '/'. For example, "/x%2Fz" is the
-    same as "/x/z" when used as a pattern.
-
-    @par Example
-    @code
-    using router_type = basic_router;
-    router_type router;
-    router.get("/hello",
-        [](Req& req, Res& res)
-        {
-            res.status(http_proto::status::ok);
-            res.set_body("Hello, world!");
-            return route::send;
-        });
-    @endcode
-
-    Router objects are lightweight, shared references to their contents.
-    Copies of a router obtained through construction, conversion, or
-    assignment do not create new instances; they all refer to the same
-    underlying data.
-
-    @par Handlers
-    Regular handlers are invoked for matching routes and have this
-    equivalent signature:
-    @code
-    route_result handler( Req& req, Res& res )
-    @endcode
-
-    The return value is a @ref route_result used to indicate the desired
-    action through @ref route enum values, or to indicate that a failure
-    occurred. Failures are represented by error codes for which
-    `system::error_code::failed()` returns `true`.
-
-    When a failing error code is produced and remains unhandled, the
-    router enters error-dispatching mode. In this mode, only error
-    handlers are invoked. Error handlers are registered globally or
-    for specific paths and execute in the order of registration whenever
-    a failing error code is present in the response.
-
-    Error handlers have this equivalent signature:
-    @code
-    route_result error_handler( Req& req, Res& res, system::error_code ec )
-    @endcode
-
-    Each error handler may return any failing @ref system::error_code,
-    which is equivalent to calling:
-    @code
-    res.next(ec); // with ec.failed() == true
-    @endcode
-    Returning @ref route::next indicates that control should proceed to
-    the next matching error handler. Returning a different failing code
-    replaces the current error and continues dispatch in error mode using
-    that new code. Error handlers are invoked until one returns a result
-    other than @ref route::next.
-
-    @par Handler requirements
-    Regular handlers must be callable with:
-    @code
-    route_result( Req&, Res& )
-    @endcode
-
-    Error handlers must be callable with:
-    @code
-    route_result( Req&, Res&, system::error_code )
-    @endcode
-    Error handlers are invoked only when the response has a failing
-    error code, and execute in sequence until one returns a result
-    other than @ref route::next.
-
-    The prefix match is not strict: middleware attached to `"/api"`
-    will also match `"/api/users"` and `"/api/data"`. When registered
-    before route handlers for the same prefix, middleware runs before
-    those routes. This is analogous to `app.use(path, ...)` in
-    Express.js.
-
-    @par Thread Safety
-    Member functions marked `const` such as @ref dispatch and @ref resume
-    may be called concurrently on routers that refer to the same data.
-    Modification of routers through calls to non-`const` member functions
-    is not thread-safe and must not be performed concurrently with any
-    other member function.
-
-    @par Constraints
-    `Req` must be publicly derived from @ref basic_request.
-    `Res` must be publicly derived from @ref basic_response.
-
-    @tparam Req The type of request object.
-    @tparam Res The type of response object.
-*/
-template
-class basic_router : public /*detail::*/any_router
-{
-    // Req must be publicly derived from basic_request
-    BOOST_CORE_STATIC_ASSERT(
-        detail::derived_from::value);
-
-    // Res must be publicly derived from basic_response
-    BOOST_CORE_STATIC_ASSERT(
-        detail::derived_from::value);
-
-    // 0 = unrecognized
-    // 1 = normal handler (Req&, Res&)
-    // 2 = error handler  (Req&, Res&, error_code)
-    // 4 = basic_router
-
-    template
-    struct handler_type
-        : std::integral_constant
-    {
-    };
-
-    // route_result( Req&, Res& ) const
-    template
-    struct handler_type()(
-                std::declval(),
-                std::declval())),
-            route_result>::value
-        >::type> : std::integral_constant {};
-
-    // route_result( Req&, Res&, system::error_code const& ) const
-    template
-    struct handler_type()(
-                std::declval(),
-                std::declval(),
-                std::declval())),
-            route_result>::value
-        >::type> : std::integral_constant {};
-
-    // basic_router
-    template
-    struct handler_type::value &&
-            std::is_convertible::value &&
-            std::is_constructible>::value
-        >::type> : std::integral_constant {};
-
-    template
-    struct handler_check : std::true_type {};
-
-    template
-    struct handler_check
-        : std::conditional<
-              ( (handler_type::value & Mask) != 0 ),
-              handler_check,
-              std::false_type
-          >::type {};
-
-public:
-    /** The type of request object used in handlers
-    */
-    using request_type = Req;
-
-    /** The type of response object used in handlers
-    */
-    using response_type = Res;
-
-    /** A fluent interface for defining handlers on a specific route.
-
-        This type represents a single route within the router and
-        provides a chainable API for registering handlers associated
-        with particular HTTP methods or for all methods collectively.
-
-        Typical usage registers one or more handlers for a route:
-        @code
-        router.route("/users/:id")
-            .get(show_user)
-            .put(update_user)
-            .all(log_access);
-        @endcode
-
-        Each call appends handlers in registration order.
-    */
-    class fluent_route;
-
-    /** Constructor
-
-        Creates an empty router with the specified configuration.
-        Routers constructed with default options inherit the values
-        of @ref router_options::case_sensitive and
-        @ref router_options::strict from the parent router, or default
-        to `false` if there is no parent. The value of
-        @ref router_options::merge_params defaults to `false` and
-        is never inherited.
-
-        @param options The configuration options to use.
-    */
-    explicit
-    basic_router(router_options options = {})
-        : any_router(options.v_)
-    {
-    }
-
-    /** Construct a router from another router with compatible types.
-
-        This constructs a router that shares the same underlying routing
-        state as another router whose request and response types are base
-        classes of `Req` and `Res`, respectively.
-
-        The resulting router participates in shared ownership of the
-        implementation; copying the router does not duplicate routes or
-        handlers, and changes visible through one router are visible
-        through all routers that share the same underlying state.
-
-        @par Constraints
-        `Req` must be derived from `OtherReq`, and `Res` must be
-        derived from `OtherRes`.
-
-        @tparam OtherReq The request type of the source router.
-        @tparam OtherRes The response type of the source router.
-        @param other The router to copy.
-    */
-    template<
-        class OtherReq, class OtherRes,
-        class = typename std::enable_if<
-            detail::derived_from::value &&
-            detail::derived_from::value>::type
-    >
-    basic_router(
-        basic_router const& other)
-        : any_router(other)
-    {
-    }
-
-    /** Add one or more middleware handlers for a path prefix.
-
-        Each handler registered with this function participates in the
-        routing and error-dispatch process for requests whose path begins
-        with the specified prefix, as described in the @ref basic_router
-        class documentation. Handlers execute in the order they are added
-        and may return @ref route::next to transfer control to the
-        subsequent handler in the chain.
-
-        @par Example
-        @code
-        router.use("/api",
-            [](Request& req, Response& res)
-            {
-                if (!authenticate(req))
-                {
-                    res.status(401);
-                    res.set_body("Unauthorized");
-                    return route::send;
-                }
-                return route::next;
-            },
-            [](Request&, Response& res)
-            {
-                res.set_header("X-Powered-By", "MyServer");
-                return route::next;
-            });
-        @endcode
-
-        @par Preconditions
-        @p `pattern` must be a valid path prefix; it may be empty to
-            indicate the root scope.
-
-        @param pattern The pattern to match.
-        @param h1 The first handler to add.
-        @param hn Additional handlers to add, invoked after @p h1 in
-            registration order.
-    */
-    template
-    void use(
-        core::string_view pattern,
-        H1&& h1, HN... hn)
-    {
-        // If you get a compile error on this line it means that
-        // one or more of the provided types is not a valid handler,
-        // error handler, or router.
-        BOOST_CORE_STATIC_ASSERT(handler_check<7, H1, HN...>::value);
-        add_impl(pattern, make_handler_list(
-            std::forward

(h1), std::forward(hn)...)); - } - - /** Add one or more global middleware handlers. - - Each handler registered with this function participates in the - routing and error-dispatch process as described in the - @ref basic_router class documentation. Handlers execute in the - order they are added and may return @ref route::next to transfer - control to the next handler in the chain. - - This is equivalent to writing: - @code - use( "/", h1, hn... ); - @endcode - - @par Example - @code - router.use( - [](Request&, Response& res) - { - res.message.erase("X-Powered-By"); - return route::next; - }); - @endcode - - @par Constraints - @li `h1` must not be convertible to @ref core::string_view. - - @param h1 The first handler to add. - @param hn Additional handlers to add, invoked after @p h1 in - registration order. - */ - template::value>::type> - void use(H1&& h1, HN&&... hn) - { - // If you get a compile error on this line it means that - // one or more of the provided types is not a valid handler, - // error handler, or router. - BOOST_CORE_STATIC_ASSERT(handler_check<7, H1, HN...>::value); - use(core::string_view(), - std::forward

(h1), std::forward(hn)...); - } - - /** Add handlers for all HTTP methods matching a path pattern. - - This registers regular handlers for the specified path pattern, - participating in dispatch as described in the @ref basic_router - class documentation. Handlers run when the route matches, - regardless of HTTP method, and execute in registration order. - Error handlers and routers cannot be passed here. A new route - object is created even if the pattern already exists. - - @code - router.route("/status") - .head(check_headers) - .get(send_status) - .all(log_access); - @endcode - - @par Preconditions - @p `pattern` must be a valid path pattern; it must not be empty. - - @param pattern The path pattern to match. - @param h1 The first handler to add. - @param hn Additional handlers to add, invoked after @p h1 in - registration order. - */ - template - void all( - core::string_view pattern, - H1&& h1, HN&&... hn) - { - // If you get a compile error on this line it means that - // one or more of the provided types is not a valid handler. - // Error handlers and routers cannot be passed here. - BOOST_CORE_STATIC_ASSERT(handler_check<1, H1, HN...>::value); - this->route(pattern).all( - std::forward

(h1), std::forward(hn)...); - } - - /** Add one or more route handlers for a method and pattern. - - This registers regular handlers for the specified HTTP verb and - path pattern, participating in dispatch as described in the - @ref basic_router class documentation. Error handlers and - routers cannot be passed here. - - @param verb The known HTTP method to match. - @param pattern The path pattern to match. - @param h1 The first handler to add. - @param hn Additional handlers to add, invoked after @p h1 in - registration order. - */ - template - void add( - http_proto::method verb, - core::string_view pattern, - H1&& h1, HN&&... hn) - { - // If you get a compile error on this line it means that - // one or more of the provided types is not a valid handler. - // Error handlers and routers cannot be passed here. - BOOST_CORE_STATIC_ASSERT(handler_check<1, H1, HN...>::value); - this->route(pattern).add(verb, - std::forward

(h1), std::forward(hn)...); - } - - /** Add one or more route handlers for a method and pattern. - - This registers regular handlers for the specified HTTP verb and - path pattern, participating in dispatch as described in the - @ref basic_router class documentation. Error handlers and - routers cannot be passed here. - - @param verb The HTTP method string to match. - @param pattern The path pattern to match. - @param h1 The first handler to add. - @param hn Additional handlers to add, invoked after @p h1 in - registration order. - */ - template - void add( - core::string_view verb, - core::string_view pattern, - H1&& h1, HN&&... hn) - { - // If you get a compile error on this line it means that - // one or more of the provided types is not a valid handler. - // Error handlers and routers cannot be passed here. - BOOST_CORE_STATIC_ASSERT(handler_check<1, H1, HN...>::value); - this->route(pattern).add(verb, - std::forward

(h1), std::forward(hn)...); - } - - /** Return a fluent route for the specified path pattern. - - Adds a new route to the router for the given pattern. - A new route object is always created, even if another - route with the same pattern already exists. The returned - @ref fluent_route reference allows method-specific handler - registration (such as GET or POST) or catch-all handlers - with @ref fluent_route::all. - - @param pattern The path expression to match against request - targets. This may include parameters or wildcards following - the router's pattern syntax. May not be empty. - @return A fluent route interface for chaining handler registrations. - */ - auto - route( - core::string_view pattern) -> fluent_route - { - return fluent_route(*this, pattern); - } - - //-------------------------------------------- - - /** Dispatch a request to the appropriate handler. - - This runs the routing and error-dispatch logic for the given HTTP - method and target URL, as described in the @ref basic_router class - documentation. - - @par Thread Safety - This function may be called concurrently on the same object along - with other `const` member functions. Each concurrent invocation - must use distinct request and response objects. - - @param verb The HTTP method to match. This must not be - @ref http_proto::method::unknown. - @param url The full request target used for route matching. - @param req The request to pass to handlers. - @param res The response to pass to handlers. - @return The @ref route_result describing how routing completed. - @throws std::invalid_argument If @p verb is - @ref http_proto::method::unknown. - */ - auto - dispatch( - http_proto::method verb, - urls::url_view const& url, - Req& req, Res& res) const -> - route_result - { - if(verb == http_proto::method::unknown) - detail::throw_invalid_argument(); - return dispatch_impl(verb, - core::string_view(), url, req, res); - } - - /** Dispatch a request to the appropriate handler using a method string. - - This runs the routing and error-dispatch logic for the given HTTP - method string and target URL, as described in the @ref basic_router - class documentation. This overload is intended for method tokens - that are not represented by @ref http_proto::method. - - @par Thread Safety - This function may be called concurrently on the same object along - with other `const` member functions. Each concurrent invocation - must use distinct request and response objects. - - @param verb The HTTP method string to match. This must not be empty. - @param url The full request target used for route matching. - @param req The request to pass to handlers. - @param res The response to pass to handlers. - @return The @ref route_result describing how routing completed. - @throws std::invalid_argument If @p verb is empty. - */ - auto - dispatch( - core::string_view verb, - urls::url_view const& url, - Req& req, Res& res) -> - route_result - { - // verb cannot be empty - if(verb.empty()) - detail::throw_invalid_argument(); - return dispatch_impl( - http_proto::method::unknown, - verb, url, req, res); - } - - /** Resume dispatch after a detached handler. - - This continues routing after a previous call to @ref dispatch - returned @ref route::detach. It recreates the routing state and - resumes as if the handler that detached had instead returned - the specified @p ec from its body. The regular routing and - error-dispatch logic then proceeds as described in the - @ref basic_router class documentation. For example, if @p ec is - @ref route::next, the next matching handlers are invoked. - - @par Thread Safety - This function may be called concurrently on the same object along - with other `const` member functions. Each concurrent invocation - must use distinct request and response objects. - - @param req The request to pass to handlers. - @param res The response to pass to handlers. - @param rv The @ref route_result to resume with, as if returned - by the detached handler. - @return The @ref route_result describing how routing completed. - */ - auto - resume( - Req& req, Res& res, - route_result const& rv) const -> - route_result - { - return resume_impl(req, res, rv); - } - -private: - // wrapper for route handlers - template - struct handler_impl : any_handler - { - typename std::decay::type h; - - template - explicit handler_impl(Args&&... args) - : h(std::forward(args)...) - { - } - - std::size_t - count() const noexcept override - { - return count( - handler_type{}); - } - - route_result - invoke( - basic_request& req, - basic_response& res) const override - { - return invoke( - static_cast(req), - static_cast(res), - handler_type{}); - } - - private: - std::size_t count( - std::integral_constant) = delete; - - std::size_t count( - std::integral_constant) const noexcept - { - return 1; - } - - std::size_t count( - std::integral_constant) const noexcept - { - return 1; - } - - std::size_t count( - std::integral_constant) const noexcept - { - return 1 + h.count(); - } - - route_result invoke(Req&, Res&, - std::integral_constant) const = delete; - - // (Req, Res) - route_result invoke(Req& req, Res& res, - std::integral_constant) const - { - auto const& ec = static_cast< - basic_response const&>(res).ec_; - if(ec.failed()) - return beast2::route::next; - // avoid racing on res.resume_ - res.resume_ = res.pos_; - auto rv = h(req, res); - if(rv == beast2::route::detach) - return rv; - res.resume_ = 0; // revert - return rv; - } - - // (Req&, Res&, error_code) - route_result - invoke(Req& req, Res& res, - std::integral_constant) const - { - auto const& ec = static_cast< - basic_response const&>(res).ec_; - if(! ec.failed()) - return beast2::route::next; - // avoid racing on res.resume_ - res.resume_ = res.pos_; - auto rv = h(req, res, ec); - if(rv == beast2::route::detach) - return rv; - res.resume_ = 0; // revert - return rv; - } - - // any_router - route_result invoke(Req& req, Res& res, - std::integral_constant) const - { - auto const& ec = static_cast< - basic_response const&>(res).ec_; - if( res.resume_ > 0 || - ! ec.failed()) - return h.dispatch_impl(req, res); - return beast2::route::next; - } - }; - - template - struct handler_list_impl : handler_list - { - template - explicit handler_list_impl(HN&&... hn) - { - n = sizeof...(HN); - p = v; - assign<0>(std::forward(hn)...); - } - - private: - template - void assign(H1&& h1, HN&&... hn) - { - v[I] = handler_ptr(new handler_impl

( - std::forward

(h1))); - assign(std::forward(hn)...); - } - - template - void assign() - { - } - - handler_ptr v[N]; - }; - - template - static auto - make_handler_list(HN&&... hn) -> - handler_list_impl - { - return handler_list_impl( - std::forward(hn)...); - } - - void append(layer& e, - http_proto::method verb, - handler_list const& handlers) - { - add_impl(e, verb, handlers); - } -}; - -//----------------------------------------------- - -template -class basic_router:: - fluent_route -{ -public: - fluent_route(fluent_route const&) = default; - - /** Add handlers that apply to all HTTP methods. - - This registers regular handlers that run for any request matching - the route's pattern, regardless of HTTP method. Handlers are - appended to the route's handler sequence and are invoked in - registration order whenever a preceding handler returns - @ref route::next. Error handlers and routers cannot be passed here. - - This function returns a @ref fluent_route, allowing additional - method registrations to be chained. For example: - @code - router.route("/resource") - .all(log_request) - .get(show_resource) - .post(update_resource); - @endcode - - @param h1 The first handler to add. - @param hn Additional handlers to add, invoked after @p h1 in - registration order. - @return The @ref fluent_route for further chained registrations. - */ - template - auto all( - H1&& h1, HN&&... hn) -> - fluent_route - { - // If you get a compile error on this line it means that - // one or more of the provided types is not a valid handler. - // Error handlers and routers cannot be passed here. - BOOST_CORE_STATIC_ASSERT(handler_check<1, H1, HN...>::value); - owner_.add_impl(e_, core::string_view(), make_handler_list( - std::forward

(h1), std::forward(hn)...)); - return *this; - } - - /** Add handlers for a specific HTTP method. - - This registers regular handlers for the given method on the - current route, participating in dispatch as described in the - @ref basic_router class documentation. Handlers are appended - to the route's handler sequence and invoked in registration - order whenever a preceding handler returns @ref route::next. - Error handlers and routers cannot be passed here. - - @param verb The HTTP method to match. - @param h1 The first handler to add. - @param hn Additional handlers to add, invoked after @p h1 in - registration order. - @return The @ref fluent_route for further chained registrations. - */ - template - auto add( - http_proto::method verb, - H1&& h1, HN&&... hn) -> - fluent_route - { - // If you get a compile error on this line it means that - // one or more of the provided types is not a valid handler. - // Error handlers and routers cannot be passed here. - BOOST_CORE_STATIC_ASSERT(handler_check<1, H1, HN...>::value); - owner_.add_impl(e_, verb, make_handler_list( - std::forward

(h1), std::forward(hn)...)); - return *this; - } - - /** Add handlers for a method name. - - This registers regular handlers for the given HTTP method string - on the current route, participating in dispatch as described in - the @ref basic_router class documentation. This overload is - intended for methods not represented by @ref http_proto::method. - Handlers are appended to the route's handler sequence and invoked - in registration order whenever a preceding handler returns - @ref route::next. - - @par Constraints - @li Each handler must be a regular handler; error handlers and - routers cannot be passed. - - @param verb The HTTP method string to match. - @param h1 The first handler to add. - @param hn Additional handlers to add, invoked after @p h1 in - registration order. - @return The @ref fluent_route for further chained registrations. - */ - template - auto add( - core::string_view verb, - H1&& h1, HN&&... hn) -> - fluent_route - { - // If you get a compile error on this line it means that - // one or more of the provided types is not a valid handler. - // Error handlers and routers cannot be passed here. - BOOST_CORE_STATIC_ASSERT(handler_check<1, H1, HN...>::value); - owner_.add_impl(e_, verb, make_handler_list( - std::forward

(h1), std::forward(hn)...)); - return *this; - } - -private: - friend class basic_router; - fluent_route( - basic_router& owner, - core::string_view pattern) - : e_(owner.new_layer(pattern)) - , owner_(owner) - { - } - - layer& e_; - basic_router& owner_; -}; - -} // beast2 -} // boost - -#endif diff --git a/include/boost/beast2/server/http_stream.hpp b/include/boost/beast2/server/http_stream.hpp index 9e765e77..349668fe 100644 --- a/include/boost/beast2/server/http_stream.hpp +++ b/include/boost/beast2/server/http_stream.hpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -26,6 +25,7 @@ #include #include #include +#include #include #include @@ -47,7 +47,7 @@ namespace beast2 { */ template class http_stream - : private detacher::owner + : private http_proto::detacher::owner { public: /** Constructor. @@ -81,7 +81,7 @@ class http_stream The stream must be in a connected, correct state for a new session. */ - void on_stream_begin(acceptor_config const& config); + void on_stream_begin(http_proto::acceptor_config const& config); private: void do_read(); @@ -89,15 +89,15 @@ class http_stream system::error_code ec, std::size_t bytes_transferred); void on_headers(); - void do_dispatch(route_result rv = {}); - void do_respond(route_result rv); + void do_dispatch(http_proto::route_result rv = {}); + void do_respond(http_proto::route_result rv); void do_write(); void on_write( system::error_code const& ec, std::size_t bytes_transferred); void on_complete(); - resumer do_detach() override; - void do_resume(route_result const& ec) override; + http_proto::resumer do_detach() override; + void do_resume(http_proto::route_result const& ec) override; void do_close(); void do_fail(core::string_view s, system::error_code const& ec); @@ -116,12 +116,12 @@ class http_stream AsyncStream& stream_; router_asio routes_; any_lambda close_; - acceptor_config const* pconfig_ = nullptr; + http_proto::acceptor_config const* pconfig_ = nullptr; using work_guard = asio::executor_work_guard().get_executor())>; std::unique_ptr pwg_; - Request req_; + http_proto::Request req_; ResponseAsio res_; }; @@ -178,7 +178,7 @@ http_stream( req_.parser = http_proto::request_parser(app); res_.serializer = http_proto::serializer(app); - res_.detach = detacher(*this); + res_.detach = http_proto::detacher(*this); } // called to start a new HTTP session. @@ -187,7 +187,7 @@ template void http_stream:: on_stream_begin( - acceptor_config const& config) + http_proto::acceptor_config const& config) { pconfig_ = &config; @@ -268,7 +268,8 @@ on_headers() template void http_stream:: -do_dispatch(route_result rv) +do_dispatch( + http_proto::route_result rv) { if(! rv.failed()) { @@ -289,16 +290,16 @@ template void http_stream:: do_respond( - route_result rv) + http_proto::route_result rv) { - BOOST_ASSERT(rv != route::next_route); + BOOST_ASSERT(rv != http_proto::route::next_route); - if(rv == route::close) + if(rv == http_proto::route::close) { return do_close(); } - if(rv == route::complete) + if(rv == http_proto::route::complete) { // VFALCO what if the connection was closed or keep-alive=false? // handler sendt the response? @@ -306,7 +307,7 @@ do_respond( return on_write(system::error_code(), 0); } - if(rv == route::detach) + if(rv == http_proto::route::detach) { // didn't call res.detach()? if(! pwg_) @@ -314,7 +315,7 @@ do_respond( return; } - if(rv == route::next) + if(rv == http_proto::route::next) { // unhandled request auto const status = http_proto::status::not_found; @@ -322,11 +323,11 @@ do_respond( //res_.message.set_keep_alive(false); // VFALCO? res_.set_body(http_proto::to_string(status)); } - else if(rv != route::send) + else if(rv != http_proto::route::send) { // error message of last resort BOOST_ASSERT(rv.failed()); - BOOST_ASSERT(! is_route_result(rv)); + BOOST_ASSERT(! http_proto::is_route_result(rv)); res_.status(http_proto::status::internal_server_error); std::string s; format_to(s, "An internal server error occurred: {}", rv.message()); @@ -377,7 +378,7 @@ template auto http_stream:: do_detach() -> - resumer + http_proto::resumer { BOOST_ASSERT(stream_.get_executor().running_in_this_thread()); @@ -387,14 +388,15 @@ do_detach() -> // VFALCO cancel timer - return resumer(*this); + return http_proto::resumer(*this); } // called by resume(rv) template void http_stream:: -do_resume(route_result const& rv) +do_resume( + http_proto::route_result const& rv) { asio::dispatch( stream_.get_executor(), diff --git a/include/boost/beast2/server/plain_worker.hpp b/include/boost/beast2/server/plain_worker.hpp index f6ebb13e..b0cc47a4 100644 --- a/include/boost/beast2/server/plain_worker.hpp +++ b/include/boost/beast2/server/plain_worker.hpp @@ -36,7 +36,7 @@ class plain_worker using socket_type = asio::basic_stream_socket; using stream_type = socket_type; - using acceptor_config = beast2::acceptor_config; + using acceptor_config = http_proto::acceptor_config; template plain_worker( diff --git a/include/boost/beast2/server/route_handler.hpp b/include/boost/beast2/server/route_handler.hpp deleted file mode 100644 index 2b1be58f..00000000 --- a/include/boost/beast2/server/route_handler.hpp +++ /dev/null @@ -1,257 +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_ROUTE_HANDLER_HPP -#define BOOST_BEAST2_SERVER_ROUTE_HANDLER_HPP - -#include -#include -#include -#include // VFALCO forward declare? -#include // VFALCO forward declare? -#include // VFALCO forward declare? -#include // VFALCO forward declare? -#include -#include -#include - -namespace boost { -namespace beast2 { - -struct acceptor_config -{ - bool is_ssl; - bool is_admin; -}; - -//----------------------------------------------- - -/** Request object for HTTP route handlers -*/ -struct Request : basic_request -{ - /** The complete request target - - This is the parsed directly from the start - line contained in the HTTP request and is - never modified. - */ - urls::url_view url; - - /** The HTTP request message - */ - http_proto::request message; - - /** The HTTP request parser - This can be used to take over reading the body. - */ - http_proto::request_parser parser; - - /** A container for storing arbitrary data associated with the request. - This starts out empty for each new request. - */ - capy::datastore data; -}; - -//----------------------------------------------- - -/** Response object for HTTP route handlers -*/ -struct BOOST_SYMBOL_VISIBLE - Response : basic_response -{ - /** The HTTP response message - */ - http_proto::response message; - - /** The HTTP response serializer - */ - http_proto::serializer serializer; - - /** The detacher for this session. - This can be used to detach from the - session and take over managing the - connection. - */ - detacher detach; - - /** A container for storing arbitrary data associated with the session. - - This starts out empty for each new session. - */ - capy::datastore data; - - /** Destructor. - */ - BOOST_BEAST2_DECL virtual ~Response(); - - /** Reset the object for a new request. - This clears any state associated with - the previous request, preparing the object - for use with a new request. - */ - BOOST_BEAST2_DECL void reset(); - - /** Set the status code of the response. - @par Example - @code - res.status(http_proto::status::not_found); - @endcode - @param code The status code to set. - @return A reference to this response. - */ - BOOST_BEAST2_DECL - Response& - status(http_proto::status code); - - BOOST_BEAST2_DECL - Response& - set_body(std::string s); - - // VFALCO this doc isn't quite right because it doesn't explain - // the possibility that post will return the final result immediately, - // and it needs to remind the user that calling a function which - // returns route_result means they have to return the value right away - // without doing anything else. - // - // VFALCO we have to detect calls to detach inside `f` and throw - // - /** Submit cooperative work. - - This function detaches the current handler from the session, - and immediately invokes the specified function object @p f. - When the function returns normally, the function object is - placed into an implementation-defined work queue to be invoked - again. Otherwise, if the function calls `resume(rv)` then the - session is resumed and behaves as if the original route handler - had returned the value `rv`. - - When the function object is invoked, it runs in the same context - as the original handler invocation. If the function object - attempts to call @ref post again, or attempts to call @ref detach, - an exception is thrown. - - The function object @p f must have this equivalent signature: - @code - void ( resumer resume ); - @endcode - - @param f The function object to invoke. - @param c The continuation function to be invoked when f finishes. - */ - template - auto - post(F&& f) -> route_result; - -protected: - /** A task to be invoked later - */ - struct task - { - virtual ~task() = default; - - /** Invoke the task. - - @return true if the task resumed the session. - */ - virtual bool invoke() = 0; - }; - - /** Post task_ to be invoked later - - Subclasses must schedule task_ to be invoked at an unspecified - point in the future. - */ - BOOST_BEAST2_DECL - virtual void do_post(); - - std::unique_ptr task_; -}; - -//----------------------------------------------- - -template -auto -Response:: -post(F&& f) -> route_result -{ - // task already posted - if(task_) - detail::throw_invalid_argument(); - - struct BOOST_SYMBOL_VISIBLE - immediate : detacher::owner - { - route_result rv; - bool set = false; - void do_resume( - route_result const& rv_) override - { - rv = rv_; - set = true; - } - }; - - class BOOST_SYMBOL_VISIBLE model - : public task - , public detacher::owner - { - public: - model(Response& res, - F&& f, resumer resume) - : res_(res) - , f_(std::forward(f)) - , resume_(resume) - { - } - - bool invoke() override - { - resumed_ = false; - // VFALCO analyze exception safety - f_(resumer(*this)); - return resumed_; - } - - void do_resume( - route_result const& rv) override - { - resumed_ = true; - resumer resume(resume_); - res_.task_.reset(); // destroys *this - resume(rv); - } - - private: - Response& res_; - typename std::decay::type f_; - resumer resume_; - bool resumed_; - }; - - // first call - immediate impl; - f(resumer(impl)); - if(impl.set) - return impl.rv; - - return detach( - [&](resumer resume) - { - task_ = std::unique_ptr(new model( - *this, std::forward(f), resume)); - do_post(); - }); -} - - -} // beast2 -} // boost - -#endif diff --git a/include/boost/beast2/server/route_handler_asio.hpp b/include/boost/beast2/server/route_handler_asio.hpp index 6a119e0d..c266b5ac 100644 --- a/include/boost/beast2/server/route_handler_asio.hpp +++ b/include/boost/beast2/server/route_handler_asio.hpp @@ -11,7 +11,7 @@ #define BOOST_BEAST2_SERVER_ROUTE_HANDLER_ASIO_HPP #include -#include +#include #include #include @@ -21,7 +21,7 @@ namespace beast2 { /** Response object for Asio HTTP route handlers */ template -class ResponseAsio : public Response +class ResponseAsio : public http_proto::Response { public: using stream_type = typename std::decay::type; diff --git a/include/boost/beast2/server/router.hpp b/include/boost/beast2/server/router.hpp index f6eb7061..3e2c32a6 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 +#include namespace boost { namespace beast2 { /** The sans-IO router type */ -using router = basic_router; +using router = http_proto::basic_router; } // beast2 } // boost diff --git a/include/boost/beast2/server/router_asio.hpp b/include/boost/beast2/server/router_asio.hpp index e43f1d44..e1efcd9b 100644 --- a/include/boost/beast2/server/router_asio.hpp +++ b/include/boost/beast2/server/router_asio.hpp @@ -11,7 +11,7 @@ #define BOOST_BEAST2_SERVER_ROUTER_ASIO_HPP #include -#include +#include #include namespace boost { @@ -20,7 +20,8 @@ namespace beast2 { /** The Asio-aware router type */ template -using router_asio = basic_router>; +using router_asio = http_proto::basic_router< + http_proto::Request, ResponseAsio>; } // beast2 } // boost diff --git a/include/boost/beast2/server/router_types.hpp b/include/boost/beast2/server/router_types.hpp deleted file mode 100644 index 49f846fc..00000000 --- a/include/boost/beast2/server/router_types.hpp +++ /dev/null @@ -1,351 +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_ROUTER_STATE_HPP -#define BOOST_BEAST2_SERVER_ROUTER_STATE_HPP - -#include -#include -#include -#include -#include -#include -#include - -namespace boost { -namespace beast2 { - -/** The result type returned by a route handler. - - Route handlers use this type to report errors that prevent - normal processing. A handler must never return a non-failing - (i.e. `ec.failed() == false`) value. Returning a default-constructed - `system::error_code` is disallowed; handlers that complete - successfully must instead return a valid @ref route result. -*/ -using route_result = system::error_code; - -/** Route handler return values - - These values determine how the caller proceeds after invoking - a route handler. Each enumerator represents a distinct control - action—whether the request was handled, should continue to the - next route, transfers ownership of the session, or signals that - the connection should be closed. -*/ -enum class route -{ - /** The handler requests that the connection be closed. - - No further requests will be processed. The caller should - close the connection once the current response, if any, - has been sent. - */ - close = 1, - - /** The handler completed the request. - - The response has been fully transmitted, and no further - handlers or routes will be invoked. The caller should continue - by either reading the next request on a persistent connection - or closing the session if it is not keep-alive. - */ - complete, - - /** The handler detached from the session. - - Ownership of the session or stream has been transferred to - the handler. The caller will not perform further I/O or manage - the connection after this return value. - */ - detach, - - /** The handler declined to process the request. - - The handler chose not to generate a response. The caller - continues invoking the remaining handlers in the same route - until one returns @ref send. If none do, the caller proceeds - to evaluate the next matching route. - - This value is returned by @ref basic_router::dispatch if no - handlers in any route handle the request. - */ - next, - - /** The handler declined the current route. - - The handler wishes to skip any remaining handlers in the - current route and move on to the next matching route. The - caller stops invoking handlers in this route and resumes - evaluation with the next candidate route. - */ - next_route, - - /** The request was handled. - - The route handler processed the request and prepared - the response serializer. The caller will send the response - before reading the next request or closing the connection. - */ - send -}; - -//------------------------------------------------ - -} // beast2 -namespace system { -template<> -struct is_error_code_enum< - ::boost::beast2::route> -{ - static bool const value = true; -}; -} // system -namespace beast2 { - -namespace detail { -struct BOOST_SYMBOL_VISIBLE route_cat_type - : system::error_category -{ - BOOST_BEAST2_DECL const char* name() const noexcept override; - BOOST_BEAST2_DECL std::string message(int) const override; - BOOST_BEAST2_DECL char const* message( - int, char*, std::size_t) const noexcept override; - BOOST_SYSTEM_CONSTEXPR route_cat_type() - : error_category(0x51c90d393754ecdf ) - { - } -}; -BOOST_BEAST2_DECL extern route_cat_type route_cat; -} // detail - -inline -BOOST_SYSTEM_CONSTEXPR -system::error_code -make_error_code(route ev) noexcept -{ - return system::error_code{static_cast< - std::underlying_type::type>(ev), - detail::route_cat}; -} - -/** Return true if `rv` is a route result. - - A @ref route_result can hold any error code, - and this function returns `true` only if `rv` - holds a value from the @ref route enumeration. -*/ -inline bool is_route_result( - route_result rv) noexcept -{ - return &rv.category() == &detail::route_cat; -} - -//------------------------------------------------ - -class resumer; - -/** Function to detach a route handler from its session - - This holds an reference to an implementation - which detaches the handler from its session. -*/ -class detacher -{ -public: - /** Base class of the implementation - */ - struct BOOST_SYMBOL_VISIBLE - owner - { - BOOST_BEAST2_DECL virtual resumer do_detach(); - virtual void do_resume(route_result const&) = 0; - }; - - detacher() = default; - detacher(detacher const&) = default; - detacher& operator=(detacher const&) = default; - - explicit - detacher( - owner& who) noexcept - : p_(&who) - { - } - - /** Detach and invoke the given function - - The function will be invoked with this equivalent signature: - @code - void( resumer ); - @endcode - - @return A @ref route_result equal to @ref route::detach - */ - template - route_result - operator()(F&& f); - -private: - friend resumer; - // Clang doesn't consider uninstantiated templates - // when checking for unused private fields. - owner* p_ - #if defined(__clang__) - __attribute__((unused)) - #endif - = nullptr; -}; - -//------------------------------------------------ - -/** Function to resume a route handler's session - - This holds a reference to an implementation - which resumes the handler's session. The resume - function is returned by calling @ref detach. -*/ -class resumer -{ -public: - /** Constructor - - Default constructed resume functions will - be empty. An exception is thrown when - attempting to invoke an empty object. - */ - resumer() = default; - - /** Constructor - - Copies of resume functions behave the same - as the original - */ - resumer(resumer const&) = default; - - /** Assignment - - Copies of resume functions behave the same - as the original - */ - resumer& operator=(resumer const&) = default; - - /** Constructor - */ - explicit - resumer( - detacher::owner& who) noexcept - : p_(&who) - { - } - - /** Resume the session - - A session is resumed as if the detached - handler returned the route result in @p rv. - - @param ec The error code to resume with. - - @throw std::invalid_argument If the object is empty. - */ - void operator()( - route_result const& rv) const - { - if(! p_) - detail::throw_invalid_argument(); - p_->do_resume(rv); - } - -private: - // Clang doesn't consider uninstantiated templates - // when checking for unused private fields. - detacher::owner* p_ - #if defined(__clang__) - __attribute__((unused)) - #endif - = nullptr; -}; - -template -auto -detacher:: -operator()(F&& f) -> - route_result -{ - if(! p_) - detail::throw_logic_error(); - std::forward(f)(p_->do_detach()); - return route::detach; -} - -//------------------------------------------------ - -namespace detail { -class any_router; -} // detail -template -class basic_router; - -/** Base class for request objects - - This is a required public base for any `Request` - type used with @ref basic_router. -*/ -class basic_request -{ -public: - /** The mount path of the current router - - This is the portion of the request path - which was matched to select the handler. - The remaining portion is available in - @ref path. - */ - core::string_view base_path; - - /** The current pathname, relative to the base path - */ - core::string_view path; - -private: - friend class /*detail::*/any_router; - struct match_result; - http_proto::method verb_ = - http_proto::method::unknown; - std::string verb_str_; - std::string decoded_path_; - bool addedSlash_ = false; - bool case_sensitive = false; - bool strict = false; -}; - -//----------------------------------------------- - -/** Base class for response objects - - This is a required public base for any `Response` - type used with @ref basic_router. -*/ -class basic_response -{ -private: - friend class /*detail::*/any_router; - template - friend class basic_router; - - std::size_t pos_ = 0; - std::size_t resume_ = 0; - system::error_code ec_; - unsigned int opt_ = 0; -}; - -} // beast2 -} // boost - -#endif diff --git a/include/boost/beast2/server/serve_redirect.hpp b/include/boost/beast2/server/serve_redirect.hpp index 61cf9356..36230782 100644 --- a/include/boost/beast2/server/serve_redirect.hpp +++ b/include/boost/beast2/server/serve_redirect.hpp @@ -11,7 +11,7 @@ #define BOOST_BEAST2_SERVER_SERVE_REDIRECT_HPP #include -#include +#include namespace boost { namespace beast2 { @@ -19,8 +19,10 @@ namespace beast2 { struct serve_redirect { BOOST_BEAST2_DECL - system::error_code - operator()(Request&, Response&) const; + http_proto::route_result + operator()( + http_proto::Request&, + http_proto::Response&) const; }; } // beast2 diff --git a/include/boost/beast2/server/serve_static.hpp b/include/boost/beast2/server/serve_static.hpp index bba18c16..a4376d67 100644 --- a/include/boost/beast2/server/serve_static.hpp +++ b/include/boost/beast2/server/serve_static.hpp @@ -11,7 +11,7 @@ #define BOOST_BEAST2_SERVER_SERVE_STATIC_HPP #include -#include +#include namespace boost { namespace beast2 { @@ -164,7 +164,9 @@ struct serve_static indicate the request was not handled. */ BOOST_BEAST2_DECL - system::error_code operator()(Request&, Response&) const; + system::error_code operator()( + http_proto::Request&, + http_proto::Response&) const; private: struct impl; diff --git a/src/server/basic_router.cpp b/src/server/basic_router.cpp deleted file mode 100644 index bfe79b10..00000000 --- a/src/server/basic_router.cpp +++ /dev/null @@ -1,876 +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 "src/server/route_rule.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace boost { -namespace beast2 { - -//namespace detail { - -// VFALCO Temporarily here until Boost.URL merges the fix -static -bool -ci_is_equal( - core::string_view s0, - core::string_view s1) noexcept -{ - auto n = s0.size(); - if(s1.size() != n) - return false; - auto p1 = s0.data(); - auto p2 = s1.data(); - char a, b; - // fast loop - while(n--) - { - a = *p1++; - b = *p2++; - if(a != b) - goto slow; - } - return true; - do - { - a = *p1++; - b = *p2++; - slow: - if( grammar::to_lower(a) != - grammar::to_lower(b)) - return false; - } - while(n--); - return true; -} - - -//------------------------------------------------ -/* - -pattern target path(use) path(get) -------------------------------------------------- -/ / / -/ /api /api -/api /api / /api -/api /api/ / /api/ -/api /api/ / no-match strict -/api /api/v0 /v0 no-match -/api/ /api / /api -/api/ /api / no-match strict -/api/ /api/ / /api/ -/api/ /api/v0 /v0 no-match - -*/ - -//------------------------------------------------ - -any_router::any_handler::~any_handler() {} - -//------------------------------------------------ - -/* -static -void -make_lower(std::string& s) -{ - for(auto& c : s) - c = grammar::to_lower(c); -} -*/ - -// decode all percent escapes -static -std::string -pct_decode( - urls::pct_string_view s) -{ - std::string result; - core::string_view sv(s); - result.reserve(s.size()); - auto it = sv.data(); - auto const end = it + sv.size(); - for(;;) - { - if(it == end) - break; - if(*it != '%') - { - result.push_back(*it++); - continue; - } - ++it; -#if 0 - // pct_string_view can never have invalid pct-encodings - if(it == end) - goto invalid; -#endif - auto d0 = urls::grammar::hexdig_value(*it++); -#if 0 - // pct_string_view can never have invalid pct-encodings - if( d0 < 0 || - it == end) - goto invalid; -#endif - auto d1 = urls::grammar::hexdig_value(*it++); -#if 0 - // pct_string_view can never have invalid pct-encodings - if(d1 < 0) - goto invalid; -#endif - result.push_back(d0 * 16 + d1); - } - return result; -#if 0 -invalid: - // can't get here, as received a pct_string_view - detail::throw_invalid_argument(); -#endif -} - -// decode all percent escapes except slashes '/' and '\' -static -std::string -pct_decode_path( - urls::pct_string_view s) -{ - std::string result; - core::string_view sv(s); - result.reserve(s.size()); - auto it = sv.data(); - auto const end = it + sv.size(); - for(;;) - { - if(it == end) - break; - if(*it != '%') - { - result.push_back(*it++); - continue; - } - ++it; -#if 0 - // pct_string_view can never have invalid pct-encodings - if(it == end) - goto invalid; -#endif - auto d0 = urls::grammar::hexdig_value(*it++); -#if 0 - // pct_string_view can never have invalid pct-encodings - if( d0 < 0 || - it == end) - goto invalid; -#endif - auto d1 = urls::grammar::hexdig_value(*it++); -#if 0 - // pct_string_view can never have invalid pct-encodings - if(d1 < 0) - goto invalid; -#endif - char c = d0 * 16 + d1; - if( c != '/' && - c != '\\') - { - result.push_back(c); - continue; - } - result.append(it - 3, 3); - } - return result; -#if 0 -invalid: - // can't get here, as received a pct_string_view - detail::throw_invalid_argument(); -#endif -} - -//------------------------------------------------ - -//} // detail - -struct basic_request:: - match_result -{ - void adjust_path( - basic_request& req, - std::size_t n) - { - n_ = n; - if(n_ == 0) - return; - req.base_path = { - req.base_path.data(), - req.base_path.size() + n_ }; - if(n_ < req.path.size()) - { - req.path.remove_prefix(n_); - } - else - { - // append a soft slash - req.path = { req.decoded_path_.data() + - req.decoded_path_.size() - 1, 1}; - BOOST_ASSERT(req.path == "/"); - } - } - - void restore_path( - basic_request& req) - { - if( n_ > 0 && - req.addedSlash_ && - req.path.data() == - req.decoded_path_.data() + - req.decoded_path_.size() - 1) - { - // remove soft slash - req.path = { - req.base_path.data() + - req.base_path.size(), 0 }; - } - req.base_path.remove_suffix(n_); - req.path = { - req.path.data() - n_, - req.path.size() + n_ }; - } - -private: - std::size_t n_ = 0; // chars moved from path to base_path -}; - -//------------------------------------------------ - -//namespace detail { - -// Matches a path against a pattern -struct any_router::matcher -{ - bool const end; // false for middleware - - matcher( - core::string_view pat, - bool end_) - : end(end_) - , decoded_pat_( - [&pat] - { - auto s = pct_decode(pat); - if( s.size() > 1 - && s.back() == '/') - s.pop_back(); - return s; - }()) - , slash_(pat == "/") - { - if(! slash_) - pv_ = grammar::parse( - decoded_pat_, path_rule).value(); - } - - /** Return true if req.path is a match - */ - bool operator()( - basic_request& req, - match_result& mr) const - { - BOOST_ASSERT(! req.path.empty()); - if( slash_ && ( - ! end || - req.path == "/")) - { - // params = {}; - mr.adjust_path(req, 0); - return true; - } - auto it = req.path.data(); - auto pit = pv_.segs.begin(); - auto const end_ = it + req.path.size(); - auto const pend = pv_.segs.end(); - while(it != end_ && pit != pend) - { - // prefix has to match - auto s = core::string_view(it, end_); - if(! req.case_sensitive) - { - if(pit->prefix.size() > s.size()) - return false; - s = s.substr(0, pit->prefix.size()); - //if(! grammar::ci_is_equal(s, pit->prefix)) - if(! ci_is_equal(s, pit->prefix)) - return false; - } - else - { - if(! s.starts_with(pit->prefix)) - return false; - } - it += pit->prefix.size(); - ++pit; - } - if(end) - { - // require full match - if( it != end_ || - pit != pend) - return false; - } - else if(pit != pend) - { - return false; - } - // number of matching characters - auto const n = it - req.path.data(); - mr.adjust_path(req, n); - return true; - } - -private: - stable_string decoded_pat_; - path_rule_t::value_type pv_; - bool slash_; -}; - -//------------------------------------------------ - -struct any_router::layer -{ - struct entry - { - handler_ptr handler; - - // only for end routes - http_proto::method verb = - http_proto::method::unknown; - std::string verb_str; - bool all; - - explicit entry( - handler_ptr h) noexcept - : handler(std::move(h)) - , all(true) - { - } - - entry( - http_proto::method verb_, - handler_ptr h) noexcept - : handler(std::move(h)) - , verb(verb_) - , all(false) - { - BOOST_ASSERT(verb != - http_proto::method::unknown); - } - - entry( - core::string_view verb_str_, - handler_ptr h) noexcept - : handler(std::move(h)) - , verb(http_proto::string_to_method(verb_str_)) - , all(false) - { - if(verb != http_proto::method::unknown) - return; - verb_str = verb_str_; - } - - bool match_method( - basic_request const& req) const noexcept - { - if(all) - return true; - if(verb != http_proto::method::unknown) - return req.verb_ == verb; - if(req.verb_ != http_proto::method::unknown) - return false; - return req.verb_str_ == verb_str; - } - }; - - matcher match; - std::vector entries; - - // middleware layer - layer( - core::string_view pat, - handler_list handlers) - : match(pat, false) - { - entries.reserve(handlers.n); - for(std::size_t i = 0; i < handlers.n; ++i) - entries.emplace_back(std::move(handlers.p[i])); - } - - // route layer - explicit layer( - core::string_view pat) - : match(pat, true) - { - } - - std::size_t count() const noexcept - { - std::size_t n = 0; - for(auto const& e : entries) - n += e.handler->count(); - return n; - } -}; - -//------------------------------------------------ - -struct any_router::impl -{ - std::atomic refs{1}; - std::vector layers; - opt_flags opt; - - explicit impl( - opt_flags opt_) noexcept - : opt(opt_) - { - } -}; - -//------------------------------------------------ - -any_router:: -~any_router() -{ - if(! impl_) - return; - if(--impl_->refs == 0) - delete impl_; -} - -any_router:: -any_router( - opt_flags opt) - : impl_(new impl(opt)) -{ -} - -any_router:: -any_router(any_router&& other) noexcept - :impl_(other.impl_) -{ - other.impl_ = nullptr; -} - -any_router:: -any_router(any_router const& other) noexcept -{ - impl_ = other.impl_; - ++impl_->refs; -} - -any_router& -any_router:: -operator=(any_router&& other) noexcept -{ - auto p = impl_; - impl_ = other.impl_; - other.impl_ = nullptr; - if(p && --p->refs == 0) - delete p; - return *this; -} - -any_router& -any_router:: -operator=(any_router const& other) noexcept -{ - auto p = impl_; - impl_ = other.impl_; - ++impl_->refs; - if(p && --p->refs == 0) - delete p; - return *this; -} - -//------------------------------------------------ - -std::size_t -any_router:: -count() const noexcept -{ - std::size_t n = 0; - for(auto const& i : impl_->layers) - for(auto const& e : i.entries) - n += e.handler->count(); - return n; -} - -auto -any_router:: -new_layer( - core::string_view pattern) -> layer& -{ - // the pattern must not be empty - if(pattern.empty()) - detail::throw_invalid_argument(); - // delete the last route if it is empty, - // this happens if they call route() without - // adding anything - if(! impl_->layers.empty() && - impl_->layers.back().entries.empty()) - impl_->layers.pop_back(); - impl_->layers.emplace_back(pattern); - return impl_->layers.back(); -}; - -void -any_router:: -add_impl( - core::string_view pattern, - handler_list const& handlers) -{ - if( pattern.empty()) - pattern = "/"; - impl_->layers.emplace_back( - pattern, std::move(handlers)); -} - -void -any_router:: -add_impl( - layer& e, - http_proto::method verb, - handler_list const& handlers) -{ - // cannot be unknown - if(verb == http_proto::method::unknown) - detail::throw_invalid_argument(); - - e.entries.reserve(e.entries.size() + handlers.n); - for(std::size_t i = 0; i < handlers.n; ++i) - e.entries.emplace_back(verb, - std::move(handlers.p[i])); -} - -void -any_router:: -add_impl( - layer& e, - core::string_view verb_str, - handler_list const& handlers) -{ - e.entries.reserve(e.entries.size() + handlers.n); - - if(verb_str.empty()) - { - // all - for(std::size_t i = 0; i < handlers.n; ++i) - e.entries.emplace_back( - std::move(handlers.p[i])); - return; - } - - // possibly custom string - for(std::size_t i = 0; i < handlers.n; ++i) - e.entries.emplace_back(verb_str, - std::move(handlers.p[i])); -} - -//------------------------------------------------ - -auto -any_router:: -resume_impl( - basic_request& req, basic_response& res, - route_result ec) const -> - route_result -{ - BOOST_ASSERT(res.resume_ > 0); - if( ec == route::send || - ec == route::close || - ec == route::complete) - return ec; - if(! is_route_result(ec)) - { - // must indicate failure - if(! ec.failed()) - detail::throw_invalid_argument(); - } - - // restore base_path and path - req.base_path = { req.decoded_path_.data(), 0 }; - req.path = req.decoded_path_; - if(req.addedSlash_) - req.path.remove_suffix(1); - - // resume_ was set in the handler's wrapper - BOOST_ASSERT(res.resume_ == res.pos_); - res.pos_ = 0; - res.ec_ = ec; - return do_dispatch(req, res); -} - -// top-level dispatch that gets called first -route_result -any_router:: -dispatch_impl( - http_proto::method verb, - core::string_view verb_str, - urls::url_view const& url, - basic_request& req, - basic_response& res) const -{ - // VFALCO we could reuse the string storage by not clearing them - // set req.case_sensitive, req.strict to default of false - req = {}; - if(verb == http_proto::method::unknown) - { - BOOST_ASSERT(! verb_str.empty()); - verb = http_proto::string_to_method(verb_str); - if(verb == http_proto::method::unknown) - req.verb_str_ = verb_str; - } - else - { - BOOST_ASSERT(verb_str.empty()); - } - req.verb_ = verb; - // VFALCO use reusing-StringToken - req.decoded_path_ = - pct_decode_path(url.encoded_path()); - BOOST_ASSERT(! req.decoded_path_.empty()); - req.base_path = { req.decoded_path_.data(), 0 }; - req.path = req.decoded_path_; - if(req.decoded_path_.back() != '/') - { - req.decoded_path_.push_back('/'); - req.addedSlash_ = true; - } - BOOST_ASSERT(req.case_sensitive == false); - BOOST_ASSERT(req.strict == false); - - res = {}; - - // we cannot do anything after do_dispatch returns, - // other than return the route_result, or else we - // could race with the detached operation trying to resume. - return do_dispatch(req, res); -} - -// recursive dispatch -route_result -any_router:: -dispatch_impl( - basic_request& req, - basic_response& res) const -{ - // options are recursive and need to be restored on - // exception or when returning to a calling router. - struct option_saver - { - option_saver( - basic_request& req) noexcept - : req_(&req) - , case_sensitive_(req.case_sensitive) - , strict_(req.strict) - { - } - - ~option_saver() - { - if(! req_) - return; - req_->case_sensitive = case_sensitive_; - req_->strict = strict_; - }; - - void cancel() noexcept - { - req_ = nullptr; - } - - private: - basic_request* req_; - bool case_sensitive_; - bool strict_; - }; - - option_saver restore_options(req); - - // inherit or apply options - if((impl_->opt & 2) != 0) - req.case_sensitive = true; - else if((impl_->opt & 4) != 0) - req.case_sensitive = false; - - if((impl_->opt & 8) != 0) - req.strict = true; - else if((impl_->opt & 16) != 0) - req.strict = false; - - // nested routers count as 1 call - //++res.pos_; - - match_result mr; - for(auto const& i : impl_->layers) - { - if(res.resume_ > 0) - { - auto const n = i.count(); // handlers in layer - if(res.pos_ + n < res.resume_) - { - res.pos_ += n; // skip layer - continue; - } - // repeat match to recreate the stack - bool is_match = i.match(req, mr); - BOOST_ASSERT(is_match); - (void)is_match; - } - else - { - if(i.match.end && res.ec_.failed()) - { - // routes can't have error handlers - res.pos_ += i.count(); // skip layer - continue; - } - if(! i.match(req, mr)) - { - // not a match - res.pos_ += i.count(); // skip layer - continue; - } - } - for(auto it = i.entries.begin(); - it != i.entries.end(); ++it) - { - auto const& e(*it); - if(res.resume_) - { - auto const n = e.handler->count(); - if(res.pos_ + n < res.resume_) - { - res.pos_ += n; // skip entry - continue; - } - BOOST_ASSERT(e.match_method(req)); - } - else if(i.match.end) - { - // check verb for match - if(! e.match_method(req)) - { - res.pos_ += e.handler->count(); // skip entry - continue; - } - } - - route_result rv; - // increment before invoke - ++res.pos_; - if(res.pos_ != res.resume_) - { - // call the handler - rv = e.handler->invoke(req, res); - // res.pos_ can be incremented further - // inside the above call to invoke. - if(rv == route::detach) - { - // It is essential that we return immediately, without - // doing anything after route::detach is returned, - // otherwise we could race with the detached operation - // attempting to call resume(). - restore_options.cancel(); - return rv; - } - } - else - { - // a subrouter never detaches on its own - BOOST_ASSERT(e.handler->count() == 1); - // can't detach on resume - if(res.ec_ == route::detach) - detail::throw_invalid_argument(); - // do resume - res.resume_ = 0; - rv = res.ec_; - res.ec_ = {}; - } - if( rv == route::send || - rv == route::complete || - rv == route::close) - return rv; - if(rv == route::next) - continue; // next entry - if(rv == route::next_route) - { - // middleware can't return next_route - if(! i.match.end) - detail::throw_invalid_argument(); - while(++it != i.entries.end()) - res.pos_ += it->handler->count(); - break; // skip remaining entries - } - // we must handle all route enums - BOOST_ASSERT(! is_route_result(rv)); - if(! rv.failed()) - { - // handler must return non-successful error_code - detail::throw_invalid_argument(); - } - // error handling mode - res.ec_ = rv; - if(! i.match.end) - continue; // next entry - // routes don't have error handlers - while(++it != i.entries.end()) - res.pos_ += it->handler->count(); - break; // skip remaining entries - } - - mr.restore_path(req); - } - - return route::next; -} - -route_result -any_router:: -do_dispatch( - basic_request& req, - basic_response& res) const -{ - auto rv = dispatch_impl(req, res); - BOOST_ASSERT(is_route_result(rv)); - BOOST_ASSERT(rv != route::next_route); - if(rv != route::next) - { - // when rv == route::detach we must return immediately, - // without attempting to perform any additional operations. - return rv; - } - if(! res.ec_.failed()) - { - // unhandled route - return route::next; - } - // error condition - return res.ec_; -} - -//} // detail - -} // beast2 -} // boost diff --git a/src/server/http_server.cpp b/src/server/http_server.cpp index d8bab14c..2fdabdf7 100644 --- a/src/server/http_server.cpp +++ b/src/server/http_server.cpp @@ -38,7 +38,7 @@ class http_server_impl unsigned short port) { w_.emplace( - acceptor_config{ false, false }, + http_proto::acceptor_config{ false, false }, asio::ip::tcp::endpoint( asio::ip::make_address(addr), port), diff --git a/src/server/route_handler.cpp b/src/server/route_handler.cpp deleted file mode 100644 index 0a74531a..00000000 --- a/src/server/route_handler.cpp +++ /dev/null @@ -1,66 +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 - -namespace boost { -namespace beast2 { - -Response:: -~Response() -{ -} - -Response& -Response:: -status( - http_proto::status code) -{ - message.set_start_line(code, message.version()); - return *this; -} - -Response& -Response:: -set_body(std::string s) -{ - if(! message.exists(http_proto::field::content_type)) - { - // VFALCO TODO detect Content-Type - message.set(http_proto::field::content_type, - "text/plain; charset=UTF-8"); - } - - if(!message.exists(http_proto::field::content_length)) - { - message.set_payload_size(s.size()); - } - - serializer.start(message, - http_proto::string_body(std::string(s))); - - return *this; -} - -void -Response:: -do_post() -{ - BOOST_ASSERT(task_); - // invoke until task resumes - for(;;) - if(task_->invoke()) - break; -} - -} // beast2 -} // boost diff --git a/src/server/router_types.cpp b/src/server/router_types.cpp deleted file mode 100644 index 5c27e849..00000000 --- a/src/server/router_types.cpp +++ /dev/null @@ -1,83 +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 - -namespace boost { -namespace beast2 { - -namespace detail { - -const char* -route_cat_type:: -name() const noexcept -{ - return "boost.http_proto.route"; -} - -std::string -route_cat_type:: -message(int code) const -{ - return message(code, nullptr, 0); -} - -char const* -route_cat_type:: -message( - int code, - char*, - std::size_t) const noexcept -{ - switch(static_cast(code)) - { - case route::close: return "http_proto::route::close"; - case route::complete: return "http_proto::route::complete"; - case route::detach: return "http_proto::route::detach"; - case route::next: return "http_proto::route::next"; - case route::next_route: return "http_proto::route::next_route"; - case route::send: return "http_proto::route::send"; - default: - return "http_proto::route::?"; - } -} - -// msvc 14.0 has a bug that warns about inability -// to use constexpr construction here, even though -// there's no constexpr construction -#if defined(_MSC_VER) && _MSC_VER <= 1900 -# pragma warning( push ) -# pragma warning( disable : 4592 ) -#endif - -#if defined(__cpp_constinit) && __cpp_constinit >= 201907L -constinit route_cat_type route_cat; -#else -route_cat_type route_cat; -#endif - -#if defined(_MSC_VER) && _MSC_VER <= 1900 -# pragma warning( pop ) -#endif - -} // detail - -resumer -detacher:: -owner:: -do_detach() -{ - detail::throw_logic_error(); -} - -} // beast2 -} // boost diff --git a/src/server/serve_redirect.cpp b/src/server/serve_redirect.cpp index 247439aa..80306630 100644 --- a/src/server/serve_redirect.cpp +++ b/src/server/serve_redirect.cpp @@ -106,9 +106,9 @@ prepare_error( auto serve_redirect:: operator()( - Request& req, - Response& res) const -> - system::error_code + http_proto::Request& req, + http_proto::Response& res) const -> + http_proto::route_result { std::string body; prepare_error(res.message, body, @@ -119,7 +119,7 @@ operator()( res.message.append(http_proto::field::location, u1.buffer()); res.serializer.start(res.message, http_proto::string_body( std::move(body))); - return {}; + return http_proto::route::send; } } // beast2 diff --git a/src/server/serve_static.cpp b/src/server/serve_static.cpp index 959212d1..4d6ca1cd 100644 --- a/src/server/serve_static.cpp +++ b/src/server/serve_static.cpp @@ -170,22 +170,22 @@ serve_static( auto serve_static:: operator()( - Request& req, - Response& res) const -> - route_result + http_proto::Request& req, + http_proto::Response& res) const -> + http_proto::route_result { // Allow: GET, HEAD if( req.message.method() != http_proto::method::get && req.message.method() != http_proto::method::head) { if(impl_->opt.fallthrough) - return route::next; + return http_proto::route::next; res.message.set_status( http_proto::status::method_not_allowed); res.message.set(http_proto::field::allow, "GET, HEAD"); res.set_body(""); - return route::send; + return http_proto::route::send; } // Build the path to the requested file @@ -218,12 +218,12 @@ operator()( // send file res.serializer.start( res.message, std::move(f), size); - return route::send; + return http_proto::route::send; } if( ec == system::errc::no_such_file_or_directory && ! impl_->opt.fallthrough) - return route::next; + return http_proto::route::next; BOOST_ASSERT(ec.failed()); return ec; diff --git a/test/unit/server/basic_router.cpp b/test/unit/server/basic_router.cpp deleted file mode 100644 index 6c0cc1e4..00000000 --- a/test/unit/server/basic_router.cpp +++ /dev/null @@ -1,1499 +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 "src/server/route_rule.hpp" - -#include "test_suite.hpp" - -namespace boost { -namespace beast2 { - -struct basic_router_test -{ - void compileTimeTests() - { - struct Req : basic_request {}; - struct Res : basic_response {}; - struct OtherReq : basic_request {}; - - BOOST_CORE_STATIC_ASSERT(std::is_copy_assignable>::value); - - struct h0 { void operator()(); }; - struct h1 { system::error_code operator()(); }; - struct h2 { system::error_code operator()(int); }; - struct h3 { system::error_code operator()(Req&, Res&) const; }; - struct h4 { system::error_code operator()(Req&, Res&, system::error_code) const; }; - struct h5 { void operator()(Req&, Res&) {} }; - struct h6 { void operator()(Req&, Res&, system::error_code) {} }; - struct h7 { system::error_code operator()(Req&, Res&, int); }; - struct h8 { system::error_code operator()(Req, Res&, int); }; - struct h9 { system::error_code operator()(Req, Res&, system::error_code const&) const; }; - -#if 0 - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 1); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 1); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 1); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value == 1); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 1); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 1); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 1); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 1); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 1); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 1); - - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 2); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 2); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 2); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 2); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value == 2); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 2); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 2); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 2); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value != 2); - BOOST_CORE_STATIC_ASSERT(detail::handler_type::value == 2); - - BOOST_CORE_STATIC_ASSERT(detail::handler_type< - basic_router, Req, Res>::value == 4); - BOOST_CORE_STATIC_ASSERT(detail::handler_type< - basic_router, Req, Res>::value == 4); - BOOST_CORE_STATIC_ASSERT(detail::handler_type< - basic_router, Req, Res>::value == 4); - BOOST_CORE_STATIC_ASSERT(detail::handler_type< - basic_router, Req, Res>::value == 0); -#endif - } - - //-------------------------------------------- - - using Req = basic_request; - using Res = basic_response; - - /** A handler for testing - */ - struct handler - { - ~handler() - { - if(alive_) - BOOST_TEST_EQ(called_, want_ != 0); - } - - explicit handler( - int want, system::error_code ec = - http_proto::error::success) - : want_(want) - , ec_(ec) - { - } - - handler(handler&& other) - { - BOOST_ASSERT(other.alive_); - BOOST_ASSERT(! other.called_); - want_ = other.want_; - alive_ = true; - other.alive_ = false; - ec_ = other.ec_; - } - - route_result operator()(Req&, Res&) const - { - called_ = true; - switch(want_) - { - default: - case 0: return route::close; - case 1: return route::send; - case 2: return route::next; - case 3: return ec_; - case 4: return route::next_route; - } - } - - private: - // 0 = not called - // 1 = called - // 2 = next - // 3 = error - // 4 = next_route - int want_; - bool alive_ = true; - bool mutable called_ = false; - system::error_code ec_; - }; - - /** An error handler for testing - */ - struct err_handler - { - ~err_handler() - { - if(alive_) - BOOST_TEST_EQ(called_, want_ != 0); - } - - err_handler( - int want, - system::error_code ec) - : want_(want) - , ec_(ec) - { - } - - err_handler(err_handler&& other) - { - BOOST_ASSERT(other.alive_); - BOOST_ASSERT(! other.called_); - want_ = other.want_; - alive_ = true; - other.alive_ = false; - ec_ = other.ec_; - } - - route_result operator()( - Req&, Res&, system::error_code ec) const - { - called_ = true; - switch(want_) - { - default: - case 0: return route::close; - case 1: - BOOST_TEST(ec == ec_); - return route::send; - case 2: - BOOST_TEST(ec == ec_); - return route::next; - case 3: - BOOST_TEST(ec.failed()); - return ec_; - } - } - - private: - // 0 = not called - // 1 = called, expecting ec_ - // 2 = next, expecting ec_ - // 3 = change error - int want_; - bool alive_ = true; - bool mutable called_ = false; - system::error_code ec_; - }; - - // handler to check base_url and path - struct path - { - ~path() - { - if(alive_) - BOOST_TEST(called_); - } - path(path&& other) - { - BOOST_ASSERT(other.alive_); - BOOST_ASSERT(! other.called_); - alive_ = true; - other.alive_ = false; - base_path_ = other.base_path_; - path_ = other.path_; - } - path( - core::string_view path = "/") - : path_(path) - { - } - path( - core::string_view base_path, - core::string_view path) - : base_path_(base_path) - , path_(path) - { - } - route_result operator()(Req& req, Res&) const - { - called_ = true; - BOOST_TEST_EQ(req.base_path, base_path_); - BOOST_TEST_EQ(req.path, path_); - return route::next; - } - private: - bool alive_ = true; - bool mutable called_ = false; - core::string_view base_path_; - core::string_view path_; - }; - - //------------------------------------------- - - // must NOT be called - static handler skip() - { - return handler(0); - } - - // must be called - static handler send() - { - return handler(1); - } - - // must be called, returns route::next - static handler next() - { - return handler(2); - } - - // must be called, returns ec - static handler fail( - system::error_code ec) - { - return handler(3, ec); - } - - // must NOT be called - static err_handler err_skip() - { - return err_handler(0, - http_proto::error::success); - } - - // must be called, expects ec, returns route::send - static err_handler err_send( - system::error_code ec) - { - return err_handler(1, ec); - } - - // must be called with `ec`, returns route::next - static err_handler err_next( - system::error_code ec) - { - return err_handler(2, ec); - } - - // must be called, returns a new error `ec` - static err_handler err_return( - system::error_code ec) - { - return err_handler(3, ec); - } - - using test_router = basic_router; - - void check( - test_router& r, - core::string_view url, - route_result rv0 = route::send) - { - Req req; - Res res; - auto rv = r.dispatch( - http_proto::method::get, - urls::url_view(url), req, res); - if(BOOST_TEST_EQ(rv.message(), rv0.message())) - BOOST_TEST(rv == rv0); - } - - void check( - test_router& r, - http_proto::method verb, - core::string_view url, - route_result rv0 = route::send) - { - Req req; - Res res; - auto rv = r.dispatch(verb, - urls::url_view(url), req, res); - if(BOOST_TEST_EQ(rv.message(), rv0.message())) - BOOST_TEST(rv == rv0); - } - - void check( - test_router& r, - core::string_view verb, - core::string_view url, - route_result rv0 = route::send) - { - Req req; - Res res; - auto rv = r.dispatch(verb, - urls::url_view(url), req, res); - if(BOOST_TEST_EQ(rv.message(), rv0.message())) - BOOST_TEST(rv == rv0); - } - - //-------------------------------------------- - - // special members - void testSpecial() - { - // default construction - { - test_router r; - check(r, "/", route::next); - } - - // copy construction - { - test_router r0; - r0.use(send()); - check(r0, "/"); - test_router r1(r0); - check(r1, "/"); - check(r0, "/"); - } - - // move assignment - { - test_router r0; - r0.use(send()); - check(r0, "/"); - test_router r1; - check(r1, "/", route::next); - r1 = std::move(r0); - check(r1, "/"); - } - - // copy assignment - { - test_router r0; - r0.use(send()); - check(r0, "/"); - test_router r1; - check(r1, "/", route::next); - r1 = r0; - check(r1, "/"); - check(r0, "/"); - } - - // options - { - // make sure this compiles - test_router r(router_options() - .case_sensitive(true) - .merge_params(true) - .strict(false)); - } - } - - void testUse() - { - system::error_code const er = - http_proto::error::bad_connection; - system::error_code const er2 = - http_proto::error::bad_expect; - - // pathless - { - test_router r; - r.use(send()); - check(r,"/"); - } - { - test_router r; - r.use( - path(), - send()); - check(r,"/"); - } - { - test_router r; - r.use( - send(), - skip()); - check(r,"/"); - } - { - test_router r; - r.use(send()); - r.use(skip()); - check(r,"/"); - } - { - test_router r; - r.use( - next(), - send()); - check(r,"/"); - } - { - test_router r; - r.use(next()); - r.use(send()); - check(r,"/"); - } - { - test_router r; - r.use(next()); - r.use(send()); - r.use(skip()); - check(r,"/"); - } - { - test_router r; - r.use( - next(), - send(), - skip()); - check(r,"/"); - } - - // pathless with errors - { - test_router r; - r.use(fail(er)); - check(r, "/", er); - } - { - test_router r; - r.use(next()); - r.use(err_skip()); - r.use(fail(er)); - r.use(skip()); - r.use(err_send(er)); - r.use(skip()); - r.use(err_skip()); - check(r,"/"); - } - { - test_router r; - r.use( - next(), - err_skip(), - fail(er), - skip(), - err_send(er), - skip(), - err_skip()); - check(r,"/"); - } - { - test_router r; - r.use(next()); - r.use(err_skip()); - r.use(fail(er)); - r.use(skip()); - r.use(err_return(er2)); - r.use(skip()); - r.use(err_next(er2)); - r.use(err_send(er2)); - check(r,"/"); - } - { - test_router r; - r.use( - next(), - err_skip(), - fail(er), - skip(), - err_return(er2), - skip(), - err_next(er2), - err_send(er2)); - check(r,"/"); - } - { - // cannot return success - test_router r; - r.use(fail(system::error_code())); - BOOST_TEST_THROWS(check(r,"/"), - std::invalid_argument); - } - { - // can't change failure to success - test_router r; - r.use( - fail(er), - err_return(system::error_code{})); - BOOST_TEST_THROWS(check(r,"/"), - std::invalid_argument); - } - - // pathless, returning route enums - { - test_router r; - r.use(fail(route::close)); - check(r,"/", route::close); - } - { - test_router r; - r.use(fail(route::complete)); - check(r,"/", route::complete); - } - { - test_router r; - r.use(fail(route::detach)); - check(r,"/", route::detach); - } - { - test_router r; - r.use(fail(route::next)); - check(r,"/", route::next); - } - { - // middleware can't return route::next_route - test_router r; - r.use(fail(route::next_route)); - BOOST_TEST_THROWS(check(r,"/", route::next), - std::invalid_argument); - } - { - test_router r; - r.use(fail(route::send)); - check(r,"/", route::send); - } - - // empty path - { - test_router r; - r.use("", send()); - check(r,"/"); - check(r,"/api"); - } - { - test_router r; - r.use("", - path("/api"), - send()); - check(r,"/api"); - } - - // prefix matching - { - test_router r; - r.use("/api", skip()); - check(r,"/", route::next); - } - { - test_router r; - r.use("/api", skip()); - check(r,"/", route::next); - check(r,"/a", route::next); - check(r,"/ap", route::next); - } - { - test_router r; - r.use("/api", send()); - check(r,"/api"); - check(r,"/api/"); - check(r,"/api/more"); - } - { - test_router r; - r.use("/api", - path("/api", "/more"), - send()); - check(r,"/api/more"); - } - { - test_router r; - r.use("/api/more", - path("/api/more", "/"), - send()); - check(r,"/api/more"); - } - { - test_router r; - r.use("/api", next()); - r.use("/api", send()); - check(r,"/api"); - check(r,"/api/"); - check(r,"/api/more"); - } - { - test_router r; - r.use("/api", - next(), - send()); - check(r,"/api"); - check(r,"/api/"); - check(r,"/api/more"); - } - { - test_router r; - r.use("/api", - next(), - send(), - err_skip(), - skip()); - check(r,"/api"); - check(r,"/api/"); - check(r,"/api/more"); - } - { - test_router r; - r.use("/api", skip()); - r.use("/", send()); - check(r,"/"); - } - { - test_router r; - r.use("/", next()); - r.use("/api", skip()); - r.use("/", send()); - check(r,"/"); - } - { - test_router r; - r.use("/x", next()); - r.use("/api", skip()); - r.use("/y", skip()); - r.use("/x", send()); - check(r,"/x"); - } - { - // no match - test_router r; - r.use("/x", skip()); - r.use("/api", skip()); - r.use("/y", skip()); - r.use("/x", skip()); - check(r,"/", route::next); - } - - // errors and matching - { - test_router r; - r.use("/x", skip()); - r.use("/api", skip()); - r.use("/y", fail(er)); - r.use("/y", skip()); - r.use("/x", err_skip()); - r.use("/y", err_return(er2)); - r.use("/z/", err_skip()); - r.use("/y", err_next(er2)); - r.use("/y", err_send(er2)); - r.use("/y", err_skip()); - r.use("/y", skip()); - check(r,"/y"); - } - { - test_router r; - r.use("/x", skip()); - r.use("/api", skip()); - r.use("/y", - fail(er), - skip()); - r.use("/x", err_skip()); - r.use("/y", err_return(er2)); - r.use("/z/", err_skip()); - r.use("/y", - err_next(er2), - err_send(er2), - err_skip(), - skip()); - check(r,"/y"); - } - - // case sensitivity - { - test_router r(router_options() - .case_sensitive(true)); - r.use("/x", skip()); - check(r, "/X", route::next); - } - { - test_router r(router_options() - .case_sensitive(false)); - r.use("/x", send()); - check(r, "/X"); - } - { - test_router r; - r.use("/x", send()); - check(r, "/X"); - } - } - - void testDispatch() - { - // dispatch - { - test_router r; - r.use(skip()); - BOOST_TEST_THROWS( - check(r, http_proto::method::unknown, "/", route::next), - std::invalid_argument); - } - } - - void testRoute() - { - static auto const GET = http_proto::method::get; - static auto const POST = http_proto::method::post; - static system::error_code const er = - http_proto::error::bad_connection; - static system::error_code const er2 = - http_proto::error::bad_expect; - - // empty - { - test_router r; - check(r, "/", route::next); - check(r, GET, "/", route::next); - check(r, POST, "/", route::next); - check(r, "GET", "/", route::next); - check(r, "POST", "/", route::next); - BOOST_TEST_THROWS( - check(r, "", "/", route::next), - std::invalid_argument); - } - - // add - { - test_router r; - r.add(GET, "/", - path(), - send()); - check(r, GET, "/"); - check(r, "GET", "/"); - check(r, "get", "/", route::next); - check(r, POST, "/", route::next); - check(r, "POST", "/", route::next); - check(r, "post", "/", route::next); - } - { - test_router r; - r.add(POST, "/", send()); - check(r, POST, "/"); - check(r, "POST", "/"); - check(r, "Post", "/", route::next); - check(r, GET, "/", route::next); - check(r, "GET", "/", route::next); - check(r, "get", "/", route::next); - } - { - test_router r; - r.add(GET, "/x", skip()); - r.add(POST, "/y", skip()); - r.add(GET, "/y", - path("/y", "/"), - send()); - r.add(GET, "/z", skip()); - check(r, GET, "/y"); - } - { - test_router r; - r.add("HACK", "/", next()); - r.add("CRACK", "/", send()); - r.add(GET, "/", skip()); - check(r, "CRACK", "/"); - check(r, "crack", "/", route::next); - check(r, "HACK", "/", route::next); - } - - // route.add - { - test_router r; - r.route("/") - .add(GET, send()); - check(r, GET, "/"); - check(r, "GET", "/"); - check(r, "get", "/", route::next); - check(r, POST, "/", route::next); - check(r, "POST", "/", route::next); - check(r, "post", "/", route::next); - } - { - test_router r; - r.route("/") - .add(POST, send()); - check(r, POST, "/"); - check(r, "POST", "/"); - check(r, "Post", "/", route::next); - check(r, GET, "/", route::next); - check(r, "GET", "/", route::next); - check(r, "get", "/", route::next); - } - { - test_router r; - r.route("/x").add(GET, skip()); - r.route("/y") - .add(POST, skip()) - .add(GET, send()); - r.route("/z") - .add(GET, skip()); - check(r, GET, "/y"); - } - { - test_router r; - r.route("/") - .add("HACK", next()) - .add("CRACK", send()) - .add(GET, skip()); - check(r, "CRACK", "/"); - check(r, "crack", "/", route::next); - check(r, "HACK", "/", route::next); - } - - // mix with use - { - test_router r; - r.use(next()); - r.add(POST, "/x", skip()); - r.add(POST, "/y", skip()); - r.use("/z", next()); - r.add(POST, "/y", skip()); - r.add(POST, "/z", send()); - r.add(POST, "/z", skip()); - r.use(skip()); - check(r, POST, "/z"); - } - - // verb matching - { - test_router r; - r.add(GET, "/", send()); - check(r, GET, "/"); - check(r, POST, "/", route::next); - check(r, "GET", "/"); - check(r, "POST", "/", route::next); - check(r, "get", "/", route::next); - check(r, "Get", "/", route::next); - check(r, "gEt", "/", route::next); - check(r, "geT", "/", route::next); - check(r, "post", "/", route::next); - } - { - test_router r; - r.route("/") - .add(POST, skip()) - .add(GET, send()) - .add(GET, skip()) - .add(POST, skip()); - check(r, GET, "/"); - } - { - test_router r; - r.route("/") - .add(GET, skip()) - .add(POST, send()) - .add(POST, skip()) - .add(GET, skip()); - check(r, POST, "/"); - } - - // all - { - test_router r; - r.all("/x", skip()); - r.all("/y", send()); - r.all("/z", skip()); - check(r, GET, "/y"); - } - { - test_router r; - r.all("/y", next()); - r.all("/y", send()); - r.all("/z", skip()); - check(r, GET, "/y"); - } - { - test_router r; - r.add(GET, "/y", next()); - r.all("/y", send()); - r.all("/z", skip()); - check(r, GET, "/y"); - } - { - test_router r; - r.add(POST, "/y", skip()); - r.all("/y", send()); - r.all("/z", skip()); - check(r, GET, "/y"); - } - { - test_router r; - r.add(GET, "/x", skip()); - r.all("/y", send()); - r.use("/z", skip()); - check(r, GET, "/y"); - } - { - test_router r; - BOOST_TEST_THROWS( - r.all("", skip()), - std::invalid_argument); - } - - // error handling - { - test_router r; - r.use(err_skip()); - r.route("/") - .add(GET, skip()) - .add(POST, skip()) - .add("FAIL", fail(er)) - .add("HEAD", skip()); - check(r, "FAIL", "/", er); - } - { - test_router r; - r.use(err_skip()); - r.route("/") - .add(GET, skip()) - .add(POST, skip()) - .add("FAIL", fail(er)) - .add("HEAD", skip()); - r.use( - err_send(er), - err_skip()); - check(r, "FAIL", "/"); - } - { - test_router r; - r.route("/") - .add(GET, skip()) - .add(POST, fail(er)) - .add(POST, skip()); - r.use( - err_return(er2), - err_next(er2)); - r.use( - err_send(er2)); - check(r, POST, "/"); - } - - // request with known method, custom route - { - test_router r; - r.route("/") - .add("BEAUCOMP", skip()); - check(r, GET, "/", route::next); - } - - // clean up empty routes - { - test_router r; - r.route("/empty"); - r.route("/not-empty") - .add(GET, send()); - check(r, "/empty", route::next); - check(r, "/not-empty"); - } - - // bad verb - { - test_router r; - BOOST_TEST_THROWS( - r.route("/").add(http_proto::method::unknown, skip()), - std::invalid_argument); - } - - // skip route on error - { - test_router r; - r.route("/") - .add(GET, next()) - .add(GET, fail(er)) - .add(GET, skip()) - .all(skip()) - .add(POST, skip()); - r.route("/") - .all(skip()); - r.use(err_send(er)); - check(r, GET, "/"); - } - - // skip route on next_route - { - test_router r; - r.route("/") - .add(GET, next()) - .add(GET, next()) - .add(GET, fail(route::next_route)) - .add(GET, skip()) - .add(GET, skip()) - .add(GET, skip()) - .add(GET, skip()) - .all(skip()); - r.route("/") - .add(GET, send()); - check(r, GET, "/"); - } - } - - void testSubRouter() - { - static auto const GET = http_proto::method::get; - static auto const POST = http_proto::method::post; - static system::error_code const er = - http_proto::error::bad_connection; - static system::error_code const er2 = - http_proto::error::bad_expect; - - // sub-middleware - { - test_router r; - r.use("/api", []{ - test_router r; - r.use("/v1", - skip()); - r.use("/v2", - path("/api/v2", "/"), - send()); - return r; }()); - check(r,"/api/v2"); - } - - // error handling - { - test_router r; - r.use("/api", []{ - test_router r; - r.use("/v1", - path("/api/v1", "/"), - fail(er)); // return er - return r; }()); - check(r,"/api/v1", er); - } - { - test_router r; - r.use("/api", []{ - test_router r; - r.use("/v1", - path("/api/v1", "/"), - fail(er)); - return r; }()); - r.use(err_next(er)); // next - check(r,"/api/v1", er); - } - { - test_router r; - r.use("/api", []{ - test_router r; - r.use("/v1", - path("/api/v1", "/"), - fail(er)); - return r; }()); - r.use( - skip()); - r.use([]{ - test_router r; - r.use( - skip(), - err_skip()); - return r; }()); - r.use("/api/v2", - err_skip()); - r.use( - err_next(er)); - check(r,"/api/v1", er); - } - { - test_router r; - r.use([]{ - test_router r; - r.use([]{ - test_router r; - r.use(fail(er)); - return r; }()); - r.use( - err_next(er), - skip()); - return r; }()); - r.use( - err_return(er2), - err_next(er2), - err_return(er)); - check(r, "/", er); - } - - // sub routes - { - test_router r; - r.use("/api", []{ - test_router r; - r.route("/user") - .add(POST, path("/api/user", "/")) - .add(GET, skip()) - .add(POST, send()); - return r; }()); - check(r, POST, "/api/user"); - } - - // nested options - { - test_router r(router_options() - .case_sensitive(true)); - r.use("/api", []{ - test_router r; - r.route("/USER") - .add(GET, skip()); - r.route("/user") - .add(GET, send()); - return r; }()); - check(r, "/api/user"); - } - { - test_router r(router_options() - .case_sensitive(true)); - r.use("/api", []{ - test_router r(router_options() - .case_sensitive(false)); - r.route("/USER") - .add(GET, send()); - r.route("/user") - .add(GET, skip()); - return r; }()); - check(r, "/api/user"); - } - { - test_router r; - r.use("/api", []{ - test_router r(router_options() - .case_sensitive(true)); - r.route("/USER") - .add(GET, send()); - r.route("/user") - .add(GET, skip()); - return r; }()); - check(r, "/api/USER"); - } - } - - void testErr() - { - static auto const GET = http_proto::method::get; - static system::error_code const er = - http_proto::error::bad_connection; - static system::error_code const er2 = - http_proto::error::bad_content_length; - { - test_router r; - r.use(err_skip()); - check(r,"/", route::next); - } - { - test_router r; - r.use("", err_skip()); - check(r,"/", route::next); - } - { - test_router r; - r.use(send()); - r.use(err_skip()); - check(r,"/"); - } - { - test_router r; - r.use(fail(er)); - r.use(err_send(er)); - r.use(err_skip()); - check(r,"/", route::send); - } - { - test_router r; - r.use(fail(er)); - r.use("", err_send(er)); - r.use(err_skip()); - check(r,"/", route::send); - } - { - test_router r; - r.use(fail(er2)); - r.use(err_send(er2)); - r.use(err_skip()); - check(r,"/", route::send); - } - - // mount points - { - test_router r; - r.use("/api", fail(er)); - r.use("/api", err_send(er)); - r.use("/x", err_skip()); - check(r, "/api"); - } - { - test_router r; - r.use("/x", fail(er)); - r.use("/api", err_skip()); - r.use("/x", err_send(er)); - check(r, "/x/data"); - } - - // replacing errors - { - test_router r; - r.use(fail(er)); - r.use(err_return(er2)); - r.use(err_send(er2)); - check(r, "/"); - } - - { - test_router r; - r.use(fail(er)); - r.use(skip()); - r.use(err_send(er)); - check(r, "/"); - } - - // route-level vs. router-level - { - test_router r; - r.route("/").add(GET, fail(er)); - r.use(err_send(er)); - check(r, "/"); - } - - // subrouters - { - test_router r; - r.use("/api", []{ - test_router r; - r.use( - fail(er), - err_send(er), - err_skip()); - return r; }()); - r.use(err_skip()); - check(r, "/api"); - } - { - test_router r; - r.use("/api", []{ - test_router r; - r.use( - fail(er), - err_next(er), - skip()); - return r; }()); - r.use(err_send(er)); - r.use(err_skip()); - check(r, "/api"); - } - } - - void testPath() - { - auto const path = []( - core::string_view pat, - core::string_view target, - core::string_view good) - { - test_router r; - r.use( pat, - [&](Req& req, Res&) - { - BOOST_TEST_EQ(req.path, good); - return route::send; - }); - Req req; - Res res; - r.dispatch( - http_proto::method::get, - urls::url_view(target), - req, res); - }; - - path("/", "/", "/"); - path("/", "/api", "/api"); - path("/api", "/api", "/"); - path("/api", "/api/", "/"); - path("/api", "/api/", "/"); - path("/api", "/api/v0", "/v0"); - path("/api/", "/api", "/"); - path("/api/", "/api", "/"); - path("/api/", "/api/", "/"); - path("/api/", "/api/v0", "/v0"); - } - - void testPctDecode() - { - static auto const GET = http_proto::method::get; - - // slash - { - test_router r; - r.add(GET, "/auth/login", skip()); - check(r, "/auth%2flogin", route::next); - } - - // backslash - { - test_router r; - r.add(GET, "/auth\\login", skip()); - check(r, "/auth%5clogin", route::next); - } - - // unreserved - { - test_router r; - r.add(GET, "/a", send()); - check(r, "/%61"); - } - { - test_router r; - r.add(GET, "/%61", send()); - check(r, "/%61"); - } - { - test_router r; - r.add(GET, "/%61", send()); - check(r, "/a"); - } - } - - void testDetach() - { - static auto const GET = http_proto::method::get; - { - test_router r; - r.use(next()); - r.use(fail(route::detach)); - check(r,"/", route::detach); - } - { - test_router r; - r.use(next()); - r.use(fail(route::detach)); - Req req; - Res res; - auto rv1 = r.dispatch(GET, urls::url_view("/"), req, res); - BOOST_TEST(rv1 == route::detach); - } - { - test_router r; - r.use(next()); - r.use(fail(route::detach)); - r.use(next()); - r.use(fail(route::send)); - Req req; - Res res; - auto rv1 = r.dispatch(GET, urls::url_view("/"), req, res); - BOOST_TEST(rv1 == route::detach); - auto rv2 = r.resume(req, res, route::next); - BOOST_TEST(rv2 == route::send); - } - { - test_router r; - r.use(next()); - r.use([]{ - test_router r; - r.use( - next(), - fail(route::detach), - path(), - next()); - return r; }()); - r.use(send()); - Req req; - Res res; - auto rv1 = r.dispatch(GET, urls::url_view("/"), req, res); - BOOST_TEST(rv1 == route::detach); - auto rv2 = r.resume(req, res, route::next); - BOOST_TEST(rv2 == route::send); - } - - // return values - { - test_router r; - r.use(fail(route::detach)); - Req req; - Res res; - { - auto rv1 = r.dispatch(GET, urls::url_view("/"), req, res); - BOOST_TEST(rv1 == route::detach); - auto rv2 = r.resume(req, res, route::send); - BOOST_TEST(rv2 == route::send); - } - { - auto rv1 = r.dispatch(GET, urls::url_view("/"), req, res); - BOOST_TEST(rv1 == route::detach); - auto rv2 = r.resume(req, res, route::close); - BOOST_TEST(rv2 == route::close); - } - { - auto rv1 = r.dispatch(GET, urls::url_view("/"), req, res); - BOOST_TEST(rv1 == route::detach); - auto rv2 = r.resume(req, res, route::complete); - BOOST_TEST(rv2 == route::complete); - } - { - auto rv1 = r.dispatch(GET, urls::url_view("/"), req, res); - BOOST_TEST(rv1 == route::detach); - BOOST_TEST_THROWS(r.resume(req, res, system::error_code()), - std::invalid_argument); - } - } - - // path restoration - { - test_router r; - r.use(next()); - r.use("/api", []{ - test_router r; - r.use( - next(), - fail(route::detach), - path("/api", "/"), - next()); - return r; }()); - r.use("/api", send()); - Req req; - Res res; - auto rv1 = r.dispatch(GET, urls::url_view("/api"), req, res); - BOOST_TEST(rv1 == route::detach); - auto rv2 = r.resume(req, res, route::next); - BOOST_TEST(rv2 == route::send); - } - - // detach on resume - { - test_router r; - r.use(fail(route::detach)); - Req req; - Res res; - auto rv1 = r.dispatch(GET, urls::url_view("/"), req, res); - BOOST_TEST(rv1 == route::detach); - BOOST_TEST_THROWS(r.resume(req, res, route::detach), - std::invalid_argument); - } - - // invalid detach - { - test_router r; - r.use(fail(route::detach)); - Req req; - Res res; - auto rv1 = r.dispatch(GET, urls::url_view("/"), req, res); - BOOST_TEST(rv1 == route::detach); - BOOST_TEST_THROWS(r.resume(req, res, system::error_code()), - std::invalid_argument); - } - } - - void run() - { - testSpecial(); - testUse(); - testDispatch(); - testRoute(); - testSubRouter(); - testErr(); - testPath(); - testPctDecode(); - testDetach(); - } -}; - -TEST_SUITE( - basic_router_test, - "boost.http_proto.server.basic_router"); - -} // beast2 -} // boost diff --git a/test/unit/server/route_handler.cpp b/test/unit/server/route_handler.cpp deleted file mode 100644 index 8ca97180..00000000 --- a/test/unit/server/route_handler.cpp +++ /dev/null @@ -1,104 +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 { - -struct route_handler_test -{ - using test_router = router; - - void check( - test_router& r, - http_proto::method verb, - core::string_view url, - route_result rv0 = route::send) - { - Request req; - Response res; - auto rv = r.dispatch( - verb, urls::url_view(url), req, res); - if(BOOST_TEST_EQ(rv.message(), rv0.message())) - BOOST_TEST(rv == rv0); - } - - void testData() - { - static auto const POST = http_proto::method::post; - - struct auth_token - { - bool valid = false; - }; - - struct session_token - { - bool valid = false; - }; - - test_router r; - r.use( - [](Request&, Response& res) - { - // create session_token - auto& st = res.data.try_emplace(); - BOOST_TEST_EQ(st.valid, false); - return route::next; - }); - r.use("/user", - [](Request&, Response& res) - { - // make session token valid - auto* st = res.data.find(); - if(BOOST_TEST_NE(st, nullptr)) - st->valid = true; - return route::next; - }); - r.route("/user/auth") - .add(POST, - [](Request& req, Response& res) - { - auto& st = res.data.get(); - BOOST_TEST_EQ(st.valid, true); - // create auth_token each time - auto& at = req.data.emplace(); - at.valid = true; - return route::next; - }, - [](Request& req, Response& res) - { - auto& at = req.data.get(); - auto& st = res.data.get(); - BOOST_TEST_EQ(at.valid, true); - BOOST_TEST_EQ(st.valid, true); - return route::send; - }); - check(r, POST, urls::url_view("/user/auth")); - } - - void run() - { - testData(); - } -}; - -TEST_SUITE( - route_handler_test, - "boost.http_proto.server.route_handler"); - -} // beast2 -} // boost diff --git a/test/unit/server/route_handler_asio.cpp b/test/unit/server/route_handler_asio.cpp index bea47575..ad1f0ed9 100644 --- a/test/unit/server/route_handler_asio.cpp +++ b/test/unit/server/route_handler_asio.cpp @@ -10,7 +10,7 @@ // Test that header file is self-contained. #include -#include +#include #include #include @@ -30,15 +30,15 @@ struct route_handler_asio_test } }; - using test_router = basic_router< - Request, ResponseAsio>; + using test_router = http_proto::basic_router< + http_proto::Request, ResponseAsio>; void check( test_router& r, core::string_view url, - route_result rv0 = route::send) + http_proto::route_result rv0 = http_proto::route::send) { - Request req; + http_proto::Request req; ResponseAsio res; auto rv = r.dispatch( http_proto::method::get, @@ -50,13 +50,13 @@ struct route_handler_asio_test struct handler { template - route_result + http_proto::route_result operator()( - Request&, + http_proto::Request&, ResponseAsio&) const { BOOST_TEST(true); - return route::send; + return http_proto::route::send; } }; diff --git a/test/unit/server/router_types.cpp b/test/unit/server/router_types.cpp deleted file mode 100644 index 7350991b..00000000 --- a/test/unit/server/router_types.cpp +++ /dev/null @@ -1,30 +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 "test_suite.hpp" - -namespace boost { -namespace beast2 { - -struct router_types_test -{ - void run() - { - } -}; - -TEST_SUITE( - router_types_test, - "boost.http_proto.server.router_types"); - -} // beast2 -} // boost From 40f4977854b3551cb3fbc713dfe49cfb81e84a56 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 30 Nov 2025 19:43:46 -0800 Subject: [PATCH 07/40] feat: cors in example --- example/server/main.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/example/server/main.cpp b/example/server/main.cpp index 89dbfc4a..0f87bb56 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -71,8 +72,9 @@ int server_main( int argc, char* argv[] ) //srv.wwwroot.use("/log", serve_log_admin(app)); //srv.wwwroot.use("/alt", serve_static( argv[3] )); //srv.wwwroot.use("/detach", serve_detached()); - srv.wwwroot.use(post_work()); + //srv.wwwroot.use(post_work()); srv.wwwroot.use( + http_proto::cors(), []( http_proto::Request& req, http_proto::Response& res) -> http_proto::route_result From ac33b901f23ecf145f684b33ba5ad8471d3c7a00 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Tue, 2 Dec 2025 18:56:54 -0800 Subject: [PATCH 08/40] chore: route_params --- example/server/main.cpp | 5 +- example/server/post_work.cpp | 5 +- example/server/post_work.hpp | 3 +- example/server/serve_detached.hpp | 9 +-- example/server/serve_log_admin.cpp | 18 ++--- include/boost/beast2/server/http_stream.hpp | 79 +++++++++---------- .../beast2/server/route_handler_asio.hpp | 9 ++- include/boost/beast2/server/router.hpp | 2 +- include/boost/beast2/server/router_asio.hpp | 2 +- .../boost/beast2/server/serve_redirect.hpp | 3 +- include/boost/beast2/server/serve_static.hpp | 3 +- src/server/route_rule.hpp | 10 +++ src/server/serve_redirect.cpp | 13 ++- src/server/serve_static.cpp | 29 ++++--- test/unit/server/route_handler_asio.cpp | 10 +-- 15 files changed, 98 insertions(+), 102 deletions(-) diff --git a/example/server/main.cpp b/example/server/main.cpp index 0f87bb56..9713599d 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -75,9 +75,8 @@ int server_main( int argc, char* argv[] ) //srv.wwwroot.use(post_work()); srv.wwwroot.use( http_proto::cors(), - []( http_proto::Request& req, - http_proto::Response& res) -> - http_proto::route_result + []( http_proto::route_params&) -> + http_proto::route_result { return http_proto::route::next; }); diff --git a/example/server/post_work.cpp b/example/server/post_work.cpp index 5420aad0..db9a5136 100644 --- a/example/server/post_work.cpp +++ b/example/server/post_work.cpp @@ -63,10 +63,9 @@ struct task http_proto::route_result post_work:: operator()( - http_proto::Request&, - http_proto::Response& res) const + http_proto::route_params& p) const { - return res.post(task()); + return p.post(task()); } } // beast2 diff --git a/example/server/post_work.hpp b/example/server/post_work.hpp index ca097e87..b75c398d 100644 --- a/example/server/post_work.hpp +++ b/example/server/post_work.hpp @@ -20,8 +20,7 @@ struct post_work { system::error_code operator()( - http_proto::Request&, - http_proto::Response& res) const; + http_proto::route_params&) const; }; } // beast2 diff --git a/example/server/serve_detached.hpp b/example/server/serve_detached.hpp index d11dae09..bd1c8498 100644 --- a/example/server/serve_detached.hpp +++ b/example/server/serve_detached.hpp @@ -39,10 +39,9 @@ class serve_detached system::error_code operator()( - http_proto::Request&, - http_proto::Response& res) const + http_proto::route_params& p) const { - return res.detach( + return p.detach( [&](http_proto::resumer resume) { asio::post(*tp_, @@ -50,8 +49,8 @@ class serve_detached { // Simulate some asynchronous work std::this_thread::sleep_for(std::chrono::seconds(1)); - res.status(http_proto::status::ok); - res.set_body("Hello from serve_detached!\n"); + p.status(http_proto::status::ok); + p.set_body("Hello from serve_detached!\n"); resume(http_proto::route::send); // resume( res.send("Hello from serve_detached!\n") ); }); diff --git a/example/server/serve_log_admin.cpp b/example/server/serve_log_admin.cpp index 93ab8263..32a15f55 100644 --- a/example/server/serve_log_admin.cpp +++ b/example/server/serve_log_admin.cpp @@ -30,8 +30,7 @@ class serve_log_page system::error_code operator()( - http_proto::Request&, - http_proto::Response& res) const + http_proto::route_params& p) const { auto const v = ls_.get_sections(); std::string s; @@ -72,9 +71,9 @@ class serve_log_page format_to(s, "\n"); format_to(s, "\n"); - res.status(http_proto::status::ok); - res.message.set(http_proto::field::content_type, "text/html; charset=UTF-8"); - res.set_body(std::move(s)); + p.status(http_proto::status::ok); + p.res.set(http_proto::field::content_type, "text/html; charset=UTF-8"); + p.set_body(std::move(s)); return http_proto::route::send; } @@ -97,12 +96,11 @@ class handle_submit system::error_code operator()( - http_proto::Request&, - http_proto::Response& res) const + http_proto::route_params& p) const { - res.status(http_proto::status::ok); - res.message.set(http_proto::field::content_type, "plain/text; charset=UTF-8"); - res.set_body("submit"); + p.status(http_proto::status::ok); + p.res.set(http_proto::field::content_type, "plain/text; charset=UTF-8"); + p.set_body("submit"); return http_proto::route::send; } diff --git a/include/boost/beast2/server/http_stream.hpp b/include/boost/beast2/server/http_stream.hpp index 349668fe..1d813c11 100644 --- a/include/boost/beast2/server/http_stream.hpp +++ b/include/boost/beast2/server/http_stream.hpp @@ -121,8 +121,7 @@ class http_stream using work_guard = asio::executor_work_guard().get_executor())>; std::unique_ptr pwg_; - http_proto::Request req_; - ResponseAsio res_; + asio_route_params p_; }; //------------------------------------------------ @@ -173,12 +172,12 @@ http_stream( , stream_(stream) , routes_(std::move(routes)) , close_(close) - , res_(stream_) + , p_(stream_) { - req_.parser = http_proto::request_parser(app); + p_.parser = http_proto::request_parser(app); - res_.serializer = http_proto::serializer(app); - res_.detach = http_proto::detacher(*this); + p_.serializer = http_proto::serializer(app); + p_.detach = http_proto::detacher(*this); } // called to start a new HTTP session. @@ -191,8 +190,8 @@ on_stream_begin( { pconfig_ = &config; - req_.parser.reset(); - res_.data.clear(); + p_.parser.reset(); + p_.session_data.clear(); do_read(); } @@ -202,9 +201,9 @@ void http_stream:: do_read() { - req_.parser.start(); + p_.parser.start(); - beast2::async_read(stream_, req_.parser, + beast2::async_read(stream_, p_.parser, call_mf(&http_stream::on_read, this)); } @@ -225,7 +224,7 @@ on_read( "{} http_stream::on_read bytes={}", this->id(), bytes_transferred); - BOOST_ASSERT(req_.parser.is_complete()); + BOOST_ASSERT(p_.parser.is_complete()); on_headers(); } @@ -237,27 +236,26 @@ http_stream:: on_headers() { // set up Request and Response objects - res_.serializer.reset(); // VFALCO HACK for now we make a copy of the message - req_.message = req_.parser.get(); - //res_.message.set_version(req_.message.version()); - res_.message.set_start_line( // VFALCO WTF - http_proto::status::ok, req_.message.version()); - res_.message.set_keep_alive(req_.message.keep_alive()); - res_.data.clear(); + p_.req = p_.parser.get(); + p_.route_data.clear(); + p_.res.set_start_line( // VFALCO WTF + http_proto::status::ok, p_.req.version()); + p_.res.set_keep_alive(p_.req.keep_alive()); + p_.serializer.reset(); // parse the URL { - auto rv = urls::parse_uri_reference(req_.message.target()); + auto rv = urls::parse_uri_reference(p_.req.target()); if(rv.has_error()) { // error parsing URL - res_.status(http_proto::status::bad_request); - res_.set_body("Bad Request: " + rv.error().message()); + p_.status(http_proto::status::bad_request); + p_.set_body("Bad Request: " + rv.error().message()); return do_respond(rv.error()); } - req_.url = rv.value(); + p_.url = rv.value(); } // invoke handlers for the route @@ -275,11 +273,11 @@ do_dispatch( { BOOST_ASSERT(! pwg_); // can't be detached rv = routes_.dispatch( - req_.message.method(), req_.url, req_, res_); + p_.req.method(), p_.url, p_); } else { - rv = routes_.resume(req_, res_, rv); + rv = routes_.resume(p_, rv); } do_respond(rv); @@ -303,13 +301,13 @@ do_respond( { // VFALCO what if the connection was closed or keep-alive=false? // handler sendt the response? - BOOST_ASSERT(res_.serializer.is_done()); + BOOST_ASSERT(p_.serializer.is_done()); return on_write(system::error_code(), 0); } if(rv == http_proto::route::detach) { - // didn't call res.detach()? + // didn't call detach()? if(! pwg_) detail::throw_logic_error(); return; @@ -319,20 +317,19 @@ do_respond( { // unhandled request auto const status = http_proto::status::not_found; - res_.status(status); - //res_.message.set_keep_alive(false); // VFALCO? - res_.set_body(http_proto::to_string(status)); + p_.status(status); + p_.set_body(http_proto::to_string(status)); } else if(rv != http_proto::route::send) { // error message of last resort BOOST_ASSERT(rv.failed()); BOOST_ASSERT(! http_proto::is_route_result(rv)); - res_.status(http_proto::status::internal_server_error); + p_.status(http_proto::status::internal_server_error); std::string s; format_to(s, "An internal server error occurred: {}", rv.message()); - res_.message.set_keep_alive(false); // VFALCO? - res_.set_body(s); + p_.res.set_keep_alive(false); // VFALCO? + p_.set_body(s); } do_write(); @@ -344,8 +341,8 @@ void http_stream:: do_write() { - BOOST_ASSERT(! res_.serializer.is_done()); - beast2::async_write(stream_, res_.serializer, + BOOST_ASSERT(! p_.serializer.is_done()); + beast2::async_write(stream_, p_.serializer, call_mf(&http_stream::on_write, this)); } @@ -362,13 +359,13 @@ on_write( if(ec.failed()) return do_fail("http_stream::on_write", ec); - BOOST_ASSERT(res_.serializer.is_done()); + BOOST_ASSERT(p_.serializer.is_done()); LOG_TRC(this->sect_)( "{} http_stream::on_write bytes={}", this->id(), bytes_transferred); - if(res_.message.keep_alive()) + if(p_.res.keep_alive()) return do_read(); do_close(); @@ -419,8 +416,8 @@ do_fail( LOG_TRC(this->sect_)("{}: {}", s, ec.message()); // tidy up lingering objects - req_.parser.reset(); - res_.serializer.reset(); + p_.parser.reset(); + p_.serializer.reset(); close_(ec); } @@ -441,9 +438,9 @@ void http_stream:: clear() noexcept { - req_.parser.reset(); - res_.serializer.reset(); - res_.message.clear(); + p_.parser.reset(); + p_.serializer.reset(); + p_.res.clear(); } } // beast2 diff --git a/include/boost/beast2/server/route_handler_asio.hpp b/include/boost/beast2/server/route_handler_asio.hpp index c266b5ac..c00a9dbc 100644 --- a/include/boost/beast2/server/route_handler_asio.hpp +++ b/include/boost/beast2/server/route_handler_asio.hpp @@ -18,10 +18,11 @@ namespace boost { namespace beast2 { -/** Response object for Asio HTTP route handlers +/** Route parameters object for Asio HTTP route handlers */ template -class ResponseAsio : public http_proto::Response +class asio_route_params + : public http_proto::route_params { public: using stream_type = typename std::decay::type; @@ -30,7 +31,7 @@ class ResponseAsio : public http_proto::Response template explicit - ResponseAsio( + asio_route_params( Args&&... args) : stream(std::forward(args)...) { @@ -44,7 +45,7 @@ class ResponseAsio : public http_proto::Response template void -ResponseAsio:: +asio_route_params:: do_post() { asio::post( diff --git a/include/boost/beast2/server/router.hpp b/include/boost/beast2/server/router.hpp index 3e2c32a6..c967dfba 100644 --- a/include/boost/beast2/server/router.hpp +++ b/include/boost/beast2/server/router.hpp @@ -19,7 +19,7 @@ namespace beast2 { /** The sans-IO router type */ -using router = http_proto::basic_router; +using router = http_proto::basic_router; } // beast2 } // boost diff --git a/include/boost/beast2/server/router_asio.hpp b/include/boost/beast2/server/router_asio.hpp index e1efcd9b..8860a8a7 100644 --- a/include/boost/beast2/server/router_asio.hpp +++ b/include/boost/beast2/server/router_asio.hpp @@ -21,7 +21,7 @@ namespace beast2 { */ template using router_asio = http_proto::basic_router< - http_proto::Request, ResponseAsio>; + asio_route_params>; } // beast2 } // boost diff --git a/include/boost/beast2/server/serve_redirect.hpp b/include/boost/beast2/server/serve_redirect.hpp index 36230782..adc105a1 100644 --- a/include/boost/beast2/server/serve_redirect.hpp +++ b/include/boost/beast2/server/serve_redirect.hpp @@ -21,8 +21,7 @@ struct serve_redirect BOOST_BEAST2_DECL http_proto::route_result operator()( - http_proto::Request&, - http_proto::Response&) const; + http_proto::route_params&) const; }; } // beast2 diff --git a/include/boost/beast2/server/serve_static.hpp b/include/boost/beast2/server/serve_static.hpp index a4376d67..fb9d5d88 100644 --- a/include/boost/beast2/server/serve_static.hpp +++ b/include/boost/beast2/server/serve_static.hpp @@ -165,8 +165,7 @@ struct serve_static */ BOOST_BEAST2_DECL system::error_code operator()( - http_proto::Request&, - http_proto::Response&) const; + http_proto::route_params&) const; private: struct impl; diff --git a/src/server/route_rule.hpp b/src/server/route_rule.hpp index 98cc1adb..3c5e2f03 100644 --- a/src/server/route_rule.hpp +++ b/src/server/route_rule.hpp @@ -262,6 +262,7 @@ struct route_seg core::string_view constraint; char ptype = 0; // ':' | '?' | NULL char modifier = 0; + char term; // param terminator or NULL }; struct param_segment_rule_t @@ -343,6 +344,15 @@ struct path_rule_t return rv1.error(); route_seg rs = rv1.value(); rs.prefix = { it2, it1 }; + if(it != end) + { + if( *it == ':' || + *it == '*') + { + // can't have ":id:id" + return grammar::error::syntax; + } + } rv.segs.push_back(rs); it1 = it; continue; diff --git a/src/server/serve_redirect.cpp b/src/server/serve_redirect.cpp index 80306630..8d61994e 100644 --- a/src/server/serve_redirect.cpp +++ b/src/server/serve_redirect.cpp @@ -106,18 +106,17 @@ prepare_error( auto serve_redirect:: operator()( - http_proto::Request& req, - http_proto::Response& res) const -> + http_proto::route_params& p) const -> http_proto::route_result { std::string body; - prepare_error(res.message, body, - http_proto::status::moved_permanently, req.message); - urls::url u1(req.message.target()); + prepare_error(p.res, body, + http_proto::status::moved_permanently, p.req); + urls::url u1(p.req.target()); u1.set_scheme_id(urls::scheme::https); u1.set_host_address("localhost"); // VFALCO WTF IS THIS! - res.message.append(http_proto::field::location, u1.buffer()); - res.serializer.start(res.message, + p.res.append(http_proto::field::location, u1.buffer()); + p.serializer.start(p.res, http_proto::string_body( std::move(body))); return http_proto::route::send; } diff --git a/src/server/serve_static.cpp b/src/server/serve_static.cpp index 4d6ca1cd..2f8d67cd 100644 --- a/src/server/serve_static.cpp +++ b/src/server/serve_static.cpp @@ -170,28 +170,27 @@ serve_static( auto serve_static:: operator()( - http_proto::Request& req, - http_proto::Response& res) const -> + http_proto::route_params& p) const -> http_proto::route_result { // Allow: GET, HEAD - if( req.message.method() != http_proto::method::get && - req.message.method() != http_proto::method::head) + if( p.req.method() != http_proto::method::get && + p.req.method() != http_proto::method::head) { if(impl_->opt.fallthrough) return http_proto::route::next; - res.message.set_status( + p.res.set_status( http_proto::status::method_not_allowed); - res.message.set(http_proto::field::allow, "GET, HEAD"); - res.set_body(""); + p.res.set(http_proto::field::allow, "GET, HEAD"); + p.set_body(""); return http_proto::route::send; } // Build the path to the requested file std::string path; - path_cat(path, impl_->path, req.path); - if(req.parser.get().target().back() == '/') + path_cat(path, impl_->path, p.path); + if(p.parser.get().target().back() == '/') { path.push_back('/'); path.append("index.html"); @@ -206,18 +205,18 @@ operator()( size = f.size(ec); if(! ec.failed()) { - res.message.set_start_line( + p.res.set_start_line( http_proto::status::ok, - req.message.version()); - res.message.set_payload_size(size); + p.req.version()); + p.res.set_payload_size(size); auto mt = mime_type(get_extension(path)); - res.message.append( + p.res.append( http_proto::field::content_type, mt); // send file - res.serializer.start( - res.message, std::move(f), size); + p.serializer.start( + p.res, std::move(f), size); return http_proto::route::send; } diff --git a/test/unit/server/route_handler_asio.cpp b/test/unit/server/route_handler_asio.cpp index ad1f0ed9..1f4c21d6 100644 --- a/test/unit/server/route_handler_asio.cpp +++ b/test/unit/server/route_handler_asio.cpp @@ -31,18 +31,17 @@ struct route_handler_asio_test }; using test_router = http_proto::basic_router< - http_proto::Request, ResponseAsio>; + asio_route_params>; void check( test_router& r, core::string_view url, http_proto::route_result rv0 = http_proto::route::send) { - http_proto::Request req; - ResponseAsio res; + asio_route_params req; auto rv = r.dispatch( http_proto::method::get, - urls::url_view(url), req, res); + urls::url_view(url), req); if(BOOST_TEST_EQ(rv.message(), rv0.message())) BOOST_TEST(rv == rv0); } @@ -52,8 +51,7 @@ struct route_handler_asio_test template http_proto::route_result operator()( - http_proto::Request&, - ResponseAsio&) const + asio_route_params&) const { BOOST_TEST(true); return http_proto::route::send; From e16efd78a0c6c327c192273d46971817f13d3295 Mon Sep 17 00:00:00 2001 From: Mungo Gill Date: Wed, 10 Dec 2025 14:52:51 +0000 Subject: [PATCH 09/40] Add compat to the cmake test dependencies --- test/cmake_test/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test/cmake_test/CMakeLists.txt b/test/cmake_test/CMakeLists.txt index a30f99ca..c644f6ec 100644 --- a/test/cmake_test/CMakeLists.txt +++ b/test/cmake_test/CMakeLists.txt @@ -36,6 +36,7 @@ else() context date_time buffers + compat core capy static_assert From 4c1170c10aa4868f696ad620d38ac19d16195cff Mon Sep 17 00:00:00 2001 From: Mungo Gill Date: Thu, 20 Nov 2025 15:23:32 +0000 Subject: [PATCH 10/40] Additional format() testing --- include/boost/beast2/format.hpp | 87 ++++++++++++++++++++++++++------- test/unit/format.cpp | 45 +++++++++++++---- 2 files changed, 106 insertions(+), 26 deletions(-) diff --git a/include/boost/beast2/format.hpp b/include/boost/beast2/format.hpp index 4c5c9e7f..fb3994f5 100644 --- a/include/boost/beast2/format.hpp +++ b/include/boost/beast2/format.hpp @@ -11,7 +11,9 @@ #define BOOST_BEAST2_FORMAT_HPP #include +#include #include + #include #include #include @@ -46,21 +48,59 @@ struct format_impl next() { has_placeholder = false; + bool unmatched_open = false; + bool unmatched_close = false; while (p != end) { - if (*p++ != '{') - continue; - if (p == end) - break; - if (*p++ == '}') + if(unmatched_open) + { + if(*p == '{') + { + p++; + core::string_view seg(p0, (p - 1) - p0); + p0 = p; + return seg; + } + if(*p == '}') + { + p++; + core::string_view seg(p0, (p - 2) - p0); + p0 = p; + has_placeholder = true; + return seg; + } + detail::throw_invalid_argument( + "invalid format string, unmatched {"); + } + if(unmatched_close) + { + if(*p == '}') + { + p++; + core::string_view seg(p0, (p - 1) - p0); + p0 = p; + return seg; + } + detail::throw_invalid_argument( + "invalid format string, unmatched }"); + } + if (*p == '{') + { + unmatched_open = true; + } + if(*p == '}') { - core::string_view seg( - p0, (p - 2) - p0); - p0 = p; - has_placeholder = true; - return seg; + unmatched_close = true; } + p++; } + if (unmatched_open) + detail::throw_invalid_argument( + "invalid format string, unmatched {"); + if(unmatched_close) + detail::throw_invalid_argument( + "invalid format string, unmatched }"); + core::string_view seg( p0, end - p0); p0 = end; @@ -71,10 +111,14 @@ struct format_impl void do_arg(Arg const& arg) { core::string_view seg = next(); - if (seg.size()) - os.write(seg.data(), static_cast< - std::streamsize>(seg.size())); - if (has_placeholder) + while(seg.size()) + { + os.write(seg.data(), static_cast(seg.size())); + if(has_placeholder) + break; + seg = next(); + } + if(has_placeholder) os << arg; }; @@ -83,9 +127,18 @@ struct format_impl { using expander = int[]; (void)expander{0, (do_arg(args), 0)...}; - if (p0 < end) - os.write(p0, static_cast< - std::streamsize>(end - p0)); + + core::string_view seg; + do + { + seg = next(); + if(has_placeholder) + detail::throw_invalid_argument( + "too few format arguments provided"); + if(seg.size()) + os.write(seg.data(), static_cast(seg.size())); + } + while(seg.size()); } }; diff --git a/test/unit/format.cpp b/test/unit/format.cpp index aabf66f0..a4c506ac 100644 --- a/test/unit/format.cpp +++ b/test/unit/format.cpp @@ -28,22 +28,49 @@ struct format_test BOOST_TEST_EQ(s, match); } + template + void e( + core::string_view match, + core::string_view fs, + Args const&... args) + { + BOOST_TEST_THROWS(f(match, fs, args...), std::invalid_argument); + } + void run() { + // Bad format strings, string arg. + e("{}", "{}"); + e("{", "{"); + e("}", "}"); + e("}{", "}{"); + e("{", "{", "x"); + e("}", "}", "x"); + e("}{", "}{", "x"); + e("{", "{", "x"); + e("}", "}", "x"); + e("}{", "}{", "x"); + e("1{}2{}3","1{}2{}3"); + e("1a2{}3", "1{}2{}3", "a"); + + // Good format strings, string arg. f("x", "x"); - f("{}", "{}"); - f("{", "{"); - f("}", "}"); - f("}{", "}{"); + f("{", "{{"); + f("}", "}}"); + f("}{", "}}{{"); f("x", "x"); f("x", "{}", "x"); - f("{", "{", "x"); - f("}", "}", "x"); - f("}{", "}{", "x"); - f("1{}2{}3","1{}2{}3"); - f("1a2{}3", "1{}2{}3", "a"); + f("x", "{}", "x"); + f("{", "{{", "x"); + f("}", "}}", "x"); + f("}{", "}}{{", "x"); f("1a2b3", "1{}2{}3", "a", "b"); + f("1a2b3", "1{}2{}3", "a", "b", "c"); f("hello world!", "hello {}!", "world"); + f("hello world! {} ", "hello {}! {{}} ", "world"); + + // Good format string, char arg + f("x", "{}", 'x'); } }; From b64bc65db8d778c4bf5de8143f2d792812e6a118 Mon Sep 17 00:00:00 2001 From: Mungo Gill Date: Tue, 25 Nov 2025 16:30:02 +0000 Subject: [PATCH 11/40] test stream write cancellation support and beast2::write tests --- include/boost/beast2/impl/write.hpp | 17 +- .../boost/beast2/test/detail/stream_state.hpp | 42 +- include/boost/beast2/test/impl/stream.hpp | 407 ++++++++++++------ include/boost/beast2/test/stream.hpp | 14 +- test/unit/CMakeLists.txt | 4 +- test/unit/write.cpp | 297 ++++++++++++- 6 files changed, 615 insertions(+), 166 deletions(-) diff --git a/include/boost/beast2/impl/write.hpp b/include/boost/beast2/impl/write.hpp index 7eb10f1e..0efd95c8 100644 --- a/include/boost/beast2/impl/write.hpp +++ b/include/boost/beast2/impl/write.hpp @@ -54,6 +54,9 @@ class write_some_op BOOST_ASIO_CORO_REENTER(*this) { + self.reset_cancellation_state( + asio::enable_total_cancellation()); + rv = sr_.prepare(); if(! rv) { @@ -117,8 +120,17 @@ class write_op { BOOST_ASIO_CORO_REENTER(*this) { + self.reset_cancellation_state(asio::enable_total_cancellation()); + do { + if(!!self.cancelled()) + { + ec = asio::error::operation_aborted; + + break; // goto upcall + } + BOOST_ASIO_CORO_YIELD { BOOST_ASIO_HANDLER_LOCATION(( @@ -128,12 +140,13 @@ class write_op dest_, sr_, std::move(self)); } n_ += bytes_transferred; + if(ec.failed()) - break; + break; // goto upcall } while(! sr_.is_done()); - // upcall + // upcall: self.complete(ec, n_ ); } } diff --git a/include/boost/beast2/test/detail/stream_state.hpp b/include/boost/beast2/test/detail/stream_state.hpp index fc73055e..53bb5550 100644 --- a/include/boost/beast2/test/detail/stream_state.hpp +++ b/include/boost/beast2/test/detail/stream_state.hpp @@ -66,9 +66,9 @@ class stream_service //------------------------------------------------------------------------------ -struct stream_read_op_base +struct stream_op_base { - virtual ~stream_read_op_base() = default; + virtual ~stream_op_base() = default; virtual void operator()(system::error_code ec) = 0; }; @@ -89,8 +89,9 @@ struct stream_state std::mutex m; std::string storage; buffers::string_buffer b; - std::condition_variable cv; - std::unique_ptr op; + //std::condition_variable cv; + std::unique_ptr rop; + std::unique_ptr wop; stream_status code = stream_status::ok; fail_count* fc = nullptr; std::size_t nread = 0; @@ -133,14 +134,17 @@ void stream_service:: shutdown() { - std::vector> v; - std::lock_guard g1(sp_->m_); - v.reserve(sp_->v_.size()); - for(auto p : sp_->v_) + std::vector> v; { - std::lock_guard g2(p->m); - v.emplace_back(std::move(p->op)); - p->code = detail::stream_status::eof; + std::lock_guard g1(sp_->m_); + v.reserve(2 * sp_->v_.size()); + for(auto p : sp_->v_) + { + std::lock_guard g2(p->m); + v.emplace_back(std::move(p->rop)); + v.emplace_back(std::move(p->wop)); + p->code = detail::stream_status::eof; + } } } @@ -200,8 +204,11 @@ stream_state:: ~stream_state() { // cancel outstanding read - if(op != nullptr) - (*op)(asio::error::operation_aborted); + if(rop != nullptr) + (*rop)(asio::error::operation_aborted); + // cancel outstanding write + if(wop != nullptr) + (*wop)(asio::error::operation_aborted); } inline @@ -223,16 +230,13 @@ void stream_state:: notify_read() { - if(op) + if(rop) { - auto op_ = std::move(op); + auto op_ = std::move(rop); op_->operator()(system::error_code{}); } - else - { - cv.notify_all(); - } } + } // detail } // test } // beast2 diff --git a/include/boost/beast2/test/impl/stream.hpp b/include/boost/beast2/test/impl/stream.hpp index 5144609d..ec626e4e 100644 --- a/include/boost/beast2/test/impl/stream.hpp +++ b/include/boost/beast2/test/impl/stream.hpp @@ -16,7 +16,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -47,136 +49,252 @@ struct extract_executor_op return ex; } }; -} // detail -template -template -class basic_stream::read_op : public detail::stream_read_op_base +template +class lambda_base { - class lambda +protected: + Handler h_; + boost::weak_ptr iwp_; + boost::weak_ptr owp_; + Buffers b_; + asio::executor_work_guard< + asio::associated_executor_t> + wg2_; + + class cancellation_handler { - Handler h_; - boost::weak_ptr wp_; - Buffers b_; - asio::executor_work_guard< - asio::associated_executor_t> wg2_; - - class cancellation_handler + public: + explicit cancellation_handler( + boost::weak_ptr wp) + : wp_(std::move(wp)) { - public: - explicit - cancellation_handler( - boost::weak_ptr wp) - : wp_(std::move(wp)) - { - } + } - void - operator()(asio::cancellation_type type) const + void + operator()(asio::cancellation_type type) const + { + if(type != asio::cancellation_type::none) { - if(type != asio::cancellation_type::none) + if(auto sp = wp_.lock()) { - if(auto sp = wp_.lock()) + std::unique_ptr p; { - std::unique_ptr p; - { - std::lock_guard lock(sp->m); - p = std::move(sp->op); - } - if(p != nullptr) - (*p)(asio::error::operation_aborted); + std::lock_guard lock(sp->m); + if(Reader) + p = std::move(sp->rop); + else + p = std::move(sp->wop); } + if(p != nullptr) + (*p)(asio::error::operation_aborted); } } + } - private: - boost::weak_ptr wp_; - }; + private: + boost::weak_ptr wp_; + }; - public: - template - lambda( - Handler_&& h, - boost::shared_ptr const& s, - Buffers const& b) - : h_(std::forward(h)) - , wp_(s) - , b_(b) - , wg2_(asio::get_associated_executor(h_, s->exec)) - { - auto c_slot = asio::get_associated_cancellation_slot(h_); - if (c_slot.is_connected()) - c_slot.template emplace(wp_); - } +public: + template + lambda_base( + Handler_&& h, + boost::shared_ptr const& in, + boost::weak_ptr const& out, + Buffers const& b) + : h_(std::forward(h)) + , iwp_(in) + , owp_(out) + , b_(b) + , wg2_(asio::get_associated_executor(h_, in->exec)) + { + auto c_slot = asio::get_associated_cancellation_slot(h_); + if(c_slot.is_connected()) + c_slot.template emplace(iwp_); + } - using allocator_type = asio::associated_allocator_t; + using allocator_type = asio::associated_allocator_t; - allocator_type get_allocator() const noexcept - { - return asio::get_associated_allocator(h_); - } + allocator_type + get_allocator() const noexcept + { + return asio::get_associated_allocator(h_); + } - using cancellation_slot_type = - asio::associated_cancellation_slot_t; + using cancellation_slot_type = + asio::associated_cancellation_slot_t; - cancellation_slot_type - get_cancellation_slot() const noexcept - { - return asio::get_associated_cancellation_slot(h_, - asio::cancellation_slot()); - } + cancellation_slot_type + get_cancellation_slot() const noexcept + { + return asio::get_associated_cancellation_slot( + h_, asio::cancellation_slot()); + } +}; + +} // detail + +template +template +class basic_stream::read_op : public detail::stream_op_base +{ + class lambda : public detail::lambda_base + { + public: + using base = detail::lambda_base; void operator()(system::error_code ec) { std::size_t bytes_transferred = 0; - auto sp = wp_.lock(); + auto sp = base::iwp_.lock(); if(! sp) { ec = asio::error::operation_aborted; } - if(! ec) + if(!ec) { std::lock_guard lock(sp->m); - BOOST_ASSERT(! sp->op); + BOOST_ASSERT(!sp->rop); if(sp->b.size() > 0) { - bytes_transferred = - buffers::copy( - b_, sp->b.data(), sp->read_max); + bytes_transferred = buffers::copy( + base::b_, + sp->b.data(), + sp->read_max); sp->b.consume(bytes_transferred); sp->nread_bytes += bytes_transferred; } - else if (buffers::size(b_) > 0) + else if( + buffers::size( + base::b_) > 0) { ec = asio::error::eof; } } - asio::dispatch(wg2_.get_executor(), - asio::append(std::move(h_), ec, bytes_transferred)); - wg2_.reset(); + asio::dispatch( + base::wg2_.get_executor(), + asio::append(std::move(base::h_), ec, bytes_transferred)); + base::wg2_.reset(); + sp->rop.reset(nullptr); + } + + template + lambda( + Handler_&& h, + boost::shared_ptr const& in, + boost::weak_ptr const& out, + Buffers const& b) + : base(std::forward(h), in, out, b) + { } }; - lambda fn_; + std::unique_ptr fnp_; asio::executor_work_guard wg1_; public: template read_op( Handler_&& h, - boost::shared_ptr const& s, + boost::shared_ptr const& in, + boost::weak_ptr const& out, + Buffers const& b) + : fnp_(new lambda(std::forward(h), in, out, b)) + , wg1_(in->exec) + { + } + + void + operator()(system::error_code ec) override + { + std::unique_ptr fnp(std::move(fnp_)); + if(fnp) + asio::post( + wg1_.get_executor(), asio::append(std::move(*fnp), ec)); + wg1_.reset(); + } +}; + +template +template +class basic_stream::write_op : public detail::stream_op_base +{ + class lambda : public detail::lambda_base + { + public: + using base = detail::lambda_base; + + void + operator()(system::error_code ec) + { + std::size_t bytes_transferred = 0; + auto isp = base::iwp_.lock(); + if(!isp) + { + ec = asio::error::operation_aborted; + } + auto osp = base::owp_.lock(); + if(!osp) + { + ec = asio::error::operation_aborted; + } + if(!ec) + { + // copy buffers + std::size_t n = std::min( + buffers::size(base::b_), isp->write_max); + { + std::lock_guard lock(osp->m); + n = buffers::copy(osp->b.prepare(n), base::b_); + osp->b.commit(n); + osp->nwrite_bytes += n; + osp->notify_read(); + } + bytes_transferred = n; + } + + asio::dispatch( + base::wg2_.get_executor(), + asio::append(std::move(base::h_), ec, bytes_transferred)); + base::wg2_.reset(); + isp->wop.reset(nullptr); + } + + template + lambda( + Handler_&& h, + boost::shared_ptr const& in, + boost::weak_ptr const& out, + Buffers const& b) + : base(std::forward(h), in, out, b) + { + } + }; + + std::unique_ptr fnp_; + asio::executor_work_guard wg1_; + +public: + template + write_op( + Handler_&& h, + boost::shared_ptr const& in, + boost::weak_ptr const& out, Buffers const& b) - : fn_(std::forward(h), s, b) - , wg1_(s->exec) + : fnp_(new lambda(std::forward(h), in, out, b)) + , wg1_(in->exec) { } void operator()(system::error_code ec) override { - asio::post(wg1_.get_executor(), asio::append(std::move(fn_), ec)); + std::unique_ptr fnp(std::move(fnp_)); + if(fnp) + asio::post(wg1_.get_executor(), asio::append(std::move(*fnp), ec)); wg1_.reset(); } }; @@ -184,14 +302,14 @@ class basic_stream::read_op : public detail::stream_read_op_base template struct basic_stream::run_read_op { - boost::shared_ptr const& in; + boost::shared_ptr const& in_; using executor_type = typename basic_stream::executor_type; executor_type get_executor() const noexcept { - return detail::extract_executor_op()(in->exec); + return detail::extract_executor_op()(in_->exec); } template< @@ -200,6 +318,7 @@ struct basic_stream::run_read_op void operator()( ReadHandler&& h, + boost::weak_ptr out, MutableBufferSequence const& buffers) { // If you get an error on the following line it means @@ -207,14 +326,11 @@ struct basic_stream::run_read_op // requirements for the handler. initiate_read( - in, - std::unique_ptr{ - new read_op< + in_, + out, + std::unique_ptr{ new read_op< typename std::decay::type, - MutableBufferSequence>( - std::move(h), - in, - buffers)}, + MutableBufferSequence>(std::move(h), in_, out, buffers) }, buffers::size(buffers)); } }; @@ -238,46 +354,20 @@ struct basic_stream::run_write_op void operator()( WriteHandler&& h, - boost::weak_ptr out_, + boost::weak_ptr out, ConstBufferSequence const& buffers) { // If you get an error on the following line it means // that your handler does not meet the documented type // requirements for the handler. - ++in_->nwrite; - auto const upcall = [&](system::error_code ec, std::size_t n) - { - asio::post(in_->exec, asio::append(std::move(h), ec, n)); - }; - - // test failure - system::error_code ec; - std::size_t n = 0; - if(in_->fc && in_->fc->fail(ec)) - return upcall(ec, n); - - // A request to write 0 bytes to a stream is a no-op. - if(buffers::size(buffers) == 0) - return upcall(ec, n); - - // connection closed - auto out = out_.lock(); - if(! out) - return upcall(asio::error::connection_reset, n); - - // copy buffers - n = std::min( - buffers::size(buffers), in_->write_max); - { - std::lock_guard lock(out->m); - n = buffers::copy(out->b.prepare(n), buffers); - out->b.commit(n); - out->nwrite_bytes += n; - out->notify_read(); - } - BOOST_ASSERT(! ec); - upcall(ec, n); + initiate_write( + in_, + out, + std::unique_ptr{ new write_op< + typename std::decay::type, + ConstBufferSequence>(std::move(h), in_, out, buffers) }, + buffers::size(buffers)); } }; @@ -301,6 +391,7 @@ async_read_some( void(system::error_code, std::size_t)>( run_read_op{in_}, handler, + out_, buffers); } @@ -345,49 +436,95 @@ auto basic_stream::get_executor() noexcept -> executor_type return detail::extract_executor_op()(in_->exec); } - //------------------------------------------------------------------------------ template -void basic_stream::initiate_read( - boost::shared_ptr const& in_, - std::unique_ptr&& op, +void +basic_stream::initiate_read( + boost::shared_ptr const& in, + boost::weak_ptr const& out, + std::unique_ptr&& rop, std::size_t buf_size) { - std::unique_lock lock(in_->m); + (void)out; + + std::unique_lock lock(in->m); - ++in_->nread; - if(in_->op != nullptr) - BOOST_THROW_EXCEPTION( - std::logic_error{"in_->op != nullptr"}); + ++in->nread; + if(in->rop != nullptr) + BOOST_THROW_EXCEPTION(std::logic_error{ "in_->rop != nullptr" }); // test failure system::error_code ec; - if(in_->fc && in_->fc->fail(ec)) + if(in->fc && in->fc->fail(ec)) { lock.unlock(); - (*op)(ec); + (*rop)(ec); return; } // A request to read 0 bytes from a stream is a no-op. - if(buf_size == 0 || buffers::size(in_->b.data()) > 0) + if(buf_size == 0 || buffers::size(in->b.data()) > 0) { lock.unlock(); - (*op)(ec); + (*rop)(ec); return; } // deliver error - if(in_->code != detail::stream_status::ok) + if(in->code != detail::stream_status::ok) { lock.unlock(); - (*op)(asio::error::eof); + (*rop)(asio::error::eof); return; } // complete when bytes available or closed - in_->op = std::move(op); + in->rop = std::move(rop); +} + +//------------------------------------------------------------------------------ + +template +void basic_stream::initiate_write( + boost::shared_ptr const& in, + boost::weak_ptr const& out, + std::unique_ptr&& wop, + std::size_t buf_size) +{ + { + std::unique_lock lock(in->m); + + ++in->nwrite; + + // test failure + system::error_code ec; + if(in->fc && in->fc->fail(ec)) + { + lock.unlock(); + (*wop)(ec); + return; + } + } + + // A request to write 0 bytes to a stream is a no-op. + if(buf_size == 0) + { + (*wop)(system::error_code{}); + return; + } + + // connection closed + auto osp = out.lock(); + if(!osp) + { + (*wop)(asio::error::connection_reset); + return; + } + + in->wop = std::move(wop); + //auto op = std::move(in_->wop); + in->wop->operator()(system::error_code{}); } //------------------------------------------------------------------------------ diff --git a/include/boost/beast2/test/stream.hpp b/include/boost/beast2/test/stream.hpp index a5b636ea..9cf5051b 100644 --- a/include/boost/beast2/test/stream.hpp +++ b/include/boost/beast2/test/stream.hpp @@ -131,6 +131,9 @@ class basic_stream template class read_op; + template + class write_op; + struct run_read_op; struct run_write_op; @@ -138,7 +141,16 @@ class basic_stream void initiate_read( boost::shared_ptr const& in, - std::unique_ptr&& op, + boost::weak_ptr const& out, + std::unique_ptr&& op, + std::size_t buf_size); + + + static void + initiate_write( + boost::shared_ptr const& in, + boost::weak_ptr const& out, + std::unique_ptr&& op, std::size_t buf_size); #if ! BOOST_BEAST2_DOXYGEN diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 47781c44..b65645ea 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -7,7 +7,9 @@ # Official repository: https://github.com/cppalliance/beast2 # -add_subdirectory(../../../url/extra/test_suite test_suite) +if(NOT TARGET boost_url_test_suite) + add_subdirectory(../../../url/extra/test_suite test_suite) +endif() file(GLOB_RECURSE PFILES CONFIGURE_DEPENDS *.cpp *.hpp) list(APPEND PFILES diff --git a/test/unit/write.cpp b/test/unit/write.cpp index 90c5da40..543fd0a4 100644 --- a/test/unit/write.cpp +++ b/test/unit/write.cpp @@ -15,28 +15,309 @@ namespace boost { namespace beast2 { -class any_async_read_stream -{ -}; +//class any_async_read_stream +//{ +//}; +// +//class write_test +//{ +//public: +// void +// testWrite() +// { +// } +// +// void +// run() +// { +// testWrite(); +// } +//}; +// +//TEST_SUITE( +// write_test, +// "boost.beast2.write"); + +} // beast2 +} // boost + +// +// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) +// Copyright (c) 2025 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 +// + +// Test that header file is self-contained. +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_helpers.hpp" + +#include + +namespace boost { +namespace beast2 { class write_test { + core::string_view const headers = + "HTTP/1.1 200 OK\r\n" + "Content-Length: 3\r\n" + "\r\n"; + core::string_view const body = + "abc"; + core::string_view const msg = + "HTTP/1.1 200 OK\r\n" + "Content-Length: 3\r\n" + "\r\n" + "abc"; + public: void - testWrite() + testAsyncWriteSome() { + boost::asio::io_context ioc; + boost::capy::polystore capy_ctx; + http_proto::install_serializer_service(capy_ctx, {}); + + // async_write_some completes when the serializer writes the message. + { + test::stream ts{ ioc }, tr{ ioc }; + ts.connect(tr); + ts.write_size(1); + + http_proto::serializer sr(capy_ctx); + sr.reset(); + + http_proto::response res(headers); + sr.start(res, buffers::const_buffer(body.data(), body.size())); + + for(std::size_t total = 0; total < msg.size(); total++) + { + async_write_some( + ts, + sr, + [&](system::error_code ec, std::size_t n) + { + BOOST_TEST(!ec.failed()); + BOOST_TEST_EQ(n, 1); + }); + test::run(ioc); + } + BOOST_TEST(sr.is_done()); + BOOST_TEST_EQ(tr.str(), msg); + + BOOST_TEST_EQ(tr.str(), msg); + } + + // async_write_some reports stream errors + { + test::fail_count fc(3, asio::error::network_down); + test::stream ts{ ioc, fc }, tr{ ioc }; + ts.connect(tr); + ts.write_size(1); + + http_proto::serializer sr(capy_ctx); + sr.reset(); + + http_proto::response res(headers); + sr.start(res, buffers::const_buffer(body.data(), body.size())); + + for(int count = 0; count < 3; count++) + { + async_write_some( + ts, + sr, + [&](system::error_code ec, std::size_t n) + { + if (count < 2) + { + BOOST_TEST(!ec.failed()); + BOOST_TEST_EQ(n, 1); + } + else + { + BOOST_TEST_EQ(ec, asio::error::network_down); + BOOST_TEST_EQ(n, 0); + } + }); + test::run(ioc); + + auto expected = msg.substr(0, (count == 0) ? 1 : 2); + BOOST_TEST_EQ(tr.str(), expected); + } + } + + // async_write_some cancellation + { + boost::array ctypes{ + { asio::cancellation_type::total, + asio::cancellation_type::partial, + asio::cancellation_type::terminal }}; + + for(auto ctype : ctypes) + { + test::stream ts{ ioc }, tr{ ioc }; + ts.connect(tr); + ts.write_size(5); + + asio::cancellation_signal c_signal; + + http_proto::serializer sr(capy_ctx); + sr.reset(); + + http_proto::response res(headers); + sr.start(res, buffers::const_buffer(body.data(), body.size())); + + // async_read_some cancels after reading 0 bytes + async_write_some( + ts, + sr, + asio::bind_cancellation_slot( + c_signal.slot(), + [](system::error_code ec, std::size_t n) + { + BOOST_TEST_EQ(n, 5); + BOOST_TEST(!ec.failed()); + })); + c_signal.emit(ctype); + + test::run(ioc); + + BOOST_TEST_EQ(tr.str(), "HTTP/"); + } + } + } + + void + testAsyncWrite() + { + boost::asio::io_context ioc; + capy::polystore capy_ctx; + http_proto::install_serializer_service(capy_ctx, {}); + + // async_write completes when the serializer writes + // the entire message. + { + test::stream ts{ ioc }, tr{ ioc }; + ts.connect(tr); + ts.write_size(1); + + http_proto::serializer sr(capy_ctx); + sr.reset(); + + http_proto::response res(headers); + sr.start(res, buffers::const_buffer(body.data(), body.size())); + + async_write( + ts, + sr, + [&](system::error_code ec, std::size_t n) + { + BOOST_TEST(!ec.failed()); + BOOST_TEST_EQ(n, msg.size()); + }); + + test::run(ioc); + + BOOST_TEST_EQ(ts.nwrite(), msg.size()); // because of ts.write_size(1) + BOOST_TEST(sr.is_done()); + BOOST_TEST_EQ(tr.str(), msg); + } + + // async_write reports stream errors + { + test::fail_count fc(3, asio::error::network_down); + test::stream ts{ ioc, fc }, tr{ ioc }; + ts.connect(tr); + ts.write_size(1); + + http_proto::serializer sr(capy_ctx); + sr.reset(); + + http_proto::response res(headers); + sr.start(res, buffers::const_buffer(body.data(), body.size())); + + async_write( + ts, + sr, + [&](system::error_code ec, std::size_t n) + { + BOOST_TEST_EQ(ec, asio::error::network_down); + BOOST_TEST_EQ(n, 2); + }); + test::run(ioc); + + auto expected = msg.substr(0, 2); + BOOST_TEST_EQ(tr.str(), expected); + + } + + // async_write cancellation + { + boost::array ctypes{ + { asio::cancellation_type::total, + asio::cancellation_type::partial, + asio::cancellation_type::terminal }}; + + for(auto ctype : ctypes) + { + test::stream ts{ ioc }, tr{ ioc }; + ts.connect(tr); + ts.write_size(5); + + asio::cancellation_signal c_signal; + + http_proto::serializer sr(capy_ctx); + sr.reset(); + + http_proto::response res(headers); + sr.start(res, buffers::const_buffer(body.data(), body.size())); + + // cancel after writing + async_write( + ts, + sr, + asio::bind_cancellation_slot( + c_signal.slot(), + [](system::error_code ec, std::size_t n) + { + BOOST_TEST_EQ(n, 5); + BOOST_TEST_EQ(ec, asio::error::operation_aborted); + })); + c_signal.emit(ctype); + + test::run(ioc); + + BOOST_TEST_EQ(tr.str(), "HTTP/"); + } + } } void run() { - testWrite(); + testAsyncWriteSome(); + testAsyncWrite(); } }; -TEST_SUITE( - write_test, - "boost.beast2.write"); +TEST_SUITE(write_test, "boost.beast2.write"); } // beast2 } // boost From 3ca9508c82b50e9cfcbe4324ddb5b7f794a22ebe Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 19 Dec 2025 18:06:04 -0800 Subject: [PATCH 12/40] chore: json exploration --- example/post-rpc.html | 137 ++++++++++++++++++++++++++++++++++ example/server/CMakeLists.txt | 1 + example/server/main.cpp | 119 ++++++++++++++++++++++++++--- 3 files changed, 248 insertions(+), 9 deletions(-) create mode 100644 example/post-rpc.html diff --git a/example/post-rpc.html b/example/post-rpc.html new file mode 100644 index 00000000..136ff6e4 --- /dev/null +++ b/example/post-rpc.html @@ -0,0 +1,137 @@ + + + + + + JSON POST Client + + + +
+

JSON POST Client

+ + + + + + + + + +
+
+ + + + \ No newline at end of file diff --git a/example/server/CMakeLists.txt b/example/server/CMakeLists.txt index 139c9213..9f19ed63 100644 --- a/example/server/CMakeLists.txt +++ b/example/server/CMakeLists.txt @@ -26,6 +26,7 @@ target_include_directories(beast2_server_example PRIVATE .) target_link_libraries( beast2_server_example Boost::beast2 + Boost::json Boost::url OpenSSL::SSL OpenSSL::Crypto diff --git a/example/server/main.cpp b/example/server/main.cpp index 9713599d..698d28c5 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -24,9 +24,13 @@ #include #include #include +#include #include namespace boost { + +namespace http = http_proto; + namespace beast2 { void install_services(capy::application& app) @@ -42,12 +46,101 @@ void install_services(capy::application& app) #endif // VFALCO These ugly incantations are needed for http_proto and will hopefully go away soon. - http_proto::install_parser_service(app, - http_proto::request_parser::config()); - http_proto::install_serializer_service(app, - http_proto::serializer::config()); + http::install_parser_service(app, + http::request_parser::config()); + http::install_serializer_service(app, + http::serializer::config()); } +/* + +struct json_rpc; + +// Handle POST by parsing JSON-RPC, +// storing `json_rpc` in `rp.request_data` +// This validates the JSON and can respond with an error +// +app.use("/rpc", json_rpc_post()) + +// Process the JSON-RPC command +app.post( + "/rpc", + json_post(), + do_json_rpc() + ); + +*/ + +class json_sink : public http::sink +{ +public: + explicit + json_sink( + json::value& jv) : jv_(jv) + { + } + +private: + results + on_write( + buffers::const_buffer b, + bool more) override + { + results rv; + if(more) + { + rv.bytes = pr_.write_some( + static_cast( + b.data()), b.size(), rv.ec); + } + else + { + rv.bytes = pr_.write( + static_cast(b.data()), + b.size(), rv.ec); + } + if(! rv.ec.failed()) + { + jv_ = pr_.release(); + return rv; + } + return rv; + } + + json::value& jv_; + json::parser pr_; +}; + +struct post_json_rpc +{ + auto operator()( + http::route_params& rp) const -> + http::route_result + { + if(! rp.is_method(http::method::post)) + return http::route::next; + BOOST_ASSERT(rp.parser.is_complete()); + auto& jv = rp.route_data.emplace(); + rp.parser.set_body(jv); + system::error_code ec; + rp.parser.parse(ec); + if(ec.failed()) + return ec; + return http::route::next; + } +}; + +struct do_json_rpc +{ + auto operator()( + http::route_params&) const -> + http::route_result + { + return http::route::next; + } +}; + + int server_main( int argc, char* argv[] ) { try @@ -73,12 +166,20 @@ int server_main( int argc, char* argv[] ) //srv.wwwroot.use("/alt", serve_static( argv[3] )); //srv.wwwroot.use("/detach", serve_detached()); //srv.wwwroot.use(post_work()); - srv.wwwroot.use( - http_proto::cors(), - []( http_proto::route_params&) -> - http_proto::route_result + http::cors_options opts; + opts.allowedHeaders = "Content-Type"; + srv.wwwroot.use("/rpc", + http::cors(opts), + post_json_rpc(), + []( http::route_params& rp) -> + http::route_result { - return http_proto::route::next; + if(rp.parser.is_complete()) + { + auto s = rp.parser.body(); + return http::route::next; + } + return http::route::next; }); srv.wwwroot.use("/", serve_static( argv[3] )); From 3532e92777d667d97f166dfe67d2cb786c88c75a Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sat, 20 Dec 2025 06:56:07 -0800 Subject: [PATCH 13/40] feat: coroutine types --- .cursorignore | 25 +++++ .github/workflows/ci-failure-auto-fix.yml | 97 ---------------- .github/workflows/claude.yml | 58 ---------- example/CMakeLists.txt | 1 + example/Jamfile | 1 + example/client/burl/CMakeLists.txt | 67 ++++++----- example/client/jsonrpc/CMakeLists.txt | 27 +++-- example/cpp20/CMakeLists.txt | 10 ++ example/cpp20/Jamfile | 10 ++ example/cpp20/co_spawn/CMakeLists.txt | 29 +++++ example/cpp20/co_spawn/Jamfile | 35 ++++++ example/cpp20/co_spawn/async_42.hpp | 38 +++++++ example/cpp20/co_spawn/async_result.hpp | 100 +++++++++++++++++ example/cpp20/co_spawn/main.cpp | 93 +++++++++++++++ example/cpp20/co_spawn/stream.hpp | 26 +++++ example/server/main.cpp | 18 +-- example/server/post_work.cpp | 29 ----- example/server/serve_detached.hpp | 8 +- include/boost/beast2/body_read_stream.hpp | 9 +- include/boost/beast2/detail/config.hpp | 16 +++ .../boost/beast2/impl/body_read_stream.hpp | 6 +- include/boost/beast2/impl/read.hpp | 20 ++-- include/boost/beast2/impl/write.hpp | 22 ++-- include/boost/beast2/read.hpp | 6 +- include/boost/beast2/server/body_source.hpp | 4 +- include/boost/beast2/server/http_stream.hpp | 62 +++++----- include/boost/beast2/server/plain_worker.hpp | 2 +- .../beast2/server/route_handler_asio.hpp | 28 ++++- include/boost/beast2/server/router.hpp | 2 +- include/boost/beast2/server/router_asio.hpp | 2 +- .../boost/beast2/server/serve_redirect.hpp | 4 +- include/boost/beast2/server/serve_static.hpp | 2 +- include/boost/beast2/spawn.hpp | 80 +++++++++++++ include/boost/beast2/write.hpp | 6 +- src/error.cpp | 4 +- src/server/http_server.cpp | 2 +- src/server/serve_redirect.cpp | 30 ++--- src/server/serve_static.cpp | 30 ++--- test/unit/stream.cpp | 106 ++++++++++++++++++ 39 files changed, 764 insertions(+), 351 deletions(-) create mode 100644 .cursorignore delete mode 100644 .github/workflows/ci-failure-auto-fix.yml delete mode 100644 .github/workflows/claude.yml create mode 100644 example/cpp20/CMakeLists.txt create mode 100644 example/cpp20/Jamfile create mode 100644 example/cpp20/co_spawn/CMakeLists.txt create mode 100644 example/cpp20/co_spawn/Jamfile create mode 100644 example/cpp20/co_spawn/async_42.hpp create mode 100644 example/cpp20/co_spawn/async_result.hpp create mode 100644 example/cpp20/co_spawn/main.cpp create mode 100644 example/cpp20/co_spawn/stream.hpp create mode 100644 include/boost/beast2/spawn.hpp create mode 100644 test/unit/stream.cpp diff --git a/.cursorignore b/.cursorignore new file mode 100644 index 00000000..2a869fdb --- /dev/null +++ b/.cursorignore @@ -0,0 +1,25 @@ +/bin +/bin64 + +/__build__ +/toolchain.cmake + +# Emacs +*# + +# Vim +*~ + +# Visual Studio +/.vs +/out + +# Visual Studio Code +/.vscode +CMakeUserPresets.json + +# clangd +/.cache +/.clangd +/compile_commands.json +# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv) diff --git a/.github/workflows/ci-failure-auto-fix.yml b/.github/workflows/ci-failure-auto-fix.yml deleted file mode 100644 index ff08a7a2..00000000 --- a/.github/workflows/ci-failure-auto-fix.yml +++ /dev/null @@ -1,97 +0,0 @@ -name: Auto Fix CI Failures - -on: - workflow_run: - workflows: ["CI"] - types: - - completed - -permissions: - contents: write - pull-requests: write - actions: read - issues: write - id-token: write # Required for OIDC token exchange - -jobs: - auto-fix: - if: | - github.event.workflow_run.conclusion == 'failure' && - github.event.workflow_run.pull_requests[0] && - !startsWith(github.event.workflow_run.head_branch, 'claude-auto-fix-ci-') - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v5 - with: - ref: ${{ github.event.workflow_run.head_branch }} - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Setup git identity - run: | - git config --global user.email "claude[bot]@users.noreply.github.com" - git config --global user.name "claude[bot]" - - - name: Create fix branch - id: branch - run: | - BRANCH_NAME="claude-auto-fix-ci-${{ github.event.workflow_run.head_branch }}-${{ github.run_id }}" - git checkout -b "$BRANCH_NAME" - echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT - - - name: Get CI failure details - id: failure_details - uses: actions/github-script@v7 - with: - script: | - const run = await github.rest.actions.getWorkflowRun({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: ${{ github.event.workflow_run.id }} - }); - - const jobs = await github.rest.actions.listJobsForWorkflowRun({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: ${{ github.event.workflow_run.id }} - }); - - const failedJobs = jobs.data.jobs.filter(job => job.conclusion === 'failure'); - - let errorLogs = []; - for (const job of failedJobs) { - const logs = await github.rest.actions.downloadJobLogsForWorkflowRun({ - owner: context.repo.owner, - repo: context.repo.repo, - job_id: job.id - }); - errorLogs.push({ - jobName: job.name, - logs: logs.data - }); - } - - return { - runUrl: run.data.html_url, - failedJobs: failedJobs.map(j => j.name), - errorLogs: errorLogs - }; - - - name: Fix CI failures with Claude - id: claude - uses: anthropics/claude-code-action@v1 - with: - prompt: | - /fix-ci - Failed CI Run: ${{ fromJSON(steps.failure_details.outputs.result).runUrl }} - Failed Jobs: ${{ join(fromJSON(steps.failure_details.outputs.result).failedJobs, ', ') }} - PR Number: ${{ github.event.workflow_run.pull_requests[0].number }} - Branch Name: ${{ steps.branch.outputs.branch_name }} - Base Branch: ${{ github.event.workflow_run.head_branch }} - Repository: ${{ github.repository }} - - Error logs: - ${{ toJSON(fromJSON(steps.failure_details.outputs.result).errorLogs) }} - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - claude_args: "--allowedTools 'Edit,MultiEdit,Write,Read,Glob,Grep,LS,Bash(git:*),Bash(bun:*),Bash(npm:*),Bash(npx:*),Bash(gh:*)'" \ No newline at end of file diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml deleted file mode 100644 index c264a6a9..00000000 --- a/.github/workflows/claude.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Claude Code - -on: - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - issues: - types: [opened, assigned] - pull_request_review: - types: [submitted] - -jobs: - claude: - if: | - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || - (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - issues: write - id-token: write - actions: read # Required for Claude to read CI results on PRs - steps: - - name: Checkout repository - uses: actions/checkout@v5 - with: - fetch-depth: 1 - - - name: Run Claude Code - id: claude - uses: anthropics/claude-code-action@v1 - with: - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - - # Optional: Customize the trigger phrase (default: @claude) - # trigger_phrase: "/claude" - - # Optional: Trigger when specific user is assigned to an issue - # assignee_trigger: "claude-bot" - - # Optional: Configure Claude's behavior with CLI arguments - # claude_args: | - # --model claude-opus-4-1-20250805 - # --max-turns 10 - # --allowedTools "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)" - # --system-prompt "Follow our coding standards. Ensure all new code has tests. Use TypeScript for new files." - - # Optional: Advanced settings configuration - # settings: | - # { - # "env": { - # "NODE_ENV": "test" - # } - # } \ No newline at end of file diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index f9abbaca..36fb1b41 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -10,3 +10,4 @@ add_subdirectory(client) add_subdirectory(server) +add_subdirectory(cpp20) diff --git a/example/Jamfile b/example/Jamfile index 7d19c3d5..b92d59e0 100644 --- a/example/Jamfile +++ b/example/Jamfile @@ -10,3 +10,4 @@ build-project client ; build-project server ; +build-project cpp20 ; diff --git a/example/client/burl/CMakeLists.txt b/example/client/burl/CMakeLists.txt index 5cd5e701..21e68b92 100644 --- a/example/client/burl/CMakeLists.txt +++ b/example/client/burl/CMakeLists.txt @@ -7,48 +7,47 @@ # Official repository: https://github.com/cppalliance/beast2 # -if (CMAKE_CXX_STANDARD EQUAL 20) - file(GLOB_RECURSE PFILES CONFIGURE_DEPENDS *.cpp *.hpp - CMakeLists.txt - Jamfile) +file(GLOB_RECURSE PFILES CONFIGURE_DEPENDS *.cpp *.hpp + CMakeLists.txt + Jamfile) - source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "" FILES ${PFILES}) +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "" FILES ${PFILES}) - add_executable(beast2_example_client_burl ${PFILES}) +add_executable(beast2_example_client_burl ${PFILES}) - target_compile_definitions(beast2_example_client_burl - PRIVATE BOOST_ASIO_NO_DEPRECATED) +target_compile_definitions(beast2_example_client_burl + PRIVATE BOOST_ASIO_NO_DEPRECATED) - set_property(TARGET beast2_example_client_burl - PROPERTY FOLDER "examples") +set_property(TARGET beast2_example_client_burl + PROPERTY FOLDER "examples") - find_package(OpenSSL REQUIRED) +find_package(OpenSSL REQUIRED) - target_link_libraries(beast2_example_client_burl - Boost::beast2 - Boost::url - Boost::program_options - Boost::scope - OpenSSL::SSL - OpenSSL::Crypto) +target_compile_features(beast2_example_client_burl PUBLIC cxx_std_20) - if (WIN32) - target_link_libraries(beast2_example_client_burl crypt32) - endif() +target_link_libraries(beast2_example_client_burl + Boost::beast2 + Boost::url + Boost::program_options + Boost::scope + OpenSSL::SSL + OpenSSL::Crypto) - if (TARGET Boost::capy_zlib) - target_link_libraries(beast2_example_client_burl Boost::capy_zlib) - endif() - - if (TARGET Boost::capy_brotli) - target_link_libraries(beast2_example_client_burl Boost::capy_brotli) - endif() +if (WIN32) + target_link_libraries(beast2_example_client_burl crypt32) +endif() - list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") - find_package(Libpsl) - if (Libpsl_FOUND) - target_link_libraries(beast2_example_client_burl Libpsl::Libpsl) - target_compile_definitions(beast2_example_client_burl PRIVATE BURL_HAS_LIBPSL) - endif () +if (TARGET Boost::capy_zlib) + target_link_libraries(beast2_example_client_burl Boost::capy_zlib) +endif() +if (TARGET Boost::capy_brotli) + target_link_libraries(beast2_example_client_burl Boost::capy_brotli) endif() + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +find_package(Libpsl) +if (Libpsl_FOUND) + target_link_libraries(beast2_example_client_burl Libpsl::Libpsl) + target_compile_definitions(beast2_example_client_burl PRIVATE BURL_HAS_LIBPSL) +endif () diff --git a/example/client/jsonrpc/CMakeLists.txt b/example/client/jsonrpc/CMakeLists.txt index d3c4b40f..50695995 100644 --- a/example/client/jsonrpc/CMakeLists.txt +++ b/example/client/jsonrpc/CMakeLists.txt @@ -52,17 +52,16 @@ if (TARGET Boost::capy_brotli) endif() # CPP20 Example -if (CMAKE_CXX_STANDARD EQUAL 20) - add_executable(beast2_example_client_jsonrpc_cpp20 cpp20.cpp eth_methods.hpp Jamfile) - set_property(TARGET beast2_example_client_jsonrpc_cpp20 - PROPERTY FOLDER "examples") - target_link_libraries(beast2_example_client_jsonrpc_cpp20 - PRIVATE - beast2_example_client_jsonrpc_lib) - if (TARGET Boost::capy_zlib) - target_link_libraries(beast2_example_client_jsonrpc_cpp20 PRIVATE Boost::capy_zlib) - endif() - if (TARGET Boost::capy_brotli) - target_link_libraries(beast2_example_client_jsonrpc_cpp20 PRIVATE Boost::capy_brotli) - endif() -endif () +add_executable(beast2_example_client_jsonrpc_cpp20 cpp20.cpp eth_methods.hpp Jamfile) +set_property(TARGET beast2_example_client_jsonrpc_cpp20 + PROPERTY FOLDER "examples") +target_compile_features(beast2_example_client_jsonrpc_cpp20 PUBLIC cxx_std_20) +target_link_libraries(beast2_example_client_jsonrpc_cpp20 + PRIVATE + beast2_example_client_jsonrpc_lib) +if (TARGET Boost::capy_zlib) + target_link_libraries(beast2_example_client_jsonrpc_cpp20 PRIVATE Boost::capy_zlib) +endif() +if (TARGET Boost::capy_brotli) + target_link_libraries(beast2_example_client_jsonrpc_cpp20 PRIVATE Boost::capy_brotli) +endif() diff --git a/example/cpp20/CMakeLists.txt b/example/cpp20/CMakeLists.txt new file mode 100644 index 00000000..21d46b2d --- /dev/null +++ b/example/cpp20/CMakeLists.txt @@ -0,0 +1,10 @@ +# +# 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 +# + +add_subdirectory(co_spawn) diff --git a/example/cpp20/Jamfile b/example/cpp20/Jamfile new file mode 100644 index 00000000..2e6efaaa --- /dev/null +++ b/example/cpp20/Jamfile @@ -0,0 +1,10 @@ +# +# 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 +# + +build-project co_spawn ; diff --git a/example/cpp20/co_spawn/CMakeLists.txt b/example/cpp20/co_spawn/CMakeLists.txt new file mode 100644 index 00000000..25015b44 --- /dev/null +++ b/example/cpp20/co_spawn/CMakeLists.txt @@ -0,0 +1,29 @@ +# +# Copyright (c) 2025 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 +# + +file(GLOB_RECURSE PFILES CONFIGURE_DEPENDS *.cpp *.hpp + CMakeLists.txt + Jamfile) + +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "" FILES ${PFILES}) + +add_executable(beast2_example_co_spawn ${PFILES}) + +target_compile_definitions(beast2_example_co_spawn + PRIVATE BOOST_ASIO_NO_DEPRECATED) + +set_property(TARGET beast2_example_co_spawn + PROPERTY FOLDER "examples") + +find_package(OpenSSL REQUIRED) + +target_compile_features(beast2_example_co_spawn PUBLIC cxx_std_20) + +target_link_libraries(beast2_example_co_spawn + Boost::beast2) diff --git a/example/cpp20/co_spawn/Jamfile b/example/cpp20/co_spawn/Jamfile new file mode 100644 index 00000000..61a440a4 --- /dev/null +++ b/example/cpp20/co_spawn/Jamfile @@ -0,0 +1,35 @@ +# +# Copyright (c) 2025 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 +# + +using openssl ; +import ac ; + +lib advapi32 ; +lib crypt32 ; +lib gdi32 ; +lib user32 ; + +project + : requirements + /boost/beast2//boost_beast2 + [ ac.check-library /boost/capy//boost_capy_zlib : /boost/capy//boost_capy_zlib : ] + [ ac.check-library /boost/capy//boost_capy_brotli : /boost/capy//boost_capy_brotli : ] + [ ac.check-library /openssl//ssl : /openssl//ssl/shared : no ] + [ ac.check-library /openssl//crypto : /openssl//crypto/shared : no ] + windows:advapi32 + windows:crypt32 + windows:gdi32 + windows:user32 + /boost/url//boost_url + . + ; + +exe get : + [ glob *.cpp ] + ; diff --git a/example/cpp20/co_spawn/async_42.hpp b/example/cpp20/co_spawn/async_42.hpp new file mode 100644 index 00000000..c9d2e198 --- /dev/null +++ b/example/cpp20/co_spawn/async_42.hpp @@ -0,0 +1,38 @@ +// +// 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/capy +// + +#ifndef BOOST_BEAST2_TASK_HPP +#define BOOST_BEAST2_TASK_HPP + +#include +#include + +namespace boost { +namespace beast2 { + +template +auto async_42(Executor const& exec, CompletionToken&& token) +{ + return asio::async_initiate( + [](auto handler, Executor exec) + { + boost::asio::post(exec, + [handler = std::move(handler)]() mutable + { + std::move(handler)(42); + }); + }, + token, + exec); +} + +} // beast2 +} // boost + +#endif diff --git a/example/cpp20/co_spawn/async_result.hpp b/example/cpp20/co_spawn/async_result.hpp new file mode 100644 index 00000000..d40ec7f5 --- /dev/null +++ b/example/cpp20/co_spawn/async_result.hpp @@ -0,0 +1,100 @@ +// +// 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/capy +// + +#ifndef BOOST_CAPY_ASYNC_RESULT_HPP +#define BOOST_CAPY_ASYNC_RESULT_HPP + +#include +#include +#include +#include +#include + +namespace boost { +namespace capy { + +template +class async_result +{ +public: + struct impl_base + { + virtual ~impl_base() = default; + virtual void start(std::function on_done) = 0; + virtual T get_result() = 0; + }; + +private: + std::unique_ptr impl_; + +public: + explicit async_result(std::unique_ptr p) : impl_(std::move(p)) {} + + async_result(async_result&&) = default; + async_result& operator=(async_result&&) = default; + + bool await_ready() const noexcept { return false; } + + void await_suspend(std::coroutine_handle<> h) + { + impl_->start([h]{ h.resume(); }); + } + + T await_resume() + { + return impl_->get_result(); + } +}; + +//----------------------------------------------------------------------------- + +template +struct async_result_impl : capy::async_result::impl_base +{ + DeferredOp op_; + std::variant result_; + + explicit async_result_impl(DeferredOp&& op) + : op_(std::forward(op)) + { + } + + void start(std::function on_done) override + { + std::move(op_)( + [this, on_done = std::move(on_done)](auto&&... args) mutable + { + result_.template emplace<1>(T{std::forward(args)...}); + on_done(); + }); + } + + T get_result() override + { + if (result_.index() == 0 && std::get<0>(result_)) + std::rethrow_exception(std::get<0>(result_)); + return std::move(std::get<1>(result_)); + } +}; + +//----------------------------------------------------------------------------- + +template +capy::async_result +make_async_result(DeferredOp&& op) +{ + using impl_type = async_result_impl>; + return capy::async_result( + std::make_unique(std::forward(op))); +} + +} // capy +} // boost + +#endif diff --git a/example/cpp20/co_spawn/main.cpp b/example/cpp20/co_spawn/main.cpp new file mode 100644 index 00000000..f61d8ac6 --- /dev/null +++ b/example/cpp20/co_spawn/main.cpp @@ -0,0 +1,93 @@ +// +// 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 "async_42.hpp" +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace boost { + +namespace buffers { + +auto read_all( + any_stream s, + buffers::mutable_buffer b) + -> capy::task +{ + std::size_t total = 0; + while(b.size() > 0) + { + auto [ec, n] = co_await s.read_some(b); + total += n; + if(ec.failed()) + co_return { ec, total }; + buffers::remove_prefix(b, n); + } + co_return { {}, total }; +} + +} // buffers + +namespace beast2 { + +struct stream +{ + asio::any_io_executor ex_; + + template + stream(Executor const& ex) + : ex_(ex) + { + } + + capy::async_result + read_some() + { + return capy::make_async_result( + async_42(ex_, asio::deferred)); + } +}; + +} // beast2 + +capy::task handler() +{ + co_return 42; +} + +void boost_main() +{ + asio::io_context ioc; + + beast2::spawn( + ioc.get_executor(), + handler(), + [](std::variant result) + { + if (result.index() == 0) + std::rethrow_exception(std::get<0>(result)); + std::cout << "result: " << std::get<1>(result) << "\n"; + }); + + ioc.run(); +} + +} // boost + +int main(int, char**) +{ + boost::boost_main(); +} diff --git a/example/cpp20/co_spawn/stream.hpp b/example/cpp20/co_spawn/stream.hpp new file mode 100644 index 00000000..73ef10ef --- /dev/null +++ b/example/cpp20/co_spawn/stream.hpp @@ -0,0 +1,26 @@ +// +// 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/capy +// + +#ifndef BOOST_CAPY_STREAM_HPP +#define BOOST_CAPY_STREAM_HPP + +#include "task.hpp" + +namespace boost { +namespace capy { + +class stream +{ + +}; + +} // capy +} // boost + +#endif diff --git a/example/server/main.cpp b/example/server/main.cpp index 698d28c5..f98a1e52 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -28,9 +28,6 @@ #include namespace boost { - -namespace http = http_proto; - namespace beast2 { void install_services(capy::application& app) @@ -79,7 +76,6 @@ class json_sink : public http::sink json::value& jv) : jv_(jv) { } - private: results on_write( @@ -140,6 +136,13 @@ struct do_json_rpc } }; +auto +do_request( + http::route_params& rp) -> + capy::task +{ + co_return http::route::next; +} int server_main( int argc, char* argv[] ) { @@ -162,12 +165,11 @@ int server_main( int argc, char* argv[] ) (unsigned short)std::atoi(argv[2]), std::atoi(argv[4])); - //srv.wwwroot.use("/log", serve_log_admin(app)); - //srv.wwwroot.use("/alt", serve_static( argv[3] )); - //srv.wwwroot.use("/detach", serve_detached()); - //srv.wwwroot.use(post_work()); http::cors_options opts; opts.allowedHeaders = "Content-Type"; + + srv.wwwroot.use( http::co_route( do_request ) ); + srv.wwwroot.use("/rpc", http::cors(opts), post_json_rpc(), diff --git a/example/server/post_work.cpp b/example/server/post_work.cpp index db9a5136..eca5a121 100644 --- a/example/server/post_work.cpp +++ b/example/server/post_work.cpp @@ -12,35 +12,6 @@ namespace boost { namespace beast2 { -/* -struct etag -{ - Request& req; - Response& res; - sha1_state digest; - - void operator()( resumer resume ) - { - char buf[8192]; - system::error_code ec; - auto nread = res.body.read( - buffers::make_buffer(buf), ec); - digest.update( buf, nread ); - if(ec == error::eof) - { - res.body.rewind(); - res.set_header( - http::field::etag, - to_hex(digest.finalize()) ); - return resume( route::next ); - } - if( ec.failed() ) - return resume( ec ); - // we will get called again - } -}; -*/ - namespace { struct task diff --git a/example/server/serve_detached.hpp b/example/server/serve_detached.hpp index bd1c8498..e9a9337d 100644 --- a/example/server/serve_detached.hpp +++ b/example/server/serve_detached.hpp @@ -39,9 +39,9 @@ class serve_detached system::error_code operator()( - http_proto::route_params& p) const + http_proto::route_params& rp) const { - return p.detach( + return rp.suspend( [&](http_proto::resumer resume) { asio::post(*tp_, @@ -49,8 +49,8 @@ class serve_detached { // Simulate some asynchronous work std::this_thread::sleep_for(std::chrono::seconds(1)); - p.status(http_proto::status::ok); - p.set_body("Hello from serve_detached!\n"); + rp.status(http_proto::status::ok); + rp.set_body("Hello from serve_detached!\n"); resume(http_proto::route::send); // resume( res.send("Hello from serve_detached!\n") ); }); diff --git a/include/boost/beast2/body_read_stream.hpp b/include/boost/beast2/body_read_stream.hpp index c9287f50..cc2fa404 100644 --- a/include/boost/beast2/body_read_stream.hpp +++ b/include/boost/beast2/body_read_stream.hpp @@ -10,6 +10,7 @@ #ifndef BOOST_BEAST2_BODY_READ_STREAM_HPP #define BOOST_BEAST2_BODY_READ_STREAM_HPP +#include #include #include #include @@ -33,7 +34,7 @@ namespace beast2 { referenced in the construction of this object. @see - @ref http_proto::parser. + @ref http::parser. */ template class body_read_stream @@ -71,13 +72,13 @@ class body_read_stream This object's executor is initialized to that of the underlying stream. - @param pr A http_proto::parser object which will perform the parsing of + @param pr A http::parser object which will perform the parsing of the HTTP message and extraction of the body. This must be initialized by the caller and ownership of the parser is retained by the caller, which must guarantee that it remains valid until the handler is called. */ - explicit body_read_stream(AsyncReadStream& s, http_proto::parser& pr); + explicit body_read_stream(AsyncReadStream& s, http::parser& pr); /** Read some data asynchronously. @@ -150,7 +151,7 @@ class body_read_stream private: AsyncReadStream& stream_; - http_proto::parser& pr_; + http::parser& pr_; }; } // beast2 diff --git a/include/boost/beast2/detail/config.hpp b/include/boost/beast2/detail/config.hpp index 405f49cf..f80fc1d7 100644 --- a/include/boost/beast2/detail/config.hpp +++ b/include/boost/beast2/detail/config.hpp @@ -36,6 +36,16 @@ namespace beast2 { # include # endif +//------------------------------------------------ + +#if defined(__cpp_lib_coroutine) && __cpp_lib_coroutine >= 201902L +# define BOOST_BEAST_HAS_CORO 1 +#elif defined(__cpp_impl_coroutine) && __cpp_impl_coroutines >= 201902L +# define BOOST_BEAST_HAS_CORO 1 +#endif + +//------------------------------------------------ + // Add source location to error codes #ifdef BOOST_BEAST2_NO_SOURCE_LOCATION # define BOOST_BEAST2_ERR(ev) (::boost::system::error_code(ev)) @@ -53,6 +63,12 @@ namespace beast2 { #endif } // beast2 + +namespace http_proto {} +namespace beast2 { +namespace http = http_proto; +} + } // boost #endif diff --git a/include/boost/beast2/impl/body_read_stream.hpp b/include/boost/beast2/impl/body_read_stream.hpp index 8b0ae287..550f3ee8 100644 --- a/include/boost/beast2/impl/body_read_stream.hpp +++ b/include/boost/beast2/impl/body_read_stream.hpp @@ -31,13 +31,13 @@ class body_read_stream_op : public asio::coroutine AsyncReadStream& stream_; MutableBufferSequence mb_; - http_proto::parser& pr_; + http::parser& pr_; public: body_read_stream_op( AsyncReadStream& s, MutableBufferSequence&& mb, - http_proto::parser& pr) noexcept + http::parser& pr) noexcept : stream_(s) , mb_(std::move(mb)) , pr_(pr) @@ -133,7 +133,7 @@ class body_read_stream_op : public asio::coroutine template body_read_stream::body_read_stream( AsyncReadStream& s, - http_proto::parser& pr) + http::parser& pr) : stream_(s) , pr_(pr) { diff --git a/include/boost/beast2/impl/read.hpp b/include/boost/beast2/impl/read.hpp index 7ca0fa7e..151d593b 100644 --- a/include/boost/beast2/impl/read.hpp +++ b/include/boost/beast2/impl/read.hpp @@ -30,15 +30,15 @@ class read_until_op : public asio::coroutine { AsyncStream& stream_; - http_proto::parser& pr_; + http::parser& pr_; std::size_t total_bytes_ = 0; - bool (&condition_)(http_proto::parser&); + bool (&condition_)(http::parser&); public: read_until_op( AsyncStream& s, - http_proto::parser& pr, - bool (&condition)(http_proto::parser&)) noexcept + http::parser& pr, + bool (&condition)(http::parser&)) noexcept : stream_(s) , pr_(pr) , condition_(condition) @@ -62,7 +62,7 @@ class read_until_op for(;;) { pr_.parse(ec); - if(ec == http_proto::condition::need_more_input) + if(ec == http::condition::need_more_input) { if(!!self.cancelled()) { @@ -127,14 +127,14 @@ class read_until_op inline bool -got_header_condition(http_proto::parser& pr) +got_header_condition(http::parser& pr) { return pr.got_header(); } inline bool -is_complete_condition(http_proto::parser& pr) +is_complete_condition(http::parser& pr) { return pr.is_complete(); } @@ -151,7 +151,7 @@ BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void (system::error_code, std::size_t)) async_read_some( AsyncReadStream& s, - http_proto::parser& pr, + http::parser& pr, CompletionToken&& token) { return asio::async_compose< @@ -171,7 +171,7 @@ BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void (system::error_code, std::size_t)) async_read_header( AsyncReadStream& s, - http_proto::parser& pr, + http::parser& pr, CompletionToken&& token) { // TODO: async_read_header should not perform a read @@ -187,7 +187,7 @@ BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void (system::error_code, std::size_t)) async_read( AsyncReadStream& s, - http_proto::parser& pr, + http::parser& pr, CompletionToken&& token) { return asio::async_compose< diff --git a/include/boost/beast2/impl/write.hpp b/include/boost/beast2/impl/write.hpp index 0efd95c8..8da9b051 100644 --- a/include/boost/beast2/impl/write.hpp +++ b/include/boost/beast2/impl/write.hpp @@ -29,15 +29,15 @@ class write_some_op : public asio::coroutine { using buffers_type = - http_proto::serializer::const_buffers_type; + http::serializer::const_buffers_type; WriteStream& dest_; - http_proto::serializer& sr_; + http::serializer& sr_; public: write_some_op( WriteStream& dest, - http_proto::serializer& sr) noexcept + http::serializer& sr) noexcept : dest_(dest) , sr_(sr) { @@ -99,13 +99,13 @@ class write_op : public asio::coroutine { WriteStream& dest_; - http_proto::serializer& sr_; + http::serializer& sr_; std::size_t n_ = 0; public: write_op( WriteStream& dest, - http_proto::serializer& sr) noexcept + http::serializer& sr) noexcept : dest_(dest) , sr_(sr) { @@ -165,7 +165,7 @@ class relay_some_op WriteStream& dest_; ReadStream& src_; CompletionCondition cond_; - http_proto::serializer& sr_; + http::serializer& sr_; std::size_t bytes_read_ = 0; public: @@ -173,7 +173,7 @@ class relay_some_op WriteStream& dest, ReadStream& src, CompletionCondition const& cond, - http_proto::serializer& sr) noexcept + http::serializer& sr) noexcept : dest_(dest) , src_(src) , cond_(cond) @@ -189,7 +189,7 @@ class relay_some_op std::size_t bytes_transferred = 0) { urls::result< - http_proto::serializer::buffers> rv; + http::serializer::buffers> rv; BOOST_ASIO_CORO_REENTER(*this) { @@ -241,7 +241,7 @@ BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void (system::error_code, std::size_t)) async_write_some( AsyncWriteStream& dest, - http_proto::serializer& sr, + http::serializer& sr, CompletionToken&& token) { return asio::async_compose< @@ -260,7 +260,7 @@ BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void (system::error_code, std::size_t)) async_write( AsyncWriteStream& dest, - http_proto::serializer& sr, + http::serializer& sr, CompletionToken&& token) { return asio::async_compose< @@ -285,7 +285,7 @@ async_relay_some( AsyncWriteStream& dest, AsyncReadStream& src, CompletionCondition const& cond, - http_proto::serializer& sr, + http::serializer& sr, CompletionToken&& token) { return asio::async_compose< diff --git a/include/boost/beast2/read.hpp b/include/boost/beast2/read.hpp index aece67c1..c328127d 100644 --- a/include/boost/beast2/read.hpp +++ b/include/boost/beast2/read.hpp @@ -85,7 +85,7 @@ BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void (system::error_code, std::size_t)) async_read_header( AsyncReadStream& s, - http_proto::parser& pr, + http::parser& pr, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN( typename AsyncReadStream::executor_type)); @@ -156,7 +156,7 @@ BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void (system::error_code, std::size_t)) async_read_some( AsyncReadStream& s, - http_proto::parser& pr, + http::parser& pr, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN( typename AsyncReadStream::executor_type)); @@ -225,7 +225,7 @@ BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void (system::error_code, std::size_t)) async_read( AsyncReadStream& s, - http_proto::parser& pr, + http::parser& pr, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN( typename AsyncReadStream::executor_type)); diff --git a/include/boost/beast2/server/body_source.hpp b/include/boost/beast2/server/body_source.hpp index 2d485325..8a19ee9b 100644 --- a/include/boost/beast2/server/body_source.hpp +++ b/include/boost/beast2/server/body_source.hpp @@ -187,7 +187,7 @@ class body_source if(impl_) return impl_->read(dest, n, ec); // empty - ec = http_proto::error::end_of_stream; + ec = http::error::end_of_stream; return 0; } @@ -402,7 +402,7 @@ body_source( buffers::sans_prefix(bs_, nread_)); nread_ += n; if(nread_ >= size_) - ec = http_proto::error::end_of_stream; + ec = http::error::end_of_stream; else ec = {}; return n; diff --git a/include/boost/beast2/server/http_stream.hpp b/include/boost/beast2/server/http_stream.hpp index 1d813c11..a2a1f3b2 100644 --- a/include/boost/beast2/server/http_stream.hpp +++ b/include/boost/beast2/server/http_stream.hpp @@ -47,7 +47,7 @@ namespace beast2 { */ template class http_stream - : private http_proto::detacher::owner + : private http::suspender::owner { public: /** Constructor. @@ -81,7 +81,7 @@ class http_stream The stream must be in a connected, correct state for a new session. */ - void on_stream_begin(http_proto::acceptor_config const& config); + void on_stream_begin(http::acceptor_config const& config); private: void do_read(); @@ -89,15 +89,15 @@ class http_stream system::error_code ec, std::size_t bytes_transferred); void on_headers(); - void do_dispatch(http_proto::route_result rv = {}); - void do_respond(http_proto::route_result rv); + void do_dispatch(http::route_result rv = {}); + void do_respond(http::route_result rv); void do_write(); void on_write( system::error_code const& ec, std::size_t bytes_transferred); void on_complete(); - http_proto::resumer do_detach() override; - void do_resume(http_proto::route_result const& ec) override; + http::resumer do_suspend() override; + void do_resume(http::route_result const& ec) override; void do_close(); void do_fail(core::string_view s, system::error_code const& ec); @@ -116,7 +116,7 @@ class http_stream AsyncStream& stream_; router_asio routes_; any_lambda close_; - http_proto::acceptor_config const* pconfig_ = nullptr; + http::acceptor_config const* pconfig_ = nullptr; using work_guard = asio::executor_work_guard().get_executor())>; @@ -174,10 +174,10 @@ http_stream( , close_(close) , p_(stream_) { - p_.parser = http_proto::request_parser(app); + p_.parser = http::request_parser(app); - p_.serializer = http_proto::serializer(app); - p_.detach = http_proto::detacher(*this); + p_.serializer = http::serializer(app); + p_.suspend = http::suspender(*this); } // called to start a new HTTP session. @@ -186,7 +186,7 @@ template void http_stream:: on_stream_begin( - http_proto::acceptor_config const& config) + http::acceptor_config const& config) { pconfig_ = &config; @@ -240,7 +240,7 @@ on_headers() p_.req = p_.parser.get(); p_.route_data.clear(); p_.res.set_start_line( // VFALCO WTF - http_proto::status::ok, p_.req.version()); + http::status::ok, p_.req.version()); p_.res.set_keep_alive(p_.req.keep_alive()); p_.serializer.reset(); @@ -250,7 +250,7 @@ on_headers() if(rv.has_error()) { // error parsing URL - p_.status(http_proto::status::bad_request); + p_.status(http::status::bad_request); p_.set_body("Bad Request: " + rv.error().message()); return do_respond(rv.error()); } @@ -267,11 +267,11 @@ template void http_stream:: do_dispatch( - http_proto::route_result rv) + http::route_result rv) { if(! rv.failed()) { - BOOST_ASSERT(! pwg_); // can't be detached + BOOST_ASSERT(! pwg_); // can't be suspended rv = routes_.dispatch( p_.req.method(), p_.url, p_); } @@ -288,16 +288,16 @@ template void http_stream:: do_respond( - http_proto::route_result rv) + http::route_result rv) { - BOOST_ASSERT(rv != http_proto::route::next_route); + BOOST_ASSERT(rv != http::route::next_route); - if(rv == http_proto::route::close) + if(rv == http::route::close) { return do_close(); } - if(rv == http_proto::route::complete) + if(rv == http::route::complete) { // VFALCO what if the connection was closed or keep-alive=false? // handler sendt the response? @@ -305,27 +305,27 @@ do_respond( return on_write(system::error_code(), 0); } - if(rv == http_proto::route::detach) + if(rv == http::route::suspend) { - // didn't call detach()? + // didn't call suspend()? if(! pwg_) detail::throw_logic_error(); return; } - if(rv == http_proto::route::next) + if(rv == http::route::next) { // unhandled request - auto const status = http_proto::status::not_found; + auto const status = http::status::not_found; p_.status(status); - p_.set_body(http_proto::to_string(status)); + p_.set_body(http::to_string(status)); } - else if(rv != http_proto::route::send) + else if(rv != http::route::send) { // error message of last resort BOOST_ASSERT(rv.failed()); - BOOST_ASSERT(! http_proto::is_route_result(rv)); - p_.status(http_proto::status::internal_server_error); + BOOST_ASSERT(! http::is_route_result(rv)); + p_.status(http::status::internal_server_error); std::string s; format_to(s, "An internal server error occurred: {}", rv.message()); p_.res.set_keep_alive(false); // VFALCO? @@ -374,8 +374,8 @@ on_write( template auto http_stream:: -do_detach() -> - http_proto::resumer +do_suspend() -> + http::resumer { BOOST_ASSERT(stream_.get_executor().running_in_this_thread()); @@ -385,7 +385,7 @@ do_detach() -> // VFALCO cancel timer - return http_proto::resumer(*this); + return http::resumer(*this); } // called by resume(rv) @@ -393,7 +393,7 @@ template void http_stream:: do_resume( - http_proto::route_result const& rv) + http::route_result const& rv) { asio::dispatch( stream_.get_executor(), diff --git a/include/boost/beast2/server/plain_worker.hpp b/include/boost/beast2/server/plain_worker.hpp index b0cc47a4..2b630960 100644 --- a/include/boost/beast2/server/plain_worker.hpp +++ b/include/boost/beast2/server/plain_worker.hpp @@ -36,7 +36,7 @@ class plain_worker using socket_type = asio::basic_stream_socket; using stream_type = socket_type; - using acceptor_config = http_proto::acceptor_config; + using acceptor_config = http::acceptor_config; template plain_worker( diff --git a/include/boost/beast2/server/route_handler_asio.hpp b/include/boost/beast2/server/route_handler_asio.hpp index c00a9dbc..d63f50e3 100644 --- a/include/boost/beast2/server/route_handler_asio.hpp +++ b/include/boost/beast2/server/route_handler_asio.hpp @@ -11,6 +11,7 @@ #define BOOST_BEAST2_SERVER_ROUTE_HANDLER_ASIO_HPP #include +#include #include #include #include @@ -22,7 +23,7 @@ namespace beast2 { */ template class asio_route_params - : public http_proto::route_params + : public http::route_params { public: using stream_type = typename std::decay::type; @@ -38,6 +39,31 @@ class asio_route_params } private: + auto + spawn( + capy::task t) -> + http::route_result override + { + return this->suspend( + [&](http::resumer resume) + { + beast2::spawn( + stream.get_executor(), + std::move(t), + [resume](std::variant< + std::exception_ptr, + http::route_result> v) + { + if(v.index() == 0) + { + std::rethrow_exception( + std::get<0>(v)); + } + resume(std::get<1>(v)); + }); + }); + } + void do_post() override; }; diff --git a/include/boost/beast2/server/router.hpp b/include/boost/beast2/server/router.hpp index c967dfba..aca812c5 100644 --- a/include/boost/beast2/server/router.hpp +++ b/include/boost/beast2/server/router.hpp @@ -19,7 +19,7 @@ namespace beast2 { /** The sans-IO router type */ -using router = http_proto::basic_router; +using router = http::basic_router; } // beast2 } // boost diff --git a/include/boost/beast2/server/router_asio.hpp b/include/boost/beast2/server/router_asio.hpp index 8860a8a7..dc466c0a 100644 --- a/include/boost/beast2/server/router_asio.hpp +++ b/include/boost/beast2/server/router_asio.hpp @@ -20,7 +20,7 @@ namespace beast2 { /** The Asio-aware router type */ template -using router_asio = http_proto::basic_router< +using router_asio = http::basic_router< asio_route_params>; } // beast2 diff --git a/include/boost/beast2/server/serve_redirect.hpp b/include/boost/beast2/server/serve_redirect.hpp index adc105a1..49452295 100644 --- a/include/boost/beast2/server/serve_redirect.hpp +++ b/include/boost/beast2/server/serve_redirect.hpp @@ -19,9 +19,9 @@ namespace beast2 { struct serve_redirect { BOOST_BEAST2_DECL - http_proto::route_result + http::route_result operator()( - http_proto::route_params&) const; + http::route_params&) const; }; } // beast2 diff --git a/include/boost/beast2/server/serve_static.hpp b/include/boost/beast2/server/serve_static.hpp index fb9d5d88..0975ea3b 100644 --- a/include/boost/beast2/server/serve_static.hpp +++ b/include/boost/beast2/server/serve_static.hpp @@ -165,7 +165,7 @@ struct serve_static */ BOOST_BEAST2_DECL system::error_code operator()( - http_proto::route_params&) const; + http::route_params&) const; private: struct impl; diff --git a/include/boost/beast2/spawn.hpp b/include/boost/beast2/spawn.hpp new file mode 100644 index 00000000..66a23966 --- /dev/null +++ b/include/boost/beast2/spawn.hpp @@ -0,0 +1,80 @@ +// +// 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_SPAWN_HPP +#define BOOST_BEAST2_SPAWN_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace beast2 { + +/** Launch a capy::task on the given executor. + + This function is similar to boost::asio::co_spawn, but is used to + launch a capy::task instead of an asio::awaitable. + @param ex The executor on which the task will be launched. + @param t The task to launch. + @param handler The completion handler to be invoked when the task + completes. The handler signature is: + @code + void(std::variant) + @endcode + where the variant holds either an exception_ptr if an exception + was thrown, or the result of type T. + @return The result of the asynchronous initiation. +*/ +template< + class Executor, + class T, + class CompletionHandler> +auto spawn( + Executor const& ex, + capy::task t, + CompletionHandler&& handler) +{ + return asio::async_initiate< + CompletionHandler, + void(std::variant)>( + [ex_ = ex](auto handler, capy::task t) + { + auto h = t.release(); + auto* p = &h.promise(); + + p->on_done_ = [handler = std::move(handler), h, p]() mutable + { + auto& r = p->result_; + if (r.index() == 2) + std::move(handler)(std::variant( + std::in_place_index<0>, std::get<2>(r))); + else + std::move(handler)(std::variant( + std::in_place_index<1>, std::move(std::get<1>(r)))); + h.destroy(); + }; + + asio::post(ex_, [h]{ h.resume(); }); + }, + handler, + std::move(t)); +} + +} // beast2 +} // boost + +#endif diff --git a/include/boost/beast2/write.hpp b/include/boost/beast2/write.hpp index 5dd79c57..d71919dd 100644 --- a/include/boost/beast2/write.hpp +++ b/include/boost/beast2/write.hpp @@ -30,7 +30,7 @@ BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void (system::error_code, std::size_t)) async_write_some( AsyncWriteStream& dest, - http_proto::serializer& sr, + http::serializer& sr, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN( typename AsyncWriteStream::executor_type)); @@ -47,7 +47,7 @@ BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void (system::error_code, std::size_t)) async_write( AsyncWriteStream& dest, - http_proto::serializer& sr, + http::serializer& sr, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN( typename AsyncWriteStream::executor_type)); @@ -69,7 +69,7 @@ async_relay_some( AsyncWriteStream& dest, AsyncReadStream& src, CompletionCondition const& cond, - http_proto::serializer& sr, + http::serializer& sr, CompletionToken&& token BOOST_ASIO_DEFAULT_COMPLETION_TOKEN( typename AsyncWriteStream::executor_type)); diff --git a/src/error.cpp b/src/error.cpp index feccb695..d49d9d5b 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -39,9 +39,9 @@ message( { switch(static_cast(code)) { - case error::success: return "http_proto::error::success"; + case error::success: return "http::error::success"; default: - return "http_proto::error::?"; + return "http::error::?"; } } diff --git a/src/server/http_server.cpp b/src/server/http_server.cpp index 2fdabdf7..1bb612c0 100644 --- a/src/server/http_server.cpp +++ b/src/server/http_server.cpp @@ -38,7 +38,7 @@ class http_server_impl unsigned short port) { w_.emplace( - http_proto::acceptor_config{ false, false }, + http::acceptor_config{ false, false }, asio::ip::tcp::endpoint( asio::ip::make_address(addr), port), diff --git a/src/server/serve_redirect.cpp b/src/server/serve_redirect.cpp index 8d61994e..3dd58f34 100644 --- a/src/server/serve_redirect.cpp +++ b/src/server/serve_redirect.cpp @@ -70,22 +70,22 @@ make_http_date() static void prepare_error( - http_proto::response& res, + http::response& res, std::string& body, - http_proto::status code, - http_proto::request_base const& req) + http::status code, + http::request_base const& req) { res.set_start_line(code, req.version()); - res.append(http_proto::field::server, "boost"); - res.append(http_proto::field::date, make_http_date()); - res.append(http_proto::field::cache_control, "no-store"); - res.append(http_proto::field::content_type, "text/html"); - res.append(http_proto::field::content_language, "en"); + res.append(http::field::server, "boost"); + res.append(http::field::date, make_http_date()); + res.append(http::field::cache_control, "no-store"); + res.append(http::field::content_type, "text/html"); + res.append(http::field::content_language, "en"); // format the numeric code followed by the reason string auto title = std::to_string( static_cast::type>(code)); + http::status>::type>(code)); title.push_back(' '); title.append( res.reason() ); @@ -106,19 +106,19 @@ prepare_error( auto serve_redirect:: operator()( - http_proto::route_params& p) const -> - http_proto::route_result + http::route_params& p) const -> + http::route_result { std::string body; prepare_error(p.res, body, - http_proto::status::moved_permanently, p.req); + http::status::moved_permanently, p.req); urls::url u1(p.req.target()); u1.set_scheme_id(urls::scheme::https); u1.set_host_address("localhost"); // VFALCO WTF IS THIS! - p.res.append(http_proto::field::location, u1.buffer()); + p.res.append(http::field::location, u1.buffer()); p.serializer.start(p.res, - http_proto::string_body( std::move(body))); - return http_proto::route::send; + http::string_body( std::move(body))); + return http::route::send; } } // beast2 diff --git a/src/server/serve_static.cpp b/src/server/serve_static.cpp index 2f8d67cd..ad867abe 100644 --- a/src/server/serve_static.cpp +++ b/src/server/serve_static.cpp @@ -170,21 +170,21 @@ serve_static( auto serve_static:: operator()( - http_proto::route_params& p) const -> - http_proto::route_result + http::route_params& p) const -> + http::route_result { // Allow: GET, HEAD - if( p.req.method() != http_proto::method::get && - p.req.method() != http_proto::method::head) + if( p.req.method() != http::method::get && + p.req.method() != http::method::head) { if(impl_->opt.fallthrough) - return http_proto::route::next; + return http::route::next; p.res.set_status( - http_proto::status::method_not_allowed); - p.res.set(http_proto::field::allow, "GET, HEAD"); + http::status::method_not_allowed); + p.res.set(http::field::allow, "GET, HEAD"); p.set_body(""); - return http_proto::route::send; + return http::route::send; } // Build the path to the requested file @@ -198,31 +198,31 @@ operator()( // Attempt to open the file system::error_code ec; - http_proto::file f; + http::file f; std::uint64_t size = 0; - f.open(path.c_str(), http_proto::file_mode::scan, ec); + f.open(path.c_str(), http::file_mode::scan, ec); if(! ec.failed()) size = f.size(ec); if(! ec.failed()) { p.res.set_start_line( - http_proto::status::ok, + http::status::ok, p.req.version()); p.res.set_payload_size(size); auto mt = mime_type(get_extension(path)); p.res.append( - http_proto::field::content_type, mt); + http::field::content_type, mt); // send file - p.serializer.start( + p.serializer.start( p.res, std::move(f), size); - return http_proto::route::send; + return http::route::send; } if( ec == system::errc::no_such_file_or_directory && ! impl_->opt.fallthrough) - return http_proto::route::next; + return http::route::next; BOOST_ASSERT(ec.failed()); return ec; diff --git a/test/unit/stream.cpp b/test/unit/stream.cpp new file mode 100644 index 00000000..d0a51ed9 --- /dev/null +++ b/test/unit/stream.cpp @@ -0,0 +1,106 @@ +// +// Copyright (c) 2016-2019 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 +#include +#include +#include + +#include "test_suite.hpp" + +#ifdef BOOST_BEAST_HAS_CORO + +namespace boost { +namespace beast2 { + +template +auto make_stream( + AsyncStream&& asioStream) -> + buffers::any_stream +{ + struct impl : buffers::any_stream::impl + { + typename std::decay::type stream_; + + explicit impl(AsyncStream&& s) + : stream_(std::forward(s)) + { + } + + buffers::async_io_result + read_some( + buffers::mutable_buffer b) override + { + return capy::make_async_result( + stream_.async_read_some(b, asio::deferred)); + } + + buffers::async_io_result + write_some( + buffers::const_buffer b) override + { + return capy::make_async_result( + stream_.async_write_some(b, asio::deferred)); + } + }; + + return buffers::any_stream(std::make_shared( + std::forward(asioStream))); +} + +struct stream_test +{ + capy::task + t1() + { + co_return 67; + } + + capy::task + do_read(buffers::any_stream stream) + { + char buf[256]; + buffers::mutable_buffer b(buf, sizeof(buf)); + auto rv = co_await stream.read_some(b); + core::string_view sv(buf, rv.bytes_transferred); + BOOST_TEST(! rv.ec.failed()); + BOOST_TEST_EQ(sv, "lorem ipsum"); + co_return 67; + } + + void + run() + { + asio::io_context ioc; + + spawn( + ioc.get_executor(), + do_read(make_stream(test::stream(ioc, "lorem ipsum"))), + [](std::variant result) + { + if (result.index() == 0) + std::rethrow_exception(std::get<0>(result)); + BOOST_TEST_EQ(std::get<1>(result), 67); + }); + + ioc.run(); + } +}; + +TEST_SUITE(stream_test, "boost.beast2.stream"); + +} // beast2 +} // boost + +#endif From dd89ae8a304a68308543db0348feac063cc01556 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Wed, 24 Dec 2025 02:09:15 -0800 Subject: [PATCH 14/40] chore: fixes --- example/client/burl/message.cpp | 5 +++-- include/boost/beast2.hpp | 2 ++ src/server/serve_static.cpp | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/example/client/burl/message.cpp b/example/client/burl/message.cpp index 551ec9cc..c0ad53af 100644 --- a/example/client/burl/message.cpp +++ b/example/client/burl/message.cpp @@ -10,6 +10,7 @@ #include "message.hpp" #include "mime_type.hpp" +#include #include #include @@ -77,9 +78,9 @@ file_body::content_length() const http_proto::file_source file_body::body() const { - http_proto::file file; + boost::capy::file file; error_code ec; - file.open(path_.c_str(), http_proto::file_mode::read, ec); + file.open(path_.c_str(), boost::capy::file_mode::read, ec); if(ec) throw system_error{ ec }; diff --git a/include/boost/beast2.hpp b/include/boost/beast2.hpp index 08e75724..0b17a646 100644 --- a/include/boost/beast2.hpp +++ b/include/boost/beast2.hpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,7 @@ #include #include #include +#include //#include #include diff --git a/src/server/serve_static.cpp b/src/server/serve_static.cpp index ad867abe..0430f323 100644 --- a/src/server/serve_static.cpp +++ b/src/server/serve_static.cpp @@ -198,9 +198,9 @@ operator()( // Attempt to open the file system::error_code ec; - http::file f; + capy::file f; std::uint64_t size = 0; - f.open(path.c_str(), http::file_mode::scan, ec); + f.open(path.c_str(), capy::file_mode::scan, ec); if(! ec.failed()) size = f.size(ec); if(! ec.failed()) From b95d418310dbf4b0a32d1cfbce40a64b6e895d76 Mon Sep 17 00:00:00 2001 From: Mohammad Nejati Date: Wed, 24 Dec 2025 10:33:44 +0000 Subject: [PATCH 15/40] guard coroutine code with BOOST_CAPY_HAS_CORO --- example/client/burl/CMakeLists.txt | 4 ++++ example/client/burl/multipart_form.cpp | 7 ++++--- example/client/jsonrpc/CMakeLists.txt | 4 ++++ example/cpp20/co_spawn/CMakeLists.txt | 4 ++++ example/cpp20/co_spawn/Jamfile | 6 ++++++ example/server/Jamfile | 1 + example/server/main.cpp | 6 +++++- include/boost/beast2/server/route_handler_asio.hpp | 2 ++ include/boost/beast2/spawn.hpp | 7 ++++++- 9 files changed, 36 insertions(+), 5 deletions(-) diff --git a/example/client/burl/CMakeLists.txt b/example/client/burl/CMakeLists.txt index 21e68b92..f31650dd 100644 --- a/example/client/burl/CMakeLists.txt +++ b/example/client/burl/CMakeLists.txt @@ -7,6 +7,10 @@ # Official repository: https://github.com/cppalliance/beast2 # +if(NOT cxx_std_20 IN_LIST CMAKE_CXX_COMPILE_FEATURES) + return() +endif() + file(GLOB_RECURSE PFILES CONFIGURE_DEPENDS *.cpp *.hpp CMakeLists.txt Jamfile) diff --git a/example/client/burl/multipart_form.cpp b/example/client/burl/multipart_form.cpp index 02ad288c..61fff06b 100644 --- a/example/client/burl/multipart_form.cpp +++ b/example/client/burl/multipart_form.cpp @@ -11,12 +11,13 @@ #include #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; @@ -171,9 +172,9 @@ multipart_form::source::on_read(buffers::mutable_buffer mb) auto read = [&](const std::string& path, uint64_t size) { - http_proto::file file; + capy::file file; - file.open(path.c_str(), http_proto::file_mode::read, rs.ec); + file.open(path.c_str(), capy::file_mode::read, rs.ec); if(rs.ec) return false; diff --git a/example/client/jsonrpc/CMakeLists.txt b/example/client/jsonrpc/CMakeLists.txt index 50695995..462095f5 100644 --- a/example/client/jsonrpc/CMakeLists.txt +++ b/example/client/jsonrpc/CMakeLists.txt @@ -7,6 +7,10 @@ # Official repository: https://github.com/cppalliance/beast2 # +if(NOT cxx_std_20 IN_LIST CMAKE_CXX_COMPILE_FEATURES) + return() +endif() + file(GLOB_RECURSE PFILES CONFIGURE_DEPENDS *.cpp *.hpp CMakeLists.txt Jamfile) diff --git a/example/cpp20/co_spawn/CMakeLists.txt b/example/cpp20/co_spawn/CMakeLists.txt index 25015b44..115b26af 100644 --- a/example/cpp20/co_spawn/CMakeLists.txt +++ b/example/cpp20/co_spawn/CMakeLists.txt @@ -7,6 +7,10 @@ # Official repository: https://github.com/cppalliance/beast2 # +if(NOT cxx_std_20 IN_LIST CMAKE_CXX_COMPILE_FEATURES) + return() +endif() + file(GLOB_RECURSE PFILES CONFIGURE_DEPENDS *.cpp *.hpp CMakeLists.txt Jamfile) diff --git a/example/cpp20/co_spawn/Jamfile b/example/cpp20/co_spawn/Jamfile index 61a440a4..77befb7b 100644 --- a/example/cpp20/co_spawn/Jamfile +++ b/example/cpp20/co_spawn/Jamfile @@ -7,6 +7,8 @@ # Official repository: https://github.com/cppalliance/beast2 # +import config : requires ; + using openssl ; import ac ; @@ -32,4 +34,8 @@ project exe get : [ glob *.cpp ] + : requirements + [ requires + cxx20_hdr_coroutine + ] ; diff --git a/example/server/Jamfile b/example/server/Jamfile index 93255dd3..180f3d45 100644 --- a/example/server/Jamfile +++ b/example/server/Jamfile @@ -20,6 +20,7 @@ project : requirements /boost/beast2//boost_beast2 /boost/url//boost_url + /boost/json//boost_json [ ac.check-library /boost/capy//boost_capy_zlib : /boost/capy//boost_capy_zlib : ] [ ac.check-library /boost/capy//boost_capy_brotli : /boost/capy//boost_capy_brotli : ] [ ac.check-library /openssl//ssl : /openssl//ssl/shared : no ] diff --git a/example/server/main.cpp b/example/server/main.cpp index f98a1e52..794c71f8 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -136,6 +136,7 @@ struct do_json_rpc } }; +#ifdef BOOST_CAPY_HAS_CORO auto do_request( http::route_params& rp) -> @@ -143,6 +144,7 @@ do_request( { co_return http::route::next; } +#endif int server_main( int argc, char* argv[] ) { @@ -168,7 +170,9 @@ int server_main( int argc, char* argv[] ) http::cors_options opts; opts.allowedHeaders = "Content-Type"; +#ifdef BOOST_CAPY_HAS_CORO srv.wwwroot.use( http::co_route( do_request ) ); +#endif srv.wwwroot.use("/rpc", http::cors(opts), @@ -178,7 +182,7 @@ int server_main( int argc, char* argv[] ) { if(rp.parser.is_complete()) { - auto s = rp.parser.body(); + // auto s = rp.parser.body(); return http::route::next; } return http::route::next; diff --git a/include/boost/beast2/server/route_handler_asio.hpp b/include/boost/beast2/server/route_handler_asio.hpp index d63f50e3..a6ec8a1f 100644 --- a/include/boost/beast2/server/route_handler_asio.hpp +++ b/include/boost/beast2/server/route_handler_asio.hpp @@ -39,6 +39,7 @@ class asio_route_params } private: +#ifdef BOOST_CAPY_HAS_CORO auto spawn( capy::task t) -> @@ -63,6 +64,7 @@ class asio_route_params }); }); } +#endif void do_post() override; }; diff --git a/include/boost/beast2/spawn.hpp b/include/boost/beast2/spawn.hpp index 66a23966..4912f04e 100644 --- a/include/boost/beast2/spawn.hpp +++ b/include/boost/beast2/spawn.hpp @@ -10,8 +10,11 @@ #ifndef BOOST_BEAST2_SPAWN_HPP #define BOOST_BEAST2_SPAWN_HPP -#include #include + +#ifdef BOOST_CAPY_HAS_CORO + +#include #include #include #include @@ -78,3 +81,5 @@ auto spawn( } // boost #endif + +#endif From 2707cedc1cc7a4a2d781acdcb9a2654bc5eab3dc Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Wed, 24 Dec 2025 15:02:35 -0800 Subject: [PATCH 16/40] feat: read request body --- example/server/main.cpp | 99 +++++---------- include/boost/beast2/impl/read.hpp | 49 ++++++++ include/boost/beast2/server/http_stream.hpp | 116 ++++++++++++------ .../beast2/server/route_handler_asio.hpp | 14 +++ 4 files changed, 171 insertions(+), 107 deletions(-) diff --git a/example/server/main.cpp b/example/server/main.cpp index 794c71f8..47cbc73b 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -49,33 +49,22 @@ void install_services(capy::application& app) http::serializer::config()); } -/* - -struct json_rpc; - -// Handle POST by parsing JSON-RPC, -// storing `json_rpc` in `rp.request_data` -// This validates the JSON and can respond with an error -// -app.use("/rpc", json_rpc_post()) - -// Process the JSON-RPC command -app.post( - "/rpc", - json_post(), - do_json_rpc() - ); - -*/ - class json_sink : public http::sink { public: - explicit + json_sink(json_sink&&) = default; + json_sink( - json::value& jv) : jv_(jv) + json::storage_ptr sp = {}) + : pr_(new json::parser(std::move(sp))) { } + + auto release() -> json::value + { + return pr_->release(); + } + private: results on_write( @@ -85,29 +74,25 @@ class json_sink : public http::sink results rv; if(more) { - rv.bytes = pr_.write_some( + rv.bytes = pr_->write_some( static_cast( b.data()), b.size(), rv.ec); } else { - rv.bytes = pr_.write( + rv.bytes = pr_->write( static_cast(b.data()), b.size(), rv.ec); } if(! rv.ec.failed()) - { - jv_ = pr_.release(); return rv; - } return rv; } - json::value& jv_; - json::parser pr_; + std::unique_ptr pr_; }; -struct post_json_rpc +struct do_json_rpc { auto operator()( http::route_params& rp) const -> @@ -115,36 +100,26 @@ struct post_json_rpc { if(! rp.is_method(http::method::post)) return http::route::next; - BOOST_ASSERT(rp.parser.is_complete()); - auto& jv = rp.route_data.emplace(); - rp.parser.set_body(jv); - system::error_code ec; - rp.parser.parse(ec); - if(ec.failed()) - return ec; - return http::route::next; + return rp.read_body( + json_sink(), + [this, &rp]( + json::value jv) -> + http::route_result + { + return dispatch(std::move(jv)); + }); } -}; -struct do_json_rpc -{ - auto operator()( - http::route_params&) const -> + // process the JSON-RPC request + auto dispatch( + json::value jv) const -> http::route_result { + (void)jv; return http::route::next; } -}; -#ifdef BOOST_CAPY_HAS_CORO -auto -do_request( - http::route_params& rp) -> - capy::task -{ - co_return http::route::next; -} -#endif +}; int server_main( int argc, char* argv[] ) { @@ -170,23 +145,11 @@ int server_main( int argc, char* argv[] ) http::cors_options opts; opts.allowedHeaders = "Content-Type"; -#ifdef BOOST_CAPY_HAS_CORO - srv.wwwroot.use( http::co_route( do_request ) ); -#endif - - srv.wwwroot.use("/rpc", + srv.wwwroot.use( + "/rpc", http::cors(opts), - post_json_rpc(), - []( http::route_params& rp) -> - http::route_result - { - if(rp.parser.is_complete()) - { - // auto s = rp.parser.body(); - return http::route::next; - } - return http::route::next; - }); + do_json_rpc() + ); srv.wwwroot.use("/", serve_static( argv[3] )); app.start(); diff --git a/include/boost/beast2/impl/read.hpp b/include/boost/beast2/impl/read.hpp index 151d593b..483030a1 100644 --- a/include/boost/beast2/impl/read.hpp +++ b/include/boost/beast2/impl/read.hpp @@ -143,6 +143,23 @@ is_complete_condition(http::parser& pr) //------------------------------------------------ +/** Asynchronously reads some data into the parser. + + This function is used to asynchronously read data from a + stream into the parser's input sequence. This function will always + keep reading until a complete header is obtained. + The function call will invoke the completion token + with the following signature: + @code + void(system::error_code ec + std::size_t bytes_transferred); + @endcode + @note The parser's input sequence may contain additional data + beyond what was required to complete the header. + @param s The stream to read from. + @param pr The parser to read data into. + @param token The completion token. +*/ template< class AsyncReadStream, BOOST_ASIO_COMPLETION_TOKEN_FOR( @@ -163,6 +180,22 @@ async_read_some( s); } +/** Asynchronously reads data into the parser until the header is complete. + This function is used to asynchronously read data from a + stream into the parser's input sequence until the parser's + header is complete. + The function call will invoke the completion token + with the following signature: + @code + void(system::error_code ec + std::size_t bytes_transferred); + @endcode + @note The parser's input sequence may contain additional data + beyond what was required to complete the header. + @param s The stream to read from. + @param pr The parser to read data into. + @param token The completion token. +*/ template< class AsyncReadStream, BOOST_ASIO_COMPLETION_TOKEN_FOR( @@ -179,6 +212,22 @@ async_read_header( return async_read_some(s, pr, std::move(token)); } +/** Asynchronously reads data into the parser until the message is complete. + This function is used to asynchronously read data from a + stream into the parser's input sequence until the parser's + message is complete. + The function call will invoke the completion token + with the following signature: + @code + void(system::error_code ec + std::size_t bytes_transferred); + @endcode + @note The parser's input sequence may contain additional data + beyond what was required to complete the message. + @param s The stream to read from. + @param pr The parser to read data into. + @param token The completion token. +*/ template< class AsyncReadStream, BOOST_ASIO_COMPLETION_TOKEN_FOR( diff --git a/include/boost/beast2/server/http_stream.hpp b/include/boost/beast2/server/http_stream.hpp index a2a1f3b2..4c3ae495 100644 --- a/include/boost/beast2/server/http_stream.hpp +++ b/include/boost/beast2/server/http_stream.hpp @@ -90,6 +90,10 @@ class http_stream std::size_t bytes_transferred); void on_headers(); void do_dispatch(http::route_result rv = {}); + void do_read_body(); + void on_read_body( + system::error_code ec, + std::size_t bytes_transferred); void do_respond(http::route_result rv); void do_write(); void on_write( @@ -121,7 +125,7 @@ class http_stream using work_guard = asio::executor_work_guard().get_executor())>; std::unique_ptr pwg_; - asio_route_params p_; + asio_route_params rp_; }; //------------------------------------------------ @@ -172,12 +176,12 @@ http_stream( , stream_(stream) , routes_(std::move(routes)) , close_(close) - , p_(stream_) + , rp_(stream_) { - p_.parser = http::request_parser(app); + rp_.parser = http::request_parser(app); - p_.serializer = http::serializer(app); - p_.suspend = http::suspender(*this); + rp_.serializer = http::serializer(app); + rp_.suspend = http::suspender(*this); } // called to start a new HTTP session. @@ -190,8 +194,8 @@ on_stream_begin( { pconfig_ = &config; - p_.parser.reset(); - p_.session_data.clear(); + rp_.parser.reset(); + rp_.session_data.clear(); do_read(); } @@ -201,9 +205,11 @@ void http_stream:: do_read() { - p_.parser.start(); + rp_.parser.start(); - beast2::async_read(stream_, p_.parser, + beast2::async_read_some( + stream_, + rp_.parser, call_mf(&http_stream::on_read, this)); } @@ -224,8 +230,6 @@ on_read( "{} http_stream::on_read bytes={}", this->id(), bytes_transferred); - BOOST_ASSERT(p_.parser.is_complete()); - on_headers(); } @@ -237,25 +241,25 @@ on_headers() { // set up Request and Response objects // VFALCO HACK for now we make a copy of the message - p_.req = p_.parser.get(); - p_.route_data.clear(); - p_.res.set_start_line( // VFALCO WTF - http::status::ok, p_.req.version()); - p_.res.set_keep_alive(p_.req.keep_alive()); - p_.serializer.reset(); + rp_.req = rp_.parser.get(); + rp_.route_data.clear(); + rp_.res.set_start_line( // VFALCO WTF + http::status::ok, rp_.req.version()); + rp_.res.set_keep_alive(rp_.req.keep_alive()); + rp_.serializer.reset(); // parse the URL { - auto rv = urls::parse_uri_reference(p_.req.target()); + auto rv = urls::parse_uri_reference(rp_.req.target()); if(rv.has_error()) { // error parsing URL - p_.status(http::status::bad_request); - p_.set_body("Bad Request: " + rv.error().message()); + rp_.status(http::status::bad_request); + rp_.set_body("Bad Request: " + rv.error().message()); return do_respond(rv.error()); } - p_.url = rv.value(); + rp_.url = rv.value(); } // invoke handlers for the route @@ -273,16 +277,48 @@ do_dispatch( { BOOST_ASSERT(! pwg_); // can't be suspended rv = routes_.dispatch( - p_.req.method(), p_.url, p_); + rp_.req.method(), rp_.url, rp_); } else { - rv = routes_.resume(p_, rv); + rv = routes_.resume(rp_, rv); } do_respond(rv); } +// finish reading the body +template +void +http_stream:: +do_read_body() +{ + beast2::async_read( + stream_, + rp_.parser, + call_mf(&http_stream::on_read_body, this)); +} + +// called repeatedly when reading the body +template +void +http_stream:: +on_read_body( + system::error_code ec, + std::size_t bytes_transferred) +{ + if(ec.failed()) + return do_fail("http_stream::on_read_body", ec); + + LOG_TRC(this->sect_)( + "{} http_stream::on_read_body bytes={}", + this->id(), bytes_transferred); + + BOOST_ASSERT(rp_.parser.is_complete()); + + rp_.do_finish(); +} + // called after obtaining a route result template void @@ -300,8 +336,8 @@ do_respond( if(rv == http::route::complete) { // VFALCO what if the connection was closed or keep-alive=false? - // handler sendt the response? - BOOST_ASSERT(p_.serializer.is_done()); + // handler sent the response? + BOOST_ASSERT(rp_.serializer.is_done()); return on_write(system::error_code(), 0); } @@ -310,6 +346,8 @@ do_respond( // didn't call suspend()? if(! pwg_) detail::throw_logic_error(); + if(rp_.parser.is_body_set()) + return do_read_body(); return; } @@ -317,19 +355,19 @@ do_respond( { // unhandled request auto const status = http::status::not_found; - p_.status(status); - p_.set_body(http::to_string(status)); + rp_.status(status); + rp_.set_body(http::to_string(status)); } else if(rv != http::route::send) { // error message of last resort BOOST_ASSERT(rv.failed()); BOOST_ASSERT(! http::is_route_result(rv)); - p_.status(http::status::internal_server_error); + rp_.status(http::status::internal_server_error); std::string s; format_to(s, "An internal server error occurred: {}", rv.message()); - p_.res.set_keep_alive(false); // VFALCO? - p_.set_body(s); + rp_.res.set_keep_alive(false); // VFALCO? + rp_.set_body(s); } do_write(); @@ -341,8 +379,8 @@ void http_stream:: do_write() { - BOOST_ASSERT(! p_.serializer.is_done()); - beast2::async_write(stream_, p_.serializer, + BOOST_ASSERT(! rp_.serializer.is_done()); + beast2::async_write(stream_, rp_.serializer, call_mf(&http_stream::on_write, this)); } @@ -359,13 +397,13 @@ on_write( if(ec.failed()) return do_fail("http_stream::on_write", ec); - BOOST_ASSERT(p_.serializer.is_done()); + BOOST_ASSERT(rp_.serializer.is_done()); LOG_TRC(this->sect_)( "{} http_stream::on_write bytes={}", this->id(), bytes_transferred); - if(p_.res.keep_alive()) + if(rp_.res.keep_alive()) return do_read(); do_close(); @@ -416,8 +454,8 @@ do_fail( LOG_TRC(this->sect_)("{}: {}", s, ec.message()); // tidy up lingering objects - p_.parser.reset(); - p_.serializer.reset(); + rp_.parser.reset(); + rp_.serializer.reset(); close_(ec); } @@ -438,9 +476,9 @@ void http_stream:: clear() noexcept { - p_.parser.reset(); - p_.serializer.reset(); - p_.res.clear(); + rp_.parser.reset(); + rp_.serializer.reset(); + rp_.res.clear(); } } // beast2 diff --git a/include/boost/beast2/server/route_handler_asio.hpp b/include/boost/beast2/server/route_handler_asio.hpp index a6ec8a1f..9dfde614 100644 --- a/include/boost/beast2/server/route_handler_asio.hpp +++ b/include/boost/beast2/server/route_handler_asio.hpp @@ -39,6 +39,20 @@ class asio_route_params } private: +public: + // VFALCO This needs to be private + void do_finish() + { + if(finish_) + { + auto f = std::move(finish_); + finish_ = {}; + f(); + } + } + +private: + #ifdef BOOST_CAPY_HAS_CORO auto spawn( From 74c25ca3d0f664cf5eb14d22ceaee79b454fe484 Mon Sep 17 00:00:00 2001 From: Mohammad Nejati Date: Wed, 31 Dec 2025 17:02:15 +0000 Subject: [PATCH 17/40] use super-project for cmake subdir test --- test/cmake_test/CMakeLists.txt | 70 +--------------------------------- 1 file changed, 2 insertions(+), 68 deletions(-) diff --git a/test/cmake_test/CMakeLists.txt b/test/cmake_test/CMakeLists.txt index c644f6ec..5d0b2166 100644 --- a/test/cmake_test/CMakeLists.txt +++ b/test/cmake_test/CMakeLists.txt @@ -16,74 +16,8 @@ if(BOOST_CI_INSTALL_TEST) find_package(Boost CONFIG REQUIRED COMPONENTS beast2) else() set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) - add_subdirectory(../.. boostorg/beast2) - - set(BOOST_URL_BUILD_TESTS OFF CACHE BOOL "" FORCE) - - set(deps - # Primary dependencies - - asio - assert - config - http_proto - system - throw_exception - - # Secondary dependencies - - align - context - date_time - buffers - compat - core - capy - static_assert - type_traits - url - winapi - variant2 - mp11 - pool - predef - smart_ptr - algorithm - io - lexical_cast - numeric/conversion - range - tokenizer - utility - container_hash - optional - array - bind - concept_check - exception - function - iterator - mpl - regex - tuple - unordered - describe - container - conversion - preprocessor - integer - detail - intrusive - move - fusion - function_types - functional - typeof - ) - - foreach(dep IN LISTS deps) - add_subdirectory(../../../${dep} boostorg/${dep} EXCLUDE_FROM_ALL) - endforeach() + set(BOOST_INCLUDE_LIBRARIES beast2) + add_subdirectory(../../../.. boostorg/boost) endif() add_executable(main main.cpp) From 7611ff3e2ba0d7db41d7290d7b60266f55c1796d Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Tue, 30 Dec 2025 12:22:10 -0800 Subject: [PATCH 18/40] Add wrap_executor for capy::executor integration - Add wrap_executor() to adapt Asio executors to capy::executor - Integrate capy::executor into route_params and spawn for executor affinity - Refactor http_stream, plain_worker, and worker_ssl for new executor model - Add comprehensive wrap_executor unit tests - Remove obsolete route_handler_asio.hpp and post_work files --- CLAUDE.md | 64 -- example/server/main.cpp | 56 +- example/server/post_work.cpp | 43 -- example/server/post_work.hpp | 29 - include/boost/beast2.hpp | 1 + include/boost/beast2/detail/config.hpp | 4 +- include/boost/beast2/server/http_stream.hpp | 43 +- include/boost/beast2/server/plain_worker.hpp | 26 +- .../beast2/server/route_handler_asio.hpp | 49 -- include/boost/beast2/server/worker_ssl.hpp | 37 +- include/boost/beast2/spawn.hpp | 15 +- include/boost/beast2/wrap_executor.hpp | 181 ++++++ test/unit/wrap_executor.cpp | 603 ++++++++++++++++++ 13 files changed, 927 insertions(+), 224 deletions(-) delete mode 100644 CLAUDE.md delete mode 100644 example/server/post_work.cpp delete mode 100644 example/server/post_work.hpp create mode 100644 include/boost/beast2/wrap_executor.hpp create mode 100644 test/unit/wrap_executor.cpp diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index b961be36..00000000 --- a/CLAUDE.md +++ /dev/null @@ -1,64 +0,0 @@ -# CLAUDE.md - -## Code Style - -- C++11 unless otherwise specified -- Boost C++ Libraries naming conventions (snake_case) -- 4-space indentation, no tabs -- Braces on their own line for classes/functions - -## Javadoc Documentation - -Follow Boost C++ Libraries Javadoc style: - -- Brief descriptions on first line after `/**` -- Functions returning values: brief starts with "Return" -- Use `@param` for function parameters -- Use `@tparam` for template parameters, except: - - Variadic args (`Args...`) — omit - - Types deduced from function parameters — omit (self-evident from `@param`) -- Use `@return` for return value details -- Use `@pre` for preconditions -- Use `@post` for postconditions -- Use `@throws` for exceptions -- Use `@note` for important notes -- Use `@see` for cross-references -- Use `@code` / `@endcode` for examples - -## Examples - -```cpp -/** Return the size of the buffer sequence. - - @param buffers The buffer sequence to measure. - - @return The total byte count. -*/ -template -std::size_t -buffer_size(BufferSequence const& buffers); -``` - -No `@tparam` needed—`BufferSequence` is evident from `@param buffers`. - -```cpp -/** Return the default value. - - @tparam T The value type. -*/ -template -T default_value(); -``` - -`@tparam` needed—`T` has no corresponding function parameter. - -## Preferences - -- Concise, dry answers -- Full files, not diffs -- Accurate, compiling C++ code - -## Preconditions and Postconditions - -- The caller of any function returning a route_result must immediately return the route_result and cannot perform other actions -- route handlers which return a route_result must never return a system::error_code which would return false from failed() \ No newline at end of file diff --git a/example/server/main.cpp b/example/server/main.cpp index 47cbc73b..3acc5470 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -8,7 +8,6 @@ // #include "certificate.hpp" -#include "post_work.hpp" #include "serve_detached.hpp" #include "serve_log_admin.hpp" #include @@ -17,9 +16,12 @@ #include #include #include +#include +#include #include #include #include +#include #include #include #include @@ -30,6 +32,8 @@ namespace boost { namespace beast2 { +capy::thread_pool g_tp; + void install_services(capy::application& app) { #ifdef BOOST_CAPY_HAS_BROTLI @@ -102,7 +106,7 @@ struct do_json_rpc return http::route::next; return rp.read_body( json_sink(), - [this, &rp]( + [this]( json::value jv) -> http::route_result { @@ -121,6 +125,46 @@ struct do_json_rpc }; +#ifdef BOOST_BEAST2_HAS_CORO +auto +my_coro( + http::route_params& rp) -> + capy::task +{ + (void)rp; + asio::thread_pool tp(1); + co_await capy::make_async_result( + [&tp](auto&& handler) + { + asio::post(tp.get_executor(), + [handler = std::move(handler)]() mutable + { + // Simulate some asynchronous work + std::this_thread::sleep_for(std::chrono::seconds(1)); + handler(); + }); + }); + co_return http::route::next; +} + +auto +do_bcrypt( + http::route_params& rp) -> + capy::task +{ + std::string password = "boost"; + //auto rv = co_await capy::bcrypt::async_hash(password); + co_await g_tp.get_executor().submit( + []() + { + std::this_thread::sleep_for(std::chrono::seconds(1)); + }); + co_return http::route::next; +} + + +#endif + int server_main( int argc, char* argv[] ) { try @@ -150,6 +194,14 @@ int server_main( int argc, char* argv[] ) http::cors(opts), do_json_rpc() ); +#ifdef BOOST_BEAST2_HAS_CORO + srv.wwwroot.use( + "/spawn", + http::co_route(my_coro)); + srv.wwwroot.use( + "/bcrypt", + http::co_route(do_bcrypt)); +#endif srv.wwwroot.use("/", serve_static( argv[3] )); app.start(); diff --git a/example/server/post_work.cpp b/example/server/post_work.cpp deleted file mode 100644 index eca5a121..00000000 --- a/example/server/post_work.cpp +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (c) 2022 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 "post_work.hpp" - -namespace boost { -namespace beast2 { - -namespace { - -struct task -{ - std::size_t i = 10; - - void - operator()(http_proto::resumer resume) - { - if(i--) - return; - resume(http_proto::route::next); - } -}; - -} // (anon) - -//------------------------------------------------ - -http_proto::route_result -post_work:: -operator()( - http_proto::route_params& p) const -{ - return p.post(task()); -} - -} // beast2 -} // boost diff --git a/example/server/post_work.hpp b/example/server/post_work.hpp deleted file mode 100644 index b75c398d..00000000 --- a/example/server/post_work.hpp +++ /dev/null @@ -1,29 +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_POST_WORK_HPP -#define BOOST_BEAST2_SERVER_POST_WORK_HPP - -#include -#include - -namespace boost { -namespace beast2 { - -struct post_work -{ - system::error_code - operator()( - http_proto::route_params&) const; -}; - -} // beast2 -} // boost - -#endif diff --git a/include/boost/beast2.hpp b/include/boost/beast2.hpp index 0b17a646..64cb64be 100644 --- a/include/boost/beast2.hpp +++ b/include/boost/beast2.hpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include diff --git a/include/boost/beast2/detail/config.hpp b/include/boost/beast2/detail/config.hpp index f80fc1d7..9e00d20d 100644 --- a/include/boost/beast2/detail/config.hpp +++ b/include/boost/beast2/detail/config.hpp @@ -39,9 +39,9 @@ namespace beast2 { //------------------------------------------------ #if defined(__cpp_lib_coroutine) && __cpp_lib_coroutine >= 201902L -# define BOOST_BEAST_HAS_CORO 1 +# define BOOST_BEAST2_HAS_CORO 1 #elif defined(__cpp_impl_coroutine) && __cpp_impl_coroutines >= 201902L -# define BOOST_BEAST_HAS_CORO 1 +# define BOOST_BEAST2_HAS_CORO 1 #endif //------------------------------------------------ diff --git a/include/boost/beast2/server/http_stream.hpp b/include/boost/beast2/server/http_stream.hpp index 4c3ae495..265ff469 100644 --- a/include/boost/beast2/server/http_stream.hpp +++ b/include/boost/beast2/server/http_stream.hpp @@ -12,13 +12,14 @@ #include #include +#include #include #include -#include #include #include #include -#include +#include +#include #include #include #include @@ -101,7 +102,8 @@ class http_stream std::size_t bytes_transferred); void on_complete(); http::resumer do_suspend() override; - void do_resume(http::route_result const& ec) override; + void do_resume(http::route_result const& rv) override; + void do_resume(std::exception_ptr ep) override; void do_close(); void do_fail(core::string_view s, system::error_code const& ec); @@ -182,6 +184,7 @@ http_stream( rp_.serializer = http::serializer(app); rp_.suspend = http::suspender(*this); + rp_.ex = wrap_executor(stream_.get_executor()); } // called to start a new HTTP session. @@ -444,6 +447,40 @@ do_resume( }); } +// called by resume(ep) +template +void +http_stream:: +do_resume( + std::exception_ptr ep) +{ + asio::dispatch( + stream_.get_executor(), + [this, ep] + { + BOOST_ASSERT(pwg_.get() != nullptr); + pwg_.reset(); + + rp_.status(http::status::internal_server_error); + try + { + std::rethrow_exception(ep); + } + catch(std::exception const& e) + { + std::string s; + format_to(s, "An internal server error occurred: {}", e.what()); + rp_.set_body(s); + } + catch(...) + { + rp_.set_body("An internal server error occurred"); + } + rp_.res.set_keep_alive(false); + do_write(); + }); +} + // called when a non-recoverable error occurs template void diff --git a/include/boost/beast2/server/plain_worker.hpp b/include/boost/beast2/server/plain_worker.hpp index 2b630960..8a7c40f7 100644 --- a/include/boost/beast2/server/plain_worker.hpp +++ b/include/boost/beast2/server/plain_worker.hpp @@ -20,16 +20,21 @@ #include #include #include +#include namespace boost { namespace beast2 { template class plain_worker - : public http_stream< - asio::basic_stream_socket - > + : private boost::base_from_member< + asio::basic_stream_socket> + , public http_stream< + asio::basic_stream_socket> { + using base_member = boost::base_from_member< + asio::basic_stream_socket>; + public: using executor_type = Executor; using protocol_type = Protocol; @@ -51,7 +56,7 @@ class plain_worker socket_type& socket() noexcept { - return stream_; + return this->member; } typename Protocol::endpoint& @@ -78,7 +83,6 @@ class plain_worker void do_close(system::error_code const& ec); workers_base& wb_; - stream_type stream_; typename Protocol::endpoint ep_; }; @@ -91,16 +95,16 @@ plain_worker( workers_base& wb, Executor0 const& ex, router_asio rr) - : http_stream( + : base_member(Executor(ex)) + , http_stream( wb.app(), - stream_, + this->member, std::move(rr), [this](system::error_code const& ec) { this->do_close(ec); }) , wb_(wb) - , stream_(Executor(ex)) { } @@ -110,7 +114,7 @@ plain_worker:: cancel() { system::error_code ec; - stream_.cancel(ec); + this->member.cancel(ec); } //-------------------------------------------- @@ -121,7 +125,7 @@ void plain_worker:: on_accept(acceptor_config const* pconfig) { - BOOST_ASSERT(stream_.get_executor().running_in_this_thread()); + BOOST_ASSERT(this->member.get_executor().running_in_this_thread()); // VFALCO TODO timeout this->on_stream_begin(*pconfig); } @@ -156,7 +160,7 @@ reset() { // Clean up any previous connection. system::error_code ec; - stream_.close(ec); + this->member.close(ec); } /** Close the connection to end the session diff --git a/include/boost/beast2/server/route_handler_asio.hpp b/include/boost/beast2/server/route_handler_asio.hpp index 9dfde614..d93cf4d2 100644 --- a/include/boost/beast2/server/route_handler_asio.hpp +++ b/include/boost/beast2/server/route_handler_asio.hpp @@ -11,9 +11,7 @@ #define BOOST_BEAST2_SERVER_ROUTE_HANDLER_ASIO_HPP #include -#include #include -#include #include namespace boost { @@ -51,55 +49,8 @@ class asio_route_params } } -private: - -#ifdef BOOST_CAPY_HAS_CORO - auto - spawn( - capy::task t) -> - http::route_result override - { - return this->suspend( - [&](http::resumer resume) - { - beast2::spawn( - stream.get_executor(), - std::move(t), - [resume](std::variant< - std::exception_ptr, - http::route_result> v) - { - if(v.index() == 0) - { - std::rethrow_exception( - std::get<0>(v)); - } - resume(std::get<1>(v)); - }); - }); - } -#endif - - void do_post() override; }; -//----------------------------------------------- - -template -void -asio_route_params:: -do_post() -{ - asio::post( - stream.get_executor(), - [&] - { - if(task_->invoke()) - return; - do_post(); - }); -} - } // beast2 } // boost diff --git a/include/boost/beast2/server/worker_ssl.hpp b/include/boost/beast2/server/worker_ssl.hpp index 0d0f38af..52125fa6 100644 --- a/include/boost/beast2/server/worker_ssl.hpp +++ b/include/boost/beast2/server/worker_ssl.hpp @@ -23,6 +23,7 @@ #include #include #include +#include namespace boost { @@ -38,10 +39,15 @@ template< class Executor, class Protocol = asio::ip::tcp > -class worker_ssl : public http_stream< - ssl_stream> -> +class worker_ssl + : private boost::base_from_member< + ssl_stream>> + , public http_stream< + ssl_stream>> { + using base_member = boost::base_from_member< + ssl_stream>>; + public: using executor_type = Executor; using protocol_type = Protocol; @@ -68,7 +74,7 @@ class worker_ssl : public http_stream< socket_type& socket() noexcept { - return stream_.next_layer(); + return this->member.next_layer(); } typename Protocol::endpoint& @@ -102,7 +108,6 @@ class worker_ssl : public http_stream< workers_base& wb_; asio::ssl::context& ssl_ctx_; - stream_type stream_; typename Protocol::endpoint ep_; }; @@ -116,9 +121,10 @@ worker_ssl( Executor0 const& ex, asio::ssl::context& ssl_ctx, router_asio rr) - : http_stream( + : base_member(Executor(ex), ssl_ctx) + , http_stream( wb.app(), - stream_, + this->member, std::move(rr), [this](system::error_code const& ec) { @@ -126,7 +132,6 @@ worker_ssl( }) , wb_(wb) , ssl_ctx_(ssl_ctx) - , stream_(Executor(ex), ssl_ctx) { } @@ -136,7 +141,7 @@ worker_ssl:: cancel() { system::error_code ec; - stream_.next_layer().cancel(ec); + this->member.next_layer().cancel(ec); } //-------------------------------------------- @@ -147,12 +152,12 @@ void worker_ssl:: on_accept(acceptor_config const* pconfig) { - BOOST_ASSERT(stream_.get_executor().running_in_this_thread()); + BOOST_ASSERT(this->member.get_executor().running_in_this_thread()); // VFALCO TODO timeout - stream_.set_ssl(pconfig->is_ssl); + this->member.set_ssl(pconfig->is_ssl); if(! pconfig->is_ssl) return this->on_accept(*pconfig); - return stream_.stream().async_handshake( + return this->member.stream().async_handshake( asio::ssl::stream_base::server, asio::prepend(call_mf( &worker_ssl::on_handshake, this), pconfig)); @@ -187,7 +192,7 @@ on_shutdown(system::error_code ec) "{} worker_ssl::on_shutdown", this->id()); - stream_.next_layer().shutdown( + this->member.next_layer().shutdown( asio::socket_base::shutdown_both, ec); // error ignored @@ -225,14 +230,14 @@ reset() { // Clean up any previous connection. system::error_code ec; - stream_.next_layer().close(ec); + this->member.next_layer().close(ec); // asio::ssl::stream has an internal state which cannot be reset. // In order to perform the handshake again, we destroy the old // object and assign a new one, in a way that preserves the // original socket to avoid churning file handles. // - stream_ = stream_type(std::move(stream_.next_layer()), ssl_ctx_); + this->member = stream_type(std::move(this->member.next_layer()), ssl_ctx_); } /** Close the connection to end the session @@ -250,7 +255,7 @@ do_close(system::error_code const& ec) wb_.do_idle(this); return; } - stream_.stream().async_shutdown(call_mf( + this->member.stream().async_shutdown(call_mf( &worker_ssl::on_shutdown, this)); return; } diff --git a/include/boost/beast2/spawn.hpp b/include/boost/beast2/spawn.hpp index 4912f04e..683a5567 100644 --- a/include/boost/beast2/spawn.hpp +++ b/include/boost/beast2/spawn.hpp @@ -10,13 +10,15 @@ #ifndef BOOST_BEAST2_SPAWN_HPP #define BOOST_BEAST2_SPAWN_HPP -#include +#include -#ifdef BOOST_CAPY_HAS_CORO +#ifdef BOOST_BEAST2_HAS_CORO -#include +#include +#include #include #include +#include #include #include #include @@ -59,9 +61,12 @@ auto spawn( auto h = t.release(); auto* p = &h.promise(); - p->on_done_ = [handler = std::move(handler), h, p]() mutable + // Set executor to ensure executor affinity + p->ex = wrap_executor(ex_); + + p->on_done = [handler = std::move(handler), h, p]() mutable { - auto& r = p->result_; + auto& r = p->result; if (r.index() == 2) std::move(handler)(std::variant( std::in_place_index<0>, std::get<2>(r))); diff --git a/include/boost/beast2/wrap_executor.hpp b/include/boost/beast2/wrap_executor.hpp new file mode 100644 index 00000000..ec98633d --- /dev/null +++ b/include/boost/beast2/wrap_executor.hpp @@ -0,0 +1,181 @@ +// +// 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_WRAP_EXECUTOR_HPP +#define BOOST_BEAST2_WRAP_EXECUTOR_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace beast2 { + +namespace detail { + +// Metadata stored before each work allocation +struct work_metadata +{ + void* raw; + std::size_t total_size; +}; + +template +struct asio_executor_impl +{ + friend struct capy::executor::access; + + using allocator_type = typename + asio::associated_allocator::type; + + AsioExecutor exec_; + allocator_type alloc_; + + explicit + asio_executor_impl(AsioExecutor ex) + : exec_(std::move(ex)) + , alloc_(asio::get_associated_allocator(exec_)) + { + } + + // Move constructor + asio_executor_impl(asio_executor_impl&& other) noexcept + : exec_(std::move(other.exec_)) + , alloc_(std::move(other.alloc_)) + { + } + + // Move assignment + asio_executor_impl& operator=(asio_executor_impl&& other) noexcept + { + if(this != &other) + { + exec_ = std::move(other.exec_); + alloc_ = std::move(other.alloc_); + } + return *this; + } + + // Delete copy operations + asio_executor_impl(asio_executor_impl const&) = delete; + asio_executor_impl& operator=(asio_executor_impl const&) = delete; + +private: + void* + allocate(std::size_t size, std::size_t align) + { + // Rebind allocator to char for byte-level allocation + using char_alloc = typename std::allocator_traits< + allocator_type>::template rebind_alloc; + char_alloc a(alloc_); + + // We need space for: + // - metadata struct (aligned to work_metadata alignment) + // - padding for work alignment + // - work object + std::size_t const meta_size = sizeof(work_metadata); + std::size_t const total_size = meta_size + align + size; + + // Allocate raw storage + char* raw = std::allocator_traits::allocate(a, total_size); + + // Compute aligned pointer for work after metadata + char* after_meta = raw + meta_size; + void* aligned = after_meta; + std::size_t space = total_size - meta_size; + aligned = std::align(align, size, aligned, space); + + // Store metadata immediately before the aligned work region + work_metadata* meta = reinterpret_cast( + static_cast(aligned) - sizeof(work_metadata)); + meta->raw = raw; + meta->total_size = total_size; + + return aligned; + } + + void + deallocate(void* p, std::size_t /*size*/, std::size_t /*align*/) + { + using char_alloc = typename std::allocator_traits< + allocator_type>::template rebind_alloc; + char_alloc a(alloc_); + + // Retrieve metadata stored before p + work_metadata* meta = reinterpret_cast( + static_cast(p) - sizeof(work_metadata)); + + std::allocator_traits::deallocate( + a, static_cast(meta->raw), meta->total_size); + } + + void + submit(capy::executor::work* w) + { + // Capture a copy of allocator for the lambda + allocator_type alloc_copy = alloc_; + asio::post(exec_, + [w, alloc_copy]() mutable + { + using char_alloc = typename std::allocator_traits< + allocator_type>::template rebind_alloc; + char_alloc a(alloc_copy); + + // Retrieve metadata stored before w + work_metadata* meta = reinterpret_cast( + reinterpret_cast(w) - sizeof(work_metadata)); + void* raw = meta->raw; + std::size_t total_size = meta->total_size; + + w->invoke(); + w->~work(); + + std::allocator_traits::deallocate( + a, static_cast(raw), total_size); + }); + } +}; + +} // detail + +/** Return a capy::executor from an Asio executor. + + This function wraps an Asio executor in a capy::executor, + mapping the capy::executor implementation API to the + corresponding Asio executor operations. + + The returned executor uses get_associated_allocator on + the Asio executor to obtain the allocator for work items. + + @param ex The Asio executor to wrap. + + @return A capy::executor that submits work via the + provided Asio executor. +*/ +template +capy::executor +wrap_executor(AsioExecutor ex) +{ + return capy::executor::wrap( + detail::asio_executor_impl< + typename std::decay::type>( + std::move(ex))); +} + +} // beast2 +} // boost + +#endif + diff --git a/test/unit/wrap_executor.cpp b/test/unit/wrap_executor.cpp new file mode 100644 index 00000000..cf643776 --- /dev/null +++ b/test/unit/wrap_executor.cpp @@ -0,0 +1,603 @@ +// +// 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 "test_helpers.hpp" + +#include +#include +#include +#include + +namespace boost { +namespace beast2 { + +//----------------------------------------------------------------------------- + +/** Tracking allocator to verify allocator usage. +*/ +template +struct tracking_allocator +{ + using value_type = T; + + std::shared_ptr> alloc_count; + std::shared_ptr> dealloc_count; + + tracking_allocator() + : alloc_count(std::make_shared>(0)) + , dealloc_count(std::make_shared>(0)) + { + } + + template + tracking_allocator(tracking_allocator const& other) + : alloc_count(other.alloc_count) + , dealloc_count(other.dealloc_count) + { + } + + T* allocate(std::size_t n) + { + ++(*alloc_count); + return static_cast(::operator new(n * sizeof(T))); + } + + void deallocate(T* p, std::size_t) + { + ++(*dealloc_count); + ::operator delete(p); + } + + template + bool operator==(tracking_allocator const& other) const + { + return alloc_count == other.alloc_count; + } + + template + bool operator!=(tracking_allocator const& other) const + { + return !(*this == other); + } +}; + +/** Executor wrapper that associates an allocator. +*/ +template +struct executor_with_allocator +{ + Executor exec_; + Allocator alloc_; + + using inner_executor_type = Executor; + + executor_with_allocator( + Executor ex, + Allocator alloc) + : exec_(std::move(ex)) + , alloc_(std::move(alloc)) + { + } + + Executor const& get_inner_executor() const noexcept + { + return exec_; + } + + Allocator get_allocator() const noexcept + { + return alloc_; + } + + // Forward executor operations + auto context() const noexcept -> decltype(exec_.context()) + { + return exec_.context(); + } + + void on_work_started() const noexcept + { + exec_.on_work_started(); + } + + void on_work_finished() const noexcept + { + exec_.on_work_finished(); + } + + template + void dispatch(F&& f, A const& a) const + { + exec_.dispatch(std::forward(f), a); + } + + template + void post(F&& f, A const& a) const + { + exec_.post(std::forward(f), a); + } + + template + void defer(F&& f, A const& a) const + { + exec_.defer(std::forward(f), a); + } + + bool operator==(executor_with_allocator const& other) const noexcept + { + return exec_ == other.exec_; + } + + bool operator!=(executor_with_allocator const& other) const noexcept + { + return exec_ != other.exec_; + } +}; + +} // beast2 + +// Specialize associated_allocator for our wrapper +namespace asio { + +template +struct associated_allocator< + beast2::executor_with_allocator, + DefaultAllocator> +{ + using type = Allocator; + + static type get( + beast2::executor_with_allocator const& ex, + DefaultAllocator const& = DefaultAllocator()) noexcept + { + return ex.get_allocator(); + } +}; + +} // asio + +namespace beast2 { + +//----------------------------------------------------------------------------- + +struct wrap_executor_test +{ + void + testWrapExecutor() + { + asio::io_context ioc; + capy::executor ex = wrap_executor(ioc.get_executor()); + BOOST_TEST(static_cast(ex)); + } + + void + testPostLambda() + { + asio::io_context ioc; + capy::executor ex = wrap_executor(ioc.get_executor()); + + bool called = false; + ex.post([&called]{ called = true; }); + + BOOST_TEST(!called); + test::run(ioc); + BOOST_TEST(called); + } + + void + testPostMultiple() + { + asio::io_context ioc; + capy::executor ex = wrap_executor(ioc.get_executor()); + + int count = 0; + ex.post([&count]{ ++count; }); + ex.post([&count]{ ++count; }); + ex.post([&count]{ ++count; }); + + BOOST_TEST_EQ(count, 0); + test::run(ioc); + BOOST_TEST_EQ(count, 3); + } + + void + testPostWithCapture() + { + asio::io_context ioc; + capy::executor ex = wrap_executor(ioc.get_executor()); + + int result = 0; + int a = 10, b = 20; + ex.post([&result, a, b]{ result = a + b; }); + + test::run(ioc); + BOOST_TEST_EQ(result, 30); + } + + void + testPostWithMoveOnlyCapture() + { + struct callable + { + int& result; + std::unique_ptr ptr; + + void operator()() + { + result = *ptr; + } + }; + + asio::io_context ioc; + capy::executor ex = wrap_executor(ioc.get_executor()); + + int result = 0; + std::unique_ptr ptr(new int(42)); + ex.post(callable{result, std::move(ptr)}); + + test::run(ioc); + BOOST_TEST_EQ(result, 42); + } + + void + testCopyExecutor() + { + asio::io_context ioc; + capy::executor exec1 = wrap_executor(ioc.get_executor()); + capy::executor exec2 = exec1; + + int count = 0; + exec1.post([&count]{ ++count; }); + exec2.post([&count]{ ++count; }); + + test::run(ioc); + BOOST_TEST_EQ(count, 2); + } + + void + testMoveExecutor() + { + asio::io_context ioc; + capy::executor exec1 = wrap_executor(ioc.get_executor()); + capy::executor exec2 = std::move(exec1); + + bool called = false; + exec2.post([&called]{ called = true; }); + + test::run(ioc); + BOOST_TEST(called); + } + + void + testWithStrand() + { + asio::io_context ioc; + asio::strand strand( + ioc.get_executor()); + capy::executor ex = wrap_executor(strand); + + int count = 0; + ex.post([&count]{ ++count; }); + ex.post([&count]{ ++count; }); + + test::run(ioc); + BOOST_TEST_EQ(count, 2); + } + + void + testAssociatedAllocator() + { + asio::io_context ioc; + tracking_allocator alloc; + + executor_with_allocator< + asio::io_context::executor_type, + tracking_allocator> wrapped_exec( + ioc.get_executor(), alloc); + + capy::executor ex = wrap_executor(wrapped_exec); + + bool called = false; + ex.post([&called]{ called = true; }); + + // Before running, allocation should have happened + BOOST_TEST_GT(*alloc.alloc_count, 0); + + test::run(ioc); + BOOST_TEST(called); + + // After running, deallocation should have happened + BOOST_TEST_GT(*alloc.dealloc_count, 0); + BOOST_TEST_EQ(*alloc.alloc_count, *alloc.dealloc_count); + } + + void + testAsyncPostNonVoid() + { + asio::io_context ioc; + capy::executor ex = wrap_executor(ioc.get_executor()); + + int result = 0; + bool handler_called = false; + + ex.submit( + []{ return 42; }, + [&](system::result r) + { + handler_called = true; + if(r.has_value()) + result = r.value(); + }); + + test::run(ioc); + BOOST_TEST(handler_called); + BOOST_TEST_EQ(result, 42); + } + + void + testAsyncPostVoid() + { + asio::io_context ioc; + capy::executor ex = wrap_executor(ioc.get_executor()); + + bool work_called = false; + bool handler_called = false; + + ex.submit( + [&work_called]{ work_called = true; }, + [&handler_called](system::result) + { + handler_called = true; + }); + + test::run(ioc); + BOOST_TEST(work_called); + BOOST_TEST(handler_called); + } + + void + testAsyncPostException() + { + asio::io_context ioc; + capy::executor ex = wrap_executor(ioc.get_executor()); + + bool handler_called = false; + bool got_exception = false; + + ex.submit( + []() -> int { throw std::runtime_error("test"); }, + [&](system::result r) + { + handler_called = true; + if(r.has_error()) + got_exception = true; + }); + + test::run(ioc); + BOOST_TEST(handler_called); + BOOST_TEST(got_exception); + } + + // Test that mimics route_params storage pattern + void + testStoredInStruct() + { + struct params + { + capy::executor ex; + }; + + asio::io_context ioc; + params p; + p.ex = wrap_executor(ioc.get_executor()); + + bool called = false; + p.ex.post([&called]{ called = true; }); + + test::run(ioc); + BOOST_TEST(called); + } + + // Test executor survives struct move + void + testStoredInStructAfterMove() + { + struct params + { + capy::executor ex; + int dummy = 0; + }; + + asio::io_context ioc; + params p1; + p1.ex = wrap_executor(ioc.get_executor()); + + // Move the struct + params p2 = std::move(p1); + + bool called = false; + p2.ex.post([&called]{ called = true; }); + + test::run(ioc); + BOOST_TEST(called); + } + + // Test executor assignment (not construction) + void + testAssignment() + { + asio::io_context ioc; + capy::executor ex; + BOOST_TEST(!static_cast(ex)); + + ex = wrap_executor(ioc.get_executor()); + BOOST_TEST(static_cast(ex)); + + bool called = false; + ex.post([&called]{ called = true; }); + + test::run(ioc); + BOOST_TEST(called); + } + + // Test multiple assignments + void + testReassignment() + { + asio::io_context ioc1; + asio::io_context ioc2; + + capy::executor ex = wrap_executor(ioc1.get_executor()); + + bool called1 = false; + ex.post([&called1]{ called1 = true; }); + test::run(ioc1); + BOOST_TEST(called1); + + // Reassign to different executor + ex = wrap_executor(ioc2.get_executor()); + + bool called2 = false; + ex.post([&called2]{ called2 = true; }); + test::run(ioc2); + BOOST_TEST(called2); + } + + // Test that the underlying asio executor is valid + void + testUnderlyingExecutorValid() + { + asio::io_context ioc; + auto asio_exec = ioc.get_executor(); + + // Wrap it + capy::executor ex = wrap_executor(asio_exec); + + // Post work multiple times to stress test + int count = 0; + for(int i = 0; i < 100; ++i) + { + ex.post([&count]{ ++count; }); + } + + test::run(ioc); + BOOST_TEST_EQ(count, 100); + } + + // Test wrap_executor with temporary executor + void + testWrapTemporary() + { + asio::io_context ioc; + + // Wrap a temporary - this is how it's used in http_stream + capy::executor ex = wrap_executor(ioc.get_executor()); + + bool called = false; + ex.post([&called]{ called = true; }); + + test::run(ioc); + BOOST_TEST(called); + } + + // Test that asio_executor_impl is properly initialized + void + testExecutorImplInitialized() + { + asio::io_context ioc; + + // Create the wrapper directly to test initialization + detail::asio_executor_impl impl( + ioc.get_executor()); + + // The impl should be usable - create work and submit it + struct test_work : capy::executor::work + { + bool& called; + explicit test_work(bool& c) : called(c) {} + void invoke() override { called = true; } + }; + + bool called = false; + + // Allocate, construct, and submit work + void* storage = capy::executor::access::allocate( + impl, sizeof(test_work), alignof(test_work)); + test_work* w = ::new(storage) test_work(called); + capy::executor::access::submit(impl, w); + + test::run(ioc); + BOOST_TEST(called); + } + + // Test that moved asio_executor_impl is valid + void + testExecutorImplAfterMove() + { + asio::io_context ioc; + + detail::asio_executor_impl impl1( + ioc.get_executor()); + + // Move the impl + auto impl2 = std::move(impl1); + + struct test_work : capy::executor::work + { + bool& called; + explicit test_work(bool& c) : called(c) {} + void invoke() override { called = true; } + }; + + bool called = false; + void* storage = capy::executor::access::allocate( + impl2, sizeof(test_work), alignof(test_work)); + test_work* w = ::new(storage) test_work(called); + capy::executor::access::submit(impl2, w); + + test::run(ioc); + BOOST_TEST(called); + } + + void + run() + { + testWrapExecutor(); + testPostLambda(); + testPostMultiple(); + testPostWithCapture(); + testPostWithMoveOnlyCapture(); + testCopyExecutor(); + testMoveExecutor(); + testWithStrand(); + testAssociatedAllocator(); + testAsyncPostNonVoid(); + testAsyncPostVoid(); + testAsyncPostException(); + testStoredInStruct(); + testStoredInStructAfterMove(); + testAssignment(); + testReassignment(); + testUnderlyingExecutorValid(); + testWrapTemporary(); + testExecutorImplInitialized(); + testExecutorImplAfterMove(); + } +}; + +TEST_SUITE( + wrap_executor_test, + "boost.beast2.wrap_executor"); + +} // beast2 +} // boost + From 3cbf40737d1e09101fd2611e6d92cc140c9c2796 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Tue, 30 Dec 2025 14:23:50 -0800 Subject: [PATCH 19/40] Fix CI failure by adding missing Boost dependencies to CMakeLists.txt --- CMakeLists.txt | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f581903..00126af4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,10 +57,19 @@ set(BOOST_SRC_DIR ${DEFAULT_BOOST_SRC_DIR} CACHE STRING "Boost source dir to use set(BOOST_BEAST2_DEPENDENCIES Boost::asio Boost::assert + Boost::buffers + Boost::capy Boost::config + Boost::core + Boost::describe Boost::http_proto + Boost::json + Boost::mp11 + Boost::static_assert Boost::system Boost::throw_exception + Boost::url + Boost::variant2 ) foreach (BOOST_BEAST2_DEPENDENCY ${BOOST_BEAST2_DEPENDENCIES}) @@ -70,10 +79,10 @@ foreach (BOOST_BEAST2_DEPENDENCY ${BOOST_BEAST2_DEPENDENCIES}) endforeach () # Conditional dependencies if (BOOST_BEAST2_BUILD_TESTS) - set(BOOST_BEAST2_UNIT_TEST_LIBRARIES beast url) + set(BOOST_BEAST2_UNIT_TEST_LIBRARIES beast) endif () if (BOOST_BEAST2_BUILD_EXAMPLES) - set(BOOST_BEAST2_EXAMPLE_LIBRARIES json program_options scope url multiprecision) + set(BOOST_BEAST2_EXAMPLE_LIBRARIES program_options scope multiprecision) endif () # Complete dependency list set(BOOST_INCLUDE_LIBRARIES ${BOOST_BEAST2_INCLUDE_LIBRARIES} ${BOOST_BEAST2_UNIT_TEST_LIBRARIES} ${BOOST_BEAST2_EXAMPLE_LIBRARIES}) @@ -115,7 +124,7 @@ if (BOOST_BEAST2_IS_ROOT) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${BOOST_SRC_DIR}/tools/cmake/include") else () # From Boost Package - find_package(Boost REQUIRED COMPONENTS buffers http_proto program_options scope url) + find_package(Boost REQUIRED COMPONENTS buffers capy http_proto json program_options scope system url) foreach (BOOST_INCLUDE_LIBRARY ${BOOST_INCLUDE_LIBRARIES}) if (NOT TARGET Boost::${BOOST_INCLUDE_LIBRARY}) add_library(Boost::${BOOST_INCLUDE_LIBRARY} ALIAS Boost::headers) From 5023467b1425a0cb998494371ec5d494dc04cf11 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Tue, 30 Dec 2025 15:03:44 -0800 Subject: [PATCH 20/40] Add missing Boost::endian dependency --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 00126af4..f73fe086 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,6 +62,7 @@ set(BOOST_BEAST2_DEPENDENCIES Boost::config Boost::core Boost::describe + Boost::endian Boost::http_proto Boost::json Boost::mp11 From 2c1ea767b907ff9ce20a81bf325c7e41ebea09be Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Tue, 30 Dec 2025 15:13:59 -0800 Subject: [PATCH 21/40] Add endian module to boost-clone steps --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a4f12931..e3a93fdb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -908,6 +908,7 @@ jobs: with: branch: ${{ (github.ref_name == 'master' && github.ref_name) || 'develop' }} boost-dir: boost-source + modules: endian modules-exclude-paths: '' scan-modules-dir: | http-io-root @@ -1236,6 +1237,7 @@ jobs: with: branch: ${{ (github.ref_name == 'master' && github.ref_name) || 'develop' }} boost-dir: boost-source + modules: endian modules-exclude-paths: '' scan-modules-dir: | http-io-root From 640308a3eccbb14b7e48780ea09253657be3a3a9 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Tue, 30 Dec 2025 15:16:11 -0800 Subject: [PATCH 22/40] Revert modules parameter - unverified syntax --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3a93fdb..a4f12931 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -908,7 +908,6 @@ jobs: with: branch: ${{ (github.ref_name == 'master' && github.ref_name) || 'develop' }} boost-dir: boost-source - modules: endian modules-exclude-paths: '' scan-modules-dir: | http-io-root @@ -1237,7 +1236,6 @@ jobs: with: branch: ${{ (github.ref_name == 'master' && github.ref_name) || 'develop' }} boost-dir: boost-source - modules: endian modules-exclude-paths: '' scan-modules-dir: | http-io-root From eb5995c5a8b01a67add5cd6640495c48200bc854 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Wed, 31 Dec 2025 04:48:13 -0800 Subject: [PATCH 23/40] Remove co_spawn example --- example/CMakeLists.txt | 1 - example/Jamfile | 1 - example/cpp20/CMakeLists.txt | 10 --- example/cpp20/Jamfile | 10 --- example/cpp20/co_spawn/CMakeLists.txt | 33 -------- example/cpp20/co_spawn/Jamfile | 41 ---------- example/cpp20/co_spawn/async_42.hpp | 38 --------- example/cpp20/co_spawn/async_result.hpp | 100 ------------------------ example/cpp20/co_spawn/main.cpp | 93 ---------------------- example/cpp20/co_spawn/stream.hpp | 26 ------ 10 files changed, 353 deletions(-) delete mode 100644 example/cpp20/CMakeLists.txt delete mode 100644 example/cpp20/Jamfile delete mode 100644 example/cpp20/co_spawn/CMakeLists.txt delete mode 100644 example/cpp20/co_spawn/Jamfile delete mode 100644 example/cpp20/co_spawn/async_42.hpp delete mode 100644 example/cpp20/co_spawn/async_result.hpp delete mode 100644 example/cpp20/co_spawn/main.cpp delete mode 100644 example/cpp20/co_spawn/stream.hpp diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 36fb1b41..f9abbaca 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -10,4 +10,3 @@ add_subdirectory(client) add_subdirectory(server) -add_subdirectory(cpp20) diff --git a/example/Jamfile b/example/Jamfile index b92d59e0..7d19c3d5 100644 --- a/example/Jamfile +++ b/example/Jamfile @@ -10,4 +10,3 @@ build-project client ; build-project server ; -build-project cpp20 ; diff --git a/example/cpp20/CMakeLists.txt b/example/cpp20/CMakeLists.txt deleted file mode 100644 index 21d46b2d..00000000 --- a/example/cpp20/CMakeLists.txt +++ /dev/null @@ -1,10 +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 -# - -add_subdirectory(co_spawn) diff --git a/example/cpp20/Jamfile b/example/cpp20/Jamfile deleted file mode 100644 index 2e6efaaa..00000000 --- a/example/cpp20/Jamfile +++ /dev/null @@ -1,10 +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 -# - -build-project co_spawn ; diff --git a/example/cpp20/co_spawn/CMakeLists.txt b/example/cpp20/co_spawn/CMakeLists.txt deleted file mode 100644 index 115b26af..00000000 --- a/example/cpp20/co_spawn/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -# -# Copyright (c) 2025 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 -# - -if(NOT cxx_std_20 IN_LIST CMAKE_CXX_COMPILE_FEATURES) - return() -endif() - -file(GLOB_RECURSE PFILES CONFIGURE_DEPENDS *.cpp *.hpp - CMakeLists.txt - Jamfile) - -source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "" FILES ${PFILES}) - -add_executable(beast2_example_co_spawn ${PFILES}) - -target_compile_definitions(beast2_example_co_spawn - PRIVATE BOOST_ASIO_NO_DEPRECATED) - -set_property(TARGET beast2_example_co_spawn - PROPERTY FOLDER "examples") - -find_package(OpenSSL REQUIRED) - -target_compile_features(beast2_example_co_spawn PUBLIC cxx_std_20) - -target_link_libraries(beast2_example_co_spawn - Boost::beast2) diff --git a/example/cpp20/co_spawn/Jamfile b/example/cpp20/co_spawn/Jamfile deleted file mode 100644 index 77befb7b..00000000 --- a/example/cpp20/co_spawn/Jamfile +++ /dev/null @@ -1,41 +0,0 @@ -# -# Copyright (c) 2025 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 -# - -import config : requires ; - -using openssl ; -import ac ; - -lib advapi32 ; -lib crypt32 ; -lib gdi32 ; -lib user32 ; - -project - : requirements - /boost/beast2//boost_beast2 - [ ac.check-library /boost/capy//boost_capy_zlib : /boost/capy//boost_capy_zlib : ] - [ ac.check-library /boost/capy//boost_capy_brotli : /boost/capy//boost_capy_brotli : ] - [ ac.check-library /openssl//ssl : /openssl//ssl/shared : no ] - [ ac.check-library /openssl//crypto : /openssl//crypto/shared : no ] - windows:advapi32 - windows:crypt32 - windows:gdi32 - windows:user32 - /boost/url//boost_url - . - ; - -exe get : - [ glob *.cpp ] - : requirements - [ requires - cxx20_hdr_coroutine - ] - ; diff --git a/example/cpp20/co_spawn/async_42.hpp b/example/cpp20/co_spawn/async_42.hpp deleted file mode 100644 index c9d2e198..00000000 --- a/example/cpp20/co_spawn/async_42.hpp +++ /dev/null @@ -1,38 +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/capy -// - -#ifndef BOOST_BEAST2_TASK_HPP -#define BOOST_BEAST2_TASK_HPP - -#include -#include - -namespace boost { -namespace beast2 { - -template -auto async_42(Executor const& exec, CompletionToken&& token) -{ - return asio::async_initiate( - [](auto handler, Executor exec) - { - boost::asio::post(exec, - [handler = std::move(handler)]() mutable - { - std::move(handler)(42); - }); - }, - token, - exec); -} - -} // beast2 -} // boost - -#endif diff --git a/example/cpp20/co_spawn/async_result.hpp b/example/cpp20/co_spawn/async_result.hpp deleted file mode 100644 index d40ec7f5..00000000 --- a/example/cpp20/co_spawn/async_result.hpp +++ /dev/null @@ -1,100 +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/capy -// - -#ifndef BOOST_CAPY_ASYNC_RESULT_HPP -#define BOOST_CAPY_ASYNC_RESULT_HPP - -#include -#include -#include -#include -#include - -namespace boost { -namespace capy { - -template -class async_result -{ -public: - struct impl_base - { - virtual ~impl_base() = default; - virtual void start(std::function on_done) = 0; - virtual T get_result() = 0; - }; - -private: - std::unique_ptr impl_; - -public: - explicit async_result(std::unique_ptr p) : impl_(std::move(p)) {} - - async_result(async_result&&) = default; - async_result& operator=(async_result&&) = default; - - bool await_ready() const noexcept { return false; } - - void await_suspend(std::coroutine_handle<> h) - { - impl_->start([h]{ h.resume(); }); - } - - T await_resume() - { - return impl_->get_result(); - } -}; - -//----------------------------------------------------------------------------- - -template -struct async_result_impl : capy::async_result::impl_base -{ - DeferredOp op_; - std::variant result_; - - explicit async_result_impl(DeferredOp&& op) - : op_(std::forward(op)) - { - } - - void start(std::function on_done) override - { - std::move(op_)( - [this, on_done = std::move(on_done)](auto&&... args) mutable - { - result_.template emplace<1>(T{std::forward(args)...}); - on_done(); - }); - } - - T get_result() override - { - if (result_.index() == 0 && std::get<0>(result_)) - std::rethrow_exception(std::get<0>(result_)); - return std::move(std::get<1>(result_)); - } -}; - -//----------------------------------------------------------------------------- - -template -capy::async_result -make_async_result(DeferredOp&& op) -{ - using impl_type = async_result_impl>; - return capy::async_result( - std::make_unique(std::forward(op))); -} - -} // capy -} // boost - -#endif diff --git a/example/cpp20/co_spawn/main.cpp b/example/cpp20/co_spawn/main.cpp deleted file mode 100644 index f61d8ac6..00000000 --- a/example/cpp20/co_spawn/main.cpp +++ /dev/null @@ -1,93 +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 "async_42.hpp" -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace boost { - -namespace buffers { - -auto read_all( - any_stream s, - buffers::mutable_buffer b) - -> capy::task -{ - std::size_t total = 0; - while(b.size() > 0) - { - auto [ec, n] = co_await s.read_some(b); - total += n; - if(ec.failed()) - co_return { ec, total }; - buffers::remove_prefix(b, n); - } - co_return { {}, total }; -} - -} // buffers - -namespace beast2 { - -struct stream -{ - asio::any_io_executor ex_; - - template - stream(Executor const& ex) - : ex_(ex) - { - } - - capy::async_result - read_some() - { - return capy::make_async_result( - async_42(ex_, asio::deferred)); - } -}; - -} // beast2 - -capy::task handler() -{ - co_return 42; -} - -void boost_main() -{ - asio::io_context ioc; - - beast2::spawn( - ioc.get_executor(), - handler(), - [](std::variant result) - { - if (result.index() == 0) - std::rethrow_exception(std::get<0>(result)); - std::cout << "result: " << std::get<1>(result) << "\n"; - }); - - ioc.run(); -} - -} // boost - -int main(int, char**) -{ - boost::boost_main(); -} diff --git a/example/cpp20/co_spawn/stream.hpp b/example/cpp20/co_spawn/stream.hpp deleted file mode 100644 index 73ef10ef..00000000 --- a/example/cpp20/co_spawn/stream.hpp +++ /dev/null @@ -1,26 +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/capy -// - -#ifndef BOOST_CAPY_STREAM_HPP -#define BOOST_CAPY_STREAM_HPP - -#include "task.hpp" - -namespace boost { -namespace capy { - -class stream -{ - -}; - -} // capy -} // boost - -#endif From 0d3be31fcb2d3023a81a3c33ac61c59fe9aa733f Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Wed, 31 Dec 2025 04:49:52 -0800 Subject: [PATCH 24/40] Remove spawn.hpp and related changes --- example/server/main.cpp | 3 +- include/boost/beast2.hpp | 1 - include/boost/beast2/spawn.hpp | 90 ---------------------------------- test/unit/stream.cpp | 1 - 4 files changed, 1 insertion(+), 94 deletions(-) delete mode 100644 include/boost/beast2/spawn.hpp diff --git a/example/server/main.cpp b/example/server/main.cpp index 3acc5470..10bc19bd 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -133,7 +132,7 @@ my_coro( { (void)rp; asio::thread_pool tp(1); - co_await capy::make_async_result( + co_await capy::make_async_op( [&tp](auto&& handler) { asio::post(tp.get_executor(), diff --git a/include/boost/beast2.hpp b/include/boost/beast2.hpp index 64cb64be..d7de8709 100644 --- a/include/boost/beast2.hpp +++ b/include/boost/beast2.hpp @@ -40,7 +40,6 @@ #include #include #include -#include //#include #include diff --git a/include/boost/beast2/spawn.hpp b/include/boost/beast2/spawn.hpp deleted file mode 100644 index 683a5567..00000000 --- a/include/boost/beast2/spawn.hpp +++ /dev/null @@ -1,90 +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_SPAWN_HPP -#define BOOST_BEAST2_SPAWN_HPP - -#include - -#ifdef BOOST_BEAST2_HAS_CORO - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace boost { -namespace beast2 { - -/** Launch a capy::task on the given executor. - - This function is similar to boost::asio::co_spawn, but is used to - launch a capy::task instead of an asio::awaitable. - @param ex The executor on which the task will be launched. - @param t The task to launch. - @param handler The completion handler to be invoked when the task - completes. The handler signature is: - @code - void(std::variant) - @endcode - where the variant holds either an exception_ptr if an exception - was thrown, or the result of type T. - @return The result of the asynchronous initiation. -*/ -template< - class Executor, - class T, - class CompletionHandler> -auto spawn( - Executor const& ex, - capy::task t, - CompletionHandler&& handler) -{ - return asio::async_initiate< - CompletionHandler, - void(std::variant)>( - [ex_ = ex](auto handler, capy::task t) - { - auto h = t.release(); - auto* p = &h.promise(); - - // Set executor to ensure executor affinity - p->ex = wrap_executor(ex_); - - p->on_done = [handler = std::move(handler), h, p]() mutable - { - auto& r = p->result; - if (r.index() == 2) - std::move(handler)(std::variant( - std::in_place_index<0>, std::get<2>(r))); - else - std::move(handler)(std::variant( - std::in_place_index<1>, std::move(std::get<1>(r)))); - h.destroy(); - }; - - asio::post(ex_, [h]{ h.resume(); }); - }, - handler, - std::move(t)); -} - -} // beast2 -} // boost - -#endif - -#endif diff --git a/test/unit/stream.cpp b/test/unit/stream.cpp index d0a51ed9..1e1fb2ae 100644 --- a/test/unit/stream.cpp +++ b/test/unit/stream.cpp @@ -11,7 +11,6 @@ //#include #include -#include #include #include #include From e38a74aab80bcd4ce0cc89d34045855e03909a6e Mon Sep 17 00:00:00 2001 From: Mohammad Nejati Date: Sun, 4 Jan 2026 15:02:39 +0000 Subject: [PATCH 25/40] C++20 is the minimum --- .drone.star | 6 +- .github/workflows/ci.yml | 325 +++++------------- CMakeLists.txt | 2 +- build/Jamfile | 6 +- doc/modules/ROOT/pages/index.adoc | 7 +- example/client/CMakeLists.txt | 2 +- example/client/burl/CMakeLists.txt | 6 - example/client/burl/Jamfile | 1 - example/client/jsonrpc/CMakeLists.txt | 30 +- example/client/jsonrpc/Jamfile | 13 +- example/client/jsonrpc/cpp11.cpp | 277 --------------- .../client/jsonrpc/{cpp20.cpp => main.cpp} | 0 meta/explicit-failures-markup.xml | 13 +- 13 files changed, 94 insertions(+), 594 deletions(-) delete mode 100644 example/client/jsonrpc/cpp11.cpp rename example/client/jsonrpc/{cpp20.cpp => main.cpp} (100%) diff --git a/.drone.star b/.drone.star index cae42f51..81d36457 100644 --- a/.drone.star +++ b/.drone.star @@ -14,8 +14,8 @@ def main(ctx): return generate( # Compilers [ - 'gcc >=5.0', - 'clang >=3.9', + 'gcc >=10.0', + 'clang >=10.0', 'msvc >=14.1', 'arm64-gcc latest', 's390x-gcc latest', @@ -27,7 +27,7 @@ def main(ctx): 'x86-msvc latest' ], # Standards - '>=11', + '>=20', packages=['zlib1g', 'zlib1g-dev', 'libbrotli-dev']) # from https://github.com/cppalliance/ci-automation diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a4f12931..a85b42df 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,33 +62,33 @@ jobs: - compiler: "msvc" version: "14.42" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" runs-on: "windows-2022" b2-toolset: "msvc-14.4" generator: "Visual Studio 17 2022" is-latest: true - name: "MSVC 14.42: C++17-20" + name: "MSVC 14.42: C++20" shared: false build-type: "Release" build-cmake: true - compiler: "msvc" version: "14.34" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" runs-on: "windows-2022" b2-toolset: "msvc-14.3" generator: "Visual Studio 17 2022" is-latest: true - name: "MSVC 14.34: C++17-20" + name: "MSVC 14.34: C++20" shared: true build-type: "Release" build-cmake: true - compiler: "msvc" version: "14.34" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" runs-on: "windows-2022" b2-toolset: "msvc-14.3" @@ -101,13 +101,13 @@ jobs: - compiler: "msvc" version: "14.34" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" runs-on: "windows-2022" b2-toolset: "msvc-14.3" generator: "Visual Studio 17 2022" is-latest: true - name: "MSVC 14.34: C++17-20" + name: "MSVC 14.34: C++20" shared: true build-type: "Release" build-cmake: true @@ -220,7 +220,7 @@ jobs: - compiler: "gcc" version: "15" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" cxx: "g++-15" cc: "gcc-15" @@ -228,14 +228,14 @@ jobs: container: "ubuntu:25.04" b2-toolset: "gcc" is-latest: true - name: "GCC 15: C++17-20" + name: "GCC 15: C++20" shared: false build-type: "Release" build-cmake: true - compiler: "gcc" version: "15" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" cxx: "g++-15" cc: "gcc-15" @@ -243,14 +243,14 @@ jobs: container: "ubuntu:25.04" b2-toolset: "gcc" is-latest: true - name: "GCC 15: C++17-20 (no zlib)" + name: "GCC 15: C++20 (no zlib)" shared: true build-type: "Release" build-cmake: true - compiler: "gcc" version: "15" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" cxx: "g++-15" cc: "gcc-15" @@ -258,7 +258,7 @@ jobs: container: "ubuntu:25.04" b2-toolset: "gcc" is-latest: true - name: "GCC 15: C++17-20 (x86)" + name: "GCC 15: C++20 (x86)" shared: false x86: true build-type: "Release" @@ -266,7 +266,7 @@ jobs: - compiler: "gcc" version: "15" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" cxx: "g++-15" cc: "gcc-15" @@ -274,14 +274,14 @@ jobs: container: "ubuntu:25.04" b2-toolset: "gcc" is-latest: true - name: "GCC 15: C++17-20" + name: "GCC 15: C++20" shared: true build-type: "Release" build-cmake: true - compiler: "gcc" version: "15" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" cxx: "g++-15" cc: "gcc-15" @@ -289,7 +289,7 @@ jobs: container: "ubuntu:25.04" b2-toolset: "gcc" is-latest: true - name: "GCC 15: C++17-20 (x86)" + name: "GCC 15: C++20 (x86)" shared: false x86: true build-type: "Release" @@ -298,7 +298,7 @@ jobs: - compiler: "gcc" version: "15" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" cxx: "g++-15" cc: "gcc-15" @@ -306,14 +306,14 @@ jobs: container: "ubuntu:25.04" b2-toolset: "gcc" is-latest: true - name: "GCC 15: C++17-20 (asan)" + name: "GCC 15: C++20 (asan)" shared: true asan: true build-type: "RelWithDebInfo" - compiler: "gcc" version: "15" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" cxx: "g++-15" cc: "gcc-15" @@ -321,7 +321,7 @@ jobs: container: "ubuntu:25.04" b2-toolset: "gcc" is-latest: true - name: "GCC 15: C++17-20 (asan, x86)" + name: "GCC 15: C++20 (asan, x86)" shared: false asan: true x86: true @@ -330,7 +330,7 @@ jobs: - compiler: "gcc" version: "15" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" cxx: "g++-15" cc: "gcc-15" @@ -338,14 +338,14 @@ jobs: container: "ubuntu:25.04" b2-toolset: "gcc" is-latest: true - name: "GCC 15: C++17-20 (ubsan)" + name: "GCC 15: C++20 (ubsan)" shared: false ubsan: true build-type: "RelWithDebInfo" - compiler: "gcc" version: "15" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" cxx: "g++-15" cc: "gcc-15" @@ -353,7 +353,7 @@ jobs: container: "ubuntu:25.04" b2-toolset: "gcc" is-latest: true - name: "GCC 15: C++17-20 (ubsan, x86)" + name: "GCC 15: C++20 (ubsan, x86)" shared: false ubsan: true x86: true @@ -362,38 +362,38 @@ jobs: - compiler: "gcc" version: "14" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" cxx: "g++-14" cc: "gcc-14" runs-on: "ubuntu-24.04" b2-toolset: "gcc" - name: "GCC 14: C++17-20" + name: "GCC 14: C++20" shared: true build-type: "Release" - compiler: "gcc" version: "13" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" cxx: "g++-13" cc: "gcc-13" runs-on: "ubuntu-24.04" b2-toolset: "gcc" - name: "GCC 13: C++17-20" + name: "GCC 13: C++20" shared: true build-type: "Release" - compiler: "gcc" version: "13" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" cxx: "g++-13" cc: "gcc-13" runs-on: "ubuntu-24.04" b2-toolset: "gcc" is-latest: true - name: "GCC 13: C++17-20 (coverage)" + name: "GCC 13: C++20 (coverage)" shared: false coverage: true build-type: "Debug" @@ -403,106 +403,27 @@ jobs: - compiler: "gcc" version: "12" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" cxx: "g++-12" cc: "gcc-12" runs-on: "ubuntu-latest" container: "ubuntu:22.04" b2-toolset: "gcc" - name: "GCC 12: C++17-20" + name: "GCC 12: C++20" shared: true build-type: "Release" - compiler: "gcc" version: "11" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" cxx: "g++-11" cc: "gcc-11" runs-on: "ubuntu-latest" container: "ubuntu:22.04" b2-toolset: "gcc" - name: "GCC 11: C++17-20" - shared: false - build-type: "Release" - - - compiler: "gcc" - version: "10" - cxxstd: "14,17" - latest-cxxstd: "17" - cxx: "g++-10" - cc: "gcc-10" - runs-on: "ubuntu-latest" - container: "ubuntu:20.04" - b2-toolset: "gcc" - name: "GCC 10: C++14-17" - shared: true - build-type: "Release" - - - compiler: "gcc" - version: "9" - cxxstd: "14,17" - latest-cxxstd: "17" - cxx: "g++-9" - cc: "gcc-9" - runs-on: "ubuntu-latest" - container: "ubuntu:20.04" - b2-toolset: "gcc" - name: "GCC 9: C++14-17" - shared: false - build-type: "Release" - - - compiler: "gcc" - version: "8" - cxxstd: "14,17" - latest-cxxstd: "17" - cxx: "g++-8" - cc: "gcc-8" - runs-on: "ubuntu-latest" - container: "ubuntu:20.04" - b2-toolset: "gcc" - name: "GCC 8: C++14-17" - shared: true - build-type: "Release" - - - compiler: "gcc" - version: "7" - cxxstd: "14,17" - latest-cxxstd: "17" - cxx: "g++-7" - cc: "gcc-7" - runs-on: "ubuntu-latest" - container: "ubuntu:20.04" - b2-toolset: "gcc" - name: "GCC 7: C++14-17" - shared: false - build-type: "Release" - - - compiler: "gcc" - version: "6" - cxxstd: "11,14" - latest-cxxstd: "14" - cxx: "g++-6" - cc: "gcc-6" - runs-on: "ubuntu-latest" - container: "ubuntu:18.04" - b2-toolset: "gcc" - name: "GCC 6: C++11-14" - shared: true - build-type: "Release" - - - compiler: "gcc" - version: "5" - cxxstd: "11" - latest-cxxstd: "11" - cxx: "g++-5" - cc: "gcc-5" - runs-on: "ubuntu-latest" - container: "ubuntu:18.04" - b2-toolset: "gcc" - is-earliest: true - name: "GCC 5: C++11" + name: "GCC 11: C++20" shared: false build-type: "Release" @@ -557,7 +478,7 @@ jobs: - compiler: "clang" version: "20" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" cxx: "clang++-20" cc: "clang-20" @@ -565,14 +486,14 @@ jobs: container: "ubuntu:24.04" b2-toolset: "clang" is-latest: true - name: "Clang 20: C++17-20 (asan)" + name: "Clang 20: C++20 (asan)" shared: false asan: true build-type: "RelWithDebInfo" - compiler: "clang" version: "20" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" cxx: "clang++-20" cc: "clang-20" @@ -580,7 +501,7 @@ jobs: container: "ubuntu:24.04" b2-toolset: "clang" is-latest: true - name: "Clang 20: C++17-20 (asan, x86)" + name: "Clang 20: C++20 (asan, x86)" shared: true asan: true x86: true @@ -645,31 +566,31 @@ jobs: - compiler: "clang" version: "17" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" cxx: "clang++-17" cc: "clang-17" runs-on: "ubuntu-24.04" b2-toolset: "clang" - name: "Clang 17: C++17-20" + name: "Clang 17: C++20" shared: false build-type: "Release" - compiler: "clang" version: "16" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" cxx: "clang++-16" cc: "clang-16" runs-on: "ubuntu-24.04" b2-toolset: "clang" - name: "Clang 16: C++17-20" + name: "Clang 16: C++20" shared: true build-type: "Release" - compiler: "clang" version: "15" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" cxx: "clang++-15" cc: "clang-15" @@ -682,7 +603,7 @@ jobs: - compiler: "clang" version: "14" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" cxx: "clang++-14" cc: "clang-14" @@ -695,148 +616,56 @@ jobs: - compiler: "clang" version: "13" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" cxx: "clang++-13" cc: "clang-13" runs-on: "ubuntu-latest" container: "ubuntu:22.04" b2-toolset: "clang" - name: "Clang 13: C++17-20" + name: "Clang 13: C++20" shared: false build-type: "Release" - compiler: "clang" version: "12" - cxxstd: "17,20" + cxxstd: "20" latest-cxxstd: "20" cxx: "clang++-12" cc: "clang-12" runs-on: "ubuntu-latest" container: "ubuntu:22.04" b2-toolset: "clang" - name: "Clang 12: C++17-20" + name: "Clang 12: C++20" shared: true build-type: "Release" - compiler: "clang" version: "11" - cxxstd: "14,17" - latest-cxxstd: "17" + cxxstd: "20" + latest-cxxstd: "20" cxx: "clang++-11" cc: "clang-11" runs-on: "ubuntu-latest" container: "ubuntu:22.04" b2-toolset: "clang" - name: "Clang 11: C++14-17" + name: "Clang 11: C++20" shared: false build-type: "Release" - compiler: "clang" version: "10" - cxxstd: "14,17" - latest-cxxstd: "17" + cxxstd: "20" + latest-cxxstd: "20" cxx: "clang++-10" cc: "clang-10" runs-on: "ubuntu-latest" container: "ubuntu:20.04" b2-toolset: "clang" - name: "Clang 10: C++14-17" - shared: true - build-type: "Release" - - - compiler: "clang" - version: "9" - cxxstd: "14,17" - latest-cxxstd: "17" - cxx: "clang++-9" - cc: "clang-9" - runs-on: "ubuntu-latest" - container: "ubuntu:20.04" - b2-toolset: "clang" - name: "Clang 9: C++14-17" - shared: false - build-type: "Release" - - - compiler: "clang" - version: "8" - cxxstd: "14,17" - latest-cxxstd: "17" - cxx: "clang++-8" - cc: "clang-8" - runs-on: "ubuntu-latest" - container: "ubuntu:20.04" - b2-toolset: "clang" - name: "Clang 8: C++14-17" - shared: true - build-type: "Release" - - - compiler: "clang" - version: "7" - cxxstd: "14,17" - latest-cxxstd: "17" - cxx: "clang++-7" - cc: "clang-7" - runs-on: "ubuntu-latest" - container: "ubuntu:20.04" - b2-toolset: "clang" - name: "Clang 7: C++14-17" - shared: false - build-type: "Release" - - - compiler: "clang" - version: "6" - cxxstd: "14,17" - latest-cxxstd: "17" - cxx: "clang++-6.0" - cc: "clang-6.0" - runs-on: "ubuntu-latest" - container: "ubuntu:20.04" - b2-toolset: "clang" - name: "Clang 6: C++14-17" - shared: true - build-type: "Release" - - - compiler: "clang" - version: "5" - cxxstd: "11,14" - latest-cxxstd: "14" - cxx: "clang++-5.0" - cc: "clang-5.0" - runs-on: "ubuntu-latest" - container: "ubuntu:18.04" - b2-toolset: "clang" - name: "Clang 5: C++11-14" - shared: false - build-type: "Release" - - - compiler: "clang" - version: "4" - cxxstd: "11,14" - latest-cxxstd: "14" - cxx: "clang++-4.0" - cc: "clang-4.0" - runs-on: "ubuntu-latest" - container: "ubuntu:18.04" - b2-toolset: "clang" - name: "Clang 4: C++11-14" + name: "Clang 10: C++20" shared: true build-type: "Release" - - compiler: "clang" - version: "3.9" - cxxstd: "11" - latest-cxxstd: "11" - cxx: "clang++-3.9" - cc: "clang-3.9" - runs-on: "ubuntu-latest" - container: "ubuntu:18.04" - b2-toolset: "clang" - is-earliest: true - name: "Clang 3.9: C++11" - shared: false - build-type: "Release" - name: ${{ matrix.name }} runs-on: ${{ fromJSON(needs.runner-selection.outputs.labelmatrix)[matrix.runs-on] }} container: @@ -1101,28 +930,28 @@ jobs: ref-source-dir: boost-root/libs/beast2/test/cmake_test toolchain: ${{ (startsWith(matrix.runs-on, 'windows') && steps.patch-user-config.outputs.toolchain) || '' }} - - name: Root Project CMake Workflow - uses: alandefreitas/cpp-actions/cmake-workflow@v1.9.0 - if: ${{ matrix.build-cmake || matrix.is-earliest }} - with: - source-dir: boost-root/libs/${{ steps.patch.outputs.module }} - build-dir: __build_root_test__ - run-tests: true - generator: ${{ matrix.generator }} - generator-toolset: ${{ matrix.generator-toolset }} - build-type: ${{ matrix.build-type }} - install: false - cxxstd: ${{ matrix.latest-cxxstd }} - cc: ${{ steps.setup-cpp.outputs.cc || matrix.cc }} - ccflags: ${{ matrix.ccflags }} - cxx: ${{ steps.setup-cpp.outputs.cxx || matrix.cxx }} - cxxflags: ${{ matrix.cxxflags }} - shared: ${{ matrix.shared }} - cmake-version: '>=3.20' - package: false - package-artifact: false - ref-source-dir: boost-root - toolchain: ${{ (startsWith(matrix.runs-on, 'windows') && steps.patch-user-config.outputs.toolchain) || '' }} + # - name: Root Project CMake Workflow + # uses: alandefreitas/cpp-actions/cmake-workflow@v1.9.0 + # if: ${{ matrix.build-cmake || matrix.is-earliest }} + # with: + # source-dir: boost-root/libs/${{ steps.patch.outputs.module }} + # build-dir: __build_root_test__ + # run-tests: true + # generator: ${{ matrix.generator }} + # generator-toolset: ${{ matrix.generator-toolset }} + # build-type: ${{ matrix.build-type }} + # install: false + # cxxstd: ${{ matrix.latest-cxxstd }} + # cc: ${{ steps.setup-cpp.outputs.cc || matrix.cc }} + # ccflags: ${{ matrix.ccflags }} + # cxx: ${{ steps.setup-cpp.outputs.cxx || matrix.cxx }} + # cxxflags: ${{ matrix.cxxflags }} + # shared: ${{ matrix.shared }} + # cmake-version: '>=3.20' + # package: false + # package-artifact: false + # ref-source-dir: boost-root + # toolchain: ${{ (startsWith(matrix.runs-on, 'windows') && steps.patch-user-config.outputs.toolchain) || '' }} - name: FlameGraph uses: alandefreitas/cpp-actions/flamegraph@v1.9.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index f73fe086..e1162fc2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -151,7 +151,7 @@ source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/src PREFIX "src" FILES ${BOOST_BEA function(boost_beast2_setup_properties target) - target_compile_features(${target} PUBLIC cxx_constexpr) + target_compile_features(${target} PUBLIC cxx_std_20) target_include_directories(${target} PUBLIC "${PROJECT_SOURCE_DIR}/include" PRIVATE "${PROJECT_SOURCE_DIR}") diff --git a/build/Jamfile b/build/Jamfile index e2870fba..af54ac18 100644 --- a/build/Jamfile +++ b/build/Jamfile @@ -11,11 +11,7 @@ import config : requires ; constant c11-requires : [ requires - cxx11_constexpr - cxx11_decltype - cxx11_hdr_tuple - cxx11_template_aliases - cxx11_variadic_templates + cxx20_hdr_coroutine ] ; diff --git a/doc/modules/ROOT/pages/index.adoc b/doc/modules/ROOT/pages/index.adoc index 1c7dc9e7..8181191a 100644 --- a/doc/modules/ROOT/pages/index.adoc +++ b/doc/modules/ROOT/pages/index.adoc @@ -14,13 +14,12 @@ This is a portable C++ library which implements the HTTP/1 protocol using Boost.HTTP.Proto and Boost.Asio. The library is distinguished by these provided features: -* Require only C++11 * Works without exceptions * Fast compilation, few templates == Requirements -* Requires Boost and a compiler supporting at least C++11 +* Requires Boost and a compiler supporting at least C++20 * Link to a static or dynamically linked version of this library * Supports `-fno-exceptions`, detected automatically @@ -28,8 +27,8 @@ provided features: Boost.Buffers has been tested with the following compilers: -* gcc: 5 to 14 (except 8.0.1) -* clang: 3.9, 4 to 18 +* gcc: 10 to 15 +* clang: 11 to 20 * msvc: 14.1 to 14.42 == Quality Assurance diff --git a/example/client/CMakeLists.txt b/example/client/CMakeLists.txt index 7bf9965f..6ec818ed 100644 --- a/example/client/CMakeLists.txt +++ b/example/client/CMakeLists.txt @@ -12,7 +12,7 @@ find_package(OpenSSL) add_subdirectory(visit) if (OPENSSL_FOUND) - add_subdirectory(burl) + # add_subdirectory(burl) add_subdirectory(get) add_subdirectory(jsonrpc) endif () diff --git a/example/client/burl/CMakeLists.txt b/example/client/burl/CMakeLists.txt index f31650dd..921ecea6 100644 --- a/example/client/burl/CMakeLists.txt +++ b/example/client/burl/CMakeLists.txt @@ -7,10 +7,6 @@ # Official repository: https://github.com/cppalliance/beast2 # -if(NOT cxx_std_20 IN_LIST CMAKE_CXX_COMPILE_FEATURES) - return() -endif() - file(GLOB_RECURSE PFILES CONFIGURE_DEPENDS *.cpp *.hpp CMakeLists.txt Jamfile) @@ -27,8 +23,6 @@ set_property(TARGET beast2_example_client_burl find_package(OpenSSL REQUIRED) -target_compile_features(beast2_example_client_burl PUBLIC cxx_std_20) - target_link_libraries(beast2_example_client_burl Boost::beast2 Boost::url diff --git a/example/client/burl/Jamfile b/example/client/burl/Jamfile index b342c0ee..666382f1 100644 --- a/example/client/burl/Jamfile +++ b/example/client/burl/Jamfile @@ -20,7 +20,6 @@ lib user32 ; project : requirements [ requires - cxx20_hdr_concepts cxx20_hdr_format ] /boost/beast2//boost_beast2 diff --git a/example/client/jsonrpc/CMakeLists.txt b/example/client/jsonrpc/CMakeLists.txt index 462095f5..998f4895 100644 --- a/example/client/jsonrpc/CMakeLists.txt +++ b/example/client/jsonrpc/CMakeLists.txt @@ -7,10 +7,6 @@ # Official repository: https://github.com/cppalliance/beast2 # -if(NOT cxx_std_20 IN_LIST CMAKE_CXX_COMPILE_FEATURES) - return() -endif() - file(GLOB_RECURSE PFILES CONFIGURE_DEPENDS *.cpp *.hpp CMakeLists.txt Jamfile) @@ -41,31 +37,15 @@ if (WIN32) target_link_libraries(beast2_example_client_jsonrpc_lib PUBLIC crypt32) endif() -# CPP11 Example -add_executable(beast2_example_client_jsonrpc_cpp11 cpp11.cpp eth_methods.hpp Jamfile) -set_property(TARGET beast2_example_client_jsonrpc_cpp11 - PROPERTY FOLDER "examples") -target_link_libraries(beast2_example_client_jsonrpc_cpp11 - PRIVATE - beast2_example_client_jsonrpc_lib) -if (TARGET Boost::capy_zlib) - target_link_libraries(beast2_example_client_jsonrpc_cpp11 PRIVATE Boost::capy_zlib) -endif() -if (TARGET Boost::capy_brotli) - target_link_libraries(beast2_example_client_jsonrpc_cpp11 PRIVATE Boost::capy_brotli) -endif() - -# CPP20 Example -add_executable(beast2_example_client_jsonrpc_cpp20 cpp20.cpp eth_methods.hpp Jamfile) -set_property(TARGET beast2_example_client_jsonrpc_cpp20 +add_executable(beast2_example_client_jsonrpc main.cpp eth_methods.hpp Jamfile) +set_property(TARGET beast2_example_client_jsonrpc PROPERTY FOLDER "examples") -target_compile_features(beast2_example_client_jsonrpc_cpp20 PUBLIC cxx_std_20) -target_link_libraries(beast2_example_client_jsonrpc_cpp20 +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_cpp20 PRIVATE Boost::capy_zlib) + target_link_libraries(beast2_example_client_jsonrpc PRIVATE Boost::capy_zlib) endif() if (TARGET Boost::capy_brotli) - target_link_libraries(beast2_example_client_jsonrpc_cpp20 PRIVATE Boost::capy_brotli) + target_link_libraries(beast2_example_client_jsonrpc PRIVATE Boost::capy_brotli) endif() diff --git a/example/client/jsonrpc/Jamfile b/example/client/jsonrpc/Jamfile index 8cf688b9..e828aa44 100644 --- a/example/client/jsonrpc/Jamfile +++ b/example/client/jsonrpc/Jamfile @@ -33,16 +33,7 @@ project . ; -exe cpp11 : - cpp11.cpp +exe jsonrpc_example : + main.cpp [ glob jsonrpc/*.cpp ] ; - -exe cpp20 : - cpp20.cpp - [ glob jsonrpc/*.cpp ] - : requirements - [ requires - cxx20_hdr_coroutine - ] - ; diff --git a/example/client/jsonrpc/cpp11.cpp b/example/client/jsonrpc/cpp11.cpp deleted file mode 100644 index 8bfa32bb..00000000 --- a/example/client/jsonrpc/cpp11.cpp +++ /dev/null @@ -1,277 +0,0 @@ -// -// Copyright (c) 2025 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 "jsonrpc/client.hpp" - -#include "eth_methods.hpp" - -#include -#include -#include -#include - -#include -#include - -using namespace boost; -using namespace std::placeholders; - -// Bring Ethereum methods into scope -using namespace eth_methods; - -class session -{ - jsonrpc::client client_; - -public: - session( - asio::io_context& ioc, - asio::ssl::context& ssl_ctx, - capy::polystore& ctx) - : client_( - urls::url("https://ethereum.publicnode.com"), - ctx, - ioc.get_executor(), - ssl_ctx) - { - } - - void - run() - { - // Set the user agent - client_.http_fields().set( - http_proto::field::user_agent, "Boost.Http.Io"); - - // Connect to the endpoint - client_.async_connect( - std::bind(&session::on_connect, this, _1)); - } - -private: - void - on_connect(system::error_code ec) - { - if(ec) - return fail(ec, "connect"); - - // Get Ethereum node client software and version - client_.async_call( - web3_clientVersion, - std::bind(&session::on_clientVersion, this, _1, _2)); - } - - void - on_clientVersion( - jsonrpc::error error, json::string version) - { - if(error.code()) - return fail(error); - - std::cout - << "web3 client: " - << version << '\n'; - - // Get the latest block number - client_.async_call( - eth_blockNumber, - std::bind(&session::on_blockNumber, this, _1, _2)); - } - - void - on_blockNumber( - jsonrpc::error error, json::string block_num) - { - if(error.code()) - return fail(error); - - std::cout - << "Block height: " - << block_num << '\n'; - - // Get block details - client_.async_call( - eth_getBlockByNumber, - { block_num, false }, - std::bind(&session::on_getBlockByNumber, this, _1, _2)); - } - - void - on_getBlockByNumber( - jsonrpc::error error, json::object block) - { - if(error.code()) - return fail(error); - - std::cout<< "Block hash: " << block["hash"] << '\n'; - std::cout<< "Block size: " << block["size"] << " Bytes\n"; - std::cout<< "Timestamp: " << block["timestamp"] << '\n'; - std::cout<< "Transactions: " << block["transactions"].as_array().size() << '\n'; - - // Get account balance - client_.async_call( - eth_getBalance, - { - "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae", - block["number"] - }, - std::bind(&session::on_getBalance, this, _1, _2)); - } - - void - on_getBalance( - jsonrpc::error error, json::string balance) - { - if(error.code()) - return fail(error); - - std::cout - << "Balance: " - << balance << '\n'; - - // Estimate gas for a transfer - client_.async_call( - eth_estimateGas, - { - json::object - { - { "from", "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae" }, - { "to", "0x281055afc982d96fab65b3a49cac8b878184cb16" }, - { "value", "0x2386F26FC10000" } // 0.01 ETH in wei - } - }, - std::bind(&session::on_estimateGas, this, _1, _2)); - } - - void - on_estimateGas( - jsonrpc::error error, json::string gas_estimate) - { - if(error.code()) - return fail(error); - - std::cout - << "Gas estimate: " - << gas_estimate << '\n'; - - // Get the current gas price - client_.async_call( - eth_gasPrice, - std::bind(&session::on_gasPrice, this, _1, _2)); - } - - void - on_gasPrice( - jsonrpc::error error, json::string gas_price) - { - if(error.code()) - return fail(error); - - std::cout - << "Gas price: " - << gas_price << '\n'; - - // Gracefully close the stream - client_.async_shutdown( - std::bind(&session::on_shutdown, this, _1)); - } - - void - on_shutdown(system::error_code ec) - { - if(ec && ec != asio::ssl::error::stream_truncated) - return fail(ec, "shutdown"); - } - - static - void - fail(system::error_code ec, const char* operation) - { - std::cerr - << operation << ": " - << ec.message() << "\n"; - } - - static - void - fail(const jsonrpc::error& error) - { - if(!error.info().empty()) - std::cerr << error.info() << " "; - std::cerr << error.code().what() << "\n"; - } -}; - -/* -Sample output: - -web3 client: "erigon/3.0.4/linux-amd64/go1.23.9" -Block height: "0x161a027" -Block hash: "0xe35f0fb6199e295ff7ba864b034fbbcf3f722b089cf5fe18181f5a83352b2645" -Block size: "0x1604d" Bytes -Timestamp: "0x68a47207" -Transactions: 201 -Balance: "0x2647cc23d6974bdb8179" -Gas estimate: "0x5208" -Gas price: "0x1c9ad53b" -*/ - -int -main(int, char*[]) -{ - try - { - // The io_context is required for all I/O - asio::io_context ioc; - - // 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 - capy::polystore capy_ctx; - - // Install parser service - { - http_proto::response_parser::config cfg; - cfg.min_buffer = 64 * 1024; - #ifdef BOOST_CAPY_HAS_BROTLI - cfg.apply_brotli_decoder = true; - capy::brotli::install_decode_service(capy_ctx); - #endif - #ifdef BOOST_CAPY_HAS_ZLIB - cfg.apply_deflate_decoder = true; - cfg.apply_gzip_decoder = true; - capy::zlib::install_inflate_service(capy_ctx); - #endif - http_proto::install_parser_service(capy_ctx, cfg); - } - - // Install serializer service with default configuration - http_proto::install_serializer_service(capy_ctx, {}); - - // Root certificates used for verification - ssl_ctx.set_default_verify_paths(); - - // Verify the remote server's certificate - ssl_ctx.set_verify_mode(asio::ssl::verify_peer); - - session s(ioc, ssl_ctx, capy_ctx); - s.run(); - - ioc.run(); - - return EXIT_SUCCESS; - } - catch(const std::exception& e) - { - std::cerr << "Error: " << e.what() << std::endl; - return EXIT_FAILURE; - } -} diff --git a/example/client/jsonrpc/cpp20.cpp b/example/client/jsonrpc/main.cpp similarity index 100% rename from example/client/jsonrpc/cpp20.cpp rename to example/client/jsonrpc/main.cpp diff --git a/meta/explicit-failures-markup.xml b/meta/explicit-failures-markup.xml index bc7abccb..5422b7cd 100644 --- a/meta/explicit-failures-markup.xml +++ b/meta/explicit-failures-markup.xml @@ -3,18 +3,7 @@ - - - - - - - - - - - - C++11 is the minimum requirement. + C++20 is the minimum requirement. From eebe8cad20a833da20823f7e20c7acbb2b6b83d3 Mon Sep 17 00:00:00 2001 From: Mohammad Nejati Date: Mon, 5 Jan 2026 11:18:43 +0000 Subject: [PATCH 26/40] fix docs build --- CMakeLists.txt | 6 +++--- doc/mrdocs.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e1162fc2..15e3eba8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -149,7 +149,6 @@ source_group("" FILES "include/boost/beast2.hpp" "build/Jamfile") source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/include/boost/beast2 PREFIX "include" FILES ${BOOST_BEAST2_HEADERS}) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/src PREFIX "src" FILES ${BOOST_BEAST2_SOURCES}) - function(boost_beast2_setup_properties target) target_compile_features(${target} PUBLIC cxx_std_20) target_include_directories(${target} @@ -174,9 +173,10 @@ if (BOOST_BEAST2_MRDOCS_BUILD) file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/mrdocs.cpp" "#include \n") add_library(boost_beast2_mrdocs "${CMAKE_CURRENT_BINARY_DIR}/mrdocs.cpp") boost_beast2_setup_properties(boost_beast2_mrdocs) - boost_beast2_setup_properties(boost_beast2_mrdocs PUBLIC BOOST_BEAST2_MRDOCS) + target_compile_definitions(boost_beast2_mrdocs PUBLIC BOOST_BEAST2_MRDOCS) + set_target_properties(boost_beast2_mrdocs PROPERTIES EXPORT_COMPILE_COMMANDS ON) return() -endif() +endif () add_library(boost_beast2 include/boost/beast2.hpp build/Jamfile ${BOOST_BEAST2_HEADERS} ${BOOST_BEAST2_SOURCES}) add_library(Boost::beast2 ALIAS boost_beast2) diff --git a/doc/mrdocs.yml b/doc/mrdocs.yml index 8a10fc1a..3a2054ec 100644 --- a/doc/mrdocs.yml +++ b/doc/mrdocs.yml @@ -33,4 +33,4 @@ use-system-stdlib: true warn-unnamed-param: true warn-if-undoc-enum-val: false -cmake: '-DCMAKE_CXX_STANDARD=20 -DBOOST_BEAST2_MRDOCS_BUILD=ON -DBOOST_BEAST2_BUILD_TESTS=OFF -DBOOST_BEAST2_BUILD_EXAMPLES=OFF' +cmake: '-DCMAKE_CXX_STANDARD=20 -DBOOST_BEAST2_MRDOCS_BUILD=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=OFF' From a456c0f945d88b2e13d675357bf6a857ff6ce44f Mon Sep 17 00:00:00 2001 From: Mohammad Nejati Date: Tue, 6 Jan 2026 12:23:26 +0000 Subject: [PATCH 27/40] chore: rename http_proto to http --- .drone/drone.bat | 2 +- .drone/drone.sh | 8 +- .github/workflows/ci.yml | 14 +- CMakeLists.txt | 7 +- build/Jamfile | 4 +- doc/antora.yml | 4 +- ...gen.tag.xml => boost-http-doxygen.tag.xml} | 1360 +++++++++-------- example/client/burl/any_stream.hpp | 10 +- example/client/burl/connect.cpp | 14 +- example/client/burl/main.cpp | 88 +- example/client/burl/message.cpp | 30 +- example/client/burl/message.hpp | 22 +- example/client/burl/multipart_form.cpp | 4 +- example/client/burl/multipart_form.hpp | 10 +- example/client/burl/options.hpp | 6 +- example/client/burl/request.hpp | 22 +- example/client/get/main.cpp | 34 +- example/client/jsonrpc/jsonrpc/client.cpp | 10 +- example/client/jsonrpc/jsonrpc/client.hpp | 14 +- example/client/jsonrpc/main.cpp | 6 +- example/client/visit/main.cpp | 16 +- example/server/Jamfile | 2 +- example/server/main.cpp | 8 +- example/server/serve_detached.hpp | 10 +- example/server/serve_log_admin.cpp | 20 +- include/boost/beast2/body_read_stream.hpp | 2 +- include/boost/beast2/detail/config.hpp | 4 +- include/boost/beast2/impl/read.hpp | 4 +- include/boost/beast2/impl/write.hpp | 2 +- include/boost/beast2/read.hpp | 4 +- include/boost/beast2/server/body_source.hpp | 2 +- include/boost/beast2/server/http_stream.hpp | 10 +- .../beast2/server/route_handler_asio.hpp | 2 +- include/boost/beast2/server/router.hpp | 4 +- include/boost/beast2/server/router_asio.hpp | 2 +- .../boost/beast2/server/serve_redirect.hpp | 2 +- include/boost/beast2/server/serve_static.hpp | 2 +- include/boost/beast2/write.hpp | 2 +- src/server/serve_redirect.cpp | 8 +- src/server/serve_static.cpp | 2 +- test/unit/body_read_stream.cpp | 6 +- test/unit/read.cpp | 18 +- test/unit/sandbox.cpp | 6 +- test/unit/server/body_source.cpp | 6 +- test/unit/server/route_handler_asio.cpp | 12 +- test/unit/write.cpp | 30 +- 46 files changed, 1007 insertions(+), 848 deletions(-) rename doc/tagfiles/{boost-http_proto-doxygen.tag.xml => boost-http-doxygen.tag.xml} (55%) diff --git a/.drone/drone.bat b/.drone/drone.bat index 0db571c2..b310d140 100644 --- a/.drone/drone.bat +++ b/.drone/drone.bat @@ -47,7 +47,7 @@ pushd !BOOST_ROOT!\libs git clone https://github.com/cppalliance/capy -b !BOOST_BRANCH! --depth 1 popd pushd !BOOST_ROOT!\libs -git clone https://github.com/cppalliance/http_proto -b !BOOST_BRANCH! --depth 1 +git clone https://github.com/cppalliance/http -b !BOOST_BRANCH! --depth 1 popd echo '==================================> COMPILE' diff --git a/.drone/drone.sh b/.drone/drone.sh index 23732c17..2a249e5b 100644 --- a/.drone/drone.sh +++ b/.drone/drone.sh @@ -47,9 +47,9 @@ common_install () { popd fi - if [ ! -d "$BOOST_ROOT/libs/http_proto" ]; then + if [ ! -d "$BOOST_ROOT/libs/http" ]; then pushd $BOOST_ROOT/libs - git clone https://github.com/cppalliance/http_proto -b $BOOST_BRANCH --depth 1 + git clone https://github.com/cppalliance/http -b $BOOST_BRANCH --depth 1 popd fi } @@ -139,9 +139,9 @@ if [ ! -d "$BOOST_ROOT/libs/capy" ]; then popd fi -if [ ! -d "$BOOST_ROOT/libs/http_proto" ]; then +if [ ! -d "$BOOST_ROOT/libs/http" ]; then pushd $BOOST_ROOT/libs - git clone https://github.com/cppalliance/http_proto -b $BOOST_BRANCH --depth 1 + git clone https://github.com/cppalliance/http -b $BOOST_BRANCH --depth 1 popd fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a85b42df..1110c661 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -694,7 +694,7 @@ jobs: - name: Clone Boost.Http.Proto uses: actions/checkout@v3 with: - repository: cppalliance/http_proto + repository: cppalliance/http path: http-proto-root ref: develop @@ -745,7 +745,7 @@ jobs: capy-root scan-modules-ignore: | beast2 - http_proto + http buffers capy @@ -822,7 +822,7 @@ jobs: # Patch boost-root with workspace module cp -r "$workspace_root"/http-io-root "libs/$module" - cp -r "$workspace_root"/http-proto-root libs/http_proto + cp -r "$workspace_root"/http-proto-root libs/http cp -r "$workspace_root"/buffers-root libs/buffers cp -r "$workspace_root"/capy-root libs/capy @@ -846,7 +846,7 @@ jobs: cxxflags: ${{ (matrix.asan && '-fsanitize=pointer-subtract') || '' }} user-config: ${{ ((startsWith(matrix.runs-on, 'windows') || startsWith(matrix.runs-on, 'macOS')) && format('{0}/user-config.jam', steps.patch.outputs.workspace_root)) || '' }} stop-on-error: true - extra-args: "libs/beast2/example" # https://github.com/ashtum/cpp-actions/issues/23 + extra-args: "libs/beast2/example" - name: Boost CMake Workflow uses: alandefreitas/cpp-actions/cmake-workflow@v1.9.0 @@ -1041,7 +1041,7 @@ jobs: - name: Clone Boost.Http.Proto uses: actions/checkout@v3 with: - repository: cppalliance/http_proto + repository: cppalliance/http path: http-proto-root ref: develop @@ -1073,7 +1073,7 @@ jobs: capy-root scan-modules-ignore: | beast2 - http_proto + http buffers capy @@ -1108,7 +1108,7 @@ jobs: # Patch boost-root with workspace module cp -r "$workspace_root"/http-io-root "libs/$module" - cp -r "$workspace_root"/http-proto-root libs/http_proto + cp -r "$workspace_root"/http-proto-root libs/http cp -r "$workspace_root"/buffers-root libs/buffers cp -r "$workspace_root"/capy-root libs/capy diff --git a/CMakeLists.txt b/CMakeLists.txt index 15e3eba8..1bb2fe8c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,15 +63,14 @@ set(BOOST_BEAST2_DEPENDENCIES Boost::core Boost::describe Boost::endian - Boost::http_proto + Boost::http Boost::json Boost::mp11 Boost::static_assert Boost::system Boost::throw_exception Boost::url - Boost::variant2 - ) + Boost::variant2) foreach (BOOST_BEAST2_DEPENDENCY ${BOOST_BEAST2_DEPENDENCIES}) if (BOOST_BEAST2_DEPENDENCY MATCHES "^[ ]*Boost::([A-Za-z0-9_]+)[ ]*$") @@ -125,7 +124,7 @@ if (BOOST_BEAST2_IS_ROOT) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${BOOST_SRC_DIR}/tools/cmake/include") else () # From Boost Package - find_package(Boost REQUIRED COMPONENTS buffers capy http_proto json program_options scope system url) + find_package(Boost REQUIRED COMPONENTS buffers capy http json program_options scope system url) foreach (BOOST_INCLUDE_LIBRARY ${BOOST_INCLUDE_LIBRARIES}) if (NOT TARGET Boost::${BOOST_INCLUDE_LIBRARY}) add_library(Boost::${BOOST_INCLUDE_LIBRARY} ALIAS Boost::headers) diff --git a/build/Jamfile b/build/Jamfile index af54ac18..6b2a3763 100644 --- a/build/Jamfile +++ b/build/Jamfile @@ -47,11 +47,11 @@ explicit beast2_sources ; lib boost_beast2 : beast2_sources : requirements - /boost//http_proto + /boost//http ../ BOOST_BEAST2_SOURCE : usage-requirements - /boost//http_proto + /boost//http ; boost-install boost_beast2 ; diff --git a/doc/antora.yml b/doc/antora.yml index 6dcc0082..6f547189 100644 --- a/doc/antora.yml +++ b/doc/antora.yml @@ -13,8 +13,8 @@ ext: config: doc/mrdocs.yml cpp-tagfiles: files: - - file: ./doc/tagfiles/boost-http_proto-doxygen.tag.xml + - file: ./doc/tagfiles/boost-http-doxygen.tag.xml base_url: 'xref:reference:' using-namespaces: - boost::beast2 - - boost::http_proto + - boost::http diff --git a/doc/tagfiles/boost-http_proto-doxygen.tag.xml b/doc/tagfiles/boost-http-doxygen.tag.xml similarity index 55% rename from doc/tagfiles/boost-http_proto-doxygen.tag.xml rename to doc/tagfiles/boost-http-doxygen.tag.xml index 88ccc858..48273eab 100644 --- a/doc/tagfiles/boost-http_proto-doxygen.tag.xml +++ b/doc/tagfiles/boost-http-doxygen.tag.xml @@ -1,2545 +1,2705 @@ - boost::http_proto - boost/http_proto.adoc - boost::http_proto::grammar - boost::http_proto::is_sink - boost::http_proto::is_source - boost::http_proto::fields - boost::http_proto::fields_base - boost::http_proto::file - boost::http_proto::file_sink - boost::http_proto::file_source - boost::http_proto::header_limits - boost::http_proto::message_base - boost::http_proto::metadata - boost::http_proto::parameter - boost::http_proto::parser - boost::http_proto::quoted_token_view - boost::http_proto::request - boost::http_proto::request_base - boost::http_proto::request_parser - boost::http_proto::response - boost::http_proto::response_base - boost::http_proto::response_parser - boost::http_proto::serializer - boost::http_proto::sink - boost::http_proto::source - boost::http_proto::static_request - boost::http_proto::static_response - boost::http_proto::string_body - boost::http_proto::upgrade_protocol - boost::http_proto::condition - boost::http_proto::content_coding - boost::http_proto::error - boost::http_proto::field - boost::http_proto::file_mode - boost::http_proto::method - boost::http_proto::payload - boost::http_proto::status - boost::http_proto::status_class - boost::http_proto::version - boost::http_proto::swap - boost::http_proto::to_status_class - boost::http_proto::to_string - boost::http_proto::operator<< - boost::http_proto::parameter_rule - boost::http_proto::quoted_token_rule - boost::http_proto::tchars - boost::http_proto::token_rule - boost::http_proto::upgrade_protocol_rule - boost::http_proto::upgrade_rule + boost::http + boost/http.adoc + boost::http::grammar + boost::http::is_sink + boost::http::is_source + boost::http::route_result + boost::http::router + boost::http::acceptor_config + boost::http::any_router + boost::http::basic_router + boost::http::cors + boost::http::cors_options + boost::http::fields + boost::http::fields_base + boost::http::file_sink + boost::http::file_source + boost::http::header_limits + boost::http::message_base + boost::http::metadata + boost::http::parameter + boost::http::parser + boost::http::quoted_token_view + boost::http::request + boost::http::request_base + boost::http::request_parser + boost::http::response + boost::http::response_base + boost::http::response_parser + boost::http::resumer + boost::http::route_params + boost::http::route_params_base + boost::http::router_options + boost::http::serializer + boost::http::sink + boost::http::source + boost::http::static_request + boost::http::static_response + boost::http::string_body + boost::http::suspender + boost::http::upgrade_protocol + boost::http::condition + boost::http::content_coding + boost::http::error + boost::http::field + boost::http::method + boost::http::payload + boost::http::route + boost::http::status + boost::http::status_class + boost::http::version + boost::http::swap + boost::http::to_status_class + boost::http::to_string + boost::http::operator<< + boost::http::parameter_rule + boost::http::quoted_token_rule + boost::http::tchars + boost::http::token_rule + boost::http::upgrade_protocol_rule + boost::http::upgrade_rule + + auto + co_route + boost/http/co_route.adoc + + (std::function<capy::task<route_result>(route_params&)> f) + core::string_view combine_field_values - boost/http_proto/combine_field_values.adoc + boost/http/combine_field_values.adoc (const fields_base::subrange& vr, grammar::recycled_ptr<std::string>& temp) void install_parser_service - boost/http_proto/install_parser_service.adoc + boost/http/install_parser_service.adoc - (rts::context& ctx, const parser::config_base& cfg) + (capy::polystore& ctx, const parser::config_base& cfg) void install_serializer_service - boost/http_proto/install_serializer_service.adoc + boost/http/install_serializer_service.adoc - (rts::context& ctx, const serializer::config& cfg) + (capy::polystore& ctx, const serializer::config& cfg) status int_to_status - boost/http_proto/int_to_status.adoc + boost/http/int_to_status.adoc (unsigned int v) - implementation_defined::list_rule_t<Rule> - list_rule - boost/http_proto/list_rule.adoc + bool + is_route_result + boost/http/is_route_result.adoc - (const Rule& r, std::size_t n, std::size_t m) + (route_result rv) - system::error_code - make_error_code - boost/http_proto/make_error_code.adoc + implementation_defined::list_rule_t<Rule> + list_rule + boost/http/list_rule.adoc - (error ev) + (const Rule& r, std::size_t n, std::size_t m) system::error_condition make_error_condition - boost/http_proto/make_error_condition.adoc + boost/http/make_error_condition.adoc (condition c) - - core::string_view - obsolete_reason - boost/http_proto/obsolete_reason.adoc - - (status v) - boost::optional<field> string_to_field - boost/http_proto/string_to_field.adoc + boost/http/string_to_field.adoc (core::string_view s) method string_to_method - boost/http_proto/string_to_method.adoc + boost/http/string_to_method.adoc (core::string_view s) - boost::http_proto::grammar - boost/http_proto/grammar.adoc + boost::http::grammar + boost/http/grammar.adoc + + + boost::http::is_sink + boost/http/is_sink.adoc + + + boost::http::is_source + boost/http/is_source.adoc + + + boost::http::route_result + boost/http/route_result.adoc + + + boost::http::router + boost/http/router.adoc + + + boost::http::acceptor_config + boost/http/acceptor_config.adoc + + + boost::http::any_router + boost/http/any_router.adoc + + void + ~any_router + boost/http/any_router/2destructor.adoc + + () + + + std::size_t + count + boost/http/any_router/count.adoc + + () + + + route_result + do_dispatch + boost/http/any_router/do_dispatch.adoc + + (route_params_base&) + + + layer& + new_layer + boost/http/any_router/new_layer.adoc + + (core::string_view pattern) + + + route_result + resume_impl + boost/http/any_router/resume_impl.adoc + + (route_params_base&, route_result ec) + + + + boost::http::basic_router + boost/http/basic_router.adoc + + void + all + boost/http/basic_router/all.adoc + + (core::string_view pattern, H1&& h1, ...HN...&& hn) + + + route_result + resume + boost/http/basic_router/resume.adoc + + (Params& p, const route_result& rv) + + + fluent_route + route + boost/http/basic_router/route.adoc + + (core::string_view pattern) + - boost::http_proto::is_sink - boost/http_proto/is_sink.adoc + boost::http::cors + boost/http/cors.adoc + + void + cors + boost/http/cors/2constructor.adoc + + (cors_options options) + + + route_result + operator() + boost/http/cors/operator_call.adoc + + (route_params& p) + - boost::http_proto::is_source - boost/http_proto/is_source.adoc + boost::http::cors_options + boost/http/cors_options.adoc - boost::http_proto::fields - boost/http_proto/fields.adoc + boost::http::fields + boost/http/fields.adoc iterator begin - boost/http_proto/fields_base/begin.adoc + boost/http/fields_base/begin.adoc () core::string_view buffer - boost/http_proto/fields_base/buffer.adoc + boost/http/fields_base/buffer.adoc () std::size_t capacity_in_bytes - boost/http_proto/fields_base/capacity_in_bytes.adoc + boost/http/fields_base/capacity_in_bytes.adoc () void clear - boost/http_proto/fields_base/clear.adoc + boost/http/fields_base/clear.adoc () iterator end - boost/http_proto/fields_base/end.adoc + boost/http/fields_base/end.adoc () std::size_t max_capacity_in_bytes - boost/http_proto/fields_base/max_capacity_in_bytes.adoc + boost/http/fields_base/max_capacity_in_bytes.adoc () reverse_iterator rbegin - boost/http_proto/fields_base/rbegin.adoc + boost/http/fields_base/rbegin.adoc () reverse_iterator rend - boost/http_proto/fields_base/rend.adoc + boost/http/fields_base/rend.adoc () void reserve_bytes - boost/http_proto/fields_base/reserve_bytes.adoc + boost/http/fields_base/reserve_bytes.adoc (std::size_t n) void set_max_capacity_in_bytes - boost/http_proto/fields_base/set_max_capacity_in_bytes.adoc + boost/http/fields_base/set_max_capacity_in_bytes.adoc (std::size_t n) void shrink_to_fit - boost/http_proto/fields_base/shrink_to_fit.adoc + boost/http/fields_base/shrink_to_fit.adoc () std::size_t size - boost/http_proto/fields_base/size.adoc + boost/http/fields_base/size.adoc () void swap - boost/http_proto/fields/swap.adoc + boost/http/fields/swap.adoc (fields& other) std::size_t max_size - boost/http_proto/fields_base/max_size.adoc + boost/http/fields_base/max_size.adoc () - boost::http_proto::fields_base - boost/http_proto/fields_base.adoc + boost::http::fields_base + boost/http/fields_base.adoc void ~fields_base - boost/http_proto/fields_base/2destructor.adoc + boost/http/fields_base/2destructor.adoc () iterator begin - boost/http_proto/fields_base/begin.adoc + boost/http/fields_base/begin.adoc () core::string_view buffer - boost/http_proto/fields_base/buffer.adoc + boost/http/fields_base/buffer.adoc () std::size_t capacity_in_bytes - boost/http_proto/fields_base/capacity_in_bytes.adoc + boost/http/fields_base/capacity_in_bytes.adoc () void clear - boost/http_proto/fields_base/clear.adoc + boost/http/fields_base/clear.adoc () iterator end - boost/http_proto/fields_base/end.adoc + boost/http/fields_base/end.adoc () std::size_t max_capacity_in_bytes - boost/http_proto/fields_base/max_capacity_in_bytes.adoc + boost/http/fields_base/max_capacity_in_bytes.adoc () reverse_iterator rbegin - boost/http_proto/fields_base/rbegin.adoc + boost/http/fields_base/rbegin.adoc () reverse_iterator rend - boost/http_proto/fields_base/rend.adoc + boost/http/fields_base/rend.adoc () void reserve_bytes - boost/http_proto/fields_base/reserve_bytes.adoc + boost/http/fields_base/reserve_bytes.adoc (std::size_t n) void set_max_capacity_in_bytes - boost/http_proto/fields_base/set_max_capacity_in_bytes.adoc + boost/http/fields_base/set_max_capacity_in_bytes.adoc (std::size_t n) void shrink_to_fit - boost/http_proto/fields_base/shrink_to_fit.adoc + boost/http/fields_base/shrink_to_fit.adoc () std::size_t size - boost/http_proto/fields_base/size.adoc + boost/http/fields_base/size.adoc () std::size_t max_size - boost/http_proto/fields_base/max_size.adoc - - () - - - - boost::http_proto::file - boost/http_proto/file.adoc - - void - ~file - boost/http_proto/file/2destructor.adoc - - () - - - file& - operator= - boost/http_proto/file/operator_assign.adoc - - (file&& other) - - - bool - is_open - boost/http_proto/file/is_open.adoc + boost/http/fields_base/max_size.adoc () - boost::http_proto::file_sink - boost/http_proto/file_sink.adoc + boost::http::file_sink + boost/http/file_sink.adoc void ~file_sink - boost/http_proto/file_sink/2destructor.adoc + boost/http/file_sink/2destructor.adoc () results write - boost/http_proto/sink/write.adoc + boost/http/sink/write.adoc (const ConstBufferSequence& bs, bool more) - boost::http_proto::file_source - boost/http_proto/file_source.adoc + boost::http::file_source + boost/http/file_source.adoc void ~file_source - boost/http_proto/file_source/2destructor.adoc + boost/http/file_source/2destructor.adoc () results read - boost/http_proto/source/read.adoc + boost/http/source/read.adoc (const MutableBufferSequence& bs) - boost::http_proto::header_limits - boost/http_proto/header_limits.adoc + boost::http::header_limits + boost/http/header_limits.adoc std::size_t valid_space_needed - boost/http_proto/header_limits/valid_space_needed.adoc + boost/http/header_limits/valid_space_needed.adoc () - boost::http_proto::message_base - boost/http_proto/message_base.adoc + boost::http::message_base + boost/http/message_base.adoc iterator begin - boost/http_proto/fields_base/begin.adoc + boost/http/fields_base/begin.adoc () core::string_view buffer - boost/http_proto/fields_base/buffer.adoc + boost/http/fields_base/buffer.adoc () std::size_t capacity_in_bytes - boost/http_proto/fields_base/capacity_in_bytes.adoc + boost/http/fields_base/capacity_in_bytes.adoc () bool chunked - boost/http_proto/message_base/chunked.adoc + boost/http/message_base/chunked.adoc () void clear - boost/http_proto/fields_base/clear.adoc + boost/http/fields_base/clear.adoc () iterator end - boost/http_proto/fields_base/end.adoc + boost/http/fields_base/end.adoc () bool keep_alive - boost/http_proto/message_base/keep_alive.adoc + boost/http/message_base/keep_alive.adoc () std::size_t max_capacity_in_bytes - boost/http_proto/fields_base/max_capacity_in_bytes.adoc + boost/http/fields_base/max_capacity_in_bytes.adoc () - const http_proto::metadata& + const http::metadata& metadata - boost/http_proto/message_base/metadata.adoc + boost/http/message_base/metadata.adoc () - http_proto::payload + http::payload payload - boost/http_proto/message_base/payload.adoc + boost/http/message_base/payload.adoc () uint64_t payload_size - boost/http_proto/message_base/payload_size.adoc + boost/http/message_base/payload_size.adoc () reverse_iterator rbegin - boost/http_proto/fields_base/rbegin.adoc + boost/http/fields_base/rbegin.adoc () reverse_iterator rend - boost/http_proto/fields_base/rend.adoc + boost/http/fields_base/rend.adoc () void reserve_bytes - boost/http_proto/fields_base/reserve_bytes.adoc + boost/http/fields_base/reserve_bytes.adoc (std::size_t n) void set_chunked - boost/http_proto/message_base/set_chunked.adoc + boost/http/message_base/set_chunked.adoc (bool value) void set_content_length - boost/http_proto/message_base/set_content_length.adoc + boost/http/message_base/set_content_length.adoc (uint64_t n) void set_keep_alive - boost/http_proto/message_base/set_keep_alive.adoc + boost/http/message_base/set_keep_alive.adoc (bool value) void set_max_capacity_in_bytes - boost/http_proto/fields_base/set_max_capacity_in_bytes.adoc + boost/http/fields_base/set_max_capacity_in_bytes.adoc (std::size_t n) void set_payload_size - boost/http_proto/message_base/set_payload_size.adoc + boost/http/message_base/set_payload_size.adoc (uint64_t n) void shrink_to_fit - boost/http_proto/fields_base/shrink_to_fit.adoc + boost/http/fields_base/shrink_to_fit.adoc () std::size_t size - boost/http_proto/fields_base/size.adoc + boost/http/fields_base/size.adoc () - http_proto::version + http::version version - boost/http_proto/message_base/version.adoc + boost/http/message_base/version.adoc () std::size_t max_size - boost/http_proto/fields_base/max_size.adoc + boost/http/fields_base/max_size.adoc () - boost::http_proto::metadata - boost/http_proto/metadata.adoc + boost::http::metadata + boost/http/metadata.adoc void metadata - boost/http_proto/metadata/2constructor.adoc + boost/http/metadata/2constructor.adoc () - boost::http_proto::parameter - boost/http_proto/parameter.adoc + boost::http::parameter + boost/http/parameter.adoc - boost::http_proto::parser - boost/http_proto/parser.adoc + boost::http::parser + boost/http/parser.adoc core::string_view body - boost/http_proto/parser/body.adoc + boost/http/parser/body.adoc () void commit - boost/http_proto/parser/commit.adoc + boost/http/parser/commit.adoc (std::size_t n) void commit_eof - boost/http_proto/parser/commit_eof.adoc + boost/http/parser/commit_eof.adoc () void consume_body - boost/http_proto/parser/consume_body.adoc + boost/http/parser/consume_body.adoc (std::size_t n) bool got_header - boost/http_proto/parser/got_header.adoc + boost/http/parser/got_header.adoc + + () + + + bool + is_body_set + boost/http/parser/is_body_set.adoc () bool is_complete - boost/http_proto/parser/is_complete.adoc + boost/http/parser/is_complete.adoc () void parse - boost/http_proto/parser/parse.adoc + boost/http/parser/parse.adoc (system::error_code& ec) mutable_buffers_type prepare - boost/http_proto/parser/prepare.adoc + boost/http/parser/prepare.adoc () const_buffers_type pull_body - boost/http_proto/parser/pull_body.adoc + boost/http/parser/pull_body.adoc () core::string_view release_buffered_data - boost/http_proto/parser/release_buffered_data.adoc + boost/http/parser/release_buffered_data.adoc () void reset - boost/http_proto/parser/reset.adoc + boost/http/parser/reset.adoc () void set_body_limit - boost/http_proto/parser/set_body_limit.adoc + boost/http/parser/set_body_limit.adoc (uint64_t n) void start - boost/http_proto/parser/start.adoc + boost/http/parser/start.adoc () void ~parser - boost/http_proto/parser/2destructor.adoc + boost/http/parser/2destructor.adoc () - bool - is_body_set - boost/http_proto/parser/is_body_set.adoc + void + assign + boost/http/parser/assign.adoc - () + (parser&& other) const static_request& safe_get_request - boost/http_proto/parser/safe_get_request.adoc + boost/http/parser/safe_get_request.adoc () const static_response& safe_get_response - boost/http_proto/parser/safe_get_response.adoc + boost/http/parser/safe_get_response.adoc () void start_impl - boost/http_proto/parser/start_impl.adoc + boost/http/parser/start_impl.adoc - (bool ) + (bool) detail::workspace& ws - boost/http_proto/parser/ws.adoc + boost/http/parser/ws.adoc () - boost::http_proto::quoted_token_view - boost/http_proto/quoted_token_view.adoc + boost::http::quoted_token_view + boost/http/quoted_token_view.adoc quoted_token_view& operator= - boost/http_proto/quoted_token_view/operator_assign-07.adoc + boost/http/quoted_token_view/operator_assign-0e.adoc (const quoted_token_view& other) const_reference at - boost/http_proto/quoted_token_view/at.adoc + boost/http/quoted_token_view/at.adoc (size_type pos) const_reference back - boost/http_proto/quoted_token_view/back.adoc + boost/http/quoted_token_view/back.adoc () const_iterator begin - boost/http_proto/quoted_token_view/begin.adoc + boost/http/quoted_token_view/begin.adoc () const_iterator cbegin - boost/http_proto/quoted_token_view/cbegin.adoc + boost/http/quoted_token_view/cbegin.adoc () const_iterator cend - boost/http_proto/quoted_token_view/cend.adoc + boost/http/quoted_token_view/cend.adoc () size_type copy - boost/http_proto/quoted_token_view/copy.adoc + boost/http/quoted_token_view/copy.adoc (char* s, size_type n, size_type pos) const_reverse_iterator crbegin - boost/http_proto/quoted_token_view/crbegin.adoc + boost/http/quoted_token_view/crbegin.adoc () const_reverse_iterator crend - boost/http_proto/quoted_token_view/crend.adoc + boost/http/quoted_token_view/crend.adoc () const_pointer data - boost/http_proto/quoted_token_view/data.adoc + boost/http/quoted_token_view/data.adoc () bool empty - boost/http_proto/quoted_token_view/empty.adoc + boost/http/quoted_token_view/empty.adoc () const_iterator end - boost/http_proto/quoted_token_view/end.adoc + boost/http/quoted_token_view/end.adoc () const_reference front - boost/http_proto/quoted_token_view/front.adoc + boost/http/quoted_token_view/front.adoc () bool has_escapes - boost/http_proto/quoted_token_view/has_escapes.adoc + boost/http/quoted_token_view/has_escapes.adoc () size_type length - boost/http_proto/quoted_token_view/length.adoc + boost/http/quoted_token_view/length.adoc () size_type max_size - boost/http_proto/quoted_token_view/max_size.adoc + boost/http/quoted_token_view/max_size.adoc () const_reference operator[] - boost/http_proto/quoted_token_view/operator_subs.adoc + boost/http/quoted_token_view/operator_subs.adoc (size_type pos) const_reverse_iterator rbegin - boost/http_proto/quoted_token_view/rbegin.adoc + boost/http/quoted_token_view/rbegin.adoc () const_reverse_iterator rend - boost/http_proto/quoted_token_view/rend.adoc + boost/http/quoted_token_view/rend.adoc () size_type size - boost/http_proto/quoted_token_view/size.adoc + boost/http/quoted_token_view/size.adoc () core::string_view substr - boost/http_proto/quoted_token_view/substr.adoc + boost/http/quoted_token_view/substr.adoc (size_type pos, size_type n) std::size_t unescaped_size - boost/http_proto/quoted_token_view/unescaped_size.adoc + boost/http/quoted_token_view/unescaped_size.adoc () std::string operator basic_string<char> - boost/http_proto/quoted_token_view/2conversion-042.adoc + boost/http/quoted_token_view/2conversion-08.adoc () std::string_view operator basic_string_view<char> - boost/http_proto/quoted_token_view/2conversion-04e.adoc + boost/http/quoted_token_view/2conversion-04.adoc () string_view_base& operator= - boost/http_proto/quoted_token_view/operator_assign-0e.adoc + boost/http/quoted_token_view/operator_assign-07.adoc (const string_view_base& other) void swap - boost/http_proto/quoted_token_view/swap.adoc + boost/http/quoted_token_view/swap.adoc (string_view_base& s) - boost::http_proto::request - boost/http_proto/request.adoc + boost::http::request + boost/http/request.adoc iterator begin - boost/http_proto/fields_base/begin.adoc + boost/http/fields_base/begin.adoc () core::string_view buffer - boost/http_proto/fields_base/buffer.adoc + boost/http/fields_base/buffer.adoc () std::size_t capacity_in_bytes - boost/http_proto/fields_base/capacity_in_bytes.adoc + boost/http/fields_base/capacity_in_bytes.adoc () bool chunked - boost/http_proto/message_base/chunked.adoc + boost/http/message_base/chunked.adoc () void clear - boost/http_proto/fields_base/clear.adoc + boost/http/fields_base/clear.adoc () iterator end - boost/http_proto/fields_base/end.adoc + boost/http/fields_base/end.adoc () bool keep_alive - boost/http_proto/message_base/keep_alive.adoc + boost/http/message_base/keep_alive.adoc () std::size_t max_capacity_in_bytes - boost/http_proto/fields_base/max_capacity_in_bytes.adoc + boost/http/fields_base/max_capacity_in_bytes.adoc () - const http_proto::metadata& + const http::metadata& metadata - boost/http_proto/message_base/metadata.adoc + boost/http/message_base/metadata.adoc () - http_proto::method + http::method method - boost/http_proto/request_base/method.adoc + boost/http/request_base/method.adoc () core::string_view method_text - boost/http_proto/request_base/method_text.adoc + boost/http/request_base/method_text.adoc () - http_proto::payload + http::payload payload - boost/http_proto/message_base/payload.adoc + boost/http/message_base/payload.adoc () uint64_t payload_size - boost/http_proto/message_base/payload_size.adoc + boost/http/message_base/payload_size.adoc () reverse_iterator rbegin - boost/http_proto/fields_base/rbegin.adoc + boost/http/fields_base/rbegin.adoc () reverse_iterator rend - boost/http_proto/fields_base/rend.adoc + boost/http/fields_base/rend.adoc () void reserve_bytes - boost/http_proto/fields_base/reserve_bytes.adoc + boost/http/fields_base/reserve_bytes.adoc (std::size_t n) void set_chunked - boost/http_proto/message_base/set_chunked.adoc + boost/http/message_base/set_chunked.adoc (bool value) void set_content_length - boost/http_proto/message_base/set_content_length.adoc + boost/http/message_base/set_content_length.adoc (uint64_t n) void set_expect_100_continue - boost/http_proto/request_base/set_expect_100_continue.adoc + boost/http/request_base/set_expect_100_continue.adoc (bool b) void set_keep_alive - boost/http_proto/message_base/set_keep_alive.adoc + boost/http/message_base/set_keep_alive.adoc (bool value) void set_max_capacity_in_bytes - boost/http_proto/fields_base/set_max_capacity_in_bytes.adoc + boost/http/fields_base/set_max_capacity_in_bytes.adoc (std::size_t n) void set_payload_size - boost/http_proto/message_base/set_payload_size.adoc + boost/http/message_base/set_payload_size.adoc (uint64_t n) void set_target - boost/http_proto/request_base/set_target.adoc + boost/http/request_base/set_target.adoc (core::string_view s) void set_version - boost/http_proto/request_base/set_version.adoc + boost/http/request_base/set_version.adoc - (http_proto::version v) + (http::version v) void shrink_to_fit - boost/http_proto/fields_base/shrink_to_fit.adoc + boost/http/fields_base/shrink_to_fit.adoc () std::size_t size - boost/http_proto/fields_base/size.adoc + boost/http/fields_base/size.adoc () void swap - boost/http_proto/request/swap.adoc + boost/http/request/swap.adoc (request& other) core::string_view target - boost/http_proto/request_base/target.adoc + boost/http/request_base/target.adoc () - http_proto::version + http::version version - boost/http_proto/message_base/version.adoc + boost/http/message_base/version.adoc () std::size_t max_size - boost/http_proto/fields_base/max_size.adoc + boost/http/fields_base/max_size.adoc () - boost::http_proto::request_base - boost/http_proto/request_base.adoc + boost::http::request_base + boost/http/request_base.adoc iterator begin - boost/http_proto/fields_base/begin.adoc + boost/http/fields_base/begin.adoc () core::string_view buffer - boost/http_proto/fields_base/buffer.adoc + boost/http/fields_base/buffer.adoc () std::size_t capacity_in_bytes - boost/http_proto/fields_base/capacity_in_bytes.adoc + boost/http/fields_base/capacity_in_bytes.adoc () bool chunked - boost/http_proto/message_base/chunked.adoc + boost/http/message_base/chunked.adoc () void clear - boost/http_proto/fields_base/clear.adoc + boost/http/fields_base/clear.adoc () iterator end - boost/http_proto/fields_base/end.adoc + boost/http/fields_base/end.adoc () bool keep_alive - boost/http_proto/message_base/keep_alive.adoc + boost/http/message_base/keep_alive.adoc () std::size_t max_capacity_in_bytes - boost/http_proto/fields_base/max_capacity_in_bytes.adoc + boost/http/fields_base/max_capacity_in_bytes.adoc () - const http_proto::metadata& + const http::metadata& metadata - boost/http_proto/message_base/metadata.adoc + boost/http/message_base/metadata.adoc () - http_proto::method + http::method method - boost/http_proto/request_base/method.adoc + boost/http/request_base/method.adoc () core::string_view method_text - boost/http_proto/request_base/method_text.adoc + boost/http/request_base/method_text.adoc () - http_proto::payload + http::payload payload - boost/http_proto/message_base/payload.adoc + boost/http/message_base/payload.adoc () uint64_t payload_size - boost/http_proto/message_base/payload_size.adoc + boost/http/message_base/payload_size.adoc () reverse_iterator rbegin - boost/http_proto/fields_base/rbegin.adoc + boost/http/fields_base/rbegin.adoc () reverse_iterator rend - boost/http_proto/fields_base/rend.adoc + boost/http/fields_base/rend.adoc () void reserve_bytes - boost/http_proto/fields_base/reserve_bytes.adoc + boost/http/fields_base/reserve_bytes.adoc (std::size_t n) void set_chunked - boost/http_proto/message_base/set_chunked.adoc + boost/http/message_base/set_chunked.adoc (bool value) void set_content_length - boost/http_proto/message_base/set_content_length.adoc + boost/http/message_base/set_content_length.adoc (uint64_t n) void set_expect_100_continue - boost/http_proto/request_base/set_expect_100_continue.adoc + boost/http/request_base/set_expect_100_continue.adoc (bool b) void set_keep_alive - boost/http_proto/message_base/set_keep_alive.adoc + boost/http/message_base/set_keep_alive.adoc (bool value) void set_max_capacity_in_bytes - boost/http_proto/fields_base/set_max_capacity_in_bytes.adoc + boost/http/fields_base/set_max_capacity_in_bytes.adoc (std::size_t n) void set_payload_size - boost/http_proto/message_base/set_payload_size.adoc + boost/http/message_base/set_payload_size.adoc (uint64_t n) void set_target - boost/http_proto/request_base/set_target.adoc + boost/http/request_base/set_target.adoc (core::string_view s) void set_version - boost/http_proto/request_base/set_version.adoc + boost/http/request_base/set_version.adoc - (http_proto::version v) + (http::version v) void shrink_to_fit - boost/http_proto/fields_base/shrink_to_fit.adoc + boost/http/fields_base/shrink_to_fit.adoc () std::size_t size - boost/http_proto/fields_base/size.adoc + boost/http/fields_base/size.adoc () core::string_view target - boost/http_proto/request_base/target.adoc + boost/http/request_base/target.adoc () - http_proto::version + http::version version - boost/http_proto/message_base/version.adoc + boost/http/message_base/version.adoc () std::size_t max_size - boost/http_proto/fields_base/max_size.adoc + boost/http/fields_base/max_size.adoc () void set_start_line_impl - boost/http_proto/request_base/set_start_line_impl.adoc + boost/http/request_base/set_start_line_impl.adoc - (http_proto::method m, core::string_view ms, core::string_view t, http_proto::version v) + (http::method m, core::string_view ms, core::string_view t, http::version v) - boost::http_proto::request_parser - boost/http_proto/request_parser.adoc + boost::http::request_parser + boost/http/request_parser.adoc void ~request_parser - boost/http_proto/request_parser/2destructor.adoc + boost/http/request_parser/2destructor.adoc () + + request_parser& + operator= + boost/http/request_parser/operator_assign.adoc + + (request_parser&& other) + core::string_view body - boost/http_proto/parser/body.adoc + boost/http/parser/body.adoc () void commit - boost/http_proto/parser/commit.adoc + boost/http/parser/commit.adoc (std::size_t n) void commit_eof - boost/http_proto/parser/commit_eof.adoc + boost/http/parser/commit_eof.adoc () void consume_body - boost/http_proto/parser/consume_body.adoc + boost/http/parser/consume_body.adoc (std::size_t n) const static_request& get - boost/http_proto/request_parser/get.adoc + boost/http/request_parser/get.adoc () bool got_header - boost/http_proto/parser/got_header.adoc + boost/http/parser/got_header.adoc + + () + + + bool + is_body_set + boost/http/parser/is_body_set.adoc () bool is_complete - boost/http_proto/parser/is_complete.adoc + boost/http/parser/is_complete.adoc () void parse - boost/http_proto/parser/parse.adoc + boost/http/parser/parse.adoc (system::error_code& ec) mutable_buffers_type prepare - boost/http_proto/parser/prepare.adoc + boost/http/parser/prepare.adoc () const_buffers_type pull_body - boost/http_proto/parser/pull_body.adoc + boost/http/parser/pull_body.adoc () core::string_view release_buffered_data - boost/http_proto/parser/release_buffered_data.adoc + boost/http/parser/release_buffered_data.adoc () void reset - boost/http_proto/parser/reset.adoc + boost/http/parser/reset.adoc () void set_body_limit - boost/http_proto/parser/set_body_limit.adoc + boost/http/parser/set_body_limit.adoc (uint64_t n) void start - boost/http_proto/parser/start.adoc + boost/http/parser/start.adoc () - boost::http_proto::response - boost/http_proto/response.adoc + boost::http::response + boost/http/response.adoc iterator begin - boost/http_proto/fields_base/begin.adoc + boost/http/fields_base/begin.adoc () core::string_view buffer - boost/http_proto/fields_base/buffer.adoc + boost/http/fields_base/buffer.adoc () std::size_t capacity_in_bytes - boost/http_proto/fields_base/capacity_in_bytes.adoc + boost/http/fields_base/capacity_in_bytes.adoc () bool chunked - boost/http_proto/message_base/chunked.adoc + boost/http/message_base/chunked.adoc () void clear - boost/http_proto/fields_base/clear.adoc + boost/http/fields_base/clear.adoc () iterator end - boost/http_proto/fields_base/end.adoc + boost/http/fields_base/end.adoc () bool keep_alive - boost/http_proto/message_base/keep_alive.adoc + boost/http/message_base/keep_alive.adoc () std::size_t max_capacity_in_bytes - boost/http_proto/fields_base/max_capacity_in_bytes.adoc + boost/http/fields_base/max_capacity_in_bytes.adoc () - const http_proto::metadata& + const http::metadata& metadata - boost/http_proto/message_base/metadata.adoc + boost/http/message_base/metadata.adoc () - http_proto::payload + http::payload payload - boost/http_proto/message_base/payload.adoc + boost/http/message_base/payload.adoc () uint64_t payload_size - boost/http_proto/message_base/payload_size.adoc + boost/http/message_base/payload_size.adoc () reverse_iterator rbegin - boost/http_proto/fields_base/rbegin.adoc + boost/http/fields_base/rbegin.adoc () core::string_view reason - boost/http_proto/response_base/reason.adoc + boost/http/response_base/reason.adoc () reverse_iterator rend - boost/http_proto/fields_base/rend.adoc + boost/http/fields_base/rend.adoc () void reserve_bytes - boost/http_proto/fields_base/reserve_bytes.adoc + boost/http/fields_base/reserve_bytes.adoc (std::size_t n) void set_chunked - boost/http_proto/message_base/set_chunked.adoc + boost/http/message_base/set_chunked.adoc (bool value) void set_content_length - boost/http_proto/message_base/set_content_length.adoc + boost/http/message_base/set_content_length.adoc (uint64_t n) void set_keep_alive - boost/http_proto/message_base/set_keep_alive.adoc + boost/http/message_base/set_keep_alive.adoc (bool value) void set_max_capacity_in_bytes - boost/http_proto/fields_base/set_max_capacity_in_bytes.adoc + boost/http/fields_base/set_max_capacity_in_bytes.adoc (std::size_t n) void set_payload_size - boost/http_proto/message_base/set_payload_size.adoc + boost/http/message_base/set_payload_size.adoc (uint64_t n) + + void + set_status + boost/http/response_base/set_status.adoc + + (http::status sc) + + + void + set_version + boost/http/response_base/set_version.adoc + + (http::version v) + void shrink_to_fit - boost/http_proto/fields_base/shrink_to_fit.adoc + boost/http/fields_base/shrink_to_fit.adoc () std::size_t size - boost/http_proto/fields_base/size.adoc + boost/http/fields_base/size.adoc () - http_proto::status + http::status status - boost/http_proto/response_base/status.adoc + boost/http/response_base/status.adoc () unsigned short status_int - boost/http_proto/response_base/status_int.adoc + boost/http/response_base/status_int.adoc () void swap - boost/http_proto/response/swap.adoc + boost/http/response/swap.adoc (response& other) - http_proto::version + http::version version - boost/http_proto/message_base/version.adoc + boost/http/message_base/version.adoc () std::size_t max_size - boost/http_proto/fields_base/max_size.adoc + boost/http/fields_base/max_size.adoc () - boost::http_proto::response_base - boost/http_proto/response_base.adoc + boost::http::response_base + boost/http/response_base.adoc iterator begin - boost/http_proto/fields_base/begin.adoc + boost/http/fields_base/begin.adoc () core::string_view buffer - boost/http_proto/fields_base/buffer.adoc + boost/http/fields_base/buffer.adoc () std::size_t capacity_in_bytes - boost/http_proto/fields_base/capacity_in_bytes.adoc + boost/http/fields_base/capacity_in_bytes.adoc () bool chunked - boost/http_proto/message_base/chunked.adoc + boost/http/message_base/chunked.adoc () void clear - boost/http_proto/fields_base/clear.adoc + boost/http/fields_base/clear.adoc () iterator end - boost/http_proto/fields_base/end.adoc + boost/http/fields_base/end.adoc () bool keep_alive - boost/http_proto/message_base/keep_alive.adoc + boost/http/message_base/keep_alive.adoc () std::size_t max_capacity_in_bytes - boost/http_proto/fields_base/max_capacity_in_bytes.adoc + boost/http/fields_base/max_capacity_in_bytes.adoc () - const http_proto::metadata& + const http::metadata& metadata - boost/http_proto/message_base/metadata.adoc + boost/http/message_base/metadata.adoc () - http_proto::payload + http::payload payload - boost/http_proto/message_base/payload.adoc + boost/http/message_base/payload.adoc () uint64_t payload_size - boost/http_proto/message_base/payload_size.adoc + boost/http/message_base/payload_size.adoc () reverse_iterator rbegin - boost/http_proto/fields_base/rbegin.adoc + boost/http/fields_base/rbegin.adoc () core::string_view reason - boost/http_proto/response_base/reason.adoc + boost/http/response_base/reason.adoc () reverse_iterator rend - boost/http_proto/fields_base/rend.adoc + boost/http/fields_base/rend.adoc () void reserve_bytes - boost/http_proto/fields_base/reserve_bytes.adoc + boost/http/fields_base/reserve_bytes.adoc (std::size_t n) void set_chunked - boost/http_proto/message_base/set_chunked.adoc + boost/http/message_base/set_chunked.adoc (bool value) void set_content_length - boost/http_proto/message_base/set_content_length.adoc + boost/http/message_base/set_content_length.adoc (uint64_t n) void set_keep_alive - boost/http_proto/message_base/set_keep_alive.adoc + boost/http/message_base/set_keep_alive.adoc (bool value) void set_max_capacity_in_bytes - boost/http_proto/fields_base/set_max_capacity_in_bytes.adoc + boost/http/fields_base/set_max_capacity_in_bytes.adoc (std::size_t n) void set_payload_size - boost/http_proto/message_base/set_payload_size.adoc + boost/http/message_base/set_payload_size.adoc (uint64_t n) + + void + set_status + boost/http/response_base/set_status.adoc + + (http::status sc) + + + void + set_version + boost/http/response_base/set_version.adoc + + (http::version v) + void shrink_to_fit - boost/http_proto/fields_base/shrink_to_fit.adoc + boost/http/fields_base/shrink_to_fit.adoc () std::size_t size - boost/http_proto/fields_base/size.adoc + boost/http/fields_base/size.adoc () - http_proto::status + http::status status - boost/http_proto/response_base/status.adoc + boost/http/response_base/status.adoc () unsigned short status_int - boost/http_proto/response_base/status_int.adoc + boost/http/response_base/status_int.adoc () - http_proto::version + http::version version - boost/http_proto/message_base/version.adoc + boost/http/message_base/version.adoc () std::size_t max_size - boost/http_proto/fields_base/max_size.adoc + boost/http/fields_base/max_size.adoc () void set_start_line_impl - boost/http_proto/response_base/set_start_line_impl.adoc + boost/http/response_base/set_start_line_impl.adoc - (http_proto::status sc, unsigned short si, core::string_view reason, http_proto::version v) + (http::status sc, unsigned short si, core::string_view reason, http::version v) - boost::http_proto::response_parser - boost/http_proto/response_parser.adoc - - void - ~response_parser - boost/http_proto/response_parser/2destructor.adoc - - () - - - core::string_view - body - boost/http_proto/parser/body.adoc - - () - + boost::http::response_parser + boost/http/response_parser.adoc + + + boost::http::resumer + boost/http/resumer.adoc - void - commit - boost/http_proto/parser/commit.adoc + resumer& + operator= + boost/http/resumer/operator_assign.adoc - (std::size_t n) + (const resumer& other) + + + boost::http::route_params + boost/http/route_params.adoc void - commit_eof - boost/http_proto/parser/commit_eof.adoc + ~route_params + boost/http/route_params/2destructor.adoc () - void - consume_body - boost/http_proto/parser/consume_body.adoc - - (std::size_t n) - - - const static_response& - get - boost/http_proto/response_parser/get.adoc + route_result + read_body + boost/http/route_params/read_body.adoc - () + (ValueSink&& sink, Callback&& callback) - bool - got_header - boost/http_proto/parser/got_header.adoc + void + reset + boost/http/route_params/reset.adoc () - bool - is_complete - boost/http_proto/parser/is_complete.adoc + route_params& + set_body + boost/http/route_params/set_body.adoc - () + (std::string s) - void - parse - boost/http_proto/parser/parse.adoc + route_result + spawn + boost/http/route_params/spawn.adoc - (system::error_code& ec) + (capy::task<route_result> coro) - mutable_buffers_type - prepare - boost/http_proto/parser/prepare.adoc - - () - - - const_buffers_type - pull_body - boost/http_proto/parser/pull_body.adoc + route_params& + status + boost/http/route_params/status.adoc - () + (http::status code) + + + boost::http::route_params_base + boost/http/route_params_base.adoc - core::string_view - release_buffered_data - boost/http_proto/parser/release_buffered_data.adoc + route_params_base& + operator= + boost/http/route_params_base/operator_assign.adoc - () + (const route_params_base&) + + + boost::http::router_options + boost/http/router_options.adoc void - reset - boost/http_proto/parser/reset.adoc + router_options + boost/http/router_options/2constructor.adoc () - void - set_body_limit - boost/http_proto/parser/set_body_limit.adoc + router_options& + case_sensitive + boost/http/router_options/case_sensitive.adoc - (uint64_t n) + (bool value) - void - start - boost/http_proto/parser/start.adoc + router_options& + merge_params + boost/http/router_options/merge_params.adoc - () + (bool value) - void - start_head_response - boost/http_proto/response_parser/start_head_response.adoc + router_options& + strict + boost/http/router_options/strict.adoc - () + (bool value) - boost::http_proto::serializer - boost/http_proto/serializer.adoc + boost::http::serializer + boost/http/serializer.adoc void ~serializer - boost/http_proto/serializer/2destructor.adoc + boost/http/serializer/2destructor.adoc () + + serializer& + operator= + boost/http/serializer/operator_assign.adoc + + (serializer&& other) + void consume - boost/http_proto/serializer/consume.adoc + boost/http/serializer/consume.adoc (std::size_t n) bool is_done - boost/http_proto/serializer/is_done.adoc + boost/http/serializer/is_done.adoc () system::result<const_buffers_type> prepare - boost/http_proto/serializer/prepare.adoc + boost/http/serializer/prepare.adoc () void reset - boost/http_proto/serializer/reset.adoc + boost/http/serializer/reset.adoc () stream start_stream - boost/http_proto/serializer/start_stream.adoc + boost/http/serializer/start_stream.adoc (const message_base& m) void start_buffers - boost/http_proto/serializer/start_buffers.adoc + boost/http/serializer/start_buffers.adoc - (const message_base& , cbs_gen& ) + (const message_base&, cbs_gen&) void start_init - boost/http_proto/serializer/start_init.adoc + boost/http/serializer/start_init.adoc - (const message_base& ) + (const message_base&) void start_source - boost/http_proto/serializer/start_source.adoc + boost/http/serializer/start_source.adoc - (const message_base& , source& ) + (const message_base&, source&) detail::workspace& ws - boost/http_proto/serializer/ws.adoc + boost/http/serializer/ws.adoc () - boost::http_proto::sink - boost/http_proto/sink.adoc + boost::http::sink + boost/http/sink.adoc results write - boost/http_proto/sink/write.adoc + boost/http/sink/write.adoc (const ConstBufferSequence& bs, bool more) - boost::http_proto::source - boost/http_proto/source.adoc + boost::http::source + boost/http/source.adoc results read - boost/http_proto/source/read.adoc + boost/http/source/read.adoc (const MutableBufferSequence& bs) - boost::http_proto::static_request - boost/http_proto/static_request.adoc + boost::http::static_request + boost/http/static_request.adoc iterator begin - boost/http_proto/fields_base/begin.adoc + boost/http/fields_base/begin.adoc () core::string_view buffer - boost/http_proto/fields_base/buffer.adoc + boost/http/fields_base/buffer.adoc () std::size_t capacity_in_bytes - boost/http_proto/fields_base/capacity_in_bytes.adoc + boost/http/fields_base/capacity_in_bytes.adoc () bool chunked - boost/http_proto/message_base/chunked.adoc + boost/http/message_base/chunked.adoc () void clear - boost/http_proto/fields_base/clear.adoc + boost/http/fields_base/clear.adoc () iterator end - boost/http_proto/fields_base/end.adoc + boost/http/fields_base/end.adoc () bool keep_alive - boost/http_proto/message_base/keep_alive.adoc + boost/http/message_base/keep_alive.adoc () std::size_t max_capacity_in_bytes - boost/http_proto/fields_base/max_capacity_in_bytes.adoc + boost/http/fields_base/max_capacity_in_bytes.adoc () - const http_proto::metadata& + const http::metadata& metadata - boost/http_proto/message_base/metadata.adoc + boost/http/message_base/metadata.adoc () - http_proto::method + http::method method - boost/http_proto/request_base/method.adoc + boost/http/request_base/method.adoc () core::string_view method_text - boost/http_proto/request_base/method_text.adoc + boost/http/request_base/method_text.adoc () - http_proto::payload + http::payload payload - boost/http_proto/message_base/payload.adoc + boost/http/message_base/payload.adoc () uint64_t payload_size - boost/http_proto/message_base/payload_size.adoc + boost/http/message_base/payload_size.adoc () reverse_iterator rbegin - boost/http_proto/fields_base/rbegin.adoc + boost/http/fields_base/rbegin.adoc () reverse_iterator rend - boost/http_proto/fields_base/rend.adoc + boost/http/fields_base/rend.adoc () void reserve_bytes - boost/http_proto/fields_base/reserve_bytes.adoc + boost/http/fields_base/reserve_bytes.adoc (std::size_t n) void set_chunked - boost/http_proto/message_base/set_chunked.adoc + boost/http/message_base/set_chunked.adoc (bool value) void set_content_length - boost/http_proto/message_base/set_content_length.adoc + boost/http/message_base/set_content_length.adoc (uint64_t n) void set_expect_100_continue - boost/http_proto/request_base/set_expect_100_continue.adoc + boost/http/request_base/set_expect_100_continue.adoc (bool b) void set_keep_alive - boost/http_proto/message_base/set_keep_alive.adoc + boost/http/message_base/set_keep_alive.adoc (bool value) void set_max_capacity_in_bytes - boost/http_proto/fields_base/set_max_capacity_in_bytes.adoc + boost/http/fields_base/set_max_capacity_in_bytes.adoc (std::size_t n) void set_payload_size - boost/http_proto/message_base/set_payload_size.adoc + boost/http/message_base/set_payload_size.adoc (uint64_t n) void set_target - boost/http_proto/request_base/set_target.adoc + boost/http/request_base/set_target.adoc (core::string_view s) void set_version - boost/http_proto/request_base/set_version.adoc + boost/http/request_base/set_version.adoc - (http_proto::version v) + (http::version v) void shrink_to_fit - boost/http_proto/fields_base/shrink_to_fit.adoc + boost/http/fields_base/shrink_to_fit.adoc () std::size_t size - boost/http_proto/fields_base/size.adoc + boost/http/fields_base/size.adoc () core::string_view target - boost/http_proto/request_base/target.adoc + boost/http/request_base/target.adoc () - http_proto::version + http::version version - boost/http_proto/message_base/version.adoc + boost/http/message_base/version.adoc () std::size_t max_size - boost/http_proto/fields_base/max_size.adoc + boost/http/fields_base/max_size.adoc () - boost::http_proto::static_response - boost/http_proto/static_response.adoc + boost::http::static_response + boost/http/static_response.adoc iterator begin - boost/http_proto/fields_base/begin.adoc + boost/http/fields_base/begin.adoc () core::string_view buffer - boost/http_proto/fields_base/buffer.adoc + boost/http/fields_base/buffer.adoc () std::size_t capacity_in_bytes - boost/http_proto/fields_base/capacity_in_bytes.adoc + boost/http/fields_base/capacity_in_bytes.adoc () bool chunked - boost/http_proto/message_base/chunked.adoc + boost/http/message_base/chunked.adoc () void clear - boost/http_proto/fields_base/clear.adoc + boost/http/fields_base/clear.adoc () iterator end - boost/http_proto/fields_base/end.adoc + boost/http/fields_base/end.adoc () bool keep_alive - boost/http_proto/message_base/keep_alive.adoc + boost/http/message_base/keep_alive.adoc () std::size_t max_capacity_in_bytes - boost/http_proto/fields_base/max_capacity_in_bytes.adoc + boost/http/fields_base/max_capacity_in_bytes.adoc () - const http_proto::metadata& + const http::metadata& metadata - boost/http_proto/message_base/metadata.adoc + boost/http/message_base/metadata.adoc () - http_proto::payload + http::payload payload - boost/http_proto/message_base/payload.adoc + boost/http/message_base/payload.adoc () uint64_t payload_size - boost/http_proto/message_base/payload_size.adoc + boost/http/message_base/payload_size.adoc () reverse_iterator rbegin - boost/http_proto/fields_base/rbegin.adoc + boost/http/fields_base/rbegin.adoc () core::string_view reason - boost/http_proto/response_base/reason.adoc + boost/http/response_base/reason.adoc () reverse_iterator rend - boost/http_proto/fields_base/rend.adoc + boost/http/fields_base/rend.adoc () void reserve_bytes - boost/http_proto/fields_base/reserve_bytes.adoc + boost/http/fields_base/reserve_bytes.adoc (std::size_t n) void set_chunked - boost/http_proto/message_base/set_chunked.adoc + boost/http/message_base/set_chunked.adoc (bool value) void set_content_length - boost/http_proto/message_base/set_content_length.adoc + boost/http/message_base/set_content_length.adoc (uint64_t n) void set_keep_alive - boost/http_proto/message_base/set_keep_alive.adoc + boost/http/message_base/set_keep_alive.adoc (bool value) void set_max_capacity_in_bytes - boost/http_proto/fields_base/set_max_capacity_in_bytes.adoc + boost/http/fields_base/set_max_capacity_in_bytes.adoc (std::size_t n) void set_payload_size - boost/http_proto/message_base/set_payload_size.adoc + boost/http/message_base/set_payload_size.adoc (uint64_t n) + + void + set_status + boost/http/response_base/set_status.adoc + + (http::status sc) + + + void + set_version + boost/http/response_base/set_version.adoc + + (http::version v) + void shrink_to_fit - boost/http_proto/fields_base/shrink_to_fit.adoc + boost/http/fields_base/shrink_to_fit.adoc () std::size_t size - boost/http_proto/fields_base/size.adoc + boost/http/fields_base/size.adoc () - http_proto::status + http::status status - boost/http_proto/response_base/status.adoc + boost/http/response_base/status.adoc () unsigned short status_int - boost/http_proto/response_base/status_int.adoc + boost/http/response_base/status_int.adoc () - http_proto::version + http::version version - boost/http_proto/message_base/version.adoc + boost/http/message_base/version.adoc () std::size_t max_size - boost/http_proto/fields_base/max_size.adoc + boost/http/fields_base/max_size.adoc () - boost::http_proto::string_body - boost/http_proto/string_body.adoc + boost::http::string_body + boost/http/string_body.adoc const_iterator begin - boost/http_proto/string_body/begin.adoc + boost/http/string_body/begin.adoc () const_iterator end - boost/http_proto/string_body/end.adoc + boost/http/string_body/end.adoc () - boost::http_proto::upgrade_protocol - boost/http_proto/upgrade_protocol.adoc + boost::http::suspender + boost/http/suspender.adoc + + suspender& + operator= + boost/http/suspender/operator_assign.adoc + + (const suspender& other) + + + route_result + operator() + boost/http/suspender/operator_call.adoc + + (F&& f) + + + + boost::http::upgrade_protocol + boost/http/upgrade_protocol.adoc - boost::http_proto::condition - boost/http_proto/condition.adoc + boost::http::condition + boost/http/condition.adoc - boost::http_proto::content_coding - boost/http_proto/content_coding.adoc + boost::http::content_coding + boost/http/content_coding.adoc - boost::http_proto::error - boost/http_proto/error.adoc + boost::http::error + boost/http/error.adoc - boost::http_proto::field - boost/http_proto/field.adoc + boost::http::field + boost/http/field.adoc - boost::http_proto::file_mode - boost/http_proto/file_mode.adoc + boost::http::method + boost/http/method.adoc - boost::http_proto::method - boost/http_proto/method.adoc + boost::http::payload + boost/http/payload.adoc - boost::http_proto::payload - boost/http_proto/payload.adoc + boost::http::route + boost/http/route.adoc - boost::http_proto::status - boost/http_proto/status.adoc + boost::http::status + boost/http/status.adoc - boost::http_proto::status_class - boost/http_proto/status_class.adoc + boost::http::status_class + boost/http/status_class.adoc - boost::http_proto::version - boost/http_proto/version.adoc + boost::http::version + boost/http/version.adoc - boost::http_proto::swap - boost/http_proto/swap-0e.adoc + boost::http::swap + boost/http/swap-0b.adoc - boost::http_proto::to_status_class - boost/http_proto/to_status_class-08.adoc + boost::http::to_status_class + boost/http/to_status_class-03.adoc - boost::http_proto::to_string - boost/http_proto/to_string-0078.adoc + boost::http::to_string + boost/http/to_string-082.adoc - boost::http_proto::operator<< - boost/http_proto/operator_lshift-0f.adoc + boost::http::operator<< + boost/http/operator_lshift-0d.adoc - boost::http_proto::parameter_rule - boost/http_proto/parameter_rule.adoc + boost::http::parameter_rule + boost/http/parameter_rule.adoc - boost::http_proto::quoted_token_rule - boost/http_proto/quoted_token_rule.adoc + boost::http::quoted_token_rule + boost/http/quoted_token_rule.adoc - boost::http_proto::tchars - boost/http_proto/tchars.adoc + boost::http::tchars + boost/http/tchars.adoc - boost::http_proto::token_rule - boost/http_proto/token_rule.adoc + boost::http::token_rule + boost/http/token_rule.adoc - boost::http_proto::upgrade_protocol_rule - boost/http_proto/upgrade_protocol_rule.adoc + boost::http::upgrade_protocol_rule + boost/http/upgrade_protocol_rule.adoc - boost::http_proto::upgrade_rule - boost/http_proto/upgrade_rule.adoc + boost::http::upgrade_rule + boost/http/upgrade_rule.adoc diff --git a/example/client/burl/any_stream.hpp b/example/client/burl/any_stream.hpp index 62d348fe..3f247675 100644 --- a/example/client/burl/any_stream.hpp +++ b/example/client/burl/any_stream.hpp @@ -18,19 +18,19 @@ #include #include #include -#include -#include +#include +#include namespace asio = boost::asio; namespace buffers = boost::buffers; -namespace http_proto = boost::http_proto; +namespace http = boost::http; using error_code = boost::system::error_code; class any_stream { public: - using const_buffers_type = http_proto::serializer::const_buffers_type; - using mutable_buffers_type = http_proto::parser::mutable_buffers_type; + using const_buffers_type = http::serializer::const_buffers_type; + using mutable_buffers_type = http::parser::mutable_buffers_type; using executor_type = asio::any_io_executor; template diff --git a/example/client/burl/connect.cpp b/example/client/burl/connect.cpp index 7ed265ba..b9855d17 100644 --- a/example/client/burl/connect.cpp +++ b/example/client/burl/connect.cpp @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include namespace core = boost::core; @@ -166,8 +166,8 @@ connect_http_proxy( // Connect to the proxy server co_await asio::async_connect(stream, rresults); - using field = http_proto::field; - auto request = http_proto::request{}; + using field = http::field; + auto request = http::request{}; auto host_port = [&]() { auto rs = url.encoded_host().decode(); @@ -176,7 +176,7 @@ connect_http_proxy( return rs; }(); - request.set_method(http_proto::method::connect); + request.set_method(http::method::connect); request.set_target(host_port); request.set(field::host, host_port); request.set(field::proxy_connection, "keep-alive"); @@ -190,8 +190,8 @@ connect_http_proxy( request.set(field::proxy_authorization, basic_auth); } - auto serializer = http_proto::serializer{ capy_ctx }; - auto parser = http_proto::response_parser{ capy_ctx }; + auto serializer = http::serializer{ capy_ctx }; + auto parser = http::response_parser{ capy_ctx }; serializer.start(request); co_await beast2::async_write(stream, serializer); @@ -200,7 +200,7 @@ connect_http_proxy( parser.start(); co_await beast2::async_read_header(stream, parser); - if(parser.get().status() != http_proto::status::ok) + if(parser.get().status() != http::status::ok) throw std::runtime_error{ "Proxy server rejected the connection" }; } diff --git a/example/client/burl/main.cpp b/example/client/burl/main.cpp index 54139594..e5efc919 100644 --- a/example/client/burl/main.cpp +++ b/example/client/burl/main.cpp @@ -30,7 +30,7 @@ #include #include #include -#include +#include #include #include #include @@ -59,7 +59,7 @@ constexpr bool capy_has_brotli = false; void set_target( const operation_config& oc, - http_proto::request& request, + http::request& request, const urls::url_view& url) { if(oc.request_target) @@ -78,21 +78,21 @@ struct is_redirect_result }; is_redirect_result -is_redirect(const operation_config& oc, http_proto::status status) noexcept +is_redirect(const operation_config& oc, http::status status) noexcept { // The specifications do not intend for 301 and 302 // redirects to change the HTTP method, but most // user agents do change the method in practice. switch(status) { - case http_proto::status::moved_permanently: + case http::status::moved_permanently: return { true, !oc.post301 }; - case http_proto::status::found: + case http::status::found: return { true, !oc.post302 }; - case http_proto::status::see_other: + case http::status::see_other: return { true, !oc.post303 }; - case http_proto::status::temporary_redirect: - case http_proto::status::permanent_redirect: + case http::status::temporary_redirect: + case http::status::permanent_redirect: return { true, false }; default: return { false, false }; @@ -100,16 +100,16 @@ is_redirect(const operation_config& oc, http_proto::status status) noexcept } bool -is_transient_error(http_proto::status status) noexcept +is_transient_error(http::status status) noexcept { switch(status) { - case http_proto::status::request_timeout: - case http_proto::status::too_many_requests: - case http_proto::status::internal_server_error: - case http_proto::status::bad_gateway: - case http_proto::status::service_unavailable: - case http_proto::status::gateway_timeout: + case http::status::request_timeout: + case http::status::too_many_requests: + case http::status::internal_server_error: + case http::status::bad_gateway: + case http::status::service_unavailable: + case http::status::gateway_timeout: return true; default: return false; @@ -118,14 +118,14 @@ is_transient_error(http_proto::status status) noexcept bool can_reuse_connection( - http_proto::response_base const& response, + http::response_base const& response, const urls::url_view& a, const urls::url_view& b) noexcept { if(a.encoded_origin() != b.encoded_origin()) return false; - if(response.version() != http_proto::version::http_1_1) + if(response.version() != http::version::http_1_1) return false; if(response.metadata().connection.close) @@ -137,28 +137,28 @@ can_reuse_connection( bool should_ignore_body( const operation_config& oc, - http_proto::response_base const& response) noexcept + http::response_base const& response) noexcept { - if(oc.resume_from && !response.count(http_proto::field::content_range)) + if(oc.resume_from && !response.count(http::field::content_range)) return true; return false; } boost::optional -body_size(http_proto::response_base const& response) +body_size(http::response_base const& response) { - if(response.payload() == http_proto::payload::size) + if(response.payload() == http::payload::size) return response.payload_size(); return boost::none; } urls::url redirect_url( - http_proto::response_base const& response, + http::response_base const& response, const urls::url_view& referer) { - auto it = response.find(http_proto::field::location); + auto it = response.find(http::field::location); if(it != response.end()) { auto rs = urls::parse_uri_reference(it->value); @@ -225,21 +225,21 @@ report_progress(progress_meter& pm) } } -http_proto::request +http::request create_request( const operation_config& oc, const message& msg, const urls::url_view& url) { - using field = http_proto::field; - using method = http_proto::method; - using version = http_proto::version; + using field = http::field; + using method = http::method; + using version = http::version; if(oc.disallow_username_in_url && url.has_userinfo()) throw std::runtime_error( "Credentials was passed in the URL when prohibited"); - auto request = http_proto::request{}; + auto request = http::request{}; request.set_method(oc.no_body ? method::head : method::get); @@ -308,7 +308,7 @@ create_request( return request; } -class sink : public http_proto::sink +class sink : public http::sink { progress_meter* pm_; any_ostream* os_; @@ -337,7 +337,7 @@ class sink : public http_proto::sink } }; -asio::awaitable +asio::awaitable perform_request( operation_config oc, boost::optional& header_output, @@ -348,11 +348,11 @@ perform_request( message msg, request_opt request_opt) { - using field = http_proto::field; + using field = http::field; auto executor = co_await asio::this_coro::executor; auto stream = any_stream{ asio::ip::tcp::socket{ executor } }; - auto parser = http_proto::response_parser{ capy_ctx }; - auto serializer = http_proto::serializer{ capy_ctx }; + auto parser = http::response_parser{ capy_ctx }; + auto serializer = http::serializer{ capy_ctx }; urls::url url = [&]() { @@ -440,7 +440,7 @@ perform_request( }(); if(oc.skip_existing && fs::exists(output_path)) - co_return http_proto::status::ok; + co_return http::status::ok; auto output = any_ostream{ output_path, !!oc.resume_from }; auto request = create_request(oc, msg, url); @@ -474,7 +474,7 @@ perform_request( stream.write_limit(oc.sendpersecond.value()); }; - auto stream_headers = [&](http_proto::response_base const& response) + auto stream_headers = [&](http::response_base const& response) { if(oc.show_headers) output << response.buffer(); @@ -518,7 +518,7 @@ perform_request( set_cookies(url, trusted); msg.start_serializer(serializer, request); - if(request.method() == http_proto::method::head) + if(request.method() == http::method::head) parser.start_head_response(); else parser.start(); @@ -562,9 +562,9 @@ perform_request( } // Change the method according to RFC 9110, Section 15.4.4. - if(need_method_change && request.method() != http_proto::method::head) + if(need_method_change && request.method() != http::method::head) { - request.set_method(http_proto::method::get); + request.set_method(http::method::get); request.erase(field::content_length); request.erase(field::content_encoding); request.erase(field::content_type); @@ -618,7 +618,7 @@ perform_request( } if(oc.resume_from && - parser.get().status() != http_proto::status::range_not_satisfiable && + parser.get().status() != http::status::range_not_satisfiable && parser.get().count(field::content_range) == 0) { throw std::runtime_error( @@ -663,7 +663,7 @@ perform_request( asio::awaitable retry( const operation_config& oc, - std::function()> request_task) + std::function()> request_task) { auto executor = co_await asio::this_coro::executor; auto timer = asio::steady_timer{ executor }; @@ -771,7 +771,7 @@ co_main(int argc, char* argv[]) // parser service { - http_proto::response_parser::config cfg; + http::response_parser::config cfg; cfg.body_limit = oc.max_filesize; cfg.min_buffer = 1024 * 1024; if constexpr(capy_has_brotli) @@ -785,14 +785,14 @@ co_main(int argc, char* argv[]) cfg.apply_gzip_decoder = true; capy::zlib::install_inflate_service(capy_ctx); } - http_proto::install_parser_service(capy_ctx, cfg); + http::install_parser_service(capy_ctx, cfg); } // serializer service { - http_proto::serializer::config cfg; + http::serializer::config cfg; cfg.payload_buffer = 1024 * 1024; - http_proto::install_serializer_service(capy_ctx, cfg); + http::install_serializer_service(capy_ctx, cfg); } if(!oc.headerfile.empty()) diff --git a/example/client/burl/message.cpp b/example/client/burl/message.cpp index c0ad53af..1e68abd7 100644 --- a/example/client/burl/message.cpp +++ b/example/client/burl/message.cpp @@ -11,7 +11,7 @@ #include "mime_type.hpp" #include -#include +#include #include #include @@ -26,10 +26,10 @@ string_body::string_body(std::string body, std::string content_type) { } -http_proto::method +http::method string_body::method() const noexcept { - return http_proto::method::post; + return http::method::post; } core::string_view @@ -57,10 +57,10 @@ file_body::file_body(std::string path) { } -http_proto::method +http::method file_body::method() const noexcept { - return http_proto::method::put; + return http::method::put; } core::string_view @@ -75,7 +75,7 @@ file_body::content_length() const return fs::file_size(path_); } -http_proto::file_source +http::file_source file_body::body() const { boost::capy::file file; @@ -84,12 +84,12 @@ file_body::body() const if(ec) throw system_error{ ec }; - return http_proto::file_source{ std::move(file), content_length() }; + return http::file_source{ std::move(file), content_length() }; } // ----------------------------------------------------------------------------- -boost::http_proto::source::results +boost::http::source::results stdin_body::source::on_read(buffers::mutable_buffer mb) { std::cin.read(static_cast(mb.data()), mb.size()); @@ -99,10 +99,10 @@ stdin_body::source::on_read(buffers::mutable_buffer mb) .finished = std::cin.eof() }; } -http_proto::method +http::method stdin_body::method() const noexcept { - return http_proto::method::put; + return http::method::put; } core::string_view @@ -126,12 +126,12 @@ stdin_body::body() const // ----------------------------------------------------------------------------- void -message::set_headers(http_proto::request& request) const +message::set_headers(http::request& request) const { std::visit( [&](auto& f) { - using field = http_proto::field; + using field = http::field; if constexpr(!std::is_same_v) { request.set_method(f.method()); @@ -143,7 +143,7 @@ message::set_headers(http_proto::request& request) const { request.set_content_length(content_length.value()); if(content_length.value() >= 1024 * 1024 && - request.version() == http_proto::version::http_1_1) + request.version() == http::version::http_1_1) request.set(field::expect, "100-continue"); } else @@ -158,8 +158,8 @@ message::set_headers(http_proto::request& request) const void message::start_serializer( - http_proto::serializer& serializer, - http_proto::request& request) const + http::serializer& serializer, + http::request& request) const { std::visit( [&](auto& f) diff --git a/example/client/burl/message.hpp b/example/client/burl/message.hpp index 8b6725ff..bbd3052e 100644 --- a/example/client/burl/message.hpp +++ b/example/client/burl/message.hpp @@ -12,9 +12,9 @@ #include "multipart_form.hpp" -#include -#include -#include +#include +#include +#include #include @@ -28,7 +28,7 @@ class string_body public: string_body(std::string body, std::string content_type); - http_proto::method + http::method method() const noexcept; core::string_view @@ -48,7 +48,7 @@ class file_body public: file_body(std::string path); - http_proto::method + http::method method() const noexcept; core::string_view @@ -57,21 +57,21 @@ class file_body std::uint64_t content_length() const; - http_proto::file_source + http::file_source body() const; }; class stdin_body { public: - class source : public http_proto::source + class source : public http::source { public: results on_read(buffers::mutable_buffer mb) override; }; - http_proto::method + http::method method() const noexcept; core::string_view @@ -106,12 +106,12 @@ class message } void - set_headers(http_proto::request& request) const; + set_headers(http::request& request) const; void start_serializer( - http_proto::serializer& serializer, - http_proto::request& request) const; + http::serializer& serializer, + http::request& request) const; }; #endif diff --git a/example/client/burl/multipart_form.cpp b/example/client/burl/multipart_form.cpp index 61fff06b..529f70c3 100644 --- a/example/client/burl/multipart_form.cpp +++ b/example/client/burl/multipart_form.cpp @@ -84,10 +84,10 @@ multipart_form::append( serialize_headers(headers) }); } -http_proto::method +http::method multipart_form::method() const noexcept { - return http_proto::method::post; + return http::method::post; } std::string diff --git a/example/client/burl/multipart_form.hpp b/example/client/burl/multipart_form.hpp index 0bce3932..2ae80643 100644 --- a/example/client/burl/multipart_form.hpp +++ b/example/client/burl/multipart_form.hpp @@ -12,8 +12,8 @@ #include #include -#include -#include +#include +#include #include #include @@ -21,7 +21,7 @@ #include namespace buffers = boost::buffers; -namespace http_proto = boost::http_proto; +namespace http = boost::http; using error_code = boost::system::error_code; class multipart_form @@ -55,7 +55,7 @@ class multipart_form boost::optional content_type = {}, std::vector headers = {}); - http_proto::method + http::method method() const noexcept; std::string @@ -68,7 +68,7 @@ class multipart_form body() const; }; -class multipart_form::source : public http_proto::source +class multipart_form::source : public http::source { const multipart_form* form_; std::vector::const_iterator it_{ form_->pacapy_.begin() }; diff --git a/example/client/burl/options.hpp b/example/client/burl/options.hpp index 9aed639c..feb287a6 100644 --- a/example/client/burl/options.hpp +++ b/example/client/burl/options.hpp @@ -13,7 +13,7 @@ #include "message.hpp" #include -#include +#include #include #include @@ -26,7 +26,7 @@ namespace asio = boost::asio; namespace ch = std::chrono; namespace fs = std::filesystem; -namespace http_proto = boost::http_proto; +namespace http = boost::http; namespace urls = boost::urls; struct operation_config @@ -80,7 +80,7 @@ struct operation_config bool rm_partial = false; bool use_httpget = false; boost::optional request_target; - http_proto::fields headers; + http::fields headers; std::vector omitheaders; bool show_headers = false; bool cookiesession = false; diff --git a/example/client/burl/request.hpp b/example/client/burl/request.hpp index fb54aa6c..2665d651 100644 --- a/example/client/burl/request.hpp +++ b/example/client/burl/request.hpp @@ -16,8 +16,8 @@ #include #include #include -#include -#include +#include +#include #include #include @@ -25,15 +25,15 @@ namespace asio = boost::asio; namespace ch = std::chrono; namespace beast2 = boost::beast2; -namespace http_proto = boost::http_proto; +namespace http = boost::http; using error_code = boost::system::error_code; template class async_request_op { AsyncReadStream& stream_; - http_proto::serializer& serializer_; - http_proto::response_parser& parser_; + http::serializer& serializer_; + http::response_parser& parser_; ch::steady_clock::duration exp100_timeout_; asio::coroutine c; @@ -52,8 +52,8 @@ class async_request_op public: async_request_op( AsyncReadStream& stream, - http_proto::serializer& serializer, - http_proto::response_parser& parser, + http::serializer& serializer, + http::response_parser& parser, ch::steady_clock::duration exp100_timeout) : stream_{ stream } , serializer_{ serializer } @@ -83,7 +83,7 @@ class async_request_op return self.complete(ec); } - if(ec != http_proto::error::expect_100_continue) + if(ec != http::error::expect_100_continue) return self.complete(ec); // TODO: use associated allocator @@ -115,7 +115,7 @@ class async_request_op exp100->timer.cancel(); if(ec || parser.get().status() != - http_proto::status::continue_) + http::status::continue_) { exp100->state = exp100::cancelled; } @@ -156,8 +156,8 @@ template< auto async_request( AsyncReadStream& stream, - http_proto::serializer& serializer, - http_proto::response_parser& parser, + http::serializer& serializer, + http::response_parser& parser, ch::steady_clock::duration expect100_timeout, CompletionToken&& token = CompletionToken{}) { diff --git a/example/client/get/main.cpp b/example/client/get/main.cpp index 4c2815e1..dcb84ff7 100644 --- a/example/client/get/main.cpp +++ b/example/client/get/main.cpp @@ -14,9 +14,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include #include #include #include @@ -37,9 +37,9 @@ class session asio::ssl::context& ssl_ctx_; asio::ssl::stream stream_; asio::ip::tcp::resolver resolver_; - http_proto::serializer sr_; - http_proto::response_parser pr_; - http_proto::request req_; + http::serializer sr_; + http::response_parser pr_; + http::request req_; urls::url url_; std::string host_; std::string port_; @@ -62,7 +62,7 @@ class session void run(urls::url_view url) { - using field = http_proto::field; + using field = http::field; // Set up an HTTP GET request if(!url.encoded_target().empty()) @@ -251,9 +251,9 @@ class session void on_redirect_response( - http_proto::response_base const& response) + http::response_base const& response) { - using field = http_proto::field; + using field = http::field; if(max_redirects_ == 0) return fail("Maximum redirects followed"); @@ -367,9 +367,9 @@ class session static bool - is_redirect(http_proto::status s) noexcept + is_redirect(http::status s) noexcept { - using status = http_proto::status; + using status = http::status; switch(s) { case status::moved_permanently: @@ -390,14 +390,14 @@ class session static bool can_reuse_connection( - http_proto::response_base const& response, + http::response_base const& response, urls::url_view a, urls::url_view b) noexcept { if(a.encoded_origin() != b.encoded_origin()) return false; - if(response.version() != http_proto::version::http_1_1) + if(response.version() != http::version::http_1_1) return false; if(response.metadata().connection.close) @@ -407,7 +407,7 @@ class session } // Writes body to standard out - struct stdout_sink : http_proto::sink + struct stdout_sink : http::sink { results on_write(buffers::const_buffer cb, bool) override @@ -446,7 +446,7 @@ main(int argc, char* argv[]) // Install parser service { - http_proto::response_parser::config cfg; + http::response_parser::config cfg; cfg.body_limit = std::uint64_t(-1); cfg.min_buffer = 64 * 1024; #ifdef BOOST_CAPY_HAS_BROTLI @@ -458,11 +458,11 @@ main(int argc, char* argv[]) cfg.apply_gzip_decoder = true; capy::zlib::install_inflate_service(capy_ctx); #endif - http_proto::install_parser_service(capy_ctx, cfg); + http::install_parser_service(capy_ctx, cfg); } // Install serializer service with default configuration - http_proto::install_serializer_service(capy_ctx, {}); + http::install_serializer_service(capy_ctx, {}); // Root certificates used for verification ssl_ctx.set_default_verify_paths(); diff --git a/example/client/jsonrpc/jsonrpc/client.cpp b/example/client/jsonrpc/jsonrpc/client.cpp index 86b658d7..503c3a20 100644 --- a/example/client/jsonrpc/jsonrpc/client.cpp +++ b/example/client/jsonrpc/jsonrpc/client.cpp @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include #include #include @@ -223,7 +223,7 @@ class stream_impl : public any_stream }; }; -class json_sink : public http_proto::sink +class json_sink : public http::sink { json::stream_parser& jpr_; @@ -250,7 +250,7 @@ class json_sink : public http_proto::sink } }; -class json_source : public http_proto::source +class json_source : public http::source { json::serializer& jsr_; json::value v_; @@ -404,9 +404,9 @@ client( , endpoint_(std::move(endpoint)) , sr_(capy_ctx) , pr_(capy_ctx) - , req_(http_proto::method::post, "/") + , req_(http::method::post, "/") { - using field = http_proto::field; + using field = http::field; if(!endpoint_.encoded_target().empty()) req_.set_target(endpoint_.encoded_target()); diff --git a/example/client/jsonrpc/jsonrpc/client.hpp b/example/client/jsonrpc/jsonrpc/client.hpp index 49b09512..4265cfd4 100644 --- a/example/client/jsonrpc/jsonrpc/client.hpp +++ b/example/client/jsonrpc/jsonrpc/client.hpp @@ -19,9 +19,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include #include #include #include @@ -35,9 +35,9 @@ class client { std::unique_ptr stream_; boost::urls::url endpoint_; - boost::http_proto::serializer sr_; - boost::http_proto::response_parser pr_; - boost::http_proto::request req_; + boost::http::serializer sr_; + boost::http::response_parser pr_; + boost::http::request req_; boost::json::stream_parser jpr_; boost::json::serializer jsr_; std::uint64_t id_ = 0; @@ -104,7 +104,7 @@ class client This function can be used to customize HTTP headers, for example, to add the required credentials. */ - boost::http_proto::fields_base& + boost::http::fields_base& http_fields() { return req_; diff --git a/example/client/jsonrpc/main.cpp b/example/client/jsonrpc/main.cpp index b4179b68..0b7c9f54 100644 --- a/example/client/jsonrpc/main.cpp +++ b/example/client/jsonrpc/main.cpp @@ -151,7 +151,7 @@ main(int, char*[]) // Install parser service { - http_proto::response_parser::config cfg; + http::response_parser::config cfg; cfg.min_buffer = 64 * 1024; #ifdef BOOST_CAPY_HAS_BROTLI cfg.apply_brotli_decoder = true; @@ -162,11 +162,11 @@ main(int, char*[]) cfg.apply_gzip_decoder = true; capy::zlib::install_inflate_service(capy_ctx); #endif - http_proto::install_parser_service(capy_ctx, cfg); + http::install_parser_service(capy_ctx, cfg); } // Install serializer service with default configuration - http_proto::install_serializer_service(capy_ctx, {}); + http::install_serializer_service(capy_ctx, {}); // Root certificates used for verification ssl_ctx.set_default_verify_paths(); diff --git a/example/client/visit/main.cpp b/example/client/visit/main.cpp index 3796a63c..c5f3c15e 100644 --- a/example/client/visit/main.cpp +++ b/example/client/visit/main.cpp @@ -14,8 +14,8 @@ #include #include #include -#include -#include +#include +#include #include #include @@ -196,7 +196,7 @@ struct worker boost::asio::ip::tcp, executor_type>; socket_type sock; - boost::http_proto::response_parser pr; + boost::http::response_parser pr; boost::urls::url_view url; explicit @@ -246,12 +246,12 @@ struct worker void do_request() { - boost::http_proto::request req; + boost::http::request req; auto path = url.encoded_path(); req.set_start_line( - boost::http_proto::method::get, + boost::http::method::get, path.empty() ? "/" : path, - boost::http_proto::version::http_1_1); + boost::http::version::http_1_1); do_shutdown(); } @@ -277,8 +277,8 @@ main(int argc, char* argv[]) (void)argv; boost::capy::polystore ctx; - boost::http_proto::parser::config_base cfg; - boost::http_proto::install_parser_service(ctx, cfg); + boost::http::parser::config_base cfg; + boost::http::install_parser_service(ctx, cfg); boost::asio::io_context ioc; diff --git a/example/server/Jamfile b/example/server/Jamfile index 180f3d45..960d0c9b 100644 --- a/example/server/Jamfile +++ b/example/server/Jamfile @@ -5,7 +5,7 @@ # 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/http_proto +# Official repository: https://github.com/CPPAlliance/http # using openssl ; diff --git a/example/server/main.cpp b/example/server/main.cpp index 10bc19bd..02905dbb 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -17,9 +17,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include #include #include #include @@ -45,7 +45,7 @@ void install_services(capy::application& app) capy::zlib::install_inflate_service(app); #endif - // VFALCO These ugly incantations are needed for http_proto and will hopefully go away soon. + // 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, diff --git a/example/server/serve_detached.hpp b/example/server/serve_detached.hpp index e9a9337d..a9d8cc9f 100644 --- a/example/server/serve_detached.hpp +++ b/example/server/serve_detached.hpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include #include @@ -39,19 +39,19 @@ class serve_detached system::error_code operator()( - http_proto::route_params& rp) const + http::route_params& rp) const { return rp.suspend( - [&](http_proto::resumer resume) + [&](http::resumer resume) { asio::post(*tp_, [&, resume]() { // Simulate some asynchronous work std::this_thread::sleep_for(std::chrono::seconds(1)); - rp.status(http_proto::status::ok); + rp.status(http::status::ok); rp.set_body("Hello from serve_detached!\n"); - resume(http_proto::route::send); + resume(http::route::send); // resume( res.send("Hello from serve_detached!\n") ); }); }); diff --git a/example/server/serve_log_admin.cpp b/example/server/serve_log_admin.cpp index 32a15f55..b313ee9d 100644 --- a/example/server/serve_log_admin.cpp +++ b/example/server/serve_log_admin.cpp @@ -30,7 +30,7 @@ class serve_log_page system::error_code operator()( - http_proto::route_params& p) const + http::route_params& p) const { auto const v = ls_.get_sections(); std::string s; @@ -71,10 +71,10 @@ class serve_log_page format_to(s, "\n"); format_to(s, "\n"); - p.status(http_proto::status::ok); - p.res.set(http_proto::field::content_type, "text/html; charset=UTF-8"); + p.status(http::status::ok); + p.res.set(http::field::content_type, "text/html; charset=UTF-8"); p.set_body(std::move(s)); - return http_proto::route::send; + return http::route::send; } private: @@ -96,12 +96,12 @@ class handle_submit system::error_code operator()( - http_proto::route_params& p) const + http::route_params& p) const { - p.status(http_proto::status::ok); - p.res.set(http_proto::field::content_type, "plain/text; charset=UTF-8"); + p.status(http::status::ok); + p.res.set(http::field::content_type, "plain/text; charset=UTF-8"); p.set_body("submit"); - return http_proto::route::send; + return http::route::send; } private: @@ -117,8 +117,8 @@ serve_log_admin( capy::polystore& ps) { router r; - r.add(http_proto::method::get, "/", serve_log_page(ps)); - r.add(http_proto::method::get, "/submit", handle_submit(ps)); + r.add(http::method::get, "/", serve_log_page(ps)); + r.add(http::method::get, "/submit", handle_submit(ps)); return r; } diff --git a/include/boost/beast2/body_read_stream.hpp b/include/boost/beast2/body_read_stream.hpp index cc2fa404..3ec089a1 100644 --- a/include/boost/beast2/body_read_stream.hpp +++ b/include/boost/beast2/body_read_stream.hpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include namespace boost { diff --git a/include/boost/beast2/detail/config.hpp b/include/boost/beast2/detail/config.hpp index 9e00d20d..e671fc9c 100644 --- a/include/boost/beast2/detail/config.hpp +++ b/include/boost/beast2/detail/config.hpp @@ -64,9 +64,9 @@ namespace beast2 { } // beast2 -namespace http_proto {} +namespace http {} namespace beast2 { -namespace http = http_proto; +namespace http = http; } } // boost diff --git a/include/boost/beast2/impl/read.hpp b/include/boost/beast2/impl/read.hpp index 483030a1..d7c72d9d 100644 --- a/include/boost/beast2/impl/read.hpp +++ b/include/boost/beast2/impl/read.hpp @@ -12,8 +12,8 @@ #define BOOST_BEAST2_IMPL_READ_HPP #include -#include -#include +#include +#include #include #include #include diff --git a/include/boost/beast2/impl/write.hpp b/include/boost/beast2/impl/write.hpp index 8da9b051..5f3ada1d 100644 --- a/include/boost/beast2/impl/write.hpp +++ b/include/boost/beast2/impl/write.hpp @@ -17,7 +17,7 @@ #include #include #include -#include +#include namespace boost { namespace beast2 { diff --git a/include/boost/beast2/read.hpp b/include/boost/beast2/read.hpp index c328127d..29b2b681 100644 --- a/include/boost/beast2/read.hpp +++ b/include/boost/beast2/read.hpp @@ -12,8 +12,8 @@ #define BOOST_BEAST2_READ_HPP #include -#include -#include +#include +#include #include #include #include diff --git a/include/boost/beast2/server/body_source.hpp b/include/boost/beast2/server/body_source.hpp index 8a19ee9b..8b0296c7 100644 --- a/include/boost/beast2/server/body_source.hpp +++ b/include/boost/beast2/server/body_source.hpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/include/boost/beast2/server/http_stream.hpp b/include/boost/beast2/server/http_stream.hpp index 265ff469..e2dcb2db 100644 --- a/include/boost/beast2/server/http_stream.hpp +++ b/include/boost/beast2/server/http_stream.hpp @@ -22,11 +22,11 @@ #include #include #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include diff --git a/include/boost/beast2/server/route_handler_asio.hpp b/include/boost/beast2/server/route_handler_asio.hpp index d93cf4d2..ac221099 100644 --- a/include/boost/beast2/server/route_handler_asio.hpp +++ b/include/boost/beast2/server/route_handler_asio.hpp @@ -11,7 +11,7 @@ #define BOOST_BEAST2_SERVER_ROUTE_HANDLER_ASIO_HPP #include -#include +#include #include namespace boost { diff --git a/include/boost/beast2/server/router.hpp b/include/boost/beast2/server/router.hpp index aca812c5..4f80d204 100644 --- a/include/boost/beast2/server/router.hpp +++ b/include/boost/beast2/server/router.hpp @@ -11,8 +11,8 @@ #define BOOST_BEAST2_SERVER_ROUTER_HPP #include -#include -#include +#include +#include namespace boost { namespace beast2 { diff --git a/include/boost/beast2/server/router_asio.hpp b/include/boost/beast2/server/router_asio.hpp index dc466c0a..481e7b5d 100644 --- a/include/boost/beast2/server/router_asio.hpp +++ b/include/boost/beast2/server/router_asio.hpp @@ -11,7 +11,7 @@ #define BOOST_BEAST2_SERVER_ROUTER_ASIO_HPP #include -#include +#include #include namespace boost { diff --git a/include/boost/beast2/server/serve_redirect.hpp b/include/boost/beast2/server/serve_redirect.hpp index 49452295..e2189dd7 100644 --- a/include/boost/beast2/server/serve_redirect.hpp +++ b/include/boost/beast2/server/serve_redirect.hpp @@ -11,7 +11,7 @@ #define BOOST_BEAST2_SERVER_SERVE_REDIRECT_HPP #include -#include +#include namespace boost { namespace beast2 { diff --git a/include/boost/beast2/server/serve_static.hpp b/include/boost/beast2/server/serve_static.hpp index 0975ea3b..abe09da5 100644 --- a/include/boost/beast2/server/serve_static.hpp +++ b/include/boost/beast2/server/serve_static.hpp @@ -11,7 +11,7 @@ #define BOOST_BEAST2_SERVER_SERVE_STATIC_HPP #include -#include +#include namespace boost { namespace beast2 { diff --git a/include/boost/beast2/write.hpp b/include/boost/beast2/write.hpp index d71919dd..31e3b02e 100644 --- a/include/boost/beast2/write.hpp +++ b/include/boost/beast2/write.hpp @@ -11,7 +11,7 @@ #define BOOST_BEAST2_WRITE_HPP #include -#include +#include #include #include diff --git a/src/server/serve_redirect.cpp b/src/server/serve_redirect.cpp index 3dd58f34..8ae11a62 100644 --- a/src/server/serve_redirect.cpp +++ b/src/server/serve_redirect.cpp @@ -8,9 +8,9 @@ // #include -#include -#include -#include +#include +#include +#include #include #include #include @@ -26,7 +26,7 @@ namespace beast2 { //------------------------------------------------ -/// Returns the current system time formatted as an HTTP-date per RFC 9110 §5.6.7. +/// Returns the current system time formatted as an HTTP-date per RFC 9110 �5.6.7. /// Example: "Sat, 11 Oct 2025 02:12:34 GMT" static std::string diff --git a/src/server/serve_static.cpp b/src/server/serve_static.cpp index 0430f323..e5767772 100644 --- a/src/server/serve_static.cpp +++ b/src/server/serve_static.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include #include #include diff --git a/test/unit/body_read_stream.cpp b/test/unit/body_read_stream.cpp index 773b84b7..dc984ae6 100644 --- a/test/unit/body_read_stream.cpp +++ b/test/unit/body_read_stream.cpp @@ -109,7 +109,7 @@ struct ctx_base ctx_base() { - http_proto::install_parser_service(capy_ctx_, {}); + http::install_parser_service(capy_ctx_, {}); } }; @@ -131,7 +131,7 @@ struct single_tester : public ctx_base boost::asio::io_context ioc_; test::stream ts_; - http_proto::response_parser pr_; + http::response_parser pr_; // Create a destination buffer std::string s_; @@ -353,7 +353,7 @@ struct single_tester : public ctx_base // Ensure we get an error by making the body limit too small pr_.set_body_limit(2); - async_read_some(1024, http_proto::error::body_too_large, 0); + async_read_some(1024, http::error::body_too_large, 0); test::run(ioc_); } diff --git a/test/unit/read.cpp b/test/unit/read.cpp index 2771b6cb..632db1db 100644 --- a/test/unit/read.cpp +++ b/test/unit/read.cpp @@ -37,13 +37,13 @@ class read_test { boost::asio::io_context ioc; boost::capy::polystore capy_ctx; - http_proto::install_parser_service(capy_ctx, {}); + http::install_parser_service(capy_ctx, {}); // async_read_some completes when the parser reads // the header section of the message. { test::stream ts(ioc, msg); - http_proto::response_parser pr(capy_ctx); + http::response_parser pr(capy_ctx); pr.reset(); pr.start(); @@ -84,7 +84,7 @@ class read_test { test::fail_count fc(11, asio::error::network_down); test::stream ts(ioc, fc, msg); - http_proto::response_parser pr(capy_ctx); + http::response_parser pr(capy_ctx); pr.reset(); pr.start(); @@ -108,7 +108,7 @@ class read_test // async_read_some reports parser errors { test::stream ts(ioc, msg); - http_proto::response_parser pr(capy_ctx); + http::response_parser pr(capy_ctx); pr.reset(); pr.start(); @@ -121,7 +121,7 @@ class read_test async_read_some( ts, pr, - test::fail_handler(http_proto::error::body_too_large)); + test::fail_handler(http::error::body_too_large)); test::run(ioc); } @@ -129,7 +129,7 @@ class read_test { test::stream ts(ioc); asio::cancellation_signal c_signal; - http_proto::response_parser pr(capy_ctx); + http::response_parser pr(capy_ctx); pr.reset(); pr.start(); @@ -186,13 +186,13 @@ class read_test { boost::asio::io_context ioc; capy::polystore capy_ctx; - http_proto::install_parser_service(capy_ctx, {}); + http::install_parser_service(capy_ctx, {}); // async_read completes when the parser reads // the entire message. { test::stream ts(ioc, msg); - http_proto::response_parser pr(capy_ctx); + http::response_parser pr(capy_ctx); pr.reset(); pr.start(); @@ -223,7 +223,7 @@ class read_test [&]() { test::stream ts(ioc); - http_proto::response_parser pr(capy_ctx); + http::response_parser pr(capy_ctx); pr.reset(); pr.start(); diff --git a/test/unit/sandbox.cpp b/test/unit/sandbox.cpp index 85acf00b..c6b35476 100644 --- a/test/unit/sandbox.cpp +++ b/test/unit/sandbox.cpp @@ -14,14 +14,14 @@ #include #include #include -#include +#include #include #include #include #include namespace boost { -namespace http_proto { +namespace http { static std::size_t constexpr buffer_bytes = 8 * 1024 * 1024; static std::size_t constexpr payload_size = buffer_bytes * 10; @@ -223,7 +223,7 @@ TEST_SUITE( sandbox_test, "boost.beast2.sandbox"); -} // http_proto +} // http } // boost #endif diff --git a/test/unit/server/body_source.cpp b/test/unit/server/body_source.cpp index 5fad4d15..235c5bef 100644 --- a/test/unit/server/body_source.cpp +++ b/test/unit/server/body_source.cpp @@ -79,7 +79,7 @@ struct read_source if(ec_.failed()) ec = ec_; else - ec = http_proto::error::end_of_stream; + ec = http::error::end_of_stream; } else { @@ -133,7 +133,7 @@ struct body_source_test { auto nread = b.read(buf, n, ec); s.append(buf, nread); - if(ec == http_proto::error::end_of_stream) + if(ec == http::error::end_of_stream) { BOOST_TEST(! fec.failed()); BOOST_TEST_EQ(s, s0); @@ -238,7 +238,7 @@ struct body_source_test { core::string_view s1("Hello, world!"); system::error_code fec = - http_proto::error::bad_connection; + http::error::bad_connection; body_source b1((read_source(s1, fec))); BOOST_TEST_EQ(b1.has_size(), false); diff --git a/test/unit/server/route_handler_asio.cpp b/test/unit/server/route_handler_asio.cpp index 1f4c21d6..5453f09e 100644 --- a/test/unit/server/route_handler_asio.cpp +++ b/test/unit/server/route_handler_asio.cpp @@ -10,7 +10,7 @@ // Test that header file is self-contained. #include -#include +#include #include #include @@ -30,17 +30,17 @@ struct route_handler_asio_test } }; - using test_router = http_proto::basic_router< + using test_router = http::basic_router< asio_route_params>; void check( test_router& r, core::string_view url, - http_proto::route_result rv0 = http_proto::route::send) + http::route_result rv0 = http::route::send) { asio_route_params req; auto rv = r.dispatch( - http_proto::method::get, + http::method::get, urls::url_view(url), req); if(BOOST_TEST_EQ(rv.message(), rv0.message())) BOOST_TEST(rv == rv0); @@ -49,12 +49,12 @@ struct route_handler_asio_test struct handler { template - http_proto::route_result + http::route_result operator()( asio_route_params&) const { BOOST_TEST(true); - return http_proto::route::send; + return http::route::send; } }; diff --git a/test/unit/write.cpp b/test/unit/write.cpp index 543fd0a4..88b64c52 100644 --- a/test/unit/write.cpp +++ b/test/unit/write.cpp @@ -64,7 +64,7 @@ namespace beast2 { #include #include #include -#include +#include #include "test_helpers.hpp" @@ -93,7 +93,7 @@ class write_test { boost::asio::io_context ioc; boost::capy::polystore capy_ctx; - http_proto::install_serializer_service(capy_ctx, {}); + http::install_serializer_service(capy_ctx, {}); // async_write_some completes when the serializer writes the message. { @@ -101,10 +101,10 @@ class write_test ts.connect(tr); ts.write_size(1); - http_proto::serializer sr(capy_ctx); + http::serializer sr(capy_ctx); sr.reset(); - http_proto::response res(headers); + http::response res(headers); sr.start(res, buffers::const_buffer(body.data(), body.size())); for(std::size_t total = 0; total < msg.size(); total++) @@ -132,10 +132,10 @@ class write_test ts.connect(tr); ts.write_size(1); - http_proto::serializer sr(capy_ctx); + http::serializer sr(capy_ctx); sr.reset(); - http_proto::response res(headers); + http::response res(headers); sr.start(res, buffers::const_buffer(body.data(), body.size())); for(int count = 0; count < 3; count++) @@ -178,10 +178,10 @@ class write_test asio::cancellation_signal c_signal; - http_proto::serializer sr(capy_ctx); + http::serializer sr(capy_ctx); sr.reset(); - http_proto::response res(headers); + http::response res(headers); sr.start(res, buffers::const_buffer(body.data(), body.size())); // async_read_some cancels after reading 0 bytes @@ -209,7 +209,7 @@ class write_test { boost::asio::io_context ioc; capy::polystore capy_ctx; - http_proto::install_serializer_service(capy_ctx, {}); + http::install_serializer_service(capy_ctx, {}); // async_write completes when the serializer writes // the entire message. @@ -218,10 +218,10 @@ class write_test ts.connect(tr); ts.write_size(1); - http_proto::serializer sr(capy_ctx); + http::serializer sr(capy_ctx); sr.reset(); - http_proto::response res(headers); + http::response res(headers); sr.start(res, buffers::const_buffer(body.data(), body.size())); async_write( @@ -247,10 +247,10 @@ class write_test ts.connect(tr); ts.write_size(1); - http_proto::serializer sr(capy_ctx); + http::serializer sr(capy_ctx); sr.reset(); - http_proto::response res(headers); + http::response res(headers); sr.start(res, buffers::const_buffer(body.data(), body.size())); async_write( @@ -283,10 +283,10 @@ class write_test asio::cancellation_signal c_signal; - http_proto::serializer sr(capy_ctx); + http::serializer sr(capy_ctx); sr.reset(); - http_proto::response res(headers); + http::response res(headers); sr.start(res, buffers::const_buffer(body.data(), body.size())); // cancel after writing From 6d8376895bd77617abe892b38d573e01fcc9a89f Mon Sep 17 00:00:00 2001 From: Mungo Gill Date: Thu, 11 Dec 2025 15:58:57 +0000 Subject: [PATCH 28/40] Create body_write_stream --- include/boost/beast2/body_write_stream.hpp | 267 ++++++ .../boost/beast2/impl/body_write_stream.hpp | 236 +++++ test/unit/body_read_stream.cpp | 4 + test/unit/body_write_stream.cpp | 827 ++++++++++++++++++ 4 files changed, 1334 insertions(+) create mode 100644 include/boost/beast2/body_write_stream.hpp create mode 100644 include/boost/beast2/impl/body_write_stream.hpp create mode 100644 test/unit/body_write_stream.cpp diff --git a/include/boost/beast2/body_write_stream.hpp b/include/boost/beast2/body_write_stream.hpp new file mode 100644 index 00000000..8def1dde --- /dev/null +++ b/include/boost/beast2/body_write_stream.hpp @@ -0,0 +1,267 @@ +// +// Copyright (c) 2025 Mungo Gill +// +// 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_BODY_WRITE_STREAM_HPP +#define BOOST_BEAST2_BODY_WRITE_STREAM_HPP + +#include +#include +#include + +#include + +namespace boost { +namespace beast2 { + +namespace detail { + +template +class body_write_stream_op; + +template +class body_write_stream_close_op; + +} // detail + +/** A body writer for HTTP/1 messages. + + This type is modelled on asio's AsyncWriteStream, and is constructed with a + reference to an underlying AsyncWriteStream. + + Any call to `async_write_some` initially triggers writes to the underlying + stream until all of the HTTP headers and at least one byte of the body have + been written and processed. Thereafter, each subsequent call to + `async_write_some` processes at least one byte of the body, triggering, where + required, calls to the underlying stream's `async_write_some` method. The + body data is read from the referenced ConstBufferSequence. + + All processing depends on a beast2::serializer object owned by the caller and + referenced in the construction of this object. + + @par Deviations from AsyncWriteStream + + This type deviates from the strict AsyncWriteStream requirements in the + following ways: + + @li Deferred error reporting: If an error or cancellation occurs + after data has been successfully committed to the serializer, the + operation completes with success and reports the number of bytes + consumed. The error is saved and reported on the next call to + `async_write_some`. This differs from the AsyncWriteStream requirement + that on error, `bytes_transferred` must be 0. This behaviour ensures + that the caller knows exactly how many bytes were consumed by the + serializer, preventing data loss or duplication. + + @see + @ref http::serializer. +*/ +template +class body_write_stream +{ + +public: + /** The type of the executor associated with the stream. + + This will be the type of executor used to invoke completion handlers + which do not have an explicit associated executor. + */ + using executor_type = + decltype(std::declval().get_executor()); + + /** Return the executor associated with the object. + + This function may be used to obtain the executor object that the stream + uses to dispatch completion handlers without an associated executor. + + @return A copy of the executor that stream will use to dispatch + handlers. + */ + executor_type + get_executor() + { + return stream_.get_executor(); + } + + /** Constructor + + This constructor creates the stream which retains a reference to the + underlying stream. The underlying stream then needs to be open before + data can be written + + @param s The underlying stream to which the HTTP message is written. + This object's executor is initialized to that of the + underlying stream. + + @param sr A http::serializer object which will perform the serialization of + the HTTP message and extraction of the body. This must be + initialized by the caller and ownership of the serializer is + retained by the caller, which must guarantee that it remains + valid until the handler is called. + + @param srs A http::serializer::stream object which must have been + obtained by a call to `start_stream` on `sr`. Ownership of + the serializer::stream is transferred to this object. + */ + explicit body_write_stream( + AsyncWriteStream& s, + http::serializer& sr, + http::serializer::stream srs); + + /** Write some data asynchronously. + + This function is used to asynchronously write data to the stream. + + This call always returns immediately. The asynchronous operation will + continue until one of the following conditions is true: + + @li One or more bytes are written from `cb` to the body stored in the + serializer and one or more bytes are written from the serializer to the + underlying stream. + + @li An error occurs. + + The algorithm, known as a composed asynchronous operation, is + implemented in terms of calls to the underlying stream's + `async_write_some` function. The program must ensure that no other calls + implemented using the underlying stream's `async_write_some` are + performed until this operation completes. + + @param cb The buffer sequence from which the body data will be read. If + the size of the buffer sequence is zero bytes, the operation always + completes immediately with no error. Although the buffers object may be + copied as necessary, ownership of the underlying memory blocks is + retained by the caller, which must guarantee that they remain valid until + the handler is called. Where the internal buffer of the contained + serializer is not of sufficient size to hold the data to be copied from + cb, the remainder may be written by subsequent calls to this function. + + @param handler The completion handler to invoke when the operation + completes. The implementation takes ownership of the handler by + performing a decay-copy. The equivalent function signature of the + handler must be: + @code + void handler( + error_code const& error, // result of operation + std::size_t bytes_transferred // the number of bytes consumed from + // cb by the serializer + ); + @endcode + Regardless of whether the asynchronous operation + completes immediately or not, the completion handler will not be invoked + from within this function. On immediate completion, invocation of the + handler will be performed in a manner equivalent to using + `asio::async_immediate`. + + @note The `async_write_some` operation may not transmit all of the + requested number of bytes. Consider using the function + `asio::async_write` if you need to ensure that the requested amount of + data is written before the asynchronous operation completes. + + @note This function does not guarantee that all of the consumed data is + written to the underlying stream. For this reason one or more calls to + `async_write_some` must be followed by a call to `async_close` to put the + serializer into the `done` state and to write all data remaining in the + serializer to the underlying stream. + + @par Per-Operation Cancellation + + This asynchronous operation supports cancellation for the following + net::cancellation_type values: + + @li @c net::cancellation_type::terminal + @li @c net::cancellation_type::partial + @li @c net::cancellation_type::total + + if they are also supported by the underlying stream's @c async_write_some + operation. + */ + template< + class ConstBufferSequence, + BOOST_ASIO_COMPLETION_TOKEN_FOR(void(system::error_code, std::size_t)) + CompletionToken> + BOOST_ASIO_INITFN_AUTO_RESULT_TYPE( + CompletionToken, + void(system::error_code, std::size_t)) + async_write_some(ConstBufferSequence const& cb, CompletionToken&& handler); + + /** Close serializer::stream and flush remaining data to the underlying stream asynchronously. + + This function is used to asynchronously call `close` on the + `serializer::stream` object referenced in the construction of this + `body_write_stream` and write all remaining data in the serializer to the + underlying stream. + + This call always returns immediately. The asynchronous operation will + continue until one of the following conditions is true: + + @li All remaining output bytes of the serializer are written to the + underlying stream and the serializer's `is_done()` method returns true. + + @li An error occurs. + + The algorithm, known as a composed asynchronous operation, is + implemented in terms of calls to the underlying stream's + `async_write_some` function. The program must ensure that no other calls + implemented using the underlying stream's `async_write_some` are + performed until this operation completes. + + @param handler The completion handler to invoke when the operation + completes. The implementation takes ownership of the handler by + performing a decay-copy. The equivalent function signature of the + handler must be: + @code + void handler( + error_code const& error // result of operation + ); + @endcode + Regardless of whether the asynchronous operation + completes immediately or not, the completion handler will not be invoked + from within this function. On immediate completion, invocation of the + handler will be performed in a manner equivalent to using + `asio::async_immediate`. + + @par Per-Operation Cancellation + + This asynchronous operation supports cancellation for the following + net::cancellation_type values: + + @li @c net::cancellation_type::terminal + @li @c net::cancellation_type::partial + @li @c net::cancellation_type::total + + if they are also supported by the underlying stream's @c async_write_some + operation. + */ + template< + BOOST_ASIO_COMPLETION_TOKEN_FOR(void(system::error_code)) + CompletionToken> + BOOST_ASIO_INITFN_AUTO_RESULT_TYPE( + CompletionToken, + void(system::error_code)) + async_close(CompletionToken&& handler); + +private: + template + friend class detail::body_write_stream_op; + + template + friend class detail::body_write_stream_close_op; + + AsyncWriteStream& stream_; + http::serializer& sr_; + http::serializer::stream srs_; + system::error_code ec_; +}; + +} // beast2 +} // boost + +#include + +#endif // BOOST_BEAST2_BODY_WRITE_STREAM_HPP diff --git a/include/boost/beast2/impl/body_write_stream.hpp b/include/boost/beast2/impl/body_write_stream.hpp new file mode 100644 index 00000000..9757db91 --- /dev/null +++ b/include/boost/beast2/impl/body_write_stream.hpp @@ -0,0 +1,236 @@ +// +// Copyright (c) 2025 Mungo Gill +// +// 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_IMPL_BODY_WRITE_STREAM_HPP +#define BOOST_BEAST2_IMPL_BODY_WRITE_STREAM_HPP + +#include + +#include +#include +#include +#include + +namespace boost { +namespace beast2 { + +namespace detail { + +template +class body_write_stream_close_op : public asio::coroutine +{ + body_write_stream& bws_; + +public: + body_write_stream_close_op( + body_write_stream& bws) noexcept + : bws_(bws) + { + } + + template + void + operator()( + Self& self, + system::error_code ec = {}, + std::size_t = 0) + { + BOOST_ASIO_CORO_REENTER(*this) + { + self.reset_cancellation_state(asio::enable_total_cancellation()); + + // Check for a saved error from a previous async_write_some call. + if(bws_.ec_.failed()) + { + ec = bws_.ec_; + bws_.ec_ = {}; + BOOST_ASIO_CORO_YIELD + { + BOOST_ASIO_HANDLER_LOCATION( + (__FILE__, __LINE__, "immediate")); + auto io_ex = self.get_io_executor(); + asio::async_immediate( + io_ex, + asio::append(std::move(self), ec)); + } + goto upcall; + } + + bws_.srs_.close(); + + BOOST_ASIO_CORO_YIELD + { + BOOST_ASIO_HANDLER_LOCATION( + (__FILE__, __LINE__, "async_write_some")); + beast2::async_write(bws_.stream_, bws_.sr_, std::move(self)); + } + + upcall: + self.complete(ec); + } + } +}; + +template +class body_write_stream_op : public asio::coroutine +{ + body_write_stream& bws_; + ConstBufferSequence cb_; + std::size_t bytes_; + +public: + body_write_stream_op( + body_write_stream& bws, + ConstBufferSequence const& cb) noexcept + : bws_(bws) + , cb_(cb) + , bytes_(0) + { + } + + template + void + operator()( + Self& self, + system::error_code ec = {}, + std::size_t = 0) + { + BOOST_ASIO_CORO_REENTER(*this) + { + // Verify preconditions + BOOST_ASSERT(!bws_.sr_.is_done()); + + self.reset_cancellation_state(asio::enable_total_cancellation()); + + // A zero-sized buffer is a special case, we are required to + // complete immediately with no error. Also check for a saved + // error from a previous call. + if(bws_.ec_.failed() || + buffers::size(cb_) == 0) + { + ec = bws_.ec_; + bws_.ec_ = {}; + BOOST_ASIO_CORO_YIELD + { + BOOST_ASIO_HANDLER_LOCATION( + (__FILE__, __LINE__, "immediate")); + auto io_ex = self.get_io_executor(); + asio::async_immediate( + io_ex, + asio::append(std::move(self), ec)); + } + goto upcall; + } + + // The serializer's internal buffer may be full, so we may have no + // option but to try to write to the stream to clear space. + // This may require multiple attempts as buffer space cannot + // be cleared until the headers have been written. + for(;;) + { + bytes_ = asio::buffer_copy(bws_.srs_.prepare(), cb_); + bws_.srs_.commit(bytes_); + + BOOST_ASIO_CORO_YIELD + { + BOOST_ASIO_HANDLER_LOCATION( + (__FILE__, __LINE__, "async_write_some")); + async_write_some(bws_.stream_, bws_.sr_, std::move(self)); + } + + if(ec.failed()) + { + if(bytes_ != 0) + { + bws_.ec_ = ec; + ec = {}; + } + break; + } + + if(bytes_ != 0) + break; + + if(!!self.cancelled()) + { + ec = asio::error::operation_aborted; + break; + } + } + + upcall: + self.complete(ec, bytes_); + } + } +}; + +} // detail + +//------------------------------------------------ + +// TODO: copy in Beast's stream traits to check if AsyncWriteStream +// is an AsyncWriteStream, and also static_assert that body_write_stream is too. + +template +body_write_stream:: +body_write_stream( + AsyncWriteStream& s, + http::serializer& sr, + http::serializer::stream srs) + : stream_(s) + , sr_(sr) + , srs_(std::move(srs)) +{ + // Verify preconditions + BOOST_ASSERT(srs_.is_open()); +} + +template +template< + class ConstBufferSequence, + BOOST_ASIO_COMPLETION_TOKEN_FOR(void(system::error_code, std::size_t)) + CompletionToken> +BOOST_ASIO_INITFN_AUTO_RESULT_TYPE( + CompletionToken, + void(system::error_code, std::size_t)) +body_write_stream:: +async_write_some( + ConstBufferSequence const& cb, + CompletionToken&& token) +{ + return asio:: + async_compose( + detail::body_write_stream_op{ + *this, cb }, + token, + stream_); +} + +template +template< + BOOST_ASIO_COMPLETION_TOKEN_FOR(void(system::error_code)) + CompletionToken> +BOOST_ASIO_INITFN_AUTO_RESULT_TYPE( + CompletionToken, + void(system::error_code)) +body_write_stream:: +async_close( + CompletionToken&& token) +{ + return asio:: + async_compose( + detail::body_write_stream_close_op{ *this }, + token, + stream_); +} + +} // beast2 +} // boost + +#endif // BOOST_BEAST2_IMPL_BODY_WRITE_STREAM_HPP diff --git a/test/unit/body_read_stream.cpp b/test/unit/body_read_stream.cpp index dc984ae6..5eb8c737 100644 --- a/test/unit/body_read_stream.cpp +++ b/test/unit/body_read_stream.cpp @@ -23,6 +23,8 @@ namespace boost { namespace beast2 { +namespace { + template std::string test_to_string(Buffers const& bs) @@ -359,6 +361,8 @@ struct single_tester : public ctx_base } }; +} // anonymous namespace + struct body_read_stream_test { void diff --git a/test/unit/body_write_stream.cpp b/test/unit/body_write_stream.cpp new file mode 100644 index 00000000..8d2cb133 --- /dev/null +++ b/test/unit/body_write_stream.cpp @@ -0,0 +1,827 @@ +// +// Copyright (c) 2025 Mungo Gill +// +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_helpers.hpp" +#include + +#include +#include +#include + +namespace boost { +namespace beast2 { + +namespace { + +template +std::string +test_to_string(Buffers const& bs) +{ + std::string s(buffers::size(bs), 0); + s.resize(buffers::copy(buffers::make_buffer(&s[0], s.size()), bs)); + return s; +} + +class test_handler +{ + boost::optional ec_; + boost::optional n_; + bool pass_ = false; + boost::source_location loc_{ BOOST_CURRENT_LOCATION }; + +public: + test_handler(boost::source_location loc = BOOST_CURRENT_LOCATION) + : loc_(loc) + { + } + + explicit test_handler( + system::error_code ec, + std::size_t n = 0, + boost::source_location loc = BOOST_CURRENT_LOCATION) + : ec_(ec) + , n_(n) + , loc_(loc) + { + } + + test_handler(test_handler&& other) noexcept + : ec_(other.ec_) + , n_(other.n_) + , pass_(boost::exchange(other.pass_, true)) + , loc_(other.loc_) + { + } + + ~test_handler() + { + test_suite::any_runner::instance().test( + pass_, "handler never invoked", "", loc_.file_name(), loc_.line()); + } + + template + void + operator()(system::error_code ec, std::size_t n = 0, Args&&...) + { + test_suite::any_runner::instance().test( + !pass_, + "handler invoked multiple times", + "", + loc_.file_name(), + loc_.line()); + + test_suite::any_runner::instance().test( + !ec_.has_value() || ec == *ec_, + ec.message().c_str(), + "", + loc_.file_name(), + loc_.line()); + + char buf[64]; + snprintf(buf, 64, "%u", (unsigned int)n); + + test_suite::any_runner::instance().test( + !n_.has_value() || n == *n_, + buf, + "", + loc_.file_name(), + loc_.line()); + + pass_ = true; + } +}; + +// Parser service install done in a base class to avoid order-of-initialisation +// issues (this needs to happen before the parser pr_ is constructed) +struct ctx_base +{ + capy::polystore capy_ctx_; + + ctx_base() + { + http::install_parser_service(capy_ctx_, {}); + http::install_serializer_service(capy_ctx_, {}); + } +}; + +struct single_tester : public ctx_base +{ + std::string body_ = "Hello World!"; + + std::string header_ = + "HTTP/1.1 200 OK\r\n" + "Content-Length: 12\r\n" + "\r\n"; + + std::string msg_ = header_ + body_; + + std::size_t header_length_ = header_.size(); + std::size_t body_length_ = body_.size(); + std::size_t msg_length_ = msg_.size(); + + boost::asio::io_context ioc_; + + test::stream ts_; + http::response_parser pr_; + + // Create a destination buffer + std::string s_; + boost::buffers::string_buffer buf_; + + // The object under test + body_read_stream brs_; + + test::stream wts_, rts_; + + http::serializer sr_; + + http::response res_; + + std::size_t srs_capacity_; + + std::optional> bws_; + + single_tester() + : ts_(ioc_, msg_) + , pr_(capy_ctx_) + , buf_(&s_) + , brs_(ts_, pr_) + , wts_(ioc_) + , rts_(ioc_) + , sr_(capy_ctx_) + , res_(header_) + { + wts_.connect(rts_); + pr_.reset(); + pr_.start(); + sr_.reset(); + auto srs = sr_.start_stream(res_); + srs_capacity_ = srs.capacity(); + bws_.emplace(wts_, sr_, std::move(srs)); + } + + body_write_stream& + bws() + { + return *bws_; + } + + void + async_read_some(std::size_t bs, system::error_code ec, std::size_t n) + { + brs_.async_read_some(buf_.prepare(bs), test_handler(ec, n)); + } + + void + async_write_some(std::size_t bs, system::error_code ec, std::size_t n) + { + bws().async_write_some(buf_.prepare(bs), test_handler(ec, n)); + } + + void + async_close(system::error_code ec) + { + bws().async_close(test_handler(ec, 0)); + } + + buffers::const_buffer + make_test_buffer(std::size_t size) + { + std::string val = body_.substr(0, size); + val.resize(size, '.'); + boost::buffers::string_buffer sb(&val); + return sb.data(); + } + + std::size_t + chunking_expected_n( + std::size_t bs, + std::size_t cs, + bool first, + std::size_t read_so_far) + { + std::size_t expected = 0; + if(read_so_far < body_length_) + { + expected = cs; + // In the first iteration we remove any of the data that was + // associcated with the headers. + if(first) + { + expected -= (header_length_ % cs); + // The `beast2::async_read_some` will always read move from + // the wire immediately after the headers, even if we have a + // partial body in memory already. This should be removable + // once `async_read_some` changes. + if(expected < cs) + { + expected += cs; + } + } + expected = std::min(expected, body_length_ - read_so_far); + expected = std::min(bs, expected); + } + return expected; + } + + struct chunking_handler + { + system::error_code ec_; + std::size_t* written_; + + chunking_handler( + system::error_code ec, + std::size_t* written) + : ec_(ec) + , written_(written) + { + } + + void + operator()(system::error_code ec, std::size_t n) + { + BOOST_TEST_EQ(ec, ec_); + *written_ += n; + } + }; + + // Ensure the edge case of being passed a zero-sized buffer works. + void + test_zero_sized_buffer() + { + // Ensure a read into a zero sized buffer returns with no error. + std::string val; + boost::buffers::string_buffer sb(&val); + auto cb = sb.data(); + bws().async_write_some(cb, test_handler(system::error_code{}, 0)); + test::run(ioc_); + } + + // Test for a given buffer size (bs) and stream read size (cs) + void + test_with_chunking(std::size_t bs, std::size_t cs, int iters = 15) + { + wts_.write_size(cs); // Limit und stream write size to cs + + std::string finals; + finals.reserve(bs * iters); + + std::size_t total = 0; + std::size_t prev = 0; + std::size_t writes = 0; + for(int i = 0; i < iters; i++) + { + // Construct a buffer of size bs + std::string val = body_.substr(0, bs); + val.resize(bs, '.'); + boost::buffers::string_buffer sb(&val); + auto cb = sb.data(); + + finals += val; + + // Calculate how many bytes we expect to read on each iteration + // std::size_t expected = chunking_expected_n(bs, cs, (i == 0), + // total); + + while(cb.size() > 0) + { + std::size_t emin = 1; + std::size_t emax = std::min({ bs, srs_capacity_, cb.size() }); + + std::size_t written = 0; + bws().async_write_some( + cb, + chunking_handler( + system::error_code{}, + &written)); + + auto count = test::run(ioc_); + BOOST_TEST_GE(count, 1); + BOOST_TEST_LE(count, (size_t) 1 + header_length_ / cs); + + //std::cout << "count " << count << std::endl; + + total += written; + cb += written; + writes++; + + if (written < emin) + { + std::cout << "err" << std::endl; + } + + BOOST_TEST_GE(total, writes); + BOOST_TEST_GE(written, emin); + BOOST_TEST_LE(written, emax); + + BOOST_TEST_GE(rts_.nwrite_bytes() - prev, 1); + + prev = rts_.nwrite_bytes(); + + BOOST_TEST_LE(rts_.nwrite_bytes(), bs * (i+1) + header_length_); + + BOOST_TEST(!sr_.is_done()); + } + } + + BOOST_TEST_EQ(total, bs * iters); + + BOOST_TEST_LE( + rts_.nwrite_bytes(), cs * writes + header_length_); + + bws().async_close(test::success_handler()); + + auto count = test::run(ioc_); + BOOST_TEST_GE(count, 1); + + BOOST_TEST_GT(rts_.nwrite_bytes(), iters + writes); + BOOST_TEST_LE(rts_.nwrite_bytes(), cs * (writes + count) + header_length_); + + BOOST_TEST_EQ(rts_.nwrite_bytes(), bs * iters + header_length_); + + BOOST_TEST(sr_.is_done()); + + BOOST_TEST(rts_.str() == header_ + finals); + } + + void + test_with_ignored_cancel_signal(std::size_t len) + { + std::string val = body_.substr(0, len); + boost::buffers::string_buffer sb(&val); + auto cb = sb.data(); + + // Add a signal to test cancellation + asio::cancellation_signal c_signal; + + // First call: cancellation occurs after data is written to serializer. + // The callback should receive success with the bytes written. + // With the simplified loop, cancellation after successful write + // is treated as success (the data was written). + std::size_t expected_bytes = std::min(len, body_length_); + bws().async_write_some( + cb, + asio::bind_cancellation_slot( + c_signal.slot(), + test_handler(system::error_code{}, expected_bytes))); + + // send a cancellation + c_signal.emit(asio::cancellation_type::total); + + // Run up until the point of cancellation. + test::run(ioc_); + + BOOST_TEST(!sr_.is_done()); + + // Second call: write the remainder successfully. + // Cancellation after successful write is not saved, so this succeeds. + std::string remainder = body_.substr(len); + boost::buffers::string_buffer sb2(&remainder); + auto cb2 = sb2.data(); + + std::size_t remainder_len = body_length_ - len; + bws().async_write_some( + cb2, + test_handler(system::error_code{}, remainder_len)); + + test::run(ioc_); + + BOOST_TEST(!sr_.is_done()); + + // Third call: close the stream and verify the message. + bws().async_close(test_handler(system::error_code{})); + + test::run(ioc_); + + BOOST_TEST(sr_.is_done()); + BOOST_TEST(rts_.str() == msg_); + } + + void + test_asio_async_write(std::size_t cs, bool use_asio_buffer) + { + // limit chunk size on the underlying stream + wts_.write_size(cs); + + if(use_asio_buffer) + { + asio::async_write( + bws(), + asio::buffer(body_.data(), body_.size()), + test_handler(system::error_code{}, body_length_)); + } + else + { + asio::async_write( + bws(), + buffers::const_buffer(body_.data(), body_.size()), + test_handler(system::error_code{}, body_length_)); + } + + test::run(ioc_); + + BOOST_TEST(!sr_.is_done()); + + // Close the stream to flush remaining data + bws().async_close(test_handler(system::error_code{})); + + test::run(ioc_); + + BOOST_TEST(sr_.is_done()); + BOOST_TEST(rts_.str() == msg_); + } + + void + test_stream_errors() + { + // Create a write test stream that fails on the first write. + test::fail_count fc(0, asio::error::network_down); + test::stream wts(ioc_, fc); + test::stream rts(ioc_); + wts.connect(rts); + + // Create a fresh serializer for this test + http::serializer sr(capy_ctx_); + sr.reset(); + http::response res(header_); + + // Create a new body_write_stream with the failing stream + body_write_stream bws(wts, sr, sr.start_stream(res)); + + // First call: data is committed to the serializer before the + // stream write fails. Due to deferred error handling, this + // returns success with the committed bytes, and saves the error. + std::string val = body_; + boost::buffers::string_buffer sb(&val); + auto cb = sb.data(); + + bws.async_write_some( + cb, + test_handler(system::error_code{}, body_length_)); + + // The operation completes with 1 handler invocation. + BOOST_TEST_EQ(test::run(ioc_), 1); + + // Second call: receives the deferred error with 0 bytes. + bws.async_write_some( + cb, + test_handler(asio::error::network_down, 0)); + + // The deferred error is returned via async_immediate. + BOOST_TEST_EQ(test::run(ioc_), 1); + } + + void + test_close_with_saved_error() + { + // Create a write test stream that fails on the first write. + test::fail_count fc(0, asio::error::network_down); + test::stream wts(ioc_, fc); + test::stream rts(ioc_); + wts.connect(rts); + + // Create a fresh serializer for this test + http::serializer sr(capy_ctx_); + sr.reset(); + http::response res(header_); + + // Create a new body_write_stream with the failing stream + body_write_stream bws(wts, sr, sr.start_stream(res)); + + // First call: data is committed to the serializer before the + // stream write fails. Due to deferred error handling, this + // returns success with the committed bytes, and saves the error. + std::string val = body_; + boost::buffers::string_buffer sb(&val); + auto cb = sb.data(); + + bws.async_write_some( + cb, + test_handler(system::error_code{}, body_length_)); + + // The operation completes with 1 handler invocation. + BOOST_TEST_EQ(test::run(ioc_), 1); + + // async_close receives the saved error immediately. + bws.async_close(test_handler(asio::error::network_down)); + + // The deferred error is returned via async_immediate. + BOOST_TEST_EQ(test::run(ioc_), 1); + } + + void + test_close_errors() + { + // Create a write test stream that fails on the second write. + // The first write will succeed (writing body data), but the + // close operation will fail when flushing remaining data. + test::fail_count fc(1, asio::error::network_down); + test::stream wts(ioc_, fc); + test::stream rts(ioc_); + wts.connect(rts); + + // Limit write size so data remains in serializer after first write. + wts.write_size(1); + + // Create a fresh serializer for this test + http::serializer sr(capy_ctx_); + sr.reset(); + http::response res(header_); + + // Create a new body_write_stream with the failing stream + body_write_stream bws(wts, sr, sr.start_stream(res)); + + // Write body data - this should succeed. + std::string val = body_; + boost::buffers::string_buffer sb(&val); + auto cb = sb.data(); + + bws.async_write_some( + cb, + test_handler(system::error_code{}, body_length_)); + + BOOST_TEST_EQ(test::run(ioc_), 1); + + // Close the stream - this should fail when trying to flush + // the remaining serializer data to the underlying stream. + bws.async_close(test_handler(asio::error::network_down)); + + BOOST_TEST_GE(test::run(ioc_), 1); + } + + // Test cancellation during buffer-clearing loop (when bytes_ == 0). + // This covers the case where the serializer buffer is full and we're + // waiting for space, then get cancelled before any user data is copied. + void + test_cancel_during_buffer_clear() + { + wts_.write_size(1); // Very slow drain + + // First, fill the serializer's buffer completely by writing + // data equal to its capacity + std::size_t cap = srs_capacity_; + std::string fill_data(cap, 'F'); + buffers::const_buffer fill_cb(fill_data.data(), fill_data.size()); + + bool fill_complete = false; + bws().async_write_some( + fill_cb, + [&](system::error_code, std::size_t) + { + fill_complete = true; + }); + + test::run(ioc_); + BOOST_TEST(fill_complete); + + // Now the buffer should be full. The next write should enter + // the buffer-clearing loop with bytes_ == 0 on the first iteration. + std::string more_data(64, 'X'); + buffers::const_buffer cb(more_data.data(), more_data.size()); + + asio::cancellation_signal c_signal; + + system::error_code result_ec; + std::size_t result_bytes = 0; + + bws().async_write_some( + cb, + asio::bind_cancellation_slot( + c_signal.slot(), + [&](system::error_code ec, std::size_t n) + { + result_ec = ec; + result_bytes = n; + })); + + // Emit cancellation immediately - we should be in the loop + // with bytes_ == 0 because the buffer is full + c_signal.emit(asio::cancellation_type::total); + + // Let the operation complete + test::run(ioc_); + + // Should complete with operation_aborted and 0 bytes + // because cancellation occurred while bytes_ == 0 + BOOST_TEST_EQ(result_ec, asio::error::operation_aborted); + BOOST_TEST_EQ(result_bytes, 0u); + } +}; + +#ifdef BOOST_BEAST2_HAS_CORO + +// Result type for async write operations +struct write_result +{ + system::error_code ec; + std::size_t bytes_transferred; +}; + +// Helper to wrap async_write_some for coroutines +template +capy::async_op +coro_write_some( + body_write_stream& bws, + ConstBufferSequence const& buffers) +{ + return capy::make_async_op( + bws.async_write_some(buffers, asio::deferred)); +} + +// Helper to wrap async_close for coroutines +template +capy::async_op +coro_close(body_write_stream& bws) +{ + return capy::make_async_op( + bws.async_close(asio::deferred)); +} + +capy::task +do_coro_write( + test::stream& wts, + test::stream& rts, + http::serializer& sr, + http::serializer::stream srs, + std::string const& body, + std::string const& expected_msg) +{ + body_write_stream bws(wts, sr, std::move(srs)); + + // Write body data using co_await + buffers::const_buffer cb(body.data(), body.size()); + std::size_t total_written = 0; + + while(cb.size() > 0) + { + auto result = co_await coro_write_some(bws, cb); + BOOST_TEST(!result.ec.failed()); + BOOST_TEST_GT(result.bytes_transferred, 0u); + total_written += result.bytes_transferred; + cb += result.bytes_transferred; + } + + BOOST_TEST_EQ(total_written, body.size()); + BOOST_TEST(!sr.is_done()); + + // Close the stream + auto ec = co_await coro_close(bws); + BOOST_TEST(!ec.failed()); + + BOOST_TEST(sr.is_done()); + BOOST_TEST_EQ(rts.str(), expected_msg); + + co_return; +} + +void +test_coroutine() +{ + // Set up context with parser and serializer services + capy::polystore capy_ctx; + http::install_parser_service(capy_ctx, {}); + http::install_serializer_service(capy_ctx, {}); + + std::string body = "Hello World!"; + std::string header = + "HTTP/1.1 200 OK\r\n" + "Content-Length: 12\r\n" + "\r\n"; + std::string expected_msg = header + body; + + asio::io_context ioc; + + test::stream wts(ioc); + test::stream rts(ioc); + wts.connect(rts); + + http::serializer sr(capy_ctx); + sr.reset(); + + http::response res(header); + auto srs = sr.start_stream(res); + + capy::spawn( + wrap_executor(ioc.get_executor()), + do_coro_write(wts, rts, sr, std::move(srs), body, expected_msg), + [](system::result result) + { + if(result.has_error()) + std::rethrow_exception(result.error()); + }); + + ioc.run(); +} + +#endif // BOOST_BEAST2_HAS_CORO + +} // anonymous namespace. + +struct body_write_stream_test +{ + void + run() + { + // Read into a zero sized buffer should return immediately without error + { + single_tester().test_zero_sized_buffer(); + } + + // async_read_some reads the body for various chunk + // sizes. + { + int sizes[] = { 1, 2, 13, 1597, 100'000 }; + // Iterate through buffer sizes + for(std::size_t bs: sizes) + { + // Iterate through chunk sizes + for(std::size_t cs : sizes) + { + if(cs > bs / 10000) + { + auto iters = static_cast( + std::min((size_t)10, (cs / bs) + 1)); + single_tester().test_with_chunking(bs, cs, iters); + } + } + } + } + + // Test async_write_some with ignored cancellation signal + { + // Iterate through different amounts of data written before + // cancellation. Only go up to body_length_ since that's the + // maximum useful data. + std::size_t body_len = single_tester().body_length_; + for(std::size_t len = 1; len <= body_len; len++) + { + single_tester().test_with_ignored_cancel_signal(len); + } + } + + // Test asio::async_write works with body_write_stream + { + // pick a representative chunk size + std::size_t cs = 5; + + // Perform the test using the Boost Buffers buffer directly + single_tester().test_asio_async_write(cs, false); + // And again using an asio buffer wrapper + single_tester().test_asio_async_write(cs, true); + } + + // async_write_some reports stream errors + { + single_tester().test_stream_errors(); + } + + // async_close reports saved errors + { + single_tester().test_close_with_saved_error(); + } + + // async_close reports stream errors during flush + { + single_tester().test_close_errors(); + } + + // Test cancellation during buffer-clearing loop + { + single_tester().test_cancel_during_buffer_clear(); + } + +#ifdef BOOST_BEAST2_HAS_CORO + // Test C++20 coroutine compatibility + { + test_coroutine(); + } +#endif + } +}; + +TEST_SUITE(body_write_stream_test, "boost.beast2.body_write_stream"); + +} // beast2 +} // boost From 465a7323fbfb41c8ec841cee6bf2cd2d2394f62e Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Mon, 12 Jan 2026 20:31:26 -0800 Subject: [PATCH 29/40] Migrate from Boost.Buffers to Boost.Capy - Replace all buffers:: types with capy:: equivalents - Rewrite wrap_executor to create asio_dispatcher (satisfies dispatcher concept) - Update CMakeLists.txt to remove Boost::buffers dependency - Update test and example files for new API --- .gitignore | 2 + CMakeLists.txt | 3 +- example/client/burl/any_stream.hpp | 16 +- example/client/burl/main.cpp | 5 +- example/client/burl/message.cpp | 5 +- example/client/burl/message.hpp | 4 +- example/client/burl/multipart_form.cpp | 16 +- example/client/burl/multipart_form.hpp | 6 +- example/client/get/main.cpp | 2 +- example/client/jsonrpc/jsonrpc/any_stream.hpp | 6 +- example/client/jsonrpc/jsonrpc/client.cpp | 8 +- example/server/main.cpp | 2 +- .../boost/beast2/impl/body_read_stream.hpp | 3 +- .../boost/beast2/impl/body_write_stream.hpp | 3 +- include/boost/beast2/server/body_source.hpp | 56 +- .../boost/beast2/test/detail/stream_state.hpp | 4 +- include/boost/beast2/test/impl/stream.hpp | 32 +- include/boost/beast2/test/stream.hpp | 4 +- include/boost/beast2/wrap_executor.hpp | 163 ++--- test/unit/body_read_stream.cpp | 12 +- test/unit/body_write_stream.cpp | 46 +- test/unit/read.cpp | 8 +- test/unit/server/body_source.cpp | 16 +- test/unit/stream.cpp | 24 +- test/unit/wrap_executor.cpp | 585 ++---------------- test/unit/write.cpp | 18 +- 26 files changed, 252 insertions(+), 797 deletions(-) diff --git a/.gitignore b/.gitignore index 42d344c2..00be625f 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ temp # Visual Studio Code /.vscode + +/.temp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1bb2fe8c..55c79501 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,7 +57,6 @@ set(BOOST_SRC_DIR ${DEFAULT_BOOST_SRC_DIR} CACHE STRING "Boost source dir to use set(BOOST_BEAST2_DEPENDENCIES Boost::asio Boost::assert - Boost::buffers Boost::capy Boost::config Boost::core @@ -124,7 +123,7 @@ if (BOOST_BEAST2_IS_ROOT) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${BOOST_SRC_DIR}/tools/cmake/include") else () # From Boost Package - find_package(Boost REQUIRED COMPONENTS buffers capy http json program_options scope system url) + find_package(Boost REQUIRED COMPONENTS capy http json program_options scope system url) foreach (BOOST_INCLUDE_LIBRARY ${BOOST_INCLUDE_LIBRARIES}) if (NOT TARGET Boost::${BOOST_INCLUDE_LIBRARY}) add_library(Boost::${BOOST_INCLUDE_LIBRARY} ALIAS Boost::headers) diff --git a/example/client/burl/any_stream.hpp b/example/client/burl/any_stream.hpp index 3f247675..ed13e94d 100644 --- a/example/client/burl/any_stream.hpp +++ b/example/client/burl/any_stream.hpp @@ -16,13 +16,13 @@ #include #include #include -#include +#include #include #include #include namespace asio = boost::asio; -namespace buffers = boost::buffers; +namespace capy = boost::capy; namespace http = boost::http; using error_code = boost::system::error_code; @@ -56,7 +56,7 @@ class any_stream virtual void async_write_some( - const buffers::slice_of>& buffers, + const capy::slice_of>& buffers, asio::any_completion_handler handler) override { @@ -65,7 +65,7 @@ class any_stream virtual void async_read_some( - const buffers::slice_of>& buffers, + const capy::slice_of>& buffers, asio::any_completion_handler handler) override { @@ -153,7 +153,7 @@ class any_stream } BOOST_ASIO_CORO_YIELD stream_->async_write_some( - buffers::prefix(buffers, wr_remain_), + capy::prefix(buffers, wr_remain_), std::move(self)); wr_remain_ -= n; self.complete(ec, n); @@ -191,7 +191,7 @@ class any_stream } BOOST_ASIO_CORO_YIELD stream_->async_read_some( - buffers::prefix(buffers, rd_remain_), + capy::prefix(buffers, rd_remain_), std::move(self)); rd_remain_ -= n; self.complete(ec, n); @@ -226,12 +226,12 @@ class any_stream virtual void async_write_some( - const buffers::slice_of>&, + const capy::slice_of>&, asio::any_completion_handler) = 0; virtual void async_read_some( - const buffers::slice_of>&, + const capy::slice_of>&, asio::any_completion_handler) = 0; virtual void async_shutdown( diff --git a/example/client/burl/main.cpp b/example/client/burl/main.cpp index e5efc919..1f318507 100644 --- a/example/client/burl/main.cpp +++ b/example/client/burl/main.cpp @@ -28,7 +28,7 @@ #include #include #include -#include +#include #include #include #include @@ -41,6 +41,7 @@ #include namespace beast2 = boost::beast2; +namespace capy = boost::capy; namespace scope = boost::scope; using system_error = boost::system::system_error; @@ -323,7 +324,7 @@ class sink : public http::sink } results - on_write(buffers::const_buffer cb, bool) override + on_write(capy::const_buffer cb, bool) override { auto chunk = core::string_view(static_cast(cb.data()), cb.size()); diff --git a/example/client/burl/message.cpp b/example/client/burl/message.cpp index 1e68abd7..d9601dc6 100644 --- a/example/client/burl/message.cpp +++ b/example/client/burl/message.cpp @@ -17,6 +17,7 @@ #include #include +namespace capy = boost::capy; namespace fs = std::filesystem; using system_error = boost::system::system_error; @@ -44,7 +45,7 @@ string_body::content_length() const noexcept return body_.size(); } -buffers::const_buffer +capy::const_buffer string_body::body() const noexcept { return { body_.data(), body_.size() }; @@ -90,7 +91,7 @@ file_body::body() const // ----------------------------------------------------------------------------- boost::http::source::results -stdin_body::source::on_read(buffers::mutable_buffer mb) +stdin_body::source::on_read(capy::mutable_buffer mb) { std::cin.read(static_cast(mb.data()), mb.size()); diff --git a/example/client/burl/message.hpp b/example/client/burl/message.hpp index bbd3052e..3d3caef4 100644 --- a/example/client/burl/message.hpp +++ b/example/client/burl/message.hpp @@ -37,7 +37,7 @@ class string_body std::size_t content_length() const noexcept; - buffers::const_buffer + capy::const_buffer body() const noexcept; }; @@ -68,7 +68,7 @@ class stdin_body { public: results - on_read(buffers::mutable_buffer mb) override; + on_read(capy::mutable_buffer mb) override; }; http::method diff --git a/example/client/burl/multipart_form.cpp b/example/client/burl/multipart_form.cpp index 529f70c3..16c0edab 100644 --- a/example/client/burl/multipart_form.cpp +++ b/example/client/burl/multipart_form.cpp @@ -9,8 +9,8 @@ #include "multipart_form.hpp" -#include -#include +#include +#include #include #include @@ -148,18 +148,18 @@ multipart_form::source::source(const multipart_form* form) noexcept } multipart_form::source::results -multipart_form::source::on_read(buffers::mutable_buffer mb) +multipart_form::source::on_read(capy::mutable_buffer mb) { auto rs = results{}; auto copy = [&](core::string_view sv) { - buffers::const_buffer source(sv.data(), sv.size()); - buffers::remove_prefix(mb, static_cast(skip_)); + capy::const_buffer source(sv.data(), sv.size()); + capy::remove_prefix(mb, static_cast(skip_)); - auto copied = buffers::copy(mb, source); + auto copied = capy::copy(mb, source); - buffers::remove_prefix(mb, copied); + capy::remove_prefix(mb, copied); rs.bytes += copied; skip_ += copied; @@ -189,7 +189,7 @@ multipart_form::source::on_read(buffers::mutable_buffer mb) if(rs.ec) return false; - buffers::remove_prefix(mb, read); + capy::remove_prefix(mb, read); rs.bytes += read; skip_ += read; diff --git a/example/client/burl/multipart_form.hpp b/example/client/burl/multipart_form.hpp index 2ae80643..09a0068f 100644 --- a/example/client/burl/multipart_form.hpp +++ b/example/client/burl/multipart_form.hpp @@ -10,7 +10,7 @@ #ifndef BURL_MULTIPART_FORM_HPP #define BURL_MULTIPART_FORM_HPP -#include +#include #include #include #include @@ -20,7 +20,7 @@ #include #include -namespace buffers = boost::buffers; +namespace capy = boost::capy; namespace http = boost::http; using error_code = boost::system::error_code; @@ -79,7 +79,7 @@ class multipart_form::source : public http::source explicit source(const multipart_form* form) noexcept; results - on_read(buffers::mutable_buffer mb) override; + on_read(capy::mutable_buffer mb) override; }; #endif diff --git a/example/client/get/main.cpp b/example/client/get/main.cpp index dcb84ff7..a9bca59b 100644 --- a/example/client/get/main.cpp +++ b/example/client/get/main.cpp @@ -410,7 +410,7 @@ class session struct stdout_sink : http::sink { results - on_write(buffers::const_buffer cb, bool) override + on_write(capy::const_buffer cb, bool) override { std::cout.write( static_cast(cb.data()), cb.size()); diff --git a/example/client/jsonrpc/jsonrpc/any_stream.hpp b/example/client/jsonrpc/jsonrpc/any_stream.hpp index 4efecf61..8a8c7c59 100644 --- a/example/client/jsonrpc/jsonrpc/any_stream.hpp +++ b/example/client/jsonrpc/jsonrpc/any_stream.hpp @@ -11,7 +11,7 @@ #define JSONRPC_ANY_STREAM_HPP #include -#include +#include #include #include @@ -33,12 +33,12 @@ class any_stream virtual void async_write_some( - const boost::span&, + const boost::span&, boost::asio::any_completion_handler) = 0; virtual void async_read_some( - const boost::span&, + const boost::span&, boost::asio::any_completion_handler) = 0; virtual void diff --git a/example/client/jsonrpc/jsonrpc/client.cpp b/example/client/jsonrpc/jsonrpc/client.cpp index 503c3a20..5823c29a 100644 --- a/example/client/jsonrpc/jsonrpc/client.cpp +++ b/example/client/jsonrpc/jsonrpc/client.cpp @@ -84,7 +84,7 @@ class stream_impl : public any_stream void async_write_some( - const boost::span& buffers, + const boost::span& buffers, asio::any_completion_handler handler) override { @@ -96,7 +96,7 @@ class stream_impl : public any_stream void async_read_some( - const boost::span& buffers, + const boost::span& buffers, asio::any_completion_handler handler) override { @@ -236,7 +236,7 @@ class json_sink : public http::sink private: results on_write( - buffers::const_buffer b, + capy::const_buffer b, bool more) override { results ret; @@ -266,7 +266,7 @@ class json_source : public http::source private: results on_read( - buffers::mutable_buffer b) override + capy::mutable_buffer b) override { results ret; ret.bytes = jsr_.read( diff --git a/example/server/main.cpp b/example/server/main.cpp index 02905dbb..f431461a 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -71,7 +71,7 @@ class json_sink : public http::sink private: results on_write( - buffers::const_buffer b, + capy::const_buffer b, bool more) override { results rv; diff --git a/include/boost/beast2/impl/body_read_stream.hpp b/include/boost/beast2/impl/body_read_stream.hpp index 550f3ee8..febf9e57 100644 --- a/include/boost/beast2/impl/body_read_stream.hpp +++ b/include/boost/beast2/impl/body_read_stream.hpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -57,7 +58,7 @@ class body_read_stream_op : public asio::coroutine { self.reset_cancellation_state(asio::enable_total_cancellation()); - if(buffers::size(mb_) == 0) + if(capy::buffer_size(mb_) == 0) { BOOST_ASIO_CORO_YIELD { diff --git a/include/boost/beast2/impl/body_write_stream.hpp b/include/boost/beast2/impl/body_write_stream.hpp index 9757db91..d036a29d 100644 --- a/include/boost/beast2/impl/body_write_stream.hpp +++ b/include/boost/beast2/impl/body_write_stream.hpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -112,7 +113,7 @@ class body_write_stream_op : public asio::coroutine // complete immediately with no error. Also check for a saved // error from a previous call. if(bws_.ec_.failed() || - buffers::size(cb_) == 0) + capy::buffer_size(cb_) == 0) { ec = bws_.ec_; bws_.ec_ = {}; diff --git a/include/boost/beast2/server/body_source.hpp b/include/boost/beast2/server/body_source.hpp index 8b0296c7..09cc805c 100644 --- a/include/boost/beast2/server/body_source.hpp +++ b/include/boost/beast2/server/body_source.hpp @@ -14,11 +14,11 @@ #include #include #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include @@ -90,7 +90,7 @@ class body_source template::type, body_source>::value && - buffers::is_read_source::type>::value + capy::is_read_source::type>::value , int>::type = 0> body_source(ReadSource&& body); @@ -99,7 +99,7 @@ class body_source template::type, body_source>::value && - buffers::is_read_source::type>::value + capy::is_read_source::type>::value , int>::type = 0> body_source( std::size_t known_size, @@ -110,7 +110,7 @@ class body_source template::type, body_source>::value && - buffers::is_data_source::type>::value + capy::is_data_source::type>::value , int>::type = 0> body_source(DataSource&& body); @@ -153,7 +153,7 @@ class body_source @return A span of buffers representing the body. */ auto data() const -> - span + span { if(impl_) return impl_->data(); @@ -202,7 +202,7 @@ class body_source detail::throw_invalid_argument(); } virtual auto data() const -> - span + span { detail::throw_invalid_argument(); } @@ -220,7 +220,7 @@ class body_source template::type, body_source>::value && - buffers::is_read_source::type>::value + capy::is_read_source::type>::value , int>::type> body_source:: body_source( @@ -253,7 +253,7 @@ body_source( return 0; } auto nread = body_.read( - buffers::mutable_buffer(dest, size), ec); + capy::mutable_buffer(dest, size), ec); ec_ = ec; return nread; } @@ -269,7 +269,7 @@ body_source( template::type, body_source>::value && - buffers::is_read_source::type>::value + capy::is_read_source::type>::value , int>::type> body_source:: body_source( @@ -317,7 +317,7 @@ body_source( return 0; } auto nread = body_.read( - buffers::mutable_buffer(dest, size), ec); + capy::mutable_buffer(dest, size), ec); ec_ = ec; return nread; } @@ -333,7 +333,7 @@ body_source( template::type, body_source>::value && - buffers::is_data_source::type>::value + capy::is_data_source::type>::value , int>::type> body_source:: body_source( @@ -343,7 +343,7 @@ body_source( { typename std::decay::type body_; std::size_t size_ = 0; - span bs_; + span bs_; std::size_t nread_ = 0; explicit model( @@ -351,19 +351,19 @@ body_source( : body_(std::forward(body)) { auto const& data = body_.data(); - auto const& end = buffers::end(data); + auto const& end = capy::end(data); auto p = reinterpret_cast< - buffers::const_buffer*>(this+1); + capy::const_buffer*>(this+1); std::size_t length = 0; - for(auto it = buffers::begin(data); it != end; ++it) + for(auto it = capy::begin(data); it != end; ++it) { - boost::buffers::const_buffer cb(*it); + boost::capy::const_buffer cb(*it); size_ += cb.size(); *p++ = cb; ++length; } bs_ = { reinterpret_cast< - buffers::const_buffer*>(this + 1), length }; + capy::const_buffer*>(this + 1), length }; } bool has_size() const noexcept override @@ -381,7 +381,7 @@ body_source( return size_; } - span + span data() const override { return bs_; @@ -397,9 +397,9 @@ body_source( std::size_t n0, system::error_code& ec) override { - std::size_t n = buffers::copy( - buffers::mutable_buffer(dest, n0), - buffers::sans_prefix(bs_, nread_)); + 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; @@ -411,8 +411,8 @@ body_source( std::size_t length = 0; auto const& data = body.data(); - auto const& end = buffers::end(data); - for(auto it = buffers::begin(data); + auto const& end = capy::end(data); + for(auto it = capy::begin(data); it != end; ++it) { ++length; @@ -421,7 +421,7 @@ body_source( // VFALCO this requires DataSource to be nothrow // move constructible for strong exception safety. auto p = ::operator new(sizeof(model) + - length * sizeof(buffers::const_buffer)); + length * sizeof(capy::const_buffer)); impl_ = ::new(p) model( std::forward(body)); } diff --git a/include/boost/beast2/test/detail/stream_state.hpp b/include/boost/beast2/test/detail/stream_state.hpp index 53bb5550..5760f7a5 100644 --- a/include/boost/beast2/test/detail/stream_state.hpp +++ b/include/boost/beast2/test/detail/stream_state.hpp @@ -13,7 +13,7 @@ #include #include -#include +#include #include #include #include @@ -88,7 +88,7 @@ struct stream_state boost::weak_ptr wp; std::mutex m; std::string storage; - buffers::string_buffer b; + capy::string_buffer b; //std::condition_variable cv; std::unique_ptr rop; std::unique_ptr wop; diff --git a/include/boost/beast2/test/impl/stream.hpp b/include/boost/beast2/test/impl/stream.hpp index ec626e4e..9f679e59 100644 --- a/include/boost/beast2/test/impl/stream.hpp +++ b/include/boost/beast2/test/impl/stream.hpp @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include @@ -159,7 +159,7 @@ class basic_stream::read_op : public detail::stream_op_base BOOST_ASSERT(!sp->rop); if(sp->b.size() > 0) { - bytes_transferred = buffers::copy( + bytes_transferred = capy::copy( base::b_, sp->b.data(), sp->read_max); @@ -167,7 +167,7 @@ class basic_stream::read_op : public detail::stream_op_base sp->nread_bytes += bytes_transferred; } else if( - buffers::size( + capy::buffer_size( base::b_) > 0) { ec = asio::error::eof; @@ -245,10 +245,10 @@ class basic_stream::write_op : public detail::stream_op_base { // copy buffers std::size_t n = std::min( - buffers::size(base::b_), isp->write_max); + capy::buffer_size(base::b_), isp->write_max); { std::lock_guard lock(osp->m); - n = buffers::copy(osp->b.prepare(n), base::b_); + n = capy::copy(osp->b.prepare(n), base::b_); osp->b.commit(n); osp->nwrite_bytes += n; osp->notify_read(); @@ -331,7 +331,7 @@ struct basic_stream::run_read_op std::unique_ptr{ new read_op< typename std::decay::type, MutableBufferSequence>(std::move(h), in_, out, buffers) }, - buffers::size(buffers)); + capy::buffer_size(buffers)); } }; @@ -367,7 +367,7 @@ struct basic_stream::run_write_op std::unique_ptr{ new write_op< typename std::decay::type, ConstBufferSequence>(std::move(h), in_, out, buffers) }, - buffers::size(buffers)); + capy::buffer_size(buffers)); } }; @@ -464,7 +464,7 @@ basic_stream::initiate_read( } // A request to read 0 bytes from a stream is a no-op. - if(buf_size == 0 || buffers::size(in->b.data()) > 0) + if(buf_size == 0 || capy::buffer_size(in->b.data()) > 0) { lock.unlock(); (*rop)(ec); @@ -589,9 +589,9 @@ basic_stream( core::string_view s) : in_(detail::stream_service::make_impl(ioc.get_executor(), nullptr)) { - in_->b.commit(buffers::copy( + in_->b.commit(capy::copy( in_->b.prepare(s.size()), - buffers::const_buffer(s.data(), s.size()))); + capy::const_buffer(s.data(), s.size()))); } template @@ -602,9 +602,9 @@ basic_stream( core::string_view s) : in_(detail::stream_service::make_impl(ioc.get_executor(), &fc)) { - in_->b.commit(buffers::copy( + in_->b.commit(capy::copy( in_->b.prepare(s.size()), - buffers::const_buffer(s.data(), s.size()))); + capy::const_buffer(s.data(), s.size()))); } template @@ -629,9 +629,9 @@ basic_stream:: str() const { auto const bs = in_->b.data(); - if(buffers::size(bs) == 0) + if(capy::buffer_size(bs) == 0) return {}; - buffers::const_buffer const b = *asio::buffer_sequence_begin(bs); + capy::const_buffer const b = *asio::buffer_sequence_begin(bs); return {static_cast(b.data()), b.size()}; } @@ -641,9 +641,9 @@ basic_stream:: append(core::string_view s) { std::lock_guard lock{in_->m}; - in_->b.commit(buffers::copy( + in_->b.commit(capy::copy( in_->b.prepare(s.size()), - buffers::const_buffer(s.data(), s.size()))); + capy::const_buffer(s.data(), s.size()))); } template diff --git a/include/boost/beast2/test/stream.hpp b/include/boost/beast2/test/stream.hpp index 9cf5051b..18979a32 100644 --- a/include/boost/beast2/test/stream.hpp +++ b/include/boost/beast2/test/stream.hpp @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include #include @@ -175,7 +175,7 @@ class basic_stream #endif public: - using buffer_type = buffers::string_buffer; + using buffer_type = capy::string_buffer; /** Destructor diff --git a/include/boost/beast2/wrap_executor.hpp b/include/boost/beast2/wrap_executor.hpp index ec98633d..3b1ff7ae 100644 --- a/include/boost/beast2/wrap_executor.hpp +++ b/include/boost/beast2/wrap_executor.hpp @@ -11,12 +11,9 @@ #define BOOST_BEAST2_WRAP_EXECUTOR_HPP #include -#include -#include +#include #include -#include -#include -#include +#include #include #include @@ -25,157 +22,69 @@ namespace beast2 { namespace detail { -// Metadata stored before each work allocation -struct work_metadata -{ - void* raw; - std::size_t total_size; -}; +/** A dispatcher that posts coroutine handles to an Asio executor. + This class wraps an Asio executor and satisfies the capy::dispatcher + concept. When invoked with a coroutine handle, it posts the handle's + resumption to the wrapped executor for later execution. +*/ template -struct asio_executor_impl +class asio_dispatcher { - friend struct capy::executor::access; - - using allocator_type = typename - asio::associated_allocator::type; - AsioExecutor exec_; - allocator_type alloc_; +public: explicit - asio_executor_impl(AsioExecutor ex) + asio_dispatcher(AsioExecutor ex) : exec_(std::move(ex)) - , alloc_(asio::get_associated_allocator(exec_)) { } - // Move constructor - asio_executor_impl(asio_executor_impl&& other) noexcept - : exec_(std::move(other.exec_)) - , alloc_(std::move(other.alloc_)) - { - } + asio_dispatcher(asio_dispatcher const&) = default; + asio_dispatcher(asio_dispatcher&&) = default; + asio_dispatcher& operator=(asio_dispatcher const&) = default; + asio_dispatcher& operator=(asio_dispatcher&&) = default; - // Move assignment - asio_executor_impl& operator=(asio_executor_impl&& other) noexcept - { - if(this != &other) - { - exec_ = std::move(other.exec_); - alloc_ = std::move(other.alloc_); - } - return *this; - } + /** Dispatch a coroutine handle for execution. - // Delete copy operations - asio_executor_impl(asio_executor_impl const&) = delete; - asio_executor_impl& operator=(asio_executor_impl const&) = delete; + Posts the coroutine's resumption to the Asio executor. + Returns noop_coroutine since the work is scheduled for later. -private: - void* - allocate(std::size_t size, std::size_t align) + @param h The coroutine handle to dispatch. + @return std::noop_coroutine() indicating no symmetric transfer. + */ + capy::coro + operator()(capy::coro h) const { - // Rebind allocator to char for byte-level allocation - using char_alloc = typename std::allocator_traits< - allocator_type>::template rebind_alloc; - char_alloc a(alloc_); - - // We need space for: - // - metadata struct (aligned to work_metadata alignment) - // - padding for work alignment - // - work object - std::size_t const meta_size = sizeof(work_metadata); - std::size_t const total_size = meta_size + align + size; - - // Allocate raw storage - char* raw = std::allocator_traits::allocate(a, total_size); - - // Compute aligned pointer for work after metadata - char* after_meta = raw + meta_size; - void* aligned = after_meta; - std::size_t space = total_size - meta_size; - aligned = std::align(align, size, aligned, space); - - // Store metadata immediately before the aligned work region - work_metadata* meta = reinterpret_cast( - static_cast(aligned) - sizeof(work_metadata)); - meta->raw = raw; - meta->total_size = total_size; - - return aligned; - } - - void - deallocate(void* p, std::size_t /*size*/, std::size_t /*align*/) - { - using char_alloc = typename std::allocator_traits< - allocator_type>::template rebind_alloc; - char_alloc a(alloc_); - - // Retrieve metadata stored before p - work_metadata* meta = reinterpret_cast( - static_cast(p) - sizeof(work_metadata)); - - std::allocator_traits::deallocate( - a, static_cast(meta->raw), meta->total_size); - } - - void - submit(capy::executor::work* w) - { - // Capture a copy of allocator for the lambda - allocator_type alloc_copy = alloc_; - asio::post(exec_, - [w, alloc_copy]() mutable - { - using char_alloc = typename std::allocator_traits< - allocator_type>::template rebind_alloc; - char_alloc a(alloc_copy); - - // Retrieve metadata stored before w - work_metadata* meta = reinterpret_cast( - reinterpret_cast(w) - sizeof(work_metadata)); - void* raw = meta->raw; - std::size_t total_size = meta->total_size; - - w->invoke(); - w->~work(); - - std::allocator_traits::deallocate( - a, static_cast(raw), total_size); - }); + asio::post(exec_, [h]() mutable { + h.resume(); + }); + return std::noop_coroutine(); } }; } // detail -/** Return a capy::executor from an Asio executor. +/** Return a capy::any_dispatcher from an Asio executor. - This function wraps an Asio executor in a capy::executor, - mapping the capy::executor implementation API to the - corresponding Asio executor operations. - - The returned executor uses get_associated_allocator on - the Asio executor to obtain the allocator for work items. + This function wraps an Asio executor in a dispatcher that can be + stored in capy::any_dispatcher. When the dispatcher is invoked with + a coroutine handle, it posts the handle's resumption to the Asio + executor for later execution. @param ex The Asio executor to wrap. - @return A capy::executor that submits work via the - provided Asio executor. + @return A dispatcher that posts work to the provided Asio executor. */ template -capy::executor -wrap_executor(AsioExecutor ex) +detail::asio_dispatcher::type> +wrap_executor(AsioExecutor&& ex) { - return capy::executor::wrap( - detail::asio_executor_impl< - typename std::decay::type>( - std::move(ex))); + return detail::asio_dispatcher::type>( + std::forward(ex)); } } // beast2 } // boost #endif - diff --git a/test/unit/body_read_stream.cpp b/test/unit/body_read_stream.cpp index 5eb8c737..8f2e131c 100644 --- a/test/unit/body_read_stream.cpp +++ b/test/unit/body_read_stream.cpp @@ -11,7 +11,7 @@ #include #include -#include +#include #include #include "test_helpers.hpp" @@ -29,8 +29,8 @@ template std::string test_to_string(Buffers const& bs) { - std::string s(buffers::size(bs), 0); - s.resize(buffers::copy(buffers::make_buffer(&s[0], s.size()), bs)); + std::string s(capy::buffer_size(bs), 0); + s.resize(capy::copy(capy::make_buffer(&s[0], s.size()), bs)); return s; } @@ -137,7 +137,7 @@ struct single_tester : public ctx_base // Create a destination buffer std::string s_; - boost::buffers::string_buffer buf_; + boost::capy::string_buffer buf_; // The object under test body_read_stream brs_; @@ -197,13 +197,13 @@ struct single_tester : public ctx_base std::size_t n_; system::error_code ec_; std::size_t* total_; - boost::buffers::string_buffer* buf_; + boost::capy::string_buffer* buf_; chunking_handler( std::size_t n, system::error_code ec, std::size_t* total, - boost::buffers::string_buffer* buf) + boost::capy::string_buffer* buf) : n_(n) , ec_(ec) , total_(total) diff --git a/test/unit/body_write_stream.cpp b/test/unit/body_write_stream.cpp index 8d2cb133..30b3dd33 100644 --- a/test/unit/body_write_stream.cpp +++ b/test/unit/body_write_stream.cpp @@ -16,8 +16,9 @@ #include #include #include -#include +#include #include +#include #include #include #include @@ -39,8 +40,8 @@ template std::string test_to_string(Buffers const& bs) { - std::string s(buffers::size(bs), 0); - s.resize(buffers::copy(buffers::make_buffer(&s[0], s.size()), bs)); + std::string s(capy::buffer_size(bs), 0); + s.resize(capy::copy(capy::make_buffer(&s[0], s.size()), bs)); return s; } @@ -148,7 +149,7 @@ struct single_tester : public ctx_base // Create a destination buffer std::string s_; - boost::buffers::string_buffer buf_; + boost::capy::string_buffer buf_; // The object under test body_read_stream brs_; @@ -206,12 +207,12 @@ struct single_tester : public ctx_base bws().async_close(test_handler(ec, 0)); } - buffers::const_buffer + capy::const_buffer make_test_buffer(std::size_t size) { std::string val = body_.substr(0, size); val.resize(size, '.'); - boost::buffers::string_buffer sb(&val); + boost::capy::string_buffer sb(&val); return sb.data(); } @@ -273,7 +274,7 @@ struct single_tester : public ctx_base { // Ensure a read into a zero sized buffer returns with no error. std::string val; - boost::buffers::string_buffer sb(&val); + boost::capy::string_buffer sb(&val); auto cb = sb.data(); bws().async_write_some(cb, test_handler(system::error_code{}, 0)); test::run(ioc_); @@ -296,7 +297,7 @@ struct single_tester : public ctx_base // Construct a buffer of size bs std::string val = body_.substr(0, bs); val.resize(bs, '.'); - boost::buffers::string_buffer sb(&val); + boost::capy::string_buffer sb(&val); auto cb = sb.data(); finals += val; @@ -370,7 +371,7 @@ struct single_tester : public ctx_base test_with_ignored_cancel_signal(std::size_t len) { std::string val = body_.substr(0, len); - boost::buffers::string_buffer sb(&val); + boost::capy::string_buffer sb(&val); auto cb = sb.data(); // Add a signal to test cancellation @@ -398,7 +399,7 @@ struct single_tester : public ctx_base // Second call: write the remainder successfully. // Cancellation after successful write is not saved, so this succeeds. std::string remainder = body_.substr(len); - boost::buffers::string_buffer sb2(&remainder); + boost::capy::string_buffer sb2(&remainder); auto cb2 = sb2.data(); std::size_t remainder_len = body_length_ - len; @@ -436,7 +437,7 @@ struct single_tester : public ctx_base { asio::async_write( bws(), - buffers::const_buffer(body_.data(), body_.size()), + capy::const_buffer(body_.data(), body_.size()), test_handler(system::error_code{}, body_length_)); } @@ -474,7 +475,7 @@ struct single_tester : public ctx_base // stream write fails. Due to deferred error handling, this // returns success with the committed bytes, and saves the error. std::string val = body_; - boost::buffers::string_buffer sb(&val); + boost::capy::string_buffer sb(&val); auto cb = sb.data(); bws.async_write_some( @@ -514,7 +515,7 @@ struct single_tester : public ctx_base // stream write fails. Due to deferred error handling, this // returns success with the committed bytes, and saves the error. std::string val = body_; - boost::buffers::string_buffer sb(&val); + boost::capy::string_buffer sb(&val); auto cb = sb.data(); bws.async_write_some( @@ -555,7 +556,7 @@ struct single_tester : public ctx_base // Write body data - this should succeed. std::string val = body_; - boost::buffers::string_buffer sb(&val); + boost::capy::string_buffer sb(&val); auto cb = sb.data(); bws.async_write_some( @@ -583,7 +584,7 @@ struct single_tester : public ctx_base // data equal to its capacity std::size_t cap = srs_capacity_; std::string fill_data(cap, 'F'); - buffers::const_buffer fill_cb(fill_data.data(), fill_data.size()); + capy::const_buffer fill_cb(fill_data.data(), fill_data.size()); bool fill_complete = false; bws().async_write_some( @@ -599,7 +600,7 @@ struct single_tester : public ctx_base // Now the buffer should be full. The next write should enter // the buffer-clearing loop with bytes_ == 0 on the first iteration. std::string more_data(64, 'X'); - buffers::const_buffer cb(more_data.data(), more_data.size()); + capy::const_buffer cb(more_data.data(), more_data.size()); asio::cancellation_signal c_signal; @@ -671,7 +672,7 @@ do_coro_write( body_write_stream bws(wts, sr, std::move(srs)); // Write body data using co_await - buffers::const_buffer cb(body.data(), body.size()); + capy::const_buffer cb(body.data(), body.size()); std::size_t total_written = 0; while(cb.size() > 0) @@ -723,14 +724,9 @@ test_coroutine() http::response res(header); auto srs = sr.start_stream(res); - capy::spawn( - wrap_executor(ioc.get_executor()), - do_coro_write(wts, rts, sr, std::move(srs), body, expected_msg), - [](system::result result) - { - if(result.has_error()) - std::rethrow_exception(result.error()); - }); + // Launch coroutine using async_run (default handler rethrows exceptions) + capy::async_run(wrap_executor(ioc.get_executor()))( + do_coro_write(wts, rts, sr, std::move(srs), body, expected_msg)); ioc.run(); } diff --git a/test/unit/read.cpp b/test/unit/read.cpp index 632db1db..b894f72b 100644 --- a/test/unit/read.cpp +++ b/test/unit/read.cpp @@ -15,8 +15,8 @@ #include #include #include -#include -#include +#include +#include #include #include "test_helpers.hpp" @@ -228,9 +228,9 @@ class read_test pr.start(); pr.commit( - buffers::copy( + capy::copy( pr.prepare(), - buffers::const_buffer( + capy::const_buffer( msg.data(), msg.size()))); diff --git a/test/unit/server/body_source.cpp b/test/unit/server/body_source.cpp index 235c5bef..ea0f0f27 100644 --- a/test/unit/server/body_source.cpp +++ b/test/unit/server/body_source.cpp @@ -30,7 +30,7 @@ struct data_source { } - buffers::const_buffer + capy::const_buffer data() const { return { s_.data(), s_.size() }; @@ -68,9 +68,9 @@ struct read_source ec = ec_; return 0; } - auto n = buffers::copy( + auto n = capy::copy( dest, - buffers::const_buffer( + capy::const_buffer( s_.data() + nread_, s_.size() - nread_)); nread_ += n; @@ -155,7 +155,7 @@ struct body_source_test BOOST_TEST_EQ(b.has_size(), true); BOOST_TEST_EQ(b.size(), 0); BOOST_TEST_EQ(b.has_buffers(), true); - BOOST_TEST_EQ(buffers::size(b.data()), 0); + BOOST_TEST_EQ(capy::buffer_size(b.data()), 0); BOOST_TEST_NO_THROW(b.rewind()); grind(b, ""); } @@ -169,7 +169,7 @@ struct body_source_test BOOST_TEST_EQ(b1.has_size(), true); BOOST_TEST_EQ(b1.size(), s1.size()); BOOST_TEST_EQ(b1.has_buffers(), true); - BOOST_TEST_EQ(buffers::size(b1.data()), s1.size()); + BOOST_TEST_EQ(capy::buffer_size(b1.data()), s1.size()); BOOST_TEST_NO_THROW(b1.rewind()); grind(b1, s1); @@ -177,7 +177,7 @@ struct body_source_test BOOST_TEST_EQ(b2.has_size(), true); BOOST_TEST_EQ(b2.size(), s1.size()); BOOST_TEST_EQ(b2.has_buffers(), true); - BOOST_TEST_EQ(buffers::size(b2.data()), s1.size()); + BOOST_TEST_EQ(capy::buffer_size(b2.data()), s1.size()); BOOST_TEST_NO_THROW(b2.rewind()); grind(b2, s1); @@ -185,7 +185,7 @@ struct body_source_test BOOST_TEST_EQ(b1.has_size(), true); BOOST_TEST_EQ(b1.size(), s2.size()); BOOST_TEST_EQ(b1.has_buffers(), true); - BOOST_TEST_EQ(buffers::size(b1.data()), s2.size()); + BOOST_TEST_EQ(capy::buffer_size(b1.data()), s2.size()); BOOST_TEST_NO_THROW(b1.rewind()); grind(b1, s2); } @@ -212,7 +212,7 @@ struct body_source_test BOOST_TEST_EQ(b1.has_size(), true); BOOST_TEST_EQ(b1.size(), 0); BOOST_TEST_EQ(b1.has_buffers(), true); - BOOST_TEST_EQ(buffers::size(b1.data()), 0); + BOOST_TEST_EQ(capy::buffer_size(b1.data()), 0); BOOST_TEST_NO_THROW(b1.rewind()); grind(b2, s1); diff --git a/test/unit/stream.cpp b/test/unit/stream.cpp index 1e1fb2ae..d2d036f0 100644 --- a/test/unit/stream.cpp +++ b/test/unit/stream.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include @@ -26,9 +26,9 @@ namespace beast2 { template auto make_stream( AsyncStream&& asioStream) -> - buffers::any_stream + capy::any_stream { - struct impl : buffers::any_stream::impl + struct impl : capy::any_stream::impl { typename std::decay::type stream_; @@ -37,24 +37,24 @@ auto make_stream( { } - buffers::async_io_result + capy::async_io_result read_some( - buffers::mutable_buffer b) override + capy::mutable_buffer b) override { - return capy::make_async_result( + return capy::make_async_result( stream_.async_read_some(b, asio::deferred)); } - buffers::async_io_result + capy::async_io_result write_some( - buffers::const_buffer b) override + capy::const_buffer b) override { - return capy::make_async_result( + return capy::make_async_result( stream_.async_write_some(b, asio::deferred)); } }; - return buffers::any_stream(std::make_shared( + return capy::any_stream(std::make_shared( std::forward(asioStream))); } @@ -67,10 +67,10 @@ struct stream_test } capy::task - do_read(buffers::any_stream stream) + do_read(capy::any_stream stream) { char buf[256]; - buffers::mutable_buffer b(buf, sizeof(buf)); + capy::mutable_buffer b(buf, sizeof(buf)); auto rv = co_await stream.read_some(b); core::string_view sv(buf, rv.bytes_transferred); BOOST_TEST(! rv.ec.failed()); diff --git a/test/unit/wrap_executor.cpp b/test/unit/wrap_executor.cpp index cf643776..60b8ecc4 100644 --- a/test/unit/wrap_executor.cpp +++ b/test/unit/wrap_executor.cpp @@ -14,267 +14,53 @@ #include #include -#include -#include +#include +#include +#include namespace boost { namespace beast2 { //----------------------------------------------------------------------------- -/** Tracking allocator to verify allocator usage. -*/ -template -struct tracking_allocator -{ - using value_type = T; - - std::shared_ptr> alloc_count; - std::shared_ptr> dealloc_count; - - tracking_allocator() - : alloc_count(std::make_shared>(0)) - , dealloc_count(std::make_shared>(0)) - { - } - - template - tracking_allocator(tracking_allocator const& other) - : alloc_count(other.alloc_count) - , dealloc_count(other.dealloc_count) - { - } - - T* allocate(std::size_t n) - { - ++(*alloc_count); - return static_cast(::operator new(n * sizeof(T))); - } - - void deallocate(T* p, std::size_t) - { - ++(*dealloc_count); - ::operator delete(p); - } - - template - bool operator==(tracking_allocator const& other) const - { - return alloc_count == other.alloc_count; - } - - template - bool operator!=(tracking_allocator const& other) const - { - return !(*this == other); - } -}; - -/** Executor wrapper that associates an allocator. -*/ -template -struct executor_with_allocator -{ - Executor exec_; - Allocator alloc_; - - using inner_executor_type = Executor; - - executor_with_allocator( - Executor ex, - Allocator alloc) - : exec_(std::move(ex)) - , alloc_(std::move(alloc)) - { - } - - Executor const& get_inner_executor() const noexcept - { - return exec_; - } - - Allocator get_allocator() const noexcept - { - return alloc_; - } - - // Forward executor operations - auto context() const noexcept -> decltype(exec_.context()) - { - return exec_.context(); - } - - void on_work_started() const noexcept - { - exec_.on_work_started(); - } - - void on_work_finished() const noexcept - { - exec_.on_work_finished(); - } - - template - void dispatch(F&& f, A const& a) const - { - exec_.dispatch(std::forward(f), a); - } - - template - void post(F&& f, A const& a) const - { - exec_.post(std::forward(f), a); - } - - template - void defer(F&& f, A const& a) const - { - exec_.defer(std::forward(f), a); - } - - bool operator==(executor_with_allocator const& other) const noexcept - { - return exec_ == other.exec_; - } - - bool operator!=(executor_with_allocator const& other) const noexcept - { - return exec_ != other.exec_; - } -}; - -} // beast2 - -// Specialize associated_allocator for our wrapper -namespace asio { - -template -struct associated_allocator< - beast2::executor_with_allocator, - DefaultAllocator> -{ - using type = Allocator; - - static type get( - beast2::executor_with_allocator const& ex, - DefaultAllocator const& = DefaultAllocator()) noexcept - { - return ex.get_allocator(); - } -}; - -} // asio - -namespace beast2 { - -//----------------------------------------------------------------------------- - struct wrap_executor_test { void testWrapExecutor() { + // Verify wrap_executor returns a type that satisfies dispatcher concept asio::io_context ioc; - capy::executor ex = wrap_executor(ioc.get_executor()); - BOOST_TEST(static_cast(ex)); - } - - void - testPostLambda() - { - asio::io_context ioc; - capy::executor ex = wrap_executor(ioc.get_executor()); - - bool called = false; - ex.post([&called]{ called = true; }); - - BOOST_TEST(!called); - test::run(ioc); - BOOST_TEST(called); + auto dispatcher = wrap_executor(ioc.get_executor()); + + // Should be storable in any_dispatcher + capy::any_dispatcher any_disp(dispatcher); + BOOST_TEST(static_cast(any_disp)); } void - testPostMultiple() + testDispatchCoroutine() { asio::io_context ioc; - capy::executor ex = wrap_executor(ioc.get_executor()); - - int count = 0; - ex.post([&count]{ ++count; }); - ex.post([&count]{ ++count; }); - ex.post([&count]{ ++count; }); - - BOOST_TEST_EQ(count, 0); - test::run(ioc); - BOOST_TEST_EQ(count, 3); - } - - void - testPostWithCapture() - { - asio::io_context ioc; - capy::executor ex = wrap_executor(ioc.get_executor()); - - int result = 0; - int a = 10, b = 20; - ex.post([&result, a, b]{ result = a + b; }); - - test::run(ioc); - BOOST_TEST_EQ(result, 30); - } - - void - testPostWithMoveOnlyCapture() - { - struct callable - { - int& result; - std::unique_ptr ptr; - - void operator()() - { - result = *ptr; - } + + bool resumed = false; + + // Create and run a simple coroutine + auto make_task = [&resumed]() -> capy::task { + resumed = true; + co_return; }; - - asio::io_context ioc; - capy::executor ex = wrap_executor(ioc.get_executor()); - - int result = 0; - std::unique_ptr ptr(new int(42)); - ex.post(callable{result, std::move(ptr)}); - - test::run(ioc); - BOOST_TEST_EQ(result, 42); - } - - void - testCopyExecutor() - { - asio::io_context ioc; - capy::executor exec1 = wrap_executor(ioc.get_executor()); - capy::executor exec2 = exec1; - - int count = 0; - exec1.post([&count]{ ++count; }); - exec2.post([&count]{ ++count; }); - - test::run(ioc); - BOOST_TEST_EQ(count, 2); - } - - void - testMoveExecutor() - { - asio::io_context ioc; - capy::executor exec1 = wrap_executor(ioc.get_executor()); - capy::executor exec2 = std::move(exec1); - - bool called = false; - exec2.post([&called]{ called = true; }); - + + // Launch using async_run with our dispatcher + capy::async_run(wrap_executor(ioc.get_executor()))(make_task()); + + // Coroutine should not have run yet (just scheduled) + BOOST_TEST(!resumed); + + // Run the io_context to execute posted work test::run(ioc); - BOOST_TEST(called); + + // Now it should have run + BOOST_TEST(resumed); } void @@ -283,314 +69,74 @@ struct wrap_executor_test asio::io_context ioc; asio::strand strand( ioc.get_executor()); - capy::executor ex = wrap_executor(strand); - - int count = 0; - ex.post([&count]{ ++count; }); - ex.post([&count]{ ++count; }); - - test::run(ioc); - BOOST_TEST_EQ(count, 2); - } - - void - testAssociatedAllocator() - { - asio::io_context ioc; - tracking_allocator alloc; - - executor_with_allocator< - asio::io_context::executor_type, - tracking_allocator> wrapped_exec( - ioc.get_executor(), alloc); - - capy::executor ex = wrap_executor(wrapped_exec); - - bool called = false; - ex.post([&called]{ called = true; }); - - // Before running, allocation should have happened - BOOST_TEST_GT(*alloc.alloc_count, 0); - - test::run(ioc); - BOOST_TEST(called); - - // After running, deallocation should have happened - BOOST_TEST_GT(*alloc.dealloc_count, 0); - BOOST_TEST_EQ(*alloc.alloc_count, *alloc.dealloc_count); + auto dispatcher = wrap_executor(strand); + + // Should be storable in any_dispatcher + capy::any_dispatcher any_disp(dispatcher); + BOOST_TEST(static_cast(any_disp)); } void - testAsyncPostNonVoid() - { - asio::io_context ioc; - capy::executor ex = wrap_executor(ioc.get_executor()); - - int result = 0; - bool handler_called = false; - - ex.submit( - []{ return 42; }, - [&](system::result r) - { - handler_called = true; - if(r.has_value()) - result = r.value(); - }); - - test::run(ioc); - BOOST_TEST(handler_called); - BOOST_TEST_EQ(result, 42); - } - - void - testAsyncPostVoid() - { - asio::io_context ioc; - capy::executor ex = wrap_executor(ioc.get_executor()); - - bool work_called = false; - bool handler_called = false; - - ex.submit( - [&work_called]{ work_called = true; }, - [&handler_called](system::result) - { - handler_called = true; - }); - - test::run(ioc); - BOOST_TEST(work_called); - BOOST_TEST(handler_called); - } - - void - testAsyncPostException() - { - asio::io_context ioc; - capy::executor ex = wrap_executor(ioc.get_executor()); - - bool handler_called = false; - bool got_exception = false; - - ex.submit( - []() -> int { throw std::runtime_error("test"); }, - [&](system::result r) - { - handler_called = true; - if(r.has_error()) - got_exception = true; - }); - - test::run(ioc); - BOOST_TEST(handler_called); - BOOST_TEST(got_exception); - } - - // Test that mimics route_params storage pattern - void - testStoredInStruct() + testAnyDispatcherStorage() { + // Test pattern used in http route_params struct params { - capy::executor ex; + capy::any_dispatcher ex; }; asio::io_context ioc; params p; p.ex = wrap_executor(ioc.get_executor()); - - bool called = false; - p.ex.post([&called]{ called = true; }); - - test::run(ioc); - BOOST_TEST(called); - } - - // Test executor survives struct move - void - testStoredInStructAfterMove() - { - struct params - { - capy::executor ex; - int dummy = 0; - }; - - asio::io_context ioc; - params p1; - p1.ex = wrap_executor(ioc.get_executor()); - - // Move the struct - params p2 = std::move(p1); - - bool called = false; - p2.ex.post([&called]{ called = true; }); - - test::run(ioc); - BOOST_TEST(called); + + BOOST_TEST(static_cast(p.ex)); } - // Test executor assignment (not construction) void - testAssignment() + testAnyDispatcherCopy() { asio::io_context ioc; - capy::executor ex; - BOOST_TEST(!static_cast(ex)); - - ex = wrap_executor(ioc.get_executor()); - BOOST_TEST(static_cast(ex)); - - bool called = false; - ex.post([&called]{ called = true; }); - - test::run(ioc); - BOOST_TEST(called); - } - - // Test multiple assignments - void - testReassignment() - { - asio::io_context ioc1; - asio::io_context ioc2; - - capy::executor ex = wrap_executor(ioc1.get_executor()); - - bool called1 = false; - ex.post([&called1]{ called1 = true; }); - test::run(ioc1); - BOOST_TEST(called1); - - // Reassign to different executor - ex = wrap_executor(ioc2.get_executor()); - - bool called2 = false; - ex.post([&called2]{ called2 = true; }); - test::run(ioc2); - BOOST_TEST(called2); + auto dispatcher = wrap_executor(ioc.get_executor()); + + capy::any_dispatcher disp1(dispatcher); + capy::any_dispatcher disp2 = disp1; + + BOOST_TEST(static_cast(disp1)); + BOOST_TEST(static_cast(disp2)); + BOOST_TEST(disp1 == disp2); } - // Test that the underlying asio executor is valid void - testUnderlyingExecutorValid() + testMultipleCoroutines() { asio::io_context ioc; - auto asio_exec = ioc.get_executor(); - - // Wrap it - capy::executor ex = wrap_executor(asio_exec); - - // Post work multiple times to stress test + int count = 0; - for(int i = 0; i < 100; ++i) - { - ex.post([&count]{ ++count; }); - } - - test::run(ioc); - BOOST_TEST_EQ(count, 100); - } - - // Test wrap_executor with temporary executor - void - testWrapTemporary() - { - asio::io_context ioc; - - // Wrap a temporary - this is how it's used in http_stream - capy::executor ex = wrap_executor(ioc.get_executor()); - - bool called = false; - ex.post([&called]{ called = true; }); - - test::run(ioc); - BOOST_TEST(called); - } - - // Test that asio_executor_impl is properly initialized - void - testExecutorImplInitialized() - { - asio::io_context ioc; - - // Create the wrapper directly to test initialization - detail::asio_executor_impl impl( - ioc.get_executor()); - - // The impl should be usable - create work and submit it - struct test_work : capy::executor::work - { - bool& called; - explicit test_work(bool& c) : called(c) {} - void invoke() override { called = true; } + + auto make_task = [&count]() -> capy::task { + ++count; + co_return; }; - - bool called = false; - - // Allocate, construct, and submit work - void* storage = capy::executor::access::allocate( - impl, sizeof(test_work), alignof(test_work)); - test_work* w = ::new(storage) test_work(called); - capy::executor::access::submit(impl, w); - - test::run(ioc); - BOOST_TEST(called); - } - - // Test that moved asio_executor_impl is valid - void - testExecutorImplAfterMove() - { - asio::io_context ioc; - - detail::asio_executor_impl impl1( - ioc.get_executor()); - - // Move the impl - auto impl2 = std::move(impl1); - - struct test_work : capy::executor::work - { - bool& called; - explicit test_work(bool& c) : called(c) {} - void invoke() override { called = true; } - }; - - bool called = false; - void* storage = capy::executor::access::allocate( - impl2, sizeof(test_work), alignof(test_work)); - test_work* w = ::new(storage) test_work(called); - capy::executor::access::submit(impl2, w); - + + // Launch multiple coroutines + capy::async_run(wrap_executor(ioc.get_executor()))(make_task()); + capy::async_run(wrap_executor(ioc.get_executor()))(make_task()); + capy::async_run(wrap_executor(ioc.get_executor()))(make_task()); + + BOOST_TEST_EQ(count, 0); test::run(ioc); - BOOST_TEST(called); + BOOST_TEST_EQ(count, 3); } void run() { testWrapExecutor(); - testPostLambda(); - testPostMultiple(); - testPostWithCapture(); - testPostWithMoveOnlyCapture(); - testCopyExecutor(); - testMoveExecutor(); + testDispatchCoroutine(); testWithStrand(); - testAssociatedAllocator(); - testAsyncPostNonVoid(); - testAsyncPostVoid(); - testAsyncPostException(); - testStoredInStruct(); - testStoredInStructAfterMove(); - testAssignment(); - testReassignment(); - testUnderlyingExecutorValid(); - testWrapTemporary(); - testExecutorImplInitialized(); - testExecutorImplAfterMove(); + testAnyDispatcherStorage(); + testAnyDispatcherCopy(); + testMultipleCoroutines(); } }; @@ -600,4 +146,3 @@ TEST_SUITE( } // beast2 } // boost - diff --git a/test/unit/write.cpp b/test/unit/write.cpp index 88b64c52..59652f6d 100644 --- a/test/unit/write.cpp +++ b/test/unit/write.cpp @@ -60,9 +60,9 @@ namespace beast2 { #include #include #include -#include -#include -#include +#include +#include +#include #include #include @@ -105,7 +105,7 @@ class write_test sr.reset(); http::response res(headers); - sr.start(res, buffers::const_buffer(body.data(), body.size())); + sr.start(res, capy::const_buffer(body.data(), body.size())); for(std::size_t total = 0; total < msg.size(); total++) { @@ -136,7 +136,7 @@ class write_test sr.reset(); http::response res(headers); - sr.start(res, buffers::const_buffer(body.data(), body.size())); + sr.start(res, capy::const_buffer(body.data(), body.size())); for(int count = 0; count < 3; count++) { @@ -182,7 +182,7 @@ class write_test sr.reset(); http::response res(headers); - sr.start(res, buffers::const_buffer(body.data(), body.size())); + sr.start(res, capy::const_buffer(body.data(), body.size())); // async_read_some cancels after reading 0 bytes async_write_some( @@ -222,7 +222,7 @@ class write_test sr.reset(); http::response res(headers); - sr.start(res, buffers::const_buffer(body.data(), body.size())); + sr.start(res, capy::const_buffer(body.data(), body.size())); async_write( ts, @@ -251,7 +251,7 @@ class write_test sr.reset(); http::response res(headers); - sr.start(res, buffers::const_buffer(body.data(), body.size())); + sr.start(res, capy::const_buffer(body.data(), body.size())); async_write( ts, @@ -287,7 +287,7 @@ class write_test sr.reset(); http::response res(headers); - sr.start(res, buffers::const_buffer(body.data(), body.size())); + sr.start(res, capy::const_buffer(body.data(), body.size())); // cancel after writing async_write( From 060c66a1509d98aaeda2e9ca8eafa6e516053db9 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Mon, 12 Jan 2026 20:37:11 -0800 Subject: [PATCH 30/40] chore: add build output policy and disable coro routes temporarily - Add .cursor/rules/build-outputs.mdc to enforce .temp/ directory for all build artifacts - Disable coroutine route handlers in example server with #if 0 (was #ifdef BOOST_BEAST2_HAS_CORO) --- .cursor/rules/build-outputs.mdc | 50 +++++++++++++++++++++++++++++++++ example/server/main.cpp | 4 +-- 2 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 .cursor/rules/build-outputs.mdc diff --git a/.cursor/rules/build-outputs.mdc b/.cursor/rules/build-outputs.mdc new file mode 100644 index 00000000..0f8c7dfb --- /dev/null +++ b/.cursor/rules/build-outputs.mdc @@ -0,0 +1,50 @@ +--- +description: Build outputs must go in .temp directory +globs: +alwaysApply: true +--- + +# Build Output Directory Policy + +**All build artifacts, intermediate files, and temporary outputs MUST go in the `.temp/` directory.** + +## Required Directory Structure + +- `.temp/build-msvc/` - MSVC/Visual Studio builds +- `.temp/build-gcc/` - GCC builds (via MSYS2) +- `.temp/build-clang/` - Clang builds +- `.temp/build-ninja/` - Ninja builds +- `.temp/` - Any other temporary or generated files + +## Rules + +1. **Never create executables, object files, or build artifacts in source directories** +2. **Never commit anything from `.temp/`** - it is in `.gitignore` +3. When configuring CMake, always use `-B .temp/build-` +4. When running direct compiler commands, output to `.temp/build-/` + +## Example Commands + +### CMake (MSVC) +```powershell +cmake -B .temp/build-msvc -G "Visual Studio 18 2026" -A x64 +cmake --build .temp/build-msvc --config Release +``` + +### CMake (GCC via MSYS2) +```bash +cmake -B .temp/build-gcc -G "Unix Makefiles" +cmake --build .temp/build-gcc +``` + +### Direct GCC compilation +```bash +mkdir -p .temp/build-gcc +g++ -std=c++20 -O3 src/main.cpp -o .temp/build-gcc/main.exe +``` + +### Direct MSVC compilation +```powershell +New-Item -ItemType Directory -Force -Path .temp/build-msvc +cl /std:c++20 /O2 src/main.cpp /Fe:.temp/build-msvc/main.exe +``` diff --git a/example/server/main.cpp b/example/server/main.cpp index f431461a..36376b75 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -124,7 +124,7 @@ struct do_json_rpc }; -#ifdef BOOST_BEAST2_HAS_CORO +#if 0 auto my_coro( http::route_params& rp) -> @@ -193,7 +193,7 @@ int server_main( int argc, char* argv[] ) http::cors(opts), do_json_rpc() ); -#ifdef BOOST_BEAST2_HAS_CORO +#if 0 srv.wwwroot.use( "/spawn", http::co_route(my_coro)); From 4478c990e584b4989d78b487ee6bddccdffd97fd Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Mon, 12 Jan 2026 20:46:26 -0800 Subject: [PATCH 31/40] refactor: remove SSL/OpenSSL support from beast2 - Remove ssl_stream.hpp, worker_ssl.hpp, https_server.hpp/cpp - Remove certificate.hpp/cpp SSL certificate utilities - Remove ssl_stream.cpp, https_server.cpp, stream.cpp tests - Remove OpenSSL dependency from CMakeLists.txt and Jamfile - Remove BOOST_BEAST2_HAS_CORO macro from config.hpp - Remove signal_set handling from asio_io_context.cpp - Clean up commented SSL includes in beast2.hpp - Remove conditional coro guards from body_write_stream.cpp --- example/server/CMakeLists.txt | 8 - example/server/Jamfile | 12 - example/server/certificate.cpp | 138 ---------- example/server/certificate.hpp | 36 --- example/server/main.cpp | 1 - include/boost/beast2.hpp | 3 - include/boost/beast2/detail/config.hpp | 8 - include/boost/beast2/server/http_stream.hpp | 2 - include/boost/beast2/server/https_server.hpp | 32 --- include/boost/beast2/server/worker_ssl.hpp | 269 ------------------- include/boost/beast2/ssl_stream.hpp | 196 -------------- src/asio_io_context.cpp | 19 -- src/server/https_server.cpp | 23 -- test/unit/CMakeLists.txt | 2 - test/unit/Jamfile | 23 +- test/unit/body_write_stream.cpp | 6 - test/unit/server/https_server.cpp | 28 -- test/unit/ssl_stream.cpp | 35 --- test/unit/stream.cpp | 105 -------- 19 files changed, 1 insertion(+), 945 deletions(-) delete mode 100644 example/server/certificate.cpp delete mode 100644 example/server/certificate.hpp delete mode 100644 include/boost/beast2/server/https_server.hpp delete mode 100644 include/boost/beast2/server/worker_ssl.hpp delete mode 100644 include/boost/beast2/ssl_stream.hpp delete mode 100644 src/server/https_server.cpp delete mode 100644 test/unit/server/https_server.cpp delete mode 100644 test/unit/ssl_stream.cpp delete mode 100644 test/unit/stream.cpp diff --git a/example/server/CMakeLists.txt b/example/server/CMakeLists.txt index 9f19ed63..e8578e01 100644 --- a/example/server/CMakeLists.txt +++ b/example/server/CMakeLists.txt @@ -21,21 +21,14 @@ target_compile_definitions(beast2_server_example set_property(TARGET beast2_server_example PROPERTY FOLDER "examples") -find_package(OpenSSL REQUIRED) target_include_directories(beast2_server_example PRIVATE .) target_link_libraries( beast2_server_example Boost::beast2 Boost::json Boost::url - OpenSSL::SSL - OpenSSL::Crypto ) -if (WIN32) - target_link_libraries(beast2_server_example crypt32) -endif() - if (TARGET Boost::capy_zlib) target_link_libraries(beast2_server_example Boost::capy_zlib) endif() @@ -43,4 +36,3 @@ endif() if (TARGET Boost::capy_brotli) target_link_libraries(beast2_server_example Boost::capy_brotli) endif() - diff --git a/example/server/Jamfile b/example/server/Jamfile index 960d0c9b..d9e15c1c 100644 --- a/example/server/Jamfile +++ b/example/server/Jamfile @@ -8,14 +8,8 @@ # Official repository: https://github.com/CPPAlliance/http # -using openssl ; import ac ; -lib advapi32 ; -lib crypt32 ; -lib gdi32 ; -lib user32 ; - project : requirements /boost/beast2//boost_beast2 @@ -23,12 +17,6 @@ project /boost/json//boost_json [ ac.check-library /boost/capy//boost_capy_zlib : /boost/capy//boost_capy_zlib : ] [ ac.check-library /boost/capy//boost_capy_brotli : /boost/capy//boost_capy_brotli : ] - [ ac.check-library /openssl//ssl : /openssl//ssl/shared : no ] - [ ac.check-library /openssl//crypto : /openssl//crypto/shared : no ] - windows:advapi32 - windows:crypt32 - windows:gdi32 - windows:user32 . ; diff --git a/example/server/certificate.cpp b/example/server/certificate.cpp deleted file mode 100644 index 1e3e4dd4..00000000 --- a/example/server/certificate.cpp +++ /dev/null @@ -1,138 +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 "certificate.hpp" -#include -#include - -namespace boost { -namespace beast2 { - -void -load_server_certificate( - asio::ssl::context& ctx) -{ -/* - Using Windows with OpenSSL version "1.1.1s 1 Nov 2022" - - 1. Generate a Root CA - - openssl genrsa -out rootCA.key 4096 - openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 10000 -out rootCA.pem -subj "//CN=Boost Test Root CA" - - 2. Create a Server Key - - openssl genrsa -out server.key 2048 -*/ - using keypair = std::pair; - - // certificates for SSL listening ports - keypair const certs[] = { -{ -/* Create a Signed Server Certificate with the Test Root CA - - openssl req -new -key server.key -out server.csr -subj "//CN=localhost" - openssl x509 -req -in server.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out server.crt -days 10000 -sha256 -extfile san.cnf -*/ - "-----BEGIN CERTIFICATE-----\n" - "MIID2TCCAcGgAwIBAgIURQ6waOrVlt/YykIgwb+46o0UtCUwDQYJKoZIhvcNAQEL\n" - "BQAwHTEbMBkGA1UEAwwSQm9vc3QgVGVzdCBSb290IENBMCAXDTI1MTAxMjAxMjIx\n" - "N1oYDzIwNTMwMjI3MDEyMjE3WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0G\n" - "CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkHGuSplDQrW5f0JJwUK9UivHZMkE5\n" - "CDEThsUwjrqubofLpR49EzfAcBRWWQ1R0QmzK2sKnKQnku4IliFyitw/OAHsJrr4\n" - "c3OHXfpOOwtd4Kg3BP3P3oeAsO+IELrQIsJp/mrjOJKtBVTZ8kl5ZYrf94fEMivn\n" - "JnZ+neb2kPiSPTnAtFSBVSQc9aHU7Wg1gtQkUuEIkjUBvPzxGKi0m3nuZfUDJpev\n" - "2OB7fRftIPjiqZ/1n1k2CYLqLMBIXAeeYAjBgzM0x4UG3SW7jlPeoDI34XQ7dYxQ\n" - "K5jjs3OhoLs5x0za1sZ7MXkDRAqO5Cgeg3kNb5VlhjVzR8Njtapx4mXtAgMBAAGj\n" - "GDAWMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAgEAjEHn\n" - "AIfxiYXWBsVPtYsRbHzWYoNSIWGkzwauMaDPDGzeMV1ajKV1dBp8NHKBg/jlKdaQ\n" - "vGiKHLlPwSGRlDIyyglG1qsH7unQHV0w+cCh19Uoc0gtv9q4zTUuyDhk8eufIdEr\n" - "1SPNMIqJQ47A2KrYo31rd+HxnyoCit1fM8SUWwM0tLaoEM3iTF33LI5CBkT2VPbv\n" - "qJL/68qQFDeTIUGQJjPK9rs/cElqsweVfWF+O2Mn9wA9aJyc5+0jsOGrfADO/cI1\n" - "OA1HXgawSQjXKST74aprd0gBxACXrG+wA1G6NNsawp80xjADSTyCfNxtQVvY1W2n\n" - "9kJuionCnDUnhqEOnhnZdq1XikoOYMRJP0BxFHX8SNbxmJouJRWvusmh91lAgbQv\n" - "JVXlHcEQGYMIYIr5y6dLlZ55SamC9aK3vO+q8s9AEYZO4OTUi2WQ2bfrxcn3vI/0\n" - "UzLh8LzE1A2OT5Gin0jWKbeKpqXH2RAfgaEAf1bzDA92xMEjVGBrBoXagvUOGzMG\n" - "cDcj4WHzcO6BWENPkRZ2JNNIxIZRK/wr9Mw/q/Yu9iDNVw4XOEx+iGjh1cRHy1FH\n" - "VuS/N5CQCBnmhDKYrZ3Lz+D/l0CGc30A1jjfvlrDiE4k9NyrV8wVHqmO73vivAvW\n" - "hYG9wLo13LnWG9JrtT8Drl/H0YbuL4C46n/mua8=\n" - "-----END CERTIFICATE-----\n", - - "-----BEGIN RSA PRIVATE KEY-----\n" - "MIIEpgIBAAKCAQEA5BxrkqZQ0K1uX9CScFCvVIrx2TJBOQgxE4bFMI66rm6Hy6Ue\n" - "PRM3wHAUVlkNUdEJsytrCpykJ5LuCJYhcorcPzgB7Ca6+HNzh136TjsLXeCoNwT9\n" - "z96HgLDviBC60CLCaf5q4ziSrQVU2fJJeWWK3/eHxDIr5yZ2fp3m9pD4kj05wLRU\n" - "gVUkHPWh1O1oNYLUJFLhCJI1Abz88RiotJt57mX1AyaXr9jge30X7SD44qmf9Z9Z\n" - "NgmC6izASFwHnmAIwYMzNMeFBt0lu45T3qAyN+F0O3WMUCuY47NzoaC7OcdM2tbG\n" - "ezF5A0QKjuQoHoN5DW+VZYY1c0fDY7WqceJl7QIDAQABAoIBAQCfXyvZPdHgugsP\n" - "bk2hov2cd6cZNH9VNV/0YIiMsGvFSvwdT7OcwDyHesb6vSUNMJsyTvduZppZ+9HK\n" - "tfmQaWwPzzWopDalNyRUQ1iKJ759TGS6bAZYoQTS6MuxqN6cZGyoWVScg/4WXE84\n" - "JosnAcbRS8PTU6pQyRKoy/F9+zNwF30B0GwQpNwEZMX7QvPQPoUIcurE8HmP7NhB\n" - "fuZn/af6q9N+D56fOaD7Rg4kdtBMa0cv4mKojaq3ymx5RK8ccPTDiXC0idg7uRVC\n" - "13sFf9H46Z7kIimyhPUrwTapMyrx1kjQNZa97YYBAHGfLwRoYsGu5XMWIXKDkZHz\n" - "Bt2U92iBAoGBAPJRdRqoHdZIe42rq1kEpIis4Eitn4idjuW6D8kgzPU8IT7UreT/\n" - "eS5c7ThnbwI3B966pHWzAPMiktc3sXQUIXlBy1vu1A6paqU+HpA9pbwB3tHfLsgm\n" - "akabqfv/nWpzKJt5kLlbNOkkCvJ8FvotyeCCrdCeip+q6d8NZc7s9YQZAoGBAPD9\n" - "m5nUHKR7tcE8SNc9pcBp1AoILuhp9XbUgP2YkePO8KfnHPUSU5LIT0c1sasm1k/w\n" - "ehpaqirjSAjYkjKQTfwj4SVP9LAlCb5kz0rhZJqdiQUtSK3oPIDzcXfutgZjRbyX\n" - "vR9r3a2DLoYJ/IxuajyvICImKSMHEySnCoxaEgr1AoGBALI2VGC5edAp2Kx1r/w1\n" - "HOjj88Of5a+s6PZtY8SxGevWQEEcW5QKi84cS97qu0quvFwDeoaRksY+DC66aAkN\n" - "8Rxj1jMTr+Pkl2lWCVZd8HEYEw7ZDGfpUMoDG/4YnWY3sYq+2kBoIr7AYki6GJAA\n" - "cvNqSHkg0KTjJ0ODb/fCcEKpAoGBALUX3rXaDywLSqnLA3G7gbL108E2JQnBlhOV\n" - "3Ni0rezitTV3FuuSufqzS9/XGYvjw2iO7TKgrv9Li/YZyML2baPr0mSXkOhM7OWG\n" - "G7/JYDBP8YdSYCtPOSgtyDa3y1FBiEYQQK48AHlC+tL+7ikZT/wKHbuLsZ4A0wHY\n" - "BLUzehuBAoGBAMC9mGnaYU4D9PjCqC96MyEnZjdpf5eGByx5GhV6O6kEx0wJbjPc\n" - "kq+QAn1cp1tVjPcNfOK62LxV5E5t4eCv/Su5dmQHWGVDZBrdug5osyLFaFZA8Lw/\n" - "GKCs22hqgjPqSrGnCXMV6bZkwvy3cmxNkWuEiiA67PYyc6aFpTcOj8fi\n" - "-----END RSA PRIVATE KEY-----\n" -} - }; - -/* More secure Diffie-Helman parameters - - openssl dhparam -out dh.pem 2048 -*/ - std::string const dh = - "-----BEGIN DH PARAMETERS-----\n" - "MIIBCAKCAQEAu7R9qRNtiuayUH9FLFIIQJ9GmhKpdL/gcLG8+5/6x+RN+cgPwQgQ\n" - "FYqTtIHRgINxtxdZqUxnrcg6jbW13r7b8A7uWURsrW5T3Hy68v4SFY5F+c/a97m+\n" - "LyUHW12iwCqZPlwdl4Zvb/uAtrn3xjvl3Buea4nGPAlTlHVKR1OH8IuWPnxUvjXp\n" - "slcI5c20LQ3Z2znM3csLNGkgiGKIPLCb9Sq8Zx1+gCDQk9DjDC4K8ELDqvbwDz8m\n" - "760pgC5eQ0z1lgmxvRVgPZOx9twwO1/VhpISpGnb7vihEb+06jQtXZIC3LrANfhe\n" - "bnbac08nYv9yt7Caf2Zfy1UDvkeLtPYs2wIBAg==\n" - "-----END DH PARAMETERS-----\n"; - - ctx.set_password_callback( - [](std::size_t, - asio::ssl::context_base::password_purpose) - { - return "test"; - }); - - ctx.set_options( - asio::ssl::context::default_workarounds | - asio::ssl::context::no_sslv2 | - asio::ssl::context::single_dh_use); - - for(auto const& t : certs) - { - ctx.use_certificate_chain(asio::buffer(t.first)); - - // use_private_key applies to the last inserted certificate, - // see: https://linux.die.net/man/3/ssl_ctx_use_privatekey - // - ctx.use_private_key(asio::buffer(t.second), - asio::ssl::context::file_format::pem); - } - - ctx.use_tmp_dh(asio::buffer(dh)); -} - -} // beast2 -} // boost diff --git a/example/server/certificate.hpp b/example/server/certificate.hpp deleted file mode 100644 index 5bb4f7c9..00000000 --- a/example/server/certificate.hpp +++ /dev/null @@ -1,36 +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_EXAMPLE_SERVER_CERTIFICATE_HPP -#define BOOST_BEAST2_EXAMPLE_SERVER_CERTIFICATE_HPP - -#include -#include -#include -#include - -namespace boost { -namespace beast2 { - -/* Load a signed certificate into the ssl context, and configure - the context for use with a server. - - For this to work with the browser or operating system, it is - necessary to import the certificate in the file "beast-test-CA.crt", - which accompanies the library, into the local certificate store, - browser, or operating system depending on your environment. -*/ -void -load_server_certificate( - asio::ssl::context& ctx); - -} // beast2 -} // boost - -#endif diff --git a/example/server/main.cpp b/example/server/main.cpp index 36376b75..e3d7fec2 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -7,7 +7,6 @@ // Official repository: https://github.com/cppalliance/beast2 // -#include "certificate.hpp" #include "serve_detached.hpp" #include "serve_log_admin.hpp" #include diff --git a/include/boost/beast2.hpp b/include/boost/beast2.hpp index d7de8709..f7f31cf1 100644 --- a/include/boost/beast2.hpp +++ b/include/boost/beast2.hpp @@ -15,14 +15,12 @@ #include #include #include -//#include #include #include #include #include #include #include -//#include #include #include #include @@ -40,7 +38,6 @@ #include #include #include -//#include #include #endif diff --git a/include/boost/beast2/detail/config.hpp b/include/boost/beast2/detail/config.hpp index e671fc9c..0a5f9a88 100644 --- a/include/boost/beast2/detail/config.hpp +++ b/include/boost/beast2/detail/config.hpp @@ -38,14 +38,6 @@ namespace beast2 { //------------------------------------------------ -#if defined(__cpp_lib_coroutine) && __cpp_lib_coroutine >= 201902L -# define BOOST_BEAST2_HAS_CORO 1 -#elif defined(__cpp_impl_coroutine) && __cpp_impl_coroutines >= 201902L -# define BOOST_BEAST2_HAS_CORO 1 -#endif - -//------------------------------------------------ - // Add source location to error codes #ifdef BOOST_BEAST2_NO_SOURCE_LOCATION # define BOOST_BEAST2_ERR(ev) (::boost::system::error_code(ev)) diff --git a/include/boost/beast2/server/http_stream.hpp b/include/boost/beast2/server/http_stream.hpp index e2dcb2db..233cbc6c 100644 --- a/include/boost/beast2/server/http_stream.hpp +++ b/include/boost/beast2/server/http_stream.hpp @@ -424,8 +424,6 @@ do_suspend() -> BOOST_ASSERT(! pwg_); pwg_.reset(new work_guard(stream_.get_executor())); - // VFALCO cancel timer - return http::resumer(*this); } diff --git a/include/boost/beast2/server/https_server.hpp b/include/boost/beast2/server/https_server.hpp deleted file mode 100644 index 640f072d..00000000 --- a/include/boost/beast2/server/https_server.hpp +++ /dev/null @@ -1,32 +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_HTTPS_SERVER_HPP -#define BOOST_BEAST2_SERVER_HTTPS_SERVER_HPP - -#include -//#include -#include - -namespace boost { -namespace beast2 { - -class https_server -{ -public: - BOOST_BEAST2_DECL - https_server(); - - -}; - -} // beast2 -} // boost - -#endif diff --git a/include/boost/beast2/server/worker_ssl.hpp b/include/boost/beast2/server/worker_ssl.hpp deleted file mode 100644 index 52125fa6..00000000 --- a/include/boost/beast2/server/worker_ssl.hpp +++ /dev/null @@ -1,269 +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_WORKER_SSL_HPP -#define BOOST_BEAST2_SERVER_WORKER_SSL_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace boost { - -namespace asio { -namespace ip { -class tcp; // forward declaration -} // ip -} // asio - -namespace beast2 { - -template< - class Executor, - class Protocol = asio::ip::tcp -> -class worker_ssl - : private boost::base_from_member< - ssl_stream>> - , public http_stream< - ssl_stream>> -{ - using base_member = boost::base_from_member< - ssl_stream>>; - -public: - using executor_type = Executor; - using protocol_type = Protocol; - using socket_type = - asio::basic_stream_socket; - using stream_type = ssl_stream; - - using acceptor_config = beast2::acceptor_config; - - template< - class Executor0, - class = typename std::enable_if::value>::type - > - worker_ssl( - workers_base& wb, - Executor0 const& ex, - asio::ssl::context& ssl_ctx, - router_asio rr); - - application& app() noexcept - { - return wb_.app(); - } - - socket_type& socket() noexcept - { - return this->member.next_layer(); - } - - typename Protocol::endpoint& - endpoint() noexcept - { - return ep_; - } - - /** Cancel all outstanding I/O - */ - void cancel(); - - // Called when an incoming connection is accepted - void on_accept(acceptor_config const* pconfig); - - void on_handshake( - acceptor_config const* pconfig, - system::error_code const& ec); - - void on_shutdown(system::error_code ec); - - void do_fail( - core::string_view s, system::error_code const& ec); - - void reset(); - -private: - /** Called when the logical session ends - */ - void do_close(system::error_code const& ec); - - workers_base& wb_; - asio::ssl::context& ssl_ctx_; - typename Protocol::endpoint ep_; -}; - -//------------------------------------------------ - -template -template -worker_ssl:: -worker_ssl( - workers_base& wb, - Executor0 const& ex, - asio::ssl::context& ssl_ctx, - router_asio rr) - : base_member(Executor(ex), ssl_ctx) - , http_stream( - wb.app(), - this->member, - std::move(rr), - [this](system::error_code const& ec) - { - this->do_close(ec); - }) - , wb_(wb) - , ssl_ctx_(ssl_ctx) -{ -} - -template -void -worker_ssl:: -cancel() -{ - system::error_code ec; - this->member.next_layer().cancel(ec); -} - -//-------------------------------------------- - -// Called when an incoming connection is accepted -template -void -worker_ssl:: -on_accept(acceptor_config const* pconfig) -{ - BOOST_ASSERT(this->member.get_executor().running_in_this_thread()); - // VFALCO TODO timeout - this->member.set_ssl(pconfig->is_ssl); - if(! pconfig->is_ssl) - return this->on_accept(*pconfig); - return this->member.stream().async_handshake( - asio::ssl::stream_base::server, - asio::prepend(call_mf( - &worker_ssl::on_handshake, this), pconfig)); -} - -template -void -worker_ssl:: -on_handshake( - acceptor_config const* pconfig, - system::error_code const& ec) -{ - if(ec.failed()) - return do_fail("worker_ssl::on_handshake", ec); - - LOG_TRC(this->sect_)( - "{} worker_ssl::on_handshake", - this->id()); - - this->on_accept(*pconfig); -} - -template -void -worker_ssl:: -on_shutdown(system::error_code ec) -{ - if(ec.failed()) - return do_fail("worker_ssl::on_shutdown", ec); - - LOG_TRC(this->sect_)( - "{} worker_ssl::on_shutdown", - this->id()); - - this->member.next_layer().shutdown( - asio::socket_base::shutdown_both, ec); - // error ignored - - reset(); - wb_.do_idle(this); -} - -template -void -worker_ssl:: -do_fail( - core::string_view s, system::error_code const& ec) -{ - reset(); - - if(ec == asio::error::operation_aborted) - { - LOG_TRC(this->sect_)( - "{} {}: {}", - this->id(), s, ec.message()); - // this means the worker was stopped, don't submit new work - return; - } - - LOG_DBG(this->sect_)( - "{} {}: {}", - this->id(), s, ec.message()); - wb_.do_idle(this); -} - -template -void -worker_ssl:: -reset() -{ - // Clean up any previous connection. - system::error_code ec; - this->member.next_layer().close(ec); - - // asio::ssl::stream has an internal state which cannot be reset. - // In order to perform the handshake again, we destroy the old - // object and assign a new one, in a way that preserves the - // original socket to avoid churning file handles. - // - this->member = stream_type(std::move(this->member.next_layer()), ssl_ctx_); -} - -/** Close the connection to end the session -*/ -template -void -worker_ssl:: -do_close(system::error_code const& ec) -{ - if(! ec.failed()) - { - if(! this->pconfig_->is_ssl) - { - reset(); - wb_.do_idle(this); - return; - } - this->member.stream().async_shutdown(call_mf( - &worker_ssl::on_shutdown, this)); - return; - } - - do_fail("worker_ssl::do_close", ec); -} - -} // beast2 -} // boost - -#endif diff --git a/include/boost/beast2/ssl_stream.hpp b/include/boost/beast2/ssl_stream.hpp deleted file mode 100644 index b65fd9f4..00000000 --- a/include/boost/beast2/ssl_stream.hpp +++ /dev/null @@ -1,196 +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_SSL_SOCKET_HPP -#define BOOST_BEAST2_SSL_SOCKET_HPP - -#include -#include -#include - -namespace boost { -namespace beast2 { - -/** A stream which can be either plain or SSL -*/ -template -class ssl_stream -{ - struct initiate_async_read_some; - struct initiate_async_write_some; - -public: - using ssl_stream_type = asio::ssl::stream; - - using next_layer_type = typename ssl_stream_type::next_layer_type; - - using lowest_layer_type = typename ssl_stream_type::lowest_layer_type; - - using executor_type = typename ssl_stream_type::executor_type; - - /** Destructor - - @note A @c stream object must not be destroyed while there are - pending asynchronous operations associated with it. - */ - ~ssl_stream() = default; - - ssl_stream(ssl_stream&&) = default; - - ssl_stream& operator=(ssl_stream&& other) = default; - - template - explicit - ssl_stream(Args&&... args) - : stream_(std::forward(args)...) - { - } - - executor_type get_executor() noexcept - { - return stream_.get_executor(); - } - - next_layer_type& next_layer() - { - return stream_.next_layer(); - } - - next_layer_type const& next_layer() const - { - return stream_.next_layer(); - } - - lowest_layer_type& lowest_layer() - { - return stream_.lowest_layer(); - } - - lowest_layer_type const& lowest_layer() const - { - return stream_.lowest_layer(); - } - - ssl_stream_type& stream() - { - return stream_; - } - - ssl_stream_type const& stream() const - { - return stream_; - } - - void set_ssl(bool ssl) noexcept - { - is_ssl_ = ssl; - } - - bool is_ssl() const noexcept - { - return is_ssl_; - } - - template > - auto async_read_some(MutableBufferSequence const& buffers, - ReadToken&& token = asio::default_completion_token_t()) -> - decltype(asio::async_initiate( - std::declval(), token, buffers)) - { - return asio::async_initiate( - initiate_async_read_some(this), token, buffers); - } - - template > - auto async_write_some(ConstBufferSequence const& buffers, - WriteToken&& token = asio::default_completion_token_t()) -> - decltype(asio::async_initiate( - std::declval(), token, buffers)) - { - return asio::async_initiate( - initiate_async_write_some(this), token, buffers); - } - -private: - struct initiate_async_read_some - { - using executor_type = typename ssl_stream::executor_type; - - explicit initiate_async_read_some( - ssl_stream* self) noexcept - : self_(self) - { - } - - executor_type get_executor() const noexcept - { - return self_->get_executor(); - } - - template - void operator()(ReadHandler&& handler, - MutableBufferSequence const& buffers) const - { - if(self_->is_ssl_) - self_->stream_.async_read_some(buffers, - std::forward(handler)); - else - self_->next_layer().async_read_some(buffers, - std::forward(handler)); - } - - ssl_stream* self_; - }; - - struct initiate_async_write_some - { - using executor_type = typename ssl_stream::executor_type; - - explicit initiate_async_write_some( - ssl_stream* self) noexcept - : self_(self) - { - } - - executor_type get_executor() const noexcept - { - return self_->get_executor(); - } - - template - void operator()(WriteHandler&& handler, - ConstBufferSequence const& buffers) const - { - if(self_->is_ssl_) - self_->stream_.async_write_some(buffers, - std::forward(handler)); - else - self_->next_layer().async_write_some(buffers, - std::forward(handler)); - } - - ssl_stream* self_; - }; - - ssl_stream_type stream_; - bool is_ssl_ = false; -}; - -} // beast2 -} // boost - -#endif diff --git a/src/asio_io_context.cpp b/src/asio_io_context.cpp index 9ed07894..b8c7f69f 100644 --- a/src/asio_io_context.cpp +++ b/src/asio_io_context.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include @@ -38,7 +37,6 @@ class asio_io_context_impl : app_(app) , num_threads_(num_threads) , ioc_(num_threads) - , sigs_(ioc_.get_executor(), SIGINT, SIGTERM) , work_(ioc_.get_executor()) { if(num_threads > 1) @@ -69,11 +67,6 @@ class asio_io_context_impl void start() override { - // Capture SIGINT and SIGTERM to - // perform a clean shutdown - sigs_.async_wait(call_mf( - &asio_io_context_impl::on_signal, this)); - for(auto& t : vt_) { t = std::thread( @@ -87,25 +80,13 @@ class asio_io_context_impl void stop() override { - system::error_code ec; - sigs_.cancel(ec); // VFALCO should we use the 0-arg overload? work_.reset(); } private: - void - on_signal( - system::error_code const& ec, int) - { - if(ec == asio::error::operation_aborted) - return; - app_.stop(); - } - capy::application& app_; int num_threads_; asio::io_context ioc_; - asio::signal_set sigs_; asio::executor_work_guard< asio::io_context::executor_type> work_; std::vector vt_; diff --git a/src/server/https_server.cpp b/src/server/https_server.cpp deleted file mode 100644 index a8949389..00000000 --- a/src/server/https_server.cpp +++ /dev/null @@ -1,23 +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 { - -namespace { - -} // (anon) - -//------------------------------------------------ - -} // beast2 -} // boost - diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index b65645ea..1049228a 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -20,10 +20,8 @@ source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "" FILES ${PFILES}) add_executable(boost_beast2_tests ${PFILES}) target_include_directories(boost_beast2_tests PRIVATE . ../../) -find_package(OpenSSL REQUIRED) target_link_libraries(boost_beast2_tests PRIVATE boost_url_test_suite_with_main - OpenSSL::SSL Boost::beast2) # Register individual tests with CTest diff --git a/test/unit/Jamfile b/test/unit/Jamfile index ad442eca..0ba0832e 100644 --- a/test/unit/Jamfile +++ b/test/unit/Jamfile @@ -8,14 +8,6 @@ # import testing ; -import ac ; - -using openssl ; - -lib advapi32 ; -lib crypt32 ; -lib gdi32 ; -lib user32 ; project : requirements @@ -33,20 +25,7 @@ project clang-win:"-Wno-unused-private-field" ; -for local f in [ glob-tree-ex . : *.cpp : ssl_*.cpp ] +for local f in [ glob-tree-ex . : *.cpp ] { run $(f) ; } - -for local f in [ glob-tree-ex . : ssl_*.cpp ] -{ - run $(f) - : requirements - [ ac.check-library /openssl//ssl : /openssl//ssl/shared : no ] - [ ac.check-library /openssl//crypto : /openssl//crypto/shared : no ] - windows:advapi32 - windows:crypt32 - windows:gdi32 - windows:user32 - ; -} diff --git a/test/unit/body_write_stream.cpp b/test/unit/body_write_stream.cpp index 30b3dd33..1f75a404 100644 --- a/test/unit/body_write_stream.cpp +++ b/test/unit/body_write_stream.cpp @@ -631,8 +631,6 @@ struct single_tester : public ctx_base } }; -#ifdef BOOST_BEAST2_HAS_CORO - // Result type for async write operations struct write_result { @@ -731,8 +729,6 @@ test_coroutine() ioc.run(); } -#endif // BOOST_BEAST2_HAS_CORO - } // anonymous namespace. struct body_write_stream_test @@ -808,12 +804,10 @@ struct body_write_stream_test single_tester().test_cancel_during_buffer_clear(); } -#ifdef BOOST_BEAST2_HAS_CORO // Test C++20 coroutine compatibility { test_coroutine(); } -#endif } }; diff --git a/test/unit/server/https_server.cpp b/test/unit/server/https_server.cpp deleted file mode 100644 index 9a58b2d1..00000000 --- a/test/unit/server/https_server.cpp +++ /dev/null @@ -1,28 +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 "test_suite.hpp" - -namespace boost { -namespace beast2 { - -struct https_server_test -{ - void run() - { - } -}; - -TEST_SUITE(https_server_test, "boost.beast2.server.https_server"); - -} // beast2 -} // boost diff --git a/test/unit/ssl_stream.cpp b/test/unit/ssl_stream.cpp deleted file mode 100644 index 17af0fd0..00000000 --- a/test/unit/ssl_stream.cpp +++ /dev/null @@ -1,35 +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 { - -class ssl_stream_test -{ -public: - void - run() - { - asio::io_context ioc; - asio::ssl::context ctx(asio::ssl::context::tlsv12); - ssl_stream ss(ioc.get_executor(), ctx); - // ss.async_read_some(asio::mutable_buffer{}); - } -}; - -TEST_SUITE(ssl_stream_test, "boost.beast2.ssl_stream"); - -} // beast2 -} // boost diff --git a/test/unit/stream.cpp b/test/unit/stream.cpp deleted file mode 100644 index d2d036f0..00000000 --- a/test/unit/stream.cpp +++ /dev/null @@ -1,105 +0,0 @@ -// -// Copyright (c) 2016-2019 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 -#include -#include - -#include "test_suite.hpp" - -#ifdef BOOST_BEAST_HAS_CORO - -namespace boost { -namespace beast2 { - -template -auto make_stream( - AsyncStream&& asioStream) -> - capy::any_stream -{ - struct impl : capy::any_stream::impl - { - typename std::decay::type stream_; - - explicit impl(AsyncStream&& s) - : stream_(std::forward(s)) - { - } - - capy::async_io_result - read_some( - capy::mutable_buffer b) override - { - return capy::make_async_result( - stream_.async_read_some(b, asio::deferred)); - } - - capy::async_io_result - write_some( - capy::const_buffer b) override - { - return capy::make_async_result( - stream_.async_write_some(b, asio::deferred)); - } - }; - - return capy::any_stream(std::make_shared( - std::forward(asioStream))); -} - -struct stream_test -{ - capy::task - t1() - { - co_return 67; - } - - capy::task - do_read(capy::any_stream stream) - { - char buf[256]; - capy::mutable_buffer b(buf, sizeof(buf)); - auto rv = co_await stream.read_some(b); - core::string_view sv(buf, rv.bytes_transferred); - BOOST_TEST(! rv.ec.failed()); - BOOST_TEST_EQ(sv, "lorem ipsum"); - co_return 67; - } - - void - run() - { - asio::io_context ioc; - - spawn( - ioc.get_executor(), - do_read(make_stream(test::stream(ioc, "lorem ipsum"))), - [](std::variant result) - { - if (result.index() == 0) - std::rethrow_exception(std::get<0>(result)); - BOOST_TEST_EQ(std::get<1>(result), 67); - }); - - ioc.run(); - } -}; - -TEST_SUITE(stream_test, "boost.beast2.stream"); - -} // beast2 -} // boost - -#endif From 8d21045c0ff2104517d4b5a5e4976c49dacffc27 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Tue, 13 Jan 2026 04:00:47 -0800 Subject: [PATCH 32/40] Refactor server to use corosio instead of asio - Remove obsolete asio-based types (body_read_stream, body_write_stream, read, write, etc.) - Replace router_asio/route_handler_asio with router_corosio/route_handler_corosio - Remove obsolete test stream types - Delete workers.cpp (implementation now fully inline in header) - Update http_server to use corosio io_context - Clean up examples to use new corosio-based API --- CMakeLists.txt | 4 +- build/Jamfile | 11 +- example/CMakeLists.txt | 2 +- example/server/CMakeLists.txt | 3 - example/server/main.cpp | 4 +- example/server/serve_detached.hpp | 67 -- include/boost/beast2.hpp | 19 +- include/boost/beast2/asio_io_context.hpp | 73 -- include/boost/beast2/body_read_stream.hpp | 162 ---- include/boost/beast2/body_write_stream.hpp | 267 ------- include/boost/beast2/buffer.hpp | 7 +- .../boost/beast2/impl/body_read_stream.hpp | 166 ---- .../boost/beast2/impl/body_write_stream.hpp | 237 ------ include/boost/beast2/impl/read.hpp | 254 ------ include/boost/beast2/impl/write.hpp | 308 -------- include/boost/beast2/read.hpp | 238 ------ include/boost/beast2/server/call_mf.hpp | 72 -- include/boost/beast2/server/http_server.hpp | 48 +- include/boost/beast2/server/http_stream.hpp | 632 +++++++-------- include/boost/beast2/server/plain_worker.hpp | 174 +---- ...ler_asio.hpp => route_handler_corosio.hpp} | 26 +- .../{router_asio.hpp => router_corosio.hpp} | 12 +- include/boost/beast2/server/workers.hpp | 369 ++++----- .../boost/beast2/test/detail/service_base.hpp | 40 - .../boost/beast2/test/detail/stream_state.hpp | 245 ------ include/boost/beast2/test/impl/stream.hpp | 722 ------------------ include/boost/beast2/test/stream.hpp | 514 ------------- include/boost/beast2/test/tcp.hpp | 57 -- include/boost/beast2/wrap_executor.hpp | 90 --- include/boost/beast2/write.hpp | 83 -- src/asio_io_context.cpp | 122 --- src/server/http_server.cpp | 83 +- src/server/workers.cpp | 19 - 33 files changed, 513 insertions(+), 4617 deletions(-) delete mode 100644 example/server/serve_detached.hpp delete mode 100644 include/boost/beast2/asio_io_context.hpp delete mode 100644 include/boost/beast2/body_read_stream.hpp delete mode 100644 include/boost/beast2/body_write_stream.hpp delete mode 100644 include/boost/beast2/impl/body_read_stream.hpp delete mode 100644 include/boost/beast2/impl/body_write_stream.hpp delete mode 100644 include/boost/beast2/impl/read.hpp delete mode 100644 include/boost/beast2/impl/write.hpp delete mode 100644 include/boost/beast2/read.hpp delete mode 100644 include/boost/beast2/server/call_mf.hpp rename include/boost/beast2/server/{route_handler_asio.hpp => route_handler_corosio.hpp} (58%) rename include/boost/beast2/server/{router_asio.hpp => router_corosio.hpp} (63%) delete mode 100644 include/boost/beast2/test/detail/service_base.hpp delete mode 100644 include/boost/beast2/test/detail/stream_state.hpp delete mode 100644 include/boost/beast2/test/impl/stream.hpp delete mode 100644 include/boost/beast2/test/stream.hpp delete mode 100644 include/boost/beast2/test/tcp.hpp delete mode 100644 include/boost/beast2/wrap_executor.hpp delete mode 100644 include/boost/beast2/write.hpp delete mode 100644 src/asio_io_context.cpp delete mode 100644 src/server/workers.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 55c79501..b1ae3901 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,7 +55,7 @@ set(BOOST_SRC_DIR ${DEFAULT_BOOST_SRC_DIR} CACHE STRING "Boost source dir to use #------------------------------------------------- # The boost super-project requires one explicit dependency per-line. set(BOOST_BEAST2_DEPENDENCIES - Boost::asio + Boost::corosio Boost::assert Boost::capy Boost::config @@ -123,7 +123,7 @@ if (BOOST_BEAST2_IS_ROOT) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${BOOST_SRC_DIR}/tools/cmake/include") else () # From Boost Package - find_package(Boost REQUIRED COMPONENTS capy http json program_options scope system url) + find_package(Boost REQUIRED COMPONENTS capy corosio http json program_options scope system url) foreach (BOOST_INCLUDE_LIBRARY ${BOOST_INCLUDE_LIBRARIES}) if (NOT TARGET Boost::${BOOST_INCLUDE_LIBRARY}) add_library(Boost::${BOOST_INCLUDE_LIBRARY} ALIAS Boost::headers) diff --git a/build/Jamfile b/build/Jamfile index 6b2a3763..bf1ff920 100644 --- a/build/Jamfile +++ b/build/Jamfile @@ -28,15 +28,6 @@ project boost/beast2 shared:BOOST_BEAST2_DYN_LINK=1 static:BOOST_BEAST2_STATIC_LINK=1 clang-win:"-Wno-unused-private-field" - - #TODO: Remove the following defines once asio headers are no longer included in source files - windows:_WIN32_WINNT=0x0601 - windows,gcc:ws2_32 - windows,gcc:mswsock - windows,gcc-cygwin:__USE_W32_SOCKETS - msvc:_SILENCE_CXX17_ALLOCATOR_VOID_DEPRECATION_WARNING - msvc:_SILENCE_CXX17_ADAPTOR_TYPEDEFS_DEPRECATION_WARNING - msvc:_SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING : source-location $(BEAST2_ROOT) ; @@ -47,10 +38,12 @@ explicit beast2_sources ; lib boost_beast2 : beast2_sources : requirements + /boost//corosio /boost//http ../ BOOST_BEAST2_SOURCE : usage-requirements + /boost//corosio /boost//http ; diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index f9abbaca..d4b2ddbc 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -8,5 +8,5 @@ # Official repository: https://github.com/cppalliance/beast2 # -add_subdirectory(client) +# add_subdirectory(client) # disabled for now add_subdirectory(server) diff --git a/example/server/CMakeLists.txt b/example/server/CMakeLists.txt index e8578e01..f2f1508b 100644 --- a/example/server/CMakeLists.txt +++ b/example/server/CMakeLists.txt @@ -15,9 +15,6 @@ source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "" FILES ${PFILES}) add_executable(beast2_server_example ${PFILES}) -target_compile_definitions(beast2_server_example - PRIVATE BOOST_ASIO_NO_DEPRECATED) - set_property(TARGET beast2_server_example PROPERTY FOLDER "examples") diff --git a/example/server/main.cpp b/example/server/main.cpp index e3d7fec2..d7765b83 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -7,9 +7,7 @@ // Official repository: https://github.com/cppalliance/beast2 // -#include "serve_detached.hpp" #include "serve_log_admin.hpp" -#include #include #include #include @@ -203,7 +201,7 @@ int server_main( int argc, char* argv[] ) srv.wwwroot.use("/", serve_static( argv[3] )); app.start(); - srv.attach(); + srv.run(); } catch( std::exception const& e ) { diff --git a/example/server/serve_detached.hpp b/example/server/serve_detached.hpp deleted file mode 100644 index a9d8cc9f..00000000 --- a/example/server/serve_detached.hpp +++ /dev/null @@ -1,67 +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_DETACHED_HPP -#define BOOST_BEAST2_SERVER_SERVE_DETACHED_HPP - -#include -#include -#include -#include -#include -#include -#include - -namespace boost { -namespace beast2 { - -/** A route handler which delegates work to a foreign thread -*/ -class serve_detached -{ -public: - ~serve_detached() - { - } - - serve_detached() - : tp_(new asio::thread_pool(1)) - { - } - - serve_detached(serve_detached&&) = default; - - system::error_code - operator()( - http::route_params& rp) const - { - return rp.suspend( - [&](http::resumer resume) - { - asio::post(*tp_, - [&, resume]() - { - // Simulate some asynchronous work - std::this_thread::sleep_for(std::chrono::seconds(1)); - rp.status(http::status::ok); - rp.set_body("Hello from serve_detached!\n"); - resume(http::route::send); - // resume( res.send("Hello from serve_detached!\n") ); - }); - }); - } - -private: - std::unique_ptr tp_; -}; - -} // beast2 -} // boost - -#endif diff --git a/include/boost/beast2.hpp b/include/boost/beast2.hpp index f7f31cf1..865e71a1 100644 --- a/include/boost/beast2.hpp +++ b/include/boost/beast2.hpp @@ -10,34 +10,29 @@ #ifndef BOOST_BEAST2_HPP #define BOOST_BEAST2_HPP +// Server components #include -#include #include #include #include -#include -#include +#include #include -#include +#include #include #include #include + +// Test utilities #include #include -#include -#include -#include -#include + +// Core utilities #include #include #include #include -#include #include #include #include -#include -#include -#include #endif diff --git a/include/boost/beast2/asio_io_context.hpp b/include/boost/beast2/asio_io_context.hpp deleted file mode 100644 index 2b63ef21..00000000 --- a/include/boost/beast2/asio_io_context.hpp +++ /dev/null @@ -1,73 +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_ASIO_IO_CONTEXT_HPP -#define BOOST_BEAST2_ASIO_IO_CONTEXT_HPP - -#include -#include -#include - -namespace boost { -namespace beast2 { - -/** Asio's io_context as an application part -*/ -class BOOST_SYMBOL_VISIBLE - asio_io_context -{ -public: - using executor_type = - asio::io_context::executor_type; - - /** Destructor - */ - BOOST_BEAST2_DECL - ~asio_io_context(); - - virtual - executor_type - get_executor() noexcept = 0; - - /** Return the concurrency level - */ - virtual - std::size_t - concurrency() const noexcept = 0; - - /** Run the context - - This function attaches the current thread to I/O context - so that it may be used for executing submitted function - objects. Blocks the calling thread until the part is stopped - and has no outstanding work. - */ - virtual void attach() = 0; - - virtual void start() = 0; - virtual void stop() = 0; -}; - -BOOST_BEAST2_DECL -auto -install_single_threaded_asio_io_context( - capy::application& app) -> - asio_io_context&; - -BOOST_BEAST2_DECL -auto -install_multi_threaded_asio_io_context( - capy::application& app, - int num_threads) -> - asio_io_context&; - -} // beast2 -} // boost - -#endif diff --git a/include/boost/beast2/body_read_stream.hpp b/include/boost/beast2/body_read_stream.hpp deleted file mode 100644 index 3ec089a1..00000000 --- a/include/boost/beast2/body_read_stream.hpp +++ /dev/null @@ -1,162 +0,0 @@ -// -// Copyright (c) 2025 Mungo Gill -// -// 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_BODY_READ_STREAM_HPP -#define BOOST_BEAST2_BODY_READ_STREAM_HPP - -#include -#include -#include -#include - -namespace boost { -namespace beast2 { - -/** A body reader for HTTP/1 messages. - - This type meets the requirements of asio's AsyncReadStream, and is - constructed with a reference to an underlying AsyncReadStream. - - Any call to `async_read_some` initially triggers reads from the underlying - stream until all of the HTTP headers and at least one byte of the body have - been read and processed. Thereafter, each subsequent call to - `async_read_some` processes at least one byte of the body, triggering, where - required, calls to the underlying stream's `async_read_some` method. The - resulting body data is stored in the referenced MutableBufferSequence. - - All processing depends on a beast2::parser object owned by the caller and - referenced in the construction of this object. - - @see - @ref http::parser. -*/ -template -class body_read_stream -{ - -public: - /** The type of the executor associated with the stream. - - This will be the type of executor used to invoke completion handlers - which do not have an explicit associated executor. - */ - typedef typename AsyncReadStream::executor_type executor_type; - - /** Get the executor associated with the object. - - This function may be used to obtain the executor object that the stream - uses to dispatch completion handlers without an assocaited executor. - - @return A copy of the executor that stream will use to dispatch - handlers. - */ - executor_type - get_executor() - { - return stream_.get_executor(); - } - - /** Constructor - - This constructor creates the stream by forwarding all arguments to the - underlying socket. The socket then needs to be open and connected or - accepted before data can be sent or received on it. - - @param s The underlying stream from which the HTTP message is read. - This object's executor is initialized to that of the - underlying stream. - - @param pr A http::parser object which will perform the parsing of - the HTTP message and extraction of the body. This must be - initialized by the caller and ownership of the parser is - retained by the caller, which must guarantee that it remains - valid until the handler is called. - */ - explicit body_read_stream(AsyncReadStream& s, http::parser& pr); - - /** Read some data asynchronously. - - This function is used to asynchronously read data from the stream. - - This call always returns immediately. The asynchronous operation will - continue until one of the following conditions is true: - - @li The HTTP headers are read in full from the underlying stream and one - or more bytes of the body are read from the stream and stored in the - buffer `mb`. - - @li An error occurs. - - The algorithm, known as a composed asynchronous operation, is - implemented in terms of calls to the underlying stream's - `async_read_some` function. The program must ensure that no other calls - implemented using the underlying stream's `async_read_some` are - performed until this operation completes. - - @param mb The buffers into which the body data will be read. If the - size of the buffers is zero bytes, the operation always completes - immediately with no error. Although the buffers object may be copied as - necessary, ownership of the underlying memory blocks is retained by the - caller, which must guarantee that they remain valid until the handler is - called. Where the mb buffer is not of sufficient size to hold the read - data, the remainder may be read by subsequent calls to this function. - - @param handler The completion handler to invoke when the operation - completes. The implementation takes ownership of the handler by - performing a decay-copy. The equivalent function signature of the - handler must be: - @code - void handler( - error_code const& error, // result of operation - std::size_t bytes_transferred // the number of bytes consumed by the parser - ); - @endcode - Regardless of whether the asynchronous operation - completes immediately or not, the completion handler will not be invoked - from within this function. On immediate completion, invocation of the - handler will be performed in a manner equivalent to using - `asio::async_immediate`. - - @note The `async_read_some` operation may not receive all of the - requested number of bytes. Consider using the function - `asio::async_read` if you need to ensure that the requested amount of - data is read before the asynchronous operation completes. - - @par Per-Operation Cancellation - - This asynchronous operation supports cancellation for the following - net::cancellation_type values: - - @li @c net::cancellation_type::terminal - @li @c net::cancellation_type::partial - @li @c net::cancellation_type::total - - if they are also supported by the underlying stream's @c async_read_some - operation. - */ - template< - class MutableBufferSequence, - BOOST_ASIO_COMPLETION_TOKEN_FOR(void(system::error_code, std::size_t)) - CompletionToken> - BOOST_ASIO_INITFN_AUTO_RESULT_TYPE( - CompletionToken, - void(system::error_code, std::size_t)) - async_read_some(MutableBufferSequence mb, CompletionToken&& handler); - -private: - AsyncReadStream& stream_; - http::parser& pr_; -}; - -} // beast2 -} // boost - -#include - -#endif // BOOST_BEAST2_BODY_READ_STREAM_HPP diff --git a/include/boost/beast2/body_write_stream.hpp b/include/boost/beast2/body_write_stream.hpp deleted file mode 100644 index 8def1dde..00000000 --- a/include/boost/beast2/body_write_stream.hpp +++ /dev/null @@ -1,267 +0,0 @@ -// -// Copyright (c) 2025 Mungo Gill -// -// 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_BODY_WRITE_STREAM_HPP -#define BOOST_BEAST2_BODY_WRITE_STREAM_HPP - -#include -#include -#include - -#include - -namespace boost { -namespace beast2 { - -namespace detail { - -template -class body_write_stream_op; - -template -class body_write_stream_close_op; - -} // detail - -/** A body writer for HTTP/1 messages. - - This type is modelled on asio's AsyncWriteStream, and is constructed with a - reference to an underlying AsyncWriteStream. - - Any call to `async_write_some` initially triggers writes to the underlying - stream until all of the HTTP headers and at least one byte of the body have - been written and processed. Thereafter, each subsequent call to - `async_write_some` processes at least one byte of the body, triggering, where - required, calls to the underlying stream's `async_write_some` method. The - body data is read from the referenced ConstBufferSequence. - - All processing depends on a beast2::serializer object owned by the caller and - referenced in the construction of this object. - - @par Deviations from AsyncWriteStream - - This type deviates from the strict AsyncWriteStream requirements in the - following ways: - - @li Deferred error reporting: If an error or cancellation occurs - after data has been successfully committed to the serializer, the - operation completes with success and reports the number of bytes - consumed. The error is saved and reported on the next call to - `async_write_some`. This differs from the AsyncWriteStream requirement - that on error, `bytes_transferred` must be 0. This behaviour ensures - that the caller knows exactly how many bytes were consumed by the - serializer, preventing data loss or duplication. - - @see - @ref http::serializer. -*/ -template -class body_write_stream -{ - -public: - /** The type of the executor associated with the stream. - - This will be the type of executor used to invoke completion handlers - which do not have an explicit associated executor. - */ - using executor_type = - decltype(std::declval().get_executor()); - - /** Return the executor associated with the object. - - This function may be used to obtain the executor object that the stream - uses to dispatch completion handlers without an associated executor. - - @return A copy of the executor that stream will use to dispatch - handlers. - */ - executor_type - get_executor() - { - return stream_.get_executor(); - } - - /** Constructor - - This constructor creates the stream which retains a reference to the - underlying stream. The underlying stream then needs to be open before - data can be written - - @param s The underlying stream to which the HTTP message is written. - This object's executor is initialized to that of the - underlying stream. - - @param sr A http::serializer object which will perform the serialization of - the HTTP message and extraction of the body. This must be - initialized by the caller and ownership of the serializer is - retained by the caller, which must guarantee that it remains - valid until the handler is called. - - @param srs A http::serializer::stream object which must have been - obtained by a call to `start_stream` on `sr`. Ownership of - the serializer::stream is transferred to this object. - */ - explicit body_write_stream( - AsyncWriteStream& s, - http::serializer& sr, - http::serializer::stream srs); - - /** Write some data asynchronously. - - This function is used to asynchronously write data to the stream. - - This call always returns immediately. The asynchronous operation will - continue until one of the following conditions is true: - - @li One or more bytes are written from `cb` to the body stored in the - serializer and one or more bytes are written from the serializer to the - underlying stream. - - @li An error occurs. - - The algorithm, known as a composed asynchronous operation, is - implemented in terms of calls to the underlying stream's - `async_write_some` function. The program must ensure that no other calls - implemented using the underlying stream's `async_write_some` are - performed until this operation completes. - - @param cb The buffer sequence from which the body data will be read. If - the size of the buffer sequence is zero bytes, the operation always - completes immediately with no error. Although the buffers object may be - copied as necessary, ownership of the underlying memory blocks is - retained by the caller, which must guarantee that they remain valid until - the handler is called. Where the internal buffer of the contained - serializer is not of sufficient size to hold the data to be copied from - cb, the remainder may be written by subsequent calls to this function. - - @param handler The completion handler to invoke when the operation - completes. The implementation takes ownership of the handler by - performing a decay-copy. The equivalent function signature of the - handler must be: - @code - void handler( - error_code const& error, // result of operation - std::size_t bytes_transferred // the number of bytes consumed from - // cb by the serializer - ); - @endcode - Regardless of whether the asynchronous operation - completes immediately or not, the completion handler will not be invoked - from within this function. On immediate completion, invocation of the - handler will be performed in a manner equivalent to using - `asio::async_immediate`. - - @note The `async_write_some` operation may not transmit all of the - requested number of bytes. Consider using the function - `asio::async_write` if you need to ensure that the requested amount of - data is written before the asynchronous operation completes. - - @note This function does not guarantee that all of the consumed data is - written to the underlying stream. For this reason one or more calls to - `async_write_some` must be followed by a call to `async_close` to put the - serializer into the `done` state and to write all data remaining in the - serializer to the underlying stream. - - @par Per-Operation Cancellation - - This asynchronous operation supports cancellation for the following - net::cancellation_type values: - - @li @c net::cancellation_type::terminal - @li @c net::cancellation_type::partial - @li @c net::cancellation_type::total - - if they are also supported by the underlying stream's @c async_write_some - operation. - */ - template< - class ConstBufferSequence, - BOOST_ASIO_COMPLETION_TOKEN_FOR(void(system::error_code, std::size_t)) - CompletionToken> - BOOST_ASIO_INITFN_AUTO_RESULT_TYPE( - CompletionToken, - void(system::error_code, std::size_t)) - async_write_some(ConstBufferSequence const& cb, CompletionToken&& handler); - - /** Close serializer::stream and flush remaining data to the underlying stream asynchronously. - - This function is used to asynchronously call `close` on the - `serializer::stream` object referenced in the construction of this - `body_write_stream` and write all remaining data in the serializer to the - underlying stream. - - This call always returns immediately. The asynchronous operation will - continue until one of the following conditions is true: - - @li All remaining output bytes of the serializer are written to the - underlying stream and the serializer's `is_done()` method returns true. - - @li An error occurs. - - The algorithm, known as a composed asynchronous operation, is - implemented in terms of calls to the underlying stream's - `async_write_some` function. The program must ensure that no other calls - implemented using the underlying stream's `async_write_some` are - performed until this operation completes. - - @param handler The completion handler to invoke when the operation - completes. The implementation takes ownership of the handler by - performing a decay-copy. The equivalent function signature of the - handler must be: - @code - void handler( - error_code const& error // result of operation - ); - @endcode - Regardless of whether the asynchronous operation - completes immediately or not, the completion handler will not be invoked - from within this function. On immediate completion, invocation of the - handler will be performed in a manner equivalent to using - `asio::async_immediate`. - - @par Per-Operation Cancellation - - This asynchronous operation supports cancellation for the following - net::cancellation_type values: - - @li @c net::cancellation_type::terminal - @li @c net::cancellation_type::partial - @li @c net::cancellation_type::total - - if they are also supported by the underlying stream's @c async_write_some - operation. - */ - template< - BOOST_ASIO_COMPLETION_TOKEN_FOR(void(system::error_code)) - CompletionToken> - BOOST_ASIO_INITFN_AUTO_RESULT_TYPE( - CompletionToken, - void(system::error_code)) - async_close(CompletionToken&& handler); - -private: - template - friend class detail::body_write_stream_op; - - template - friend class detail::body_write_stream_close_op; - - AsyncWriteStream& stream_; - http::serializer& sr_; - http::serializer::stream srs_; - system::error_code ec_; -}; - -} // beast2 -} // boost - -#include - -#endif // BOOST_BEAST2_BODY_WRITE_STREAM_HPP diff --git a/include/boost/beast2/buffer.hpp b/include/boost/beast2/buffer.hpp index 17a1e5d6..f3db4777 100644 --- a/include/boost/beast2/buffer.hpp +++ b/include/boost/beast2/buffer.hpp @@ -11,13 +11,18 @@ #define BOOST_BEAST2_BUFFER_HPP #include -#include +#include #include #include namespace boost { namespace beast2 { +// Re-export buffer types from capy +using capy::mutable_buffer; +using capy::const_buffer; +using capy::buffer_size; + } // beast2 } // boost diff --git a/include/boost/beast2/impl/body_read_stream.hpp b/include/boost/beast2/impl/body_read_stream.hpp deleted file mode 100644 index febf9e57..00000000 --- a/include/boost/beast2/impl/body_read_stream.hpp +++ /dev/null @@ -1,166 +0,0 @@ -// -// Copyright (c) 2025 Mungo Gill -// -// 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_IMPL_BODY_READ_STREAM_HPP -#define BOOST_BEAST2_IMPL_BODY_READ_STREAM_HPP - -#include -#include - -#include -#include -#include -#include -#include - -#include - -namespace boost { -namespace beast2 { - -namespace detail { - -template -class body_read_stream_op : public asio::coroutine -{ - - AsyncReadStream& stream_; - MutableBufferSequence mb_; - http::parser& pr_; - -public: - body_read_stream_op( - AsyncReadStream& s, - MutableBufferSequence&& mb, - http::parser& pr) noexcept - : stream_(s) - , mb_(std::move(mb)) - , pr_(pr) - { - } - - template - void - operator()( - Self& self, - system::error_code ec = {}, - std::size_t bytes_transferred = 0) - { - boost::ignore_unused(bytes_transferred); - - BOOST_ASIO_CORO_REENTER(*this) - { - self.reset_cancellation_state(asio::enable_total_cancellation()); - - if(capy::buffer_size(mb_) == 0) - { - BOOST_ASIO_CORO_YIELD - { - BOOST_ASIO_HANDLER_LOCATION( - (__FILE__, __LINE__, "immediate")); - auto io_ex = self.get_io_executor(); - asio::async_immediate( - io_ex, - asio::append( - std::move(self), system::error_code{})); - } - goto upcall; - } - - if(!pr_.got_header()) - { - BOOST_ASIO_CORO_YIELD - { - BOOST_ASIO_HANDLER_LOCATION( - (__FILE__, __LINE__, "async_read_header")); - beast2::async_read_header( - stream_, pr_, std::move(self)); - } - if(ec.failed()) - goto upcall; - } - - if(!!self.cancelled()) - { - ec = asio::error::operation_aborted; - goto upcall; - } - - BOOST_ASIO_CORO_YIELD - { - BOOST_ASIO_HANDLER_LOCATION( - (__FILE__, __LINE__, "async_read_some")); - beast2::async_read_some(stream_, pr_, std::move(self)); - } - - upcall: - std::size_t n = 0; - - if(!ec.failed()) - { - auto dbs = asio::buffer_size(mb_); - - if(dbs > 0) - { - auto source_buf = pr_.pull_body(); - - n = asio::buffer_copy(mb_, source_buf); - - pr_.consume_body(n); - - ec = (n != 0) ? system::error_code{} - : asio::stream_errc::eof; - } - } - - self.complete(ec, n); - } - } -}; - -} // detail - -//------------------------------------------------ - -// TODO: copy in Beast's stream traits to check if AsyncReadStream -// is an AsyncReadStream, and also static_assert that body_read_stream is too. - -template -body_read_stream::body_read_stream( - AsyncReadStream& s, - http::parser& pr) - : stream_(s) - , pr_(pr) -{ -} - -template -template< - class MutableBufferSequence, - BOOST_ASIO_COMPLETION_TOKEN_FOR(void(system::error_code, std::size_t)) - CompletionToken> -BOOST_ASIO_INITFN_AUTO_RESULT_TYPE( - CompletionToken, - void(system::error_code, std::size_t)) -body_read_stream::async_read_some( - MutableBufferSequence mb, - CompletionToken&& token) -{ - return asio:: - async_compose( - detail::body_read_stream_op{ - stream_, std::move(mb), pr_ }, - token, - stream_); -} - -} // beast2 -} // boost - -#endif // BOOST_BEAST2_IMPL_BODY_READ_STREAM_HPP diff --git a/include/boost/beast2/impl/body_write_stream.hpp b/include/boost/beast2/impl/body_write_stream.hpp deleted file mode 100644 index d036a29d..00000000 --- a/include/boost/beast2/impl/body_write_stream.hpp +++ /dev/null @@ -1,237 +0,0 @@ -// -// Copyright (c) 2025 Mungo Gill -// -// 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_IMPL_BODY_WRITE_STREAM_HPP -#define BOOST_BEAST2_IMPL_BODY_WRITE_STREAM_HPP - -#include - -#include -#include -#include -#include -#include - -namespace boost { -namespace beast2 { - -namespace detail { - -template -class body_write_stream_close_op : public asio::coroutine -{ - body_write_stream& bws_; - -public: - body_write_stream_close_op( - body_write_stream& bws) noexcept - : bws_(bws) - { - } - - template - void - operator()( - Self& self, - system::error_code ec = {}, - std::size_t = 0) - { - BOOST_ASIO_CORO_REENTER(*this) - { - self.reset_cancellation_state(asio::enable_total_cancellation()); - - // Check for a saved error from a previous async_write_some call. - if(bws_.ec_.failed()) - { - ec = bws_.ec_; - bws_.ec_ = {}; - BOOST_ASIO_CORO_YIELD - { - BOOST_ASIO_HANDLER_LOCATION( - (__FILE__, __LINE__, "immediate")); - auto io_ex = self.get_io_executor(); - asio::async_immediate( - io_ex, - asio::append(std::move(self), ec)); - } - goto upcall; - } - - bws_.srs_.close(); - - BOOST_ASIO_CORO_YIELD - { - BOOST_ASIO_HANDLER_LOCATION( - (__FILE__, __LINE__, "async_write_some")); - beast2::async_write(bws_.stream_, bws_.sr_, std::move(self)); - } - - upcall: - self.complete(ec); - } - } -}; - -template -class body_write_stream_op : public asio::coroutine -{ - body_write_stream& bws_; - ConstBufferSequence cb_; - std::size_t bytes_; - -public: - body_write_stream_op( - body_write_stream& bws, - ConstBufferSequence const& cb) noexcept - : bws_(bws) - , cb_(cb) - , bytes_(0) - { - } - - template - void - operator()( - Self& self, - system::error_code ec = {}, - std::size_t = 0) - { - BOOST_ASIO_CORO_REENTER(*this) - { - // Verify preconditions - BOOST_ASSERT(!bws_.sr_.is_done()); - - self.reset_cancellation_state(asio::enable_total_cancellation()); - - // A zero-sized buffer is a special case, we are required to - // complete immediately with no error. Also check for a saved - // error from a previous call. - if(bws_.ec_.failed() || - capy::buffer_size(cb_) == 0) - { - ec = bws_.ec_; - bws_.ec_ = {}; - BOOST_ASIO_CORO_YIELD - { - BOOST_ASIO_HANDLER_LOCATION( - (__FILE__, __LINE__, "immediate")); - auto io_ex = self.get_io_executor(); - asio::async_immediate( - io_ex, - asio::append(std::move(self), ec)); - } - goto upcall; - } - - // The serializer's internal buffer may be full, so we may have no - // option but to try to write to the stream to clear space. - // This may require multiple attempts as buffer space cannot - // be cleared until the headers have been written. - for(;;) - { - bytes_ = asio::buffer_copy(bws_.srs_.prepare(), cb_); - bws_.srs_.commit(bytes_); - - BOOST_ASIO_CORO_YIELD - { - BOOST_ASIO_HANDLER_LOCATION( - (__FILE__, __LINE__, "async_write_some")); - async_write_some(bws_.stream_, bws_.sr_, std::move(self)); - } - - if(ec.failed()) - { - if(bytes_ != 0) - { - bws_.ec_ = ec; - ec = {}; - } - break; - } - - if(bytes_ != 0) - break; - - if(!!self.cancelled()) - { - ec = asio::error::operation_aborted; - break; - } - } - - upcall: - self.complete(ec, bytes_); - } - } -}; - -} // detail - -//------------------------------------------------ - -// TODO: copy in Beast's stream traits to check if AsyncWriteStream -// is an AsyncWriteStream, and also static_assert that body_write_stream is too. - -template -body_write_stream:: -body_write_stream( - AsyncWriteStream& s, - http::serializer& sr, - http::serializer::stream srs) - : stream_(s) - , sr_(sr) - , srs_(std::move(srs)) -{ - // Verify preconditions - BOOST_ASSERT(srs_.is_open()); -} - -template -template< - class ConstBufferSequence, - BOOST_ASIO_COMPLETION_TOKEN_FOR(void(system::error_code, std::size_t)) - CompletionToken> -BOOST_ASIO_INITFN_AUTO_RESULT_TYPE( - CompletionToken, - void(system::error_code, std::size_t)) -body_write_stream:: -async_write_some( - ConstBufferSequence const& cb, - CompletionToken&& token) -{ - return asio:: - async_compose( - detail::body_write_stream_op{ - *this, cb }, - token, - stream_); -} - -template -template< - BOOST_ASIO_COMPLETION_TOKEN_FOR(void(system::error_code)) - CompletionToken> -BOOST_ASIO_INITFN_AUTO_RESULT_TYPE( - CompletionToken, - void(system::error_code)) -body_write_stream:: -async_close( - CompletionToken&& token) -{ - return asio:: - async_compose( - detail::body_write_stream_close_op{ *this }, - token, - stream_); -} - -} // beast2 -} // boost - -#endif // BOOST_BEAST2_IMPL_BODY_WRITE_STREAM_HPP diff --git a/include/boost/beast2/impl/read.hpp b/include/boost/beast2/impl/read.hpp deleted file mode 100644 index d7c72d9d..00000000 --- a/include/boost/beast2/impl/read.hpp +++ /dev/null @@ -1,254 +0,0 @@ -// -// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) -// Copyright (c) 2025 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 BOOST_BEAST2_IMPL_READ_HPP -#define BOOST_BEAST2_IMPL_READ_HPP - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace boost { -namespace beast2 { - -namespace detail { - -template -class read_until_op - : public asio::coroutine -{ - AsyncStream& stream_; - http::parser& pr_; - std::size_t total_bytes_ = 0; - bool (&condition_)(http::parser&); - -public: - read_until_op( - AsyncStream& s, - http::parser& pr, - bool (&condition)(http::parser&)) noexcept - : stream_(s) - , pr_(pr) - , condition_(condition) - { - } - - template - void - operator()( - Self& self, - system::error_code ec = {}, - std::size_t bytes_transferred = 0) - { - BOOST_ASIO_CORO_REENTER(*this) - { - self.reset_cancellation_state( - asio::enable_total_cancellation()); - - for(;;) - { - for(;;) - { - pr_.parse(ec); - if(ec == http::condition::need_more_input) - { - if(!!self.cancelled()) - { - ec = asio::error::operation_aborted; - goto upcall; - } - // specific to http_io::async_read_some - if(total_bytes_ != 0 && condition_(pr_)) - { - ec = {}; - goto upcall; - } - break; - } - if(ec.failed() || condition_(pr_)) - { - if(total_bytes_ == 0) - { - BOOST_ASIO_CORO_YIELD - { - BOOST_ASIO_HANDLER_LOCATION(( - __FILE__, __LINE__, - "immediate")); - auto io_ex = self.get_io_executor(); - asio::async_immediate( - io_ex, - asio::append(std::move(self), ec)); - } - } - goto upcall; - } - } - BOOST_ASIO_CORO_YIELD - { - BOOST_ASIO_HANDLER_LOCATION(( - __FILE__, __LINE__, - "async_read_some")); - stream_.async_read_some( - pr_.prepare(), - std::move(self)); - } - pr_.commit(bytes_transferred); - total_bytes_ += bytes_transferred; - if(ec == asio::error::eof) - { - BOOST_ASSERT( - bytes_transferred == 0); - pr_.commit_eof(); - ec = {}; - } - else if(ec.failed()) - { - goto upcall; - } - } - - upcall: - self.complete(ec, total_bytes_); - } - } -}; - -inline -bool -got_header_condition(http::parser& pr) -{ - return pr.got_header(); -} - -inline -bool -is_complete_condition(http::parser& pr) -{ - return pr.is_complete(); -} - -} // detail - -//------------------------------------------------ - -/** Asynchronously reads some data into the parser. - - This function is used to asynchronously read data from a - stream into the parser's input sequence. This function will always - keep reading until a complete header is obtained. - The function call will invoke the completion token - with the following signature: - @code - void(system::error_code ec - std::size_t bytes_transferred); - @endcode - @note The parser's input sequence may contain additional data - beyond what was required to complete the header. - @param s The stream to read from. - @param pr The parser to read data into. - @param token The completion token. -*/ -template< - class AsyncReadStream, - BOOST_ASIO_COMPLETION_TOKEN_FOR( - void(system::error_code, std::size_t)) CompletionToken> -BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, - void (system::error_code, std::size_t)) -async_read_some( - AsyncReadStream& s, - http::parser& pr, - CompletionToken&& token) -{ - return asio::async_compose< - CompletionToken, - void(system::error_code, std::size_t)>( - detail::read_until_op - {s, pr, detail::got_header_condition}, - token, - s); -} - -/** Asynchronously reads data into the parser until the header is complete. - This function is used to asynchronously read data from a - stream into the parser's input sequence until the parser's - header is complete. - The function call will invoke the completion token - with the following signature: - @code - void(system::error_code ec - std::size_t bytes_transferred); - @endcode - @note The parser's input sequence may contain additional data - beyond what was required to complete the header. - @param s The stream to read from. - @param pr The parser to read data into. - @param token The completion token. -*/ -template< - class AsyncReadStream, - BOOST_ASIO_COMPLETION_TOKEN_FOR( - void(system::error_code, std::size_t)) CompletionToken> -BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, - void (system::error_code, std::size_t)) -async_read_header( - AsyncReadStream& s, - http::parser& pr, - CompletionToken&& token) -{ - // TODO: async_read_header should not perform a read - // operation if `parser::got_header() == true`. - return async_read_some(s, pr, std::move(token)); -} - -/** Asynchronously reads data into the parser until the message is complete. - This function is used to asynchronously read data from a - stream into the parser's input sequence until the parser's - message is complete. - The function call will invoke the completion token - with the following signature: - @code - void(system::error_code ec - std::size_t bytes_transferred); - @endcode - @note The parser's input sequence may contain additional data - beyond what was required to complete the message. - @param s The stream to read from. - @param pr The parser to read data into. - @param token The completion token. -*/ -template< - class AsyncReadStream, - BOOST_ASIO_COMPLETION_TOKEN_FOR( - void(system::error_code, std::size_t)) CompletionToken> -BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, - void (system::error_code, std::size_t)) -async_read( - AsyncReadStream& s, - http::parser& pr, - CompletionToken&& token) -{ - return asio::async_compose< - CompletionToken, - void(system::error_code, std::size_t)>( - detail::read_until_op - {s, pr, detail::is_complete_condition}, - token, - s); -} - -} // beast2 -} // boost - -#endif diff --git a/include/boost/beast2/impl/write.hpp b/include/boost/beast2/impl/write.hpp deleted file mode 100644 index 5f3ada1d..00000000 --- a/include/boost/beast2/impl/write.hpp +++ /dev/null @@ -1,308 +0,0 @@ -// -// Copyright (c) 2016-2019 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_IMPL_WRITE_HPP -#define BOOST_BEAST2_IMPL_WRITE_HPP - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace boost { -namespace beast2 { - -namespace detail { - -template -class write_some_op - : public asio::coroutine -{ - using buffers_type = - http::serializer::const_buffers_type; - - WriteStream& dest_; - http::serializer& sr_; - -public: - write_some_op( - WriteStream& dest, - http::serializer& sr) noexcept - : dest_(dest) - , sr_(sr) - { - } - - template - void - operator()( - Self& self, - system::error_code ec = {}, - std::size_t bytes_transferred = {}) - { - system::result rv; - - BOOST_ASIO_CORO_REENTER(*this) - { - self.reset_cancellation_state( - asio::enable_total_cancellation()); - - rv = sr_.prepare(); - if(! rv) - { - ec = rv.error(); - BOOST_ASIO_CORO_YIELD - { - BOOST_ASIO_HANDLER_LOCATION(( - __FILE__, __LINE__, - "immediate")); - auto io_ex = self.get_io_executor(); - asio::async_immediate( - io_ex, - asio::append(std::move(self), ec)); - } - goto upcall; - } - - BOOST_ASIO_CORO_YIELD - { - BOOST_ASIO_HANDLER_LOCATION(( - __FILE__, __LINE__, - "beast2::write_some_op")); - dest_.async_write_some( - *rv, - std::move(self)); - } - sr_.consume(bytes_transferred); - - upcall: - self.complete( - ec, bytes_transferred ); - } - } -}; - -//------------------------------------------------ - -template -class write_op - : public asio::coroutine -{ - WriteStream& dest_; - http::serializer& sr_; - std::size_t n_ = 0; - -public: - write_op( - WriteStream& dest, - http::serializer& sr) noexcept - : dest_(dest) - , sr_(sr) - { - } - - template - void - operator()( - Self& self, - system::error_code ec = {}, - std::size_t bytes_transferred = 0) - { - BOOST_ASIO_CORO_REENTER(*this) - { - self.reset_cancellation_state(asio::enable_total_cancellation()); - - do - { - if(!!self.cancelled()) - { - ec = asio::error::operation_aborted; - - break; // goto upcall - } - - BOOST_ASIO_CORO_YIELD - { - BOOST_ASIO_HANDLER_LOCATION(( - __FILE__, __LINE__, - "beast2::write_op")); - async_write_some( - dest_, sr_, std::move(self)); - } - n_ += bytes_transferred; - - if(ec.failed()) - break; // goto upcall - } - while(! sr_.is_done()); - - // upcall: - self.complete(ec, n_ ); - } - } -}; - -//------------------------------------------------ - -#if 0 -template< - class WriteStream, - class ReadStream, - class CompletionCondition> -class relay_some_op - : public asio::coroutine -{ - WriteStream& dest_; - ReadStream& src_; - CompletionCondition cond_; - http::serializer& sr_; - std::size_t bytes_read_ = 0; - -public: - relay_some_op( - WriteStream& dest, - ReadStream& src, - CompletionCondition const& cond, - http::serializer& sr) noexcept - : dest_(dest) - , src_(src) - , cond_(cond) - , sr_(sr) - { - } - - template - void - operator()( - Self& self, - system::error_code ec = {}, - std::size_t bytes_transferred = 0) - { - urls::result< - http::serializer::buffers> rv; - - BOOST_ASIO_CORO_REENTER(*this) - { - // Nothing to do - BOOST_ASSERT(! sr_.is_complete()); - - rv = sr_.prepare(); - if(! rv) - { - ec = rv.error(); - BOOST_ASIO_CORO_YIELD - { - BOOST_ASIO_HANDLER_LOCATION(( - __FILE__, __LINE__, - "beast2::relay_some_op")); - asio::post(std::move(self)); - } - goto upcall; - } - - BOOST_ASIO_CORO_YIELD - { - BOOST_ASIO_HANDLER_LOCATION(( - __FILE__, __LINE__, - "beast2::relay_some_op")); - dest_.async_write_some( - write_buffers(*rv), - std::move(self)); - } - sr_.consume(bytes_transferred); - - upcall: - self.complete( - ec, bytes_transferred ); - } - } -}; -#endif - -} // detail - -//------------------------------------------------ - -template< - class AsyncWriteStream, - BOOST_ASIO_COMPLETION_TOKEN_FOR( - void(system::error_code, std::size_t)) CompletionToken> -BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, - void (system::error_code, std::size_t)) -async_write_some( - AsyncWriteStream& dest, - http::serializer& sr, - CompletionToken&& token) -{ - return asio::async_compose< - CompletionToken, - void(system::error_code, std::size_t)>( - detail::write_some_op< - AsyncWriteStream>{dest, sr}, - token, dest); -} - -template< - class AsyncWriteStream, - BOOST_ASIO_COMPLETION_TOKEN_FOR( - void(system::error_code, std::size_t)) CompletionToken> -BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, - void (system::error_code, std::size_t)) -async_write( - AsyncWriteStream& dest, - http::serializer& sr, - CompletionToken&& token) -{ - return asio::async_compose< - CompletionToken, - void(system::error_code, std::size_t)>( - detail::write_op< - AsyncWriteStream>{dest, sr}, - token, - dest); -} - -#if 0 -template< - class AsyncWriteStream, - class AsyncReadStream, - class CompletionCondition, - BOOST_ASIO_COMPLETION_TOKEN_FOR( - void(system::error_code, std::size_t)) CompletionToken> -BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, - void (system::error_code, std::size_t)) -async_relay_some( - AsyncWriteStream& dest, - AsyncReadStream& src, - CompletionCondition const& cond, - http::serializer& sr, - CompletionToken&& token) -{ - return asio::async_compose< - CompletionToken, - void(system::error_code, std::size_t)>( - detail::relay_some_op< - AsyncWriteStream, - AsyncReadStream, - CompletionCondition>{ - dest, src, cond, sr}, - token, - dest, - src); -} -#endif - -} // beast2 -} // boost - -#endif diff --git a/include/boost/beast2/read.hpp b/include/boost/beast2/read.hpp deleted file mode 100644 index 29b2b681..00000000 --- a/include/boost/beast2/read.hpp +++ /dev/null @@ -1,238 +0,0 @@ -// -// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) -// Copyright (c) 2025 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 BOOST_BEAST2_READ_HPP -#define BOOST_BEAST2_READ_HPP - -#include -#include -#include -#include -#include -#include - -namespace boost { -namespace beast2 { - -/** Read a complete header from the stream. - - This function is used to asynchronously read a complete - message header from a stream into an instance of parser. - The function call always returns immediately. The - asynchronous operation will continue until one of the - following conditions is true: - - @li The parser reads the header section of the message. - @li An error occurs. - - This operation is implemented in terms of zero or more - calls to the stream's `async_read_some` function, and is - known as a composed operation. The program must - ensure that the stream performs no other reads until - this operation completes. - - @param stream The stream from which the data is to be - read. The type must meet the AsyncReadStream - requirements. - - @param parser The parser to use. The object must remain - valid at least until the handler is called; ownership is - not transferred. - - @param token The completion token that will be used to - produce a completion handler, which will be called when - the read completes. The function signature of the - completion handler must be: - @code - void handler( - error_code const& error, // result of operation - std::size_t bytes_transferred // the number of bytes consumed by the parser - ); - @endcode - Regardless of whether the asynchronous operation - completes immediately or not, the completion handler - will not be invoked from within this function. On - immediate completion, invocation of the handler will be - performed in a manner equivalent to using - `asio::async_immediate`. - - @par Per-Operation Cancellation - - This asynchronous operation supports cancellation for - the following `asio::cancellation_type` values: - - @li `asio::cancellation_type::terminal` - @li `asio::cancellation_type::partial` - @li `asio::cancellation_type::total` - - if they are also supported by the AsyncReadStream type's - async_read_some operation. -*/ -template< - class AsyncReadStream, - BOOST_ASIO_COMPLETION_TOKEN_FOR( - void(system::error_code, std::size_t)) CompletionToken - BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE( - typename AsyncReadStream::executor_type)> -BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, - void (system::error_code, std::size_t)) -async_read_header( - AsyncReadStream& s, - http::parser& pr, - CompletionToken&& token - BOOST_ASIO_DEFAULT_COMPLETION_TOKEN( - typename AsyncReadStream::executor_type)); - -/** Read part of the message body from the stream. - - This function is used to asynchronously read part of a - message body from a stream into an instance of parser. - The function call always returns immediately. The - asynchronous operation will continue until one of the - following conditions is true: - - @li The parser reads the header section of the message. - @li The parser reads the entire message. - @li Additional body data becomes available. - @li An error occurs. - - This operation is implemented in terms of zero or more - calls to the stream's `async_read_some` function, and is - known as a composed operation. The program must - ensure that the stream performs no other reads until - this operation completes. - - @param stream The stream from which the data is to be - read. The type must meet the AsyncReadStream - requirements. - - @param parser The parser to use. The object must remain - valid at least until the handler is called; ownership is - not transferred. - - @param token The completion token that will be used to - produce a completion handler, which will be called when - the read completes. The function signature of the - completion handler must be: - @code - void handler( - error_code const& error, // result of operation - std::size_t bytes_transferred // the number of bytes consumed by the parser - ); - @endcode - Regardless of whether the asynchronous operation - completes immediately or not, the completion handler - will not be invoked from within this function. On - immediate completion, invocation of the handler will be - performed in a manner equivalent to using - `asio::async_immediate`. - - @par Per-Operation Cancellation - - This asynchronous operation supports cancellation for - the following `asio::cancellation_type` values: - - @li `asio::cancellation_type::terminal` - @li `asio::cancellation_type::partial` - @li `asio::cancellation_type::total` - - if they are also supported by the AsyncReadStream type's - async_read_some operation. -*/ -template< - class AsyncReadStream, - BOOST_ASIO_COMPLETION_TOKEN_FOR( - void(system::error_code, std::size_t)) CompletionToken - BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE( - typename AsyncReadStream::executor_type)> -BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, - void (system::error_code, std::size_t)) -async_read_some( - AsyncReadStream& s, - http::parser& pr, - CompletionToken&& token - BOOST_ASIO_DEFAULT_COMPLETION_TOKEN( - typename AsyncReadStream::executor_type)); - -/** Read a complete message from the stream. - - This function is used to asynchronously read a complete - message from a stream into an instance of parser. - The function call always returns immediately. The - asynchronous operation will continue until one of the - following conditions is true: - - @li The parser reads the entire message. - @li An error occurs. - - This operation is implemented in terms of zero or more - calls to the stream's `async_read_some` function, and is - known as a composed operation. The program must - ensure that the stream performs no other reads until - this operation completes. - - @param stream The stream from which the data is to be - read. The type must meet the AsyncReadStream - requirements. - - @param parser The parser to use. The object must remain - valid at least until the handler is called; ownership is - not transferred. - - @param token The completion token that will be used to - produce a completion handler, which will be called when - the read completes. The function signature of the - completion handler must be: - @code - void handler( - error_code const& error, // result of operation - std::size_t bytes_transferred // the number of bytes consumed by the parser - ); - @endcode - Regardless of whether the asynchronous operation - completes immediately or not, the completion handler - will not be invoked from within this function. On - immediate completion, invocation of the handler will be - performed in a manner equivalent to using - `asio::async_immediate`. - - @par Per-Operation Cancellation - - This asynchronous operation supports cancellation for - the following `asio::cancellation_type` values: - - @li `asio::cancellation_type::terminal` - @li `asio::cancellation_type::partial` - @li `asio::cancellation_type::total` - - if they are also supported by the AsyncReadStream type's - async_read_some operation. -*/ -template< - class AsyncReadStream, - BOOST_ASIO_COMPLETION_TOKEN_FOR( - void(system::error_code, std::size_t)) CompletionToken - BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE( - typename AsyncReadStream::executor_type)> -BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, - void (system::error_code, std::size_t)) -async_read( - AsyncReadStream& s, - http::parser& pr, - CompletionToken&& token - BOOST_ASIO_DEFAULT_COMPLETION_TOKEN( - typename AsyncReadStream::executor_type)); - -} // beast2 -} // boost - -#include - -#endif diff --git a/include/boost/beast2/server/call_mf.hpp b/include/boost/beast2/server/call_mf.hpp deleted file mode 100644 index 6e470b34..00000000 --- a/include/boost/beast2/server/call_mf.hpp +++ /dev/null @@ -1,72 +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_CALL_MF_HPP -#define BOOST_BEAST2_SERVER_CALL_MF_HPP - -#include -#include -#include - -namespace boost { -namespace beast2 { - -namespace detail { -// Primary template -template -struct result_of; - -// Partial specialization for callable types -template -struct result_of -{ -private: - template - static auto test(int) -> decltype(std::declval()(std::declval()...)); - - template - static void test(...); - -public: - using type = decltype(test(0)); -}; -template -using result_of_t = typename result_of::type; -} // detail - -template -class call_mf_impl -{ - T* obj_; - MemFn memfn_; - -public: - call_mf_impl(T* obj, MemFn memfn) - : obj_(obj), memfn_(memfn) - { - } - - template - auto operator()(Args&&... args) const - -> detail::result_of_t - { - return (obj_->*memfn_)(std::forward(args)...); - } -}; - -template -inline call_mf_impl call_mf(MemFn memfn, T* obj) -{ - return call_mf_impl(obj, memfn); -} - -} // beast2 -} // boost - -#endif diff --git a/include/boost/beast2/server/http_server.hpp b/include/boost/beast2/server/http_server.hpp index d1283914..5de920f9 100644 --- a/include/boost/beast2/server/http_server.hpp +++ b/include/boost/beast2/server/http_server.hpp @@ -11,45 +11,61 @@ #define BOOST_BEAST2_SERVER_HTTP_SERVER_HPP #include -#include +#include #include -#include +#include namespace boost { namespace beast2 { -template +/** An HTTP server using Corosio for I/O. +*/ class http_server { public: - ~http_server() = default; + virtual ~http_server() = default; http_server() = default; - router_asio wwwroot; + /** The router for handling HTTP requests. + */ + router_corosio wwwroot; + + /** Run the server. + + This function attaches the current thread to the I/O context + so that it may be used for executing operations. Blocks the + calling thread until the server is stopped and has no + outstanding work. + */ + virtual void run() = 0; - /** Run the server + /** Stop the server. - This function attaches the current thread to I/O context - so that it may be used for executing submitted function - objects. Blocks the calling thread until the part is stopped - and has no outstanding work. + Signals the server to stop accepting new connections and + cancel outstanding operations. */ - virtual void attach() = 0; + virtual void stop() = 0; }; //------------------------------------------------ +/** Install a plain (non-TLS) HTTP server into an application. + + @param app The application to install the server into. + @param addr The address to bind to (e.g. "0.0.0.0"). + @param port The port to listen on. + @param num_workers The number of worker sockets to preallocate. + + @return A reference to the installed server. +*/ BOOST_BEAST2_DECL -auto +http_server& install_plain_http_server( capy::application& app, char const* addr, unsigned short port, - std::size_t num_workers) -> - http_server>&; + std::size_t num_workers); } // beast2 } // boost diff --git a/include/boost/beast2/server/http_stream.hpp b/include/boost/beast2/server/http_stream.hpp index 233cbc6c..8d3f6ca9 100644 --- a/include/boost/beast2/server/http_stream.hpp +++ b/include/boost/beast2/server/http_stream.hpp @@ -14,21 +14,21 @@ #include #include #include -#include -#include -#include -#include -#include -#include +#include +#include #include #include +#include +#include +#include +#include #include #include #include #include #include +#include #include -#include namespace boost { namespace beast2 { @@ -37,137 +37,81 @@ namespace beast2 { /** An HTTP server stream which routes requests to handlers and sends responses. - An object of this type wraps an asynchronous Boost.ASIO stream and implements - a high level server connection which reads HTTP requests, routes them to - handlers installed in a router, and sends the HTTP response. + This class provides a coroutine-based HTTP server session that reads + HTTP requests, routes them to handlers installed in a router, and + sends the HTTP response. - @par Requires - `AsyncStream` must satisfy AsyncReadStream and AsyncWriteStream - - @tparam AsyncStream The type of asynchronous stream. + The session runs as a coroutine task and uses Corosio for async I/O. */ -template class http_stream - : private http::suspender::owner { public: /** Constructor. - This initializes a new HTTP connection object that operates on - the given stream, uses the specified router to dispatch incoming - requests, and calls the supplied completion function when the - connection closes or fails. - - Construction does not start any I/O; call @ref on_stream_begin when - the stream is connected to the remote peer to begin reading - requests and processing them. + Initializes a new HTTP connection object that operates on + the given socket and uses the specified router to dispatch + incoming requests. @param app The owning application, used to access shared services such as logging and protocol objects. - @param stream The underlying asynchronous stream to read from - and write to. The caller is responsible for maintaining its - lifetime for the duration of the session. + @param sock The socket to read from and write to. @param routes The router used to dispatch incoming HTTP requests. - @param close_fn The function invoked when the connection is closed - or an unrecoverable error occurs. */ http_stream( capy::application& app, - AsyncStream& stream, - router_asio routes, - any_lambda close_fn); + corosio::socket& sock, + router_corosio routes); + + /** Run the HTTP session as a coroutine. - /** Called to start a new HTTP session + Reads HTTP requests, dispatches them through the router, + and writes responses until the connection closes or an + error occurs. - The stream must be in a connected, - correct state for a new session. + @param config The acceptor configuration for this connection. + + @return A task that completes when the session ends. */ - void on_stream_begin(http::acceptor_config const& config); + capy::task + run(http::acceptor_config const& config); private: - void do_read(); - void on_read( - system::error_code ec, - std::size_t bytes_transferred); + capy::task> + read_header(); + + capy::task> + read_body(); + + capy::task> + write_response(); + void on_headers(); - void do_dispatch(http::route_result rv = {}); - void do_read_body(); - void on_read_body( - system::error_code ec, - std::size_t bytes_transferred); + http::route_result do_dispatch(); void do_respond(http::route_result rv); - void do_write(); - void on_write( - system::error_code const& ec, - std::size_t bytes_transferred); - void on_complete(); - http::resumer do_suspend() override; - void do_resume(http::route_result const& rv) override; - void do_resume(std::exception_ptr ep) override; - void do_close(); - void do_fail(core::string_view s, - system::error_code const& ec); - void clear() noexcept; -protected: std::string id() const { return std::string("[") + std::to_string(id_) + "] "; } -protected: - struct resetter; + void clear() noexcept; + section sect_; std::size_t id_ = 0; - AsyncStream& stream_; - router_asio routes_; - any_lambda close_; + corosio::socket& sock_; + router_corosio routes_; http::acceptor_config const* pconfig_ = nullptr; - - using work_guard = asio::executor_work_guard().get_executor())>; - std::unique_ptr pwg_; - asio_route_params rp_; -}; - -//------------------------------------------------ - -// for exception safety -template -struct http_stream:: - resetter -{ - ~resetter() - { - if(clear_) - owner_.clear(); - } - - explicit resetter( - http_stream& owner) noexcept - : owner_(owner) - { - } - - void accept() - { - clear_ = false; - } - -private: - http_stream& owner_; - bool clear_ = true; + corosio_route_params rp_; }; //------------------------------------------------ -template -http_stream:: +inline +http_stream:: http_stream( capy::application& app, - AsyncStream& stream, - router_asio routes, - any_lambda close) + corosio::socket& sock, + router_corosio routes) : sect_(use_log_service(app).get_section("http_stream")) , id_( []() noexcept @@ -175,340 +119,290 @@ http_stream( static std::size_t n = 0; return ++n; }()) - , stream_(stream) + , sock_(sock) , routes_(std::move(routes)) - , close_(close) - , rp_(stream_) + , rp_(sock_) { rp_.parser = http::request_parser(app); - rp_.serializer = http::serializer(app); - rp_.suspend = http::suspender(*this); - rp_.ex = wrap_executor(stream_.get_executor()); + // Note: suspend mechanism removed - handlers must complete synchronously } -// called to start a new HTTP session. -// the connection must be in the correct state already. -template -void -http_stream:: -on_stream_begin( - http::acceptor_config const& config) +inline +capy::task +http_stream:: +run(http::acceptor_config const& config) { pconfig_ = &config; - rp_.parser.reset(); - rp_.session_data.clear(); - do_read(); -} + for (;;) + { + // Reset parser for new request + rp_.parser.reset(); + rp_.session_data.clear(); + rp_.parser.start(); + + // Read HTTP request header + auto [ec, n] = co_await read_header(); + if (ec) + { + LOG_TRC(sect_)("{} read_header: {}", id(), ec.message()); + break; + } -// begin reading the request -template -void -http_stream:: -do_read() -{ - rp_.parser.start(); + LOG_TRC(sect_)("{} read_header bytes={}", id(), n); - beast2::async_read_some( - stream_, - rp_.parser, - call_mf(&http_stream::on_read, this)); -} + // Process headers and dispatch + on_headers(); -// called when the read operation completes -template -void -http_stream:: -on_read( - system::error_code ec, - std::size_t bytes_transferred) -{ - (void)bytes_transferred; + auto rv = do_dispatch(); + do_respond(rv); - if(ec.failed()) - return do_fail("http_stream::on_read", ec); + // Write response + if (!rp_.serializer.is_done()) + { + auto [wec, wn] = co_await write_response(); + if (wec) + { + LOG_TRC(sect_)("{} write_response: {}", id(), wec.message()); + break; + } + LOG_TRC(sect_)("{} write_response bytes={}", id(), wn); + } - LOG_TRC(this->sect_)( - "{} http_stream::on_read bytes={}", - this->id(), bytes_transferred); + // Check keep-alive + if (!rp_.res.keep_alive()) + break; + } - on_headers(); + clear(); } -// called to set up the response after reading the request -template -void -http_stream:: -on_headers() +inline +capy::task> +http_stream:: +read_header() { - // set up Request and Response objects - // VFALCO HACK for now we make a copy of the message - rp_.req = rp_.parser.get(); - rp_.route_data.clear(); - rp_.res.set_start_line( // VFALCO WTF - http::status::ok, rp_.req.version()); - rp_.res.set_keep_alive(rp_.req.keep_alive()); - rp_.serializer.reset(); + std::size_t total_bytes = 0; + system::error_code ec; - // parse the URL + for (;;) { - auto rv = urls::parse_uri_reference(rp_.req.target()); - if(rv.has_error()) + // Try to parse what we have + rp_.parser.parse(ec); + + if (ec == http::condition::need_more_input) { - // error parsing URL - rp_.status(http::status::bad_request); - rp_.set_body("Bad Request: " + rv.error().message()); - return do_respond(rv.error()); + // Need to read more data + auto buf = rp_.parser.prepare(); + auto [read_ec, n] = co_await sock_.read_some(buf); + + if (read_ec) + { + co_return {read_ec, total_bytes}; + } + + if (n == 0) + { + // EOF + rp_.parser.commit_eof(); + ec = {}; + } + else + { + rp_.parser.commit(n); + total_bytes += n; + } + continue; } - rp_.url = rv.value(); - } + if (ec.failed()) + { + co_return {ec, total_bytes}; + } - // invoke handlers for the route - do_dispatch(); + // Header complete + if (rp_.parser.got_header()) + { + co_return {{}, total_bytes}; + } + } } -// called to dispatch or resume the route -template -void -http_stream:: -do_dispatch( - http::route_result rv) +inline +capy::task> +http_stream:: +read_body() { - if(! rv.failed()) - { - BOOST_ASSERT(! pwg_); // can't be suspended - rv = routes_.dispatch( - rp_.req.method(), rp_.url, rp_); - } - else + std::size_t total_bytes = 0; + system::error_code ec; + + while (!rp_.parser.is_complete()) { - rv = routes_.resume(rp_, rv); + rp_.parser.parse(ec); + + if (ec == http::condition::need_more_input) + { + auto buf = rp_.parser.prepare(); + auto [read_ec, n] = co_await sock_.read_some(buf); + + if (read_ec) + { + co_return {read_ec, total_bytes}; + } + + if (n == 0) + { + rp_.parser.commit_eof(); + } + else + { + rp_.parser.commit(n); + total_bytes += n; + } + continue; + } + + if (ec.failed()) + { + co_return {ec, total_bytes}; + } } - do_respond(rv); + co_return {{}, total_bytes}; } -// finish reading the body -template -void -http_stream:: -do_read_body() +inline +capy::task> +http_stream:: +write_response() { - beast2::async_read( - stream_, - rp_.parser, - call_mf(&http_stream::on_read_body, this)); + std::size_t total_bytes = 0; + + while (!rp_.serializer.is_done()) + { + auto rv = rp_.serializer.prepare(); + if (!rv) + { + co_return {rv.error(), total_bytes}; + } + + auto bufs = *rv; + std::size_t buf_size = 0; + for (auto const& buf : bufs) + buf_size += buf.size(); + + if (buf_size == 0) + { + // Serializer done + break; + } + + // Write buffers - we need to write them all + std::size_t written = 0; + for (auto const& buf : bufs) + { + auto [ec, n] = co_await sock_.write_some( + capy::const_buffer(buf.data(), buf.size())); + if (ec) + { + co_return {ec, total_bytes + written}; + } + written += n; + } + + rp_.serializer.consume(written); + total_bytes += written; + } + + co_return {{}, total_bytes}; } -// called repeatedly when reading the body -template -void -http_stream:: -on_read_body( - system::error_code ec, - std::size_t bytes_transferred) +inline +void +http_stream:: +on_headers() { - if(ec.failed()) - return do_fail("http_stream::on_read_body", ec); - - LOG_TRC(this->sect_)( - "{} http_stream::on_read_body bytes={}", - this->id(), bytes_transferred); + // Set up Request and Response objects + rp_.req = rp_.parser.get(); + rp_.route_data.clear(); + rp_.res.set_start_line( + http::status::ok, rp_.req.version()); + rp_.res.set_keep_alive(rp_.req.keep_alive()); + rp_.serializer.reset(); - BOOST_ASSERT(rp_.parser.is_complete()); + // Parse the URL + auto rv = urls::parse_uri_reference(rp_.req.target()); + if (rv.has_error()) + { + rp_.status(http::status::bad_request); + rp_.set_body("Bad Request: " + rv.error().message()); + return; + } - rp_.do_finish(); + rp_.url = rv.value(); } -// called after obtaining a route result -template -void -http_stream:: -do_respond( - http::route_result rv) +inline +http::route_result +http_stream:: +do_dispatch() { - BOOST_ASSERT(rv != http::route::next_route); + return routes_.dispatch( + rp_.req.method(), rp_.url, rp_); +} - if(rv == http::route::close) +inline +void +http_stream:: +do_respond(http::route_result rv) +{ + if (rv == http::route::close) { - return do_close(); + rp_.res.set_keep_alive(false); + return; } - if(rv == http::route::complete) + if (rv == http::route::complete) { - // VFALCO what if the connection was closed or keep-alive=false? - // handler sent the response? - BOOST_ASSERT(rp_.serializer.is_done()); - return on_write(system::error_code(), 0); + // Handler sent the response directly + return; } - if(rv == http::route::suspend) + if (rv == http::route::suspend) { - // didn't call suspend()? - if(! pwg_) - detail::throw_logic_error(); - if(rp_.parser.is_body_set()) - return do_read_body(); + // Suspend not supported - treat as internal error + rp_.status(http::status::internal_server_error); + rp_.set_body("Handler suspend not supported"); + rp_.res.set_keep_alive(false); return; } - if(rv == http::route::next) + if (rv == http::route::next) { - // unhandled request + // Unhandled request auto const status = http::status::not_found; rp_.status(status); rp_.set_body(http::to_string(status)); + return; } - else if(rv != http::route::send) + + if (rv != http::route::send) { - // error message of last resort + // Error message of last resort BOOST_ASSERT(rv.failed()); - BOOST_ASSERT(! http::is_route_result(rv)); + BOOST_ASSERT(!http::is_route_result(rv)); rp_.status(http::status::internal_server_error); std::string s; format_to(s, "An internal server error occurred: {}", rv.message()); - rp_.res.set_keep_alive(false); // VFALCO? + rp_.res.set_keep_alive(false); rp_.set_body(s); } - - do_write(); -} - -// begin writing the response -template -void -http_stream:: -do_write() -{ - BOOST_ASSERT(! rp_.serializer.is_done()); - beast2::async_write(stream_, rp_.serializer, - call_mf(&http_stream::on_write, this)); -} - -// called when the write operation completes -template -void -http_stream:: -on_write( - system::error_code const& ec, - std::size_t bytes_transferred) -{ - (void)bytes_transferred; - - if(ec.failed()) - return do_fail("http_stream::on_write", ec); - - BOOST_ASSERT(rp_.serializer.is_done()); - - LOG_TRC(this->sect_)( - "{} http_stream::on_write bytes={}", - this->id(), bytes_transferred); - - if(rp_.res.keep_alive()) - return do_read(); - - do_close(); -} - -template -auto -http_stream:: -do_suspend() -> - http::resumer -{ - BOOST_ASSERT(stream_.get_executor().running_in_this_thread()); - - // can't call twice - BOOST_ASSERT(! pwg_); - pwg_.reset(new work_guard(stream_.get_executor())); - - return http::resumer(*this); -} - -// called by resume(rv) -template -void -http_stream:: -do_resume( - http::route_result const& rv) -{ - asio::dispatch( - stream_.get_executor(), - [this, rv] - { - BOOST_ASSERT(pwg_.get() != nullptr); - pwg_.reset(); - - do_dispatch(rv); - }); -} - -// called by resume(ep) -template -void -http_stream:: -do_resume( - std::exception_ptr ep) -{ - asio::dispatch( - stream_.get_executor(), - [this, ep] - { - BOOST_ASSERT(pwg_.get() != nullptr); - pwg_.reset(); - - rp_.status(http::status::internal_server_error); - try - { - std::rethrow_exception(ep); - } - catch(std::exception const& e) - { - std::string s; - format_to(s, "An internal server error occurred: {}", e.what()); - rp_.set_body(s); - } - catch(...) - { - rp_.set_body("An internal server error occurred"); - } - rp_.res.set_keep_alive(false); - do_write(); - }); -} - -// called when a non-recoverable error occurs -template -void -http_stream:: -do_fail( - core::string_view s, system::error_code const& ec) -{ - LOG_TRC(this->sect_)("{}: {}", s, ec.message()); - - // tidy up lingering objects - rp_.parser.reset(); - rp_.serializer.reset(); - - close_(ec); -} - -// end the session -template -void -http_stream:: -do_close() -{ - clear(); - close_({}); } -// clear everything, releasing transient objects -template +inline void -http_stream:: +http_stream:: clear() noexcept { rp_.parser.reset(); diff --git a/include/boost/beast2/server/plain_worker.hpp b/include/boost/beast2/server/plain_worker.hpp index 8a7c40f7..a8acba9a 100644 --- a/include/boost/beast2/server/plain_worker.hpp +++ b/include/boost/beast2/server/plain_worker.hpp @@ -10,177 +10,9 @@ #ifndef BOOST_BEAST2_SERVER_PLAIN_WORKER_HPP #define BOOST_BEAST2_SERVER_PLAIN_WORKER_HPP -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace boost { -namespace beast2 { - -template -class plain_worker - : private boost::base_from_member< - asio::basic_stream_socket> - , public http_stream< - asio::basic_stream_socket> -{ - using base_member = boost::base_from_member< - asio::basic_stream_socket>; - -public: - using executor_type = Executor; - using protocol_type = Protocol; - using socket_type = - asio::basic_stream_socket; - using stream_type = socket_type; - using acceptor_config = http::acceptor_config; - - template - plain_worker( - workers_base& wb, - Executor0 const& ex, - router_asio rr); - - capy::application& app() noexcept - { - return wb_.app(); - } - - socket_type& socket() noexcept - { - return this->member; - } - - typename Protocol::endpoint& - endpoint() noexcept - { - return ep_; - } - - /** Cancel all outstanding I/O - */ - void cancel(); - - // Called when an incoming connection is accepted - void on_accept(acceptor_config const* pconfig); - - void do_fail( - core::string_view s, system::error_code const& ec); - - void reset(); - -private: - /** Called when the logical session ends - */ - void do_close(system::error_code const& ec); +// plain_worker functionality is now part of workers.hpp +// This header is kept for compatibility - workers_base& wb_; - typename Protocol::endpoint ep_; -}; - -//------------------------------------------------ - -template -template -plain_worker:: -plain_worker( - workers_base& wb, - Executor0 const& ex, - router_asio rr) - : base_member(Executor(ex)) - , http_stream( - wb.app(), - this->member, - std::move(rr), - [this](system::error_code const& ec) - { - this->do_close(ec); - }) - , wb_(wb) -{ -} - -template -void -plain_worker:: -cancel() -{ - system::error_code ec; - this->member.cancel(ec); -} - -//-------------------------------------------- - -// Called when an incoming connection is accepted -template -void -plain_worker:: -on_accept(acceptor_config const* pconfig) -{ - BOOST_ASSERT(this->member.get_executor().running_in_this_thread()); - // VFALCO TODO timeout - this->on_stream_begin(*pconfig); -} - -template -void -plain_worker:: -do_fail( - core::string_view s, system::error_code const& ec) -{ - reset(); - - if(ec == asio::error::operation_aborted) - { - LOG_TRC(this->sect_)( - "{} {}: {}", - this->id(), s, ec.message()); - // this means the worker was stopped, don't submit new work - return; - } - - LOG_DBG(this->sect_)( - "{} {}: {}", - this->id(), s, ec.message()); - wb_.do_idle(this); -} - -template -void -plain_worker:: -reset() -{ - // Clean up any previous connection. - system::error_code ec; - this->member.close(ec); -} - -/** Close the connection to end the session -*/ -template -void -plain_worker:: -do_close(system::error_code const& ec) -{ - if(! ec.failed()) - { - reset(); - wb_.do_idle(this); - return; - } - - do_fail("plain_worker::do_close", ec); -} - -} // beast2 -} // boost +#include #endif diff --git a/include/boost/beast2/server/route_handler_asio.hpp b/include/boost/beast2/server/route_handler_corosio.hpp similarity index 58% rename from include/boost/beast2/server/route_handler_asio.hpp rename to include/boost/beast2/server/route_handler_corosio.hpp index ac221099..b8a5d35c 100644 --- a/include/boost/beast2/server/route_handler_asio.hpp +++ b/include/boost/beast2/server/route_handler_corosio.hpp @@ -7,38 +7,33 @@ // Official repository: https://github.com/cppalliance/beast2 // -#ifndef BOOST_BEAST2_SERVER_ROUTE_HANDLER_ASIO_HPP -#define BOOST_BEAST2_SERVER_ROUTE_HANDLER_ASIO_HPP +#ifndef BOOST_BEAST2_SERVER_ROUTE_HANDLER_COROSIO_HPP +#define BOOST_BEAST2_SERVER_ROUTE_HANDLER_COROSIO_HPP #include #include -#include +#include namespace boost { namespace beast2 { -/** Route parameters object for Asio HTTP route handlers +/** Route parameters object for Corosio HTTP route handlers */ -template -class asio_route_params +class corosio_route_params : public http::route_params { public: - using stream_type = typename std::decay::type; + using stream_type = corosio::socket; - AsyncStream stream; + corosio::socket& stream; - template explicit - asio_route_params( - Args&&... args) - : stream(std::forward(args)...) + corosio_route_params( + corosio::socket& s) + : stream(s) { } -private: -public: - // VFALCO This needs to be private void do_finish() { if(finish_) @@ -48,7 +43,6 @@ class asio_route_params f(); } } - }; } // beast2 diff --git a/include/boost/beast2/server/router_asio.hpp b/include/boost/beast2/server/router_corosio.hpp similarity index 63% rename from include/boost/beast2/server/router_asio.hpp rename to include/boost/beast2/server/router_corosio.hpp index 481e7b5d..c0d89e4a 100644 --- a/include/boost/beast2/server/router_asio.hpp +++ b/include/boost/beast2/server/router_corosio.hpp @@ -7,21 +7,19 @@ // Official repository: https://github.com/cppalliance/beast2 // -#ifndef BOOST_BEAST2_SERVER_ROUTER_ASIO_HPP -#define BOOST_BEAST2_SERVER_ROUTER_ASIO_HPP +#ifndef BOOST_BEAST2_SERVER_ROUTER_COROSIO_HPP +#define BOOST_BEAST2_SERVER_ROUTER_COROSIO_HPP #include #include -#include +#include namespace boost { namespace beast2 { -/** The Asio-aware router type +/** The Corosio-aware router type */ -template -using router_asio = http::basic_router< - asio_route_params>; +using router_corosio = http::basic_router; } // beast2 } // boost diff --git a/include/boost/beast2/server/workers.hpp b/include/boost/beast2/server/workers.hpp index 40cd76c3..7713d4de 100644 --- a/include/boost/beast2/server/workers.hpp +++ b/include/boost/beast2/server/workers.hpp @@ -13,301 +13,222 @@ #include #include #include +#include +#include #include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include + +#include namespace boost { namespace beast2 { -class BOOST_SYMBOL_VISIBLE - workers_base +/** A preallocated worker that handles one HTTP connection at a time. +*/ +struct http_worker { -public: - BOOST_BEAST2_DECL - virtual ~workers_base(); - - virtual capy::application& app() noexcept = 0; - virtual void do_idle(void* worker) = 0; -}; - -/** A set of accepting sockets and their workers. - - This implements a set of listening ports as a server service. An array of - workers created upon construction is used to accept incoming connections - and handle their sessions. + corosio::socket sock; + bool in_use = false; - @par Worker exemplar - @code - template< class Executor > - struct Worker + explicit http_worker(corosio::io_context& ioc) + : sock(ioc) { - using executor_type = Executor; - using protocol_type = asio::ip::tcp; - using socket_type = asio::basic_stream_socket; - using acceptor_config = beast2::acceptor_config; + } + + http_worker(http_worker&&) = default; + http_worker& operator=(http_worker&&) = default; +}; - asio::basic_stream_socket& socket() noexcept; - typename protocol_type::endpoint& endpoint() noexcept; +//------------------------------------------------ - void on_accept(); - }; - @endcode +/** A set of accepting sockets and their workers. - @tparam Executor The type of executor used by acceptor sockets. - @tparam Worker The type of worker to use. + This implements a server that accepts incoming connections + and handles HTTP requests using preallocated workers. */ -template class workers - : public workers_base { public: - using executor_type = typename Worker::executor_type; - using protocol_type = typename Worker::protocol_type; - using acceptor_type = asio::basic_socket_acceptor; - using acceptor_config = typename Worker::acceptor_config; - using socket_type = asio::basic_stream_socket; - - ~workers() - { - } + ~workers() = default; /** Constructor - @param app The @ref application which holds this part - @param ex The executor to use for acceptor sockets - @param concurrency The number of threads calling io_context::run - @param num_workers The number of workers to construct - @param args Arguments forwarded to each worker's constructor + @param app The application which holds this server + @param ioc The I/O context for async operations + @param num_workers The number of workers to preallocate + @param routes The router for dispatching requests */ - template workers( capy::application& app, - Executor1 const& ex, - unsigned concurrency, + corosio::io_context& ioc, std::size_t num_workers, - Args&&... args); + router_corosio routes); - /** Add an acceptor + /** Add a listening endpoint + + @param config Acceptor configuration + @param ep The endpoint to listen on */ - template void - emplace( - acceptor_config&& config, - Args&&... args) - { - va_.emplace_back( - concurrency_, - std::move(config), - acceptor_type(ex_, - std::forward(args)...)); - } + listen( + http::acceptor_config config, + corosio::endpoint ep); + + /** Start the accept loop + Must be called after listen() to begin accepting connections. + */ void start(); + + /** Stop accepting and cancel all connections + */ void stop(); private: - struct acceptor; - struct worker; + capy::task + accept_loop(corosio::acceptor& acc, http::acceptor_config config); - capy::application& app() noexcept override; - void do_idle(void*) override; - void do_accepts(); - void on_accept(acceptor*, worker*, - system::error_code const&); - void do_stop(); + capy::task + run_session(http_worker& w, http::acceptor_config const& config); capy::application& app_; + corosio::io_context& ioc_; section sect_; - executor_type ex_; - fixed_array vw_; - std::vector va_; - worker* idle_ = nullptr; - std::size_t n_idle_ = 0; - unsigned concurrency_; - bool stop_ = false; + router_corosio routes_; + std::vector workers_; + std::vector acceptors_; + std::vector configs_; + bool stopped_ = false; }; //------------------------------------------------ -template -struct workers:: - acceptor -{ - template - explicit - acceptor( - std::size_t concurrency, - acceptor_config&& config_, - acceptor_type&& sock_) - : config(std::move(config_)) - , sock(std::move(sock_)) - , need(concurrency) - { - } - - acceptor_config config; - asio::basic_socket_acceptor< - protocol_type, executor_type> sock; - std::size_t need; // number of accepts we need -}; - -template -struct workers:: - worker -{ - worker* next; - Worker w; - - template - explicit worker( - worker* next_, Args&&... args) - : next(next_) - , w(std::forward(args)...) - { - } -}; - -//------------------------------------------------ - -template -template -workers:: +inline +workers:: workers( capy::application& app, - Executor1 const& ex, - unsigned concurrency, + corosio::io_context& ioc, std::size_t num_workers, - Args&&... args) + router_corosio routes) : app_(app) + , ioc_(ioc) , sect_(use_log_service(app).get_section("workers")) - , ex_(executor_type(ex)) - , vw_(num_workers) - , concurrency_(concurrency) + , routes_(std::move(routes)) { - while(! vw_.is_full()) - idle_ = &vw_.emplace_back(idle_, *this, - std::forward(args)...); - n_idle_ = vw_.size(); + workers_.reserve(num_workers); + for (std::size_t i = 0; i < num_workers; ++i) + workers_.emplace_back(ioc_); } -template -capy::application& -workers:: -app() noexcept -{ - return app_; -} - -template +inline void -workers:: -do_idle(void* pw) +workers:: +listen( + http::acceptor_config config, + corosio::endpoint ep) { - asio::dispatch(ex_, - [this, pw]() - { - // recover the `worker` pointer without using offsetof - worker* w = vw_.data() + ( - reinterpret_cast(pw) - - reinterpret_cast(vw_.data())) / - sizeof(worker); - // push - w->next = idle_; - idle_ = w; - ++n_idle_; - do_accepts(); - }); + acceptors_.emplace_back(ioc_); + acceptors_.back().listen(ep); + configs_.push_back(config); } -template + +inline void -workers:: +workers:: start() { - asio::dispatch(ex_, call_mf(&workers::do_accepts, this)); + stopped_ = false; + for (std::size_t i = 0; i < acceptors_.size(); ++i) + { + capy::async_run(ioc_.get_executor())( + accept_loop(acceptors_[i], configs_[i])); + } } -template +inline void -workers:: +workers:: stop() { - asio::dispatch(ex_, call_mf(&workers::do_stop, this)); + stopped_ = true; + for (auto& acc : acceptors_) + acc.cancel(); + for (auto& w : workers_) + { + if (w.in_use) + w.sock.cancel(); + } } -template -void -workers:: -do_accepts() +inline +capy::task +workers:: +accept_loop(corosio::acceptor& acc, http::acceptor_config config) { - BOOST_ASSERT(ex_.running_in_this_thread()); - if(stop_) - return; - if(idle_) + while (!stopped_) { - for(auto& a : va_) + // Find a free worker + http_worker* free_worker = nullptr; + for (auto& w : workers_) { - while(a.need > 0) + if (!w.in_use) { - --a.need; - // pop - auto pw = idle_; - idle_ = idle_->next; - --n_idle_; - a.sock.async_accept(pw->w.socket(), pw->w.endpoint(), - asio::prepend(call_mf(&workers::on_accept, this), &a, pw)); - if(! idle_) - goto busy; + free_worker = &w; + break; } } - return; - } -busy: - // all workers are busy - // VFALCO log to warn the server admin? - return; -} -template -void -workers:: -on_accept( - acceptor* pa, - worker* pw, - system::error_code const& ec) -{ - BOOST_ASSERT(ex_.running_in_this_thread()); - ++pa->need; - if(ec.failed()) - { - // push - pw->next = idle_; - idle_ = pw; - ++n_idle_; - LOG_DBG(sect_)("async_accept: {}", ec.message()); - return do_accepts(); + if (!free_worker) + { + // All workers busy - accept and immediately close + // A production server might queue or have backpressure + LOG_DBG(sect_)("All workers busy, rejecting connection"); + corosio::socket temp(ioc_); + auto [ec] = co_await acc.accept(temp); + if (ec) + { + if (stopped_) + break; + LOG_DBG(sect_)("accept error: {}", ec.message()); + } + temp.close(); + continue; + } + + // Accept into the free worker's socket + auto [ec] = co_await acc.accept(free_worker->sock); + if (ec) + { + if (stopped_) + break; + LOG_DBG(sect_)("accept error: {}", ec.message()); + continue; + } + + // Spawn session coroutine + capy::async_run(ioc_.get_executor())( + run_session(*free_worker, config)); } - do_accepts(); - asio::dispatch(pw->w.socket().get_executor(), asio::prepend( - call_mf(&Worker::on_accept, &pw->w), &pa->config)); } -template -void -workers:: -do_stop() +inline +capy::task +workers:: +run_session(http_worker& w, http::acceptor_config const& config) { - stop_ = true; + w.in_use = true; - for(auto& a : va_) - { - system::error_code ec; - a.sock.cancel(ec); // error ignored - } - for(auto& w : vw_) - w.w.cancel(); + http_stream stream(app_, w.sock, routes_); + co_await stream.run(config); + + w.sock.close(); + w.in_use = false; } } // beast2 diff --git a/include/boost/beast2/test/detail/service_base.hpp b/include/boost/beast2/test/detail/service_base.hpp deleted file mode 100644 index b82cb825..00000000 --- a/include/boost/beast2/test/detail/service_base.hpp +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright (c) 2016-2019 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/cppaliance/beast2 -// - -#ifndef BOOST_BEAST2_TEST_DETAIL_SERVICE_BASE_HPP -#define BOOST_BEAST2_TEST_DETAIL_SERVICE_BASE_HPP - -#include - -namespace boost { -namespace beast2 { -namespace test { -namespace detail { - -template -struct service_base : asio::execution_context::service -{ - static asio::execution_context::id const id; - - explicit - service_base(asio::execution_context& ctx) - : asio::execution_context::service(ctx) - { - } -}; - -template -asio::execution_context::id const service_base::id; - -} // detail -} // test -} // beast2 -} // boost - -#endif diff --git a/include/boost/beast2/test/detail/stream_state.hpp b/include/boost/beast2/test/detail/stream_state.hpp deleted file mode 100644 index 5760f7a5..00000000 --- a/include/boost/beast2/test/detail/stream_state.hpp +++ /dev/null @@ -1,245 +0,0 @@ -// -// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) -// Copyright (c) 2020 Richard Hodges (hodges.r@gmail.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/cppaliance/beast2 -// - -#ifndef BOOST_BEAST2_TEST_DETAIL_STREAM_STATE_HPP -#define BOOST_BEAST2_TEST_DETAIL_STREAM_STATE_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace boost { -namespace beast2 { -namespace test { -namespace detail { - -struct stream_state; - -struct stream_service_impl -{ - std::mutex m_; - std::vector v_; - - void - remove(stream_state& impl); -}; - -//------------------------------------------------------------------------------ - -class stream_service - : public beast2::test::detail::service_base -{ - boost::shared_ptr sp_; - - void - shutdown() override; - -public: - explicit - stream_service(asio::execution_context& ctx); - - static - auto - make_impl( - asio::any_io_executor exec, - test::fail_count* fc) -> - boost::shared_ptr; -}; - -//------------------------------------------------------------------------------ - -struct stream_op_base -{ - virtual ~stream_op_base() = default; - virtual void operator()(system::error_code ec) = 0; -}; - -//------------------------------------------------------------------------------ - -enum class stream_status -{ - ok, - eof, -}; - -//------------------------------------------------------------------------------ - -struct stream_state -{ - asio::any_io_executor exec; - boost::weak_ptr wp; - std::mutex m; - std::string storage; - capy::string_buffer b; - //std::condition_variable cv; - std::unique_ptr rop; - std::unique_ptr wop; - stream_status code = stream_status::ok; - fail_count* fc = nullptr; - std::size_t nread = 0; - std::size_t nread_bytes = 0; - std::size_t nwrite = 0; - std::size_t nwrite_bytes = 0; - std::size_t read_max = - (std::numeric_limits::max)(); - std::size_t write_max = - (std::numeric_limits::max)(); - - stream_state( - asio::any_io_executor exec_, - boost::weak_ptr wp_, - fail_count* fc_); - - stream_state(stream_state&&) = delete; - - ~stream_state(); - - void - remove() noexcept; - - void - notify_read(); -}; - -//------------------------------------------------------------------------------ - -inline -stream_service:: -stream_service(asio::execution_context& ctx) - : beast2::test::detail::service_base(ctx) - , sp_(boost::make_shared()) -{ -} - -inline -void -stream_service:: -shutdown() -{ - std::vector> v; - { - std::lock_guard g1(sp_->m_); - v.reserve(2 * sp_->v_.size()); - for(auto p : sp_->v_) - { - std::lock_guard g2(p->m); - v.emplace_back(std::move(p->rop)); - v.emplace_back(std::move(p->wop)); - p->code = detail::stream_status::eof; - } - } -} - -inline -auto -stream_service:: -make_impl( - asio::any_io_executor exec, - test::fail_count* fc) -> - boost::shared_ptr -{ -#if defined(BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT) - auto& ctx = exec.context(); -#else - auto& ctx = asio::query( - exec, - asio::execution::context); -#endif - auto& svc = asio::use_service(ctx); - auto sp = boost::make_shared(exec, svc.sp_, fc); - std::lock_guard g(svc.sp_->m_); - svc.sp_->v_.push_back(sp.get()); - return sp; -} - -//------------------------------------------------------------------------------ - -inline -void -stream_service_impl:: -remove(stream_state& impl) -{ - std::lock_guard g(m_); - *std::find( - v_.begin(), v_.end(), - &impl) = std::move(v_.back()); - v_.pop_back(); -} - -//------------------------------------------------------------------------------ - -inline -stream_state:: -stream_state( - asio::any_io_executor exec_, - boost::weak_ptr wp_, - fail_count* fc_) - : exec(std::move(exec_)) - , wp(std::move(wp_)) - , b(&storage) - , fc(fc_) -{ -} - -inline -stream_state:: -~stream_state() -{ - // cancel outstanding read - if(rop != nullptr) - (*rop)(asio::error::operation_aborted); - // cancel outstanding write - if(wop != nullptr) - (*wop)(asio::error::operation_aborted); -} - -inline -void -stream_state:: -remove() noexcept -{ - auto sp = wp.lock(); - - // If this goes off, it means the lifetime of a test::stream object - // extended beyond the lifetime of the associated execution context. - BOOST_ASSERT(sp); - - sp->remove(*this); -} - -inline -void -stream_state:: -notify_read() -{ - if(rop) - { - auto op_ = std::move(rop); - op_->operator()(system::error_code{}); - } -} - -} // detail -} // test -} // beast2 -} // boost - -#endif diff --git a/include/boost/beast2/test/impl/stream.hpp b/include/boost/beast2/test/impl/stream.hpp deleted file mode 100644 index 9f679e59..00000000 --- a/include/boost/beast2/test/impl/stream.hpp +++ /dev/null @@ -1,722 +0,0 @@ -// -// Copyright (c) 2016-2019 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/cppaliance/beast2 -// - -#ifndef BOOST_BEAST2_TEST_IMPL_STREAM_HPP -#define BOOST_BEAST2_TEST_IMPL_STREAM_HPP - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace boost { -namespace beast2 { -namespace test { - -namespace detail -{ -template -struct extract_executor_op -{ - To operator()(asio::any_io_executor& ex) const - { - assert(ex.template target()); - return *ex.template target(); - } -}; - -template<> -struct extract_executor_op -{ - asio::any_io_executor operator()(asio::any_io_executor& ex) const - { - return ex; - } -}; - -template -class lambda_base -{ -protected: - Handler h_; - boost::weak_ptr iwp_; - boost::weak_ptr owp_; - Buffers b_; - asio::executor_work_guard< - asio::associated_executor_t> - wg2_; - - class cancellation_handler - { - public: - explicit cancellation_handler( - boost::weak_ptr wp) - : wp_(std::move(wp)) - { - } - - void - operator()(asio::cancellation_type type) const - { - if(type != asio::cancellation_type::none) - { - if(auto sp = wp_.lock()) - { - std::unique_ptr p; - { - std::lock_guard lock(sp->m); - if(Reader) - p = std::move(sp->rop); - else - p = std::move(sp->wop); - } - if(p != nullptr) - (*p)(asio::error::operation_aborted); - } - } - } - - private: - boost::weak_ptr wp_; - }; - -public: - template - lambda_base( - Handler_&& h, - boost::shared_ptr const& in, - boost::weak_ptr const& out, - Buffers const& b) - : h_(std::forward(h)) - , iwp_(in) - , owp_(out) - , b_(b) - , wg2_(asio::get_associated_executor(h_, in->exec)) - { - auto c_slot = asio::get_associated_cancellation_slot(h_); - if(c_slot.is_connected()) - c_slot.template emplace(iwp_); - } - - using allocator_type = asio::associated_allocator_t; - - allocator_type - get_allocator() const noexcept - { - return asio::get_associated_allocator(h_); - } - - using cancellation_slot_type = - asio::associated_cancellation_slot_t; - - cancellation_slot_type - get_cancellation_slot() const noexcept - { - return asio::get_associated_cancellation_slot( - h_, asio::cancellation_slot()); - } -}; - -} // detail - -template -template -class basic_stream::read_op : public detail::stream_op_base -{ - class lambda : public detail::lambda_base - { - public: - using base = detail::lambda_base; - - void - operator()(system::error_code ec) - { - std::size_t bytes_transferred = 0; - auto sp = base::iwp_.lock(); - if(! sp) - { - ec = asio::error::operation_aborted; - } - if(!ec) - { - std::lock_guard lock(sp->m); - BOOST_ASSERT(!sp->rop); - if(sp->b.size() > 0) - { - bytes_transferred = capy::copy( - base::b_, - sp->b.data(), - sp->read_max); - sp->b.consume(bytes_transferred); - sp->nread_bytes += bytes_transferred; - } - else if( - capy::buffer_size( - base::b_) > 0) - { - ec = asio::error::eof; - } - } - - asio::dispatch( - base::wg2_.get_executor(), - asio::append(std::move(base::h_), ec, bytes_transferred)); - base::wg2_.reset(); - sp->rop.reset(nullptr); - } - - template - lambda( - Handler_&& h, - boost::shared_ptr const& in, - boost::weak_ptr const& out, - Buffers const& b) - : base(std::forward(h), in, out, b) - { - } - }; - - std::unique_ptr fnp_; - asio::executor_work_guard wg1_; - -public: - template - read_op( - Handler_&& h, - boost::shared_ptr const& in, - boost::weak_ptr const& out, - Buffers const& b) - : fnp_(new lambda(std::forward(h), in, out, b)) - , wg1_(in->exec) - { - } - - void - operator()(system::error_code ec) override - { - std::unique_ptr fnp(std::move(fnp_)); - if(fnp) - asio::post( - wg1_.get_executor(), asio::append(std::move(*fnp), ec)); - wg1_.reset(); - } -}; - -template -template -class basic_stream::write_op : public detail::stream_op_base -{ - class lambda : public detail::lambda_base - { - public: - using base = detail::lambda_base; - - void - operator()(system::error_code ec) - { - std::size_t bytes_transferred = 0; - auto isp = base::iwp_.lock(); - if(!isp) - { - ec = asio::error::operation_aborted; - } - auto osp = base::owp_.lock(); - if(!osp) - { - ec = asio::error::operation_aborted; - } - if(!ec) - { - // copy buffers - std::size_t n = std::min( - capy::buffer_size(base::b_), isp->write_max); - { - std::lock_guard lock(osp->m); - n = capy::copy(osp->b.prepare(n), base::b_); - osp->b.commit(n); - osp->nwrite_bytes += n; - osp->notify_read(); - } - bytes_transferred = n; - } - - asio::dispatch( - base::wg2_.get_executor(), - asio::append(std::move(base::h_), ec, bytes_transferred)); - base::wg2_.reset(); - isp->wop.reset(nullptr); - } - - template - lambda( - Handler_&& h, - boost::shared_ptr const& in, - boost::weak_ptr const& out, - Buffers const& b) - : base(std::forward(h), in, out, b) - { - } - }; - - std::unique_ptr fnp_; - asio::executor_work_guard wg1_; - -public: - template - write_op( - Handler_&& h, - boost::shared_ptr const& in, - boost::weak_ptr const& out, - Buffers const& b) - : fnp_(new lambda(std::forward(h), in, out, b)) - , wg1_(in->exec) - { - } - - void - operator()(system::error_code ec) override - { - std::unique_ptr fnp(std::move(fnp_)); - if(fnp) - asio::post(wg1_.get_executor(), asio::append(std::move(*fnp), ec)); - wg1_.reset(); - } -}; - -template -struct basic_stream::run_read_op -{ - boost::shared_ptr const& in_; - - using executor_type = typename basic_stream::executor_type; - - executor_type - get_executor() const noexcept - { - return detail::extract_executor_op()(in_->exec); - } - - template< - class ReadHandler, - class MutableBufferSequence> - void - operator()( - ReadHandler&& h, - boost::weak_ptr out, - MutableBufferSequence const& buffers) - { - // If you get an error on the following line it means - // that your handler does not meet the documented type - // requirements for the handler. - - initiate_read( - in_, - out, - std::unique_ptr{ new read_op< - typename std::decay::type, - MutableBufferSequence>(std::move(h), in_, out, buffers) }, - capy::buffer_size(buffers)); - } -}; - -template -struct basic_stream::run_write_op -{ - boost::shared_ptr const& in_; - - using executor_type = typename basic_stream::executor_type; - - executor_type - get_executor() const noexcept - { - return detail::extract_executor_op()(in_->exec); - } - - template< - class WriteHandler, - class ConstBufferSequence> - void - operator()( - WriteHandler&& h, - boost::weak_ptr out, - ConstBufferSequence const& buffers) - { - // If you get an error on the following line it means - // that your handler does not meet the documented type - // requirements for the handler. - - initiate_write( - in_, - out, - std::unique_ptr{ new write_op< - typename std::decay::type, - ConstBufferSequence>(std::move(h), in_, out, buffers) }, - capy::buffer_size(buffers)); - } -}; - -//------------------------------------------------------------------------------ - -template -template -BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(ReadHandler, void(system::error_code, std::size_t)) -basic_stream:: -async_read_some( - MutableBufferSequence const& buffers, - ReadHandler&& handler) -{ - static_assert(asio::is_mutable_buffer_sequence< - MutableBufferSequence>::value, - "MutableBufferSequence type requirements not met"); - - return asio::async_initiate< - ReadHandler, - void(system::error_code, std::size_t)>( - run_read_op{in_}, - handler, - out_, - buffers); -} - -template -template -BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(WriteHandler, void(system::error_code, std::size_t)) -basic_stream:: -async_write_some( - ConstBufferSequence const& buffers, - WriteHandler&& handler) -{ - static_assert(asio::is_const_buffer_sequence< - ConstBufferSequence>::value, - "ConstBufferSequence type requirements not met"); - - return asio::async_initiate< - WriteHandler, - void(system::error_code, std::size_t)>( - run_write_op{in_}, - handler, - out_, - buffers); -} - -//------------------------------------------------------------------------------ - -template -basic_stream -connect(stream& to, Arg1&& arg1, ArgN&&... argn) -{ - stream from{ - std::forward(arg1), - std::forward(argn)...}; - from.connect(to); - return from; -} - -template -auto basic_stream::get_executor() noexcept -> executor_type -{ - return detail::extract_executor_op()(in_->exec); -} - -//------------------------------------------------------------------------------ - -template -void -basic_stream::initiate_read( - boost::shared_ptr const& in, - boost::weak_ptr const& out, - std::unique_ptr&& rop, - std::size_t buf_size) -{ - (void)out; - - std::unique_lock lock(in->m); - - ++in->nread; - if(in->rop != nullptr) - BOOST_THROW_EXCEPTION(std::logic_error{ "in_->rop != nullptr" }); - - // test failure - system::error_code ec; - if(in->fc && in->fc->fail(ec)) - { - lock.unlock(); - (*rop)(ec); - return; - } - - // A request to read 0 bytes from a stream is a no-op. - if(buf_size == 0 || capy::buffer_size(in->b.data()) > 0) - { - lock.unlock(); - (*rop)(ec); - return; - } - - // deliver error - if(in->code != detail::stream_status::ok) - { - lock.unlock(); - (*rop)(asio::error::eof); - return; - } - - // complete when bytes available or closed - in->rop = std::move(rop); -} - -//------------------------------------------------------------------------------ - -template -void basic_stream::initiate_write( - boost::shared_ptr const& in, - boost::weak_ptr const& out, - std::unique_ptr&& wop, - std::size_t buf_size) -{ - { - std::unique_lock lock(in->m); - - ++in->nwrite; - - // test failure - system::error_code ec; - if(in->fc && in->fc->fail(ec)) - { - lock.unlock(); - (*wop)(ec); - return; - } - } - - // A request to write 0 bytes to a stream is a no-op. - if(buf_size == 0) - { - (*wop)(system::error_code{}); - return; - } - - // connection closed - auto osp = out.lock(); - if(!osp) - { - (*wop)(asio::error::connection_reset); - return; - } - - in->wop = std::move(wop); - //auto op = std::move(in_->wop); - in->wop->operator()(system::error_code{}); -} - -//------------------------------------------------------------------------------ - -template -basic_stream:: -~basic_stream() -{ - close(); - in_->remove(); -} - -template -basic_stream:: -basic_stream(basic_stream&& other) -{ - auto in = detail::stream_service::make_impl( - other.in_->exec, other.in_->fc); - in_ = std::move(other.in_); - out_ = std::move(other.out_); - other.in_ = in; -} - - -template -basic_stream& -basic_stream:: -operator=(basic_stream&& other) -{ - close(); - auto in = detail::stream_service::make_impl( - other.in_->exec, other.in_->fc); - in_->remove(); - in_ = std::move(other.in_); - out_ = std::move(other.out_); - other.in_ = in; - return *this; -} - -//------------------------------------------------------------------------------ - -template -basic_stream:: -basic_stream(executor_type exec) - : in_(detail::stream_service::make_impl(std::move(exec), nullptr)) -{ -} - -template -basic_stream:: -basic_stream( - asio::io_context& ioc, - fail_count& fc) - : in_(detail::stream_service::make_impl(ioc.get_executor(), &fc)) -{ -} - -template -basic_stream:: -basic_stream( - asio::io_context& ioc, - core::string_view s) - : in_(detail::stream_service::make_impl(ioc.get_executor(), nullptr)) -{ - in_->b.commit(capy::copy( - in_->b.prepare(s.size()), - capy::const_buffer(s.data(), s.size()))); -} - -template -basic_stream:: -basic_stream( - asio::io_context& ioc, - fail_count& fc, - core::string_view s) - : in_(detail::stream_service::make_impl(ioc.get_executor(), &fc)) -{ - in_->b.commit(capy::copy( - in_->b.prepare(s.size()), - capy::const_buffer(s.data(), s.size()))); -} - -template -void -basic_stream:: -connect(basic_stream& remote) -{ - BOOST_ASSERT(! out_.lock()); - BOOST_ASSERT(! remote.out_.lock()); - std::lock(in_->m, remote.in_->m); - std::lock_guard guard1{in_->m, std::adopt_lock}; - std::lock_guard guard2{remote.in_->m, std::adopt_lock}; - out_ = remote.in_; - remote.out_ = in_; - in_->code = detail::stream_status::ok; - remote.in_->code = detail::stream_status::ok; -} - -template -core::string_view -basic_stream:: -str() const -{ - auto const bs = in_->b.data(); - if(capy::buffer_size(bs) == 0) - return {}; - capy::const_buffer const b = *asio::buffer_sequence_begin(bs); - return {static_cast(b.data()), b.size()}; -} - -template -void -basic_stream:: -append(core::string_view s) -{ - std::lock_guard lock{in_->m}; - in_->b.commit(capy::copy( - in_->b.prepare(s.size()), - capy::const_buffer(s.data(), s.size()))); -} - -template -void -basic_stream:: -clear() -{ - std::lock_guard lock{in_->m}; - in_->b.consume(in_->b.size()); -} - -template -void -basic_stream:: -close() -{ - { - std::lock_guard lock(in_->m); - in_->code = detail::stream_status::eof; - in_->notify_read(); - } - - // disconnect - { - auto out = out_.lock(); - out_.reset(); - - // notify peer - if(out) - { - std::lock_guard lock(out->m); - if(out->code == detail::stream_status::ok) - { - out->code = detail::stream_status::eof; - out->notify_read(); - } - } - } -} - -template -void -basic_stream:: -close_remote() -{ - std::lock_guard lock{in_->m}; - if(in_->code == detail::stream_status::ok) - { - in_->code = detail::stream_status::eof; - in_->notify_read(); - } -} - -//------------------------------------------------------------------------------ - -template -basic_stream -connect(basic_stream& to) -{ - basic_stream from(to.get_executor()); - from.connect(to); - return from; -} - -template -void -connect(basic_stream& s1, basic_stream& s2) -{ - s1.connect(s2); -} - -} // test -} // beast2 -} // boost - -#endif diff --git a/include/boost/beast2/test/stream.hpp b/include/boost/beast2/test/stream.hpp deleted file mode 100644 index 18979a32..00000000 --- a/include/boost/beast2/test/stream.hpp +++ /dev/null @@ -1,514 +0,0 @@ -// -// Copyright (c) 2016-2019 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/cppaliance/beast2 -// - -#ifndef BOOST_BEAST2_TEST_STREAM_HPP -#define BOOST_BEAST2_TEST_STREAM_HPP - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -// Forward declarations -namespace boost { -namespace asio { -namespace ssl { -template class stream; -} // ssl -} // asio -} // boost - -namespace boost { -namespace beast2 { -namespace test { - -/** A two-way socket useful for unit testing - - An instance of this class simulates a traditional socket, - while also providing features useful for unit testing. - Each endpoint maintains an independent buffer called - the input area. Writes from one endpoint append data - to the peer's pending input area. When an endpoint performs - a read and data is present in the input area, the data is - delivered to the blocking or asynchronous operation. Otherwise - the operation is blocked or deferred until data is made - available, or until the endpoints become disconnected. - - These streams may be used anywhere an algorithm accepts a - reference to a synchronous or asynchronous read or write - stream. It is possible to use a test stream in a call to - `asio::read_until`, or in a call to - @ref boost::beast2::http::async_write for example. - - As with Boost.Asio I/O objects, a @ref stream constructs - with a reference to the `asio::io_context` to use for - handling asynchronous I/O. For asynchronous operations, the - stream follows the same rules as a traditional asio socket - with respect to how completion handlers for asynchronous - operations are performed. - - To facilitate testing, these streams support some additional - features: - - @li The input area, represented by a @ref beast2::basic_flat_buffer, - may be directly accessed by the caller to inspect the contents - before or after the remote endpoint writes data. This allows - a unit test to verify that the received data matches. - - @li Data may be manually appended to the input area. This data - will delivered in the next call to - @ref stream::read_some or @ref stream::async_read_some. - This allows predefined test vectors to be set up for testing - read algorithms. - - @li The stream may be constructed with a fail count. The - stream will eventually fail with a predefined error after a - certain number of operations, where the number of operations - is controlled by the test. When a test loops over a range of - operation counts, it is possible to exercise every possible - point of failure in the algorithm being tested. When used - correctly the technique allows the tests to reach a high - percentage of code coverage. - - @par Thread Safety - @e Distinct @e objects: Safe.@n - @e Shared @e objects: Unsafe. - The application must also ensure that all asynchronous - operations are performed within the same implicit or explicit strand. - - @par Concepts - @li SyncReadStream - @li SyncWriteStream - @li AsyncReadStream - @li AsyncWriteStream -*/ -template -class basic_stream; - -template -class basic_stream -{ -public: - /// The type of the executor associated with the object. - using executor_type = - Executor; - - /// Rebinds the socket type to another executor. - template - struct rebind_executor - { - /// The socket type when rebound to the specified executor. - typedef basic_stream other; - }; - -private: - template - friend class basic_stream; - - boost::shared_ptr in_; - boost::weak_ptr out_; - - template - class read_op; - - template - class write_op; - - struct run_read_op; - struct run_write_op; - - static - void - initiate_read( - boost::shared_ptr const& in, - boost::weak_ptr const& out, - std::unique_ptr&& op, - std::size_t buf_size); - - - static void - initiate_write( - boost::shared_ptr const& in, - boost::weak_ptr const& out, - std::unique_ptr&& op, - std::size_t buf_size); - -#if ! BOOST_BEAST2_DOXYGEN - // boost::asio::ssl::stream needs these - // DEPRECATED - template - friend class boost::asio::ssl::stream; - // DEPRECATED - using lowest_layer_type = basic_stream; - // DEPRECATED - lowest_layer_type& - lowest_layer() noexcept - { - return *this; - } - // DEPRECATED - lowest_layer_type const& - lowest_layer() const noexcept - { - return *this; - } -#endif - -public: - using buffer_type = capy::string_buffer; - - /** Destructor - - If an asynchronous read operation is pending, it will - simply be discarded with no notification to the completion - handler. - - If a connection is established while the stream is destroyed, - the peer will see the error `asio::error::connection_reset` - when performing any reads or writes. - */ - ~basic_stream(); - - /** Move Constructor - - Moving the stream while asynchronous operations are pending - results in undefined behavior. - */ - basic_stream(basic_stream&& other); - - /** Move Constructor - - Moving the stream while asynchronous operations are pending - results in undefined behavior. - */ - template - basic_stream(basic_stream&& other) - : in_(std::move(other.in_)) - , out_(std::move(other.out_)) - { - BOOST_ASSERT(in_->exec.template target() != nullptr); - in_->exec = executor_type(*in_->exec.template target()); - } - - /** Move Assignment - - Moving the stream while asynchronous operations are pending - results in undefined behavior. - */ - basic_stream& - operator=(basic_stream&& other); - - template - basic_stream& - operator==(basic_stream&& other); - - /** Construct a stream - - The stream will be created in a disconnected state. - - @param context The `io_context` object that the stream will use to - dispatch handlers for any asynchronous operations. - */ - template ::value>::type> - explicit - basic_stream(ExecutionContext& context) - : basic_stream(context.get_executor()) - { - } - - /** Construct a stream - - The stream will be created in a disconnected state. - - @param exec The `executor` object that the stream will use to - dispatch handlers for any asynchronous operations. - */ - explicit - basic_stream(executor_type exec); - - /** Construct a stream - - The stream will be created in a disconnected state. - - @param ioc The `io_context` object that the stream will use to - dispatch handlers for any asynchronous operations. - - @param fc The @ref fail_count to associate with the stream. - Each I/O operation performed on the stream will increment the - fail count. When the fail count reaches its internal limit, - a simulated failure error will be raised. - */ - basic_stream( - asio::io_context& ioc, - fail_count& fc); - - /** Construct a stream - - The stream will be created in a disconnected state. - - @param ioc The `io_context` object that the stream will use to - dispatch handlers for any asynchronous operations. - - @param s A string which will be appended to the input area, not - including the null terminator. - */ - basic_stream( - asio::io_context& ioc, - core::string_view s); - - /** Construct a stream - - The stream will be created in a disconnected state. - - @param ioc The `io_context` object that the stream will use to - dispatch handlers for any asynchronous operations. - - @param fc The @ref fail_count to associate with the stream. - Each I/O operation performed on the stream will increment the - fail count. When the fail count reaches its internal limit, - a simulated failure error will be raised. - - @param s A string which will be appended to the input area, not - including the null terminator. - */ - basic_stream( - asio::io_context& ioc, - fail_count& fc, - core::string_view s); - - /// Establish a connection - void - connect(basic_stream& remote); - - /// Return the executor associated with the object. - executor_type - get_executor() noexcept; - - /// Set the maximum number of bytes returned by read_some - void - read_size(std::size_t n) noexcept - { - in_->read_max = n; - } - - /// Set the maximum number of bytes returned by write_some - void - write_size(std::size_t n) noexcept - { - in_->write_max = n; - } - - /// Direct input buffer access - buffer_type& - buffer() noexcept - { - return in_->b; - } - - /// Returns a string view representing the pending input data - core::string_view - str() const; - - /// Appends a string to the pending input data - void - append(core::string_view s); - - /// Clear the pending input area - void - clear(); - - /// Return the number of reads - std::size_t - nread() const noexcept - { - return in_->nread; - } - - /// Return the number of bytes read - std::size_t - nread_bytes() const noexcept - { - return in_->nread_bytes; - } - - /// Return the number of writes - std::size_t - nwrite() const noexcept - { - return in_->nwrite; - } - - /// Return the number of bytes written - std::size_t - nwrite_bytes() const noexcept - { - return in_->nwrite_bytes; - } - - /** Close the stream. - - The other end of the connection will see - `error::eof` after reading all the remaining data. - */ - void - close(); - - /** Close the other end of the stream. - - This end of the connection will see - `error::eof` after reading all the remaining data. - */ - void - close_remote(); - - /** Start an asynchronous read. - - This function is used to asynchronously read one or more bytes of data from - the stream. The function call always returns immediately. - - @param buffers The buffers into which the data will be read. Although the - buffers object may be copied as necessary, ownership of the underlying - buffers is retained by the caller, which must guarantee that they remain - valid until the handler is called. - - @param handler The completion handler to invoke when the operation - completes. The implementation takes ownership of the handler by - performing a decay-copy. The equivalent function signature of - the handler must be: - @code - void handler( - system::error_code const& ec, // Result of operation. - std::size_t bytes_transferred // Number of bytes read. - ); - @endcode - If the handler has an associated immediate executor, - an immediate completion will be dispatched to it. - Otherwise, the handler will not be invoked from within - this function. Invocation of the handler will be performed - by dispatching to the immediate executor. If no - immediate executor is specified, this is equivalent - to using `asio::post`. - @note The `async_read_some` operation may not read all of the requested number of - bytes. Consider using the function `asio::async_read` if you need - to ensure that the requested amount of data is read before the asynchronous - operation completes. - */ - template< - class MutableBufferSequence, - BOOST_ASIO_COMPLETION_TOKEN_FOR(void(system::error_code, std::size_t)) ReadHandler - BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)> - BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(ReadHandler, void(system::error_code, std::size_t)) - async_read_some( - MutableBufferSequence const& buffers, - ReadHandler&& handler BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)); - - /** Start an asynchronous write. - - This function is used to asynchronously write one or more bytes of data to - the stream. The function call always returns immediately. - - @param buffers The data to be written to the stream. Although the buffers - object may be copied as necessary, ownership of the underlying buffers is - retained by the caller, which must guarantee that they remain valid until - the handler is called. - - @param handler The completion handler to invoke when the operation - completes. The implementation takes ownership of the handler by - performing a decay-copy. The equivalent function signature of - the handler must be: - @code - void handler( - system::error_code const& ec, // Result of operation. - std::size_t bytes_transferred // Number of bytes written. - ); - @endcode - If the handler has an associated immediate executor, - an immediate completion will be dispatched to it. - Otherwise, the handler will not be invoked from within - this function. Invocation of the handler will be performed - by dispatching to the immediate executor. If no - immediate executor is specified, this is equivalent - to using `asio::post`. - @note The `async_write_some` operation may not transmit all of the data to - the peer. Consider using the function `asio::async_write` if you need - to ensure that all data is written before the asynchronous operation completes. - */ - template< - class ConstBufferSequence, - BOOST_ASIO_COMPLETION_TOKEN_FOR(void(system::error_code, std::size_t)) WriteHandler - BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)> - BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(WriteHandler, void(system::error_code, std::size_t)) - async_write_some( - ConstBufferSequence const& buffers, - WriteHandler&& handler BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) - ); -}; - -#if ! BOOST_BEAST2_DOXYGEN -template -void -beast2_close_socket(basic_stream& s) -{ - s.close(); -} -#endif - -#if BOOST_BEAST2_DOXYGEN -/** Return a new stream connected to the given stream - - @param to The stream to connect to. - - @param args Optional arguments forwarded to the new stream's constructor. - - @return The new, connected stream. -*/ -template -template -basic_stream -connect(basic_stream& to, Args&&... args); - -#else -template -basic_stream -connect(basic_stream& to); - -template -void -connect(basic_stream& s1, basic_stream& s2); - -template -basic_stream -connect(basic_stream& to, Arg1&& arg1, ArgN&&... argn); -#endif - -using stream = basic_stream<>; - -} // test -} // beast2 -} // boost - -#include - -#endif diff --git a/include/boost/beast2/test/tcp.hpp b/include/boost/beast2/test/tcp.hpp deleted file mode 100644 index 6c1a1d95..00000000 --- a/include/boost/beast2/test/tcp.hpp +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) 2016-2019 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/cppaliance/beast2 -// - -#ifndef BOOST_BEAST2_TEST_TCP_HPP -#define BOOST_BEAST2_TEST_TCP_HPP - -#include - -#include -#include - -namespace boost { -namespace beast2 { -namespace test { - -/** Connect two TCP sockets together. -*/ -template -bool -connect( - asio::io_context& ioc, - asio::basic_stream_socket& s1, - asio::basic_stream_socket& s2) - -{ - asio::basic_socket_acceptor< - asio::ip::tcp, Executor> a(s1.get_executor()); - auto ep = asio::ip::tcp::endpoint( - asio::ip::make_address_v4("127.0.0.1"), 0); - a.open(ep.protocol()); - a.set_option( - asio::socket_base::reuse_address(true)); - a.bind(ep); - a.listen(0); - ep = a.local_endpoint(); - a.async_accept(s2, asio::detached); - s1.async_connect(ep, asio::detached); - ioc.run(); - ioc.restart(); - if(! s1.remote_endpoint() == s2.local_endpoint()) - return false; - if(! s2.remote_endpoint() == s1.local_endpoint()) - return false; - return true; -} - -} // test -} // beast2 -} // boost - -#endif diff --git a/include/boost/beast2/wrap_executor.hpp b/include/boost/beast2/wrap_executor.hpp deleted file mode 100644 index 3b1ff7ae..00000000 --- a/include/boost/beast2/wrap_executor.hpp +++ /dev/null @@ -1,90 +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_WRAP_EXECUTOR_HPP -#define BOOST_BEAST2_WRAP_EXECUTOR_HPP - -#include -#include -#include -#include -#include -#include - -namespace boost { -namespace beast2 { - -namespace detail { - -/** A dispatcher that posts coroutine handles to an Asio executor. - - This class wraps an Asio executor and satisfies the capy::dispatcher - concept. When invoked with a coroutine handle, it posts the handle's - resumption to the wrapped executor for later execution. -*/ -template -class asio_dispatcher -{ - AsioExecutor exec_; - -public: - explicit - asio_dispatcher(AsioExecutor ex) - : exec_(std::move(ex)) - { - } - - asio_dispatcher(asio_dispatcher const&) = default; - asio_dispatcher(asio_dispatcher&&) = default; - asio_dispatcher& operator=(asio_dispatcher const&) = default; - asio_dispatcher& operator=(asio_dispatcher&&) = default; - - /** Dispatch a coroutine handle for execution. - - Posts the coroutine's resumption to the Asio executor. - Returns noop_coroutine since the work is scheduled for later. - - @param h The coroutine handle to dispatch. - @return std::noop_coroutine() indicating no symmetric transfer. - */ - capy::coro - operator()(capy::coro h) const - { - asio::post(exec_, [h]() mutable { - h.resume(); - }); - return std::noop_coroutine(); - } -}; - -} // detail - -/** Return a capy::any_dispatcher from an Asio executor. - - This function wraps an Asio executor in a dispatcher that can be - stored in capy::any_dispatcher. When the dispatcher is invoked with - a coroutine handle, it posts the handle's resumption to the Asio - executor for later execution. - - @param ex The Asio executor to wrap. - - @return A dispatcher that posts work to the provided Asio executor. -*/ -template -detail::asio_dispatcher::type> -wrap_executor(AsioExecutor&& ex) -{ - return detail::asio_dispatcher::type>( - std::forward(ex)); -} - -} // beast2 -} // boost - -#endif diff --git a/include/boost/beast2/write.hpp b/include/boost/beast2/write.hpp deleted file mode 100644 index 31e3b02e..00000000 --- a/include/boost/beast2/write.hpp +++ /dev/null @@ -1,83 +0,0 @@ -// -// Copyright (c) 2016-2019 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_WRITE_HPP -#define BOOST_BEAST2_WRITE_HPP - -#include -#include -#include -#include - -namespace boost { -namespace beast2 { - -/** Write HTTP data to a stream -*/ -template< - class AsyncWriteStream, - BOOST_ASIO_COMPLETION_TOKEN_FOR( - void(system::error_code, std::size_t)) CompletionToken - BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE( - typename AsyncWriteStream::executor_type)> -BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, - void (system::error_code, std::size_t)) -async_write_some( - AsyncWriteStream& dest, - http::serializer& sr, - CompletionToken&& token - BOOST_ASIO_DEFAULT_COMPLETION_TOKEN( - typename AsyncWriteStream::executor_type)); - -/** Write HTTP data to a stream -*/ -template< - class AsyncWriteStream, - BOOST_ASIO_COMPLETION_TOKEN_FOR( - void(system::error_code, std::size_t)) CompletionToken - BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE( - typename AsyncWriteStream::executor_type)> -BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, - void (system::error_code, std::size_t)) -async_write( - AsyncWriteStream& dest, - http::serializer& sr, - CompletionToken&& token - BOOST_ASIO_DEFAULT_COMPLETION_TOKEN( - typename AsyncWriteStream::executor_type)); - -#if 0 -/** -*/ -template< - class AsyncWriteStream, - class AsyncReadStream, - class CompletionCondition, - BOOST_ASIO_COMPLETION_TOKEN_FOR( - void(system::error_code, std::size_t)) CompletionToken - BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE( - typename AsyncWriteStream::executor_type)> -BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, - void (system::error_code, std::size_t)) -async_relay_some( - AsyncWriteStream& dest, - AsyncReadStream& src, - CompletionCondition const& cond, - http::serializer& sr, - CompletionToken&& token - BOOST_ASIO_DEFAULT_COMPLETION_TOKEN( - typename AsyncWriteStream::executor_type)); -#endif - -} // beast2 -} // boost - -#include - -#endif diff --git a/src/asio_io_context.cpp b/src/asio_io_context.cpp deleted file mode 100644 index b8c7f69f..00000000 --- a/src/asio_io_context.cpp +++ /dev/null @@ -1,122 +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 -#include - -namespace boost { -namespace beast2 { - -namespace { - -/** Asio's io_context as an application part -*/ -class asio_io_context_impl - : public asio_io_context -{ -public: - using key_type = asio_io_context; - - ~asio_io_context_impl() - { - } - - asio_io_context_impl( - capy::application& app, - int num_threads) - : app_(app) - , num_threads_(num_threads) - , ioc_(num_threads) - , work_(ioc_.get_executor()) - { - if(num_threads > 1) - vt_.resize(num_threads - 1); - } - - executor_type - get_executor() noexcept override - { - return ioc_.get_executor(); - } - - std::size_t - concurrency() const noexcept override - { - return num_threads_; - } - - void attach() override - { - // VFALCO exception catcher? - ioc_.run(); - - // VFALCO can't figure out where to put this - for(auto& t : vt_) - t.join(); - } - - void start() override - { - for(auto& t : vt_) - { - t = std::thread( - [&] - { - // VFALCO exception catcher? - ioc_.run(); - }); - } - } - - void stop() override - { - work_.reset(); - } - -private: - capy::application& app_; - int num_threads_; - asio::io_context ioc_; - asio::executor_work_guard< - asio::io_context::executor_type> work_; - std::vector vt_; -}; - -} // (anon) - -//------------------------------------------------ - -asio_io_context:: -~asio_io_context() = default; - -auto -install_single_threaded_asio_io_context( - capy::application& app) -> - asio_io_context& -{ - return app.emplace< - asio_io_context_impl>(app, 1); -} - -auto -install_multi_threaded_asio_io_context( - capy::application& app, - int num_threads) -> - asio_io_context& -{ - return app.emplace< - asio_io_context_impl>(app, num_threads); -} - -} // beast2 -} // boost diff --git a/src/server/http_server.cpp b/src/server/http_server.cpp index 1bb612c0..ebdbddb9 100644 --- a/src/server/http_server.cpp +++ b/src/server/http_server.cpp @@ -8,88 +8,77 @@ // #include -#include -#include -#include +#include #include +#include +#include +#include namespace boost { namespace beast2 { namespace { -template class http_server_impl - : public http_server + : public http_server { public: http_server_impl( capy::application& app, - std::size_t num_workers) - : ioc_(install_single_threaded_asio_io_context(app)) - , w_(app, - ioc_.get_executor(), 1, num_workers, - ioc_.get_executor(), this->wwwroot) - { - } - - void add_port( char const* addr, - unsigned short port) - { - w_.emplace( - http::acceptor_config{ false, false }, - asio::ip::tcp::endpoint( - asio::ip::make_address(addr), - port), - true); - } - - void start() + unsigned short port, + std::size_t num_workers) + : ioc_() + , workers_(app, ioc_, num_workers, this->wwwroot) { - w_.start(); + // Parse address and create endpoint + auto addr_result = urls::parse_ipv4_address(addr); + if (addr_result.has_error()) + { + // Fallback to any address if parsing fails + workers_.listen( + http::acceptor_config{false, false}, + corosio::endpoint(port)); + } + else + { + workers_.listen( + http::acceptor_config{false, false}, + corosio::endpoint(addr_result.value(), port)); + } } - void stop() + void run() override { - w_.stop(); + workers_.start(); + ioc_.run(); } - void attach() override + void stop() override { - ioc_.attach(); + workers_.stop(); + ioc_.stop(); } private: - using workers_type = workers< plain_worker< - asio::io_context::executor_type, asio::ip::tcp> >; - - asio_io_context& ioc_; - workers_type w_; + corosio::io_context ioc_; + workers workers_; }; } // (anon) //------------------------------------------------ -auto +http_server& install_plain_http_server( capy::application& app, char const* addr, unsigned short port, - std::size_t num_workers) -> - http_server>& + std::size_t num_workers) { - using stream_type = asio::basic_stream_socket< - asio::ip::tcp, asio::io_context::executor_type>; - auto& srv = app.emplace>( - app, num_workers); - srv.add_port(addr, port); - return srv; + return app.emplace( + app, addr, port, num_workers); } } // beast2 } // boost - diff --git a/src/server/workers.cpp b/src/server/workers.cpp deleted file mode 100644 index 2a740b87..00000000 --- a/src/server/workers.cpp +++ /dev/null @@ -1,19 +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 { - -workers_base:: -~workers_base() = default; - -} // beast2 -} // boost From ddf66287ebdbccf90620981059589a7dc8ac43fd Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Tue, 13 Jan 2026 04:17:55 -0800 Subject: [PATCH 33/40] Remove cursor rules and deprecated plain_worker.hpp --- .cursor/rules/build-outputs.mdc | 50 -------------------- include/boost/beast2/server/plain_worker.hpp | 18 ------- 2 files changed, 68 deletions(-) delete mode 100644 .cursor/rules/build-outputs.mdc delete mode 100644 include/boost/beast2/server/plain_worker.hpp diff --git a/.cursor/rules/build-outputs.mdc b/.cursor/rules/build-outputs.mdc deleted file mode 100644 index 0f8c7dfb..00000000 --- a/.cursor/rules/build-outputs.mdc +++ /dev/null @@ -1,50 +0,0 @@ ---- -description: Build outputs must go in .temp directory -globs: -alwaysApply: true ---- - -# Build Output Directory Policy - -**All build artifacts, intermediate files, and temporary outputs MUST go in the `.temp/` directory.** - -## Required Directory Structure - -- `.temp/build-msvc/` - MSVC/Visual Studio builds -- `.temp/build-gcc/` - GCC builds (via MSYS2) -- `.temp/build-clang/` - Clang builds -- `.temp/build-ninja/` - Ninja builds -- `.temp/` - Any other temporary or generated files - -## Rules - -1. **Never create executables, object files, or build artifacts in source directories** -2. **Never commit anything from `.temp/`** - it is in `.gitignore` -3. When configuring CMake, always use `-B .temp/build-` -4. When running direct compiler commands, output to `.temp/build-/` - -## Example Commands - -### CMake (MSVC) -```powershell -cmake -B .temp/build-msvc -G "Visual Studio 18 2026" -A x64 -cmake --build .temp/build-msvc --config Release -``` - -### CMake (GCC via MSYS2) -```bash -cmake -B .temp/build-gcc -G "Unix Makefiles" -cmake --build .temp/build-gcc -``` - -### Direct GCC compilation -```bash -mkdir -p .temp/build-gcc -g++ -std=c++20 -O3 src/main.cpp -o .temp/build-gcc/main.exe -``` - -### Direct MSVC compilation -```powershell -New-Item -ItemType Directory -Force -Path .temp/build-msvc -cl /std:c++20 /O2 src/main.cpp /Fe:.temp/build-msvc/main.exe -``` diff --git a/include/boost/beast2/server/plain_worker.hpp b/include/boost/beast2/server/plain_worker.hpp deleted file mode 100644 index a8acba9a..00000000 --- a/include/boost/beast2/server/plain_worker.hpp +++ /dev/null @@ -1,18 +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_PLAIN_WORKER_HPP -#define BOOST_BEAST2_SERVER_PLAIN_WORKER_HPP - -// plain_worker functionality is now part of workers.hpp -// This header is kept for compatibility - -#include - -#endif From 788534524973366e4bdb2c54641290a09dcc0248 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Tue, 13 Jan 2026 05:13:25 -0800 Subject: [PATCH 34/40] Remove asio-dependent unit tests Remove test files that require Boost.Asio: - asio_io_context.cpp - body_read_stream.cpp - body_write_stream.cpp - client.cpp - read.cpp - write.cpp - wrap_executor.cpp - sandbox.cpp - test_helpers.hpp - server/call_mf.cpp (missing header) - server/route_handler_asio.cpp - server/router_asio.cpp Remaining 8 test suites pass with 525 assertions. --- test/unit/asio_io_context.cpp | 12 - test/unit/body_read_stream.cpp | 429 ------------- test/unit/body_write_stream.cpp | 817 ------------------------ test/unit/client.cpp | 99 --- test/unit/read.cpp | 264 -------- test/unit/sandbox.cpp | 229 ------- test/unit/server/call_mf.cpp | 11 - test/unit/server/route_handler_asio.cpp | 74 --- test/unit/server/router_asio.cpp | 49 -- test/unit/test_helpers.hpp | 199 ------ test/unit/wrap_executor.cpp | 148 ----- test/unit/write.cpp | 323 ---------- 12 files changed, 2654 deletions(-) delete mode 100644 test/unit/asio_io_context.cpp delete mode 100644 test/unit/body_read_stream.cpp delete mode 100644 test/unit/body_write_stream.cpp delete mode 100644 test/unit/client.cpp delete mode 100644 test/unit/read.cpp delete mode 100644 test/unit/sandbox.cpp delete mode 100644 test/unit/server/call_mf.cpp delete mode 100644 test/unit/server/route_handler_asio.cpp delete mode 100644 test/unit/server/router_asio.cpp delete mode 100644 test/unit/test_helpers.hpp delete mode 100644 test/unit/wrap_executor.cpp delete mode 100644 test/unit/write.cpp diff --git a/test/unit/asio_io_context.cpp b/test/unit/asio_io_context.cpp deleted file mode 100644 index c46a1883..00000000 --- a/test/unit/asio_io_context.cpp +++ /dev/null @@ -1,12 +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 - diff --git a/test/unit/body_read_stream.cpp b/test/unit/body_read_stream.cpp deleted file mode 100644 index 8f2e131c..00000000 --- a/test/unit/body_read_stream.cpp +++ /dev/null @@ -1,429 +0,0 @@ -// -// Copyright (c) 2025 Mungo Gill -// -// 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 - -#include "test_helpers.hpp" -#include - -#include -#include - -namespace boost { -namespace beast2 { - -namespace { - -template -std::string -test_to_string(Buffers const& bs) -{ - std::string s(capy::buffer_size(bs), 0); - s.resize(capy::copy(capy::make_buffer(&s[0], s.size()), bs)); - return s; -} - -class test_handler -{ - boost::optional ec_; - boost::optional n_; - bool pass_ = false; - boost::source_location loc_{ BOOST_CURRENT_LOCATION }; - -public: - test_handler(boost::source_location loc = BOOST_CURRENT_LOCATION) - : loc_(loc) - { - } - - explicit test_handler( - system::error_code ec, - std::size_t n, - boost::source_location loc = BOOST_CURRENT_LOCATION) - : ec_(ec) - , n_(n) - , loc_(loc) - { - } - - test_handler(test_handler&& other) - : ec_(other.ec_) - , n_(other.n_) - , pass_(boost::exchange(other.pass_, true)) - , loc_(other.loc_) - { - } - - ~test_handler() - { - test_suite::any_runner::instance().test( - pass_, "handler never invoked", "", loc_.file_name(), loc_.line()); - } - - template - void - operator()(system::error_code ec, std::size_t n, Args&&...) - { - test_suite::any_runner::instance().test( - !pass_, - "handler invoked multiple times", - "", - loc_.file_name(), - loc_.line()); - - test_suite::any_runner::instance().test( - !ec_.has_value() || ec == *ec_, - ec.message().c_str(), - "", - loc_.file_name(), - loc_.line()); - - char buf[64]; - snprintf(buf, 64, "%u", (unsigned int) n); - - test_suite::any_runner::instance().test( - !n_.has_value() || n == *n_, - buf, - "", - loc_.file_name(), - loc_.line()); - - pass_ = true; - } -}; - -// Parser service install done in a base class to avoid order-of-initialisation -// issues (this needs to happen before the parser pr_ is constructed) -struct ctx_base -{ - capy::polystore capy_ctx_; - - ctx_base() - { - http::install_parser_service(capy_ctx_, {}); - } -}; - -struct single_tester : public ctx_base -{ - std::string body_ = "Hello World!"; - - std::string header_ = - "HTTP/1.1 200 OK\r\n" - "Content-Length: 12\r\n" - "\r\n"; - - std::string msg_ = header_ + body_; - - std::size_t header_length_ = header_.size(); - std::size_t body_length_ = body_.size(); - std::size_t msg_length_ = msg_.size(); - - boost::asio::io_context ioc_; - - test::stream ts_; - http::response_parser pr_; - - // Create a destination buffer - std::string s_; - boost::capy::string_buffer buf_; - - // The object under test - body_read_stream brs_; - - single_tester() - : ts_(ioc_, msg_) - , pr_(capy_ctx_) - , buf_(&s_) - , brs_(ts_, pr_) - { - pr_.reset(); - pr_.start(); - } - - void - async_read_some(std::size_t bs, system::error_code ec, std::size_t n) - { - brs_.async_read_some( - buf_.prepare(bs), - test_handler(ec, n)); - } - - - std::size_t - chunking_expected_n( - std::size_t bs, - std::size_t cs, - bool first, - std::size_t read_so_far) - { - std::size_t expected = 0; - if(read_so_far < body_length_) - { - expected = cs; - // In the first iteration we remove any of the data that was - // associcated with the headers. - if(first) - { - expected -= (header_length_ % cs); - // The `beast2::async_read_some` will always read move from - // the wire immediately after the headers, even if we have a - // partial body in memory already. This should be removable - // once `async_read_some` changes. - if(expected < cs) - { - expected += cs; - } - } - expected = std::min(expected, body_length_ - read_so_far); - expected = std::min(bs, expected); - } - return expected; - } - - struct chunking_handler - { - std::size_t n_; - system::error_code ec_; - std::size_t* total_; - boost::capy::string_buffer* buf_; - - chunking_handler( - std::size_t n, - system::error_code ec, - std::size_t* total, - boost::capy::string_buffer* buf) - : n_(n) - , ec_(ec) - , total_(total) - , buf_(buf) - { - } - - void - operator()(system::error_code ec, std::size_t n) - { - BOOST_TEST_EQ(ec, ec_); - BOOST_TEST_EQ(n, n_); - buf_->commit(n); - *total_ += n; - } - }; - - - // Ensure the edge case of being passed a zero-sized buffer works. - void - test_zero_sized_buffer() - { - // Ensure a read into a zero sized buffer returns with no error. - async_read_some(0, system::error_code{}, 0); - test::run(ioc_); - } - - // Test for a given buffer size (bs) and stream read size (cs) - void - test_with_chunking(std::size_t bs, std::size_t cs) - { - ts_.read_size(cs); // Limit read size to cs - - std::size_t total = 0; - for(std::size_t i = 0; i < body_length_; i++) - { - // Calculate how many bytes we expect to read on each iteration - std::size_t expected = chunking_expected_n(bs, cs, (i == 0), total); - - // Read into a buffer of size bs - brs_.async_read_some( - buf_.prepare(bs), - chunking_handler( - expected, - (total < body_length_) ? system::error_code{} - : asio::error::eof, - &total, - &buf_)); - - auto count = test::run(ioc_); - if(i > 0) // The initial run reads the header so can call multiple handlers. - BOOST_TEST_EQ(count, 1); - - BOOST_TEST(pr_.got_header()); - } - - BOOST_TEST(pr_.is_complete()); - BOOST_TEST_EQ(buf_.size(), body_length_); - BOOST_TEST_EQ(total, body_length_); - BOOST_TEST(test_to_string(buf_.data()) == body_); - } - - void - test_with_cancellation(std::size_t len) - { - ts_.clear(); - ts_.append(msg_.substr(0, len)); - - // Add a signal to test cancellation - asio::cancellation_signal c_signal; - - brs_.async_read_some( - buf_.prepare(1024), - asio::bind_cancellation_slot( - c_signal.slot(), - test_handler(asio::error::operation_aborted, 0))); - - // send a cancellation - c_signal.emit(asio::cancellation_type::total); - - // Run up until the point of cancellation. - test::run(ioc_); - - BOOST_TEST(pr_.got_header() == (len >= header_length_)); - BOOST_TEST(!pr_.is_complete()); - - // Append the remainder of the message and try again. - std::string remainder = msg_.substr(len); - ts_.append(remainder); - brs_.async_read_some( - buf_.prepare(1024), - test_handler(system::error_code{}, body_length_)); - - // Continue running until the end. - test::run(ioc_); - - BOOST_TEST(pr_.got_header()); - BOOST_TEST(pr_.is_complete()); - } - - void - test_asio_async_read(std::size_t cs, bool use_asio_buffer) - { - // limit chunk size - ts_.read_size(cs); - - if (use_asio_buffer) - { - asio::async_read( - brs_, - asio::buffer(buf_.prepare(1024).data(), 1024), - test_handler(asio::error::eof, body_length_)); - } else { - asio::async_read( - brs_, - buf_.prepare(1024), - test_handler(asio::error::eof, body_length_)); - } - - test::run(ioc_); - - buf_.commit(body_length_); - - BOOST_TEST(pr_.got_header()); - BOOST_TEST(pr_.is_complete()); - - BOOST_TEST_EQ(buf_.size(), body_length_); - BOOST_TEST(test_to_string(buf_.data()) == body_); - } - - void - test_stream_errors() - { - // Replace the test stream by one with a failure count and a single-byte - // read size. - test::fail_count fc(11, asio::error::network_down); - test::stream ts(ioc_, fc, msg_); - ts.read_size(1); - ts_ = std::move(ts); - - async_read_some(1024, asio::error::network_down, 0); - - BOOST_TEST_EQ(test::run(ioc_), 11); - } - - void - test_parser_errors() - { - // Ensure we get an error by making the body limit too small - pr_.set_body_limit(2); - - async_read_some(1024, http::error::body_too_large, 0); - - test::run(ioc_); - } -}; - -} // anonymous namespace - -struct body_read_stream_test -{ - void - run() - { - std::size_t msg_length = single_tester().msg_length_; - - // Read into a zero sized buffer should return immediately without error - { - single_tester().test_zero_sized_buffer(); - } - - // async_read_some reads the body for various chunk - // sizes. - { - // Iterate through buffer sizes - for(std::size_t bs = 1; bs < msg_length + 2; bs++) - { - // Iterate through chunk sizes - for(std::size_t cs = 1; cs < msg_length + 2; cs++) - { - single_tester().test_with_chunking(bs, cs); - } - } - } - - // Test async_read_some cancellation - { - // Iterate through the point in the message the cancellation happens. - for(std::size_t len = 1; len < msg_length; len++) - { - single_tester().test_with_cancellation(len); - } - } - - // Test asio::async_read works - { - // pick a representative chunk, as we already do the looping over - // all chunks above. - std::size_t cs = 5; - - // Perform the test using the Boost Buffers buffer directly - single_tester().test_asio_async_read(cs, false); - // And again using an asio buffer wrapper - single_tester().test_asio_async_read(cs, true); - } - - // async_read_some reports stream errors - { - single_tester().test_stream_errors(); - } - - // async_read_some reports parser errors - { - single_tester().test_parser_errors(); - - } - } -}; - -TEST_SUITE(body_read_stream_test, "boost.beast2.body_read_stream"); - -} // beast2 -} // boost diff --git a/test/unit/body_write_stream.cpp b/test/unit/body_write_stream.cpp deleted file mode 100644 index 1f75a404..00000000 --- a/test/unit/body_write_stream.cpp +++ /dev/null @@ -1,817 +0,0 @@ -// -// Copyright (c) 2025 Mungo Gill -// -// 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "test_helpers.hpp" -#include - -#include -#include -#include - -namespace boost { -namespace beast2 { - -namespace { - -template -std::string -test_to_string(Buffers const& bs) -{ - std::string s(capy::buffer_size(bs), 0); - s.resize(capy::copy(capy::make_buffer(&s[0], s.size()), bs)); - return s; -} - -class test_handler -{ - boost::optional ec_; - boost::optional n_; - bool pass_ = false; - boost::source_location loc_{ BOOST_CURRENT_LOCATION }; - -public: - test_handler(boost::source_location loc = BOOST_CURRENT_LOCATION) - : loc_(loc) - { - } - - explicit test_handler( - system::error_code ec, - std::size_t n = 0, - boost::source_location loc = BOOST_CURRENT_LOCATION) - : ec_(ec) - , n_(n) - , loc_(loc) - { - } - - test_handler(test_handler&& other) noexcept - : ec_(other.ec_) - , n_(other.n_) - , pass_(boost::exchange(other.pass_, true)) - , loc_(other.loc_) - { - } - - ~test_handler() - { - test_suite::any_runner::instance().test( - pass_, "handler never invoked", "", loc_.file_name(), loc_.line()); - } - - template - void - operator()(system::error_code ec, std::size_t n = 0, Args&&...) - { - test_suite::any_runner::instance().test( - !pass_, - "handler invoked multiple times", - "", - loc_.file_name(), - loc_.line()); - - test_suite::any_runner::instance().test( - !ec_.has_value() || ec == *ec_, - ec.message().c_str(), - "", - loc_.file_name(), - loc_.line()); - - char buf[64]; - snprintf(buf, 64, "%u", (unsigned int)n); - - test_suite::any_runner::instance().test( - !n_.has_value() || n == *n_, - buf, - "", - loc_.file_name(), - loc_.line()); - - pass_ = true; - } -}; - -// Parser service install done in a base class to avoid order-of-initialisation -// issues (this needs to happen before the parser pr_ is constructed) -struct ctx_base -{ - capy::polystore capy_ctx_; - - ctx_base() - { - http::install_parser_service(capy_ctx_, {}); - http::install_serializer_service(capy_ctx_, {}); - } -}; - -struct single_tester : public ctx_base -{ - std::string body_ = "Hello World!"; - - std::string header_ = - "HTTP/1.1 200 OK\r\n" - "Content-Length: 12\r\n" - "\r\n"; - - std::string msg_ = header_ + body_; - - std::size_t header_length_ = header_.size(); - std::size_t body_length_ = body_.size(); - std::size_t msg_length_ = msg_.size(); - - boost::asio::io_context ioc_; - - test::stream ts_; - http::response_parser pr_; - - // Create a destination buffer - std::string s_; - boost::capy::string_buffer buf_; - - // The object under test - body_read_stream brs_; - - test::stream wts_, rts_; - - http::serializer sr_; - - http::response res_; - - std::size_t srs_capacity_; - - std::optional> bws_; - - single_tester() - : ts_(ioc_, msg_) - , pr_(capy_ctx_) - , buf_(&s_) - , brs_(ts_, pr_) - , wts_(ioc_) - , rts_(ioc_) - , sr_(capy_ctx_) - , res_(header_) - { - wts_.connect(rts_); - pr_.reset(); - pr_.start(); - sr_.reset(); - auto srs = sr_.start_stream(res_); - srs_capacity_ = srs.capacity(); - bws_.emplace(wts_, sr_, std::move(srs)); - } - - body_write_stream& - bws() - { - return *bws_; - } - - void - async_read_some(std::size_t bs, system::error_code ec, std::size_t n) - { - brs_.async_read_some(buf_.prepare(bs), test_handler(ec, n)); - } - - void - async_write_some(std::size_t bs, system::error_code ec, std::size_t n) - { - bws().async_write_some(buf_.prepare(bs), test_handler(ec, n)); - } - - void - async_close(system::error_code ec) - { - bws().async_close(test_handler(ec, 0)); - } - - capy::const_buffer - make_test_buffer(std::size_t size) - { - std::string val = body_.substr(0, size); - val.resize(size, '.'); - boost::capy::string_buffer sb(&val); - return sb.data(); - } - - std::size_t - chunking_expected_n( - std::size_t bs, - std::size_t cs, - bool first, - std::size_t read_so_far) - { - std::size_t expected = 0; - if(read_so_far < body_length_) - { - expected = cs; - // In the first iteration we remove any of the data that was - // associcated with the headers. - if(first) - { - expected -= (header_length_ % cs); - // The `beast2::async_read_some` will always read move from - // the wire immediately after the headers, even if we have a - // partial body in memory already. This should be removable - // once `async_read_some` changes. - if(expected < cs) - { - expected += cs; - } - } - expected = std::min(expected, body_length_ - read_so_far); - expected = std::min(bs, expected); - } - return expected; - } - - struct chunking_handler - { - system::error_code ec_; - std::size_t* written_; - - chunking_handler( - system::error_code ec, - std::size_t* written) - : ec_(ec) - , written_(written) - { - } - - void - operator()(system::error_code ec, std::size_t n) - { - BOOST_TEST_EQ(ec, ec_); - *written_ += n; - } - }; - - // Ensure the edge case of being passed a zero-sized buffer works. - void - test_zero_sized_buffer() - { - // Ensure a read into a zero sized buffer returns with no error. - std::string val; - boost::capy::string_buffer sb(&val); - auto cb = sb.data(); - bws().async_write_some(cb, test_handler(system::error_code{}, 0)); - test::run(ioc_); - } - - // Test for a given buffer size (bs) and stream read size (cs) - void - test_with_chunking(std::size_t bs, std::size_t cs, int iters = 15) - { - wts_.write_size(cs); // Limit und stream write size to cs - - std::string finals; - finals.reserve(bs * iters); - - std::size_t total = 0; - std::size_t prev = 0; - std::size_t writes = 0; - for(int i = 0; i < iters; i++) - { - // Construct a buffer of size bs - std::string val = body_.substr(0, bs); - val.resize(bs, '.'); - boost::capy::string_buffer sb(&val); - auto cb = sb.data(); - - finals += val; - - // Calculate how many bytes we expect to read on each iteration - // std::size_t expected = chunking_expected_n(bs, cs, (i == 0), - // total); - - while(cb.size() > 0) - { - std::size_t emin = 1; - std::size_t emax = std::min({ bs, srs_capacity_, cb.size() }); - - std::size_t written = 0; - bws().async_write_some( - cb, - chunking_handler( - system::error_code{}, - &written)); - - auto count = test::run(ioc_); - BOOST_TEST_GE(count, 1); - BOOST_TEST_LE(count, (size_t) 1 + header_length_ / cs); - - //std::cout << "count " << count << std::endl; - - total += written; - cb += written; - writes++; - - if (written < emin) - { - std::cout << "err" << std::endl; - } - - BOOST_TEST_GE(total, writes); - BOOST_TEST_GE(written, emin); - BOOST_TEST_LE(written, emax); - - BOOST_TEST_GE(rts_.nwrite_bytes() - prev, 1); - - prev = rts_.nwrite_bytes(); - - BOOST_TEST_LE(rts_.nwrite_bytes(), bs * (i+1) + header_length_); - - BOOST_TEST(!sr_.is_done()); - } - } - - BOOST_TEST_EQ(total, bs * iters); - - BOOST_TEST_LE( - rts_.nwrite_bytes(), cs * writes + header_length_); - - bws().async_close(test::success_handler()); - - auto count = test::run(ioc_); - BOOST_TEST_GE(count, 1); - - BOOST_TEST_GT(rts_.nwrite_bytes(), iters + writes); - BOOST_TEST_LE(rts_.nwrite_bytes(), cs * (writes + count) + header_length_); - - BOOST_TEST_EQ(rts_.nwrite_bytes(), bs * iters + header_length_); - - BOOST_TEST(sr_.is_done()); - - BOOST_TEST(rts_.str() == header_ + finals); - } - - void - test_with_ignored_cancel_signal(std::size_t len) - { - std::string val = body_.substr(0, len); - boost::capy::string_buffer sb(&val); - auto cb = sb.data(); - - // Add a signal to test cancellation - asio::cancellation_signal c_signal; - - // First call: cancellation occurs after data is written to serializer. - // The callback should receive success with the bytes written. - // With the simplified loop, cancellation after successful write - // is treated as success (the data was written). - std::size_t expected_bytes = std::min(len, body_length_); - bws().async_write_some( - cb, - asio::bind_cancellation_slot( - c_signal.slot(), - test_handler(system::error_code{}, expected_bytes))); - - // send a cancellation - c_signal.emit(asio::cancellation_type::total); - - // Run up until the point of cancellation. - test::run(ioc_); - - BOOST_TEST(!sr_.is_done()); - - // Second call: write the remainder successfully. - // Cancellation after successful write is not saved, so this succeeds. - std::string remainder = body_.substr(len); - boost::capy::string_buffer sb2(&remainder); - auto cb2 = sb2.data(); - - std::size_t remainder_len = body_length_ - len; - bws().async_write_some( - cb2, - test_handler(system::error_code{}, remainder_len)); - - test::run(ioc_); - - BOOST_TEST(!sr_.is_done()); - - // Third call: close the stream and verify the message. - bws().async_close(test_handler(system::error_code{})); - - test::run(ioc_); - - BOOST_TEST(sr_.is_done()); - BOOST_TEST(rts_.str() == msg_); - } - - void - test_asio_async_write(std::size_t cs, bool use_asio_buffer) - { - // limit chunk size on the underlying stream - wts_.write_size(cs); - - if(use_asio_buffer) - { - asio::async_write( - bws(), - asio::buffer(body_.data(), body_.size()), - test_handler(system::error_code{}, body_length_)); - } - else - { - asio::async_write( - bws(), - capy::const_buffer(body_.data(), body_.size()), - test_handler(system::error_code{}, body_length_)); - } - - test::run(ioc_); - - BOOST_TEST(!sr_.is_done()); - - // Close the stream to flush remaining data - bws().async_close(test_handler(system::error_code{})); - - test::run(ioc_); - - BOOST_TEST(sr_.is_done()); - BOOST_TEST(rts_.str() == msg_); - } - - void - test_stream_errors() - { - // Create a write test stream that fails on the first write. - test::fail_count fc(0, asio::error::network_down); - test::stream wts(ioc_, fc); - test::stream rts(ioc_); - wts.connect(rts); - - // Create a fresh serializer for this test - http::serializer sr(capy_ctx_); - sr.reset(); - http::response res(header_); - - // Create a new body_write_stream with the failing stream - body_write_stream bws(wts, sr, sr.start_stream(res)); - - // First call: data is committed to the serializer before the - // stream write fails. Due to deferred error handling, this - // returns success with the committed bytes, and saves the error. - std::string val = body_; - boost::capy::string_buffer sb(&val); - auto cb = sb.data(); - - bws.async_write_some( - cb, - test_handler(system::error_code{}, body_length_)); - - // The operation completes with 1 handler invocation. - BOOST_TEST_EQ(test::run(ioc_), 1); - - // Second call: receives the deferred error with 0 bytes. - bws.async_write_some( - cb, - test_handler(asio::error::network_down, 0)); - - // The deferred error is returned via async_immediate. - BOOST_TEST_EQ(test::run(ioc_), 1); - } - - void - test_close_with_saved_error() - { - // Create a write test stream that fails on the first write. - test::fail_count fc(0, asio::error::network_down); - test::stream wts(ioc_, fc); - test::stream rts(ioc_); - wts.connect(rts); - - // Create a fresh serializer for this test - http::serializer sr(capy_ctx_); - sr.reset(); - http::response res(header_); - - // Create a new body_write_stream with the failing stream - body_write_stream bws(wts, sr, sr.start_stream(res)); - - // First call: data is committed to the serializer before the - // stream write fails. Due to deferred error handling, this - // returns success with the committed bytes, and saves the error. - std::string val = body_; - boost::capy::string_buffer sb(&val); - auto cb = sb.data(); - - bws.async_write_some( - cb, - test_handler(system::error_code{}, body_length_)); - - // The operation completes with 1 handler invocation. - BOOST_TEST_EQ(test::run(ioc_), 1); - - // async_close receives the saved error immediately. - bws.async_close(test_handler(asio::error::network_down)); - - // The deferred error is returned via async_immediate. - BOOST_TEST_EQ(test::run(ioc_), 1); - } - - void - test_close_errors() - { - // Create a write test stream that fails on the second write. - // The first write will succeed (writing body data), but the - // close operation will fail when flushing remaining data. - test::fail_count fc(1, asio::error::network_down); - test::stream wts(ioc_, fc); - test::stream rts(ioc_); - wts.connect(rts); - - // Limit write size so data remains in serializer after first write. - wts.write_size(1); - - // Create a fresh serializer for this test - http::serializer sr(capy_ctx_); - sr.reset(); - http::response res(header_); - - // Create a new body_write_stream with the failing stream - body_write_stream bws(wts, sr, sr.start_stream(res)); - - // Write body data - this should succeed. - std::string val = body_; - boost::capy::string_buffer sb(&val); - auto cb = sb.data(); - - bws.async_write_some( - cb, - test_handler(system::error_code{}, body_length_)); - - BOOST_TEST_EQ(test::run(ioc_), 1); - - // Close the stream - this should fail when trying to flush - // the remaining serializer data to the underlying stream. - bws.async_close(test_handler(asio::error::network_down)); - - BOOST_TEST_GE(test::run(ioc_), 1); - } - - // Test cancellation during buffer-clearing loop (when bytes_ == 0). - // This covers the case where the serializer buffer is full and we're - // waiting for space, then get cancelled before any user data is copied. - void - test_cancel_during_buffer_clear() - { - wts_.write_size(1); // Very slow drain - - // First, fill the serializer's buffer completely by writing - // data equal to its capacity - std::size_t cap = srs_capacity_; - std::string fill_data(cap, 'F'); - capy::const_buffer fill_cb(fill_data.data(), fill_data.size()); - - bool fill_complete = false; - bws().async_write_some( - fill_cb, - [&](system::error_code, std::size_t) - { - fill_complete = true; - }); - - test::run(ioc_); - BOOST_TEST(fill_complete); - - // Now the buffer should be full. The next write should enter - // the buffer-clearing loop with bytes_ == 0 on the first iteration. - std::string more_data(64, 'X'); - capy::const_buffer cb(more_data.data(), more_data.size()); - - asio::cancellation_signal c_signal; - - system::error_code result_ec; - std::size_t result_bytes = 0; - - bws().async_write_some( - cb, - asio::bind_cancellation_slot( - c_signal.slot(), - [&](system::error_code ec, std::size_t n) - { - result_ec = ec; - result_bytes = n; - })); - - // Emit cancellation immediately - we should be in the loop - // with bytes_ == 0 because the buffer is full - c_signal.emit(asio::cancellation_type::total); - - // Let the operation complete - test::run(ioc_); - - // Should complete with operation_aborted and 0 bytes - // because cancellation occurred while bytes_ == 0 - BOOST_TEST_EQ(result_ec, asio::error::operation_aborted); - BOOST_TEST_EQ(result_bytes, 0u); - } -}; - -// Result type for async write operations -struct write_result -{ - system::error_code ec; - std::size_t bytes_transferred; -}; - -// Helper to wrap async_write_some for coroutines -template -capy::async_op -coro_write_some( - body_write_stream& bws, - ConstBufferSequence const& buffers) -{ - return capy::make_async_op( - bws.async_write_some(buffers, asio::deferred)); -} - -// Helper to wrap async_close for coroutines -template -capy::async_op -coro_close(body_write_stream& bws) -{ - return capy::make_async_op( - bws.async_close(asio::deferred)); -} - -capy::task -do_coro_write( - test::stream& wts, - test::stream& rts, - http::serializer& sr, - http::serializer::stream srs, - std::string const& body, - std::string const& expected_msg) -{ - body_write_stream bws(wts, sr, std::move(srs)); - - // Write body data using co_await - capy::const_buffer cb(body.data(), body.size()); - std::size_t total_written = 0; - - while(cb.size() > 0) - { - auto result = co_await coro_write_some(bws, cb); - BOOST_TEST(!result.ec.failed()); - BOOST_TEST_GT(result.bytes_transferred, 0u); - total_written += result.bytes_transferred; - cb += result.bytes_transferred; - } - - BOOST_TEST_EQ(total_written, body.size()); - BOOST_TEST(!sr.is_done()); - - // Close the stream - auto ec = co_await coro_close(bws); - BOOST_TEST(!ec.failed()); - - BOOST_TEST(sr.is_done()); - BOOST_TEST_EQ(rts.str(), expected_msg); - - co_return; -} - -void -test_coroutine() -{ - // Set up context with parser and serializer services - capy::polystore capy_ctx; - http::install_parser_service(capy_ctx, {}); - http::install_serializer_service(capy_ctx, {}); - - std::string body = "Hello World!"; - std::string header = - "HTTP/1.1 200 OK\r\n" - "Content-Length: 12\r\n" - "\r\n"; - std::string expected_msg = header + body; - - asio::io_context ioc; - - test::stream wts(ioc); - test::stream rts(ioc); - wts.connect(rts); - - http::serializer sr(capy_ctx); - sr.reset(); - - http::response res(header); - auto srs = sr.start_stream(res); - - // Launch coroutine using async_run (default handler rethrows exceptions) - capy::async_run(wrap_executor(ioc.get_executor()))( - do_coro_write(wts, rts, sr, std::move(srs), body, expected_msg)); - - ioc.run(); -} - -} // anonymous namespace. - -struct body_write_stream_test -{ - void - run() - { - // Read into a zero sized buffer should return immediately without error - { - single_tester().test_zero_sized_buffer(); - } - - // async_read_some reads the body for various chunk - // sizes. - { - int sizes[] = { 1, 2, 13, 1597, 100'000 }; - // Iterate through buffer sizes - for(std::size_t bs: sizes) - { - // Iterate through chunk sizes - for(std::size_t cs : sizes) - { - if(cs > bs / 10000) - { - auto iters = static_cast( - std::min((size_t)10, (cs / bs) + 1)); - single_tester().test_with_chunking(bs, cs, iters); - } - } - } - } - - // Test async_write_some with ignored cancellation signal - { - // Iterate through different amounts of data written before - // cancellation. Only go up to body_length_ since that's the - // maximum useful data. - std::size_t body_len = single_tester().body_length_; - for(std::size_t len = 1; len <= body_len; len++) - { - single_tester().test_with_ignored_cancel_signal(len); - } - } - - // Test asio::async_write works with body_write_stream - { - // pick a representative chunk size - std::size_t cs = 5; - - // Perform the test using the Boost Buffers buffer directly - single_tester().test_asio_async_write(cs, false); - // And again using an asio buffer wrapper - single_tester().test_asio_async_write(cs, true); - } - - // async_write_some reports stream errors - { - single_tester().test_stream_errors(); - } - - // async_close reports saved errors - { - single_tester().test_close_with_saved_error(); - } - - // async_close reports stream errors during flush - { - single_tester().test_close_errors(); - } - - // Test cancellation during buffer-clearing loop - { - single_tester().test_cancel_during_buffer_clear(); - } - - // Test C++20 coroutine compatibility - { - test_coroutine(); - } - } -}; - -TEST_SUITE(body_write_stream_test, "boost.beast2.body_write_stream"); - -} // beast2 -} // boost diff --git a/test/unit/client.cpp b/test/unit/client.cpp deleted file mode 100644 index 405fe46f..00000000 --- a/test/unit/client.cpp +++ /dev/null @@ -1,99 +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 "test_suite.hpp" - -namespace boost { -namespace beast2 { - -struct success_handler -{ - bool pass = false; - - void - operator()(system::error_code ec, ...) - { - pass = BOOST_TEST(! ec.failed()); - } -}; - -/** Connect two TCP sockets together. -*/ -template -bool -connect( - asio::basic_stream_socket& s1, - asio::basic_stream_socket& s2) - -{ - BOOST_ASSERT(s1.get_executor() == s2.get_executor()); - try - { - asio::basic_socket_acceptor< - asio::ip::tcp, Executor> a(s1.get_executor()); - auto ep = asio::ip::tcp::endpoint( - asio::ip::make_address_v4("127.0.0.1"), 0); - a.open(ep.protocol()); - a.set_option( - asio::socket_base::reuse_address(true)); - a.bind(ep); - a.listen(0); - ep = a.local_endpoint(); - a.async_accept(s2, success_handler()); - s1.async_connect(ep, success_handler()); - s1.get_executor().context().restart(); - s1.get_executor().context().run(); - if(! BOOST_TEST_EQ(s1.remote_endpoint(), s2.local_endpoint())) - return false; - if(! BOOST_TEST_EQ(s2.remote_endpoint(), s1.local_endpoint())) - return false; - } - catch(std::exception const&) - { - BOOST_TEST_FAIL(); - return false; - } - - return true; -} - -using socket_type = - asio::basic_stream_socket< - asio::ip::tcp, - asio::io_context::executor_type>; - -class client_test -{ -public: - void - testClient() - { - asio::io_context ioc; - socket_type s0(ioc.get_executor()); - socket_type s1(ioc.get_executor()); - connect(s0, s1); - client s(std::move(s1)); - } - - void - run() - { - testClient(); - } -}; - -TEST_SUITE(client_test, "boost.beast2.client"); - -} // beast2 -} // boost diff --git a/test/unit/read.cpp b/test/unit/read.cpp deleted file mode 100644 index b894f72b..00000000 --- a/test/unit/read.cpp +++ /dev/null @@ -1,264 +0,0 @@ -// -// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) -// Copyright (c) 2025 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 -// - -// Test that header file is self-contained. -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "test_helpers.hpp" - -namespace boost { -namespace beast2 { - -class read_test -{ - core::string_view const msg = - "HTTP/1.1 200 OK\r\n" - "Content-Length: 3\r\n" - "\r\n" - "abc"; -public: - void - testAsyncReadSome() - { - boost::asio::io_context ioc; - boost::capy::polystore capy_ctx; - http::install_parser_service(capy_ctx, {}); - - // async_read_some completes when the parser reads - // the header section of the message. - { - test::stream ts(ioc, msg); - http::response_parser pr(capy_ctx); - pr.reset(); - pr.start(); - - // limit async_read_some for better coverage - ts.read_size(1); - - // header - async_read_some( - ts, - pr, - [&](system::error_code ec, std::size_t n) - { - BOOST_TEST(! ec.failed()); - BOOST_TEST_EQ(n, msg.size() - 3); // minus body - }); - test::run(ioc); - BOOST_TEST(pr.got_header()); - BOOST_TEST(! pr.is_complete()); - - // body - for(auto i = 0; i < 3; i++) - { - async_read_some( - ts, - pr, - [&](system::error_code ec, std::size_t n) - { - BOOST_TEST(! ec.failed()); - BOOST_TEST_EQ(n, 1); // because of ts.read_size(1) - }); - BOOST_TEST_EQ(test::run(ioc), 1); - } - BOOST_TEST(pr.is_complete()); - BOOST_TEST(pr.body() == "abc"); - } - - // async_read_some reports stream errors - { - test::fail_count fc(11, asio::error::network_down); - test::stream ts(ioc, fc, msg); - http::response_parser pr(capy_ctx); - pr.reset(); - pr.start(); - - // limit async_read_some for better coverage - ts.read_size(1); - - bool invoked = false; - async_read_some( - ts, - pr, - [&](system::error_code ec, std::size_t n) - { - invoked = true; - BOOST_TEST_EQ(ec, asio::error::network_down); - BOOST_TEST_EQ(n, 10); - }); - BOOST_TEST_EQ(test::run(ioc), 11); - BOOST_TEST(invoked); - } - - // async_read_some reports parser errors - { - test::stream ts(ioc, msg); - http::response_parser pr(capy_ctx); - pr.reset(); - pr.start(); - - // read header - async_read_some(ts, pr, test::success_handler()); - test::run(ioc); - - // read body - pr.set_body_limit(2); - async_read_some( - ts, - pr, - test::fail_handler(http::error::body_too_large)); - test::run(ioc); - } - - // async_read_some cancellation - { - test::stream ts(ioc); - asio::cancellation_signal c_signal; - http::response_parser pr(capy_ctx); - pr.reset(); - pr.start(); - - // async_read_some cancels after reading 0 bytes - async_read_some( - ts, - pr, - asio::bind_cancellation_slot( - c_signal.slot(), - [](system::error_code ec, std::size_t n) - { - BOOST_TEST_EQ(n, 0); - BOOST_TEST_EQ(ec, asio::error::operation_aborted); - })); - c_signal.emit(asio::cancellation_type::total); - test::run(ioc); - - // append 8 bytes of the msg - ts.append(msg.substr(0, 8)); - - // async_read_some cancels after reading 8 bytes - async_read_some( - ts, - pr, - asio::bind_cancellation_slot( - c_signal.slot(), - [](system::error_code ec, std::size_t n) - { - BOOST_TEST_EQ(n, 8); - BOOST_TEST_EQ(ec, asio::error::operation_aborted); - })); - c_signal.emit(asio::cancellation_type::total); - test::run(ioc); - - // append rest of the msg - ts.append(msg.substr(8, msg.npos)); - - // async_read_some succeeds - async_read_some(ts, pr, test::success_handler()); - test::run(ioc); - BOOST_TEST(pr.got_header()); - } - } - - void - testAsyncReadHeader() - { - // currently, async_read_header and - // async_read_some are identical - } - - void - testAsyncRead() - { - boost::asio::io_context ioc; - capy::polystore capy_ctx; - http::install_parser_service(capy_ctx, {}); - - // async_read completes when the parser reads - // the entire message. - { - test::stream ts(ioc, msg); - http::response_parser pr(capy_ctx); - pr.reset(); - pr.start(); - - // limit async_read_some for better coverage - ts.read_size(1); - - async_read( - ts, - pr, - [&](system::error_code ec, std::size_t n) - { - BOOST_TEST(! ec.failed()); - BOOST_TEST_EQ(n, msg.size()); - }); - - test::run(ioc); - - BOOST_TEST_EQ(ts.nread(), msg.size()); // because of ts.read_size(1) - BOOST_TEST(pr.is_complete()); - BOOST_TEST(pr.body() == "abc"); - } - - // async_read completes immediatly when - // parser contains enough data - { - asio::post( - ioc, - [&]() - { - test::stream ts(ioc); - http::response_parser pr(capy_ctx); - pr.reset(); - pr.start(); - - pr.commit( - capy::copy( - pr.prepare(), - capy::const_buffer( - msg.data(), - msg.size()))); - - async_read( - ts, - pr, - asio::bind_immediate_executor( - ioc.get_executor(), - test::success_handler())); - - BOOST_TEST_EQ(ts.nread(), 0); - BOOST_TEST(pr.is_complete()); - BOOST_TEST(pr.body() == "abc"); - }); - BOOST_TEST_EQ(test::run(ioc), 1); - } - } - - void - run() - { - testAsyncReadSome(); - testAsyncReadHeader(); - testAsyncRead(); - } -}; - -TEST_SUITE(read_test, "boost.beast2.read"); - -} // beast2 -} // boost diff --git a/test/unit/sandbox.cpp b/test/unit/sandbox.cpp deleted file mode 100644 index c6b35476..00000000 --- a/test/unit/sandbox.cpp +++ /dev/null @@ -1,229 +0,0 @@ -// -// Copyright (c) 2016-2019 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 "test_suite.hpp" - -#if 0 - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace boost { -namespace http { - -static std::size_t constexpr buffer_bytes = 8 * 1024 * 1024; -static std::size_t constexpr payload_size = buffer_bytes * 10; - -using tcp = asio::ip::tcp; -namespace ssl = asio::ssl; -using string_view = - boost::core::string_view; - -//static auto const& log = test_suite::log; -static std::stringstream log; - -struct logging_socket : asio::ip::tcp::socket -{ - using asio::ip::tcp::socket::socket; - - template - BOOST_ASIO_INITFN_AUTO_RESULT_TYPE_PREFIX(WriteToken, - void (boost::system::error_code, std::size_t)) - async_write_some(const ConstBufferSequence& buffers, - BOOST_ASIO_MOVE_ARG(WriteToken) token - BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)) - BOOST_ASIO_INITFN_AUTO_RESULT_TYPE_SUFFIX(( - async_initiate( - declval(), token, - buffers, socket_base::message_flags(0)))) - { - return asio::ip::tcp::socket::async_write_some( - buffers, - asio::deferred([](boost::system::error_code ec, std::size_t n) - { - log << "async_write_some: " << n << "\n"; - return asio::deferred.values(ec, n); - }))(std::forward(token)); - } - -}; - -class my_transfer_all_t -{ -public: - typedef std::size_t result_type; - - template - std::size_t operator()(const Error& err, std::size_t) - { - return !!err ? 0 : std::size_t(-1); - } -}; - -static constexpr my_transfer_all_t my_transfer_all{}; - -struct sandbox_test -{ - void - do_write( - asio::io_context& ioc, - asio::yield_context yield) - { - using clock_type = - std::chrono::high_resolution_clock; - - tcp::resolver dns(ioc); - logging_socket sock(ioc); - std::unique_ptr up(new char[buffer_bytes]); - asio::mutable_buffer mb(up.get(), buffer_bytes); - request req; - - asio::async_connect( - sock, - dns.resolve( - "httpbin.cpp.al", "http"), - yield); - - // header - req.set_start_line( - method::post, "/post", version::http_1_1); - req.append(field::host, "httpbin.cpp.al"); - req.append(field::accept, "application/text"); - req.append(field::user_agent, "boost"); - req.set_payload_size(payload_size); - asio::async_write( - sock, - asio::buffer(req.buffer()), - yield); - - // body - std::size_t n = 0; - std::size_t count = 0; - while(n < payload_size) - { - auto amount = payload_size - n; - if( amount > buffer_bytes) - amount = buffer_bytes; - auto const t0 = clock_type::now(); - auto bytes_transferred = - asio::async_write( - sock, - asio::mutable_buffer( - mb.data(), - amount), - my_transfer_all, - yield); - auto const ms = std::chrono::duration_cast< - std::chrono::milliseconds>( - clock_type::now() - t0).count(); - log << - "write " << bytes_transferred << - " bytes in " << ms << - "ms\n"; - - n += bytes_transferred; - ++count; - } - log << count << " writes total\n\n"; - } - - void - do_write_some( - asio::io_context& ioc, - asio::yield_context yield) - { - using clock_type = - std::chrono::high_resolution_clock; - - tcp::resolver dns(ioc); - tcp::socket sock(ioc); - std::unique_ptr up(new char[buffer_bytes]); - asio::mutable_buffer mb(up.get(), buffer_bytes); - request req; - - asio::async_connect( - sock, - dns.resolve( - "httpbin.cpp.al", "http"), - yield); - - // header - req.set_start_line( - method::post, "/post", version::http_1_1); - req.append(field::host, "httpbin.cpp.al"); - req.append(field::accept, "application/text"); - req.append(field::user_agent, "boost"); - req.set_payload_size(payload_size); - asio::async_write( - sock, - asio::buffer(req.buffer()), - yield); - - // body - std::size_t n = 0; - std::size_t count = 0; - while(n < payload_size) - { - auto amount = payload_size - n; - if( amount > buffer_bytes) - amount = buffer_bytes; - auto const t0 = clock_type::now(); - auto bytes_transferred = - sock.async_write_some( - asio::mutable_buffer( - mb.data(), - amount), - yield); - auto const ms = std::chrono::duration_cast< - std::chrono::milliseconds>( - clock_type::now() - t0).count(); - log << - "write_some " << bytes_transferred << - " bytes in " << ms << - "ms\n"; - - n += bytes_transferred; - ++count; - } - log << count << " write_somes total\n\n"; - } - - void - run() - { - asio::io_context ioc; - asio::spawn(ioc, [&]( - asio::yield_context yield) - { - do_write( ioc, yield ); - do_write_some( ioc, yield ); - }); - ioc.run(); - test_suite::log << log.str(); - } -}; - -TEST_SUITE( - sandbox_test, - "boost.beast2.sandbox"); - -} // http -} // boost - -#endif diff --git a/test/unit/server/call_mf.cpp b/test/unit/server/call_mf.cpp deleted file mode 100644 index ace88d5c..00000000 --- a/test/unit/server/call_mf.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 diff --git a/test/unit/server/route_handler_asio.cpp b/test/unit/server/route_handler_asio.cpp deleted file mode 100644 index 5453f09e..00000000 --- a/test/unit/server/route_handler_asio.cpp +++ /dev/null @@ -1,74 +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 - -#include "test_suite.hpp" - -namespace boost { -namespace beast2 { - -struct route_handler_asio_test -{ - struct stream - { - asio::any_io_executor - get_executor() const - { - return {}; - } - }; - - using test_router = http::basic_router< - asio_route_params>; - - void check( - test_router& r, - core::string_view url, - http::route_result rv0 = http::route::send) - { - asio_route_params req; - auto rv = r.dispatch( - http::method::get, - urls::url_view(url), req); - if(BOOST_TEST_EQ(rv.message(), rv0.message())) - BOOST_TEST(rv == rv0); - } - - struct handler - { - template - http::route_result - operator()( - asio_route_params&) const - { - BOOST_TEST(true); - return http::route::send; - } - }; - - void run() - { - test_router r; - r.use(handler{}); - check(r,"/"); - } -}; - -TEST_SUITE( - route_handler_asio_test, - "boost.beast2.server.route_handler_asio"); - -} // beast2 -} // boost diff --git a/test/unit/server/router_asio.cpp b/test/unit/server/router_asio.cpp deleted file mode 100644 index 98b34980..00000000 --- a/test/unit/server/router_asio.cpp +++ /dev/null @@ -1,49 +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 - -#include "test_suite.hpp" - -namespace boost { -namespace beast2 { - -namespace { - -struct Stream -{ - asio::any_io_executor - get_executor() const - { - return {}; - } -}; - -BOOST_CORE_STATIC_ASSERT( - std::is_constructible>::value); - -} // (anon) - -struct router_asio_test -{ - void - run() - { - } -}; - -TEST_SUITE(router_asio_test, "boost.beast2.server.router_asio"); - -} // beast2 -} // boost \ No newline at end of file diff --git a/test/unit/test_helpers.hpp b/test/unit/test_helpers.hpp deleted file mode 100644 index 4dc1c05a..00000000 --- a/test/unit/test_helpers.hpp +++ /dev/null @@ -1,199 +0,0 @@ -// -// Copyright (c) 2016-2019 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_TEST_TEST_HELPERS_HPP -#define BOOST_BEAST2_TEST_TEST_HELPERS_HPP - -#include -#include -#include - -#include "test_suite.hpp" - -namespace boost { -namespace beast2 { -namespace test { - -/** A CompletionHandler used for testing. - - This completion handler is used by tests to ensure correctness - of behavior. It is designed as a single type to reduce template - instantiations, with configurable settings through constructor - arguments. Typically this type will be used in type lists and - not instantiated directly; instances of this class are returned - by the helper functions listed below. - - @see success_handler, @ref fail_handler, @ref any_handler -*/ -class handler -{ - boost::optional ec_; - bool pass_ = false; - boost::source_location loc_{BOOST_CURRENT_LOCATION}; - - public: - handler( - boost::source_location loc = BOOST_CURRENT_LOCATION) - : loc_(loc) - { - } - - explicit - handler( - system::error_code ec, - boost::source_location loc = BOOST_CURRENT_LOCATION) - : ec_(ec) - , loc_(loc) - { - } - - explicit - handler( - boost::none_t, - boost::source_location loc = BOOST_CURRENT_LOCATION) - : loc_(loc) - { - } - - handler(handler&& other) - : ec_(other.ec_) - , pass_(boost::exchange(other.pass_, true)) - , loc_(other.loc_) - - { - } - - ~handler() - { - test_suite::any_runner::instance().test( - pass_, - "handler never invoked", - "", - loc_.file_name(), - loc_.line()); - } - - template - void - operator()(system::error_code ec, Args&&...) - { - test_suite::any_runner::instance().test( - !pass_, - "handler invoked multiple times", - "", - loc_.file_name(), - loc_.line()); - - test_suite::any_runner::instance().test( - !ec_.has_value() || ec == *ec_, - ec.message().c_str(), - "", - loc_.file_name(), - loc_.line()); - pass_ = true; - } -}; - -/** Return a test CompletionHandler which requires success. - - The returned handler can be invoked with any signature whose - first parameter is an `system::error_code`. The handler fails the test - if: - - @li The handler is destroyed without being invoked, or - - @li The handler is invoked with a non-successful error code. -*/ -inline -handler -success_handler(boost::source_location loc = BOOST_CURRENT_LOCATION) noexcept -{ - return handler(system::error_code{}, loc); -} - -/** Return a test CompletionHandler which requires invocation. - - The returned handler can be invoked with any signature. - The handler fails the test if: - - @li The handler is destroyed without being invoked. -*/ -inline -handler -any_handler(boost::source_location loc = BOOST_CURRENT_LOCATION) noexcept -{ - return handler(boost::none, loc); -} - -/** Return a test CompletionHandler which requires a specific error code. - - This handler can be invoked with any signature whose first - parameter is an `system::error_code`. The handler fails the test if: - - @li The handler is destroyed without being invoked. - - @li The handler is invoked with an error code different from - what is specified. - - @param ec The error code to specify. -*/ -inline -handler -fail_handler(system::error_code ec,boost::source_location loc = BOOST_CURRENT_LOCATION) noexcept -{ - return handler(ec, loc); -} - -/** Run an I/O context. - - This function runs and dispatches handlers on the specified - I/O context, until one of the following conditions is true: - - @li The I/O context runs out of work. - - @param ioc The I/O context to run -*/ -inline -std::size_t -run(asio::io_context& ioc) -{ - std::size_t n = ioc.run(); - ioc.restart(); - return n; -} - -/** Run an I/O context for a certain amount of time. - - This function runs and dispatches handlers on the specified - I/O context, until one of the following conditions is true: - - @li The I/O context runs out of work. - - @li No completions occur and the specified amount of time has elapsed. - - @param ioc The I/O context to run - - @param elapsed The maximum amount of time to run for. -*/ -template -std::size_t -run_for( - asio::io_context& ioc, - std::chrono::duration elapsed) -{ - std::size_t n = ioc.run_for(elapsed); - ioc.restart(); - return n; -} - -} // test -} // beast2 -} // boost - -#endif diff --git a/test/unit/wrap_executor.cpp b/test/unit/wrap_executor.cpp deleted file mode 100644 index 60b8ecc4..00000000 --- a/test/unit/wrap_executor.cpp +++ /dev/null @@ -1,148 +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 "test_helpers.hpp" - -#include -#include -#include -#include -#include - -namespace boost { -namespace beast2 { - -//----------------------------------------------------------------------------- - -struct wrap_executor_test -{ - void - testWrapExecutor() - { - // Verify wrap_executor returns a type that satisfies dispatcher concept - asio::io_context ioc; - auto dispatcher = wrap_executor(ioc.get_executor()); - - // Should be storable in any_dispatcher - capy::any_dispatcher any_disp(dispatcher); - BOOST_TEST(static_cast(any_disp)); - } - - void - testDispatchCoroutine() - { - asio::io_context ioc; - - bool resumed = false; - - // Create and run a simple coroutine - auto make_task = [&resumed]() -> capy::task { - resumed = true; - co_return; - }; - - // Launch using async_run with our dispatcher - capy::async_run(wrap_executor(ioc.get_executor()))(make_task()); - - // Coroutine should not have run yet (just scheduled) - BOOST_TEST(!resumed); - - // Run the io_context to execute posted work - test::run(ioc); - - // Now it should have run - BOOST_TEST(resumed); - } - - void - testWithStrand() - { - asio::io_context ioc; - asio::strand strand( - ioc.get_executor()); - auto dispatcher = wrap_executor(strand); - - // Should be storable in any_dispatcher - capy::any_dispatcher any_disp(dispatcher); - BOOST_TEST(static_cast(any_disp)); - } - - void - testAnyDispatcherStorage() - { - // Test pattern used in http route_params - struct params - { - capy::any_dispatcher ex; - }; - - asio::io_context ioc; - params p; - p.ex = wrap_executor(ioc.get_executor()); - - BOOST_TEST(static_cast(p.ex)); - } - - void - testAnyDispatcherCopy() - { - asio::io_context ioc; - auto dispatcher = wrap_executor(ioc.get_executor()); - - capy::any_dispatcher disp1(dispatcher); - capy::any_dispatcher disp2 = disp1; - - BOOST_TEST(static_cast(disp1)); - BOOST_TEST(static_cast(disp2)); - BOOST_TEST(disp1 == disp2); - } - - void - testMultipleCoroutines() - { - asio::io_context ioc; - - int count = 0; - - auto make_task = [&count]() -> capy::task { - ++count; - co_return; - }; - - // Launch multiple coroutines - capy::async_run(wrap_executor(ioc.get_executor()))(make_task()); - capy::async_run(wrap_executor(ioc.get_executor()))(make_task()); - capy::async_run(wrap_executor(ioc.get_executor()))(make_task()); - - BOOST_TEST_EQ(count, 0); - test::run(ioc); - BOOST_TEST_EQ(count, 3); - } - - void - run() - { - testWrapExecutor(); - testDispatchCoroutine(); - testWithStrand(); - testAnyDispatcherStorage(); - testAnyDispatcherCopy(); - testMultipleCoroutines(); - } -}; - -TEST_SUITE( - wrap_executor_test, - "boost.beast2.wrap_executor"); - -} // beast2 -} // boost diff --git a/test/unit/write.cpp b/test/unit/write.cpp deleted file mode 100644 index 59652f6d..00000000 --- a/test/unit/write.cpp +++ /dev/null @@ -1,323 +0,0 @@ -// -// Copyright (c) 2016-2019 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 "test_suite.hpp" - -namespace boost { -namespace beast2 { - -//class any_async_read_stream -//{ -//}; -// -//class write_test -//{ -//public: -// void -// testWrite() -// { -// } -// -// void -// run() -// { -// testWrite(); -// } -//}; -// -//TEST_SUITE( -// write_test, -// "boost.beast2.write"); - -} // beast2 -} // boost - -// -// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) -// Copyright (c) 2025 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 -// - -// Test that header file is self-contained. -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "test_helpers.hpp" - -#include - -namespace boost { -namespace beast2 { - -class write_test -{ - core::string_view const headers = - "HTTP/1.1 200 OK\r\n" - "Content-Length: 3\r\n" - "\r\n"; - core::string_view const body = - "abc"; - core::string_view const msg = - "HTTP/1.1 200 OK\r\n" - "Content-Length: 3\r\n" - "\r\n" - "abc"; - -public: - void - testAsyncWriteSome() - { - boost::asio::io_context ioc; - boost::capy::polystore capy_ctx; - http::install_serializer_service(capy_ctx, {}); - - // async_write_some completes when the serializer writes the message. - { - test::stream ts{ ioc }, tr{ ioc }; - ts.connect(tr); - ts.write_size(1); - - http::serializer sr(capy_ctx); - sr.reset(); - - http::response res(headers); - sr.start(res, capy::const_buffer(body.data(), body.size())); - - for(std::size_t total = 0; total < msg.size(); total++) - { - async_write_some( - ts, - sr, - [&](system::error_code ec, std::size_t n) - { - BOOST_TEST(!ec.failed()); - BOOST_TEST_EQ(n, 1); - }); - test::run(ioc); - } - BOOST_TEST(sr.is_done()); - BOOST_TEST_EQ(tr.str(), msg); - - BOOST_TEST_EQ(tr.str(), msg); - } - - // async_write_some reports stream errors - { - test::fail_count fc(3, asio::error::network_down); - test::stream ts{ ioc, fc }, tr{ ioc }; - ts.connect(tr); - ts.write_size(1); - - http::serializer sr(capy_ctx); - sr.reset(); - - http::response res(headers); - sr.start(res, capy::const_buffer(body.data(), body.size())); - - for(int count = 0; count < 3; count++) - { - async_write_some( - ts, - sr, - [&](system::error_code ec, std::size_t n) - { - if (count < 2) - { - BOOST_TEST(!ec.failed()); - BOOST_TEST_EQ(n, 1); - } - else - { - BOOST_TEST_EQ(ec, asio::error::network_down); - BOOST_TEST_EQ(n, 0); - } - }); - test::run(ioc); - - auto expected = msg.substr(0, (count == 0) ? 1 : 2); - BOOST_TEST_EQ(tr.str(), expected); - } - } - - // async_write_some cancellation - { - boost::array ctypes{ - { asio::cancellation_type::total, - asio::cancellation_type::partial, - asio::cancellation_type::terminal }}; - - for(auto ctype : ctypes) - { - test::stream ts{ ioc }, tr{ ioc }; - ts.connect(tr); - ts.write_size(5); - - asio::cancellation_signal c_signal; - - http::serializer sr(capy_ctx); - sr.reset(); - - http::response res(headers); - sr.start(res, capy::const_buffer(body.data(), body.size())); - - // async_read_some cancels after reading 0 bytes - async_write_some( - ts, - sr, - asio::bind_cancellation_slot( - c_signal.slot(), - [](system::error_code ec, std::size_t n) - { - BOOST_TEST_EQ(n, 5); - BOOST_TEST(!ec.failed()); - })); - c_signal.emit(ctype); - - test::run(ioc); - - BOOST_TEST_EQ(tr.str(), "HTTP/"); - } - } - } - - void - testAsyncWrite() - { - boost::asio::io_context ioc; - capy::polystore capy_ctx; - http::install_serializer_service(capy_ctx, {}); - - // async_write completes when the serializer writes - // the entire message. - { - test::stream ts{ ioc }, tr{ ioc }; - ts.connect(tr); - ts.write_size(1); - - http::serializer sr(capy_ctx); - sr.reset(); - - http::response res(headers); - sr.start(res, capy::const_buffer(body.data(), body.size())); - - async_write( - ts, - sr, - [&](system::error_code ec, std::size_t n) - { - BOOST_TEST(!ec.failed()); - BOOST_TEST_EQ(n, msg.size()); - }); - - test::run(ioc); - - BOOST_TEST_EQ(ts.nwrite(), msg.size()); // because of ts.write_size(1) - BOOST_TEST(sr.is_done()); - BOOST_TEST_EQ(tr.str(), msg); - } - - // async_write reports stream errors - { - test::fail_count fc(3, asio::error::network_down); - test::stream ts{ ioc, fc }, tr{ ioc }; - ts.connect(tr); - ts.write_size(1); - - http::serializer sr(capy_ctx); - sr.reset(); - - http::response res(headers); - sr.start(res, capy::const_buffer(body.data(), body.size())); - - async_write( - ts, - sr, - [&](system::error_code ec, std::size_t n) - { - BOOST_TEST_EQ(ec, asio::error::network_down); - BOOST_TEST_EQ(n, 2); - }); - test::run(ioc); - - auto expected = msg.substr(0, 2); - BOOST_TEST_EQ(tr.str(), expected); - - } - - // async_write cancellation - { - boost::array ctypes{ - { asio::cancellation_type::total, - asio::cancellation_type::partial, - asio::cancellation_type::terminal }}; - - for(auto ctype : ctypes) - { - test::stream ts{ ioc }, tr{ ioc }; - ts.connect(tr); - ts.write_size(5); - - asio::cancellation_signal c_signal; - - http::serializer sr(capy_ctx); - sr.reset(); - - http::response res(headers); - sr.start(res, capy::const_buffer(body.data(), body.size())); - - // cancel after writing - async_write( - ts, - sr, - asio::bind_cancellation_slot( - c_signal.slot(), - [](system::error_code ec, std::size_t n) - { - BOOST_TEST_EQ(n, 5); - BOOST_TEST_EQ(ec, asio::error::operation_aborted); - })); - c_signal.emit(ctype); - - test::run(ioc); - - BOOST_TEST_EQ(tr.str(), "HTTP/"); - } - } - } - - void - run() - { - testAsyncWriteSome(); - testAsyncWrite(); - } -}; - -TEST_SUITE(write_test, "boost.beast2.write"); - -} // beast2 -} // boost From 4d58b486d01ba8d0885e5ab1c1706de512a3aed9 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Thu, 15 Jan 2026 04:16:38 -0800 Subject: [PATCH 35/40] chore: add CMake presets and clean up ignore files - Add CMakePresets.json with Ninja generator configuration - Simplify .gitignore to focus on IDE and build output directories - Remove redundant .cursorignore file --- .cursorignore | 25 ------------------------- .gitignore | 24 +++++------------------- CMakePresets.json | 16 ++++++++++++++++ 3 files changed, 21 insertions(+), 44 deletions(-) delete mode 100644 .cursorignore create mode 100644 CMakePresets.json diff --git a/.cursorignore b/.cursorignore deleted file mode 100644 index 2a869fdb..00000000 --- a/.cursorignore +++ /dev/null @@ -1,25 +0,0 @@ -/bin -/bin64 - -/__build__ -/toolchain.cmake - -# Emacs -*# - -# Vim -*~ - -# Visual Studio -/.vs -/out - -# Visual Studio Code -/.vscode -CMakeUserPresets.json - -# clangd -/.cache -/.clangd -/compile_commands.json -# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv) diff --git a/.gitignore b/.gitignore index 00be625f..64be8704 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,5 @@ -/bin -/bin64 -/_build* -temp - -# Emacs -*# - -# Vim -*~ - -# Visual Studio -/.vs -/out - -# Visual Studio Code -/.vscode - -/.temp +/.vscode/ +/build/ +!/build/Jamfile +/out/ +CMakeUserPresets.json diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 00000000..ca4abfb1 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,16 @@ +{ + "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 From e326c338cb7cb3e3adba8d26101d4299680768ca Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Thu, 15 Jan 2026 04:46:57 -0800 Subject: [PATCH 36/40] refactor: update capy include paths for library reorganization - Update thread_pool.hpp include to boost/capy/ex/thread_pool.hpp - Update polystore.hpp includes to boost/capy/core/polystore.hpp - Update async_run.hpp include to boost/capy/ex/async_run.hpp --- example/server/main.cpp | 2 +- example/server/serve_log_admin.hpp | 2 +- include/boost/beast2/log_service.hpp | 2 +- include/boost/beast2/server/workers.hpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/example/server/main.cpp b/example/server/main.cpp index d7765b83..20453ef5 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/example/server/serve_log_admin.hpp b/example/server/serve_log_admin.hpp index 10e51d57..1851833c 100644 --- a/example/server/serve_log_admin.hpp +++ b/example/server/serve_log_admin.hpp @@ -12,7 +12,7 @@ #include #include -#include +#include namespace boost { namespace beast2 { diff --git a/include/boost/beast2/log_service.hpp b/include/boost/beast2/log_service.hpp index 4099f3d4..9a975330 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 diff --git a/include/boost/beast2/server/workers.hpp b/include/boost/beast2/server/workers.hpp index 7713d4de..b60a880e 100644 --- a/include/boost/beast2/server/workers.hpp +++ b/include/boost/beast2/server/workers.hpp @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include #include #include From 09006cb8b1842310a291ca6776bd5d6e07bce08e Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Thu, 15 Jan 2026 10:20:09 -0800 Subject: [PATCH 37/40] Tidy up .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 64be8704..06c484e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ /.vscode/ -/build/ +/build/* !/build/Jamfile /out/ CMakeUserPresets.json From a151fa293790b75734886c340291d43c3785fd4c Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sat, 17 Jan 2026 09:30:33 -0800 Subject: [PATCH 38/40] Call run_async --- include/boost/beast2/server/workers.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/boost/beast2/server/workers.hpp b/include/boost/beast2/server/workers.hpp index b60a880e..979c1adb 100644 --- a/include/boost/beast2/server/workers.hpp +++ b/include/boost/beast2/server/workers.hpp @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include #include #include @@ -146,7 +146,7 @@ start() stopped_ = false; for (std::size_t i = 0; i < acceptors_.size(); ++i) { - capy::async_run(ioc_.get_executor())( + capy::run_async(ioc_.get_executor())( accept_loop(acceptors_[i], configs_[i])); } } @@ -212,7 +212,7 @@ accept_loop(corosio::acceptor& acc, http::acceptor_config config) } // Spawn session coroutine - capy::async_run(ioc_.get_executor())( + capy::run_async(ioc_.get_executor())( run_session(*free_worker, config)); } } From 3cb7a4ec1833fa1dd20ecc560a4570d4b9063b5d Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sat, 17 Jan 2026 18:55:46 -0800 Subject: [PATCH 39/40] coroutine-driven router and http-server --- example/server/main.cpp | 50 +++- example/server/serve_log_admin.cpp | 2 + include/boost/beast2/application.hpp | 28 +++ include/boost/beast2/http_server.hpp | 46 ++++ include/boost/beast2/server/http_stream.hpp | 16 +- include/boost/beast2/server/router.hpp | 4 +- .../boost/beast2/server/router_corosio.hpp | 4 +- include/boost/beast2/server/workers.hpp | 6 +- src/http_server.cpp | 233 ++++++++++++++++++ src/server/http_server.cpp | 2 +- test/unit/http_server.cpp | 28 +++ 11 files changed, 401 insertions(+), 18 deletions(-) create mode 100644 include/boost/beast2/application.hpp create mode 100644 include/boost/beast2/http_server.hpp create mode 100644 src/http_server.cpp create mode 100644 test/unit/http_server.cpp diff --git a/example/server/main.cpp b/example/server/main.cpp index 20453ef5..b4605c51 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -8,8 +8,9 @@ // #include "serve_log_admin.hpp" -#include +//#include #include +#include #include #include #include @@ -17,14 +18,18 @@ #include #include #include -#include +#include +#include #include #include #include #include #include +#include #include +#include + namespace boost { namespace beast2 { @@ -173,9 +178,39 @@ int server_main( int argc, char* argv[] ) } capy::application app; + corosio::io_context ioc; install_services(app); + auto addr = urls::parse_ipv4_address(argv[1]); + if(addr.has_error()) + { + std::cerr << "Invalid address: " << argv[1] << "\n"; + return EXIT_FAILURE; + } + auto port = static_cast(std::atoi(argv[2])); + corosio::endpoint ep(addr.value(), port); + + http::router rr; + + rr.use( + [](http::route_params& rp) -> capy::task + { + (void)rp; + co_return http::route::next; + }); + http_server hsrv(ioc, std::atoi(argv[4]), std::move(rr)); + auto ec = hsrv.bind(ep); + if(ec) + { + std::cerr << "Bind failed: " << ec.message() << "\n"; + return EXIT_FAILURE; + } + + hsrv.start(); + ioc.run(); + +#if 0 auto& srv = install_plain_http_server( app, argv[1], @@ -185,23 +220,30 @@ int server_main( int argc, char* argv[] ) http::cors_options opts; opts.allowedHeaders = "Content-Type"; + srv.wwwroot.use( + []( http::route_params& rp ) -> + capy::task + { + co_return {}; + }); srv.wwwroot.use( "/rpc", http::cors(opts), do_json_rpc() ); -#if 0 + srv.wwwroot.use( "/spawn", http::co_route(my_coro)); srv.wwwroot.use( "/bcrypt", http::co_route(do_bcrypt)); -#endif + srv.wwwroot.use("/", serve_static( argv[3] )); app.start(); srv.run(); +#endif } catch( std::exception const& e ) { diff --git a/example/server/serve_log_admin.cpp b/example/server/serve_log_admin.cpp index b313ee9d..575754b8 100644 --- a/example/server/serve_log_admin.cpp +++ b/example/server/serve_log_admin.cpp @@ -112,6 +112,7 @@ class handle_submit //------------------------------------------------ +#if 0 router serve_log_admin( capy::polystore& ps) @@ -121,6 +122,7 @@ serve_log_admin( r.add(http::method::get, "/submit", handle_submit(ps)); return r; } +#endif } // beast2 } // boost diff --git a/include/boost/beast2/application.hpp b/include/boost/beast2/application.hpp new file mode 100644 index 00000000..345b3b3d --- /dev/null +++ b/include/boost/beast2/application.hpp @@ -0,0 +1,28 @@ +// +// 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_APPLICATION_HPP +#define BOOST_BEAST2_APPLICATION_HPP + +#include +#include +#include + +namespace boost { +namespace beast2 { + +class port +{ +public: +}; + +} // beast2 +} // boost + +#endif diff --git a/include/boost/beast2/http_server.hpp b/include/boost/beast2/http_server.hpp new file mode 100644 index 00000000..303f347f --- /dev/null +++ b/include/boost/beast2/http_server.hpp @@ -0,0 +1,46 @@ +// +// Copyright (c) 2026 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_HTTP_SERVER_HPP +#define BOOST_BEAST2_HTTP_SERVER_HPP + +#include +#include +#include +#include + +namespace boost { +namespace http { class flat_router; } +namespace beast2 { + +class BOOST_BEAST2_DECL + http_server : public corosio::tcp_server +{ + struct impl; + impl* impl_; + +public: + ~http_server(); + + http_server( + corosio::io_context& ctx, + std::size_t num_workers, + http::flat_router router); + +private: + struct worker; + + capy::task + do_session(worker& w); +}; + +} // beast2 +} // boost + +#endif diff --git a/include/boost/beast2/server/http_stream.hpp b/include/boost/beast2/server/http_stream.hpp index 8d3f6ca9..c9bf8d4e 100644 --- a/include/boost/beast2/server/http_stream.hpp +++ b/include/boost/beast2/server/http_stream.hpp @@ -26,7 +26,8 @@ #include #include #include -#include +#include +#include #include #include @@ -60,7 +61,7 @@ class http_stream http_stream( capy::application& app, corosio::socket& sock, - router_corosio routes); + http::flat_router& routes); /** Run the HTTP session as a coroutine. @@ -99,7 +100,7 @@ class http_stream section sect_; std::size_t id_ = 0; corosio::socket& sock_; - router_corosio routes_; + //http::flat_router& routes_; http::acceptor_config const* pconfig_ = nullptr; corosio_route_params rp_; }; @@ -111,7 +112,7 @@ http_stream:: http_stream( capy::application& app, corosio::socket& sock, - router_corosio routes) + http::flat_router& /*routes*/) : sect_(use_log_service(app).get_section("http_stream")) , id_( []() noexcept @@ -120,7 +121,7 @@ http_stream( return ++n; }()) , sock_(sock) - , routes_(std::move(routes)) + //, routes_(routes) , rp_(sock_) { rp_.parser = http::request_parser(app); @@ -348,8 +349,9 @@ http::route_result http_stream:: do_dispatch() { - return routes_.dispatch( - rp_.req.method(), rp_.url, rp_); + return {}; + //return routes_.dispatch( + //rp_.req.method(), rp_.url, rp_); } inline diff --git a/include/boost/beast2/server/router.hpp b/include/boost/beast2/server/router.hpp index 4f80d204..ed94d146 100644 --- a/include/boost/beast2/server/router.hpp +++ b/include/boost/beast2/server/router.hpp @@ -11,7 +11,7 @@ #define BOOST_BEAST2_SERVER_ROUTER_HPP #include -#include +#include #include namespace boost { @@ -19,7 +19,7 @@ namespace beast2 { /** The sans-IO router type */ -using router = http::basic_router; +using router = http::router; } // beast2 } // boost diff --git a/include/boost/beast2/server/router_corosio.hpp b/include/boost/beast2/server/router_corosio.hpp index c0d89e4a..d091128d 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::basic_router; +using router_corosio = http::router; } // beast2 } // boost diff --git a/include/boost/beast2/server/workers.hpp b/include/boost/beast2/server/workers.hpp index 979c1adb..f7251abf 100644 --- a/include/boost/beast2/server/workers.hpp +++ b/include/boost/beast2/server/workers.hpp @@ -100,7 +100,7 @@ class workers capy::application& app_; corosio::io_context& ioc_; section sect_; - router_corosio routes_; + http::flat_router routes_; std::vector workers_; std::vector acceptors_; std::vector configs_; @@ -220,7 +220,9 @@ accept_loop(corosio::acceptor& acc, http::acceptor_config config) inline capy::task workers:: -run_session(http_worker& w, http::acceptor_config const& config) +run_session( + http_worker& w, + http::acceptor_config const& config) { w.in_use = true; diff --git a/src/http_server.cpp b/src/http_server.cpp new file mode 100644 index 00000000..72777050 --- /dev/null +++ b/src/http_server.cpp @@ -0,0 +1,233 @@ +// +// Copyright (c) 2026 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace boost { +namespace beast2 { + +struct http_server::impl +{ + http::flat_router router; + capy::application app; + + 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()); + } +}; + +struct http_server:: + worker : http_server::worker_base +{ + using launcher = launcher; + + http_server* srv; + corosio::io_context& ctx; + capy::strand strand; + corosio::socket sock; + corosio_route_params rp; + + worker( + corosio::io_context& ctx_, + http_server* srv_) + : srv(srv_) + , ctx(ctx_) + , strand(ctx_.get_executor()) + , sock(ctx_) + , rp(sock) + { + sock.open(); + rp.parser = http::request_parser(srv->impl_->app); + rp.serializer = http::serializer(srv->impl_->app); + } + + corosio::socket& socket() override + { + return sock; + } + + void run(launcher launch) override + { + launch(ctx.get_executor(), srv->do_session(*this)); + } + + capy::task> + read_header() + { + std::size_t total_bytes = 0; + system::error_code ec; + + for (;;) + { + // Try to parse what we have + rp.parser.parse(ec); + + if (ec == http::condition::need_more_input) + { + // Need to read more data + auto buf = rp.parser.prepare(); + auto [read_ec, n] = co_await sock.read_some(buf); + + if (read_ec) + co_return {read_ec, total_bytes}; + + if (n == 0) + { + // EOF + rp.parser.commit_eof(); + ec = {}; + } + else + { + rp.parser.commit(n); + total_bytes += n; + } + continue; + } + + if (ec.failed()) + co_return {ec, total_bytes}; + + if (rp.parser.got_header()) + co_return {{}, total_bytes}; + } + } + + void on_headers() + { + // Set up Request and Response objects + rp.req = rp.parser.get(); + rp.route_data.clear(); + rp.res.set_start_line( + http::status::ok, rp.req.version()); + rp.res.set_keep_alive(rp.req.keep_alive()); + rp.serializer.reset(); + + // Parse the URL + auto rv = urls::parse_uri_reference(rp.req.target()); + if (rv.has_error()) + { + rp.status(http::status::bad_request); + rp.set_body("Bad Request: " + rv.error().message()); + return; + } + + rp.url = rv.value(); + } +}; + +http_server:: +~http_server() +{ + delete impl_; +} + +http_server:: +http_server( + corosio::io_context& ctx, + std::size_t num_workers, + http::flat_router router) + : tcp_server(ctx, ctx.get_executor()) + , impl_(new impl(std::move(router))) +{ + wv_.reserve(num_workers); + for(std::size_t i = 0; i < num_workers; ++i) + wv_.emplace(ctx, this); +} + +capy::task +http_server:: +do_session( + worker& w) +{ + struct guard + { + corosio_route_params& rp; + + guard(corosio_route_params& rp_) + : rp(rp_) + { + } + + ~guard() + { + rp.parser.reset(); + rp.session_data.clear(); + rp.parser.start(); + } + }; + + guard g(w.rp); // clear things when session ends + + for(;;) + { + w.rp.parser.reset(); + w.rp.session_data.clear(); + w.rp.parser.start(); + + // Read HTTP request header + auto [ec, n] = co_await w.read_header(); + if (ec) + { + //LOG_TRC(sect_)("{} read_header: {}", id(), ec.message()); + break; + } + + //LOG_TRC(sect_)("{} read_header bytes={}", id(), n); + + // Process headers and dispatch + w.on_headers(); + + auto rv = co_await impl_->router.dispatch( + w.rp.req.method(), w.rp.url, w.rp); +#if 0 + do_respond(rv); + + // Write response + if (!rp.serializer.is_done()) + { + auto [wec, wn] = co_await write_response(); + if (wec) + { + LOG_TRC(sect_)("{} write_response: {}", id(), wec.message()); + break; + } + LOG_TRC(sect_)("{} write_response bytes={}", id(), wn); + } + + // Check keep-alive + if (!rp.res.keep_alive()) + break; +#endif + } +} + +} // beast2 +} // boost diff --git a/src/server/http_server.cpp b/src/server/http_server.cpp index ebdbddb9..5d966a14 100644 --- a/src/server/http_server.cpp +++ b/src/server/http_server.cpp @@ -29,7 +29,7 @@ class http_server_impl unsigned short port, std::size_t num_workers) : ioc_() - , workers_(app, ioc_, num_workers, this->wwwroot) + , workers_(app, ioc_, num_workers, std::move(this->wwwroot)) { // Parse address and create endpoint auto addr_result = urls::parse_ipv4_address(addr); diff --git a/test/unit/http_server.cpp b/test/unit/http_server.cpp new file mode 100644 index 00000000..63a8231b --- /dev/null +++ b/test/unit/http_server.cpp @@ -0,0 +1,28 @@ +// +// Copyright (c) 2026 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 "test_suite.hpp" + +namespace boost { +namespace beast2 { + +struct http_server_test +{ + void run() + { + } +}; + +TEST_SUITE(http_server_test, "boost.beast2.http_server"); + +} // beast2 +} // boost From 0e9013cdbba5a34b83636b2703e850360ed3919e Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 18 Jan 2026 18:13:20 -0800 Subject: [PATCH 40/40] Remove and refactor obsolete APIs --- example/server/main.cpp | 29 +-- example/server/serve_log_admin.cpp | 10 +- include/boost/beast2/server/http_server.hpp | 73 ------ include/boost/beast2/server/http_stream.hpp | 4 + .../beast2/server/route_handler_corosio.hpp | 10 - .../boost/beast2/server/serve_redirect.hpp | 30 --- include/boost/beast2/server/workers.hpp | 239 ------------------ src/http_server.cpp | 4 +- src/server/http_server.cpp | 84 ------ src/server/serve_redirect.cpp | 126 --------- src/server/serve_static.cpp | 5 +- test/unit/server/http_server.cpp | 28 -- test/unit/server/http_stream.cpp | 28 -- 13 files changed, 20 insertions(+), 650 deletions(-) delete mode 100644 include/boost/beast2/server/http_server.hpp delete mode 100644 include/boost/beast2/server/serve_redirect.hpp delete mode 100644 include/boost/beast2/server/workers.hpp delete mode 100644 src/server/http_server.cpp delete mode 100644 src/server/serve_redirect.cpp delete mode 100644 test/unit/server/http_server.cpp delete mode 100644 test/unit/server/http_stream.cpp diff --git a/example/server/main.cpp b/example/server/main.cpp index b4605c51..6c1d0f7e 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -54,6 +54,7 @@ void install_services(capy::application& app) http::serializer::config()); } +#if 0 class json_sink : public http::sink { public: @@ -125,28 +126,9 @@ struct do_json_rpc } }; +#endif #if 0 -auto -my_coro( - http::route_params& rp) -> - capy::task -{ - (void)rp; - asio::thread_pool tp(1); - co_await capy::make_async_op( - [&tp](auto&& handler) - { - asio::post(tp.get_executor(), - [handler = std::move(handler)]() mutable - { - // Simulate some asynchronous work - std::this_thread::sleep_for(std::chrono::seconds(1)); - handler(); - }); - }); - co_return http::route::next; -} auto do_bcrypt( @@ -232,12 +214,7 @@ int server_main( int argc, char* argv[] ) do_json_rpc() ); - srv.wwwroot.use( - "/spawn", - http::co_route(my_coro)); - srv.wwwroot.use( - "/bcrypt", - http::co_route(do_bcrypt)); + srv.wwwroot.use( "/bcrypt", do_bcrypt ); srv.wwwroot.use("/", serve_static( argv[3] )); diff --git a/example/server/serve_log_admin.cpp b/example/server/serve_log_admin.cpp index 575754b8..743d27da 100644 --- a/example/server/serve_log_admin.cpp +++ b/example/server/serve_log_admin.cpp @@ -30,8 +30,10 @@ class serve_log_page system::error_code operator()( - http::route_params& p) const + http::route_params&) const { + return {}; +#if 0 auto const v = ls_.get_sections(); std::string s; format_to(s, "\n"); @@ -75,6 +77,7 @@ class serve_log_page p.res.set(http::field::content_type, "text/html; charset=UTF-8"); p.set_body(std::move(s)); return http::route::send; +#endif } private: @@ -96,12 +99,15 @@ class handle_submit system::error_code operator()( - http::route_params& p) const + http::route_params&) const { + return {}; +#if 0 p.status(http::status::ok); p.res.set(http::field::content_type, "plain/text; charset=UTF-8"); p.set_body("submit"); return http::route::send; +#endif } private: diff --git a/include/boost/beast2/server/http_server.hpp b/include/boost/beast2/server/http_server.hpp deleted file mode 100644 index 5de920f9..00000000 --- a/include/boost/beast2/server/http_server.hpp +++ /dev/null @@ -1,73 +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_HTTP_SERVER_HPP -#define BOOST_BEAST2_SERVER_HTTP_SERVER_HPP - -#include -#include -#include -#include - -namespace boost { -namespace beast2 { - -/** An HTTP server using Corosio for I/O. -*/ -class http_server -{ -public: - virtual ~http_server() = default; - - http_server() = default; - - /** The router for handling HTTP requests. - */ - router_corosio wwwroot; - - /** Run the server. - - This function attaches the current thread to the I/O context - so that it may be used for executing operations. Blocks the - calling thread until the server is stopped and has no - outstanding work. - */ - virtual void run() = 0; - - /** Stop the server. - - Signals the server to stop accepting new connections and - cancel outstanding operations. - */ - virtual void stop() = 0; -}; - -//------------------------------------------------ - -/** Install a plain (non-TLS) HTTP server into an application. - - @param app The application to install the server into. - @param addr The address to bind to (e.g. "0.0.0.0"). - @param port The port to listen on. - @param num_workers The number of worker sockets to preallocate. - - @return A reference to the installed server. -*/ -BOOST_BEAST2_DECL -http_server& -install_plain_http_server( - capy::application& app, - char const* addr, - unsigned short port, - std::size_t num_workers); - -} // beast2 -} // boost - -#endif diff --git a/include/boost/beast2/server/http_stream.hpp b/include/boost/beast2/server/http_stream.hpp index c9bf8d4e..f48cf853 100644 --- a/include/boost/beast2/server/http_stream.hpp +++ b/include/boost/beast2/server/http_stream.hpp @@ -10,6 +10,8 @@ #ifndef BOOST_BEAST2_SERVER_HTTP_STREAM_HPP #define BOOST_BEAST2_SERVER_HTTP_STREAM_HPP +#if 0 + #include #include #include @@ -416,3 +418,5 @@ clear() noexcept } // boost #endif + +#endif diff --git a/include/boost/beast2/server/route_handler_corosio.hpp b/include/boost/beast2/server/route_handler_corosio.hpp index b8a5d35c..dcda4305 100644 --- a/include/boost/beast2/server/route_handler_corosio.hpp +++ b/include/boost/beast2/server/route_handler_corosio.hpp @@ -33,16 +33,6 @@ class corosio_route_params : stream(s) { } - - void do_finish() - { - if(finish_) - { - auto f = std::move(finish_); - finish_ = {}; - f(); - } - } }; } // beast2 diff --git a/include/boost/beast2/server/serve_redirect.hpp b/include/boost/beast2/server/serve_redirect.hpp deleted file mode 100644 index e2189dd7..00000000 --- a/include/boost/beast2/server/serve_redirect.hpp +++ /dev/null @@ -1,30 +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_REDIRECT_HPP -#define BOOST_BEAST2_SERVER_SERVE_REDIRECT_HPP - -#include -#include - -namespace boost { -namespace beast2 { - -struct serve_redirect -{ - BOOST_BEAST2_DECL - http::route_result - operator()( - http::route_params&) const; -}; - -} // beast2 -} // boost - -#endif diff --git a/include/boost/beast2/server/workers.hpp b/include/boost/beast2/server/workers.hpp deleted file mode 100644 index f7251abf..00000000 --- a/include/boost/beast2/server/workers.hpp +++ /dev/null @@ -1,239 +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_WORKERS_HPP -#define BOOST_BEAST2_SERVER_WORKERS_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace boost { -namespace beast2 { - -/** A preallocated worker that handles one HTTP connection at a time. -*/ -struct http_worker -{ - corosio::socket sock; - bool in_use = false; - - explicit http_worker(corosio::io_context& ioc) - : sock(ioc) - { - } - - http_worker(http_worker&&) = default; - http_worker& operator=(http_worker&&) = default; -}; - -//------------------------------------------------ - -/** A set of accepting sockets and their workers. - - This implements a server that accepts incoming connections - and handles HTTP requests using preallocated workers. -*/ -class workers -{ -public: - ~workers() = default; - - /** Constructor - - @param app The application which holds this server - @param ioc The I/O context for async operations - @param num_workers The number of workers to preallocate - @param routes The router for dispatching requests - */ - workers( - capy::application& app, - corosio::io_context& ioc, - std::size_t num_workers, - router_corosio routes); - - /** Add a listening endpoint - - @param config Acceptor configuration - @param ep The endpoint to listen on - */ - void - listen( - http::acceptor_config config, - corosio::endpoint ep); - - /** Start the accept loop - - Must be called after listen() to begin accepting connections. - */ - void start(); - - /** Stop accepting and cancel all connections - */ - void stop(); - -private: - capy::task - accept_loop(corosio::acceptor& acc, http::acceptor_config config); - - capy::task - run_session(http_worker& w, http::acceptor_config const& config); - - capy::application& app_; - corosio::io_context& ioc_; - section sect_; - http::flat_router routes_; - std::vector workers_; - std::vector acceptors_; - std::vector configs_; - bool stopped_ = false; -}; - -//------------------------------------------------ - -inline -workers:: -workers( - capy::application& app, - corosio::io_context& ioc, - std::size_t num_workers, - router_corosio routes) - : app_(app) - , ioc_(ioc) - , sect_(use_log_service(app).get_section("workers")) - , routes_(std::move(routes)) -{ - workers_.reserve(num_workers); - for (std::size_t i = 0; i < num_workers; ++i) - workers_.emplace_back(ioc_); -} - -inline -void -workers:: -listen( - http::acceptor_config config, - corosio::endpoint ep) -{ - acceptors_.emplace_back(ioc_); - acceptors_.back().listen(ep); - configs_.push_back(config); -} - -inline -void -workers:: -start() -{ - stopped_ = false; - for (std::size_t i = 0; i < acceptors_.size(); ++i) - { - capy::run_async(ioc_.get_executor())( - accept_loop(acceptors_[i], configs_[i])); - } -} - -inline -void -workers:: -stop() -{ - stopped_ = true; - for (auto& acc : acceptors_) - acc.cancel(); - for (auto& w : workers_) - { - if (w.in_use) - w.sock.cancel(); - } -} - -inline -capy::task -workers:: -accept_loop(corosio::acceptor& acc, http::acceptor_config config) -{ - while (!stopped_) - { - // Find a free worker - http_worker* free_worker = nullptr; - for (auto& w : workers_) - { - if (!w.in_use) - { - free_worker = &w; - break; - } - } - - if (!free_worker) - { - // All workers busy - accept and immediately close - // A production server might queue or have backpressure - LOG_DBG(sect_)("All workers busy, rejecting connection"); - corosio::socket temp(ioc_); - auto [ec] = co_await acc.accept(temp); - if (ec) - { - if (stopped_) - break; - LOG_DBG(sect_)("accept error: {}", ec.message()); - } - temp.close(); - continue; - } - - // Accept into the free worker's socket - auto [ec] = co_await acc.accept(free_worker->sock); - if (ec) - { - if (stopped_) - break; - LOG_DBG(sect_)("accept error: {}", ec.message()); - continue; - } - - // Spawn session coroutine - capy::run_async(ioc_.get_executor())( - run_session(*free_worker, config)); - } -} - -inline -capy::task -workers:: -run_session( - http_worker& w, - http::acceptor_config const& config) -{ - w.in_use = true; - - http_stream stream(app_, w.sock, routes_); - co_await stream.run(config); - - w.sock.close(); - w.in_use = false; -} - -} // beast2 -} // boost - -#endif diff --git a/src/http_server.cpp b/src/http_server.cpp index 72777050..9bacd76c 100644 --- a/src/http_server.cpp +++ b/src/http_server.cpp @@ -44,10 +44,8 @@ struct http_server::impl }; struct http_server:: - worker : http_server::worker_base + worker : tcp_server::worker_base { - using launcher = launcher; - http_server* srv; corosio::io_context& ctx; capy::strand strand; diff --git a/src/server/http_server.cpp b/src/server/http_server.cpp deleted file mode 100644 index 5d966a14..00000000 --- a/src/server/http_server.cpp +++ /dev/null @@ -1,84 +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 -#include - -namespace boost { -namespace beast2 { - -namespace { - -class http_server_impl - : public http_server -{ -public: - http_server_impl( - capy::application& app, - char const* addr, - unsigned short port, - std::size_t num_workers) - : ioc_() - , workers_(app, ioc_, num_workers, std::move(this->wwwroot)) - { - // Parse address and create endpoint - auto addr_result = urls::parse_ipv4_address(addr); - if (addr_result.has_error()) - { - // Fallback to any address if parsing fails - workers_.listen( - http::acceptor_config{false, false}, - corosio::endpoint(port)); - } - else - { - workers_.listen( - http::acceptor_config{false, false}, - corosio::endpoint(addr_result.value(), port)); - } - } - - void run() override - { - workers_.start(); - ioc_.run(); - } - - void stop() override - { - workers_.stop(); - ioc_.stop(); - } - -private: - corosio::io_context ioc_; - workers workers_; -}; - -} // (anon) - -//------------------------------------------------ - -http_server& -install_plain_http_server( - capy::application& app, - char const* addr, - unsigned short port, - std::size_t num_workers) -{ - return app.emplace( - app, addr, port, num_workers); -} - -} // beast2 -} // boost diff --git a/src/server/serve_redirect.cpp b/src/server/serve_redirect.cpp deleted file mode 100644 index 8ae11a62..00000000 --- a/src/server/serve_redirect.cpp +++ /dev/null @@ -1,126 +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 -#include -#include -#include -#include -#include -#include - -#include - -namespace boost { -namespace beast2 { - -//------------------------------------------------ - -/// Returns the current system time formatted as an HTTP-date per RFC 9110 �5.6.7. -/// Example: "Sat, 11 Oct 2025 02:12:34 GMT" -static -std::string -make_http_date() -{ - using namespace std; - - // Get current time in UTC - std::time_t t = std::time(nullptr); - std::tm tm_utc{}; -#if defined(_WIN32) - gmtime_s(&tm_utc, &t); -#else - gmtime_r(&t, &tm_utc); -#endif - - char const* wkday[] = { - "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" - }; - char const* month[] = { - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" - }; - - // Format strictly according to RFC 9110 (fixed-width, English locale) - char buf[40]; - std::snprintf( - buf, sizeof(buf), - "%s, %02d %s %04d %02d:%02d:%02d GMT", - wkday[tm_utc.tm_wday], - tm_utc.tm_mday, - month[tm_utc.tm_mon], - tm_utc.tm_year + 1900, - tm_utc.tm_hour, - tm_utc.tm_min, - tm_utc.tm_sec); - - return std::string(buf); -} - -static -void -prepare_error( - http::response& res, - std::string& body, - http::status code, - http::request_base const& req) -{ - res.set_start_line(code, req.version()); - res.append(http::field::server, "boost"); - res.append(http::field::date, make_http_date()); - res.append(http::field::cache_control, "no-store"); - res.append(http::field::content_type, "text/html"); - res.append(http::field::content_language, "en"); - - // format the numeric code followed by the reason string - auto title = std::to_string( - static_cast::type>(code)); - title.push_back(' '); - title.append( res.reason() ); - - std::ostringstream ss; - ss << - "" - "" - "" << title << "" - "\n" - "" - "

" << title << "

" - "" - "" - ; - body = ss.str(); -} - -auto -serve_redirect:: -operator()( - http::route_params& p) const -> - http::route_result -{ - std::string body; - prepare_error(p.res, body, - http::status::moved_permanently, p.req); - urls::url u1(p.req.target()); - u1.set_scheme_id(urls::scheme::https); - u1.set_host_address("localhost"); // VFALCO WTF IS THIS! - p.res.append(http::field::location, u1.buffer()); - p.serializer.start(p.res, - http::string_body( std::move(body))); - return http::route::send; -} - -} // beast2 -} // boost - diff --git a/src/server/serve_static.cpp b/src/server/serve_static.cpp index e5767772..96616a2c 100644 --- a/src/server/serve_static.cpp +++ b/src/server/serve_static.cpp @@ -170,9 +170,11 @@ serve_static( auto serve_static:: operator()( - http::route_params& p) const -> + 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) @@ -226,6 +228,7 @@ operator()( BOOST_ASSERT(ec.failed()); return ec; +#endif } } // beast2 diff --git a/test/unit/server/http_server.cpp b/test/unit/server/http_server.cpp deleted file mode 100644 index 2c299aeb..00000000 --- a/test/unit/server/http_server.cpp +++ /dev/null @@ -1,28 +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 "test_suite.hpp" - -namespace boost { -namespace beast2 { - -struct http_server_test -{ - void run() - { - } -}; - -TEST_SUITE(http_server_test, "boost.beast2.server.http_server"); - -} // beast2 -} // boost diff --git a/test/unit/server/http_stream.cpp b/test/unit/server/http_stream.cpp deleted file mode 100644 index eb558dc5..00000000 --- a/test/unit/server/http_stream.cpp +++ /dev/null @@ -1,28 +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 "test_suite.hpp" - -namespace boost { -namespace beast2 { - -struct http_stream_test -{ - void run() - { - } -}; - -TEST_SUITE(http_stream_test, "boost.beast2.server.http_stream_test"); - -} // beast2 -} // boost