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..f31650dd 100644 --- a/example/client/burl/CMakeLists.txt +++ b/example/client/burl/CMakeLists.txt @@ -7,48 +7,51 @@ # Official repository: https://github.com/cppalliance/beast2 # -if (CMAKE_CXX_STANDARD EQUAL 20) - file(GLOB_RECURSE PFILES CONFIGURE_DEPENDS *.cpp *.hpp - CMakeLists.txt - Jamfile) +if(NOT cxx_std_20 IN_LIST CMAKE_CXX_COMPILE_FEATURES) + return() +endif() - source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "" FILES ${PFILES}) +file(GLOB_RECURSE PFILES CONFIGURE_DEPENDS *.cpp *.hpp + CMakeLists.txt + Jamfile) - add_executable(beast2_example_client_burl ${PFILES}) +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "" FILES ${PFILES}) - target_compile_definitions(beast2_example_client_burl - PRIVATE BOOST_ASIO_NO_DEPRECATED) +add_executable(beast2_example_client_burl ${PFILES}) - set_property(TARGET beast2_example_client_burl - PROPERTY FOLDER "examples") +target_compile_definitions(beast2_example_client_burl + PRIVATE BOOST_ASIO_NO_DEPRECATED) - find_package(OpenSSL REQUIRED) +set_property(TARGET beast2_example_client_burl + PROPERTY FOLDER "examples") - target_link_libraries(beast2_example_client_burl - Boost::beast2 - Boost::url - Boost::program_options - Boost::scope - OpenSSL::SSL - OpenSSL::Crypto) +find_package(OpenSSL REQUIRED) - if (WIN32) - target_link_libraries(beast2_example_client_burl crypt32) - endif() +target_compile_features(beast2_example_client_burl PUBLIC cxx_std_20) - if (TARGET Boost::capy_zlib) - target_link_libraries(beast2_example_client_burl Boost::capy_zlib) - 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_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/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/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 d3c4b40f..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) @@ -52,17 +56,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..115b26af --- /dev/null +++ b/example/cpp20/co_spawn/CMakeLists.txt @@ -0,0 +1,33 @@ +# +# 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 new file mode 100644 index 00000000..77befb7b --- /dev/null +++ b/example/cpp20/co_spawn/Jamfile @@ -0,0 +1,41 @@ +# +# 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 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/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/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 9713599d..794c71f8 100644 --- a/example/server/main.cpp +++ b/example/server/main.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include namespace boost { @@ -42,12 +43,109 @@ 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; + } +}; + +#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[] ) { try @@ -69,16 +167,25 @@ 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()); - srv.wwwroot.use( - http_proto::cors(), - []( http_proto::route_params&) -> - http_proto::route_result + 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), + 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] )); 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.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/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..a6ec8a1f 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,33 @@ 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; }; 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..4912f04e --- /dev/null +++ b/include/boost/beast2/spawn.hpp @@ -0,0 +1,85 @@ +// +// 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_CAPY_HAS_CORO + +#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 + +#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..0430f323 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; + capy::file f; std::uint64_t size = 0; - f.open(path.c_str(), http_proto::file_mode::scan, ec); + f.open(path.c_str(), capy::file_mode::scan, ec); if(! ec.failed()) size = f.size(ec); if(! ec.failed()) { p.res.set_start_line( - http_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