xref: /openbmc/bmcweb/http/http_client.hpp (revision d14a48ff)
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
100*d14a48ffSCarson Labrado struct ConnectionPolicy
101f52c03c1SCarson Labrado {
102f52c03c1SCarson Labrado     uint32_t maxRetryAttempts = 5;
103*d14a48ffSCarson Labrado 
104*d14a48ffSCarson Labrado     // the max size of requests in bytes.  0 for unlimited
105*d14a48ffSCarson Labrado     boost::optional<uint64_t> requestByteLimit = httpReadBodyLimit;
106*d14a48ffSCarson Labrado 
107*d14a48ffSCarson Labrado     size_t maxConnections = 1;
108*d14a48ffSCarson Labrado 
109f52c03c1SCarson Labrado     std::string retryPolicyAction = "TerminateAfterRetries";
110*d14a48ffSCarson Labrado 
111*d14a48ffSCarson 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,
122*d14a48ffSCarson Labrado         const std::function<void(bool, uint32_t, Response&)>& callbackIn) :
1238a592810SEd Tanous         req(std::move(reqIn)),
124*d14a48ffSCarson 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;
134*d14a48ffSCarson 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;
149f52c03c1SCarson Labrado     crow::async_resolve::Resolver resolver;
1500d5f5cf4SEd Tanous     boost::asio::ip::tcp::socket conn;
1510d5f5cf4SEd Tanous     std::optional<boost::beast::ssl_stream<boost::asio::ip::tcp::socket&>>
1520d5f5cf4SEd Tanous         sslConn;
153e38778a5SAppaRao Puli 
154f52c03c1SCarson Labrado     boost::asio::steady_timer timer;
15584b35604SEd Tanous 
156f52c03c1SCarson Labrado     friend class ConnectionPool;
157bd030d0aSAppaRao Puli 
15829a82b08SSunitha Harish     void doResolve()
15929a82b08SSunitha Harish     {
16029a82b08SSunitha Harish         state = ConnState::resolveInProgress;
161f52c03c1SCarson Labrado         BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":"
162f52c03c1SCarson Labrado                          << std::to_string(port)
163f52c03c1SCarson Labrado                          << ", id: " << std::to_string(connId);
16429a82b08SSunitha Harish 
1653d36e3a5SEd Tanous         resolver.asyncResolve(host, port,
1663d36e3a5SEd Tanous                               std::bind_front(&ConnectionInfo::afterResolve,
1673d36e3a5SEd Tanous                                               this, shared_from_this()));
1683d36e3a5SEd Tanous     }
1693d36e3a5SEd Tanous 
1703d36e3a5SEd Tanous     void afterResolve(
1713d36e3a5SEd Tanous         const std::shared_ptr<ConnectionInfo>& /*self*/,
17229a82b08SSunitha Harish         const boost::beast::error_code ec,
1733d36e3a5SEd Tanous         const std::vector<boost::asio::ip::tcp::endpoint>& endpointList)
1743d36e3a5SEd Tanous     {
17526f6976fSEd Tanous         if (ec || (endpointList.empty()))
17629a82b08SSunitha Harish         {
17729a82b08SSunitha Harish             BMCWEB_LOG_ERROR << "Resolve failed: " << ec.message();
1783d36e3a5SEd Tanous             state = ConnState::resolveFailed;
1793d36e3a5SEd Tanous             waitAndRetry();
18029a82b08SSunitha Harish             return;
18129a82b08SSunitha Harish         }
1823d36e3a5SEd Tanous         BMCWEB_LOG_DEBUG << "Resolved " << host << ":" << std::to_string(port)
1833d36e3a5SEd Tanous                          << ", id: " << std::to_string(connId);
1842a5689a7SAppaRao Puli         state = ConnState::connectInProgress;
1852a5689a7SAppaRao Puli 
186f52c03c1SCarson Labrado         BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":"
187f52c03c1SCarson Labrado                          << std::to_string(port)
188f52c03c1SCarson Labrado                          << ", id: " << std::to_string(connId);
189b00dcc27SEd Tanous 
1900d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
1910d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
1920d5f5cf4SEd Tanous 
1930d5f5cf4SEd Tanous         boost::asio::async_connect(
1940d5f5cf4SEd Tanous             conn, endpointList,
195e38778a5SAppaRao Puli             std::bind_front(&ConnectionInfo::afterConnect, this,
196e38778a5SAppaRao Puli                             shared_from_this()));
197e38778a5SAppaRao Puli     }
198e38778a5SAppaRao Puli 
199e38778a5SAppaRao Puli     void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/,
200e38778a5SAppaRao Puli                       boost::beast::error_code ec,
201e38778a5SAppaRao Puli                       const boost::asio::ip::tcp::endpoint& endpoint)
202e38778a5SAppaRao Puli     {
203513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
204513d1ffcSCarson Labrado         // this branch
205513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
206513d1ffcSCarson Labrado         {
207513d1ffcSCarson Labrado             return;
208513d1ffcSCarson Labrado         }
209513d1ffcSCarson Labrado 
2100d5f5cf4SEd Tanous         timer.cancel();
2112a5689a7SAppaRao Puli         if (ec)
2122a5689a7SAppaRao Puli         {
213002d39b4SEd Tanous             BMCWEB_LOG_ERROR << "Connect " << endpoint.address().to_string()
214002d39b4SEd Tanous                              << ":" << std::to_string(endpoint.port())
215e38778a5SAppaRao Puli                              << ", id: " << std::to_string(connId)
2162a5689a7SAppaRao Puli                              << " failed: " << ec.message();
217e38778a5SAppaRao Puli             state = ConnState::connectFailed;
218e38778a5SAppaRao Puli             waitAndRetry();
2192a5689a7SAppaRao Puli             return;
2202a5689a7SAppaRao Puli         }
221e38778a5SAppaRao Puli         BMCWEB_LOG_DEBUG << "Connected to: " << endpoint.address().to_string()
222e38778a5SAppaRao Puli                          << ":" << std::to_string(endpoint.port())
223e38778a5SAppaRao Puli                          << ", id: " << std::to_string(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*/,
249e38778a5SAppaRao Puli                            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         {
261e38778a5SAppaRao Puli             BMCWEB_LOG_ERROR << "SSL Handshake failed -"
262e38778a5SAppaRao Puli                              << " id: " << std::to_string(connId)
263e38778a5SAppaRao Puli                              << " error: " << ec.message();
264e38778a5SAppaRao Puli             state = ConnState::handshakeFailed;
265e38778a5SAppaRao Puli             waitAndRetry();
266e38778a5SAppaRao Puli             return;
267e38778a5SAppaRao Puli         }
268e38778a5SAppaRao Puli         BMCWEB_LOG_DEBUG << "SSL Handshake successful -"
269e38778a5SAppaRao Puli                          << " id: " << std::to_string(connId);
270e38778a5SAppaRao Puli         state = ConnState::connected;
271e38778a5SAppaRao Puli         sendMessage();
2722a5689a7SAppaRao Puli     }
2732a5689a7SAppaRao Puli 
274f52c03c1SCarson Labrado     void sendMessage()
2752a5689a7SAppaRao Puli     {
2762a5689a7SAppaRao Puli         state = ConnState::sendInProgress;
2772a5689a7SAppaRao Puli 
278bd030d0aSAppaRao Puli         // Set a timeout on the operation
2790d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
2800d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
281bd030d0aSAppaRao Puli 
282bd030d0aSAppaRao Puli         // Send the HTTP request to the remote host
283e38778a5SAppaRao Puli         if (sslConn)
284e38778a5SAppaRao Puli         {
285e38778a5SAppaRao Puli             boost::beast::http::async_write(
286e38778a5SAppaRao Puli                 *sslConn, req,
287e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterWrite, this,
288e38778a5SAppaRao Puli                                 shared_from_this()));
289e38778a5SAppaRao Puli         }
290e38778a5SAppaRao Puli         else
291e38778a5SAppaRao Puli         {
292bd030d0aSAppaRao Puli             boost::beast::http::async_write(
293bd030d0aSAppaRao Puli                 conn, req,
294e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterWrite, this,
295e38778a5SAppaRao Puli                                 shared_from_this()));
296e38778a5SAppaRao Puli         }
297e38778a5SAppaRao Puli     }
298e38778a5SAppaRao Puli 
299e38778a5SAppaRao Puli     void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/,
300e38778a5SAppaRao Puli                     const boost::beast::error_code& ec, size_t bytesTransferred)
301e38778a5SAppaRao Puli     {
302513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
303513d1ffcSCarson Labrado         // this branch
304513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
305513d1ffcSCarson Labrado         {
306513d1ffcSCarson Labrado             return;
307513d1ffcSCarson Labrado         }
308513d1ffcSCarson Labrado 
3090d5f5cf4SEd Tanous         timer.cancel();
310bd030d0aSAppaRao Puli         if (ec)
311bd030d0aSAppaRao Puli         {
312002d39b4SEd Tanous             BMCWEB_LOG_ERROR << "sendMessage() failed: " << ec.message();
313e38778a5SAppaRao Puli             state = ConnState::sendFailed;
314e38778a5SAppaRao Puli             waitAndRetry();
315bd030d0aSAppaRao Puli             return;
316bd030d0aSAppaRao Puli         }
317bd030d0aSAppaRao Puli         BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
318bd030d0aSAppaRao Puli                          << bytesTransferred;
319bd030d0aSAppaRao Puli 
320e38778a5SAppaRao Puli         recvMessage();
321bd030d0aSAppaRao Puli     }
322bd030d0aSAppaRao Puli 
323bd030d0aSAppaRao Puli     void recvMessage()
324bd030d0aSAppaRao Puli     {
3256eaa1d2fSSunitha Harish         state = ConnState::recvInProgress;
3266eaa1d2fSSunitha Harish 
3276eaa1d2fSSunitha Harish         parser.emplace(std::piecewise_construct, std::make_tuple());
328*d14a48ffSCarson Labrado 
329*d14a48ffSCarson Labrado         parser->body_limit(connPolicy->requestByteLimit);
3306eaa1d2fSSunitha Harish 
3310d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
3320d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
3330d5f5cf4SEd Tanous 
334bd030d0aSAppaRao Puli         // Receive the HTTP response
335e38778a5SAppaRao Puli         if (sslConn)
336e38778a5SAppaRao Puli         {
337e38778a5SAppaRao Puli             boost::beast::http::async_read(
338e38778a5SAppaRao Puli                 *sslConn, buffer, *parser,
339e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterRead, this,
340e38778a5SAppaRao Puli                                 shared_from_this()));
341e38778a5SAppaRao Puli         }
342e38778a5SAppaRao Puli         else
343e38778a5SAppaRao Puli         {
344bd030d0aSAppaRao Puli             boost::beast::http::async_read(
3456eaa1d2fSSunitha Harish                 conn, buffer, *parser,
346e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterRead, this,
347e38778a5SAppaRao Puli                                 shared_from_this()));
348e38778a5SAppaRao Puli         }
349e38778a5SAppaRao Puli     }
350e38778a5SAppaRao Puli 
351e38778a5SAppaRao Puli     void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/,
352e38778a5SAppaRao Puli                    const boost::beast::error_code& ec,
353e38778a5SAppaRao Puli                    const std::size_t& bytesTransferred)
354e38778a5SAppaRao Puli     {
355513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
356513d1ffcSCarson Labrado         // this branch
357513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
358513d1ffcSCarson Labrado         {
359513d1ffcSCarson Labrado             return;
360513d1ffcSCarson Labrado         }
361513d1ffcSCarson Labrado 
3620d5f5cf4SEd Tanous         timer.cancel();
363e38778a5SAppaRao Puli         if (ec && ec != boost::asio::ssl::error::stream_truncated)
364bd030d0aSAppaRao Puli         {
365002d39b4SEd Tanous             BMCWEB_LOG_ERROR << "recvMessage() failed: " << ec.message();
366e38778a5SAppaRao Puli             state = ConnState::recvFailed;
367e38778a5SAppaRao Puli             waitAndRetry();
368bd030d0aSAppaRao Puli             return;
369bd030d0aSAppaRao Puli         }
370bd030d0aSAppaRao Puli         BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
371bd030d0aSAppaRao Puli                          << bytesTransferred;
372e38778a5SAppaRao Puli         BMCWEB_LOG_DEBUG << "recvMessage() data: " << parser->get().body();
373bd030d0aSAppaRao Puli 
374e38778a5SAppaRao Puli         unsigned int respCode = parser->get().result_int();
375e38778a5SAppaRao Puli         BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: " << respCode;
3766eaa1d2fSSunitha Harish 
377a7a80296SCarson Labrado         // Make sure the received response code is valid as defined by
378a7a80296SCarson Labrado         // the associated retry policy
379*d14a48ffSCarson Labrado         if (connPolicy->invalidResp(respCode))
3806eaa1d2fSSunitha Harish         {
3816eaa1d2fSSunitha Harish             // The listener failed to receive the Sent-Event
382002d39b4SEd Tanous             BMCWEB_LOG_ERROR << "recvMessage() Listener Failed to "
3837adb85acSSunitha Harish                                 "receive Sent-Event. Header Response Code: "
3847adb85acSSunitha Harish                              << respCode;
385e38778a5SAppaRao Puli             state = ConnState::recvFailed;
386e38778a5SAppaRao Puli             waitAndRetry();
3876eaa1d2fSSunitha Harish             return;
3886eaa1d2fSSunitha Harish         }
389bd030d0aSAppaRao Puli 
390f52c03c1SCarson Labrado         // Send is successful
391f52c03c1SCarson Labrado         // Reset the counter just in case this was after retrying
392e38778a5SAppaRao Puli         retryCount = 0;
3936eaa1d2fSSunitha Harish 
3946eaa1d2fSSunitha Harish         // Keep the connection alive if server supports it
3956eaa1d2fSSunitha Harish         // Else close the connection
3966eaa1d2fSSunitha Harish         BMCWEB_LOG_DEBUG << "recvMessage() keepalive : "
397e38778a5SAppaRao Puli                          << parser->keep_alive();
3986eaa1d2fSSunitha Harish 
399039a47e3SCarson Labrado         // Copy the response into a Response object so that it can be
400039a47e3SCarson Labrado         // processed by the callback function.
401e38778a5SAppaRao Puli         res.stringResponse = parser->release();
402e38778a5SAppaRao Puli         callback(parser->keep_alive(), connId, res);
403513d1ffcSCarson Labrado         res.clear();
404bd030d0aSAppaRao Puli     }
405bd030d0aSAppaRao Puli 
4060d5f5cf4SEd Tanous     static void onTimeout(const std::weak_ptr<ConnectionInfo>& weakSelf,
4075e7e2dc5SEd Tanous                           const boost::system::error_code& ec)
4080d5f5cf4SEd Tanous     {
4090d5f5cf4SEd Tanous         if (ec == boost::asio::error::operation_aborted)
4100d5f5cf4SEd Tanous         {
4110d5f5cf4SEd Tanous             BMCWEB_LOG_DEBUG
412513d1ffcSCarson Labrado                 << "async_wait failed since the operation is aborted";
4130d5f5cf4SEd Tanous             return;
4140d5f5cf4SEd Tanous         }
4150d5f5cf4SEd Tanous         if (ec)
4160d5f5cf4SEd Tanous         {
4170d5f5cf4SEd Tanous             BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message();
4180d5f5cf4SEd Tanous             // If the timer fails, we need to close the socket anyway, same as
4190d5f5cf4SEd Tanous             // if it expired.
4200d5f5cf4SEd Tanous         }
4210d5f5cf4SEd Tanous         std::shared_ptr<ConnectionInfo> self = weakSelf.lock();
4220d5f5cf4SEd Tanous         if (self == nullptr)
4230d5f5cf4SEd Tanous         {
4240d5f5cf4SEd Tanous             return;
4250d5f5cf4SEd Tanous         }
4260d5f5cf4SEd Tanous         self->waitAndRetry();
4270d5f5cf4SEd Tanous     }
4280d5f5cf4SEd Tanous 
4296eaa1d2fSSunitha Harish     void waitAndRetry()
430bd030d0aSAppaRao Puli     {
431*d14a48ffSCarson Labrado         if ((retryCount >= connPolicy->maxRetryAttempts) ||
432e38778a5SAppaRao Puli             (state == ConnState::sslInitFailed))
4332a5689a7SAppaRao Puli         {
4346eaa1d2fSSunitha Harish             BMCWEB_LOG_ERROR << "Maximum number of retries reached.";
435f52c03c1SCarson Labrado             BMCWEB_LOG_DEBUG << "Retry policy: "
436*d14a48ffSCarson Labrado                              << connPolicy->retryPolicyAction;
437039a47e3SCarson Labrado 
438*d14a48ffSCarson Labrado             if (connPolicy->retryPolicyAction == "TerminateAfterRetries")
439fe44eb0bSAyushi Smriti             {
440fe44eb0bSAyushi Smriti                 // TODO: delete subscription
441fe44eb0bSAyushi Smriti                 state = ConnState::terminated;
442fe44eb0bSAyushi Smriti             }
443*d14a48ffSCarson Labrado             if (connPolicy->retryPolicyAction == "SuspendRetries")
444fe44eb0bSAyushi Smriti             {
4452a5689a7SAppaRao Puli                 state = ConnState::suspended;
4462a5689a7SAppaRao Puli             }
447513d1ffcSCarson Labrado 
448513d1ffcSCarson Labrado             // We want to return a 502 to indicate there was an error with
449513d1ffcSCarson Labrado             // the external server
450513d1ffcSCarson Labrado             res.result(boost::beast::http::status::bad_gateway);
451513d1ffcSCarson Labrado             callback(false, connId, res);
452513d1ffcSCarson Labrado             res.clear();
453513d1ffcSCarson Labrado 
4546eaa1d2fSSunitha Harish             // Reset the retrycount to zero so that client can try connecting
4556eaa1d2fSSunitha Harish             // again if needed
456fe44eb0bSAyushi Smriti             retryCount = 0;
4572a5689a7SAppaRao Puli             return;
4582a5689a7SAppaRao Puli         }
4592a5689a7SAppaRao Puli 
4602a5689a7SAppaRao Puli         retryCount++;
461fe44eb0bSAyushi Smriti 
462f52c03c1SCarson Labrado         BMCWEB_LOG_DEBUG << "Attempt retry after "
463f52c03c1SCarson Labrado                          << std::to_string(
464*d14a48ffSCarson Labrado                                 connPolicy->retryIntervalSecs.count())
465fe44eb0bSAyushi Smriti                          << " seconds. RetryCount = " << retryCount;
466*d14a48ffSCarson Labrado         timer.expires_after(connPolicy->retryIntervalSecs);
4673d36e3a5SEd Tanous         timer.async_wait(std::bind_front(&ConnectionInfo::onTimerDone, this,
4683d36e3a5SEd Tanous                                          shared_from_this()));
4693d36e3a5SEd Tanous     }
4703d36e3a5SEd Tanous 
4713d36e3a5SEd Tanous     void onTimerDone(const std::shared_ptr<ConnectionInfo>& /*self*/,
4723d36e3a5SEd Tanous                      const boost::system::error_code& ec)
4733d36e3a5SEd Tanous     {
4746eaa1d2fSSunitha Harish         if (ec == boost::asio::error::operation_aborted)
4756eaa1d2fSSunitha Harish         {
4766eaa1d2fSSunitha Harish             BMCWEB_LOG_DEBUG
4776eaa1d2fSSunitha Harish                 << "async_wait failed since the operation is aborted"
4786eaa1d2fSSunitha Harish                 << ec.message();
4796eaa1d2fSSunitha Harish         }
4806eaa1d2fSSunitha Harish         else if (ec)
4816eaa1d2fSSunitha Harish         {
4826eaa1d2fSSunitha Harish             BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message();
4836eaa1d2fSSunitha Harish             // Ignore the error and continue the retry loop to attempt
4846eaa1d2fSSunitha Harish             // sending the event as per the retry policy
4856eaa1d2fSSunitha Harish         }
4866eaa1d2fSSunitha Harish 
487f52c03c1SCarson Labrado         // Let's close the connection and restart from resolve.
4883d36e3a5SEd Tanous         doClose(true);
4892a5689a7SAppaRao Puli     }
4902a5689a7SAppaRao Puli 
491e38778a5SAppaRao Puli     void shutdownConn(bool retry)
492fe44eb0bSAyushi Smriti     {
493f52c03c1SCarson Labrado         boost::beast::error_code ec;
4940d5f5cf4SEd Tanous         conn.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
495f52c03c1SCarson Labrado         conn.close();
496f52c03c1SCarson Labrado 
497f52c03c1SCarson Labrado         // not_connected happens sometimes so don't bother reporting it.
498f52c03c1SCarson Labrado         if (ec && ec != boost::beast::errc::not_connected)
4992a5689a7SAppaRao Puli         {
500f52c03c1SCarson Labrado             BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
501f52c03c1SCarson Labrado                              << ", id: " << std::to_string(connId)
502f52c03c1SCarson Labrado                              << " shutdown failed: " << ec.message();
5036eaa1d2fSSunitha Harish         }
5045cab68f3SCarson Labrado         else
5055cab68f3SCarson Labrado         {
506f52c03c1SCarson Labrado             BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
507f52c03c1SCarson Labrado                              << ", id: " << std::to_string(connId)
508f52c03c1SCarson Labrado                              << " closed gracefully";
5095cab68f3SCarson Labrado         }
510ca723762SEd Tanous 
511e38778a5SAppaRao Puli         if (retry)
51292a74e56SAppaRao Puli         {
513f52c03c1SCarson Labrado             // Now let's try to resend the data
514f52c03c1SCarson Labrado             state = ConnState::retry;
5150d5f5cf4SEd Tanous             doResolve();
516e38778a5SAppaRao Puli         }
517e38778a5SAppaRao Puli         else
518e38778a5SAppaRao Puli         {
519e38778a5SAppaRao Puli             state = ConnState::closed;
520e38778a5SAppaRao Puli         }
521e38778a5SAppaRao Puli     }
522e38778a5SAppaRao Puli 
523e38778a5SAppaRao Puli     void doClose(bool retry = false)
524e38778a5SAppaRao Puli     {
525e38778a5SAppaRao Puli         if (!sslConn)
526e38778a5SAppaRao Puli         {
527e38778a5SAppaRao Puli             shutdownConn(retry);
528e38778a5SAppaRao Puli             return;
529e38778a5SAppaRao Puli         }
530e38778a5SAppaRao Puli 
531e38778a5SAppaRao Puli         sslConn->async_shutdown(
532e38778a5SAppaRao Puli             std::bind_front(&ConnectionInfo::afterSslShutdown, this,
533e38778a5SAppaRao Puli                             shared_from_this(), retry));
534e38778a5SAppaRao Puli     }
535e38778a5SAppaRao Puli 
536e38778a5SAppaRao Puli     void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/,
537e38778a5SAppaRao Puli                           bool retry, const boost::system::error_code& ec)
538e38778a5SAppaRao Puli     {
539e38778a5SAppaRao Puli 
540e38778a5SAppaRao Puli         if (ec)
541e38778a5SAppaRao Puli         {
542e38778a5SAppaRao Puli             BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
543e38778a5SAppaRao Puli                              << ", id: " << std::to_string(connId)
544e38778a5SAppaRao Puli                              << " shutdown failed: " << ec.message();
545e38778a5SAppaRao Puli         }
546e38778a5SAppaRao Puli         else
547e38778a5SAppaRao Puli         {
548e38778a5SAppaRao Puli             BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
549e38778a5SAppaRao Puli                              << ", id: " << std::to_string(connId)
550e38778a5SAppaRao Puli                              << " closed gracefully";
551e38778a5SAppaRao Puli         }
552e38778a5SAppaRao Puli         shutdownConn(retry);
553e38778a5SAppaRao Puli     }
554e38778a5SAppaRao Puli 
555e38778a5SAppaRao Puli     void setCipherSuiteTLSext()
556e38778a5SAppaRao Puli     {
557e38778a5SAppaRao Puli         if (!sslConn)
558e38778a5SAppaRao Puli         {
559e38778a5SAppaRao Puli             return;
560e38778a5SAppaRao Puli         }
561e38778a5SAppaRao Puli         // NOTE: The SSL_set_tlsext_host_name is defined in tlsv1.h header
562e38778a5SAppaRao Puli         // file but its having old style casting (name is cast to void*).
563e38778a5SAppaRao Puli         // Since bmcweb compiler treats all old-style-cast as error, its
564e38778a5SAppaRao Puli         // causing the build failure. So replaced the same macro inline and
565e38778a5SAppaRao Puli         // did corrected the code by doing static_cast to viod*. This has to
566e38778a5SAppaRao Puli         // be fixed in openssl library in long run. Set SNI Hostname (many
567e38778a5SAppaRao Puli         // hosts need this to handshake successfully)
568e38778a5SAppaRao Puli         if (SSL_ctrl(sslConn->native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME,
569e38778a5SAppaRao Puli                      TLSEXT_NAMETYPE_host_name,
570e38778a5SAppaRao Puli                      static_cast<void*>(&host.front())) == 0)
571e38778a5SAppaRao Puli 
572e38778a5SAppaRao Puli         {
573e38778a5SAppaRao Puli             boost::beast::error_code ec{static_cast<int>(::ERR_get_error()),
574e38778a5SAppaRao Puli                                         boost::asio::error::get_ssl_category()};
575e38778a5SAppaRao Puli 
576e38778a5SAppaRao Puli             BMCWEB_LOG_ERROR << "SSL_set_tlsext_host_name " << host << ":"
577e38778a5SAppaRao Puli                              << port << ", id: " << std::to_string(connId)
578e38778a5SAppaRao Puli                              << " failed: " << ec.message();
579e38778a5SAppaRao Puli             // Set state as sslInit failed so that we close the connection
580e38778a5SAppaRao Puli             // and take appropriate action as per retry configuration.
581e38778a5SAppaRao Puli             state = ConnState::sslInitFailed;
582e38778a5SAppaRao Puli             waitAndRetry();
583e38778a5SAppaRao Puli             return;
584e38778a5SAppaRao Puli         }
585bd030d0aSAppaRao Puli     }
586bd030d0aSAppaRao Puli 
587bd030d0aSAppaRao Puli   public:
588*d14a48ffSCarson Labrado     explicit ConnectionInfo(
589*d14a48ffSCarson Labrado         boost::asio::io_context& iocIn, const std::string& idIn,
590*d14a48ffSCarson Labrado         const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
591*d14a48ffSCarson Labrado         const std::string& destIPIn, uint16_t destPortIn, bool useSSL,
592*d14a48ffSCarson Labrado         unsigned int connIdIn) :
5938a592810SEd Tanous         subId(idIn),
594*d14a48ffSCarson Labrado         connPolicy(connPolicyIn), host(destIPIn), port(destPortIn),
595*d14a48ffSCarson Labrado         connId(connIdIn), conn(iocIn), timer(iocIn)
596e38778a5SAppaRao Puli     {
597e38778a5SAppaRao Puli         if (useSSL)
598e38778a5SAppaRao Puli         {
599e38778a5SAppaRao Puli             std::optional<boost::asio::ssl::context> sslCtx =
600e38778a5SAppaRao Puli                 ensuressl::getSSLClientContext();
601e38778a5SAppaRao Puli 
602e38778a5SAppaRao Puli             if (!sslCtx)
603e38778a5SAppaRao Puli             {
604e38778a5SAppaRao Puli                 BMCWEB_LOG_ERROR << "prepareSSLContext failed - " << host << ":"
605e38778a5SAppaRao Puli                                  << port << ", id: " << std::to_string(connId);
606e38778a5SAppaRao Puli                 // Don't retry if failure occurs while preparing SSL context
607e38778a5SAppaRao Puli                 // such as certificate is invalid or set cipher failure or set
608e38778a5SAppaRao Puli                 // host name failure etc... Setting conn state to sslInitFailed
609e38778a5SAppaRao Puli                 // and connection state will be transitioned to next state
610e38778a5SAppaRao Puli                 // depending on retry policy set by subscription.
611e38778a5SAppaRao Puli                 state = ConnState::sslInitFailed;
612e38778a5SAppaRao Puli                 waitAndRetry();
613e38778a5SAppaRao Puli                 return;
614e38778a5SAppaRao Puli             }
615e38778a5SAppaRao Puli             sslConn.emplace(conn, *sslCtx);
616e38778a5SAppaRao Puli             setCipherSuiteTLSext();
617e38778a5SAppaRao Puli         }
618e38778a5SAppaRao Puli     }
619f52c03c1SCarson Labrado };
620bd030d0aSAppaRao Puli 
621f52c03c1SCarson Labrado class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
622bd030d0aSAppaRao Puli {
623f52c03c1SCarson Labrado   private:
624f52c03c1SCarson Labrado     boost::asio::io_context& ioc;
625e38778a5SAppaRao Puli     std::string id;
626*d14a48ffSCarson Labrado     std::shared_ptr<ConnectionPolicy> connPolicy;
627e38778a5SAppaRao Puli     std::string destIP;
628e38778a5SAppaRao Puli     uint16_t destPort;
629e38778a5SAppaRao Puli     bool useSSL;
630f52c03c1SCarson Labrado     std::vector<std::shared_ptr<ConnectionInfo>> connections;
631f52c03c1SCarson Labrado     boost::container::devector<PendingRequest> requestQueue;
632f52c03c1SCarson Labrado 
633f52c03c1SCarson Labrado     friend class HttpClient;
634f52c03c1SCarson Labrado 
635244256ccSCarson Labrado     // Configure a connections's request, callback, and retry info in
636244256ccSCarson Labrado     // preparation to begin sending the request
637f52c03c1SCarson Labrado     void setConnProps(ConnectionInfo& conn)
638bd030d0aSAppaRao Puli     {
639f52c03c1SCarson Labrado         if (requestQueue.empty())
640f52c03c1SCarson Labrado         {
641f52c03c1SCarson Labrado             BMCWEB_LOG_ERROR
642f52c03c1SCarson Labrado                 << "setConnProps() should not have been called when requestQueue is empty";
643bd030d0aSAppaRao Puli             return;
644bd030d0aSAppaRao Puli         }
645bd030d0aSAppaRao Puli 
646244256ccSCarson Labrado         auto nextReq = requestQueue.front();
647244256ccSCarson Labrado         conn.req = std::move(nextReq.req);
648244256ccSCarson Labrado         conn.callback = std::move(nextReq.callback);
649f52c03c1SCarson Labrado 
650f52c03c1SCarson Labrado         BMCWEB_LOG_DEBUG << "Setting properties for connection " << conn.host
651f52c03c1SCarson Labrado                          << ":" << std::to_string(conn.port)
652a7a80296SCarson Labrado                          << ", id: " << std::to_string(conn.connId);
653f52c03c1SCarson Labrado 
654f52c03c1SCarson Labrado         // We can remove the request from the queue at this point
655f52c03c1SCarson Labrado         requestQueue.pop_front();
656f52c03c1SCarson Labrado     }
657f52c03c1SCarson Labrado 
658f52c03c1SCarson Labrado     // Gets called as part of callback after request is sent
659f52c03c1SCarson Labrado     // Reuses the connection if there are any requests waiting to be sent
660f52c03c1SCarson Labrado     // Otherwise closes the connection if it is not a keep-alive
661f52c03c1SCarson Labrado     void sendNext(bool keepAlive, uint32_t connId)
662f52c03c1SCarson Labrado     {
663f52c03c1SCarson Labrado         auto conn = connections[connId];
66446a81465SCarson Labrado 
66546a81465SCarson Labrado         // Allow the connection's handler to be deleted
66646a81465SCarson Labrado         // This is needed because of Redfish Aggregation passing an
66746a81465SCarson Labrado         // AsyncResponse shared_ptr to this callback
66846a81465SCarson Labrado         conn->callback = nullptr;
66946a81465SCarson Labrado 
670f52c03c1SCarson Labrado         // Reuse the connection to send the next request in the queue
671f52c03c1SCarson Labrado         if (!requestQueue.empty())
672f52c03c1SCarson Labrado         {
673f52c03c1SCarson Labrado             BMCWEB_LOG_DEBUG << std::to_string(requestQueue.size())
674f52c03c1SCarson Labrado                              << " requests remaining in queue for " << destIP
675f52c03c1SCarson Labrado                              << ":" << std::to_string(destPort)
676f52c03c1SCarson Labrado                              << ", reusing connnection "
677f52c03c1SCarson Labrado                              << std::to_string(connId);
678f52c03c1SCarson Labrado 
679f52c03c1SCarson Labrado             setConnProps(*conn);
680f52c03c1SCarson Labrado 
681f52c03c1SCarson Labrado             if (keepAlive)
682f52c03c1SCarson Labrado             {
683f52c03c1SCarson Labrado                 conn->sendMessage();
6842a5689a7SAppaRao Puli             }
6852a5689a7SAppaRao Puli             else
6862a5689a7SAppaRao Puli             {
687f52c03c1SCarson Labrado                 // Server is not keep-alive enabled so we need to close the
688f52c03c1SCarson Labrado                 // connection and then start over from resolve
689f52c03c1SCarson Labrado                 conn->doClose();
690f52c03c1SCarson Labrado                 conn->doResolve();
691f52c03c1SCarson Labrado             }
692f52c03c1SCarson Labrado             return;
693f52c03c1SCarson Labrado         }
694f52c03c1SCarson Labrado 
695f52c03c1SCarson Labrado         // No more messages to send so close the connection if necessary
696f52c03c1SCarson Labrado         if (keepAlive)
697f52c03c1SCarson Labrado         {
698f52c03c1SCarson Labrado             conn->state = ConnState::idle;
699f52c03c1SCarson Labrado         }
700f52c03c1SCarson Labrado         else
701f52c03c1SCarson Labrado         {
702f52c03c1SCarson Labrado             // Abort the connection since server is not keep-alive enabled
703f52c03c1SCarson Labrado             conn->state = ConnState::abortConnection;
704f52c03c1SCarson Labrado             conn->doClose();
7052a5689a7SAppaRao Puli         }
706bd030d0aSAppaRao Puli     }
707bd030d0aSAppaRao Puli 
708244256ccSCarson Labrado     void sendData(std::string& data, const std::string& destUri,
709244256ccSCarson Labrado                   const boost::beast::http::fields& httpHeader,
710244256ccSCarson Labrado                   const boost::beast::http::verb verb,
7116b3db60dSEd Tanous                   const std::function<void(Response&)>& resHandler)
712fe44eb0bSAyushi Smriti     {
713244256ccSCarson Labrado         // Construct the request to be sent
714244256ccSCarson Labrado         boost::beast::http::request<boost::beast::http::string_body> thisReq(
715244256ccSCarson Labrado             verb, destUri, 11, "", httpHeader);
716244256ccSCarson Labrado         thisReq.set(boost::beast::http::field::host, destIP);
717244256ccSCarson Labrado         thisReq.keep_alive(true);
718244256ccSCarson Labrado         thisReq.body() = std::move(data);
719244256ccSCarson Labrado         thisReq.prepare_payload();
7203d36e3a5SEd Tanous         auto cb = std::bind_front(&ConnectionPool::afterSendData,
7213d36e3a5SEd Tanous                                   weak_from_this(), resHandler);
722f52c03c1SCarson Labrado         // Reuse an existing connection if one is available
723f52c03c1SCarson Labrado         for (unsigned int i = 0; i < connections.size(); i++)
724fe44eb0bSAyushi Smriti         {
725f52c03c1SCarson Labrado             auto conn = connections[i];
726f52c03c1SCarson Labrado             if ((conn->state == ConnState::idle) ||
727f52c03c1SCarson Labrado                 (conn->state == ConnState::initialized) ||
728f52c03c1SCarson Labrado                 (conn->state == ConnState::closed))
729f52c03c1SCarson Labrado             {
730244256ccSCarson Labrado                 conn->req = std::move(thisReq);
731f52c03c1SCarson Labrado                 conn->callback = std::move(cb);
732f52c03c1SCarson Labrado                 std::string commonMsg = std::to_string(i) + " from pool " +
733f52c03c1SCarson Labrado                                         destIP + ":" + std::to_string(destPort);
734f52c03c1SCarson Labrado 
735f52c03c1SCarson Labrado                 if (conn->state == ConnState::idle)
736f52c03c1SCarson Labrado                 {
737f52c03c1SCarson Labrado                     BMCWEB_LOG_DEBUG << "Grabbing idle connection "
738f52c03c1SCarson Labrado                                      << commonMsg;
739f52c03c1SCarson Labrado                     conn->sendMessage();
740f52c03c1SCarson Labrado                 }
741f52c03c1SCarson Labrado                 else
742f52c03c1SCarson Labrado                 {
743f52c03c1SCarson Labrado                     BMCWEB_LOG_DEBUG << "Reusing existing connection "
744f52c03c1SCarson Labrado                                      << commonMsg;
745f52c03c1SCarson Labrado                     conn->doResolve();
746f52c03c1SCarson Labrado                 }
747f52c03c1SCarson Labrado                 return;
748f52c03c1SCarson Labrado             }
749f52c03c1SCarson Labrado         }
750f52c03c1SCarson Labrado 
751f52c03c1SCarson Labrado         // All connections in use so create a new connection or add request to
752f52c03c1SCarson Labrado         // the queue
753*d14a48ffSCarson Labrado         if (connections.size() < connPolicy->maxConnections)
754f52c03c1SCarson Labrado         {
755f52c03c1SCarson Labrado             BMCWEB_LOG_DEBUG << "Adding new connection to pool " << destIP
756f52c03c1SCarson Labrado                              << ":" << std::to_string(destPort);
757f52c03c1SCarson Labrado             auto conn = addConnection();
758244256ccSCarson Labrado             conn->req = std::move(thisReq);
759f52c03c1SCarson Labrado             conn->callback = std::move(cb);
760f52c03c1SCarson Labrado             conn->doResolve();
761f52c03c1SCarson Labrado         }
762f52c03c1SCarson Labrado         else if (requestQueue.size() < maxRequestQueueSize)
763f52c03c1SCarson Labrado         {
764f52c03c1SCarson Labrado             BMCWEB_LOG_ERROR << "Max pool size reached. Adding data to queue.";
765*d14a48ffSCarson Labrado             requestQueue.emplace_back(std::move(thisReq), std::move(cb));
766f52c03c1SCarson Labrado         }
767f52c03c1SCarson Labrado         else
768f52c03c1SCarson Labrado         {
76943e14d38SCarson Labrado             // If we can't buffer the request then we should let the callback
77043e14d38SCarson Labrado             // handle a 429 Too Many Requests dummy response
771f52c03c1SCarson Labrado             BMCWEB_LOG_ERROR << destIP << ":" << std::to_string(destPort)
772f52c03c1SCarson Labrado                              << " request queue full.  Dropping request.";
77343e14d38SCarson Labrado             Response dummyRes;
77443e14d38SCarson Labrado             dummyRes.result(boost::beast::http::status::too_many_requests);
77543e14d38SCarson Labrado             resHandler(dummyRes);
776f52c03c1SCarson Labrado         }
777f52c03c1SCarson Labrado     }
778f52c03c1SCarson Labrado 
7793d36e3a5SEd Tanous     // Callback to be called once the request has been sent
7803d36e3a5SEd Tanous     static void afterSendData(const std::weak_ptr<ConnectionPool>& weakSelf,
7813d36e3a5SEd Tanous                               const std::function<void(Response&)>& resHandler,
7823d36e3a5SEd Tanous                               bool keepAlive, uint32_t connId, Response& res)
7833d36e3a5SEd Tanous     {
7843d36e3a5SEd Tanous         // Allow provided callback to perform additional processing of the
7853d36e3a5SEd Tanous         // request
7863d36e3a5SEd Tanous         resHandler(res);
7873d36e3a5SEd Tanous 
7883d36e3a5SEd Tanous         // If requests remain in the queue then we want to reuse this
7893d36e3a5SEd Tanous         // connection to send the next request
7903d36e3a5SEd Tanous         std::shared_ptr<ConnectionPool> self = weakSelf.lock();
7913d36e3a5SEd Tanous         if (!self)
7923d36e3a5SEd Tanous         {
7933d36e3a5SEd Tanous             BMCWEB_LOG_CRITICAL << self << " Failed to capture connection";
7943d36e3a5SEd Tanous             return;
7953d36e3a5SEd Tanous         }
7963d36e3a5SEd Tanous 
7973d36e3a5SEd Tanous         self->sendNext(keepAlive, connId);
7983d36e3a5SEd Tanous     }
7993d36e3a5SEd Tanous 
800f52c03c1SCarson Labrado     std::shared_ptr<ConnectionInfo>& addConnection()
801f52c03c1SCarson Labrado     {
802f52c03c1SCarson Labrado         unsigned int newId = static_cast<unsigned int>(connections.size());
803f52c03c1SCarson Labrado 
804e38778a5SAppaRao Puli         auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>(
805*d14a48ffSCarson Labrado             ioc, id, connPolicy, destIP, destPort, useSSL, newId));
806f52c03c1SCarson Labrado 
807f52c03c1SCarson Labrado         BMCWEB_LOG_DEBUG << "Added connection "
808f52c03c1SCarson Labrado                          << std::to_string(connections.size() - 1)
809f52c03c1SCarson Labrado                          << " to pool " << destIP << ":"
810f52c03c1SCarson Labrado                          << std::to_string(destPort);
811f52c03c1SCarson Labrado 
812f52c03c1SCarson Labrado         return ret;
813f52c03c1SCarson Labrado     }
814f52c03c1SCarson Labrado 
815f52c03c1SCarson Labrado   public:
816*d14a48ffSCarson Labrado     explicit ConnectionPool(
817*d14a48ffSCarson Labrado         boost::asio::io_context& iocIn, const std::string& idIn,
818*d14a48ffSCarson Labrado         const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
819*d14a48ffSCarson Labrado         const std::string& destIPIn, uint16_t destPortIn, bool useSSLIn) :
8208a592810SEd Tanous         ioc(iocIn),
821*d14a48ffSCarson Labrado         id(idIn), connPolicy(connPolicyIn), destIP(destIPIn),
822*d14a48ffSCarson Labrado         destPort(destPortIn), useSSL(useSSLIn)
823f52c03c1SCarson Labrado     {
824f52c03c1SCarson Labrado         BMCWEB_LOG_DEBUG << "Initializing connection pool for " << destIP << ":"
825f52c03c1SCarson Labrado                          << std::to_string(destPort);
826f52c03c1SCarson Labrado 
827f52c03c1SCarson Labrado         // Initialize the pool with a single connection
828f52c03c1SCarson Labrado         addConnection();
829fe44eb0bSAyushi Smriti     }
830bd030d0aSAppaRao Puli };
831bd030d0aSAppaRao Puli 
832f52c03c1SCarson Labrado class HttpClient
833f52c03c1SCarson Labrado {
834f52c03c1SCarson Labrado   private:
835f52c03c1SCarson Labrado     std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
836f52c03c1SCarson Labrado         connectionPools;
837f52c03c1SCarson Labrado     boost::asio::io_context& ioc =
838f52c03c1SCarson Labrado         crow::connections::systemBus->get_io_context();
839*d14a48ffSCarson Labrado     std::shared_ptr<ConnectionPolicy> connPolicy;
840f52c03c1SCarson Labrado 
841039a47e3SCarson Labrado     // Used as a dummy callback by sendData() in order to call
842039a47e3SCarson Labrado     // sendDataWithCallback()
84302cad96eSEd Tanous     static void genericResHandler(const Response& res)
844039a47e3SCarson Labrado     {
845039a47e3SCarson Labrado         BMCWEB_LOG_DEBUG << "Response handled with return code: "
846039a47e3SCarson Labrado                          << std::to_string(res.resultInt());
8474ee8e211SEd Tanous     }
848039a47e3SCarson Labrado 
849f52c03c1SCarson Labrado   public:
850*d14a48ffSCarson Labrado     HttpClient() = delete;
851*d14a48ffSCarson Labrado     explicit HttpClient(const std::shared_ptr<ConnectionPolicy>& connPolicyIn) :
852*d14a48ffSCarson Labrado         connPolicy(connPolicyIn)
853*d14a48ffSCarson Labrado     {}
854f52c03c1SCarson Labrado     HttpClient(const HttpClient&) = delete;
855f52c03c1SCarson Labrado     HttpClient& operator=(const HttpClient&) = delete;
856f52c03c1SCarson Labrado     HttpClient(HttpClient&&) = delete;
857f52c03c1SCarson Labrado     HttpClient& operator=(HttpClient&&) = delete;
858f52c03c1SCarson Labrado     ~HttpClient() = default;
859f52c03c1SCarson Labrado 
860039a47e3SCarson Labrado     // Send a request to destIP:destPort where additional processing of the
861039a47e3SCarson Labrado     // result is not required
862*d14a48ffSCarson Labrado     void sendData(std::string& data, const std::string& destIP,
863*d14a48ffSCarson Labrado                   uint16_t destPort, const std::string& destUri, bool useSSL,
864f52c03c1SCarson Labrado                   const boost::beast::http::fields& httpHeader,
865*d14a48ffSCarson Labrado                   const boost::beast::http::verb verb)
866f52c03c1SCarson Labrado     {
867e38778a5SAppaRao Puli         const std::function<void(Response&)> cb = genericResHandler;
868*d14a48ffSCarson Labrado         sendDataWithCallback(data, destIP, destPort, destUri, useSSL,
869*d14a48ffSCarson Labrado                              httpHeader, verb, cb);
870039a47e3SCarson Labrado     }
871039a47e3SCarson Labrado 
872039a47e3SCarson Labrado     // Send request to destIP:destPort and use the provided callback to
873039a47e3SCarson Labrado     // handle the response
874*d14a48ffSCarson Labrado     void sendDataWithCallback(std::string& data, const std::string& destIP,
875*d14a48ffSCarson Labrado                               uint16_t destPort, const std::string& destUri,
876*d14a48ffSCarson Labrado                               bool useSSL,
877039a47e3SCarson Labrado                               const boost::beast::http::fields& httpHeader,
878244256ccSCarson Labrado                               const boost::beast::http::verb verb,
8796b3db60dSEd Tanous                               const std::function<void(Response&)>& resHandler)
880039a47e3SCarson Labrado     {
881e38778a5SAppaRao Puli         std::string clientKey = useSSL ? "https" : "http";
882e38778a5SAppaRao Puli         clientKey += destIP;
883e38778a5SAppaRao Puli         clientKey += ":";
884e38778a5SAppaRao Puli         clientKey += std::to_string(destPort);
885*d14a48ffSCarson Labrado         auto pool = connectionPools.try_emplace(clientKey);
886*d14a48ffSCarson Labrado         if (pool.first->second == nullptr)
887f52c03c1SCarson Labrado         {
888*d14a48ffSCarson Labrado             pool.first->second = std::make_shared<ConnectionPool>(
889*d14a48ffSCarson Labrado                 ioc, clientKey, connPolicy, destIP, destPort, useSSL);
890f52c03c1SCarson Labrado         }
891f52c03c1SCarson Labrado         // Send the data using either the existing connection pool or the newly
892f52c03c1SCarson Labrado         // created connection pool
893*d14a48ffSCarson Labrado         pool.first->second->sendData(data, destUri, httpHeader, verb,
894e38778a5SAppaRao Puli                                      resHandler);
895f52c03c1SCarson Labrado     }
896f52c03c1SCarson Labrado };
897bd030d0aSAppaRao Puli } // namespace crow
898