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" 1977665bdaSNan Zhou #include "http_response.hpp" 203ccb3adbSEd Tanous #include "logging.hpp" 213ccb3adbSEd Tanous #include "ssl_key_handler.hpp" 2277665bdaSNan Zhou 230d5f5cf4SEd Tanous #include <boost/asio/connect.hpp> 24bb49eb5cSEd Tanous #include <boost/asio/io_context.hpp> 2529a82b08SSunitha Harish #include <boost/asio/ip/address.hpp> 2629a82b08SSunitha Harish #include <boost/asio/ip/basic_endpoint.hpp> 27bb49eb5cSEd Tanous #include <boost/asio/ip/tcp.hpp> 28e38778a5SAppaRao Puli #include <boost/asio/ssl/context.hpp> 29e38778a5SAppaRao Puli #include <boost/asio/ssl/error.hpp> 30d43cd0caSEd Tanous #include <boost/asio/steady_timer.hpp> 31d43cd0caSEd Tanous #include <boost/beast/core/flat_buffer.hpp> 32bb49eb5cSEd Tanous #include <boost/beast/core/flat_static_buffer.hpp> 33d43cd0caSEd Tanous #include <boost/beast/http/message.hpp> 34bb49eb5cSEd Tanous #include <boost/beast/http/parser.hpp> 35bb49eb5cSEd Tanous #include <boost/beast/http/read.hpp> 36bb49eb5cSEd Tanous #include <boost/beast/http/write.hpp> 37e38778a5SAppaRao Puli #include <boost/beast/ssl/ssl_stream.hpp> 38bd030d0aSAppaRao Puli #include <boost/beast/version.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> 43a716aa74SEd Tanous #include <boost/url/url_view.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 { 119*52e31629SEd Tanous boost::beast::http::request<bmcweb::FileBody> req; 120039a47e3SCarson Labrado std::function<void(bool, uint32_t, Response&)> callback; 121039a47e3SCarson Labrado PendingRequest( 122*52e31629SEd Tanous boost::beast::http::request<bmcweb::FileBody>&& 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 141*52e31629SEd Tanous http::request<bmcweb::FileBody> req; 142*52e31629SEd Tanous using parser_type = http::response_parser<bmcweb::FileBody>; 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 152f8ca6d79SEd Tanous #ifdef BMCWEB_DBUS_DNS_RESOLVER 153e1452beaSEd Tanous using Resolver = async_resolve::Resolver; 154f8ca6d79SEd Tanous #else 155f8ca6d79SEd Tanous using Resolver = boost::asio::ip::tcp::resolver; 156f8ca6d79SEd Tanous #endif 157f8ca6d79SEd Tanous Resolver resolver; 158f8ca6d79SEd Tanous 1590d5f5cf4SEd Tanous boost::asio::ip::tcp::socket conn; 1600d5f5cf4SEd Tanous std::optional<boost::beast::ssl_stream<boost::asio::ip::tcp::socket&>> 1610d5f5cf4SEd Tanous sslConn; 162e38778a5SAppaRao Puli 163f52c03c1SCarson Labrado boost::asio::steady_timer timer; 16484b35604SEd Tanous 165f52c03c1SCarson Labrado friend class ConnectionPool; 166bd030d0aSAppaRao Puli 16729a82b08SSunitha Harish void doResolve() 16829a82b08SSunitha Harish { 16929a82b08SSunitha Harish state = ConnState::resolveInProgress; 170a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Trying to resolve: {}, id: {}", host, connId); 17129a82b08SSunitha Harish 172a716aa74SEd Tanous resolver.async_resolve(host.encoded_host_address(), host.port(), 1733d36e3a5SEd Tanous std::bind_front(&ConnectionInfo::afterResolve, 1743d36e3a5SEd Tanous this, shared_from_this())); 1753d36e3a5SEd Tanous } 1763d36e3a5SEd Tanous 177f8ca6d79SEd Tanous void afterResolve(const std::shared_ptr<ConnectionInfo>& /*self*/, 178f8ca6d79SEd Tanous const boost::system::error_code& ec, 179f8ca6d79SEd Tanous const Resolver::results_type& endpointList) 1803d36e3a5SEd Tanous { 18126f6976fSEd Tanous if (ec || (endpointList.empty())) 18229a82b08SSunitha Harish { 183a716aa74SEd Tanous BMCWEB_LOG_ERROR("Resolve failed: {} {}", ec.message(), host); 1843d36e3a5SEd Tanous state = ConnState::resolveFailed; 1853d36e3a5SEd Tanous waitAndRetry(); 18629a82b08SSunitha Harish return; 18729a82b08SSunitha Harish } 188a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Resolved {}, id: {}", host, connId); 1892a5689a7SAppaRao Puli state = ConnState::connectInProgress; 1902a5689a7SAppaRao Puli 191a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Trying to connect to: {}, id: {}", host, connId); 192b00dcc27SEd Tanous 1930d5f5cf4SEd Tanous timer.expires_after(std::chrono::seconds(30)); 1940d5f5cf4SEd Tanous timer.async_wait(std::bind_front(onTimeout, weak_from_this())); 1950d5f5cf4SEd Tanous 1960d5f5cf4SEd Tanous boost::asio::async_connect( 1970d5f5cf4SEd Tanous conn, endpointList, 198e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterConnect, this, 199e38778a5SAppaRao Puli shared_from_this())); 200e38778a5SAppaRao Puli } 201e38778a5SAppaRao Puli 202e38778a5SAppaRao Puli void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/, 20381c4e330SEd Tanous const boost::beast::error_code& ec, 204e38778a5SAppaRao Puli const boost::asio::ip::tcp::endpoint& endpoint) 205e38778a5SAppaRao Puli { 206513d1ffcSCarson Labrado // The operation already timed out. We don't want do continue down 207513d1ffcSCarson Labrado // this branch 208513d1ffcSCarson Labrado if (ec && ec == boost::asio::error::operation_aborted) 209513d1ffcSCarson Labrado { 210513d1ffcSCarson Labrado return; 211513d1ffcSCarson Labrado } 212513d1ffcSCarson Labrado 2130d5f5cf4SEd Tanous timer.cancel(); 2142a5689a7SAppaRao Puli if (ec) 2152a5689a7SAppaRao Puli { 21662598e31SEd Tanous BMCWEB_LOG_ERROR("Connect {}:{}, id: {} failed: {}", 217a716aa74SEd Tanous endpoint.address().to_string(), endpoint.port(), 218a716aa74SEd Tanous connId, ec.message()); 219e38778a5SAppaRao Puli state = ConnState::connectFailed; 220e38778a5SAppaRao Puli waitAndRetry(); 2212a5689a7SAppaRao Puli return; 2222a5689a7SAppaRao Puli } 223a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Connected to: {}:{}, id: {}", 224a716aa74SEd Tanous endpoint.address().to_string(), endpoint.port(), 225a716aa74SEd Tanous connId); 226e38778a5SAppaRao Puli if (sslConn) 227e38778a5SAppaRao Puli { 2280d5f5cf4SEd Tanous doSslHandshake(); 229e38778a5SAppaRao Puli return; 230e38778a5SAppaRao Puli } 231e38778a5SAppaRao Puli state = ConnState::connected; 232e38778a5SAppaRao Puli sendMessage(); 233e38778a5SAppaRao Puli } 234e38778a5SAppaRao Puli 2350d5f5cf4SEd Tanous void doSslHandshake() 236e38778a5SAppaRao Puli { 237e38778a5SAppaRao Puli if (!sslConn) 238e38778a5SAppaRao Puli { 239e38778a5SAppaRao Puli return; 240e38778a5SAppaRao Puli } 241e38778a5SAppaRao Puli state = ConnState::handshakeInProgress; 2420d5f5cf4SEd Tanous timer.expires_after(std::chrono::seconds(30)); 2430d5f5cf4SEd Tanous timer.async_wait(std::bind_front(onTimeout, weak_from_this())); 244e38778a5SAppaRao Puli sslConn->async_handshake( 245e38778a5SAppaRao Puli boost::asio::ssl::stream_base::client, 246e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterSslHandshake, this, 247e38778a5SAppaRao Puli shared_from_this())); 248e38778a5SAppaRao Puli } 249e38778a5SAppaRao Puli 250e38778a5SAppaRao Puli void afterSslHandshake(const std::shared_ptr<ConnectionInfo>& /*self*/, 25181c4e330SEd Tanous const boost::beast::error_code& ec) 252e38778a5SAppaRao Puli { 253513d1ffcSCarson Labrado // The operation already timed out. We don't want do continue down 254513d1ffcSCarson Labrado // this branch 255513d1ffcSCarson Labrado if (ec && ec == boost::asio::error::operation_aborted) 256513d1ffcSCarson Labrado { 257513d1ffcSCarson Labrado return; 258513d1ffcSCarson Labrado } 259513d1ffcSCarson Labrado 2600d5f5cf4SEd Tanous timer.cancel(); 261e38778a5SAppaRao Puli if (ec) 262e38778a5SAppaRao Puli { 263a716aa74SEd Tanous BMCWEB_LOG_ERROR("SSL Handshake failed - id: {} error: {}", connId, 264a716aa74SEd Tanous ec.message()); 265e38778a5SAppaRao Puli state = ConnState::handshakeFailed; 266e38778a5SAppaRao Puli waitAndRetry(); 267e38778a5SAppaRao Puli return; 268e38778a5SAppaRao Puli } 269a716aa74SEd Tanous BMCWEB_LOG_DEBUG("SSL Handshake successful - id: {}", connId); 270e38778a5SAppaRao Puli state = ConnState::connected; 271e38778a5SAppaRao Puli sendMessage(); 2722a5689a7SAppaRao Puli } 2732a5689a7SAppaRao Puli 274f52c03c1SCarson Labrado void sendMessage() 2752a5689a7SAppaRao Puli { 2762a5689a7SAppaRao Puli state = ConnState::sendInProgress; 2772a5689a7SAppaRao Puli 278bd030d0aSAppaRao Puli // Set a timeout on the operation 2790d5f5cf4SEd Tanous timer.expires_after(std::chrono::seconds(30)); 2800d5f5cf4SEd Tanous timer.async_wait(std::bind_front(onTimeout, weak_from_this())); 281bd030d0aSAppaRao Puli 282bd030d0aSAppaRao Puli // Send the HTTP request to the remote host 283e38778a5SAppaRao Puli if (sslConn) 284e38778a5SAppaRao Puli { 285e38778a5SAppaRao Puli boost::beast::http::async_write( 286e38778a5SAppaRao Puli *sslConn, req, 287e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterWrite, this, 288e38778a5SAppaRao Puli shared_from_this())); 289e38778a5SAppaRao Puli } 290e38778a5SAppaRao Puli else 291e38778a5SAppaRao Puli { 292bd030d0aSAppaRao Puli boost::beast::http::async_write( 293bd030d0aSAppaRao Puli conn, req, 294e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterWrite, this, 295e38778a5SAppaRao Puli shared_from_this())); 296e38778a5SAppaRao Puli } 297e38778a5SAppaRao Puli } 298e38778a5SAppaRao Puli 299e38778a5SAppaRao Puli void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/, 300e38778a5SAppaRao Puli const boost::beast::error_code& ec, size_t bytesTransferred) 301e38778a5SAppaRao Puli { 302513d1ffcSCarson Labrado // The operation already timed out. We don't want do continue down 303513d1ffcSCarson Labrado // this branch 304513d1ffcSCarson Labrado if (ec && ec == boost::asio::error::operation_aborted) 305513d1ffcSCarson Labrado { 306513d1ffcSCarson Labrado return; 307513d1ffcSCarson Labrado } 308513d1ffcSCarson Labrado 3090d5f5cf4SEd Tanous timer.cancel(); 310bd030d0aSAppaRao Puli if (ec) 311bd030d0aSAppaRao Puli { 312a716aa74SEd Tanous BMCWEB_LOG_ERROR("sendMessage() failed: {} {}", ec.message(), host); 313e38778a5SAppaRao Puli state = ConnState::sendFailed; 314e38778a5SAppaRao Puli waitAndRetry(); 315bd030d0aSAppaRao Puli return; 316bd030d0aSAppaRao Puli } 31762598e31SEd Tanous BMCWEB_LOG_DEBUG("sendMessage() bytes transferred: {}", 31862598e31SEd Tanous bytesTransferred); 319bd030d0aSAppaRao Puli 320e38778a5SAppaRao Puli recvMessage(); 321bd030d0aSAppaRao Puli } 322bd030d0aSAppaRao Puli 323bd030d0aSAppaRao Puli void recvMessage() 324bd030d0aSAppaRao Puli { 3256eaa1d2fSSunitha Harish state = ConnState::recvInProgress; 3266eaa1d2fSSunitha Harish 327e01d0c36SEd Tanous parser_type& thisParser = parser.emplace(std::piecewise_construct, 328e01d0c36SEd Tanous std::make_tuple()); 329d14a48ffSCarson Labrado 330e01d0c36SEd Tanous thisParser.body_limit(connPolicy->requestByteLimit); 3316eaa1d2fSSunitha Harish 3320d5f5cf4SEd Tanous timer.expires_after(std::chrono::seconds(30)); 3330d5f5cf4SEd Tanous timer.async_wait(std::bind_front(onTimeout, weak_from_this())); 3340d5f5cf4SEd Tanous 335bd030d0aSAppaRao Puli // Receive the HTTP response 336e38778a5SAppaRao Puli if (sslConn) 337e38778a5SAppaRao Puli { 338e38778a5SAppaRao Puli boost::beast::http::async_read( 339e01d0c36SEd Tanous *sslConn, buffer, thisParser, 340e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterRead, this, 341e38778a5SAppaRao Puli shared_from_this())); 342e38778a5SAppaRao Puli } 343e38778a5SAppaRao Puli else 344e38778a5SAppaRao Puli { 345bd030d0aSAppaRao Puli boost::beast::http::async_read( 346e01d0c36SEd Tanous conn, buffer, thisParser, 347e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterRead, this, 348e38778a5SAppaRao Puli shared_from_this())); 349e38778a5SAppaRao Puli } 350e38778a5SAppaRao Puli } 351e38778a5SAppaRao Puli 352e38778a5SAppaRao Puli void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/, 353e38778a5SAppaRao Puli const boost::beast::error_code& ec, 354e38778a5SAppaRao Puli const std::size_t& bytesTransferred) 355e38778a5SAppaRao Puli { 356513d1ffcSCarson Labrado // The operation already timed out. We don't want do continue down 357513d1ffcSCarson Labrado // this branch 358513d1ffcSCarson Labrado if (ec && ec == boost::asio::error::operation_aborted) 359513d1ffcSCarson Labrado { 360513d1ffcSCarson Labrado return; 361513d1ffcSCarson Labrado } 362513d1ffcSCarson Labrado 3630d5f5cf4SEd Tanous timer.cancel(); 364e38778a5SAppaRao Puli if (ec && ec != boost::asio::ssl::error::stream_truncated) 365bd030d0aSAppaRao Puli { 366a716aa74SEd Tanous BMCWEB_LOG_ERROR("recvMessage() failed: {} from {}", ec.message(), 367a716aa74SEd Tanous host); 368e38778a5SAppaRao Puli state = ConnState::recvFailed; 369e38778a5SAppaRao Puli waitAndRetry(); 370bd030d0aSAppaRao Puli return; 371bd030d0aSAppaRao Puli } 37262598e31SEd Tanous BMCWEB_LOG_DEBUG("recvMessage() bytes transferred: {}", 37362598e31SEd Tanous bytesTransferred); 374e01d0c36SEd Tanous if (!parser) 375e01d0c36SEd Tanous { 376e01d0c36SEd Tanous return; 377e01d0c36SEd Tanous } 378*52e31629SEd Tanous BMCWEB_LOG_DEBUG("recvMessage() data: {}", parser->get().body().str()); 379bd030d0aSAppaRao Puli 380e38778a5SAppaRao Puli unsigned int respCode = parser->get().result_int(); 38162598e31SEd Tanous BMCWEB_LOG_DEBUG("recvMessage() Header Response Code: {}", respCode); 3826eaa1d2fSSunitha Harish 383f3cb5df9SAbhilash Raju // Handle the case of stream_truncated. Some servers close the ssl 384f3cb5df9SAbhilash Raju // connection uncleanly, so check to see if we got a full response 385f3cb5df9SAbhilash Raju // before we handle this as an error. 386f3cb5df9SAbhilash Raju if (!parser->is_done()) 387f3cb5df9SAbhilash Raju { 388f3cb5df9SAbhilash Raju state = ConnState::recvFailed; 389f3cb5df9SAbhilash Raju waitAndRetry(); 390f3cb5df9SAbhilash Raju return; 391f3cb5df9SAbhilash Raju } 392f3cb5df9SAbhilash Raju 393a7a80296SCarson Labrado // Make sure the received response code is valid as defined by 394a7a80296SCarson Labrado // the associated retry policy 395d14a48ffSCarson Labrado if (connPolicy->invalidResp(respCode)) 3966eaa1d2fSSunitha Harish { 3976eaa1d2fSSunitha Harish // The listener failed to receive the Sent-Event 39862598e31SEd Tanous BMCWEB_LOG_ERROR( 39962598e31SEd Tanous "recvMessage() Listener Failed to " 400a716aa74SEd Tanous "receive Sent-Event. Header Response Code: {} from {}", 401a716aa74SEd Tanous respCode, host); 402e38778a5SAppaRao Puli state = ConnState::recvFailed; 403e38778a5SAppaRao Puli waitAndRetry(); 4046eaa1d2fSSunitha Harish return; 4056eaa1d2fSSunitha Harish } 406bd030d0aSAppaRao Puli 407f52c03c1SCarson Labrado // Send is successful 408f52c03c1SCarson Labrado // Reset the counter just in case this was after retrying 409e38778a5SAppaRao Puli retryCount = 0; 4106eaa1d2fSSunitha Harish 4116eaa1d2fSSunitha Harish // Keep the connection alive if server supports it 4126eaa1d2fSSunitha Harish // Else close the connection 41362598e31SEd Tanous BMCWEB_LOG_DEBUG("recvMessage() keepalive : {}", parser->keep_alive()); 4146eaa1d2fSSunitha Harish 415039a47e3SCarson Labrado // Copy the response into a Response object so that it can be 416039a47e3SCarson Labrado // processed by the callback function. 41727b0cf90SEd Tanous res.response = parser->release(); 418e38778a5SAppaRao Puli callback(parser->keep_alive(), connId, res); 419513d1ffcSCarson Labrado res.clear(); 420bd030d0aSAppaRao Puli } 421bd030d0aSAppaRao Puli 4220d5f5cf4SEd Tanous static void onTimeout(const std::weak_ptr<ConnectionInfo>& weakSelf, 4235e7e2dc5SEd Tanous const boost::system::error_code& ec) 4240d5f5cf4SEd Tanous { 4250d5f5cf4SEd Tanous if (ec == boost::asio::error::operation_aborted) 4260d5f5cf4SEd Tanous { 42762598e31SEd Tanous BMCWEB_LOG_DEBUG( 42862598e31SEd Tanous "async_wait failed since the operation is aborted"); 4290d5f5cf4SEd Tanous return; 4300d5f5cf4SEd Tanous } 4310d5f5cf4SEd Tanous if (ec) 4320d5f5cf4SEd Tanous { 43362598e31SEd Tanous BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message()); 43427b0cf90SEd Tanous // If the timer fails, we need to close the socket anyway, same 43527b0cf90SEd Tanous // as if it expired. 4360d5f5cf4SEd Tanous } 4370d5f5cf4SEd Tanous std::shared_ptr<ConnectionInfo> self = weakSelf.lock(); 4380d5f5cf4SEd Tanous if (self == nullptr) 4390d5f5cf4SEd Tanous { 4400d5f5cf4SEd Tanous return; 4410d5f5cf4SEd Tanous } 4420d5f5cf4SEd Tanous self->waitAndRetry(); 4430d5f5cf4SEd Tanous } 4440d5f5cf4SEd Tanous 4456eaa1d2fSSunitha Harish void waitAndRetry() 446bd030d0aSAppaRao Puli { 447d14a48ffSCarson Labrado if ((retryCount >= connPolicy->maxRetryAttempts) || 448e38778a5SAppaRao Puli (state == ConnState::sslInitFailed)) 4492a5689a7SAppaRao Puli { 450a716aa74SEd Tanous BMCWEB_LOG_ERROR("Maximum number of retries reached. {}", host); 45162598e31SEd Tanous BMCWEB_LOG_DEBUG("Retry policy: {}", connPolicy->retryPolicyAction); 452039a47e3SCarson Labrado 453d14a48ffSCarson Labrado if (connPolicy->retryPolicyAction == "TerminateAfterRetries") 454fe44eb0bSAyushi Smriti { 455fe44eb0bSAyushi Smriti // TODO: delete subscription 456fe44eb0bSAyushi Smriti state = ConnState::terminated; 457fe44eb0bSAyushi Smriti } 458d14a48ffSCarson Labrado if (connPolicy->retryPolicyAction == "SuspendRetries") 459fe44eb0bSAyushi Smriti { 4602a5689a7SAppaRao Puli state = ConnState::suspended; 4612a5689a7SAppaRao Puli } 462513d1ffcSCarson Labrado 463513d1ffcSCarson Labrado // We want to return a 502 to indicate there was an error with 464513d1ffcSCarson Labrado // the external server 465513d1ffcSCarson Labrado res.result(boost::beast::http::status::bad_gateway); 466513d1ffcSCarson Labrado callback(false, connId, res); 467513d1ffcSCarson Labrado res.clear(); 468513d1ffcSCarson Labrado 46927b0cf90SEd Tanous // Reset the retrycount to zero so that client can try 47027b0cf90SEd Tanous // connecting again if needed 471fe44eb0bSAyushi Smriti retryCount = 0; 4722a5689a7SAppaRao Puli return; 4732a5689a7SAppaRao Puli } 4742a5689a7SAppaRao Puli 4752a5689a7SAppaRao Puli retryCount++; 476fe44eb0bSAyushi Smriti 47762598e31SEd Tanous BMCWEB_LOG_DEBUG("Attempt retry after {} seconds. RetryCount = {}", 478a716aa74SEd Tanous connPolicy->retryIntervalSecs.count(), retryCount); 479d14a48ffSCarson Labrado timer.expires_after(connPolicy->retryIntervalSecs); 4803d36e3a5SEd Tanous timer.async_wait(std::bind_front(&ConnectionInfo::onTimerDone, this, 4813d36e3a5SEd Tanous shared_from_this())); 4823d36e3a5SEd Tanous } 4833d36e3a5SEd Tanous 4843d36e3a5SEd Tanous void onTimerDone(const std::shared_ptr<ConnectionInfo>& /*self*/, 4853d36e3a5SEd Tanous const boost::system::error_code& ec) 4863d36e3a5SEd Tanous { 4876eaa1d2fSSunitha Harish if (ec == boost::asio::error::operation_aborted) 4886eaa1d2fSSunitha Harish { 48962598e31SEd Tanous BMCWEB_LOG_DEBUG( 49062598e31SEd Tanous "async_wait failed since the operation is aborted{}", 49162598e31SEd Tanous ec.message()); 4926eaa1d2fSSunitha Harish } 4936eaa1d2fSSunitha Harish else if (ec) 4946eaa1d2fSSunitha Harish { 49562598e31SEd Tanous BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message()); 4966eaa1d2fSSunitha Harish // Ignore the error and continue the retry loop to attempt 4976eaa1d2fSSunitha Harish // sending the event as per the retry policy 4986eaa1d2fSSunitha Harish } 4996eaa1d2fSSunitha Harish 500f52c03c1SCarson Labrado // Let's close the connection and restart from resolve. 501f3cb5df9SAbhilash Raju shutdownConn(true); 502f3cb5df9SAbhilash Raju } 503f3cb5df9SAbhilash Raju 504f3cb5df9SAbhilash Raju void restartConnection() 505f3cb5df9SAbhilash Raju { 506f3cb5df9SAbhilash Raju BMCWEB_LOG_DEBUG("{}, id: {} restartConnection", host, 507f3cb5df9SAbhilash Raju std::to_string(connId)); 508f3cb5df9SAbhilash Raju initializeConnection(host.scheme() == "https"); 509f3cb5df9SAbhilash Raju doResolve(); 5102a5689a7SAppaRao Puli } 5112a5689a7SAppaRao Puli 512e38778a5SAppaRao Puli void shutdownConn(bool retry) 513fe44eb0bSAyushi Smriti { 514f52c03c1SCarson Labrado boost::beast::error_code ec; 5150d5f5cf4SEd Tanous conn.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); 516f52c03c1SCarson Labrado conn.close(); 517f52c03c1SCarson Labrado 518f52c03c1SCarson Labrado // not_connected happens sometimes so don't bother reporting it. 519f52c03c1SCarson Labrado if (ec && ec != boost::beast::errc::not_connected) 5202a5689a7SAppaRao Puli { 521a716aa74SEd Tanous BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId, 52262598e31SEd Tanous ec.message()); 5236eaa1d2fSSunitha Harish } 5245cab68f3SCarson Labrado else 5255cab68f3SCarson Labrado { 526a716aa74SEd Tanous BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId); 5275cab68f3SCarson Labrado } 528ca723762SEd Tanous 529e38778a5SAppaRao Puli if (retry) 53092a74e56SAppaRao Puli { 531f52c03c1SCarson Labrado // Now let's try to resend the data 532f52c03c1SCarson Labrado state = ConnState::retry; 533f3cb5df9SAbhilash Raju restartConnection(); 534e38778a5SAppaRao Puli } 535e38778a5SAppaRao Puli else 536e38778a5SAppaRao Puli { 537e38778a5SAppaRao Puli state = ConnState::closed; 538e38778a5SAppaRao Puli } 539e38778a5SAppaRao Puli } 540e38778a5SAppaRao Puli 541e38778a5SAppaRao Puli void doClose(bool retry = false) 542e38778a5SAppaRao Puli { 543e38778a5SAppaRao Puli if (!sslConn) 544e38778a5SAppaRao Puli { 545e38778a5SAppaRao Puli shutdownConn(retry); 546e38778a5SAppaRao Puli return; 547e38778a5SAppaRao Puli } 548e38778a5SAppaRao Puli 549e38778a5SAppaRao Puli sslConn->async_shutdown( 550e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterSslShutdown, this, 551e38778a5SAppaRao Puli shared_from_this(), retry)); 552e38778a5SAppaRao Puli } 553e38778a5SAppaRao Puli 554e38778a5SAppaRao Puli void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/, 555e38778a5SAppaRao Puli bool retry, const boost::system::error_code& ec) 556e38778a5SAppaRao Puli { 557e38778a5SAppaRao Puli if (ec) 558e38778a5SAppaRao Puli { 559a716aa74SEd Tanous BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId, 56062598e31SEd Tanous ec.message()); 561e38778a5SAppaRao Puli } 562e38778a5SAppaRao Puli else 563e38778a5SAppaRao Puli { 564a716aa74SEd Tanous BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId); 565e38778a5SAppaRao Puli } 566e38778a5SAppaRao Puli shutdownConn(retry); 567e38778a5SAppaRao Puli } 568e38778a5SAppaRao Puli 569e38778a5SAppaRao Puli void setCipherSuiteTLSext() 570e38778a5SAppaRao Puli { 571e38778a5SAppaRao Puli if (!sslConn) 572e38778a5SAppaRao Puli { 573e38778a5SAppaRao Puli return; 574e38778a5SAppaRao Puli } 575e7c2991eSRavi Teja 576e7c2991eSRavi Teja if (host.host_type() != boost::urls::host_type::name) 577e7c2991eSRavi Teja { 578e7c2991eSRavi Teja // Avoid setting SNI hostname if its IP address 579e7c2991eSRavi Teja return; 580e7c2991eSRavi Teja } 581e7c2991eSRavi Teja // Create a null terminated string for SSL 582a716aa74SEd Tanous std::string hostname(host.encoded_host_address()); 583e38778a5SAppaRao Puli // NOTE: The SSL_set_tlsext_host_name is defined in tlsv1.h header 584e38778a5SAppaRao Puli // file but its having old style casting (name is cast to void*). 585e38778a5SAppaRao Puli // Since bmcweb compiler treats all old-style-cast as error, its 586e38778a5SAppaRao Puli // causing the build failure. So replaced the same macro inline and 587e38778a5SAppaRao Puli // did corrected the code by doing static_cast to viod*. This has to 588e38778a5SAppaRao Puli // be fixed in openssl library in long run. Set SNI Hostname (many 589e38778a5SAppaRao Puli // hosts need this to handshake successfully) 590e38778a5SAppaRao Puli if (SSL_ctrl(sslConn->native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME, 591e38778a5SAppaRao Puli TLSEXT_NAMETYPE_host_name, 592a716aa74SEd Tanous static_cast<void*>(hostname.data())) == 0) 593e38778a5SAppaRao Puli 594e38778a5SAppaRao Puli { 595e38778a5SAppaRao Puli boost::beast::error_code ec{static_cast<int>(::ERR_get_error()), 596e38778a5SAppaRao Puli boost::asio::error::get_ssl_category()}; 597e38778a5SAppaRao Puli 598a716aa74SEd Tanous BMCWEB_LOG_ERROR("SSL_set_tlsext_host_name {}, id: {} failed: {}", 599a716aa74SEd Tanous host, connId, ec.message()); 600e38778a5SAppaRao Puli // Set state as sslInit failed so that we close the connection 601e38778a5SAppaRao Puli // and take appropriate action as per retry configuration. 602e38778a5SAppaRao Puli state = ConnState::sslInitFailed; 603e38778a5SAppaRao Puli waitAndRetry(); 604e38778a5SAppaRao Puli return; 605e38778a5SAppaRao Puli } 606bd030d0aSAppaRao Puli } 607bd030d0aSAppaRao Puli 608f3cb5df9SAbhilash Raju void initializeConnection(bool ssl) 609e38778a5SAppaRao Puli { 610f3cb5df9SAbhilash Raju conn = boost::asio::ip::tcp::socket(ioc); 611f3cb5df9SAbhilash Raju if (ssl) 612e38778a5SAppaRao Puli { 613e38778a5SAppaRao Puli std::optional<boost::asio::ssl::context> sslCtx = 614e38778a5SAppaRao Puli ensuressl::getSSLClientContext(); 615e38778a5SAppaRao Puli 616e38778a5SAppaRao Puli if (!sslCtx) 617e38778a5SAppaRao Puli { 618a716aa74SEd Tanous BMCWEB_LOG_ERROR("prepareSSLContext failed - {}, id: {}", host, 619a716aa74SEd Tanous connId); 620e38778a5SAppaRao Puli // Don't retry if failure occurs while preparing SSL context 62127b0cf90SEd Tanous // such as certificate is invalid or set cipher failure or 62227b0cf90SEd Tanous // set host name failure etc... Setting conn state to 62327b0cf90SEd Tanous // sslInitFailed and connection state will be transitioned 62427b0cf90SEd Tanous // to next state depending on retry policy set by 62527b0cf90SEd Tanous // subscription. 626e38778a5SAppaRao Puli state = ConnState::sslInitFailed; 627e38778a5SAppaRao Puli waitAndRetry(); 628e38778a5SAppaRao Puli return; 629e38778a5SAppaRao Puli } 630e38778a5SAppaRao Puli sslConn.emplace(conn, *sslCtx); 631e38778a5SAppaRao Puli setCipherSuiteTLSext(); 632e38778a5SAppaRao Puli } 633e38778a5SAppaRao Puli } 634f3cb5df9SAbhilash Raju 635f3cb5df9SAbhilash Raju public: 636f3cb5df9SAbhilash Raju explicit ConnectionInfo( 637f3cb5df9SAbhilash Raju boost::asio::io_context& iocIn, const std::string& idIn, 638f3cb5df9SAbhilash Raju const std::shared_ptr<ConnectionPolicy>& connPolicyIn, 639f3cb5df9SAbhilash Raju boost::urls::url_view hostIn, unsigned int connIdIn) : 640f3cb5df9SAbhilash Raju subId(idIn), 641f3cb5df9SAbhilash Raju connPolicy(connPolicyIn), host(hostIn), connId(connIdIn), ioc(iocIn), 642f3cb5df9SAbhilash Raju resolver(iocIn), conn(iocIn), timer(iocIn) 643f3cb5df9SAbhilash Raju { 644f3cb5df9SAbhilash Raju initializeConnection(host.scheme() == "https"); 645f3cb5df9SAbhilash Raju } 646f52c03c1SCarson Labrado }; 647bd030d0aSAppaRao Puli 648f52c03c1SCarson Labrado class ConnectionPool : public std::enable_shared_from_this<ConnectionPool> 649bd030d0aSAppaRao Puli { 650f52c03c1SCarson Labrado private: 651f52c03c1SCarson Labrado boost::asio::io_context& ioc; 652e38778a5SAppaRao Puli std::string id; 653d14a48ffSCarson Labrado std::shared_ptr<ConnectionPolicy> connPolicy; 654a716aa74SEd Tanous boost::urls::url destIP; 655f52c03c1SCarson Labrado std::vector<std::shared_ptr<ConnectionInfo>> connections; 656f52c03c1SCarson Labrado boost::container::devector<PendingRequest> requestQueue; 657f52c03c1SCarson Labrado 658f52c03c1SCarson Labrado friend class HttpClient; 659f52c03c1SCarson Labrado 660244256ccSCarson Labrado // Configure a connections's request, callback, and retry info in 661244256ccSCarson Labrado // preparation to begin sending the request 662f52c03c1SCarson Labrado void setConnProps(ConnectionInfo& conn) 663bd030d0aSAppaRao Puli { 664f52c03c1SCarson Labrado if (requestQueue.empty()) 665f52c03c1SCarson Labrado { 66662598e31SEd Tanous BMCWEB_LOG_ERROR( 66762598e31SEd Tanous "setConnProps() should not have been called when requestQueue is empty"); 668bd030d0aSAppaRao Puli return; 669bd030d0aSAppaRao Puli } 670bd030d0aSAppaRao Puli 671*52e31629SEd Tanous PendingRequest& nextReq = requestQueue.front(); 672244256ccSCarson Labrado conn.req = std::move(nextReq.req); 673244256ccSCarson Labrado conn.callback = std::move(nextReq.callback); 674f52c03c1SCarson Labrado 675a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Setting properties for connection {}, id: {}", 676a716aa74SEd Tanous conn.host, conn.connId); 677f52c03c1SCarson Labrado 678f52c03c1SCarson Labrado // We can remove the request from the queue at this point 679f52c03c1SCarson Labrado requestQueue.pop_front(); 680f52c03c1SCarson Labrado } 681f52c03c1SCarson Labrado 682f52c03c1SCarson Labrado // Gets called as part of callback after request is sent 683f52c03c1SCarson Labrado // Reuses the connection if there are any requests waiting to be sent 684f52c03c1SCarson Labrado // Otherwise closes the connection if it is not a keep-alive 685f52c03c1SCarson Labrado void sendNext(bool keepAlive, uint32_t connId) 686f52c03c1SCarson Labrado { 687f52c03c1SCarson Labrado auto conn = connections[connId]; 68846a81465SCarson Labrado 68946a81465SCarson Labrado // Allow the connection's handler to be deleted 69046a81465SCarson Labrado // This is needed because of Redfish Aggregation passing an 69146a81465SCarson Labrado // AsyncResponse shared_ptr to this callback 69246a81465SCarson Labrado conn->callback = nullptr; 69346a81465SCarson Labrado 694f52c03c1SCarson Labrado // Reuse the connection to send the next request in the queue 695f52c03c1SCarson Labrado if (!requestQueue.empty()) 696f52c03c1SCarson Labrado { 69762598e31SEd Tanous BMCWEB_LOG_DEBUG( 6988ece0e45SEd Tanous "{} requests remaining in queue for {}, reusing connection {}", 699a716aa74SEd Tanous requestQueue.size(), destIP, connId); 700f52c03c1SCarson Labrado 701f52c03c1SCarson Labrado setConnProps(*conn); 702f52c03c1SCarson Labrado 703f52c03c1SCarson Labrado if (keepAlive) 704f52c03c1SCarson Labrado { 705f52c03c1SCarson Labrado conn->sendMessage(); 7062a5689a7SAppaRao Puli } 7072a5689a7SAppaRao Puli else 7082a5689a7SAppaRao Puli { 709f52c03c1SCarson Labrado // Server is not keep-alive enabled so we need to close the 710f52c03c1SCarson Labrado // connection and then start over from resolve 711f52c03c1SCarson Labrado conn->doClose(); 712f52c03c1SCarson Labrado conn->doResolve(); 713f52c03c1SCarson Labrado } 714f52c03c1SCarson Labrado return; 715f52c03c1SCarson Labrado } 716f52c03c1SCarson Labrado 717f52c03c1SCarson Labrado // No more messages to send so close the connection if necessary 718f52c03c1SCarson Labrado if (keepAlive) 719f52c03c1SCarson Labrado { 720f52c03c1SCarson Labrado conn->state = ConnState::idle; 721f52c03c1SCarson Labrado } 722f52c03c1SCarson Labrado else 723f52c03c1SCarson Labrado { 724f52c03c1SCarson Labrado // Abort the connection since server is not keep-alive enabled 725f52c03c1SCarson Labrado conn->state = ConnState::abortConnection; 726f52c03c1SCarson Labrado conn->doClose(); 7272a5689a7SAppaRao Puli } 728bd030d0aSAppaRao Puli } 729bd030d0aSAppaRao Puli 730a716aa74SEd Tanous void sendData(std::string&& data, boost::urls::url_view destUri, 731244256ccSCarson Labrado const boost::beast::http::fields& httpHeader, 732244256ccSCarson Labrado const boost::beast::http::verb verb, 7336b3db60dSEd Tanous const std::function<void(Response&)>& resHandler) 734fe44eb0bSAyushi Smriti { 735244256ccSCarson Labrado // Construct the request to be sent 736*52e31629SEd Tanous boost::beast::http::request<bmcweb::FileBody> thisReq( 737a716aa74SEd Tanous verb, destUri.encoded_target(), 11, "", httpHeader); 738a716aa74SEd Tanous thisReq.set(boost::beast::http::field::host, 739a716aa74SEd Tanous destUri.encoded_host_address()); 740244256ccSCarson Labrado thisReq.keep_alive(true); 741*52e31629SEd Tanous thisReq.body().str() = std::move(data); 742244256ccSCarson Labrado thisReq.prepare_payload(); 7433d36e3a5SEd Tanous auto cb = std::bind_front(&ConnectionPool::afterSendData, 7443d36e3a5SEd Tanous weak_from_this(), resHandler); 745f52c03c1SCarson Labrado // Reuse an existing connection if one is available 746f52c03c1SCarson Labrado for (unsigned int i = 0; i < connections.size(); i++) 747fe44eb0bSAyushi Smriti { 748f52c03c1SCarson Labrado auto conn = connections[i]; 749f52c03c1SCarson Labrado if ((conn->state == ConnState::idle) || 750f52c03c1SCarson Labrado (conn->state == ConnState::initialized) || 751f52c03c1SCarson Labrado (conn->state == ConnState::closed)) 752f52c03c1SCarson Labrado { 753244256ccSCarson Labrado conn->req = std::move(thisReq); 754f52c03c1SCarson Labrado conn->callback = std::move(cb); 755a716aa74SEd Tanous std::string commonMsg = std::format("{} from pool {}", i, id); 756f52c03c1SCarson Labrado 757f52c03c1SCarson Labrado if (conn->state == ConnState::idle) 758f52c03c1SCarson Labrado { 75962598e31SEd Tanous BMCWEB_LOG_DEBUG("Grabbing idle connection {}", commonMsg); 760f52c03c1SCarson Labrado conn->sendMessage(); 761f52c03c1SCarson Labrado } 762f52c03c1SCarson Labrado else 763f52c03c1SCarson Labrado { 76462598e31SEd Tanous BMCWEB_LOG_DEBUG("Reusing existing connection {}", 76562598e31SEd Tanous commonMsg); 766f52c03c1SCarson Labrado conn->doResolve(); 767f52c03c1SCarson Labrado } 768f52c03c1SCarson Labrado return; 769f52c03c1SCarson Labrado } 770f52c03c1SCarson Labrado } 771f52c03c1SCarson Labrado 77227b0cf90SEd Tanous // All connections in use so create a new connection or add request 77327b0cf90SEd Tanous // to the queue 774d14a48ffSCarson Labrado if (connections.size() < connPolicy->maxConnections) 775f52c03c1SCarson Labrado { 776a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Adding new connection to pool {}", id); 777f52c03c1SCarson Labrado auto conn = addConnection(); 778244256ccSCarson Labrado conn->req = std::move(thisReq); 779f52c03c1SCarson Labrado conn->callback = std::move(cb); 780f52c03c1SCarson Labrado conn->doResolve(); 781f52c03c1SCarson Labrado } 782f52c03c1SCarson Labrado else if (requestQueue.size() < maxRequestQueueSize) 783f52c03c1SCarson Labrado { 784a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Max pool size reached. Adding data to queue {}", 785a716aa74SEd Tanous id); 786d14a48ffSCarson Labrado requestQueue.emplace_back(std::move(thisReq), std::move(cb)); 787f52c03c1SCarson Labrado } 788f52c03c1SCarson Labrado else 789f52c03c1SCarson Labrado { 79027b0cf90SEd Tanous // If we can't buffer the request then we should let the 79127b0cf90SEd Tanous // callback handle a 429 Too Many Requests dummy response 79262598e31SEd Tanous BMCWEB_LOG_ERROR("{}:{} request queue full. Dropping request.", 793a716aa74SEd Tanous id); 79443e14d38SCarson Labrado Response dummyRes; 79543e14d38SCarson Labrado dummyRes.result(boost::beast::http::status::too_many_requests); 79643e14d38SCarson Labrado resHandler(dummyRes); 797f52c03c1SCarson Labrado } 798f52c03c1SCarson Labrado } 799f52c03c1SCarson Labrado 8003d36e3a5SEd Tanous // Callback to be called once the request has been sent 8013d36e3a5SEd Tanous static void afterSendData(const std::weak_ptr<ConnectionPool>& weakSelf, 8023d36e3a5SEd Tanous const std::function<void(Response&)>& resHandler, 8033d36e3a5SEd Tanous bool keepAlive, uint32_t connId, Response& res) 8043d36e3a5SEd Tanous { 8053d36e3a5SEd Tanous // Allow provided callback to perform additional processing of the 8063d36e3a5SEd Tanous // request 8073d36e3a5SEd Tanous resHandler(res); 8083d36e3a5SEd Tanous 8093d36e3a5SEd Tanous // If requests remain in the queue then we want to reuse this 8103d36e3a5SEd Tanous // connection to send the next request 8113d36e3a5SEd Tanous std::shared_ptr<ConnectionPool> self = weakSelf.lock(); 8123d36e3a5SEd Tanous if (!self) 8133d36e3a5SEd Tanous { 81462598e31SEd Tanous BMCWEB_LOG_CRITICAL("{} Failed to capture connection", 81562598e31SEd Tanous logPtr(self.get())); 8163d36e3a5SEd Tanous return; 8173d36e3a5SEd Tanous } 8183d36e3a5SEd Tanous 8193d36e3a5SEd Tanous self->sendNext(keepAlive, connId); 8203d36e3a5SEd Tanous } 8213d36e3a5SEd Tanous 822f52c03c1SCarson Labrado std::shared_ptr<ConnectionInfo>& addConnection() 823f52c03c1SCarson Labrado { 824f52c03c1SCarson Labrado unsigned int newId = static_cast<unsigned int>(connections.size()); 825f52c03c1SCarson Labrado 826e38778a5SAppaRao Puli auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>( 827a716aa74SEd Tanous ioc, id, connPolicy, destIP, newId)); 828f52c03c1SCarson Labrado 829a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Added connection {} to pool {}", 830a716aa74SEd Tanous connections.size() - 1, id); 831f52c03c1SCarson Labrado 832f52c03c1SCarson Labrado return ret; 833f52c03c1SCarson Labrado } 834f52c03c1SCarson Labrado 835f52c03c1SCarson Labrado public: 836d14a48ffSCarson Labrado explicit ConnectionPool( 837d14a48ffSCarson Labrado boost::asio::io_context& iocIn, const std::string& idIn, 838d14a48ffSCarson Labrado const std::shared_ptr<ConnectionPolicy>& connPolicyIn, 839a716aa74SEd Tanous boost::urls::url_view destIPIn) : 8408a592810SEd Tanous ioc(iocIn), 841a716aa74SEd Tanous id(idIn), connPolicy(connPolicyIn), destIP(destIPIn) 842f52c03c1SCarson Labrado { 843a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Initializing connection pool for {}", id); 844f52c03c1SCarson Labrado 845f52c03c1SCarson Labrado // Initialize the pool with a single connection 846f52c03c1SCarson Labrado addConnection(); 847fe44eb0bSAyushi Smriti } 848bd030d0aSAppaRao Puli }; 849bd030d0aSAppaRao Puli 850f52c03c1SCarson Labrado class HttpClient 851f52c03c1SCarson Labrado { 852f52c03c1SCarson Labrado private: 853f52c03c1SCarson Labrado std::unordered_map<std::string, std::shared_ptr<ConnectionPool>> 854f52c03c1SCarson Labrado connectionPools; 855f8ca6d79SEd Tanous boost::asio::io_context& ioc; 856d14a48ffSCarson Labrado std::shared_ptr<ConnectionPolicy> connPolicy; 857f52c03c1SCarson Labrado 858039a47e3SCarson Labrado // Used as a dummy callback by sendData() in order to call 859039a47e3SCarson Labrado // sendDataWithCallback() 86002cad96eSEd Tanous static void genericResHandler(const Response& res) 861039a47e3SCarson Labrado { 86262598e31SEd Tanous BMCWEB_LOG_DEBUG("Response handled with return code: {}", 863a716aa74SEd Tanous res.resultInt()); 8644ee8e211SEd Tanous } 865039a47e3SCarson Labrado 866f52c03c1SCarson Labrado public: 867d14a48ffSCarson Labrado HttpClient() = delete; 868f8ca6d79SEd Tanous explicit HttpClient(boost::asio::io_context& iocIn, 869f8ca6d79SEd Tanous const std::shared_ptr<ConnectionPolicy>& connPolicyIn) : 870f8ca6d79SEd Tanous ioc(iocIn), 871d14a48ffSCarson Labrado connPolicy(connPolicyIn) 872d14a48ffSCarson Labrado {} 873f8ca6d79SEd Tanous 874f52c03c1SCarson Labrado HttpClient(const HttpClient&) = delete; 875f52c03c1SCarson Labrado HttpClient& operator=(const HttpClient&) = delete; 876f52c03c1SCarson Labrado HttpClient(HttpClient&&) = delete; 877f52c03c1SCarson Labrado HttpClient& operator=(HttpClient&&) = delete; 878f52c03c1SCarson Labrado ~HttpClient() = default; 879f52c03c1SCarson Labrado 880a716aa74SEd Tanous // Send a request to destIP where additional processing of the 881039a47e3SCarson Labrado // result is not required 882a716aa74SEd Tanous void sendData(std::string&& data, boost::urls::url_view destUri, 883f52c03c1SCarson Labrado const boost::beast::http::fields& httpHeader, 884d14a48ffSCarson Labrado const boost::beast::http::verb verb) 885f52c03c1SCarson Labrado { 886e38778a5SAppaRao Puli const std::function<void(Response&)> cb = genericResHandler; 887a716aa74SEd Tanous sendDataWithCallback(std::move(data), destUri, httpHeader, verb, cb); 888039a47e3SCarson Labrado } 889039a47e3SCarson Labrado 890a716aa74SEd Tanous // Send request to destIP and use the provided callback to 891039a47e3SCarson Labrado // handle the response 892a716aa74SEd Tanous void sendDataWithCallback(std::string&& data, boost::urls::url_view destUrl, 893039a47e3SCarson Labrado const boost::beast::http::fields& httpHeader, 894244256ccSCarson Labrado const boost::beast::http::verb verb, 8956b3db60dSEd Tanous const std::function<void(Response&)>& resHandler) 896039a47e3SCarson Labrado { 897a716aa74SEd Tanous std::string clientKey = std::format("{}://{}", destUrl.scheme(), 898a716aa74SEd Tanous destUrl.encoded_host_and_port()); 899d14a48ffSCarson Labrado auto pool = connectionPools.try_emplace(clientKey); 900d14a48ffSCarson Labrado if (pool.first->second == nullptr) 901f52c03c1SCarson Labrado { 902d14a48ffSCarson Labrado pool.first->second = std::make_shared<ConnectionPool>( 903a716aa74SEd Tanous ioc, clientKey, connPolicy, destUrl); 904f52c03c1SCarson Labrado } 90527b0cf90SEd Tanous // Send the data using either the existing connection pool or the 90627b0cf90SEd Tanous // newly created connection pool 907a716aa74SEd Tanous pool.first->second->sendData(std::move(data), destUrl, httpHeader, verb, 908e38778a5SAppaRao Puli resHandler); 909f52c03c1SCarson Labrado } 910f52c03c1SCarson Labrado }; 911bd030d0aSAppaRao Puli } // namespace crow 912