xref: /openbmc/bmcweb/http/http_client.hpp (revision 52e31629)
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/write.hpp>
37e38778a5SAppaRao Puli #include <boost/beast/ssl/ssl_stream.hpp>
38bd030d0aSAppaRao Puli #include <boost/beast/version.hpp>
39f52c03c1SCarson Labrado #include <boost/container/devector.hpp>
40bb49eb5cSEd Tanous #include <boost/system/error_code.hpp>
4127b0cf90SEd Tanous #include <boost/url/format.hpp>
4227b0cf90SEd Tanous #include <boost/url/url.hpp>
43a716aa74SEd Tanous #include <boost/url/url_view.hpp>
441214b7e7SGunnar Mills 
45bd030d0aSAppaRao Puli #include <cstdlib>
46bd030d0aSAppaRao Puli #include <functional>
47bd030d0aSAppaRao Puli #include <iostream>
48bd030d0aSAppaRao Puli #include <memory>
492a5689a7SAppaRao Puli #include <queue>
50bd030d0aSAppaRao Puli #include <string>
51bd030d0aSAppaRao Puli 
52bd030d0aSAppaRao Puli namespace crow
53bd030d0aSAppaRao Puli {
5427b0cf90SEd Tanous // With Redfish Aggregation it is assumed we will connect to another
5527b0cf90SEd Tanous // instance of BMCWeb which can handle 100 simultaneous connections.
5666d90c2cSCarson Labrado constexpr size_t maxPoolSize = 20;
5766d90c2cSCarson Labrado constexpr size_t maxRequestQueueSize = 500;
5817dcc312SCarson Labrado constexpr unsigned int httpReadBodyLimit = 131072;
594d94272fSCarson Labrado constexpr unsigned int httpReadBufferSize = 4096;
602a5689a7SAppaRao Puli 
61bd030d0aSAppaRao Puli enum class ConnState
62bd030d0aSAppaRao Puli {
632a5689a7SAppaRao Puli     initialized,
6429a82b08SSunitha Harish     resolveInProgress,
6529a82b08SSunitha Harish     resolveFailed,
662a5689a7SAppaRao Puli     connectInProgress,
672a5689a7SAppaRao Puli     connectFailed,
68bd030d0aSAppaRao Puli     connected,
69e38778a5SAppaRao Puli     handshakeInProgress,
70e38778a5SAppaRao Puli     handshakeFailed,
712a5689a7SAppaRao Puli     sendInProgress,
722a5689a7SAppaRao Puli     sendFailed,
736eaa1d2fSSunitha Harish     recvInProgress,
742a5689a7SAppaRao Puli     recvFailed,
752a5689a7SAppaRao Puli     idle,
76fe44eb0bSAyushi Smriti     closed,
776eaa1d2fSSunitha Harish     suspended,
786eaa1d2fSSunitha Harish     terminated,
796eaa1d2fSSunitha Harish     abortConnection,
80e38778a5SAppaRao Puli     sslInitFailed,
816eaa1d2fSSunitha Harish     retry
82bd030d0aSAppaRao Puli };
83bd030d0aSAppaRao Puli 
84a7a80296SCarson Labrado static inline boost::system::error_code
85a7a80296SCarson Labrado     defaultRetryHandler(unsigned int respCode)
86a7a80296SCarson Labrado {
87a7a80296SCarson Labrado     // As a default, assume 200X is alright
8862598e31SEd Tanous     BMCWEB_LOG_DEBUG("Using default check for response code validity");
89a7a80296SCarson Labrado     if ((respCode < 200) || (respCode >= 300))
90a7a80296SCarson Labrado     {
91a7a80296SCarson Labrado         return boost::system::errc::make_error_code(
92a7a80296SCarson Labrado             boost::system::errc::result_out_of_range);
93a7a80296SCarson Labrado     }
94a7a80296SCarson Labrado 
95a7a80296SCarson Labrado     // Return 0 if the response code is valid
96a7a80296SCarson Labrado     return boost::system::errc::make_error_code(boost::system::errc::success);
97a7a80296SCarson Labrado };
98a7a80296SCarson Labrado 
9927b0cf90SEd Tanous // We need to allow retry information to be set before a message has been
10027b0cf90SEd Tanous // sent and a connection pool has been created
101d14a48ffSCarson Labrado struct ConnectionPolicy
102f52c03c1SCarson Labrado {
103f52c03c1SCarson Labrado     uint32_t maxRetryAttempts = 5;
104d14a48ffSCarson Labrado 
105d14a48ffSCarson Labrado     // the max size of requests in bytes.  0 for unlimited
106d14a48ffSCarson Labrado     boost::optional<uint64_t> requestByteLimit = httpReadBodyLimit;
107d14a48ffSCarson Labrado 
108d14a48ffSCarson Labrado     size_t maxConnections = 1;
109d14a48ffSCarson Labrado 
110f52c03c1SCarson Labrado     std::string retryPolicyAction = "TerminateAfterRetries";
111d14a48ffSCarson Labrado 
112d14a48ffSCarson Labrado     std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0);
113a7a80296SCarson Labrado     std::function<boost::system::error_code(unsigned int respCode)>
114a7a80296SCarson Labrado         invalidResp = defaultRetryHandler;
115f52c03c1SCarson Labrado };
116f52c03c1SCarson Labrado 
117f52c03c1SCarson Labrado struct PendingRequest
118f52c03c1SCarson Labrado {
119*52e31629SEd Tanous     boost::beast::http::request<bmcweb::FileBody> req;
120039a47e3SCarson Labrado     std::function<void(bool, uint32_t, Response&)> callback;
121039a47e3SCarson Labrado     PendingRequest(
122*52e31629SEd Tanous         boost::beast::http::request<bmcweb::FileBody>&& reqIn,
123d14a48ffSCarson Labrado         const std::function<void(bool, uint32_t, Response&)>& callbackIn) :
1248a592810SEd Tanous         req(std::move(reqIn)),
125d14a48ffSCarson Labrado         callback(callbackIn)
126f52c03c1SCarson Labrado     {}
127f52c03c1SCarson Labrado };
128f52c03c1SCarson Labrado 
129e01d0c36SEd Tanous namespace http = boost::beast::http;
130f52c03c1SCarson Labrado class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo>
131bd030d0aSAppaRao Puli {
132bd030d0aSAppaRao Puli   private:
133f52c03c1SCarson Labrado     ConnState state = ConnState::initialized;
134f52c03c1SCarson Labrado     uint32_t retryCount = 0;
135f52c03c1SCarson Labrado     std::string subId;
136d14a48ffSCarson Labrado     std::shared_ptr<ConnectionPolicy> connPolicy;
137a716aa74SEd Tanous     boost::urls::url host;
138f52c03c1SCarson Labrado     uint32_t connId;
139f52c03c1SCarson Labrado 
140f52c03c1SCarson Labrado     // Data buffers
141*52e31629SEd Tanous     http::request<bmcweb::FileBody> req;
142*52e31629SEd Tanous     using parser_type = http::response_parser<bmcweb::FileBody>;
143e01d0c36SEd Tanous     std::optional<parser_type> parser;
1444d94272fSCarson Labrado     boost::beast::flat_static_buffer<httpReadBufferSize> buffer;
145039a47e3SCarson Labrado     Response res;
1466eaa1d2fSSunitha Harish 
147f52c03c1SCarson Labrado     // Ascync callables
148039a47e3SCarson Labrado     std::function<void(bool, uint32_t, Response&)> callback;
149f8ca6d79SEd Tanous 
150f3cb5df9SAbhilash Raju     boost::asio::io_context& ioc;
151f3cb5df9SAbhilash Raju 
152f8ca6d79SEd Tanous #ifdef BMCWEB_DBUS_DNS_RESOLVER
153e1452beaSEd Tanous     using Resolver = async_resolve::Resolver;
154f8ca6d79SEd Tanous #else
155f8ca6d79SEd Tanous     using Resolver = boost::asio::ip::tcp::resolver;
156f8ca6d79SEd Tanous #endif
157f8ca6d79SEd Tanous     Resolver resolver;
158f8ca6d79SEd Tanous 
1590d5f5cf4SEd Tanous     boost::asio::ip::tcp::socket conn;
1600d5f5cf4SEd Tanous     std::optional<boost::beast::ssl_stream<boost::asio::ip::tcp::socket&>>
1610d5f5cf4SEd Tanous         sslConn;
162e38778a5SAppaRao Puli 
163f52c03c1SCarson Labrado     boost::asio::steady_timer timer;
16484b35604SEd Tanous 
165f52c03c1SCarson Labrado     friend class ConnectionPool;
166bd030d0aSAppaRao Puli 
16729a82b08SSunitha Harish     void doResolve()
16829a82b08SSunitha Harish     {
16929a82b08SSunitha Harish         state = ConnState::resolveInProgress;
170a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Trying to resolve: {}, id: {}", host, connId);
17129a82b08SSunitha Harish 
172a716aa74SEd Tanous         resolver.async_resolve(host.encoded_host_address(), host.port(),
1733d36e3a5SEd Tanous                                std::bind_front(&ConnectionInfo::afterResolve,
1743d36e3a5SEd Tanous                                                this, shared_from_this()));
1753d36e3a5SEd Tanous     }
1763d36e3a5SEd Tanous 
177f8ca6d79SEd Tanous     void afterResolve(const std::shared_ptr<ConnectionInfo>& /*self*/,
178f8ca6d79SEd Tanous                       const boost::system::error_code& ec,
179f8ca6d79SEd Tanous                       const Resolver::results_type& endpointList)
1803d36e3a5SEd Tanous     {
18126f6976fSEd Tanous         if (ec || (endpointList.empty()))
18229a82b08SSunitha Harish         {
183a716aa74SEd Tanous             BMCWEB_LOG_ERROR("Resolve failed: {} {}", ec.message(), host);
1843d36e3a5SEd Tanous             state = ConnState::resolveFailed;
1853d36e3a5SEd Tanous             waitAndRetry();
18629a82b08SSunitha Harish             return;
18729a82b08SSunitha Harish         }
188a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Resolved {}, id: {}", host, connId);
1892a5689a7SAppaRao Puli         state = ConnState::connectInProgress;
1902a5689a7SAppaRao Puli 
191a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Trying to connect to: {}, id: {}", host, connId);
192b00dcc27SEd Tanous 
1930d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
1940d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
1950d5f5cf4SEd Tanous 
1960d5f5cf4SEd Tanous         boost::asio::async_connect(
1970d5f5cf4SEd Tanous             conn, endpointList,
198e38778a5SAppaRao Puli             std::bind_front(&ConnectionInfo::afterConnect, this,
199e38778a5SAppaRao Puli                             shared_from_this()));
200e38778a5SAppaRao Puli     }
201e38778a5SAppaRao Puli 
202e38778a5SAppaRao Puli     void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/,
20381c4e330SEd Tanous                       const boost::beast::error_code& ec,
204e38778a5SAppaRao Puli                       const boost::asio::ip::tcp::endpoint& endpoint)
205e38778a5SAppaRao Puli     {
206513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
207513d1ffcSCarson Labrado         // this branch
208513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
209513d1ffcSCarson Labrado         {
210513d1ffcSCarson Labrado             return;
211513d1ffcSCarson Labrado         }
212513d1ffcSCarson Labrado 
2130d5f5cf4SEd Tanous         timer.cancel();
2142a5689a7SAppaRao Puli         if (ec)
2152a5689a7SAppaRao Puli         {
21662598e31SEd Tanous             BMCWEB_LOG_ERROR("Connect {}:{}, id: {} failed: {}",
217a716aa74SEd Tanous                              endpoint.address().to_string(), endpoint.port(),
218a716aa74SEd Tanous                              connId, ec.message());
219e38778a5SAppaRao Puli             state = ConnState::connectFailed;
220e38778a5SAppaRao Puli             waitAndRetry();
2212a5689a7SAppaRao Puli             return;
2222a5689a7SAppaRao Puli         }
223a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Connected to: {}:{}, id: {}",
224a716aa74SEd Tanous                          endpoint.address().to_string(), endpoint.port(),
225a716aa74SEd Tanous                          connId);
226e38778a5SAppaRao Puli         if (sslConn)
227e38778a5SAppaRao Puli         {
2280d5f5cf4SEd Tanous             doSslHandshake();
229e38778a5SAppaRao Puli             return;
230e38778a5SAppaRao Puli         }
231e38778a5SAppaRao Puli         state = ConnState::connected;
232e38778a5SAppaRao Puli         sendMessage();
233e38778a5SAppaRao Puli     }
234e38778a5SAppaRao Puli 
2350d5f5cf4SEd Tanous     void doSslHandshake()
236e38778a5SAppaRao Puli     {
237e38778a5SAppaRao Puli         if (!sslConn)
238e38778a5SAppaRao Puli         {
239e38778a5SAppaRao Puli             return;
240e38778a5SAppaRao Puli         }
241e38778a5SAppaRao Puli         state = ConnState::handshakeInProgress;
2420d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
2430d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
244e38778a5SAppaRao Puli         sslConn->async_handshake(
245e38778a5SAppaRao Puli             boost::asio::ssl::stream_base::client,
246e38778a5SAppaRao Puli             std::bind_front(&ConnectionInfo::afterSslHandshake, this,
247e38778a5SAppaRao Puli                             shared_from_this()));
248e38778a5SAppaRao Puli     }
249e38778a5SAppaRao Puli 
250e38778a5SAppaRao Puli     void afterSslHandshake(const std::shared_ptr<ConnectionInfo>& /*self*/,
25181c4e330SEd Tanous                            const boost::beast::error_code& ec)
252e38778a5SAppaRao Puli     {
253513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
254513d1ffcSCarson Labrado         // this branch
255513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
256513d1ffcSCarson Labrado         {
257513d1ffcSCarson Labrado             return;
258513d1ffcSCarson Labrado         }
259513d1ffcSCarson Labrado 
2600d5f5cf4SEd Tanous         timer.cancel();
261e38778a5SAppaRao Puli         if (ec)
262e38778a5SAppaRao Puli         {
263a716aa74SEd Tanous             BMCWEB_LOG_ERROR("SSL Handshake failed - id: {} error: {}", connId,
264a716aa74SEd Tanous                              ec.message());
265e38778a5SAppaRao Puli             state = ConnState::handshakeFailed;
266e38778a5SAppaRao Puli             waitAndRetry();
267e38778a5SAppaRao Puli             return;
268e38778a5SAppaRao Puli         }
269a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("SSL Handshake successful - id: {}", connId);
270e38778a5SAppaRao Puli         state = ConnState::connected;
271e38778a5SAppaRao Puli         sendMessage();
2722a5689a7SAppaRao Puli     }
2732a5689a7SAppaRao Puli 
274f52c03c1SCarson Labrado     void sendMessage()
2752a5689a7SAppaRao Puli     {
2762a5689a7SAppaRao Puli         state = ConnState::sendInProgress;
2772a5689a7SAppaRao Puli 
278bd030d0aSAppaRao Puli         // Set a timeout on the operation
2790d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
2800d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
281bd030d0aSAppaRao Puli 
282bd030d0aSAppaRao Puli         // Send the HTTP request to the remote host
283e38778a5SAppaRao Puli         if (sslConn)
284e38778a5SAppaRao Puli         {
285e38778a5SAppaRao Puli             boost::beast::http::async_write(
286e38778a5SAppaRao Puli                 *sslConn, req,
287e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterWrite, this,
288e38778a5SAppaRao Puli                                 shared_from_this()));
289e38778a5SAppaRao Puli         }
290e38778a5SAppaRao Puli         else
291e38778a5SAppaRao Puli         {
292bd030d0aSAppaRao Puli             boost::beast::http::async_write(
293bd030d0aSAppaRao Puli                 conn, req,
294e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterWrite, this,
295e38778a5SAppaRao Puli                                 shared_from_this()));
296e38778a5SAppaRao Puli         }
297e38778a5SAppaRao Puli     }
298e38778a5SAppaRao Puli 
299e38778a5SAppaRao Puli     void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/,
300e38778a5SAppaRao Puli                     const boost::beast::error_code& ec, size_t bytesTransferred)
301e38778a5SAppaRao Puli     {
302513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
303513d1ffcSCarson Labrado         // this branch
304513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
305513d1ffcSCarson Labrado         {
306513d1ffcSCarson Labrado             return;
307513d1ffcSCarson Labrado         }
308513d1ffcSCarson Labrado 
3090d5f5cf4SEd Tanous         timer.cancel();
310bd030d0aSAppaRao Puli         if (ec)
311bd030d0aSAppaRao Puli         {
312a716aa74SEd Tanous             BMCWEB_LOG_ERROR("sendMessage() failed: {} {}", ec.message(), host);
313e38778a5SAppaRao Puli             state = ConnState::sendFailed;
314e38778a5SAppaRao Puli             waitAndRetry();
315bd030d0aSAppaRao Puli             return;
316bd030d0aSAppaRao Puli         }
31762598e31SEd Tanous         BMCWEB_LOG_DEBUG("sendMessage() bytes transferred: {}",
31862598e31SEd Tanous                          bytesTransferred);
319bd030d0aSAppaRao Puli 
320e38778a5SAppaRao Puli         recvMessage();
321bd030d0aSAppaRao Puli     }
322bd030d0aSAppaRao Puli 
323bd030d0aSAppaRao Puli     void recvMessage()
324bd030d0aSAppaRao Puli     {
3256eaa1d2fSSunitha Harish         state = ConnState::recvInProgress;
3266eaa1d2fSSunitha Harish 
327e01d0c36SEd Tanous         parser_type& thisParser = parser.emplace(std::piecewise_construct,
328e01d0c36SEd Tanous                                                  std::make_tuple());
329d14a48ffSCarson Labrado 
330e01d0c36SEd Tanous         thisParser.body_limit(connPolicy->requestByteLimit);
3316eaa1d2fSSunitha Harish 
3320d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
3330d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
3340d5f5cf4SEd Tanous 
335bd030d0aSAppaRao Puli         // Receive the HTTP response
336e38778a5SAppaRao Puli         if (sslConn)
337e38778a5SAppaRao Puli         {
338e38778a5SAppaRao Puli             boost::beast::http::async_read(
339e01d0c36SEd Tanous                 *sslConn, buffer, thisParser,
340e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterRead, this,
341e38778a5SAppaRao Puli                                 shared_from_this()));
342e38778a5SAppaRao Puli         }
343e38778a5SAppaRao Puli         else
344e38778a5SAppaRao Puli         {
345bd030d0aSAppaRao Puli             boost::beast::http::async_read(
346e01d0c36SEd Tanous                 conn, buffer, thisParser,
347e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterRead, this,
348e38778a5SAppaRao Puli                                 shared_from_this()));
349e38778a5SAppaRao Puli         }
350e38778a5SAppaRao Puli     }
351e38778a5SAppaRao Puli 
352e38778a5SAppaRao Puli     void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/,
353e38778a5SAppaRao Puli                    const boost::beast::error_code& ec,
354e38778a5SAppaRao Puli                    const std::size_t& bytesTransferred)
355e38778a5SAppaRao Puli     {
356513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
357513d1ffcSCarson Labrado         // this branch
358513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
359513d1ffcSCarson Labrado         {
360513d1ffcSCarson Labrado             return;
361513d1ffcSCarson Labrado         }
362513d1ffcSCarson Labrado 
3630d5f5cf4SEd Tanous         timer.cancel();
364e38778a5SAppaRao Puli         if (ec && ec != boost::asio::ssl::error::stream_truncated)
365bd030d0aSAppaRao Puli         {
366a716aa74SEd Tanous             BMCWEB_LOG_ERROR("recvMessage() failed: {} from {}", ec.message(),
367a716aa74SEd Tanous                              host);
368e38778a5SAppaRao Puli             state = ConnState::recvFailed;
369e38778a5SAppaRao Puli             waitAndRetry();
370bd030d0aSAppaRao Puli             return;
371bd030d0aSAppaRao Puli         }
37262598e31SEd Tanous         BMCWEB_LOG_DEBUG("recvMessage() bytes transferred: {}",
37362598e31SEd Tanous                          bytesTransferred);
374e01d0c36SEd Tanous         if (!parser)
375e01d0c36SEd Tanous         {
376e01d0c36SEd Tanous             return;
377e01d0c36SEd Tanous         }
378*52e31629SEd Tanous         BMCWEB_LOG_DEBUG("recvMessage() data: {}", parser->get().body().str());
379bd030d0aSAppaRao Puli 
380e38778a5SAppaRao Puli         unsigned int respCode = parser->get().result_int();
38162598e31SEd Tanous         BMCWEB_LOG_DEBUG("recvMessage() Header Response Code: {}", respCode);
3826eaa1d2fSSunitha Harish 
383f3cb5df9SAbhilash Raju         // Handle the case of stream_truncated.  Some servers close the ssl
384f3cb5df9SAbhilash Raju         // connection uncleanly, so check to see if we got a full response
385f3cb5df9SAbhilash Raju         // before we handle this as an error.
386f3cb5df9SAbhilash Raju         if (!parser->is_done())
387f3cb5df9SAbhilash Raju         {
388f3cb5df9SAbhilash Raju             state = ConnState::recvFailed;
389f3cb5df9SAbhilash Raju             waitAndRetry();
390f3cb5df9SAbhilash Raju             return;
391f3cb5df9SAbhilash Raju         }
392f3cb5df9SAbhilash Raju 
393a7a80296SCarson Labrado         // Make sure the received response code is valid as defined by
394a7a80296SCarson Labrado         // the associated retry policy
395d14a48ffSCarson Labrado         if (connPolicy->invalidResp(respCode))
3966eaa1d2fSSunitha Harish         {
3976eaa1d2fSSunitha Harish             // The listener failed to receive the Sent-Event
39862598e31SEd Tanous             BMCWEB_LOG_ERROR(
39962598e31SEd Tanous                 "recvMessage() Listener Failed to "
400a716aa74SEd Tanous                 "receive Sent-Event. Header Response Code: {} from {}",
401a716aa74SEd Tanous                 respCode, host);
402e38778a5SAppaRao Puli             state = ConnState::recvFailed;
403e38778a5SAppaRao Puli             waitAndRetry();
4046eaa1d2fSSunitha Harish             return;
4056eaa1d2fSSunitha Harish         }
406bd030d0aSAppaRao Puli 
407f52c03c1SCarson Labrado         // Send is successful
408f52c03c1SCarson Labrado         // Reset the counter just in case this was after retrying
409e38778a5SAppaRao Puli         retryCount = 0;
4106eaa1d2fSSunitha Harish 
4116eaa1d2fSSunitha Harish         // Keep the connection alive if server supports it
4126eaa1d2fSSunitha Harish         // Else close the connection
41362598e31SEd Tanous         BMCWEB_LOG_DEBUG("recvMessage() keepalive : {}", parser->keep_alive());
4146eaa1d2fSSunitha Harish 
415039a47e3SCarson Labrado         // Copy the response into a Response object so that it can be
416039a47e3SCarson Labrado         // processed by the callback function.
41727b0cf90SEd Tanous         res.response = parser->release();
418e38778a5SAppaRao Puli         callback(parser->keep_alive(), connId, res);
419513d1ffcSCarson Labrado         res.clear();
420bd030d0aSAppaRao Puli     }
421bd030d0aSAppaRao Puli 
4220d5f5cf4SEd Tanous     static void onTimeout(const std::weak_ptr<ConnectionInfo>& weakSelf,
4235e7e2dc5SEd Tanous                           const boost::system::error_code& ec)
4240d5f5cf4SEd Tanous     {
4250d5f5cf4SEd Tanous         if (ec == boost::asio::error::operation_aborted)
4260d5f5cf4SEd Tanous         {
42762598e31SEd Tanous             BMCWEB_LOG_DEBUG(
42862598e31SEd Tanous                 "async_wait failed since the operation is aborted");
4290d5f5cf4SEd Tanous             return;
4300d5f5cf4SEd Tanous         }
4310d5f5cf4SEd Tanous         if (ec)
4320d5f5cf4SEd Tanous         {
43362598e31SEd Tanous             BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
43427b0cf90SEd Tanous             // If the timer fails, we need to close the socket anyway, same
43527b0cf90SEd Tanous             // as if it expired.
4360d5f5cf4SEd Tanous         }
4370d5f5cf4SEd Tanous         std::shared_ptr<ConnectionInfo> self = weakSelf.lock();
4380d5f5cf4SEd Tanous         if (self == nullptr)
4390d5f5cf4SEd Tanous         {
4400d5f5cf4SEd Tanous             return;
4410d5f5cf4SEd Tanous         }
4420d5f5cf4SEd Tanous         self->waitAndRetry();
4430d5f5cf4SEd Tanous     }
4440d5f5cf4SEd Tanous 
4456eaa1d2fSSunitha Harish     void waitAndRetry()
446bd030d0aSAppaRao Puli     {
447d14a48ffSCarson Labrado         if ((retryCount >= connPolicy->maxRetryAttempts) ||
448e38778a5SAppaRao Puli             (state == ConnState::sslInitFailed))
4492a5689a7SAppaRao Puli         {
450a716aa74SEd Tanous             BMCWEB_LOG_ERROR("Maximum number of retries reached. {}", host);
45162598e31SEd Tanous             BMCWEB_LOG_DEBUG("Retry policy: {}", connPolicy->retryPolicyAction);
452039a47e3SCarson Labrado 
453d14a48ffSCarson Labrado             if (connPolicy->retryPolicyAction == "TerminateAfterRetries")
454fe44eb0bSAyushi Smriti             {
455fe44eb0bSAyushi Smriti                 // TODO: delete subscription
456fe44eb0bSAyushi Smriti                 state = ConnState::terminated;
457fe44eb0bSAyushi Smriti             }
458d14a48ffSCarson Labrado             if (connPolicy->retryPolicyAction == "SuspendRetries")
459fe44eb0bSAyushi Smriti             {
4602a5689a7SAppaRao Puli                 state = ConnState::suspended;
4612a5689a7SAppaRao Puli             }
462513d1ffcSCarson Labrado 
463513d1ffcSCarson Labrado             // We want to return a 502 to indicate there was an error with
464513d1ffcSCarson Labrado             // the external server
465513d1ffcSCarson Labrado             res.result(boost::beast::http::status::bad_gateway);
466513d1ffcSCarson Labrado             callback(false, connId, res);
467513d1ffcSCarson Labrado             res.clear();
468513d1ffcSCarson Labrado 
46927b0cf90SEd Tanous             // Reset the retrycount to zero so that client can try
47027b0cf90SEd Tanous             // connecting again if needed
471fe44eb0bSAyushi Smriti             retryCount = 0;
4722a5689a7SAppaRao Puli             return;
4732a5689a7SAppaRao Puli         }
4742a5689a7SAppaRao Puli 
4752a5689a7SAppaRao Puli         retryCount++;
476fe44eb0bSAyushi Smriti 
47762598e31SEd Tanous         BMCWEB_LOG_DEBUG("Attempt retry after {} seconds. RetryCount = {}",
478a716aa74SEd Tanous                          connPolicy->retryIntervalSecs.count(), retryCount);
479d14a48ffSCarson Labrado         timer.expires_after(connPolicy->retryIntervalSecs);
4803d36e3a5SEd Tanous         timer.async_wait(std::bind_front(&ConnectionInfo::onTimerDone, this,
4813d36e3a5SEd Tanous                                          shared_from_this()));
4823d36e3a5SEd Tanous     }
4833d36e3a5SEd Tanous 
4843d36e3a5SEd Tanous     void onTimerDone(const std::shared_ptr<ConnectionInfo>& /*self*/,
4853d36e3a5SEd Tanous                      const boost::system::error_code& ec)
4863d36e3a5SEd Tanous     {
4876eaa1d2fSSunitha Harish         if (ec == boost::asio::error::operation_aborted)
4886eaa1d2fSSunitha Harish         {
48962598e31SEd Tanous             BMCWEB_LOG_DEBUG(
49062598e31SEd Tanous                 "async_wait failed since the operation is aborted{}",
49162598e31SEd Tanous                 ec.message());
4926eaa1d2fSSunitha Harish         }
4936eaa1d2fSSunitha Harish         else if (ec)
4946eaa1d2fSSunitha Harish         {
49562598e31SEd Tanous             BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
4966eaa1d2fSSunitha Harish             // Ignore the error and continue the retry loop to attempt
4976eaa1d2fSSunitha Harish             // sending the event as per the retry policy
4986eaa1d2fSSunitha Harish         }
4996eaa1d2fSSunitha Harish 
500f52c03c1SCarson Labrado         // Let's close the connection and restart from resolve.
501f3cb5df9SAbhilash Raju         shutdownConn(true);
502f3cb5df9SAbhilash Raju     }
503f3cb5df9SAbhilash Raju 
504f3cb5df9SAbhilash Raju     void restartConnection()
505f3cb5df9SAbhilash Raju     {
506f3cb5df9SAbhilash Raju         BMCWEB_LOG_DEBUG("{}, id: {}  restartConnection", host,
507f3cb5df9SAbhilash Raju                          std::to_string(connId));
508f3cb5df9SAbhilash Raju         initializeConnection(host.scheme() == "https");
509f3cb5df9SAbhilash Raju         doResolve();
5102a5689a7SAppaRao Puli     }
5112a5689a7SAppaRao Puli 
512e38778a5SAppaRao Puli     void shutdownConn(bool retry)
513fe44eb0bSAyushi Smriti     {
514f52c03c1SCarson Labrado         boost::beast::error_code ec;
5150d5f5cf4SEd Tanous         conn.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
516f52c03c1SCarson Labrado         conn.close();
517f52c03c1SCarson Labrado 
518f52c03c1SCarson Labrado         // not_connected happens sometimes so don't bother reporting it.
519f52c03c1SCarson Labrado         if (ec && ec != boost::beast::errc::not_connected)
5202a5689a7SAppaRao Puli         {
521a716aa74SEd Tanous             BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId,
52262598e31SEd Tanous                              ec.message());
5236eaa1d2fSSunitha Harish         }
5245cab68f3SCarson Labrado         else
5255cab68f3SCarson Labrado         {
526a716aa74SEd Tanous             BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId);
5275cab68f3SCarson Labrado         }
528ca723762SEd Tanous 
529e38778a5SAppaRao Puli         if (retry)
53092a74e56SAppaRao Puli         {
531f52c03c1SCarson Labrado             // Now let's try to resend the data
532f52c03c1SCarson Labrado             state = ConnState::retry;
533f3cb5df9SAbhilash Raju             restartConnection();
534e38778a5SAppaRao Puli         }
535e38778a5SAppaRao Puli         else
536e38778a5SAppaRao Puli         {
537e38778a5SAppaRao Puli             state = ConnState::closed;
538e38778a5SAppaRao Puli         }
539e38778a5SAppaRao Puli     }
540e38778a5SAppaRao Puli 
541e38778a5SAppaRao Puli     void doClose(bool retry = false)
542e38778a5SAppaRao Puli     {
543e38778a5SAppaRao Puli         if (!sslConn)
544e38778a5SAppaRao Puli         {
545e38778a5SAppaRao Puli             shutdownConn(retry);
546e38778a5SAppaRao Puli             return;
547e38778a5SAppaRao Puli         }
548e38778a5SAppaRao Puli 
549e38778a5SAppaRao Puli         sslConn->async_shutdown(
550e38778a5SAppaRao Puli             std::bind_front(&ConnectionInfo::afterSslShutdown, this,
551e38778a5SAppaRao Puli                             shared_from_this(), retry));
552e38778a5SAppaRao Puli     }
553e38778a5SAppaRao Puli 
554e38778a5SAppaRao Puli     void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/,
555e38778a5SAppaRao Puli                           bool retry, const boost::system::error_code& ec)
556e38778a5SAppaRao Puli     {
557e38778a5SAppaRao Puli         if (ec)
558e38778a5SAppaRao Puli         {
559a716aa74SEd Tanous             BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId,
56062598e31SEd Tanous                              ec.message());
561e38778a5SAppaRao Puli         }
562e38778a5SAppaRao Puli         else
563e38778a5SAppaRao Puli         {
564a716aa74SEd Tanous             BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId);
565e38778a5SAppaRao Puli         }
566e38778a5SAppaRao Puli         shutdownConn(retry);
567e38778a5SAppaRao Puli     }
568e38778a5SAppaRao Puli 
569e38778a5SAppaRao Puli     void setCipherSuiteTLSext()
570e38778a5SAppaRao Puli     {
571e38778a5SAppaRao Puli         if (!sslConn)
572e38778a5SAppaRao Puli         {
573e38778a5SAppaRao Puli             return;
574e38778a5SAppaRao Puli         }
575e7c2991eSRavi Teja 
576e7c2991eSRavi Teja         if (host.host_type() != boost::urls::host_type::name)
577e7c2991eSRavi Teja         {
578e7c2991eSRavi Teja             // Avoid setting SNI hostname if its IP address
579e7c2991eSRavi Teja             return;
580e7c2991eSRavi Teja         }
581e7c2991eSRavi Teja         // Create a null terminated string for SSL
582a716aa74SEd Tanous         std::string hostname(host.encoded_host_address());
583e38778a5SAppaRao Puli         // NOTE: The SSL_set_tlsext_host_name is defined in tlsv1.h header
584e38778a5SAppaRao Puli         // file but its having old style casting (name is cast to void*).
585e38778a5SAppaRao Puli         // Since bmcweb compiler treats all old-style-cast as error, its
586e38778a5SAppaRao Puli         // causing the build failure. So replaced the same macro inline and
587e38778a5SAppaRao Puli         // did corrected the code by doing static_cast to viod*. This has to
588e38778a5SAppaRao Puli         // be fixed in openssl library in long run. Set SNI Hostname (many
589e38778a5SAppaRao Puli         // hosts need this to handshake successfully)
590e38778a5SAppaRao Puli         if (SSL_ctrl(sslConn->native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME,
591e38778a5SAppaRao Puli                      TLSEXT_NAMETYPE_host_name,
592a716aa74SEd Tanous                      static_cast<void*>(hostname.data())) == 0)
593e38778a5SAppaRao Puli 
594e38778a5SAppaRao Puli         {
595e38778a5SAppaRao Puli             boost::beast::error_code ec{static_cast<int>(::ERR_get_error()),
596e38778a5SAppaRao Puli                                         boost::asio::error::get_ssl_category()};
597e38778a5SAppaRao Puli 
598a716aa74SEd Tanous             BMCWEB_LOG_ERROR("SSL_set_tlsext_host_name {}, id: {} failed: {}",
599a716aa74SEd Tanous                              host, connId, ec.message());
600e38778a5SAppaRao Puli             // Set state as sslInit failed so that we close the connection
601e38778a5SAppaRao Puli             // and take appropriate action as per retry configuration.
602e38778a5SAppaRao Puli             state = ConnState::sslInitFailed;
603e38778a5SAppaRao Puli             waitAndRetry();
604e38778a5SAppaRao Puli             return;
605e38778a5SAppaRao Puli         }
606bd030d0aSAppaRao Puli     }
607bd030d0aSAppaRao Puli 
608f3cb5df9SAbhilash Raju     void initializeConnection(bool ssl)
609e38778a5SAppaRao Puli     {
610f3cb5df9SAbhilash Raju         conn = boost::asio::ip::tcp::socket(ioc);
611f3cb5df9SAbhilash Raju         if (ssl)
612e38778a5SAppaRao Puli         {
613e38778a5SAppaRao Puli             std::optional<boost::asio::ssl::context> sslCtx =
614e38778a5SAppaRao Puli                 ensuressl::getSSLClientContext();
615e38778a5SAppaRao Puli 
616e38778a5SAppaRao Puli             if (!sslCtx)
617e38778a5SAppaRao Puli             {
618a716aa74SEd Tanous                 BMCWEB_LOG_ERROR("prepareSSLContext failed - {}, id: {}", host,
619a716aa74SEd Tanous                                  connId);
620e38778a5SAppaRao Puli                 // Don't retry if failure occurs while preparing SSL context
62127b0cf90SEd Tanous                 // such as certificate is invalid or set cipher failure or
62227b0cf90SEd Tanous                 // set host name failure etc... Setting conn state to
62327b0cf90SEd Tanous                 // sslInitFailed and connection state will be transitioned
62427b0cf90SEd Tanous                 // to next state depending on retry policy set by
62527b0cf90SEd Tanous                 // subscription.
626e38778a5SAppaRao Puli                 state = ConnState::sslInitFailed;
627e38778a5SAppaRao Puli                 waitAndRetry();
628e38778a5SAppaRao Puli                 return;
629e38778a5SAppaRao Puli             }
630e38778a5SAppaRao Puli             sslConn.emplace(conn, *sslCtx);
631e38778a5SAppaRao Puli             setCipherSuiteTLSext();
632e38778a5SAppaRao Puli         }
633e38778a5SAppaRao Puli     }
634f3cb5df9SAbhilash Raju 
635f3cb5df9SAbhilash Raju   public:
636f3cb5df9SAbhilash Raju     explicit ConnectionInfo(
637f3cb5df9SAbhilash Raju         boost::asio::io_context& iocIn, const std::string& idIn,
638f3cb5df9SAbhilash Raju         const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
639f3cb5df9SAbhilash Raju         boost::urls::url_view hostIn, unsigned int connIdIn) :
640f3cb5df9SAbhilash Raju         subId(idIn),
641f3cb5df9SAbhilash Raju         connPolicy(connPolicyIn), host(hostIn), connId(connIdIn), ioc(iocIn),
642f3cb5df9SAbhilash Raju         resolver(iocIn), conn(iocIn), timer(iocIn)
643f3cb5df9SAbhilash Raju     {
644f3cb5df9SAbhilash Raju         initializeConnection(host.scheme() == "https");
645f3cb5df9SAbhilash Raju     }
646f52c03c1SCarson Labrado };
647bd030d0aSAppaRao Puli 
648f52c03c1SCarson Labrado class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
649bd030d0aSAppaRao Puli {
650f52c03c1SCarson Labrado   private:
651f52c03c1SCarson Labrado     boost::asio::io_context& ioc;
652e38778a5SAppaRao Puli     std::string id;
653d14a48ffSCarson Labrado     std::shared_ptr<ConnectionPolicy> connPolicy;
654a716aa74SEd Tanous     boost::urls::url destIP;
655f52c03c1SCarson Labrado     std::vector<std::shared_ptr<ConnectionInfo>> connections;
656f52c03c1SCarson Labrado     boost::container::devector<PendingRequest> requestQueue;
657f52c03c1SCarson Labrado 
658f52c03c1SCarson Labrado     friend class HttpClient;
659f52c03c1SCarson Labrado 
660244256ccSCarson Labrado     // Configure a connections's request, callback, and retry info in
661244256ccSCarson Labrado     // preparation to begin sending the request
662f52c03c1SCarson Labrado     void setConnProps(ConnectionInfo& conn)
663bd030d0aSAppaRao Puli     {
664f52c03c1SCarson Labrado         if (requestQueue.empty())
665f52c03c1SCarson Labrado         {
66662598e31SEd Tanous             BMCWEB_LOG_ERROR(
66762598e31SEd Tanous                 "setConnProps() should not have been called when requestQueue is empty");
668bd030d0aSAppaRao Puli             return;
669bd030d0aSAppaRao Puli         }
670bd030d0aSAppaRao Puli 
671*52e31629SEd Tanous         PendingRequest& nextReq = requestQueue.front();
672244256ccSCarson Labrado         conn.req = std::move(nextReq.req);
673244256ccSCarson Labrado         conn.callback = std::move(nextReq.callback);
674f52c03c1SCarson Labrado 
675a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Setting properties for connection {}, id: {}",
676a716aa74SEd Tanous                          conn.host, conn.connId);
677f52c03c1SCarson Labrado 
678f52c03c1SCarson Labrado         // We can remove the request from the queue at this point
679f52c03c1SCarson Labrado         requestQueue.pop_front();
680f52c03c1SCarson Labrado     }
681f52c03c1SCarson Labrado 
682f52c03c1SCarson Labrado     // Gets called as part of callback after request is sent
683f52c03c1SCarson Labrado     // Reuses the connection if there are any requests waiting to be sent
684f52c03c1SCarson Labrado     // Otherwise closes the connection if it is not a keep-alive
685f52c03c1SCarson Labrado     void sendNext(bool keepAlive, uint32_t connId)
686f52c03c1SCarson Labrado     {
687f52c03c1SCarson Labrado         auto conn = connections[connId];
68846a81465SCarson Labrado 
68946a81465SCarson Labrado         // Allow the connection's handler to be deleted
69046a81465SCarson Labrado         // This is needed because of Redfish Aggregation passing an
69146a81465SCarson Labrado         // AsyncResponse shared_ptr to this callback
69246a81465SCarson Labrado         conn->callback = nullptr;
69346a81465SCarson Labrado 
694f52c03c1SCarson Labrado         // Reuse the connection to send the next request in the queue
695f52c03c1SCarson Labrado         if (!requestQueue.empty())
696f52c03c1SCarson Labrado         {
69762598e31SEd Tanous             BMCWEB_LOG_DEBUG(
6988ece0e45SEd Tanous                 "{} requests remaining in queue for {}, reusing connection {}",
699a716aa74SEd Tanous                 requestQueue.size(), destIP, connId);
700f52c03c1SCarson Labrado 
701f52c03c1SCarson Labrado             setConnProps(*conn);
702f52c03c1SCarson Labrado 
703f52c03c1SCarson Labrado             if (keepAlive)
704f52c03c1SCarson Labrado             {
705f52c03c1SCarson Labrado                 conn->sendMessage();
7062a5689a7SAppaRao Puli             }
7072a5689a7SAppaRao Puli             else
7082a5689a7SAppaRao Puli             {
709f52c03c1SCarson Labrado                 // Server is not keep-alive enabled so we need to close the
710f52c03c1SCarson Labrado                 // connection and then start over from resolve
711f52c03c1SCarson Labrado                 conn->doClose();
712f52c03c1SCarson Labrado                 conn->doResolve();
713f52c03c1SCarson Labrado             }
714f52c03c1SCarson Labrado             return;
715f52c03c1SCarson Labrado         }
716f52c03c1SCarson Labrado 
717f52c03c1SCarson Labrado         // No more messages to send so close the connection if necessary
718f52c03c1SCarson Labrado         if (keepAlive)
719f52c03c1SCarson Labrado         {
720f52c03c1SCarson Labrado             conn->state = ConnState::idle;
721f52c03c1SCarson Labrado         }
722f52c03c1SCarson Labrado         else
723f52c03c1SCarson Labrado         {
724f52c03c1SCarson Labrado             // Abort the connection since server is not keep-alive enabled
725f52c03c1SCarson Labrado             conn->state = ConnState::abortConnection;
726f52c03c1SCarson Labrado             conn->doClose();
7272a5689a7SAppaRao Puli         }
728bd030d0aSAppaRao Puli     }
729bd030d0aSAppaRao Puli 
730a716aa74SEd Tanous     void sendData(std::string&& data, boost::urls::url_view destUri,
731244256ccSCarson Labrado                   const boost::beast::http::fields& httpHeader,
732244256ccSCarson Labrado                   const boost::beast::http::verb verb,
7336b3db60dSEd Tanous                   const std::function<void(Response&)>& resHandler)
734fe44eb0bSAyushi Smriti     {
735244256ccSCarson Labrado         // Construct the request to be sent
736*52e31629SEd Tanous         boost::beast::http::request<bmcweb::FileBody> thisReq(
737a716aa74SEd Tanous             verb, destUri.encoded_target(), 11, "", httpHeader);
738a716aa74SEd Tanous         thisReq.set(boost::beast::http::field::host,
739a716aa74SEd Tanous                     destUri.encoded_host_address());
740244256ccSCarson Labrado         thisReq.keep_alive(true);
741*52e31629SEd Tanous         thisReq.body().str() = std::move(data);
742244256ccSCarson Labrado         thisReq.prepare_payload();
7433d36e3a5SEd Tanous         auto cb = std::bind_front(&ConnectionPool::afterSendData,
7443d36e3a5SEd Tanous                                   weak_from_this(), resHandler);
745f52c03c1SCarson Labrado         // Reuse an existing connection if one is available
746f52c03c1SCarson Labrado         for (unsigned int i = 0; i < connections.size(); i++)
747fe44eb0bSAyushi Smriti         {
748f52c03c1SCarson Labrado             auto conn = connections[i];
749f52c03c1SCarson Labrado             if ((conn->state == ConnState::idle) ||
750f52c03c1SCarson Labrado                 (conn->state == ConnState::initialized) ||
751f52c03c1SCarson Labrado                 (conn->state == ConnState::closed))
752f52c03c1SCarson Labrado             {
753244256ccSCarson Labrado                 conn->req = std::move(thisReq);
754f52c03c1SCarson Labrado                 conn->callback = std::move(cb);
755a716aa74SEd Tanous                 std::string commonMsg = std::format("{} from pool {}", i, id);
756f52c03c1SCarson Labrado 
757f52c03c1SCarson Labrado                 if (conn->state == ConnState::idle)
758f52c03c1SCarson Labrado                 {
75962598e31SEd Tanous                     BMCWEB_LOG_DEBUG("Grabbing idle connection {}", commonMsg);
760f52c03c1SCarson Labrado                     conn->sendMessage();
761f52c03c1SCarson Labrado                 }
762f52c03c1SCarson Labrado                 else
763f52c03c1SCarson Labrado                 {
76462598e31SEd Tanous                     BMCWEB_LOG_DEBUG("Reusing existing connection {}",
76562598e31SEd Tanous                                      commonMsg);
766f52c03c1SCarson Labrado                     conn->doResolve();
767f52c03c1SCarson Labrado                 }
768f52c03c1SCarson Labrado                 return;
769f52c03c1SCarson Labrado             }
770f52c03c1SCarson Labrado         }
771f52c03c1SCarson Labrado 
77227b0cf90SEd Tanous         // All connections in use so create a new connection or add request
77327b0cf90SEd Tanous         // to the queue
774d14a48ffSCarson Labrado         if (connections.size() < connPolicy->maxConnections)
775f52c03c1SCarson Labrado         {
776a716aa74SEd Tanous             BMCWEB_LOG_DEBUG("Adding new connection to pool {}", id);
777f52c03c1SCarson Labrado             auto conn = addConnection();
778244256ccSCarson Labrado             conn->req = std::move(thisReq);
779f52c03c1SCarson Labrado             conn->callback = std::move(cb);
780f52c03c1SCarson Labrado             conn->doResolve();
781f52c03c1SCarson Labrado         }
782f52c03c1SCarson Labrado         else if (requestQueue.size() < maxRequestQueueSize)
783f52c03c1SCarson Labrado         {
784a716aa74SEd Tanous             BMCWEB_LOG_DEBUG("Max pool size reached. Adding data to queue {}",
785a716aa74SEd Tanous                              id);
786d14a48ffSCarson Labrado             requestQueue.emplace_back(std::move(thisReq), std::move(cb));
787f52c03c1SCarson Labrado         }
788f52c03c1SCarson Labrado         else
789f52c03c1SCarson Labrado         {
79027b0cf90SEd Tanous             // If we can't buffer the request then we should let the
79127b0cf90SEd Tanous             // callback handle a 429 Too Many Requests dummy response
79262598e31SEd Tanous             BMCWEB_LOG_ERROR("{}:{} request queue full.  Dropping request.",
793a716aa74SEd Tanous                              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,
839a716aa74SEd Tanous         boost::urls::url_view 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
882a716aa74SEd Tanous     void sendData(std::string&& data, boost::urls::url_view 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
892a716aa74SEd Tanous     void sendDataWithCallback(std::string&& data, boost::urls::url_view destUrl,
893039a47e3SCarson Labrado                               const boost::beast::http::fields& httpHeader,
894244256ccSCarson Labrado                               const boost::beast::http::verb verb,
8956b3db60dSEd Tanous                               const std::function<void(Response&)>& resHandler)
896039a47e3SCarson Labrado     {
897a716aa74SEd Tanous         std::string clientKey = std::format("{}://{}", destUrl.scheme(),
898a716aa74SEd Tanous                                             destUrl.encoded_host_and_port());
899d14a48ffSCarson Labrado         auto pool = connectionPools.try_emplace(clientKey);
900d14a48ffSCarson Labrado         if (pool.first->second == nullptr)
901f52c03c1SCarson Labrado         {
902d14a48ffSCarson Labrado             pool.first->second = std::make_shared<ConnectionPool>(
903a716aa74SEd Tanous                 ioc, clientKey, connPolicy, destUrl);
904f52c03c1SCarson Labrado         }
90527b0cf90SEd Tanous         // Send the data using either the existing connection pool or the
90627b0cf90SEd Tanous         // newly created connection pool
907a716aa74SEd Tanous         pool.first->second->sendData(std::move(data), destUrl, httpHeader, verb,
908e38778a5SAppaRao Puli                                      resHandler);
909f52c03c1SCarson Labrado     }
910f52c03c1SCarson Labrado };
911bd030d0aSAppaRao Puli } // namespace crow
912