| #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 |