1bd030d0aSAppaRao Puli /* 2bd030d0aSAppaRao Puli // Copyright (c) 2020 Intel Corporation 3bd030d0aSAppaRao Puli // 4bd030d0aSAppaRao Puli // Licensed under the Apache License, Version 2.0 (the "License"); 5bd030d0aSAppaRao Puli // you may not use this file except in compliance with the License. 6bd030d0aSAppaRao Puli // You may obtain a copy of the License at 7bd030d0aSAppaRao Puli // 8bd030d0aSAppaRao Puli // http://www.apache.org/licenses/LICENSE-2.0 9bd030d0aSAppaRao Puli // 10bd030d0aSAppaRao Puli // Unless required by applicable law or agreed to in writing, software 11bd030d0aSAppaRao Puli // distributed under the License is distributed on an "AS IS" BASIS, 12bd030d0aSAppaRao Puli // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13bd030d0aSAppaRao Puli // See the License for the specific language governing permissions and 14bd030d0aSAppaRao Puli // limitations under the License. 15bd030d0aSAppaRao Puli */ 16bd030d0aSAppaRao Puli #pragma once 1777665bdaSNan Zhou 1877665bdaSNan Zhou #include "async_resolve.hpp" 19b2896149SEd Tanous #include "http_body.hpp" 2077665bdaSNan Zhou #include "http_response.hpp" 213ccb3adbSEd Tanous #include "logging.hpp" 223ccb3adbSEd Tanous #include "ssl_key_handler.hpp" 2377665bdaSNan Zhou 240d5f5cf4SEd Tanous #include <boost/asio/connect.hpp> 25bb49eb5cSEd Tanous #include <boost/asio/io_context.hpp> 2629a82b08SSunitha Harish #include <boost/asio/ip/address.hpp> 2729a82b08SSunitha Harish #include <boost/asio/ip/basic_endpoint.hpp> 28bb49eb5cSEd Tanous #include <boost/asio/ip/tcp.hpp> 29e38778a5SAppaRao Puli #include <boost/asio/ssl/context.hpp> 30e38778a5SAppaRao Puli #include <boost/asio/ssl/error.hpp> 31003301a2SEd Tanous #include <boost/asio/ssl/stream.hpp> 32d43cd0caSEd Tanous #include <boost/asio/steady_timer.hpp> 33bb49eb5cSEd Tanous #include <boost/beast/core/flat_static_buffer.hpp> 34d43cd0caSEd Tanous #include <boost/beast/http/message.hpp> 354d69861fSEd Tanous #include <boost/beast/http/message_generator.hpp> 36bb49eb5cSEd Tanous #include <boost/beast/http/parser.hpp> 37bb49eb5cSEd Tanous #include <boost/beast/http/read.hpp> 38bb49eb5cSEd Tanous #include <boost/beast/http/write.hpp> 39f52c03c1SCarson Labrado #include <boost/container/devector.hpp> 40bb49eb5cSEd Tanous #include <boost/system/error_code.hpp> 4127b0cf90SEd Tanous #include <boost/url/format.hpp> 4227b0cf90SEd Tanous #include <boost/url/url.hpp> 434a7fbefdSEd Tanous #include <boost/url/url_view_base.hpp> 441214b7e7SGunnar Mills 45bd030d0aSAppaRao Puli #include <cstdlib> 46bd030d0aSAppaRao Puli #include <functional> 47bd030d0aSAppaRao Puli #include <iostream> 48bd030d0aSAppaRao Puli #include <memory> 492a5689a7SAppaRao Puli #include <queue> 50bd030d0aSAppaRao Puli #include <string> 51bd030d0aSAppaRao Puli 52bd030d0aSAppaRao Puli namespace crow 53bd030d0aSAppaRao Puli { 5427b0cf90SEd Tanous // With Redfish Aggregation it is assumed we will connect to another 5527b0cf90SEd Tanous // instance of BMCWeb which can handle 100 simultaneous connections. 5666d90c2cSCarson Labrado constexpr size_t maxPoolSize = 20; 5766d90c2cSCarson Labrado constexpr size_t maxRequestQueueSize = 500; 5817dcc312SCarson Labrado constexpr unsigned int httpReadBodyLimit = 131072; 594d94272fSCarson Labrado constexpr unsigned int httpReadBufferSize = 4096; 602a5689a7SAppaRao Puli 61bd030d0aSAppaRao Puli enum class ConnState 62bd030d0aSAppaRao Puli { 632a5689a7SAppaRao Puli initialized, 6429a82b08SSunitha Harish resolveInProgress, 6529a82b08SSunitha Harish resolveFailed, 662a5689a7SAppaRao Puli connectInProgress, 672a5689a7SAppaRao Puli connectFailed, 68bd030d0aSAppaRao Puli connected, 69e38778a5SAppaRao Puli handshakeInProgress, 70e38778a5SAppaRao Puli handshakeFailed, 712a5689a7SAppaRao Puli sendInProgress, 722a5689a7SAppaRao Puli sendFailed, 736eaa1d2fSSunitha Harish recvInProgress, 742a5689a7SAppaRao Puli recvFailed, 752a5689a7SAppaRao Puli idle, 76fe44eb0bSAyushi Smriti closed, 776eaa1d2fSSunitha Harish suspended, 786eaa1d2fSSunitha Harish terminated, 796eaa1d2fSSunitha Harish abortConnection, 80e38778a5SAppaRao Puli sslInitFailed, 816eaa1d2fSSunitha Harish retry 82bd030d0aSAppaRao Puli }; 83bd030d0aSAppaRao Puli 84a7a80296SCarson Labrado static inline boost::system::error_code 85a7a80296SCarson Labrado defaultRetryHandler(unsigned int respCode) 86a7a80296SCarson Labrado { 87a7a80296SCarson Labrado // As a default, assume 200X is alright 8862598e31SEd Tanous BMCWEB_LOG_DEBUG("Using default check for response code validity"); 89a7a80296SCarson Labrado if ((respCode < 200) || (respCode >= 300)) 90a7a80296SCarson Labrado { 91a7a80296SCarson Labrado return boost::system::errc::make_error_code( 92a7a80296SCarson Labrado boost::system::errc::result_out_of_range); 93a7a80296SCarson Labrado } 94a7a80296SCarson Labrado 95a7a80296SCarson Labrado // Return 0 if the response code is valid 96a7a80296SCarson Labrado return boost::system::errc::make_error_code(boost::system::errc::success); 97a7a80296SCarson Labrado }; 98a7a80296SCarson Labrado 9927b0cf90SEd Tanous // We need to allow retry information to be set before a message has been 10027b0cf90SEd Tanous // sent and a connection pool has been created 101d14a48ffSCarson Labrado struct ConnectionPolicy 102f52c03c1SCarson Labrado { 103f52c03c1SCarson Labrado uint32_t maxRetryAttempts = 5; 104d14a48ffSCarson Labrado 105d14a48ffSCarson Labrado // the max size of requests in bytes. 0 for unlimited 106d14a48ffSCarson Labrado boost::optional<uint64_t> requestByteLimit = httpReadBodyLimit; 107d14a48ffSCarson Labrado 108d14a48ffSCarson Labrado size_t maxConnections = 1; 109d14a48ffSCarson Labrado 110f52c03c1SCarson Labrado std::string retryPolicyAction = "TerminateAfterRetries"; 111d14a48ffSCarson Labrado 112d14a48ffSCarson Labrado std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0); 113a7a80296SCarson Labrado std::function<boost::system::error_code(unsigned int respCode)> 114a7a80296SCarson Labrado invalidResp = defaultRetryHandler; 115f52c03c1SCarson Labrado }; 116f52c03c1SCarson Labrado 117f52c03c1SCarson Labrado struct PendingRequest 118f52c03c1SCarson Labrado { 119b2896149SEd Tanous boost::beast::http::request<bmcweb::HttpBody> req; 120039a47e3SCarson Labrado std::function<void(bool, uint32_t, Response&)> callback; 121039a47e3SCarson Labrado PendingRequest( 122b2896149SEd Tanous boost::beast::http::request<bmcweb::HttpBody>&& reqIn, 123d14a48ffSCarson Labrado const std::function<void(bool, uint32_t, Response&)>& callbackIn) : 1248a592810SEd Tanous req(std::move(reqIn)), 125d14a48ffSCarson Labrado callback(callbackIn) 126f52c03c1SCarson Labrado {} 127f52c03c1SCarson Labrado }; 128f52c03c1SCarson Labrado 129e01d0c36SEd Tanous namespace http = boost::beast::http; 130f52c03c1SCarson Labrado class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo> 131bd030d0aSAppaRao Puli { 132bd030d0aSAppaRao Puli private: 133f52c03c1SCarson Labrado ConnState state = ConnState::initialized; 134f52c03c1SCarson Labrado uint32_t retryCount = 0; 135f52c03c1SCarson Labrado std::string subId; 136d14a48ffSCarson Labrado std::shared_ptr<ConnectionPolicy> connPolicy; 137a716aa74SEd Tanous boost::urls::url host; 138f52c03c1SCarson Labrado uint32_t connId; 139f52c03c1SCarson Labrado 140f52c03c1SCarson Labrado // Data buffers 141b2896149SEd Tanous http::request<bmcweb::HttpBody> req; 142b2896149SEd Tanous using parser_type = http::response_parser<bmcweb::HttpBody>; 143e01d0c36SEd Tanous std::optional<parser_type> parser; 1444d94272fSCarson Labrado boost::beast::flat_static_buffer<httpReadBufferSize> buffer; 145039a47e3SCarson Labrado Response res; 1466eaa1d2fSSunitha Harish 147f52c03c1SCarson Labrado // Ascync callables 148039a47e3SCarson Labrado std::function<void(bool, uint32_t, Response&)> callback; 149f8ca6d79SEd Tanous 150f3cb5df9SAbhilash Raju boost::asio::io_context& ioc; 151f3cb5df9SAbhilash Raju 152*25b54dbaSEd Tanous using Resolver = std::conditional_t<BMCWEB_DNS_RESOLVER == "systemd-dbus", 153*25b54dbaSEd Tanous async_resolve::Resolver, 154*25b54dbaSEd Tanous boost::asio::ip::tcp::resolver>; 155f8ca6d79SEd Tanous Resolver resolver; 156f8ca6d79SEd Tanous 1570d5f5cf4SEd Tanous boost::asio::ip::tcp::socket conn; 158003301a2SEd Tanous std::optional<boost::asio::ssl::stream<boost::asio::ip::tcp::socket&>> 1590d5f5cf4SEd Tanous sslConn; 160e38778a5SAppaRao Puli 161f52c03c1SCarson Labrado boost::asio::steady_timer timer; 16284b35604SEd Tanous 163f52c03c1SCarson Labrado friend class ConnectionPool; 164bd030d0aSAppaRao Puli 16529a82b08SSunitha Harish void doResolve() 16629a82b08SSunitha Harish { 16729a82b08SSunitha Harish state = ConnState::resolveInProgress; 168a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Trying to resolve: {}, id: {}", host, connId); 16929a82b08SSunitha Harish 170a716aa74SEd Tanous resolver.async_resolve(host.encoded_host_address(), host.port(), 1713d36e3a5SEd Tanous std::bind_front(&ConnectionInfo::afterResolve, 1723d36e3a5SEd Tanous this, shared_from_this())); 1733d36e3a5SEd Tanous } 1743d36e3a5SEd Tanous 175f8ca6d79SEd Tanous void afterResolve(const std::shared_ptr<ConnectionInfo>& /*self*/, 176f8ca6d79SEd Tanous const boost::system::error_code& ec, 177f8ca6d79SEd Tanous const Resolver::results_type& endpointList) 1783d36e3a5SEd Tanous { 17926f6976fSEd Tanous if (ec || (endpointList.empty())) 18029a82b08SSunitha Harish { 181a716aa74SEd Tanous BMCWEB_LOG_ERROR("Resolve failed: {} {}", ec.message(), host); 1823d36e3a5SEd Tanous state = ConnState::resolveFailed; 1833d36e3a5SEd Tanous waitAndRetry(); 18429a82b08SSunitha Harish return; 18529a82b08SSunitha Harish } 186a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Resolved {}, id: {}", host, connId); 1872a5689a7SAppaRao Puli state = ConnState::connectInProgress; 1882a5689a7SAppaRao Puli 189a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Trying to connect to: {}, id: {}", host, connId); 190b00dcc27SEd Tanous 1910d5f5cf4SEd Tanous timer.expires_after(std::chrono::seconds(30)); 1920d5f5cf4SEd Tanous timer.async_wait(std::bind_front(onTimeout, weak_from_this())); 1930d5f5cf4SEd Tanous 1940d5f5cf4SEd Tanous boost::asio::async_connect( 1950d5f5cf4SEd Tanous conn, endpointList, 196e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterConnect, this, 197e38778a5SAppaRao Puli shared_from_this())); 198e38778a5SAppaRao Puli } 199e38778a5SAppaRao Puli 200e38778a5SAppaRao Puli void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/, 20181c4e330SEd Tanous const boost::beast::error_code& ec, 202e38778a5SAppaRao Puli const boost::asio::ip::tcp::endpoint& endpoint) 203e38778a5SAppaRao Puli { 204513d1ffcSCarson Labrado // The operation already timed out. We don't want do continue down 205513d1ffcSCarson Labrado // this branch 206513d1ffcSCarson Labrado if (ec && ec == boost::asio::error::operation_aborted) 207513d1ffcSCarson Labrado { 208513d1ffcSCarson Labrado return; 209513d1ffcSCarson Labrado } 210513d1ffcSCarson Labrado 2110d5f5cf4SEd Tanous timer.cancel(); 2122a5689a7SAppaRao Puli if (ec) 2132a5689a7SAppaRao Puli { 21462598e31SEd Tanous BMCWEB_LOG_ERROR("Connect {}:{}, id: {} failed: {}", 215a716aa74SEd Tanous endpoint.address().to_string(), endpoint.port(), 216a716aa74SEd Tanous connId, ec.message()); 217e38778a5SAppaRao Puli state = ConnState::connectFailed; 218e38778a5SAppaRao Puli waitAndRetry(); 2192a5689a7SAppaRao Puli return; 2202a5689a7SAppaRao Puli } 221a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Connected to: {}:{}, id: {}", 222a716aa74SEd Tanous endpoint.address().to_string(), endpoint.port(), 223a716aa74SEd Tanous connId); 224e38778a5SAppaRao Puli if (sslConn) 225e38778a5SAppaRao Puli { 2260d5f5cf4SEd Tanous doSslHandshake(); 227e38778a5SAppaRao Puli return; 228e38778a5SAppaRao Puli } 229e38778a5SAppaRao Puli state = ConnState::connected; 230e38778a5SAppaRao Puli sendMessage(); 231e38778a5SAppaRao Puli } 232e38778a5SAppaRao Puli 2330d5f5cf4SEd Tanous void doSslHandshake() 234e38778a5SAppaRao Puli { 235e38778a5SAppaRao Puli if (!sslConn) 236e38778a5SAppaRao Puli { 237e38778a5SAppaRao Puli return; 238e38778a5SAppaRao Puli } 239e38778a5SAppaRao Puli state = ConnState::handshakeInProgress; 2400d5f5cf4SEd Tanous timer.expires_after(std::chrono::seconds(30)); 2410d5f5cf4SEd Tanous timer.async_wait(std::bind_front(onTimeout, weak_from_this())); 242e38778a5SAppaRao Puli sslConn->async_handshake( 243e38778a5SAppaRao Puli boost::asio::ssl::stream_base::client, 244e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterSslHandshake, this, 245e38778a5SAppaRao Puli shared_from_this())); 246e38778a5SAppaRao Puli } 247e38778a5SAppaRao Puli 248e38778a5SAppaRao Puli void afterSslHandshake(const std::shared_ptr<ConnectionInfo>& /*self*/, 24981c4e330SEd Tanous const boost::beast::error_code& ec) 250e38778a5SAppaRao Puli { 251513d1ffcSCarson Labrado // The operation already timed out. We don't want do continue down 252513d1ffcSCarson Labrado // this branch 253513d1ffcSCarson Labrado if (ec && ec == boost::asio::error::operation_aborted) 254513d1ffcSCarson Labrado { 255513d1ffcSCarson Labrado return; 256513d1ffcSCarson Labrado } 257513d1ffcSCarson Labrado 2580d5f5cf4SEd Tanous timer.cancel(); 259e38778a5SAppaRao Puli if (ec) 260e38778a5SAppaRao Puli { 261a716aa74SEd Tanous BMCWEB_LOG_ERROR("SSL Handshake failed - id: {} error: {}", connId, 262a716aa74SEd Tanous ec.message()); 263e38778a5SAppaRao Puli state = ConnState::handshakeFailed; 264e38778a5SAppaRao Puli waitAndRetry(); 265e38778a5SAppaRao Puli return; 266e38778a5SAppaRao Puli } 267a716aa74SEd Tanous BMCWEB_LOG_DEBUG("SSL Handshake successful - id: {}", connId); 268e38778a5SAppaRao Puli state = ConnState::connected; 269e38778a5SAppaRao Puli sendMessage(); 2702a5689a7SAppaRao Puli } 2712a5689a7SAppaRao Puli 272f52c03c1SCarson Labrado void sendMessage() 2732a5689a7SAppaRao Puli { 2742a5689a7SAppaRao Puli state = ConnState::sendInProgress; 2752a5689a7SAppaRao Puli 276bd030d0aSAppaRao Puli // Set a timeout on the operation 2770d5f5cf4SEd Tanous timer.expires_after(std::chrono::seconds(30)); 2780d5f5cf4SEd Tanous timer.async_wait(std::bind_front(onTimeout, weak_from_this())); 2794d69861fSEd Tanous boost::beast::http::message_generator messageGenerator(std::move(req)); 280bd030d0aSAppaRao Puli // Send the HTTP request to the remote host 281e38778a5SAppaRao Puli if (sslConn) 282e38778a5SAppaRao Puli { 2834d69861fSEd Tanous boost::beast::async_write( 2844d69861fSEd Tanous *sslConn, std::move(messageGenerator), 285e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterWrite, this, 286e38778a5SAppaRao Puli shared_from_this())); 287e38778a5SAppaRao Puli } 288e38778a5SAppaRao Puli else 289e38778a5SAppaRao Puli { 2904d69861fSEd Tanous boost::beast::async_write( 2914d69861fSEd Tanous conn, std::move(messageGenerator), 292e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterWrite, this, 293e38778a5SAppaRao Puli shared_from_this())); 294e38778a5SAppaRao Puli } 295e38778a5SAppaRao Puli } 296e38778a5SAppaRao Puli 297e38778a5SAppaRao Puli void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/, 298e38778a5SAppaRao Puli const boost::beast::error_code& ec, size_t bytesTransferred) 299e38778a5SAppaRao Puli { 300513d1ffcSCarson Labrado // The operation already timed out. We don't want do continue down 301513d1ffcSCarson Labrado // this branch 302513d1ffcSCarson Labrado if (ec && ec == boost::asio::error::operation_aborted) 303513d1ffcSCarson Labrado { 304513d1ffcSCarson Labrado return; 305513d1ffcSCarson Labrado } 306513d1ffcSCarson Labrado 3070d5f5cf4SEd Tanous timer.cancel(); 308bd030d0aSAppaRao Puli if (ec) 309bd030d0aSAppaRao Puli { 310a716aa74SEd Tanous BMCWEB_LOG_ERROR("sendMessage() failed: {} {}", ec.message(), host); 311e38778a5SAppaRao Puli state = ConnState::sendFailed; 312e38778a5SAppaRao Puli waitAndRetry(); 313bd030d0aSAppaRao Puli return; 314bd030d0aSAppaRao Puli } 31562598e31SEd Tanous BMCWEB_LOG_DEBUG("sendMessage() bytes transferred: {}", 31662598e31SEd Tanous bytesTransferred); 317bd030d0aSAppaRao Puli 318e38778a5SAppaRao Puli recvMessage(); 319bd030d0aSAppaRao Puli } 320bd030d0aSAppaRao Puli 321bd030d0aSAppaRao Puli void recvMessage() 322bd030d0aSAppaRao Puli { 3236eaa1d2fSSunitha Harish state = ConnState::recvInProgress; 3246eaa1d2fSSunitha Harish 325e01d0c36SEd Tanous parser_type& thisParser = parser.emplace(std::piecewise_construct, 326e01d0c36SEd Tanous std::make_tuple()); 327d14a48ffSCarson Labrado 328e01d0c36SEd Tanous thisParser.body_limit(connPolicy->requestByteLimit); 3296eaa1d2fSSunitha Harish 3300d5f5cf4SEd Tanous timer.expires_after(std::chrono::seconds(30)); 3310d5f5cf4SEd Tanous timer.async_wait(std::bind_front(onTimeout, weak_from_this())); 3320d5f5cf4SEd Tanous 333bd030d0aSAppaRao Puli // Receive the HTTP response 334e38778a5SAppaRao Puli if (sslConn) 335e38778a5SAppaRao Puli { 336e38778a5SAppaRao Puli boost::beast::http::async_read( 337e01d0c36SEd Tanous *sslConn, buffer, thisParser, 338e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterRead, this, 339e38778a5SAppaRao Puli shared_from_this())); 340e38778a5SAppaRao Puli } 341e38778a5SAppaRao Puli else 342e38778a5SAppaRao Puli { 343bd030d0aSAppaRao Puli boost::beast::http::async_read( 344e01d0c36SEd Tanous conn, buffer, thisParser, 345e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterRead, this, 346e38778a5SAppaRao Puli shared_from_this())); 347e38778a5SAppaRao Puli } 348e38778a5SAppaRao Puli } 349e38778a5SAppaRao Puli 350e38778a5SAppaRao Puli void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/, 351e38778a5SAppaRao Puli const boost::beast::error_code& ec, 352e38778a5SAppaRao Puli const std::size_t& bytesTransferred) 353e38778a5SAppaRao Puli { 354513d1ffcSCarson Labrado // The operation already timed out. We don't want do continue down 355513d1ffcSCarson Labrado // this branch 356513d1ffcSCarson Labrado if (ec && ec == boost::asio::error::operation_aborted) 357513d1ffcSCarson Labrado { 358513d1ffcSCarson Labrado return; 359513d1ffcSCarson Labrado } 360513d1ffcSCarson Labrado 3610d5f5cf4SEd Tanous timer.cancel(); 362e38778a5SAppaRao Puli if (ec && ec != boost::asio::ssl::error::stream_truncated) 363bd030d0aSAppaRao Puli { 364a716aa74SEd Tanous BMCWEB_LOG_ERROR("recvMessage() failed: {} from {}", ec.message(), 365a716aa74SEd Tanous host); 366e38778a5SAppaRao Puli state = ConnState::recvFailed; 367e38778a5SAppaRao Puli waitAndRetry(); 368bd030d0aSAppaRao Puli return; 369bd030d0aSAppaRao Puli } 37062598e31SEd Tanous BMCWEB_LOG_DEBUG("recvMessage() bytes transferred: {}", 37162598e31SEd Tanous bytesTransferred); 372e01d0c36SEd Tanous if (!parser) 373e01d0c36SEd Tanous { 374e01d0c36SEd Tanous return; 375e01d0c36SEd Tanous } 37652e31629SEd Tanous BMCWEB_LOG_DEBUG("recvMessage() data: {}", parser->get().body().str()); 377bd030d0aSAppaRao Puli 378e38778a5SAppaRao Puli unsigned int respCode = parser->get().result_int(); 37962598e31SEd Tanous BMCWEB_LOG_DEBUG("recvMessage() Header Response Code: {}", respCode); 3806eaa1d2fSSunitha Harish 381f3cb5df9SAbhilash Raju // Handle the case of stream_truncated. Some servers close the ssl 382f3cb5df9SAbhilash Raju // connection uncleanly, so check to see if we got a full response 383f3cb5df9SAbhilash Raju // before we handle this as an error. 384f3cb5df9SAbhilash Raju if (!parser->is_done()) 385f3cb5df9SAbhilash Raju { 386f3cb5df9SAbhilash Raju state = ConnState::recvFailed; 387f3cb5df9SAbhilash Raju waitAndRetry(); 388f3cb5df9SAbhilash Raju return; 389f3cb5df9SAbhilash Raju } 390f3cb5df9SAbhilash Raju 391a7a80296SCarson Labrado // Make sure the received response code is valid as defined by 392a7a80296SCarson Labrado // the associated retry policy 393d14a48ffSCarson Labrado if (connPolicy->invalidResp(respCode)) 3946eaa1d2fSSunitha Harish { 3956eaa1d2fSSunitha Harish // The listener failed to receive the Sent-Event 39662598e31SEd Tanous BMCWEB_LOG_ERROR( 39762598e31SEd Tanous "recvMessage() Listener Failed to " 398a716aa74SEd Tanous "receive Sent-Event. Header Response Code: {} from {}", 399a716aa74SEd Tanous respCode, host); 400e38778a5SAppaRao Puli state = ConnState::recvFailed; 401e38778a5SAppaRao Puli waitAndRetry(); 4026eaa1d2fSSunitha Harish return; 4036eaa1d2fSSunitha Harish } 404bd030d0aSAppaRao Puli 405f52c03c1SCarson Labrado // Send is successful 406f52c03c1SCarson Labrado // Reset the counter just in case this was after retrying 407e38778a5SAppaRao Puli retryCount = 0; 4086eaa1d2fSSunitha Harish 4096eaa1d2fSSunitha Harish // Keep the connection alive if server supports it 4106eaa1d2fSSunitha Harish // Else close the connection 41162598e31SEd Tanous BMCWEB_LOG_DEBUG("recvMessage() keepalive : {}", parser->keep_alive()); 4126eaa1d2fSSunitha Harish 413039a47e3SCarson Labrado // Copy the response into a Response object so that it can be 414039a47e3SCarson Labrado // processed by the callback function. 41527b0cf90SEd Tanous res.response = parser->release(); 416e38778a5SAppaRao Puli callback(parser->keep_alive(), connId, res); 417513d1ffcSCarson Labrado res.clear(); 418bd030d0aSAppaRao Puli } 419bd030d0aSAppaRao Puli 4200d5f5cf4SEd Tanous static void onTimeout(const std::weak_ptr<ConnectionInfo>& weakSelf, 4215e7e2dc5SEd Tanous const boost::system::error_code& ec) 4220d5f5cf4SEd Tanous { 4230d5f5cf4SEd Tanous if (ec == boost::asio::error::operation_aborted) 4240d5f5cf4SEd Tanous { 42562598e31SEd Tanous BMCWEB_LOG_DEBUG( 42662598e31SEd Tanous "async_wait failed since the operation is aborted"); 4270d5f5cf4SEd Tanous return; 4280d5f5cf4SEd Tanous } 4290d5f5cf4SEd Tanous if (ec) 4300d5f5cf4SEd Tanous { 43162598e31SEd Tanous BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message()); 43227b0cf90SEd Tanous // If the timer fails, we need to close the socket anyway, same 43327b0cf90SEd Tanous // as if it expired. 4340d5f5cf4SEd Tanous } 4350d5f5cf4SEd Tanous std::shared_ptr<ConnectionInfo> self = weakSelf.lock(); 4360d5f5cf4SEd Tanous if (self == nullptr) 4370d5f5cf4SEd Tanous { 4380d5f5cf4SEd Tanous return; 4390d5f5cf4SEd Tanous } 4400d5f5cf4SEd Tanous self->waitAndRetry(); 4410d5f5cf4SEd Tanous } 4420d5f5cf4SEd Tanous 4436eaa1d2fSSunitha Harish void waitAndRetry() 444bd030d0aSAppaRao Puli { 445d14a48ffSCarson Labrado if ((retryCount >= connPolicy->maxRetryAttempts) || 446e38778a5SAppaRao Puli (state == ConnState::sslInitFailed)) 4472a5689a7SAppaRao Puli { 448a716aa74SEd Tanous BMCWEB_LOG_ERROR("Maximum number of retries reached. {}", host); 44962598e31SEd Tanous BMCWEB_LOG_DEBUG("Retry policy: {}", connPolicy->retryPolicyAction); 450039a47e3SCarson Labrado 451d14a48ffSCarson Labrado if (connPolicy->retryPolicyAction == "TerminateAfterRetries") 452fe44eb0bSAyushi Smriti { 453fe44eb0bSAyushi Smriti // TODO: delete subscription 454fe44eb0bSAyushi Smriti state = ConnState::terminated; 455fe44eb0bSAyushi Smriti } 456d14a48ffSCarson Labrado if (connPolicy->retryPolicyAction == "SuspendRetries") 457fe44eb0bSAyushi Smriti { 4582a5689a7SAppaRao Puli state = ConnState::suspended; 4592a5689a7SAppaRao Puli } 460513d1ffcSCarson Labrado 461513d1ffcSCarson Labrado // We want to return a 502 to indicate there was an error with 462513d1ffcSCarson Labrado // the external server 463513d1ffcSCarson Labrado res.result(boost::beast::http::status::bad_gateway); 464513d1ffcSCarson Labrado callback(false, connId, res); 465513d1ffcSCarson Labrado res.clear(); 466513d1ffcSCarson Labrado 46727b0cf90SEd Tanous // Reset the retrycount to zero so that client can try 46827b0cf90SEd Tanous // connecting again if needed 469fe44eb0bSAyushi Smriti retryCount = 0; 4702a5689a7SAppaRao Puli return; 4712a5689a7SAppaRao Puli } 4722a5689a7SAppaRao Puli 4732a5689a7SAppaRao Puli retryCount++; 474fe44eb0bSAyushi Smriti 47562598e31SEd Tanous BMCWEB_LOG_DEBUG("Attempt retry after {} seconds. RetryCount = {}", 476a716aa74SEd Tanous connPolicy->retryIntervalSecs.count(), retryCount); 477d14a48ffSCarson Labrado timer.expires_after(connPolicy->retryIntervalSecs); 4783d36e3a5SEd Tanous timer.async_wait(std::bind_front(&ConnectionInfo::onTimerDone, this, 4793d36e3a5SEd Tanous shared_from_this())); 4803d36e3a5SEd Tanous } 4813d36e3a5SEd Tanous 4823d36e3a5SEd Tanous void onTimerDone(const std::shared_ptr<ConnectionInfo>& /*self*/, 4833d36e3a5SEd Tanous const boost::system::error_code& ec) 4843d36e3a5SEd Tanous { 4856eaa1d2fSSunitha Harish if (ec == boost::asio::error::operation_aborted) 4866eaa1d2fSSunitha Harish { 48762598e31SEd Tanous BMCWEB_LOG_DEBUG( 48862598e31SEd Tanous "async_wait failed since the operation is aborted{}", 48962598e31SEd Tanous ec.message()); 4906eaa1d2fSSunitha Harish } 4916eaa1d2fSSunitha Harish else if (ec) 4926eaa1d2fSSunitha Harish { 49362598e31SEd Tanous BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message()); 4946eaa1d2fSSunitha Harish // Ignore the error and continue the retry loop to attempt 4956eaa1d2fSSunitha Harish // sending the event as per the retry policy 4966eaa1d2fSSunitha Harish } 4976eaa1d2fSSunitha Harish 498f52c03c1SCarson Labrado // Let's close the connection and restart from resolve. 499f3cb5df9SAbhilash Raju shutdownConn(true); 500f3cb5df9SAbhilash Raju } 501f3cb5df9SAbhilash Raju 502f3cb5df9SAbhilash Raju void restartConnection() 503f3cb5df9SAbhilash Raju { 504f3cb5df9SAbhilash Raju BMCWEB_LOG_DEBUG("{}, id: {} restartConnection", host, 505f3cb5df9SAbhilash Raju std::to_string(connId)); 506f3cb5df9SAbhilash Raju initializeConnection(host.scheme() == "https"); 507f3cb5df9SAbhilash Raju doResolve(); 5082a5689a7SAppaRao Puli } 5092a5689a7SAppaRao Puli 510e38778a5SAppaRao Puli void shutdownConn(bool retry) 511fe44eb0bSAyushi Smriti { 512f52c03c1SCarson Labrado boost::beast::error_code ec; 5130d5f5cf4SEd Tanous conn.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); 514f52c03c1SCarson Labrado conn.close(); 515f52c03c1SCarson Labrado 516f52c03c1SCarson Labrado // not_connected happens sometimes so don't bother reporting it. 517f52c03c1SCarson Labrado if (ec && ec != boost::beast::errc::not_connected) 5182a5689a7SAppaRao Puli { 519a716aa74SEd Tanous BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId, 52062598e31SEd Tanous ec.message()); 5216eaa1d2fSSunitha Harish } 5225cab68f3SCarson Labrado else 5235cab68f3SCarson Labrado { 524a716aa74SEd Tanous BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId); 5255cab68f3SCarson Labrado } 526ca723762SEd Tanous 527e38778a5SAppaRao Puli if (retry) 52892a74e56SAppaRao Puli { 529f52c03c1SCarson Labrado // Now let's try to resend the data 530f52c03c1SCarson Labrado state = ConnState::retry; 531f3cb5df9SAbhilash Raju restartConnection(); 532e38778a5SAppaRao Puli } 533e38778a5SAppaRao Puli else 534e38778a5SAppaRao Puli { 535e38778a5SAppaRao Puli state = ConnState::closed; 536e38778a5SAppaRao Puli } 537e38778a5SAppaRao Puli } 538e38778a5SAppaRao Puli 539e38778a5SAppaRao Puli void doClose(bool retry = false) 540e38778a5SAppaRao Puli { 541e38778a5SAppaRao Puli if (!sslConn) 542e38778a5SAppaRao Puli { 543e38778a5SAppaRao Puli shutdownConn(retry); 544e38778a5SAppaRao Puli return; 545e38778a5SAppaRao Puli } 546e38778a5SAppaRao Puli 547e38778a5SAppaRao Puli sslConn->async_shutdown( 548e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterSslShutdown, this, 549e38778a5SAppaRao Puli shared_from_this(), retry)); 550e38778a5SAppaRao Puli } 551e38778a5SAppaRao Puli 552e38778a5SAppaRao Puli void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/, 553e38778a5SAppaRao Puli bool retry, const boost::system::error_code& ec) 554e38778a5SAppaRao Puli { 555e38778a5SAppaRao Puli if (ec) 556e38778a5SAppaRao Puli { 557a716aa74SEd Tanous BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId, 55862598e31SEd Tanous ec.message()); 559e38778a5SAppaRao Puli } 560e38778a5SAppaRao Puli else 561e38778a5SAppaRao Puli { 562a716aa74SEd Tanous BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId); 563e38778a5SAppaRao Puli } 564e38778a5SAppaRao Puli shutdownConn(retry); 565e38778a5SAppaRao Puli } 566e38778a5SAppaRao Puli 567e38778a5SAppaRao Puli void setCipherSuiteTLSext() 568e38778a5SAppaRao Puli { 569e38778a5SAppaRao Puli if (!sslConn) 570e38778a5SAppaRao Puli { 571e38778a5SAppaRao Puli return; 572e38778a5SAppaRao Puli } 573e7c2991eSRavi Teja 574e7c2991eSRavi Teja if (host.host_type() != boost::urls::host_type::name) 575e7c2991eSRavi Teja { 576e7c2991eSRavi Teja // Avoid setting SNI hostname if its IP address 577e7c2991eSRavi Teja return; 578e7c2991eSRavi Teja } 579e7c2991eSRavi Teja // Create a null terminated string for SSL 580a716aa74SEd Tanous std::string hostname(host.encoded_host_address()); 581e38778a5SAppaRao Puli // NOTE: The SSL_set_tlsext_host_name is defined in tlsv1.h header 582e38778a5SAppaRao Puli // file but its having old style casting (name is cast to void*). 583e38778a5SAppaRao Puli // Since bmcweb compiler treats all old-style-cast as error, its 584e38778a5SAppaRao Puli // causing the build failure. So replaced the same macro inline and 585e38778a5SAppaRao Puli // did corrected the code by doing static_cast to viod*. This has to 586e38778a5SAppaRao Puli // be fixed in openssl library in long run. Set SNI Hostname (many 587e38778a5SAppaRao Puli // hosts need this to handshake successfully) 588e38778a5SAppaRao Puli if (SSL_ctrl(sslConn->native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME, 589e38778a5SAppaRao Puli TLSEXT_NAMETYPE_host_name, 590a716aa74SEd Tanous static_cast<void*>(hostname.data())) == 0) 591e38778a5SAppaRao Puli 592e38778a5SAppaRao Puli { 593e38778a5SAppaRao Puli boost::beast::error_code ec{static_cast<int>(::ERR_get_error()), 594e38778a5SAppaRao Puli boost::asio::error::get_ssl_category()}; 595e38778a5SAppaRao Puli 596a716aa74SEd Tanous BMCWEB_LOG_ERROR("SSL_set_tlsext_host_name {}, id: {} failed: {}", 597a716aa74SEd Tanous host, connId, ec.message()); 598e38778a5SAppaRao Puli // Set state as sslInit failed so that we close the connection 599e38778a5SAppaRao Puli // and take appropriate action as per retry configuration. 600e38778a5SAppaRao Puli state = ConnState::sslInitFailed; 601e38778a5SAppaRao Puli waitAndRetry(); 602e38778a5SAppaRao Puli return; 603e38778a5SAppaRao Puli } 604bd030d0aSAppaRao Puli } 605bd030d0aSAppaRao Puli 606f3cb5df9SAbhilash Raju void initializeConnection(bool ssl) 607e38778a5SAppaRao Puli { 608f3cb5df9SAbhilash Raju conn = boost::asio::ip::tcp::socket(ioc); 609f3cb5df9SAbhilash Raju if (ssl) 610e38778a5SAppaRao Puli { 611e38778a5SAppaRao Puli std::optional<boost::asio::ssl::context> sslCtx = 612e38778a5SAppaRao Puli ensuressl::getSSLClientContext(); 613e38778a5SAppaRao Puli 614e38778a5SAppaRao Puli if (!sslCtx) 615e38778a5SAppaRao Puli { 616a716aa74SEd Tanous BMCWEB_LOG_ERROR("prepareSSLContext failed - {}, id: {}", host, 617a716aa74SEd Tanous connId); 618e38778a5SAppaRao Puli // Don't retry if failure occurs while preparing SSL context 61927b0cf90SEd Tanous // such as certificate is invalid or set cipher failure or 62027b0cf90SEd Tanous // set host name failure etc... Setting conn state to 62127b0cf90SEd Tanous // sslInitFailed and connection state will be transitioned 62227b0cf90SEd Tanous // to next state depending on retry policy set by 62327b0cf90SEd Tanous // subscription. 624e38778a5SAppaRao Puli state = ConnState::sslInitFailed; 625e38778a5SAppaRao Puli waitAndRetry(); 626e38778a5SAppaRao Puli return; 627e38778a5SAppaRao Puli } 628e38778a5SAppaRao Puli sslConn.emplace(conn, *sslCtx); 629e38778a5SAppaRao Puli setCipherSuiteTLSext(); 630e38778a5SAppaRao Puli } 631e38778a5SAppaRao Puli } 632f3cb5df9SAbhilash Raju 633f3cb5df9SAbhilash Raju public: 634f3cb5df9SAbhilash Raju explicit ConnectionInfo( 635f3cb5df9SAbhilash Raju boost::asio::io_context& iocIn, const std::string& idIn, 636f3cb5df9SAbhilash Raju const std::shared_ptr<ConnectionPolicy>& connPolicyIn, 6374a7fbefdSEd Tanous const boost::urls::url_view_base& hostIn, unsigned int connIdIn) : 638f3cb5df9SAbhilash Raju subId(idIn), 639f3cb5df9SAbhilash Raju connPolicy(connPolicyIn), host(hostIn), connId(connIdIn), ioc(iocIn), 640f3cb5df9SAbhilash Raju resolver(iocIn), conn(iocIn), timer(iocIn) 641f3cb5df9SAbhilash Raju { 642f3cb5df9SAbhilash Raju initializeConnection(host.scheme() == "https"); 643f3cb5df9SAbhilash Raju } 644f52c03c1SCarson Labrado }; 645bd030d0aSAppaRao Puli 646f52c03c1SCarson Labrado class ConnectionPool : public std::enable_shared_from_this<ConnectionPool> 647bd030d0aSAppaRao Puli { 648f52c03c1SCarson Labrado private: 649f52c03c1SCarson Labrado boost::asio::io_context& ioc; 650e38778a5SAppaRao Puli std::string id; 651d14a48ffSCarson Labrado std::shared_ptr<ConnectionPolicy> connPolicy; 652a716aa74SEd Tanous boost::urls::url destIP; 653f52c03c1SCarson Labrado std::vector<std::shared_ptr<ConnectionInfo>> connections; 654f52c03c1SCarson Labrado boost::container::devector<PendingRequest> requestQueue; 655f52c03c1SCarson Labrado 656f52c03c1SCarson Labrado friend class HttpClient; 657f52c03c1SCarson Labrado 658244256ccSCarson Labrado // Configure a connections's request, callback, and retry info in 659244256ccSCarson Labrado // preparation to begin sending the request 660f52c03c1SCarson Labrado void setConnProps(ConnectionInfo& conn) 661bd030d0aSAppaRao Puli { 662f52c03c1SCarson Labrado if (requestQueue.empty()) 663f52c03c1SCarson Labrado { 66462598e31SEd Tanous BMCWEB_LOG_ERROR( 66562598e31SEd Tanous "setConnProps() should not have been called when requestQueue is empty"); 666bd030d0aSAppaRao Puli return; 667bd030d0aSAppaRao Puli } 668bd030d0aSAppaRao Puli 66952e31629SEd Tanous PendingRequest& nextReq = requestQueue.front(); 670244256ccSCarson Labrado conn.req = std::move(nextReq.req); 671244256ccSCarson Labrado conn.callback = std::move(nextReq.callback); 672f52c03c1SCarson Labrado 673a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Setting properties for connection {}, id: {}", 674a716aa74SEd Tanous conn.host, conn.connId); 675f52c03c1SCarson Labrado 676f52c03c1SCarson Labrado // We can remove the request from the queue at this point 677f52c03c1SCarson Labrado requestQueue.pop_front(); 678f52c03c1SCarson Labrado } 679f52c03c1SCarson Labrado 680f52c03c1SCarson Labrado // Gets called as part of callback after request is sent 681f52c03c1SCarson Labrado // Reuses the connection if there are any requests waiting to be sent 682f52c03c1SCarson Labrado // Otherwise closes the connection if it is not a keep-alive 683f52c03c1SCarson Labrado void sendNext(bool keepAlive, uint32_t connId) 684f52c03c1SCarson Labrado { 685f52c03c1SCarson Labrado auto conn = connections[connId]; 68646a81465SCarson Labrado 68746a81465SCarson Labrado // Allow the connection's handler to be deleted 68846a81465SCarson Labrado // This is needed because of Redfish Aggregation passing an 68946a81465SCarson Labrado // AsyncResponse shared_ptr to this callback 69046a81465SCarson Labrado conn->callback = nullptr; 69146a81465SCarson Labrado 692f52c03c1SCarson Labrado // Reuse the connection to send the next request in the queue 693f52c03c1SCarson Labrado if (!requestQueue.empty()) 694f52c03c1SCarson Labrado { 69562598e31SEd Tanous BMCWEB_LOG_DEBUG( 6968ece0e45SEd Tanous "{} requests remaining in queue for {}, reusing connection {}", 697a716aa74SEd Tanous requestQueue.size(), destIP, connId); 698f52c03c1SCarson Labrado 699f52c03c1SCarson Labrado setConnProps(*conn); 700f52c03c1SCarson Labrado 701f52c03c1SCarson Labrado if (keepAlive) 702f52c03c1SCarson Labrado { 703f52c03c1SCarson Labrado conn->sendMessage(); 7042a5689a7SAppaRao Puli } 7052a5689a7SAppaRao Puli else 7062a5689a7SAppaRao Puli { 707f52c03c1SCarson Labrado // Server is not keep-alive enabled so we need to close the 708f52c03c1SCarson Labrado // connection and then start over from resolve 709f52c03c1SCarson Labrado conn->doClose(); 710f52c03c1SCarson Labrado conn->doResolve(); 711f52c03c1SCarson Labrado } 712f52c03c1SCarson Labrado return; 713f52c03c1SCarson Labrado } 714f52c03c1SCarson Labrado 715f52c03c1SCarson Labrado // No more messages to send so close the connection if necessary 716f52c03c1SCarson Labrado if (keepAlive) 717f52c03c1SCarson Labrado { 718f52c03c1SCarson Labrado conn->state = ConnState::idle; 719f52c03c1SCarson Labrado } 720f52c03c1SCarson Labrado else 721f52c03c1SCarson Labrado { 722f52c03c1SCarson Labrado // Abort the connection since server is not keep-alive enabled 723f52c03c1SCarson Labrado conn->state = ConnState::abortConnection; 724f52c03c1SCarson Labrado conn->doClose(); 7252a5689a7SAppaRao Puli } 726bd030d0aSAppaRao Puli } 727bd030d0aSAppaRao Puli 7284a7fbefdSEd Tanous void sendData(std::string&& data, const boost::urls::url_view_base& destUri, 729244256ccSCarson Labrado const boost::beast::http::fields& httpHeader, 730244256ccSCarson Labrado const boost::beast::http::verb verb, 7316b3db60dSEd Tanous const std::function<void(Response&)>& resHandler) 732fe44eb0bSAyushi Smriti { 733244256ccSCarson Labrado // Construct the request to be sent 734b2896149SEd Tanous boost::beast::http::request<bmcweb::HttpBody> thisReq( 735a716aa74SEd Tanous verb, destUri.encoded_target(), 11, "", httpHeader); 736a716aa74SEd Tanous thisReq.set(boost::beast::http::field::host, 737a716aa74SEd Tanous destUri.encoded_host_address()); 738244256ccSCarson Labrado thisReq.keep_alive(true); 73952e31629SEd Tanous thisReq.body().str() = std::move(data); 740244256ccSCarson Labrado thisReq.prepare_payload(); 7413d36e3a5SEd Tanous auto cb = std::bind_front(&ConnectionPool::afterSendData, 7423d36e3a5SEd Tanous weak_from_this(), resHandler); 743f52c03c1SCarson Labrado // Reuse an existing connection if one is available 744f52c03c1SCarson Labrado for (unsigned int i = 0; i < connections.size(); i++) 745fe44eb0bSAyushi Smriti { 746f52c03c1SCarson Labrado auto conn = connections[i]; 747f52c03c1SCarson Labrado if ((conn->state == ConnState::idle) || 748f52c03c1SCarson Labrado (conn->state == ConnState::initialized) || 749f52c03c1SCarson Labrado (conn->state == ConnState::closed)) 750f52c03c1SCarson Labrado { 751244256ccSCarson Labrado conn->req = std::move(thisReq); 752f52c03c1SCarson Labrado conn->callback = std::move(cb); 753a716aa74SEd Tanous std::string commonMsg = std::format("{} from pool {}", i, id); 754f52c03c1SCarson Labrado 755f52c03c1SCarson Labrado if (conn->state == ConnState::idle) 756f52c03c1SCarson Labrado { 75762598e31SEd Tanous BMCWEB_LOG_DEBUG("Grabbing idle connection {}", commonMsg); 758f52c03c1SCarson Labrado conn->sendMessage(); 759f52c03c1SCarson Labrado } 760f52c03c1SCarson Labrado else 761f52c03c1SCarson Labrado { 76262598e31SEd Tanous BMCWEB_LOG_DEBUG("Reusing existing connection {}", 76362598e31SEd Tanous commonMsg); 764f52c03c1SCarson Labrado conn->doResolve(); 765f52c03c1SCarson Labrado } 766f52c03c1SCarson Labrado return; 767f52c03c1SCarson Labrado } 768f52c03c1SCarson Labrado } 769f52c03c1SCarson Labrado 77027b0cf90SEd Tanous // All connections in use so create a new connection or add request 77127b0cf90SEd Tanous // to the queue 772d14a48ffSCarson Labrado if (connections.size() < connPolicy->maxConnections) 773f52c03c1SCarson Labrado { 774a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Adding new connection to pool {}", id); 775f52c03c1SCarson Labrado auto conn = addConnection(); 776244256ccSCarson Labrado conn->req = std::move(thisReq); 777f52c03c1SCarson Labrado conn->callback = std::move(cb); 778f52c03c1SCarson Labrado conn->doResolve(); 779f52c03c1SCarson Labrado } 780f52c03c1SCarson Labrado else if (requestQueue.size() < maxRequestQueueSize) 781f52c03c1SCarson Labrado { 782a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Max pool size reached. Adding data to queue {}", 783a716aa74SEd Tanous id); 784d14a48ffSCarson Labrado requestQueue.emplace_back(std::move(thisReq), std::move(cb)); 785f52c03c1SCarson Labrado } 786f52c03c1SCarson Labrado else 787f52c03c1SCarson Labrado { 78827b0cf90SEd Tanous // If we can't buffer the request then we should let the 78927b0cf90SEd Tanous // callback handle a 429 Too Many Requests dummy response 7906ea90760SEd Tanous BMCWEB_LOG_ERROR("{} request queue full. Dropping request.", id); 79143e14d38SCarson Labrado Response dummyRes; 79243e14d38SCarson Labrado dummyRes.result(boost::beast::http::status::too_many_requests); 79343e14d38SCarson Labrado resHandler(dummyRes); 794f52c03c1SCarson Labrado } 795f52c03c1SCarson Labrado } 796f52c03c1SCarson Labrado 7973d36e3a5SEd Tanous // Callback to be called once the request has been sent 7983d36e3a5SEd Tanous static void afterSendData(const std::weak_ptr<ConnectionPool>& weakSelf, 7993d36e3a5SEd Tanous const std::function<void(Response&)>& resHandler, 8003d36e3a5SEd Tanous bool keepAlive, uint32_t connId, Response& res) 8013d36e3a5SEd Tanous { 8023d36e3a5SEd Tanous // Allow provided callback to perform additional processing of the 8033d36e3a5SEd Tanous // request 8043d36e3a5SEd Tanous resHandler(res); 8053d36e3a5SEd Tanous 8063d36e3a5SEd Tanous // If requests remain in the queue then we want to reuse this 8073d36e3a5SEd Tanous // connection to send the next request 8083d36e3a5SEd Tanous std::shared_ptr<ConnectionPool> self = weakSelf.lock(); 8093d36e3a5SEd Tanous if (!self) 8103d36e3a5SEd Tanous { 81162598e31SEd Tanous BMCWEB_LOG_CRITICAL("{} Failed to capture connection", 81262598e31SEd Tanous logPtr(self.get())); 8133d36e3a5SEd Tanous return; 8143d36e3a5SEd Tanous } 8153d36e3a5SEd Tanous 8163d36e3a5SEd Tanous self->sendNext(keepAlive, connId); 8173d36e3a5SEd Tanous } 8183d36e3a5SEd Tanous 819f52c03c1SCarson Labrado std::shared_ptr<ConnectionInfo>& addConnection() 820f52c03c1SCarson Labrado { 821f52c03c1SCarson Labrado unsigned int newId = static_cast<unsigned int>(connections.size()); 822f52c03c1SCarson Labrado 823e38778a5SAppaRao Puli auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>( 824a716aa74SEd Tanous ioc, id, connPolicy, destIP, newId)); 825f52c03c1SCarson Labrado 826a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Added connection {} to pool {}", 827a716aa74SEd Tanous connections.size() - 1, id); 828f52c03c1SCarson Labrado 829f52c03c1SCarson Labrado return ret; 830f52c03c1SCarson Labrado } 831f52c03c1SCarson Labrado 832f52c03c1SCarson Labrado public: 833d14a48ffSCarson Labrado explicit ConnectionPool( 834d14a48ffSCarson Labrado boost::asio::io_context& iocIn, const std::string& idIn, 835d14a48ffSCarson Labrado const std::shared_ptr<ConnectionPolicy>& connPolicyIn, 8364a7fbefdSEd Tanous const boost::urls::url_view_base& destIPIn) : 8378a592810SEd Tanous ioc(iocIn), 838a716aa74SEd Tanous id(idIn), connPolicy(connPolicyIn), destIP(destIPIn) 839f52c03c1SCarson Labrado { 840a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Initializing connection pool for {}", id); 841f52c03c1SCarson Labrado 842f52c03c1SCarson Labrado // Initialize the pool with a single connection 843f52c03c1SCarson Labrado addConnection(); 844fe44eb0bSAyushi Smriti } 845bd030d0aSAppaRao Puli }; 846bd030d0aSAppaRao Puli 847f52c03c1SCarson Labrado class HttpClient 848f52c03c1SCarson Labrado { 849f52c03c1SCarson Labrado private: 850f52c03c1SCarson Labrado std::unordered_map<std::string, std::shared_ptr<ConnectionPool>> 851f52c03c1SCarson Labrado connectionPools; 852f8ca6d79SEd Tanous boost::asio::io_context& ioc; 853d14a48ffSCarson Labrado std::shared_ptr<ConnectionPolicy> connPolicy; 854f52c03c1SCarson Labrado 855039a47e3SCarson Labrado // Used as a dummy callback by sendData() in order to call 856039a47e3SCarson Labrado // sendDataWithCallback() 85702cad96eSEd Tanous static void genericResHandler(const Response& res) 858039a47e3SCarson Labrado { 85962598e31SEd Tanous BMCWEB_LOG_DEBUG("Response handled with return code: {}", 860a716aa74SEd Tanous res.resultInt()); 8614ee8e211SEd Tanous } 862039a47e3SCarson Labrado 863f52c03c1SCarson Labrado public: 864d14a48ffSCarson Labrado HttpClient() = delete; 865f8ca6d79SEd Tanous explicit HttpClient(boost::asio::io_context& iocIn, 866f8ca6d79SEd Tanous const std::shared_ptr<ConnectionPolicy>& connPolicyIn) : 867f8ca6d79SEd Tanous ioc(iocIn), 868d14a48ffSCarson Labrado connPolicy(connPolicyIn) 869d14a48ffSCarson Labrado {} 870f8ca6d79SEd Tanous 871f52c03c1SCarson Labrado HttpClient(const HttpClient&) = delete; 872f52c03c1SCarson Labrado HttpClient& operator=(const HttpClient&) = delete; 873f52c03c1SCarson Labrado HttpClient(HttpClient&&) = delete; 874f52c03c1SCarson Labrado HttpClient& operator=(HttpClient&&) = delete; 875f52c03c1SCarson Labrado ~HttpClient() = default; 876f52c03c1SCarson Labrado 877a716aa74SEd Tanous // Send a request to destIP where additional processing of the 878039a47e3SCarson Labrado // result is not required 8794a7fbefdSEd Tanous void sendData(std::string&& data, const boost::urls::url_view_base& destUri, 880f52c03c1SCarson Labrado const boost::beast::http::fields& httpHeader, 881d14a48ffSCarson Labrado const boost::beast::http::verb verb) 882f52c03c1SCarson Labrado { 883e38778a5SAppaRao Puli const std::function<void(Response&)> cb = genericResHandler; 884a716aa74SEd Tanous sendDataWithCallback(std::move(data), destUri, httpHeader, verb, cb); 885039a47e3SCarson Labrado } 886039a47e3SCarson Labrado 887a716aa74SEd Tanous // Send request to destIP and use the provided callback to 888039a47e3SCarson Labrado // handle the response 8894a7fbefdSEd Tanous void sendDataWithCallback(std::string&& data, 8904a7fbefdSEd Tanous const boost::urls::url_view_base& destUrl, 891039a47e3SCarson Labrado const boost::beast::http::fields& httpHeader, 892244256ccSCarson Labrado const boost::beast::http::verb verb, 8936b3db60dSEd Tanous const std::function<void(Response&)>& resHandler) 894039a47e3SCarson Labrado { 895a716aa74SEd Tanous std::string clientKey = std::format("{}://{}", destUrl.scheme(), 896a716aa74SEd Tanous destUrl.encoded_host_and_port()); 897d14a48ffSCarson Labrado auto pool = connectionPools.try_emplace(clientKey); 898d14a48ffSCarson Labrado if (pool.first->second == nullptr) 899f52c03c1SCarson Labrado { 900d14a48ffSCarson Labrado pool.first->second = std::make_shared<ConnectionPool>( 901a716aa74SEd Tanous ioc, clientKey, connPolicy, destUrl); 902f52c03c1SCarson Labrado } 90327b0cf90SEd Tanous // Send the data using either the existing connection pool or the 90427b0cf90SEd Tanous // newly created connection pool 905a716aa74SEd Tanous pool.first->second->sendData(std::move(data), destUrl, httpHeader, verb, 906e38778a5SAppaRao Puli resHandler); 907f52c03c1SCarson Labrado } 908f52c03c1SCarson Labrado }; 909bd030d0aSAppaRao Puli } // namespace crow 910