xref: /openbmc/bmcweb/http/http_client.hpp (revision f8ca6d79)
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;
149*f8ca6d79SEd Tanous 
150*f8ca6d79SEd Tanous #ifdef BMCWEB_DBUS_DNS_RESOLVER
151*f8ca6d79SEd Tanous     using Resolver = crow::async_resolve::Resolver;
152*f8ca6d79SEd Tanous #else
153*f8ca6d79SEd Tanous     using Resolver = boost::asio::ip::tcp::resolver;
154*f8ca6d79SEd Tanous #endif
155*f8ca6d79SEd Tanous     Resolver resolver;
156*f8ca6d79SEd 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 
172*f8ca6d79SEd 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 
177*f8ca6d79SEd Tanous     void afterResolve(const std::shared_ptr<ConnectionInfo>& /*self*/,
178*f8ca6d79SEd Tanous                       const boost::system::error_code& ec,
179*f8ca6d79SEd Tanous                       const Resolver::results_type& endpointList)
1803d36e3a5SEd Tanous     {
18126f6976fSEd Tanous         if (ec || (endpointList.empty()))
18229a82b08SSunitha Harish         {
18329a82b08SSunitha Harish             BMCWEB_LOG_ERROR << "Resolve failed: " << ec.message();
1843d36e3a5SEd Tanous             state = ConnState::resolveFailed;
1853d36e3a5SEd Tanous             waitAndRetry();
18629a82b08SSunitha Harish             return;
18729a82b08SSunitha Harish         }
1883d36e3a5SEd Tanous         BMCWEB_LOG_DEBUG << "Resolved " << host << ":" << std::to_string(port)
1893d36e3a5SEd Tanous                          << ", id: " << std::to_string(connId);
1902a5689a7SAppaRao Puli         state = ConnState::connectInProgress;
1912a5689a7SAppaRao Puli 
192f52c03c1SCarson Labrado         BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":"
193f52c03c1SCarson Labrado                          << std::to_string(port)
194f52c03c1SCarson Labrado                          << ", id: " << std::to_string(connId);
195b00dcc27SEd Tanous 
1960d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
1970d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
1980d5f5cf4SEd Tanous 
1990d5f5cf4SEd Tanous         boost::asio::async_connect(
2000d5f5cf4SEd Tanous             conn, endpointList,
201e38778a5SAppaRao Puli             std::bind_front(&ConnectionInfo::afterConnect, this,
202e38778a5SAppaRao Puli                             shared_from_this()));
203e38778a5SAppaRao Puli     }
204e38778a5SAppaRao Puli 
205e38778a5SAppaRao Puli     void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/,
20681c4e330SEd Tanous                       const boost::beast::error_code& ec,
207e38778a5SAppaRao Puli                       const boost::asio::ip::tcp::endpoint& endpoint)
208e38778a5SAppaRao Puli     {
209513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
210513d1ffcSCarson Labrado         // this branch
211513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
212513d1ffcSCarson Labrado         {
213513d1ffcSCarson Labrado             return;
214513d1ffcSCarson Labrado         }
215513d1ffcSCarson Labrado 
2160d5f5cf4SEd Tanous         timer.cancel();
2172a5689a7SAppaRao Puli         if (ec)
2182a5689a7SAppaRao Puli         {
219002d39b4SEd Tanous             BMCWEB_LOG_ERROR << "Connect " << endpoint.address().to_string()
220002d39b4SEd Tanous                              << ":" << std::to_string(endpoint.port())
221e38778a5SAppaRao Puli                              << ", id: " << std::to_string(connId)
2222a5689a7SAppaRao Puli                              << " failed: " << ec.message();
223e38778a5SAppaRao Puli             state = ConnState::connectFailed;
224e38778a5SAppaRao Puli             waitAndRetry();
2252a5689a7SAppaRao Puli             return;
2262a5689a7SAppaRao Puli         }
227e38778a5SAppaRao Puli         BMCWEB_LOG_DEBUG << "Connected to: " << endpoint.address().to_string()
228e38778a5SAppaRao Puli                          << ":" << std::to_string(endpoint.port())
229e38778a5SAppaRao Puli                          << ", id: " << std::to_string(connId);
230e38778a5SAppaRao Puli         if (sslConn)
231e38778a5SAppaRao Puli         {
2320d5f5cf4SEd Tanous             doSslHandshake();
233e38778a5SAppaRao Puli             return;
234e38778a5SAppaRao Puli         }
235e38778a5SAppaRao Puli         state = ConnState::connected;
236e38778a5SAppaRao Puli         sendMessage();
237e38778a5SAppaRao Puli     }
238e38778a5SAppaRao Puli 
2390d5f5cf4SEd Tanous     void doSslHandshake()
240e38778a5SAppaRao Puli     {
241e38778a5SAppaRao Puli         if (!sslConn)
242e38778a5SAppaRao Puli         {
243e38778a5SAppaRao Puli             return;
244e38778a5SAppaRao Puli         }
245e38778a5SAppaRao Puli         state = ConnState::handshakeInProgress;
2460d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
2470d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
248e38778a5SAppaRao Puli         sslConn->async_handshake(
249e38778a5SAppaRao Puli             boost::asio::ssl::stream_base::client,
250e38778a5SAppaRao Puli             std::bind_front(&ConnectionInfo::afterSslHandshake, this,
251e38778a5SAppaRao Puli                             shared_from_this()));
252e38778a5SAppaRao Puli     }
253e38778a5SAppaRao Puli 
254e38778a5SAppaRao Puli     void afterSslHandshake(const std::shared_ptr<ConnectionInfo>& /*self*/,
25581c4e330SEd Tanous                            const boost::beast::error_code& ec)
256e38778a5SAppaRao Puli     {
257513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
258513d1ffcSCarson Labrado         // this branch
259513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
260513d1ffcSCarson Labrado         {
261513d1ffcSCarson Labrado             return;
262513d1ffcSCarson Labrado         }
263513d1ffcSCarson Labrado 
2640d5f5cf4SEd Tanous         timer.cancel();
265e38778a5SAppaRao Puli         if (ec)
266e38778a5SAppaRao Puli         {
267e38778a5SAppaRao Puli             BMCWEB_LOG_ERROR << "SSL Handshake failed -"
268e38778a5SAppaRao Puli                              << " id: " << std::to_string(connId)
269e38778a5SAppaRao Puli                              << " error: " << ec.message();
270e38778a5SAppaRao Puli             state = ConnState::handshakeFailed;
271e38778a5SAppaRao Puli             waitAndRetry();
272e38778a5SAppaRao Puli             return;
273e38778a5SAppaRao Puli         }
274e38778a5SAppaRao Puli         BMCWEB_LOG_DEBUG << "SSL Handshake successful -"
275e38778a5SAppaRao Puli                          << " id: " << std::to_string(connId);
276e38778a5SAppaRao Puli         state = ConnState::connected;
277e38778a5SAppaRao Puli         sendMessage();
2782a5689a7SAppaRao Puli     }
2792a5689a7SAppaRao Puli 
280f52c03c1SCarson Labrado     void sendMessage()
2812a5689a7SAppaRao Puli     {
2822a5689a7SAppaRao Puli         state = ConnState::sendInProgress;
2832a5689a7SAppaRao Puli 
284bd030d0aSAppaRao Puli         // Set a timeout on the operation
2850d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
2860d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
287bd030d0aSAppaRao Puli 
288bd030d0aSAppaRao Puli         // Send the HTTP request to the remote host
289e38778a5SAppaRao Puli         if (sslConn)
290e38778a5SAppaRao Puli         {
291e38778a5SAppaRao Puli             boost::beast::http::async_write(
292e38778a5SAppaRao Puli                 *sslConn, req,
293e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterWrite, this,
294e38778a5SAppaRao Puli                                 shared_from_this()));
295e38778a5SAppaRao Puli         }
296e38778a5SAppaRao Puli         else
297e38778a5SAppaRao Puli         {
298bd030d0aSAppaRao Puli             boost::beast::http::async_write(
299bd030d0aSAppaRao Puli                 conn, req,
300e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterWrite, this,
301e38778a5SAppaRao Puli                                 shared_from_this()));
302e38778a5SAppaRao Puli         }
303e38778a5SAppaRao Puli     }
304e38778a5SAppaRao Puli 
305e38778a5SAppaRao Puli     void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/,
306e38778a5SAppaRao Puli                     const boost::beast::error_code& ec, size_t bytesTransferred)
307e38778a5SAppaRao Puli     {
308513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
309513d1ffcSCarson Labrado         // this branch
310513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
311513d1ffcSCarson Labrado         {
312513d1ffcSCarson Labrado             return;
313513d1ffcSCarson Labrado         }
314513d1ffcSCarson Labrado 
3150d5f5cf4SEd Tanous         timer.cancel();
316bd030d0aSAppaRao Puli         if (ec)
317bd030d0aSAppaRao Puli         {
318002d39b4SEd Tanous             BMCWEB_LOG_ERROR << "sendMessage() failed: " << ec.message();
319e38778a5SAppaRao Puli             state = ConnState::sendFailed;
320e38778a5SAppaRao Puli             waitAndRetry();
321bd030d0aSAppaRao Puli             return;
322bd030d0aSAppaRao Puli         }
323bd030d0aSAppaRao Puli         BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
324bd030d0aSAppaRao Puli                          << bytesTransferred;
325bd030d0aSAppaRao Puli 
326e38778a5SAppaRao Puli         recvMessage();
327bd030d0aSAppaRao Puli     }
328bd030d0aSAppaRao Puli 
329bd030d0aSAppaRao Puli     void recvMessage()
330bd030d0aSAppaRao Puli     {
3316eaa1d2fSSunitha Harish         state = ConnState::recvInProgress;
3326eaa1d2fSSunitha Harish 
3336eaa1d2fSSunitha Harish         parser.emplace(std::piecewise_construct, std::make_tuple());
334d14a48ffSCarson Labrado 
335d14a48ffSCarson Labrado         parser->body_limit(connPolicy->requestByteLimit);
3366eaa1d2fSSunitha Harish 
3370d5f5cf4SEd Tanous         timer.expires_after(std::chrono::seconds(30));
3380d5f5cf4SEd Tanous         timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
3390d5f5cf4SEd Tanous 
340bd030d0aSAppaRao Puli         // Receive the HTTP response
341e38778a5SAppaRao Puli         if (sslConn)
342e38778a5SAppaRao Puli         {
343e38778a5SAppaRao Puli             boost::beast::http::async_read(
344e38778a5SAppaRao Puli                 *sslConn, buffer, *parser,
345e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterRead, this,
346e38778a5SAppaRao Puli                                 shared_from_this()));
347e38778a5SAppaRao Puli         }
348e38778a5SAppaRao Puli         else
349e38778a5SAppaRao Puli         {
350bd030d0aSAppaRao Puli             boost::beast::http::async_read(
3516eaa1d2fSSunitha Harish                 conn, buffer, *parser,
352e38778a5SAppaRao Puli                 std::bind_front(&ConnectionInfo::afterRead, this,
353e38778a5SAppaRao Puli                                 shared_from_this()));
354e38778a5SAppaRao Puli         }
355e38778a5SAppaRao Puli     }
356e38778a5SAppaRao Puli 
357e38778a5SAppaRao Puli     void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/,
358e38778a5SAppaRao Puli                    const boost::beast::error_code& ec,
359e38778a5SAppaRao Puli                    const std::size_t& bytesTransferred)
360e38778a5SAppaRao Puli     {
361513d1ffcSCarson Labrado         // The operation already timed out.  We don't want do continue down
362513d1ffcSCarson Labrado         // this branch
363513d1ffcSCarson Labrado         if (ec && ec == boost::asio::error::operation_aborted)
364513d1ffcSCarson Labrado         {
365513d1ffcSCarson Labrado             return;
366513d1ffcSCarson Labrado         }
367513d1ffcSCarson Labrado 
3680d5f5cf4SEd Tanous         timer.cancel();
369e38778a5SAppaRao Puli         if (ec && ec != boost::asio::ssl::error::stream_truncated)
370bd030d0aSAppaRao Puli         {
371002d39b4SEd Tanous             BMCWEB_LOG_ERROR << "recvMessage() failed: " << ec.message();
372e38778a5SAppaRao Puli             state = ConnState::recvFailed;
373e38778a5SAppaRao Puli             waitAndRetry();
374bd030d0aSAppaRao Puli             return;
375bd030d0aSAppaRao Puli         }
376bd030d0aSAppaRao Puli         BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
377bd030d0aSAppaRao Puli                          << bytesTransferred;
378e38778a5SAppaRao Puli         BMCWEB_LOG_DEBUG << "recvMessage() data: " << parser->get().body();
379bd030d0aSAppaRao Puli 
380e38778a5SAppaRao Puli         unsigned int respCode = parser->get().result_int();
381e38778a5SAppaRao Puli         BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: " << respCode;
3826eaa1d2fSSunitha Harish 
383a7a80296SCarson Labrado         // Make sure the received response code is valid as defined by
384a7a80296SCarson Labrado         // the associated retry policy
385d14a48ffSCarson Labrado         if (connPolicy->invalidResp(respCode))
3866eaa1d2fSSunitha Harish         {
3876eaa1d2fSSunitha Harish             // The listener failed to receive the Sent-Event
388002d39b4SEd Tanous             BMCWEB_LOG_ERROR << "recvMessage() Listener Failed to "
3897adb85acSSunitha Harish                                 "receive Sent-Event. Header Response Code: "
3907adb85acSSunitha Harish                              << respCode;
391e38778a5SAppaRao Puli             state = ConnState::recvFailed;
392e38778a5SAppaRao Puli             waitAndRetry();
3936eaa1d2fSSunitha Harish             return;
3946eaa1d2fSSunitha Harish         }
395bd030d0aSAppaRao Puli 
396f52c03c1SCarson Labrado         // Send is successful
397f52c03c1SCarson Labrado         // Reset the counter just in case this was after retrying
398e38778a5SAppaRao Puli         retryCount = 0;
3996eaa1d2fSSunitha Harish 
4006eaa1d2fSSunitha Harish         // Keep the connection alive if server supports it
4016eaa1d2fSSunitha Harish         // Else close the connection
4026eaa1d2fSSunitha Harish         BMCWEB_LOG_DEBUG << "recvMessage() keepalive : "
403e38778a5SAppaRao Puli                          << parser->keep_alive();
4046eaa1d2fSSunitha Harish 
405039a47e3SCarson Labrado         // Copy the response into a Response object so that it can be
406039a47e3SCarson Labrado         // processed by the callback function.
407e38778a5SAppaRao Puli         res.stringResponse = parser->release();
408e38778a5SAppaRao Puli         callback(parser->keep_alive(), connId, res);
409513d1ffcSCarson Labrado         res.clear();
410bd030d0aSAppaRao Puli     }
411bd030d0aSAppaRao Puli 
4120d5f5cf4SEd Tanous     static void onTimeout(const std::weak_ptr<ConnectionInfo>& weakSelf,
4135e7e2dc5SEd Tanous                           const boost::system::error_code& ec)
4140d5f5cf4SEd Tanous     {
4150d5f5cf4SEd Tanous         if (ec == boost::asio::error::operation_aborted)
4160d5f5cf4SEd Tanous         {
4170d5f5cf4SEd Tanous             BMCWEB_LOG_DEBUG
418513d1ffcSCarson Labrado                 << "async_wait failed since the operation is aborted";
4190d5f5cf4SEd Tanous             return;
4200d5f5cf4SEd Tanous         }
4210d5f5cf4SEd Tanous         if (ec)
4220d5f5cf4SEd Tanous         {
4230d5f5cf4SEd Tanous             BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message();
4240d5f5cf4SEd Tanous             // If the timer fails, we need to close the socket anyway, same as
4250d5f5cf4SEd Tanous             // if it expired.
4260d5f5cf4SEd Tanous         }
4270d5f5cf4SEd Tanous         std::shared_ptr<ConnectionInfo> self = weakSelf.lock();
4280d5f5cf4SEd Tanous         if (self == nullptr)
4290d5f5cf4SEd Tanous         {
4300d5f5cf4SEd Tanous             return;
4310d5f5cf4SEd Tanous         }
4320d5f5cf4SEd Tanous         self->waitAndRetry();
4330d5f5cf4SEd Tanous     }
4340d5f5cf4SEd Tanous 
4356eaa1d2fSSunitha Harish     void waitAndRetry()
436bd030d0aSAppaRao Puli     {
437d14a48ffSCarson Labrado         if ((retryCount >= connPolicy->maxRetryAttempts) ||
438e38778a5SAppaRao Puli             (state == ConnState::sslInitFailed))
4392a5689a7SAppaRao Puli         {
4406eaa1d2fSSunitha Harish             BMCWEB_LOG_ERROR << "Maximum number of retries reached.";
441f52c03c1SCarson Labrado             BMCWEB_LOG_DEBUG << "Retry policy: "
442d14a48ffSCarson Labrado                              << connPolicy->retryPolicyAction;
443039a47e3SCarson Labrado 
444d14a48ffSCarson Labrado             if (connPolicy->retryPolicyAction == "TerminateAfterRetries")
445fe44eb0bSAyushi Smriti             {
446fe44eb0bSAyushi Smriti                 // TODO: delete subscription
447fe44eb0bSAyushi Smriti                 state = ConnState::terminated;
448fe44eb0bSAyushi Smriti             }
449d14a48ffSCarson Labrado             if (connPolicy->retryPolicyAction == "SuspendRetries")
450fe44eb0bSAyushi Smriti             {
4512a5689a7SAppaRao Puli                 state = ConnState::suspended;
4522a5689a7SAppaRao Puli             }
453513d1ffcSCarson Labrado 
454513d1ffcSCarson Labrado             // We want to return a 502 to indicate there was an error with
455513d1ffcSCarson Labrado             // the external server
456513d1ffcSCarson Labrado             res.result(boost::beast::http::status::bad_gateway);
457513d1ffcSCarson Labrado             callback(false, connId, res);
458513d1ffcSCarson Labrado             res.clear();
459513d1ffcSCarson Labrado 
4606eaa1d2fSSunitha Harish             // Reset the retrycount to zero so that client can try connecting
4616eaa1d2fSSunitha Harish             // again if needed
462fe44eb0bSAyushi Smriti             retryCount = 0;
4632a5689a7SAppaRao Puli             return;
4642a5689a7SAppaRao Puli         }
4652a5689a7SAppaRao Puli 
4662a5689a7SAppaRao Puli         retryCount++;
467fe44eb0bSAyushi Smriti 
468f52c03c1SCarson Labrado         BMCWEB_LOG_DEBUG << "Attempt retry after "
469f52c03c1SCarson Labrado                          << std::to_string(
470d14a48ffSCarson Labrado                                 connPolicy->retryIntervalSecs.count())
471fe44eb0bSAyushi Smriti                          << " seconds. RetryCount = " << retryCount;
472d14a48ffSCarson Labrado         timer.expires_after(connPolicy->retryIntervalSecs);
4733d36e3a5SEd Tanous         timer.async_wait(std::bind_front(&ConnectionInfo::onTimerDone, this,
4743d36e3a5SEd Tanous                                          shared_from_this()));
4753d36e3a5SEd Tanous     }
4763d36e3a5SEd Tanous 
4773d36e3a5SEd Tanous     void onTimerDone(const std::shared_ptr<ConnectionInfo>& /*self*/,
4783d36e3a5SEd Tanous                      const boost::system::error_code& ec)
4793d36e3a5SEd Tanous     {
4806eaa1d2fSSunitha Harish         if (ec == boost::asio::error::operation_aborted)
4816eaa1d2fSSunitha Harish         {
4826eaa1d2fSSunitha Harish             BMCWEB_LOG_DEBUG
4836eaa1d2fSSunitha Harish                 << "async_wait failed since the operation is aborted"
4846eaa1d2fSSunitha Harish                 << ec.message();
4856eaa1d2fSSunitha Harish         }
4866eaa1d2fSSunitha Harish         else if (ec)
4876eaa1d2fSSunitha Harish         {
4886eaa1d2fSSunitha Harish             BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message();
4896eaa1d2fSSunitha Harish             // Ignore the error and continue the retry loop to attempt
4906eaa1d2fSSunitha Harish             // sending the event as per the retry policy
4916eaa1d2fSSunitha Harish         }
4926eaa1d2fSSunitha Harish 
493f52c03c1SCarson Labrado         // Let's close the connection and restart from resolve.
4943d36e3a5SEd Tanous         doClose(true);
4952a5689a7SAppaRao Puli     }
4962a5689a7SAppaRao Puli 
497e38778a5SAppaRao Puli     void shutdownConn(bool retry)
498fe44eb0bSAyushi Smriti     {
499f52c03c1SCarson Labrado         boost::beast::error_code ec;
5000d5f5cf4SEd Tanous         conn.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
501f52c03c1SCarson Labrado         conn.close();
502f52c03c1SCarson Labrado 
503f52c03c1SCarson Labrado         // not_connected happens sometimes so don't bother reporting it.
504f52c03c1SCarson Labrado         if (ec && ec != boost::beast::errc::not_connected)
5052a5689a7SAppaRao Puli         {
506f52c03c1SCarson Labrado             BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
507f52c03c1SCarson Labrado                              << ", id: " << std::to_string(connId)
508f52c03c1SCarson Labrado                              << " shutdown failed: " << ec.message();
5096eaa1d2fSSunitha Harish         }
5105cab68f3SCarson Labrado         else
5115cab68f3SCarson Labrado         {
512f52c03c1SCarson Labrado             BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
513f52c03c1SCarson Labrado                              << ", id: " << std::to_string(connId)
514f52c03c1SCarson Labrado                              << " closed gracefully";
5155cab68f3SCarson Labrado         }
516ca723762SEd Tanous 
517e38778a5SAppaRao Puli         if (retry)
51892a74e56SAppaRao Puli         {
519f52c03c1SCarson Labrado             // Now let's try to resend the data
520f52c03c1SCarson Labrado             state = ConnState::retry;
5210d5f5cf4SEd Tanous             doResolve();
522e38778a5SAppaRao Puli         }
523e38778a5SAppaRao Puli         else
524e38778a5SAppaRao Puli         {
525e38778a5SAppaRao Puli             state = ConnState::closed;
526e38778a5SAppaRao Puli         }
527e38778a5SAppaRao Puli     }
528e38778a5SAppaRao Puli 
529e38778a5SAppaRao Puli     void doClose(bool retry = false)
530e38778a5SAppaRao Puli     {
531e38778a5SAppaRao Puli         if (!sslConn)
532e38778a5SAppaRao Puli         {
533e38778a5SAppaRao Puli             shutdownConn(retry);
534e38778a5SAppaRao Puli             return;
535e38778a5SAppaRao Puli         }
536e38778a5SAppaRao Puli 
537e38778a5SAppaRao Puli         sslConn->async_shutdown(
538e38778a5SAppaRao Puli             std::bind_front(&ConnectionInfo::afterSslShutdown, this,
539e38778a5SAppaRao Puli                             shared_from_this(), retry));
540e38778a5SAppaRao Puli     }
541e38778a5SAppaRao Puli 
542e38778a5SAppaRao Puli     void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/,
543e38778a5SAppaRao Puli                           bool retry, const boost::system::error_code& ec)
544e38778a5SAppaRao Puli     {
545e38778a5SAppaRao Puli         if (ec)
546e38778a5SAppaRao Puli         {
547e38778a5SAppaRao Puli             BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
548e38778a5SAppaRao Puli                              << ", id: " << std::to_string(connId)
549e38778a5SAppaRao Puli                              << " shutdown failed: " << ec.message();
550e38778a5SAppaRao Puli         }
551e38778a5SAppaRao Puli         else
552e38778a5SAppaRao Puli         {
553e38778a5SAppaRao Puli             BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
554e38778a5SAppaRao Puli                              << ", id: " << std::to_string(connId)
555e38778a5SAppaRao Puli                              << " closed gracefully";
556e38778a5SAppaRao Puli         }
557e38778a5SAppaRao Puli         shutdownConn(retry);
558e38778a5SAppaRao Puli     }
559e38778a5SAppaRao Puli 
560e38778a5SAppaRao Puli     void setCipherSuiteTLSext()
561e38778a5SAppaRao Puli     {
562e38778a5SAppaRao Puli         if (!sslConn)
563e38778a5SAppaRao Puli         {
564e38778a5SAppaRao Puli             return;
565e38778a5SAppaRao Puli         }
566e38778a5SAppaRao Puli         // NOTE: The SSL_set_tlsext_host_name is defined in tlsv1.h header
567e38778a5SAppaRao Puli         // file but its having old style casting (name is cast to void*).
568e38778a5SAppaRao Puli         // Since bmcweb compiler treats all old-style-cast as error, its
569e38778a5SAppaRao Puli         // causing the build failure. So replaced the same macro inline and
570e38778a5SAppaRao Puli         // did corrected the code by doing static_cast to viod*. This has to
571e38778a5SAppaRao Puli         // be fixed in openssl library in long run. Set SNI Hostname (many
572e38778a5SAppaRao Puli         // hosts need this to handshake successfully)
573e38778a5SAppaRao Puli         if (SSL_ctrl(sslConn->native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME,
574e38778a5SAppaRao Puli                      TLSEXT_NAMETYPE_host_name,
575e38778a5SAppaRao Puli                      static_cast<void*>(&host.front())) == 0)
576e38778a5SAppaRao Puli 
577e38778a5SAppaRao Puli         {
578e38778a5SAppaRao Puli             boost::beast::error_code ec{static_cast<int>(::ERR_get_error()),
579e38778a5SAppaRao Puli                                         boost::asio::error::get_ssl_category()};
580e38778a5SAppaRao Puli 
581e38778a5SAppaRao Puli             BMCWEB_LOG_ERROR << "SSL_set_tlsext_host_name " << host << ":"
582e38778a5SAppaRao Puli                              << port << ", id: " << std::to_string(connId)
583e38778a5SAppaRao Puli                              << " failed: " << ec.message();
584e38778a5SAppaRao Puli             // Set state as sslInit failed so that we close the connection
585e38778a5SAppaRao Puli             // and take appropriate action as per retry configuration.
586e38778a5SAppaRao Puli             state = ConnState::sslInitFailed;
587e38778a5SAppaRao Puli             waitAndRetry();
588e38778a5SAppaRao Puli             return;
589e38778a5SAppaRao Puli         }
590bd030d0aSAppaRao Puli     }
591bd030d0aSAppaRao Puli 
592bd030d0aSAppaRao Puli   public:
593d14a48ffSCarson Labrado     explicit ConnectionInfo(
594d14a48ffSCarson Labrado         boost::asio::io_context& iocIn, const std::string& idIn,
595d14a48ffSCarson Labrado         const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
596d14a48ffSCarson Labrado         const std::string& destIPIn, uint16_t destPortIn, bool useSSL,
597d14a48ffSCarson Labrado         unsigned int connIdIn) :
5988a592810SEd Tanous         subId(idIn),
599d14a48ffSCarson Labrado         connPolicy(connPolicyIn), host(destIPIn), port(destPortIn),
600*f8ca6d79SEd Tanous         connId(connIdIn), resolver(iocIn), conn(iocIn), timer(iocIn)
601e38778a5SAppaRao Puli     {
602e38778a5SAppaRao Puli         if (useSSL)
603e38778a5SAppaRao Puli         {
604e38778a5SAppaRao Puli             std::optional<boost::asio::ssl::context> sslCtx =
605e38778a5SAppaRao Puli                 ensuressl::getSSLClientContext();
606e38778a5SAppaRao Puli 
607e38778a5SAppaRao Puli             if (!sslCtx)
608e38778a5SAppaRao Puli             {
609e38778a5SAppaRao Puli                 BMCWEB_LOG_ERROR << "prepareSSLContext failed - " << host << ":"
610e38778a5SAppaRao Puli                                  << port << ", id: " << std::to_string(connId);
611e38778a5SAppaRao Puli                 // Don't retry if failure occurs while preparing SSL context
612e38778a5SAppaRao Puli                 // such as certificate is invalid or set cipher failure or set
613e38778a5SAppaRao Puli                 // host name failure etc... Setting conn state to sslInitFailed
614e38778a5SAppaRao Puli                 // and connection state will be transitioned to next state
615e38778a5SAppaRao Puli                 // depending on retry policy set by subscription.
616e38778a5SAppaRao Puli                 state = ConnState::sslInitFailed;
617e38778a5SAppaRao Puli                 waitAndRetry();
618e38778a5SAppaRao Puli                 return;
619e38778a5SAppaRao Puli             }
620e38778a5SAppaRao Puli             sslConn.emplace(conn, *sslCtx);
621e38778a5SAppaRao Puli             setCipherSuiteTLSext();
622e38778a5SAppaRao Puli         }
623e38778a5SAppaRao Puli     }
624f52c03c1SCarson Labrado };
625bd030d0aSAppaRao Puli 
626f52c03c1SCarson Labrado class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
627bd030d0aSAppaRao Puli {
628f52c03c1SCarson Labrado   private:
629f52c03c1SCarson Labrado     boost::asio::io_context& ioc;
630e38778a5SAppaRao Puli     std::string id;
631d14a48ffSCarson Labrado     std::shared_ptr<ConnectionPolicy> connPolicy;
632e38778a5SAppaRao Puli     std::string destIP;
633e38778a5SAppaRao Puli     uint16_t destPort;
634e38778a5SAppaRao Puli     bool useSSL;
635f52c03c1SCarson Labrado     std::vector<std::shared_ptr<ConnectionInfo>> connections;
636f52c03c1SCarson Labrado     boost::container::devector<PendingRequest> requestQueue;
637f52c03c1SCarson Labrado 
638f52c03c1SCarson Labrado     friend class HttpClient;
639f52c03c1SCarson Labrado 
640244256ccSCarson Labrado     // Configure a connections's request, callback, and retry info in
641244256ccSCarson Labrado     // preparation to begin sending the request
642f52c03c1SCarson Labrado     void setConnProps(ConnectionInfo& conn)
643bd030d0aSAppaRao Puli     {
644f52c03c1SCarson Labrado         if (requestQueue.empty())
645f52c03c1SCarson Labrado         {
646f52c03c1SCarson Labrado             BMCWEB_LOG_ERROR
647f52c03c1SCarson Labrado                 << "setConnProps() should not have been called when requestQueue is empty";
648bd030d0aSAppaRao Puli             return;
649bd030d0aSAppaRao Puli         }
650bd030d0aSAppaRao Puli 
651244256ccSCarson Labrado         auto nextReq = requestQueue.front();
652244256ccSCarson Labrado         conn.req = std::move(nextReq.req);
653244256ccSCarson Labrado         conn.callback = std::move(nextReq.callback);
654f52c03c1SCarson Labrado 
655f52c03c1SCarson Labrado         BMCWEB_LOG_DEBUG << "Setting properties for connection " << conn.host
656f52c03c1SCarson Labrado                          << ":" << std::to_string(conn.port)
657a7a80296SCarson Labrado                          << ", id: " << std::to_string(conn.connId);
658f52c03c1SCarson Labrado 
659f52c03c1SCarson Labrado         // We can remove the request from the queue at this point
660f52c03c1SCarson Labrado         requestQueue.pop_front();
661f52c03c1SCarson Labrado     }
662f52c03c1SCarson Labrado 
663f52c03c1SCarson Labrado     // Gets called as part of callback after request is sent
664f52c03c1SCarson Labrado     // Reuses the connection if there are any requests waiting to be sent
665f52c03c1SCarson Labrado     // Otherwise closes the connection if it is not a keep-alive
666f52c03c1SCarson Labrado     void sendNext(bool keepAlive, uint32_t connId)
667f52c03c1SCarson Labrado     {
668f52c03c1SCarson Labrado         auto conn = connections[connId];
66946a81465SCarson Labrado 
67046a81465SCarson Labrado         // Allow the connection's handler to be deleted
67146a81465SCarson Labrado         // This is needed because of Redfish Aggregation passing an
67246a81465SCarson Labrado         // AsyncResponse shared_ptr to this callback
67346a81465SCarson Labrado         conn->callback = nullptr;
67446a81465SCarson Labrado 
675f52c03c1SCarson Labrado         // Reuse the connection to send the next request in the queue
676f52c03c1SCarson Labrado         if (!requestQueue.empty())
677f52c03c1SCarson Labrado         {
678f52c03c1SCarson Labrado             BMCWEB_LOG_DEBUG << std::to_string(requestQueue.size())
679f52c03c1SCarson Labrado                              << " requests remaining in queue for " << destIP
680f52c03c1SCarson Labrado                              << ":" << std::to_string(destPort)
681f52c03c1SCarson Labrado                              << ", reusing connnection "
682f52c03c1SCarson Labrado                              << std::to_string(connId);
683f52c03c1SCarson Labrado 
684f52c03c1SCarson Labrado             setConnProps(*conn);
685f52c03c1SCarson Labrado 
686f52c03c1SCarson Labrado             if (keepAlive)
687f52c03c1SCarson Labrado             {
688f52c03c1SCarson Labrado                 conn->sendMessage();
6892a5689a7SAppaRao Puli             }
6902a5689a7SAppaRao Puli             else
6912a5689a7SAppaRao Puli             {
692f52c03c1SCarson Labrado                 // Server is not keep-alive enabled so we need to close the
693f52c03c1SCarson Labrado                 // connection and then start over from resolve
694f52c03c1SCarson Labrado                 conn->doClose();
695f52c03c1SCarson Labrado                 conn->doResolve();
696f52c03c1SCarson Labrado             }
697f52c03c1SCarson Labrado             return;
698f52c03c1SCarson Labrado         }
699f52c03c1SCarson Labrado 
700f52c03c1SCarson Labrado         // No more messages to send so close the connection if necessary
701f52c03c1SCarson Labrado         if (keepAlive)
702f52c03c1SCarson Labrado         {
703f52c03c1SCarson Labrado             conn->state = ConnState::idle;
704f52c03c1SCarson Labrado         }
705f52c03c1SCarson Labrado         else
706f52c03c1SCarson Labrado         {
707f52c03c1SCarson Labrado             // Abort the connection since server is not keep-alive enabled
708f52c03c1SCarson Labrado             conn->state = ConnState::abortConnection;
709f52c03c1SCarson Labrado             conn->doClose();
7102a5689a7SAppaRao Puli         }
711bd030d0aSAppaRao Puli     }
712bd030d0aSAppaRao Puli 
713244256ccSCarson Labrado     void sendData(std::string& data, const std::string& destUri,
714244256ccSCarson Labrado                   const boost::beast::http::fields& httpHeader,
715244256ccSCarson Labrado                   const boost::beast::http::verb verb,
7166b3db60dSEd Tanous                   const std::function<void(Response&)>& resHandler)
717fe44eb0bSAyushi Smriti     {
718244256ccSCarson Labrado         // Construct the request to be sent
719244256ccSCarson Labrado         boost::beast::http::request<boost::beast::http::string_body> thisReq(
720244256ccSCarson Labrado             verb, destUri, 11, "", httpHeader);
721244256ccSCarson Labrado         thisReq.set(boost::beast::http::field::host, destIP);
722244256ccSCarson Labrado         thisReq.keep_alive(true);
723244256ccSCarson Labrado         thisReq.body() = std::move(data);
724244256ccSCarson Labrado         thisReq.prepare_payload();
7253d36e3a5SEd Tanous         auto cb = std::bind_front(&ConnectionPool::afterSendData,
7263d36e3a5SEd Tanous                                   weak_from_this(), resHandler);
727f52c03c1SCarson Labrado         // Reuse an existing connection if one is available
728f52c03c1SCarson Labrado         for (unsigned int i = 0; i < connections.size(); i++)
729fe44eb0bSAyushi Smriti         {
730f52c03c1SCarson Labrado             auto conn = connections[i];
731f52c03c1SCarson Labrado             if ((conn->state == ConnState::idle) ||
732f52c03c1SCarson Labrado                 (conn->state == ConnState::initialized) ||
733f52c03c1SCarson Labrado                 (conn->state == ConnState::closed))
734f52c03c1SCarson Labrado             {
735244256ccSCarson Labrado                 conn->req = std::move(thisReq);
736f52c03c1SCarson Labrado                 conn->callback = std::move(cb);
737f52c03c1SCarson Labrado                 std::string commonMsg = std::to_string(i) + " from pool " +
738f52c03c1SCarson Labrado                                         destIP + ":" + std::to_string(destPort);
739f52c03c1SCarson Labrado 
740f52c03c1SCarson Labrado                 if (conn->state == ConnState::idle)
741f52c03c1SCarson Labrado                 {
742f52c03c1SCarson Labrado                     BMCWEB_LOG_DEBUG << "Grabbing idle connection "
743f52c03c1SCarson Labrado                                      << commonMsg;
744f52c03c1SCarson Labrado                     conn->sendMessage();
745f52c03c1SCarson Labrado                 }
746f52c03c1SCarson Labrado                 else
747f52c03c1SCarson Labrado                 {
748f52c03c1SCarson Labrado                     BMCWEB_LOG_DEBUG << "Reusing existing connection "
749f52c03c1SCarson Labrado                                      << commonMsg;
750f52c03c1SCarson Labrado                     conn->doResolve();
751f52c03c1SCarson Labrado                 }
752f52c03c1SCarson Labrado                 return;
753f52c03c1SCarson Labrado             }
754f52c03c1SCarson Labrado         }
755f52c03c1SCarson Labrado 
756f52c03c1SCarson Labrado         // All connections in use so create a new connection or add request to
757f52c03c1SCarson Labrado         // the queue
758d14a48ffSCarson Labrado         if (connections.size() < connPolicy->maxConnections)
759f52c03c1SCarson Labrado         {
760f52c03c1SCarson Labrado             BMCWEB_LOG_DEBUG << "Adding new connection to pool " << destIP
761f52c03c1SCarson Labrado                              << ":" << std::to_string(destPort);
762f52c03c1SCarson Labrado             auto conn = addConnection();
763244256ccSCarson Labrado             conn->req = std::move(thisReq);
764f52c03c1SCarson Labrado             conn->callback = std::move(cb);
765f52c03c1SCarson Labrado             conn->doResolve();
766f52c03c1SCarson Labrado         }
767f52c03c1SCarson Labrado         else if (requestQueue.size() < maxRequestQueueSize)
768f52c03c1SCarson Labrado         {
769f52c03c1SCarson Labrado             BMCWEB_LOG_ERROR << "Max pool size reached. Adding data to queue.";
770d14a48ffSCarson Labrado             requestQueue.emplace_back(std::move(thisReq), std::move(cb));
771f52c03c1SCarson Labrado         }
772f52c03c1SCarson Labrado         else
773f52c03c1SCarson Labrado         {
77443e14d38SCarson Labrado             // If we can't buffer the request then we should let the callback
77543e14d38SCarson Labrado             // handle a 429 Too Many Requests dummy response
776f52c03c1SCarson Labrado             BMCWEB_LOG_ERROR << destIP << ":" << std::to_string(destPort)
777f52c03c1SCarson Labrado                              << " request queue full.  Dropping request.";
77843e14d38SCarson Labrado             Response dummyRes;
77943e14d38SCarson Labrado             dummyRes.result(boost::beast::http::status::too_many_requests);
78043e14d38SCarson Labrado             resHandler(dummyRes);
781f52c03c1SCarson Labrado         }
782f52c03c1SCarson Labrado     }
783f52c03c1SCarson Labrado 
7843d36e3a5SEd Tanous     // Callback to be called once the request has been sent
7853d36e3a5SEd Tanous     static void afterSendData(const std::weak_ptr<ConnectionPool>& weakSelf,
7863d36e3a5SEd Tanous                               const std::function<void(Response&)>& resHandler,
7873d36e3a5SEd Tanous                               bool keepAlive, uint32_t connId, Response& res)
7883d36e3a5SEd Tanous     {
7893d36e3a5SEd Tanous         // Allow provided callback to perform additional processing of the
7903d36e3a5SEd Tanous         // request
7913d36e3a5SEd Tanous         resHandler(res);
7923d36e3a5SEd Tanous 
7933d36e3a5SEd Tanous         // If requests remain in the queue then we want to reuse this
7943d36e3a5SEd Tanous         // connection to send the next request
7953d36e3a5SEd Tanous         std::shared_ptr<ConnectionPool> self = weakSelf.lock();
7963d36e3a5SEd Tanous         if (!self)
7973d36e3a5SEd Tanous         {
7983d36e3a5SEd Tanous             BMCWEB_LOG_CRITICAL << self << " Failed to capture connection";
7993d36e3a5SEd Tanous             return;
8003d36e3a5SEd Tanous         }
8013d36e3a5SEd Tanous 
8023d36e3a5SEd Tanous         self->sendNext(keepAlive, connId);
8033d36e3a5SEd Tanous     }
8043d36e3a5SEd Tanous 
805f52c03c1SCarson Labrado     std::shared_ptr<ConnectionInfo>& addConnection()
806f52c03c1SCarson Labrado     {
807f52c03c1SCarson Labrado         unsigned int newId = static_cast<unsigned int>(connections.size());
808f52c03c1SCarson Labrado 
809e38778a5SAppaRao Puli         auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>(
810d14a48ffSCarson Labrado             ioc, id, connPolicy, destIP, destPort, useSSL, newId));
811f52c03c1SCarson Labrado 
812f52c03c1SCarson Labrado         BMCWEB_LOG_DEBUG << "Added connection "
813f52c03c1SCarson Labrado                          << std::to_string(connections.size() - 1)
814f52c03c1SCarson Labrado                          << " to pool " << destIP << ":"
815f52c03c1SCarson Labrado                          << std::to_string(destPort);
816f52c03c1SCarson Labrado 
817f52c03c1SCarson Labrado         return ret;
818f52c03c1SCarson Labrado     }
819f52c03c1SCarson Labrado 
820f52c03c1SCarson Labrado   public:
821d14a48ffSCarson Labrado     explicit ConnectionPool(
822d14a48ffSCarson Labrado         boost::asio::io_context& iocIn, const std::string& idIn,
823d14a48ffSCarson Labrado         const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
824d14a48ffSCarson Labrado         const std::string& destIPIn, uint16_t destPortIn, bool useSSLIn) :
8258a592810SEd Tanous         ioc(iocIn),
826d14a48ffSCarson Labrado         id(idIn), connPolicy(connPolicyIn), destIP(destIPIn),
827d14a48ffSCarson Labrado         destPort(destPortIn), useSSL(useSSLIn)
828f52c03c1SCarson Labrado     {
829f52c03c1SCarson Labrado         BMCWEB_LOG_DEBUG << "Initializing connection pool for " << destIP << ":"
830f52c03c1SCarson Labrado                          << std::to_string(destPort);
831f52c03c1SCarson Labrado 
832f52c03c1SCarson Labrado         // Initialize the pool with a single connection
833f52c03c1SCarson Labrado         addConnection();
834fe44eb0bSAyushi Smriti     }
835bd030d0aSAppaRao Puli };
836bd030d0aSAppaRao Puli 
837f52c03c1SCarson Labrado class HttpClient
838f52c03c1SCarson Labrado {
839f52c03c1SCarson Labrado   private:
840f52c03c1SCarson Labrado     std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
841f52c03c1SCarson Labrado         connectionPools;
842*f8ca6d79SEd Tanous     boost::asio::io_context& ioc;
843d14a48ffSCarson Labrado     std::shared_ptr<ConnectionPolicy> connPolicy;
844f52c03c1SCarson Labrado 
845039a47e3SCarson Labrado     // Used as a dummy callback by sendData() in order to call
846039a47e3SCarson Labrado     // sendDataWithCallback()
84702cad96eSEd Tanous     static void genericResHandler(const Response& res)
848039a47e3SCarson Labrado     {
849039a47e3SCarson Labrado         BMCWEB_LOG_DEBUG << "Response handled with return code: "
850039a47e3SCarson Labrado                          << std::to_string(res.resultInt());
8514ee8e211SEd Tanous     }
852039a47e3SCarson Labrado 
853f52c03c1SCarson Labrado   public:
854d14a48ffSCarson Labrado     HttpClient() = delete;
855*f8ca6d79SEd Tanous     explicit HttpClient(boost::asio::io_context& iocIn,
856*f8ca6d79SEd Tanous                         const std::shared_ptr<ConnectionPolicy>& connPolicyIn) :
857*f8ca6d79SEd Tanous         ioc(iocIn),
858d14a48ffSCarson Labrado         connPolicy(connPolicyIn)
859d14a48ffSCarson Labrado     {}
860*f8ca6d79SEd Tanous 
861f52c03c1SCarson Labrado     HttpClient(const HttpClient&) = delete;
862f52c03c1SCarson Labrado     HttpClient& operator=(const HttpClient&) = delete;
863f52c03c1SCarson Labrado     HttpClient(HttpClient&&) = delete;
864f52c03c1SCarson Labrado     HttpClient& operator=(HttpClient&&) = delete;
865f52c03c1SCarson Labrado     ~HttpClient() = default;
866f52c03c1SCarson Labrado 
867039a47e3SCarson Labrado     // Send a request to destIP:destPort where additional processing of the
868039a47e3SCarson Labrado     // result is not required
869d14a48ffSCarson Labrado     void sendData(std::string& data, const std::string& destIP,
870d14a48ffSCarson Labrado                   uint16_t destPort, const std::string& destUri, bool useSSL,
871f52c03c1SCarson Labrado                   const boost::beast::http::fields& httpHeader,
872d14a48ffSCarson Labrado                   const boost::beast::http::verb verb)
873f52c03c1SCarson Labrado     {
874e38778a5SAppaRao Puli         const std::function<void(Response&)> cb = genericResHandler;
875d14a48ffSCarson Labrado         sendDataWithCallback(data, destIP, destPort, destUri, useSSL,
876d14a48ffSCarson Labrado                              httpHeader, verb, cb);
877039a47e3SCarson Labrado     }
878039a47e3SCarson Labrado 
879039a47e3SCarson Labrado     // Send request to destIP:destPort and use the provided callback to
880039a47e3SCarson Labrado     // handle the response
881d14a48ffSCarson Labrado     void sendDataWithCallback(std::string& data, const std::string& destIP,
882d14a48ffSCarson Labrado                               uint16_t destPort, const std::string& destUri,
883d14a48ffSCarson Labrado                               bool useSSL,
884039a47e3SCarson Labrado                               const boost::beast::http::fields& httpHeader,
885244256ccSCarson Labrado                               const boost::beast::http::verb verb,
8866b3db60dSEd Tanous                               const std::function<void(Response&)>& resHandler)
887039a47e3SCarson Labrado     {
888e38778a5SAppaRao Puli         std::string clientKey = useSSL ? "https" : "http";
889e38778a5SAppaRao Puli         clientKey += destIP;
890e38778a5SAppaRao Puli         clientKey += ":";
891e38778a5SAppaRao Puli         clientKey += std::to_string(destPort);
892d14a48ffSCarson Labrado         auto pool = connectionPools.try_emplace(clientKey);
893d14a48ffSCarson Labrado         if (pool.first->second == nullptr)
894f52c03c1SCarson Labrado         {
895d14a48ffSCarson Labrado             pool.first->second = std::make_shared<ConnectionPool>(
896d14a48ffSCarson Labrado                 ioc, clientKey, connPolicy, destIP, destPort, useSSL);
897f52c03c1SCarson Labrado         }
898f52c03c1SCarson Labrado         // Send the data using either the existing connection pool or the newly
899f52c03c1SCarson Labrado         // created connection pool
900d14a48ffSCarson Labrado         pool.first->second->sendData(data, destUri, httpHeader, verb,
901e38778a5SAppaRao Puli                                      resHandler);
902f52c03c1SCarson Labrado     }
903f52c03c1SCarson Labrado };
904bd030d0aSAppaRao Puli } // namespace crow
905