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