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 <memory> 482a5689a7SAppaRao Puli #include <queue> 49bd030d0aSAppaRao Puli #include <string> 50bd030d0aSAppaRao Puli 51bd030d0aSAppaRao Puli namespace crow 52bd030d0aSAppaRao Puli { 5327b0cf90SEd Tanous // With Redfish Aggregation it is assumed we will connect to another 5427b0cf90SEd Tanous // instance of BMCWeb which can handle 100 simultaneous connections. 5566d90c2cSCarson Labrado constexpr size_t maxPoolSize = 20; 5666d90c2cSCarson Labrado constexpr size_t maxRequestQueueSize = 500; 5717dcc312SCarson Labrado constexpr unsigned int httpReadBodyLimit = 131072; 584d94272fSCarson Labrado constexpr unsigned int httpReadBufferSize = 4096; 592a5689a7SAppaRao Puli 60bd030d0aSAppaRao Puli enum class ConnState 61bd030d0aSAppaRao Puli { 622a5689a7SAppaRao Puli initialized, 6329a82b08SSunitha Harish resolveInProgress, 6429a82b08SSunitha Harish resolveFailed, 652a5689a7SAppaRao Puli connectInProgress, 662a5689a7SAppaRao Puli connectFailed, 67bd030d0aSAppaRao Puli connected, 68e38778a5SAppaRao Puli handshakeInProgress, 69e38778a5SAppaRao Puli handshakeFailed, 702a5689a7SAppaRao Puli sendInProgress, 712a5689a7SAppaRao Puli sendFailed, 726eaa1d2fSSunitha Harish recvInProgress, 732a5689a7SAppaRao Puli recvFailed, 742a5689a7SAppaRao Puli idle, 75fe44eb0bSAyushi Smriti closed, 766eaa1d2fSSunitha Harish suspended, 776eaa1d2fSSunitha Harish terminated, 786eaa1d2fSSunitha Harish abortConnection, 79e38778a5SAppaRao Puli sslInitFailed, 806eaa1d2fSSunitha Harish retry 81bd030d0aSAppaRao Puli }; 82bd030d0aSAppaRao Puli 83a7a80296SCarson Labrado static inline boost::system::error_code defaultRetryHandler(unsigned int respCode)84a7a80296SCarson Labrado defaultRetryHandler(unsigned int respCode) 85a7a80296SCarson Labrado { 86a7a80296SCarson Labrado // As a default, assume 200X is alright 8762598e31SEd Tanous BMCWEB_LOG_DEBUG("Using default check for response code validity"); 88a7a80296SCarson Labrado if ((respCode < 200) || (respCode >= 300)) 89a7a80296SCarson Labrado { 90a7a80296SCarson Labrado return boost::system::errc::make_error_code( 91a7a80296SCarson Labrado boost::system::errc::result_out_of_range); 92a7a80296SCarson Labrado } 93a7a80296SCarson Labrado 94a7a80296SCarson Labrado // Return 0 if the response code is valid 95a7a80296SCarson Labrado return boost::system::errc::make_error_code(boost::system::errc::success); 96a7a80296SCarson Labrado }; 97a7a80296SCarson Labrado 9827b0cf90SEd Tanous // We need to allow retry information to be set before a message has been 9927b0cf90SEd Tanous // sent and a connection pool has been created 100d14a48ffSCarson Labrado struct ConnectionPolicy 101f52c03c1SCarson Labrado { 102f52c03c1SCarson Labrado uint32_t maxRetryAttempts = 5; 103d14a48ffSCarson Labrado 104d14a48ffSCarson Labrado // the max size of requests in bytes. 0 for unlimited 105d14a48ffSCarson Labrado boost::optional<uint64_t> requestByteLimit = httpReadBodyLimit; 106d14a48ffSCarson Labrado 107d14a48ffSCarson Labrado size_t maxConnections = 1; 108d14a48ffSCarson Labrado 109f52c03c1SCarson Labrado std::string retryPolicyAction = "TerminateAfterRetries"; 110d14a48ffSCarson Labrado 111d14a48ffSCarson Labrado std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0); 112a7a80296SCarson Labrado std::function<boost::system::error_code(unsigned int respCode)> 113a7a80296SCarson Labrado invalidResp = defaultRetryHandler; 114f52c03c1SCarson Labrado }; 115f52c03c1SCarson Labrado 116f52c03c1SCarson Labrado struct PendingRequest 117f52c03c1SCarson Labrado { 118b2896149SEd Tanous boost::beast::http::request<bmcweb::HttpBody> req; 119039a47e3SCarson Labrado std::function<void(bool, uint32_t, Response&)> callback; PendingRequestcrow::PendingRequest120039a47e3SCarson Labrado PendingRequest( 121b2896149SEd Tanous boost::beast::http::request<bmcweb::HttpBody>&& reqIn, 122d14a48ffSCarson Labrado const std::function<void(bool, uint32_t, Response&)>& callbackIn) : 1238a592810SEd Tanous req(std::move(reqIn)), 124d14a48ffSCarson Labrado callback(callbackIn) 125f52c03c1SCarson Labrado {} 126f52c03c1SCarson Labrado }; 127f52c03c1SCarson Labrado 128e01d0c36SEd Tanous namespace http = boost::beast::http; 129f52c03c1SCarson Labrado class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo> 130bd030d0aSAppaRao Puli { 131bd030d0aSAppaRao Puli private: 132f52c03c1SCarson Labrado ConnState state = ConnState::initialized; 133f52c03c1SCarson Labrado uint32_t retryCount = 0; 134f52c03c1SCarson Labrado std::string subId; 135d14a48ffSCarson Labrado std::shared_ptr<ConnectionPolicy> connPolicy; 136a716aa74SEd Tanous boost::urls::url host; 137f52c03c1SCarson Labrado uint32_t connId; 138f52c03c1SCarson Labrado 139f52c03c1SCarson Labrado // Data buffers 140b2896149SEd Tanous http::request<bmcweb::HttpBody> req; 141b2896149SEd Tanous using parser_type = http::response_parser<bmcweb::HttpBody>; 142e01d0c36SEd Tanous std::optional<parser_type> parser; 1434d94272fSCarson Labrado boost::beast::flat_static_buffer<httpReadBufferSize> buffer; 144039a47e3SCarson Labrado Response res; 1456eaa1d2fSSunitha Harish 146f52c03c1SCarson Labrado // Ascync callables 147039a47e3SCarson Labrado std::function<void(bool, uint32_t, Response&)> callback; 148f8ca6d79SEd Tanous 149f3cb5df9SAbhilash Raju boost::asio::io_context& ioc; 150f3cb5df9SAbhilash Raju 15125b54dbaSEd Tanous using Resolver = std::conditional_t<BMCWEB_DNS_RESOLVER == "systemd-dbus", 15225b54dbaSEd Tanous async_resolve::Resolver, 15325b54dbaSEd Tanous boost::asio::ip::tcp::resolver>; 154f8ca6d79SEd Tanous Resolver resolver; 155f8ca6d79SEd Tanous 1560d5f5cf4SEd Tanous boost::asio::ip::tcp::socket conn; 157003301a2SEd Tanous std::optional<boost::asio::ssl::stream<boost::asio::ip::tcp::socket&>> 1580d5f5cf4SEd Tanous sslConn; 159e38778a5SAppaRao Puli 160f52c03c1SCarson Labrado boost::asio::steady_timer timer; 16184b35604SEd Tanous 162f52c03c1SCarson Labrado friend class ConnectionPool; 163bd030d0aSAppaRao Puli doResolve()16429a82b08SSunitha Harish void doResolve() 16529a82b08SSunitha Harish { 16629a82b08SSunitha Harish state = ConnState::resolveInProgress; 167a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Trying to resolve: {}, id: {}", host, connId); 16829a82b08SSunitha Harish 169a716aa74SEd Tanous resolver.async_resolve(host.encoded_host_address(), host.port(), 1703d36e3a5SEd Tanous std::bind_front(&ConnectionInfo::afterResolve, 1713d36e3a5SEd Tanous this, shared_from_this())); 1723d36e3a5SEd Tanous } 1733d36e3a5SEd Tanous afterResolve(const std::shared_ptr<ConnectionInfo> &,const boost::system::error_code & ec,const Resolver::results_type & endpointList)174f8ca6d79SEd Tanous void afterResolve(const std::shared_ptr<ConnectionInfo>& /*self*/, 175f8ca6d79SEd Tanous const boost::system::error_code& ec, 176f8ca6d79SEd Tanous const Resolver::results_type& endpointList) 1773d36e3a5SEd Tanous { 17826f6976fSEd Tanous if (ec || (endpointList.empty())) 17929a82b08SSunitha Harish { 180a716aa74SEd Tanous BMCWEB_LOG_ERROR("Resolve failed: {} {}", ec.message(), host); 1813d36e3a5SEd Tanous state = ConnState::resolveFailed; 1823d36e3a5SEd Tanous waitAndRetry(); 18329a82b08SSunitha Harish return; 18429a82b08SSunitha Harish } 185a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Resolved {}, id: {}", host, connId); 1862a5689a7SAppaRao Puli state = ConnState::connectInProgress; 1872a5689a7SAppaRao Puli 188a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Trying to connect to: {}, id: {}", host, connId); 189b00dcc27SEd Tanous 1900d5f5cf4SEd Tanous timer.expires_after(std::chrono::seconds(30)); 1910d5f5cf4SEd Tanous timer.async_wait(std::bind_front(onTimeout, weak_from_this())); 1920d5f5cf4SEd Tanous 1930d5f5cf4SEd Tanous boost::asio::async_connect( 1940d5f5cf4SEd Tanous conn, endpointList, 195e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterConnect, this, 196e38778a5SAppaRao Puli shared_from_this())); 197e38778a5SAppaRao Puli } 198e38778a5SAppaRao Puli afterConnect(const std::shared_ptr<ConnectionInfo> &,const boost::beast::error_code & ec,const boost::asio::ip::tcp::endpoint & endpoint)199e38778a5SAppaRao Puli void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/, 20081c4e330SEd Tanous const boost::beast::error_code& ec, 201e38778a5SAppaRao Puli const boost::asio::ip::tcp::endpoint& endpoint) 202e38778a5SAppaRao Puli { 203513d1ffcSCarson Labrado // The operation already timed out. We don't want do continue down 204513d1ffcSCarson Labrado // this branch 205513d1ffcSCarson Labrado if (ec && ec == boost::asio::error::operation_aborted) 206513d1ffcSCarson Labrado { 207513d1ffcSCarson Labrado return; 208513d1ffcSCarson Labrado } 209513d1ffcSCarson Labrado 2100d5f5cf4SEd Tanous timer.cancel(); 2112a5689a7SAppaRao Puli if (ec) 2122a5689a7SAppaRao Puli { 21362598e31SEd Tanous BMCWEB_LOG_ERROR("Connect {}:{}, id: {} failed: {}", 214a716aa74SEd Tanous endpoint.address().to_string(), endpoint.port(), 215a716aa74SEd Tanous connId, ec.message()); 216e38778a5SAppaRao Puli state = ConnState::connectFailed; 217e38778a5SAppaRao Puli waitAndRetry(); 2182a5689a7SAppaRao Puli return; 2192a5689a7SAppaRao Puli } 220a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Connected to: {}:{}, id: {}", 221a716aa74SEd Tanous endpoint.address().to_string(), endpoint.port(), 222a716aa74SEd Tanous connId); 223e38778a5SAppaRao Puli if (sslConn) 224e38778a5SAppaRao Puli { 2250d5f5cf4SEd Tanous doSslHandshake(); 226e38778a5SAppaRao Puli return; 227e38778a5SAppaRao Puli } 228e38778a5SAppaRao Puli state = ConnState::connected; 229e38778a5SAppaRao Puli sendMessage(); 230e38778a5SAppaRao Puli } 231e38778a5SAppaRao Puli doSslHandshake()2320d5f5cf4SEd Tanous void doSslHandshake() 233e38778a5SAppaRao Puli { 234e38778a5SAppaRao Puli if (!sslConn) 235e38778a5SAppaRao Puli { 236e38778a5SAppaRao Puli return; 237e38778a5SAppaRao Puli } 238e38778a5SAppaRao Puli state = ConnState::handshakeInProgress; 2390d5f5cf4SEd Tanous timer.expires_after(std::chrono::seconds(30)); 2400d5f5cf4SEd Tanous timer.async_wait(std::bind_front(onTimeout, weak_from_this())); 241e38778a5SAppaRao Puli sslConn->async_handshake( 242e38778a5SAppaRao Puli boost::asio::ssl::stream_base::client, 243e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterSslHandshake, this, 244e38778a5SAppaRao Puli shared_from_this())); 245e38778a5SAppaRao Puli } 246e38778a5SAppaRao Puli afterSslHandshake(const std::shared_ptr<ConnectionInfo> &,const boost::beast::error_code & ec)247e38778a5SAppaRao Puli void afterSslHandshake(const std::shared_ptr<ConnectionInfo>& /*self*/, 24881c4e330SEd Tanous const boost::beast::error_code& ec) 249e38778a5SAppaRao Puli { 250513d1ffcSCarson Labrado // The operation already timed out. We don't want do continue down 251513d1ffcSCarson Labrado // this branch 252513d1ffcSCarson Labrado if (ec && ec == boost::asio::error::operation_aborted) 253513d1ffcSCarson Labrado { 254513d1ffcSCarson Labrado return; 255513d1ffcSCarson Labrado } 256513d1ffcSCarson Labrado 2570d5f5cf4SEd Tanous timer.cancel(); 258e38778a5SAppaRao Puli if (ec) 259e38778a5SAppaRao Puli { 260a716aa74SEd Tanous BMCWEB_LOG_ERROR("SSL Handshake failed - id: {} error: {}", connId, 261a716aa74SEd Tanous ec.message()); 262e38778a5SAppaRao Puli state = ConnState::handshakeFailed; 263e38778a5SAppaRao Puli waitAndRetry(); 264e38778a5SAppaRao Puli return; 265e38778a5SAppaRao Puli } 266a716aa74SEd Tanous BMCWEB_LOG_DEBUG("SSL Handshake successful - id: {}", connId); 267e38778a5SAppaRao Puli state = ConnState::connected; 268e38778a5SAppaRao Puli sendMessage(); 2692a5689a7SAppaRao Puli } 2702a5689a7SAppaRao Puli sendMessage()271f52c03c1SCarson Labrado void sendMessage() 2722a5689a7SAppaRao Puli { 2732a5689a7SAppaRao Puli state = ConnState::sendInProgress; 2742a5689a7SAppaRao Puli 275bd030d0aSAppaRao Puli // Set a timeout on the operation 2760d5f5cf4SEd Tanous timer.expires_after(std::chrono::seconds(30)); 2770d5f5cf4SEd Tanous timer.async_wait(std::bind_front(onTimeout, weak_from_this())); 2784d69861fSEd Tanous boost::beast::http::message_generator messageGenerator(std::move(req)); 279bd030d0aSAppaRao Puli // Send the HTTP request to the remote host 280e38778a5SAppaRao Puli if (sslConn) 281e38778a5SAppaRao Puli { 2824d69861fSEd Tanous boost::beast::async_write( 2834d69861fSEd Tanous *sslConn, std::move(messageGenerator), 284e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterWrite, this, 285e38778a5SAppaRao Puli shared_from_this())); 286e38778a5SAppaRao Puli } 287e38778a5SAppaRao Puli else 288e38778a5SAppaRao Puli { 2894d69861fSEd Tanous boost::beast::async_write( 2904d69861fSEd Tanous conn, std::move(messageGenerator), 291e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterWrite, this, 292e38778a5SAppaRao Puli shared_from_this())); 293e38778a5SAppaRao Puli } 294e38778a5SAppaRao Puli } 295e38778a5SAppaRao Puli afterWrite(const std::shared_ptr<ConnectionInfo> &,const boost::beast::error_code & ec,size_t bytesTransferred)296e38778a5SAppaRao Puli void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/, 297e38778a5SAppaRao Puli const boost::beast::error_code& ec, size_t bytesTransferred) 298e38778a5SAppaRao Puli { 299513d1ffcSCarson Labrado // The operation already timed out. We don't want do continue down 300513d1ffcSCarson Labrado // this branch 301513d1ffcSCarson Labrado if (ec && ec == boost::asio::error::operation_aborted) 302513d1ffcSCarson Labrado { 303513d1ffcSCarson Labrado return; 304513d1ffcSCarson Labrado } 305513d1ffcSCarson Labrado 3060d5f5cf4SEd Tanous timer.cancel(); 307bd030d0aSAppaRao Puli if (ec) 308bd030d0aSAppaRao Puli { 309a716aa74SEd Tanous BMCWEB_LOG_ERROR("sendMessage() failed: {} {}", ec.message(), host); 310e38778a5SAppaRao Puli state = ConnState::sendFailed; 311e38778a5SAppaRao Puli waitAndRetry(); 312bd030d0aSAppaRao Puli return; 313bd030d0aSAppaRao Puli } 31462598e31SEd Tanous BMCWEB_LOG_DEBUG("sendMessage() bytes transferred: {}", 31562598e31SEd Tanous bytesTransferred); 316bd030d0aSAppaRao Puli 317e38778a5SAppaRao Puli recvMessage(); 318bd030d0aSAppaRao Puli } 319bd030d0aSAppaRao Puli recvMessage()320bd030d0aSAppaRao Puli void recvMessage() 321bd030d0aSAppaRao Puli { 3226eaa1d2fSSunitha Harish state = ConnState::recvInProgress; 3236eaa1d2fSSunitha Harish 324e01d0c36SEd Tanous parser_type& thisParser = parser.emplace(std::piecewise_construct, 325e01d0c36SEd Tanous std::make_tuple()); 326d14a48ffSCarson Labrado 327e01d0c36SEd Tanous thisParser.body_limit(connPolicy->requestByteLimit); 3286eaa1d2fSSunitha Harish 3290d5f5cf4SEd Tanous timer.expires_after(std::chrono::seconds(30)); 3300d5f5cf4SEd Tanous timer.async_wait(std::bind_front(onTimeout, weak_from_this())); 3310d5f5cf4SEd Tanous 332bd030d0aSAppaRao Puli // Receive the HTTP response 333e38778a5SAppaRao Puli if (sslConn) 334e38778a5SAppaRao Puli { 335e38778a5SAppaRao Puli boost::beast::http::async_read( 336e01d0c36SEd Tanous *sslConn, buffer, thisParser, 337e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterRead, this, 338e38778a5SAppaRao Puli shared_from_this())); 339e38778a5SAppaRao Puli } 340e38778a5SAppaRao Puli else 341e38778a5SAppaRao Puli { 342bd030d0aSAppaRao Puli boost::beast::http::async_read( 343e01d0c36SEd Tanous conn, buffer, thisParser, 344e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterRead, this, 345e38778a5SAppaRao Puli shared_from_this())); 346e38778a5SAppaRao Puli } 347e38778a5SAppaRao Puli } 348e38778a5SAppaRao Puli afterRead(const std::shared_ptr<ConnectionInfo> &,const boost::beast::error_code & ec,const std::size_t & bytesTransferred)349e38778a5SAppaRao Puli void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/, 350e38778a5SAppaRao Puli const boost::beast::error_code& ec, 351e38778a5SAppaRao Puli const std::size_t& bytesTransferred) 352e38778a5SAppaRao Puli { 353513d1ffcSCarson Labrado // The operation already timed out. We don't want do continue down 354513d1ffcSCarson Labrado // this branch 355513d1ffcSCarson Labrado if (ec && ec == boost::asio::error::operation_aborted) 356513d1ffcSCarson Labrado { 357513d1ffcSCarson Labrado return; 358513d1ffcSCarson Labrado } 359513d1ffcSCarson Labrado 3600d5f5cf4SEd Tanous timer.cancel(); 361e38778a5SAppaRao Puli if (ec && ec != boost::asio::ssl::error::stream_truncated) 362bd030d0aSAppaRao Puli { 363a716aa74SEd Tanous BMCWEB_LOG_ERROR("recvMessage() failed: {} from {}", ec.message(), 364a716aa74SEd Tanous host); 365e38778a5SAppaRao Puli state = ConnState::recvFailed; 366e38778a5SAppaRao Puli waitAndRetry(); 367bd030d0aSAppaRao Puli return; 368bd030d0aSAppaRao Puli } 36962598e31SEd Tanous BMCWEB_LOG_DEBUG("recvMessage() bytes transferred: {}", 37062598e31SEd Tanous bytesTransferred); 371e01d0c36SEd Tanous if (!parser) 372e01d0c36SEd Tanous { 373e01d0c36SEd Tanous return; 374e01d0c36SEd Tanous } 37552e31629SEd Tanous BMCWEB_LOG_DEBUG("recvMessage() data: {}", parser->get().body().str()); 376bd030d0aSAppaRao Puli 377e38778a5SAppaRao Puli unsigned int respCode = parser->get().result_int(); 37862598e31SEd Tanous BMCWEB_LOG_DEBUG("recvMessage() Header Response Code: {}", respCode); 3796eaa1d2fSSunitha Harish 380f3cb5df9SAbhilash Raju // Handle the case of stream_truncated. Some servers close the ssl 381f3cb5df9SAbhilash Raju // connection uncleanly, so check to see if we got a full response 382f3cb5df9SAbhilash Raju // before we handle this as an error. 383f3cb5df9SAbhilash Raju if (!parser->is_done()) 384f3cb5df9SAbhilash Raju { 385f3cb5df9SAbhilash Raju state = ConnState::recvFailed; 386f3cb5df9SAbhilash Raju waitAndRetry(); 387f3cb5df9SAbhilash Raju return; 388f3cb5df9SAbhilash Raju } 389f3cb5df9SAbhilash Raju 390a7a80296SCarson Labrado // Make sure the received response code is valid as defined by 391a7a80296SCarson Labrado // the associated retry policy 392d14a48ffSCarson Labrado if (connPolicy->invalidResp(respCode)) 3936eaa1d2fSSunitha Harish { 3946eaa1d2fSSunitha Harish // The listener failed to receive the Sent-Event 39562598e31SEd Tanous BMCWEB_LOG_ERROR( 39662598e31SEd Tanous "recvMessage() Listener Failed to " 397a716aa74SEd Tanous "receive Sent-Event. Header Response Code: {} from {}", 398a716aa74SEd Tanous respCode, host); 399e38778a5SAppaRao Puli state = ConnState::recvFailed; 400e38778a5SAppaRao Puli waitAndRetry(); 4016eaa1d2fSSunitha Harish return; 4026eaa1d2fSSunitha Harish } 403bd030d0aSAppaRao Puli 404f52c03c1SCarson Labrado // Send is successful 405f52c03c1SCarson Labrado // Reset the counter just in case this was after retrying 406e38778a5SAppaRao Puli retryCount = 0; 4076eaa1d2fSSunitha Harish 4086eaa1d2fSSunitha Harish // Keep the connection alive if server supports it 4096eaa1d2fSSunitha Harish // Else close the connection 41062598e31SEd Tanous BMCWEB_LOG_DEBUG("recvMessage() keepalive : {}", parser->keep_alive()); 4116eaa1d2fSSunitha Harish 412039a47e3SCarson Labrado // Copy the response into a Response object so that it can be 413039a47e3SCarson Labrado // processed by the callback function. 41427b0cf90SEd Tanous res.response = parser->release(); 415e38778a5SAppaRao Puli callback(parser->keep_alive(), connId, res); 416513d1ffcSCarson Labrado res.clear(); 417bd030d0aSAppaRao Puli } 418bd030d0aSAppaRao Puli onTimeout(const std::weak_ptr<ConnectionInfo> & weakSelf,const boost::system::error_code & ec)4190d5f5cf4SEd Tanous static void onTimeout(const std::weak_ptr<ConnectionInfo>& weakSelf, 4205e7e2dc5SEd Tanous const boost::system::error_code& ec) 4210d5f5cf4SEd Tanous { 4220d5f5cf4SEd Tanous if (ec == boost::asio::error::operation_aborted) 4230d5f5cf4SEd Tanous { 42462598e31SEd Tanous BMCWEB_LOG_DEBUG( 42562598e31SEd Tanous "async_wait failed since the operation is aborted"); 4260d5f5cf4SEd Tanous return; 4270d5f5cf4SEd Tanous } 4280d5f5cf4SEd Tanous if (ec) 4290d5f5cf4SEd Tanous { 43062598e31SEd Tanous BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message()); 43127b0cf90SEd Tanous // If the timer fails, we need to close the socket anyway, same 43227b0cf90SEd Tanous // as if it expired. 4330d5f5cf4SEd Tanous } 4340d5f5cf4SEd Tanous std::shared_ptr<ConnectionInfo> self = weakSelf.lock(); 4350d5f5cf4SEd Tanous if (self == nullptr) 4360d5f5cf4SEd Tanous { 4370d5f5cf4SEd Tanous return; 4380d5f5cf4SEd Tanous } 4390d5f5cf4SEd Tanous self->waitAndRetry(); 4400d5f5cf4SEd Tanous } 4410d5f5cf4SEd Tanous waitAndRetry()4426eaa1d2fSSunitha Harish void waitAndRetry() 443bd030d0aSAppaRao Puli { 444d14a48ffSCarson Labrado if ((retryCount >= connPolicy->maxRetryAttempts) || 445e38778a5SAppaRao Puli (state == ConnState::sslInitFailed)) 4462a5689a7SAppaRao Puli { 447a716aa74SEd Tanous BMCWEB_LOG_ERROR("Maximum number of retries reached. {}", host); 44862598e31SEd Tanous BMCWEB_LOG_DEBUG("Retry policy: {}", connPolicy->retryPolicyAction); 449039a47e3SCarson Labrado 450d14a48ffSCarson Labrado if (connPolicy->retryPolicyAction == "TerminateAfterRetries") 451fe44eb0bSAyushi Smriti { 452fe44eb0bSAyushi Smriti // TODO: delete subscription 453fe44eb0bSAyushi Smriti state = ConnState::terminated; 454fe44eb0bSAyushi Smriti } 455d14a48ffSCarson Labrado if (connPolicy->retryPolicyAction == "SuspendRetries") 456fe44eb0bSAyushi Smriti { 4572a5689a7SAppaRao Puli state = ConnState::suspended; 4582a5689a7SAppaRao Puli } 459513d1ffcSCarson Labrado 460513d1ffcSCarson Labrado // We want to return a 502 to indicate there was an error with 461513d1ffcSCarson Labrado // the external server 462513d1ffcSCarson Labrado res.result(boost::beast::http::status::bad_gateway); 463513d1ffcSCarson Labrado callback(false, connId, res); 464513d1ffcSCarson Labrado res.clear(); 465513d1ffcSCarson Labrado 46627b0cf90SEd Tanous // Reset the retrycount to zero so that client can try 46727b0cf90SEd Tanous // connecting again if needed 468fe44eb0bSAyushi Smriti retryCount = 0; 4692a5689a7SAppaRao Puli return; 4702a5689a7SAppaRao Puli } 4712a5689a7SAppaRao Puli 4722a5689a7SAppaRao Puli retryCount++; 473fe44eb0bSAyushi Smriti 47462598e31SEd Tanous BMCWEB_LOG_DEBUG("Attempt retry after {} seconds. RetryCount = {}", 475a716aa74SEd Tanous connPolicy->retryIntervalSecs.count(), retryCount); 476d14a48ffSCarson Labrado timer.expires_after(connPolicy->retryIntervalSecs); 4773d36e3a5SEd Tanous timer.async_wait(std::bind_front(&ConnectionInfo::onTimerDone, this, 4783d36e3a5SEd Tanous shared_from_this())); 4793d36e3a5SEd Tanous } 4803d36e3a5SEd Tanous onTimerDone(const std::shared_ptr<ConnectionInfo> &,const boost::system::error_code & ec)4813d36e3a5SEd Tanous void onTimerDone(const std::shared_ptr<ConnectionInfo>& /*self*/, 4823d36e3a5SEd Tanous const boost::system::error_code& ec) 4833d36e3a5SEd Tanous { 4846eaa1d2fSSunitha Harish if (ec == boost::asio::error::operation_aborted) 4856eaa1d2fSSunitha Harish { 48662598e31SEd Tanous BMCWEB_LOG_DEBUG( 48762598e31SEd Tanous "async_wait failed since the operation is aborted{}", 48862598e31SEd Tanous ec.message()); 4896eaa1d2fSSunitha Harish } 4906eaa1d2fSSunitha Harish else if (ec) 4916eaa1d2fSSunitha Harish { 49262598e31SEd Tanous BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message()); 4936eaa1d2fSSunitha Harish // Ignore the error and continue the retry loop to attempt 4946eaa1d2fSSunitha Harish // sending the event as per the retry policy 4956eaa1d2fSSunitha Harish } 4966eaa1d2fSSunitha Harish 497f52c03c1SCarson Labrado // Let's close the connection and restart from resolve. 498f3cb5df9SAbhilash Raju shutdownConn(true); 499f3cb5df9SAbhilash Raju } 500f3cb5df9SAbhilash Raju restartConnection()501f3cb5df9SAbhilash Raju void restartConnection() 502f3cb5df9SAbhilash Raju { 503f3cb5df9SAbhilash Raju BMCWEB_LOG_DEBUG("{}, id: {} restartConnection", host, 504f3cb5df9SAbhilash Raju std::to_string(connId)); 505f3cb5df9SAbhilash Raju initializeConnection(host.scheme() == "https"); 506f3cb5df9SAbhilash Raju doResolve(); 5072a5689a7SAppaRao Puli } 5082a5689a7SAppaRao Puli shutdownConn(bool retry)509e38778a5SAppaRao Puli void shutdownConn(bool retry) 510fe44eb0bSAyushi Smriti { 511f52c03c1SCarson Labrado boost::beast::error_code ec; 5120d5f5cf4SEd Tanous conn.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); 513f52c03c1SCarson Labrado conn.close(); 514f52c03c1SCarson Labrado 515f52c03c1SCarson Labrado // not_connected happens sometimes so don't bother reporting it. 516f52c03c1SCarson Labrado if (ec && ec != boost::beast::errc::not_connected) 5172a5689a7SAppaRao Puli { 518a716aa74SEd Tanous BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId, 51962598e31SEd Tanous ec.message()); 5206eaa1d2fSSunitha Harish } 5215cab68f3SCarson Labrado else 5225cab68f3SCarson Labrado { 523a716aa74SEd Tanous BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId); 5245cab68f3SCarson Labrado } 525ca723762SEd Tanous 526e38778a5SAppaRao Puli if (retry) 52792a74e56SAppaRao Puli { 528f52c03c1SCarson Labrado // Now let's try to resend the data 529f52c03c1SCarson Labrado state = ConnState::retry; 530f3cb5df9SAbhilash Raju restartConnection(); 531e38778a5SAppaRao Puli } 532e38778a5SAppaRao Puli else 533e38778a5SAppaRao Puli { 534e38778a5SAppaRao Puli state = ConnState::closed; 535e38778a5SAppaRao Puli } 536e38778a5SAppaRao Puli } 537e38778a5SAppaRao Puli doClose(bool retry=false)538e38778a5SAppaRao Puli void doClose(bool retry = false) 539e38778a5SAppaRao Puli { 540e38778a5SAppaRao Puli if (!sslConn) 541e38778a5SAppaRao Puli { 542e38778a5SAppaRao Puli shutdownConn(retry); 543e38778a5SAppaRao Puli return; 544e38778a5SAppaRao Puli } 545e38778a5SAppaRao Puli 546e38778a5SAppaRao Puli sslConn->async_shutdown( 547e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterSslShutdown, this, 548e38778a5SAppaRao Puli shared_from_this(), retry)); 549e38778a5SAppaRao Puli } 550e38778a5SAppaRao Puli afterSslShutdown(const std::shared_ptr<ConnectionInfo> &,bool retry,const boost::system::error_code & ec)551e38778a5SAppaRao Puli void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/, 552e38778a5SAppaRao Puli bool retry, const boost::system::error_code& ec) 553e38778a5SAppaRao Puli { 554e38778a5SAppaRao Puli if (ec) 555e38778a5SAppaRao Puli { 556a716aa74SEd Tanous BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId, 55762598e31SEd Tanous ec.message()); 558e38778a5SAppaRao Puli } 559e38778a5SAppaRao Puli else 560e38778a5SAppaRao Puli { 561a716aa74SEd Tanous BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId); 562e38778a5SAppaRao Puli } 563e38778a5SAppaRao Puli shutdownConn(retry); 564e38778a5SAppaRao Puli } 565e38778a5SAppaRao Puli setCipherSuiteTLSext()566e38778a5SAppaRao Puli void setCipherSuiteTLSext() 567e38778a5SAppaRao Puli { 568e38778a5SAppaRao Puli if (!sslConn) 569e38778a5SAppaRao Puli { 570e38778a5SAppaRao Puli return; 571e38778a5SAppaRao Puli } 572e7c2991eSRavi Teja 573e7c2991eSRavi Teja if (host.host_type() != boost::urls::host_type::name) 574e7c2991eSRavi Teja { 575e7c2991eSRavi Teja // Avoid setting SNI hostname if its IP address 576e7c2991eSRavi Teja return; 577e7c2991eSRavi Teja } 578e7c2991eSRavi Teja // Create a null terminated string for SSL 579a716aa74SEd Tanous std::string hostname(host.encoded_host_address()); 580e38778a5SAppaRao Puli // NOTE: The SSL_set_tlsext_host_name is defined in tlsv1.h header 581e38778a5SAppaRao Puli // file but its having old style casting (name is cast to void*). 582e38778a5SAppaRao Puli // Since bmcweb compiler treats all old-style-cast as error, its 583e38778a5SAppaRao Puli // causing the build failure. So replaced the same macro inline and 584e38778a5SAppaRao Puli // did corrected the code by doing static_cast to viod*. This has to 585e38778a5SAppaRao Puli // be fixed in openssl library in long run. Set SNI Hostname (many 586e38778a5SAppaRao Puli // hosts need this to handshake successfully) 587e38778a5SAppaRao Puli if (SSL_ctrl(sslConn->native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME, 588e38778a5SAppaRao Puli TLSEXT_NAMETYPE_host_name, 589a716aa74SEd Tanous static_cast<void*>(hostname.data())) == 0) 590e38778a5SAppaRao Puli 591e38778a5SAppaRao Puli { 592e38778a5SAppaRao Puli boost::beast::error_code ec{static_cast<int>(::ERR_get_error()), 593e38778a5SAppaRao Puli boost::asio::error::get_ssl_category()}; 594e38778a5SAppaRao Puli 595a716aa74SEd Tanous BMCWEB_LOG_ERROR("SSL_set_tlsext_host_name {}, id: {} failed: {}", 596a716aa74SEd Tanous host, connId, ec.message()); 597e38778a5SAppaRao Puli // Set state as sslInit failed so that we close the connection 598e38778a5SAppaRao Puli // and take appropriate action as per retry configuration. 599e38778a5SAppaRao Puli state = ConnState::sslInitFailed; 600e38778a5SAppaRao Puli waitAndRetry(); 601e38778a5SAppaRao Puli return; 602e38778a5SAppaRao Puli } 603bd030d0aSAppaRao Puli } 604bd030d0aSAppaRao Puli initializeConnection(bool ssl)605f3cb5df9SAbhilash Raju void initializeConnection(bool ssl) 606e38778a5SAppaRao Puli { 607f3cb5df9SAbhilash Raju conn = boost::asio::ip::tcp::socket(ioc); 608f3cb5df9SAbhilash Raju if (ssl) 609e38778a5SAppaRao Puli { 610e38778a5SAppaRao Puli std::optional<boost::asio::ssl::context> sslCtx = 611e38778a5SAppaRao Puli ensuressl::getSSLClientContext(); 612e38778a5SAppaRao Puli 613e38778a5SAppaRao Puli if (!sslCtx) 614e38778a5SAppaRao Puli { 615a716aa74SEd Tanous BMCWEB_LOG_ERROR("prepareSSLContext failed - {}, id: {}", host, 616a716aa74SEd Tanous connId); 617e38778a5SAppaRao Puli // Don't retry if failure occurs while preparing SSL context 61827b0cf90SEd Tanous // such as certificate is invalid or set cipher failure or 61927b0cf90SEd Tanous // set host name failure etc... Setting conn state to 62027b0cf90SEd Tanous // sslInitFailed and connection state will be transitioned 62127b0cf90SEd Tanous // to next state depending on retry policy set by 62227b0cf90SEd Tanous // subscription. 623e38778a5SAppaRao Puli state = ConnState::sslInitFailed; 624e38778a5SAppaRao Puli waitAndRetry(); 625e38778a5SAppaRao Puli return; 626e38778a5SAppaRao Puli } 627e38778a5SAppaRao Puli sslConn.emplace(conn, *sslCtx); 628e38778a5SAppaRao Puli setCipherSuiteTLSext(); 629e38778a5SAppaRao Puli } 630e38778a5SAppaRao Puli } 631f3cb5df9SAbhilash Raju 632f3cb5df9SAbhilash Raju public: ConnectionInfo(boost::asio::io_context & iocIn,const std::string & idIn,const std::shared_ptr<ConnectionPolicy> & connPolicyIn,const boost::urls::url_view_base & hostIn,unsigned int connIdIn)633f3cb5df9SAbhilash Raju explicit ConnectionInfo( 634f3cb5df9SAbhilash Raju boost::asio::io_context& iocIn, const std::string& idIn, 635f3cb5df9SAbhilash Raju const std::shared_ptr<ConnectionPolicy>& connPolicyIn, 6364a7fbefdSEd Tanous const boost::urls::url_view_base& hostIn, unsigned int connIdIn) : 637f3cb5df9SAbhilash Raju subId(idIn), 638f3cb5df9SAbhilash Raju connPolicy(connPolicyIn), host(hostIn), connId(connIdIn), ioc(iocIn), 639f3cb5df9SAbhilash Raju resolver(iocIn), conn(iocIn), timer(iocIn) 640f3cb5df9SAbhilash Raju { 641f3cb5df9SAbhilash Raju initializeConnection(host.scheme() == "https"); 642f3cb5df9SAbhilash Raju } 643f52c03c1SCarson Labrado }; 644bd030d0aSAppaRao Puli 645f52c03c1SCarson Labrado class ConnectionPool : public std::enable_shared_from_this<ConnectionPool> 646bd030d0aSAppaRao Puli { 647f52c03c1SCarson Labrado private: 648f52c03c1SCarson Labrado boost::asio::io_context& ioc; 649e38778a5SAppaRao Puli std::string id; 650d14a48ffSCarson Labrado std::shared_ptr<ConnectionPolicy> connPolicy; 651a716aa74SEd Tanous boost::urls::url destIP; 652f52c03c1SCarson Labrado std::vector<std::shared_ptr<ConnectionInfo>> connections; 653f52c03c1SCarson Labrado boost::container::devector<PendingRequest> requestQueue; 654f52c03c1SCarson Labrado 655f52c03c1SCarson Labrado friend class HttpClient; 656f52c03c1SCarson Labrado 657244256ccSCarson Labrado // Configure a connections's request, callback, and retry info in 658244256ccSCarson Labrado // preparation to begin sending the request setConnProps(ConnectionInfo & conn)659f52c03c1SCarson Labrado void setConnProps(ConnectionInfo& conn) 660bd030d0aSAppaRao Puli { 661f52c03c1SCarson Labrado if (requestQueue.empty()) 662f52c03c1SCarson Labrado { 66362598e31SEd Tanous BMCWEB_LOG_ERROR( 66462598e31SEd Tanous "setConnProps() should not have been called when requestQueue is empty"); 665bd030d0aSAppaRao Puli return; 666bd030d0aSAppaRao Puli } 667bd030d0aSAppaRao Puli 66852e31629SEd Tanous PendingRequest& nextReq = requestQueue.front(); 669244256ccSCarson Labrado conn.req = std::move(nextReq.req); 670244256ccSCarson Labrado conn.callback = std::move(nextReq.callback); 671f52c03c1SCarson Labrado 672a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Setting properties for connection {}, id: {}", 673a716aa74SEd Tanous conn.host, conn.connId); 674f52c03c1SCarson Labrado 675f52c03c1SCarson Labrado // We can remove the request from the queue at this point 676f52c03c1SCarson Labrado requestQueue.pop_front(); 677f52c03c1SCarson Labrado } 678f52c03c1SCarson Labrado 679f52c03c1SCarson Labrado // Gets called as part of callback after request is sent 680f52c03c1SCarson Labrado // Reuses the connection if there are any requests waiting to be sent 681f52c03c1SCarson Labrado // Otherwise closes the connection if it is not a keep-alive sendNext(bool keepAlive,uint32_t connId)682f52c03c1SCarson Labrado void sendNext(bool keepAlive, uint32_t connId) 683f52c03c1SCarson Labrado { 684f52c03c1SCarson Labrado auto conn = connections[connId]; 68546a81465SCarson Labrado 68646a81465SCarson Labrado // Allow the connection's handler to be deleted 68746a81465SCarson Labrado // This is needed because of Redfish Aggregation passing an 68846a81465SCarson Labrado // AsyncResponse shared_ptr to this callback 68946a81465SCarson Labrado conn->callback = nullptr; 69046a81465SCarson Labrado 691f52c03c1SCarson Labrado // Reuse the connection to send the next request in the queue 692f52c03c1SCarson Labrado if (!requestQueue.empty()) 693f52c03c1SCarson Labrado { 69462598e31SEd Tanous BMCWEB_LOG_DEBUG( 6958ece0e45SEd Tanous "{} requests remaining in queue for {}, reusing connection {}", 696a716aa74SEd Tanous requestQueue.size(), destIP, connId); 697f52c03c1SCarson Labrado 698f52c03c1SCarson Labrado setConnProps(*conn); 699f52c03c1SCarson Labrado 700f52c03c1SCarson Labrado if (keepAlive) 701f52c03c1SCarson Labrado { 702f52c03c1SCarson Labrado conn->sendMessage(); 7032a5689a7SAppaRao Puli } 7042a5689a7SAppaRao Puli else 7052a5689a7SAppaRao Puli { 706f52c03c1SCarson Labrado // Server is not keep-alive enabled so we need to close the 707f52c03c1SCarson Labrado // connection and then start over from resolve 708f52c03c1SCarson Labrado conn->doClose(); 709*2ecde74fSAbhilash Raju conn->restartConnection(); 710f52c03c1SCarson Labrado } 711f52c03c1SCarson Labrado return; 712f52c03c1SCarson Labrado } 713f52c03c1SCarson Labrado 714f52c03c1SCarson Labrado // No more messages to send so close the connection if necessary 715f52c03c1SCarson Labrado if (keepAlive) 716f52c03c1SCarson Labrado { 717f52c03c1SCarson Labrado conn->state = ConnState::idle; 718f52c03c1SCarson Labrado } 719f52c03c1SCarson Labrado else 720f52c03c1SCarson Labrado { 721f52c03c1SCarson Labrado // Abort the connection since server is not keep-alive enabled 722f52c03c1SCarson Labrado conn->state = ConnState::abortConnection; 723f52c03c1SCarson Labrado conn->doClose(); 7242a5689a7SAppaRao Puli } 725bd030d0aSAppaRao Puli } 726bd030d0aSAppaRao Puli sendData(std::string && data,const boost::urls::url_view_base & destUri,const boost::beast::http::fields & httpHeader,const boost::beast::http::verb verb,const std::function<void (Response &)> & resHandler)7274a7fbefdSEd Tanous void sendData(std::string&& data, const boost::urls::url_view_base& destUri, 728244256ccSCarson Labrado const boost::beast::http::fields& httpHeader, 729244256ccSCarson Labrado const boost::beast::http::verb verb, 7306b3db60dSEd Tanous const std::function<void(Response&)>& resHandler) 731fe44eb0bSAyushi Smriti { 732244256ccSCarson Labrado // Construct the request to be sent 733b2896149SEd Tanous boost::beast::http::request<bmcweb::HttpBody> thisReq( 734a716aa74SEd Tanous verb, destUri.encoded_target(), 11, "", httpHeader); 735a716aa74SEd Tanous thisReq.set(boost::beast::http::field::host, 736a716aa74SEd Tanous destUri.encoded_host_address()); 737244256ccSCarson Labrado thisReq.keep_alive(true); 73852e31629SEd Tanous thisReq.body().str() = std::move(data); 739244256ccSCarson Labrado thisReq.prepare_payload(); 7403d36e3a5SEd Tanous auto cb = std::bind_front(&ConnectionPool::afterSendData, 7413d36e3a5SEd Tanous weak_from_this(), resHandler); 742f52c03c1SCarson Labrado // Reuse an existing connection if one is available 743f52c03c1SCarson Labrado for (unsigned int i = 0; i < connections.size(); i++) 744fe44eb0bSAyushi Smriti { 745f52c03c1SCarson Labrado auto conn = connections[i]; 746f52c03c1SCarson Labrado if ((conn->state == ConnState::idle) || 747f52c03c1SCarson Labrado (conn->state == ConnState::initialized) || 748f52c03c1SCarson Labrado (conn->state == ConnState::closed)) 749f52c03c1SCarson Labrado { 750244256ccSCarson Labrado conn->req = std::move(thisReq); 751f52c03c1SCarson Labrado conn->callback = std::move(cb); 752a716aa74SEd Tanous std::string commonMsg = std::format("{} from pool {}", i, id); 753f52c03c1SCarson Labrado 754f52c03c1SCarson Labrado if (conn->state == ConnState::idle) 755f52c03c1SCarson Labrado { 75662598e31SEd Tanous BMCWEB_LOG_DEBUG("Grabbing idle connection {}", commonMsg); 757f52c03c1SCarson Labrado conn->sendMessage(); 758f52c03c1SCarson Labrado } 759f52c03c1SCarson Labrado else 760f52c03c1SCarson Labrado { 76162598e31SEd Tanous BMCWEB_LOG_DEBUG("Reusing existing connection {}", 76262598e31SEd Tanous commonMsg); 763*2ecde74fSAbhilash Raju conn->restartConnection(); 764f52c03c1SCarson Labrado } 765f52c03c1SCarson Labrado return; 766f52c03c1SCarson Labrado } 767f52c03c1SCarson Labrado } 768f52c03c1SCarson Labrado 76927b0cf90SEd Tanous // All connections in use so create a new connection or add request 77027b0cf90SEd Tanous // to the queue 771d14a48ffSCarson Labrado if (connections.size() < connPolicy->maxConnections) 772f52c03c1SCarson Labrado { 773a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Adding new connection to pool {}", id); 774f52c03c1SCarson Labrado auto conn = addConnection(); 775244256ccSCarson Labrado conn->req = std::move(thisReq); 776f52c03c1SCarson Labrado conn->callback = std::move(cb); 777f52c03c1SCarson Labrado conn->doResolve(); 778f52c03c1SCarson Labrado } 779f52c03c1SCarson Labrado else if (requestQueue.size() < maxRequestQueueSize) 780f52c03c1SCarson Labrado { 781a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Max pool size reached. Adding data to queue {}", 782a716aa74SEd Tanous id); 783d14a48ffSCarson Labrado requestQueue.emplace_back(std::move(thisReq), std::move(cb)); 784f52c03c1SCarson Labrado } 785f52c03c1SCarson Labrado else 786f52c03c1SCarson Labrado { 78727b0cf90SEd Tanous // If we can't buffer the request then we should let the 78827b0cf90SEd Tanous // callback handle a 429 Too Many Requests dummy response 7896ea90760SEd Tanous BMCWEB_LOG_ERROR("{} request queue full. Dropping request.", id); 79043e14d38SCarson Labrado Response dummyRes; 79143e14d38SCarson Labrado dummyRes.result(boost::beast::http::status::too_many_requests); 79243e14d38SCarson Labrado resHandler(dummyRes); 793f52c03c1SCarson Labrado } 794f52c03c1SCarson Labrado } 795f52c03c1SCarson Labrado 7963d36e3a5SEd Tanous // Callback to be called once the request has been sent afterSendData(const std::weak_ptr<ConnectionPool> & weakSelf,const std::function<void (Response &)> & resHandler,bool keepAlive,uint32_t connId,Response & res)7973d36e3a5SEd Tanous static void afterSendData(const std::weak_ptr<ConnectionPool>& weakSelf, 7983d36e3a5SEd Tanous const std::function<void(Response&)>& resHandler, 7993d36e3a5SEd Tanous bool keepAlive, uint32_t connId, Response& res) 8003d36e3a5SEd Tanous { 8013d36e3a5SEd Tanous // Allow provided callback to perform additional processing of the 8023d36e3a5SEd Tanous // request 8033d36e3a5SEd Tanous resHandler(res); 8043d36e3a5SEd Tanous 8053d36e3a5SEd Tanous // If requests remain in the queue then we want to reuse this 8063d36e3a5SEd Tanous // connection to send the next request 8073d36e3a5SEd Tanous std::shared_ptr<ConnectionPool> self = weakSelf.lock(); 8083d36e3a5SEd Tanous if (!self) 8093d36e3a5SEd Tanous { 81062598e31SEd Tanous BMCWEB_LOG_CRITICAL("{} Failed to capture connection", 81162598e31SEd Tanous logPtr(self.get())); 8123d36e3a5SEd Tanous return; 8133d36e3a5SEd Tanous } 8143d36e3a5SEd Tanous 8153d36e3a5SEd Tanous self->sendNext(keepAlive, connId); 8163d36e3a5SEd Tanous } 8173d36e3a5SEd Tanous addConnection()818f52c03c1SCarson Labrado std::shared_ptr<ConnectionInfo>& addConnection() 819f52c03c1SCarson Labrado { 820f52c03c1SCarson Labrado unsigned int newId = static_cast<unsigned int>(connections.size()); 821f52c03c1SCarson Labrado 822e38778a5SAppaRao Puli auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>( 823a716aa74SEd Tanous ioc, id, connPolicy, destIP, newId)); 824f52c03c1SCarson Labrado 825a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Added connection {} to pool {}", 826a716aa74SEd Tanous connections.size() - 1, id); 827f52c03c1SCarson Labrado 828f52c03c1SCarson Labrado return ret; 829f52c03c1SCarson Labrado } 830f52c03c1SCarson Labrado 831f52c03c1SCarson Labrado public: ConnectionPool(boost::asio::io_context & iocIn,const std::string & idIn,const std::shared_ptr<ConnectionPolicy> & connPolicyIn,const boost::urls::url_view_base & destIPIn)832d14a48ffSCarson Labrado explicit ConnectionPool( 833d14a48ffSCarson Labrado boost::asio::io_context& iocIn, const std::string& idIn, 834d14a48ffSCarson Labrado const std::shared_ptr<ConnectionPolicy>& connPolicyIn, 8354a7fbefdSEd Tanous const boost::urls::url_view_base& destIPIn) : 8368a592810SEd Tanous ioc(iocIn), 837a716aa74SEd Tanous id(idIn), connPolicy(connPolicyIn), destIP(destIPIn) 838f52c03c1SCarson Labrado { 839a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Initializing connection pool for {}", id); 840f52c03c1SCarson Labrado 841f52c03c1SCarson Labrado // Initialize the pool with a single connection 842f52c03c1SCarson Labrado addConnection(); 843fe44eb0bSAyushi Smriti } 844bd030d0aSAppaRao Puli }; 845bd030d0aSAppaRao Puli 846f52c03c1SCarson Labrado class HttpClient 847f52c03c1SCarson Labrado { 848f52c03c1SCarson Labrado private: 849f52c03c1SCarson Labrado std::unordered_map<std::string, std::shared_ptr<ConnectionPool>> 850f52c03c1SCarson Labrado connectionPools; 851f8ca6d79SEd Tanous boost::asio::io_context& ioc; 852d14a48ffSCarson Labrado std::shared_ptr<ConnectionPolicy> connPolicy; 853f52c03c1SCarson Labrado 854039a47e3SCarson Labrado // Used as a dummy callback by sendData() in order to call 855039a47e3SCarson Labrado // sendDataWithCallback() genericResHandler(const Response & res)85602cad96eSEd Tanous static void genericResHandler(const Response& res) 857039a47e3SCarson Labrado { 85862598e31SEd Tanous BMCWEB_LOG_DEBUG("Response handled with return code: {}", 859a716aa74SEd Tanous res.resultInt()); 8604ee8e211SEd Tanous } 861039a47e3SCarson Labrado 862f52c03c1SCarson Labrado public: 863d14a48ffSCarson Labrado HttpClient() = delete; HttpClient(boost::asio::io_context & iocIn,const std::shared_ptr<ConnectionPolicy> & connPolicyIn)864f8ca6d79SEd Tanous explicit HttpClient(boost::asio::io_context& iocIn, 865f8ca6d79SEd Tanous const std::shared_ptr<ConnectionPolicy>& connPolicyIn) : 866f8ca6d79SEd Tanous ioc(iocIn), 867d14a48ffSCarson Labrado connPolicy(connPolicyIn) 868d14a48ffSCarson Labrado {} 869f8ca6d79SEd Tanous 870f52c03c1SCarson Labrado HttpClient(const HttpClient&) = delete; 871f52c03c1SCarson Labrado HttpClient& operator=(const HttpClient&) = delete; 872f52c03c1SCarson Labrado HttpClient(HttpClient&&) = delete; 873f52c03c1SCarson Labrado HttpClient& operator=(HttpClient&&) = delete; 874f52c03c1SCarson Labrado ~HttpClient() = default; 875f52c03c1SCarson Labrado 876a716aa74SEd Tanous // Send a request to destIP where additional processing of the 877039a47e3SCarson Labrado // result is not required sendData(std::string && data,const boost::urls::url_view_base & destUri,const boost::beast::http::fields & httpHeader,const boost::beast::http::verb verb)8784a7fbefdSEd Tanous void sendData(std::string&& data, const boost::urls::url_view_base& destUri, 879f52c03c1SCarson Labrado const boost::beast::http::fields& httpHeader, 880d14a48ffSCarson Labrado const boost::beast::http::verb verb) 881f52c03c1SCarson Labrado { 882e38778a5SAppaRao Puli const std::function<void(Response&)> cb = genericResHandler; 883a716aa74SEd Tanous sendDataWithCallback(std::move(data), destUri, httpHeader, verb, cb); 884039a47e3SCarson Labrado } 885039a47e3SCarson Labrado 886a716aa74SEd Tanous // Send request to destIP and use the provided callback to 887039a47e3SCarson Labrado // handle the response sendDataWithCallback(std::string && data,const boost::urls::url_view_base & destUrl,const boost::beast::http::fields & httpHeader,const boost::beast::http::verb verb,const std::function<void (Response &)> & resHandler)8884a7fbefdSEd Tanous void sendDataWithCallback(std::string&& data, 8894a7fbefdSEd Tanous const boost::urls::url_view_base& destUrl, 890039a47e3SCarson Labrado const boost::beast::http::fields& httpHeader, 891244256ccSCarson Labrado const boost::beast::http::verb verb, 8926b3db60dSEd Tanous const std::function<void(Response&)>& resHandler) 893039a47e3SCarson Labrado { 894a716aa74SEd Tanous std::string clientKey = std::format("{}://{}", destUrl.scheme(), 895a716aa74SEd Tanous destUrl.encoded_host_and_port()); 896d14a48ffSCarson Labrado auto pool = connectionPools.try_emplace(clientKey); 897d14a48ffSCarson Labrado if (pool.first->second == nullptr) 898f52c03c1SCarson Labrado { 899d14a48ffSCarson Labrado pool.first->second = std::make_shared<ConnectionPool>( 900a716aa74SEd Tanous ioc, clientKey, connPolicy, destUrl); 901f52c03c1SCarson Labrado } 90227b0cf90SEd Tanous // Send the data using either the existing connection pool or the 90327b0cf90SEd Tanous // newly created connection pool 904a716aa74SEd Tanous pool.first->second->sendData(std::move(data), destUrl, httpHeader, verb, 905e38778a5SAppaRao Puli resHandler); 906f52c03c1SCarson Labrado } 907f52c03c1SCarson Labrado }; 908bd030d0aSAppaRao Puli } // namespace crow 909