diff --git a/smacc2/include/smacc2/client_bases/smacc_action_client_base.hpp b/smacc2/include/smacc2/client_bases/smacc_action_client_base.hpp index ec1011a23..746162e35 100644 --- a/smacc2/include/smacc2/client_bases/smacc_action_client_base.hpp +++ b/smacc2/include/smacc2/client_bases/smacc_action_client_base.hpp @@ -332,8 +332,8 @@ class SmaccActionClientBase : public ISmaccActionClient << getName() << "] Action request " // << rclcpp_action::to_string(this->goalHandle_->get_goal_id()) <<". Goal sent to " << this->action_endpoint_ - << "\": " << std::endl - << goal); + << "\": " << std::endl); + // << goal); // if (client_->isServerConnected()) // { diff --git a/smacc2/include/smacc2/client_bases/smacc_http_client.hpp b/smacc2/include/smacc2/client_bases/smacc_http_client.hpp deleted file mode 100644 index 0d42bae37..000000000 --- a/smacc2/include/smacc2/client_bases/smacc_http_client.hpp +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright 2021 RobosoftAI Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/***************************************************************************************************************** - * - * Authors: Jaycee Lock - * - ******************************************************************************************************************/ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace smacc2 { -namespace client_bases { -class SmaccHttpClient : public smacc2::ISmaccClient { - class http_session : public std::enable_shared_from_this { - boost::asio::ip::tcp::resolver resolver_; - boost::beast::tcp_stream stream_; - boost::beast::flat_buffer buffer_; // (Must persist between reads) - boost::beast::http::request req_; - boost::beast::http::response res_; - - public: - using TResponse = - const boost::beast::http::response &; - - // Objects are constructed with a strand to - // ensure that handlers do not execute concurrently. - http_session(boost::asio::io_context &ioc, - const std::function response) - : resolver_(boost::asio::make_strand(ioc)), - stream_(boost::asio::make_strand(ioc)), - onResponse{response} {} - - // Start the asynchronous operation - void run(const std::string &host, const std::string &target, - const std::string &port, - const boost::beast::http::verb http_method, const int &version) { - // Set up an HTTP request - req_.version(version); - req_.method(http_method); - req_.target(target); - req_.set(boost::beast::http::field::host, host); - req_.set(boost::beast::http::field::user_agent, - BOOST_BEAST_VERSION_STRING); - - // Look up the domain name - resolver_.async_resolve( - host.c_str(), port.c_str(), - boost::beast::bind_front_handler(&http_session::on_resolve, - shared_from_this())); - } - - void on_resolve(boost::beast::error_code ec, - boost::asio::ip::tcp::resolver::results_type results) { - if (ec) return fail(ec, "resolve"); - - // Set a timeout on the operation - stream_.expires_after(std::chrono::seconds(30)); - - // Make the connection on the IP address we get from a lookup - stream_.async_connect( - results, boost::beast::bind_front_handler(&http_session::on_connect, - shared_from_this())); - } - - private: - void fail(boost::beast::error_code ec, char const *what) { - std::cerr << what << ": " << ec.message() << "\n"; - res_.result(boost::beast::http::status::bad_request); - res_.reason() = ec.message(); - onResponse(res_); - } - - void on_connect( - boost::beast::error_code ec, - boost::asio::ip::tcp::resolver::results_type::endpoint_type) { - if (ec) return fail(ec, "connect"); - - // Set a timeout on the operation - stream_.expires_after(std::chrono::seconds(30)); - - // Send the HTTP request to the remote host - boost::beast::http::async_write( - stream_, req_, - boost::beast::bind_front_handler(&http_session::on_write, - shared_from_this())); - } - - void on_write(boost::beast::error_code ec, std::size_t bytes_transferred) { - boost::ignore_unused(bytes_transferred); - - if (ec) return fail(ec, "write"); - - // Receive the HTTP response - boost::beast::http::async_read( - stream_, buffer_, res_, - boost::beast::bind_front_handler(&http_session::on_read, - shared_from_this())); - } - - void on_read(boost::beast::error_code ec, std::size_t bytes_transferred) { - boost::ignore_unused(bytes_transferred); - - if (ec) return fail(ec, "read"); - - // Gracefully close the socket - stream_.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, - ec); - - // not_connected happens sometimes so don't bother reporting it. - if (ec && ec != boost::beast::errc::not_connected) - return fail(ec, "shutdown"); - - // If we get here then the connection is closed gracefully - onResponse(res_); - } - - std::function &response)> - onResponse; - }; - - public: - enum class kHttpRequestMethod { - GET = static_cast(boost::beast::http::verb::get), - POST = static_cast(boost::beast::http::verb::post), - }; - - explicit SmaccHttpClient(const std::string &server, const int &timeout = 1500) - : worker_guard_{boost::asio::make_work_guard(io_context_)}, - initialized_{false}, - server_name_{server}, - timeout_{timeout} { - if (!server.substr(0, 5).compare("https")) { - // User explicitly using http - is_ssl_ = false; - } else { - is_ssl_ = true; - } - } - - virtual ~SmaccHttpClient() { - worker_guard_.reset(); - tcp_connection_runner_.join(); - } - - void configure() { - if (!initialized_) { - tcp_connection_runner_ = std::thread{[&]() { io_context_.run(); }}; - this->initialized_ = true; - } - } - - template - boost::signals2::connection onResponseReceived( - void (T::*callback)(const std::string &), T *object) { - return this->getStateMachine()->createSignalConnection(onResponseReceived_, - callback, object); - } - - void makeRequest(const kHttpRequestMethod http_method, - const std::string &path = "/") { - std::make_shared(io_context_, callbackHandler) - ->run(server_name_, path, is_ssl_ ? "443" : "80", - static_cast(http_method), HTTP_VERSION); - } - - private: - const int HTTP_VERSION = 11; - - bool initialized_; - bool is_ssl_; - std::string server_name_; - int timeout_; - - std::thread tcp_connection_runner_; - boost::asio::io_context io_context_; - boost::asio::executor_work_guard - worker_guard_; - - std::function callbackHandler; - - smacc2::SmaccSignal onResponseReceived_; -}; -} // namespace client_bases -} // namespace smacc2 diff --git a/smacc2_client_library/http_client/CMakeLists.txt b/smacc2_client_library/http_client/CMakeLists.txt new file mode 100644 index 000000000..31b7fb596 --- /dev/null +++ b/smacc2_client_library/http_client/CMakeLists.txt @@ -0,0 +1,41 @@ +cmake_minimum_required(VERSION 3.5) +project(http_client) + +# Default to C++17 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# find dependencies +find_package(ament_cmake REQUIRED) +find_package(smacc2 REQUIRED) + +include_directories( + include + ${smacc2_INCLUDE_DIRS} +) + +file(GLOB_RECURSE SRC_FILES src *.cpp) + +add_library(${PROJECT_NAME} + ${SRC_FILES} +) + +target_link_libraries(${PROJECT_NAME} ${smacc2_LIBRARIES}) +ament_target_dependencies(${PROJECT_NAME} smacc2) +ament_export_include_directories(include) +ament_export_libraries(${PROJECT_NAME}) + +install( + DIRECTORY include/ + DESTINATION include) + +install(TARGETS + ${PROJECT_NAME} + DESTINATION lib/) + +ament_package() diff --git a/smacc2_client_library/http_client/include/http_client/http_client.hpp b/smacc2_client_library/http_client/include/http_client/http_client.hpp new file mode 100644 index 000000000..7c74fd089 --- /dev/null +++ b/smacc2_client_library/http_client/include/http_client/http_client.hpp @@ -0,0 +1,116 @@ +// Copyright 2023 RobosoftAI Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/***************************************************************************************************************** + * + * Authors: Jaycee Lock + * + ******************************************************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace cl_http { +class ClHttp : public smacc2::ISmaccClient { + class http_session : public std::enable_shared_from_this { + public: + using TResponse = + const boost::beast::http::response &; + + // Objects are constructed with a strand to + // ensure that handlers do not execute concurrently. + http_session(boost::asio::io_context &ioc, + const std::function response); + + // Start the asynchronous operation + void run(const std::string &host, const std::string &target, + const std::string &port, + const boost::beast::http::verb http_method, const int &version); + + void on_resolve(boost::beast::error_code ec, + boost::asio::ip::tcp::resolver::results_type results); + + private: + void fail(boost::beast::error_code ec, char const *what); + void on_connect( + boost::beast::error_code ec, + boost::asio::ip::tcp::resolver::results_type::endpoint_type); + void on_write(boost::beast::error_code ec, std::size_t bytes_transferred); + void on_read(boost::beast::error_code ec, std::size_t bytes_transferred); + + std::function &response)> + onResponse; + + boost::asio::ip::tcp::resolver resolver_; + boost::beast::tcp_stream stream_; + boost::beast::flat_buffer buffer_; // (Must persist between reads) + boost::beast::http::request req_; + boost::beast::http::response res_; + }; + + public: + enum class kHttpRequestMethod { + GET = static_cast(boost::beast::http::verb::get), + POST = static_cast(boost::beast::http::verb::post), + }; + + template + boost::signals2::connection onResponseReceived( + void (T::*callback)(const std::string &), T *object) { + return this->getStateMachine()->createSignalConnection(onResponseReceived_, + callback, object); + } + + explicit ClHttp(const std::string &server, const int &timeout = 1500); + + virtual ~ClHttp(); + + void configure(); + void makeRequest(const kHttpRequestMethod http_method, + const std::string &path = "/"); + + private: + const int HTTP_VERSION = 11; + + bool initialized_; + bool is_ssl_; + int timeout_; + std::string server_name_; + + boost::asio::io_context io_context_; + boost::asio::executor_work_guard + worker_guard_; + std::thread tcp_connection_runner_; + + std::function callbackHandler; + + smacc2::SmaccSignal onResponseReceived_; +}; +} // namespace cl_http diff --git a/smacc2_client_library/http_client/package.xml b/smacc2_client_library/http_client/package.xml new file mode 100644 index 000000000..b969cb72f --- /dev/null +++ b/smacc2_client_library/http_client/package.xml @@ -0,0 +1,17 @@ + + + + http_client + 2.3.18 + The http_client package + Jaycee Lock + Apache-2.0 + + ament_cmake + + smacc2 + + + ament_cmake + + diff --git a/smacc2_client_library/http_client/src/http_client/http_client.cpp b/smacc2_client_library/http_client/src/http_client/http_client.cpp new file mode 100644 index 000000000..e15b4cccf --- /dev/null +++ b/smacc2_client_library/http_client/src/http_client/http_client.cpp @@ -0,0 +1,129 @@ +#include + +namespace cl_http { + +/////////////////////////// +// ClHttp implementation // +// //////////////////////// + +ClHttp::ClHttp(const std::string &server, const int &timeout) + : initialized_{false}, + timeout_{timeout}, + server_name_{server}, + worker_guard_{boost::asio::make_work_guard(io_context_)} { + // User explicitly using http + is_ssl_ = server.substr(0, 5).compare("https") ? true : false; +} + +ClHttp::~ClHttp() { + worker_guard_.reset(); + tcp_connection_runner_.join(); +} + +void ClHttp::configure() { + if (!initialized_) { + tcp_connection_runner_ = std::thread{[&]() { io_context_.run(); }}; + this->initialized_ = true; + } +} + +void ClHttp::makeRequest(const kHttpRequestMethod http_method, + const std::string &path) { + std::make_shared(io_context_, callbackHandler) + ->run(server_name_, path, is_ssl_ ? "443" : "80", + static_cast(http_method), HTTP_VERSION); +} + +///////////////////////////////// +// http_session implementation // +///////////////////////////////// + +ClHttp::http_session::http_session( + boost::asio::io_context &ioc, const std::function response) + : onResponse{response}, + resolver_(boost::asio::make_strand(ioc)), + stream_(boost::asio::make_strand(ioc)) {} + +void ClHttp::http_session::run(const std::string &host, + const std::string &target, + const std::string &port, + const boost::beast::http::verb http_method, + const int &version) { + // Set up an HTTP request + req_.version(version); + req_.method(http_method); + req_.target(target); + req_.set(boost::beast::http::field::host, host); + req_.set(boost::beast::http::field::user_agent, BOOST_BEAST_VERSION_STRING); + + // Look up the domain name + resolver_.async_resolve(host.c_str(), port.c_str(), + boost::beast::bind_front_handler( + &http_session::on_resolve, shared_from_this())); +} + +void ClHttp::http_session::on_resolve( + boost::beast::error_code ec, + boost::asio::ip::tcp::resolver::results_type results) { + if (ec) return fail(ec, "resolve"); + + // Set a timeout on the operation + stream_.expires_after(std::chrono::seconds(30)); + + // Make the connection on the IP address we get from a lookup + stream_.async_connect( + results, boost::beast::bind_front_handler(&http_session::on_connect, + shared_from_this())); +} +void ClHttp::http_session::fail(boost::beast::error_code ec, char const *what) { + std::cerr << what << ": " << ec.message() << "\n"; + res_.result(boost::beast::http::status::bad_request); + res_.reason() = ec.message(); + onResponse(res_); +} + +void ClHttp::http_session::on_connect( + boost::beast::error_code ec, + boost::asio::ip::tcp::resolver::results_type::endpoint_type) { + if (ec) return fail(ec, "connect"); + + // Set a timeout on the operation + stream_.expires_after(std::chrono::seconds(30)); + + // Send the HTTP request to the remote host + boost::beast::http::async_write( + stream_, req_, + boost::beast::bind_front_handler(&http_session::on_write, + shared_from_this())); +} + +void ClHttp::http_session::on_write(boost::beast::error_code ec, + std::size_t bytes_transferred) { + boost::ignore_unused(bytes_transferred); + + if (ec) return fail(ec, "write"); + + // Receive the HTTP response + boost::beast::http::async_read( + stream_, buffer_, res_, + boost::beast::bind_front_handler(&http_session::on_read, + shared_from_this())); +} + +void ClHttp::http_session::on_read(boost::beast::error_code ec, + std::size_t bytes_transferred) { + boost::ignore_unused(bytes_transferred); + + if (ec) return fail(ec, "read"); + + // Gracefully close the socket + stream_.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); + + // not_connected happens sometimes so don't bother reporting it. + if (ec && ec != boost::beast::errc::not_connected) + return fail(ec, "shutdown"); + + // If we get here then the connection is closed gracefully + onResponse(res_); +} +} // namespace cl_http diff --git a/smacc2_sm_reference_library/sm_atomic_http/CMakeLists.txt b/smacc2_sm_reference_library/sm_atomic_http/CMakeLists.txt index 5b7210e72..69e912187 100644 --- a/smacc2_sm_reference_library/sm_atomic_http/CMakeLists.txt +++ b/smacc2_sm_reference_library/sm_atomic_http/CMakeLists.txt @@ -14,11 +14,14 @@ endif() find_package(ament_cmake REQUIRED) find_package(smacc2 REQUIRED) find_package(ros_timer_client REQUIRED) +find_package(http_client REQUIRED) find_package(Boost COMPONENTS thread REQUIRED) include_directories(include ${smacc2_INCLUDE_DIRS} - ${ros_timer_client_INCLUDE_DIRS}) + ${ros_timer_client_INCLUDE_DIRS} + ${http_client_INCLUDE_DIRS} +) add_executable(${PROJECT_NAME}_node src/sm_atomic_http/sm_atomic_http_node.cpp) @@ -26,6 +29,7 @@ add_executable(${PROJECT_NAME}_node target_link_libraries(${PROJECT_NAME}_node ${smacc2_LIBRARIES} ${ros_timer_client_LIBRARIES} + ${http_client_LIBRARIES} ${Boost_LIBRARIES} ) diff --git a/smacc2_sm_reference_library/sm_atomic_http/include/sm_atomic_http/clients/client_behaviors/cb_http_request.hpp b/smacc2_sm_reference_library/sm_atomic_http/include/sm_atomic_http/clients/client_behaviors/cb_http_request.hpp index efc2e33eb..4c506fad9 100644 --- a/smacc2_sm_reference_library/sm_atomic_http/include/sm_atomic_http/clients/client_behaviors/cb_http_request.hpp +++ b/smacc2_sm_reference_library/sm_atomic_http/include/sm_atomic_http/clients/client_behaviors/cb_http_request.hpp @@ -1,8 +1,10 @@ #pragma once -#include +#include #include +#include + namespace sm_atomic_http { template @@ -29,13 +31,13 @@ class CbHttpRequest : public smacc2::SmaccClientBehavior { RCLCPP_INFO(getLogger(), "On Entry!"); cl_http_->makeRequest( - smacc2::client_bases::SmaccHttpClient::kHttpRequestMethod::GET); + cl_http::ClHttp::kHttpRequestMethod::GET); } void onExit() override { RCLCPP_INFO(getLogger(), "Cb on exit!"); } private: - smacc2::client_bases::SmaccHttpClient* cl_http_; + cl_http::ClHttp* cl_http_; std::function triggerTranstition; }; diff --git a/smacc2_sm_reference_library/sm_atomic_http/include/sm_atomic_http/orthogonals/or_http.hpp b/smacc2_sm_reference_library/sm_atomic_http/include/sm_atomic_http/orthogonals/or_http.hpp index ab3a4b03a..2f43da5fe 100644 --- a/smacc2_sm_reference_library/sm_atomic_http/include/sm_atomic_http/orthogonals/or_http.hpp +++ b/smacc2_sm_reference_library/sm_atomic_http/include/sm_atomic_http/orthogonals/or_http.hpp @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#pragma once + +#include #include namespace sm_atomic_http { @@ -20,7 +22,7 @@ class OrHttp : public smacc2::Orthogonal { public: void onInitialize() override { auto http_client = - this->createClient( + this->createClient( "https://www.google.com"); } }; diff --git a/smacc2_sm_reference_library/sm_atomic_http/package.xml b/smacc2_sm_reference_library/sm_atomic_http/package.xml index 5c5a73a24..d4824fab8 100644 --- a/smacc2_sm_reference_library/sm_atomic_http/package.xml +++ b/smacc2_sm_reference_library/sm_atomic_http/package.xml @@ -10,6 +10,7 @@ ament_cmake ros_timer_client + http_client smacc2 xterm