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