| #include <nlohmann/json.hpp> |
| #include "bmc/http_connection.h" |
| |
| #include "bmc/daemon_context_bmc.h" |
| #include "boost/asio/ip/tcp.hpp" // NOLINT |
| #include "boost/asio/ssl.hpp" // NOLINT |
| #include "boost/asio/ssl/stream.hpp" // NOLINT |
| #include "boost/beast/core.hpp" // NOLINT |
| #include "boost/beast/core/tcp_stream.hpp" // NOLINT |
| #include "boost/beast/http.hpp" // NOLINT |
| #include "boost/beast/version.hpp" // NOLINT |
| #include "absl/status/status.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/log/log.h" |
| |
| |
| namespace safepower_agent { |
| |
| namespace beast = boost::beast; |
| namespace http = beast::http; |
| |
| using boost::beast::flat_buffer; |
| using boost::beast::tcp_stream; |
| |
| |
| HttpConnection::HttpConnection(absl::Duration connection_timeout, |
| absl::Duration write_timeout): |
| stream_(boost::asio::make_strand |
| (DaemonContextBMC::Get().get_io_context())), |
| connection_timeout_(connection_timeout), |
| write_timeout_(write_timeout) |
| {} |
| |
| // perform_connection = connect -> write -> read -> callback |
| // all failing cases, skip to user provided callback with error |
| void HttpConnection::PerformConnection( |
| http::verb verb, |
| absl::string_view target, |
| absl::AnyInvocable<void(absl::StatusOr<nlohmann::json>) && > in_callback, |
| const nlohmann::json& body, |
| const absl::string_view ip, |
| const uint16_t port){ |
| verb_ = verb; |
| body_ = body.dump(); |
| target_ = target; |
| provided_callback_ = std::move(in_callback); |
| |
| boost::system::error_code ec; |
| boost::asio::ip::address address = |
| boost::asio::ip::address::from_string(std::string(ip).c_str(), ec); |
| if (ec){ |
| LOG(INFO) << "Unable to build address from string ip:" << |
| std::string(ip); |
| } |
| end_point_.address(address); |
| end_point_.port(port); |
| stream_.expires_after(absl::ToChronoMilliseconds(connection_timeout_)); |
| VLOG(2) << absl::StrFormat( "Connecting to endpoint : %s:%d %s %s", |
| ip, port, target, body_); |
| |
| auto self = shared_from_this(); |
| stream_.async_connect(end_point_, |
| [self](beast::error_code ec) { |
| self->Write(ec); |
| }); |
| } |
| |
| void HttpConnection::Write(beast::error_code ec) |
| { |
| if (ec) { |
| std::move(provided_callback_)(absl::UnavailableError( |
| absl::StrCat("Failed to connect to BMC: ", ec.message()))); |
| return; |
| } |
| req_.method(verb_); |
| req_.target(target_); |
| req_.version(11); /*http version 1.1*/ |
| req_.set(http::field::host, "localhost"); |
| req_.set(http::field::content_type, "application/json"); |
| // string value must outlive the async write |
| req_.body() = body_; |
| VLOG(3) << absl::StrCat("http request body: ", req_.body());; |
| req_.prepare_payload(); |
| stream_.expires_after(absl::ToChronoMilliseconds(write_timeout_)); |
| auto self = shared_from_this(); |
| http::async_write(stream_, req_, |
| [self](beast::error_code ec, size_t t){ |
| self->Read(ec, t); |
| }); |
| } |
| |
| void HttpConnection::Read(beast::error_code ec, size_t ) { |
| if (ec) { |
| std::move(provided_callback_)(absl::UnavailableError( |
| absl::StrCat("Failed in http write:", ec.message()))); |
| return; |
| } |
| auto self = shared_from_this(); |
| http::async_read(stream_, buffer_, res_, |
| [self](beast::error_code ec, size_t t) |
| { |
| self->Callback(ec, t); |
| }); |
| } |
| |
| void HttpConnection::Callback( |
| beast::error_code ec, |
| std::size_t ) |
| { |
| absl::Status status = absl::OkStatus(); |
| if (ec) { |
| LOG(ERROR) << "callback error handler: " << ec.message(); |
| status = absl::UnknownError( |
| absl::StrCat("Callback error handler: ", ec.message())); |
| std::move(provided_callback_)(status); |
| return; |
| } |
| VLOG(3) << absl::StrCat("http response body: ", res_.body()); |
| |
| if (res_.result() != http::status::ok) { |
| LOG(ERROR) << "bad http code " << res_.result(); |
| status = absl::UnavailableError( |
| absl::StrCat("HTTP request failed with status: ", res_.result())); |
| std::move(provided_callback_)(status); |
| return; |
| } |
| nlohmann::json js_body = nlohmann::json::parse(res_.body(), /* cb */ nullptr, |
| /* allow_exceptions */ false); |
| if (js_body.is_discarded()) { |
| status = absl::InvalidArgumentError( |
| absl::StrCat("Failed to parse JSON from BMC: ", res_.body())); |
| LOG(ERROR) << "response json " << res_.body(); |
| std::move(provided_callback_)(status); |
| return; |
| } |
| std::move(provided_callback_)(js_body); |
| return; |
| } |
| } // namespace safepower_agent |