blob: 6e5613bd690e7a3f58f8ad988c5403881230e612 [file] [log] [blame]
Ed Tanous40e9b922024-09-10 13:50:16 -07001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright OpenBMC Authors
3// SPDX-FileCopyrightText: Copyright 2020 Intel Corporation
AppaRao Pulibd030d02020-03-20 03:34:29 +05304#pragma once
Nan Zhou77665bd2022-10-12 20:28:58 +00005
Ed Tanousd7857202025-01-28 15:32:26 -08006#include "bmcweb_config.h"
7
Nan Zhou77665bd2022-10-12 20:28:58 +00008#include "async_resolve.hpp"
Ed Tanousd7857202025-01-28 15:32:26 -08009#include "boost_formatters.hpp"
Ed Tanousb2896142024-01-31 15:25:47 -080010#include "http_body.hpp"
Nan Zhou77665bd2022-10-12 20:28:58 +000011#include "http_response.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080012#include "logging.hpp"
13#include "ssl_key_handler.hpp"
Nan Zhou77665bd2022-10-12 20:28:58 +000014
Ed Tanousd7857202025-01-28 15:32:26 -080015#include <openssl/err.h>
16#include <openssl/ssl.h>
17#include <openssl/tls1.h>
18
Ed Tanous0d5f5cf2022-03-12 15:30:55 -080019#include <boost/asio/connect.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080020#include <boost/asio/error.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070021#include <boost/asio/io_context.hpp>
Sunitha Harish29a82b02021-02-18 15:54:16 +053022#include <boost/asio/ip/address.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070023#include <boost/asio/ip/tcp.hpp>
AppaRao Pulie38778a2022-06-27 23:09:03 +000024#include <boost/asio/ssl/context.hpp>
25#include <boost/asio/ssl/error.hpp>
Ed Tanous003301a2024-04-16 09:59:19 -070026#include <boost/asio/ssl/stream.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080027#include <boost/asio/ssl/stream_base.hpp>
Ed Tanousd43cd0c2020-09-30 20:46:53 -070028#include <boost/asio/steady_timer.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080029#include <boost/beast/core/error.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070030#include <boost/beast/core/flat_static_buffer.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080031#include <boost/beast/http/field.hpp>
32#include <boost/beast/http/fields.hpp>
Ed Tanousd43cd0c2020-09-30 20:46:53 -070033#include <boost/beast/http/message.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070034#include <boost/beast/http/parser.hpp>
35#include <boost/beast/http/read.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080036#include <boost/beast/http/status.hpp>
37#include <boost/beast/http/verb.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070038#include <boost/beast/http/write.hpp>
Carson Labradof52c03c2022-03-23 18:50:15 +000039#include <boost/container/devector.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080040#include <boost/optional/optional.hpp>
41#include <boost/system/errc.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070042#include <boost/system/error_code.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080043#include <boost/url/host_type.hpp>
Ed Tanous27b0cf92023-08-07 12:02:40 -070044#include <boost/url/url.hpp>
Ed Tanous4a7fbef2024-04-06 16:03:49 -070045#include <boost/url/url_view_base.hpp>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050046
Ed Tanousd7857202025-01-28 15:32:26 -080047#include <chrono>
48#include <cstdint>
AppaRao Pulibd030d02020-03-20 03:34:29 +053049#include <cstdlib>
Ed Tanousd7857202025-01-28 15:32:26 -080050#include <format>
AppaRao Pulibd030d02020-03-20 03:34:29 +053051#include <functional>
AppaRao Pulibd030d02020-03-20 03:34:29 +053052#include <memory>
Ed Tanousd7857202025-01-28 15:32:26 -080053#include <optional>
AppaRao Pulibd030d02020-03-20 03:34:29 +053054#include <string>
Ed Tanousd7857202025-01-28 15:32:26 -080055#include <string_view>
56#include <type_traits>
57#include <unordered_map>
58#include <utility>
59#include <vector>
AppaRao Pulibd030d02020-03-20 03:34:29 +053060
61namespace crow
62{
Ed Tanous27b0cf92023-08-07 12:02:40 -070063// With Redfish Aggregation it is assumed we will connect to another
64// instance of BMCWeb which can handle 100 simultaneous connections.
Carson Labrado66d90c22022-12-07 22:34:33 +000065constexpr size_t maxPoolSize = 20;
66constexpr size_t maxRequestQueueSize = 500;
Carson Labrado17dcc312022-07-28 22:17:28 +000067constexpr unsigned int httpReadBodyLimit = 131072;
Carson Labrado4d942722022-06-22 22:16:10 +000068constexpr unsigned int httpReadBufferSize = 4096;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053069
AppaRao Pulibd030d02020-03-20 03:34:29 +053070enum class ConnState
71{
AppaRao Puli2a5689a2020-04-29 15:24:31 +053072 initialized,
Sunitha Harish29a82b02021-02-18 15:54:16 +053073 resolveInProgress,
74 resolveFailed,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053075 connectInProgress,
76 connectFailed,
AppaRao Pulibd030d02020-03-20 03:34:29 +053077 connected,
AppaRao Pulie38778a2022-06-27 23:09:03 +000078 handshakeInProgress,
79 handshakeFailed,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053080 sendInProgress,
81 sendFailed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053082 recvInProgress,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053083 recvFailed,
84 idle,
Ayushi Smritife44eb02020-05-15 15:24:45 +053085 closed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053086 suspended,
87 terminated,
88 abortConnection,
AppaRao Pulie38778a2022-06-27 23:09:03 +000089 sslInitFailed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053090 retry
AppaRao Pulibd030d02020-03-20 03:34:29 +053091};
92
Ed Tanous4ff0f1f2024-09-04 17:27:37 -070093inline boost::system::error_code defaultRetryHandler(unsigned int respCode)
Carson Labradoa7a80292022-06-01 16:01:52 +000094{
95 // As a default, assume 200X is alright
Ed Tanous62598e32023-07-17 17:06:25 -070096 BMCWEB_LOG_DEBUG("Using default check for response code validity");
Carson Labradoa7a80292022-06-01 16:01:52 +000097 if ((respCode < 200) || (respCode >= 300))
98 {
99 return boost::system::errc::make_error_code(
100 boost::system::errc::result_out_of_range);
101 }
102
103 // Return 0 if the response code is valid
104 return boost::system::errc::make_error_code(boost::system::errc::success);
105};
106
Ed Tanous27b0cf92023-08-07 12:02:40 -0700107// We need to allow retry information to be set before a message has been
108// sent and a connection pool has been created
Carson Labradod14a48f2023-02-22 00:24:54 +0000109struct ConnectionPolicy
Carson Labradof52c03c2022-03-23 18:50:15 +0000110{
111 uint32_t maxRetryAttempts = 5;
Carson Labradod14a48f2023-02-22 00:24:54 +0000112
113 // the max size of requests in bytes. 0 for unlimited
114 boost::optional<uint64_t> requestByteLimit = httpReadBodyLimit;
115
116 size_t maxConnections = 1;
117
Carson Labradof52c03c2022-03-23 18:50:15 +0000118 std::string retryPolicyAction = "TerminateAfterRetries";
Carson Labradod14a48f2023-02-22 00:24:54 +0000119
120 std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0);
Carson Labradoa7a80292022-06-01 16:01:52 +0000121 std::function<boost::system::error_code(unsigned int respCode)>
122 invalidResp = defaultRetryHandler;
Carson Labradof52c03c2022-03-23 18:50:15 +0000123};
124
125struct PendingRequest
126{
Ed Tanousb2896142024-01-31 15:25:47 -0800127 boost::beast::http::request<bmcweb::HttpBody> req;
Carson Labrado039a47e2022-04-05 16:03:20 +0000128 std::function<void(bool, uint32_t, Response&)> callback;
Carson Labrado039a47e2022-04-05 16:03:20 +0000129 PendingRequest(
Ed Tanousb2896142024-01-31 15:25:47 -0800130 boost::beast::http::request<bmcweb::HttpBody>&& reqIn,
Carson Labradod14a48f2023-02-22 00:24:54 +0000131 const std::function<void(bool, uint32_t, Response&)>& callbackIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400132 req(std::move(reqIn)), callback(callbackIn)
Carson Labradof52c03c2022-03-23 18:50:15 +0000133 {}
134};
135
Ed Tanouse01d0c32023-06-30 13:21:32 -0700136namespace http = boost::beast::http;
Carson Labradof52c03c2022-03-23 18:50:15 +0000137class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo>
AppaRao Pulibd030d02020-03-20 03:34:29 +0530138{
139 private:
Carson Labradof52c03c2022-03-23 18:50:15 +0000140 ConnState state = ConnState::initialized;
141 uint32_t retryCount = 0;
Carson Labradof52c03c2022-03-23 18:50:15 +0000142 std::string subId;
Carson Labradod14a48f2023-02-22 00:24:54 +0000143 std::shared_ptr<ConnectionPolicy> connPolicy;
Ed Tanousa716aa72023-08-01 11:35:53 -0700144 boost::urls::url host;
Ed Tanous19bb3622024-07-05 10:07:40 -0500145 ensuressl::VerifyCertificate verifyCert;
Carson Labradof52c03c2022-03-23 18:50:15 +0000146 uint32_t connId;
Carson Labradof52c03c2022-03-23 18:50:15 +0000147 // Data buffers
Ed Tanousb2896142024-01-31 15:25:47 -0800148 http::request<bmcweb::HttpBody> req;
149 using parser_type = http::response_parser<bmcweb::HttpBody>;
Ed Tanouse01d0c32023-06-30 13:21:32 -0700150 std::optional<parser_type> parser;
Carson Labrado4d942722022-06-22 22:16:10 +0000151 boost::beast::flat_static_buffer<httpReadBufferSize> buffer;
Carson Labrado039a47e2022-04-05 16:03:20 +0000152 Response res;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530153
Carson Labradof52c03c2022-03-23 18:50:15 +0000154 // Ascync callables
Carson Labrado039a47e2022-04-05 16:03:20 +0000155 std::function<void(bool, uint32_t, Response&)> callback;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700156
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600157 boost::asio::io_context& ioc;
158
Ed Tanous25b54db2024-04-17 15:40:31 -0700159 using Resolver = std::conditional_t<BMCWEB_DNS_RESOLVER == "systemd-dbus",
160 async_resolve::Resolver,
161 boost::asio::ip::tcp::resolver>;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700162 Resolver resolver;
163
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800164 boost::asio::ip::tcp::socket conn;
Ed Tanous003301a2024-04-16 09:59:19 -0700165 std::optional<boost::asio::ssl::stream<boost::asio::ip::tcp::socket&>>
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800166 sslConn;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000167
Carson Labradof52c03c2022-03-23 18:50:15 +0000168 boost::asio::steady_timer timer;
Ed Tanous84b35602021-09-08 20:06:32 -0700169
Carson Labradof52c03c2022-03-23 18:50:15 +0000170 friend class ConnectionPool;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530171
Sunitha Harish29a82b02021-02-18 15:54:16 +0530172 void doResolve()
173 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530174 state = ConnState::resolveInProgress;
Ed Tanousa716aa72023-08-01 11:35:53 -0700175 BMCWEB_LOG_DEBUG("Trying to resolve: {}, id: {}", host, connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530176
Ed Tanousa716aa72023-08-01 11:35:53 -0700177 resolver.async_resolve(host.encoded_host_address(), host.port(),
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700178 std::bind_front(&ConnectionInfo::afterResolve,
179 this, shared_from_this()));
Sunitha Harish29a82b02021-02-18 15:54:16 +0530180 }
181
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700182 void afterResolve(const std::shared_ptr<ConnectionInfo>& /*self*/,
183 const boost::system::error_code& ec,
184 const Resolver::results_type& endpointList)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530185 {
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700186 if (ec || (endpointList.empty()))
187 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700188 BMCWEB_LOG_ERROR("Resolve failed: {} {}", ec.message(), host);
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700189 state = ConnState::resolveFailed;
190 waitAndRetry();
191 return;
192 }
Ed Tanousa716aa72023-08-01 11:35:53 -0700193 BMCWEB_LOG_DEBUG("Resolved {}, id: {}", host, connId);
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530194 state = ConnState::connectInProgress;
195
Ed Tanousa716aa72023-08-01 11:35:53 -0700196 BMCWEB_LOG_DEBUG("Trying to connect to: {}, id: {}", host, connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530197
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800198 timer.expires_after(std::chrono::seconds(30));
199 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
200
201 boost::asio::async_connect(
202 conn, endpointList,
203 std::bind_front(&ConnectionInfo::afterConnect, this,
204 shared_from_this()));
AppaRao Pulie38778a2022-06-27 23:09:03 +0000205 }
206
207 void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/,
Ed Tanous81c4e332023-05-18 10:30:34 -0700208 const boost::beast::error_code& ec,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000209 const boost::asio::ip::tcp::endpoint& endpoint)
210 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000211 // The operation already timed out. We don't want do continue down
212 // this branch
213 if (ec && ec == boost::asio::error::operation_aborted)
214 {
215 return;
216 }
217
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800218 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000219 if (ec)
220 {
Ed Tanous62598e32023-07-17 17:06:25 -0700221 BMCWEB_LOG_ERROR("Connect {}:{}, id: {} failed: {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700222 endpoint.address().to_string(), endpoint.port(),
223 connId, ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000224 state = ConnState::connectFailed;
225 waitAndRetry();
226 return;
227 }
Ed Tanousa716aa72023-08-01 11:35:53 -0700228 BMCWEB_LOG_DEBUG("Connected to: {}:{}, id: {}",
229 endpoint.address().to_string(), endpoint.port(),
230 connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000231 if (sslConn)
232 {
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800233 doSslHandshake();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000234 return;
235 }
236 state = ConnState::connected;
237 sendMessage();
238 }
239
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800240 void doSslHandshake()
AppaRao Pulie38778a2022-06-27 23:09:03 +0000241 {
242 if (!sslConn)
243 {
244 return;
245 }
246 state = ConnState::handshakeInProgress;
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800247 timer.expires_after(std::chrono::seconds(30));
248 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
AppaRao Pulie38778a2022-06-27 23:09:03 +0000249 sslConn->async_handshake(
250 boost::asio::ssl::stream_base::client,
251 std::bind_front(&ConnectionInfo::afterSslHandshake, this,
252 shared_from_this()));
253 }
254
255 void afterSslHandshake(const std::shared_ptr<ConnectionInfo>& /*self*/,
Ed Tanous81c4e332023-05-18 10:30:34 -0700256 const boost::beast::error_code& ec)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000257 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000258 // The operation already timed out. We don't want do continue down
259 // this branch
260 if (ec && ec == boost::asio::error::operation_aborted)
261 {
262 return;
263 }
264
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800265 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000266 if (ec)
267 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700268 BMCWEB_LOG_ERROR("SSL Handshake failed - id: {} error: {}", connId,
269 ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000270 state = ConnState::handshakeFailed;
271 waitAndRetry();
272 return;
273 }
Ed Tanousa716aa72023-08-01 11:35:53 -0700274 BMCWEB_LOG_DEBUG("SSL Handshake successful - id: {}", connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000275 state = ConnState::connected;
276 sendMessage();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530277 }
278
Carson Labradof52c03c2022-03-23 18:50:15 +0000279 void sendMessage()
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530280 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530281 state = ConnState::sendInProgress;
282
AppaRao Pulibd030d02020-03-20 03:34:29 +0530283 // Set a timeout on the operation
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800284 timer.expires_after(std::chrono::seconds(30));
285 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
AppaRao Pulibd030d02020-03-20 03:34:29 +0530286 // Send the HTTP request to the remote host
AppaRao Pulie38778a2022-06-27 23:09:03 +0000287 if (sslConn)
288 {
Ed Tanouscd504a92024-08-19 11:46:20 -0700289 boost::beast::http::async_write(
290 *sslConn, req,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000291 std::bind_front(&ConnectionInfo::afterWrite, this,
292 shared_from_this()));
293 }
294 else
295 {
Ed Tanouscd504a92024-08-19 11:46:20 -0700296 boost::beast::http::async_write(
297 conn, req,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000298 std::bind_front(&ConnectionInfo::afterWrite, this,
299 shared_from_this()));
300 }
301 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530302
AppaRao Pulie38778a2022-06-27 23:09:03 +0000303 void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/,
304 const boost::beast::error_code& ec, size_t bytesTransferred)
305 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000306 // The operation already timed out. We don't want do continue down
307 // this branch
308 if (ec && ec == boost::asio::error::operation_aborted)
309 {
310 return;
311 }
312
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800313 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000314 if (ec)
315 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700316 BMCWEB_LOG_ERROR("sendMessage() failed: {} {}", ec.message(), host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000317 state = ConnState::sendFailed;
318 waitAndRetry();
319 return;
320 }
Ed Tanous62598e32023-07-17 17:06:25 -0700321 BMCWEB_LOG_DEBUG("sendMessage() bytes transferred: {}",
322 bytesTransferred);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000323
324 recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530325 }
326
327 void recvMessage()
328 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530329 state = ConnState::recvInProgress;
330
Ed Tanous38afdb92024-12-11 23:57:53 -0800331 parser_type& thisParser = parser.emplace();
Carson Labradod14a48f2023-02-22 00:24:54 +0000332
Ed Tanouse01d0c32023-06-30 13:21:32 -0700333 thisParser.body_limit(connPolicy->requestByteLimit);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530334
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800335 timer.expires_after(std::chrono::seconds(30));
336 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
337
AppaRao Pulibd030d02020-03-20 03:34:29 +0530338 // Receive the HTTP response
AppaRao Pulie38778a2022-06-27 23:09:03 +0000339 if (sslConn)
340 {
341 boost::beast::http::async_read(
Ed Tanouse01d0c32023-06-30 13:21:32 -0700342 *sslConn, buffer, thisParser,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000343 std::bind_front(&ConnectionInfo::afterRead, this,
344 shared_from_this()));
345 }
346 else
347 {
348 boost::beast::http::async_read(
Ed Tanouse01d0c32023-06-30 13:21:32 -0700349 conn, buffer, thisParser,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000350 std::bind_front(&ConnectionInfo::afterRead, this,
351 shared_from_this()));
352 }
353 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530354
AppaRao Pulie38778a2022-06-27 23:09:03 +0000355 void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/,
356 const boost::beast::error_code& ec,
357 const std::size_t& bytesTransferred)
358 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000359 // The operation already timed out. We don't want do continue down
360 // this branch
361 if (ec && ec == boost::asio::error::operation_aborted)
362 {
363 return;
364 }
365
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800366 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000367 if (ec && ec != boost::asio::ssl::error::stream_truncated)
368 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700369 BMCWEB_LOG_ERROR("recvMessage() failed: {} from {}", ec.message(),
370 host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000371 state = ConnState::recvFailed;
372 waitAndRetry();
373 return;
374 }
Ed Tanous62598e32023-07-17 17:06:25 -0700375 BMCWEB_LOG_DEBUG("recvMessage() bytes transferred: {}",
376 bytesTransferred);
Ed Tanouse01d0c32023-06-30 13:21:32 -0700377 if (!parser)
378 {
379 return;
380 }
Ed Tanous52e31622024-01-23 16:31:11 -0800381 BMCWEB_LOG_DEBUG("recvMessage() data: {}", parser->get().body().str());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000382
383 unsigned int respCode = parser->get().result_int();
Ed Tanous62598e32023-07-17 17:06:25 -0700384 BMCWEB_LOG_DEBUG("recvMessage() Header Response Code: {}", respCode);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000385
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600386 // Handle the case of stream_truncated. Some servers close the ssl
387 // connection uncleanly, so check to see if we got a full response
388 // before we handle this as an error.
389 if (!parser->is_done())
390 {
391 state = ConnState::recvFailed;
392 waitAndRetry();
393 return;
394 }
395
AppaRao Pulie38778a2022-06-27 23:09:03 +0000396 // Make sure the received response code is valid as defined by
397 // the associated retry policy
Carson Labradod14a48f2023-02-22 00:24:54 +0000398 if (connPolicy->invalidResp(respCode))
AppaRao Pulie38778a2022-06-27 23:09:03 +0000399 {
400 // The listener failed to receive the Sent-Event
Ed Tanous62598e32023-07-17 17:06:25 -0700401 BMCWEB_LOG_ERROR(
402 "recvMessage() Listener Failed to "
Ed Tanousa716aa72023-08-01 11:35:53 -0700403 "receive Sent-Event. Header Response Code: {} from {}",
404 respCode, host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000405 state = ConnState::recvFailed;
406 waitAndRetry();
407 return;
408 }
Ed Tanous002d39b2022-05-31 08:59:27 -0700409
AppaRao Pulie38778a2022-06-27 23:09:03 +0000410 // Send is successful
411 // Reset the counter just in case this was after retrying
412 retryCount = 0;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530413
AppaRao Pulie38778a2022-06-27 23:09:03 +0000414 // Keep the connection alive if server supports it
415 // Else close the connection
Ed Tanous62598e32023-07-17 17:06:25 -0700416 BMCWEB_LOG_DEBUG("recvMessage() keepalive : {}", parser->keep_alive());
AppaRao Pulibd030d02020-03-20 03:34:29 +0530417
AppaRao Pulie38778a2022-06-27 23:09:03 +0000418 // Copy the response into a Response object so that it can be
419 // processed by the callback function.
Ed Tanous27b0cf92023-08-07 12:02:40 -0700420 res.response = parser->release();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000421 callback(parser->keep_alive(), connId, res);
Carson Labrado513d1ff2022-07-19 00:38:15 +0000422 res.clear();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530423 }
424
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800425 static void onTimeout(const std::weak_ptr<ConnectionInfo>& weakSelf,
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800426 const boost::system::error_code& ec)
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800427 {
428 if (ec == boost::asio::error::operation_aborted)
429 {
Ed Tanous62598e32023-07-17 17:06:25 -0700430 BMCWEB_LOG_DEBUG(
431 "async_wait failed since the operation is aborted");
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800432 return;
433 }
434 if (ec)
435 {
Ed Tanous62598e32023-07-17 17:06:25 -0700436 BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
Ed Tanous27b0cf92023-08-07 12:02:40 -0700437 // If the timer fails, we need to close the socket anyway, same
438 // as if it expired.
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800439 }
440 std::shared_ptr<ConnectionInfo> self = weakSelf.lock();
441 if (self == nullptr)
442 {
443 return;
444 }
445 self->waitAndRetry();
446 }
447
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530448 void waitAndRetry()
AppaRao Pulibd030d02020-03-20 03:34:29 +0530449 {
Carson Labradod14a48f2023-02-22 00:24:54 +0000450 if ((retryCount >= connPolicy->maxRetryAttempts) ||
AppaRao Pulie38778a2022-06-27 23:09:03 +0000451 (state == ConnState::sslInitFailed))
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530452 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700453 BMCWEB_LOG_ERROR("Maximum number of retries reached. {}", host);
Ed Tanous62598e32023-07-17 17:06:25 -0700454 BMCWEB_LOG_DEBUG("Retry policy: {}", connPolicy->retryPolicyAction);
Carson Labrado039a47e2022-04-05 16:03:20 +0000455
Carson Labradod14a48f2023-02-22 00:24:54 +0000456 if (connPolicy->retryPolicyAction == "TerminateAfterRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530457 {
458 // TODO: delete subscription
459 state = ConnState::terminated;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530460 }
Carson Labradod14a48f2023-02-22 00:24:54 +0000461 if (connPolicy->retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530462 {
463 state = ConnState::suspended;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530464 }
Carson Labrado513d1ff2022-07-19 00:38:15 +0000465
466 // We want to return a 502 to indicate there was an error with
467 // the external server
468 res.result(boost::beast::http::status::bad_gateway);
469 callback(false, connId, res);
470 res.clear();
471
Ed Tanous27b0cf92023-08-07 12:02:40 -0700472 // Reset the retrycount to zero so that client can try
473 // connecting again if needed
Ed Tanous3174e4d2020-10-07 11:41:22 -0700474 retryCount = 0;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530475 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530476 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530477
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530478 retryCount++;
479
Ed Tanous62598e32023-07-17 17:06:25 -0700480 BMCWEB_LOG_DEBUG("Attempt retry after {} seconds. RetryCount = {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700481 connPolicy->retryIntervalSecs.count(), retryCount);
Carson Labradod14a48f2023-02-22 00:24:54 +0000482 timer.expires_after(connPolicy->retryIntervalSecs);
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700483 timer.async_wait(std::bind_front(&ConnectionInfo::onTimerDone, this,
484 shared_from_this()));
485 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530486
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700487 void onTimerDone(const std::shared_ptr<ConnectionInfo>& /*self*/,
488 const boost::system::error_code& ec)
489 {
490 if (ec == boost::asio::error::operation_aborted)
491 {
Ed Tanous62598e32023-07-17 17:06:25 -0700492 BMCWEB_LOG_DEBUG(
493 "async_wait failed since the operation is aborted{}",
494 ec.message());
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700495 }
496 else if (ec)
497 {
Ed Tanous62598e32023-07-17 17:06:25 -0700498 BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700499 // Ignore the error and continue the retry loop to attempt
500 // sending the event as per the retry policy
501 }
502
503 // Let's close the connection and restart from resolve.
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600504 shutdownConn(true);
505 }
506
507 void restartConnection()
508 {
509 BMCWEB_LOG_DEBUG("{}, id: {} restartConnection", host,
510 std::to_string(connId));
511 initializeConnection(host.scheme() == "https");
512 doResolve();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530513 }
514
AppaRao Pulie38778a2022-06-27 23:09:03 +0000515 void shutdownConn(bool retry)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530516 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000517 boost::beast::error_code ec;
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800518 conn.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
Carson Labradof52c03c2022-03-23 18:50:15 +0000519 conn.close();
520
521 // not_connected happens sometimes so don't bother reporting it.
522 if (ec && ec != boost::beast::errc::not_connected)
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530523 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700524 BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId,
Ed Tanous62598e32023-07-17 17:06:25 -0700525 ec.message());
Carson Labradof52c03c2022-03-23 18:50:15 +0000526 }
Carson Labrado5cab68f2022-07-11 22:26:21 +0000527 else
528 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700529 BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId);
Carson Labrado5cab68f2022-07-11 22:26:21 +0000530 }
Ed Tanousca723762022-06-28 19:40:39 -0700531
Carson Labrado513d1ff2022-07-19 00:38:15 +0000532 if (retry)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000533 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000534 // Now let's try to resend the data
535 state = ConnState::retry;
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600536 restartConnection();
Carson Labrado513d1ff2022-07-19 00:38:15 +0000537 }
538 else
539 {
540 state = ConnState::closed;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000541 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000542 }
543
AppaRao Pulie38778a2022-06-27 23:09:03 +0000544 void doClose(bool retry = false)
Carson Labradof52c03c2022-03-23 18:50:15 +0000545 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000546 if (!sslConn)
547 {
548 shutdownConn(retry);
549 return;
550 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000551
AppaRao Pulie38778a2022-06-27 23:09:03 +0000552 sslConn->async_shutdown(
553 std::bind_front(&ConnectionInfo::afterSslShutdown, this,
554 shared_from_this(), retry));
555 }
556
557 void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/,
558 bool retry, const boost::system::error_code& ec)
559 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000560 if (ec)
Carson Labradof52c03c2022-03-23 18:50:15 +0000561 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700562 BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId,
Ed Tanous62598e32023-07-17 17:06:25 -0700563 ec.message());
Carson Labradof52c03c2022-03-23 18:50:15 +0000564 }
Carson Labrado5cab68f2022-07-11 22:26:21 +0000565 else
566 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700567 BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId);
Carson Labrado5cab68f2022-07-11 22:26:21 +0000568 }
AppaRao Pulie38778a2022-06-27 23:09:03 +0000569 shutdownConn(retry);
570 }
Ed Tanousca723762022-06-28 19:40:39 -0700571
AppaRao Pulie38778a2022-06-27 23:09:03 +0000572 void setCipherSuiteTLSext()
573 {
574 if (!sslConn)
575 {
576 return;
577 }
Ravi Tejae7c29912023-07-31 09:39:32 -0500578
579 if (host.host_type() != boost::urls::host_type::name)
580 {
581 // Avoid setting SNI hostname if its IP address
582 return;
583 }
584 // Create a null terminated string for SSL
Ed Tanousa716aa72023-08-01 11:35:53 -0700585 std::string hostname(host.encoded_host_address());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000586 // NOTE: The SSL_set_tlsext_host_name is defined in tlsv1.h header
587 // file but its having old style casting (name is cast to void*).
588 // Since bmcweb compiler treats all old-style-cast as error, its
589 // causing the build failure. So replaced the same macro inline and
590 // did corrected the code by doing static_cast to viod*. This has to
591 // be fixed in openssl library in long run. Set SNI Hostname (many
592 // hosts need this to handshake successfully)
593 if (SSL_ctrl(sslConn->native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME,
594 TLSEXT_NAMETYPE_host_name,
Ed Tanousa716aa72023-08-01 11:35:53 -0700595 static_cast<void*>(hostname.data())) == 0)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000596
597 {
598 boost::beast::error_code ec{static_cast<int>(::ERR_get_error()),
599 boost::asio::error::get_ssl_category()};
600
Ed Tanousa716aa72023-08-01 11:35:53 -0700601 BMCWEB_LOG_ERROR("SSL_set_tlsext_host_name {}, id: {} failed: {}",
602 host, connId, ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000603 // Set state as sslInit failed so that we close the connection
604 // and take appropriate action as per retry configuration.
605 state = ConnState::sslInitFailed;
606 waitAndRetry();
607 return;
608 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530609 }
610
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600611 void initializeConnection(bool ssl)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000612 {
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600613 conn = boost::asio::ip::tcp::socket(ioc);
614 if (ssl)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000615 {
616 std::optional<boost::asio::ssl::context> sslCtx =
Ed Tanous19bb3622024-07-05 10:07:40 -0500617 ensuressl::getSSLClientContext(verifyCert);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000618
619 if (!sslCtx)
620 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700621 BMCWEB_LOG_ERROR("prepareSSLContext failed - {}, id: {}", host,
622 connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000623 // Don't retry if failure occurs while preparing SSL context
Ed Tanous27b0cf92023-08-07 12:02:40 -0700624 // such as certificate is invalid or set cipher failure or
625 // set host name failure etc... Setting conn state to
626 // sslInitFailed and connection state will be transitioned
627 // to next state depending on retry policy set by
628 // subscription.
AppaRao Pulie38778a2022-06-27 23:09:03 +0000629 state = ConnState::sslInitFailed;
630 waitAndRetry();
631 return;
632 }
633 sslConn.emplace(conn, *sslCtx);
634 setCipherSuiteTLSext();
635 }
636 }
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600637
638 public:
639 explicit ConnectionInfo(
640 boost::asio::io_context& iocIn, const std::string& idIn,
641 const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
Ed Tanous19bb3622024-07-05 10:07:40 -0500642 const boost::urls::url_view_base& hostIn,
643 ensuressl::VerifyCertificate verifyCertIn, unsigned int connIdIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400644 subId(idIn), connPolicy(connPolicyIn), host(hostIn),
645 verifyCert(verifyCertIn), connId(connIdIn), ioc(iocIn), resolver(iocIn),
646 conn(iocIn), timer(iocIn)
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600647 {
648 initializeConnection(host.scheme() == "https");
649 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000650};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530651
Carson Labradof52c03c2022-03-23 18:50:15 +0000652class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
653{
654 private:
655 boost::asio::io_context& ioc;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000656 std::string id;
Carson Labradod14a48f2023-02-22 00:24:54 +0000657 std::shared_ptr<ConnectionPolicy> connPolicy;
Ed Tanousa716aa72023-08-01 11:35:53 -0700658 boost::urls::url destIP;
Carson Labradof52c03c2022-03-23 18:50:15 +0000659 std::vector<std::shared_ptr<ConnectionInfo>> connections;
660 boost::container::devector<PendingRequest> requestQueue;
Ed Tanous19bb3622024-07-05 10:07:40 -0500661 ensuressl::VerifyCertificate verifyCert;
Carson Labradof52c03c2022-03-23 18:50:15 +0000662
663 friend class HttpClient;
664
Carson Labrado244256c2022-04-27 17:16:32 +0000665 // Configure a connections's request, callback, and retry info in
666 // preparation to begin sending the request
Carson Labradof52c03c2022-03-23 18:50:15 +0000667 void setConnProps(ConnectionInfo& conn)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530668 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000669 if (requestQueue.empty())
AppaRao Pulibd030d02020-03-20 03:34:29 +0530670 {
Ed Tanous62598e32023-07-17 17:06:25 -0700671 BMCWEB_LOG_ERROR(
672 "setConnProps() should not have been called when requestQueue is empty");
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530673 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530674 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530675
Ed Tanous52e31622024-01-23 16:31:11 -0800676 PendingRequest& nextReq = requestQueue.front();
Carson Labrado244256c2022-04-27 17:16:32 +0000677 conn.req = std::move(nextReq.req);
678 conn.callback = std::move(nextReq.callback);
Carson Labradof52c03c2022-03-23 18:50:15 +0000679
Ed Tanousa716aa72023-08-01 11:35:53 -0700680 BMCWEB_LOG_DEBUG("Setting properties for connection {}, id: {}",
681 conn.host, conn.connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000682
683 // We can remove the request from the queue at this point
684 requestQueue.pop_front();
685 }
686
Carson Labradof52c03c2022-03-23 18:50:15 +0000687 // Gets called as part of callback after request is sent
688 // Reuses the connection if there are any requests waiting to be sent
689 // Otherwise closes the connection if it is not a keep-alive
690 void sendNext(bool keepAlive, uint32_t connId)
691 {
692 auto conn = connections[connId];
Carson Labrado46a81462022-04-27 21:11:37 +0000693
694 // Allow the connection's handler to be deleted
695 // This is needed because of Redfish Aggregation passing an
696 // AsyncResponse shared_ptr to this callback
697 conn->callback = nullptr;
698
Carson Labradof52c03c2022-03-23 18:50:15 +0000699 // Reuse the connection to send the next request in the queue
700 if (!requestQueue.empty())
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530701 {
Ed Tanous62598e32023-07-17 17:06:25 -0700702 BMCWEB_LOG_DEBUG(
Ed Tanous8ece0e42024-01-02 13:16:50 -0800703 "{} requests remaining in queue for {}, reusing connection {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700704 requestQueue.size(), destIP, connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000705
706 setConnProps(*conn);
707
708 if (keepAlive)
709 {
710 conn->sendMessage();
711 }
712 else
713 {
714 // Server is not keep-alive enabled so we need to close the
715 // connection and then start over from resolve
716 conn->doClose();
Abhilash Raju2ecde742024-06-01 02:01:01 -0500717 conn->restartConnection();
Carson Labradof52c03c2022-03-23 18:50:15 +0000718 }
719 return;
720 }
721
722 // No more messages to send so close the connection if necessary
723 if (keepAlive)
724 {
725 conn->state = ConnState::idle;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530726 }
727 else
728 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000729 // Abort the connection since server is not keep-alive enabled
730 conn->state = ConnState::abortConnection;
731 conn->doClose();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530732 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530733 }
734
Ed Tanous4a7fbef2024-04-06 16:03:49 -0700735 void sendData(std::string&& data, const boost::urls::url_view_base& destUri,
Carson Labrado244256c2022-04-27 17:16:32 +0000736 const boost::beast::http::fields& httpHeader,
737 const boost::beast::http::verb verb,
Ed Tanous6b3db602022-06-28 19:41:44 -0700738 const std::function<void(Response&)>& resHandler)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530739 {
Carson Labrado244256c2022-04-27 17:16:32 +0000740 // Construct the request to be sent
Ed Tanousb2896142024-01-31 15:25:47 -0800741 boost::beast::http::request<bmcweb::HttpBody> thisReq(
Ed Tanousa716aa72023-08-01 11:35:53 -0700742 verb, destUri.encoded_target(), 11, "", httpHeader);
743 thisReq.set(boost::beast::http::field::host,
744 destUri.encoded_host_address());
Carson Labrado244256c2022-04-27 17:16:32 +0000745 thisReq.keep_alive(true);
Ed Tanous52e31622024-01-23 16:31:11 -0800746 thisReq.body().str() = std::move(data);
Carson Labrado244256c2022-04-27 17:16:32 +0000747 thisReq.prepare_payload();
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700748 auto cb = std::bind_front(&ConnectionPool::afterSendData,
749 weak_from_this(), resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000750 // Reuse an existing connection if one is available
751 for (unsigned int i = 0; i < connections.size(); i++)
752 {
753 auto conn = connections[i];
754 if ((conn->state == ConnState::idle) ||
755 (conn->state == ConnState::initialized) ||
756 (conn->state == ConnState::closed))
757 {
Carson Labrado244256c2022-04-27 17:16:32 +0000758 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000759 conn->callback = std::move(cb);
Ed Tanousa716aa72023-08-01 11:35:53 -0700760 std::string commonMsg = std::format("{} from pool {}", i, id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000761
762 if (conn->state == ConnState::idle)
763 {
Ed Tanous62598e32023-07-17 17:06:25 -0700764 BMCWEB_LOG_DEBUG("Grabbing idle connection {}", commonMsg);
Carson Labradof52c03c2022-03-23 18:50:15 +0000765 conn->sendMessage();
766 }
767 else
768 {
Ed Tanous62598e32023-07-17 17:06:25 -0700769 BMCWEB_LOG_DEBUG("Reusing existing connection {}",
770 commonMsg);
Abhilash Raju2ecde742024-06-01 02:01:01 -0500771 conn->restartConnection();
Carson Labradof52c03c2022-03-23 18:50:15 +0000772 }
773 return;
774 }
775 }
776
Ed Tanous27b0cf92023-08-07 12:02:40 -0700777 // All connections in use so create a new connection or add request
778 // to the queue
Carson Labradod14a48f2023-02-22 00:24:54 +0000779 if (connections.size() < connPolicy->maxConnections)
Carson Labradof52c03c2022-03-23 18:50:15 +0000780 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700781 BMCWEB_LOG_DEBUG("Adding new connection to pool {}", id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000782 auto conn = addConnection();
Carson Labrado244256c2022-04-27 17:16:32 +0000783 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000784 conn->callback = std::move(cb);
Carson Labradof52c03c2022-03-23 18:50:15 +0000785 conn->doResolve();
786 }
787 else if (requestQueue.size() < maxRequestQueueSize)
788 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700789 BMCWEB_LOG_DEBUG("Max pool size reached. Adding data to queue {}",
790 id);
Carson Labradod14a48f2023-02-22 00:24:54 +0000791 requestQueue.emplace_back(std::move(thisReq), std::move(cb));
Carson Labradof52c03c2022-03-23 18:50:15 +0000792 }
793 else
794 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700795 // If we can't buffer the request then we should let the
796 // callback handle a 429 Too Many Requests dummy response
Ed Tanous6ea90762024-04-07 08:38:44 -0700797 BMCWEB_LOG_ERROR("{} request queue full. Dropping request.", id);
Carson Labrado43e14d32022-11-09 00:25:20 +0000798 Response dummyRes;
799 dummyRes.result(boost::beast::http::status::too_many_requests);
800 resHandler(dummyRes);
Carson Labradof52c03c2022-03-23 18:50:15 +0000801 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530802 }
803
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700804 // Callback to be called once the request has been sent
805 static void afterSendData(const std::weak_ptr<ConnectionPool>& weakSelf,
806 const std::function<void(Response&)>& resHandler,
807 bool keepAlive, uint32_t connId, Response& res)
808 {
809 // Allow provided callback to perform additional processing of the
810 // request
811 resHandler(res);
812
813 // If requests remain in the queue then we want to reuse this
814 // connection to send the next request
815 std::shared_ptr<ConnectionPool> self = weakSelf.lock();
816 if (!self)
817 {
Ed Tanous62598e32023-07-17 17:06:25 -0700818 BMCWEB_LOG_CRITICAL("{} Failed to capture connection",
819 logPtr(self.get()));
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700820 return;
821 }
822
823 self->sendNext(keepAlive, connId);
824 }
825
Carson Labradof52c03c2022-03-23 18:50:15 +0000826 std::shared_ptr<ConnectionInfo>& addConnection()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530827 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000828 unsigned int newId = static_cast<unsigned int>(connections.size());
829
AppaRao Pulie38778a2022-06-27 23:09:03 +0000830 auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>(
Ed Tanous19bb3622024-07-05 10:07:40 -0500831 ioc, id, connPolicy, destIP, verifyCert, newId));
Carson Labradof52c03c2022-03-23 18:50:15 +0000832
Ed Tanousa716aa72023-08-01 11:35:53 -0700833 BMCWEB_LOG_DEBUG("Added connection {} to pool {}",
834 connections.size() - 1, id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000835
836 return ret;
837 }
838
839 public:
Carson Labradod14a48f2023-02-22 00:24:54 +0000840 explicit ConnectionPool(
841 boost::asio::io_context& iocIn, const std::string& idIn,
842 const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
Ed Tanous19bb3622024-07-05 10:07:40 -0500843 const boost::urls::url_view_base& destIPIn,
844 ensuressl::VerifyCertificate verifyCertIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400845 ioc(iocIn), id(idIn), connPolicy(connPolicyIn), destIP(destIPIn),
Ed Tanous19bb3622024-07-05 10:07:40 -0500846 verifyCert(verifyCertIn)
Carson Labradof52c03c2022-03-23 18:50:15 +0000847 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700848 BMCWEB_LOG_DEBUG("Initializing connection pool for {}", id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000849
850 // Initialize the pool with a single connection
851 addConnection();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530852 }
Myung Baea0969c72024-09-19 08:46:45 -0400853
854 // Check whether all connections are terminated
855 bool areAllConnectionsTerminated()
856 {
857 if (connections.empty())
858 {
859 BMCWEB_LOG_DEBUG("There are no connections for pool id:{}", id);
860 return false;
861 }
862 for (const auto& conn : connections)
863 {
864 if (conn != nullptr && conn->state != ConnState::terminated)
865 {
866 BMCWEB_LOG_DEBUG(
867 "Not all connections of pool id:{} are terminated", id);
868 return false;
869 }
870 }
871 BMCWEB_LOG_INFO("All connections of pool id:{} are terminated", id);
872 return true;
873 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530874};
875
Carson Labradof52c03c2022-03-23 18:50:15 +0000876class HttpClient
877{
878 private:
879 std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
880 connectionPools;
Ed Tanous4b712a22023-08-02 12:56:52 -0700881
882 // reference_wrapper here makes HttpClient movable
883 std::reference_wrapper<boost::asio::io_context> ioc;
Carson Labradod14a48f2023-02-22 00:24:54 +0000884 std::shared_ptr<ConnectionPolicy> connPolicy;
Carson Labradof52c03c2022-03-23 18:50:15 +0000885
Carson Labrado039a47e2022-04-05 16:03:20 +0000886 // Used as a dummy callback by sendData() in order to call
887 // sendDataWithCallback()
Ed Tanous02cad962022-06-30 16:50:15 -0700888 static void genericResHandler(const Response& res)
Carson Labrado039a47e2022-04-05 16:03:20 +0000889 {
Ed Tanous62598e32023-07-17 17:06:25 -0700890 BMCWEB_LOG_DEBUG("Response handled with return code: {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700891 res.resultInt());
Ed Tanous4ee8e212022-05-28 09:42:51 -0700892 }
Carson Labrado039a47e2022-04-05 16:03:20 +0000893
Carson Labradof52c03c2022-03-23 18:50:15 +0000894 public:
Carson Labradod14a48f2023-02-22 00:24:54 +0000895 HttpClient() = delete;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700896 explicit HttpClient(boost::asio::io_context& iocIn,
897 const std::shared_ptr<ConnectionPolicy>& connPolicyIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400898 ioc(iocIn), connPolicy(connPolicyIn)
Carson Labradod14a48f2023-02-22 00:24:54 +0000899 {}
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700900
Carson Labradof52c03c2022-03-23 18:50:15 +0000901 HttpClient(const HttpClient&) = delete;
902 HttpClient& operator=(const HttpClient&) = delete;
Ed Tanous4b712a22023-08-02 12:56:52 -0700903 HttpClient(HttpClient&& client) = default;
904 HttpClient& operator=(HttpClient&& client) = default;
Carson Labradof52c03c2022-03-23 18:50:15 +0000905 ~HttpClient() = default;
906
Ed Tanousa716aa72023-08-01 11:35:53 -0700907 // Send a request to destIP where additional processing of the
Carson Labrado039a47e2022-04-05 16:03:20 +0000908 // result is not required
Ed Tanous4a7fbef2024-04-06 16:03:49 -0700909 void sendData(std::string&& data, const boost::urls::url_view_base& destUri,
Ed Tanous19bb3622024-07-05 10:07:40 -0500910 ensuressl::VerifyCertificate verifyCert,
Carson Labradof52c03c2022-03-23 18:50:15 +0000911 const boost::beast::http::fields& httpHeader,
Carson Labradod14a48f2023-02-22 00:24:54 +0000912 const boost::beast::http::verb verb)
Carson Labradof52c03c2022-03-23 18:50:15 +0000913 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000914 const std::function<void(Response&)> cb = genericResHandler;
Ed Tanous19bb3622024-07-05 10:07:40 -0500915 sendDataWithCallback(std::move(data), destUri, verifyCert, httpHeader,
916 verb, cb);
Carson Labrado039a47e2022-04-05 16:03:20 +0000917 }
918
Ed Tanousa716aa72023-08-01 11:35:53 -0700919 // Send request to destIP and use the provided callback to
Carson Labrado039a47e2022-04-05 16:03:20 +0000920 // handle the response
Ed Tanous4a7fbef2024-04-06 16:03:49 -0700921 void sendDataWithCallback(std::string&& data,
922 const boost::urls::url_view_base& destUrl,
Ed Tanous19bb3622024-07-05 10:07:40 -0500923 ensuressl::VerifyCertificate verifyCert,
Carson Labrado039a47e2022-04-05 16:03:20 +0000924 const boost::beast::http::fields& httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000925 const boost::beast::http::verb verb,
Ed Tanous6b3db602022-06-28 19:41:44 -0700926 const std::function<void(Response&)>& resHandler)
Carson Labrado039a47e2022-04-05 16:03:20 +0000927 {
Ed Tanous19bb3622024-07-05 10:07:40 -0500928 std::string_view verify = "ssl_verify";
929 if (verifyCert == ensuressl::VerifyCertificate::NoVerify)
930 {
931 verify = "ssl no verify";
932 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400933 std::string clientKey =
934 std::format("{}{}://{}", verify, destUrl.scheme(),
935 destUrl.encoded_host_and_port());
Carson Labradod14a48f2023-02-22 00:24:54 +0000936 auto pool = connectionPools.try_emplace(clientKey);
937 if (pool.first->second == nullptr)
Carson Labradof52c03c2022-03-23 18:50:15 +0000938 {
Carson Labradod14a48f2023-02-22 00:24:54 +0000939 pool.first->second = std::make_shared<ConnectionPool>(
Ed Tanous19bb3622024-07-05 10:07:40 -0500940 ioc, clientKey, connPolicy, destUrl, verifyCert);
Carson Labradof52c03c2022-03-23 18:50:15 +0000941 }
Ed Tanous27b0cf92023-08-07 12:02:40 -0700942 // Send the data using either the existing connection pool or the
943 // newly created connection pool
Ed Tanousa716aa72023-08-01 11:35:53 -0700944 pool.first->second->sendData(std::move(data), destUrl, httpHeader, verb,
Carson Labradod14a48f2023-02-22 00:24:54 +0000945 resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000946 }
Myung Baea0969c72024-09-19 08:46:45 -0400947
948 // Test whether all connections are terminated (after MaxRetryAttempts)
949 bool isTerminated()
950 {
951 for (const auto& pool : connectionPools)
952 {
953 if (pool.second != nullptr &&
954 !pool.second->areAllConnectionsTerminated())
955 {
956 BMCWEB_LOG_DEBUG(
957 "Not all of client connections are terminated");
958 return false;
959 }
960 }
961 BMCWEB_LOG_DEBUG("All client connections are terminated");
962 return true;
963 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000964};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530965} // namespace crow