xref: /openbmc/bmcweb/http/http_client.hpp (revision 3ccb3adb)
1bd030d0aSAppaRao Puli /*
2bd030d0aSAppaRao Puli // Copyright (c) 2020 Intel Corporation
3bd030d0aSAppaRao Puli //
4bd030d0aSAppaRao Puli // Licensed under the Apache License, Version 2.0 (the "License");
5bd030d0aSAppaRao Puli // you may not use this file except in compliance with the License.
6bd030d0aSAppaRao Puli // You may obtain a copy of the License at
7bd030d0aSAppaRao Puli //
8bd030d0aSAppaRao Puli //      http://www.apache.org/licenses/LICENSE-2.0
9bd030d0aSAppaRao Puli //
10bd030d0aSAppaRao Puli // Unless required by applicable law or agreed to in writing, software
11bd030d0aSAppaRao Puli // distributed under the License is distributed on an "AS IS" BASIS,
12bd030d0aSAppaRao Puli // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13bd030d0aSAppaRao Puli // See the License for the specific language governing permissions and
14bd030d0aSAppaRao Puli // limitations under the License.
15bd030d0aSAppaRao Puli */
16bd030d0aSAppaRao Puli #pragma once
1777665bdaSNan Zhou 
1877665bdaSNan Zhou #include "async_resolve.hpp"
1977665bdaSNan Zhou #include "http_response.hpp"
20*3ccb3adbSEd Tanous #include "logging.hpp"
21*3ccb3adbSEd Tanous #include "ssl_key_handler.hpp"
2277665bdaSNan Zhou 
230d5f5cf4SEd Tanous #include <boost/asio/connect.hpp>
24bb49eb5cSEd Tanous #include <boost/asio/io_context.hpp>
2529a82b08SSunitha Harish #include <boost/asio/ip/address.hpp>
2629a82b08SSunitha Harish #include <boost/asio/ip/basic_endpoint.hpp>
27bb49eb5cSEd Tanous #include <boost/asio/ip/tcp.hpp>
28e38778a5SAppaRao Puli #include <boost/asio/ssl/context.hpp>
29e38778a5SAppaRao Puli #include <boost/asio/ssl/error.hpp>
30d43cd0caSEd Tanous #include <boost/asio/steady_timer.hpp>
31d43cd0caSEd Tanous #include <boost/beast/core/flat_buffer.hpp>
32bb49eb5cSEd Tanous #include <boost/beast/core/flat_static_buffer.hpp>
33d43cd0caSEd Tanous #include <boost/beast/http/message.hpp>
34bb49eb5cSEd Tanous #include <boost/beast/http/parser.hpp>
35bb49eb5cSEd Tanous #include <boost/beast/http/read.hpp>
36bb49eb5cSEd Tanous #include <boost/beast/http/string_body.hpp>
37bb49eb5cSEd Tanous #include <boost/beast/http/write.hpp>
38e38778a5SAppaRao Puli #include <boost/beast/ssl/ssl_stream.hpp>
39bd030d0aSAppaRao Puli #include <boost/beast/version.hpp>
40f52c03c1SCarson Labrado #include <boost/container/devector.hpp>
41bb49eb5cSEd Tanous #include <boost/system/error_code.hpp>
421214b7e7SGunnar Mills 
43bd030d0aSAppaRao Puli #include <cstdlib>
44bd030d0aSAppaRao Puli #include <functional>
45bd030d0aSAppaRao Puli #include <iostream>
46bd030d0aSAppaRao Puli #include <memory>
472a5689a7SAppaRao Puli #include <queue>
48bd030d0aSAppaRao Puli #include <string>
49bd030d0aSAppaRao Puli 
50bd030d0aSAppaRao Puli namespace crow
51bd030d0aSAppaRao Puli {
52bd030d0aSAppaRao Puli 
53f52c03c1SCarson Labrado // It is assumed that the BMC should be able to handle 4 parallel connections
54f52c03c1SCarson Labrado constexpr uint8_t maxPoolSize = 4;
55f52c03c1SCarson Labrado constexpr uint8_t maxRequestQueueSize = 50;
5617dcc312SCarson Labrado constexpr unsigned int httpReadBodyLimit = 131072;
574d94272fSCarson Labrado constexpr unsigned int httpReadBufferSize = 4096;
582a5689a7SAppaRao Puli 
59bd030d0aSAppaRao Puli enum class ConnState
60bd030d0aSAppaRao Puli {
612a5689a7SAppaRao Puli     initialized,
6229a82b08SSunitha Harish     resolveInProgress,
6329a82b08SSunitha Harish     resolveFailed,
642a5689a7SAppaRao Puli     connectInProgress,
652a5689a7SAppaRao Puli     connectFailed,
66bd030d0aSAppaRao Puli     connected,
67e38778a5SAppaRao Puli     handshakeInProgress,
68e38778a5SAppaRao Puli     handshakeFailed,
692a5689a7SAppaRao Puli     sendInProgress,
702a5689a7SAppaRao Puli     sendFailed,
716eaa1d2fSSunitha Harish     recvInProgress,
722a5689a7SAppaRao Puli     recvFailed,
732a5689a7SAppaRao Puli     idle,
74fe44eb0bSAyushi Smriti     closed,
756eaa1d2fSSunitha Harish     suspended,
766eaa1d2fSSunitha Harish     terminated,
776eaa1d2fSSunitha Harish     abortConnection,
78e38778a5SAppaRao Puli     sslInitFailed,
796eaa1d2fSSunitha Harish     retry
80bd030d0aSAppaRao Puli };
81bd030d0aSAppaRao Puli 
82a7a80296SCarson Labrado static inline boost::system::error_code
83a7a80296SCarson Labrado     defaultRetryHandler(unsigned int respCode)
84a7a80296SCarson Labrado {
85a7a80296SCarson Labrado     // As a default, assume 200X is alright
86a7a80296SCarson Labrado     BMCWEB_LOG_DEBUG << "Using default check for response code validity";
87a7a80296SCarson Labrado     if ((respCode < 200) || (respCode >= 300))
88a7a80296SCarson Labrado     {
89a7a80296SCarson Labrado         return boost::system::errc::make_error_code(
90a7a80296SCarson Labrado             boost::system::errc::result_out_of_range);
91a7a80296SCarson Labrado     }
92a7a80296SCarson Labrado 
93a7a80296SCarson Labrado     // Return 0 if the response code is valid
94a7a80296SCarson Labrado     return boost::system::errc::make_error_code(boost::system::errc::success);
95a7a80296SCarson Labrado };
96a7a80296SCarson Labrado 
97f52c03c1SCarson Labrado // We need to allow retry information to be set before a message has been sent
98f52c03c1SCarson Labrado // and a connection pool has been created
99f52c03c1SCarson Labrado struct RetryPolicyData
100f52c03c1SCarson Labrado {
101f52c03c1SCarson Labrado     uint32_t maxRetryAttempts = 5;
102f52c03c1SCarson Labrado     std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0);
103f52c03c1SCarson Labrado     std::string retryPolicyAction = "TerminateAfterRetries";
104a7a80296SCarson Labrado     std::function<boost::system::error_code(unsigned int respCode)>
105a7a80296SCarson Labrado         invalidResp = defaultRetryHandler;
106f52c03c1SCarson Labrado };
107f52c03c1SCarson Labrado 
108f52c03c1SCarson Labrado struct PendingRequest
109f52c03c1SCarson Labrado {
110244256ccSCarson Labrado     boost::beast::http::request<boost::beast::http::string_body> req;
111039a47e3SCarson Labrado     std::function<void(bool, uint32_t, Response&)> callback;
112f52c03c1SCarson Labrado     RetryPolicyData retryPolicy;
113039a47e3SCarson Labrado     PendingRequest(
1148a592810SEd Tanous         boost::beast::http::request<boost::beast::http::string_body>&& reqIn,
1158a592810SEd Tanous         const std::function<void(bool, uint32_t, Response&)>& callbackIn,
1168a592810SEd Tanous         const RetryPolicyData& retryPolicyIn) :
1178a592810SEd Tanous         req(std::move(reqIn)),
1188a592810SEd Tanous         callback(callbackIn), retryPolicy(retryPolicyIn)
119f52c03c1SCarson Labrado     {}
120f52c03c1SCarson Labrado };
121f52c03c1SCarson Labrado 
122f52c03c1SCarson Labrado class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo>
123bd030d0aSAppaRao Puli {
124bd030d0aSAppaRao Puli   private:
125f52c03c1SCarson Labrado     ConnState state = ConnState::initialized;
126f52c03c1SCarson Labrado     uint32_t retryCount = 0;
127f52c03c1SCarson Labrado     std::string subId;
128f52c03c1SCarson Labrado     std::string host;
129f52c03c1SCarson Labrado     uint16_t port;
130f52c03c1SCarson Labrado     uint32_t connId;
131f52c03c1SCarson Labrado 
132f52c03c1SCarson Labrado     // Retry policy information
133f52c03c1SCarson Labrado     // This should be updated before each message is sent
134f52c03c1SCarson Labrado     RetryPolicyData retryPolicy;
135f52c03c1SCarson Labrado 
136f52c03c1SCarson Labrado     // Data buffers
137bd030d0aSAppaRao Puli     boost::beast::http::request<boost::beast::http::string_body> req;
1386eaa1d2fSSunitha Harish     std::optional<
1396eaa1d2fSSunitha Harish         boost::beast::http::response_parser<boost::beast::http::string_body>>
1406eaa1d2fSSunitha Harish         parser;
1414d94272fSCarson Labrado     boost::beast::flat_static_buffer<httpReadBufferSize> buffer;
142039a47e3SCarson Labrado     Response res;
1436eaa1d2fSSunitha Harish 
144f52c03c1SCarson Labrado     // Ascync callables
145039a47e3SCarson Labrado     std::function<void(bool, uint32_t, Response&)> callback;
146f52c03c1SCarson Labrado     crow::async_resolve::Resolver resolver;
1470d5f5cf4SEd Tanous     boost::asio::ip::tcp::socket conn;
1480d5f5cf4SEd Tanous     std::optional<boost::beast::ssl_stream<boost::asio::ip::tcp::socket&>>
1490d5f5cf4SEd Tanous         sslConn;
150e38778a5SAppaRao Puli 
151f52c03c1SCarson Labrado     boost::asio::steady_timer timer;
15284b35604SEd Tanous 
153f52c03c1SCarson Labrado     friend class ConnectionPool;
154bd030d0aSAppaRao Puli 
15529a82b08SSunitha Harish     void doResolve()
15629a82b08SSunitha Harish     {
15729a82b08SSunitha Harish         state = ConnState::resolveInProgress;
158f52c03c1SCarson Labrado         BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":"
159f52c03c1SCarson Labrado                          << std::to_string(port)
160f52c03c1SCarson Labrado                          << ", id: " << std::to_string(connId);
16129a82b08SSunitha Harish 
1623d36e3a5SEd Tanous         resolver.asyncResolve(host, port,
1633d36e3a5SEd Tanous                               std::bind_front(&ConnectionInfo::afterResolve,
1643d36e3a5SEd Tanous                                               this, shared_from_this()));
1653d36e3a5SEd Tanous     }
1663d36e3a5SEd Tanous 
1673d36e3a5SEd Tanous     void afterResolve(
1683d36e3a5SEd Tanous         const std::shared_ptr<ConnectionInfo>& /*self*/,
16929a82b08SSunitha Harish         const boost::beast::error_code ec,
1703d36e3a5SEd Tanous         const std::vector<boost::asio::ip::tcp::endpoint>& endpointList)
1713d36e3a5SEd Tanous     {
17226f6976fSEd Tanous         if (ec || (endpointList.empty()))
17329a82b08SSunitha Harish         {
17429a82b08SSunitha Harish             BMCWEB_LOG_ERROR << "Resolve failed: " << ec.message();
1753d36e3a5SEd Tanous             state = ConnState::resolveFailed;
1763d36e3a5SEd Tanous             waitAndRetry();
17729a82b08SSunitha Harish             return;
17829a82b08SSunitha Harish         }
1793d36e3a5SEd Tanous         BMCWEB_LOG_DEBUG << "Resolved " << host << ":" << std::to_string(port)
1803d36e3a5SEd Tanous                          << ", id: " << std::to_string(connId);
1812a5689a7SAppaRao Puli         state = ConnState::connectInProgress;
1822a5689a7SAppaRao Puli 
183f52c03c1SCarson Labrado         BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":"
184f52c03c1SCarson Labrado                          << std::to_string(port)
185f52c03c1SCarson Labrado                          << ", id: " << std::to_string(connId);
186b00dcc27SEd Tanous 
1870d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
1880d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
1890d5f5cf4SEd Tanous 
1900d5f5cf4SEd Tanous         boost::asio::async_connect(
1910d5f5cf4SEd Tanous             conn, endpointList,
192e38778a5SAppaRao Puli             std::bind_front(&ConnectionInfo::afterConnect, this,
193e38778a5SAppaRao Puli                             shared_from_this()));
194e38778a5SAppaRao Puli     }
195e38778a5SAppaRao Puli 
196e38778a5SAppaRao Puli     void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/,
197e38778a5SAppaRao Puli                       boost::beast::error_code ec,
198e38778a5SAppaRao Puli                       const boost::asio::ip::tcp::endpoint& endpoint)
199e38778a5SAppaRao Puli     {
200513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
201513d1ffcSCarson Labrado         // this branch
202513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
203513d1ffcSCarson Labrado         {
204513d1ffcSCarson Labrado             return;
205513d1ffcSCarson Labrado         }
206513d1ffcSCarson Labrado 
2070d5f5cf4SEd Tanous         timer.cancel();
2082a5689a7SAppaRao Puli         if (ec)
2092a5689a7SAppaRao Puli         {
210002d39b4SEd Tanous             BMCWEB_LOG_ERROR << "Connect " << endpoint.address().to_string()
211002d39b4SEd Tanous                              << ":" << std::to_string(endpoint.port())
212e38778a5SAppaRao Puli                              << ", id: " << std::to_string(connId)
2132a5689a7SAppaRao Puli                              << " failed: " << ec.message();
214e38778a5SAppaRao Puli             state = ConnState::connectFailed;
215e38778a5SAppaRao Puli             waitAndRetry();
2162a5689a7SAppaRao Puli             return;
2172a5689a7SAppaRao Puli         }
218e38778a5SAppaRao Puli         BMCWEB_LOG_DEBUG << "Connected to: " << endpoint.address().to_string()
219e38778a5SAppaRao Puli                          << ":" << std::to_string(endpoint.port())
220e38778a5SAppaRao Puli                          << ", id: " << std::to_string(connId);
221e38778a5SAppaRao Puli         if (sslConn)
222e38778a5SAppaRao Puli         {
2230d5f5cf4SEd Tanous             doSslHandshake();
224e38778a5SAppaRao Puli             return;
225e38778a5SAppaRao Puli         }
226e38778a5SAppaRao Puli         state = ConnState::connected;
227e38778a5SAppaRao Puli         sendMessage();
228e38778a5SAppaRao Puli     }
229e38778a5SAppaRao Puli 
2300d5f5cf4SEd Tanous     void doSslHandshake()
231e38778a5SAppaRao Puli     {
232e38778a5SAppaRao Puli         if (!sslConn)
233e38778a5SAppaRao Puli         {
234e38778a5SAppaRao Puli             return;
235e38778a5SAppaRao Puli         }
236e38778a5SAppaRao Puli         state = ConnState::handshakeInProgress;
2370d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
2380d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
239e38778a5SAppaRao Puli         sslConn->async_handshake(
240e38778a5SAppaRao Puli             boost::asio::ssl::stream_base::client,
241e38778a5SAppaRao Puli             std::bind_front(&ConnectionInfo::afterSslHandshake, this,
242e38778a5SAppaRao Puli                             shared_from_this()));
243e38778a5SAppaRao Puli     }
244e38778a5SAppaRao Puli 
245e38778a5SAppaRao Puli     void afterSslHandshake(const std::shared_ptr<ConnectionInfo>& /*self*/,
246e38778a5SAppaRao Puli                            boost::beast::error_code ec)
247e38778a5SAppaRao Puli     {
248513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
249513d1ffcSCarson Labrado         // this branch
250513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
251513d1ffcSCarson Labrado         {
252513d1ffcSCarson Labrado             return;
253513d1ffcSCarson Labrado         }
254513d1ffcSCarson Labrado 
2550d5f5cf4SEd Tanous         timer.cancel();
256e38778a5SAppaRao Puli         if (ec)
257e38778a5SAppaRao Puli         {
258e38778a5SAppaRao Puli             BMCWEB_LOG_ERROR << "SSL Handshake failed -"
259e38778a5SAppaRao Puli                              << " id: " << std::to_string(connId)
260e38778a5SAppaRao Puli                              << " error: " << ec.message();
261e38778a5SAppaRao Puli             state = ConnState::handshakeFailed;
262e38778a5SAppaRao Puli             waitAndRetry();
263e38778a5SAppaRao Puli             return;
264e38778a5SAppaRao Puli         }
265e38778a5SAppaRao Puli         BMCWEB_LOG_DEBUG << "SSL Handshake successful -"
266e38778a5SAppaRao Puli                          << " id: " << std::to_string(connId);
267e38778a5SAppaRao Puli         state = ConnState::connected;
268e38778a5SAppaRao Puli         sendMessage();
2692a5689a7SAppaRao Puli     }
2702a5689a7SAppaRao Puli 
271f52c03c1SCarson Labrado     void sendMessage()
2722a5689a7SAppaRao Puli     {
2732a5689a7SAppaRao Puli         state = ConnState::sendInProgress;
2742a5689a7SAppaRao Puli 
275bd030d0aSAppaRao Puli         // Set a timeout on the operation
2760d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
2770d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
278bd030d0aSAppaRao Puli 
279bd030d0aSAppaRao Puli         // Send the HTTP request to the remote host
280e38778a5SAppaRao Puli         if (sslConn)
281e38778a5SAppaRao Puli         {
282e38778a5SAppaRao Puli             boost::beast::http::async_write(
283e38778a5SAppaRao Puli                 *sslConn, req,
284e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterWrite, this,
285e38778a5SAppaRao Puli                                 shared_from_this()));
286e38778a5SAppaRao Puli         }
287e38778a5SAppaRao Puli         else
288e38778a5SAppaRao Puli         {
289bd030d0aSAppaRao Puli             boost::beast::http::async_write(
290bd030d0aSAppaRao Puli                 conn, req,
291e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterWrite, this,
292e38778a5SAppaRao Puli                                 shared_from_this()));
293e38778a5SAppaRao Puli         }
294e38778a5SAppaRao Puli     }
295e38778a5SAppaRao Puli 
296e38778a5SAppaRao Puli     void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/,
297e38778a5SAppaRao Puli                     const boost::beast::error_code& ec, size_t bytesTransferred)
298e38778a5SAppaRao Puli     {
299513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
300513d1ffcSCarson Labrado         // this branch
301513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
302513d1ffcSCarson Labrado         {
303513d1ffcSCarson Labrado             return;
304513d1ffcSCarson Labrado         }
305513d1ffcSCarson Labrado 
3060d5f5cf4SEd Tanous         timer.cancel();
307bd030d0aSAppaRao Puli         if (ec)
308bd030d0aSAppaRao Puli         {
309002d39b4SEd Tanous             BMCWEB_LOG_ERROR << "sendMessage() failed: " << ec.message();
310e38778a5SAppaRao Puli             state = ConnState::sendFailed;
311e38778a5SAppaRao Puli             waitAndRetry();
312bd030d0aSAppaRao Puli             return;
313bd030d0aSAppaRao Puli         }
314bd030d0aSAppaRao Puli         BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
315bd030d0aSAppaRao Puli                          << bytesTransferred;
316bd030d0aSAppaRao Puli 
317e38778a5SAppaRao Puli         recvMessage();
318bd030d0aSAppaRao Puli     }
319bd030d0aSAppaRao Puli 
320bd030d0aSAppaRao Puli     void recvMessage()
321bd030d0aSAppaRao Puli     {
3226eaa1d2fSSunitha Harish         state = ConnState::recvInProgress;
3236eaa1d2fSSunitha Harish 
3246eaa1d2fSSunitha Harish         parser.emplace(std::piecewise_construct, std::make_tuple());
3256eaa1d2fSSunitha Harish         parser->body_limit(httpReadBodyLimit);
3266eaa1d2fSSunitha Harish 
3270d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
3280d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
3290d5f5cf4SEd Tanous 
330bd030d0aSAppaRao Puli         // Receive the HTTP response
331e38778a5SAppaRao Puli         if (sslConn)
332e38778a5SAppaRao Puli         {
333e38778a5SAppaRao Puli             boost::beast::http::async_read(
334e38778a5SAppaRao Puli                 *sslConn, buffer, *parser,
335e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterRead, this,
336e38778a5SAppaRao Puli                                 shared_from_this()));
337e38778a5SAppaRao Puli         }
338e38778a5SAppaRao Puli         else
339e38778a5SAppaRao Puli         {
340bd030d0aSAppaRao Puli             boost::beast::http::async_read(
3416eaa1d2fSSunitha Harish                 conn, buffer, *parser,
342e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterRead, this,
343e38778a5SAppaRao Puli                                 shared_from_this()));
344e38778a5SAppaRao Puli         }
345e38778a5SAppaRao Puli     }
346e38778a5SAppaRao Puli 
347e38778a5SAppaRao Puli     void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/,
348e38778a5SAppaRao Puli                    const boost::beast::error_code& ec,
349e38778a5SAppaRao Puli                    const std::size_t& bytesTransferred)
350e38778a5SAppaRao Puli     {
351513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
352513d1ffcSCarson Labrado         // this branch
353513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
354513d1ffcSCarson Labrado         {
355513d1ffcSCarson Labrado             return;
356513d1ffcSCarson Labrado         }
357513d1ffcSCarson Labrado 
3580d5f5cf4SEd Tanous         timer.cancel();
359e38778a5SAppaRao Puli         if (ec && ec != boost::asio::ssl::error::stream_truncated)
360bd030d0aSAppaRao Puli         {
361002d39b4SEd Tanous             BMCWEB_LOG_ERROR << "recvMessage() failed: " << ec.message();
362e38778a5SAppaRao Puli             state = ConnState::recvFailed;
363e38778a5SAppaRao Puli             waitAndRetry();
364bd030d0aSAppaRao Puli             return;
365bd030d0aSAppaRao Puli         }
366bd030d0aSAppaRao Puli         BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
367bd030d0aSAppaRao Puli                          << bytesTransferred;
368e38778a5SAppaRao Puli         BMCWEB_LOG_DEBUG << "recvMessage() data: " << parser->get().body();
369bd030d0aSAppaRao Puli 
370e38778a5SAppaRao Puli         unsigned int respCode = parser->get().result_int();
371e38778a5SAppaRao Puli         BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: " << respCode;
3726eaa1d2fSSunitha Harish 
373a7a80296SCarson Labrado         // Make sure the received response code is valid as defined by
374a7a80296SCarson Labrado         // the associated retry policy
375e38778a5SAppaRao Puli         if (retryPolicy.invalidResp(respCode))
3766eaa1d2fSSunitha Harish         {
3776eaa1d2fSSunitha Harish             // The listener failed to receive the Sent-Event
378002d39b4SEd Tanous             BMCWEB_LOG_ERROR << "recvMessage() Listener Failed to "
3797adb85acSSunitha Harish                                 "receive Sent-Event. Header Response Code: "
3807adb85acSSunitha Harish                              << respCode;
381e38778a5SAppaRao Puli             state = ConnState::recvFailed;
382e38778a5SAppaRao Puli             waitAndRetry();
3836eaa1d2fSSunitha Harish             return;
3846eaa1d2fSSunitha Harish         }
385bd030d0aSAppaRao Puli 
386f52c03c1SCarson Labrado         // Send is successful
387f52c03c1SCarson Labrado         // Reset the counter just in case this was after retrying
388e38778a5SAppaRao Puli         retryCount = 0;
3896eaa1d2fSSunitha Harish 
3906eaa1d2fSSunitha Harish         // Keep the connection alive if server supports it
3916eaa1d2fSSunitha Harish         // Else close the connection
3926eaa1d2fSSunitha Harish         BMCWEB_LOG_DEBUG << "recvMessage() keepalive : "
393e38778a5SAppaRao Puli                          << parser->keep_alive();
3946eaa1d2fSSunitha Harish 
395039a47e3SCarson Labrado         // Copy the response into a Response object so that it can be
396039a47e3SCarson Labrado         // processed by the callback function.
397e38778a5SAppaRao Puli         res.stringResponse = parser->release();
398e38778a5SAppaRao Puli         callback(parser->keep_alive(), connId, res);
399513d1ffcSCarson Labrado         res.clear();
400bd030d0aSAppaRao Puli     }
401bd030d0aSAppaRao Puli 
4020d5f5cf4SEd Tanous     static void onTimeout(const std::weak_ptr<ConnectionInfo>& weakSelf,
4030d5f5cf4SEd Tanous                           const boost::system::error_code ec)
4040d5f5cf4SEd Tanous     {
4050d5f5cf4SEd Tanous         if (ec == boost::asio::error::operation_aborted)
4060d5f5cf4SEd Tanous         {
4070d5f5cf4SEd Tanous             BMCWEB_LOG_DEBUG
408513d1ffcSCarson Labrado                 << "async_wait failed since the operation is aborted";
4090d5f5cf4SEd Tanous             return;
4100d5f5cf4SEd Tanous         }
4110d5f5cf4SEd Tanous         if (ec)
4120d5f5cf4SEd Tanous         {
4130d5f5cf4SEd Tanous             BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message();
4140d5f5cf4SEd Tanous             // If the timer fails, we need to close the socket anyway, same as
4150d5f5cf4SEd Tanous             // if it expired.
4160d5f5cf4SEd Tanous         }
4170d5f5cf4SEd Tanous         std::shared_ptr<ConnectionInfo> self = weakSelf.lock();
4180d5f5cf4SEd Tanous         if (self == nullptr)
4190d5f5cf4SEd Tanous         {
4200d5f5cf4SEd Tanous             return;
4210d5f5cf4SEd Tanous         }
4220d5f5cf4SEd Tanous         self->waitAndRetry();
4230d5f5cf4SEd Tanous     }
4240d5f5cf4SEd Tanous 
4256eaa1d2fSSunitha Harish     void waitAndRetry()
426bd030d0aSAppaRao Puli     {
427e38778a5SAppaRao Puli         if ((retryCount >= retryPolicy.maxRetryAttempts) ||
428e38778a5SAppaRao Puli             (state == ConnState::sslInitFailed))
4292a5689a7SAppaRao Puli         {
4306eaa1d2fSSunitha Harish             BMCWEB_LOG_ERROR << "Maximum number of retries reached.";
431f52c03c1SCarson Labrado             BMCWEB_LOG_DEBUG << "Retry policy: "
432f52c03c1SCarson Labrado                              << retryPolicy.retryPolicyAction;
433039a47e3SCarson Labrado 
434f52c03c1SCarson Labrado             if (retryPolicy.retryPolicyAction == "TerminateAfterRetries")
435fe44eb0bSAyushi Smriti             {
436fe44eb0bSAyushi Smriti                 // TODO: delete subscription
437fe44eb0bSAyushi Smriti                 state = ConnState::terminated;
438fe44eb0bSAyushi Smriti             }
439f52c03c1SCarson Labrado             if (retryPolicy.retryPolicyAction == "SuspendRetries")
440fe44eb0bSAyushi Smriti             {
4412a5689a7SAppaRao Puli                 state = ConnState::suspended;
4422a5689a7SAppaRao Puli             }
443513d1ffcSCarson Labrado 
444513d1ffcSCarson Labrado             // We want to return a 502 to indicate there was an error with
445513d1ffcSCarson Labrado             // the external server
446513d1ffcSCarson Labrado             res.result(boost::beast::http::status::bad_gateway);
447513d1ffcSCarson Labrado             callback(false, connId, res);
448513d1ffcSCarson Labrado             res.clear();
449513d1ffcSCarson Labrado 
4506eaa1d2fSSunitha Harish             // Reset the retrycount to zero so that client can try connecting
4516eaa1d2fSSunitha Harish             // again if needed
452fe44eb0bSAyushi Smriti             retryCount = 0;
4532a5689a7SAppaRao Puli             return;
4542a5689a7SAppaRao Puli         }
4552a5689a7SAppaRao Puli 
4562a5689a7SAppaRao Puli         retryCount++;
457fe44eb0bSAyushi Smriti 
458f52c03c1SCarson Labrado         BMCWEB_LOG_DEBUG << "Attempt retry after "
459f52c03c1SCarson Labrado                          << std::to_string(
460f52c03c1SCarson Labrado                                 retryPolicy.retryIntervalSecs.count())
461fe44eb0bSAyushi Smriti                          << " seconds. RetryCount = " << retryCount;
462f52c03c1SCarson Labrado         timer.expires_after(retryPolicy.retryIntervalSecs);
4633d36e3a5SEd Tanous         timer.async_wait(std::bind_front(&ConnectionInfo::onTimerDone, this,
4643d36e3a5SEd Tanous                                          shared_from_this()));
4653d36e3a5SEd Tanous     }
4663d36e3a5SEd Tanous 
4673d36e3a5SEd Tanous     void onTimerDone(const std::shared_ptr<ConnectionInfo>& /*self*/,
4683d36e3a5SEd Tanous                      const boost::system::error_code& ec)
4693d36e3a5SEd Tanous     {
4706eaa1d2fSSunitha Harish         if (ec == boost::asio::error::operation_aborted)
4716eaa1d2fSSunitha Harish         {
4726eaa1d2fSSunitha Harish             BMCWEB_LOG_DEBUG
4736eaa1d2fSSunitha Harish                 << "async_wait failed since the operation is aborted"
4746eaa1d2fSSunitha Harish                 << ec.message();
4756eaa1d2fSSunitha Harish         }
4766eaa1d2fSSunitha Harish         else if (ec)
4776eaa1d2fSSunitha Harish         {
4786eaa1d2fSSunitha Harish             BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message();
4796eaa1d2fSSunitha Harish             // Ignore the error and continue the retry loop to attempt
4806eaa1d2fSSunitha Harish             // sending the event as per the retry policy
4816eaa1d2fSSunitha Harish         }
4826eaa1d2fSSunitha Harish 
483f52c03c1SCarson Labrado         // Let's close the connection and restart from resolve.
4843d36e3a5SEd Tanous         doClose(true);
4852a5689a7SAppaRao Puli     }
4862a5689a7SAppaRao Puli 
487e38778a5SAppaRao Puli     void shutdownConn(bool retry)
488fe44eb0bSAyushi Smriti     {
489f52c03c1SCarson Labrado         boost::beast::error_code ec;
4900d5f5cf4SEd Tanous         conn.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
491f52c03c1SCarson Labrado         conn.close();
492f52c03c1SCarson Labrado 
493f52c03c1SCarson Labrado         // not_connected happens sometimes so don't bother reporting it.
494f52c03c1SCarson Labrado         if (ec && ec != boost::beast::errc::not_connected)
4952a5689a7SAppaRao Puli         {
496f52c03c1SCarson Labrado             BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
497f52c03c1SCarson Labrado                              << ", id: " << std::to_string(connId)
498f52c03c1SCarson Labrado                              << " shutdown failed: " << ec.message();
4996eaa1d2fSSunitha Harish         }
5005cab68f3SCarson Labrado         else
5015cab68f3SCarson Labrado         {
502f52c03c1SCarson Labrado             BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
503f52c03c1SCarson Labrado                              << ", id: " << std::to_string(connId)
504f52c03c1SCarson Labrado                              << " closed gracefully";
5055cab68f3SCarson Labrado         }
506ca723762SEd Tanous 
507e38778a5SAppaRao Puli         if (retry)
50892a74e56SAppaRao Puli         {
509f52c03c1SCarson Labrado             // Now let's try to resend the data
510f52c03c1SCarson Labrado             state = ConnState::retry;
5110d5f5cf4SEd Tanous             doResolve();
512e38778a5SAppaRao Puli         }
513e38778a5SAppaRao Puli         else
514e38778a5SAppaRao Puli         {
515e38778a5SAppaRao Puli             state = ConnState::closed;
516e38778a5SAppaRao Puli         }
517e38778a5SAppaRao Puli     }
518e38778a5SAppaRao Puli 
519e38778a5SAppaRao Puli     void doClose(bool retry = false)
520e38778a5SAppaRao Puli     {
521e38778a5SAppaRao Puli         if (!sslConn)
522e38778a5SAppaRao Puli         {
523e38778a5SAppaRao Puli             shutdownConn(retry);
524e38778a5SAppaRao Puli             return;
525e38778a5SAppaRao Puli         }
526e38778a5SAppaRao Puli 
527e38778a5SAppaRao Puli         sslConn->async_shutdown(
528e38778a5SAppaRao Puli             std::bind_front(&ConnectionInfo::afterSslShutdown, this,
529e38778a5SAppaRao Puli                             shared_from_this(), retry));
530e38778a5SAppaRao Puli     }
531e38778a5SAppaRao Puli 
532e38778a5SAppaRao Puli     void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/,
533e38778a5SAppaRao Puli                           bool retry, const boost::system::error_code& ec)
534e38778a5SAppaRao Puli     {
535e38778a5SAppaRao Puli 
536e38778a5SAppaRao Puli         if (ec)
537e38778a5SAppaRao Puli         {
538e38778a5SAppaRao Puli             BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
539e38778a5SAppaRao Puli                              << ", id: " << std::to_string(connId)
540e38778a5SAppaRao Puli                              << " shutdown failed: " << ec.message();
541e38778a5SAppaRao Puli         }
542e38778a5SAppaRao Puli         else
543e38778a5SAppaRao Puli         {
544e38778a5SAppaRao Puli             BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
545e38778a5SAppaRao Puli                              << ", id: " << std::to_string(connId)
546e38778a5SAppaRao Puli                              << " closed gracefully";
547e38778a5SAppaRao Puli         }
548e38778a5SAppaRao Puli         shutdownConn(retry);
549e38778a5SAppaRao Puli     }
550e38778a5SAppaRao Puli 
551e38778a5SAppaRao Puli     void setCipherSuiteTLSext()
552e38778a5SAppaRao Puli     {
553e38778a5SAppaRao Puli         if (!sslConn)
554e38778a5SAppaRao Puli         {
555e38778a5SAppaRao Puli             return;
556e38778a5SAppaRao Puli         }
557e38778a5SAppaRao Puli         // NOTE: The SSL_set_tlsext_host_name is defined in tlsv1.h header
558e38778a5SAppaRao Puli         // file but its having old style casting (name is cast to void*).
559e38778a5SAppaRao Puli         // Since bmcweb compiler treats all old-style-cast as error, its
560e38778a5SAppaRao Puli         // causing the build failure. So replaced the same macro inline and
561e38778a5SAppaRao Puli         // did corrected the code by doing static_cast to viod*. This has to
562e38778a5SAppaRao Puli         // be fixed in openssl library in long run. Set SNI Hostname (many
563e38778a5SAppaRao Puli         // hosts need this to handshake successfully)
564e38778a5SAppaRao Puli         if (SSL_ctrl(sslConn->native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME,
565e38778a5SAppaRao Puli                      TLSEXT_NAMETYPE_host_name,
566e38778a5SAppaRao Puli                      static_cast<void*>(&host.front())) == 0)
567e38778a5SAppaRao Puli 
568e38778a5SAppaRao Puli         {
569e38778a5SAppaRao Puli             boost::beast::error_code ec{static_cast<int>(::ERR_get_error()),
570e38778a5SAppaRao Puli                                         boost::asio::error::get_ssl_category()};
571e38778a5SAppaRao Puli 
572e38778a5SAppaRao Puli             BMCWEB_LOG_ERROR << "SSL_set_tlsext_host_name " << host << ":"
573e38778a5SAppaRao Puli                              << port << ", id: " << std::to_string(connId)
574e38778a5SAppaRao Puli                              << " failed: " << ec.message();
575e38778a5SAppaRao Puli             // Set state as sslInit failed so that we close the connection
576e38778a5SAppaRao Puli             // and take appropriate action as per retry configuration.
577e38778a5SAppaRao Puli             state = ConnState::sslInitFailed;
578e38778a5SAppaRao Puli             waitAndRetry();
579e38778a5SAppaRao Puli             return;
580e38778a5SAppaRao Puli         }
581bd030d0aSAppaRao Puli     }
582bd030d0aSAppaRao Puli 
583bd030d0aSAppaRao Puli   public:
584e38778a5SAppaRao Puli     explicit ConnectionInfo(boost::asio::io_context& iocIn,
585e38778a5SAppaRao Puli                             const std::string& idIn,
586e38778a5SAppaRao Puli                             const std::string& destIPIn, uint16_t destPortIn,
587e38778a5SAppaRao Puli                             bool useSSL, unsigned int connIdIn) :
5888a592810SEd Tanous         subId(idIn),
589e38778a5SAppaRao Puli         host(destIPIn), port(destPortIn), connId(connIdIn), conn(iocIn),
590e38778a5SAppaRao Puli         timer(iocIn)
591e38778a5SAppaRao Puli     {
592e38778a5SAppaRao Puli         if (useSSL)
593e38778a5SAppaRao Puli         {
594e38778a5SAppaRao Puli             std::optional<boost::asio::ssl::context> sslCtx =
595e38778a5SAppaRao Puli                 ensuressl::getSSLClientContext();
596e38778a5SAppaRao Puli 
597e38778a5SAppaRao Puli             if (!sslCtx)
598e38778a5SAppaRao Puli             {
599e38778a5SAppaRao Puli                 BMCWEB_LOG_ERROR << "prepareSSLContext failed - " << host << ":"
600e38778a5SAppaRao Puli                                  << port << ", id: " << std::to_string(connId);
601e38778a5SAppaRao Puli                 // Don't retry if failure occurs while preparing SSL context
602e38778a5SAppaRao Puli                 // such as certificate is invalid or set cipher failure or set
603e38778a5SAppaRao Puli                 // host name failure etc... Setting conn state to sslInitFailed
604e38778a5SAppaRao Puli                 // and connection state will be transitioned to next state
605e38778a5SAppaRao Puli                 // depending on retry policy set by subscription.
606e38778a5SAppaRao Puli                 state = ConnState::sslInitFailed;
607e38778a5SAppaRao Puli                 waitAndRetry();
608e38778a5SAppaRao Puli                 return;
609e38778a5SAppaRao Puli             }
610e38778a5SAppaRao Puli             sslConn.emplace(conn, *sslCtx);
611e38778a5SAppaRao Puli             setCipherSuiteTLSext();
612e38778a5SAppaRao Puli         }
613e38778a5SAppaRao Puli     }
614f52c03c1SCarson Labrado };
615bd030d0aSAppaRao Puli 
616f52c03c1SCarson Labrado class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
617bd030d0aSAppaRao Puli {
618f52c03c1SCarson Labrado   private:
619f52c03c1SCarson Labrado     boost::asio::io_context& ioc;
620e38778a5SAppaRao Puli     std::string id;
621e38778a5SAppaRao Puli     std::string destIP;
622e38778a5SAppaRao Puli     uint16_t destPort;
623e38778a5SAppaRao Puli     bool useSSL;
624f52c03c1SCarson Labrado     std::vector<std::shared_ptr<ConnectionInfo>> connections;
625f52c03c1SCarson Labrado     boost::container::devector<PendingRequest> requestQueue;
626f52c03c1SCarson Labrado 
627f52c03c1SCarson Labrado     friend class HttpClient;
628f52c03c1SCarson Labrado 
629244256ccSCarson Labrado     // Configure a connections's request, callback, and retry info in
630244256ccSCarson Labrado     // preparation to begin sending the request
631f52c03c1SCarson Labrado     void setConnProps(ConnectionInfo& conn)
632bd030d0aSAppaRao Puli     {
633f52c03c1SCarson Labrado         if (requestQueue.empty())
634f52c03c1SCarson Labrado         {
635f52c03c1SCarson Labrado             BMCWEB_LOG_ERROR
636f52c03c1SCarson Labrado                 << "setConnProps() should not have been called when requestQueue is empty";
637bd030d0aSAppaRao Puli             return;
638bd030d0aSAppaRao Puli         }
639bd030d0aSAppaRao Puli 
640244256ccSCarson Labrado         auto nextReq = requestQueue.front();
641244256ccSCarson Labrado         conn.retryPolicy = std::move(nextReq.retryPolicy);
642244256ccSCarson Labrado         conn.req = std::move(nextReq.req);
643244256ccSCarson Labrado         conn.callback = std::move(nextReq.callback);
644f52c03c1SCarson Labrado 
645f52c03c1SCarson Labrado         BMCWEB_LOG_DEBUG << "Setting properties for connection " << conn.host
646f52c03c1SCarson Labrado                          << ":" << std::to_string(conn.port)
647a7a80296SCarson Labrado                          << ", id: " << std::to_string(conn.connId);
648f52c03c1SCarson Labrado 
649f52c03c1SCarson Labrado         // We can remove the request from the queue at this point
650f52c03c1SCarson Labrado         requestQueue.pop_front();
651f52c03c1SCarson Labrado     }
652f52c03c1SCarson Labrado 
653f52c03c1SCarson Labrado     // Configures a connection to use the specific retry policy.
654f52c03c1SCarson Labrado     inline void setConnRetryPolicy(ConnectionInfo& conn,
655f52c03c1SCarson Labrado                                    const RetryPolicyData& retryPolicy)
6562a5689a7SAppaRao Puli     {
657f52c03c1SCarson Labrado         BMCWEB_LOG_DEBUG << destIP << ":" << std::to_string(destPort)
658a7a80296SCarson Labrado                          << ", id: " << std::to_string(conn.connId);
659f52c03c1SCarson Labrado 
660f52c03c1SCarson Labrado         conn.retryPolicy = retryPolicy;
661f52c03c1SCarson Labrado     }
662f52c03c1SCarson Labrado 
663f52c03c1SCarson Labrado     // Gets called as part of callback after request is sent
664f52c03c1SCarson Labrado     // Reuses the connection if there are any requests waiting to be sent
665f52c03c1SCarson Labrado     // Otherwise closes the connection if it is not a keep-alive
666f52c03c1SCarson Labrado     void sendNext(bool keepAlive, uint32_t connId)
667f52c03c1SCarson Labrado     {
668f52c03c1SCarson Labrado         auto conn = connections[connId];
66946a81465SCarson Labrado 
67046a81465SCarson Labrado         // Allow the connection's handler to be deleted
67146a81465SCarson Labrado         // This is needed because of Redfish Aggregation passing an
67246a81465SCarson Labrado         // AsyncResponse shared_ptr to this callback
67346a81465SCarson Labrado         conn->callback = nullptr;
67446a81465SCarson Labrado 
675f52c03c1SCarson Labrado         // Reuse the connection to send the next request in the queue
676f52c03c1SCarson Labrado         if (!requestQueue.empty())
677f52c03c1SCarson Labrado         {
678f52c03c1SCarson Labrado             BMCWEB_LOG_DEBUG << std::to_string(requestQueue.size())
679f52c03c1SCarson Labrado                              << " requests remaining in queue for " << destIP
680f52c03c1SCarson Labrado                              << ":" << std::to_string(destPort)
681f52c03c1SCarson Labrado                              << ", reusing connnection "
682f52c03c1SCarson Labrado                              << std::to_string(connId);
683f52c03c1SCarson Labrado 
684f52c03c1SCarson Labrado             setConnProps(*conn);
685f52c03c1SCarson Labrado 
686f52c03c1SCarson Labrado             if (keepAlive)
687f52c03c1SCarson Labrado             {
688f52c03c1SCarson Labrado                 conn->sendMessage();
6892a5689a7SAppaRao Puli             }
6902a5689a7SAppaRao Puli             else
6912a5689a7SAppaRao Puli             {
692f52c03c1SCarson Labrado                 // Server is not keep-alive enabled so we need to close the
693f52c03c1SCarson Labrado                 // connection and then start over from resolve
694f52c03c1SCarson Labrado                 conn->doClose();
695f52c03c1SCarson Labrado                 conn->doResolve();
696f52c03c1SCarson Labrado             }
697f52c03c1SCarson Labrado             return;
698f52c03c1SCarson Labrado         }
699f52c03c1SCarson Labrado 
700f52c03c1SCarson Labrado         // No more messages to send so close the connection if necessary
701f52c03c1SCarson Labrado         if (keepAlive)
702f52c03c1SCarson Labrado         {
703f52c03c1SCarson Labrado             conn->state = ConnState::idle;
704f52c03c1SCarson Labrado         }
705f52c03c1SCarson Labrado         else
706f52c03c1SCarson Labrado         {
707f52c03c1SCarson Labrado             // Abort the connection since server is not keep-alive enabled
708f52c03c1SCarson Labrado             conn->state = ConnState::abortConnection;
709f52c03c1SCarson Labrado             conn->doClose();
7102a5689a7SAppaRao Puli         }
711bd030d0aSAppaRao Puli     }
712bd030d0aSAppaRao Puli 
713244256ccSCarson Labrado     void sendData(std::string& data, const std::string& destUri,
714244256ccSCarson Labrado                   const boost::beast::http::fields& httpHeader,
715244256ccSCarson Labrado                   const boost::beast::http::verb verb,
716244256ccSCarson Labrado                   const RetryPolicyData& retryPolicy,
7176b3db60dSEd Tanous                   const std::function<void(Response&)>& resHandler)
718fe44eb0bSAyushi Smriti     {
719244256ccSCarson Labrado         // Construct the request to be sent
720244256ccSCarson Labrado         boost::beast::http::request<boost::beast::http::string_body> thisReq(
721244256ccSCarson Labrado             verb, destUri, 11, "", httpHeader);
722244256ccSCarson Labrado         thisReq.set(boost::beast::http::field::host, destIP);
723244256ccSCarson Labrado         thisReq.keep_alive(true);
724244256ccSCarson Labrado         thisReq.body() = std::move(data);
725244256ccSCarson Labrado         thisReq.prepare_payload();
7263d36e3a5SEd Tanous         auto cb = std::bind_front(&ConnectionPool::afterSendData,
7273d36e3a5SEd Tanous                                   weak_from_this(), resHandler);
728f52c03c1SCarson Labrado         // Reuse an existing connection if one is available
729f52c03c1SCarson Labrado         for (unsigned int i = 0; i < connections.size(); i++)
730fe44eb0bSAyushi Smriti         {
731f52c03c1SCarson Labrado             auto conn = connections[i];
732f52c03c1SCarson Labrado             if ((conn->state == ConnState::idle) ||
733f52c03c1SCarson Labrado                 (conn->state == ConnState::initialized) ||
734f52c03c1SCarson Labrado                 (conn->state == ConnState::closed))
735f52c03c1SCarson Labrado             {
736244256ccSCarson Labrado                 conn->req = std::move(thisReq);
737f52c03c1SCarson Labrado                 conn->callback = std::move(cb);
738f52c03c1SCarson Labrado                 setConnRetryPolicy(*conn, retryPolicy);
739f52c03c1SCarson Labrado                 std::string commonMsg = std::to_string(i) + " from pool " +
740f52c03c1SCarson Labrado                                         destIP + ":" + std::to_string(destPort);
741f52c03c1SCarson Labrado 
742f52c03c1SCarson Labrado                 if (conn->state == ConnState::idle)
743f52c03c1SCarson Labrado                 {
744f52c03c1SCarson Labrado                     BMCWEB_LOG_DEBUG << "Grabbing idle connection "
745f52c03c1SCarson Labrado                                      << commonMsg;
746f52c03c1SCarson Labrado                     conn->sendMessage();
747f52c03c1SCarson Labrado                 }
748f52c03c1SCarson Labrado                 else
749f52c03c1SCarson Labrado                 {
750f52c03c1SCarson Labrado                     BMCWEB_LOG_DEBUG << "Reusing existing connection "
751f52c03c1SCarson Labrado                                      << commonMsg;
752f52c03c1SCarson Labrado                     conn->doResolve();
753f52c03c1SCarson Labrado                 }
754f52c03c1SCarson Labrado                 return;
755f52c03c1SCarson Labrado             }
756f52c03c1SCarson Labrado         }
757f52c03c1SCarson Labrado 
758f52c03c1SCarson Labrado         // All connections in use so create a new connection or add request to
759f52c03c1SCarson Labrado         // the queue
760f52c03c1SCarson Labrado         if (connections.size() < maxPoolSize)
761f52c03c1SCarson Labrado         {
762f52c03c1SCarson Labrado             BMCWEB_LOG_DEBUG << "Adding new connection to pool " << destIP
763f52c03c1SCarson Labrado                              << ":" << std::to_string(destPort);
764f52c03c1SCarson Labrado             auto conn = addConnection();
765244256ccSCarson Labrado             conn->req = std::move(thisReq);
766f52c03c1SCarson Labrado             conn->callback = std::move(cb);
767f52c03c1SCarson Labrado             setConnRetryPolicy(*conn, retryPolicy);
768f52c03c1SCarson Labrado             conn->doResolve();
769f52c03c1SCarson Labrado         }
770f52c03c1SCarson Labrado         else if (requestQueue.size() < maxRequestQueueSize)
771f52c03c1SCarson Labrado         {
772f52c03c1SCarson Labrado             BMCWEB_LOG_ERROR << "Max pool size reached. Adding data to queue.";
773244256ccSCarson Labrado             requestQueue.emplace_back(std::move(thisReq), std::move(cb),
774f52c03c1SCarson Labrado                                       retryPolicy);
775f52c03c1SCarson Labrado         }
776f52c03c1SCarson Labrado         else
777f52c03c1SCarson Labrado         {
778f52c03c1SCarson Labrado             BMCWEB_LOG_ERROR << destIP << ":" << std::to_string(destPort)
779f52c03c1SCarson Labrado                              << " request queue full.  Dropping request.";
780f52c03c1SCarson Labrado         }
781f52c03c1SCarson Labrado     }
782f52c03c1SCarson Labrado 
7833d36e3a5SEd Tanous     // Callback to be called once the request has been sent
7843d36e3a5SEd Tanous     static void afterSendData(const std::weak_ptr<ConnectionPool>& weakSelf,
7853d36e3a5SEd Tanous                               const std::function<void(Response&)>& resHandler,
7863d36e3a5SEd Tanous                               bool keepAlive, uint32_t connId, Response& res)
7873d36e3a5SEd Tanous     {
7883d36e3a5SEd Tanous         // Allow provided callback to perform additional processing of the
7893d36e3a5SEd Tanous         // request
7903d36e3a5SEd Tanous         resHandler(res);
7913d36e3a5SEd Tanous 
7923d36e3a5SEd Tanous         // If requests remain in the queue then we want to reuse this
7933d36e3a5SEd Tanous         // connection to send the next request
7943d36e3a5SEd Tanous         std::shared_ptr<ConnectionPool> self = weakSelf.lock();
7953d36e3a5SEd Tanous         if (!self)
7963d36e3a5SEd Tanous         {
7973d36e3a5SEd Tanous             BMCWEB_LOG_CRITICAL << self << " Failed to capture connection";
7983d36e3a5SEd Tanous             return;
7993d36e3a5SEd Tanous         }
8003d36e3a5SEd Tanous 
8013d36e3a5SEd Tanous         self->sendNext(keepAlive, connId);
8023d36e3a5SEd Tanous     }
8033d36e3a5SEd Tanous 
804f52c03c1SCarson Labrado     std::shared_ptr<ConnectionInfo>& addConnection()
805f52c03c1SCarson Labrado     {
806f52c03c1SCarson Labrado         unsigned int newId = static_cast<unsigned int>(connections.size());
807f52c03c1SCarson Labrado 
808e38778a5SAppaRao Puli         auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>(
809e38778a5SAppaRao Puli             ioc, id, destIP, destPort, useSSL, newId));
810f52c03c1SCarson Labrado 
811f52c03c1SCarson Labrado         BMCWEB_LOG_DEBUG << "Added connection "
812f52c03c1SCarson Labrado                          << std::to_string(connections.size() - 1)
813f52c03c1SCarson Labrado                          << " to pool " << destIP << ":"
814f52c03c1SCarson Labrado                          << std::to_string(destPort);
815f52c03c1SCarson Labrado 
816f52c03c1SCarson Labrado         return ret;
817f52c03c1SCarson Labrado     }
818f52c03c1SCarson Labrado 
819f52c03c1SCarson Labrado   public:
8208a592810SEd Tanous     explicit ConnectionPool(boost::asio::io_context& iocIn,
8218a592810SEd Tanous                             const std::string& idIn,
822e38778a5SAppaRao Puli                             const std::string& destIPIn, uint16_t destPortIn,
823e38778a5SAppaRao Puli                             bool useSSLIn) :
8248a592810SEd Tanous         ioc(iocIn),
825e38778a5SAppaRao Puli         id(idIn), destIP(destIPIn), destPort(destPortIn), useSSL(useSSLIn)
826f52c03c1SCarson Labrado     {
827f52c03c1SCarson Labrado         BMCWEB_LOG_DEBUG << "Initializing connection pool for " << destIP << ":"
828f52c03c1SCarson Labrado                          << std::to_string(destPort);
829f52c03c1SCarson Labrado 
830f52c03c1SCarson Labrado         // Initialize the pool with a single connection
831f52c03c1SCarson Labrado         addConnection();
832fe44eb0bSAyushi Smriti     }
833bd030d0aSAppaRao Puli };
834bd030d0aSAppaRao Puli 
835f52c03c1SCarson Labrado class HttpClient
836f52c03c1SCarson Labrado {
837f52c03c1SCarson Labrado   private:
838f52c03c1SCarson Labrado     std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
839f52c03c1SCarson Labrado         connectionPools;
840f52c03c1SCarson Labrado     boost::asio::io_context& ioc =
841f52c03c1SCarson Labrado         crow::connections::systemBus->get_io_context();
842f52c03c1SCarson Labrado     std::unordered_map<std::string, RetryPolicyData> retryInfo;
843f52c03c1SCarson Labrado     HttpClient() = default;
844f52c03c1SCarson Labrado 
845039a47e3SCarson Labrado     // Used as a dummy callback by sendData() in order to call
846039a47e3SCarson Labrado     // sendDataWithCallback()
84702cad96eSEd Tanous     static void genericResHandler(const Response& res)
848039a47e3SCarson Labrado     {
849039a47e3SCarson Labrado         BMCWEB_LOG_DEBUG << "Response handled with return code: "
850039a47e3SCarson Labrado                          << std::to_string(res.resultInt());
8514ee8e211SEd Tanous     }
852039a47e3SCarson Labrado 
853f52c03c1SCarson Labrado   public:
854f52c03c1SCarson Labrado     HttpClient(const HttpClient&) = delete;
855f52c03c1SCarson Labrado     HttpClient& operator=(const HttpClient&) = delete;
856f52c03c1SCarson Labrado     HttpClient(HttpClient&&) = delete;
857f52c03c1SCarson Labrado     HttpClient& operator=(HttpClient&&) = delete;
858f52c03c1SCarson Labrado     ~HttpClient() = default;
859f52c03c1SCarson Labrado 
860f52c03c1SCarson Labrado     static HttpClient& getInstance()
861f52c03c1SCarson Labrado     {
862f52c03c1SCarson Labrado         static HttpClient handler;
863f52c03c1SCarson Labrado         return handler;
864f52c03c1SCarson Labrado     }
865f52c03c1SCarson Labrado 
866039a47e3SCarson Labrado     // Send a request to destIP:destPort where additional processing of the
867039a47e3SCarson Labrado     // result is not required
868f52c03c1SCarson Labrado     void sendData(std::string& data, const std::string& id,
869e38778a5SAppaRao Puli                   const std::string& destIP, uint16_t destPort,
870e38778a5SAppaRao Puli                   const std::string& destUri, bool useSSL,
871f52c03c1SCarson Labrado                   const boost::beast::http::fields& httpHeader,
872244256ccSCarson Labrado                   const boost::beast::http::verb verb,
873244256ccSCarson Labrado                   const std::string& retryPolicyName)
874f52c03c1SCarson Labrado     {
875e38778a5SAppaRao Puli         const std::function<void(Response&)> cb = genericResHandler;
876e38778a5SAppaRao Puli         sendDataWithCallback(data, id, destIP, destPort, destUri, useSSL,
877e38778a5SAppaRao Puli                              httpHeader, verb, retryPolicyName, cb);
878039a47e3SCarson Labrado     }
879039a47e3SCarson Labrado 
880039a47e3SCarson Labrado     // Send request to destIP:destPort and use the provided callback to
881039a47e3SCarson Labrado     // handle the response
882039a47e3SCarson Labrado     void sendDataWithCallback(std::string& data, const std::string& id,
883e38778a5SAppaRao Puli                               const std::string& destIP, uint16_t destPort,
884e38778a5SAppaRao Puli                               const std::string& destUri, bool useSSL,
885039a47e3SCarson Labrado                               const boost::beast::http::fields& httpHeader,
886244256ccSCarson Labrado                               const boost::beast::http::verb verb,
887244256ccSCarson Labrado                               const std::string& retryPolicyName,
8886b3db60dSEd Tanous                               const std::function<void(Response&)>& resHandler)
889039a47e3SCarson Labrado     {
890e38778a5SAppaRao Puli         std::string clientKey = useSSL ? "https" : "http";
891e38778a5SAppaRao Puli         clientKey += destIP;
892e38778a5SAppaRao Puli         clientKey += ":";
893e38778a5SAppaRao Puli         clientKey += std::to_string(destPort);
894f52c03c1SCarson Labrado         // Use nullptr to avoid creating a ConnectionPool each time
895e38778a5SAppaRao Puli         std::shared_ptr<ConnectionPool>& conn = connectionPools[clientKey];
896e38778a5SAppaRao Puli         if (conn == nullptr)
897f52c03c1SCarson Labrado         {
898f52c03c1SCarson Labrado             // Now actually create the ConnectionPool shared_ptr since it does
899f52c03c1SCarson Labrado             // not already exist
900e38778a5SAppaRao Puli             conn = std::make_shared<ConnectionPool>(ioc, id, destIP, destPort,
901e38778a5SAppaRao Puli                                                     useSSL);
902f52c03c1SCarson Labrado             BMCWEB_LOG_DEBUG << "Created connection pool for " << clientKey;
903f52c03c1SCarson Labrado         }
904f52c03c1SCarson Labrado         else
905f52c03c1SCarson Labrado         {
906f52c03c1SCarson Labrado             BMCWEB_LOG_DEBUG << "Using existing connection pool for "
907f52c03c1SCarson Labrado                              << clientKey;
908f52c03c1SCarson Labrado         }
909f52c03c1SCarson Labrado 
910f52c03c1SCarson Labrado         // Get the associated retry policy
911f52c03c1SCarson Labrado         auto policy = retryInfo.try_emplace(retryPolicyName);
912f52c03c1SCarson Labrado         if (policy.second)
913f52c03c1SCarson Labrado         {
914f52c03c1SCarson Labrado             BMCWEB_LOG_DEBUG << "Creating retry policy \"" << retryPolicyName
915f52c03c1SCarson Labrado                              << "\" with default values";
916f52c03c1SCarson Labrado         }
917f52c03c1SCarson Labrado 
918f52c03c1SCarson Labrado         // Send the data using either the existing connection pool or the newly
919f52c03c1SCarson Labrado         // created connection pool
920e38778a5SAppaRao Puli         conn->sendData(data, destUri, httpHeader, verb, policy.first->second,
921e38778a5SAppaRao Puli                        resHandler);
922f52c03c1SCarson Labrado     }
923f52c03c1SCarson Labrado 
924a7a80296SCarson Labrado     void setRetryConfig(
925a7a80296SCarson Labrado         const uint32_t retryAttempts, const uint32_t retryTimeoutInterval,
926a7a80296SCarson Labrado         const std::function<boost::system::error_code(unsigned int respCode)>&
927a7a80296SCarson Labrado             invalidResp,
928f52c03c1SCarson Labrado         const std::string& retryPolicyName)
929f52c03c1SCarson Labrado     {
930f52c03c1SCarson Labrado         // We need to create the retry policy if one does not already exist for
931f52c03c1SCarson Labrado         // the given retryPolicyName
932f52c03c1SCarson Labrado         auto result = retryInfo.try_emplace(retryPolicyName);
933f52c03c1SCarson Labrado         if (result.second)
934f52c03c1SCarson Labrado         {
935f52c03c1SCarson Labrado             BMCWEB_LOG_DEBUG << "setRetryConfig(): Creating new retry policy \""
936f52c03c1SCarson Labrado                              << retryPolicyName << "\"";
937f52c03c1SCarson Labrado         }
938f52c03c1SCarson Labrado         else
939f52c03c1SCarson Labrado         {
940f52c03c1SCarson Labrado             BMCWEB_LOG_DEBUG << "setRetryConfig(): Updating retry info for \""
941f52c03c1SCarson Labrado                              << retryPolicyName << "\"";
942f52c03c1SCarson Labrado         }
943f52c03c1SCarson Labrado 
944f52c03c1SCarson Labrado         result.first->second.maxRetryAttempts = retryAttempts;
945f52c03c1SCarson Labrado         result.first->second.retryIntervalSecs =
946f52c03c1SCarson Labrado             std::chrono::seconds(retryTimeoutInterval);
947a7a80296SCarson Labrado         result.first->second.invalidResp = invalidResp;
948f52c03c1SCarson Labrado     }
949f52c03c1SCarson Labrado 
950f52c03c1SCarson Labrado     void setRetryPolicy(const std::string& retryPolicy,
951f52c03c1SCarson Labrado                         const std::string& retryPolicyName)
952f52c03c1SCarson Labrado     {
953f52c03c1SCarson Labrado         // We need to create the retry policy if one does not already exist for
954f52c03c1SCarson Labrado         // the given retryPolicyName
955f52c03c1SCarson Labrado         auto result = retryInfo.try_emplace(retryPolicyName);
956f52c03c1SCarson Labrado         if (result.second)
957f52c03c1SCarson Labrado         {
958f52c03c1SCarson Labrado             BMCWEB_LOG_DEBUG << "setRetryPolicy(): Creating new retry policy \""
959f52c03c1SCarson Labrado                              << retryPolicyName << "\"";
960f52c03c1SCarson Labrado         }
961f52c03c1SCarson Labrado         else
962f52c03c1SCarson Labrado         {
963f52c03c1SCarson Labrado             BMCWEB_LOG_DEBUG << "setRetryPolicy(): Updating retry policy for \""
964f52c03c1SCarson Labrado                              << retryPolicyName << "\"";
965f52c03c1SCarson Labrado         }
966f52c03c1SCarson Labrado 
967f52c03c1SCarson Labrado         result.first->second.retryPolicyAction = retryPolicy;
968f52c03c1SCarson Labrado     }
969f52c03c1SCarson Labrado };
970bd030d0aSAppaRao Puli } // namespace crow
971