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