| #include "bmc/http_connection.h" |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <string> |
| #include <utility> |
| |
| #include "bmc/daemon_context_bmc.h" |
| #include "absl/functional/any_invocable.h" |
| #include "absl/log/log.h" |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/time/time.h" |
| |
| // NOLINTBEGIN(readability/boost) |
| #include "boost/asio/ip/tcp.hpp" |
| #include "boost/beast/core.hpp" |
| #include "boost/beast/core/tcp_stream.hpp" |
| #include "boost/beast/http.hpp" |
| #include "boost/beast/version.hpp" |
| // NOLINTEND(readability/boost) |
| |
| #include "nlohmann/json.hpp" |
| #include "nlohmann/json_fwd.hpp" |
| |
| 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:" << 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); |
| } |
| } // namespace safepower_agent |