blob: fd2c7e57b5587d5c1967a06e75eed91506a6fb80 [file] [log] [blame] [edit]
#include "tlbmc/http/http_client.h"
#include <chrono> // NOLINT
#include <cstddef>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include "absl/functional/any_invocable.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "boost/asio.hpp" //NOLINT: boost::asio is commonly used in BMC
#include "boost/asio/ip/tcp.hpp" //NOLINT: boost::asio is commonly used in BMC
#include "resource.pb.h"
namespace milotic_tlbmc {
bool HttpClient::IsActive() { return is_active_; }
absl::Status HttpClient::SendRequest(
boost::asio::io_context& io_context, std::string_view host,
std::string_view port, std::string_view target,
absl::AnyInvocable<
void(boost::beast::error_code,
boost::beast::http::response<boost::beast::http::string_body>)>
callback) {
if (is_active_) {
LOG(WARNING) << "Active job is running";
return absl::FailedPreconditionError("Active job is running");
}
is_active_ = true;
job_ = std::make_shared<ScanJob>(ScanJob{
.target = std::string(target),
.callback = std::move(callback),
});
job_->req.version(11);
job_->req.method(boost::beast::http::verb::get);
job_->req.target(target);
job_->req.set(boost::beast::http::field::host, host_);
// If the connection is already established and the host and port are the
// same, we can reuse the connection and send the request immediately.
if (connection_established_ && host_ == host && port_ == port) {
LOG(INFO) << "Connection already established";
stream_.expires_after(std::chrono::seconds(10));
boost::beast::http::async_write(
stream_, job_->req,
[this](boost::beast::error_code ec, std::size_t bytes_transferred) {
OnWrite(ec, bytes_transferred);
});
return absl::OkStatus();
}
host_ = std::string(host);
port_ = std::string(port);
resolver_.async_resolve(
host, port,
[this](boost::beast::error_code ec,
boost::asio::ip::tcp::resolver::results_type results) {
OnResolve(ec, results);
});
return absl::OkStatus();
}
void HttpClient::OnResolve(
boost::beast::error_code ec,
boost::asio::ip::tcp::resolver::results_type results) {
if (ec) {
LOG(ERROR) << "Resolve failed for " << job_->target << ": " << ec.message();
job_->callback(std::move(ec), std::move(job_->res));
is_active_ = false;
return;
}
stream_.expires_after(std::chrono::seconds(10));
stream_.async_connect(
results,
[this](boost::beast::error_code ec,
boost::asio::ip::tcp::resolver::results_type::endpoint_type
endpoint) { OnConnect(ec, endpoint); });
}
void HttpClient::OnConnect(
boost::beast::error_code ec,
boost::asio::ip::tcp::resolver::results_type::endpoint_type endpoint) {
if (ec) {
LOG(ERROR) << "Connect failed for " << job_->target << ": " << ec.message();
job_->callback(std::move(ec), std::move(job_->res));
is_active_ = false;
return;
}
connection_established_ = true;
stream_.expires_after(std::chrono::seconds(10));
boost::beast::http::async_write(
stream_, job_->req,
[this](boost::beast::error_code ec, std::size_t bytes_transferred) {
OnWrite(ec, bytes_transferred);
});
}
void HttpClient::OnWrite(boost::beast::error_code ec,
std::size_t bytes_transferred) {
if (ec) {
LOG(ERROR) << "Write failed for " << job_->target << ": " << ec.message();
job_->callback(std::move(ec), std::move(job_->res));
is_active_ = false;
// Reset the connection established flag to false if write fails.
connection_established_ = false;
return;
}
stream_.expires_after(std::chrono::seconds(10));
boost::beast::http::async_read(
stream_, buffer_, job_->res,
[this](boost::beast::error_code ec, std::size_t bytes_transferred) {
OnRead(ec, bytes_transferred);
});
}
void HttpClient::OnRead(boost::beast::error_code ec,
std::size_t bytes_transferred) {
if (ec) {
LOG(ERROR) << "Read failed for " << job_->target << ": " << ec.message();
// Reset the connection established flag to false if read fails.
connection_established_ = false;
}
job_->callback(std::move(ec), std::move(job_->res));
is_active_ = false;
}
} // namespace milotic_tlbmc