xref: /openbmc/bmcweb/http/http_client.hpp (revision e1452bea)
1bd030d0aSAppaRao Puli /*
2bd030d0aSAppaRao Puli // Copyright (c) 2020 Intel Corporation
3bd030d0aSAppaRao Puli //
4bd030d0aSAppaRao Puli // Licensed under the Apache License, Version 2.0 (the "License");
5bd030d0aSAppaRao Puli // you may not use this file except in compliance with the License.
6bd030d0aSAppaRao Puli // You may obtain a copy of the License at
7bd030d0aSAppaRao Puli //
8bd030d0aSAppaRao Puli //      http://www.apache.org/licenses/LICENSE-2.0
9bd030d0aSAppaRao Puli //
10bd030d0aSAppaRao Puli // Unless required by applicable law or agreed to in writing, software
11bd030d0aSAppaRao Puli // distributed under the License is distributed on an "AS IS" BASIS,
12bd030d0aSAppaRao Puli // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13bd030d0aSAppaRao Puli // See the License for the specific language governing permissions and
14bd030d0aSAppaRao Puli // limitations under the License.
15bd030d0aSAppaRao Puli */
16bd030d0aSAppaRao Puli #pragma once
1777665bdaSNan Zhou 
1877665bdaSNan Zhou #include "async_resolve.hpp"
1977665bdaSNan Zhou #include "http_response.hpp"
203ccb3adbSEd Tanous #include "logging.hpp"
213ccb3adbSEd Tanous #include "ssl_key_handler.hpp"
2277665bdaSNan Zhou 
230d5f5cf4SEd Tanous #include <boost/asio/connect.hpp>
24bb49eb5cSEd Tanous #include <boost/asio/io_context.hpp>
2529a82b08SSunitha Harish #include <boost/asio/ip/address.hpp>
2629a82b08SSunitha Harish #include <boost/asio/ip/basic_endpoint.hpp>
27bb49eb5cSEd Tanous #include <boost/asio/ip/tcp.hpp>
28e38778a5SAppaRao Puli #include <boost/asio/ssl/context.hpp>
29e38778a5SAppaRao Puli #include <boost/asio/ssl/error.hpp>
30d43cd0caSEd Tanous #include <boost/asio/steady_timer.hpp>
31d43cd0caSEd Tanous #include <boost/beast/core/flat_buffer.hpp>
32bb49eb5cSEd Tanous #include <boost/beast/core/flat_static_buffer.hpp>
33d43cd0caSEd Tanous #include <boost/beast/http/message.hpp>
34bb49eb5cSEd Tanous #include <boost/beast/http/parser.hpp>
35bb49eb5cSEd Tanous #include <boost/beast/http/read.hpp>
36bb49eb5cSEd Tanous #include <boost/beast/http/string_body.hpp>
37bb49eb5cSEd Tanous #include <boost/beast/http/write.hpp>
38e38778a5SAppaRao Puli #include <boost/beast/ssl/ssl_stream.hpp>
39bd030d0aSAppaRao Puli #include <boost/beast/version.hpp>
40f52c03c1SCarson Labrado #include <boost/container/devector.hpp>
41bb49eb5cSEd Tanous #include <boost/system/error_code.hpp>
421214b7e7SGunnar Mills 
43bd030d0aSAppaRao Puli #include <cstdlib>
44bd030d0aSAppaRao Puli #include <functional>
45bd030d0aSAppaRao Puli #include <iostream>
46bd030d0aSAppaRao Puli #include <memory>
472a5689a7SAppaRao Puli #include <queue>
48bd030d0aSAppaRao Puli #include <string>
49bd030d0aSAppaRao Puli 
50bd030d0aSAppaRao Puli namespace crow
51bd030d0aSAppaRao Puli {
52bd030d0aSAppaRao Puli 
5366d90c2cSCarson Labrado // With Redfish Aggregation it is assumed we will connect to another instance
5466d90c2cSCarson Labrado // of BMCWeb which can handle 100 simultaneous connections.
5566d90c2cSCarson Labrado constexpr size_t maxPoolSize = 20;
5666d90c2cSCarson Labrado constexpr size_t maxRequestQueueSize = 500;
5717dcc312SCarson Labrado constexpr unsigned int httpReadBodyLimit = 131072;
584d94272fSCarson Labrado constexpr unsigned int httpReadBufferSize = 4096;
592a5689a7SAppaRao Puli 
60bd030d0aSAppaRao Puli enum class ConnState
61bd030d0aSAppaRao Puli {
622a5689a7SAppaRao Puli     initialized,
6329a82b08SSunitha Harish     resolveInProgress,
6429a82b08SSunitha Harish     resolveFailed,
652a5689a7SAppaRao Puli     connectInProgress,
662a5689a7SAppaRao Puli     connectFailed,
67bd030d0aSAppaRao Puli     connected,
68e38778a5SAppaRao Puli     handshakeInProgress,
69e38778a5SAppaRao Puli     handshakeFailed,
702a5689a7SAppaRao Puli     sendInProgress,
712a5689a7SAppaRao Puli     sendFailed,
726eaa1d2fSSunitha Harish     recvInProgress,
732a5689a7SAppaRao Puli     recvFailed,
742a5689a7SAppaRao Puli     idle,
75fe44eb0bSAyushi Smriti     closed,
766eaa1d2fSSunitha Harish     suspended,
776eaa1d2fSSunitha Harish     terminated,
786eaa1d2fSSunitha Harish     abortConnection,
79e38778a5SAppaRao Puli     sslInitFailed,
806eaa1d2fSSunitha Harish     retry
81bd030d0aSAppaRao Puli };
82bd030d0aSAppaRao Puli 
83a7a80296SCarson Labrado static inline boost::system::error_code
84a7a80296SCarson Labrado     defaultRetryHandler(unsigned int respCode)
85a7a80296SCarson Labrado {
86a7a80296SCarson Labrado     // As a default, assume 200X is alright
87a7a80296SCarson Labrado     BMCWEB_LOG_DEBUG << "Using default check for response code validity";
88a7a80296SCarson Labrado     if ((respCode < 200) || (respCode >= 300))
89a7a80296SCarson Labrado     {
90a7a80296SCarson Labrado         return boost::system::errc::make_error_code(
91a7a80296SCarson Labrado             boost::system::errc::result_out_of_range);
92a7a80296SCarson Labrado     }
93a7a80296SCarson Labrado 
94a7a80296SCarson Labrado     // Return 0 if the response code is valid
95a7a80296SCarson Labrado     return boost::system::errc::make_error_code(boost::system::errc::success);
96a7a80296SCarson Labrado };
97a7a80296SCarson Labrado 
98f52c03c1SCarson Labrado // We need to allow retry information to be set before a message has been sent
99f52c03c1SCarson Labrado // and a connection pool has been created
100d14a48ffSCarson Labrado struct ConnectionPolicy
101f52c03c1SCarson Labrado {
102f52c03c1SCarson Labrado     uint32_t maxRetryAttempts = 5;
103d14a48ffSCarson Labrado 
104d14a48ffSCarson Labrado     // the max size of requests in bytes.  0 for unlimited
105d14a48ffSCarson Labrado     boost::optional<uint64_t> requestByteLimit = httpReadBodyLimit;
106d14a48ffSCarson Labrado 
107d14a48ffSCarson Labrado     size_t maxConnections = 1;
108d14a48ffSCarson Labrado 
109f52c03c1SCarson Labrado     std::string retryPolicyAction = "TerminateAfterRetries";
110d14a48ffSCarson Labrado 
111d14a48ffSCarson Labrado     std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0);
112a7a80296SCarson Labrado     std::function<boost::system::error_code(unsigned int respCode)>
113a7a80296SCarson Labrado         invalidResp = defaultRetryHandler;
114f52c03c1SCarson Labrado };
115f52c03c1SCarson Labrado 
116f52c03c1SCarson Labrado struct PendingRequest
117f52c03c1SCarson Labrado {
118244256ccSCarson Labrado     boost::beast::http::request<boost::beast::http::string_body> req;
119039a47e3SCarson Labrado     std::function<void(bool, uint32_t, Response&)> callback;
120039a47e3SCarson Labrado     PendingRequest(
1218a592810SEd Tanous         boost::beast::http::request<boost::beast::http::string_body>&& reqIn,
122d14a48ffSCarson Labrado         const std::function<void(bool, uint32_t, Response&)>& callbackIn) :
1238a592810SEd Tanous         req(std::move(reqIn)),
124d14a48ffSCarson Labrado         callback(callbackIn)
125f52c03c1SCarson Labrado     {}
126f52c03c1SCarson Labrado };
127f52c03c1SCarson Labrado 
128f52c03c1SCarson Labrado class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo>
129bd030d0aSAppaRao Puli {
130bd030d0aSAppaRao Puli   private:
131f52c03c1SCarson Labrado     ConnState state = ConnState::initialized;
132f52c03c1SCarson Labrado     uint32_t retryCount = 0;
133f52c03c1SCarson Labrado     std::string subId;
134d14a48ffSCarson Labrado     std::shared_ptr<ConnectionPolicy> connPolicy;
135f52c03c1SCarson Labrado     std::string host;
136f52c03c1SCarson Labrado     uint16_t port;
137f52c03c1SCarson Labrado     uint32_t connId;
138f52c03c1SCarson Labrado 
139f52c03c1SCarson Labrado     // Data buffers
140bd030d0aSAppaRao Puli     boost::beast::http::request<boost::beast::http::string_body> req;
1416eaa1d2fSSunitha Harish     std::optional<
1426eaa1d2fSSunitha Harish         boost::beast::http::response_parser<boost::beast::http::string_body>>
1436eaa1d2fSSunitha Harish         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 
150f8ca6d79SEd Tanous #ifdef BMCWEB_DBUS_DNS_RESOLVER
151*e1452beaSEd Tanous     using Resolver = async_resolve::Resolver;
152f8ca6d79SEd Tanous #else
153f8ca6d79SEd Tanous     using Resolver = boost::asio::ip::tcp::resolver;
154f8ca6d79SEd Tanous #endif
155f8ca6d79SEd Tanous     Resolver resolver;
156f8ca6d79SEd Tanous 
1570d5f5cf4SEd Tanous     boost::asio::ip::tcp::socket conn;
1580d5f5cf4SEd Tanous     std::optional<boost::beast::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;
168f52c03c1SCarson Labrado         BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":"
169f52c03c1SCarson Labrado                          << std::to_string(port)
170f52c03c1SCarson Labrado                          << ", id: " << std::to_string(connId);
17129a82b08SSunitha Harish 
172f8ca6d79SEd Tanous         resolver.async_resolve(host, std::to_string(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         {
183d7043b3aSSunitha Harish             BMCWEB_LOG_ERROR << "Resolve failed: " << ec.message() << " "
184d7043b3aSSunitha Harish                              << host << ":" << std::to_string(port);
1853d36e3a5SEd Tanous             state = ConnState::resolveFailed;
1863d36e3a5SEd Tanous             waitAndRetry();
18729a82b08SSunitha Harish             return;
18829a82b08SSunitha Harish         }
1893d36e3a5SEd Tanous         BMCWEB_LOG_DEBUG << "Resolved " << host << ":" << std::to_string(port)
1903d36e3a5SEd Tanous                          << ", id: " << std::to_string(connId);
1912a5689a7SAppaRao Puli         state = ConnState::connectInProgress;
1922a5689a7SAppaRao Puli 
193f52c03c1SCarson Labrado         BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":"
194f52c03c1SCarson Labrado                          << std::to_string(port)
195f52c03c1SCarson Labrado                          << ", id: " << std::to_string(connId);
196b00dcc27SEd Tanous 
1970d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
1980d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
1990d5f5cf4SEd Tanous 
2000d5f5cf4SEd Tanous         boost::asio::async_connect(
2010d5f5cf4SEd Tanous             conn, endpointList,
202e38778a5SAppaRao Puli             std::bind_front(&ConnectionInfo::afterConnect, this,
203e38778a5SAppaRao Puli                             shared_from_this()));
204e38778a5SAppaRao Puli     }
205e38778a5SAppaRao Puli 
206e38778a5SAppaRao Puli     void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/,
20781c4e330SEd Tanous                       const boost::beast::error_code& ec,
208e38778a5SAppaRao Puli                       const boost::asio::ip::tcp::endpoint& endpoint)
209e38778a5SAppaRao Puli     {
210513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
211513d1ffcSCarson Labrado         // this branch
212513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
213513d1ffcSCarson Labrado         {
214513d1ffcSCarson Labrado             return;
215513d1ffcSCarson Labrado         }
216513d1ffcSCarson Labrado 
2170d5f5cf4SEd Tanous         timer.cancel();
2182a5689a7SAppaRao Puli         if (ec)
2192a5689a7SAppaRao Puli         {
220002d39b4SEd Tanous             BMCWEB_LOG_ERROR << "Connect " << endpoint.address().to_string()
221002d39b4SEd Tanous                              << ":" << std::to_string(endpoint.port())
222e38778a5SAppaRao Puli                              << ", id: " << std::to_string(connId)
2232a5689a7SAppaRao Puli                              << " failed: " << ec.message();
224e38778a5SAppaRao Puli             state = ConnState::connectFailed;
225e38778a5SAppaRao Puli             waitAndRetry();
2262a5689a7SAppaRao Puli             return;
2272a5689a7SAppaRao Puli         }
228e38778a5SAppaRao Puli         BMCWEB_LOG_DEBUG << "Connected to: " << endpoint.address().to_string()
229e38778a5SAppaRao Puli                          << ":" << std::to_string(endpoint.port())
230e38778a5SAppaRao Puli                          << ", id: " << std::to_string(connId);
231e38778a5SAppaRao Puli         if (sslConn)
232e38778a5SAppaRao Puli         {
2330d5f5cf4SEd Tanous             doSslHandshake();
234e38778a5SAppaRao Puli             return;
235e38778a5SAppaRao Puli         }
236e38778a5SAppaRao Puli         state = ConnState::connected;
237e38778a5SAppaRao Puli         sendMessage();
238e38778a5SAppaRao Puli     }
239e38778a5SAppaRao Puli 
2400d5f5cf4SEd Tanous     void doSslHandshake()
241e38778a5SAppaRao Puli     {
242e38778a5SAppaRao Puli         if (!sslConn)
243e38778a5SAppaRao Puli         {
244e38778a5SAppaRao Puli             return;
245e38778a5SAppaRao Puli         }
246e38778a5SAppaRao Puli         state = ConnState::handshakeInProgress;
2470d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
2480d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
249e38778a5SAppaRao Puli         sslConn->async_handshake(
250e38778a5SAppaRao Puli             boost::asio::ssl::stream_base::client,
251e38778a5SAppaRao Puli             std::bind_front(&ConnectionInfo::afterSslHandshake, this,
252e38778a5SAppaRao Puli                             shared_from_this()));
253e38778a5SAppaRao Puli     }
254e38778a5SAppaRao Puli 
255e38778a5SAppaRao Puli     void afterSslHandshake(const std::shared_ptr<ConnectionInfo>& /*self*/,
25681c4e330SEd Tanous                            const boost::beast::error_code& ec)
257e38778a5SAppaRao Puli     {
258513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
259513d1ffcSCarson Labrado         // this branch
260513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
261513d1ffcSCarson Labrado         {
262513d1ffcSCarson Labrado             return;
263513d1ffcSCarson Labrado         }
264513d1ffcSCarson Labrado 
2650d5f5cf4SEd Tanous         timer.cancel();
266e38778a5SAppaRao Puli         if (ec)
267e38778a5SAppaRao Puli         {
268e38778a5SAppaRao Puli             BMCWEB_LOG_ERROR << "SSL Handshake failed -"
269e38778a5SAppaRao Puli                              << " id: " << std::to_string(connId)
270e38778a5SAppaRao Puli                              << " error: " << ec.message();
271e38778a5SAppaRao Puli             state = ConnState::handshakeFailed;
272e38778a5SAppaRao Puli             waitAndRetry();
273e38778a5SAppaRao Puli             return;
274e38778a5SAppaRao Puli         }
275e38778a5SAppaRao Puli         BMCWEB_LOG_DEBUG << "SSL Handshake successful -"
276e38778a5SAppaRao Puli                          << " id: " << std::to_string(connId);
277e38778a5SAppaRao Puli         state = ConnState::connected;
278e38778a5SAppaRao Puli         sendMessage();
2792a5689a7SAppaRao Puli     }
2802a5689a7SAppaRao Puli 
281f52c03c1SCarson Labrado     void sendMessage()
2822a5689a7SAppaRao Puli     {
2832a5689a7SAppaRao Puli         state = ConnState::sendInProgress;
2842a5689a7SAppaRao Puli 
285bd030d0aSAppaRao Puli         // Set a timeout on the operation
2860d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
2870d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
288bd030d0aSAppaRao Puli 
289bd030d0aSAppaRao Puli         // Send the HTTP request to the remote host
290e38778a5SAppaRao Puli         if (sslConn)
291e38778a5SAppaRao Puli         {
292e38778a5SAppaRao Puli             boost::beast::http::async_write(
293e38778a5SAppaRao Puli                 *sslConn, req,
294e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterWrite, this,
295e38778a5SAppaRao Puli                                 shared_from_this()));
296e38778a5SAppaRao Puli         }
297e38778a5SAppaRao Puli         else
298e38778a5SAppaRao Puli         {
299bd030d0aSAppaRao Puli             boost::beast::http::async_write(
300bd030d0aSAppaRao Puli                 conn, req,
301e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterWrite, this,
302e38778a5SAppaRao Puli                                 shared_from_this()));
303e38778a5SAppaRao Puli         }
304e38778a5SAppaRao Puli     }
305e38778a5SAppaRao Puli 
306e38778a5SAppaRao Puli     void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/,
307e38778a5SAppaRao Puli                     const boost::beast::error_code& ec, size_t bytesTransferred)
308e38778a5SAppaRao Puli     {
309513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
310513d1ffcSCarson Labrado         // this branch
311513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
312513d1ffcSCarson Labrado         {
313513d1ffcSCarson Labrado             return;
314513d1ffcSCarson Labrado         }
315513d1ffcSCarson Labrado 
3160d5f5cf4SEd Tanous         timer.cancel();
317bd030d0aSAppaRao Puli         if (ec)
318bd030d0aSAppaRao Puli         {
319d7043b3aSSunitha Harish             BMCWEB_LOG_ERROR << "sendMessage() failed: " << ec.message() << " "
320d7043b3aSSunitha Harish                              << host << ":" << std::to_string(port);
321e38778a5SAppaRao Puli             state = ConnState::sendFailed;
322e38778a5SAppaRao Puli             waitAndRetry();
323bd030d0aSAppaRao Puli             return;
324bd030d0aSAppaRao Puli         }
325bd030d0aSAppaRao Puli         BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
326bd030d0aSAppaRao Puli                          << bytesTransferred;
327bd030d0aSAppaRao Puli 
328e38778a5SAppaRao Puli         recvMessage();
329bd030d0aSAppaRao Puli     }
330bd030d0aSAppaRao Puli 
331bd030d0aSAppaRao Puli     void recvMessage()
332bd030d0aSAppaRao Puli     {
3336eaa1d2fSSunitha Harish         state = ConnState::recvInProgress;
3346eaa1d2fSSunitha Harish 
3356eaa1d2fSSunitha Harish         parser.emplace(std::piecewise_construct, std::make_tuple());
336d14a48ffSCarson Labrado 
337d14a48ffSCarson Labrado         parser->body_limit(connPolicy->requestByteLimit);
3386eaa1d2fSSunitha Harish 
3390d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
3400d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
3410d5f5cf4SEd Tanous 
342bd030d0aSAppaRao Puli         // Receive the HTTP response
343e38778a5SAppaRao Puli         if (sslConn)
344e38778a5SAppaRao Puli         {
345e38778a5SAppaRao Puli             boost::beast::http::async_read(
346e38778a5SAppaRao Puli                 *sslConn, buffer, *parser,
347e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterRead, this,
348e38778a5SAppaRao Puli                                 shared_from_this()));
349e38778a5SAppaRao Puli         }
350e38778a5SAppaRao Puli         else
351e38778a5SAppaRao Puli         {
352bd030d0aSAppaRao Puli             boost::beast::http::async_read(
3536eaa1d2fSSunitha Harish                 conn, buffer, *parser,
354e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterRead, this,
355e38778a5SAppaRao Puli                                 shared_from_this()));
356e38778a5SAppaRao Puli         }
357e38778a5SAppaRao Puli     }
358e38778a5SAppaRao Puli 
359e38778a5SAppaRao Puli     void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/,
360e38778a5SAppaRao Puli                    const boost::beast::error_code& ec,
361e38778a5SAppaRao Puli                    const std::size_t& bytesTransferred)
362e38778a5SAppaRao Puli     {
363513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
364513d1ffcSCarson Labrado         // this branch
365513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
366513d1ffcSCarson Labrado         {
367513d1ffcSCarson Labrado             return;
368513d1ffcSCarson Labrado         }
369513d1ffcSCarson Labrado 
3700d5f5cf4SEd Tanous         timer.cancel();
371e38778a5SAppaRao Puli         if (ec && ec != boost::asio::ssl::error::stream_truncated)
372bd030d0aSAppaRao Puli         {
373d7043b3aSSunitha Harish             BMCWEB_LOG_ERROR << "recvMessage() failed: " << ec.message()
374d7043b3aSSunitha Harish                              << " from " << host << ":" << std::to_string(port);
375e38778a5SAppaRao Puli             state = ConnState::recvFailed;
376e38778a5SAppaRao Puli             waitAndRetry();
377bd030d0aSAppaRao Puli             return;
378bd030d0aSAppaRao Puli         }
379bd030d0aSAppaRao Puli         BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
380bd030d0aSAppaRao Puli                          << bytesTransferred;
381e38778a5SAppaRao Puli         BMCWEB_LOG_DEBUG << "recvMessage() data: " << parser->get().body();
382bd030d0aSAppaRao Puli 
383e38778a5SAppaRao Puli         unsigned int respCode = parser->get().result_int();
384e38778a5SAppaRao Puli         BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: " << respCode;
3856eaa1d2fSSunitha Harish 
386a7a80296SCarson Labrado         // Make sure the received response code is valid as defined by
387a7a80296SCarson Labrado         // the associated retry policy
388d14a48ffSCarson Labrado         if (connPolicy->invalidResp(respCode))
3896eaa1d2fSSunitha Harish         {
3906eaa1d2fSSunitha Harish             // The listener failed to receive the Sent-Event
391002d39b4SEd Tanous             BMCWEB_LOG_ERROR << "recvMessage() Listener Failed to "
3927adb85acSSunitha Harish                                 "receive Sent-Event. Header Response Code: "
393d7043b3aSSunitha Harish                              << respCode << " from " << host << ":"
394d7043b3aSSunitha Harish                              << std::to_string(port);
395e38778a5SAppaRao Puli             state = ConnState::recvFailed;
396e38778a5SAppaRao Puli             waitAndRetry();
3976eaa1d2fSSunitha Harish             return;
3986eaa1d2fSSunitha Harish         }
399bd030d0aSAppaRao Puli 
400f52c03c1SCarson Labrado         // Send is successful
401f52c03c1SCarson Labrado         // Reset the counter just in case this was after retrying
402e38778a5SAppaRao Puli         retryCount = 0;
4036eaa1d2fSSunitha Harish 
4046eaa1d2fSSunitha Harish         // Keep the connection alive if server supports it
4056eaa1d2fSSunitha Harish         // Else close the connection
4066eaa1d2fSSunitha Harish         BMCWEB_LOG_DEBUG << "recvMessage() keepalive : "
407e38778a5SAppaRao Puli                          << parser->keep_alive();
4086eaa1d2fSSunitha Harish 
409039a47e3SCarson Labrado         // Copy the response into a Response object so that it can be
410039a47e3SCarson Labrado         // processed by the callback function.
411e38778a5SAppaRao Puli         res.stringResponse = parser->release();
412e38778a5SAppaRao Puli         callback(parser->keep_alive(), connId, res);
413513d1ffcSCarson Labrado         res.clear();
414bd030d0aSAppaRao Puli     }
415bd030d0aSAppaRao Puli 
4160d5f5cf4SEd Tanous     static void onTimeout(const std::weak_ptr<ConnectionInfo>& weakSelf,
4175e7e2dc5SEd Tanous                           const boost::system::error_code& ec)
4180d5f5cf4SEd Tanous     {
4190d5f5cf4SEd Tanous         if (ec == boost::asio::error::operation_aborted)
4200d5f5cf4SEd Tanous         {
4210d5f5cf4SEd Tanous             BMCWEB_LOG_DEBUG
422513d1ffcSCarson Labrado                 << "async_wait failed since the operation is aborted";
4230d5f5cf4SEd Tanous             return;
4240d5f5cf4SEd Tanous         }
4250d5f5cf4SEd Tanous         if (ec)
4260d5f5cf4SEd Tanous         {
4270d5f5cf4SEd Tanous             BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message();
4280d5f5cf4SEd Tanous             // If the timer fails, we need to close the socket anyway, same as
4290d5f5cf4SEd Tanous             // if it expired.
4300d5f5cf4SEd Tanous         }
4310d5f5cf4SEd Tanous         std::shared_ptr<ConnectionInfo> self = weakSelf.lock();
4320d5f5cf4SEd Tanous         if (self == nullptr)
4330d5f5cf4SEd Tanous         {
4340d5f5cf4SEd Tanous             return;
4350d5f5cf4SEd Tanous         }
4360d5f5cf4SEd Tanous         self->waitAndRetry();
4370d5f5cf4SEd Tanous     }
4380d5f5cf4SEd Tanous 
4396eaa1d2fSSunitha Harish     void waitAndRetry()
440bd030d0aSAppaRao Puli     {
441d14a48ffSCarson Labrado         if ((retryCount >= connPolicy->maxRetryAttempts) ||
442e38778a5SAppaRao Puli             (state == ConnState::sslInitFailed))
4432a5689a7SAppaRao Puli         {
444d7043b3aSSunitha Harish             BMCWEB_LOG_ERROR << "Maximum number of retries reached."
445d7043b3aSSunitha Harish                              << " " << host << ":" << std::to_string(port);
446f52c03c1SCarson Labrado             BMCWEB_LOG_DEBUG << "Retry policy: "
447d14a48ffSCarson Labrado                              << connPolicy->retryPolicyAction;
448039a47e3SCarson Labrado 
449d14a48ffSCarson Labrado             if (connPolicy->retryPolicyAction == "TerminateAfterRetries")
450fe44eb0bSAyushi Smriti             {
451fe44eb0bSAyushi Smriti                 // TODO: delete subscription
452fe44eb0bSAyushi Smriti                 state = ConnState::terminated;
453fe44eb0bSAyushi Smriti             }
454d14a48ffSCarson Labrado             if (connPolicy->retryPolicyAction == "SuspendRetries")
455fe44eb0bSAyushi Smriti             {
4562a5689a7SAppaRao Puli                 state = ConnState::suspended;
4572a5689a7SAppaRao Puli             }
458513d1ffcSCarson Labrado 
459513d1ffcSCarson Labrado             // We want to return a 502 to indicate there was an error with
460513d1ffcSCarson Labrado             // the external server
461513d1ffcSCarson Labrado             res.result(boost::beast::http::status::bad_gateway);
462513d1ffcSCarson Labrado             callback(false, connId, res);
463513d1ffcSCarson Labrado             res.clear();
464513d1ffcSCarson Labrado 
4656eaa1d2fSSunitha Harish             // Reset the retrycount to zero so that client can try connecting
4666eaa1d2fSSunitha Harish             // again if needed
467fe44eb0bSAyushi Smriti             retryCount = 0;
4682a5689a7SAppaRao Puli             return;
4692a5689a7SAppaRao Puli         }
4702a5689a7SAppaRao Puli 
4712a5689a7SAppaRao Puli         retryCount++;
472fe44eb0bSAyushi Smriti 
473f52c03c1SCarson Labrado         BMCWEB_LOG_DEBUG << "Attempt retry after "
474f52c03c1SCarson Labrado                          << std::to_string(
475d14a48ffSCarson Labrado                                 connPolicy->retryIntervalSecs.count())
476fe44eb0bSAyushi Smriti                          << " seconds. RetryCount = " << 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         {
4876eaa1d2fSSunitha Harish             BMCWEB_LOG_DEBUG
4886eaa1d2fSSunitha Harish                 << "async_wait failed since the operation is aborted"
4896eaa1d2fSSunitha Harish                 << ec.message();
4906eaa1d2fSSunitha Harish         }
4916eaa1d2fSSunitha Harish         else if (ec)
4926eaa1d2fSSunitha Harish         {
4936eaa1d2fSSunitha Harish             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.
4993d36e3a5SEd Tanous         doClose(true);
5002a5689a7SAppaRao Puli     }
5012a5689a7SAppaRao Puli 
502e38778a5SAppaRao Puli     void shutdownConn(bool retry)
503fe44eb0bSAyushi Smriti     {
504f52c03c1SCarson Labrado         boost::beast::error_code ec;
5050d5f5cf4SEd Tanous         conn.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
506f52c03c1SCarson Labrado         conn.close();
507f52c03c1SCarson Labrado 
508f52c03c1SCarson Labrado         // not_connected happens sometimes so don't bother reporting it.
509f52c03c1SCarson Labrado         if (ec && ec != boost::beast::errc::not_connected)
5102a5689a7SAppaRao Puli         {
511f52c03c1SCarson Labrado             BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
512f52c03c1SCarson Labrado                              << ", id: " << std::to_string(connId)
513f52c03c1SCarson Labrado                              << " shutdown failed: " << ec.message();
5146eaa1d2fSSunitha Harish         }
5155cab68f3SCarson Labrado         else
5165cab68f3SCarson Labrado         {
517f52c03c1SCarson Labrado             BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
518f52c03c1SCarson Labrado                              << ", id: " << std::to_string(connId)
519f52c03c1SCarson Labrado                              << " closed gracefully";
5205cab68f3SCarson Labrado         }
521ca723762SEd Tanous 
522e38778a5SAppaRao Puli         if (retry)
52392a74e56SAppaRao Puli         {
524f52c03c1SCarson Labrado             // Now let's try to resend the data
525f52c03c1SCarson Labrado             state = ConnState::retry;
5260d5f5cf4SEd Tanous             doResolve();
527e38778a5SAppaRao Puli         }
528e38778a5SAppaRao Puli         else
529e38778a5SAppaRao Puli         {
530e38778a5SAppaRao Puli             state = ConnState::closed;
531e38778a5SAppaRao Puli         }
532e38778a5SAppaRao Puli     }
533e38778a5SAppaRao Puli 
534e38778a5SAppaRao Puli     void doClose(bool retry = false)
535e38778a5SAppaRao Puli     {
536e38778a5SAppaRao Puli         if (!sslConn)
537e38778a5SAppaRao Puli         {
538e38778a5SAppaRao Puli             shutdownConn(retry);
539e38778a5SAppaRao Puli             return;
540e38778a5SAppaRao Puli         }
541e38778a5SAppaRao Puli 
542e38778a5SAppaRao Puli         sslConn->async_shutdown(
543e38778a5SAppaRao Puli             std::bind_front(&ConnectionInfo::afterSslShutdown, this,
544e38778a5SAppaRao Puli                             shared_from_this(), retry));
545e38778a5SAppaRao Puli     }
546e38778a5SAppaRao Puli 
547e38778a5SAppaRao Puli     void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/,
548e38778a5SAppaRao Puli                           bool retry, const boost::system::error_code& ec)
549e38778a5SAppaRao Puli     {
550e38778a5SAppaRao Puli         if (ec)
551e38778a5SAppaRao Puli         {
552e38778a5SAppaRao Puli             BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
553e38778a5SAppaRao Puli                              << ", id: " << std::to_string(connId)
554e38778a5SAppaRao Puli                              << " shutdown failed: " << ec.message();
555e38778a5SAppaRao Puli         }
556e38778a5SAppaRao Puli         else
557e38778a5SAppaRao Puli         {
558e38778a5SAppaRao Puli             BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
559e38778a5SAppaRao Puli                              << ", id: " << std::to_string(connId)
560e38778a5SAppaRao Puli                              << " closed gracefully";
561e38778a5SAppaRao Puli         }
562e38778a5SAppaRao Puli         shutdownConn(retry);
563e38778a5SAppaRao Puli     }
564e38778a5SAppaRao Puli 
565e38778a5SAppaRao Puli     void setCipherSuiteTLSext()
566e38778a5SAppaRao Puli     {
567e38778a5SAppaRao Puli         if (!sslConn)
568e38778a5SAppaRao Puli         {
569e38778a5SAppaRao Puli             return;
570e38778a5SAppaRao Puli         }
571e38778a5SAppaRao Puli         // NOTE: The SSL_set_tlsext_host_name is defined in tlsv1.h header
572e38778a5SAppaRao Puli         // file but its having old style casting (name is cast to void*).
573e38778a5SAppaRao Puli         // Since bmcweb compiler treats all old-style-cast as error, its
574e38778a5SAppaRao Puli         // causing the build failure. So replaced the same macro inline and
575e38778a5SAppaRao Puli         // did corrected the code by doing static_cast to viod*. This has to
576e38778a5SAppaRao Puli         // be fixed in openssl library in long run. Set SNI Hostname (many
577e38778a5SAppaRao Puli         // hosts need this to handshake successfully)
578e38778a5SAppaRao Puli         if (SSL_ctrl(sslConn->native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME,
579e38778a5SAppaRao Puli                      TLSEXT_NAMETYPE_host_name,
580e38778a5SAppaRao Puli                      static_cast<void*>(&host.front())) == 0)
581e38778a5SAppaRao Puli 
582e38778a5SAppaRao Puli         {
583e38778a5SAppaRao Puli             boost::beast::error_code ec{static_cast<int>(::ERR_get_error()),
584e38778a5SAppaRao Puli                                         boost::asio::error::get_ssl_category()};
585e38778a5SAppaRao Puli 
586e38778a5SAppaRao Puli             BMCWEB_LOG_ERROR << "SSL_set_tlsext_host_name " << host << ":"
587e38778a5SAppaRao Puli                              << port << ", id: " << std::to_string(connId)
588e38778a5SAppaRao Puli                              << " failed: " << ec.message();
589e38778a5SAppaRao Puli             // Set state as sslInit failed so that we close the connection
590e38778a5SAppaRao Puli             // and take appropriate action as per retry configuration.
591e38778a5SAppaRao Puli             state = ConnState::sslInitFailed;
592e38778a5SAppaRao Puli             waitAndRetry();
593e38778a5SAppaRao Puli             return;
594e38778a5SAppaRao Puli         }
595bd030d0aSAppaRao Puli     }
596bd030d0aSAppaRao Puli 
597bd030d0aSAppaRao Puli   public:
598d14a48ffSCarson Labrado     explicit ConnectionInfo(
599d14a48ffSCarson Labrado         boost::asio::io_context& iocIn, const std::string& idIn,
600d14a48ffSCarson Labrado         const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
601d14a48ffSCarson Labrado         const std::string& destIPIn, uint16_t destPortIn, bool useSSL,
602d14a48ffSCarson Labrado         unsigned int connIdIn) :
6038a592810SEd Tanous         subId(idIn),
604d14a48ffSCarson Labrado         connPolicy(connPolicyIn), host(destIPIn), port(destPortIn),
605f8ca6d79SEd Tanous         connId(connIdIn), resolver(iocIn), conn(iocIn), timer(iocIn)
606e38778a5SAppaRao Puli     {
607e38778a5SAppaRao Puli         if (useSSL)
608e38778a5SAppaRao Puli         {
609e38778a5SAppaRao Puli             std::optional<boost::asio::ssl::context> sslCtx =
610e38778a5SAppaRao Puli                 ensuressl::getSSLClientContext();
611e38778a5SAppaRao Puli 
612e38778a5SAppaRao Puli             if (!sslCtx)
613e38778a5SAppaRao Puli             {
614e38778a5SAppaRao Puli                 BMCWEB_LOG_ERROR << "prepareSSLContext failed - " << host << ":"
615e38778a5SAppaRao Puli                                  << port << ", id: " << std::to_string(connId);
616e38778a5SAppaRao Puli                 // Don't retry if failure occurs while preparing SSL context
617e38778a5SAppaRao Puli                 // such as certificate is invalid or set cipher failure or set
618e38778a5SAppaRao Puli                 // host name failure etc... Setting conn state to sslInitFailed
619e38778a5SAppaRao Puli                 // and connection state will be transitioned to next state
620e38778a5SAppaRao Puli                 // depending on retry policy set by subscription.
621e38778a5SAppaRao Puli                 state = ConnState::sslInitFailed;
622e38778a5SAppaRao Puli                 waitAndRetry();
623e38778a5SAppaRao Puli                 return;
624e38778a5SAppaRao Puli             }
625e38778a5SAppaRao Puli             sslConn.emplace(conn, *sslCtx);
626e38778a5SAppaRao Puli             setCipherSuiteTLSext();
627e38778a5SAppaRao Puli         }
628e38778a5SAppaRao Puli     }
629f52c03c1SCarson Labrado };
630bd030d0aSAppaRao Puli 
631f52c03c1SCarson Labrado class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
632bd030d0aSAppaRao Puli {
633f52c03c1SCarson Labrado   private:
634f52c03c1SCarson Labrado     boost::asio::io_context& ioc;
635e38778a5SAppaRao Puli     std::string id;
636d14a48ffSCarson Labrado     std::shared_ptr<ConnectionPolicy> connPolicy;
637e38778a5SAppaRao Puli     std::string destIP;
638e38778a5SAppaRao Puli     uint16_t destPort;
639e38778a5SAppaRao Puli     bool useSSL;
640f52c03c1SCarson Labrado     std::vector<std::shared_ptr<ConnectionInfo>> connections;
641f52c03c1SCarson Labrado     boost::container::devector<PendingRequest> requestQueue;
642f52c03c1SCarson Labrado 
643f52c03c1SCarson Labrado     friend class HttpClient;
644f52c03c1SCarson Labrado 
645244256ccSCarson Labrado     // Configure a connections's request, callback, and retry info in
646244256ccSCarson Labrado     // preparation to begin sending the request
647f52c03c1SCarson Labrado     void setConnProps(ConnectionInfo& conn)
648bd030d0aSAppaRao Puli     {
649f52c03c1SCarson Labrado         if (requestQueue.empty())
650f52c03c1SCarson Labrado         {
651f52c03c1SCarson Labrado             BMCWEB_LOG_ERROR
652f52c03c1SCarson Labrado                 << "setConnProps() should not have been called when requestQueue is empty";
653bd030d0aSAppaRao Puli             return;
654bd030d0aSAppaRao Puli         }
655bd030d0aSAppaRao Puli 
656244256ccSCarson Labrado         auto nextReq = requestQueue.front();
657244256ccSCarson Labrado         conn.req = std::move(nextReq.req);
658244256ccSCarson Labrado         conn.callback = std::move(nextReq.callback);
659f52c03c1SCarson Labrado 
660f52c03c1SCarson Labrado         BMCWEB_LOG_DEBUG << "Setting properties for connection " << conn.host
661f52c03c1SCarson Labrado                          << ":" << std::to_string(conn.port)
662a7a80296SCarson Labrado                          << ", id: " << std::to_string(conn.connId);
663f52c03c1SCarson Labrado 
664f52c03c1SCarson Labrado         // We can remove the request from the queue at this point
665f52c03c1SCarson Labrado         requestQueue.pop_front();
666f52c03c1SCarson Labrado     }
667f52c03c1SCarson Labrado 
668f52c03c1SCarson Labrado     // Gets called as part of callback after request is sent
669f52c03c1SCarson Labrado     // Reuses the connection if there are any requests waiting to be sent
670f52c03c1SCarson Labrado     // Otherwise closes the connection if it is not a keep-alive
671f52c03c1SCarson Labrado     void sendNext(bool keepAlive, uint32_t connId)
672f52c03c1SCarson Labrado     {
673f52c03c1SCarson Labrado         auto conn = connections[connId];
67446a81465SCarson Labrado 
67546a81465SCarson Labrado         // Allow the connection's handler to be deleted
67646a81465SCarson Labrado         // This is needed because of Redfish Aggregation passing an
67746a81465SCarson Labrado         // AsyncResponse shared_ptr to this callback
67846a81465SCarson Labrado         conn->callback = nullptr;
67946a81465SCarson Labrado 
680f52c03c1SCarson Labrado         // Reuse the connection to send the next request in the queue
681f52c03c1SCarson Labrado         if (!requestQueue.empty())
682f52c03c1SCarson Labrado         {
683f52c03c1SCarson Labrado             BMCWEB_LOG_DEBUG << std::to_string(requestQueue.size())
684f52c03c1SCarson Labrado                              << " requests remaining in queue for " << destIP
685f52c03c1SCarson Labrado                              << ":" << std::to_string(destPort)
686f52c03c1SCarson Labrado                              << ", reusing connnection "
687f52c03c1SCarson Labrado                              << std::to_string(connId);
688f52c03c1SCarson Labrado 
689f52c03c1SCarson Labrado             setConnProps(*conn);
690f52c03c1SCarson Labrado 
691f52c03c1SCarson Labrado             if (keepAlive)
692f52c03c1SCarson Labrado             {
693f52c03c1SCarson Labrado                 conn->sendMessage();
6942a5689a7SAppaRao Puli             }
6952a5689a7SAppaRao Puli             else
6962a5689a7SAppaRao Puli             {
697f52c03c1SCarson Labrado                 // Server is not keep-alive enabled so we need to close the
698f52c03c1SCarson Labrado                 // connection and then start over from resolve
699f52c03c1SCarson Labrado                 conn->doClose();
700f52c03c1SCarson Labrado                 conn->doResolve();
701f52c03c1SCarson Labrado             }
702f52c03c1SCarson Labrado             return;
703f52c03c1SCarson Labrado         }
704f52c03c1SCarson Labrado 
705f52c03c1SCarson Labrado         // No more messages to send so close the connection if necessary
706f52c03c1SCarson Labrado         if (keepAlive)
707f52c03c1SCarson Labrado         {
708f52c03c1SCarson Labrado             conn->state = ConnState::idle;
709f52c03c1SCarson Labrado         }
710f52c03c1SCarson Labrado         else
711f52c03c1SCarson Labrado         {
712f52c03c1SCarson Labrado             // Abort the connection since server is not keep-alive enabled
713f52c03c1SCarson Labrado             conn->state = ConnState::abortConnection;
714f52c03c1SCarson Labrado             conn->doClose();
7152a5689a7SAppaRao Puli         }
716bd030d0aSAppaRao Puli     }
717bd030d0aSAppaRao Puli 
7185e44e3d8SAppaRao Puli     void sendData(std::string&& data, const std::string& destUri,
719244256ccSCarson Labrado                   const boost::beast::http::fields& httpHeader,
720244256ccSCarson Labrado                   const boost::beast::http::verb verb,
7216b3db60dSEd Tanous                   const std::function<void(Response&)>& resHandler)
722fe44eb0bSAyushi Smriti     {
723244256ccSCarson Labrado         // Construct the request to be sent
724244256ccSCarson Labrado         boost::beast::http::request<boost::beast::http::string_body> thisReq(
725244256ccSCarson Labrado             verb, destUri, 11, "", httpHeader);
726244256ccSCarson Labrado         thisReq.set(boost::beast::http::field::host, destIP);
727244256ccSCarson Labrado         thisReq.keep_alive(true);
728244256ccSCarson Labrado         thisReq.body() = std::move(data);
729244256ccSCarson Labrado         thisReq.prepare_payload();
7303d36e3a5SEd Tanous         auto cb = std::bind_front(&ConnectionPool::afterSendData,
7313d36e3a5SEd Tanous                                   weak_from_this(), resHandler);
732f52c03c1SCarson Labrado         // Reuse an existing connection if one is available
733f52c03c1SCarson Labrado         for (unsigned int i = 0; i < connections.size(); i++)
734fe44eb0bSAyushi Smriti         {
735f52c03c1SCarson Labrado             auto conn = connections[i];
736f52c03c1SCarson Labrado             if ((conn->state == ConnState::idle) ||
737f52c03c1SCarson Labrado                 (conn->state == ConnState::initialized) ||
738f52c03c1SCarson Labrado                 (conn->state == ConnState::closed))
739f52c03c1SCarson Labrado             {
740244256ccSCarson Labrado                 conn->req = std::move(thisReq);
741f52c03c1SCarson Labrado                 conn->callback = std::move(cb);
742f52c03c1SCarson Labrado                 std::string commonMsg = std::to_string(i) + " from pool " +
743f52c03c1SCarson Labrado                                         destIP + ":" + std::to_string(destPort);
744f52c03c1SCarson Labrado 
745f52c03c1SCarson Labrado                 if (conn->state == ConnState::idle)
746f52c03c1SCarson Labrado                 {
747f52c03c1SCarson Labrado                     BMCWEB_LOG_DEBUG << "Grabbing idle connection "
748f52c03c1SCarson Labrado                                      << commonMsg;
749f52c03c1SCarson Labrado                     conn->sendMessage();
750f52c03c1SCarson Labrado                 }
751f52c03c1SCarson Labrado                 else
752f52c03c1SCarson Labrado                 {
753f52c03c1SCarson Labrado                     BMCWEB_LOG_DEBUG << "Reusing existing connection "
754f52c03c1SCarson Labrado                                      << commonMsg;
755f52c03c1SCarson Labrado                     conn->doResolve();
756f52c03c1SCarson Labrado                 }
757f52c03c1SCarson Labrado                 return;
758f52c03c1SCarson Labrado             }
759f52c03c1SCarson Labrado         }
760f52c03c1SCarson Labrado 
761f52c03c1SCarson Labrado         // All connections in use so create a new connection or add request to
762f52c03c1SCarson Labrado         // the queue
763d14a48ffSCarson Labrado         if (connections.size() < connPolicy->maxConnections)
764f52c03c1SCarson Labrado         {
765f52c03c1SCarson Labrado             BMCWEB_LOG_DEBUG << "Adding new connection to pool " << destIP
766f52c03c1SCarson Labrado                              << ":" << std::to_string(destPort);
767f52c03c1SCarson Labrado             auto conn = addConnection();
768244256ccSCarson Labrado             conn->req = std::move(thisReq);
769f52c03c1SCarson Labrado             conn->callback = std::move(cb);
770f52c03c1SCarson Labrado             conn->doResolve();
771f52c03c1SCarson Labrado         }
772f52c03c1SCarson Labrado         else if (requestQueue.size() < maxRequestQueueSize)
773f52c03c1SCarson Labrado         {
774d7043b3aSSunitha Harish             BMCWEB_LOG_ERROR << "Max pool size reached. Adding data to queue."
775d7043b3aSSunitha Harish                              << destIP << ":" << std::to_string(destPort);
776d14a48ffSCarson Labrado             requestQueue.emplace_back(std::move(thisReq), std::move(cb));
777f52c03c1SCarson Labrado         }
778f52c03c1SCarson Labrado         else
779f52c03c1SCarson Labrado         {
78043e14d38SCarson Labrado             // If we can't buffer the request then we should let the callback
78143e14d38SCarson Labrado             // handle a 429 Too Many Requests dummy response
782f52c03c1SCarson Labrado             BMCWEB_LOG_ERROR << destIP << ":" << std::to_string(destPort)
783f52c03c1SCarson Labrado                              << " request queue full.  Dropping request.";
78443e14d38SCarson Labrado             Response dummyRes;
78543e14d38SCarson Labrado             dummyRes.result(boost::beast::http::status::too_many_requests);
78643e14d38SCarson Labrado             resHandler(dummyRes);
787f52c03c1SCarson Labrado         }
788f52c03c1SCarson Labrado     }
789f52c03c1SCarson Labrado 
7903d36e3a5SEd Tanous     // Callback to be called once the request has been sent
7913d36e3a5SEd Tanous     static void afterSendData(const std::weak_ptr<ConnectionPool>& weakSelf,
7923d36e3a5SEd Tanous                               const std::function<void(Response&)>& resHandler,
7933d36e3a5SEd Tanous                               bool keepAlive, uint32_t connId, Response& res)
7943d36e3a5SEd Tanous     {
7953d36e3a5SEd Tanous         // Allow provided callback to perform additional processing of the
7963d36e3a5SEd Tanous         // request
7973d36e3a5SEd Tanous         resHandler(res);
7983d36e3a5SEd Tanous 
7993d36e3a5SEd Tanous         // If requests remain in the queue then we want to reuse this
8003d36e3a5SEd Tanous         // connection to send the next request
8013d36e3a5SEd Tanous         std::shared_ptr<ConnectionPool> self = weakSelf.lock();
8023d36e3a5SEd Tanous         if (!self)
8033d36e3a5SEd Tanous         {
8043d36e3a5SEd Tanous             BMCWEB_LOG_CRITICAL << self << " Failed to capture connection";
8053d36e3a5SEd Tanous             return;
8063d36e3a5SEd Tanous         }
8073d36e3a5SEd Tanous 
8083d36e3a5SEd Tanous         self->sendNext(keepAlive, connId);
8093d36e3a5SEd Tanous     }
8103d36e3a5SEd Tanous 
811f52c03c1SCarson Labrado     std::shared_ptr<ConnectionInfo>& addConnection()
812f52c03c1SCarson Labrado     {
813f52c03c1SCarson Labrado         unsigned int newId = static_cast<unsigned int>(connections.size());
814f52c03c1SCarson Labrado 
815e38778a5SAppaRao Puli         auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>(
816d14a48ffSCarson Labrado             ioc, id, connPolicy, destIP, destPort, useSSL, newId));
817f52c03c1SCarson Labrado 
818f52c03c1SCarson Labrado         BMCWEB_LOG_DEBUG << "Added connection "
819f52c03c1SCarson Labrado                          << std::to_string(connections.size() - 1)
820f52c03c1SCarson Labrado                          << " to pool " << destIP << ":"
821f52c03c1SCarson Labrado                          << std::to_string(destPort);
822f52c03c1SCarson Labrado 
823f52c03c1SCarson Labrado         return ret;
824f52c03c1SCarson Labrado     }
825f52c03c1SCarson Labrado 
826f52c03c1SCarson Labrado   public:
827d14a48ffSCarson Labrado     explicit ConnectionPool(
828d14a48ffSCarson Labrado         boost::asio::io_context& iocIn, const std::string& idIn,
829d14a48ffSCarson Labrado         const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
830d14a48ffSCarson Labrado         const std::string& destIPIn, uint16_t destPortIn, bool useSSLIn) :
8318a592810SEd Tanous         ioc(iocIn),
832d14a48ffSCarson Labrado         id(idIn), connPolicy(connPolicyIn), destIP(destIPIn),
833d14a48ffSCarson Labrado         destPort(destPortIn), useSSL(useSSLIn)
834f52c03c1SCarson Labrado     {
835f52c03c1SCarson Labrado         BMCWEB_LOG_DEBUG << "Initializing connection pool for " << destIP << ":"
836f52c03c1SCarson Labrado                          << std::to_string(destPort);
837f52c03c1SCarson Labrado 
838f52c03c1SCarson Labrado         // Initialize the pool with a single connection
839f52c03c1SCarson Labrado         addConnection();
840fe44eb0bSAyushi Smriti     }
841bd030d0aSAppaRao Puli };
842bd030d0aSAppaRao Puli 
843f52c03c1SCarson Labrado class HttpClient
844f52c03c1SCarson Labrado {
845f52c03c1SCarson Labrado   private:
846f52c03c1SCarson Labrado     std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
847f52c03c1SCarson Labrado         connectionPools;
848f8ca6d79SEd Tanous     boost::asio::io_context& ioc;
849d14a48ffSCarson Labrado     std::shared_ptr<ConnectionPolicy> connPolicy;
850f52c03c1SCarson Labrado 
851039a47e3SCarson Labrado     // Used as a dummy callback by sendData() in order to call
852039a47e3SCarson Labrado     // sendDataWithCallback()
85302cad96eSEd Tanous     static void genericResHandler(const Response& res)
854039a47e3SCarson Labrado     {
855039a47e3SCarson Labrado         BMCWEB_LOG_DEBUG << "Response handled with return code: "
856039a47e3SCarson Labrado                          << std::to_string(res.resultInt());
8574ee8e211SEd Tanous     }
858039a47e3SCarson Labrado 
859f52c03c1SCarson Labrado   public:
860d14a48ffSCarson Labrado     HttpClient() = delete;
861f8ca6d79SEd Tanous     explicit HttpClient(boost::asio::io_context& iocIn,
862f8ca6d79SEd Tanous                         const std::shared_ptr<ConnectionPolicy>& connPolicyIn) :
863f8ca6d79SEd Tanous         ioc(iocIn),
864d14a48ffSCarson Labrado         connPolicy(connPolicyIn)
865d14a48ffSCarson Labrado     {}
866f8ca6d79SEd Tanous 
867f52c03c1SCarson Labrado     HttpClient(const HttpClient&) = delete;
868f52c03c1SCarson Labrado     HttpClient& operator=(const HttpClient&) = delete;
869f52c03c1SCarson Labrado     HttpClient(HttpClient&&) = delete;
870f52c03c1SCarson Labrado     HttpClient& operator=(HttpClient&&) = delete;
871f52c03c1SCarson Labrado     ~HttpClient() = default;
872f52c03c1SCarson Labrado 
873039a47e3SCarson Labrado     // Send a request to destIP:destPort where additional processing of the
874039a47e3SCarson Labrado     // result is not required
8755e44e3d8SAppaRao Puli     void sendData(std::string&& data, const std::string& destIP,
876d14a48ffSCarson Labrado                   uint16_t destPort, const std::string& destUri, bool useSSL,
877f52c03c1SCarson Labrado                   const boost::beast::http::fields& httpHeader,
878d14a48ffSCarson Labrado                   const boost::beast::http::verb verb)
879f52c03c1SCarson Labrado     {
880e38778a5SAppaRao Puli         const std::function<void(Response&)> cb = genericResHandler;
8815e44e3d8SAppaRao Puli         sendDataWithCallback(std::move(data), destIP, destPort, destUri, useSSL,
882d14a48ffSCarson Labrado                              httpHeader, verb, cb);
883039a47e3SCarson Labrado     }
884039a47e3SCarson Labrado 
885039a47e3SCarson Labrado     // Send request to destIP:destPort and use the provided callback to
886039a47e3SCarson Labrado     // handle the response
8875e44e3d8SAppaRao Puli     void sendDataWithCallback(std::string&& data, const std::string& destIP,
888d14a48ffSCarson Labrado                               uint16_t destPort, const std::string& destUri,
889d14a48ffSCarson Labrado                               bool useSSL,
890039a47e3SCarson Labrado                               const boost::beast::http::fields& httpHeader,
891244256ccSCarson Labrado                               const boost::beast::http::verb verb,
8926b3db60dSEd Tanous                               const std::function<void(Response&)>& resHandler)
893039a47e3SCarson Labrado     {
894e38778a5SAppaRao Puli         std::string clientKey = useSSL ? "https" : "http";
895e38778a5SAppaRao Puli         clientKey += destIP;
896e38778a5SAppaRao Puli         clientKey += ":";
897e38778a5SAppaRao Puli         clientKey += std::to_string(destPort);
898d14a48ffSCarson Labrado         auto pool = connectionPools.try_emplace(clientKey);
899d14a48ffSCarson Labrado         if (pool.first->second == nullptr)
900f52c03c1SCarson Labrado         {
901d14a48ffSCarson Labrado             pool.first->second = std::make_shared<ConnectionPool>(
902d14a48ffSCarson Labrado                 ioc, clientKey, connPolicy, destIP, destPort, useSSL);
903f52c03c1SCarson Labrado         }
904f52c03c1SCarson Labrado         // Send the data using either the existing connection pool or the newly
905f52c03c1SCarson Labrado         // created connection pool
9065e44e3d8SAppaRao Puli         pool.first->second->sendData(std::move(data), destUri, httpHeader, verb,
907e38778a5SAppaRao Puli                                      resHandler);
908f52c03c1SCarson Labrado     }
909f52c03c1SCarson Labrado };
910bd030d0aSAppaRao Puli } // namespace crow
911