xref: /openbmc/bmcweb/http/http_client.hpp (revision 27b0cf90)
1bd030d0aSAppaRao Puli /*
2bd030d0aSAppaRao Puli // Copyright (c) 2020 Intel Corporation
3bd030d0aSAppaRao Puli //
4bd030d0aSAppaRao Puli // Licensed under the Apache License, Version 2.0 (the "License");
5bd030d0aSAppaRao Puli // you may not use this file except in compliance with the License.
6bd030d0aSAppaRao Puli // You may obtain a copy of the License at
7bd030d0aSAppaRao Puli //
8bd030d0aSAppaRao Puli //      http://www.apache.org/licenses/LICENSE-2.0
9bd030d0aSAppaRao Puli //
10bd030d0aSAppaRao Puli // Unless required by applicable law or agreed to in writing, software
11bd030d0aSAppaRao Puli // distributed under the License is distributed on an "AS IS" BASIS,
12bd030d0aSAppaRao Puli // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13bd030d0aSAppaRao Puli // See the License for the specific language governing permissions and
14bd030d0aSAppaRao Puli // limitations under the License.
15bd030d0aSAppaRao Puli */
16bd030d0aSAppaRao Puli #pragma once
1777665bdaSNan Zhou 
1877665bdaSNan Zhou #include "async_resolve.hpp"
1977665bdaSNan Zhou #include "http_response.hpp"
203ccb3adbSEd Tanous #include "logging.hpp"
213ccb3adbSEd Tanous #include "ssl_key_handler.hpp"
2277665bdaSNan Zhou 
230d5f5cf4SEd Tanous #include <boost/asio/connect.hpp>
24bb49eb5cSEd Tanous #include <boost/asio/io_context.hpp>
2529a82b08SSunitha Harish #include <boost/asio/ip/address.hpp>
2629a82b08SSunitha Harish #include <boost/asio/ip/basic_endpoint.hpp>
27bb49eb5cSEd Tanous #include <boost/asio/ip/tcp.hpp>
28e38778a5SAppaRao Puli #include <boost/asio/ssl/context.hpp>
29e38778a5SAppaRao Puli #include <boost/asio/ssl/error.hpp>
30d43cd0caSEd Tanous #include <boost/asio/steady_timer.hpp>
31d43cd0caSEd Tanous #include <boost/beast/core/flat_buffer.hpp>
32bb49eb5cSEd Tanous #include <boost/beast/core/flat_static_buffer.hpp>
33d43cd0caSEd Tanous #include <boost/beast/http/message.hpp>
34bb49eb5cSEd Tanous #include <boost/beast/http/parser.hpp>
35bb49eb5cSEd Tanous #include <boost/beast/http/read.hpp>
36bb49eb5cSEd Tanous #include <boost/beast/http/string_body.hpp>
37bb49eb5cSEd Tanous #include <boost/beast/http/write.hpp>
38e38778a5SAppaRao Puli #include <boost/beast/ssl/ssl_stream.hpp>
39bd030d0aSAppaRao Puli #include <boost/beast/version.hpp>
40f52c03c1SCarson Labrado #include <boost/container/devector.hpp>
41bb49eb5cSEd Tanous #include <boost/system/error_code.hpp>
42*27b0cf90SEd Tanous #include <boost/url/format.hpp>
43*27b0cf90SEd Tanous #include <boost/url/url.hpp>
44a716aa74SEd Tanous #include <boost/url/url_view.hpp>
451214b7e7SGunnar Mills 
46bd030d0aSAppaRao Puli #include <cstdlib>
47bd030d0aSAppaRao Puli #include <functional>
48bd030d0aSAppaRao Puli #include <iostream>
49bd030d0aSAppaRao Puli #include <memory>
502a5689a7SAppaRao Puli #include <queue>
51bd030d0aSAppaRao Puli #include <string>
52bd030d0aSAppaRao Puli 
53bd030d0aSAppaRao Puli namespace crow
54bd030d0aSAppaRao Puli {
55*27b0cf90SEd Tanous // With Redfish Aggregation it is assumed we will connect to another
56*27b0cf90SEd Tanous // instance of BMCWeb which can handle 100 simultaneous connections.
5766d90c2cSCarson Labrado constexpr size_t maxPoolSize = 20;
5866d90c2cSCarson Labrado constexpr size_t maxRequestQueueSize = 500;
5917dcc312SCarson Labrado constexpr unsigned int httpReadBodyLimit = 131072;
604d94272fSCarson Labrado constexpr unsigned int httpReadBufferSize = 4096;
612a5689a7SAppaRao Puli 
62bd030d0aSAppaRao Puli enum class ConnState
63bd030d0aSAppaRao Puli {
642a5689a7SAppaRao Puli     initialized,
6529a82b08SSunitha Harish     resolveInProgress,
6629a82b08SSunitha Harish     resolveFailed,
672a5689a7SAppaRao Puli     connectInProgress,
682a5689a7SAppaRao Puli     connectFailed,
69bd030d0aSAppaRao Puli     connected,
70e38778a5SAppaRao Puli     handshakeInProgress,
71e38778a5SAppaRao Puli     handshakeFailed,
722a5689a7SAppaRao Puli     sendInProgress,
732a5689a7SAppaRao Puli     sendFailed,
746eaa1d2fSSunitha Harish     recvInProgress,
752a5689a7SAppaRao Puli     recvFailed,
762a5689a7SAppaRao Puli     idle,
77fe44eb0bSAyushi Smriti     closed,
786eaa1d2fSSunitha Harish     suspended,
796eaa1d2fSSunitha Harish     terminated,
806eaa1d2fSSunitha Harish     abortConnection,
81e38778a5SAppaRao Puli     sslInitFailed,
826eaa1d2fSSunitha Harish     retry
83bd030d0aSAppaRao Puli };
84bd030d0aSAppaRao Puli 
85a7a80296SCarson Labrado static inline boost::system::error_code
86a7a80296SCarson Labrado     defaultRetryHandler(unsigned int respCode)
87a7a80296SCarson Labrado {
88a7a80296SCarson Labrado     // As a default, assume 200X is alright
8962598e31SEd Tanous     BMCWEB_LOG_DEBUG("Using default check for response code validity");
90a7a80296SCarson Labrado     if ((respCode < 200) || (respCode >= 300))
91a7a80296SCarson Labrado     {
92a7a80296SCarson Labrado         return boost::system::errc::make_error_code(
93a7a80296SCarson Labrado             boost::system::errc::result_out_of_range);
94a7a80296SCarson Labrado     }
95a7a80296SCarson Labrado 
96a7a80296SCarson Labrado     // Return 0 if the response code is valid
97a7a80296SCarson Labrado     return boost::system::errc::make_error_code(boost::system::errc::success);
98a7a80296SCarson Labrado };
99a7a80296SCarson Labrado 
100*27b0cf90SEd Tanous // We need to allow retry information to be set before a message has been
101*27b0cf90SEd Tanous // sent and a connection pool has been created
102d14a48ffSCarson Labrado struct ConnectionPolicy
103f52c03c1SCarson Labrado {
104f52c03c1SCarson Labrado     uint32_t maxRetryAttempts = 5;
105d14a48ffSCarson Labrado 
106d14a48ffSCarson Labrado     // the max size of requests in bytes.  0 for unlimited
107d14a48ffSCarson Labrado     boost::optional<uint64_t> requestByteLimit = httpReadBodyLimit;
108d14a48ffSCarson Labrado 
109d14a48ffSCarson Labrado     size_t maxConnections = 1;
110d14a48ffSCarson Labrado 
111f52c03c1SCarson Labrado     std::string retryPolicyAction = "TerminateAfterRetries";
112d14a48ffSCarson Labrado 
113d14a48ffSCarson Labrado     std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0);
114a7a80296SCarson Labrado     std::function<boost::system::error_code(unsigned int respCode)>
115a7a80296SCarson Labrado         invalidResp = defaultRetryHandler;
116f52c03c1SCarson Labrado };
117f52c03c1SCarson Labrado 
118f52c03c1SCarson Labrado struct PendingRequest
119f52c03c1SCarson Labrado {
120244256ccSCarson Labrado     boost::beast::http::request<boost::beast::http::string_body> req;
121039a47e3SCarson Labrado     std::function<void(bool, uint32_t, Response&)> callback;
122039a47e3SCarson Labrado     PendingRequest(
1238a592810SEd Tanous         boost::beast::http::request<boost::beast::http::string_body>&& reqIn,
124d14a48ffSCarson Labrado         const std::function<void(bool, uint32_t, Response&)>& callbackIn) :
1258a592810SEd Tanous         req(std::move(reqIn)),
126d14a48ffSCarson Labrado         callback(callbackIn)
127f52c03c1SCarson Labrado     {}
128f52c03c1SCarson Labrado };
129f52c03c1SCarson Labrado 
130e01d0c36SEd Tanous namespace http = boost::beast::http;
131f52c03c1SCarson Labrado class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo>
132bd030d0aSAppaRao Puli {
133bd030d0aSAppaRao Puli   private:
134f52c03c1SCarson Labrado     ConnState state = ConnState::initialized;
135f52c03c1SCarson Labrado     uint32_t retryCount = 0;
136f52c03c1SCarson Labrado     std::string subId;
137d14a48ffSCarson Labrado     std::shared_ptr<ConnectionPolicy> connPolicy;
138a716aa74SEd Tanous     boost::urls::url host;
139f52c03c1SCarson Labrado     uint32_t connId;
140f52c03c1SCarson Labrado 
141f52c03c1SCarson Labrado     // Data buffers
142e01d0c36SEd Tanous     http::request<http::string_body> req;
143e01d0c36SEd Tanous     using parser_type = http::response_parser<http::string_body>;
144e01d0c36SEd Tanous     std::optional<parser_type> parser;
1454d94272fSCarson Labrado     boost::beast::flat_static_buffer<httpReadBufferSize> buffer;
146039a47e3SCarson Labrado     Response res;
1476eaa1d2fSSunitha Harish 
148f52c03c1SCarson Labrado     // Ascync callables
149039a47e3SCarson Labrado     std::function<void(bool, uint32_t, Response&)> callback;
150f8ca6d79SEd Tanous 
151f8ca6d79SEd Tanous #ifdef BMCWEB_DBUS_DNS_RESOLVER
152e1452beaSEd Tanous     using Resolver = async_resolve::Resolver;
153f8ca6d79SEd Tanous #else
154f8ca6d79SEd Tanous     using Resolver = boost::asio::ip::tcp::resolver;
155f8ca6d79SEd Tanous #endif
156f8ca6d79SEd Tanous     Resolver resolver;
157f8ca6d79SEd Tanous 
1580d5f5cf4SEd Tanous     boost::asio::ip::tcp::socket conn;
1590d5f5cf4SEd Tanous     std::optional<boost::beast::ssl_stream<boost::asio::ip::tcp::socket&>>
1600d5f5cf4SEd Tanous         sslConn;
161e38778a5SAppaRao Puli 
162f52c03c1SCarson Labrado     boost::asio::steady_timer timer;
16384b35604SEd Tanous 
164f52c03c1SCarson Labrado     friend class ConnectionPool;
165bd030d0aSAppaRao Puli 
16629a82b08SSunitha Harish     void doResolve()
16729a82b08SSunitha Harish     {
16829a82b08SSunitha Harish         state = ConnState::resolveInProgress;
169a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Trying to resolve: {}, id: {}", host, connId);
17029a82b08SSunitha Harish 
171a716aa74SEd Tanous         resolver.async_resolve(host.encoded_host_address(), host.port(),
1723d36e3a5SEd Tanous                                std::bind_front(&ConnectionInfo::afterResolve,
1733d36e3a5SEd Tanous                                                this, shared_from_this()));
1743d36e3a5SEd Tanous     }
1753d36e3a5SEd Tanous 
176f8ca6d79SEd Tanous     void afterResolve(const std::shared_ptr<ConnectionInfo>& /*self*/,
177f8ca6d79SEd Tanous                       const boost::system::error_code& ec,
178f8ca6d79SEd Tanous                       const Resolver::results_type& endpointList)
1793d36e3a5SEd Tanous     {
18026f6976fSEd Tanous         if (ec || (endpointList.empty()))
18129a82b08SSunitha Harish         {
182a716aa74SEd Tanous             BMCWEB_LOG_ERROR("Resolve failed: {} {}", ec.message(), host);
1833d36e3a5SEd Tanous             state = ConnState::resolveFailed;
1843d36e3a5SEd Tanous             waitAndRetry();
18529a82b08SSunitha Harish             return;
18629a82b08SSunitha Harish         }
187a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Resolved {}, id: {}", host, connId);
1882a5689a7SAppaRao Puli         state = ConnState::connectInProgress;
1892a5689a7SAppaRao Puli 
190a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Trying to connect to: {}, id: {}", host, connId);
191b00dcc27SEd Tanous 
1920d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
1930d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
1940d5f5cf4SEd Tanous 
1950d5f5cf4SEd Tanous         boost::asio::async_connect(
1960d5f5cf4SEd Tanous             conn, endpointList,
197e38778a5SAppaRao Puli             std::bind_front(&ConnectionInfo::afterConnect, this,
198e38778a5SAppaRao Puli                             shared_from_this()));
199e38778a5SAppaRao Puli     }
200e38778a5SAppaRao Puli 
201e38778a5SAppaRao Puli     void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/,
20281c4e330SEd Tanous                       const boost::beast::error_code& ec,
203e38778a5SAppaRao Puli                       const boost::asio::ip::tcp::endpoint& endpoint)
204e38778a5SAppaRao Puli     {
205513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
206513d1ffcSCarson Labrado         // this branch
207513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
208513d1ffcSCarson Labrado         {
209513d1ffcSCarson Labrado             return;
210513d1ffcSCarson Labrado         }
211513d1ffcSCarson Labrado 
2120d5f5cf4SEd Tanous         timer.cancel();
2132a5689a7SAppaRao Puli         if (ec)
2142a5689a7SAppaRao Puli         {
21562598e31SEd Tanous             BMCWEB_LOG_ERROR("Connect {}:{}, id: {} failed: {}",
216a716aa74SEd Tanous                              endpoint.address().to_string(), endpoint.port(),
217a716aa74SEd Tanous                              connId, ec.message());
218e38778a5SAppaRao Puli             state = ConnState::connectFailed;
219e38778a5SAppaRao Puli             waitAndRetry();
2202a5689a7SAppaRao Puli             return;
2212a5689a7SAppaRao Puli         }
222a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Connected to: {}:{}, id: {}",
223a716aa74SEd Tanous                          endpoint.address().to_string(), endpoint.port(),
224a716aa74SEd Tanous                          connId);
225e38778a5SAppaRao Puli         if (sslConn)
226e38778a5SAppaRao Puli         {
2270d5f5cf4SEd Tanous             doSslHandshake();
228e38778a5SAppaRao Puli             return;
229e38778a5SAppaRao Puli         }
230e38778a5SAppaRao Puli         state = ConnState::connected;
231e38778a5SAppaRao Puli         sendMessage();
232e38778a5SAppaRao Puli     }
233e38778a5SAppaRao Puli 
2340d5f5cf4SEd Tanous     void doSslHandshake()
235e38778a5SAppaRao Puli     {
236e38778a5SAppaRao Puli         if (!sslConn)
237e38778a5SAppaRao Puli         {
238e38778a5SAppaRao Puli             return;
239e38778a5SAppaRao Puli         }
240e38778a5SAppaRao Puli         state = ConnState::handshakeInProgress;
2410d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
2420d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
243e38778a5SAppaRao Puli         sslConn->async_handshake(
244e38778a5SAppaRao Puli             boost::asio::ssl::stream_base::client,
245e38778a5SAppaRao Puli             std::bind_front(&ConnectionInfo::afterSslHandshake, this,
246e38778a5SAppaRao Puli                             shared_from_this()));
247e38778a5SAppaRao Puli     }
248e38778a5SAppaRao Puli 
249e38778a5SAppaRao Puli     void afterSslHandshake(const std::shared_ptr<ConnectionInfo>& /*self*/,
25081c4e330SEd Tanous                            const boost::beast::error_code& ec)
251e38778a5SAppaRao Puli     {
252513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
253513d1ffcSCarson Labrado         // this branch
254513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
255513d1ffcSCarson Labrado         {
256513d1ffcSCarson Labrado             return;
257513d1ffcSCarson Labrado         }
258513d1ffcSCarson Labrado 
2590d5f5cf4SEd Tanous         timer.cancel();
260e38778a5SAppaRao Puli         if (ec)
261e38778a5SAppaRao Puli         {
262a716aa74SEd Tanous             BMCWEB_LOG_ERROR("SSL Handshake failed - id: {} error: {}", connId,
263a716aa74SEd Tanous                              ec.message());
264e38778a5SAppaRao Puli             state = ConnState::handshakeFailed;
265e38778a5SAppaRao Puli             waitAndRetry();
266e38778a5SAppaRao Puli             return;
267e38778a5SAppaRao Puli         }
268a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("SSL Handshake successful - id: {}", connId);
269e38778a5SAppaRao Puli         state = ConnState::connected;
270e38778a5SAppaRao Puli         sendMessage();
2712a5689a7SAppaRao Puli     }
2722a5689a7SAppaRao Puli 
273f52c03c1SCarson Labrado     void sendMessage()
2742a5689a7SAppaRao Puli     {
2752a5689a7SAppaRao Puli         state = ConnState::sendInProgress;
2762a5689a7SAppaRao Puli 
277bd030d0aSAppaRao Puli         // Set a timeout on the operation
2780d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
2790d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
280bd030d0aSAppaRao Puli 
281bd030d0aSAppaRao Puli         // Send the HTTP request to the remote host
282e38778a5SAppaRao Puli         if (sslConn)
283e38778a5SAppaRao Puli         {
284e38778a5SAppaRao Puli             boost::beast::http::async_write(
285e38778a5SAppaRao Puli                 *sslConn, req,
286e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterWrite, this,
287e38778a5SAppaRao Puli                                 shared_from_this()));
288e38778a5SAppaRao Puli         }
289e38778a5SAppaRao Puli         else
290e38778a5SAppaRao Puli         {
291bd030d0aSAppaRao Puli             boost::beast::http::async_write(
292bd030d0aSAppaRao Puli                 conn, req,
293e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterWrite, this,
294e38778a5SAppaRao Puli                                 shared_from_this()));
295e38778a5SAppaRao Puli         }
296e38778a5SAppaRao Puli     }
297e38778a5SAppaRao Puli 
298e38778a5SAppaRao Puli     void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/,
299e38778a5SAppaRao Puli                     const boost::beast::error_code& ec, size_t bytesTransferred)
300e38778a5SAppaRao Puli     {
301513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
302513d1ffcSCarson Labrado         // this branch
303513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
304513d1ffcSCarson Labrado         {
305513d1ffcSCarson Labrado             return;
306513d1ffcSCarson Labrado         }
307513d1ffcSCarson Labrado 
3080d5f5cf4SEd Tanous         timer.cancel();
309bd030d0aSAppaRao Puli         if (ec)
310bd030d0aSAppaRao Puli         {
311a716aa74SEd Tanous             BMCWEB_LOG_ERROR("sendMessage() failed: {} {}", ec.message(), host);
312e38778a5SAppaRao Puli             state = ConnState::sendFailed;
313e38778a5SAppaRao Puli             waitAndRetry();
314bd030d0aSAppaRao Puli             return;
315bd030d0aSAppaRao Puli         }
31662598e31SEd Tanous         BMCWEB_LOG_DEBUG("sendMessage() bytes transferred: {}",
31762598e31SEd Tanous                          bytesTransferred);
318bd030d0aSAppaRao Puli 
319e38778a5SAppaRao Puli         recvMessage();
320bd030d0aSAppaRao Puli     }
321bd030d0aSAppaRao Puli 
322bd030d0aSAppaRao Puli     void recvMessage()
323bd030d0aSAppaRao Puli     {
3246eaa1d2fSSunitha Harish         state = ConnState::recvInProgress;
3256eaa1d2fSSunitha Harish 
326e01d0c36SEd Tanous         parser_type& thisParser = parser.emplace(std::piecewise_construct,
327e01d0c36SEd Tanous                                                  std::make_tuple());
328d14a48ffSCarson Labrado 
329e01d0c36SEd Tanous         thisParser.body_limit(connPolicy->requestByteLimit);
3306eaa1d2fSSunitha Harish 
3310d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
3320d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
3330d5f5cf4SEd Tanous 
334bd030d0aSAppaRao Puli         // Receive the HTTP response
335e38778a5SAppaRao Puli         if (sslConn)
336e38778a5SAppaRao Puli         {
337e38778a5SAppaRao Puli             boost::beast::http::async_read(
338e01d0c36SEd Tanous                 *sslConn, buffer, thisParser,
339e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterRead, this,
340e38778a5SAppaRao Puli                                 shared_from_this()));
341e38778a5SAppaRao Puli         }
342e38778a5SAppaRao Puli         else
343e38778a5SAppaRao Puli         {
344bd030d0aSAppaRao Puli             boost::beast::http::async_read(
345e01d0c36SEd Tanous                 conn, buffer, thisParser,
346e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterRead, this,
347e38778a5SAppaRao Puli                                 shared_from_this()));
348e38778a5SAppaRao Puli         }
349e38778a5SAppaRao Puli     }
350e38778a5SAppaRao Puli 
351e38778a5SAppaRao Puli     void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/,
352e38778a5SAppaRao Puli                    const boost::beast::error_code& ec,
353e38778a5SAppaRao Puli                    const std::size_t& bytesTransferred)
354e38778a5SAppaRao Puli     {
355513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
356513d1ffcSCarson Labrado         // this branch
357513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
358513d1ffcSCarson Labrado         {
359513d1ffcSCarson Labrado             return;
360513d1ffcSCarson Labrado         }
361513d1ffcSCarson Labrado 
3620d5f5cf4SEd Tanous         timer.cancel();
363e38778a5SAppaRao Puli         if (ec && ec != boost::asio::ssl::error::stream_truncated)
364bd030d0aSAppaRao Puli         {
365a716aa74SEd Tanous             BMCWEB_LOG_ERROR("recvMessage() failed: {} from {}", ec.message(),
366a716aa74SEd Tanous                              host);
367e38778a5SAppaRao Puli             state = ConnState::recvFailed;
368e38778a5SAppaRao Puli             waitAndRetry();
369bd030d0aSAppaRao Puli             return;
370bd030d0aSAppaRao Puli         }
37162598e31SEd Tanous         BMCWEB_LOG_DEBUG("recvMessage() bytes transferred: {}",
37262598e31SEd Tanous                          bytesTransferred);
373e01d0c36SEd Tanous         if (!parser)
374e01d0c36SEd Tanous         {
375e01d0c36SEd Tanous             return;
376e01d0c36SEd Tanous         }
37762598e31SEd Tanous         BMCWEB_LOG_DEBUG("recvMessage() data: {}", parser->get().body());
378bd030d0aSAppaRao Puli 
379e38778a5SAppaRao Puli         unsigned int respCode = parser->get().result_int();
38062598e31SEd Tanous         BMCWEB_LOG_DEBUG("recvMessage() Header Response Code: {}", respCode);
3816eaa1d2fSSunitha Harish 
382a7a80296SCarson Labrado         // Make sure the received response code is valid as defined by
383a7a80296SCarson Labrado         // the associated retry policy
384d14a48ffSCarson Labrado         if (connPolicy->invalidResp(respCode))
3856eaa1d2fSSunitha Harish         {
3866eaa1d2fSSunitha Harish             // The listener failed to receive the Sent-Event
38762598e31SEd Tanous             BMCWEB_LOG_ERROR(
38862598e31SEd Tanous                 "recvMessage() Listener Failed to "
389a716aa74SEd Tanous                 "receive Sent-Event. Header Response Code: {} from {}",
390a716aa74SEd Tanous                 respCode, host);
391e38778a5SAppaRao Puli             state = ConnState::recvFailed;
392e38778a5SAppaRao Puli             waitAndRetry();
3936eaa1d2fSSunitha Harish             return;
3946eaa1d2fSSunitha Harish         }
395bd030d0aSAppaRao Puli 
396f52c03c1SCarson Labrado         // Send is successful
397f52c03c1SCarson Labrado         // Reset the counter just in case this was after retrying
398e38778a5SAppaRao Puli         retryCount = 0;
3996eaa1d2fSSunitha Harish 
4006eaa1d2fSSunitha Harish         // Keep the connection alive if server supports it
4016eaa1d2fSSunitha Harish         // Else close the connection
40262598e31SEd Tanous         BMCWEB_LOG_DEBUG("recvMessage() keepalive : {}", parser->keep_alive());
4036eaa1d2fSSunitha Harish 
404039a47e3SCarson Labrado         // Copy the response into a Response object so that it can be
405039a47e3SCarson Labrado         // processed by the callback function.
406*27b0cf90SEd Tanous         res.response = parser->release();
407e38778a5SAppaRao Puli         callback(parser->keep_alive(), connId, res);
408513d1ffcSCarson Labrado         res.clear();
409bd030d0aSAppaRao Puli     }
410bd030d0aSAppaRao Puli 
4110d5f5cf4SEd Tanous     static void onTimeout(const std::weak_ptr<ConnectionInfo>& weakSelf,
4125e7e2dc5SEd Tanous                           const boost::system::error_code& ec)
4130d5f5cf4SEd Tanous     {
4140d5f5cf4SEd Tanous         if (ec == boost::asio::error::operation_aborted)
4150d5f5cf4SEd Tanous         {
41662598e31SEd Tanous             BMCWEB_LOG_DEBUG(
41762598e31SEd Tanous                 "async_wait failed since the operation is aborted");
4180d5f5cf4SEd Tanous             return;
4190d5f5cf4SEd Tanous         }
4200d5f5cf4SEd Tanous         if (ec)
4210d5f5cf4SEd Tanous         {
42262598e31SEd Tanous             BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
423*27b0cf90SEd Tanous             // If the timer fails, we need to close the socket anyway, same
424*27b0cf90SEd Tanous             // as if it expired.
4250d5f5cf4SEd Tanous         }
4260d5f5cf4SEd Tanous         std::shared_ptr<ConnectionInfo> self = weakSelf.lock();
4270d5f5cf4SEd Tanous         if (self == nullptr)
4280d5f5cf4SEd Tanous         {
4290d5f5cf4SEd Tanous             return;
4300d5f5cf4SEd Tanous         }
4310d5f5cf4SEd Tanous         self->waitAndRetry();
4320d5f5cf4SEd Tanous     }
4330d5f5cf4SEd Tanous 
4346eaa1d2fSSunitha Harish     void waitAndRetry()
435bd030d0aSAppaRao Puli     {
436d14a48ffSCarson Labrado         if ((retryCount >= connPolicy->maxRetryAttempts) ||
437e38778a5SAppaRao Puli             (state == ConnState::sslInitFailed))
4382a5689a7SAppaRao Puli         {
439a716aa74SEd Tanous             BMCWEB_LOG_ERROR("Maximum number of retries reached. {}", host);
44062598e31SEd Tanous             BMCWEB_LOG_DEBUG("Retry policy: {}", connPolicy->retryPolicyAction);
441039a47e3SCarson Labrado 
442d14a48ffSCarson Labrado             if (connPolicy->retryPolicyAction == "TerminateAfterRetries")
443fe44eb0bSAyushi Smriti             {
444fe44eb0bSAyushi Smriti                 // TODO: delete subscription
445fe44eb0bSAyushi Smriti                 state = ConnState::terminated;
446fe44eb0bSAyushi Smriti             }
447d14a48ffSCarson Labrado             if (connPolicy->retryPolicyAction == "SuspendRetries")
448fe44eb0bSAyushi Smriti             {
4492a5689a7SAppaRao Puli                 state = ConnState::suspended;
4502a5689a7SAppaRao Puli             }
451513d1ffcSCarson Labrado 
452513d1ffcSCarson Labrado             // We want to return a 502 to indicate there was an error with
453513d1ffcSCarson Labrado             // the external server
454513d1ffcSCarson Labrado             res.result(boost::beast::http::status::bad_gateway);
455513d1ffcSCarson Labrado             callback(false, connId, res);
456513d1ffcSCarson Labrado             res.clear();
457513d1ffcSCarson Labrado 
458*27b0cf90SEd Tanous             // Reset the retrycount to zero so that client can try
459*27b0cf90SEd Tanous             // connecting again if needed
460fe44eb0bSAyushi Smriti             retryCount = 0;
4612a5689a7SAppaRao Puli             return;
4622a5689a7SAppaRao Puli         }
4632a5689a7SAppaRao Puli 
4642a5689a7SAppaRao Puli         retryCount++;
465fe44eb0bSAyushi Smriti 
46662598e31SEd Tanous         BMCWEB_LOG_DEBUG("Attempt retry after {} seconds. RetryCount = {}",
467a716aa74SEd Tanous                          connPolicy->retryIntervalSecs.count(), retryCount);
468d14a48ffSCarson Labrado         timer.expires_after(connPolicy->retryIntervalSecs);
4693d36e3a5SEd Tanous         timer.async_wait(std::bind_front(&ConnectionInfo::onTimerDone, this,
4703d36e3a5SEd Tanous                                          shared_from_this()));
4713d36e3a5SEd Tanous     }
4723d36e3a5SEd Tanous 
4733d36e3a5SEd Tanous     void onTimerDone(const std::shared_ptr<ConnectionInfo>& /*self*/,
4743d36e3a5SEd Tanous                      const boost::system::error_code& ec)
4753d36e3a5SEd Tanous     {
4766eaa1d2fSSunitha Harish         if (ec == boost::asio::error::operation_aborted)
4776eaa1d2fSSunitha Harish         {
47862598e31SEd Tanous             BMCWEB_LOG_DEBUG(
47962598e31SEd Tanous                 "async_wait failed since the operation is aborted{}",
48062598e31SEd Tanous                 ec.message());
4816eaa1d2fSSunitha Harish         }
4826eaa1d2fSSunitha Harish         else if (ec)
4836eaa1d2fSSunitha Harish         {
48462598e31SEd Tanous             BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
4856eaa1d2fSSunitha Harish             // Ignore the error and continue the retry loop to attempt
4866eaa1d2fSSunitha Harish             // sending the event as per the retry policy
4876eaa1d2fSSunitha Harish         }
4886eaa1d2fSSunitha Harish 
489f52c03c1SCarson Labrado         // Let's close the connection and restart from resolve.
4903d36e3a5SEd Tanous         doClose(true);
4912a5689a7SAppaRao Puli     }
4922a5689a7SAppaRao Puli 
493e38778a5SAppaRao Puli     void shutdownConn(bool retry)
494fe44eb0bSAyushi Smriti     {
495f52c03c1SCarson Labrado         boost::beast::error_code ec;
4960d5f5cf4SEd Tanous         conn.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
497f52c03c1SCarson Labrado         conn.close();
498f52c03c1SCarson Labrado 
499f52c03c1SCarson Labrado         // not_connected happens sometimes so don't bother reporting it.
500f52c03c1SCarson Labrado         if (ec && ec != boost::beast::errc::not_connected)
5012a5689a7SAppaRao Puli         {
502a716aa74SEd Tanous             BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId,
50362598e31SEd Tanous                              ec.message());
5046eaa1d2fSSunitha Harish         }
5055cab68f3SCarson Labrado         else
5065cab68f3SCarson Labrado         {
507a716aa74SEd Tanous             BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId);
5085cab68f3SCarson Labrado         }
509ca723762SEd Tanous 
510e38778a5SAppaRao Puli         if (retry)
51192a74e56SAppaRao Puli         {
512f52c03c1SCarson Labrado             // Now let's try to resend the data
513f52c03c1SCarson Labrado             state = ConnState::retry;
5140d5f5cf4SEd Tanous             doResolve();
515e38778a5SAppaRao Puli         }
516e38778a5SAppaRao Puli         else
517e38778a5SAppaRao Puli         {
518e38778a5SAppaRao Puli             state = ConnState::closed;
519e38778a5SAppaRao Puli         }
520e38778a5SAppaRao Puli     }
521e38778a5SAppaRao Puli 
522e38778a5SAppaRao Puli     void doClose(bool retry = false)
523e38778a5SAppaRao Puli     {
524e38778a5SAppaRao Puli         if (!sslConn)
525e38778a5SAppaRao Puli         {
526e38778a5SAppaRao Puli             shutdownConn(retry);
527e38778a5SAppaRao Puli             return;
528e38778a5SAppaRao Puli         }
529e38778a5SAppaRao Puli 
530e38778a5SAppaRao Puli         sslConn->async_shutdown(
531e38778a5SAppaRao Puli             std::bind_front(&ConnectionInfo::afterSslShutdown, this,
532e38778a5SAppaRao Puli                             shared_from_this(), retry));
533e38778a5SAppaRao Puli     }
534e38778a5SAppaRao Puli 
535e38778a5SAppaRao Puli     void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/,
536e38778a5SAppaRao Puli                           bool retry, const boost::system::error_code& ec)
537e38778a5SAppaRao Puli     {
538e38778a5SAppaRao Puli         if (ec)
539e38778a5SAppaRao Puli         {
540a716aa74SEd Tanous             BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId,
54162598e31SEd Tanous                              ec.message());
542e38778a5SAppaRao Puli         }
543e38778a5SAppaRao Puli         else
544e38778a5SAppaRao Puli         {
545a716aa74SEd Tanous             BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId);
546e38778a5SAppaRao Puli         }
547e38778a5SAppaRao Puli         shutdownConn(retry);
548e38778a5SAppaRao Puli     }
549e38778a5SAppaRao Puli 
550e38778a5SAppaRao Puli     void setCipherSuiteTLSext()
551e38778a5SAppaRao Puli     {
552e38778a5SAppaRao Puli         if (!sslConn)
553e38778a5SAppaRao Puli         {
554e38778a5SAppaRao Puli             return;
555e38778a5SAppaRao Puli         }
556e7c2991eSRavi Teja 
557e7c2991eSRavi Teja         if (host.host_type() != boost::urls::host_type::name)
558e7c2991eSRavi Teja         {
559e7c2991eSRavi Teja             // Avoid setting SNI hostname if its IP address
560e7c2991eSRavi Teja             return;
561e7c2991eSRavi Teja         }
562e7c2991eSRavi Teja         // Create a null terminated string for SSL
563a716aa74SEd Tanous         std::string hostname(host.encoded_host_address());
564e38778a5SAppaRao Puli         // NOTE: The SSL_set_tlsext_host_name is defined in tlsv1.h header
565e38778a5SAppaRao Puli         // file but its having old style casting (name is cast to void*).
566e38778a5SAppaRao Puli         // Since bmcweb compiler treats all old-style-cast as error, its
567e38778a5SAppaRao Puli         // causing the build failure. So replaced the same macro inline and
568e38778a5SAppaRao Puli         // did corrected the code by doing static_cast to viod*. This has to
569e38778a5SAppaRao Puli         // be fixed in openssl library in long run. Set SNI Hostname (many
570e38778a5SAppaRao Puli         // hosts need this to handshake successfully)
571e38778a5SAppaRao Puli         if (SSL_ctrl(sslConn->native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME,
572e38778a5SAppaRao Puli                      TLSEXT_NAMETYPE_host_name,
573a716aa74SEd Tanous                      static_cast<void*>(hostname.data())) == 0)
574e38778a5SAppaRao Puli 
575e38778a5SAppaRao Puli         {
576e38778a5SAppaRao Puli             boost::beast::error_code ec{static_cast<int>(::ERR_get_error()),
577e38778a5SAppaRao Puli                                         boost::asio::error::get_ssl_category()};
578e38778a5SAppaRao Puli 
579a716aa74SEd Tanous             BMCWEB_LOG_ERROR("SSL_set_tlsext_host_name {}, id: {} failed: {}",
580a716aa74SEd Tanous                              host, connId, ec.message());
581e38778a5SAppaRao Puli             // Set state as sslInit failed so that we close the connection
582e38778a5SAppaRao Puli             // and take appropriate action as per retry configuration.
583e38778a5SAppaRao Puli             state = ConnState::sslInitFailed;
584e38778a5SAppaRao Puli             waitAndRetry();
585e38778a5SAppaRao Puli             return;
586e38778a5SAppaRao Puli         }
587bd030d0aSAppaRao Puli     }
588bd030d0aSAppaRao Puli 
589bd030d0aSAppaRao Puli   public:
590d14a48ffSCarson Labrado     explicit ConnectionInfo(
591d14a48ffSCarson Labrado         boost::asio::io_context& iocIn, const std::string& idIn,
592d14a48ffSCarson Labrado         const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
593a716aa74SEd Tanous         boost::urls::url_view hostIn, unsigned int connIdIn) :
5948a592810SEd Tanous         subId(idIn),
595a716aa74SEd Tanous         connPolicy(connPolicyIn), host(hostIn), connId(connIdIn),
596a716aa74SEd Tanous         resolver(iocIn), conn(iocIn), timer(iocIn)
597e38778a5SAppaRao Puli     {
598a716aa74SEd Tanous         if (host.scheme() == "https")
599e38778a5SAppaRao Puli         {
600e38778a5SAppaRao Puli             std::optional<boost::asio::ssl::context> sslCtx =
601e38778a5SAppaRao Puli                 ensuressl::getSSLClientContext();
602e38778a5SAppaRao Puli 
603e38778a5SAppaRao Puli             if (!sslCtx)
604e38778a5SAppaRao Puli             {
605a716aa74SEd Tanous                 BMCWEB_LOG_ERROR("prepareSSLContext failed - {}, id: {}", host,
606a716aa74SEd Tanous                                  connId);
607e38778a5SAppaRao Puli                 // Don't retry if failure occurs while preparing SSL context
608*27b0cf90SEd Tanous                 // such as certificate is invalid or set cipher failure or
609*27b0cf90SEd Tanous                 // set host name failure etc... Setting conn state to
610*27b0cf90SEd Tanous                 // sslInitFailed and connection state will be transitioned
611*27b0cf90SEd Tanous                 // to next state depending on retry policy set by
612*27b0cf90SEd Tanous                 // subscription.
613e38778a5SAppaRao Puli                 state = ConnState::sslInitFailed;
614e38778a5SAppaRao Puli                 waitAndRetry();
615e38778a5SAppaRao Puli                 return;
616e38778a5SAppaRao Puli             }
617e38778a5SAppaRao Puli             sslConn.emplace(conn, *sslCtx);
618e38778a5SAppaRao Puli             setCipherSuiteTLSext();
619e38778a5SAppaRao Puli         }
620e38778a5SAppaRao Puli     }
621f52c03c1SCarson Labrado };
622bd030d0aSAppaRao Puli 
623f52c03c1SCarson Labrado class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
624bd030d0aSAppaRao Puli {
625f52c03c1SCarson Labrado   private:
626f52c03c1SCarson Labrado     boost::asio::io_context& ioc;
627e38778a5SAppaRao Puli     std::string id;
628d14a48ffSCarson Labrado     std::shared_ptr<ConnectionPolicy> connPolicy;
629a716aa74SEd Tanous     boost::urls::url destIP;
630f52c03c1SCarson Labrado     std::vector<std::shared_ptr<ConnectionInfo>> connections;
631f52c03c1SCarson Labrado     boost::container::devector<PendingRequest> requestQueue;
632f52c03c1SCarson Labrado 
633f52c03c1SCarson Labrado     friend class HttpClient;
634f52c03c1SCarson Labrado 
635244256ccSCarson Labrado     // Configure a connections's request, callback, and retry info in
636244256ccSCarson Labrado     // preparation to begin sending the request
637f52c03c1SCarson Labrado     void setConnProps(ConnectionInfo& conn)
638bd030d0aSAppaRao Puli     {
639f52c03c1SCarson Labrado         if (requestQueue.empty())
640f52c03c1SCarson Labrado         {
64162598e31SEd Tanous             BMCWEB_LOG_ERROR(
64262598e31SEd Tanous                 "setConnProps() should not have been called when requestQueue is empty");
643bd030d0aSAppaRao Puli             return;
644bd030d0aSAppaRao Puli         }
645bd030d0aSAppaRao Puli 
646244256ccSCarson Labrado         auto nextReq = requestQueue.front();
647244256ccSCarson Labrado         conn.req = std::move(nextReq.req);
648244256ccSCarson Labrado         conn.callback = std::move(nextReq.callback);
649f52c03c1SCarson Labrado 
650a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Setting properties for connection {}, id: {}",
651a716aa74SEd Tanous                          conn.host, conn.connId);
652f52c03c1SCarson Labrado 
653f52c03c1SCarson Labrado         // We can remove the request from the queue at this point
654f52c03c1SCarson Labrado         requestQueue.pop_front();
655f52c03c1SCarson Labrado     }
656f52c03c1SCarson Labrado 
657f52c03c1SCarson Labrado     // Gets called as part of callback after request is sent
658f52c03c1SCarson Labrado     // Reuses the connection if there are any requests waiting to be sent
659f52c03c1SCarson Labrado     // Otherwise closes the connection if it is not a keep-alive
660f52c03c1SCarson Labrado     void sendNext(bool keepAlive, uint32_t connId)
661f52c03c1SCarson Labrado     {
662f52c03c1SCarson Labrado         auto conn = connections[connId];
66346a81465SCarson Labrado 
66446a81465SCarson Labrado         // Allow the connection's handler to be deleted
66546a81465SCarson Labrado         // This is needed because of Redfish Aggregation passing an
66646a81465SCarson Labrado         // AsyncResponse shared_ptr to this callback
66746a81465SCarson Labrado         conn->callback = nullptr;
66846a81465SCarson Labrado 
669f52c03c1SCarson Labrado         // Reuse the connection to send the next request in the queue
670f52c03c1SCarson Labrado         if (!requestQueue.empty())
671f52c03c1SCarson Labrado         {
67262598e31SEd Tanous             BMCWEB_LOG_DEBUG(
673a716aa74SEd Tanous                 "{} requests remaining in queue for {}, reusing connnection {}",
674a716aa74SEd Tanous                 requestQueue.size(), destIP, connId);
675f52c03c1SCarson Labrado 
676f52c03c1SCarson Labrado             setConnProps(*conn);
677f52c03c1SCarson Labrado 
678f52c03c1SCarson Labrado             if (keepAlive)
679f52c03c1SCarson Labrado             {
680f52c03c1SCarson Labrado                 conn->sendMessage();
6812a5689a7SAppaRao Puli             }
6822a5689a7SAppaRao Puli             else
6832a5689a7SAppaRao Puli             {
684f52c03c1SCarson Labrado                 // Server is not keep-alive enabled so we need to close the
685f52c03c1SCarson Labrado                 // connection and then start over from resolve
686f52c03c1SCarson Labrado                 conn->doClose();
687f52c03c1SCarson Labrado                 conn->doResolve();
688f52c03c1SCarson Labrado             }
689f52c03c1SCarson Labrado             return;
690f52c03c1SCarson Labrado         }
691f52c03c1SCarson Labrado 
692f52c03c1SCarson Labrado         // No more messages to send so close the connection if necessary
693f52c03c1SCarson Labrado         if (keepAlive)
694f52c03c1SCarson Labrado         {
695f52c03c1SCarson Labrado             conn->state = ConnState::idle;
696f52c03c1SCarson Labrado         }
697f52c03c1SCarson Labrado         else
698f52c03c1SCarson Labrado         {
699f52c03c1SCarson Labrado             // Abort the connection since server is not keep-alive enabled
700f52c03c1SCarson Labrado             conn->state = ConnState::abortConnection;
701f52c03c1SCarson Labrado             conn->doClose();
7022a5689a7SAppaRao Puli         }
703bd030d0aSAppaRao Puli     }
704bd030d0aSAppaRao Puli 
705a716aa74SEd Tanous     void sendData(std::string&& data, boost::urls::url_view destUri,
706244256ccSCarson Labrado                   const boost::beast::http::fields& httpHeader,
707244256ccSCarson Labrado                   const boost::beast::http::verb verb,
7086b3db60dSEd Tanous                   const std::function<void(Response&)>& resHandler)
709fe44eb0bSAyushi Smriti     {
710244256ccSCarson Labrado         // Construct the request to be sent
711244256ccSCarson Labrado         boost::beast::http::request<boost::beast::http::string_body> thisReq(
712a716aa74SEd Tanous             verb, destUri.encoded_target(), 11, "", httpHeader);
713a716aa74SEd Tanous         thisReq.set(boost::beast::http::field::host,
714a716aa74SEd Tanous                     destUri.encoded_host_address());
715244256ccSCarson Labrado         thisReq.keep_alive(true);
716244256ccSCarson Labrado         thisReq.body() = std::move(data);
717244256ccSCarson Labrado         thisReq.prepare_payload();
7183d36e3a5SEd Tanous         auto cb = std::bind_front(&ConnectionPool::afterSendData,
7193d36e3a5SEd Tanous                                   weak_from_this(), resHandler);
720f52c03c1SCarson Labrado         // Reuse an existing connection if one is available
721f52c03c1SCarson Labrado         for (unsigned int i = 0; i < connections.size(); i++)
722fe44eb0bSAyushi Smriti         {
723f52c03c1SCarson Labrado             auto conn = connections[i];
724f52c03c1SCarson Labrado             if ((conn->state == ConnState::idle) ||
725f52c03c1SCarson Labrado                 (conn->state == ConnState::initialized) ||
726f52c03c1SCarson Labrado                 (conn->state == ConnState::closed))
727f52c03c1SCarson Labrado             {
728244256ccSCarson Labrado                 conn->req = std::move(thisReq);
729f52c03c1SCarson Labrado                 conn->callback = std::move(cb);
730a716aa74SEd Tanous                 std::string commonMsg = std::format("{} from pool {}", i, id);
731f52c03c1SCarson Labrado 
732f52c03c1SCarson Labrado                 if (conn->state == ConnState::idle)
733f52c03c1SCarson Labrado                 {
73462598e31SEd Tanous                     BMCWEB_LOG_DEBUG("Grabbing idle connection {}", commonMsg);
735f52c03c1SCarson Labrado                     conn->sendMessage();
736f52c03c1SCarson Labrado                 }
737f52c03c1SCarson Labrado                 else
738f52c03c1SCarson Labrado                 {
73962598e31SEd Tanous                     BMCWEB_LOG_DEBUG("Reusing existing connection {}",
74062598e31SEd Tanous                                      commonMsg);
741f52c03c1SCarson Labrado                     conn->doResolve();
742f52c03c1SCarson Labrado                 }
743f52c03c1SCarson Labrado                 return;
744f52c03c1SCarson Labrado             }
745f52c03c1SCarson Labrado         }
746f52c03c1SCarson Labrado 
747*27b0cf90SEd Tanous         // All connections in use so create a new connection or add request
748*27b0cf90SEd Tanous         // to the queue
749d14a48ffSCarson Labrado         if (connections.size() < connPolicy->maxConnections)
750f52c03c1SCarson Labrado         {
751a716aa74SEd Tanous             BMCWEB_LOG_DEBUG("Adding new connection to pool {}", id);
752f52c03c1SCarson Labrado             auto conn = addConnection();
753244256ccSCarson Labrado             conn->req = std::move(thisReq);
754f52c03c1SCarson Labrado             conn->callback = std::move(cb);
755f52c03c1SCarson Labrado             conn->doResolve();
756f52c03c1SCarson Labrado         }
757f52c03c1SCarson Labrado         else if (requestQueue.size() < maxRequestQueueSize)
758f52c03c1SCarson Labrado         {
759a716aa74SEd Tanous             BMCWEB_LOG_DEBUG("Max pool size reached. Adding data to queue {}",
760a716aa74SEd Tanous                              id);
761d14a48ffSCarson Labrado             requestQueue.emplace_back(std::move(thisReq), std::move(cb));
762f52c03c1SCarson Labrado         }
763f52c03c1SCarson Labrado         else
764f52c03c1SCarson Labrado         {
765*27b0cf90SEd Tanous             // If we can't buffer the request then we should let the
766*27b0cf90SEd Tanous             // callback handle a 429 Too Many Requests dummy response
76762598e31SEd Tanous             BMCWEB_LOG_ERROR("{}:{} request queue full.  Dropping request.",
768a716aa74SEd Tanous                              id);
76943e14d38SCarson Labrado             Response dummyRes;
77043e14d38SCarson Labrado             dummyRes.result(boost::beast::http::status::too_many_requests);
77143e14d38SCarson Labrado             resHandler(dummyRes);
772f52c03c1SCarson Labrado         }
773f52c03c1SCarson Labrado     }
774f52c03c1SCarson Labrado 
7753d36e3a5SEd Tanous     // Callback to be called once the request has been sent
7763d36e3a5SEd Tanous     static void afterSendData(const std::weak_ptr<ConnectionPool>& weakSelf,
7773d36e3a5SEd Tanous                               const std::function<void(Response&)>& resHandler,
7783d36e3a5SEd Tanous                               bool keepAlive, uint32_t connId, Response& res)
7793d36e3a5SEd Tanous     {
7803d36e3a5SEd Tanous         // Allow provided callback to perform additional processing of the
7813d36e3a5SEd Tanous         // request
7823d36e3a5SEd Tanous         resHandler(res);
7833d36e3a5SEd Tanous 
7843d36e3a5SEd Tanous         // If requests remain in the queue then we want to reuse this
7853d36e3a5SEd Tanous         // connection to send the next request
7863d36e3a5SEd Tanous         std::shared_ptr<ConnectionPool> self = weakSelf.lock();
7873d36e3a5SEd Tanous         if (!self)
7883d36e3a5SEd Tanous         {
78962598e31SEd Tanous             BMCWEB_LOG_CRITICAL("{} Failed to capture connection",
79062598e31SEd Tanous                                 logPtr(self.get()));
7913d36e3a5SEd Tanous             return;
7923d36e3a5SEd Tanous         }
7933d36e3a5SEd Tanous 
7943d36e3a5SEd Tanous         self->sendNext(keepAlive, connId);
7953d36e3a5SEd Tanous     }
7963d36e3a5SEd Tanous 
797f52c03c1SCarson Labrado     std::shared_ptr<ConnectionInfo>& addConnection()
798f52c03c1SCarson Labrado     {
799f52c03c1SCarson Labrado         unsigned int newId = static_cast<unsigned int>(connections.size());
800f52c03c1SCarson Labrado 
801e38778a5SAppaRao Puli         auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>(
802a716aa74SEd Tanous             ioc, id, connPolicy, destIP, newId));
803f52c03c1SCarson Labrado 
804a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Added connection {} to pool {}",
805a716aa74SEd Tanous                          connections.size() - 1, id);
806f52c03c1SCarson Labrado 
807f52c03c1SCarson Labrado         return ret;
808f52c03c1SCarson Labrado     }
809f52c03c1SCarson Labrado 
810f52c03c1SCarson Labrado   public:
811d14a48ffSCarson Labrado     explicit ConnectionPool(
812d14a48ffSCarson Labrado         boost::asio::io_context& iocIn, const std::string& idIn,
813d14a48ffSCarson Labrado         const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
814a716aa74SEd Tanous         boost::urls::url_view destIPIn) :
8158a592810SEd Tanous         ioc(iocIn),
816a716aa74SEd Tanous         id(idIn), connPolicy(connPolicyIn), destIP(destIPIn)
817f52c03c1SCarson Labrado     {
818a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Initializing connection pool for {}", id);
819f52c03c1SCarson Labrado 
820f52c03c1SCarson Labrado         // Initialize the pool with a single connection
821f52c03c1SCarson Labrado         addConnection();
822fe44eb0bSAyushi Smriti     }
823bd030d0aSAppaRao Puli };
824bd030d0aSAppaRao Puli 
825f52c03c1SCarson Labrado class HttpClient
826f52c03c1SCarson Labrado {
827f52c03c1SCarson Labrado   private:
828f52c03c1SCarson Labrado     std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
829f52c03c1SCarson Labrado         connectionPools;
830f8ca6d79SEd Tanous     boost::asio::io_context& ioc;
831d14a48ffSCarson Labrado     std::shared_ptr<ConnectionPolicy> connPolicy;
832f52c03c1SCarson Labrado 
833039a47e3SCarson Labrado     // Used as a dummy callback by sendData() in order to call
834039a47e3SCarson Labrado     // sendDataWithCallback()
83502cad96eSEd Tanous     static void genericResHandler(const Response& res)
836039a47e3SCarson Labrado     {
83762598e31SEd Tanous         BMCWEB_LOG_DEBUG("Response handled with return code: {}",
838a716aa74SEd Tanous                          res.resultInt());
8394ee8e211SEd Tanous     }
840039a47e3SCarson Labrado 
841f52c03c1SCarson Labrado   public:
842d14a48ffSCarson Labrado     HttpClient() = delete;
843f8ca6d79SEd Tanous     explicit HttpClient(boost::asio::io_context& iocIn,
844f8ca6d79SEd Tanous                         const std::shared_ptr<ConnectionPolicy>& connPolicyIn) :
845f8ca6d79SEd Tanous         ioc(iocIn),
846d14a48ffSCarson Labrado         connPolicy(connPolicyIn)
847d14a48ffSCarson Labrado     {}
848f8ca6d79SEd Tanous 
849f52c03c1SCarson Labrado     HttpClient(const HttpClient&) = delete;
850f52c03c1SCarson Labrado     HttpClient& operator=(const HttpClient&) = delete;
851f52c03c1SCarson Labrado     HttpClient(HttpClient&&) = delete;
852f52c03c1SCarson Labrado     HttpClient& operator=(HttpClient&&) = delete;
853f52c03c1SCarson Labrado     ~HttpClient() = default;
854f52c03c1SCarson Labrado 
855a716aa74SEd Tanous     // Send a request to destIP where additional processing of the
856039a47e3SCarson Labrado     // result is not required
857a716aa74SEd Tanous     void sendData(std::string&& data, boost::urls::url_view destUri,
858f52c03c1SCarson Labrado                   const boost::beast::http::fields& httpHeader,
859d14a48ffSCarson Labrado                   const boost::beast::http::verb verb)
860f52c03c1SCarson Labrado     {
861e38778a5SAppaRao Puli         const std::function<void(Response&)> cb = genericResHandler;
862a716aa74SEd Tanous         sendDataWithCallback(std::move(data), destUri, httpHeader, verb, cb);
863039a47e3SCarson Labrado     }
864039a47e3SCarson Labrado 
865a716aa74SEd Tanous     // Send request to destIP and use the provided callback to
866039a47e3SCarson Labrado     // handle the response
867a716aa74SEd Tanous     void sendDataWithCallback(std::string&& data, boost::urls::url_view destUrl,
868039a47e3SCarson Labrado                               const boost::beast::http::fields& httpHeader,
869244256ccSCarson Labrado                               const boost::beast::http::verb verb,
8706b3db60dSEd Tanous                               const std::function<void(Response&)>& resHandler)
871039a47e3SCarson Labrado     {
872a716aa74SEd Tanous         std::string clientKey = std::format("{}://{}", destUrl.scheme(),
873a716aa74SEd Tanous                                             destUrl.encoded_host_and_port());
874d14a48ffSCarson Labrado         auto pool = connectionPools.try_emplace(clientKey);
875d14a48ffSCarson Labrado         if (pool.first->second == nullptr)
876f52c03c1SCarson Labrado         {
877d14a48ffSCarson Labrado             pool.first->second = std::make_shared<ConnectionPool>(
878a716aa74SEd Tanous                 ioc, clientKey, connPolicy, destUrl);
879f52c03c1SCarson Labrado         }
880*27b0cf90SEd Tanous         // Send the data using either the existing connection pool or the
881*27b0cf90SEd Tanous         // newly created connection pool
882a716aa74SEd Tanous         pool.first->second->sendData(std::move(data), destUrl, httpHeader, verb,
883e38778a5SAppaRao Puli                                      resHandler);
884f52c03c1SCarson Labrado     }
885f52c03c1SCarson Labrado };
886bd030d0aSAppaRao Puli } // namespace crow
887