xref: /openbmc/bmcweb/http/http_client.hpp (revision 4d69861f)
1bd030d0aSAppaRao Puli /*
2bd030d0aSAppaRao Puli // Copyright (c) 2020 Intel Corporation
3bd030d0aSAppaRao Puli //
4bd030d0aSAppaRao Puli // Licensed under the Apache License, Version 2.0 (the "License");
5bd030d0aSAppaRao Puli // you may not use this file except in compliance with the License.
6bd030d0aSAppaRao Puli // You may obtain a copy of the License at
7bd030d0aSAppaRao Puli //
8bd030d0aSAppaRao Puli //      http://www.apache.org/licenses/LICENSE-2.0
9bd030d0aSAppaRao Puli //
10bd030d0aSAppaRao Puli // Unless required by applicable law or agreed to in writing, software
11bd030d0aSAppaRao Puli // distributed under the License is distributed on an "AS IS" BASIS,
12bd030d0aSAppaRao Puli // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13bd030d0aSAppaRao Puli // See the License for the specific language governing permissions and
14bd030d0aSAppaRao Puli // limitations under the License.
15bd030d0aSAppaRao Puli */
16bd030d0aSAppaRao Puli #pragma once
1777665bdaSNan Zhou 
1877665bdaSNan Zhou #include "async_resolve.hpp"
19b2896149SEd Tanous #include "http_body.hpp"
2077665bdaSNan Zhou #include "http_response.hpp"
213ccb3adbSEd Tanous #include "logging.hpp"
223ccb3adbSEd Tanous #include "ssl_key_handler.hpp"
2377665bdaSNan Zhou 
240d5f5cf4SEd Tanous #include <boost/asio/connect.hpp>
25bb49eb5cSEd Tanous #include <boost/asio/io_context.hpp>
2629a82b08SSunitha Harish #include <boost/asio/ip/address.hpp>
2729a82b08SSunitha Harish #include <boost/asio/ip/basic_endpoint.hpp>
28bb49eb5cSEd Tanous #include <boost/asio/ip/tcp.hpp>
29e38778a5SAppaRao Puli #include <boost/asio/ssl/context.hpp>
30e38778a5SAppaRao Puli #include <boost/asio/ssl/error.hpp>
31d43cd0caSEd Tanous #include <boost/asio/steady_timer.hpp>
32bb49eb5cSEd Tanous #include <boost/beast/core/flat_static_buffer.hpp>
33d43cd0caSEd Tanous #include <boost/beast/http/message.hpp>
34*4d69861fSEd Tanous #include <boost/beast/http/message_generator.hpp>
35bb49eb5cSEd Tanous #include <boost/beast/http/parser.hpp>
36bb49eb5cSEd Tanous #include <boost/beast/http/read.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>
4227b0cf90SEd Tanous #include <boost/url/format.hpp>
4327b0cf90SEd Tanous #include <boost/url/url.hpp>
444a7fbefdSEd Tanous #include <boost/url/url_view_base.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 {
5527b0cf90SEd Tanous // With Redfish Aggregation it is assumed we will connect to another
5627b0cf90SEd 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 
10027b0cf90SEd Tanous // We need to allow retry information to be set before a message has been
10127b0cf90SEd 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 {
120b2896149SEd Tanous     boost::beast::http::request<bmcweb::HttpBody> req;
121039a47e3SCarson Labrado     std::function<void(bool, uint32_t, Response&)> callback;
122039a47e3SCarson Labrado     PendingRequest(
123b2896149SEd Tanous         boost::beast::http::request<bmcweb::HttpBody>&& 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
142b2896149SEd Tanous     http::request<bmcweb::HttpBody> req;
143b2896149SEd Tanous     using parser_type = http::response_parser<bmcweb::HttpBody>;
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 
151f3cb5df9SAbhilash Raju     boost::asio::io_context& ioc;
152f3cb5df9SAbhilash Raju 
153f8ca6d79SEd Tanous #ifdef BMCWEB_DBUS_DNS_RESOLVER
154e1452beaSEd Tanous     using Resolver = async_resolve::Resolver;
155f8ca6d79SEd Tanous #else
156f8ca6d79SEd Tanous     using Resolver = boost::asio::ip::tcp::resolver;
157f8ca6d79SEd Tanous #endif
158f8ca6d79SEd Tanous     Resolver resolver;
159f8ca6d79SEd Tanous 
1600d5f5cf4SEd Tanous     boost::asio::ip::tcp::socket conn;
1610d5f5cf4SEd Tanous     std::optional<boost::beast::ssl_stream<boost::asio::ip::tcp::socket&>>
1620d5f5cf4SEd Tanous         sslConn;
163e38778a5SAppaRao Puli 
164f52c03c1SCarson Labrado     boost::asio::steady_timer timer;
16584b35604SEd Tanous 
166f52c03c1SCarson Labrado     friend class ConnectionPool;
167bd030d0aSAppaRao Puli 
16829a82b08SSunitha Harish     void doResolve()
16929a82b08SSunitha Harish     {
17029a82b08SSunitha Harish         state = ConnState::resolveInProgress;
171a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Trying to resolve: {}, id: {}", host, connId);
17229a82b08SSunitha Harish 
173a716aa74SEd Tanous         resolver.async_resolve(host.encoded_host_address(), host.port(),
1743d36e3a5SEd Tanous                                std::bind_front(&ConnectionInfo::afterResolve,
1753d36e3a5SEd Tanous                                                this, shared_from_this()));
1763d36e3a5SEd Tanous     }
1773d36e3a5SEd Tanous 
178f8ca6d79SEd Tanous     void afterResolve(const std::shared_ptr<ConnectionInfo>& /*self*/,
179f8ca6d79SEd Tanous                       const boost::system::error_code& ec,
180f8ca6d79SEd Tanous                       const Resolver::results_type& endpointList)
1813d36e3a5SEd Tanous     {
18226f6976fSEd Tanous         if (ec || (endpointList.empty()))
18329a82b08SSunitha Harish         {
184a716aa74SEd Tanous             BMCWEB_LOG_ERROR("Resolve failed: {} {}", ec.message(), host);
1853d36e3a5SEd Tanous             state = ConnState::resolveFailed;
1863d36e3a5SEd Tanous             waitAndRetry();
18729a82b08SSunitha Harish             return;
18829a82b08SSunitha Harish         }
189a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Resolved {}, id: {}", host, connId);
1902a5689a7SAppaRao Puli         state = ConnState::connectInProgress;
1912a5689a7SAppaRao Puli 
192a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Trying to connect to: {}, id: {}", host, connId);
193b00dcc27SEd Tanous 
1940d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
1950d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
1960d5f5cf4SEd Tanous 
1970d5f5cf4SEd Tanous         boost::asio::async_connect(
1980d5f5cf4SEd Tanous             conn, endpointList,
199e38778a5SAppaRao Puli             std::bind_front(&ConnectionInfo::afterConnect, this,
200e38778a5SAppaRao Puli                             shared_from_this()));
201e38778a5SAppaRao Puli     }
202e38778a5SAppaRao Puli 
203e38778a5SAppaRao Puli     void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/,
20481c4e330SEd Tanous                       const boost::beast::error_code& ec,
205e38778a5SAppaRao Puli                       const boost::asio::ip::tcp::endpoint& endpoint)
206e38778a5SAppaRao Puli     {
207513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
208513d1ffcSCarson Labrado         // this branch
209513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
210513d1ffcSCarson Labrado         {
211513d1ffcSCarson Labrado             return;
212513d1ffcSCarson Labrado         }
213513d1ffcSCarson Labrado 
2140d5f5cf4SEd Tanous         timer.cancel();
2152a5689a7SAppaRao Puli         if (ec)
2162a5689a7SAppaRao Puli         {
21762598e31SEd Tanous             BMCWEB_LOG_ERROR("Connect {}:{}, id: {} failed: {}",
218a716aa74SEd Tanous                              endpoint.address().to_string(), endpoint.port(),
219a716aa74SEd Tanous                              connId, ec.message());
220e38778a5SAppaRao Puli             state = ConnState::connectFailed;
221e38778a5SAppaRao Puli             waitAndRetry();
2222a5689a7SAppaRao Puli             return;
2232a5689a7SAppaRao Puli         }
224a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Connected to: {}:{}, id: {}",
225a716aa74SEd Tanous                          endpoint.address().to_string(), endpoint.port(),
226a716aa74SEd Tanous                          connId);
227e38778a5SAppaRao Puli         if (sslConn)
228e38778a5SAppaRao Puli         {
2290d5f5cf4SEd Tanous             doSslHandshake();
230e38778a5SAppaRao Puli             return;
231e38778a5SAppaRao Puli         }
232e38778a5SAppaRao Puli         state = ConnState::connected;
233e38778a5SAppaRao Puli         sendMessage();
234e38778a5SAppaRao Puli     }
235e38778a5SAppaRao Puli 
2360d5f5cf4SEd Tanous     void doSslHandshake()
237e38778a5SAppaRao Puli     {
238e38778a5SAppaRao Puli         if (!sslConn)
239e38778a5SAppaRao Puli         {
240e38778a5SAppaRao Puli             return;
241e38778a5SAppaRao Puli         }
242e38778a5SAppaRao Puli         state = ConnState::handshakeInProgress;
2430d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
2440d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
245e38778a5SAppaRao Puli         sslConn->async_handshake(
246e38778a5SAppaRao Puli             boost::asio::ssl::stream_base::client,
247e38778a5SAppaRao Puli             std::bind_front(&ConnectionInfo::afterSslHandshake, this,
248e38778a5SAppaRao Puli                             shared_from_this()));
249e38778a5SAppaRao Puli     }
250e38778a5SAppaRao Puli 
251e38778a5SAppaRao Puli     void afterSslHandshake(const std::shared_ptr<ConnectionInfo>& /*self*/,
25281c4e330SEd Tanous                            const boost::beast::error_code& ec)
253e38778a5SAppaRao Puli     {
254513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
255513d1ffcSCarson Labrado         // this branch
256513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
257513d1ffcSCarson Labrado         {
258513d1ffcSCarson Labrado             return;
259513d1ffcSCarson Labrado         }
260513d1ffcSCarson Labrado 
2610d5f5cf4SEd Tanous         timer.cancel();
262e38778a5SAppaRao Puli         if (ec)
263e38778a5SAppaRao Puli         {
264a716aa74SEd Tanous             BMCWEB_LOG_ERROR("SSL Handshake failed - id: {} error: {}", connId,
265a716aa74SEd Tanous                              ec.message());
266e38778a5SAppaRao Puli             state = ConnState::handshakeFailed;
267e38778a5SAppaRao Puli             waitAndRetry();
268e38778a5SAppaRao Puli             return;
269e38778a5SAppaRao Puli         }
270a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("SSL Handshake successful - id: {}", connId);
271e38778a5SAppaRao Puli         state = ConnState::connected;
272e38778a5SAppaRao Puli         sendMessage();
2732a5689a7SAppaRao Puli     }
2742a5689a7SAppaRao Puli 
275f52c03c1SCarson Labrado     void sendMessage()
2762a5689a7SAppaRao Puli     {
2772a5689a7SAppaRao Puli         state = ConnState::sendInProgress;
2782a5689a7SAppaRao Puli 
279bd030d0aSAppaRao Puli         // Set a timeout on the operation
2800d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
2810d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
282*4d69861fSEd Tanous         boost::beast::http::message_generator messageGenerator(std::move(req));
283bd030d0aSAppaRao Puli         // Send the HTTP request to the remote host
284e38778a5SAppaRao Puli         if (sslConn)
285e38778a5SAppaRao Puli         {
286*4d69861fSEd Tanous             boost::beast::async_write(
287*4d69861fSEd Tanous                 *sslConn, std::move(messageGenerator),
288e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterWrite, this,
289e38778a5SAppaRao Puli                                 shared_from_this()));
290e38778a5SAppaRao Puli         }
291e38778a5SAppaRao Puli         else
292e38778a5SAppaRao Puli         {
293*4d69861fSEd Tanous             boost::beast::async_write(
294*4d69861fSEd Tanous                 conn, std::move(messageGenerator),
295e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterWrite, this,
296e38778a5SAppaRao Puli                                 shared_from_this()));
297e38778a5SAppaRao Puli         }
298e38778a5SAppaRao Puli     }
299e38778a5SAppaRao Puli 
300e38778a5SAppaRao Puli     void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/,
301e38778a5SAppaRao Puli                     const boost::beast::error_code& ec, size_t bytesTransferred)
302e38778a5SAppaRao Puli     {
303513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
304513d1ffcSCarson Labrado         // this branch
305513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
306513d1ffcSCarson Labrado         {
307513d1ffcSCarson Labrado             return;
308513d1ffcSCarson Labrado         }
309513d1ffcSCarson Labrado 
3100d5f5cf4SEd Tanous         timer.cancel();
311bd030d0aSAppaRao Puli         if (ec)
312bd030d0aSAppaRao Puli         {
313a716aa74SEd Tanous             BMCWEB_LOG_ERROR("sendMessage() failed: {} {}", ec.message(), host);
314e38778a5SAppaRao Puli             state = ConnState::sendFailed;
315e38778a5SAppaRao Puli             waitAndRetry();
316bd030d0aSAppaRao Puli             return;
317bd030d0aSAppaRao Puli         }
31862598e31SEd Tanous         BMCWEB_LOG_DEBUG("sendMessage() bytes transferred: {}",
31962598e31SEd Tanous                          bytesTransferred);
320bd030d0aSAppaRao Puli 
321e38778a5SAppaRao Puli         recvMessage();
322bd030d0aSAppaRao Puli     }
323bd030d0aSAppaRao Puli 
324bd030d0aSAppaRao Puli     void recvMessage()
325bd030d0aSAppaRao Puli     {
3266eaa1d2fSSunitha Harish         state = ConnState::recvInProgress;
3276eaa1d2fSSunitha Harish 
328e01d0c36SEd Tanous         parser_type& thisParser = parser.emplace(std::piecewise_construct,
329e01d0c36SEd Tanous                                                  std::make_tuple());
330d14a48ffSCarson Labrado 
331e01d0c36SEd Tanous         thisParser.body_limit(connPolicy->requestByteLimit);
3326eaa1d2fSSunitha Harish 
3330d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
3340d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
3350d5f5cf4SEd Tanous 
336bd030d0aSAppaRao Puli         // Receive the HTTP response
337e38778a5SAppaRao Puli         if (sslConn)
338e38778a5SAppaRao Puli         {
339e38778a5SAppaRao Puli             boost::beast::http::async_read(
340e01d0c36SEd Tanous                 *sslConn, buffer, thisParser,
341e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterRead, this,
342e38778a5SAppaRao Puli                                 shared_from_this()));
343e38778a5SAppaRao Puli         }
344e38778a5SAppaRao Puli         else
345e38778a5SAppaRao Puli         {
346bd030d0aSAppaRao Puli             boost::beast::http::async_read(
347e01d0c36SEd Tanous                 conn, buffer, thisParser,
348e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterRead, this,
349e38778a5SAppaRao Puli                                 shared_from_this()));
350e38778a5SAppaRao Puli         }
351e38778a5SAppaRao Puli     }
352e38778a5SAppaRao Puli 
353e38778a5SAppaRao Puli     void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/,
354e38778a5SAppaRao Puli                    const boost::beast::error_code& ec,
355e38778a5SAppaRao Puli                    const std::size_t& bytesTransferred)
356e38778a5SAppaRao Puli     {
357513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
358513d1ffcSCarson Labrado         // this branch
359513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
360513d1ffcSCarson Labrado         {
361513d1ffcSCarson Labrado             return;
362513d1ffcSCarson Labrado         }
363513d1ffcSCarson Labrado 
3640d5f5cf4SEd Tanous         timer.cancel();
365e38778a5SAppaRao Puli         if (ec && ec != boost::asio::ssl::error::stream_truncated)
366bd030d0aSAppaRao Puli         {
367a716aa74SEd Tanous             BMCWEB_LOG_ERROR("recvMessage() failed: {} from {}", ec.message(),
368a716aa74SEd Tanous                              host);
369e38778a5SAppaRao Puli             state = ConnState::recvFailed;
370e38778a5SAppaRao Puli             waitAndRetry();
371bd030d0aSAppaRao Puli             return;
372bd030d0aSAppaRao Puli         }
37362598e31SEd Tanous         BMCWEB_LOG_DEBUG("recvMessage() bytes transferred: {}",
37462598e31SEd Tanous                          bytesTransferred);
375e01d0c36SEd Tanous         if (!parser)
376e01d0c36SEd Tanous         {
377e01d0c36SEd Tanous             return;
378e01d0c36SEd Tanous         }
37952e31629SEd Tanous         BMCWEB_LOG_DEBUG("recvMessage() data: {}", parser->get().body().str());
380bd030d0aSAppaRao Puli 
381e38778a5SAppaRao Puli         unsigned int respCode = parser->get().result_int();
38262598e31SEd Tanous         BMCWEB_LOG_DEBUG("recvMessage() Header Response Code: {}", respCode);
3836eaa1d2fSSunitha Harish 
384f3cb5df9SAbhilash Raju         // Handle the case of stream_truncated.  Some servers close the ssl
385f3cb5df9SAbhilash Raju         // connection uncleanly, so check to see if we got a full response
386f3cb5df9SAbhilash Raju         // before we handle this as an error.
387f3cb5df9SAbhilash Raju         if (!parser->is_done())
388f3cb5df9SAbhilash Raju         {
389f3cb5df9SAbhilash Raju             state = ConnState::recvFailed;
390f3cb5df9SAbhilash Raju             waitAndRetry();
391f3cb5df9SAbhilash Raju             return;
392f3cb5df9SAbhilash Raju         }
393f3cb5df9SAbhilash Raju 
394a7a80296SCarson Labrado         // Make sure the received response code is valid as defined by
395a7a80296SCarson Labrado         // the associated retry policy
396d14a48ffSCarson Labrado         if (connPolicy->invalidResp(respCode))
3976eaa1d2fSSunitha Harish         {
3986eaa1d2fSSunitha Harish             // The listener failed to receive the Sent-Event
39962598e31SEd Tanous             BMCWEB_LOG_ERROR(
40062598e31SEd Tanous                 "recvMessage() Listener Failed to "
401a716aa74SEd Tanous                 "receive Sent-Event. Header Response Code: {} from {}",
402a716aa74SEd Tanous                 respCode, host);
403e38778a5SAppaRao Puli             state = ConnState::recvFailed;
404e38778a5SAppaRao Puli             waitAndRetry();
4056eaa1d2fSSunitha Harish             return;
4066eaa1d2fSSunitha Harish         }
407bd030d0aSAppaRao Puli 
408f52c03c1SCarson Labrado         // Send is successful
409f52c03c1SCarson Labrado         // Reset the counter just in case this was after retrying
410e38778a5SAppaRao Puli         retryCount = 0;
4116eaa1d2fSSunitha Harish 
4126eaa1d2fSSunitha Harish         // Keep the connection alive if server supports it
4136eaa1d2fSSunitha Harish         // Else close the connection
41462598e31SEd Tanous         BMCWEB_LOG_DEBUG("recvMessage() keepalive : {}", parser->keep_alive());
4156eaa1d2fSSunitha Harish 
416039a47e3SCarson Labrado         // Copy the response into a Response object so that it can be
417039a47e3SCarson Labrado         // processed by the callback function.
41827b0cf90SEd Tanous         res.response = parser->release();
419e38778a5SAppaRao Puli         callback(parser->keep_alive(), connId, res);
420513d1ffcSCarson Labrado         res.clear();
421bd030d0aSAppaRao Puli     }
422bd030d0aSAppaRao Puli 
4230d5f5cf4SEd Tanous     static void onTimeout(const std::weak_ptr<ConnectionInfo>& weakSelf,
4245e7e2dc5SEd Tanous                           const boost::system::error_code& ec)
4250d5f5cf4SEd Tanous     {
4260d5f5cf4SEd Tanous         if (ec == boost::asio::error::operation_aborted)
4270d5f5cf4SEd Tanous         {
42862598e31SEd Tanous             BMCWEB_LOG_DEBUG(
42962598e31SEd Tanous                 "async_wait failed since the operation is aborted");
4300d5f5cf4SEd Tanous             return;
4310d5f5cf4SEd Tanous         }
4320d5f5cf4SEd Tanous         if (ec)
4330d5f5cf4SEd Tanous         {
43462598e31SEd Tanous             BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
43527b0cf90SEd Tanous             // If the timer fails, we need to close the socket anyway, same
43627b0cf90SEd Tanous             // as if it expired.
4370d5f5cf4SEd Tanous         }
4380d5f5cf4SEd Tanous         std::shared_ptr<ConnectionInfo> self = weakSelf.lock();
4390d5f5cf4SEd Tanous         if (self == nullptr)
4400d5f5cf4SEd Tanous         {
4410d5f5cf4SEd Tanous             return;
4420d5f5cf4SEd Tanous         }
4430d5f5cf4SEd Tanous         self->waitAndRetry();
4440d5f5cf4SEd Tanous     }
4450d5f5cf4SEd Tanous 
4466eaa1d2fSSunitha Harish     void waitAndRetry()
447bd030d0aSAppaRao Puli     {
448d14a48ffSCarson Labrado         if ((retryCount >= connPolicy->maxRetryAttempts) ||
449e38778a5SAppaRao Puli             (state == ConnState::sslInitFailed))
4502a5689a7SAppaRao Puli         {
451a716aa74SEd Tanous             BMCWEB_LOG_ERROR("Maximum number of retries reached. {}", host);
45262598e31SEd Tanous             BMCWEB_LOG_DEBUG("Retry policy: {}", connPolicy->retryPolicyAction);
453039a47e3SCarson Labrado 
454d14a48ffSCarson Labrado             if (connPolicy->retryPolicyAction == "TerminateAfterRetries")
455fe44eb0bSAyushi Smriti             {
456fe44eb0bSAyushi Smriti                 // TODO: delete subscription
457fe44eb0bSAyushi Smriti                 state = ConnState::terminated;
458fe44eb0bSAyushi Smriti             }
459d14a48ffSCarson Labrado             if (connPolicy->retryPolicyAction == "SuspendRetries")
460fe44eb0bSAyushi Smriti             {
4612a5689a7SAppaRao Puli                 state = ConnState::suspended;
4622a5689a7SAppaRao Puli             }
463513d1ffcSCarson Labrado 
464513d1ffcSCarson Labrado             // We want to return a 502 to indicate there was an error with
465513d1ffcSCarson Labrado             // the external server
466513d1ffcSCarson Labrado             res.result(boost::beast::http::status::bad_gateway);
467513d1ffcSCarson Labrado             callback(false, connId, res);
468513d1ffcSCarson Labrado             res.clear();
469513d1ffcSCarson Labrado 
47027b0cf90SEd Tanous             // Reset the retrycount to zero so that client can try
47127b0cf90SEd Tanous             // connecting again if needed
472fe44eb0bSAyushi Smriti             retryCount = 0;
4732a5689a7SAppaRao Puli             return;
4742a5689a7SAppaRao Puli         }
4752a5689a7SAppaRao Puli 
4762a5689a7SAppaRao Puli         retryCount++;
477fe44eb0bSAyushi Smriti 
47862598e31SEd Tanous         BMCWEB_LOG_DEBUG("Attempt retry after {} seconds. RetryCount = {}",
479a716aa74SEd Tanous                          connPolicy->retryIntervalSecs.count(), retryCount);
480d14a48ffSCarson Labrado         timer.expires_after(connPolicy->retryIntervalSecs);
4813d36e3a5SEd Tanous         timer.async_wait(std::bind_front(&ConnectionInfo::onTimerDone, this,
4823d36e3a5SEd Tanous                                          shared_from_this()));
4833d36e3a5SEd Tanous     }
4843d36e3a5SEd Tanous 
4853d36e3a5SEd Tanous     void onTimerDone(const std::shared_ptr<ConnectionInfo>& /*self*/,
4863d36e3a5SEd Tanous                      const boost::system::error_code& ec)
4873d36e3a5SEd Tanous     {
4886eaa1d2fSSunitha Harish         if (ec == boost::asio::error::operation_aborted)
4896eaa1d2fSSunitha Harish         {
49062598e31SEd Tanous             BMCWEB_LOG_DEBUG(
49162598e31SEd Tanous                 "async_wait failed since the operation is aborted{}",
49262598e31SEd Tanous                 ec.message());
4936eaa1d2fSSunitha Harish         }
4946eaa1d2fSSunitha Harish         else if (ec)
4956eaa1d2fSSunitha Harish         {
49662598e31SEd Tanous             BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
4976eaa1d2fSSunitha Harish             // Ignore the error and continue the retry loop to attempt
4986eaa1d2fSSunitha Harish             // sending the event as per the retry policy
4996eaa1d2fSSunitha Harish         }
5006eaa1d2fSSunitha Harish 
501f52c03c1SCarson Labrado         // Let's close the connection and restart from resolve.
502f3cb5df9SAbhilash Raju         shutdownConn(true);
503f3cb5df9SAbhilash Raju     }
504f3cb5df9SAbhilash Raju 
505f3cb5df9SAbhilash Raju     void restartConnection()
506f3cb5df9SAbhilash Raju     {
507f3cb5df9SAbhilash Raju         BMCWEB_LOG_DEBUG("{}, id: {}  restartConnection", host,
508f3cb5df9SAbhilash Raju                          std::to_string(connId));
509f3cb5df9SAbhilash Raju         initializeConnection(host.scheme() == "https");
510f3cb5df9SAbhilash Raju         doResolve();
5112a5689a7SAppaRao Puli     }
5122a5689a7SAppaRao Puli 
513e38778a5SAppaRao Puli     void shutdownConn(bool retry)
514fe44eb0bSAyushi Smriti     {
515f52c03c1SCarson Labrado         boost::beast::error_code ec;
5160d5f5cf4SEd Tanous         conn.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
517f52c03c1SCarson Labrado         conn.close();
518f52c03c1SCarson Labrado 
519f52c03c1SCarson Labrado         // not_connected happens sometimes so don't bother reporting it.
520f52c03c1SCarson Labrado         if (ec && ec != boost::beast::errc::not_connected)
5212a5689a7SAppaRao Puli         {
522a716aa74SEd Tanous             BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId,
52362598e31SEd Tanous                              ec.message());
5246eaa1d2fSSunitha Harish         }
5255cab68f3SCarson Labrado         else
5265cab68f3SCarson Labrado         {
527a716aa74SEd Tanous             BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId);
5285cab68f3SCarson Labrado         }
529ca723762SEd Tanous 
530e38778a5SAppaRao Puli         if (retry)
53192a74e56SAppaRao Puli         {
532f52c03c1SCarson Labrado             // Now let's try to resend the data
533f52c03c1SCarson Labrado             state = ConnState::retry;
534f3cb5df9SAbhilash Raju             restartConnection();
535e38778a5SAppaRao Puli         }
536e38778a5SAppaRao Puli         else
537e38778a5SAppaRao Puli         {
538e38778a5SAppaRao Puli             state = ConnState::closed;
539e38778a5SAppaRao Puli         }
540e38778a5SAppaRao Puli     }
541e38778a5SAppaRao Puli 
542e38778a5SAppaRao Puli     void doClose(bool retry = false)
543e38778a5SAppaRao Puli     {
544e38778a5SAppaRao Puli         if (!sslConn)
545e38778a5SAppaRao Puli         {
546e38778a5SAppaRao Puli             shutdownConn(retry);
547e38778a5SAppaRao Puli             return;
548e38778a5SAppaRao Puli         }
549e38778a5SAppaRao Puli 
550e38778a5SAppaRao Puli         sslConn->async_shutdown(
551e38778a5SAppaRao Puli             std::bind_front(&ConnectionInfo::afterSslShutdown, this,
552e38778a5SAppaRao Puli                             shared_from_this(), retry));
553e38778a5SAppaRao Puli     }
554e38778a5SAppaRao Puli 
555e38778a5SAppaRao Puli     void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/,
556e38778a5SAppaRao Puli                           bool retry, const boost::system::error_code& ec)
557e38778a5SAppaRao Puli     {
558e38778a5SAppaRao Puli         if (ec)
559e38778a5SAppaRao Puli         {
560a716aa74SEd Tanous             BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId,
56162598e31SEd Tanous                              ec.message());
562e38778a5SAppaRao Puli         }
563e38778a5SAppaRao Puli         else
564e38778a5SAppaRao Puli         {
565a716aa74SEd Tanous             BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId);
566e38778a5SAppaRao Puli         }
567e38778a5SAppaRao Puli         shutdownConn(retry);
568e38778a5SAppaRao Puli     }
569e38778a5SAppaRao Puli 
570e38778a5SAppaRao Puli     void setCipherSuiteTLSext()
571e38778a5SAppaRao Puli     {
572e38778a5SAppaRao Puli         if (!sslConn)
573e38778a5SAppaRao Puli         {
574e38778a5SAppaRao Puli             return;
575e38778a5SAppaRao Puli         }
576e7c2991eSRavi Teja 
577e7c2991eSRavi Teja         if (host.host_type() != boost::urls::host_type::name)
578e7c2991eSRavi Teja         {
579e7c2991eSRavi Teja             // Avoid setting SNI hostname if its IP address
580e7c2991eSRavi Teja             return;
581e7c2991eSRavi Teja         }
582e7c2991eSRavi Teja         // Create a null terminated string for SSL
583a716aa74SEd Tanous         std::string hostname(host.encoded_host_address());
584e38778a5SAppaRao Puli         // NOTE: The SSL_set_tlsext_host_name is defined in tlsv1.h header
585e38778a5SAppaRao Puli         // file but its having old style casting (name is cast to void*).
586e38778a5SAppaRao Puli         // Since bmcweb compiler treats all old-style-cast as error, its
587e38778a5SAppaRao Puli         // causing the build failure. So replaced the same macro inline and
588e38778a5SAppaRao Puli         // did corrected the code by doing static_cast to viod*. This has to
589e38778a5SAppaRao Puli         // be fixed in openssl library in long run. Set SNI Hostname (many
590e38778a5SAppaRao Puli         // hosts need this to handshake successfully)
591e38778a5SAppaRao Puli         if (SSL_ctrl(sslConn->native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME,
592e38778a5SAppaRao Puli                      TLSEXT_NAMETYPE_host_name,
593a716aa74SEd Tanous                      static_cast<void*>(hostname.data())) == 0)
594e38778a5SAppaRao Puli 
595e38778a5SAppaRao Puli         {
596e38778a5SAppaRao Puli             boost::beast::error_code ec{static_cast<int>(::ERR_get_error()),
597e38778a5SAppaRao Puli                                         boost::asio::error::get_ssl_category()};
598e38778a5SAppaRao Puli 
599a716aa74SEd Tanous             BMCWEB_LOG_ERROR("SSL_set_tlsext_host_name {}, id: {} failed: {}",
600a716aa74SEd Tanous                              host, connId, ec.message());
601e38778a5SAppaRao Puli             // Set state as sslInit failed so that we close the connection
602e38778a5SAppaRao Puli             // and take appropriate action as per retry configuration.
603e38778a5SAppaRao Puli             state = ConnState::sslInitFailed;
604e38778a5SAppaRao Puli             waitAndRetry();
605e38778a5SAppaRao Puli             return;
606e38778a5SAppaRao Puli         }
607bd030d0aSAppaRao Puli     }
608bd030d0aSAppaRao Puli 
609f3cb5df9SAbhilash Raju     void initializeConnection(bool ssl)
610e38778a5SAppaRao Puli     {
611f3cb5df9SAbhilash Raju         conn = boost::asio::ip::tcp::socket(ioc);
612f3cb5df9SAbhilash Raju         if (ssl)
613e38778a5SAppaRao Puli         {
614e38778a5SAppaRao Puli             std::optional<boost::asio::ssl::context> sslCtx =
615e38778a5SAppaRao Puli                 ensuressl::getSSLClientContext();
616e38778a5SAppaRao Puli 
617e38778a5SAppaRao Puli             if (!sslCtx)
618e38778a5SAppaRao Puli             {
619a716aa74SEd Tanous                 BMCWEB_LOG_ERROR("prepareSSLContext failed - {}, id: {}", host,
620a716aa74SEd Tanous                                  connId);
621e38778a5SAppaRao Puli                 // Don't retry if failure occurs while preparing SSL context
62227b0cf90SEd Tanous                 // such as certificate is invalid or set cipher failure or
62327b0cf90SEd Tanous                 // set host name failure etc... Setting conn state to
62427b0cf90SEd Tanous                 // sslInitFailed and connection state will be transitioned
62527b0cf90SEd Tanous                 // to next state depending on retry policy set by
62627b0cf90SEd Tanous                 // subscription.
627e38778a5SAppaRao Puli                 state = ConnState::sslInitFailed;
628e38778a5SAppaRao Puli                 waitAndRetry();
629e38778a5SAppaRao Puli                 return;
630e38778a5SAppaRao Puli             }
631e38778a5SAppaRao Puli             sslConn.emplace(conn, *sslCtx);
632e38778a5SAppaRao Puli             setCipherSuiteTLSext();
633e38778a5SAppaRao Puli         }
634e38778a5SAppaRao Puli     }
635f3cb5df9SAbhilash Raju 
636f3cb5df9SAbhilash Raju   public:
637f3cb5df9SAbhilash Raju     explicit ConnectionInfo(
638f3cb5df9SAbhilash Raju         boost::asio::io_context& iocIn, const std::string& idIn,
639f3cb5df9SAbhilash Raju         const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
6404a7fbefdSEd Tanous         const boost::urls::url_view_base& hostIn, unsigned int connIdIn) :
641f3cb5df9SAbhilash Raju         subId(idIn),
642f3cb5df9SAbhilash Raju         connPolicy(connPolicyIn), host(hostIn), connId(connIdIn), ioc(iocIn),
643f3cb5df9SAbhilash Raju         resolver(iocIn), conn(iocIn), timer(iocIn)
644f3cb5df9SAbhilash Raju     {
645f3cb5df9SAbhilash Raju         initializeConnection(host.scheme() == "https");
646f3cb5df9SAbhilash Raju     }
647f52c03c1SCarson Labrado };
648bd030d0aSAppaRao Puli 
649f52c03c1SCarson Labrado class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
650bd030d0aSAppaRao Puli {
651f52c03c1SCarson Labrado   private:
652f52c03c1SCarson Labrado     boost::asio::io_context& ioc;
653e38778a5SAppaRao Puli     std::string id;
654d14a48ffSCarson Labrado     std::shared_ptr<ConnectionPolicy> connPolicy;
655a716aa74SEd Tanous     boost::urls::url destIP;
656f52c03c1SCarson Labrado     std::vector<std::shared_ptr<ConnectionInfo>> connections;
657f52c03c1SCarson Labrado     boost::container::devector<PendingRequest> requestQueue;
658f52c03c1SCarson Labrado 
659f52c03c1SCarson Labrado     friend class HttpClient;
660f52c03c1SCarson Labrado 
661244256ccSCarson Labrado     // Configure a connections's request, callback, and retry info in
662244256ccSCarson Labrado     // preparation to begin sending the request
663f52c03c1SCarson Labrado     void setConnProps(ConnectionInfo& conn)
664bd030d0aSAppaRao Puli     {
665f52c03c1SCarson Labrado         if (requestQueue.empty())
666f52c03c1SCarson Labrado         {
66762598e31SEd Tanous             BMCWEB_LOG_ERROR(
66862598e31SEd Tanous                 "setConnProps() should not have been called when requestQueue is empty");
669bd030d0aSAppaRao Puli             return;
670bd030d0aSAppaRao Puli         }
671bd030d0aSAppaRao Puli 
67252e31629SEd Tanous         PendingRequest& nextReq = requestQueue.front();
673244256ccSCarson Labrado         conn.req = std::move(nextReq.req);
674244256ccSCarson Labrado         conn.callback = std::move(nextReq.callback);
675f52c03c1SCarson Labrado 
676a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Setting properties for connection {}, id: {}",
677a716aa74SEd Tanous                          conn.host, conn.connId);
678f52c03c1SCarson Labrado 
679f52c03c1SCarson Labrado         // We can remove the request from the queue at this point
680f52c03c1SCarson Labrado         requestQueue.pop_front();
681f52c03c1SCarson Labrado     }
682f52c03c1SCarson Labrado 
683f52c03c1SCarson Labrado     // Gets called as part of callback after request is sent
684f52c03c1SCarson Labrado     // Reuses the connection if there are any requests waiting to be sent
685f52c03c1SCarson Labrado     // Otherwise closes the connection if it is not a keep-alive
686f52c03c1SCarson Labrado     void sendNext(bool keepAlive, uint32_t connId)
687f52c03c1SCarson Labrado     {
688f52c03c1SCarson Labrado         auto conn = connections[connId];
68946a81465SCarson Labrado 
69046a81465SCarson Labrado         // Allow the connection's handler to be deleted
69146a81465SCarson Labrado         // This is needed because of Redfish Aggregation passing an
69246a81465SCarson Labrado         // AsyncResponse shared_ptr to this callback
69346a81465SCarson Labrado         conn->callback = nullptr;
69446a81465SCarson Labrado 
695f52c03c1SCarson Labrado         // Reuse the connection to send the next request in the queue
696f52c03c1SCarson Labrado         if (!requestQueue.empty())
697f52c03c1SCarson Labrado         {
69862598e31SEd Tanous             BMCWEB_LOG_DEBUG(
6998ece0e45SEd Tanous                 "{} requests remaining in queue for {}, reusing connection {}",
700a716aa74SEd Tanous                 requestQueue.size(), destIP, connId);
701f52c03c1SCarson Labrado 
702f52c03c1SCarson Labrado             setConnProps(*conn);
703f52c03c1SCarson Labrado 
704f52c03c1SCarson Labrado             if (keepAlive)
705f52c03c1SCarson Labrado             {
706f52c03c1SCarson Labrado                 conn->sendMessage();
7072a5689a7SAppaRao Puli             }
7082a5689a7SAppaRao Puli             else
7092a5689a7SAppaRao Puli             {
710f52c03c1SCarson Labrado                 // Server is not keep-alive enabled so we need to close the
711f52c03c1SCarson Labrado                 // connection and then start over from resolve
712f52c03c1SCarson Labrado                 conn->doClose();
713f52c03c1SCarson Labrado                 conn->doResolve();
714f52c03c1SCarson Labrado             }
715f52c03c1SCarson Labrado             return;
716f52c03c1SCarson Labrado         }
717f52c03c1SCarson Labrado 
718f52c03c1SCarson Labrado         // No more messages to send so close the connection if necessary
719f52c03c1SCarson Labrado         if (keepAlive)
720f52c03c1SCarson Labrado         {
721f52c03c1SCarson Labrado             conn->state = ConnState::idle;
722f52c03c1SCarson Labrado         }
723f52c03c1SCarson Labrado         else
724f52c03c1SCarson Labrado         {
725f52c03c1SCarson Labrado             // Abort the connection since server is not keep-alive enabled
726f52c03c1SCarson Labrado             conn->state = ConnState::abortConnection;
727f52c03c1SCarson Labrado             conn->doClose();
7282a5689a7SAppaRao Puli         }
729bd030d0aSAppaRao Puli     }
730bd030d0aSAppaRao Puli 
7314a7fbefdSEd Tanous     void sendData(std::string&& data, const boost::urls::url_view_base& destUri,
732244256ccSCarson Labrado                   const boost::beast::http::fields& httpHeader,
733244256ccSCarson Labrado                   const boost::beast::http::verb verb,
7346b3db60dSEd Tanous                   const std::function<void(Response&)>& resHandler)
735fe44eb0bSAyushi Smriti     {
736244256ccSCarson Labrado         // Construct the request to be sent
737b2896149SEd Tanous         boost::beast::http::request<bmcweb::HttpBody> thisReq(
738a716aa74SEd Tanous             verb, destUri.encoded_target(), 11, "", httpHeader);
739a716aa74SEd Tanous         thisReq.set(boost::beast::http::field::host,
740a716aa74SEd Tanous                     destUri.encoded_host_address());
741244256ccSCarson Labrado         thisReq.keep_alive(true);
74252e31629SEd Tanous         thisReq.body().str() = std::move(data);
743244256ccSCarson Labrado         thisReq.prepare_payload();
7443d36e3a5SEd Tanous         auto cb = std::bind_front(&ConnectionPool::afterSendData,
7453d36e3a5SEd Tanous                                   weak_from_this(), resHandler);
746f52c03c1SCarson Labrado         // Reuse an existing connection if one is available
747f52c03c1SCarson Labrado         for (unsigned int i = 0; i < connections.size(); i++)
748fe44eb0bSAyushi Smriti         {
749f52c03c1SCarson Labrado             auto conn = connections[i];
750f52c03c1SCarson Labrado             if ((conn->state == ConnState::idle) ||
751f52c03c1SCarson Labrado                 (conn->state == ConnState::initialized) ||
752f52c03c1SCarson Labrado                 (conn->state == ConnState::closed))
753f52c03c1SCarson Labrado             {
754244256ccSCarson Labrado                 conn->req = std::move(thisReq);
755f52c03c1SCarson Labrado                 conn->callback = std::move(cb);
756a716aa74SEd Tanous                 std::string commonMsg = std::format("{} from pool {}", i, id);
757f52c03c1SCarson Labrado 
758f52c03c1SCarson Labrado                 if (conn->state == ConnState::idle)
759f52c03c1SCarson Labrado                 {
76062598e31SEd Tanous                     BMCWEB_LOG_DEBUG("Grabbing idle connection {}", commonMsg);
761f52c03c1SCarson Labrado                     conn->sendMessage();
762f52c03c1SCarson Labrado                 }
763f52c03c1SCarson Labrado                 else
764f52c03c1SCarson Labrado                 {
76562598e31SEd Tanous                     BMCWEB_LOG_DEBUG("Reusing existing connection {}",
76662598e31SEd Tanous                                      commonMsg);
767f52c03c1SCarson Labrado                     conn->doResolve();
768f52c03c1SCarson Labrado                 }
769f52c03c1SCarson Labrado                 return;
770f52c03c1SCarson Labrado             }
771f52c03c1SCarson Labrado         }
772f52c03c1SCarson Labrado 
77327b0cf90SEd Tanous         // All connections in use so create a new connection or add request
77427b0cf90SEd Tanous         // to the queue
775d14a48ffSCarson Labrado         if (connections.size() < connPolicy->maxConnections)
776f52c03c1SCarson Labrado         {
777a716aa74SEd Tanous             BMCWEB_LOG_DEBUG("Adding new connection to pool {}", id);
778f52c03c1SCarson Labrado             auto conn = addConnection();
779244256ccSCarson Labrado             conn->req = std::move(thisReq);
780f52c03c1SCarson Labrado             conn->callback = std::move(cb);
781f52c03c1SCarson Labrado             conn->doResolve();
782f52c03c1SCarson Labrado         }
783f52c03c1SCarson Labrado         else if (requestQueue.size() < maxRequestQueueSize)
784f52c03c1SCarson Labrado         {
785a716aa74SEd Tanous             BMCWEB_LOG_DEBUG("Max pool size reached. Adding data to queue {}",
786a716aa74SEd Tanous                              id);
787d14a48ffSCarson Labrado             requestQueue.emplace_back(std::move(thisReq), std::move(cb));
788f52c03c1SCarson Labrado         }
789f52c03c1SCarson Labrado         else
790f52c03c1SCarson Labrado         {
79127b0cf90SEd Tanous             // If we can't buffer the request then we should let the
79227b0cf90SEd Tanous             // callback handle a 429 Too Many Requests dummy response
7936ea90760SEd Tanous             BMCWEB_LOG_ERROR("{} request queue full.  Dropping request.", id);
79443e14d38SCarson Labrado             Response dummyRes;
79543e14d38SCarson Labrado             dummyRes.result(boost::beast::http::status::too_many_requests);
79643e14d38SCarson Labrado             resHandler(dummyRes);
797f52c03c1SCarson Labrado         }
798f52c03c1SCarson Labrado     }
799f52c03c1SCarson Labrado 
8003d36e3a5SEd Tanous     // Callback to be called once the request has been sent
8013d36e3a5SEd Tanous     static void afterSendData(const std::weak_ptr<ConnectionPool>& weakSelf,
8023d36e3a5SEd Tanous                               const std::function<void(Response&)>& resHandler,
8033d36e3a5SEd Tanous                               bool keepAlive, uint32_t connId, Response& res)
8043d36e3a5SEd Tanous     {
8053d36e3a5SEd Tanous         // Allow provided callback to perform additional processing of the
8063d36e3a5SEd Tanous         // request
8073d36e3a5SEd Tanous         resHandler(res);
8083d36e3a5SEd Tanous 
8093d36e3a5SEd Tanous         // If requests remain in the queue then we want to reuse this
8103d36e3a5SEd Tanous         // connection to send the next request
8113d36e3a5SEd Tanous         std::shared_ptr<ConnectionPool> self = weakSelf.lock();
8123d36e3a5SEd Tanous         if (!self)
8133d36e3a5SEd Tanous         {
81462598e31SEd Tanous             BMCWEB_LOG_CRITICAL("{} Failed to capture connection",
81562598e31SEd Tanous                                 logPtr(self.get()));
8163d36e3a5SEd Tanous             return;
8173d36e3a5SEd Tanous         }
8183d36e3a5SEd Tanous 
8193d36e3a5SEd Tanous         self->sendNext(keepAlive, connId);
8203d36e3a5SEd Tanous     }
8213d36e3a5SEd Tanous 
822f52c03c1SCarson Labrado     std::shared_ptr<ConnectionInfo>& addConnection()
823f52c03c1SCarson Labrado     {
824f52c03c1SCarson Labrado         unsigned int newId = static_cast<unsigned int>(connections.size());
825f52c03c1SCarson Labrado 
826e38778a5SAppaRao Puli         auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>(
827a716aa74SEd Tanous             ioc, id, connPolicy, destIP, newId));
828f52c03c1SCarson Labrado 
829a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Added connection {} to pool {}",
830a716aa74SEd Tanous                          connections.size() - 1, id);
831f52c03c1SCarson Labrado 
832f52c03c1SCarson Labrado         return ret;
833f52c03c1SCarson Labrado     }
834f52c03c1SCarson Labrado 
835f52c03c1SCarson Labrado   public:
836d14a48ffSCarson Labrado     explicit ConnectionPool(
837d14a48ffSCarson Labrado         boost::asio::io_context& iocIn, const std::string& idIn,
838d14a48ffSCarson Labrado         const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
8394a7fbefdSEd Tanous         const boost::urls::url_view_base& destIPIn) :
8408a592810SEd Tanous         ioc(iocIn),
841a716aa74SEd Tanous         id(idIn), connPolicy(connPolicyIn), destIP(destIPIn)
842f52c03c1SCarson Labrado     {
843a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Initializing connection pool for {}", id);
844f52c03c1SCarson Labrado 
845f52c03c1SCarson Labrado         // Initialize the pool with a single connection
846f52c03c1SCarson Labrado         addConnection();
847fe44eb0bSAyushi Smriti     }
848bd030d0aSAppaRao Puli };
849bd030d0aSAppaRao Puli 
850f52c03c1SCarson Labrado class HttpClient
851f52c03c1SCarson Labrado {
852f52c03c1SCarson Labrado   private:
853f52c03c1SCarson Labrado     std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
854f52c03c1SCarson Labrado         connectionPools;
855f8ca6d79SEd Tanous     boost::asio::io_context& ioc;
856d14a48ffSCarson Labrado     std::shared_ptr<ConnectionPolicy> connPolicy;
857f52c03c1SCarson Labrado 
858039a47e3SCarson Labrado     // Used as a dummy callback by sendData() in order to call
859039a47e3SCarson Labrado     // sendDataWithCallback()
86002cad96eSEd Tanous     static void genericResHandler(const Response& res)
861039a47e3SCarson Labrado     {
86262598e31SEd Tanous         BMCWEB_LOG_DEBUG("Response handled with return code: {}",
863a716aa74SEd Tanous                          res.resultInt());
8644ee8e211SEd Tanous     }
865039a47e3SCarson Labrado 
866f52c03c1SCarson Labrado   public:
867d14a48ffSCarson Labrado     HttpClient() = delete;
868f8ca6d79SEd Tanous     explicit HttpClient(boost::asio::io_context& iocIn,
869f8ca6d79SEd Tanous                         const std::shared_ptr<ConnectionPolicy>& connPolicyIn) :
870f8ca6d79SEd Tanous         ioc(iocIn),
871d14a48ffSCarson Labrado         connPolicy(connPolicyIn)
872d14a48ffSCarson Labrado     {}
873f8ca6d79SEd Tanous 
874f52c03c1SCarson Labrado     HttpClient(const HttpClient&) = delete;
875f52c03c1SCarson Labrado     HttpClient& operator=(const HttpClient&) = delete;
876f52c03c1SCarson Labrado     HttpClient(HttpClient&&) = delete;
877f52c03c1SCarson Labrado     HttpClient& operator=(HttpClient&&) = delete;
878f52c03c1SCarson Labrado     ~HttpClient() = default;
879f52c03c1SCarson Labrado 
880a716aa74SEd Tanous     // Send a request to destIP where additional processing of the
881039a47e3SCarson Labrado     // result is not required
8824a7fbefdSEd Tanous     void sendData(std::string&& data, const boost::urls::url_view_base& destUri,
883f52c03c1SCarson Labrado                   const boost::beast::http::fields& httpHeader,
884d14a48ffSCarson Labrado                   const boost::beast::http::verb verb)
885f52c03c1SCarson Labrado     {
886e38778a5SAppaRao Puli         const std::function<void(Response&)> cb = genericResHandler;
887a716aa74SEd Tanous         sendDataWithCallback(std::move(data), destUri, httpHeader, verb, cb);
888039a47e3SCarson Labrado     }
889039a47e3SCarson Labrado 
890a716aa74SEd Tanous     // Send request to destIP and use the provided callback to
891039a47e3SCarson Labrado     // handle the response
8924a7fbefdSEd Tanous     void sendDataWithCallback(std::string&& data,
8934a7fbefdSEd Tanous                               const boost::urls::url_view_base& destUrl,
894039a47e3SCarson Labrado                               const boost::beast::http::fields& httpHeader,
895244256ccSCarson Labrado                               const boost::beast::http::verb verb,
8966b3db60dSEd Tanous                               const std::function<void(Response&)>& resHandler)
897039a47e3SCarson Labrado     {
898a716aa74SEd Tanous         std::string clientKey = std::format("{}://{}", destUrl.scheme(),
899a716aa74SEd Tanous                                             destUrl.encoded_host_and_port());
900d14a48ffSCarson Labrado         auto pool = connectionPools.try_emplace(clientKey);
901d14a48ffSCarson Labrado         if (pool.first->second == nullptr)
902f52c03c1SCarson Labrado         {
903d14a48ffSCarson Labrado             pool.first->second = std::make_shared<ConnectionPool>(
904a716aa74SEd Tanous                 ioc, clientKey, connPolicy, destUrl);
905f52c03c1SCarson Labrado         }
90627b0cf90SEd Tanous         // Send the data using either the existing connection pool or the
90727b0cf90SEd Tanous         // newly created connection pool
908a716aa74SEd Tanous         pool.first->second->sendData(std::move(data), destUrl, httpHeader, verb,
909e38778a5SAppaRao Puli                                      resHandler);
910f52c03c1SCarson Labrado     }
911f52c03c1SCarson Labrado };
912bd030d0aSAppaRao Puli } // namespace crow
913