blob: 6e87ad74a3040d83f1a06d360b358d2c5594759c [file]
#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