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> 31d43cd0caSEd Tanous #include <boost/asio/steady_timer.hpp> 32d43cd0caSEd Tanous #include <boost/beast/core/flat_buffer.hpp> 33bb49eb5cSEd Tanous #include <boost/beast/core/flat_static_buffer.hpp> 34d43cd0caSEd Tanous #include <boost/beast/http/message.hpp> 35bb49eb5cSEd Tanous #include <boost/beast/http/parser.hpp> 36bb49eb5cSEd Tanous #include <boost/beast/http/read.hpp> 37bb49eb5cSEd Tanous #include <boost/beast/http/write.hpp> 38e38778a5SAppaRao Puli #include <boost/beast/ssl/ssl_stream.hpp> 39bd030d0aSAppaRao Puli #include <boost/beast/version.hpp> 40f52c03c1SCarson Labrado #include <boost/container/devector.hpp> 41bb49eb5cSEd Tanous #include <boost/system/error_code.hpp> 4227b0cf90SEd Tanous #include <boost/url/format.hpp> 4327b0cf90SEd Tanous #include <boost/url/url.hpp> 44*4a7fbefdSEd Tanous #include <boost/url/url_view_base.hpp> 451214b7e7SGunnar Mills 46bd030d0aSAppaRao Puli #include <cstdlib> 47bd030d0aSAppaRao Puli #include <functional> 48bd030d0aSAppaRao Puli #include <iostream> 49bd030d0aSAppaRao Puli #include <memory> 502a5689a7SAppaRao Puli #include <queue> 51bd030d0aSAppaRao Puli #include <string> 52bd030d0aSAppaRao Puli 53bd030d0aSAppaRao Puli namespace crow 54bd030d0aSAppaRao Puli { 5527b0cf90SEd Tanous // With Redfish Aggregation it is assumed we will connect to another 5627b0cf90SEd Tanous // instance of BMCWeb which can handle 100 simultaneous connections. 5766d90c2cSCarson Labrado constexpr size_t maxPoolSize = 20; 5866d90c2cSCarson Labrado constexpr size_t maxRequestQueueSize = 500; 5917dcc312SCarson Labrado constexpr unsigned int httpReadBodyLimit = 131072; 604d94272fSCarson Labrado constexpr unsigned int httpReadBufferSize = 4096; 612a5689a7SAppaRao Puli 62bd030d0aSAppaRao Puli enum class ConnState 63bd030d0aSAppaRao Puli { 642a5689a7SAppaRao Puli initialized, 6529a82b08SSunitha Harish resolveInProgress, 6629a82b08SSunitha Harish resolveFailed, 672a5689a7SAppaRao Puli connectInProgress, 682a5689a7SAppaRao Puli connectFailed, 69bd030d0aSAppaRao Puli connected, 70e38778a5SAppaRao Puli handshakeInProgress, 71e38778a5SAppaRao Puli handshakeFailed, 722a5689a7SAppaRao Puli sendInProgress, 732a5689a7SAppaRao Puli sendFailed, 746eaa1d2fSSunitha Harish recvInProgress, 752a5689a7SAppaRao Puli recvFailed, 762a5689a7SAppaRao Puli idle, 77fe44eb0bSAyushi Smriti closed, 786eaa1d2fSSunitha Harish suspended, 796eaa1d2fSSunitha Harish terminated, 806eaa1d2fSSunitha Harish abortConnection, 81e38778a5SAppaRao Puli sslInitFailed, 826eaa1d2fSSunitha Harish retry 83bd030d0aSAppaRao Puli }; 84bd030d0aSAppaRao Puli 85a7a80296SCarson Labrado static inline boost::system::error_code 86a7a80296SCarson Labrado defaultRetryHandler(unsigned int respCode) 87a7a80296SCarson Labrado { 88a7a80296SCarson Labrado // As a default, assume 200X is alright 8962598e31SEd Tanous BMCWEB_LOG_DEBUG("Using default check for response code validity"); 90a7a80296SCarson Labrado if ((respCode < 200) || (respCode >= 300)) 91a7a80296SCarson Labrado { 92a7a80296SCarson Labrado return boost::system::errc::make_error_code( 93a7a80296SCarson Labrado boost::system::errc::result_out_of_range); 94a7a80296SCarson Labrado } 95a7a80296SCarson Labrado 96a7a80296SCarson Labrado // Return 0 if the response code is valid 97a7a80296SCarson Labrado return boost::system::errc::make_error_code(boost::system::errc::success); 98a7a80296SCarson Labrado }; 99a7a80296SCarson Labrado 10027b0cf90SEd Tanous // We need to allow retry information to be set before a message has been 10127b0cf90SEd Tanous // sent and a connection pool has been created 102d14a48ffSCarson Labrado struct ConnectionPolicy 103f52c03c1SCarson Labrado { 104f52c03c1SCarson Labrado uint32_t maxRetryAttempts = 5; 105d14a48ffSCarson Labrado 106d14a48ffSCarson Labrado // the max size of requests in bytes. 0 for unlimited 107d14a48ffSCarson Labrado boost::optional<uint64_t> requestByteLimit = httpReadBodyLimit; 108d14a48ffSCarson Labrado 109d14a48ffSCarson Labrado size_t maxConnections = 1; 110d14a48ffSCarson Labrado 111f52c03c1SCarson Labrado std::string retryPolicyAction = "TerminateAfterRetries"; 112d14a48ffSCarson Labrado 113d14a48ffSCarson Labrado std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0); 114a7a80296SCarson Labrado std::function<boost::system::error_code(unsigned int respCode)> 115a7a80296SCarson Labrado invalidResp = defaultRetryHandler; 116f52c03c1SCarson Labrado }; 117f52c03c1SCarson Labrado 118f52c03c1SCarson Labrado struct PendingRequest 119f52c03c1SCarson Labrado { 120b2896149SEd Tanous boost::beast::http::request<bmcweb::HttpBody> req; 121039a47e3SCarson Labrado std::function<void(bool, uint32_t, Response&)> callback; 122039a47e3SCarson Labrado PendingRequest( 123b2896149SEd Tanous boost::beast::http::request<bmcweb::HttpBody>&& reqIn, 124d14a48ffSCarson Labrado const std::function<void(bool, uint32_t, Response&)>& callbackIn) : 1258a592810SEd Tanous req(std::move(reqIn)), 126d14a48ffSCarson Labrado callback(callbackIn) 127f52c03c1SCarson Labrado {} 128f52c03c1SCarson Labrado }; 129f52c03c1SCarson Labrado 130e01d0c36SEd Tanous namespace http = boost::beast::http; 131f52c03c1SCarson Labrado class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo> 132bd030d0aSAppaRao Puli { 133bd030d0aSAppaRao Puli private: 134f52c03c1SCarson Labrado ConnState state = ConnState::initialized; 135f52c03c1SCarson Labrado uint32_t retryCount = 0; 136f52c03c1SCarson Labrado std::string subId; 137d14a48ffSCarson Labrado std::shared_ptr<ConnectionPolicy> connPolicy; 138a716aa74SEd Tanous boost::urls::url host; 139f52c03c1SCarson Labrado uint32_t connId; 140f52c03c1SCarson Labrado 141f52c03c1SCarson Labrado // Data buffers 142b2896149SEd Tanous http::request<bmcweb::HttpBody> req; 143b2896149SEd Tanous using parser_type = http::response_parser<bmcweb::HttpBody>; 144e01d0c36SEd Tanous std::optional<parser_type> parser; 1454d94272fSCarson Labrado boost::beast::flat_static_buffer<httpReadBufferSize> buffer; 146039a47e3SCarson Labrado Response res; 1476eaa1d2fSSunitha Harish 148f52c03c1SCarson Labrado // Ascync callables 149039a47e3SCarson Labrado std::function<void(bool, uint32_t, Response&)> callback; 150f8ca6d79SEd Tanous 151f3cb5df9SAbhilash Raju boost::asio::io_context& ioc; 152f3cb5df9SAbhilash Raju 153f8ca6d79SEd Tanous #ifdef BMCWEB_DBUS_DNS_RESOLVER 154e1452beaSEd Tanous using Resolver = async_resolve::Resolver; 155f8ca6d79SEd Tanous #else 156f8ca6d79SEd Tanous using Resolver = boost::asio::ip::tcp::resolver; 157f8ca6d79SEd Tanous #endif 158f8ca6d79SEd Tanous Resolver resolver; 159f8ca6d79SEd Tanous 1600d5f5cf4SEd Tanous boost::asio::ip::tcp::socket conn; 1610d5f5cf4SEd Tanous std::optional<boost::beast::ssl_stream<boost::asio::ip::tcp::socket&>> 1620d5f5cf4SEd Tanous sslConn; 163e38778a5SAppaRao Puli 164f52c03c1SCarson Labrado boost::asio::steady_timer timer; 16584b35604SEd Tanous 166f52c03c1SCarson Labrado friend class ConnectionPool; 167bd030d0aSAppaRao Puli 16829a82b08SSunitha Harish void doResolve() 16929a82b08SSunitha Harish { 17029a82b08SSunitha Harish state = ConnState::resolveInProgress; 171a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Trying to resolve: {}, id: {}", host, connId); 17229a82b08SSunitha Harish 173a716aa74SEd Tanous resolver.async_resolve(host.encoded_host_address(), host.port(), 1743d36e3a5SEd Tanous std::bind_front(&ConnectionInfo::afterResolve, 1753d36e3a5SEd Tanous this, shared_from_this())); 1763d36e3a5SEd Tanous } 1773d36e3a5SEd Tanous 178f8ca6d79SEd Tanous void afterResolve(const std::shared_ptr<ConnectionInfo>& /*self*/, 179f8ca6d79SEd Tanous const boost::system::error_code& ec, 180f8ca6d79SEd Tanous const Resolver::results_type& endpointList) 1813d36e3a5SEd Tanous { 18226f6976fSEd Tanous if (ec || (endpointList.empty())) 18329a82b08SSunitha Harish { 184a716aa74SEd Tanous BMCWEB_LOG_ERROR("Resolve failed: {} {}", ec.message(), host); 1853d36e3a5SEd Tanous state = ConnState::resolveFailed; 1863d36e3a5SEd Tanous waitAndRetry(); 18729a82b08SSunitha Harish return; 18829a82b08SSunitha Harish } 189a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Resolved {}, id: {}", host, connId); 1902a5689a7SAppaRao Puli state = ConnState::connectInProgress; 1912a5689a7SAppaRao Puli 192a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Trying to connect to: {}, id: {}", host, connId); 193b00dcc27SEd Tanous 1940d5f5cf4SEd Tanous timer.expires_after(std::chrono::seconds(30)); 1950d5f5cf4SEd Tanous timer.async_wait(std::bind_front(onTimeout, weak_from_this())); 1960d5f5cf4SEd Tanous 1970d5f5cf4SEd Tanous boost::asio::async_connect( 1980d5f5cf4SEd Tanous conn, endpointList, 199e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterConnect, this, 200e38778a5SAppaRao Puli shared_from_this())); 201e38778a5SAppaRao Puli } 202e38778a5SAppaRao Puli 203e38778a5SAppaRao Puli void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/, 20481c4e330SEd Tanous const boost::beast::error_code& ec, 205e38778a5SAppaRao Puli const boost::asio::ip::tcp::endpoint& endpoint) 206e38778a5SAppaRao Puli { 207513d1ffcSCarson Labrado // The operation already timed out. We don't want do continue down 208513d1ffcSCarson Labrado // this branch 209513d1ffcSCarson Labrado if (ec && ec == boost::asio::error::operation_aborted) 210513d1ffcSCarson Labrado { 211513d1ffcSCarson Labrado return; 212513d1ffcSCarson Labrado } 213513d1ffcSCarson Labrado 2140d5f5cf4SEd Tanous timer.cancel(); 2152a5689a7SAppaRao Puli if (ec) 2162a5689a7SAppaRao Puli { 21762598e31SEd Tanous BMCWEB_LOG_ERROR("Connect {}:{}, id: {} failed: {}", 218a716aa74SEd Tanous endpoint.address().to_string(), endpoint.port(), 219a716aa74SEd Tanous connId, ec.message()); 220e38778a5SAppaRao Puli state = ConnState::connectFailed; 221e38778a5SAppaRao Puli waitAndRetry(); 2222a5689a7SAppaRao Puli return; 2232a5689a7SAppaRao Puli } 224a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Connected to: {}:{}, id: {}", 225a716aa74SEd Tanous endpoint.address().to_string(), endpoint.port(), 226a716aa74SEd Tanous connId); 227e38778a5SAppaRao Puli if (sslConn) 228e38778a5SAppaRao Puli { 2290d5f5cf4SEd Tanous doSslHandshake(); 230e38778a5SAppaRao Puli return; 231e38778a5SAppaRao Puli } 232e38778a5SAppaRao Puli state = ConnState::connected; 233e38778a5SAppaRao Puli sendMessage(); 234e38778a5SAppaRao Puli } 235e38778a5SAppaRao Puli 2360d5f5cf4SEd Tanous void doSslHandshake() 237e38778a5SAppaRao Puli { 238e38778a5SAppaRao Puli if (!sslConn) 239e38778a5SAppaRao Puli { 240e38778a5SAppaRao Puli return; 241e38778a5SAppaRao Puli } 242e38778a5SAppaRao Puli state = ConnState::handshakeInProgress; 2430d5f5cf4SEd Tanous timer.expires_after(std::chrono::seconds(30)); 2440d5f5cf4SEd Tanous timer.async_wait(std::bind_front(onTimeout, weak_from_this())); 245e38778a5SAppaRao Puli sslConn->async_handshake( 246e38778a5SAppaRao Puli boost::asio::ssl::stream_base::client, 247e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterSslHandshake, this, 248e38778a5SAppaRao Puli shared_from_this())); 249e38778a5SAppaRao Puli } 250e38778a5SAppaRao Puli 251e38778a5SAppaRao Puli void afterSslHandshake(const std::shared_ptr<ConnectionInfo>& /*self*/, 25281c4e330SEd Tanous const boost::beast::error_code& ec) 253e38778a5SAppaRao Puli { 254513d1ffcSCarson Labrado // The operation already timed out. We don't want do continue down 255513d1ffcSCarson Labrado // this branch 256513d1ffcSCarson Labrado if (ec && ec == boost::asio::error::operation_aborted) 257513d1ffcSCarson Labrado { 258513d1ffcSCarson Labrado return; 259513d1ffcSCarson Labrado } 260513d1ffcSCarson Labrado 2610d5f5cf4SEd Tanous timer.cancel(); 262e38778a5SAppaRao Puli if (ec) 263e38778a5SAppaRao Puli { 264a716aa74SEd Tanous BMCWEB_LOG_ERROR("SSL Handshake failed - id: {} error: {}", connId, 265a716aa74SEd Tanous ec.message()); 266e38778a5SAppaRao Puli state = ConnState::handshakeFailed; 267e38778a5SAppaRao Puli waitAndRetry(); 268e38778a5SAppaRao Puli return; 269e38778a5SAppaRao Puli } 270a716aa74SEd Tanous BMCWEB_LOG_DEBUG("SSL Handshake successful - id: {}", connId); 271e38778a5SAppaRao Puli state = ConnState::connected; 272e38778a5SAppaRao Puli sendMessage(); 2732a5689a7SAppaRao Puli } 2742a5689a7SAppaRao Puli 275f52c03c1SCarson Labrado void sendMessage() 2762a5689a7SAppaRao Puli { 2772a5689a7SAppaRao Puli state = ConnState::sendInProgress; 2782a5689a7SAppaRao Puli 279bd030d0aSAppaRao Puli // Set a timeout on the operation 2800d5f5cf4SEd Tanous timer.expires_after(std::chrono::seconds(30)); 2810d5f5cf4SEd Tanous timer.async_wait(std::bind_front(onTimeout, weak_from_this())); 282bd030d0aSAppaRao Puli 283bd030d0aSAppaRao Puli // Send the HTTP request to the remote host 284e38778a5SAppaRao Puli if (sslConn) 285e38778a5SAppaRao Puli { 286e38778a5SAppaRao Puli boost::beast::http::async_write( 287e38778a5SAppaRao Puli *sslConn, req, 288e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterWrite, this, 289e38778a5SAppaRao Puli shared_from_this())); 290e38778a5SAppaRao Puli } 291e38778a5SAppaRao Puli else 292e38778a5SAppaRao Puli { 293bd030d0aSAppaRao Puli boost::beast::http::async_write( 294bd030d0aSAppaRao Puli conn, req, 295e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterWrite, this, 296e38778a5SAppaRao Puli shared_from_this())); 297e38778a5SAppaRao Puli } 298e38778a5SAppaRao Puli } 299e38778a5SAppaRao Puli 300e38778a5SAppaRao Puli void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/, 301e38778a5SAppaRao Puli const boost::beast::error_code& ec, size_t bytesTransferred) 302e38778a5SAppaRao Puli { 303513d1ffcSCarson Labrado // The operation already timed out. We don't want do continue down 304513d1ffcSCarson Labrado // this branch 305513d1ffcSCarson Labrado if (ec && ec == boost::asio::error::operation_aborted) 306513d1ffcSCarson Labrado { 307513d1ffcSCarson Labrado return; 308513d1ffcSCarson Labrado } 309513d1ffcSCarson Labrado 3100d5f5cf4SEd Tanous timer.cancel(); 311bd030d0aSAppaRao Puli if (ec) 312bd030d0aSAppaRao Puli { 313a716aa74SEd Tanous BMCWEB_LOG_ERROR("sendMessage() failed: {} {}", ec.message(), host); 314e38778a5SAppaRao Puli state = ConnState::sendFailed; 315e38778a5SAppaRao Puli waitAndRetry(); 316bd030d0aSAppaRao Puli return; 317bd030d0aSAppaRao Puli } 31862598e31SEd Tanous BMCWEB_LOG_DEBUG("sendMessage() bytes transferred: {}", 31962598e31SEd Tanous bytesTransferred); 320bd030d0aSAppaRao Puli 321e38778a5SAppaRao Puli recvMessage(); 322bd030d0aSAppaRao Puli } 323bd030d0aSAppaRao Puli 324bd030d0aSAppaRao Puli void recvMessage() 325bd030d0aSAppaRao Puli { 3266eaa1d2fSSunitha Harish state = ConnState::recvInProgress; 3276eaa1d2fSSunitha Harish 328e01d0c36SEd Tanous parser_type& thisParser = parser.emplace(std::piecewise_construct, 329e01d0c36SEd Tanous std::make_tuple()); 330d14a48ffSCarson Labrado 331e01d0c36SEd Tanous thisParser.body_limit(connPolicy->requestByteLimit); 3326eaa1d2fSSunitha Harish 3330d5f5cf4SEd Tanous timer.expires_after(std::chrono::seconds(30)); 3340d5f5cf4SEd Tanous timer.async_wait(std::bind_front(onTimeout, weak_from_this())); 3350d5f5cf4SEd Tanous 336bd030d0aSAppaRao Puli // Receive the HTTP response 337e38778a5SAppaRao Puli if (sslConn) 338e38778a5SAppaRao Puli { 339e38778a5SAppaRao Puli boost::beast::http::async_read( 340e01d0c36SEd Tanous *sslConn, buffer, thisParser, 341e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterRead, this, 342e38778a5SAppaRao Puli shared_from_this())); 343e38778a5SAppaRao Puli } 344e38778a5SAppaRao Puli else 345e38778a5SAppaRao Puli { 346bd030d0aSAppaRao Puli boost::beast::http::async_read( 347e01d0c36SEd Tanous conn, buffer, thisParser, 348e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterRead, this, 349e38778a5SAppaRao Puli shared_from_this())); 350e38778a5SAppaRao Puli } 351e38778a5SAppaRao Puli } 352e38778a5SAppaRao Puli 353e38778a5SAppaRao Puli void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/, 354e38778a5SAppaRao Puli const boost::beast::error_code& ec, 355e38778a5SAppaRao Puli const std::size_t& bytesTransferred) 356e38778a5SAppaRao Puli { 357513d1ffcSCarson Labrado // The operation already timed out. We don't want do continue down 358513d1ffcSCarson Labrado // this branch 359513d1ffcSCarson Labrado if (ec && ec == boost::asio::error::operation_aborted) 360513d1ffcSCarson Labrado { 361513d1ffcSCarson Labrado return; 362513d1ffcSCarson Labrado } 363513d1ffcSCarson Labrado 3640d5f5cf4SEd Tanous timer.cancel(); 365e38778a5SAppaRao Puli if (ec && ec != boost::asio::ssl::error::stream_truncated) 366bd030d0aSAppaRao Puli { 367a716aa74SEd Tanous BMCWEB_LOG_ERROR("recvMessage() failed: {} from {}", ec.message(), 368a716aa74SEd Tanous host); 369e38778a5SAppaRao Puli state = ConnState::recvFailed; 370e38778a5SAppaRao Puli waitAndRetry(); 371bd030d0aSAppaRao Puli return; 372bd030d0aSAppaRao Puli } 37362598e31SEd Tanous BMCWEB_LOG_DEBUG("recvMessage() bytes transferred: {}", 37462598e31SEd Tanous bytesTransferred); 375e01d0c36SEd Tanous if (!parser) 376e01d0c36SEd Tanous { 377e01d0c36SEd Tanous return; 378e01d0c36SEd Tanous } 37952e31629SEd Tanous BMCWEB_LOG_DEBUG("recvMessage() data: {}", parser->get().body().str()); 380bd030d0aSAppaRao Puli 381e38778a5SAppaRao Puli unsigned int respCode = parser->get().result_int(); 38262598e31SEd Tanous BMCWEB_LOG_DEBUG("recvMessage() Header Response Code: {}", respCode); 3836eaa1d2fSSunitha Harish 384f3cb5df9SAbhilash Raju // Handle the case of stream_truncated. Some servers close the ssl 385f3cb5df9SAbhilash Raju // connection uncleanly, so check to see if we got a full response 386f3cb5df9SAbhilash Raju // before we handle this as an error. 387f3cb5df9SAbhilash Raju if (!parser->is_done()) 388f3cb5df9SAbhilash Raju { 389f3cb5df9SAbhilash Raju state = ConnState::recvFailed; 390f3cb5df9SAbhilash Raju waitAndRetry(); 391f3cb5df9SAbhilash Raju return; 392f3cb5df9SAbhilash Raju } 393f3cb5df9SAbhilash Raju 394a7a80296SCarson Labrado // Make sure the received response code is valid as defined by 395a7a80296SCarson Labrado // the associated retry policy 396d14a48ffSCarson Labrado if (connPolicy->invalidResp(respCode)) 3976eaa1d2fSSunitha Harish { 3986eaa1d2fSSunitha Harish // The listener failed to receive the Sent-Event 39962598e31SEd Tanous BMCWEB_LOG_ERROR( 40062598e31SEd Tanous "recvMessage() Listener Failed to " 401a716aa74SEd Tanous "receive Sent-Event. Header Response Code: {} from {}", 402a716aa74SEd Tanous respCode, host); 403e38778a5SAppaRao Puli state = ConnState::recvFailed; 404e38778a5SAppaRao Puli waitAndRetry(); 4056eaa1d2fSSunitha Harish return; 4066eaa1d2fSSunitha Harish } 407bd030d0aSAppaRao Puli 408f52c03c1SCarson Labrado // Send is successful 409f52c03c1SCarson Labrado // Reset the counter just in case this was after retrying 410e38778a5SAppaRao Puli retryCount = 0; 4116eaa1d2fSSunitha Harish 4126eaa1d2fSSunitha Harish // Keep the connection alive if server supports it 4136eaa1d2fSSunitha Harish // Else close the connection 41462598e31SEd Tanous BMCWEB_LOG_DEBUG("recvMessage() keepalive : {}", parser->keep_alive()); 4156eaa1d2fSSunitha Harish 416039a47e3SCarson Labrado // Copy the response into a Response object so that it can be 417039a47e3SCarson Labrado // processed by the callback function. 41827b0cf90SEd Tanous res.response = parser->release(); 419e38778a5SAppaRao Puli callback(parser->keep_alive(), connId, res); 420513d1ffcSCarson Labrado res.clear(); 421bd030d0aSAppaRao Puli } 422bd030d0aSAppaRao Puli 4230d5f5cf4SEd Tanous static void onTimeout(const std::weak_ptr<ConnectionInfo>& weakSelf, 4245e7e2dc5SEd Tanous const boost::system::error_code& ec) 4250d5f5cf4SEd Tanous { 4260d5f5cf4SEd Tanous if (ec == boost::asio::error::operation_aborted) 4270d5f5cf4SEd Tanous { 42862598e31SEd Tanous BMCWEB_LOG_DEBUG( 42962598e31SEd Tanous "async_wait failed since the operation is aborted"); 4300d5f5cf4SEd Tanous return; 4310d5f5cf4SEd Tanous } 4320d5f5cf4SEd Tanous if (ec) 4330d5f5cf4SEd Tanous { 43462598e31SEd Tanous BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message()); 43527b0cf90SEd Tanous // If the timer fails, we need to close the socket anyway, same 43627b0cf90SEd Tanous // as if it expired. 4370d5f5cf4SEd Tanous } 4380d5f5cf4SEd Tanous std::shared_ptr<ConnectionInfo> self = weakSelf.lock(); 4390d5f5cf4SEd Tanous if (self == nullptr) 4400d5f5cf4SEd Tanous { 4410d5f5cf4SEd Tanous return; 4420d5f5cf4SEd Tanous } 4430d5f5cf4SEd Tanous self->waitAndRetry(); 4440d5f5cf4SEd Tanous } 4450d5f5cf4SEd Tanous 4466eaa1d2fSSunitha Harish void waitAndRetry() 447bd030d0aSAppaRao Puli { 448d14a48ffSCarson Labrado if ((retryCount >= connPolicy->maxRetryAttempts) || 449e38778a5SAppaRao Puli (state == ConnState::sslInitFailed)) 4502a5689a7SAppaRao Puli { 451a716aa74SEd Tanous BMCWEB_LOG_ERROR("Maximum number of retries reached. {}", host); 45262598e31SEd Tanous BMCWEB_LOG_DEBUG("Retry policy: {}", connPolicy->retryPolicyAction); 453039a47e3SCarson Labrado 454d14a48ffSCarson Labrado if (connPolicy->retryPolicyAction == "TerminateAfterRetries") 455fe44eb0bSAyushi Smriti { 456fe44eb0bSAyushi Smriti // TODO: delete subscription 457fe44eb0bSAyushi Smriti state = ConnState::terminated; 458fe44eb0bSAyushi Smriti } 459d14a48ffSCarson Labrado if (connPolicy->retryPolicyAction == "SuspendRetries") 460fe44eb0bSAyushi Smriti { 4612a5689a7SAppaRao Puli state = ConnState::suspended; 4622a5689a7SAppaRao Puli } 463513d1ffcSCarson Labrado 464513d1ffcSCarson Labrado // We want to return a 502 to indicate there was an error with 465513d1ffcSCarson Labrado // the external server 466513d1ffcSCarson Labrado res.result(boost::beast::http::status::bad_gateway); 467513d1ffcSCarson Labrado callback(false, connId, res); 468513d1ffcSCarson Labrado res.clear(); 469513d1ffcSCarson Labrado 47027b0cf90SEd Tanous // Reset the retrycount to zero so that client can try 47127b0cf90SEd Tanous // connecting again if needed 472fe44eb0bSAyushi Smriti retryCount = 0; 4732a5689a7SAppaRao Puli return; 4742a5689a7SAppaRao Puli } 4752a5689a7SAppaRao Puli 4762a5689a7SAppaRao Puli retryCount++; 477fe44eb0bSAyushi Smriti 47862598e31SEd Tanous BMCWEB_LOG_DEBUG("Attempt retry after {} seconds. RetryCount = {}", 479a716aa74SEd Tanous connPolicy->retryIntervalSecs.count(), retryCount); 480d14a48ffSCarson Labrado timer.expires_after(connPolicy->retryIntervalSecs); 4813d36e3a5SEd Tanous timer.async_wait(std::bind_front(&ConnectionInfo::onTimerDone, this, 4823d36e3a5SEd Tanous shared_from_this())); 4833d36e3a5SEd Tanous } 4843d36e3a5SEd Tanous 4853d36e3a5SEd Tanous void onTimerDone(const std::shared_ptr<ConnectionInfo>& /*self*/, 4863d36e3a5SEd Tanous const boost::system::error_code& ec) 4873d36e3a5SEd Tanous { 4886eaa1d2fSSunitha Harish if (ec == boost::asio::error::operation_aborted) 4896eaa1d2fSSunitha Harish { 49062598e31SEd Tanous BMCWEB_LOG_DEBUG( 49162598e31SEd Tanous "async_wait failed since the operation is aborted{}", 49262598e31SEd Tanous ec.message()); 4936eaa1d2fSSunitha Harish } 4946eaa1d2fSSunitha Harish else if (ec) 4956eaa1d2fSSunitha Harish { 49662598e31SEd Tanous BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message()); 4976eaa1d2fSSunitha Harish // Ignore the error and continue the retry loop to attempt 4986eaa1d2fSSunitha Harish // sending the event as per the retry policy 4996eaa1d2fSSunitha Harish } 5006eaa1d2fSSunitha Harish 501f52c03c1SCarson Labrado // Let's close the connection and restart from resolve. 502f3cb5df9SAbhilash Raju shutdownConn(true); 503f3cb5df9SAbhilash Raju } 504f3cb5df9SAbhilash Raju 505f3cb5df9SAbhilash Raju void restartConnection() 506f3cb5df9SAbhilash Raju { 507f3cb5df9SAbhilash Raju BMCWEB_LOG_DEBUG("{}, id: {} restartConnection", host, 508f3cb5df9SAbhilash Raju std::to_string(connId)); 509f3cb5df9SAbhilash Raju initializeConnection(host.scheme() == "https"); 510f3cb5df9SAbhilash Raju doResolve(); 5112a5689a7SAppaRao Puli } 5122a5689a7SAppaRao Puli 513e38778a5SAppaRao Puli void shutdownConn(bool retry) 514fe44eb0bSAyushi Smriti { 515f52c03c1SCarson Labrado boost::beast::error_code ec; 5160d5f5cf4SEd Tanous conn.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); 517f52c03c1SCarson Labrado conn.close(); 518f52c03c1SCarson Labrado 519f52c03c1SCarson Labrado // not_connected happens sometimes so don't bother reporting it. 520f52c03c1SCarson Labrado if (ec && ec != boost::beast::errc::not_connected) 5212a5689a7SAppaRao Puli { 522a716aa74SEd Tanous BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId, 52362598e31SEd Tanous ec.message()); 5246eaa1d2fSSunitha Harish } 5255cab68f3SCarson Labrado else 5265cab68f3SCarson Labrado { 527a716aa74SEd Tanous BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId); 5285cab68f3SCarson Labrado } 529ca723762SEd Tanous 530e38778a5SAppaRao Puli if (retry) 53192a74e56SAppaRao Puli { 532f52c03c1SCarson Labrado // Now let's try to resend the data 533f52c03c1SCarson Labrado state = ConnState::retry; 534f3cb5df9SAbhilash Raju restartConnection(); 535e38778a5SAppaRao Puli } 536e38778a5SAppaRao Puli else 537e38778a5SAppaRao Puli { 538e38778a5SAppaRao Puli state = ConnState::closed; 539e38778a5SAppaRao Puli } 540e38778a5SAppaRao Puli } 541e38778a5SAppaRao Puli 542e38778a5SAppaRao Puli void doClose(bool retry = false) 543e38778a5SAppaRao Puli { 544e38778a5SAppaRao Puli if (!sslConn) 545e38778a5SAppaRao Puli { 546e38778a5SAppaRao Puli shutdownConn(retry); 547e38778a5SAppaRao Puli return; 548e38778a5SAppaRao Puli } 549e38778a5SAppaRao Puli 550e38778a5SAppaRao Puli sslConn->async_shutdown( 551e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterSslShutdown, this, 552e38778a5SAppaRao Puli shared_from_this(), retry)); 553e38778a5SAppaRao Puli } 554e38778a5SAppaRao Puli 555e38778a5SAppaRao Puli void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/, 556e38778a5SAppaRao Puli bool retry, const boost::system::error_code& ec) 557e38778a5SAppaRao Puli { 558e38778a5SAppaRao Puli if (ec) 559e38778a5SAppaRao Puli { 560a716aa74SEd Tanous BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId, 56162598e31SEd Tanous ec.message()); 562e38778a5SAppaRao Puli } 563e38778a5SAppaRao Puli else 564e38778a5SAppaRao Puli { 565a716aa74SEd Tanous BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId); 566e38778a5SAppaRao Puli } 567e38778a5SAppaRao Puli shutdownConn(retry); 568e38778a5SAppaRao Puli } 569e38778a5SAppaRao Puli 570e38778a5SAppaRao Puli void setCipherSuiteTLSext() 571e38778a5SAppaRao Puli { 572e38778a5SAppaRao Puli if (!sslConn) 573e38778a5SAppaRao Puli { 574e38778a5SAppaRao Puli return; 575e38778a5SAppaRao Puli } 576e7c2991eSRavi Teja 577e7c2991eSRavi Teja if (host.host_type() != boost::urls::host_type::name) 578e7c2991eSRavi Teja { 579e7c2991eSRavi Teja // Avoid setting SNI hostname if its IP address 580e7c2991eSRavi Teja return; 581e7c2991eSRavi Teja } 582e7c2991eSRavi Teja // Create a null terminated string for SSL 583a716aa74SEd Tanous std::string hostname(host.encoded_host_address()); 584e38778a5SAppaRao Puli // NOTE: The SSL_set_tlsext_host_name is defined in tlsv1.h header 585e38778a5SAppaRao Puli // file but its having old style casting (name is cast to void*). 586e38778a5SAppaRao Puli // Since bmcweb compiler treats all old-style-cast as error, its 587e38778a5SAppaRao Puli // causing the build failure. So replaced the same macro inline and 588e38778a5SAppaRao Puli // did corrected the code by doing static_cast to viod*. This has to 589e38778a5SAppaRao Puli // be fixed in openssl library in long run. Set SNI Hostname (many 590e38778a5SAppaRao Puli // hosts need this to handshake successfully) 591e38778a5SAppaRao Puli if (SSL_ctrl(sslConn->native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME, 592e38778a5SAppaRao Puli TLSEXT_NAMETYPE_host_name, 593a716aa74SEd Tanous static_cast<void*>(hostname.data())) == 0) 594e38778a5SAppaRao Puli 595e38778a5SAppaRao Puli { 596e38778a5SAppaRao Puli boost::beast::error_code ec{static_cast<int>(::ERR_get_error()), 597e38778a5SAppaRao Puli boost::asio::error::get_ssl_category()}; 598e38778a5SAppaRao Puli 599a716aa74SEd Tanous BMCWEB_LOG_ERROR("SSL_set_tlsext_host_name {}, id: {} failed: {}", 600a716aa74SEd Tanous host, connId, ec.message()); 601e38778a5SAppaRao Puli // Set state as sslInit failed so that we close the connection 602e38778a5SAppaRao Puli // and take appropriate action as per retry configuration. 603e38778a5SAppaRao Puli state = ConnState::sslInitFailed; 604e38778a5SAppaRao Puli waitAndRetry(); 605e38778a5SAppaRao Puli return; 606e38778a5SAppaRao Puli } 607bd030d0aSAppaRao Puli } 608bd030d0aSAppaRao Puli 609f3cb5df9SAbhilash Raju void initializeConnection(bool ssl) 610e38778a5SAppaRao Puli { 611f3cb5df9SAbhilash Raju conn = boost::asio::ip::tcp::socket(ioc); 612f3cb5df9SAbhilash Raju if (ssl) 613e38778a5SAppaRao Puli { 614e38778a5SAppaRao Puli std::optional<boost::asio::ssl::context> sslCtx = 615e38778a5SAppaRao Puli ensuressl::getSSLClientContext(); 616e38778a5SAppaRao Puli 617e38778a5SAppaRao Puli if (!sslCtx) 618e38778a5SAppaRao Puli { 619a716aa74SEd Tanous BMCWEB_LOG_ERROR("prepareSSLContext failed - {}, id: {}", host, 620a716aa74SEd Tanous connId); 621e38778a5SAppaRao Puli // Don't retry if failure occurs while preparing SSL context 62227b0cf90SEd Tanous // such as certificate is invalid or set cipher failure or 62327b0cf90SEd Tanous // set host name failure etc... Setting conn state to 62427b0cf90SEd Tanous // sslInitFailed and connection state will be transitioned 62527b0cf90SEd Tanous // to next state depending on retry policy set by 62627b0cf90SEd Tanous // subscription. 627e38778a5SAppaRao Puli state = ConnState::sslInitFailed; 628e38778a5SAppaRao Puli waitAndRetry(); 629e38778a5SAppaRao Puli return; 630e38778a5SAppaRao Puli } 631e38778a5SAppaRao Puli sslConn.emplace(conn, *sslCtx); 632e38778a5SAppaRao Puli setCipherSuiteTLSext(); 633e38778a5SAppaRao Puli } 634e38778a5SAppaRao Puli } 635f3cb5df9SAbhilash Raju 636f3cb5df9SAbhilash Raju public: 637f3cb5df9SAbhilash Raju explicit ConnectionInfo( 638f3cb5df9SAbhilash Raju boost::asio::io_context& iocIn, const std::string& idIn, 639f3cb5df9SAbhilash Raju const std::shared_ptr<ConnectionPolicy>& connPolicyIn, 640*4a7fbefdSEd Tanous const boost::urls::url_view_base& hostIn, unsigned int connIdIn) : 641f3cb5df9SAbhilash Raju subId(idIn), 642f3cb5df9SAbhilash Raju connPolicy(connPolicyIn), host(hostIn), connId(connIdIn), ioc(iocIn), 643f3cb5df9SAbhilash Raju resolver(iocIn), conn(iocIn), timer(iocIn) 644f3cb5df9SAbhilash Raju { 645f3cb5df9SAbhilash Raju initializeConnection(host.scheme() == "https"); 646f3cb5df9SAbhilash Raju } 647f52c03c1SCarson Labrado }; 648bd030d0aSAppaRao Puli 649f52c03c1SCarson Labrado class ConnectionPool : public std::enable_shared_from_this<ConnectionPool> 650bd030d0aSAppaRao Puli { 651f52c03c1SCarson Labrado private: 652f52c03c1SCarson Labrado boost::asio::io_context& ioc; 653e38778a5SAppaRao Puli std::string id; 654d14a48ffSCarson Labrado std::shared_ptr<ConnectionPolicy> connPolicy; 655a716aa74SEd Tanous boost::urls::url destIP; 656f52c03c1SCarson Labrado std::vector<std::shared_ptr<ConnectionInfo>> connections; 657f52c03c1SCarson Labrado boost::container::devector<PendingRequest> requestQueue; 658f52c03c1SCarson Labrado 659f52c03c1SCarson Labrado friend class HttpClient; 660f52c03c1SCarson Labrado 661244256ccSCarson Labrado // Configure a connections's request, callback, and retry info in 662244256ccSCarson Labrado // preparation to begin sending the request 663f52c03c1SCarson Labrado void setConnProps(ConnectionInfo& conn) 664bd030d0aSAppaRao Puli { 665f52c03c1SCarson Labrado if (requestQueue.empty()) 666f52c03c1SCarson Labrado { 66762598e31SEd Tanous BMCWEB_LOG_ERROR( 66862598e31SEd Tanous "setConnProps() should not have been called when requestQueue is empty"); 669bd030d0aSAppaRao Puli return; 670bd030d0aSAppaRao Puli } 671bd030d0aSAppaRao Puli 67252e31629SEd Tanous PendingRequest& nextReq = requestQueue.front(); 673244256ccSCarson Labrado conn.req = std::move(nextReq.req); 674244256ccSCarson Labrado conn.callback = std::move(nextReq.callback); 675f52c03c1SCarson Labrado 676a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Setting properties for connection {}, id: {}", 677a716aa74SEd Tanous conn.host, conn.connId); 678f52c03c1SCarson Labrado 679f52c03c1SCarson Labrado // We can remove the request from the queue at this point 680f52c03c1SCarson Labrado requestQueue.pop_front(); 681f52c03c1SCarson Labrado } 682f52c03c1SCarson Labrado 683f52c03c1SCarson Labrado // Gets called as part of callback after request is sent 684f52c03c1SCarson Labrado // Reuses the connection if there are any requests waiting to be sent 685f52c03c1SCarson Labrado // Otherwise closes the connection if it is not a keep-alive 686f52c03c1SCarson Labrado void sendNext(bool keepAlive, uint32_t connId) 687f52c03c1SCarson Labrado { 688f52c03c1SCarson Labrado auto conn = connections[connId]; 68946a81465SCarson Labrado 69046a81465SCarson Labrado // Allow the connection's handler to be deleted 69146a81465SCarson Labrado // This is needed because of Redfish Aggregation passing an 69246a81465SCarson Labrado // AsyncResponse shared_ptr to this callback 69346a81465SCarson Labrado conn->callback = nullptr; 69446a81465SCarson Labrado 695f52c03c1SCarson Labrado // Reuse the connection to send the next request in the queue 696f52c03c1SCarson Labrado if (!requestQueue.empty()) 697f52c03c1SCarson Labrado { 69862598e31SEd Tanous BMCWEB_LOG_DEBUG( 6998ece0e45SEd Tanous "{} requests remaining in queue for {}, reusing connection {}", 700a716aa74SEd Tanous requestQueue.size(), destIP, connId); 701f52c03c1SCarson Labrado 702f52c03c1SCarson Labrado setConnProps(*conn); 703f52c03c1SCarson Labrado 704f52c03c1SCarson Labrado if (keepAlive) 705f52c03c1SCarson Labrado { 706f52c03c1SCarson Labrado conn->sendMessage(); 7072a5689a7SAppaRao Puli } 7082a5689a7SAppaRao Puli else 7092a5689a7SAppaRao Puli { 710f52c03c1SCarson Labrado // Server is not keep-alive enabled so we need to close the 711f52c03c1SCarson Labrado // connection and then start over from resolve 712f52c03c1SCarson Labrado conn->doClose(); 713f52c03c1SCarson Labrado conn->doResolve(); 714f52c03c1SCarson Labrado } 715f52c03c1SCarson Labrado return; 716f52c03c1SCarson Labrado } 717f52c03c1SCarson Labrado 718f52c03c1SCarson Labrado // No more messages to send so close the connection if necessary 719f52c03c1SCarson Labrado if (keepAlive) 720f52c03c1SCarson Labrado { 721f52c03c1SCarson Labrado conn->state = ConnState::idle; 722f52c03c1SCarson Labrado } 723f52c03c1SCarson Labrado else 724f52c03c1SCarson Labrado { 725f52c03c1SCarson Labrado // Abort the connection since server is not keep-alive enabled 726f52c03c1SCarson Labrado conn->state = ConnState::abortConnection; 727f52c03c1SCarson Labrado conn->doClose(); 7282a5689a7SAppaRao Puli } 729bd030d0aSAppaRao Puli } 730bd030d0aSAppaRao Puli 731*4a7fbefdSEd Tanous void sendData(std::string&& data, const boost::urls::url_view_base& destUri, 732244256ccSCarson Labrado const boost::beast::http::fields& httpHeader, 733244256ccSCarson Labrado const boost::beast::http::verb verb, 7346b3db60dSEd Tanous const std::function<void(Response&)>& resHandler) 735fe44eb0bSAyushi Smriti { 736244256ccSCarson Labrado // Construct the request to be sent 737b2896149SEd Tanous boost::beast::http::request<bmcweb::HttpBody> thisReq( 738a716aa74SEd Tanous verb, destUri.encoded_target(), 11, "", httpHeader); 739a716aa74SEd Tanous thisReq.set(boost::beast::http::field::host, 740a716aa74SEd Tanous destUri.encoded_host_address()); 741244256ccSCarson Labrado thisReq.keep_alive(true); 74252e31629SEd Tanous thisReq.body().str() = std::move(data); 743244256ccSCarson Labrado thisReq.prepare_payload(); 7443d36e3a5SEd Tanous auto cb = std::bind_front(&ConnectionPool::afterSendData, 7453d36e3a5SEd Tanous weak_from_this(), resHandler); 746f52c03c1SCarson Labrado // Reuse an existing connection if one is available 747f52c03c1SCarson Labrado for (unsigned int i = 0; i < connections.size(); i++) 748fe44eb0bSAyushi Smriti { 749f52c03c1SCarson Labrado auto conn = connections[i]; 750f52c03c1SCarson Labrado if ((conn->state == ConnState::idle) || 751f52c03c1SCarson Labrado (conn->state == ConnState::initialized) || 752f52c03c1SCarson Labrado (conn->state == ConnState::closed)) 753f52c03c1SCarson Labrado { 754244256ccSCarson Labrado conn->req = std::move(thisReq); 755f52c03c1SCarson Labrado conn->callback = std::move(cb); 756a716aa74SEd Tanous std::string commonMsg = std::format("{} from pool {}", i, id); 757f52c03c1SCarson Labrado 758f52c03c1SCarson Labrado if (conn->state == ConnState::idle) 759f52c03c1SCarson Labrado { 76062598e31SEd Tanous BMCWEB_LOG_DEBUG("Grabbing idle connection {}", commonMsg); 761f52c03c1SCarson Labrado conn->sendMessage(); 762f52c03c1SCarson Labrado } 763f52c03c1SCarson Labrado else 764f52c03c1SCarson Labrado { 76562598e31SEd Tanous BMCWEB_LOG_DEBUG("Reusing existing connection {}", 76662598e31SEd Tanous commonMsg); 767f52c03c1SCarson Labrado conn->doResolve(); 768f52c03c1SCarson Labrado } 769f52c03c1SCarson Labrado return; 770f52c03c1SCarson Labrado } 771f52c03c1SCarson Labrado } 772f52c03c1SCarson Labrado 77327b0cf90SEd Tanous // All connections in use so create a new connection or add request 77427b0cf90SEd Tanous // to the queue 775d14a48ffSCarson Labrado if (connections.size() < connPolicy->maxConnections) 776f52c03c1SCarson Labrado { 777a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Adding new connection to pool {}", id); 778f52c03c1SCarson Labrado auto conn = addConnection(); 779244256ccSCarson Labrado conn->req = std::move(thisReq); 780f52c03c1SCarson Labrado conn->callback = std::move(cb); 781f52c03c1SCarson Labrado conn->doResolve(); 782f52c03c1SCarson Labrado } 783f52c03c1SCarson Labrado else if (requestQueue.size() < maxRequestQueueSize) 784f52c03c1SCarson Labrado { 785a716aa74SEd Tanous BMCWEB_LOG_DEBUG("Max pool size reached. Adding data to queue {}", 786a716aa74SEd Tanous id); 787d14a48ffSCarson Labrado requestQueue.emplace_back(std::move(thisReq), std::move(cb)); 788f52c03c1SCarson Labrado } 789f52c03c1SCarson Labrado else 790f52c03c1SCarson Labrado { 79127b0cf90SEd Tanous // If we can't buffer the request then we should let the 79227b0cf90SEd Tanous // callback handle a 429 Too Many Requests dummy response 7936ea90760SEd Tanous BMCWEB_LOG_ERROR("{} request queue full. Dropping request.", 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, 839*4a7fbefdSEd Tanous const boost::urls::url_view_base& 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 882*4a7fbefdSEd Tanous void sendData(std::string&& data, const boost::urls::url_view_base& 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 892*4a7fbefdSEd Tanous void sendDataWithCallback(std::string&& data, 893*4a7fbefdSEd Tanous const boost::urls::url_view_base& destUrl, 894039a47e3SCarson Labrado const boost::beast::http::fields& httpHeader, 895244256ccSCarson Labrado const boost::beast::http::verb verb, 8966b3db60dSEd Tanous const std::function<void(Response&)>& resHandler) 897039a47e3SCarson Labrado { 898a716aa74SEd Tanous std::string clientKey = std::format("{}://{}", destUrl.scheme(), 899a716aa74SEd Tanous destUrl.encoded_host_and_port()); 900d14a48ffSCarson Labrado auto pool = connectionPools.try_emplace(clientKey); 901d14a48ffSCarson Labrado if (pool.first->second == nullptr) 902f52c03c1SCarson Labrado { 903d14a48ffSCarson Labrado pool.first->second = std::make_shared<ConnectionPool>( 904a716aa74SEd Tanous ioc, clientKey, connPolicy, destUrl); 905f52c03c1SCarson Labrado } 90627b0cf90SEd Tanous // Send the data using either the existing connection pool or the 90727b0cf90SEd Tanous // newly created connection pool 908a716aa74SEd Tanous pool.first->second->sendData(std::move(data), destUrl, httpHeader, verb, 909e38778a5SAppaRao Puli resHandler); 910f52c03c1SCarson Labrado } 911f52c03c1SCarson Labrado }; 912bd030d0aSAppaRao Puli } // namespace crow 913