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 17*0d5f5cf4SEd Tanous #include <boost/asio/connect.hpp> 18bb49eb5cSEd Tanous #include <boost/asio/io_context.hpp> 1929a82b08SSunitha Harish #include <boost/asio/ip/address.hpp> 2029a82b08SSunitha Harish #include <boost/asio/ip/basic_endpoint.hpp> 21bb49eb5cSEd Tanous #include <boost/asio/ip/tcp.hpp> 22e38778a5SAppaRao Puli #include <boost/asio/ssl/context.hpp> 23e38778a5SAppaRao Puli #include <boost/asio/ssl/error.hpp> 24d43cd0caSEd Tanous #include <boost/asio/steady_timer.hpp> 25d43cd0caSEd Tanous #include <boost/beast/core/flat_buffer.hpp> 26bb49eb5cSEd Tanous #include <boost/beast/core/flat_static_buffer.hpp> 27d43cd0caSEd Tanous #include <boost/beast/http/message.hpp> 28bb49eb5cSEd Tanous #include <boost/beast/http/parser.hpp> 29bb49eb5cSEd Tanous #include <boost/beast/http/read.hpp> 30bb49eb5cSEd Tanous #include <boost/beast/http/string_body.hpp> 31bb49eb5cSEd Tanous #include <boost/beast/http/write.hpp> 32e38778a5SAppaRao Puli #include <boost/beast/ssl/ssl_stream.hpp> 33bd030d0aSAppaRao Puli #include <boost/beast/version.hpp> 34f52c03c1SCarson Labrado #include <boost/container/devector.hpp> 35bb49eb5cSEd Tanous #include <boost/system/error_code.hpp> 36bb49eb5cSEd Tanous #include <http/http_response.hpp> 3729a82b08SSunitha Harish #include <include/async_resolve.hpp> 38bb49eb5cSEd Tanous #include <logging.hpp> 39e38778a5SAppaRao Puli #include <ssl_key_handler.hpp> 401214b7e7SGunnar Mills 41bd030d0aSAppaRao Puli #include <cstdlib> 42bd030d0aSAppaRao Puli #include <functional> 43bd030d0aSAppaRao Puli #include <iostream> 44bd030d0aSAppaRao Puli #include <memory> 452a5689a7SAppaRao Puli #include <queue> 46bd030d0aSAppaRao Puli #include <string> 47bd030d0aSAppaRao Puli 48bd030d0aSAppaRao Puli namespace crow 49bd030d0aSAppaRao Puli { 50bd030d0aSAppaRao Puli 51f52c03c1SCarson Labrado // It is assumed that the BMC should be able to handle 4 parallel connections 52f52c03c1SCarson Labrado constexpr uint8_t maxPoolSize = 4; 53f52c03c1SCarson Labrado constexpr uint8_t maxRequestQueueSize = 50; 5417dcc312SCarson Labrado constexpr unsigned int httpReadBodyLimit = 131072; 554d94272fSCarson Labrado constexpr unsigned int httpReadBufferSize = 4096; 562a5689a7SAppaRao Puli 57bd030d0aSAppaRao Puli enum class ConnState 58bd030d0aSAppaRao Puli { 592a5689a7SAppaRao Puli initialized, 6029a82b08SSunitha Harish resolveInProgress, 6129a82b08SSunitha Harish resolveFailed, 622a5689a7SAppaRao Puli connectInProgress, 632a5689a7SAppaRao Puli connectFailed, 64bd030d0aSAppaRao Puli connected, 65e38778a5SAppaRao Puli handshakeInProgress, 66e38778a5SAppaRao Puli handshakeFailed, 672a5689a7SAppaRao Puli sendInProgress, 682a5689a7SAppaRao Puli sendFailed, 696eaa1d2fSSunitha Harish recvInProgress, 702a5689a7SAppaRao Puli recvFailed, 712a5689a7SAppaRao Puli idle, 72fe44eb0bSAyushi Smriti closed, 736eaa1d2fSSunitha Harish suspended, 746eaa1d2fSSunitha Harish terminated, 756eaa1d2fSSunitha Harish abortConnection, 76e38778a5SAppaRao Puli sslInitFailed, 776eaa1d2fSSunitha Harish retry 78bd030d0aSAppaRao Puli }; 79bd030d0aSAppaRao Puli 80a7a80296SCarson Labrado static inline boost::system::error_code 81a7a80296SCarson Labrado defaultRetryHandler(unsigned int respCode) 82a7a80296SCarson Labrado { 83a7a80296SCarson Labrado // As a default, assume 200X is alright 84a7a80296SCarson Labrado BMCWEB_LOG_DEBUG << "Using default check for response code validity"; 85a7a80296SCarson Labrado if ((respCode < 200) || (respCode >= 300)) 86a7a80296SCarson Labrado { 87a7a80296SCarson Labrado return boost::system::errc::make_error_code( 88a7a80296SCarson Labrado boost::system::errc::result_out_of_range); 89a7a80296SCarson Labrado } 90a7a80296SCarson Labrado 91a7a80296SCarson Labrado // Return 0 if the response code is valid 92a7a80296SCarson Labrado return boost::system::errc::make_error_code(boost::system::errc::success); 93a7a80296SCarson Labrado }; 94a7a80296SCarson Labrado 95f52c03c1SCarson Labrado // We need to allow retry information to be set before a message has been sent 96f52c03c1SCarson Labrado // and a connection pool has been created 97f52c03c1SCarson Labrado struct RetryPolicyData 98f52c03c1SCarson Labrado { 99f52c03c1SCarson Labrado uint32_t maxRetryAttempts = 5; 100f52c03c1SCarson Labrado std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0); 101f52c03c1SCarson Labrado std::string retryPolicyAction = "TerminateAfterRetries"; 102a7a80296SCarson Labrado std::function<boost::system::error_code(unsigned int respCode)> 103a7a80296SCarson Labrado invalidResp = defaultRetryHandler; 104f52c03c1SCarson Labrado }; 105f52c03c1SCarson Labrado 106f52c03c1SCarson Labrado struct PendingRequest 107f52c03c1SCarson Labrado { 108244256ccSCarson Labrado boost::beast::http::request<boost::beast::http::string_body> req; 109039a47e3SCarson Labrado std::function<void(bool, uint32_t, Response&)> callback; 110f52c03c1SCarson Labrado RetryPolicyData retryPolicy; 111039a47e3SCarson Labrado PendingRequest( 1128a592810SEd Tanous boost::beast::http::request<boost::beast::http::string_body>&& reqIn, 1138a592810SEd Tanous const std::function<void(bool, uint32_t, Response&)>& callbackIn, 1148a592810SEd Tanous const RetryPolicyData& retryPolicyIn) : 1158a592810SEd Tanous req(std::move(reqIn)), 1168a592810SEd Tanous callback(callbackIn), retryPolicy(retryPolicyIn) 117f52c03c1SCarson Labrado {} 118f52c03c1SCarson Labrado }; 119f52c03c1SCarson Labrado 120f52c03c1SCarson Labrado class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo> 121bd030d0aSAppaRao Puli { 122bd030d0aSAppaRao Puli private: 123f52c03c1SCarson Labrado ConnState state = ConnState::initialized; 124f52c03c1SCarson Labrado uint32_t retryCount = 0; 125f52c03c1SCarson Labrado std::string subId; 126f52c03c1SCarson Labrado std::string host; 127f52c03c1SCarson Labrado uint16_t port; 128f52c03c1SCarson Labrado uint32_t connId; 129f52c03c1SCarson Labrado 130f52c03c1SCarson Labrado // Retry policy information 131f52c03c1SCarson Labrado // This should be updated before each message is sent 132f52c03c1SCarson Labrado RetryPolicyData retryPolicy; 133f52c03c1SCarson Labrado 134f52c03c1SCarson Labrado // Data buffers 135bd030d0aSAppaRao Puli boost::beast::http::request<boost::beast::http::string_body> req; 1366eaa1d2fSSunitha Harish std::optional< 1376eaa1d2fSSunitha Harish boost::beast::http::response_parser<boost::beast::http::string_body>> 1386eaa1d2fSSunitha Harish parser; 1394d94272fSCarson Labrado boost::beast::flat_static_buffer<httpReadBufferSize> buffer; 140039a47e3SCarson Labrado Response res; 1416eaa1d2fSSunitha Harish 142f52c03c1SCarson Labrado // Ascync callables 143039a47e3SCarson Labrado std::function<void(bool, uint32_t, Response&)> callback; 144f52c03c1SCarson Labrado crow::async_resolve::Resolver resolver; 145*0d5f5cf4SEd Tanous boost::asio::ip::tcp::socket conn; 146*0d5f5cf4SEd Tanous std::optional<boost::beast::ssl_stream<boost::asio::ip::tcp::socket&>> 147*0d5f5cf4SEd Tanous sslConn; 148e38778a5SAppaRao Puli 149f52c03c1SCarson Labrado boost::asio::steady_timer timer; 15084b35604SEd Tanous 151f52c03c1SCarson Labrado friend class ConnectionPool; 152bd030d0aSAppaRao Puli 15329a82b08SSunitha Harish void doResolve() 15429a82b08SSunitha Harish { 15529a82b08SSunitha Harish state = ConnState::resolveInProgress; 156f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":" 157f52c03c1SCarson Labrado << std::to_string(port) 158f52c03c1SCarson Labrado << ", id: " << std::to_string(connId); 15929a82b08SSunitha Harish 16029a82b08SSunitha Harish auto respHandler = 16129a82b08SSunitha Harish [self(shared_from_this())]( 16229a82b08SSunitha Harish const boost::beast::error_code ec, 16329a82b08SSunitha Harish const std::vector<boost::asio::ip::tcp::endpoint>& 16429a82b08SSunitha Harish endpointList) { 16526f6976fSEd Tanous if (ec || (endpointList.empty())) 16629a82b08SSunitha Harish { 16729a82b08SSunitha Harish BMCWEB_LOG_ERROR << "Resolve failed: " << ec.message(); 16829a82b08SSunitha Harish self->state = ConnState::resolveFailed; 169f52c03c1SCarson Labrado self->waitAndRetry(); 17029a82b08SSunitha Harish return; 17129a82b08SSunitha Harish } 172f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Resolved " << self->host << ":" 173f52c03c1SCarson Labrado << std::to_string(self->port) 174f52c03c1SCarson Labrado << ", id: " << std::to_string(self->connId); 17529a82b08SSunitha Harish self->doConnect(endpointList); 17629a82b08SSunitha Harish }; 177f52c03c1SCarson Labrado 17829a82b08SSunitha Harish resolver.asyncResolve(host, port, std::move(respHandler)); 17929a82b08SSunitha Harish } 18029a82b08SSunitha Harish 18129a82b08SSunitha Harish void doConnect( 18229a82b08SSunitha Harish const std::vector<boost::asio::ip::tcp::endpoint>& endpointList) 183bd030d0aSAppaRao Puli { 1842a5689a7SAppaRao Puli state = ConnState::connectInProgress; 1852a5689a7SAppaRao Puli 186f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":" 187f52c03c1SCarson Labrado << std::to_string(port) 188f52c03c1SCarson Labrado << ", id: " << std::to_string(connId); 189b00dcc27SEd Tanous 190*0d5f5cf4SEd Tanous timer.expires_after(std::chrono::seconds(30)); 191*0d5f5cf4SEd Tanous timer.async_wait(std::bind_front(onTimeout, weak_from_this())); 192*0d5f5cf4SEd Tanous 193*0d5f5cf4SEd Tanous boost::asio::async_connect( 194*0d5f5cf4SEd Tanous conn, endpointList, 195e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterConnect, this, 196e38778a5SAppaRao Puli shared_from_this())); 197e38778a5SAppaRao Puli } 198e38778a5SAppaRao Puli 199e38778a5SAppaRao Puli void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/, 200e38778a5SAppaRao Puli boost::beast::error_code ec, 201e38778a5SAppaRao Puli const boost::asio::ip::tcp::endpoint& endpoint) 202e38778a5SAppaRao Puli { 203*0d5f5cf4SEd Tanous timer.cancel(); 2042a5689a7SAppaRao Puli if (ec) 2052a5689a7SAppaRao Puli { 206002d39b4SEd Tanous BMCWEB_LOG_ERROR << "Connect " << endpoint.address().to_string() 207002d39b4SEd Tanous << ":" << std::to_string(endpoint.port()) 208e38778a5SAppaRao Puli << ", id: " << std::to_string(connId) 2092a5689a7SAppaRao Puli << " failed: " << ec.message(); 210e38778a5SAppaRao Puli state = ConnState::connectFailed; 211e38778a5SAppaRao Puli waitAndRetry(); 2122a5689a7SAppaRao Puli return; 2132a5689a7SAppaRao Puli } 214e38778a5SAppaRao Puli BMCWEB_LOG_DEBUG << "Connected to: " << endpoint.address().to_string() 215e38778a5SAppaRao Puli << ":" << std::to_string(endpoint.port()) 216e38778a5SAppaRao Puli << ", id: " << std::to_string(connId); 217e38778a5SAppaRao Puli if (sslConn) 218e38778a5SAppaRao Puli { 219*0d5f5cf4SEd Tanous doSslHandshake(); 220e38778a5SAppaRao Puli return; 221e38778a5SAppaRao Puli } 222e38778a5SAppaRao Puli state = ConnState::connected; 223e38778a5SAppaRao Puli sendMessage(); 224e38778a5SAppaRao Puli } 225e38778a5SAppaRao Puli 226*0d5f5cf4SEd Tanous void doSslHandshake() 227e38778a5SAppaRao Puli { 228e38778a5SAppaRao Puli if (!sslConn) 229e38778a5SAppaRao Puli { 230e38778a5SAppaRao Puli return; 231e38778a5SAppaRao Puli } 232e38778a5SAppaRao Puli state = ConnState::handshakeInProgress; 233*0d5f5cf4SEd Tanous timer.expires_after(std::chrono::seconds(30)); 234*0d5f5cf4SEd Tanous timer.async_wait(std::bind_front(onTimeout, weak_from_this())); 235e38778a5SAppaRao Puli sslConn->async_handshake( 236e38778a5SAppaRao Puli boost::asio::ssl::stream_base::client, 237e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterSslHandshake, this, 238e38778a5SAppaRao Puli shared_from_this())); 239e38778a5SAppaRao Puli } 240e38778a5SAppaRao Puli 241e38778a5SAppaRao Puli void afterSslHandshake(const std::shared_ptr<ConnectionInfo>& /*self*/, 242e38778a5SAppaRao Puli boost::beast::error_code ec) 243e38778a5SAppaRao Puli { 244*0d5f5cf4SEd Tanous timer.cancel(); 245e38778a5SAppaRao Puli if (ec) 246e38778a5SAppaRao Puli { 247e38778a5SAppaRao Puli BMCWEB_LOG_ERROR << "SSL Handshake failed -" 248e38778a5SAppaRao Puli << " id: " << std::to_string(connId) 249e38778a5SAppaRao Puli << " error: " << ec.message(); 250e38778a5SAppaRao Puli state = ConnState::handshakeFailed; 251e38778a5SAppaRao Puli waitAndRetry(); 252e38778a5SAppaRao Puli return; 253e38778a5SAppaRao Puli } 254e38778a5SAppaRao Puli BMCWEB_LOG_DEBUG << "SSL Handshake successful -" 255e38778a5SAppaRao Puli << " id: " << std::to_string(connId); 256e38778a5SAppaRao Puli state = ConnState::connected; 257e38778a5SAppaRao Puli sendMessage(); 2582a5689a7SAppaRao Puli } 2592a5689a7SAppaRao Puli 260f52c03c1SCarson Labrado void sendMessage() 2612a5689a7SAppaRao Puli { 2622a5689a7SAppaRao Puli state = ConnState::sendInProgress; 2632a5689a7SAppaRao Puli 264bd030d0aSAppaRao Puli // Set a timeout on the operation 265*0d5f5cf4SEd Tanous timer.expires_after(std::chrono::seconds(30)); 266*0d5f5cf4SEd Tanous timer.async_wait(std::bind_front(onTimeout, weak_from_this())); 267bd030d0aSAppaRao Puli 268bd030d0aSAppaRao Puli // Send the HTTP request to the remote host 269e38778a5SAppaRao Puli if (sslConn) 270e38778a5SAppaRao Puli { 271e38778a5SAppaRao Puli boost::beast::http::async_write( 272e38778a5SAppaRao Puli *sslConn, req, 273e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterWrite, this, 274e38778a5SAppaRao Puli shared_from_this())); 275e38778a5SAppaRao Puli } 276e38778a5SAppaRao Puli else 277e38778a5SAppaRao Puli { 278bd030d0aSAppaRao Puli boost::beast::http::async_write( 279bd030d0aSAppaRao Puli conn, req, 280e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterWrite, this, 281e38778a5SAppaRao Puli shared_from_this())); 282e38778a5SAppaRao Puli } 283e38778a5SAppaRao Puli } 284e38778a5SAppaRao Puli 285e38778a5SAppaRao Puli void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/, 286e38778a5SAppaRao Puli const boost::beast::error_code& ec, size_t bytesTransferred) 287e38778a5SAppaRao Puli { 288*0d5f5cf4SEd Tanous timer.cancel(); 289bd030d0aSAppaRao Puli if (ec) 290bd030d0aSAppaRao Puli { 291002d39b4SEd Tanous BMCWEB_LOG_ERROR << "sendMessage() failed: " << ec.message(); 292e38778a5SAppaRao Puli state = ConnState::sendFailed; 293e38778a5SAppaRao Puli waitAndRetry(); 294bd030d0aSAppaRao Puli return; 295bd030d0aSAppaRao Puli } 296bd030d0aSAppaRao Puli BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: " 297bd030d0aSAppaRao Puli << bytesTransferred; 298bd030d0aSAppaRao Puli 299e38778a5SAppaRao Puli recvMessage(); 300bd030d0aSAppaRao Puli } 301bd030d0aSAppaRao Puli 302bd030d0aSAppaRao Puli void recvMessage() 303bd030d0aSAppaRao Puli { 3046eaa1d2fSSunitha Harish state = ConnState::recvInProgress; 3056eaa1d2fSSunitha Harish 3066eaa1d2fSSunitha Harish parser.emplace(std::piecewise_construct, std::make_tuple()); 3076eaa1d2fSSunitha Harish parser->body_limit(httpReadBodyLimit); 3086eaa1d2fSSunitha Harish 309*0d5f5cf4SEd Tanous timer.expires_after(std::chrono::seconds(30)); 310*0d5f5cf4SEd Tanous timer.async_wait(std::bind_front(onTimeout, weak_from_this())); 311*0d5f5cf4SEd Tanous 312bd030d0aSAppaRao Puli // Receive the HTTP response 313e38778a5SAppaRao Puli if (sslConn) 314e38778a5SAppaRao Puli { 315e38778a5SAppaRao Puli boost::beast::http::async_read( 316e38778a5SAppaRao Puli *sslConn, buffer, *parser, 317e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterRead, this, 318e38778a5SAppaRao Puli shared_from_this())); 319e38778a5SAppaRao Puli } 320e38778a5SAppaRao Puli else 321e38778a5SAppaRao Puli { 322bd030d0aSAppaRao Puli boost::beast::http::async_read( 3236eaa1d2fSSunitha Harish conn, buffer, *parser, 324e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterRead, this, 325e38778a5SAppaRao Puli shared_from_this())); 326e38778a5SAppaRao Puli } 327e38778a5SAppaRao Puli } 328e38778a5SAppaRao Puli 329e38778a5SAppaRao Puli void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/, 330e38778a5SAppaRao Puli const boost::beast::error_code& ec, 331e38778a5SAppaRao Puli const std::size_t& bytesTransferred) 332e38778a5SAppaRao Puli { 333*0d5f5cf4SEd Tanous timer.cancel(); 334e38778a5SAppaRao Puli if (ec && ec != boost::asio::ssl::error::stream_truncated) 335bd030d0aSAppaRao Puli { 336002d39b4SEd Tanous BMCWEB_LOG_ERROR << "recvMessage() failed: " << ec.message(); 337e38778a5SAppaRao Puli state = ConnState::recvFailed; 338e38778a5SAppaRao Puli waitAndRetry(); 339bd030d0aSAppaRao Puli return; 340bd030d0aSAppaRao Puli } 341bd030d0aSAppaRao Puli BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: " 342bd030d0aSAppaRao Puli << bytesTransferred; 343e38778a5SAppaRao Puli BMCWEB_LOG_DEBUG << "recvMessage() data: " << parser->get().body(); 344bd030d0aSAppaRao Puli 345e38778a5SAppaRao Puli unsigned int respCode = parser->get().result_int(); 346e38778a5SAppaRao Puli BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: " << respCode; 3476eaa1d2fSSunitha Harish 348a7a80296SCarson Labrado // Make sure the received response code is valid as defined by 349a7a80296SCarson Labrado // the associated retry policy 350e38778a5SAppaRao Puli if (retryPolicy.invalidResp(respCode)) 3516eaa1d2fSSunitha Harish { 3526eaa1d2fSSunitha Harish // The listener failed to receive the Sent-Event 353002d39b4SEd Tanous BMCWEB_LOG_ERROR << "recvMessage() Listener Failed to " 3547adb85acSSunitha Harish "receive Sent-Event. Header Response Code: " 3557adb85acSSunitha Harish << respCode; 356e38778a5SAppaRao Puli state = ConnState::recvFailed; 357e38778a5SAppaRao Puli waitAndRetry(); 3586eaa1d2fSSunitha Harish return; 3596eaa1d2fSSunitha Harish } 360bd030d0aSAppaRao Puli 361f52c03c1SCarson Labrado // Send is successful 362f52c03c1SCarson Labrado // Reset the counter just in case this was after retrying 363e38778a5SAppaRao Puli retryCount = 0; 3646eaa1d2fSSunitha Harish 3656eaa1d2fSSunitha Harish // Keep the connection alive if server supports it 3666eaa1d2fSSunitha Harish // Else close the connection 3676eaa1d2fSSunitha Harish BMCWEB_LOG_DEBUG << "recvMessage() keepalive : " 368e38778a5SAppaRao Puli << parser->keep_alive(); 3696eaa1d2fSSunitha Harish 370039a47e3SCarson Labrado // Copy the response into a Response object so that it can be 371039a47e3SCarson Labrado // processed by the callback function. 372e38778a5SAppaRao Puli res.clear(); 373e38778a5SAppaRao Puli res.stringResponse = parser->release(); 374e38778a5SAppaRao Puli callback(parser->keep_alive(), connId, res); 375bd030d0aSAppaRao Puli } 376bd030d0aSAppaRao Puli 377*0d5f5cf4SEd Tanous static void onTimeout(const std::weak_ptr<ConnectionInfo>& weakSelf, 378*0d5f5cf4SEd Tanous const boost::system::error_code ec) 379*0d5f5cf4SEd Tanous { 380*0d5f5cf4SEd Tanous if (ec == boost::asio::error::operation_aborted) 381*0d5f5cf4SEd Tanous { 382*0d5f5cf4SEd Tanous BMCWEB_LOG_DEBUG 383*0d5f5cf4SEd Tanous << "async_wait failed since the operation is aborted" 384*0d5f5cf4SEd Tanous << ec.message(); 385*0d5f5cf4SEd Tanous return; 386*0d5f5cf4SEd Tanous } 387*0d5f5cf4SEd Tanous if (ec) 388*0d5f5cf4SEd Tanous { 389*0d5f5cf4SEd Tanous BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message(); 390*0d5f5cf4SEd Tanous // If the timer fails, we need to close the socket anyway, same as 391*0d5f5cf4SEd Tanous // if it expired. 392*0d5f5cf4SEd Tanous } 393*0d5f5cf4SEd Tanous std::shared_ptr<ConnectionInfo> self = weakSelf.lock(); 394*0d5f5cf4SEd Tanous if (self == nullptr) 395*0d5f5cf4SEd Tanous { 396*0d5f5cf4SEd Tanous return; 397*0d5f5cf4SEd Tanous } 398*0d5f5cf4SEd Tanous self->waitAndRetry(); 399*0d5f5cf4SEd Tanous } 400*0d5f5cf4SEd Tanous 4016eaa1d2fSSunitha Harish void waitAndRetry() 402bd030d0aSAppaRao Puli { 403e38778a5SAppaRao Puli if ((retryCount >= retryPolicy.maxRetryAttempts) || 404e38778a5SAppaRao Puli (state == ConnState::sslInitFailed)) 4052a5689a7SAppaRao Puli { 4066eaa1d2fSSunitha Harish BMCWEB_LOG_ERROR << "Maximum number of retries reached."; 407f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Retry policy: " 408f52c03c1SCarson Labrado << retryPolicy.retryPolicyAction; 409039a47e3SCarson Labrado 410039a47e3SCarson Labrado // We want to return a 502 to indicate there was an error with the 411039a47e3SCarson Labrado // external server 412039a47e3SCarson Labrado res.clear(); 41340d799e6SEd Tanous res.result(boost::beast::http::status::bad_gateway); 414039a47e3SCarson Labrado 415f52c03c1SCarson Labrado if (retryPolicy.retryPolicyAction == "TerminateAfterRetries") 416fe44eb0bSAyushi Smriti { 417fe44eb0bSAyushi Smriti // TODO: delete subscription 418fe44eb0bSAyushi Smriti state = ConnState::terminated; 419039a47e3SCarson Labrado callback(false, connId, res); 420fe44eb0bSAyushi Smriti } 421f52c03c1SCarson Labrado if (retryPolicy.retryPolicyAction == "SuspendRetries") 422fe44eb0bSAyushi Smriti { 4232a5689a7SAppaRao Puli state = ConnState::suspended; 424039a47e3SCarson Labrado callback(false, connId, res); 4252a5689a7SAppaRao Puli } 4266eaa1d2fSSunitha Harish // Reset the retrycount to zero so that client can try connecting 4276eaa1d2fSSunitha Harish // again if needed 428fe44eb0bSAyushi Smriti retryCount = 0; 4292a5689a7SAppaRao Puli return; 4302a5689a7SAppaRao Puli } 4312a5689a7SAppaRao Puli 4322a5689a7SAppaRao Puli retryCount++; 433fe44eb0bSAyushi Smriti 434f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Attempt retry after " 435f52c03c1SCarson Labrado << std::to_string( 436f52c03c1SCarson Labrado retryPolicy.retryIntervalSecs.count()) 437fe44eb0bSAyushi Smriti << " seconds. RetryCount = " << retryCount; 438f52c03c1SCarson Labrado timer.expires_after(retryPolicy.retryIntervalSecs); 439cb13a392SEd Tanous timer.async_wait( 440f52c03c1SCarson Labrado [self(shared_from_this())](const boost::system::error_code ec) { 4416eaa1d2fSSunitha Harish if (ec == boost::asio::error::operation_aborted) 4426eaa1d2fSSunitha Harish { 4436eaa1d2fSSunitha Harish BMCWEB_LOG_DEBUG 4446eaa1d2fSSunitha Harish << "async_wait failed since the operation is aborted" 4456eaa1d2fSSunitha Harish << ec.message(); 4466eaa1d2fSSunitha Harish } 4476eaa1d2fSSunitha Harish else if (ec) 4486eaa1d2fSSunitha Harish { 4496eaa1d2fSSunitha Harish BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message(); 4506eaa1d2fSSunitha Harish // Ignore the error and continue the retry loop to attempt 4516eaa1d2fSSunitha Harish // sending the event as per the retry policy 4526eaa1d2fSSunitha Harish } 4536eaa1d2fSSunitha Harish 454f52c03c1SCarson Labrado // Let's close the connection and restart from resolve. 455e38778a5SAppaRao Puli self->doClose(true); 456fe44eb0bSAyushi Smriti }); 4572a5689a7SAppaRao Puli } 4582a5689a7SAppaRao Puli 459e38778a5SAppaRao Puli void shutdownConn(bool retry) 460fe44eb0bSAyushi Smriti { 461f52c03c1SCarson Labrado boost::beast::error_code ec; 462*0d5f5cf4SEd Tanous conn.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); 463f52c03c1SCarson Labrado conn.close(); 464f52c03c1SCarson Labrado 465f52c03c1SCarson Labrado // not_connected happens sometimes so don't bother reporting it. 466f52c03c1SCarson Labrado if (ec && ec != boost::beast::errc::not_connected) 4672a5689a7SAppaRao Puli { 468f52c03c1SCarson Labrado BMCWEB_LOG_ERROR << host << ":" << std::to_string(port) 469f52c03c1SCarson Labrado << ", id: " << std::to_string(connId) 470f52c03c1SCarson Labrado << "shutdown failed: " << ec.message(); 4716eaa1d2fSSunitha Harish } 4725cab68f3SCarson Labrado else 4735cab68f3SCarson Labrado { 474f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port) 475f52c03c1SCarson Labrado << ", id: " << std::to_string(connId) 476f52c03c1SCarson Labrado << " closed gracefully"; 4775cab68f3SCarson Labrado } 478ca723762SEd Tanous 479e38778a5SAppaRao Puli if ((state != ConnState::suspended) && (state != ConnState::terminated)) 48092a74e56SAppaRao Puli { 481e38778a5SAppaRao Puli if (retry) 48292a74e56SAppaRao Puli { 483f52c03c1SCarson Labrado // Now let's try to resend the data 484f52c03c1SCarson Labrado state = ConnState::retry; 485*0d5f5cf4SEd Tanous doResolve(); 486e38778a5SAppaRao Puli } 487e38778a5SAppaRao Puli else 488e38778a5SAppaRao Puli { 489e38778a5SAppaRao Puli state = ConnState::closed; 490e38778a5SAppaRao Puli } 491e38778a5SAppaRao Puli } 492e38778a5SAppaRao Puli } 493e38778a5SAppaRao Puli 494e38778a5SAppaRao Puli void doClose(bool retry = false) 495e38778a5SAppaRao Puli { 496e38778a5SAppaRao Puli if (!sslConn) 497e38778a5SAppaRao Puli { 498e38778a5SAppaRao Puli shutdownConn(retry); 499e38778a5SAppaRao Puli return; 500e38778a5SAppaRao Puli } 501e38778a5SAppaRao Puli 502e38778a5SAppaRao Puli sslConn->async_shutdown( 503e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterSslShutdown, this, 504e38778a5SAppaRao Puli shared_from_this(), retry)); 505e38778a5SAppaRao Puli } 506e38778a5SAppaRao Puli 507e38778a5SAppaRao Puli void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/, 508e38778a5SAppaRao Puli bool retry, const boost::system::error_code& ec) 509e38778a5SAppaRao Puli { 510e38778a5SAppaRao Puli 511e38778a5SAppaRao Puli if (ec) 512e38778a5SAppaRao Puli { 513e38778a5SAppaRao Puli BMCWEB_LOG_ERROR << host << ":" << std::to_string(port) 514e38778a5SAppaRao Puli << ", id: " << std::to_string(connId) 515e38778a5SAppaRao Puli << " shutdown failed: " << ec.message(); 516e38778a5SAppaRao Puli } 517e38778a5SAppaRao Puli else 518e38778a5SAppaRao Puli { 519e38778a5SAppaRao Puli BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port) 520e38778a5SAppaRao Puli << ", id: " << std::to_string(connId) 521e38778a5SAppaRao Puli << " closed gracefully"; 522e38778a5SAppaRao Puli } 523e38778a5SAppaRao Puli shutdownConn(retry); 524e38778a5SAppaRao Puli } 525e38778a5SAppaRao Puli 526e38778a5SAppaRao Puli void setCipherSuiteTLSext() 527e38778a5SAppaRao Puli { 528e38778a5SAppaRao Puli if (!sslConn) 529e38778a5SAppaRao Puli { 530e38778a5SAppaRao Puli return; 531e38778a5SAppaRao Puli } 532e38778a5SAppaRao Puli // NOTE: The SSL_set_tlsext_host_name is defined in tlsv1.h header 533e38778a5SAppaRao Puli // file but its having old style casting (name is cast to void*). 534e38778a5SAppaRao Puli // Since bmcweb compiler treats all old-style-cast as error, its 535e38778a5SAppaRao Puli // causing the build failure. So replaced the same macro inline and 536e38778a5SAppaRao Puli // did corrected the code by doing static_cast to viod*. This has to 537e38778a5SAppaRao Puli // be fixed in openssl library in long run. Set SNI Hostname (many 538e38778a5SAppaRao Puli // hosts need this to handshake successfully) 539e38778a5SAppaRao Puli if (SSL_ctrl(sslConn->native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME, 540e38778a5SAppaRao Puli TLSEXT_NAMETYPE_host_name, 541e38778a5SAppaRao Puli static_cast<void*>(&host.front())) == 0) 542e38778a5SAppaRao Puli 543e38778a5SAppaRao Puli { 544e38778a5SAppaRao Puli boost::beast::error_code ec{static_cast<int>(::ERR_get_error()), 545e38778a5SAppaRao Puli boost::asio::error::get_ssl_category()}; 546e38778a5SAppaRao Puli 547e38778a5SAppaRao Puli BMCWEB_LOG_ERROR << "SSL_set_tlsext_host_name " << host << ":" 548e38778a5SAppaRao Puli << port << ", id: " << std::to_string(connId) 549e38778a5SAppaRao Puli << " failed: " << ec.message(); 550e38778a5SAppaRao Puli // Set state as sslInit failed so that we close the connection 551e38778a5SAppaRao Puli // and take appropriate action as per retry configuration. 552e38778a5SAppaRao Puli state = ConnState::sslInitFailed; 553e38778a5SAppaRao Puli waitAndRetry(); 554e38778a5SAppaRao Puli return; 555e38778a5SAppaRao Puli } 556bd030d0aSAppaRao Puli } 557bd030d0aSAppaRao Puli 558bd030d0aSAppaRao Puli public: 559e38778a5SAppaRao Puli explicit ConnectionInfo(boost::asio::io_context& iocIn, 560e38778a5SAppaRao Puli const std::string& idIn, 561e38778a5SAppaRao Puli const std::string& destIPIn, uint16_t destPortIn, 562e38778a5SAppaRao Puli bool useSSL, unsigned int connIdIn) : 5638a592810SEd Tanous subId(idIn), 564e38778a5SAppaRao Puli host(destIPIn), port(destPortIn), connId(connIdIn), conn(iocIn), 565e38778a5SAppaRao Puli timer(iocIn) 566e38778a5SAppaRao Puli { 567e38778a5SAppaRao Puli if (useSSL) 568e38778a5SAppaRao Puli { 569e38778a5SAppaRao Puli std::optional<boost::asio::ssl::context> sslCtx = 570e38778a5SAppaRao Puli ensuressl::getSSLClientContext(); 571e38778a5SAppaRao Puli 572e38778a5SAppaRao Puli if (!sslCtx) 573e38778a5SAppaRao Puli { 574e38778a5SAppaRao Puli BMCWEB_LOG_ERROR << "prepareSSLContext failed - " << host << ":" 575e38778a5SAppaRao Puli << port << ", id: " << std::to_string(connId); 576e38778a5SAppaRao Puli // Don't retry if failure occurs while preparing SSL context 577e38778a5SAppaRao Puli // such as certificate is invalid or set cipher failure or set 578e38778a5SAppaRao Puli // host name failure etc... Setting conn state to sslInitFailed 579e38778a5SAppaRao Puli // and connection state will be transitioned to next state 580e38778a5SAppaRao Puli // depending on retry policy set by subscription. 581e38778a5SAppaRao Puli state = ConnState::sslInitFailed; 582e38778a5SAppaRao Puli waitAndRetry(); 583e38778a5SAppaRao Puli return; 584e38778a5SAppaRao Puli } 585e38778a5SAppaRao Puli sslConn.emplace(conn, *sslCtx); 586e38778a5SAppaRao Puli setCipherSuiteTLSext(); 587e38778a5SAppaRao Puli } 588e38778a5SAppaRao Puli } 589f52c03c1SCarson Labrado }; 590bd030d0aSAppaRao Puli 591f52c03c1SCarson Labrado class ConnectionPool : public std::enable_shared_from_this<ConnectionPool> 592bd030d0aSAppaRao Puli { 593f52c03c1SCarson Labrado private: 594f52c03c1SCarson Labrado boost::asio::io_context& ioc; 595e38778a5SAppaRao Puli std::string id; 596e38778a5SAppaRao Puli std::string destIP; 597e38778a5SAppaRao Puli uint16_t destPort; 598e38778a5SAppaRao Puli bool useSSL; 599f52c03c1SCarson Labrado std::vector<std::shared_ptr<ConnectionInfo>> connections; 600f52c03c1SCarson Labrado boost::container::devector<PendingRequest> requestQueue; 601f52c03c1SCarson Labrado 602f52c03c1SCarson Labrado friend class HttpClient; 603f52c03c1SCarson Labrado 604244256ccSCarson Labrado // Configure a connections's request, callback, and retry info in 605244256ccSCarson Labrado // preparation to begin sending the request 606f52c03c1SCarson Labrado void setConnProps(ConnectionInfo& conn) 607bd030d0aSAppaRao Puli { 608f52c03c1SCarson Labrado if (requestQueue.empty()) 609f52c03c1SCarson Labrado { 610f52c03c1SCarson Labrado BMCWEB_LOG_ERROR 611f52c03c1SCarson Labrado << "setConnProps() should not have been called when requestQueue is empty"; 612bd030d0aSAppaRao Puli return; 613bd030d0aSAppaRao Puli } 614bd030d0aSAppaRao Puli 615244256ccSCarson Labrado auto nextReq = requestQueue.front(); 616244256ccSCarson Labrado conn.retryPolicy = std::move(nextReq.retryPolicy); 617244256ccSCarson Labrado conn.req = std::move(nextReq.req); 618244256ccSCarson Labrado conn.callback = std::move(nextReq.callback); 619f52c03c1SCarson Labrado 620f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Setting properties for connection " << conn.host 621f52c03c1SCarson Labrado << ":" << std::to_string(conn.port) 622a7a80296SCarson Labrado << ", id: " << std::to_string(conn.connId); 623f52c03c1SCarson Labrado 624f52c03c1SCarson Labrado // We can remove the request from the queue at this point 625f52c03c1SCarson Labrado requestQueue.pop_front(); 626f52c03c1SCarson Labrado } 627f52c03c1SCarson Labrado 628f52c03c1SCarson Labrado // Configures a connection to use the specific retry policy. 629f52c03c1SCarson Labrado inline void setConnRetryPolicy(ConnectionInfo& conn, 630f52c03c1SCarson Labrado const RetryPolicyData& retryPolicy) 6312a5689a7SAppaRao Puli { 632f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << destIP << ":" << std::to_string(destPort) 633a7a80296SCarson Labrado << ", id: " << std::to_string(conn.connId); 634f52c03c1SCarson Labrado 635f52c03c1SCarson Labrado conn.retryPolicy = retryPolicy; 636f52c03c1SCarson Labrado } 637f52c03c1SCarson Labrado 638f52c03c1SCarson Labrado // Gets called as part of callback after request is sent 639f52c03c1SCarson Labrado // Reuses the connection if there are any requests waiting to be sent 640f52c03c1SCarson Labrado // Otherwise closes the connection if it is not a keep-alive 641f52c03c1SCarson Labrado void sendNext(bool keepAlive, uint32_t connId) 642f52c03c1SCarson Labrado { 643f52c03c1SCarson Labrado auto conn = connections[connId]; 64446a81465SCarson Labrado 64546a81465SCarson Labrado // Allow the connection's handler to be deleted 64646a81465SCarson Labrado // This is needed because of Redfish Aggregation passing an 64746a81465SCarson Labrado // AsyncResponse shared_ptr to this callback 64846a81465SCarson Labrado conn->callback = nullptr; 64946a81465SCarson Labrado 650f52c03c1SCarson Labrado // Reuse the connection to send the next request in the queue 651f52c03c1SCarson Labrado if (!requestQueue.empty()) 652f52c03c1SCarson Labrado { 653f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << std::to_string(requestQueue.size()) 654f52c03c1SCarson Labrado << " requests remaining in queue for " << destIP 655f52c03c1SCarson Labrado << ":" << std::to_string(destPort) 656f52c03c1SCarson Labrado << ", reusing connnection " 657f52c03c1SCarson Labrado << std::to_string(connId); 658f52c03c1SCarson Labrado 659f52c03c1SCarson Labrado setConnProps(*conn); 660f52c03c1SCarson Labrado 661f52c03c1SCarson Labrado if (keepAlive) 662f52c03c1SCarson Labrado { 663f52c03c1SCarson Labrado conn->sendMessage(); 6642a5689a7SAppaRao Puli } 6652a5689a7SAppaRao Puli else 6662a5689a7SAppaRao Puli { 667f52c03c1SCarson Labrado // Server is not keep-alive enabled so we need to close the 668f52c03c1SCarson Labrado // connection and then start over from resolve 669f52c03c1SCarson Labrado conn->doClose(); 670f52c03c1SCarson Labrado conn->doResolve(); 671f52c03c1SCarson Labrado } 672f52c03c1SCarson Labrado return; 673f52c03c1SCarson Labrado } 674f52c03c1SCarson Labrado 675f52c03c1SCarson Labrado // No more messages to send so close the connection if necessary 676f52c03c1SCarson Labrado if (keepAlive) 677f52c03c1SCarson Labrado { 678f52c03c1SCarson Labrado conn->state = ConnState::idle; 679f52c03c1SCarson Labrado } 680f52c03c1SCarson Labrado else 681f52c03c1SCarson Labrado { 682f52c03c1SCarson Labrado // Abort the connection since server is not keep-alive enabled 683f52c03c1SCarson Labrado conn->state = ConnState::abortConnection; 684f52c03c1SCarson Labrado conn->doClose(); 6852a5689a7SAppaRao Puli } 686bd030d0aSAppaRao Puli } 687bd030d0aSAppaRao Puli 688244256ccSCarson Labrado void sendData(std::string& data, const std::string& destUri, 689244256ccSCarson Labrado const boost::beast::http::fields& httpHeader, 690244256ccSCarson Labrado const boost::beast::http::verb verb, 691244256ccSCarson Labrado const RetryPolicyData& retryPolicy, 6926b3db60dSEd Tanous const std::function<void(Response&)>& resHandler) 693fe44eb0bSAyushi Smriti { 694f52c03c1SCarson Labrado std::weak_ptr<ConnectionPool> weakSelf = weak_from_this(); 695f52c03c1SCarson Labrado 696f52c03c1SCarson Labrado // Callback to be called once the request has been sent 697039a47e3SCarson Labrado auto cb = [weakSelf, resHandler](bool keepAlive, uint32_t connId, 698039a47e3SCarson Labrado Response& res) { 699039a47e3SCarson Labrado // Allow provided callback to perform additional processing of the 700039a47e3SCarson Labrado // request 701039a47e3SCarson Labrado resHandler(res); 702039a47e3SCarson Labrado 703f52c03c1SCarson Labrado // If requests remain in the queue then we want to reuse this 704f52c03c1SCarson Labrado // connection to send the next request 705f52c03c1SCarson Labrado std::shared_ptr<ConnectionPool> self = weakSelf.lock(); 706f52c03c1SCarson Labrado if (!self) 707f52c03c1SCarson Labrado { 708f52c03c1SCarson Labrado BMCWEB_LOG_CRITICAL << self << " Failed to capture connection"; 709f52c03c1SCarson Labrado return; 710fe44eb0bSAyushi Smriti } 711fe44eb0bSAyushi Smriti 712f52c03c1SCarson Labrado self->sendNext(keepAlive, connId); 713f52c03c1SCarson Labrado }; 714f52c03c1SCarson Labrado 715244256ccSCarson Labrado // Construct the request to be sent 716244256ccSCarson Labrado boost::beast::http::request<boost::beast::http::string_body> thisReq( 717244256ccSCarson Labrado verb, destUri, 11, "", httpHeader); 718244256ccSCarson Labrado thisReq.set(boost::beast::http::field::host, destIP); 719244256ccSCarson Labrado thisReq.keep_alive(true); 720244256ccSCarson Labrado thisReq.body() = std::move(data); 721244256ccSCarson Labrado thisReq.prepare_payload(); 722244256ccSCarson Labrado 723f52c03c1SCarson Labrado // Reuse an existing connection if one is available 724f52c03c1SCarson Labrado for (unsigned int i = 0; i < connections.size(); i++) 725fe44eb0bSAyushi Smriti { 726f52c03c1SCarson Labrado auto conn = connections[i]; 727f52c03c1SCarson Labrado if ((conn->state == ConnState::idle) || 728f52c03c1SCarson Labrado (conn->state == ConnState::initialized) || 729f52c03c1SCarson Labrado (conn->state == ConnState::closed)) 730f52c03c1SCarson Labrado { 731244256ccSCarson Labrado conn->req = std::move(thisReq); 732f52c03c1SCarson Labrado conn->callback = std::move(cb); 733f52c03c1SCarson Labrado setConnRetryPolicy(*conn, retryPolicy); 734f52c03c1SCarson Labrado std::string commonMsg = std::to_string(i) + " from pool " + 735f52c03c1SCarson Labrado destIP + ":" + std::to_string(destPort); 736f52c03c1SCarson Labrado 737f52c03c1SCarson Labrado if (conn->state == ConnState::idle) 738f52c03c1SCarson Labrado { 739f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Grabbing idle connection " 740f52c03c1SCarson Labrado << commonMsg; 741f52c03c1SCarson Labrado conn->sendMessage(); 742f52c03c1SCarson Labrado } 743f52c03c1SCarson Labrado else 744f52c03c1SCarson Labrado { 745f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Reusing existing connection " 746f52c03c1SCarson Labrado << commonMsg; 747f52c03c1SCarson Labrado conn->doResolve(); 748f52c03c1SCarson Labrado } 749f52c03c1SCarson Labrado return; 750f52c03c1SCarson Labrado } 751f52c03c1SCarson Labrado } 752f52c03c1SCarson Labrado 753f52c03c1SCarson Labrado // All connections in use so create a new connection or add request to 754f52c03c1SCarson Labrado // the queue 755f52c03c1SCarson Labrado if (connections.size() < maxPoolSize) 756f52c03c1SCarson Labrado { 757f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Adding new connection to pool " << destIP 758f52c03c1SCarson Labrado << ":" << std::to_string(destPort); 759f52c03c1SCarson Labrado auto conn = addConnection(); 760244256ccSCarson Labrado conn->req = std::move(thisReq); 761f52c03c1SCarson Labrado conn->callback = std::move(cb); 762f52c03c1SCarson Labrado setConnRetryPolicy(*conn, retryPolicy); 763f52c03c1SCarson Labrado conn->doResolve(); 764f52c03c1SCarson Labrado } 765f52c03c1SCarson Labrado else if (requestQueue.size() < maxRequestQueueSize) 766f52c03c1SCarson Labrado { 767f52c03c1SCarson Labrado BMCWEB_LOG_ERROR << "Max pool size reached. Adding data to queue."; 768244256ccSCarson Labrado requestQueue.emplace_back(std::move(thisReq), std::move(cb), 769f52c03c1SCarson Labrado retryPolicy); 770f52c03c1SCarson Labrado } 771f52c03c1SCarson Labrado else 772f52c03c1SCarson Labrado { 773f52c03c1SCarson Labrado BMCWEB_LOG_ERROR << destIP << ":" << std::to_string(destPort) 774f52c03c1SCarson Labrado << " request queue full. Dropping request."; 775f52c03c1SCarson Labrado } 776f52c03c1SCarson Labrado } 777f52c03c1SCarson Labrado 778f52c03c1SCarson Labrado std::shared_ptr<ConnectionInfo>& addConnection() 779f52c03c1SCarson Labrado { 780f52c03c1SCarson Labrado unsigned int newId = static_cast<unsigned int>(connections.size()); 781f52c03c1SCarson Labrado 782e38778a5SAppaRao Puli auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>( 783e38778a5SAppaRao Puli ioc, id, destIP, destPort, useSSL, newId)); 784f52c03c1SCarson Labrado 785f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Added connection " 786f52c03c1SCarson Labrado << std::to_string(connections.size() - 1) 787f52c03c1SCarson Labrado << " to pool " << destIP << ":" 788f52c03c1SCarson Labrado << std::to_string(destPort); 789f52c03c1SCarson Labrado 790f52c03c1SCarson Labrado return ret; 791f52c03c1SCarson Labrado } 792f52c03c1SCarson Labrado 793f52c03c1SCarson Labrado public: 7948a592810SEd Tanous explicit ConnectionPool(boost::asio::io_context& iocIn, 7958a592810SEd Tanous const std::string& idIn, 796e38778a5SAppaRao Puli const std::string& destIPIn, uint16_t destPortIn, 797e38778a5SAppaRao Puli bool useSSLIn) : 7988a592810SEd Tanous ioc(iocIn), 799e38778a5SAppaRao Puli id(idIn), destIP(destIPIn), destPort(destPortIn), useSSL(useSSLIn) 800f52c03c1SCarson Labrado { 801f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Initializing connection pool for " << destIP << ":" 802f52c03c1SCarson Labrado << std::to_string(destPort); 803f52c03c1SCarson Labrado 804f52c03c1SCarson Labrado // Initialize the pool with a single connection 805f52c03c1SCarson Labrado addConnection(); 806fe44eb0bSAyushi Smriti } 807bd030d0aSAppaRao Puli }; 808bd030d0aSAppaRao Puli 809f52c03c1SCarson Labrado class HttpClient 810f52c03c1SCarson Labrado { 811f52c03c1SCarson Labrado private: 812f52c03c1SCarson Labrado std::unordered_map<std::string, std::shared_ptr<ConnectionPool>> 813f52c03c1SCarson Labrado connectionPools; 814f52c03c1SCarson Labrado boost::asio::io_context& ioc = 815f52c03c1SCarson Labrado crow::connections::systemBus->get_io_context(); 816f52c03c1SCarson Labrado std::unordered_map<std::string, RetryPolicyData> retryInfo; 817f52c03c1SCarson Labrado HttpClient() = default; 818f52c03c1SCarson Labrado 819039a47e3SCarson Labrado // Used as a dummy callback by sendData() in order to call 820039a47e3SCarson Labrado // sendDataWithCallback() 82102cad96eSEd Tanous static void genericResHandler(const Response& res) 822039a47e3SCarson Labrado { 823039a47e3SCarson Labrado BMCWEB_LOG_DEBUG << "Response handled with return code: " 824039a47e3SCarson Labrado << std::to_string(res.resultInt()); 8254ee8e211SEd Tanous } 826039a47e3SCarson Labrado 827f52c03c1SCarson Labrado public: 828f52c03c1SCarson Labrado HttpClient(const HttpClient&) = delete; 829f52c03c1SCarson Labrado HttpClient& operator=(const HttpClient&) = delete; 830f52c03c1SCarson Labrado HttpClient(HttpClient&&) = delete; 831f52c03c1SCarson Labrado HttpClient& operator=(HttpClient&&) = delete; 832f52c03c1SCarson Labrado ~HttpClient() = default; 833f52c03c1SCarson Labrado 834f52c03c1SCarson Labrado static HttpClient& getInstance() 835f52c03c1SCarson Labrado { 836f52c03c1SCarson Labrado static HttpClient handler; 837f52c03c1SCarson Labrado return handler; 838f52c03c1SCarson Labrado } 839f52c03c1SCarson Labrado 840039a47e3SCarson Labrado // Send a request to destIP:destPort where additional processing of the 841039a47e3SCarson Labrado // result is not required 842f52c03c1SCarson Labrado void sendData(std::string& data, const std::string& id, 843e38778a5SAppaRao Puli const std::string& destIP, uint16_t destPort, 844e38778a5SAppaRao Puli const std::string& destUri, bool useSSL, 845f52c03c1SCarson Labrado const boost::beast::http::fields& httpHeader, 846244256ccSCarson Labrado const boost::beast::http::verb verb, 847244256ccSCarson Labrado const std::string& retryPolicyName) 848f52c03c1SCarson Labrado { 849e38778a5SAppaRao Puli const std::function<void(Response&)> cb = genericResHandler; 850e38778a5SAppaRao Puli sendDataWithCallback(data, id, destIP, destPort, destUri, useSSL, 851e38778a5SAppaRao Puli httpHeader, verb, retryPolicyName, cb); 852039a47e3SCarson Labrado } 853039a47e3SCarson Labrado 854039a47e3SCarson Labrado // Send request to destIP:destPort and use the provided callback to 855039a47e3SCarson Labrado // handle the response 856039a47e3SCarson Labrado void sendDataWithCallback(std::string& data, const std::string& id, 857e38778a5SAppaRao Puli const std::string& destIP, uint16_t destPort, 858e38778a5SAppaRao Puli const std::string& destUri, bool useSSL, 859039a47e3SCarson Labrado const boost::beast::http::fields& httpHeader, 860244256ccSCarson Labrado const boost::beast::http::verb verb, 861244256ccSCarson Labrado const std::string& retryPolicyName, 8626b3db60dSEd Tanous const std::function<void(Response&)>& resHandler) 863039a47e3SCarson Labrado { 864e38778a5SAppaRao Puli std::string clientKey = useSSL ? "https" : "http"; 865e38778a5SAppaRao Puli clientKey += destIP; 866e38778a5SAppaRao Puli clientKey += ":"; 867e38778a5SAppaRao Puli clientKey += std::to_string(destPort); 868f52c03c1SCarson Labrado // Use nullptr to avoid creating a ConnectionPool each time 869e38778a5SAppaRao Puli std::shared_ptr<ConnectionPool>& conn = connectionPools[clientKey]; 870e38778a5SAppaRao Puli if (conn == nullptr) 871f52c03c1SCarson Labrado { 872f52c03c1SCarson Labrado // Now actually create the ConnectionPool shared_ptr since it does 873f52c03c1SCarson Labrado // not already exist 874e38778a5SAppaRao Puli conn = std::make_shared<ConnectionPool>(ioc, id, destIP, destPort, 875e38778a5SAppaRao Puli useSSL); 876f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Created connection pool for " << clientKey; 877f52c03c1SCarson Labrado } 878f52c03c1SCarson Labrado else 879f52c03c1SCarson Labrado { 880f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Using existing connection pool for " 881f52c03c1SCarson Labrado << clientKey; 882f52c03c1SCarson Labrado } 883f52c03c1SCarson Labrado 884f52c03c1SCarson Labrado // Get the associated retry policy 885f52c03c1SCarson Labrado auto policy = retryInfo.try_emplace(retryPolicyName); 886f52c03c1SCarson Labrado if (policy.second) 887f52c03c1SCarson Labrado { 888f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Creating retry policy \"" << retryPolicyName 889f52c03c1SCarson Labrado << "\" with default values"; 890f52c03c1SCarson Labrado } 891f52c03c1SCarson Labrado 892f52c03c1SCarson Labrado // Send the data using either the existing connection pool or the newly 893f52c03c1SCarson Labrado // created connection pool 894e38778a5SAppaRao Puli conn->sendData(data, destUri, httpHeader, verb, policy.first->second, 895e38778a5SAppaRao Puli resHandler); 896f52c03c1SCarson Labrado } 897f52c03c1SCarson Labrado 898a7a80296SCarson Labrado void setRetryConfig( 899a7a80296SCarson Labrado const uint32_t retryAttempts, const uint32_t retryTimeoutInterval, 900a7a80296SCarson Labrado const std::function<boost::system::error_code(unsigned int respCode)>& 901a7a80296SCarson Labrado invalidResp, 902f52c03c1SCarson Labrado const std::string& retryPolicyName) 903f52c03c1SCarson Labrado { 904f52c03c1SCarson Labrado // We need to create the retry policy if one does not already exist for 905f52c03c1SCarson Labrado // the given retryPolicyName 906f52c03c1SCarson Labrado auto result = retryInfo.try_emplace(retryPolicyName); 907f52c03c1SCarson Labrado if (result.second) 908f52c03c1SCarson Labrado { 909f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "setRetryConfig(): Creating new retry policy \"" 910f52c03c1SCarson Labrado << retryPolicyName << "\""; 911f52c03c1SCarson Labrado } 912f52c03c1SCarson Labrado else 913f52c03c1SCarson Labrado { 914f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "setRetryConfig(): Updating retry info for \"" 915f52c03c1SCarson Labrado << retryPolicyName << "\""; 916f52c03c1SCarson Labrado } 917f52c03c1SCarson Labrado 918f52c03c1SCarson Labrado result.first->second.maxRetryAttempts = retryAttempts; 919f52c03c1SCarson Labrado result.first->second.retryIntervalSecs = 920f52c03c1SCarson Labrado std::chrono::seconds(retryTimeoutInterval); 921a7a80296SCarson Labrado result.first->second.invalidResp = invalidResp; 922f52c03c1SCarson Labrado } 923f52c03c1SCarson Labrado 924f52c03c1SCarson Labrado void setRetryPolicy(const std::string& retryPolicy, 925f52c03c1SCarson Labrado const std::string& retryPolicyName) 926f52c03c1SCarson Labrado { 927f52c03c1SCarson Labrado // We need to create the retry policy if one does not already exist for 928f52c03c1SCarson Labrado // the given retryPolicyName 929f52c03c1SCarson Labrado auto result = retryInfo.try_emplace(retryPolicyName); 930f52c03c1SCarson Labrado if (result.second) 931f52c03c1SCarson Labrado { 932f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "setRetryPolicy(): Creating new retry policy \"" 933f52c03c1SCarson Labrado << retryPolicyName << "\""; 934f52c03c1SCarson Labrado } 935f52c03c1SCarson Labrado else 936f52c03c1SCarson Labrado { 937f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "setRetryPolicy(): Updating retry policy for \"" 938f52c03c1SCarson Labrado << retryPolicyName << "\""; 939f52c03c1SCarson Labrado } 940f52c03c1SCarson Labrado 941f52c03c1SCarson Labrado result.first->second.retryPolicyAction = retryPolicy; 942f52c03c1SCarson Labrado } 943f52c03c1SCarson Labrado }; 944bd030d0aSAppaRao Puli } // namespace crow 945