xref: /openbmc/bmcweb/http/http_client.hpp (revision 25b54dba)
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>
31003301a2SEd Tanous #include <boost/asio/ssl/stream.hpp>
32d43cd0caSEd Tanous #include <boost/asio/steady_timer.hpp>
33bb49eb5cSEd Tanous #include <boost/beast/core/flat_static_buffer.hpp>
34d43cd0caSEd Tanous #include <boost/beast/http/message.hpp>
354d69861fSEd Tanous #include <boost/beast/http/message_generator.hpp>
36bb49eb5cSEd Tanous #include <boost/beast/http/parser.hpp>
37bb49eb5cSEd Tanous #include <boost/beast/http/read.hpp>
38bb49eb5cSEd Tanous #include <boost/beast/http/write.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>
434a7fbefdSEd Tanous #include <boost/url/url_view_base.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 {
119b2896149SEd Tanous     boost::beast::http::request<bmcweb::HttpBody> req;
120039a47e3SCarson Labrado     std::function<void(bool, uint32_t, Response&)> callback;
121039a47e3SCarson Labrado     PendingRequest(
122b2896149SEd Tanous         boost::beast::http::request<bmcweb::HttpBody>&& 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
141b2896149SEd Tanous     http::request<bmcweb::HttpBody> req;
142b2896149SEd Tanous     using parser_type = http::response_parser<bmcweb::HttpBody>;
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 
152*25b54dbaSEd Tanous     using Resolver = std::conditional_t<BMCWEB_DNS_RESOLVER == "systemd-dbus",
153*25b54dbaSEd Tanous                                         async_resolve::Resolver,
154*25b54dbaSEd Tanous                                         boost::asio::ip::tcp::resolver>;
155f8ca6d79SEd Tanous     Resolver resolver;
156f8ca6d79SEd Tanous 
1570d5f5cf4SEd Tanous     boost::asio::ip::tcp::socket conn;
158003301a2SEd Tanous     std::optional<boost::asio::ssl::stream<boost::asio::ip::tcp::socket&>>
1590d5f5cf4SEd Tanous         sslConn;
160e38778a5SAppaRao Puli 
161f52c03c1SCarson Labrado     boost::asio::steady_timer timer;
16284b35604SEd Tanous 
163f52c03c1SCarson Labrado     friend class ConnectionPool;
164bd030d0aSAppaRao Puli 
16529a82b08SSunitha Harish     void doResolve()
16629a82b08SSunitha Harish     {
16729a82b08SSunitha Harish         state = ConnState::resolveInProgress;
168a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Trying to resolve: {}, id: {}", host, connId);
16929a82b08SSunitha Harish 
170a716aa74SEd Tanous         resolver.async_resolve(host.encoded_host_address(), host.port(),
1713d36e3a5SEd Tanous                                std::bind_front(&ConnectionInfo::afterResolve,
1723d36e3a5SEd Tanous                                                this, shared_from_this()));
1733d36e3a5SEd Tanous     }
1743d36e3a5SEd Tanous 
175f8ca6d79SEd Tanous     void afterResolve(const std::shared_ptr<ConnectionInfo>& /*self*/,
176f8ca6d79SEd Tanous                       const boost::system::error_code& ec,
177f8ca6d79SEd Tanous                       const Resolver::results_type& endpointList)
1783d36e3a5SEd Tanous     {
17926f6976fSEd Tanous         if (ec || (endpointList.empty()))
18029a82b08SSunitha Harish         {
181a716aa74SEd Tanous             BMCWEB_LOG_ERROR("Resolve failed: {} {}", ec.message(), host);
1823d36e3a5SEd Tanous             state = ConnState::resolveFailed;
1833d36e3a5SEd Tanous             waitAndRetry();
18429a82b08SSunitha Harish             return;
18529a82b08SSunitha Harish         }
186a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Resolved {}, id: {}", host, connId);
1872a5689a7SAppaRao Puli         state = ConnState::connectInProgress;
1882a5689a7SAppaRao Puli 
189a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Trying to connect to: {}, id: {}", host, connId);
190b00dcc27SEd Tanous 
1910d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
1920d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
1930d5f5cf4SEd Tanous 
1940d5f5cf4SEd Tanous         boost::asio::async_connect(
1950d5f5cf4SEd Tanous             conn, endpointList,
196e38778a5SAppaRao Puli             std::bind_front(&ConnectionInfo::afterConnect, this,
197e38778a5SAppaRao Puli                             shared_from_this()));
198e38778a5SAppaRao Puli     }
199e38778a5SAppaRao Puli 
200e38778a5SAppaRao Puli     void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/,
20181c4e330SEd Tanous                       const boost::beast::error_code& ec,
202e38778a5SAppaRao Puli                       const boost::asio::ip::tcp::endpoint& endpoint)
203e38778a5SAppaRao Puli     {
204513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
205513d1ffcSCarson Labrado         // this branch
206513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
207513d1ffcSCarson Labrado         {
208513d1ffcSCarson Labrado             return;
209513d1ffcSCarson Labrado         }
210513d1ffcSCarson Labrado 
2110d5f5cf4SEd Tanous         timer.cancel();
2122a5689a7SAppaRao Puli         if (ec)
2132a5689a7SAppaRao Puli         {
21462598e31SEd Tanous             BMCWEB_LOG_ERROR("Connect {}:{}, id: {} failed: {}",
215a716aa74SEd Tanous                              endpoint.address().to_string(), endpoint.port(),
216a716aa74SEd Tanous                              connId, ec.message());
217e38778a5SAppaRao Puli             state = ConnState::connectFailed;
218e38778a5SAppaRao Puli             waitAndRetry();
2192a5689a7SAppaRao Puli             return;
2202a5689a7SAppaRao Puli         }
221a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Connected to: {}:{}, id: {}",
222a716aa74SEd Tanous                          endpoint.address().to_string(), endpoint.port(),
223a716aa74SEd Tanous                          connId);
224e38778a5SAppaRao Puli         if (sslConn)
225e38778a5SAppaRao Puli         {
2260d5f5cf4SEd Tanous             doSslHandshake();
227e38778a5SAppaRao Puli             return;
228e38778a5SAppaRao Puli         }
229e38778a5SAppaRao Puli         state = ConnState::connected;
230e38778a5SAppaRao Puli         sendMessage();
231e38778a5SAppaRao Puli     }
232e38778a5SAppaRao Puli 
2330d5f5cf4SEd Tanous     void doSslHandshake()
234e38778a5SAppaRao Puli     {
235e38778a5SAppaRao Puli         if (!sslConn)
236e38778a5SAppaRao Puli         {
237e38778a5SAppaRao Puli             return;
238e38778a5SAppaRao Puli         }
239e38778a5SAppaRao Puli         state = ConnState::handshakeInProgress;
2400d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
2410d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
242e38778a5SAppaRao Puli         sslConn->async_handshake(
243e38778a5SAppaRao Puli             boost::asio::ssl::stream_base::client,
244e38778a5SAppaRao Puli             std::bind_front(&ConnectionInfo::afterSslHandshake, this,
245e38778a5SAppaRao Puli                             shared_from_this()));
246e38778a5SAppaRao Puli     }
247e38778a5SAppaRao Puli 
248e38778a5SAppaRao Puli     void afterSslHandshake(const std::shared_ptr<ConnectionInfo>& /*self*/,
24981c4e330SEd Tanous                            const boost::beast::error_code& ec)
250e38778a5SAppaRao Puli     {
251513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
252513d1ffcSCarson Labrado         // this branch
253513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
254513d1ffcSCarson Labrado         {
255513d1ffcSCarson Labrado             return;
256513d1ffcSCarson Labrado         }
257513d1ffcSCarson Labrado 
2580d5f5cf4SEd Tanous         timer.cancel();
259e38778a5SAppaRao Puli         if (ec)
260e38778a5SAppaRao Puli         {
261a716aa74SEd Tanous             BMCWEB_LOG_ERROR("SSL Handshake failed - id: {} error: {}", connId,
262a716aa74SEd Tanous                              ec.message());
263e38778a5SAppaRao Puli             state = ConnState::handshakeFailed;
264e38778a5SAppaRao Puli             waitAndRetry();
265e38778a5SAppaRao Puli             return;
266e38778a5SAppaRao Puli         }
267a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("SSL Handshake successful - id: {}", connId);
268e38778a5SAppaRao Puli         state = ConnState::connected;
269e38778a5SAppaRao Puli         sendMessage();
2702a5689a7SAppaRao Puli     }
2712a5689a7SAppaRao Puli 
272f52c03c1SCarson Labrado     void sendMessage()
2732a5689a7SAppaRao Puli     {
2742a5689a7SAppaRao Puli         state = ConnState::sendInProgress;
2752a5689a7SAppaRao Puli 
276bd030d0aSAppaRao Puli         // Set a timeout on the operation
2770d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
2780d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
2794d69861fSEd Tanous         boost::beast::http::message_generator messageGenerator(std::move(req));
280bd030d0aSAppaRao Puli         // Send the HTTP request to the remote host
281e38778a5SAppaRao Puli         if (sslConn)
282e38778a5SAppaRao Puli         {
2834d69861fSEd Tanous             boost::beast::async_write(
2844d69861fSEd Tanous                 *sslConn, std::move(messageGenerator),
285e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterWrite, this,
286e38778a5SAppaRao Puli                                 shared_from_this()));
287e38778a5SAppaRao Puli         }
288e38778a5SAppaRao Puli         else
289e38778a5SAppaRao Puli         {
2904d69861fSEd Tanous             boost::beast::async_write(
2914d69861fSEd Tanous                 conn, std::move(messageGenerator),
292e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterWrite, this,
293e38778a5SAppaRao Puli                                 shared_from_this()));
294e38778a5SAppaRao Puli         }
295e38778a5SAppaRao Puli     }
296e38778a5SAppaRao Puli 
297e38778a5SAppaRao Puli     void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/,
298e38778a5SAppaRao Puli                     const boost::beast::error_code& ec, size_t bytesTransferred)
299e38778a5SAppaRao Puli     {
300513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
301513d1ffcSCarson Labrado         // this branch
302513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
303513d1ffcSCarson Labrado         {
304513d1ffcSCarson Labrado             return;
305513d1ffcSCarson Labrado         }
306513d1ffcSCarson Labrado 
3070d5f5cf4SEd Tanous         timer.cancel();
308bd030d0aSAppaRao Puli         if (ec)
309bd030d0aSAppaRao Puli         {
310a716aa74SEd Tanous             BMCWEB_LOG_ERROR("sendMessage() failed: {} {}", ec.message(), host);
311e38778a5SAppaRao Puli             state = ConnState::sendFailed;
312e38778a5SAppaRao Puli             waitAndRetry();
313bd030d0aSAppaRao Puli             return;
314bd030d0aSAppaRao Puli         }
31562598e31SEd Tanous         BMCWEB_LOG_DEBUG("sendMessage() bytes transferred: {}",
31662598e31SEd Tanous                          bytesTransferred);
317bd030d0aSAppaRao Puli 
318e38778a5SAppaRao Puli         recvMessage();
319bd030d0aSAppaRao Puli     }
320bd030d0aSAppaRao Puli 
321bd030d0aSAppaRao Puli     void recvMessage()
322bd030d0aSAppaRao Puli     {
3236eaa1d2fSSunitha Harish         state = ConnState::recvInProgress;
3246eaa1d2fSSunitha Harish 
325e01d0c36SEd Tanous         parser_type& thisParser = parser.emplace(std::piecewise_construct,
326e01d0c36SEd Tanous                                                  std::make_tuple());
327d14a48ffSCarson Labrado 
328e01d0c36SEd Tanous         thisParser.body_limit(connPolicy->requestByteLimit);
3296eaa1d2fSSunitha Harish 
3300d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
3310d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
3320d5f5cf4SEd Tanous 
333bd030d0aSAppaRao Puli         // Receive the HTTP response
334e38778a5SAppaRao Puli         if (sslConn)
335e38778a5SAppaRao Puli         {
336e38778a5SAppaRao Puli             boost::beast::http::async_read(
337e01d0c36SEd Tanous                 *sslConn, buffer, thisParser,
338e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterRead, this,
339e38778a5SAppaRao Puli                                 shared_from_this()));
340e38778a5SAppaRao Puli         }
341e38778a5SAppaRao Puli         else
342e38778a5SAppaRao Puli         {
343bd030d0aSAppaRao Puli             boost::beast::http::async_read(
344e01d0c36SEd Tanous                 conn, buffer, thisParser,
345e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterRead, this,
346e38778a5SAppaRao Puli                                 shared_from_this()));
347e38778a5SAppaRao Puli         }
348e38778a5SAppaRao Puli     }
349e38778a5SAppaRao Puli 
350e38778a5SAppaRao Puli     void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/,
351e38778a5SAppaRao Puli                    const boost::beast::error_code& ec,
352e38778a5SAppaRao Puli                    const std::size_t& bytesTransferred)
353e38778a5SAppaRao Puli     {
354513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
355513d1ffcSCarson Labrado         // this branch
356513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
357513d1ffcSCarson Labrado         {
358513d1ffcSCarson Labrado             return;
359513d1ffcSCarson Labrado         }
360513d1ffcSCarson Labrado 
3610d5f5cf4SEd Tanous         timer.cancel();
362e38778a5SAppaRao Puli         if (ec && ec != boost::asio::ssl::error::stream_truncated)
363bd030d0aSAppaRao Puli         {
364a716aa74SEd Tanous             BMCWEB_LOG_ERROR("recvMessage() failed: {} from {}", ec.message(),
365a716aa74SEd Tanous                              host);
366e38778a5SAppaRao Puli             state = ConnState::recvFailed;
367e38778a5SAppaRao Puli             waitAndRetry();
368bd030d0aSAppaRao Puli             return;
369bd030d0aSAppaRao Puli         }
37062598e31SEd Tanous         BMCWEB_LOG_DEBUG("recvMessage() bytes transferred: {}",
37162598e31SEd Tanous                          bytesTransferred);
372e01d0c36SEd Tanous         if (!parser)
373e01d0c36SEd Tanous         {
374e01d0c36SEd Tanous             return;
375e01d0c36SEd Tanous         }
37652e31629SEd Tanous         BMCWEB_LOG_DEBUG("recvMessage() data: {}", parser->get().body().str());
377bd030d0aSAppaRao Puli 
378e38778a5SAppaRao Puli         unsigned int respCode = parser->get().result_int();
37962598e31SEd Tanous         BMCWEB_LOG_DEBUG("recvMessage() Header Response Code: {}", respCode);
3806eaa1d2fSSunitha Harish 
381f3cb5df9SAbhilash Raju         // Handle the case of stream_truncated.  Some servers close the ssl
382f3cb5df9SAbhilash Raju         // connection uncleanly, so check to see if we got a full response
383f3cb5df9SAbhilash Raju         // before we handle this as an error.
384f3cb5df9SAbhilash Raju         if (!parser->is_done())
385f3cb5df9SAbhilash Raju         {
386f3cb5df9SAbhilash Raju             state = ConnState::recvFailed;
387f3cb5df9SAbhilash Raju             waitAndRetry();
388f3cb5df9SAbhilash Raju             return;
389f3cb5df9SAbhilash Raju         }
390f3cb5df9SAbhilash Raju 
391a7a80296SCarson Labrado         // Make sure the received response code is valid as defined by
392a7a80296SCarson Labrado         // the associated retry policy
393d14a48ffSCarson Labrado         if (connPolicy->invalidResp(respCode))
3946eaa1d2fSSunitha Harish         {
3956eaa1d2fSSunitha Harish             // The listener failed to receive the Sent-Event
39662598e31SEd Tanous             BMCWEB_LOG_ERROR(
39762598e31SEd Tanous                 "recvMessage() Listener Failed to "
398a716aa74SEd Tanous                 "receive Sent-Event. Header Response Code: {} from {}",
399a716aa74SEd Tanous                 respCode, host);
400e38778a5SAppaRao Puli             state = ConnState::recvFailed;
401e38778a5SAppaRao Puli             waitAndRetry();
4026eaa1d2fSSunitha Harish             return;
4036eaa1d2fSSunitha Harish         }
404bd030d0aSAppaRao Puli 
405f52c03c1SCarson Labrado         // Send is successful
406f52c03c1SCarson Labrado         // Reset the counter just in case this was after retrying
407e38778a5SAppaRao Puli         retryCount = 0;
4086eaa1d2fSSunitha Harish 
4096eaa1d2fSSunitha Harish         // Keep the connection alive if server supports it
4106eaa1d2fSSunitha Harish         // Else close the connection
41162598e31SEd Tanous         BMCWEB_LOG_DEBUG("recvMessage() keepalive : {}", parser->keep_alive());
4126eaa1d2fSSunitha Harish 
413039a47e3SCarson Labrado         // Copy the response into a Response object so that it can be
414039a47e3SCarson Labrado         // processed by the callback function.
41527b0cf90SEd Tanous         res.response = parser->release();
416e38778a5SAppaRao Puli         callback(parser->keep_alive(), connId, res);
417513d1ffcSCarson Labrado         res.clear();
418bd030d0aSAppaRao Puli     }
419bd030d0aSAppaRao Puli 
4200d5f5cf4SEd Tanous     static void onTimeout(const std::weak_ptr<ConnectionInfo>& weakSelf,
4215e7e2dc5SEd Tanous                           const boost::system::error_code& ec)
4220d5f5cf4SEd Tanous     {
4230d5f5cf4SEd Tanous         if (ec == boost::asio::error::operation_aborted)
4240d5f5cf4SEd Tanous         {
42562598e31SEd Tanous             BMCWEB_LOG_DEBUG(
42662598e31SEd Tanous                 "async_wait failed since the operation is aborted");
4270d5f5cf4SEd Tanous             return;
4280d5f5cf4SEd Tanous         }
4290d5f5cf4SEd Tanous         if (ec)
4300d5f5cf4SEd Tanous         {
43162598e31SEd Tanous             BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
43227b0cf90SEd Tanous             // If the timer fails, we need to close the socket anyway, same
43327b0cf90SEd Tanous             // as if it expired.
4340d5f5cf4SEd Tanous         }
4350d5f5cf4SEd Tanous         std::shared_ptr<ConnectionInfo> self = weakSelf.lock();
4360d5f5cf4SEd Tanous         if (self == nullptr)
4370d5f5cf4SEd Tanous         {
4380d5f5cf4SEd Tanous             return;
4390d5f5cf4SEd Tanous         }
4400d5f5cf4SEd Tanous         self->waitAndRetry();
4410d5f5cf4SEd Tanous     }
4420d5f5cf4SEd Tanous 
4436eaa1d2fSSunitha Harish     void waitAndRetry()
444bd030d0aSAppaRao Puli     {
445d14a48ffSCarson Labrado         if ((retryCount >= connPolicy->maxRetryAttempts) ||
446e38778a5SAppaRao Puli             (state == ConnState::sslInitFailed))
4472a5689a7SAppaRao Puli         {
448a716aa74SEd Tanous             BMCWEB_LOG_ERROR("Maximum number of retries reached. {}", host);
44962598e31SEd Tanous             BMCWEB_LOG_DEBUG("Retry policy: {}", connPolicy->retryPolicyAction);
450039a47e3SCarson Labrado 
451d14a48ffSCarson Labrado             if (connPolicy->retryPolicyAction == "TerminateAfterRetries")
452fe44eb0bSAyushi Smriti             {
453fe44eb0bSAyushi Smriti                 // TODO: delete subscription
454fe44eb0bSAyushi Smriti                 state = ConnState::terminated;
455fe44eb0bSAyushi Smriti             }
456d14a48ffSCarson Labrado             if (connPolicy->retryPolicyAction == "SuspendRetries")
457fe44eb0bSAyushi Smriti             {
4582a5689a7SAppaRao Puli                 state = ConnState::suspended;
4592a5689a7SAppaRao Puli             }
460513d1ffcSCarson Labrado 
461513d1ffcSCarson Labrado             // We want to return a 502 to indicate there was an error with
462513d1ffcSCarson Labrado             // the external server
463513d1ffcSCarson Labrado             res.result(boost::beast::http::status::bad_gateway);
464513d1ffcSCarson Labrado             callback(false, connId, res);
465513d1ffcSCarson Labrado             res.clear();
466513d1ffcSCarson Labrado 
46727b0cf90SEd Tanous             // Reset the retrycount to zero so that client can try
46827b0cf90SEd Tanous             // connecting again if needed
469fe44eb0bSAyushi Smriti             retryCount = 0;
4702a5689a7SAppaRao Puli             return;
4712a5689a7SAppaRao Puli         }
4722a5689a7SAppaRao Puli 
4732a5689a7SAppaRao Puli         retryCount++;
474fe44eb0bSAyushi Smriti 
47562598e31SEd Tanous         BMCWEB_LOG_DEBUG("Attempt retry after {} seconds. RetryCount = {}",
476a716aa74SEd Tanous                          connPolicy->retryIntervalSecs.count(), retryCount);
477d14a48ffSCarson Labrado         timer.expires_after(connPolicy->retryIntervalSecs);
4783d36e3a5SEd Tanous         timer.async_wait(std::bind_front(&ConnectionInfo::onTimerDone, this,
4793d36e3a5SEd Tanous                                          shared_from_this()));
4803d36e3a5SEd Tanous     }
4813d36e3a5SEd Tanous 
4823d36e3a5SEd Tanous     void onTimerDone(const std::shared_ptr<ConnectionInfo>& /*self*/,
4833d36e3a5SEd Tanous                      const boost::system::error_code& ec)
4843d36e3a5SEd Tanous     {
4856eaa1d2fSSunitha Harish         if (ec == boost::asio::error::operation_aborted)
4866eaa1d2fSSunitha Harish         {
48762598e31SEd Tanous             BMCWEB_LOG_DEBUG(
48862598e31SEd Tanous                 "async_wait failed since the operation is aborted{}",
48962598e31SEd Tanous                 ec.message());
4906eaa1d2fSSunitha Harish         }
4916eaa1d2fSSunitha Harish         else if (ec)
4926eaa1d2fSSunitha Harish         {
49362598e31SEd Tanous             BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
4946eaa1d2fSSunitha Harish             // Ignore the error and continue the retry loop to attempt
4956eaa1d2fSSunitha Harish             // sending the event as per the retry policy
4966eaa1d2fSSunitha Harish         }
4976eaa1d2fSSunitha Harish 
498f52c03c1SCarson Labrado         // Let's close the connection and restart from resolve.
499f3cb5df9SAbhilash Raju         shutdownConn(true);
500f3cb5df9SAbhilash Raju     }
501f3cb5df9SAbhilash Raju 
502f3cb5df9SAbhilash Raju     void restartConnection()
503f3cb5df9SAbhilash Raju     {
504f3cb5df9SAbhilash Raju         BMCWEB_LOG_DEBUG("{}, id: {}  restartConnection", host,
505f3cb5df9SAbhilash Raju                          std::to_string(connId));
506f3cb5df9SAbhilash Raju         initializeConnection(host.scheme() == "https");
507f3cb5df9SAbhilash Raju         doResolve();
5082a5689a7SAppaRao Puli     }
5092a5689a7SAppaRao Puli 
510e38778a5SAppaRao Puli     void shutdownConn(bool retry)
511fe44eb0bSAyushi Smriti     {
512f52c03c1SCarson Labrado         boost::beast::error_code ec;
5130d5f5cf4SEd Tanous         conn.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
514f52c03c1SCarson Labrado         conn.close();
515f52c03c1SCarson Labrado 
516f52c03c1SCarson Labrado         // not_connected happens sometimes so don't bother reporting it.
517f52c03c1SCarson Labrado         if (ec && ec != boost::beast::errc::not_connected)
5182a5689a7SAppaRao Puli         {
519a716aa74SEd Tanous             BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId,
52062598e31SEd Tanous                              ec.message());
5216eaa1d2fSSunitha Harish         }
5225cab68f3SCarson Labrado         else
5235cab68f3SCarson Labrado         {
524a716aa74SEd Tanous             BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId);
5255cab68f3SCarson Labrado         }
526ca723762SEd Tanous 
527e38778a5SAppaRao Puli         if (retry)
52892a74e56SAppaRao Puli         {
529f52c03c1SCarson Labrado             // Now let's try to resend the data
530f52c03c1SCarson Labrado             state = ConnState::retry;
531f3cb5df9SAbhilash Raju             restartConnection();
532e38778a5SAppaRao Puli         }
533e38778a5SAppaRao Puli         else
534e38778a5SAppaRao Puli         {
535e38778a5SAppaRao Puli             state = ConnState::closed;
536e38778a5SAppaRao Puli         }
537e38778a5SAppaRao Puli     }
538e38778a5SAppaRao Puli 
539e38778a5SAppaRao Puli     void doClose(bool retry = false)
540e38778a5SAppaRao Puli     {
541e38778a5SAppaRao Puli         if (!sslConn)
542e38778a5SAppaRao Puli         {
543e38778a5SAppaRao Puli             shutdownConn(retry);
544e38778a5SAppaRao Puli             return;
545e38778a5SAppaRao Puli         }
546e38778a5SAppaRao Puli 
547e38778a5SAppaRao Puli         sslConn->async_shutdown(
548e38778a5SAppaRao Puli             std::bind_front(&ConnectionInfo::afterSslShutdown, this,
549e38778a5SAppaRao Puli                             shared_from_this(), retry));
550e38778a5SAppaRao Puli     }
551e38778a5SAppaRao Puli 
552e38778a5SAppaRao Puli     void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/,
553e38778a5SAppaRao Puli                           bool retry, const boost::system::error_code& ec)
554e38778a5SAppaRao Puli     {
555e38778a5SAppaRao Puli         if (ec)
556e38778a5SAppaRao Puli         {
557a716aa74SEd Tanous             BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId,
55862598e31SEd Tanous                              ec.message());
559e38778a5SAppaRao Puli         }
560e38778a5SAppaRao Puli         else
561e38778a5SAppaRao Puli         {
562a716aa74SEd Tanous             BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId);
563e38778a5SAppaRao Puli         }
564e38778a5SAppaRao Puli         shutdownConn(retry);
565e38778a5SAppaRao Puli     }
566e38778a5SAppaRao Puli 
567e38778a5SAppaRao Puli     void setCipherSuiteTLSext()
568e38778a5SAppaRao Puli     {
569e38778a5SAppaRao Puli         if (!sslConn)
570e38778a5SAppaRao Puli         {
571e38778a5SAppaRao Puli             return;
572e38778a5SAppaRao Puli         }
573e7c2991eSRavi Teja 
574e7c2991eSRavi Teja         if (host.host_type() != boost::urls::host_type::name)
575e7c2991eSRavi Teja         {
576e7c2991eSRavi Teja             // Avoid setting SNI hostname if its IP address
577e7c2991eSRavi Teja             return;
578e7c2991eSRavi Teja         }
579e7c2991eSRavi Teja         // Create a null terminated string for SSL
580a716aa74SEd Tanous         std::string hostname(host.encoded_host_address());
581e38778a5SAppaRao Puli         // NOTE: The SSL_set_tlsext_host_name is defined in tlsv1.h header
582e38778a5SAppaRao Puli         // file but its having old style casting (name is cast to void*).
583e38778a5SAppaRao Puli         // Since bmcweb compiler treats all old-style-cast as error, its
584e38778a5SAppaRao Puli         // causing the build failure. So replaced the same macro inline and
585e38778a5SAppaRao Puli         // did corrected the code by doing static_cast to viod*. This has to
586e38778a5SAppaRao Puli         // be fixed in openssl library in long run. Set SNI Hostname (many
587e38778a5SAppaRao Puli         // hosts need this to handshake successfully)
588e38778a5SAppaRao Puli         if (SSL_ctrl(sslConn->native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME,
589e38778a5SAppaRao Puli                      TLSEXT_NAMETYPE_host_name,
590a716aa74SEd Tanous                      static_cast<void*>(hostname.data())) == 0)
591e38778a5SAppaRao Puli 
592e38778a5SAppaRao Puli         {
593e38778a5SAppaRao Puli             boost::beast::error_code ec{static_cast<int>(::ERR_get_error()),
594e38778a5SAppaRao Puli                                         boost::asio::error::get_ssl_category()};
595e38778a5SAppaRao Puli 
596a716aa74SEd Tanous             BMCWEB_LOG_ERROR("SSL_set_tlsext_host_name {}, id: {} failed: {}",
597a716aa74SEd Tanous                              host, connId, ec.message());
598e38778a5SAppaRao Puli             // Set state as sslInit failed so that we close the connection
599e38778a5SAppaRao Puli             // and take appropriate action as per retry configuration.
600e38778a5SAppaRao Puli             state = ConnState::sslInitFailed;
601e38778a5SAppaRao Puli             waitAndRetry();
602e38778a5SAppaRao Puli             return;
603e38778a5SAppaRao Puli         }
604bd030d0aSAppaRao Puli     }
605bd030d0aSAppaRao Puli 
606f3cb5df9SAbhilash Raju     void initializeConnection(bool ssl)
607e38778a5SAppaRao Puli     {
608f3cb5df9SAbhilash Raju         conn = boost::asio::ip::tcp::socket(ioc);
609f3cb5df9SAbhilash Raju         if (ssl)
610e38778a5SAppaRao Puli         {
611e38778a5SAppaRao Puli             std::optional<boost::asio::ssl::context> sslCtx =
612e38778a5SAppaRao Puli                 ensuressl::getSSLClientContext();
613e38778a5SAppaRao Puli 
614e38778a5SAppaRao Puli             if (!sslCtx)
615e38778a5SAppaRao Puli             {
616a716aa74SEd Tanous                 BMCWEB_LOG_ERROR("prepareSSLContext failed - {}, id: {}", host,
617a716aa74SEd Tanous                                  connId);
618e38778a5SAppaRao Puli                 // Don't retry if failure occurs while preparing SSL context
61927b0cf90SEd Tanous                 // such as certificate is invalid or set cipher failure or
62027b0cf90SEd Tanous                 // set host name failure etc... Setting conn state to
62127b0cf90SEd Tanous                 // sslInitFailed and connection state will be transitioned
62227b0cf90SEd Tanous                 // to next state depending on retry policy set by
62327b0cf90SEd Tanous                 // subscription.
624e38778a5SAppaRao Puli                 state = ConnState::sslInitFailed;
625e38778a5SAppaRao Puli                 waitAndRetry();
626e38778a5SAppaRao Puli                 return;
627e38778a5SAppaRao Puli             }
628e38778a5SAppaRao Puli             sslConn.emplace(conn, *sslCtx);
629e38778a5SAppaRao Puli             setCipherSuiteTLSext();
630e38778a5SAppaRao Puli         }
631e38778a5SAppaRao Puli     }
632f3cb5df9SAbhilash Raju 
633f3cb5df9SAbhilash Raju   public:
634f3cb5df9SAbhilash Raju     explicit ConnectionInfo(
635f3cb5df9SAbhilash Raju         boost::asio::io_context& iocIn, const std::string& idIn,
636f3cb5df9SAbhilash Raju         const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
6374a7fbefdSEd Tanous         const boost::urls::url_view_base& hostIn, unsigned int connIdIn) :
638f3cb5df9SAbhilash Raju         subId(idIn),
639f3cb5df9SAbhilash Raju         connPolicy(connPolicyIn), host(hostIn), connId(connIdIn), ioc(iocIn),
640f3cb5df9SAbhilash Raju         resolver(iocIn), conn(iocIn), timer(iocIn)
641f3cb5df9SAbhilash Raju     {
642f3cb5df9SAbhilash Raju         initializeConnection(host.scheme() == "https");
643f3cb5df9SAbhilash Raju     }
644f52c03c1SCarson Labrado };
645bd030d0aSAppaRao Puli 
646f52c03c1SCarson Labrado class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
647bd030d0aSAppaRao Puli {
648f52c03c1SCarson Labrado   private:
649f52c03c1SCarson Labrado     boost::asio::io_context& ioc;
650e38778a5SAppaRao Puli     std::string id;
651d14a48ffSCarson Labrado     std::shared_ptr<ConnectionPolicy> connPolicy;
652a716aa74SEd Tanous     boost::urls::url destIP;
653f52c03c1SCarson Labrado     std::vector<std::shared_ptr<ConnectionInfo>> connections;
654f52c03c1SCarson Labrado     boost::container::devector<PendingRequest> requestQueue;
655f52c03c1SCarson Labrado 
656f52c03c1SCarson Labrado     friend class HttpClient;
657f52c03c1SCarson Labrado 
658244256ccSCarson Labrado     // Configure a connections's request, callback, and retry info in
659244256ccSCarson Labrado     // preparation to begin sending the request
660f52c03c1SCarson Labrado     void setConnProps(ConnectionInfo& conn)
661bd030d0aSAppaRao Puli     {
662f52c03c1SCarson Labrado         if (requestQueue.empty())
663f52c03c1SCarson Labrado         {
66462598e31SEd Tanous             BMCWEB_LOG_ERROR(
66562598e31SEd Tanous                 "setConnProps() should not have been called when requestQueue is empty");
666bd030d0aSAppaRao Puli             return;
667bd030d0aSAppaRao Puli         }
668bd030d0aSAppaRao Puli 
66952e31629SEd Tanous         PendingRequest& nextReq = requestQueue.front();
670244256ccSCarson Labrado         conn.req = std::move(nextReq.req);
671244256ccSCarson Labrado         conn.callback = std::move(nextReq.callback);
672f52c03c1SCarson Labrado 
673a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Setting properties for connection {}, id: {}",
674a716aa74SEd Tanous                          conn.host, conn.connId);
675f52c03c1SCarson Labrado 
676f52c03c1SCarson Labrado         // We can remove the request from the queue at this point
677f52c03c1SCarson Labrado         requestQueue.pop_front();
678f52c03c1SCarson Labrado     }
679f52c03c1SCarson Labrado 
680f52c03c1SCarson Labrado     // Gets called as part of callback after request is sent
681f52c03c1SCarson Labrado     // Reuses the connection if there are any requests waiting to be sent
682f52c03c1SCarson Labrado     // Otherwise closes the connection if it is not a keep-alive
683f52c03c1SCarson Labrado     void sendNext(bool keepAlive, uint32_t connId)
684f52c03c1SCarson Labrado     {
685f52c03c1SCarson Labrado         auto conn = connections[connId];
68646a81465SCarson Labrado 
68746a81465SCarson Labrado         // Allow the connection's handler to be deleted
68846a81465SCarson Labrado         // This is needed because of Redfish Aggregation passing an
68946a81465SCarson Labrado         // AsyncResponse shared_ptr to this callback
69046a81465SCarson Labrado         conn->callback = nullptr;
69146a81465SCarson Labrado 
692f52c03c1SCarson Labrado         // Reuse the connection to send the next request in the queue
693f52c03c1SCarson Labrado         if (!requestQueue.empty())
694f52c03c1SCarson Labrado         {
69562598e31SEd Tanous             BMCWEB_LOG_DEBUG(
6968ece0e45SEd Tanous                 "{} requests remaining in queue for {}, reusing connection {}",
697a716aa74SEd Tanous                 requestQueue.size(), destIP, connId);
698f52c03c1SCarson Labrado 
699f52c03c1SCarson Labrado             setConnProps(*conn);
700f52c03c1SCarson Labrado 
701f52c03c1SCarson Labrado             if (keepAlive)
702f52c03c1SCarson Labrado             {
703f52c03c1SCarson Labrado                 conn->sendMessage();
7042a5689a7SAppaRao Puli             }
7052a5689a7SAppaRao Puli             else
7062a5689a7SAppaRao Puli             {
707f52c03c1SCarson Labrado                 // Server is not keep-alive enabled so we need to close the
708f52c03c1SCarson Labrado                 // connection and then start over from resolve
709f52c03c1SCarson Labrado                 conn->doClose();
710f52c03c1SCarson Labrado                 conn->doResolve();
711f52c03c1SCarson Labrado             }
712f52c03c1SCarson Labrado             return;
713f52c03c1SCarson Labrado         }
714f52c03c1SCarson Labrado 
715f52c03c1SCarson Labrado         // No more messages to send so close the connection if necessary
716f52c03c1SCarson Labrado         if (keepAlive)
717f52c03c1SCarson Labrado         {
718f52c03c1SCarson Labrado             conn->state = ConnState::idle;
719f52c03c1SCarson Labrado         }
720f52c03c1SCarson Labrado         else
721f52c03c1SCarson Labrado         {
722f52c03c1SCarson Labrado             // Abort the connection since server is not keep-alive enabled
723f52c03c1SCarson Labrado             conn->state = ConnState::abortConnection;
724f52c03c1SCarson Labrado             conn->doClose();
7252a5689a7SAppaRao Puli         }
726bd030d0aSAppaRao Puli     }
727bd030d0aSAppaRao Puli 
7284a7fbefdSEd Tanous     void sendData(std::string&& data, const boost::urls::url_view_base& destUri,
729244256ccSCarson Labrado                   const boost::beast::http::fields& httpHeader,
730244256ccSCarson Labrado                   const boost::beast::http::verb verb,
7316b3db60dSEd Tanous                   const std::function<void(Response&)>& resHandler)
732fe44eb0bSAyushi Smriti     {
733244256ccSCarson Labrado         // Construct the request to be sent
734b2896149SEd Tanous         boost::beast::http::request<bmcweb::HttpBody> thisReq(
735a716aa74SEd Tanous             verb, destUri.encoded_target(), 11, "", httpHeader);
736a716aa74SEd Tanous         thisReq.set(boost::beast::http::field::host,
737a716aa74SEd Tanous                     destUri.encoded_host_address());
738244256ccSCarson Labrado         thisReq.keep_alive(true);
73952e31629SEd Tanous         thisReq.body().str() = std::move(data);
740244256ccSCarson Labrado         thisReq.prepare_payload();
7413d36e3a5SEd Tanous         auto cb = std::bind_front(&ConnectionPool::afterSendData,
7423d36e3a5SEd Tanous                                   weak_from_this(), resHandler);
743f52c03c1SCarson Labrado         // Reuse an existing connection if one is available
744f52c03c1SCarson Labrado         for (unsigned int i = 0; i < connections.size(); i++)
745fe44eb0bSAyushi Smriti         {
746f52c03c1SCarson Labrado             auto conn = connections[i];
747f52c03c1SCarson Labrado             if ((conn->state == ConnState::idle) ||
748f52c03c1SCarson Labrado                 (conn->state == ConnState::initialized) ||
749f52c03c1SCarson Labrado                 (conn->state == ConnState::closed))
750f52c03c1SCarson Labrado             {
751244256ccSCarson Labrado                 conn->req = std::move(thisReq);
752f52c03c1SCarson Labrado                 conn->callback = std::move(cb);
753a716aa74SEd Tanous                 std::string commonMsg = std::format("{} from pool {}", i, id);
754f52c03c1SCarson Labrado 
755f52c03c1SCarson Labrado                 if (conn->state == ConnState::idle)
756f52c03c1SCarson Labrado                 {
75762598e31SEd Tanous                     BMCWEB_LOG_DEBUG("Grabbing idle connection {}", commonMsg);
758f52c03c1SCarson Labrado                     conn->sendMessage();
759f52c03c1SCarson Labrado                 }
760f52c03c1SCarson Labrado                 else
761f52c03c1SCarson Labrado                 {
76262598e31SEd Tanous                     BMCWEB_LOG_DEBUG("Reusing existing connection {}",
76362598e31SEd Tanous                                      commonMsg);
764f52c03c1SCarson Labrado                     conn->doResolve();
765f52c03c1SCarson Labrado                 }
766f52c03c1SCarson Labrado                 return;
767f52c03c1SCarson Labrado             }
768f52c03c1SCarson Labrado         }
769f52c03c1SCarson Labrado 
77027b0cf90SEd Tanous         // All connections in use so create a new connection or add request
77127b0cf90SEd Tanous         // to the queue
772d14a48ffSCarson Labrado         if (connections.size() < connPolicy->maxConnections)
773f52c03c1SCarson Labrado         {
774a716aa74SEd Tanous             BMCWEB_LOG_DEBUG("Adding new connection to pool {}", id);
775f52c03c1SCarson Labrado             auto conn = addConnection();
776244256ccSCarson Labrado             conn->req = std::move(thisReq);
777f52c03c1SCarson Labrado             conn->callback = std::move(cb);
778f52c03c1SCarson Labrado             conn->doResolve();
779f52c03c1SCarson Labrado         }
780f52c03c1SCarson Labrado         else if (requestQueue.size() < maxRequestQueueSize)
781f52c03c1SCarson Labrado         {
782a716aa74SEd Tanous             BMCWEB_LOG_DEBUG("Max pool size reached. Adding data to queue {}",
783a716aa74SEd Tanous                              id);
784d14a48ffSCarson Labrado             requestQueue.emplace_back(std::move(thisReq), std::move(cb));
785f52c03c1SCarson Labrado         }
786f52c03c1SCarson Labrado         else
787f52c03c1SCarson Labrado         {
78827b0cf90SEd Tanous             // If we can't buffer the request then we should let the
78927b0cf90SEd Tanous             // callback handle a 429 Too Many Requests dummy response
7906ea90760SEd Tanous             BMCWEB_LOG_ERROR("{} request queue full.  Dropping request.", id);
79143e14d38SCarson Labrado             Response dummyRes;
79243e14d38SCarson Labrado             dummyRes.result(boost::beast::http::status::too_many_requests);
79343e14d38SCarson Labrado             resHandler(dummyRes);
794f52c03c1SCarson Labrado         }
795f52c03c1SCarson Labrado     }
796f52c03c1SCarson Labrado 
7973d36e3a5SEd Tanous     // Callback to be called once the request has been sent
7983d36e3a5SEd Tanous     static void afterSendData(const std::weak_ptr<ConnectionPool>& weakSelf,
7993d36e3a5SEd Tanous                               const std::function<void(Response&)>& resHandler,
8003d36e3a5SEd Tanous                               bool keepAlive, uint32_t connId, Response& res)
8013d36e3a5SEd Tanous     {
8023d36e3a5SEd Tanous         // Allow provided callback to perform additional processing of the
8033d36e3a5SEd Tanous         // request
8043d36e3a5SEd Tanous         resHandler(res);
8053d36e3a5SEd Tanous 
8063d36e3a5SEd Tanous         // If requests remain in the queue then we want to reuse this
8073d36e3a5SEd Tanous         // connection to send the next request
8083d36e3a5SEd Tanous         std::shared_ptr<ConnectionPool> self = weakSelf.lock();
8093d36e3a5SEd Tanous         if (!self)
8103d36e3a5SEd Tanous         {
81162598e31SEd Tanous             BMCWEB_LOG_CRITICAL("{} Failed to capture connection",
81262598e31SEd Tanous                                 logPtr(self.get()));
8133d36e3a5SEd Tanous             return;
8143d36e3a5SEd Tanous         }
8153d36e3a5SEd Tanous 
8163d36e3a5SEd Tanous         self->sendNext(keepAlive, connId);
8173d36e3a5SEd Tanous     }
8183d36e3a5SEd Tanous 
819f52c03c1SCarson Labrado     std::shared_ptr<ConnectionInfo>& addConnection()
820f52c03c1SCarson Labrado     {
821f52c03c1SCarson Labrado         unsigned int newId = static_cast<unsigned int>(connections.size());
822f52c03c1SCarson Labrado 
823e38778a5SAppaRao Puli         auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>(
824a716aa74SEd Tanous             ioc, id, connPolicy, destIP, newId));
825f52c03c1SCarson Labrado 
826a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Added connection {} to pool {}",
827a716aa74SEd Tanous                          connections.size() - 1, id);
828f52c03c1SCarson Labrado 
829f52c03c1SCarson Labrado         return ret;
830f52c03c1SCarson Labrado     }
831f52c03c1SCarson Labrado 
832f52c03c1SCarson Labrado   public:
833d14a48ffSCarson Labrado     explicit ConnectionPool(
834d14a48ffSCarson Labrado         boost::asio::io_context& iocIn, const std::string& idIn,
835d14a48ffSCarson Labrado         const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
8364a7fbefdSEd Tanous         const boost::urls::url_view_base& destIPIn) :
8378a592810SEd Tanous         ioc(iocIn),
838a716aa74SEd Tanous         id(idIn), connPolicy(connPolicyIn), destIP(destIPIn)
839f52c03c1SCarson Labrado     {
840a716aa74SEd Tanous         BMCWEB_LOG_DEBUG("Initializing connection pool for {}", id);
841f52c03c1SCarson Labrado 
842f52c03c1SCarson Labrado         // Initialize the pool with a single connection
843f52c03c1SCarson Labrado         addConnection();
844fe44eb0bSAyushi Smriti     }
845bd030d0aSAppaRao Puli };
846bd030d0aSAppaRao Puli 
847f52c03c1SCarson Labrado class HttpClient
848f52c03c1SCarson Labrado {
849f52c03c1SCarson Labrado   private:
850f52c03c1SCarson Labrado     std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
851f52c03c1SCarson Labrado         connectionPools;
852f8ca6d79SEd Tanous     boost::asio::io_context& ioc;
853d14a48ffSCarson Labrado     std::shared_ptr<ConnectionPolicy> connPolicy;
854f52c03c1SCarson Labrado 
855039a47e3SCarson Labrado     // Used as a dummy callback by sendData() in order to call
856039a47e3SCarson Labrado     // sendDataWithCallback()
85702cad96eSEd Tanous     static void genericResHandler(const Response& res)
858039a47e3SCarson Labrado     {
85962598e31SEd Tanous         BMCWEB_LOG_DEBUG("Response handled with return code: {}",
860a716aa74SEd Tanous                          res.resultInt());
8614ee8e211SEd Tanous     }
862039a47e3SCarson Labrado 
863f52c03c1SCarson Labrado   public:
864d14a48ffSCarson Labrado     HttpClient() = delete;
865f8ca6d79SEd Tanous     explicit HttpClient(boost::asio::io_context& iocIn,
866f8ca6d79SEd Tanous                         const std::shared_ptr<ConnectionPolicy>& connPolicyIn) :
867f8ca6d79SEd Tanous         ioc(iocIn),
868d14a48ffSCarson Labrado         connPolicy(connPolicyIn)
869d14a48ffSCarson Labrado     {}
870f8ca6d79SEd Tanous 
871f52c03c1SCarson Labrado     HttpClient(const HttpClient&) = delete;
872f52c03c1SCarson Labrado     HttpClient& operator=(const HttpClient&) = delete;
873f52c03c1SCarson Labrado     HttpClient(HttpClient&&) = delete;
874f52c03c1SCarson Labrado     HttpClient& operator=(HttpClient&&) = delete;
875f52c03c1SCarson Labrado     ~HttpClient() = default;
876f52c03c1SCarson Labrado 
877a716aa74SEd Tanous     // Send a request to destIP where additional processing of the
878039a47e3SCarson Labrado     // result is not required
8794a7fbefdSEd Tanous     void sendData(std::string&& data, const boost::urls::url_view_base& destUri,
880f52c03c1SCarson Labrado                   const boost::beast::http::fields& httpHeader,
881d14a48ffSCarson Labrado                   const boost::beast::http::verb verb)
882f52c03c1SCarson Labrado     {
883e38778a5SAppaRao Puli         const std::function<void(Response&)> cb = genericResHandler;
884a716aa74SEd Tanous         sendDataWithCallback(std::move(data), destUri, httpHeader, verb, cb);
885039a47e3SCarson Labrado     }
886039a47e3SCarson Labrado 
887a716aa74SEd Tanous     // Send request to destIP and use the provided callback to
888039a47e3SCarson Labrado     // handle the response
8894a7fbefdSEd Tanous     void sendDataWithCallback(std::string&& data,
8904a7fbefdSEd Tanous                               const boost::urls::url_view_base& destUrl,
891039a47e3SCarson Labrado                               const boost::beast::http::fields& httpHeader,
892244256ccSCarson Labrado                               const boost::beast::http::verb verb,
8936b3db60dSEd Tanous                               const std::function<void(Response&)>& resHandler)
894039a47e3SCarson Labrado     {
895a716aa74SEd Tanous         std::string clientKey = std::format("{}://{}", destUrl.scheme(),
896a716aa74SEd Tanous                                             destUrl.encoded_host_and_port());
897d14a48ffSCarson Labrado         auto pool = connectionPools.try_emplace(clientKey);
898d14a48ffSCarson Labrado         if (pool.first->second == nullptr)
899f52c03c1SCarson Labrado         {
900d14a48ffSCarson Labrado             pool.first->second = std::make_shared<ConnectionPool>(
901a716aa74SEd Tanous                 ioc, clientKey, connPolicy, destUrl);
902f52c03c1SCarson Labrado         }
90327b0cf90SEd Tanous         // Send the data using either the existing connection pool or the
90427b0cf90SEd Tanous         // newly created connection pool
905a716aa74SEd Tanous         pool.first->second->sendData(std::move(data), destUrl, httpHeader, verb,
906e38778a5SAppaRao Puli                                      resHandler);
907f52c03c1SCarson Labrado     }
908f52c03c1SCarson Labrado };
909bd030d0aSAppaRao Puli } // namespace crow
910