From 45197b5011061303280f0b7950c27b4344bb56aa Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 25 May 2025 11:02:09 -0700 Subject: [PATCH] refactor, add client and visit --- CMakeLists.txt | 9 +- build/Jamfile | 12 +++ cmake/toolchains/msvc.cmake | 1 + example/client/CMakeLists.txt | 1 + example/client/Jamfile | 1 + example/client/visit/CMakeLists.txt | 42 ++++++++ example/client/visit/Jamfile | 32 +++++++ example/client/visit/main.cpp | 144 ++++++++++++++++++++++++++++ include/boost/http_io/client.hpp | 73 ++++++++++++++ test/unit/client.cpp | 99 +++++++++++++++++++ test/unit/read.cpp | 9 ++ 11 files changed, 419 insertions(+), 4 deletions(-) create mode 100644 example/client/visit/CMakeLists.txt create mode 100644 example/client/visit/Jamfile create mode 100644 example/client/visit/main.cpp create mode 100644 include/boost/http_io/client.hpp create mode 100644 test/unit/client.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 928a2b3..6daf283 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -131,13 +131,14 @@ endif () #------------------------------------------------- set_property(GLOBAL PROPERTY USE_FOLDERS ON) -file(GLOB_RECURSE BOOST_HTTP_IO_HEADERS CONFIGURE_DEPENDS include/boost/*.hpp include/boost/*.natvis) +file(GLOB_RECURSE BOOST_HTTP_IO_HEADERS CONFIGURE_DEPENDS include/boost/http_io/*.hpp include/boost/*.natvis) file(GLOB_RECURSE BOOST_HTTP_IO_SOURCES CONFIGURE_DEPENDS src/*.cpp src/*.hpp) -source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/include/boost PREFIX "" FILES ${BOOST_HTTP_IO_HEADERS}) -source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/src PREFIX "http_io" FILES ${BOOST_HTTP_IO_SOURCES}) +source_group("" FILES "include/boost/http_io.hpp" "build/Jamfile") +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/include/boost/http_io PREFIX "include" FILES ${BOOST_HTTP_IO_HEADERS}) +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/src PREFIX "src" FILES ${BOOST_HTTP_IO_SOURCES}) -add_library(boost_http_io ${BOOST_HTTP_IO_HEADERS} ${BOOST_HTTP_IO_SOURCES}) +add_library(boost_http_io include/boost/http_io.hpp build/Jamfile ${BOOST_HTTP_IO_HEADERS} ${BOOST_HTTP_IO_SOURCES}) add_library(Boost::http_io ALIAS boost_http_io) target_compile_features(boost_http_io PUBLIC cxx_constexpr) target_include_directories(boost_http_io PUBLIC "${PROJECT_SOURCE_DIR}/include") diff --git a/build/Jamfile b/build/Jamfile index 8cecbc8..7faf575 100644 --- a/build/Jamfile +++ b/build/Jamfile @@ -19,16 +19,28 @@ constant c11-requires : ] ; +explicit + [ searched-lib ws2_32 : : windows ] # NT + [ searched-lib mswsock : : windows ] # NT + ; + project boost/http_io : requirements $(c11-requires) shared:BOOST_HTTP_IO_DYN_LINK=1 static:BOOST_HTTP_IO_STATIC_LINK=1 windows:_WIN32_WINNT=0x0601 # VFALCO? + windows,gcc:ws2_32 + windows,gcc:mswsock + windows,gcc-cygwin:__USE_W32_SOCKETS BOOST_HTTP_IO_SOURCE : usage-requirements shared:BOOST_HTTP_IO_DYN_LINK=1 static:BOOST_HTTP_IO_STATIC_LINK=1 + windows:_WIN32_WINNT=0x0601 # VFALCO? + windows,gcc:ws2_32 + windows,gcc:mswsock + windows,gcc-cygwin:__USE_W32_SOCKETS : source-location ../src ; diff --git a/cmake/toolchains/msvc.cmake b/cmake/toolchains/msvc.cmake index b619ede..e2fdad1 100644 --- a/cmake/toolchains/msvc.cmake +++ b/cmake/toolchains/msvc.cmake @@ -9,6 +9,7 @@ add_compile_options( /permissive- # strict C++ /W4 # enable all warnings /MP # multi-processor compilation + /bigobj ) if("${CMAKE_GENERATOR_PLATFORM}" STREQUAL "Win32") # 32-bit add_compile_options( diff --git a/example/client/CMakeLists.txt b/example/client/CMakeLists.txt index eda36c5..e9ba9d7 100644 --- a/example/client/CMakeLists.txt +++ b/example/client/CMakeLists.txt @@ -8,3 +8,4 @@ # add_subdirectory(burl) +add_subdirectory(visit) diff --git a/example/client/Jamfile b/example/client/Jamfile index 7aba2bd..34eb882 100644 --- a/example/client/Jamfile +++ b/example/client/Jamfile @@ -8,3 +8,4 @@ # build-project burl ; +build-project visit ; diff --git a/example/client/visit/CMakeLists.txt b/example/client/visit/CMakeLists.txt new file mode 100644 index 0000000..e1b905d --- /dev/null +++ b/example/client/visit/CMakeLists.txt @@ -0,0 +1,42 @@ +# +# Copyright (c) 2024 Mohammad Nejati +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# +# Official repository: https://github.com/cppalliance/http_io +# + +file(GLOB_RECURSE PFILES CONFIGURE_DEPENDS *.cpp *.hpp + CMakeLists.txt + Jamfile) + +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "" FILES ${PFILES}) + +add_executable(http_io_example_client_visit ${PFILES}) + +target_compile_definitions(http_io_example_client_visit + PRIVATE BOOST_ASIO_NO_DEPRECATED) + +set_property(TARGET http_io_example_client_visit + PROPERTY FOLDER "examples") + +find_package(OpenSSL REQUIRED) +find_package(ZLIB) + +target_link_libraries(http_io_example_client_visit + boost_http_io + boost_program_options + boost_scope + OpenSSL::SSL + OpenSSL::Crypto) + +if (WIN32) + target_link_libraries(http_io_example_client_visit + crypt32) +endif() + +if (ZLIB_FOUND) + target_link_libraries(http_io_example_client_visit + boost_http_proto_zlib) +endif() diff --git a/example/client/visit/Jamfile b/example/client/visit/Jamfile new file mode 100644 index 0000000..ca74f34 --- /dev/null +++ b/example/client/visit/Jamfile @@ -0,0 +1,32 @@ +# +# Copyright (c) 2024 Mohammad Nejati +# 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/http_io +# + +import ../../../../config/checks/config : requires ; + +using openssl ; +import ac ; + +project + : requirements + $(c11-requires) + /boost/http_proto//boost_http_proto + [ ac.check-library /boost/http_proto//boost_http_proto_zlib : /boost/http_proto//boost_http_proto_zlib : ] + /boost/http_io//boost_http_io + /boost/program_options//boost_program_options + /boost/scope//scope + /openssl//ssl/shared + /openssl//crypto/shared + windows:crypt32 + . + ; + +exe visit : + [ glob *.cpp ] + ; diff --git a/example/client/visit/main.cpp b/example/client/visit/main.cpp new file mode 100644 index 0000000..dac8773 --- /dev/null +++ b/example/client/visit/main.cpp @@ -0,0 +1,144 @@ +// +// 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/http_io +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct worker +{ + using executor_type = + boost::asio::io_context::executor_type; + using resolver_type = + boost::asio::ip::basic_resolver< + boost::asio::ip::tcp, executor_type>; + using socket_type = boost::asio::basic_stream_socket< + boost::asio::ip::tcp, executor_type>; + + socket_type sock; + resolver_type resolver; + boost::http_proto::response_parser pr; + boost::urls::url_view url; + + explicit + worker( + executor_type ex, + boost::http_proto::context& ctx) + : sock(ex) + , resolver(ex) + , pr(ctx) + { + sock.open(boost::asio::ip::tcp::v4()); + } + + void + do_next() + { + do_visit("http://www.boost.org"); + } + + void + do_visit(boost::urls::url_view url_) + { + url = url_; + do_resolve(); + } + + void + do_resolve() + { + resolver.async_resolve( + url.encoded_host(), + url.scheme(), + [&]( + boost::system::error_code ec, + resolver_type::results_type results) + { + if(ec.failed()) + { + // log (target, ec.message()) + auto s = ec.message(); + return do_next(); + } + do_connect(results); + }); + } + + void + do_connect( + resolver_type::results_type results) + { + boost::asio::async_connect( + sock, + results.begin(), + results.end(), + [&]( + boost::system::error_code ec, + resolver_type::results_type::const_iterator it) + { + if(ec.failed()) + { + // log (target, ec.message()) + return do_next(); + } + do_request(); + }); + } + + void + do_request() + { + boost::http_proto::request req; + auto path = url.encoded_path(); + req.set_start_line( + boost::http_proto::method::get, + path.empty() ? "/" : path, + boost::http_proto::version::http_1_1); + + do_shutdown(); + } + + void + do_shutdown() + { + boost::system::error_code ec; + sock.shutdown(socket_type::shutdown_both, ec); + if(ec.failed()) + { + // log(ec.message()) + return do_next(); + } + do_next(); + } +}; + +int +main(int argc, char* argv[]) +{ + boost::http_proto::context ctx; + boost::http_proto::parser::config_base cfg; + boost::http_proto::install_parser_service(ctx, cfg); + + boost::asio::io_context ioc; + + worker w(ioc.get_executor(), ctx); + + w.do_next(); + + ioc.run(); + + return EXIT_SUCCESS; +} diff --git a/include/boost/http_io/client.hpp b/include/boost/http_io/client.hpp new file mode 100644 index 0000000..27bdef1 --- /dev/null +++ b/include/boost/http_io/client.hpp @@ -0,0 +1,73 @@ +// +// 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/http_io +// + +#ifndef BOOST_HTTP_IO_CLIENT_HPP +#define BOOST_HTTP_IO_CLIENT_HPP + +#include +#include +#include + +namespace boost { +namespace http_io { + +template< + class AsyncStream + /*,class Derived*/ // VFALCO CRTP for things like shutdown() +> +class client +{ +public: + using stream_type = typename + std::remove_reference::type; + + using executor_type = decltype( + std::declval().get_executor()); + + template< + class... Args, + class = std::enable_if< + std::is_constructible< + AsyncStream, Args...>::value> + > + explicit + client( + Args&&... args) noexcept( + std::is_nothrow_constructible< + AsyncStream, Args...>::value) + : stream_(std::forward(args)...) + { + } + + stream_type const& + stream() const noexcept + { + return stream_; + } + + stream_type& + stream() noexcept + { + return stream_; + } + + executor_type + get_executor() const + { + return stream_.get_executor(); + } + +private: + AsyncStream stream_; +}; + +} // http_io +} // boost + +#endif diff --git a/test/unit/client.cpp b/test/unit/client.cpp new file mode 100644 index 0000000..753201a --- /dev/null +++ b/test/unit/client.cpp @@ -0,0 +1,99 @@ +// +// 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/http_io +// + +// Test that header file is self-contained. +#include + +#include + +#include "test_suite.hpp" + +namespace boost { +namespace http_io { + +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.http_io.client"); + +} // http_io +} // boost diff --git a/test/unit/read.cpp b/test/unit/read.cpp index c37b4af..0efd694 100644 --- a/test/unit/read.cpp +++ b/test/unit/read.cpp @@ -10,6 +10,9 @@ // Test that header file is self-contained. #include +#include +#include + #include "test_suite.hpp" namespace boost { @@ -88,6 +91,12 @@ class read_test void testRead() { + boost::asio::io_context ioc; + boost::asio::post( + ioc.get_executor(), + [] + { + }); } void