blob: 8dec7493cc6bdc470f9e16ffeb20c35ee4517f03 [file] [log] [blame]
#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