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 17bb49eb5cSEd Tanous #include <boost/asio/io_context.hpp> 1829a82b08SSunitha Harish #include <boost/asio/ip/address.hpp> 1929a82b08SSunitha Harish #include <boost/asio/ip/basic_endpoint.hpp> 20bb49eb5cSEd Tanous #include <boost/asio/ip/tcp.hpp> 21*e38778a5SAppaRao Puli #include <boost/asio/ssl/context.hpp> 22*e38778a5SAppaRao Puli #include <boost/asio/ssl/error.hpp> 23d43cd0caSEd Tanous #include <boost/asio/steady_timer.hpp> 24d43cd0caSEd Tanous #include <boost/beast/core/flat_buffer.hpp> 25bb49eb5cSEd Tanous #include <boost/beast/core/flat_static_buffer.hpp> 26d43cd0caSEd Tanous #include <boost/beast/core/tcp_stream.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> 32*e38778a5SAppaRao 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> 39*e38778a5SAppaRao 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, 65*e38778a5SAppaRao Puli handshakeInProgress, 66*e38778a5SAppaRao 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, 76*e38778a5SAppaRao 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 bool runningTimer = false; 126f52c03c1SCarson Labrado std::string subId; 127f52c03c1SCarson Labrado std::string host; 128f52c03c1SCarson Labrado uint16_t port; 129f52c03c1SCarson Labrado uint32_t connId; 130f52c03c1SCarson Labrado 131f52c03c1SCarson Labrado // Retry policy information 132f52c03c1SCarson Labrado // This should be updated before each message is sent 133f52c03c1SCarson Labrado RetryPolicyData retryPolicy; 134f52c03c1SCarson Labrado 135f52c03c1SCarson Labrado // Data buffers 136bd030d0aSAppaRao Puli boost::beast::http::request<boost::beast::http::string_body> req; 1376eaa1d2fSSunitha Harish std::optional< 1386eaa1d2fSSunitha Harish boost::beast::http::response_parser<boost::beast::http::string_body>> 1396eaa1d2fSSunitha Harish parser; 1404d94272fSCarson Labrado boost::beast::flat_static_buffer<httpReadBufferSize> buffer; 141039a47e3SCarson Labrado Response res; 1426eaa1d2fSSunitha Harish 143f52c03c1SCarson Labrado // Ascync callables 144039a47e3SCarson Labrado std::function<void(bool, uint32_t, Response&)> callback; 145f52c03c1SCarson Labrado crow::async_resolve::Resolver resolver; 146f52c03c1SCarson Labrado boost::beast::tcp_stream conn; 147*e38778a5SAppaRao Puli std::optional<boost::beast::ssl_stream<boost::beast::tcp_stream&>> sslConn; 148*e38778a5SAppaRao 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 19029a82b08SSunitha Harish conn.expires_after(std::chrono::seconds(30)); 191002d39b4SEd Tanous conn.async_connect(endpointList, 192*e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterConnect, this, 193*e38778a5SAppaRao Puli shared_from_this())); 194*e38778a5SAppaRao Puli } 195*e38778a5SAppaRao Puli 196*e38778a5SAppaRao Puli void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/, 197*e38778a5SAppaRao Puli boost::beast::error_code ec, 198*e38778a5SAppaRao Puli const boost::asio::ip::tcp::endpoint& endpoint) 199*e38778a5SAppaRao Puli { 200*e38778a5SAppaRao Puli 2012a5689a7SAppaRao Puli if (ec) 2022a5689a7SAppaRao Puli { 203002d39b4SEd Tanous BMCWEB_LOG_ERROR << "Connect " << endpoint.address().to_string() 204002d39b4SEd Tanous << ":" << std::to_string(endpoint.port()) 205*e38778a5SAppaRao Puli << ", id: " << std::to_string(connId) 2062a5689a7SAppaRao Puli << " failed: " << ec.message(); 207*e38778a5SAppaRao Puli state = ConnState::connectFailed; 208*e38778a5SAppaRao Puli waitAndRetry(); 2092a5689a7SAppaRao Puli return; 2102a5689a7SAppaRao Puli } 211*e38778a5SAppaRao Puli BMCWEB_LOG_DEBUG << "Connected to: " << endpoint.address().to_string() 212*e38778a5SAppaRao Puli << ":" << std::to_string(endpoint.port()) 213*e38778a5SAppaRao Puli << ", id: " << std::to_string(connId); 214*e38778a5SAppaRao Puli if (sslConn) 215*e38778a5SAppaRao Puli { 216*e38778a5SAppaRao Puli doSSLHandshake(); 217*e38778a5SAppaRao Puli return; 218*e38778a5SAppaRao Puli } 219*e38778a5SAppaRao Puli state = ConnState::connected; 220*e38778a5SAppaRao Puli sendMessage(); 221*e38778a5SAppaRao Puli } 222*e38778a5SAppaRao Puli 223*e38778a5SAppaRao Puli void doSSLHandshake() 224*e38778a5SAppaRao Puli { 225*e38778a5SAppaRao Puli if (!sslConn) 226*e38778a5SAppaRao Puli { 227*e38778a5SAppaRao Puli return; 228*e38778a5SAppaRao Puli } 229*e38778a5SAppaRao Puli state = ConnState::handshakeInProgress; 230*e38778a5SAppaRao Puli sslConn->async_handshake( 231*e38778a5SAppaRao Puli boost::asio::ssl::stream_base::client, 232*e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterSslHandshake, this, 233*e38778a5SAppaRao Puli shared_from_this())); 234*e38778a5SAppaRao Puli } 235*e38778a5SAppaRao Puli 236*e38778a5SAppaRao Puli void afterSslHandshake(const std::shared_ptr<ConnectionInfo>& /*self*/, 237*e38778a5SAppaRao Puli boost::beast::error_code ec) 238*e38778a5SAppaRao Puli { 239*e38778a5SAppaRao Puli if (ec) 240*e38778a5SAppaRao Puli { 241*e38778a5SAppaRao Puli BMCWEB_LOG_ERROR << "SSL Handshake failed -" 242*e38778a5SAppaRao Puli << " id: " << std::to_string(connId) 243*e38778a5SAppaRao Puli << " error: " << ec.message(); 244*e38778a5SAppaRao Puli state = ConnState::handshakeFailed; 245*e38778a5SAppaRao Puli waitAndRetry(); 246*e38778a5SAppaRao Puli return; 247*e38778a5SAppaRao Puli } 248*e38778a5SAppaRao Puli BMCWEB_LOG_DEBUG << "SSL Handshake successful -" 249*e38778a5SAppaRao Puli << " id: " << std::to_string(connId); 250*e38778a5SAppaRao Puli state = ConnState::connected; 251*e38778a5SAppaRao Puli sendMessage(); 2522a5689a7SAppaRao Puli } 2532a5689a7SAppaRao Puli 254f52c03c1SCarson Labrado void sendMessage() 2552a5689a7SAppaRao Puli { 2562a5689a7SAppaRao Puli state = ConnState::sendInProgress; 2572a5689a7SAppaRao Puli 258bd030d0aSAppaRao Puli // Set a timeout on the operation 259bd030d0aSAppaRao Puli conn.expires_after(std::chrono::seconds(30)); 260bd030d0aSAppaRao Puli 261bd030d0aSAppaRao Puli // Send the HTTP request to the remote host 262*e38778a5SAppaRao Puli if (sslConn) 263*e38778a5SAppaRao Puli { 264*e38778a5SAppaRao Puli boost::beast::http::async_write( 265*e38778a5SAppaRao Puli *sslConn, req, 266*e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterWrite, this, 267*e38778a5SAppaRao Puli shared_from_this())); 268*e38778a5SAppaRao Puli } 269*e38778a5SAppaRao Puli else 270*e38778a5SAppaRao Puli { 271bd030d0aSAppaRao Puli boost::beast::http::async_write( 272bd030d0aSAppaRao Puli conn, req, 273*e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterWrite, this, 274*e38778a5SAppaRao Puli shared_from_this())); 275*e38778a5SAppaRao Puli } 276*e38778a5SAppaRao Puli } 277*e38778a5SAppaRao Puli 278*e38778a5SAppaRao Puli void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/, 279*e38778a5SAppaRao Puli const boost::beast::error_code& ec, size_t bytesTransferred) 280*e38778a5SAppaRao Puli { 281bd030d0aSAppaRao Puli if (ec) 282bd030d0aSAppaRao Puli { 283002d39b4SEd Tanous BMCWEB_LOG_ERROR << "sendMessage() failed: " << ec.message(); 284*e38778a5SAppaRao Puli state = ConnState::sendFailed; 285*e38778a5SAppaRao Puli waitAndRetry(); 286bd030d0aSAppaRao Puli return; 287bd030d0aSAppaRao Puli } 288bd030d0aSAppaRao Puli BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: " 289bd030d0aSAppaRao Puli << bytesTransferred; 290bd030d0aSAppaRao Puli 291*e38778a5SAppaRao Puli recvMessage(); 292bd030d0aSAppaRao Puli } 293bd030d0aSAppaRao Puli 294bd030d0aSAppaRao Puli void recvMessage() 295bd030d0aSAppaRao Puli { 2966eaa1d2fSSunitha Harish state = ConnState::recvInProgress; 2976eaa1d2fSSunitha Harish 2986eaa1d2fSSunitha Harish parser.emplace(std::piecewise_construct, std::make_tuple()); 2996eaa1d2fSSunitha Harish parser->body_limit(httpReadBodyLimit); 3006eaa1d2fSSunitha Harish 301bd030d0aSAppaRao Puli // Receive the HTTP response 302*e38778a5SAppaRao Puli if (sslConn) 303*e38778a5SAppaRao Puli { 304*e38778a5SAppaRao Puli boost::beast::http::async_read( 305*e38778a5SAppaRao Puli *sslConn, buffer, *parser, 306*e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterRead, this, 307*e38778a5SAppaRao Puli shared_from_this())); 308*e38778a5SAppaRao Puli } 309*e38778a5SAppaRao Puli else 310*e38778a5SAppaRao Puli { 311bd030d0aSAppaRao Puli boost::beast::http::async_read( 3126eaa1d2fSSunitha Harish conn, buffer, *parser, 313*e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterRead, this, 314*e38778a5SAppaRao Puli shared_from_this())); 315*e38778a5SAppaRao Puli } 316*e38778a5SAppaRao Puli } 317*e38778a5SAppaRao Puli 318*e38778a5SAppaRao Puli void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/, 319*e38778a5SAppaRao Puli const boost::beast::error_code& ec, 320*e38778a5SAppaRao Puli const std::size_t& bytesTransferred) 321*e38778a5SAppaRao Puli { 322*e38778a5SAppaRao Puli if (ec && ec != boost::asio::ssl::error::stream_truncated) 323bd030d0aSAppaRao Puli { 324002d39b4SEd Tanous BMCWEB_LOG_ERROR << "recvMessage() failed: " << ec.message(); 325*e38778a5SAppaRao Puli state = ConnState::recvFailed; 326*e38778a5SAppaRao Puli waitAndRetry(); 327bd030d0aSAppaRao Puli return; 328bd030d0aSAppaRao Puli } 329bd030d0aSAppaRao Puli BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: " 330bd030d0aSAppaRao Puli << bytesTransferred; 331*e38778a5SAppaRao Puli BMCWEB_LOG_DEBUG << "recvMessage() data: " << parser->get().body(); 332bd030d0aSAppaRao Puli 333*e38778a5SAppaRao Puli unsigned int respCode = parser->get().result_int(); 334*e38778a5SAppaRao Puli BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: " << respCode; 3356eaa1d2fSSunitha Harish 336a7a80296SCarson Labrado // Make sure the received response code is valid as defined by 337a7a80296SCarson Labrado // the associated retry policy 338*e38778a5SAppaRao Puli if (retryPolicy.invalidResp(respCode)) 3396eaa1d2fSSunitha Harish { 3406eaa1d2fSSunitha Harish // The listener failed to receive the Sent-Event 341002d39b4SEd Tanous BMCWEB_LOG_ERROR << "recvMessage() Listener Failed to " 3427adb85acSSunitha Harish "receive Sent-Event. Header Response Code: " 3437adb85acSSunitha Harish << respCode; 344*e38778a5SAppaRao Puli state = ConnState::recvFailed; 345*e38778a5SAppaRao Puli waitAndRetry(); 3466eaa1d2fSSunitha Harish return; 3476eaa1d2fSSunitha Harish } 348bd030d0aSAppaRao Puli 349f52c03c1SCarson Labrado // Send is successful 350f52c03c1SCarson Labrado // Reset the counter just in case this was after retrying 351*e38778a5SAppaRao Puli retryCount = 0; 3526eaa1d2fSSunitha Harish 3536eaa1d2fSSunitha Harish // Keep the connection alive if server supports it 3546eaa1d2fSSunitha Harish // Else close the connection 3556eaa1d2fSSunitha Harish BMCWEB_LOG_DEBUG << "recvMessage() keepalive : " 356*e38778a5SAppaRao Puli << parser->keep_alive(); 3576eaa1d2fSSunitha Harish 358039a47e3SCarson Labrado // Copy the response into a Response object so that it can be 359039a47e3SCarson Labrado // processed by the callback function. 360*e38778a5SAppaRao Puli res.clear(); 361*e38778a5SAppaRao Puli res.stringResponse = parser->release(); 362*e38778a5SAppaRao Puli callback(parser->keep_alive(), connId, res); 363bd030d0aSAppaRao Puli } 364bd030d0aSAppaRao Puli 3656eaa1d2fSSunitha Harish void waitAndRetry() 366bd030d0aSAppaRao Puli { 367*e38778a5SAppaRao Puli if ((retryCount >= retryPolicy.maxRetryAttempts) || 368*e38778a5SAppaRao Puli (state == ConnState::sslInitFailed)) 3692a5689a7SAppaRao Puli { 3706eaa1d2fSSunitha Harish BMCWEB_LOG_ERROR << "Maximum number of retries reached."; 371f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Retry policy: " 372f52c03c1SCarson Labrado << retryPolicy.retryPolicyAction; 373039a47e3SCarson Labrado 374039a47e3SCarson Labrado // We want to return a 502 to indicate there was an error with the 375039a47e3SCarson Labrado // external server 376039a47e3SCarson Labrado res.clear(); 37740d799e6SEd Tanous res.result(boost::beast::http::status::bad_gateway); 378039a47e3SCarson Labrado 379f52c03c1SCarson Labrado if (retryPolicy.retryPolicyAction == "TerminateAfterRetries") 380fe44eb0bSAyushi Smriti { 381fe44eb0bSAyushi Smriti // TODO: delete subscription 382fe44eb0bSAyushi Smriti state = ConnState::terminated; 383039a47e3SCarson Labrado callback(false, connId, res); 384fe44eb0bSAyushi Smriti } 385f52c03c1SCarson Labrado if (retryPolicy.retryPolicyAction == "SuspendRetries") 386fe44eb0bSAyushi Smriti { 3872a5689a7SAppaRao Puli state = ConnState::suspended; 388039a47e3SCarson Labrado callback(false, connId, res); 3892a5689a7SAppaRao Puli } 3906eaa1d2fSSunitha Harish // Reset the retrycount to zero so that client can try connecting 3916eaa1d2fSSunitha Harish // again if needed 392fe44eb0bSAyushi Smriti retryCount = 0; 3932a5689a7SAppaRao Puli return; 3942a5689a7SAppaRao Puli } 3952a5689a7SAppaRao Puli 396fe44eb0bSAyushi Smriti if (runningTimer) 397fe44eb0bSAyushi Smriti { 398fe44eb0bSAyushi Smriti BMCWEB_LOG_DEBUG << "Retry timer is already running."; 399fe44eb0bSAyushi Smriti return; 400fe44eb0bSAyushi Smriti } 401fe44eb0bSAyushi Smriti runningTimer = true; 402fe44eb0bSAyushi Smriti 4032a5689a7SAppaRao Puli retryCount++; 404fe44eb0bSAyushi Smriti 405f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Attempt retry after " 406f52c03c1SCarson Labrado << std::to_string( 407f52c03c1SCarson Labrado retryPolicy.retryIntervalSecs.count()) 408fe44eb0bSAyushi Smriti << " seconds. RetryCount = " << retryCount; 409f52c03c1SCarson Labrado timer.expires_after(retryPolicy.retryIntervalSecs); 410cb13a392SEd Tanous timer.async_wait( 411f52c03c1SCarson Labrado [self(shared_from_this())](const boost::system::error_code ec) { 4126eaa1d2fSSunitha Harish if (ec == boost::asio::error::operation_aborted) 4136eaa1d2fSSunitha Harish { 4146eaa1d2fSSunitha Harish BMCWEB_LOG_DEBUG 4156eaa1d2fSSunitha Harish << "async_wait failed since the operation is aborted" 4166eaa1d2fSSunitha Harish << ec.message(); 4176eaa1d2fSSunitha Harish } 4186eaa1d2fSSunitha Harish else if (ec) 4196eaa1d2fSSunitha Harish { 4206eaa1d2fSSunitha Harish BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message(); 4216eaa1d2fSSunitha Harish // Ignore the error and continue the retry loop to attempt 4226eaa1d2fSSunitha Harish // sending the event as per the retry policy 4236eaa1d2fSSunitha Harish } 424fe44eb0bSAyushi Smriti self->runningTimer = false; 4256eaa1d2fSSunitha Harish 426f52c03c1SCarson Labrado // Let's close the connection and restart from resolve. 427*e38778a5SAppaRao Puli self->doClose(true); 428fe44eb0bSAyushi Smriti }); 4292a5689a7SAppaRao Puli } 4302a5689a7SAppaRao Puli 431*e38778a5SAppaRao Puli void shutdownConn(bool retry) 432fe44eb0bSAyushi Smriti { 433f52c03c1SCarson Labrado boost::beast::error_code ec; 434f52c03c1SCarson Labrado conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); 435f52c03c1SCarson Labrado conn.close(); 436f52c03c1SCarson Labrado 437f52c03c1SCarson Labrado // not_connected happens sometimes so don't bother reporting it. 438f52c03c1SCarson Labrado if (ec && ec != boost::beast::errc::not_connected) 4392a5689a7SAppaRao Puli { 440f52c03c1SCarson Labrado BMCWEB_LOG_ERROR << host << ":" << std::to_string(port) 441f52c03c1SCarson Labrado << ", id: " << std::to_string(connId) 442f52c03c1SCarson Labrado << "shutdown failed: " << ec.message(); 4436eaa1d2fSSunitha Harish } 4445cab68f3SCarson Labrado else 4455cab68f3SCarson Labrado { 446f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port) 447f52c03c1SCarson Labrado << ", id: " << std::to_string(connId) 448f52c03c1SCarson Labrado << " closed gracefully"; 4495cab68f3SCarson Labrado } 450ca723762SEd Tanous 451*e38778a5SAppaRao Puli if ((state != ConnState::suspended) && (state != ConnState::terminated)) 45292a74e56SAppaRao Puli { 453*e38778a5SAppaRao Puli if (retry) 45492a74e56SAppaRao Puli { 455f52c03c1SCarson Labrado // Now let's try to resend the data 456f52c03c1SCarson Labrado state = ConnState::retry; 457*e38778a5SAppaRao Puli this->doResolve(); 458*e38778a5SAppaRao Puli } 459*e38778a5SAppaRao Puli else 460*e38778a5SAppaRao Puli { 461*e38778a5SAppaRao Puli state = ConnState::closed; 462*e38778a5SAppaRao Puli } 463*e38778a5SAppaRao Puli } 464*e38778a5SAppaRao Puli } 465*e38778a5SAppaRao Puli 466*e38778a5SAppaRao Puli void doClose(bool retry = false) 467*e38778a5SAppaRao Puli { 468*e38778a5SAppaRao Puli if (!sslConn) 469*e38778a5SAppaRao Puli { 470*e38778a5SAppaRao Puli shutdownConn(retry); 471*e38778a5SAppaRao Puli return; 472*e38778a5SAppaRao Puli } 473*e38778a5SAppaRao Puli 474*e38778a5SAppaRao Puli sslConn->async_shutdown( 475*e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterSslShutdown, this, 476*e38778a5SAppaRao Puli shared_from_this(), retry)); 477*e38778a5SAppaRao Puli } 478*e38778a5SAppaRao Puli 479*e38778a5SAppaRao Puli void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/, 480*e38778a5SAppaRao Puli bool retry, const boost::system::error_code& ec) 481*e38778a5SAppaRao Puli { 482*e38778a5SAppaRao Puli 483*e38778a5SAppaRao Puli if (ec) 484*e38778a5SAppaRao Puli { 485*e38778a5SAppaRao Puli BMCWEB_LOG_ERROR << host << ":" << std::to_string(port) 486*e38778a5SAppaRao Puli << ", id: " << std::to_string(connId) 487*e38778a5SAppaRao Puli << " shutdown failed: " << ec.message(); 488*e38778a5SAppaRao Puli } 489*e38778a5SAppaRao Puli else 490*e38778a5SAppaRao Puli { 491*e38778a5SAppaRao Puli BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port) 492*e38778a5SAppaRao Puli << ", id: " << std::to_string(connId) 493*e38778a5SAppaRao Puli << " closed gracefully"; 494*e38778a5SAppaRao Puli } 495*e38778a5SAppaRao Puli shutdownConn(retry); 496*e38778a5SAppaRao Puli } 497*e38778a5SAppaRao Puli 498*e38778a5SAppaRao Puli void setCipherSuiteTLSext() 499*e38778a5SAppaRao Puli { 500*e38778a5SAppaRao Puli if (!sslConn) 501*e38778a5SAppaRao Puli { 502*e38778a5SAppaRao Puli return; 503*e38778a5SAppaRao Puli } 504*e38778a5SAppaRao Puli // NOTE: The SSL_set_tlsext_host_name is defined in tlsv1.h header 505*e38778a5SAppaRao Puli // file but its having old style casting (name is cast to void*). 506*e38778a5SAppaRao Puli // Since bmcweb compiler treats all old-style-cast as error, its 507*e38778a5SAppaRao Puli // causing the build failure. So replaced the same macro inline and 508*e38778a5SAppaRao Puli // did corrected the code by doing static_cast to viod*. This has to 509*e38778a5SAppaRao Puli // be fixed in openssl library in long run. Set SNI Hostname (many 510*e38778a5SAppaRao Puli // hosts need this to handshake successfully) 511*e38778a5SAppaRao Puli if (SSL_ctrl(sslConn->native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME, 512*e38778a5SAppaRao Puli TLSEXT_NAMETYPE_host_name, 513*e38778a5SAppaRao Puli static_cast<void*>(&host.front())) == 0) 514*e38778a5SAppaRao Puli 515*e38778a5SAppaRao Puli { 516*e38778a5SAppaRao Puli boost::beast::error_code ec{static_cast<int>(::ERR_get_error()), 517*e38778a5SAppaRao Puli boost::asio::error::get_ssl_category()}; 518*e38778a5SAppaRao Puli 519*e38778a5SAppaRao Puli BMCWEB_LOG_ERROR << "SSL_set_tlsext_host_name " << host << ":" 520*e38778a5SAppaRao Puli << port << ", id: " << std::to_string(connId) 521*e38778a5SAppaRao Puli << " failed: " << ec.message(); 522*e38778a5SAppaRao Puli // Set state as sslInit failed so that we close the connection 523*e38778a5SAppaRao Puli // and take appropriate action as per retry configuration. 524*e38778a5SAppaRao Puli state = ConnState::sslInitFailed; 525*e38778a5SAppaRao Puli waitAndRetry(); 526*e38778a5SAppaRao Puli return; 527*e38778a5SAppaRao Puli } 528bd030d0aSAppaRao Puli } 529bd030d0aSAppaRao Puli 530bd030d0aSAppaRao Puli public: 531*e38778a5SAppaRao Puli explicit ConnectionInfo(boost::asio::io_context& iocIn, 532*e38778a5SAppaRao Puli const std::string& idIn, 533*e38778a5SAppaRao Puli const std::string& destIPIn, uint16_t destPortIn, 534*e38778a5SAppaRao Puli bool useSSL, unsigned int connIdIn) : 5358a592810SEd Tanous subId(idIn), 536*e38778a5SAppaRao Puli host(destIPIn), port(destPortIn), connId(connIdIn), conn(iocIn), 537*e38778a5SAppaRao Puli timer(iocIn) 538*e38778a5SAppaRao Puli { 539*e38778a5SAppaRao Puli if (useSSL) 540*e38778a5SAppaRao Puli { 541*e38778a5SAppaRao Puli std::optional<boost::asio::ssl::context> sslCtx = 542*e38778a5SAppaRao Puli ensuressl::getSSLClientContext(); 543*e38778a5SAppaRao Puli 544*e38778a5SAppaRao Puli if (!sslCtx) 545*e38778a5SAppaRao Puli { 546*e38778a5SAppaRao Puli BMCWEB_LOG_ERROR << "prepareSSLContext failed - " << host << ":" 547*e38778a5SAppaRao Puli << port << ", id: " << std::to_string(connId); 548*e38778a5SAppaRao Puli // Don't retry if failure occurs while preparing SSL context 549*e38778a5SAppaRao Puli // such as certificate is invalid or set cipher failure or set 550*e38778a5SAppaRao Puli // host name failure etc... Setting conn state to sslInitFailed 551*e38778a5SAppaRao Puli // and connection state will be transitioned to next state 552*e38778a5SAppaRao Puli // depending on retry policy set by subscription. 553*e38778a5SAppaRao Puli state = ConnState::sslInitFailed; 554*e38778a5SAppaRao Puli waitAndRetry(); 555*e38778a5SAppaRao Puli return; 556*e38778a5SAppaRao Puli } 557*e38778a5SAppaRao Puli sslConn.emplace(conn, *sslCtx); 558*e38778a5SAppaRao Puli setCipherSuiteTLSext(); 559*e38778a5SAppaRao Puli } 560*e38778a5SAppaRao Puli } 561f52c03c1SCarson Labrado }; 562bd030d0aSAppaRao Puli 563f52c03c1SCarson Labrado class ConnectionPool : public std::enable_shared_from_this<ConnectionPool> 564bd030d0aSAppaRao Puli { 565f52c03c1SCarson Labrado private: 566f52c03c1SCarson Labrado boost::asio::io_context& ioc; 567*e38778a5SAppaRao Puli std::string id; 568*e38778a5SAppaRao Puli std::string destIP; 569*e38778a5SAppaRao Puli uint16_t destPort; 570*e38778a5SAppaRao Puli bool useSSL; 571f52c03c1SCarson Labrado std::vector<std::shared_ptr<ConnectionInfo>> connections; 572f52c03c1SCarson Labrado boost::container::devector<PendingRequest> requestQueue; 573f52c03c1SCarson Labrado 574f52c03c1SCarson Labrado friend class HttpClient; 575f52c03c1SCarson Labrado 576244256ccSCarson Labrado // Configure a connections's request, callback, and retry info in 577244256ccSCarson Labrado // preparation to begin sending the request 578f52c03c1SCarson Labrado void setConnProps(ConnectionInfo& conn) 579bd030d0aSAppaRao Puli { 580f52c03c1SCarson Labrado if (requestQueue.empty()) 581f52c03c1SCarson Labrado { 582f52c03c1SCarson Labrado BMCWEB_LOG_ERROR 583f52c03c1SCarson Labrado << "setConnProps() should not have been called when requestQueue is empty"; 584bd030d0aSAppaRao Puli return; 585bd030d0aSAppaRao Puli } 586bd030d0aSAppaRao Puli 587244256ccSCarson Labrado auto nextReq = requestQueue.front(); 588244256ccSCarson Labrado conn.retryPolicy = std::move(nextReq.retryPolicy); 589244256ccSCarson Labrado conn.req = std::move(nextReq.req); 590244256ccSCarson Labrado conn.callback = std::move(nextReq.callback); 591f52c03c1SCarson Labrado 592f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Setting properties for connection " << conn.host 593f52c03c1SCarson Labrado << ":" << std::to_string(conn.port) 594a7a80296SCarson Labrado << ", id: " << std::to_string(conn.connId); 595f52c03c1SCarson Labrado 596f52c03c1SCarson Labrado // We can remove the request from the queue at this point 597f52c03c1SCarson Labrado requestQueue.pop_front(); 598f52c03c1SCarson Labrado } 599f52c03c1SCarson Labrado 600f52c03c1SCarson Labrado // Configures a connection to use the specific retry policy. 601f52c03c1SCarson Labrado inline void setConnRetryPolicy(ConnectionInfo& conn, 602f52c03c1SCarson Labrado const RetryPolicyData& retryPolicy) 6032a5689a7SAppaRao Puli { 604f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << destIP << ":" << std::to_string(destPort) 605a7a80296SCarson Labrado << ", id: " << std::to_string(conn.connId); 606f52c03c1SCarson Labrado 607f52c03c1SCarson Labrado conn.retryPolicy = retryPolicy; 608f52c03c1SCarson Labrado } 609f52c03c1SCarson Labrado 610f52c03c1SCarson Labrado // Gets called as part of callback after request is sent 611f52c03c1SCarson Labrado // Reuses the connection if there are any requests waiting to be sent 612f52c03c1SCarson Labrado // Otherwise closes the connection if it is not a keep-alive 613f52c03c1SCarson Labrado void sendNext(bool keepAlive, uint32_t connId) 614f52c03c1SCarson Labrado { 615f52c03c1SCarson Labrado auto conn = connections[connId]; 61646a81465SCarson Labrado 61746a81465SCarson Labrado // Allow the connection's handler to be deleted 61846a81465SCarson Labrado // This is needed because of Redfish Aggregation passing an 61946a81465SCarson Labrado // AsyncResponse shared_ptr to this callback 62046a81465SCarson Labrado conn->callback = nullptr; 62146a81465SCarson Labrado 622f52c03c1SCarson Labrado // Reuse the connection to send the next request in the queue 623f52c03c1SCarson Labrado if (!requestQueue.empty()) 624f52c03c1SCarson Labrado { 625f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << std::to_string(requestQueue.size()) 626f52c03c1SCarson Labrado << " requests remaining in queue for " << destIP 627f52c03c1SCarson Labrado << ":" << std::to_string(destPort) 628f52c03c1SCarson Labrado << ", reusing connnection " 629f52c03c1SCarson Labrado << std::to_string(connId); 630f52c03c1SCarson Labrado 631f52c03c1SCarson Labrado setConnProps(*conn); 632f52c03c1SCarson Labrado 633f52c03c1SCarson Labrado if (keepAlive) 634f52c03c1SCarson Labrado { 635f52c03c1SCarson Labrado conn->sendMessage(); 6362a5689a7SAppaRao Puli } 6372a5689a7SAppaRao Puli else 6382a5689a7SAppaRao Puli { 639f52c03c1SCarson Labrado // Server is not keep-alive enabled so we need to close the 640f52c03c1SCarson Labrado // connection and then start over from resolve 641f52c03c1SCarson Labrado conn->doClose(); 642f52c03c1SCarson Labrado conn->doResolve(); 643f52c03c1SCarson Labrado } 644f52c03c1SCarson Labrado return; 645f52c03c1SCarson Labrado } 646f52c03c1SCarson Labrado 647f52c03c1SCarson Labrado // No more messages to send so close the connection if necessary 648f52c03c1SCarson Labrado if (keepAlive) 649f52c03c1SCarson Labrado { 650f52c03c1SCarson Labrado conn->state = ConnState::idle; 651f52c03c1SCarson Labrado } 652f52c03c1SCarson Labrado else 653f52c03c1SCarson Labrado { 654f52c03c1SCarson Labrado // Abort the connection since server is not keep-alive enabled 655f52c03c1SCarson Labrado conn->state = ConnState::abortConnection; 656f52c03c1SCarson Labrado conn->doClose(); 6572a5689a7SAppaRao Puli } 658bd030d0aSAppaRao Puli } 659bd030d0aSAppaRao Puli 660244256ccSCarson Labrado void sendData(std::string& data, const std::string& destUri, 661244256ccSCarson Labrado const boost::beast::http::fields& httpHeader, 662244256ccSCarson Labrado const boost::beast::http::verb verb, 663244256ccSCarson Labrado const RetryPolicyData& retryPolicy, 6646b3db60dSEd Tanous const std::function<void(Response&)>& resHandler) 665fe44eb0bSAyushi Smriti { 666f52c03c1SCarson Labrado std::weak_ptr<ConnectionPool> weakSelf = weak_from_this(); 667f52c03c1SCarson Labrado 668f52c03c1SCarson Labrado // Callback to be called once the request has been sent 669039a47e3SCarson Labrado auto cb = [weakSelf, resHandler](bool keepAlive, uint32_t connId, 670039a47e3SCarson Labrado Response& res) { 671039a47e3SCarson Labrado // Allow provided callback to perform additional processing of the 672039a47e3SCarson Labrado // request 673039a47e3SCarson Labrado resHandler(res); 674039a47e3SCarson Labrado 675f52c03c1SCarson Labrado // If requests remain in the queue then we want to reuse this 676f52c03c1SCarson Labrado // connection to send the next request 677f52c03c1SCarson Labrado std::shared_ptr<ConnectionPool> self = weakSelf.lock(); 678f52c03c1SCarson Labrado if (!self) 679f52c03c1SCarson Labrado { 680f52c03c1SCarson Labrado BMCWEB_LOG_CRITICAL << self << " Failed to capture connection"; 681f52c03c1SCarson Labrado return; 682fe44eb0bSAyushi Smriti } 683fe44eb0bSAyushi Smriti 684f52c03c1SCarson Labrado self->sendNext(keepAlive, connId); 685f52c03c1SCarson Labrado }; 686f52c03c1SCarson Labrado 687244256ccSCarson Labrado // Construct the request to be sent 688244256ccSCarson Labrado boost::beast::http::request<boost::beast::http::string_body> thisReq( 689244256ccSCarson Labrado verb, destUri, 11, "", httpHeader); 690244256ccSCarson Labrado thisReq.set(boost::beast::http::field::host, destIP); 691244256ccSCarson Labrado thisReq.keep_alive(true); 692244256ccSCarson Labrado thisReq.body() = std::move(data); 693244256ccSCarson Labrado thisReq.prepare_payload(); 694244256ccSCarson Labrado 695f52c03c1SCarson Labrado // Reuse an existing connection if one is available 696f52c03c1SCarson Labrado for (unsigned int i = 0; i < connections.size(); i++) 697fe44eb0bSAyushi Smriti { 698f52c03c1SCarson Labrado auto conn = connections[i]; 699f52c03c1SCarson Labrado if ((conn->state == ConnState::idle) || 700f52c03c1SCarson Labrado (conn->state == ConnState::initialized) || 701f52c03c1SCarson Labrado (conn->state == ConnState::closed)) 702f52c03c1SCarson Labrado { 703244256ccSCarson Labrado conn->req = std::move(thisReq); 704f52c03c1SCarson Labrado conn->callback = std::move(cb); 705f52c03c1SCarson Labrado setConnRetryPolicy(*conn, retryPolicy); 706f52c03c1SCarson Labrado std::string commonMsg = std::to_string(i) + " from pool " + 707f52c03c1SCarson Labrado destIP + ":" + std::to_string(destPort); 708f52c03c1SCarson Labrado 709f52c03c1SCarson Labrado if (conn->state == ConnState::idle) 710f52c03c1SCarson Labrado { 711f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Grabbing idle connection " 712f52c03c1SCarson Labrado << commonMsg; 713f52c03c1SCarson Labrado conn->sendMessage(); 714f52c03c1SCarson Labrado } 715f52c03c1SCarson Labrado else 716f52c03c1SCarson Labrado { 717f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Reusing existing connection " 718f52c03c1SCarson Labrado << commonMsg; 719f52c03c1SCarson Labrado conn->doResolve(); 720f52c03c1SCarson Labrado } 721f52c03c1SCarson Labrado return; 722f52c03c1SCarson Labrado } 723f52c03c1SCarson Labrado } 724f52c03c1SCarson Labrado 725f52c03c1SCarson Labrado // All connections in use so create a new connection or add request to 726f52c03c1SCarson Labrado // the queue 727f52c03c1SCarson Labrado if (connections.size() < maxPoolSize) 728f52c03c1SCarson Labrado { 729f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Adding new connection to pool " << destIP 730f52c03c1SCarson Labrado << ":" << std::to_string(destPort); 731f52c03c1SCarson Labrado auto conn = addConnection(); 732244256ccSCarson Labrado conn->req = std::move(thisReq); 733f52c03c1SCarson Labrado conn->callback = std::move(cb); 734f52c03c1SCarson Labrado setConnRetryPolicy(*conn, retryPolicy); 735f52c03c1SCarson Labrado conn->doResolve(); 736f52c03c1SCarson Labrado } 737f52c03c1SCarson Labrado else if (requestQueue.size() < maxRequestQueueSize) 738f52c03c1SCarson Labrado { 739f52c03c1SCarson Labrado BMCWEB_LOG_ERROR << "Max pool size reached. Adding data to queue."; 740244256ccSCarson Labrado requestQueue.emplace_back(std::move(thisReq), std::move(cb), 741f52c03c1SCarson Labrado retryPolicy); 742f52c03c1SCarson Labrado } 743f52c03c1SCarson Labrado else 744f52c03c1SCarson Labrado { 745f52c03c1SCarson Labrado BMCWEB_LOG_ERROR << destIP << ":" << std::to_string(destPort) 746f52c03c1SCarson Labrado << " request queue full. Dropping request."; 747f52c03c1SCarson Labrado } 748f52c03c1SCarson Labrado } 749f52c03c1SCarson Labrado 750f52c03c1SCarson Labrado std::shared_ptr<ConnectionInfo>& addConnection() 751f52c03c1SCarson Labrado { 752f52c03c1SCarson Labrado unsigned int newId = static_cast<unsigned int>(connections.size()); 753f52c03c1SCarson Labrado 754*e38778a5SAppaRao Puli auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>( 755*e38778a5SAppaRao Puli ioc, id, destIP, destPort, useSSL, newId)); 756f52c03c1SCarson Labrado 757f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Added connection " 758f52c03c1SCarson Labrado << std::to_string(connections.size() - 1) 759f52c03c1SCarson Labrado << " to pool " << destIP << ":" 760f52c03c1SCarson Labrado << std::to_string(destPort); 761f52c03c1SCarson Labrado 762f52c03c1SCarson Labrado return ret; 763f52c03c1SCarson Labrado } 764f52c03c1SCarson Labrado 765f52c03c1SCarson Labrado public: 7668a592810SEd Tanous explicit ConnectionPool(boost::asio::io_context& iocIn, 7678a592810SEd Tanous const std::string& idIn, 768*e38778a5SAppaRao Puli const std::string& destIPIn, uint16_t destPortIn, 769*e38778a5SAppaRao Puli bool useSSLIn) : 7708a592810SEd Tanous ioc(iocIn), 771*e38778a5SAppaRao Puli id(idIn), destIP(destIPIn), destPort(destPortIn), useSSL(useSSLIn) 772f52c03c1SCarson Labrado { 773f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Initializing connection pool for " << destIP << ":" 774f52c03c1SCarson Labrado << std::to_string(destPort); 775f52c03c1SCarson Labrado 776f52c03c1SCarson Labrado // Initialize the pool with a single connection 777f52c03c1SCarson Labrado addConnection(); 778fe44eb0bSAyushi Smriti } 779bd030d0aSAppaRao Puli }; 780bd030d0aSAppaRao Puli 781f52c03c1SCarson Labrado class HttpClient 782f52c03c1SCarson Labrado { 783f52c03c1SCarson Labrado private: 784f52c03c1SCarson Labrado std::unordered_map<std::string, std::shared_ptr<ConnectionPool>> 785f52c03c1SCarson Labrado connectionPools; 786f52c03c1SCarson Labrado boost::asio::io_context& ioc = 787f52c03c1SCarson Labrado crow::connections::systemBus->get_io_context(); 788f52c03c1SCarson Labrado std::unordered_map<std::string, RetryPolicyData> retryInfo; 789f52c03c1SCarson Labrado HttpClient() = default; 790f52c03c1SCarson Labrado 791039a47e3SCarson Labrado // Used as a dummy callback by sendData() in order to call 792039a47e3SCarson Labrado // sendDataWithCallback() 79302cad96eSEd Tanous static void genericResHandler(const Response& res) 794039a47e3SCarson Labrado { 795039a47e3SCarson Labrado BMCWEB_LOG_DEBUG << "Response handled with return code: " 796039a47e3SCarson Labrado << std::to_string(res.resultInt()); 7974ee8e211SEd Tanous } 798039a47e3SCarson Labrado 799f52c03c1SCarson Labrado public: 800f52c03c1SCarson Labrado HttpClient(const HttpClient&) = delete; 801f52c03c1SCarson Labrado HttpClient& operator=(const HttpClient&) = delete; 802f52c03c1SCarson Labrado HttpClient(HttpClient&&) = delete; 803f52c03c1SCarson Labrado HttpClient& operator=(HttpClient&&) = delete; 804f52c03c1SCarson Labrado ~HttpClient() = default; 805f52c03c1SCarson Labrado 806f52c03c1SCarson Labrado static HttpClient& getInstance() 807f52c03c1SCarson Labrado { 808f52c03c1SCarson Labrado static HttpClient handler; 809f52c03c1SCarson Labrado return handler; 810f52c03c1SCarson Labrado } 811f52c03c1SCarson Labrado 812039a47e3SCarson Labrado // Send a request to destIP:destPort where additional processing of the 813039a47e3SCarson Labrado // result is not required 814f52c03c1SCarson Labrado void sendData(std::string& data, const std::string& id, 815*e38778a5SAppaRao Puli const std::string& destIP, uint16_t destPort, 816*e38778a5SAppaRao Puli const std::string& destUri, bool useSSL, 817f52c03c1SCarson Labrado const boost::beast::http::fields& httpHeader, 818244256ccSCarson Labrado const boost::beast::http::verb verb, 819244256ccSCarson Labrado const std::string& retryPolicyName) 820f52c03c1SCarson Labrado { 821*e38778a5SAppaRao Puli const std::function<void(Response&)> cb = genericResHandler; 822*e38778a5SAppaRao Puli sendDataWithCallback(data, id, destIP, destPort, destUri, useSSL, 823*e38778a5SAppaRao Puli httpHeader, verb, retryPolicyName, cb); 824039a47e3SCarson Labrado } 825039a47e3SCarson Labrado 826039a47e3SCarson Labrado // Send request to destIP:destPort and use the provided callback to 827039a47e3SCarson Labrado // handle the response 828039a47e3SCarson Labrado void sendDataWithCallback(std::string& data, const std::string& id, 829*e38778a5SAppaRao Puli const std::string& destIP, uint16_t destPort, 830*e38778a5SAppaRao Puli const std::string& destUri, bool useSSL, 831039a47e3SCarson Labrado const boost::beast::http::fields& httpHeader, 832244256ccSCarson Labrado const boost::beast::http::verb verb, 833244256ccSCarson Labrado const std::string& retryPolicyName, 8346b3db60dSEd Tanous const std::function<void(Response&)>& resHandler) 835039a47e3SCarson Labrado { 836*e38778a5SAppaRao Puli std::string clientKey = useSSL ? "https" : "http"; 837*e38778a5SAppaRao Puli clientKey += destIP; 838*e38778a5SAppaRao Puli clientKey += ":"; 839*e38778a5SAppaRao Puli clientKey += std::to_string(destPort); 840f52c03c1SCarson Labrado // Use nullptr to avoid creating a ConnectionPool each time 841*e38778a5SAppaRao Puli std::shared_ptr<ConnectionPool>& conn = connectionPools[clientKey]; 842*e38778a5SAppaRao Puli if (conn == nullptr) 843f52c03c1SCarson Labrado { 844f52c03c1SCarson Labrado // Now actually create the ConnectionPool shared_ptr since it does 845f52c03c1SCarson Labrado // not already exist 846*e38778a5SAppaRao Puli conn = std::make_shared<ConnectionPool>(ioc, id, destIP, destPort, 847*e38778a5SAppaRao Puli useSSL); 848f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Created connection pool for " << clientKey; 849f52c03c1SCarson Labrado } 850f52c03c1SCarson Labrado else 851f52c03c1SCarson Labrado { 852f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Using existing connection pool for " 853f52c03c1SCarson Labrado << clientKey; 854f52c03c1SCarson Labrado } 855f52c03c1SCarson Labrado 856f52c03c1SCarson Labrado // Get the associated retry policy 857f52c03c1SCarson Labrado auto policy = retryInfo.try_emplace(retryPolicyName); 858f52c03c1SCarson Labrado if (policy.second) 859f52c03c1SCarson Labrado { 860f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Creating retry policy \"" << retryPolicyName 861f52c03c1SCarson Labrado << "\" with default values"; 862f52c03c1SCarson Labrado } 863f52c03c1SCarson Labrado 864f52c03c1SCarson Labrado // Send the data using either the existing connection pool or the newly 865f52c03c1SCarson Labrado // created connection pool 866*e38778a5SAppaRao Puli conn->sendData(data, destUri, httpHeader, verb, policy.first->second, 867*e38778a5SAppaRao Puli resHandler); 868f52c03c1SCarson Labrado } 869f52c03c1SCarson Labrado 870a7a80296SCarson Labrado void setRetryConfig( 871a7a80296SCarson Labrado const uint32_t retryAttempts, const uint32_t retryTimeoutInterval, 872a7a80296SCarson Labrado const std::function<boost::system::error_code(unsigned int respCode)>& 873a7a80296SCarson Labrado invalidResp, 874f52c03c1SCarson Labrado const std::string& retryPolicyName) 875f52c03c1SCarson Labrado { 876f52c03c1SCarson Labrado // We need to create the retry policy if one does not already exist for 877f52c03c1SCarson Labrado // the given retryPolicyName 878f52c03c1SCarson Labrado auto result = retryInfo.try_emplace(retryPolicyName); 879f52c03c1SCarson Labrado if (result.second) 880f52c03c1SCarson Labrado { 881f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "setRetryConfig(): Creating new retry policy \"" 882f52c03c1SCarson Labrado << retryPolicyName << "\""; 883f52c03c1SCarson Labrado } 884f52c03c1SCarson Labrado else 885f52c03c1SCarson Labrado { 886f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "setRetryConfig(): Updating retry info for \"" 887f52c03c1SCarson Labrado << retryPolicyName << "\""; 888f52c03c1SCarson Labrado } 889f52c03c1SCarson Labrado 890f52c03c1SCarson Labrado result.first->second.maxRetryAttempts = retryAttempts; 891f52c03c1SCarson Labrado result.first->second.retryIntervalSecs = 892f52c03c1SCarson Labrado std::chrono::seconds(retryTimeoutInterval); 893a7a80296SCarson Labrado result.first->second.invalidResp = invalidResp; 894f52c03c1SCarson Labrado } 895f52c03c1SCarson Labrado 896f52c03c1SCarson Labrado void setRetryPolicy(const std::string& retryPolicy, 897f52c03c1SCarson Labrado const std::string& retryPolicyName) 898f52c03c1SCarson Labrado { 899f52c03c1SCarson Labrado // We need to create the retry policy if one does not already exist for 900f52c03c1SCarson Labrado // the given retryPolicyName 901f52c03c1SCarson Labrado auto result = retryInfo.try_emplace(retryPolicyName); 902f52c03c1SCarson Labrado if (result.second) 903f52c03c1SCarson Labrado { 904f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "setRetryPolicy(): Creating new retry policy \"" 905f52c03c1SCarson Labrado << retryPolicyName << "\""; 906f52c03c1SCarson Labrado } 907f52c03c1SCarson Labrado else 908f52c03c1SCarson Labrado { 909f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "setRetryPolicy(): Updating retry policy for \"" 910f52c03c1SCarson Labrado << retryPolicyName << "\""; 911f52c03c1SCarson Labrado } 912f52c03c1SCarson Labrado 913f52c03c1SCarson Labrado result.first->second.retryPolicyAction = retryPolicy; 914f52c03c1SCarson Labrado } 915f52c03c1SCarson Labrado }; 916bd030d0aSAppaRao Puli } // namespace crow 917