1bd030d0aSAppaRao Puli /* 2bd030d0aSAppaRao Puli // Copyright (c) 2020 Intel Corporation 3bd030d0aSAppaRao Puli // 4bd030d0aSAppaRao Puli // Licensed under the Apache License, Version 2.0 (the "License"); 5bd030d0aSAppaRao Puli // you may not use this file except in compliance with the License. 6bd030d0aSAppaRao Puli // You may obtain a copy of the License at 7bd030d0aSAppaRao Puli // 8bd030d0aSAppaRao Puli // http://www.apache.org/licenses/LICENSE-2.0 9bd030d0aSAppaRao Puli // 10bd030d0aSAppaRao Puli // Unless required by applicable law or agreed to in writing, software 11bd030d0aSAppaRao Puli // distributed under the License is distributed on an "AS IS" BASIS, 12bd030d0aSAppaRao Puli // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13bd030d0aSAppaRao Puli // See the License for the specific language governing permissions and 14bd030d0aSAppaRao Puli // limitations under the License. 15bd030d0aSAppaRao Puli */ 16bd030d0aSAppaRao Puli #pragma once 1777665bdaSNan Zhou 1877665bdaSNan Zhou #include "async_resolve.hpp" 1977665bdaSNan Zhou #include "http_response.hpp" 203ccb3adbSEd Tanous #include "logging.hpp" 213ccb3adbSEd Tanous #include "ssl_key_handler.hpp" 2277665bdaSNan Zhou 230d5f5cf4SEd Tanous #include <boost/asio/connect.hpp> 24bb49eb5cSEd Tanous #include <boost/asio/io_context.hpp> 2529a82b08SSunitha Harish #include <boost/asio/ip/address.hpp> 2629a82b08SSunitha Harish #include <boost/asio/ip/basic_endpoint.hpp> 27bb49eb5cSEd Tanous #include <boost/asio/ip/tcp.hpp> 28e38778a5SAppaRao Puli #include <boost/asio/ssl/context.hpp> 29e38778a5SAppaRao Puli #include <boost/asio/ssl/error.hpp> 30d43cd0caSEd Tanous #include <boost/asio/steady_timer.hpp> 31d43cd0caSEd Tanous #include <boost/beast/core/flat_buffer.hpp> 32bb49eb5cSEd Tanous #include <boost/beast/core/flat_static_buffer.hpp> 33d43cd0caSEd Tanous #include <boost/beast/http/message.hpp> 34bb49eb5cSEd Tanous #include <boost/beast/http/parser.hpp> 35bb49eb5cSEd Tanous #include <boost/beast/http/read.hpp> 36bb49eb5cSEd Tanous #include <boost/beast/http/string_body.hpp> 37bb49eb5cSEd Tanous #include <boost/beast/http/write.hpp> 38e38778a5SAppaRao Puli #include <boost/beast/ssl/ssl_stream.hpp> 39bd030d0aSAppaRao Puli #include <boost/beast/version.hpp> 40f52c03c1SCarson Labrado #include <boost/container/devector.hpp> 41bb49eb5cSEd Tanous #include <boost/system/error_code.hpp> 421214b7e7SGunnar Mills 43bd030d0aSAppaRao Puli #include <cstdlib> 44bd030d0aSAppaRao Puli #include <functional> 45bd030d0aSAppaRao Puli #include <iostream> 46bd030d0aSAppaRao Puli #include <memory> 472a5689a7SAppaRao Puli #include <queue> 48bd030d0aSAppaRao Puli #include <string> 49bd030d0aSAppaRao Puli 50bd030d0aSAppaRao Puli namespace crow 51bd030d0aSAppaRao Puli { 52bd030d0aSAppaRao Puli 5366d90c2cSCarson Labrado // With Redfish Aggregation it is assumed we will connect to another instance 5466d90c2cSCarson Labrado // of BMCWeb which can handle 100 simultaneous connections. 5566d90c2cSCarson Labrado constexpr size_t maxPoolSize = 20; 5666d90c2cSCarson Labrado constexpr size_t maxRequestQueueSize = 500; 5717dcc312SCarson Labrado constexpr unsigned int httpReadBodyLimit = 131072; 584d94272fSCarson Labrado constexpr unsigned int httpReadBufferSize = 4096; 592a5689a7SAppaRao Puli 60bd030d0aSAppaRao Puli enum class ConnState 61bd030d0aSAppaRao Puli { 622a5689a7SAppaRao Puli initialized, 6329a82b08SSunitha Harish resolveInProgress, 6429a82b08SSunitha Harish resolveFailed, 652a5689a7SAppaRao Puli connectInProgress, 662a5689a7SAppaRao Puli connectFailed, 67bd030d0aSAppaRao Puli connected, 68e38778a5SAppaRao Puli handshakeInProgress, 69e38778a5SAppaRao Puli handshakeFailed, 702a5689a7SAppaRao Puli sendInProgress, 712a5689a7SAppaRao Puli sendFailed, 726eaa1d2fSSunitha Harish recvInProgress, 732a5689a7SAppaRao Puli recvFailed, 742a5689a7SAppaRao Puli idle, 75fe44eb0bSAyushi Smriti closed, 766eaa1d2fSSunitha Harish suspended, 776eaa1d2fSSunitha Harish terminated, 786eaa1d2fSSunitha Harish abortConnection, 79e38778a5SAppaRao Puli sslInitFailed, 806eaa1d2fSSunitha Harish retry 81bd030d0aSAppaRao Puli }; 82bd030d0aSAppaRao Puli 83a7a80296SCarson Labrado static inline boost::system::error_code 84a7a80296SCarson Labrado defaultRetryHandler(unsigned int respCode) 85a7a80296SCarson Labrado { 86a7a80296SCarson Labrado // As a default, assume 200X is alright 87a7a80296SCarson Labrado BMCWEB_LOG_DEBUG << "Using default check for response code validity"; 88a7a80296SCarson Labrado if ((respCode < 200) || (respCode >= 300)) 89a7a80296SCarson Labrado { 90a7a80296SCarson Labrado return boost::system::errc::make_error_code( 91a7a80296SCarson Labrado boost::system::errc::result_out_of_range); 92a7a80296SCarson Labrado } 93a7a80296SCarson Labrado 94a7a80296SCarson Labrado // Return 0 if the response code is valid 95a7a80296SCarson Labrado return boost::system::errc::make_error_code(boost::system::errc::success); 96a7a80296SCarson Labrado }; 97a7a80296SCarson Labrado 98f52c03c1SCarson Labrado // We need to allow retry information to be set before a message has been sent 99f52c03c1SCarson Labrado // and a connection pool has been created 100d14a48ffSCarson Labrado struct ConnectionPolicy 101f52c03c1SCarson Labrado { 102f52c03c1SCarson Labrado uint32_t maxRetryAttempts = 5; 103d14a48ffSCarson Labrado 104d14a48ffSCarson Labrado // the max size of requests in bytes. 0 for unlimited 105d14a48ffSCarson Labrado boost::optional<uint64_t> requestByteLimit = httpReadBodyLimit; 106d14a48ffSCarson Labrado 107d14a48ffSCarson Labrado size_t maxConnections = 1; 108d14a48ffSCarson Labrado 109f52c03c1SCarson Labrado std::string retryPolicyAction = "TerminateAfterRetries"; 110d14a48ffSCarson Labrado 111d14a48ffSCarson Labrado std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0); 112a7a80296SCarson Labrado std::function<boost::system::error_code(unsigned int respCode)> 113a7a80296SCarson Labrado invalidResp = defaultRetryHandler; 114f52c03c1SCarson Labrado }; 115f52c03c1SCarson Labrado 116f52c03c1SCarson Labrado struct PendingRequest 117f52c03c1SCarson Labrado { 118244256ccSCarson Labrado boost::beast::http::request<boost::beast::http::string_body> req; 119039a47e3SCarson Labrado std::function<void(bool, uint32_t, Response&)> callback; 120039a47e3SCarson Labrado PendingRequest( 1218a592810SEd Tanous boost::beast::http::request<boost::beast::http::string_body>&& reqIn, 122d14a48ffSCarson Labrado const std::function<void(bool, uint32_t, Response&)>& callbackIn) : 1238a592810SEd Tanous req(std::move(reqIn)), 124d14a48ffSCarson Labrado callback(callbackIn) 125f52c03c1SCarson Labrado {} 126f52c03c1SCarson Labrado }; 127f52c03c1SCarson Labrado 128f52c03c1SCarson Labrado class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo> 129bd030d0aSAppaRao Puli { 130bd030d0aSAppaRao Puli private: 131f52c03c1SCarson Labrado ConnState state = ConnState::initialized; 132f52c03c1SCarson Labrado uint32_t retryCount = 0; 133f52c03c1SCarson Labrado std::string subId; 134d14a48ffSCarson Labrado std::shared_ptr<ConnectionPolicy> connPolicy; 135f52c03c1SCarson Labrado std::string host; 136f52c03c1SCarson Labrado uint16_t port; 137f52c03c1SCarson Labrado uint32_t connId; 138f52c03c1SCarson Labrado 139f52c03c1SCarson Labrado // Data buffers 140bd030d0aSAppaRao Puli boost::beast::http::request<boost::beast::http::string_body> req; 1416eaa1d2fSSunitha Harish std::optional< 1426eaa1d2fSSunitha Harish boost::beast::http::response_parser<boost::beast::http::string_body>> 1436eaa1d2fSSunitha Harish parser; 1444d94272fSCarson Labrado boost::beast::flat_static_buffer<httpReadBufferSize> buffer; 145039a47e3SCarson Labrado Response res; 1466eaa1d2fSSunitha Harish 147f52c03c1SCarson Labrado // Ascync callables 148039a47e3SCarson Labrado std::function<void(bool, uint32_t, Response&)> callback; 149f8ca6d79SEd Tanous 150f8ca6d79SEd Tanous #ifdef BMCWEB_DBUS_DNS_RESOLVER 151f8ca6d79SEd Tanous using Resolver = crow::async_resolve::Resolver; 152f8ca6d79SEd Tanous #else 153f8ca6d79SEd Tanous using Resolver = boost::asio::ip::tcp::resolver; 154f8ca6d79SEd Tanous #endif 155f8ca6d79SEd Tanous Resolver resolver; 156f8ca6d79SEd Tanous 1570d5f5cf4SEd Tanous boost::asio::ip::tcp::socket conn; 1580d5f5cf4SEd Tanous std::optional<boost::beast::ssl_stream<boost::asio::ip::tcp::socket&>> 1590d5f5cf4SEd Tanous sslConn; 160e38778a5SAppaRao Puli 161f52c03c1SCarson Labrado boost::asio::steady_timer timer; 16284b35604SEd Tanous 163f52c03c1SCarson Labrado friend class ConnectionPool; 164bd030d0aSAppaRao Puli 16529a82b08SSunitha Harish void doResolve() 16629a82b08SSunitha Harish { 16729a82b08SSunitha Harish state = ConnState::resolveInProgress; 168f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":" 169f52c03c1SCarson Labrado << std::to_string(port) 170f52c03c1SCarson Labrado << ", id: " << std::to_string(connId); 17129a82b08SSunitha Harish 172f8ca6d79SEd Tanous resolver.async_resolve(host, std::to_string(port), 1733d36e3a5SEd Tanous std::bind_front(&ConnectionInfo::afterResolve, 1743d36e3a5SEd Tanous this, shared_from_this())); 1753d36e3a5SEd Tanous } 1763d36e3a5SEd Tanous 177f8ca6d79SEd Tanous void afterResolve(const std::shared_ptr<ConnectionInfo>& /*self*/, 178f8ca6d79SEd Tanous const boost::system::error_code& ec, 179f8ca6d79SEd Tanous const Resolver::results_type& endpointList) 1803d36e3a5SEd Tanous { 18126f6976fSEd Tanous if (ec || (endpointList.empty())) 18229a82b08SSunitha Harish { 183*d7043b3aSSunitha Harish BMCWEB_LOG_ERROR << "Resolve failed: " << ec.message() << " " 184*d7043b3aSSunitha Harish << host << ":" << std::to_string(port); 1853d36e3a5SEd Tanous state = ConnState::resolveFailed; 1863d36e3a5SEd Tanous waitAndRetry(); 18729a82b08SSunitha Harish return; 18829a82b08SSunitha Harish } 1893d36e3a5SEd Tanous BMCWEB_LOG_DEBUG << "Resolved " << host << ":" << std::to_string(port) 1903d36e3a5SEd Tanous << ", id: " << std::to_string(connId); 1912a5689a7SAppaRao Puli state = ConnState::connectInProgress; 1922a5689a7SAppaRao Puli 193f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":" 194f52c03c1SCarson Labrado << std::to_string(port) 195f52c03c1SCarson Labrado << ", id: " << std::to_string(connId); 196b00dcc27SEd Tanous 1970d5f5cf4SEd Tanous timer.expires_after(std::chrono::seconds(30)); 1980d5f5cf4SEd Tanous timer.async_wait(std::bind_front(onTimeout, weak_from_this())); 1990d5f5cf4SEd Tanous 2000d5f5cf4SEd Tanous boost::asio::async_connect( 2010d5f5cf4SEd Tanous conn, endpointList, 202e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterConnect, this, 203e38778a5SAppaRao Puli shared_from_this())); 204e38778a5SAppaRao Puli } 205e38778a5SAppaRao Puli 206e38778a5SAppaRao Puli void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/, 20781c4e330SEd Tanous const boost::beast::error_code& ec, 208e38778a5SAppaRao Puli const boost::asio::ip::tcp::endpoint& endpoint) 209e38778a5SAppaRao Puli { 210513d1ffcSCarson Labrado // The operation already timed out. We don't want do continue down 211513d1ffcSCarson Labrado // this branch 212513d1ffcSCarson Labrado if (ec && ec == boost::asio::error::operation_aborted) 213513d1ffcSCarson Labrado { 214513d1ffcSCarson Labrado return; 215513d1ffcSCarson Labrado } 216513d1ffcSCarson Labrado 2170d5f5cf4SEd Tanous timer.cancel(); 2182a5689a7SAppaRao Puli if (ec) 2192a5689a7SAppaRao Puli { 220002d39b4SEd Tanous BMCWEB_LOG_ERROR << "Connect " << endpoint.address().to_string() 221002d39b4SEd Tanous << ":" << std::to_string(endpoint.port()) 222e38778a5SAppaRao Puli << ", id: " << std::to_string(connId) 2232a5689a7SAppaRao Puli << " failed: " << ec.message(); 224e38778a5SAppaRao Puli state = ConnState::connectFailed; 225e38778a5SAppaRao Puli waitAndRetry(); 2262a5689a7SAppaRao Puli return; 2272a5689a7SAppaRao Puli } 228e38778a5SAppaRao Puli BMCWEB_LOG_DEBUG << "Connected to: " << endpoint.address().to_string() 229e38778a5SAppaRao Puli << ":" << std::to_string(endpoint.port()) 230e38778a5SAppaRao Puli << ", id: " << std::to_string(connId); 231e38778a5SAppaRao Puli if (sslConn) 232e38778a5SAppaRao Puli { 2330d5f5cf4SEd Tanous doSslHandshake(); 234e38778a5SAppaRao Puli return; 235e38778a5SAppaRao Puli } 236e38778a5SAppaRao Puli state = ConnState::connected; 237e38778a5SAppaRao Puli sendMessage(); 238e38778a5SAppaRao Puli } 239e38778a5SAppaRao Puli 2400d5f5cf4SEd Tanous void doSslHandshake() 241e38778a5SAppaRao Puli { 242e38778a5SAppaRao Puli if (!sslConn) 243e38778a5SAppaRao Puli { 244e38778a5SAppaRao Puli return; 245e38778a5SAppaRao Puli } 246e38778a5SAppaRao Puli state = ConnState::handshakeInProgress; 2470d5f5cf4SEd Tanous timer.expires_after(std::chrono::seconds(30)); 2480d5f5cf4SEd Tanous timer.async_wait(std::bind_front(onTimeout, weak_from_this())); 249e38778a5SAppaRao Puli sslConn->async_handshake( 250e38778a5SAppaRao Puli boost::asio::ssl::stream_base::client, 251e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterSslHandshake, this, 252e38778a5SAppaRao Puli shared_from_this())); 253e38778a5SAppaRao Puli } 254e38778a5SAppaRao Puli 255e38778a5SAppaRao Puli void afterSslHandshake(const std::shared_ptr<ConnectionInfo>& /*self*/, 25681c4e330SEd Tanous const boost::beast::error_code& ec) 257e38778a5SAppaRao Puli { 258513d1ffcSCarson Labrado // The operation already timed out. We don't want do continue down 259513d1ffcSCarson Labrado // this branch 260513d1ffcSCarson Labrado if (ec && ec == boost::asio::error::operation_aborted) 261513d1ffcSCarson Labrado { 262513d1ffcSCarson Labrado return; 263513d1ffcSCarson Labrado } 264513d1ffcSCarson Labrado 2650d5f5cf4SEd Tanous timer.cancel(); 266e38778a5SAppaRao Puli if (ec) 267e38778a5SAppaRao Puli { 268e38778a5SAppaRao Puli BMCWEB_LOG_ERROR << "SSL Handshake failed -" 269e38778a5SAppaRao Puli << " id: " << std::to_string(connId) 270e38778a5SAppaRao Puli << " error: " << ec.message(); 271e38778a5SAppaRao Puli state = ConnState::handshakeFailed; 272e38778a5SAppaRao Puli waitAndRetry(); 273e38778a5SAppaRao Puli return; 274e38778a5SAppaRao Puli } 275e38778a5SAppaRao Puli BMCWEB_LOG_DEBUG << "SSL Handshake successful -" 276e38778a5SAppaRao Puli << " id: " << std::to_string(connId); 277e38778a5SAppaRao Puli state = ConnState::connected; 278e38778a5SAppaRao Puli sendMessage(); 2792a5689a7SAppaRao Puli } 2802a5689a7SAppaRao Puli 281f52c03c1SCarson Labrado void sendMessage() 2822a5689a7SAppaRao Puli { 2832a5689a7SAppaRao Puli state = ConnState::sendInProgress; 2842a5689a7SAppaRao Puli 285bd030d0aSAppaRao Puli // Set a timeout on the operation 2860d5f5cf4SEd Tanous timer.expires_after(std::chrono::seconds(30)); 2870d5f5cf4SEd Tanous timer.async_wait(std::bind_front(onTimeout, weak_from_this())); 288bd030d0aSAppaRao Puli 289bd030d0aSAppaRao Puli // Send the HTTP request to the remote host 290e38778a5SAppaRao Puli if (sslConn) 291e38778a5SAppaRao Puli { 292e38778a5SAppaRao Puli boost::beast::http::async_write( 293e38778a5SAppaRao Puli *sslConn, req, 294e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterWrite, this, 295e38778a5SAppaRao Puli shared_from_this())); 296e38778a5SAppaRao Puli } 297e38778a5SAppaRao Puli else 298e38778a5SAppaRao Puli { 299bd030d0aSAppaRao Puli boost::beast::http::async_write( 300bd030d0aSAppaRao Puli conn, req, 301e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterWrite, this, 302e38778a5SAppaRao Puli shared_from_this())); 303e38778a5SAppaRao Puli } 304e38778a5SAppaRao Puli } 305e38778a5SAppaRao Puli 306e38778a5SAppaRao Puli void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/, 307e38778a5SAppaRao Puli const boost::beast::error_code& ec, size_t bytesTransferred) 308e38778a5SAppaRao Puli { 309513d1ffcSCarson Labrado // The operation already timed out. We don't want do continue down 310513d1ffcSCarson Labrado // this branch 311513d1ffcSCarson Labrado if (ec && ec == boost::asio::error::operation_aborted) 312513d1ffcSCarson Labrado { 313513d1ffcSCarson Labrado return; 314513d1ffcSCarson Labrado } 315513d1ffcSCarson Labrado 3160d5f5cf4SEd Tanous timer.cancel(); 317bd030d0aSAppaRao Puli if (ec) 318bd030d0aSAppaRao Puli { 319*d7043b3aSSunitha Harish BMCWEB_LOG_ERROR << "sendMessage() failed: " << ec.message() << " " 320*d7043b3aSSunitha Harish << host << ":" << std::to_string(port); 321e38778a5SAppaRao Puli state = ConnState::sendFailed; 322e38778a5SAppaRao Puli waitAndRetry(); 323bd030d0aSAppaRao Puli return; 324bd030d0aSAppaRao Puli } 325bd030d0aSAppaRao Puli BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: " 326bd030d0aSAppaRao Puli << bytesTransferred; 327bd030d0aSAppaRao Puli 328e38778a5SAppaRao Puli recvMessage(); 329bd030d0aSAppaRao Puli } 330bd030d0aSAppaRao Puli 331bd030d0aSAppaRao Puli void recvMessage() 332bd030d0aSAppaRao Puli { 3336eaa1d2fSSunitha Harish state = ConnState::recvInProgress; 3346eaa1d2fSSunitha Harish 3356eaa1d2fSSunitha Harish parser.emplace(std::piecewise_construct, std::make_tuple()); 336d14a48ffSCarson Labrado 337d14a48ffSCarson Labrado parser->body_limit(connPolicy->requestByteLimit); 3386eaa1d2fSSunitha Harish 3390d5f5cf4SEd Tanous timer.expires_after(std::chrono::seconds(30)); 3400d5f5cf4SEd Tanous timer.async_wait(std::bind_front(onTimeout, weak_from_this())); 3410d5f5cf4SEd Tanous 342bd030d0aSAppaRao Puli // Receive the HTTP response 343e38778a5SAppaRao Puli if (sslConn) 344e38778a5SAppaRao Puli { 345e38778a5SAppaRao Puli boost::beast::http::async_read( 346e38778a5SAppaRao Puli *sslConn, buffer, *parser, 347e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterRead, this, 348e38778a5SAppaRao Puli shared_from_this())); 349e38778a5SAppaRao Puli } 350e38778a5SAppaRao Puli else 351e38778a5SAppaRao Puli { 352bd030d0aSAppaRao Puli boost::beast::http::async_read( 3536eaa1d2fSSunitha Harish conn, buffer, *parser, 354e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterRead, this, 355e38778a5SAppaRao Puli shared_from_this())); 356e38778a5SAppaRao Puli } 357e38778a5SAppaRao Puli } 358e38778a5SAppaRao Puli 359e38778a5SAppaRao Puli void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/, 360e38778a5SAppaRao Puli const boost::beast::error_code& ec, 361e38778a5SAppaRao Puli const std::size_t& bytesTransferred) 362e38778a5SAppaRao Puli { 363513d1ffcSCarson Labrado // The operation already timed out. We don't want do continue down 364513d1ffcSCarson Labrado // this branch 365513d1ffcSCarson Labrado if (ec && ec == boost::asio::error::operation_aborted) 366513d1ffcSCarson Labrado { 367513d1ffcSCarson Labrado return; 368513d1ffcSCarson Labrado } 369513d1ffcSCarson Labrado 3700d5f5cf4SEd Tanous timer.cancel(); 371e38778a5SAppaRao Puli if (ec && ec != boost::asio::ssl::error::stream_truncated) 372bd030d0aSAppaRao Puli { 373*d7043b3aSSunitha Harish BMCWEB_LOG_ERROR << "recvMessage() failed: " << ec.message() 374*d7043b3aSSunitha Harish << " from " << host << ":" << std::to_string(port); 375e38778a5SAppaRao Puli state = ConnState::recvFailed; 376e38778a5SAppaRao Puli waitAndRetry(); 377bd030d0aSAppaRao Puli return; 378bd030d0aSAppaRao Puli } 379bd030d0aSAppaRao Puli BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: " 380bd030d0aSAppaRao Puli << bytesTransferred; 381e38778a5SAppaRao Puli BMCWEB_LOG_DEBUG << "recvMessage() data: " << parser->get().body(); 382bd030d0aSAppaRao Puli 383e38778a5SAppaRao Puli unsigned int respCode = parser->get().result_int(); 384e38778a5SAppaRao Puli BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: " << respCode; 3856eaa1d2fSSunitha Harish 386a7a80296SCarson Labrado // Make sure the received response code is valid as defined by 387a7a80296SCarson Labrado // the associated retry policy 388d14a48ffSCarson Labrado if (connPolicy->invalidResp(respCode)) 3896eaa1d2fSSunitha Harish { 3906eaa1d2fSSunitha Harish // The listener failed to receive the Sent-Event 391002d39b4SEd Tanous BMCWEB_LOG_ERROR << "recvMessage() Listener Failed to " 3927adb85acSSunitha Harish "receive Sent-Event. Header Response Code: " 393*d7043b3aSSunitha Harish << respCode << " from " << host << ":" 394*d7043b3aSSunitha Harish << std::to_string(port); 395e38778a5SAppaRao Puli state = ConnState::recvFailed; 396e38778a5SAppaRao Puli waitAndRetry(); 3976eaa1d2fSSunitha Harish return; 3986eaa1d2fSSunitha Harish } 399bd030d0aSAppaRao Puli 400f52c03c1SCarson Labrado // Send is successful 401f52c03c1SCarson Labrado // Reset the counter just in case this was after retrying 402e38778a5SAppaRao Puli retryCount = 0; 4036eaa1d2fSSunitha Harish 4046eaa1d2fSSunitha Harish // Keep the connection alive if server supports it 4056eaa1d2fSSunitha Harish // Else close the connection 4066eaa1d2fSSunitha Harish BMCWEB_LOG_DEBUG << "recvMessage() keepalive : " 407e38778a5SAppaRao Puli << parser->keep_alive(); 4086eaa1d2fSSunitha Harish 409039a47e3SCarson Labrado // Copy the response into a Response object so that it can be 410039a47e3SCarson Labrado // processed by the callback function. 411e38778a5SAppaRao Puli res.stringResponse = parser->release(); 412e38778a5SAppaRao Puli callback(parser->keep_alive(), connId, res); 413513d1ffcSCarson Labrado res.clear(); 414bd030d0aSAppaRao Puli } 415bd030d0aSAppaRao Puli 4160d5f5cf4SEd Tanous static void onTimeout(const std::weak_ptr<ConnectionInfo>& weakSelf, 4175e7e2dc5SEd Tanous const boost::system::error_code& ec) 4180d5f5cf4SEd Tanous { 4190d5f5cf4SEd Tanous if (ec == boost::asio::error::operation_aborted) 4200d5f5cf4SEd Tanous { 4210d5f5cf4SEd Tanous BMCWEB_LOG_DEBUG 422513d1ffcSCarson Labrado << "async_wait failed since the operation is aborted"; 4230d5f5cf4SEd Tanous return; 4240d5f5cf4SEd Tanous } 4250d5f5cf4SEd Tanous if (ec) 4260d5f5cf4SEd Tanous { 4270d5f5cf4SEd Tanous BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message(); 4280d5f5cf4SEd Tanous // If the timer fails, we need to close the socket anyway, same as 4290d5f5cf4SEd Tanous // if it expired. 4300d5f5cf4SEd Tanous } 4310d5f5cf4SEd Tanous std::shared_ptr<ConnectionInfo> self = weakSelf.lock(); 4320d5f5cf4SEd Tanous if (self == nullptr) 4330d5f5cf4SEd Tanous { 4340d5f5cf4SEd Tanous return; 4350d5f5cf4SEd Tanous } 4360d5f5cf4SEd Tanous self->waitAndRetry(); 4370d5f5cf4SEd Tanous } 4380d5f5cf4SEd Tanous 4396eaa1d2fSSunitha Harish void waitAndRetry() 440bd030d0aSAppaRao Puli { 441d14a48ffSCarson Labrado if ((retryCount >= connPolicy->maxRetryAttempts) || 442e38778a5SAppaRao Puli (state == ConnState::sslInitFailed)) 4432a5689a7SAppaRao Puli { 444*d7043b3aSSunitha Harish BMCWEB_LOG_ERROR << "Maximum number of retries reached." 445*d7043b3aSSunitha Harish << " " << host << ":" << std::to_string(port); 446f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Retry policy: " 447d14a48ffSCarson Labrado << connPolicy->retryPolicyAction; 448039a47e3SCarson Labrado 449d14a48ffSCarson Labrado if (connPolicy->retryPolicyAction == "TerminateAfterRetries") 450fe44eb0bSAyushi Smriti { 451fe44eb0bSAyushi Smriti // TODO: delete subscription 452fe44eb0bSAyushi Smriti state = ConnState::terminated; 453fe44eb0bSAyushi Smriti } 454d14a48ffSCarson Labrado if (connPolicy->retryPolicyAction == "SuspendRetries") 455fe44eb0bSAyushi Smriti { 4562a5689a7SAppaRao Puli state = ConnState::suspended; 4572a5689a7SAppaRao Puli } 458513d1ffcSCarson Labrado 459513d1ffcSCarson Labrado // We want to return a 502 to indicate there was an error with 460513d1ffcSCarson Labrado // the external server 461513d1ffcSCarson Labrado res.result(boost::beast::http::status::bad_gateway); 462513d1ffcSCarson Labrado callback(false, connId, res); 463513d1ffcSCarson Labrado res.clear(); 464513d1ffcSCarson Labrado 4656eaa1d2fSSunitha Harish // Reset the retrycount to zero so that client can try connecting 4666eaa1d2fSSunitha Harish // again if needed 467fe44eb0bSAyushi Smriti retryCount = 0; 4682a5689a7SAppaRao Puli return; 4692a5689a7SAppaRao Puli } 4702a5689a7SAppaRao Puli 4712a5689a7SAppaRao Puli retryCount++; 472fe44eb0bSAyushi Smriti 473f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Attempt retry after " 474f52c03c1SCarson Labrado << std::to_string( 475d14a48ffSCarson Labrado connPolicy->retryIntervalSecs.count()) 476fe44eb0bSAyushi Smriti << " seconds. RetryCount = " << retryCount; 477d14a48ffSCarson Labrado timer.expires_after(connPolicy->retryIntervalSecs); 4783d36e3a5SEd Tanous timer.async_wait(std::bind_front(&ConnectionInfo::onTimerDone, this, 4793d36e3a5SEd Tanous shared_from_this())); 4803d36e3a5SEd Tanous } 4813d36e3a5SEd Tanous 4823d36e3a5SEd Tanous void onTimerDone(const std::shared_ptr<ConnectionInfo>& /*self*/, 4833d36e3a5SEd Tanous const boost::system::error_code& ec) 4843d36e3a5SEd Tanous { 4856eaa1d2fSSunitha Harish if (ec == boost::asio::error::operation_aborted) 4866eaa1d2fSSunitha Harish { 4876eaa1d2fSSunitha Harish BMCWEB_LOG_DEBUG 4886eaa1d2fSSunitha Harish << "async_wait failed since the operation is aborted" 4896eaa1d2fSSunitha Harish << ec.message(); 4906eaa1d2fSSunitha Harish } 4916eaa1d2fSSunitha Harish else if (ec) 4926eaa1d2fSSunitha Harish { 4936eaa1d2fSSunitha Harish BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message(); 4946eaa1d2fSSunitha Harish // Ignore the error and continue the retry loop to attempt 4956eaa1d2fSSunitha Harish // sending the event as per the retry policy 4966eaa1d2fSSunitha Harish } 4976eaa1d2fSSunitha Harish 498f52c03c1SCarson Labrado // Let's close the connection and restart from resolve. 4993d36e3a5SEd Tanous doClose(true); 5002a5689a7SAppaRao Puli } 5012a5689a7SAppaRao Puli 502e38778a5SAppaRao Puli void shutdownConn(bool retry) 503fe44eb0bSAyushi Smriti { 504f52c03c1SCarson Labrado boost::beast::error_code ec; 5050d5f5cf4SEd Tanous conn.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); 506f52c03c1SCarson Labrado conn.close(); 507f52c03c1SCarson Labrado 508f52c03c1SCarson Labrado // not_connected happens sometimes so don't bother reporting it. 509f52c03c1SCarson Labrado if (ec && ec != boost::beast::errc::not_connected) 5102a5689a7SAppaRao Puli { 511f52c03c1SCarson Labrado BMCWEB_LOG_ERROR << host << ":" << std::to_string(port) 512f52c03c1SCarson Labrado << ", id: " << std::to_string(connId) 513f52c03c1SCarson Labrado << " shutdown failed: " << ec.message(); 5146eaa1d2fSSunitha Harish } 5155cab68f3SCarson Labrado else 5165cab68f3SCarson Labrado { 517f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port) 518f52c03c1SCarson Labrado << ", id: " << std::to_string(connId) 519f52c03c1SCarson Labrado << " closed gracefully"; 5205cab68f3SCarson Labrado } 521ca723762SEd Tanous 522e38778a5SAppaRao Puli if (retry) 52392a74e56SAppaRao Puli { 524f52c03c1SCarson Labrado // Now let's try to resend the data 525f52c03c1SCarson Labrado state = ConnState::retry; 5260d5f5cf4SEd Tanous doResolve(); 527e38778a5SAppaRao Puli } 528e38778a5SAppaRao Puli else 529e38778a5SAppaRao Puli { 530e38778a5SAppaRao Puli state = ConnState::closed; 531e38778a5SAppaRao Puli } 532e38778a5SAppaRao Puli } 533e38778a5SAppaRao Puli 534e38778a5SAppaRao Puli void doClose(bool retry = false) 535e38778a5SAppaRao Puli { 536e38778a5SAppaRao Puli if (!sslConn) 537e38778a5SAppaRao Puli { 538e38778a5SAppaRao Puli shutdownConn(retry); 539e38778a5SAppaRao Puli return; 540e38778a5SAppaRao Puli } 541e38778a5SAppaRao Puli 542e38778a5SAppaRao Puli sslConn->async_shutdown( 543e38778a5SAppaRao Puli std::bind_front(&ConnectionInfo::afterSslShutdown, this, 544e38778a5SAppaRao Puli shared_from_this(), retry)); 545e38778a5SAppaRao Puli } 546e38778a5SAppaRao Puli 547e38778a5SAppaRao Puli void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/, 548e38778a5SAppaRao Puli bool retry, const boost::system::error_code& ec) 549e38778a5SAppaRao Puli { 550e38778a5SAppaRao Puli if (ec) 551e38778a5SAppaRao Puli { 552e38778a5SAppaRao Puli BMCWEB_LOG_ERROR << host << ":" << std::to_string(port) 553e38778a5SAppaRao Puli << ", id: " << std::to_string(connId) 554e38778a5SAppaRao Puli << " shutdown failed: " << ec.message(); 555e38778a5SAppaRao Puli } 556e38778a5SAppaRao Puli else 557e38778a5SAppaRao Puli { 558e38778a5SAppaRao Puli BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port) 559e38778a5SAppaRao Puli << ", id: " << std::to_string(connId) 560e38778a5SAppaRao Puli << " closed gracefully"; 561e38778a5SAppaRao Puli } 562e38778a5SAppaRao Puli shutdownConn(retry); 563e38778a5SAppaRao Puli } 564e38778a5SAppaRao Puli 565e38778a5SAppaRao Puli void setCipherSuiteTLSext() 566e38778a5SAppaRao Puli { 567e38778a5SAppaRao Puli if (!sslConn) 568e38778a5SAppaRao Puli { 569e38778a5SAppaRao Puli return; 570e38778a5SAppaRao Puli } 571e38778a5SAppaRao Puli // NOTE: The SSL_set_tlsext_host_name is defined in tlsv1.h header 572e38778a5SAppaRao Puli // file but its having old style casting (name is cast to void*). 573e38778a5SAppaRao Puli // Since bmcweb compiler treats all old-style-cast as error, its 574e38778a5SAppaRao Puli // causing the build failure. So replaced the same macro inline and 575e38778a5SAppaRao Puli // did corrected the code by doing static_cast to viod*. This has to 576e38778a5SAppaRao Puli // be fixed in openssl library in long run. Set SNI Hostname (many 577e38778a5SAppaRao Puli // hosts need this to handshake successfully) 578e38778a5SAppaRao Puli if (SSL_ctrl(sslConn->native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME, 579e38778a5SAppaRao Puli TLSEXT_NAMETYPE_host_name, 580e38778a5SAppaRao Puli static_cast<void*>(&host.front())) == 0) 581e38778a5SAppaRao Puli 582e38778a5SAppaRao Puli { 583e38778a5SAppaRao Puli boost::beast::error_code ec{static_cast<int>(::ERR_get_error()), 584e38778a5SAppaRao Puli boost::asio::error::get_ssl_category()}; 585e38778a5SAppaRao Puli 586e38778a5SAppaRao Puli BMCWEB_LOG_ERROR << "SSL_set_tlsext_host_name " << host << ":" 587e38778a5SAppaRao Puli << port << ", id: " << std::to_string(connId) 588e38778a5SAppaRao Puli << " failed: " << ec.message(); 589e38778a5SAppaRao Puli // Set state as sslInit failed so that we close the connection 590e38778a5SAppaRao Puli // and take appropriate action as per retry configuration. 591e38778a5SAppaRao Puli state = ConnState::sslInitFailed; 592e38778a5SAppaRao Puli waitAndRetry(); 593e38778a5SAppaRao Puli return; 594e38778a5SAppaRao Puli } 595bd030d0aSAppaRao Puli } 596bd030d0aSAppaRao Puli 597bd030d0aSAppaRao Puli public: 598d14a48ffSCarson Labrado explicit ConnectionInfo( 599d14a48ffSCarson Labrado boost::asio::io_context& iocIn, const std::string& idIn, 600d14a48ffSCarson Labrado const std::shared_ptr<ConnectionPolicy>& connPolicyIn, 601d14a48ffSCarson Labrado const std::string& destIPIn, uint16_t destPortIn, bool useSSL, 602d14a48ffSCarson Labrado unsigned int connIdIn) : 6038a592810SEd Tanous subId(idIn), 604d14a48ffSCarson Labrado connPolicy(connPolicyIn), host(destIPIn), port(destPortIn), 605f8ca6d79SEd Tanous connId(connIdIn), resolver(iocIn), conn(iocIn), timer(iocIn) 606e38778a5SAppaRao Puli { 607e38778a5SAppaRao Puli if (useSSL) 608e38778a5SAppaRao Puli { 609e38778a5SAppaRao Puli std::optional<boost::asio::ssl::context> sslCtx = 610e38778a5SAppaRao Puli ensuressl::getSSLClientContext(); 611e38778a5SAppaRao Puli 612e38778a5SAppaRao Puli if (!sslCtx) 613e38778a5SAppaRao Puli { 614e38778a5SAppaRao Puli BMCWEB_LOG_ERROR << "prepareSSLContext failed - " << host << ":" 615e38778a5SAppaRao Puli << port << ", id: " << std::to_string(connId); 616e38778a5SAppaRao Puli // Don't retry if failure occurs while preparing SSL context 617e38778a5SAppaRao Puli // such as certificate is invalid or set cipher failure or set 618e38778a5SAppaRao Puli // host name failure etc... Setting conn state to sslInitFailed 619e38778a5SAppaRao Puli // and connection state will be transitioned to next state 620e38778a5SAppaRao Puli // depending on retry policy set by subscription. 621e38778a5SAppaRao Puli state = ConnState::sslInitFailed; 622e38778a5SAppaRao Puli waitAndRetry(); 623e38778a5SAppaRao Puli return; 624e38778a5SAppaRao Puli } 625e38778a5SAppaRao Puli sslConn.emplace(conn, *sslCtx); 626e38778a5SAppaRao Puli setCipherSuiteTLSext(); 627e38778a5SAppaRao Puli } 628e38778a5SAppaRao Puli } 629f52c03c1SCarson Labrado }; 630bd030d0aSAppaRao Puli 631f52c03c1SCarson Labrado class ConnectionPool : public std::enable_shared_from_this<ConnectionPool> 632bd030d0aSAppaRao Puli { 633f52c03c1SCarson Labrado private: 634f52c03c1SCarson Labrado boost::asio::io_context& ioc; 635e38778a5SAppaRao Puli std::string id; 636d14a48ffSCarson Labrado std::shared_ptr<ConnectionPolicy> connPolicy; 637e38778a5SAppaRao Puli std::string destIP; 638e38778a5SAppaRao Puli uint16_t destPort; 639e38778a5SAppaRao Puli bool useSSL; 640f52c03c1SCarson Labrado std::vector<std::shared_ptr<ConnectionInfo>> connections; 641f52c03c1SCarson Labrado boost::container::devector<PendingRequest> requestQueue; 642f52c03c1SCarson Labrado 643f52c03c1SCarson Labrado friend class HttpClient; 644f52c03c1SCarson Labrado 645244256ccSCarson Labrado // Configure a connections's request, callback, and retry info in 646244256ccSCarson Labrado // preparation to begin sending the request 647f52c03c1SCarson Labrado void setConnProps(ConnectionInfo& conn) 648bd030d0aSAppaRao Puli { 649f52c03c1SCarson Labrado if (requestQueue.empty()) 650f52c03c1SCarson Labrado { 651f52c03c1SCarson Labrado BMCWEB_LOG_ERROR 652f52c03c1SCarson Labrado << "setConnProps() should not have been called when requestQueue is empty"; 653bd030d0aSAppaRao Puli return; 654bd030d0aSAppaRao Puli } 655bd030d0aSAppaRao Puli 656244256ccSCarson Labrado auto nextReq = requestQueue.front(); 657244256ccSCarson Labrado conn.req = std::move(nextReq.req); 658244256ccSCarson Labrado conn.callback = std::move(nextReq.callback); 659f52c03c1SCarson Labrado 660f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Setting properties for connection " << conn.host 661f52c03c1SCarson Labrado << ":" << std::to_string(conn.port) 662a7a80296SCarson Labrado << ", id: " << std::to_string(conn.connId); 663f52c03c1SCarson Labrado 664f52c03c1SCarson Labrado // We can remove the request from the queue at this point 665f52c03c1SCarson Labrado requestQueue.pop_front(); 666f52c03c1SCarson Labrado } 667f52c03c1SCarson Labrado 668f52c03c1SCarson Labrado // Gets called as part of callback after request is sent 669f52c03c1SCarson Labrado // Reuses the connection if there are any requests waiting to be sent 670f52c03c1SCarson Labrado // Otherwise closes the connection if it is not a keep-alive 671f52c03c1SCarson Labrado void sendNext(bool keepAlive, uint32_t connId) 672f52c03c1SCarson Labrado { 673f52c03c1SCarson Labrado auto conn = connections[connId]; 67446a81465SCarson Labrado 67546a81465SCarson Labrado // Allow the connection's handler to be deleted 67646a81465SCarson Labrado // This is needed because of Redfish Aggregation passing an 67746a81465SCarson Labrado // AsyncResponse shared_ptr to this callback 67846a81465SCarson Labrado conn->callback = nullptr; 67946a81465SCarson Labrado 680f52c03c1SCarson Labrado // Reuse the connection to send the next request in the queue 681f52c03c1SCarson Labrado if (!requestQueue.empty()) 682f52c03c1SCarson Labrado { 683f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << std::to_string(requestQueue.size()) 684f52c03c1SCarson Labrado << " requests remaining in queue for " << destIP 685f52c03c1SCarson Labrado << ":" << std::to_string(destPort) 686f52c03c1SCarson Labrado << ", reusing connnection " 687f52c03c1SCarson Labrado << std::to_string(connId); 688f52c03c1SCarson Labrado 689f52c03c1SCarson Labrado setConnProps(*conn); 690f52c03c1SCarson Labrado 691f52c03c1SCarson Labrado if (keepAlive) 692f52c03c1SCarson Labrado { 693f52c03c1SCarson Labrado conn->sendMessage(); 6942a5689a7SAppaRao Puli } 6952a5689a7SAppaRao Puli else 6962a5689a7SAppaRao Puli { 697f52c03c1SCarson Labrado // Server is not keep-alive enabled so we need to close the 698f52c03c1SCarson Labrado // connection and then start over from resolve 699f52c03c1SCarson Labrado conn->doClose(); 700f52c03c1SCarson Labrado conn->doResolve(); 701f52c03c1SCarson Labrado } 702f52c03c1SCarson Labrado return; 703f52c03c1SCarson Labrado } 704f52c03c1SCarson Labrado 705f52c03c1SCarson Labrado // No more messages to send so close the connection if necessary 706f52c03c1SCarson Labrado if (keepAlive) 707f52c03c1SCarson Labrado { 708f52c03c1SCarson Labrado conn->state = ConnState::idle; 709f52c03c1SCarson Labrado } 710f52c03c1SCarson Labrado else 711f52c03c1SCarson Labrado { 712f52c03c1SCarson Labrado // Abort the connection since server is not keep-alive enabled 713f52c03c1SCarson Labrado conn->state = ConnState::abortConnection; 714f52c03c1SCarson Labrado conn->doClose(); 7152a5689a7SAppaRao Puli } 716bd030d0aSAppaRao Puli } 717bd030d0aSAppaRao Puli 7185e44e3d8SAppaRao Puli void sendData(std::string&& data, const std::string& destUri, 719244256ccSCarson Labrado const boost::beast::http::fields& httpHeader, 720244256ccSCarson Labrado const boost::beast::http::verb verb, 7216b3db60dSEd Tanous const std::function<void(Response&)>& resHandler) 722fe44eb0bSAyushi Smriti { 723244256ccSCarson Labrado // Construct the request to be sent 724244256ccSCarson Labrado boost::beast::http::request<boost::beast::http::string_body> thisReq( 725244256ccSCarson Labrado verb, destUri, 11, "", httpHeader); 726244256ccSCarson Labrado thisReq.set(boost::beast::http::field::host, destIP); 727244256ccSCarson Labrado thisReq.keep_alive(true); 728244256ccSCarson Labrado thisReq.body() = std::move(data); 729244256ccSCarson Labrado thisReq.prepare_payload(); 7303d36e3a5SEd Tanous auto cb = std::bind_front(&ConnectionPool::afterSendData, 7313d36e3a5SEd Tanous weak_from_this(), resHandler); 732f52c03c1SCarson Labrado // Reuse an existing connection if one is available 733f52c03c1SCarson Labrado for (unsigned int i = 0; i < connections.size(); i++) 734fe44eb0bSAyushi Smriti { 735f52c03c1SCarson Labrado auto conn = connections[i]; 736f52c03c1SCarson Labrado if ((conn->state == ConnState::idle) || 737f52c03c1SCarson Labrado (conn->state == ConnState::initialized) || 738f52c03c1SCarson Labrado (conn->state == ConnState::closed)) 739f52c03c1SCarson Labrado { 740244256ccSCarson Labrado conn->req = std::move(thisReq); 741f52c03c1SCarson Labrado conn->callback = std::move(cb); 742f52c03c1SCarson Labrado std::string commonMsg = std::to_string(i) + " from pool " + 743f52c03c1SCarson Labrado destIP + ":" + std::to_string(destPort); 744f52c03c1SCarson Labrado 745f52c03c1SCarson Labrado if (conn->state == ConnState::idle) 746f52c03c1SCarson Labrado { 747f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Grabbing idle connection " 748f52c03c1SCarson Labrado << commonMsg; 749f52c03c1SCarson Labrado conn->sendMessage(); 750f52c03c1SCarson Labrado } 751f52c03c1SCarson Labrado else 752f52c03c1SCarson Labrado { 753f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Reusing existing connection " 754f52c03c1SCarson Labrado << commonMsg; 755f52c03c1SCarson Labrado conn->doResolve(); 756f52c03c1SCarson Labrado } 757f52c03c1SCarson Labrado return; 758f52c03c1SCarson Labrado } 759f52c03c1SCarson Labrado } 760f52c03c1SCarson Labrado 761f52c03c1SCarson Labrado // All connections in use so create a new connection or add request to 762f52c03c1SCarson Labrado // the queue 763d14a48ffSCarson Labrado if (connections.size() < connPolicy->maxConnections) 764f52c03c1SCarson Labrado { 765f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Adding new connection to pool " << destIP 766f52c03c1SCarson Labrado << ":" << std::to_string(destPort); 767f52c03c1SCarson Labrado auto conn = addConnection(); 768244256ccSCarson Labrado conn->req = std::move(thisReq); 769f52c03c1SCarson Labrado conn->callback = std::move(cb); 770f52c03c1SCarson Labrado conn->doResolve(); 771f52c03c1SCarson Labrado } 772f52c03c1SCarson Labrado else if (requestQueue.size() < maxRequestQueueSize) 773f52c03c1SCarson Labrado { 774*d7043b3aSSunitha Harish BMCWEB_LOG_ERROR << "Max pool size reached. Adding data to queue." 775*d7043b3aSSunitha Harish << destIP << ":" << std::to_string(destPort); 776d14a48ffSCarson Labrado requestQueue.emplace_back(std::move(thisReq), std::move(cb)); 777f52c03c1SCarson Labrado } 778f52c03c1SCarson Labrado else 779f52c03c1SCarson Labrado { 78043e14d38SCarson Labrado // If we can't buffer the request then we should let the callback 78143e14d38SCarson Labrado // handle a 429 Too Many Requests dummy response 782f52c03c1SCarson Labrado BMCWEB_LOG_ERROR << destIP << ":" << std::to_string(destPort) 783f52c03c1SCarson Labrado << " request queue full. Dropping request."; 78443e14d38SCarson Labrado Response dummyRes; 78543e14d38SCarson Labrado dummyRes.result(boost::beast::http::status::too_many_requests); 78643e14d38SCarson Labrado resHandler(dummyRes); 787f52c03c1SCarson Labrado } 788f52c03c1SCarson Labrado } 789f52c03c1SCarson Labrado 7903d36e3a5SEd Tanous // Callback to be called once the request has been sent 7913d36e3a5SEd Tanous static void afterSendData(const std::weak_ptr<ConnectionPool>& weakSelf, 7923d36e3a5SEd Tanous const std::function<void(Response&)>& resHandler, 7933d36e3a5SEd Tanous bool keepAlive, uint32_t connId, Response& res) 7943d36e3a5SEd Tanous { 7953d36e3a5SEd Tanous // Allow provided callback to perform additional processing of the 7963d36e3a5SEd Tanous // request 7973d36e3a5SEd Tanous resHandler(res); 7983d36e3a5SEd Tanous 7993d36e3a5SEd Tanous // If requests remain in the queue then we want to reuse this 8003d36e3a5SEd Tanous // connection to send the next request 8013d36e3a5SEd Tanous std::shared_ptr<ConnectionPool> self = weakSelf.lock(); 8023d36e3a5SEd Tanous if (!self) 8033d36e3a5SEd Tanous { 8043d36e3a5SEd Tanous BMCWEB_LOG_CRITICAL << self << " Failed to capture connection"; 8053d36e3a5SEd Tanous return; 8063d36e3a5SEd Tanous } 8073d36e3a5SEd Tanous 8083d36e3a5SEd Tanous self->sendNext(keepAlive, connId); 8093d36e3a5SEd Tanous } 8103d36e3a5SEd Tanous 811f52c03c1SCarson Labrado std::shared_ptr<ConnectionInfo>& addConnection() 812f52c03c1SCarson Labrado { 813f52c03c1SCarson Labrado unsigned int newId = static_cast<unsigned int>(connections.size()); 814f52c03c1SCarson Labrado 815e38778a5SAppaRao Puli auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>( 816d14a48ffSCarson Labrado ioc, id, connPolicy, destIP, destPort, useSSL, newId)); 817f52c03c1SCarson Labrado 818f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Added connection " 819f52c03c1SCarson Labrado << std::to_string(connections.size() - 1) 820f52c03c1SCarson Labrado << " to pool " << destIP << ":" 821f52c03c1SCarson Labrado << std::to_string(destPort); 822f52c03c1SCarson Labrado 823f52c03c1SCarson Labrado return ret; 824f52c03c1SCarson Labrado } 825f52c03c1SCarson Labrado 826f52c03c1SCarson Labrado public: 827d14a48ffSCarson Labrado explicit ConnectionPool( 828d14a48ffSCarson Labrado boost::asio::io_context& iocIn, const std::string& idIn, 829d14a48ffSCarson Labrado const std::shared_ptr<ConnectionPolicy>& connPolicyIn, 830d14a48ffSCarson Labrado const std::string& destIPIn, uint16_t destPortIn, bool useSSLIn) : 8318a592810SEd Tanous ioc(iocIn), 832d14a48ffSCarson Labrado id(idIn), connPolicy(connPolicyIn), destIP(destIPIn), 833d14a48ffSCarson Labrado destPort(destPortIn), useSSL(useSSLIn) 834f52c03c1SCarson Labrado { 835f52c03c1SCarson Labrado BMCWEB_LOG_DEBUG << "Initializing connection pool for " << destIP << ":" 836f52c03c1SCarson Labrado << std::to_string(destPort); 837f52c03c1SCarson Labrado 838f52c03c1SCarson Labrado // Initialize the pool with a single connection 839f52c03c1SCarson Labrado addConnection(); 840fe44eb0bSAyushi Smriti } 841bd030d0aSAppaRao Puli }; 842bd030d0aSAppaRao Puli 843f52c03c1SCarson Labrado class HttpClient 844f52c03c1SCarson Labrado { 845f52c03c1SCarson Labrado private: 846f52c03c1SCarson Labrado std::unordered_map<std::string, std::shared_ptr<ConnectionPool>> 847f52c03c1SCarson Labrado connectionPools; 848f8ca6d79SEd Tanous boost::asio::io_context& ioc; 849d14a48ffSCarson Labrado std::shared_ptr<ConnectionPolicy> connPolicy; 850f52c03c1SCarson Labrado 851039a47e3SCarson Labrado // Used as a dummy callback by sendData() in order to call 852039a47e3SCarson Labrado // sendDataWithCallback() 85302cad96eSEd Tanous static void genericResHandler(const Response& res) 854039a47e3SCarson Labrado { 855039a47e3SCarson Labrado BMCWEB_LOG_DEBUG << "Response handled with return code: " 856039a47e3SCarson Labrado << std::to_string(res.resultInt()); 8574ee8e211SEd Tanous } 858039a47e3SCarson Labrado 859f52c03c1SCarson Labrado public: 860d14a48ffSCarson Labrado HttpClient() = delete; 861f8ca6d79SEd Tanous explicit HttpClient(boost::asio::io_context& iocIn, 862f8ca6d79SEd Tanous const std::shared_ptr<ConnectionPolicy>& connPolicyIn) : 863f8ca6d79SEd Tanous ioc(iocIn), 864d14a48ffSCarson Labrado connPolicy(connPolicyIn) 865d14a48ffSCarson Labrado {} 866f8ca6d79SEd Tanous 867f52c03c1SCarson Labrado HttpClient(const HttpClient&) = delete; 868f52c03c1SCarson Labrado HttpClient& operator=(const HttpClient&) = delete; 869f52c03c1SCarson Labrado HttpClient(HttpClient&&) = delete; 870f52c03c1SCarson Labrado HttpClient& operator=(HttpClient&&) = delete; 871f52c03c1SCarson Labrado ~HttpClient() = default; 872f52c03c1SCarson Labrado 873039a47e3SCarson Labrado // Send a request to destIP:destPort where additional processing of the 874039a47e3SCarson Labrado // result is not required 8755e44e3d8SAppaRao Puli void sendData(std::string&& data, const std::string& destIP, 876d14a48ffSCarson Labrado uint16_t destPort, const std::string& destUri, bool useSSL, 877f52c03c1SCarson Labrado const boost::beast::http::fields& httpHeader, 878d14a48ffSCarson Labrado const boost::beast::http::verb verb) 879f52c03c1SCarson Labrado { 880e38778a5SAppaRao Puli const std::function<void(Response&)> cb = genericResHandler; 8815e44e3d8SAppaRao Puli sendDataWithCallback(std::move(data), destIP, destPort, destUri, useSSL, 882d14a48ffSCarson Labrado httpHeader, verb, cb); 883039a47e3SCarson Labrado } 884039a47e3SCarson Labrado 885039a47e3SCarson Labrado // Send request to destIP:destPort and use the provided callback to 886039a47e3SCarson Labrado // handle the response 8875e44e3d8SAppaRao Puli void sendDataWithCallback(std::string&& data, const std::string& destIP, 888d14a48ffSCarson Labrado uint16_t destPort, const std::string& destUri, 889d14a48ffSCarson Labrado bool useSSL, 890039a47e3SCarson Labrado const boost::beast::http::fields& httpHeader, 891244256ccSCarson Labrado const boost::beast::http::verb verb, 8926b3db60dSEd Tanous const std::function<void(Response&)>& resHandler) 893039a47e3SCarson Labrado { 894e38778a5SAppaRao Puli std::string clientKey = useSSL ? "https" : "http"; 895e38778a5SAppaRao Puli clientKey += destIP; 896e38778a5SAppaRao Puli clientKey += ":"; 897e38778a5SAppaRao Puli clientKey += std::to_string(destPort); 898d14a48ffSCarson Labrado auto pool = connectionPools.try_emplace(clientKey); 899d14a48ffSCarson Labrado if (pool.first->second == nullptr) 900f52c03c1SCarson Labrado { 901d14a48ffSCarson Labrado pool.first->second = std::make_shared<ConnectionPool>( 902d14a48ffSCarson Labrado ioc, clientKey, connPolicy, destIP, destPort, useSSL); 903f52c03c1SCarson Labrado } 904f52c03c1SCarson Labrado // Send the data using either the existing connection pool or the newly 905f52c03c1SCarson Labrado // created connection pool 9065e44e3d8SAppaRao Puli pool.first->second->sendData(std::move(data), destUri, httpHeader, verb, 907e38778a5SAppaRao Puli resHandler); 908f52c03c1SCarson Labrado } 909f52c03c1SCarson Labrado }; 910bd030d0aSAppaRao Puli } // namespace crow 911