blob: f1a5e1eae2f7ce0d2ce1402b0c60a0cb41265124 [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>
Ed Tanousd7857202025-01-28 15:32:26 -080016#include <openssl/tls1.h>
17
Ed Tanous0d5f5cf2022-03-12 15:30:55 -080018#include <boost/asio/connect.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080019#include <boost/asio/error.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070020#include <boost/asio/io_context.hpp>
Sunitha Harish29a82b02021-02-18 15:54:16 +053021#include <boost/asio/ip/address.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070022#include <boost/asio/ip/tcp.hpp>
AppaRao Pulie38778a2022-06-27 23:09:03 +000023#include <boost/asio/ssl/context.hpp>
24#include <boost/asio/ssl/error.hpp>
Ed Tanous003301a2024-04-16 09:59:19 -070025#include <boost/asio/ssl/stream.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080026#include <boost/asio/ssl/stream_base.hpp>
Ed Tanousd43cd0c2020-09-30 20:46:53 -070027#include <boost/asio/steady_timer.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080028#include <boost/beast/core/error.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070029#include <boost/beast/core/flat_static_buffer.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080030#include <boost/beast/http/field.hpp>
31#include <boost/beast/http/fields.hpp>
Ed Tanousd43cd0c2020-09-30 20:46:53 -070032#include <boost/beast/http/message.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070033#include <boost/beast/http/parser.hpp>
34#include <boost/beast/http/read.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080035#include <boost/beast/http/status.hpp>
36#include <boost/beast/http/verb.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070037#include <boost/beast/http/write.hpp>
Carson Labradof52c03c2022-03-23 18:50:15 +000038#include <boost/container/devector.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080039#include <boost/optional/optional.hpp>
40#include <boost/system/errc.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070041#include <boost/system/error_code.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080042#include <boost/url/host_type.hpp>
Ed Tanous27b0cf92023-08-07 12:02:40 -070043#include <boost/url/url.hpp>
Ed Tanous4a7fbef2024-04-06 16:03:49 -070044#include <boost/url/url_view_base.hpp>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050045
Ed Tanousd7857202025-01-28 15:32:26 -080046#include <chrono>
47#include <cstdint>
AppaRao Pulibd030d02020-03-20 03:34:29 +053048#include <cstdlib>
Ed Tanousd7857202025-01-28 15:32:26 -080049#include <format>
AppaRao Pulibd030d02020-03-20 03:34:29 +053050#include <functional>
AppaRao Pulibd030d02020-03-20 03:34:29 +053051#include <memory>
Ed Tanousd7857202025-01-28 15:32:26 -080052#include <optional>
AppaRao Pulibd030d02020-03-20 03:34:29 +053053#include <string>
Ed Tanousd7857202025-01-28 15:32:26 -080054#include <string_view>
55#include <type_traits>
56#include <unordered_map>
57#include <utility>
58#include <vector>
AppaRao Pulibd030d02020-03-20 03:34:29 +053059
60namespace crow
61{
Ed Tanous27b0cf92023-08-07 12:02:40 -070062// With Redfish Aggregation it is assumed we will connect to another
63// instance of BMCWeb which can handle 100 simultaneous connections.
Carson Labrado66d90c22022-12-07 22:34:33 +000064constexpr size_t maxPoolSize = 20;
65constexpr size_t maxRequestQueueSize = 500;
Carson Labrado17dcc312022-07-28 22:17:28 +000066constexpr unsigned int httpReadBodyLimit = 131072;
Carson Labrado4d942722022-06-22 22:16:10 +000067constexpr unsigned int httpReadBufferSize = 4096;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053068
AppaRao Pulibd030d02020-03-20 03:34:29 +053069enum class ConnState
70{
AppaRao Puli2a5689a2020-04-29 15:24:31 +053071 initialized,
Sunitha Harish29a82b02021-02-18 15:54:16 +053072 resolveInProgress,
73 resolveFailed,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053074 connectInProgress,
75 connectFailed,
AppaRao Pulibd030d02020-03-20 03:34:29 +053076 connected,
AppaRao Pulie38778a2022-06-27 23:09:03 +000077 handshakeInProgress,
78 handshakeFailed,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053079 sendInProgress,
80 sendFailed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053081 recvInProgress,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053082 recvFailed,
83 idle,
Ayushi Smritife44eb02020-05-15 15:24:45 +053084 closed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053085 suspended,
86 terminated,
87 abortConnection,
AppaRao Pulie38778a2022-06-27 23:09:03 +000088 sslInitFailed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053089 retry
AppaRao Pulibd030d02020-03-20 03:34:29 +053090};
91
Ed Tanous4ff0f1f2024-09-04 17:27:37 -070092inline boost::system::error_code defaultRetryHandler(unsigned int respCode)
Carson Labradoa7a80292022-06-01 16:01:52 +000093{
94 // As a default, assume 200X is alright
Ed Tanous62598e32023-07-17 17:06:25 -070095 BMCWEB_LOG_DEBUG("Using default check for response code validity");
Carson Labradoa7a80292022-06-01 16:01:52 +000096 if ((respCode < 200) || (respCode >= 300))
97 {
98 return boost::system::errc::make_error_code(
99 boost::system::errc::result_out_of_range);
100 }
101
102 // Return 0 if the response code is valid
103 return boost::system::errc::make_error_code(boost::system::errc::success);
104};
105
Ed Tanous27b0cf92023-08-07 12:02:40 -0700106// We need to allow retry information to be set before a message has been
107// sent and a connection pool has been created
Carson Labradod14a48f2023-02-22 00:24:54 +0000108struct ConnectionPolicy
Carson Labradof52c03c2022-03-23 18:50:15 +0000109{
110 uint32_t maxRetryAttempts = 5;
Carson Labradod14a48f2023-02-22 00:24:54 +0000111
112 // the max size of requests in bytes. 0 for unlimited
113 boost::optional<uint64_t> requestByteLimit = httpReadBodyLimit;
114
115 size_t maxConnections = 1;
116
Carson Labradof52c03c2022-03-23 18:50:15 +0000117 std::string retryPolicyAction = "TerminateAfterRetries";
Carson Labradod14a48f2023-02-22 00:24:54 +0000118
119 std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0);
Carson Labradoa7a80292022-06-01 16:01:52 +0000120 std::function<boost::system::error_code(unsigned int respCode)>
121 invalidResp = defaultRetryHandler;
Carson Labradof52c03c2022-03-23 18:50:15 +0000122};
123
124struct PendingRequest
125{
Ed Tanousb2896142024-01-31 15:25:47 -0800126 boost::beast::http::request<bmcweb::HttpBody> req;
Carson Labrado039a47e2022-04-05 16:03:20 +0000127 std::function<void(bool, uint32_t, Response&)> callback;
Carson Labrado039a47e2022-04-05 16:03:20 +0000128 PendingRequest(
Ed Tanousb2896142024-01-31 15:25:47 -0800129 boost::beast::http::request<bmcweb::HttpBody>&& reqIn,
Carson Labradod14a48f2023-02-22 00:24:54 +0000130 const std::function<void(bool, uint32_t, Response&)>& callbackIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400131 req(std::move(reqIn)), callback(callbackIn)
Carson Labradof52c03c2022-03-23 18:50:15 +0000132 {}
133};
134
Ed Tanouse01d0c32023-06-30 13:21:32 -0700135namespace http = boost::beast::http;
Carson Labradof52c03c2022-03-23 18:50:15 +0000136class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo>
AppaRao Pulibd030d02020-03-20 03:34:29 +0530137{
138 private:
Carson Labradof52c03c2022-03-23 18:50:15 +0000139 ConnState state = ConnState::initialized;
140 uint32_t retryCount = 0;
Carson Labradof52c03c2022-03-23 18:50:15 +0000141 std::string subId;
Carson Labradod14a48f2023-02-22 00:24:54 +0000142 std::shared_ptr<ConnectionPolicy> connPolicy;
Ed Tanousa716aa72023-08-01 11:35:53 -0700143 boost::urls::url host;
Ed Tanous19bb3622024-07-05 10:07:40 -0500144 ensuressl::VerifyCertificate verifyCert;
Carson Labradof52c03c2022-03-23 18:50:15 +0000145 uint32_t connId;
Carson Labradof52c03c2022-03-23 18:50:15 +0000146 // Data buffers
Ed Tanousb2896142024-01-31 15:25:47 -0800147 http::request<bmcweb::HttpBody> req;
148 using parser_type = http::response_parser<bmcweb::HttpBody>;
Ed Tanouse01d0c32023-06-30 13:21:32 -0700149 std::optional<parser_type> parser;
Carson Labrado4d942722022-06-22 22:16:10 +0000150 boost::beast::flat_static_buffer<httpReadBufferSize> buffer;
Carson Labrado039a47e2022-04-05 16:03:20 +0000151 Response res;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530152
Ed Tanousd0fd3e52025-06-10 09:19:33 -0700153 // Async callables
Carson Labrado039a47e2022-04-05 16:03:20 +0000154 std::function<void(bool, uint32_t, Response&)> callback;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700155
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600156 boost::asio::io_context& ioc;
157
Ed Tanous25b54db2024-04-17 15:40:31 -0700158 using Resolver = std::conditional_t<BMCWEB_DNS_RESOLVER == "systemd-dbus",
159 async_resolve::Resolver,
160 boost::asio::ip::tcp::resolver>;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700161 Resolver resolver;
162
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800163 boost::asio::ip::tcp::socket conn;
Ed Tanous003301a2024-04-16 09:59:19 -0700164 std::optional<boost::asio::ssl::stream<boost::asio::ip::tcp::socket&>>
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800165 sslConn;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000166
Carson Labradof52c03c2022-03-23 18:50:15 +0000167 boost::asio::steady_timer timer;
Ed Tanous84b35602021-09-08 20:06:32 -0700168
Carson Labradof52c03c2022-03-23 18:50:15 +0000169 friend class ConnectionPool;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530170
Sunitha Harish29a82b02021-02-18 15:54:16 +0530171 void doResolve()
172 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530173 state = ConnState::resolveInProgress;
Ed Tanousa716aa72023-08-01 11:35:53 -0700174 BMCWEB_LOG_DEBUG("Trying to resolve: {}, id: {}", host, connId);
Ed Tanousfa4d4c62025-09-23 16:16:14 +0530175 boost::urls::host_type hostType = host.host_type();
176 if (hostType == boost::urls::host_type::name)
177 {
178 resolver.async_resolve(
179 host.encoded_host_address(), host.port(),
180 std::bind_front(&ConnectionInfo::afterResolve, this,
181 shared_from_this()));
Sunitha Harish29a82b02021-02-18 15:54:16 +0530182
Ed Tanousfa4d4c62025-09-23 16:16:14 +0530183 return;
184 }
185 // If we already have an ip address, no need to resolve
186 Resolver::results_type ip;
187 boost::system::error_code ec;
188 boost::asio::ip::address addr =
189 boost::asio::ip::make_address(host.host_address(), ec);
190
191 if (ec)
192 {
193 BMCWEB_LOG_ERROR(
194 "Failed to parse already-parsed ip address. This should not happen {}",
195 host.host_address());
196 return;
197 }
198 boost::asio::ip::tcp::endpoint end(addr, host.port_number());
199 ip.push_back(std::move(end));
200 afterResolve(shared_from_this(), boost::system::error_code(), ip);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530201 }
202
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700203 void afterResolve(const std::shared_ptr<ConnectionInfo>& /*self*/,
204 const boost::system::error_code& ec,
205 const Resolver::results_type& endpointList)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530206 {
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700207 if (ec || (endpointList.empty()))
208 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700209 BMCWEB_LOG_ERROR("Resolve failed: {} {}", ec.message(), host);
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700210 state = ConnState::resolveFailed;
211 waitAndRetry();
212 return;
213 }
Ed Tanousa716aa72023-08-01 11:35:53 -0700214 BMCWEB_LOG_DEBUG("Resolved {}, id: {}", host, connId);
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530215 state = ConnState::connectInProgress;
216
Ed Tanousa716aa72023-08-01 11:35:53 -0700217 BMCWEB_LOG_DEBUG("Trying to connect to: {}, id: {}", host, connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530218
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800219 timer.expires_after(std::chrono::seconds(30));
220 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
221
222 boost::asio::async_connect(
223 conn, endpointList,
224 std::bind_front(&ConnectionInfo::afterConnect, this,
225 shared_from_this()));
AppaRao Pulie38778a2022-06-27 23:09:03 +0000226 }
227
228 void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/,
Ed Tanous81c4e332023-05-18 10:30:34 -0700229 const boost::beast::error_code& ec,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000230 const boost::asio::ip::tcp::endpoint& endpoint)
231 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000232 // The operation already timed out. We don't want do continue down
233 // this branch
234 if (ec && ec == boost::asio::error::operation_aborted)
235 {
236 return;
237 }
238
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800239 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000240 if (ec)
241 {
Ed Tanous62598e32023-07-17 17:06:25 -0700242 BMCWEB_LOG_ERROR("Connect {}:{}, id: {} failed: {}",
abhilashraju079fbcf2025-04-24 05:24:56 +0000243 host.encoded_host_address(), host.port(), connId,
244 ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000245 state = ConnState::connectFailed;
246 waitAndRetry();
247 return;
248 }
Ed Tanousa716aa72023-08-01 11:35:53 -0700249 BMCWEB_LOG_DEBUG("Connected to: {}:{}, id: {}",
250 endpoint.address().to_string(), endpoint.port(),
251 connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000252 if (sslConn)
253 {
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800254 doSslHandshake();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000255 return;
256 }
257 state = ConnState::connected;
258 sendMessage();
259 }
260
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800261 void doSslHandshake()
AppaRao Pulie38778a2022-06-27 23:09:03 +0000262 {
263 if (!sslConn)
264 {
265 return;
266 }
Ed Tanous3d158642025-05-12 14:20:49 -0700267 auto& ssl = *sslConn;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000268 state = ConnState::handshakeInProgress;
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800269 timer.expires_after(std::chrono::seconds(30));
270 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
Ed Tanous3d158642025-05-12 14:20:49 -0700271 ssl.async_handshake(boost::asio::ssl::stream_base::client,
272 std::bind_front(&ConnectionInfo::afterSslHandshake,
273 this, shared_from_this()));
AppaRao Pulie38778a2022-06-27 23:09:03 +0000274 }
275
276 void afterSslHandshake(const std::shared_ptr<ConnectionInfo>& /*self*/,
Ed Tanous81c4e332023-05-18 10:30:34 -0700277 const boost::beast::error_code& ec)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000278 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000279 // The operation already timed out. We don't want do continue down
280 // this branch
281 if (ec && ec == boost::asio::error::operation_aborted)
282 {
283 return;
284 }
285
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800286 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000287 if (ec)
288 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700289 BMCWEB_LOG_ERROR("SSL Handshake failed - id: {} error: {}", connId,
290 ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000291 state = ConnState::handshakeFailed;
292 waitAndRetry();
293 return;
294 }
Ed Tanousa716aa72023-08-01 11:35:53 -0700295 BMCWEB_LOG_DEBUG("SSL Handshake successful - id: {}", connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000296 state = ConnState::connected;
297 sendMessage();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530298 }
299
Carson Labradof52c03c2022-03-23 18:50:15 +0000300 void sendMessage()
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530301 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530302 state = ConnState::sendInProgress;
303
AppaRao Pulibd030d02020-03-20 03:34:29 +0530304 // Set a timeout on the operation
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800305 timer.expires_after(std::chrono::seconds(30));
306 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
AppaRao Pulibd030d02020-03-20 03:34:29 +0530307 // Send the HTTP request to the remote host
AppaRao Pulie38778a2022-06-27 23:09:03 +0000308 if (sslConn)
309 {
Ed Tanouscd504a92024-08-19 11:46:20 -0700310 boost::beast::http::async_write(
311 *sslConn, req,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000312 std::bind_front(&ConnectionInfo::afterWrite, this,
313 shared_from_this()));
314 }
315 else
316 {
Ed Tanouscd504a92024-08-19 11:46:20 -0700317 boost::beast::http::async_write(
318 conn, req,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000319 std::bind_front(&ConnectionInfo::afterWrite, this,
320 shared_from_this()));
321 }
322 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530323
AppaRao Pulie38778a2022-06-27 23:09:03 +0000324 void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/,
325 const boost::beast::error_code& ec, size_t bytesTransferred)
326 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000327 // The operation already timed out. We don't want do continue down
328 // this branch
329 if (ec && ec == boost::asio::error::operation_aborted)
330 {
331 return;
332 }
333
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800334 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000335 if (ec)
336 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700337 BMCWEB_LOG_ERROR("sendMessage() failed: {} {}", ec.message(), host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000338 state = ConnState::sendFailed;
339 waitAndRetry();
340 return;
341 }
Ed Tanous62598e32023-07-17 17:06:25 -0700342 BMCWEB_LOG_DEBUG("sendMessage() bytes transferred: {}",
343 bytesTransferred);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000344
345 recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530346 }
347
348 void recvMessage()
349 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530350 state = ConnState::recvInProgress;
351
Ed Tanous38afdb92024-12-11 23:57:53 -0800352 parser_type& thisParser = parser.emplace();
Carson Labradod14a48f2023-02-22 00:24:54 +0000353
Ed Tanouse01d0c32023-06-30 13:21:32 -0700354 thisParser.body_limit(connPolicy->requestByteLimit);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530355
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800356 timer.expires_after(std::chrono::seconds(30));
357 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
358
AppaRao Pulibd030d02020-03-20 03:34:29 +0530359 // Receive the HTTP response
AppaRao Pulie38778a2022-06-27 23:09:03 +0000360 if (sslConn)
361 {
362 boost::beast::http::async_read(
Ed Tanouse01d0c32023-06-30 13:21:32 -0700363 *sslConn, buffer, thisParser,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000364 std::bind_front(&ConnectionInfo::afterRead, this,
365 shared_from_this()));
366 }
367 else
368 {
369 boost::beast::http::async_read(
Ed Tanouse01d0c32023-06-30 13:21:32 -0700370 conn, buffer, thisParser,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000371 std::bind_front(&ConnectionInfo::afterRead, this,
372 shared_from_this()));
373 }
374 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530375
AppaRao Pulie38778a2022-06-27 23:09:03 +0000376 void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/,
377 const boost::beast::error_code& ec,
Ed Tanousd0fd3e52025-06-10 09:19:33 -0700378 const std::size_t bytesTransferred)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000379 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000380 // The operation already timed out. We don't want do continue down
381 // this branch
382 if (ec && ec == boost::asio::error::operation_aborted)
383 {
384 return;
385 }
386
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800387 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000388 if (ec && ec != boost::asio::ssl::error::stream_truncated)
389 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700390 BMCWEB_LOG_ERROR("recvMessage() failed: {} from {}", ec.message(),
391 host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000392 state = ConnState::recvFailed;
393 waitAndRetry();
394 return;
395 }
Ed Tanous62598e32023-07-17 17:06:25 -0700396 BMCWEB_LOG_DEBUG("recvMessage() bytes transferred: {}",
397 bytesTransferred);
Ed Tanouse01d0c32023-06-30 13:21:32 -0700398 if (!parser)
399 {
400 return;
401 }
Ed Tanous52e31622024-01-23 16:31:11 -0800402 BMCWEB_LOG_DEBUG("recvMessage() data: {}", parser->get().body().str());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000403
404 unsigned int respCode = parser->get().result_int();
Ed Tanous62598e32023-07-17 17:06:25 -0700405 BMCWEB_LOG_DEBUG("recvMessage() Header Response Code: {}", respCode);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000406
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600407 // Handle the case of stream_truncated. Some servers close the ssl
408 // connection uncleanly, so check to see if we got a full response
409 // before we handle this as an error.
410 if (!parser->is_done())
411 {
412 state = ConnState::recvFailed;
413 waitAndRetry();
414 return;
415 }
416
AppaRao Pulie38778a2022-06-27 23:09:03 +0000417 // Make sure the received response code is valid as defined by
418 // the associated retry policy
Carson Labradod14a48f2023-02-22 00:24:54 +0000419 if (connPolicy->invalidResp(respCode))
AppaRao Pulie38778a2022-06-27 23:09:03 +0000420 {
421 // The listener failed to receive the Sent-Event
Ed Tanous62598e32023-07-17 17:06:25 -0700422 BMCWEB_LOG_ERROR(
423 "recvMessage() Listener Failed to "
Ed Tanousa716aa72023-08-01 11:35:53 -0700424 "receive Sent-Event. Header Response Code: {} from {}",
425 respCode, host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000426 state = ConnState::recvFailed;
427 waitAndRetry();
428 return;
429 }
Ed Tanous002d39b2022-05-31 08:59:27 -0700430
AppaRao Pulie38778a2022-06-27 23:09:03 +0000431 // Send is successful
432 // Reset the counter just in case this was after retrying
433 retryCount = 0;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530434
AppaRao Pulie38778a2022-06-27 23:09:03 +0000435 // Keep the connection alive if server supports it
436 // Else close the connection
Ed Tanous62598e32023-07-17 17:06:25 -0700437 BMCWEB_LOG_DEBUG("recvMessage() keepalive : {}", parser->keep_alive());
AppaRao Pulibd030d02020-03-20 03:34:29 +0530438
AppaRao Pulie38778a2022-06-27 23:09:03 +0000439 // Copy the response into a Response object so that it can be
440 // processed by the callback function.
Ed Tanous27b0cf92023-08-07 12:02:40 -0700441 res.response = parser->release();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000442 callback(parser->keep_alive(), connId, res);
Carson Labrado513d1ff2022-07-19 00:38:15 +0000443 res.clear();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530444 }
445
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800446 static void onTimeout(const std::weak_ptr<ConnectionInfo>& weakSelf,
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800447 const boost::system::error_code& ec)
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800448 {
449 if (ec == boost::asio::error::operation_aborted)
450 {
Ed Tanous62598e32023-07-17 17:06:25 -0700451 BMCWEB_LOG_DEBUG(
452 "async_wait failed since the operation is aborted");
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800453 return;
454 }
455 if (ec)
456 {
Ed Tanous62598e32023-07-17 17:06:25 -0700457 BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
Ed Tanous27b0cf92023-08-07 12:02:40 -0700458 // If the timer fails, we need to close the socket anyway, same
459 // as if it expired.
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800460 }
461 std::shared_ptr<ConnectionInfo> self = weakSelf.lock();
462 if (self == nullptr)
463 {
464 return;
465 }
466 self->waitAndRetry();
467 }
468
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530469 void waitAndRetry()
AppaRao Pulibd030d02020-03-20 03:34:29 +0530470 {
Carson Labradod14a48f2023-02-22 00:24:54 +0000471 if ((retryCount >= connPolicy->maxRetryAttempts) ||
AppaRao Pulie38778a2022-06-27 23:09:03 +0000472 (state == ConnState::sslInitFailed))
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530473 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700474 BMCWEB_LOG_ERROR("Maximum number of retries reached. {}", host);
Ed Tanous62598e32023-07-17 17:06:25 -0700475 BMCWEB_LOG_DEBUG("Retry policy: {}", connPolicy->retryPolicyAction);
Carson Labrado039a47e2022-04-05 16:03:20 +0000476
Carson Labradod14a48f2023-02-22 00:24:54 +0000477 if (connPolicy->retryPolicyAction == "TerminateAfterRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530478 {
479 // TODO: delete subscription
480 state = ConnState::terminated;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530481 }
Carson Labradod14a48f2023-02-22 00:24:54 +0000482 if (connPolicy->retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530483 {
484 state = ConnState::suspended;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530485 }
Carson Labrado513d1ff2022-07-19 00:38:15 +0000486
487 // We want to return a 502 to indicate there was an error with
488 // the external server
489 res.result(boost::beast::http::status::bad_gateway);
490 callback(false, connId, res);
491 res.clear();
492
Ed Tanous27b0cf92023-08-07 12:02:40 -0700493 // Reset the retrycount to zero so that client can try
494 // connecting again if needed
Ed Tanous3174e4d2020-10-07 11:41:22 -0700495 retryCount = 0;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530496 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530497 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530498
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530499 retryCount++;
500
Ed Tanous62598e32023-07-17 17:06:25 -0700501 BMCWEB_LOG_DEBUG("Attempt retry after {} seconds. RetryCount = {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700502 connPolicy->retryIntervalSecs.count(), retryCount);
Carson Labradod14a48f2023-02-22 00:24:54 +0000503 timer.expires_after(connPolicy->retryIntervalSecs);
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700504 timer.async_wait(std::bind_front(&ConnectionInfo::onTimerDone, this,
505 shared_from_this()));
506 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530507
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700508 void onTimerDone(const std::shared_ptr<ConnectionInfo>& /*self*/,
509 const boost::system::error_code& ec)
510 {
511 if (ec == boost::asio::error::operation_aborted)
512 {
Ed Tanous62598e32023-07-17 17:06:25 -0700513 BMCWEB_LOG_DEBUG(
514 "async_wait failed since the operation is aborted{}",
515 ec.message());
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700516 }
517 else if (ec)
518 {
Ed Tanous62598e32023-07-17 17:06:25 -0700519 BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700520 // Ignore the error and continue the retry loop to attempt
521 // sending the event as per the retry policy
522 }
523
524 // Let's close the connection and restart from resolve.
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600525 shutdownConn(true);
526 }
527
528 void restartConnection()
529 {
530 BMCWEB_LOG_DEBUG("{}, id: {} restartConnection", host,
531 std::to_string(connId));
532 initializeConnection(host.scheme() == "https");
533 doResolve();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530534 }
535
AppaRao Pulie38778a2022-06-27 23:09:03 +0000536 void shutdownConn(bool retry)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530537 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000538 boost::beast::error_code ec;
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800539 conn.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
Carson Labradof52c03c2022-03-23 18:50:15 +0000540 conn.close();
541
542 // not_connected happens sometimes so don't bother reporting it.
543 if (ec && ec != boost::beast::errc::not_connected)
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530544 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700545 BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId,
Ed Tanous62598e32023-07-17 17:06:25 -0700546 ec.message());
Carson Labradof52c03c2022-03-23 18:50:15 +0000547 }
Carson Labrado5cab68f2022-07-11 22:26:21 +0000548 else
549 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700550 BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId);
Carson Labrado5cab68f2022-07-11 22:26:21 +0000551 }
Ed Tanousca723762022-06-28 19:40:39 -0700552
Carson Labrado513d1ff2022-07-19 00:38:15 +0000553 if (retry)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000554 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000555 // Now let's try to resend the data
556 state = ConnState::retry;
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600557 restartConnection();
Carson Labrado513d1ff2022-07-19 00:38:15 +0000558 }
559 else
560 {
561 state = ConnState::closed;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000562 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000563 }
564
AppaRao Pulie38778a2022-06-27 23:09:03 +0000565 void doClose(bool retry = false)
Carson Labradof52c03c2022-03-23 18:50:15 +0000566 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000567 if (!sslConn)
568 {
569 shutdownConn(retry);
570 return;
571 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000572
AppaRao Pulie38778a2022-06-27 23:09:03 +0000573 sslConn->async_shutdown(
574 std::bind_front(&ConnectionInfo::afterSslShutdown, this,
575 shared_from_this(), retry));
576 }
577
578 void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/,
579 bool retry, const boost::system::error_code& ec)
580 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000581 if (ec)
Carson Labradof52c03c2022-03-23 18:50:15 +0000582 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700583 BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId,
Ed Tanous62598e32023-07-17 17:06:25 -0700584 ec.message());
Carson Labradof52c03c2022-03-23 18:50:15 +0000585 }
Carson Labrado5cab68f2022-07-11 22:26:21 +0000586 else
587 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700588 BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId);
Carson Labrado5cab68f2022-07-11 22:26:21 +0000589 }
AppaRao Pulie38778a2022-06-27 23:09:03 +0000590 shutdownConn(retry);
591 }
Ed Tanousca723762022-06-28 19:40:39 -0700592
AppaRao Pulie38778a2022-06-27 23:09:03 +0000593 void setCipherSuiteTLSext()
594 {
595 if (!sslConn)
596 {
597 return;
598 }
Ravi Tejae7c29912023-07-31 09:39:32 -0500599
600 if (host.host_type() != boost::urls::host_type::name)
601 {
602 // Avoid setting SNI hostname if its IP address
603 return;
604 }
605 // Create a null terminated string for SSL
Ed Tanousa716aa72023-08-01 11:35:53 -0700606 std::string hostname(host.encoded_host_address());
Ed Tanousd0fd3e52025-06-10 09:19:33 -0700607 if (SSL_set_tlsext_host_name(sslConn->native_handle(),
608 hostname.data()) == 0)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000609
610 {
611 boost::beast::error_code ec{static_cast<int>(::ERR_get_error()),
612 boost::asio::error::get_ssl_category()};
613
Ed Tanousa716aa72023-08-01 11:35:53 -0700614 BMCWEB_LOG_ERROR("SSL_set_tlsext_host_name {}, id: {} failed: {}",
615 host, connId, ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000616 // Set state as sslInit failed so that we close the connection
617 // and take appropriate action as per retry configuration.
618 state = ConnState::sslInitFailed;
619 waitAndRetry();
620 return;
621 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530622 }
623
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600624 void initializeConnection(bool ssl)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000625 {
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600626 conn = boost::asio::ip::tcp::socket(ioc);
627 if (ssl)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000628 {
629 std::optional<boost::asio::ssl::context> sslCtx =
Ed Tanous19bb3622024-07-05 10:07:40 -0500630 ensuressl::getSSLClientContext(verifyCert);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000631
632 if (!sslCtx)
633 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700634 BMCWEB_LOG_ERROR("prepareSSLContext failed - {}, id: {}", host,
635 connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000636 // Don't retry if failure occurs while preparing SSL context
Ed Tanous27b0cf92023-08-07 12:02:40 -0700637 // such as certificate is invalid or set cipher failure or
638 // set host name failure etc... Setting conn state to
639 // sslInitFailed and connection state will be transitioned
640 // to next state depending on retry policy set by
641 // subscription.
AppaRao Pulie38778a2022-06-27 23:09:03 +0000642 state = ConnState::sslInitFailed;
643 waitAndRetry();
644 return;
645 }
646 sslConn.emplace(conn, *sslCtx);
647 setCipherSuiteTLSext();
648 }
649 }
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600650
651 public:
652 explicit ConnectionInfo(
653 boost::asio::io_context& iocIn, const std::string& idIn,
654 const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
Ed Tanous19bb3622024-07-05 10:07:40 -0500655 const boost::urls::url_view_base& hostIn,
656 ensuressl::VerifyCertificate verifyCertIn, unsigned int connIdIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400657 subId(idIn), connPolicy(connPolicyIn), host(hostIn),
658 verifyCert(verifyCertIn), connId(connIdIn), ioc(iocIn), resolver(iocIn),
659 conn(iocIn), timer(iocIn)
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600660 {
661 initializeConnection(host.scheme() == "https");
662 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000663};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530664
Carson Labradof52c03c2022-03-23 18:50:15 +0000665class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
666{
667 private:
668 boost::asio::io_context& ioc;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000669 std::string id;
Carson Labradod14a48f2023-02-22 00:24:54 +0000670 std::shared_ptr<ConnectionPolicy> connPolicy;
Ed Tanousa716aa72023-08-01 11:35:53 -0700671 boost::urls::url destIP;
Carson Labradof52c03c2022-03-23 18:50:15 +0000672 std::vector<std::shared_ptr<ConnectionInfo>> connections;
673 boost::container::devector<PendingRequest> requestQueue;
Ed Tanous19bb3622024-07-05 10:07:40 -0500674 ensuressl::VerifyCertificate verifyCert;
Carson Labradof52c03c2022-03-23 18:50:15 +0000675
676 friend class HttpClient;
677
Carson Labrado244256c2022-04-27 17:16:32 +0000678 // Configure a connections's request, callback, and retry info in
679 // preparation to begin sending the request
Carson Labradof52c03c2022-03-23 18:50:15 +0000680 void setConnProps(ConnectionInfo& conn)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530681 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000682 if (requestQueue.empty())
AppaRao Pulibd030d02020-03-20 03:34:29 +0530683 {
Ed Tanous62598e32023-07-17 17:06:25 -0700684 BMCWEB_LOG_ERROR(
685 "setConnProps() should not have been called when requestQueue is empty");
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530686 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530687 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530688
Ed Tanous52e31622024-01-23 16:31:11 -0800689 PendingRequest& nextReq = requestQueue.front();
Carson Labrado244256c2022-04-27 17:16:32 +0000690 conn.req = std::move(nextReq.req);
691 conn.callback = std::move(nextReq.callback);
Carson Labradof52c03c2022-03-23 18:50:15 +0000692
Ed Tanousa716aa72023-08-01 11:35:53 -0700693 BMCWEB_LOG_DEBUG("Setting properties for connection {}, id: {}",
694 conn.host, conn.connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000695
696 // We can remove the request from the queue at this point
697 requestQueue.pop_front();
698 }
699
Carson Labradof52c03c2022-03-23 18:50:15 +0000700 // Gets called as part of callback after request is sent
701 // Reuses the connection if there are any requests waiting to be sent
702 // Otherwise closes the connection if it is not a keep-alive
703 void sendNext(bool keepAlive, uint32_t connId)
704 {
705 auto conn = connections[connId];
Carson Labrado46a81462022-04-27 21:11:37 +0000706
707 // Allow the connection's handler to be deleted
708 // This is needed because of Redfish Aggregation passing an
709 // AsyncResponse shared_ptr to this callback
710 conn->callback = nullptr;
711
Carson Labradof52c03c2022-03-23 18:50:15 +0000712 // Reuse the connection to send the next request in the queue
713 if (!requestQueue.empty())
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530714 {
Ed Tanous62598e32023-07-17 17:06:25 -0700715 BMCWEB_LOG_DEBUG(
Ed Tanous8ece0e42024-01-02 13:16:50 -0800716 "{} requests remaining in queue for {}, reusing connection {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700717 requestQueue.size(), destIP, connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000718
719 setConnProps(*conn);
720
721 if (keepAlive)
722 {
723 conn->sendMessage();
724 }
725 else
726 {
727 // Server is not keep-alive enabled so we need to close the
728 // connection and then start over from resolve
729 conn->doClose();
Abhilash Raju2ecde742024-06-01 02:01:01 -0500730 conn->restartConnection();
Carson Labradof52c03c2022-03-23 18:50:15 +0000731 }
732 return;
733 }
734
735 // No more messages to send so close the connection if necessary
736 if (keepAlive)
737 {
738 conn->state = ConnState::idle;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530739 }
740 else
741 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000742 // Abort the connection since server is not keep-alive enabled
743 conn->state = ConnState::abortConnection;
744 conn->doClose();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530745 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530746 }
747
Ed Tanous4a7fbef2024-04-06 16:03:49 -0700748 void sendData(std::string&& data, const boost::urls::url_view_base& destUri,
Carson Labrado244256c2022-04-27 17:16:32 +0000749 const boost::beast::http::fields& httpHeader,
750 const boost::beast::http::verb verb,
Ed Tanous6b3db602022-06-28 19:41:44 -0700751 const std::function<void(Response&)>& resHandler)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530752 {
Carson Labrado244256c2022-04-27 17:16:32 +0000753 // Construct the request to be sent
Ed Tanousb2896142024-01-31 15:25:47 -0800754 boost::beast::http::request<bmcweb::HttpBody> thisReq(
Ed Tanousa716aa72023-08-01 11:35:53 -0700755 verb, destUri.encoded_target(), 11, "", httpHeader);
756 thisReq.set(boost::beast::http::field::host,
757 destUri.encoded_host_address());
Carson Labrado244256c2022-04-27 17:16:32 +0000758 thisReq.keep_alive(true);
Ed Tanous52e31622024-01-23 16:31:11 -0800759 thisReq.body().str() = std::move(data);
Carson Labrado244256c2022-04-27 17:16:32 +0000760 thisReq.prepare_payload();
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700761 auto cb = std::bind_front(&ConnectionPool::afterSendData,
762 weak_from_this(), resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000763 // Reuse an existing connection if one is available
764 for (unsigned int i = 0; i < connections.size(); i++)
765 {
766 auto conn = connections[i];
767 if ((conn->state == ConnState::idle) ||
768 (conn->state == ConnState::initialized) ||
769 (conn->state == ConnState::closed))
770 {
Carson Labrado244256c2022-04-27 17:16:32 +0000771 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000772 conn->callback = std::move(cb);
Ed Tanousa716aa72023-08-01 11:35:53 -0700773 std::string commonMsg = std::format("{} from pool {}", i, id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000774
775 if (conn->state == ConnState::idle)
776 {
Ed Tanous62598e32023-07-17 17:06:25 -0700777 BMCWEB_LOG_DEBUG("Grabbing idle connection {}", commonMsg);
Carson Labradof52c03c2022-03-23 18:50:15 +0000778 conn->sendMessage();
779 }
780 else
781 {
Ed Tanous62598e32023-07-17 17:06:25 -0700782 BMCWEB_LOG_DEBUG("Reusing existing connection {}",
783 commonMsg);
Abhilash Raju2ecde742024-06-01 02:01:01 -0500784 conn->restartConnection();
Carson Labradof52c03c2022-03-23 18:50:15 +0000785 }
786 return;
787 }
788 }
789
Ed Tanous27b0cf92023-08-07 12:02:40 -0700790 // All connections in use so create a new connection or add request
791 // to the queue
Carson Labradod14a48f2023-02-22 00:24:54 +0000792 if (connections.size() < connPolicy->maxConnections)
Carson Labradof52c03c2022-03-23 18:50:15 +0000793 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700794 BMCWEB_LOG_DEBUG("Adding new connection to pool {}", id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000795 auto conn = addConnection();
Carson Labrado244256c2022-04-27 17:16:32 +0000796 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000797 conn->callback = std::move(cb);
Carson Labradof52c03c2022-03-23 18:50:15 +0000798 conn->doResolve();
799 }
800 else if (requestQueue.size() < maxRequestQueueSize)
801 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700802 BMCWEB_LOG_DEBUG("Max pool size reached. Adding data to queue {}",
803 id);
Carson Labradod14a48f2023-02-22 00:24:54 +0000804 requestQueue.emplace_back(std::move(thisReq), std::move(cb));
Carson Labradof52c03c2022-03-23 18:50:15 +0000805 }
806 else
807 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700808 // If we can't buffer the request then we should let the
809 // callback handle a 429 Too Many Requests dummy response
Ed Tanous6ea90762024-04-07 08:38:44 -0700810 BMCWEB_LOG_ERROR("{} request queue full. Dropping request.", id);
Carson Labrado43e14d32022-11-09 00:25:20 +0000811 Response dummyRes;
812 dummyRes.result(boost::beast::http::status::too_many_requests);
813 resHandler(dummyRes);
Carson Labradof52c03c2022-03-23 18:50:15 +0000814 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530815 }
816
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700817 // Callback to be called once the request has been sent
818 static void afterSendData(const std::weak_ptr<ConnectionPool>& weakSelf,
819 const std::function<void(Response&)>& resHandler,
820 bool keepAlive, uint32_t connId, Response& res)
821 {
822 // Allow provided callback to perform additional processing of the
823 // request
824 resHandler(res);
825
826 // If requests remain in the queue then we want to reuse this
827 // connection to send the next request
828 std::shared_ptr<ConnectionPool> self = weakSelf.lock();
829 if (!self)
830 {
Ed Tanous62598e32023-07-17 17:06:25 -0700831 BMCWEB_LOG_CRITICAL("{} Failed to capture connection",
832 logPtr(self.get()));
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700833 return;
834 }
835
836 self->sendNext(keepAlive, connId);
837 }
838
Carson Labradof52c03c2022-03-23 18:50:15 +0000839 std::shared_ptr<ConnectionInfo>& addConnection()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530840 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000841 unsigned int newId = static_cast<unsigned int>(connections.size());
842
AppaRao Pulie38778a2022-06-27 23:09:03 +0000843 auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>(
Ed Tanous19bb3622024-07-05 10:07:40 -0500844 ioc, id, connPolicy, destIP, verifyCert, newId));
Carson Labradof52c03c2022-03-23 18:50:15 +0000845
Ed Tanousa716aa72023-08-01 11:35:53 -0700846 BMCWEB_LOG_DEBUG("Added connection {} to pool {}",
847 connections.size() - 1, id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000848
849 return ret;
850 }
851
852 public:
Carson Labradod14a48f2023-02-22 00:24:54 +0000853 explicit ConnectionPool(
854 boost::asio::io_context& iocIn, const std::string& idIn,
855 const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
Ed Tanous19bb3622024-07-05 10:07:40 -0500856 const boost::urls::url_view_base& destIPIn,
857 ensuressl::VerifyCertificate verifyCertIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400858 ioc(iocIn), id(idIn), connPolicy(connPolicyIn), destIP(destIPIn),
Ed Tanous19bb3622024-07-05 10:07:40 -0500859 verifyCert(verifyCertIn)
Carson Labradof52c03c2022-03-23 18:50:15 +0000860 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700861 BMCWEB_LOG_DEBUG("Initializing connection pool for {}", id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000862
863 // Initialize the pool with a single connection
864 addConnection();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530865 }
Myung Baea0969c72024-09-19 08:46:45 -0400866
867 // Check whether all connections are terminated
868 bool areAllConnectionsTerminated()
869 {
870 if (connections.empty())
871 {
872 BMCWEB_LOG_DEBUG("There are no connections for pool id:{}", id);
873 return false;
874 }
875 for (const auto& conn : connections)
876 {
877 if (conn != nullptr && conn->state != ConnState::terminated)
878 {
879 BMCWEB_LOG_DEBUG(
880 "Not all connections of pool id:{} are terminated", id);
881 return false;
882 }
883 }
884 BMCWEB_LOG_INFO("All connections of pool id:{} are terminated", id);
885 return true;
886 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530887};
888
Carson Labradof52c03c2022-03-23 18:50:15 +0000889class HttpClient
890{
891 private:
892 std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
893 connectionPools;
Ed Tanous4b712a22023-08-02 12:56:52 -0700894
895 // reference_wrapper here makes HttpClient movable
896 std::reference_wrapper<boost::asio::io_context> ioc;
Carson Labradod14a48f2023-02-22 00:24:54 +0000897 std::shared_ptr<ConnectionPolicy> connPolicy;
Carson Labradof52c03c2022-03-23 18:50:15 +0000898
Carson Labrado039a47e2022-04-05 16:03:20 +0000899 // Used as a dummy callback by sendData() in order to call
900 // sendDataWithCallback()
Ed Tanous02cad962022-06-30 16:50:15 -0700901 static void genericResHandler(const Response& res)
Carson Labrado039a47e2022-04-05 16:03:20 +0000902 {
Ed Tanous62598e32023-07-17 17:06:25 -0700903 BMCWEB_LOG_DEBUG("Response handled with return code: {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700904 res.resultInt());
Ed Tanous4ee8e212022-05-28 09:42:51 -0700905 }
Carson Labrado039a47e2022-04-05 16:03:20 +0000906
Carson Labradof52c03c2022-03-23 18:50:15 +0000907 public:
Carson Labradod14a48f2023-02-22 00:24:54 +0000908 HttpClient() = delete;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700909 explicit HttpClient(boost::asio::io_context& iocIn,
910 const std::shared_ptr<ConnectionPolicy>& connPolicyIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400911 ioc(iocIn), connPolicy(connPolicyIn)
Carson Labradod14a48f2023-02-22 00:24:54 +0000912 {}
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700913
Carson Labradof52c03c2022-03-23 18:50:15 +0000914 HttpClient(const HttpClient&) = delete;
915 HttpClient& operator=(const HttpClient&) = delete;
Ed Tanous4b712a22023-08-02 12:56:52 -0700916 HttpClient(HttpClient&& client) = default;
917 HttpClient& operator=(HttpClient&& client) = default;
Carson Labradof52c03c2022-03-23 18:50:15 +0000918 ~HttpClient() = default;
919
Ed Tanousa716aa72023-08-01 11:35:53 -0700920 // Send a request to destIP where additional processing of the
Carson Labrado039a47e2022-04-05 16:03:20 +0000921 // result is not required
Ed Tanous4a7fbef2024-04-06 16:03:49 -0700922 void sendData(std::string&& data, const boost::urls::url_view_base& destUri,
Ed Tanous19bb3622024-07-05 10:07:40 -0500923 ensuressl::VerifyCertificate verifyCert,
Carson Labradof52c03c2022-03-23 18:50:15 +0000924 const boost::beast::http::fields& httpHeader,
Carson Labradod14a48f2023-02-22 00:24:54 +0000925 const boost::beast::http::verb verb)
Carson Labradof52c03c2022-03-23 18:50:15 +0000926 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000927 const std::function<void(Response&)> cb = genericResHandler;
Ed Tanous19bb3622024-07-05 10:07:40 -0500928 sendDataWithCallback(std::move(data), destUri, verifyCert, httpHeader,
929 verb, cb);
Carson Labrado039a47e2022-04-05 16:03:20 +0000930 }
931
Ed Tanousa716aa72023-08-01 11:35:53 -0700932 // Send request to destIP and use the provided callback to
Carson Labrado039a47e2022-04-05 16:03:20 +0000933 // handle the response
Ed Tanous4a7fbef2024-04-06 16:03:49 -0700934 void sendDataWithCallback(std::string&& data,
935 const boost::urls::url_view_base& destUrl,
Ed Tanous19bb3622024-07-05 10:07:40 -0500936 ensuressl::VerifyCertificate verifyCert,
Carson Labrado039a47e2022-04-05 16:03:20 +0000937 const boost::beast::http::fields& httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000938 const boost::beast::http::verb verb,
Ed Tanous6b3db602022-06-28 19:41:44 -0700939 const std::function<void(Response&)>& resHandler)
Carson Labrado039a47e2022-04-05 16:03:20 +0000940 {
Ed Tanous19bb3622024-07-05 10:07:40 -0500941 std::string_view verify = "ssl_verify";
942 if (verifyCert == ensuressl::VerifyCertificate::NoVerify)
943 {
944 verify = "ssl no verify";
945 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400946 std::string clientKey =
947 std::format("{}{}://{}", verify, destUrl.scheme(),
948 destUrl.encoded_host_and_port());
Carson Labradod14a48f2023-02-22 00:24:54 +0000949 auto pool = connectionPools.try_emplace(clientKey);
950 if (pool.first->second == nullptr)
Carson Labradof52c03c2022-03-23 18:50:15 +0000951 {
Carson Labradod14a48f2023-02-22 00:24:54 +0000952 pool.first->second = std::make_shared<ConnectionPool>(
Ed Tanous19bb3622024-07-05 10:07:40 -0500953 ioc, clientKey, connPolicy, destUrl, verifyCert);
Carson Labradof52c03c2022-03-23 18:50:15 +0000954 }
Ed Tanous27b0cf92023-08-07 12:02:40 -0700955 // Send the data using either the existing connection pool or the
956 // newly created connection pool
Ed Tanousa716aa72023-08-01 11:35:53 -0700957 pool.first->second->sendData(std::move(data), destUrl, httpHeader, verb,
Carson Labradod14a48f2023-02-22 00:24:54 +0000958 resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000959 }
Myung Baea0969c72024-09-19 08:46:45 -0400960
961 // Test whether all connections are terminated (after MaxRetryAttempts)
962 bool isTerminated()
963 {
964 for (const auto& pool : connectionPools)
965 {
966 if (pool.second != nullptr &&
967 !pool.second->areAllConnectionsTerminated())
968 {
969 BMCWEB_LOG_DEBUG(
970 "Not all of client connections are terminated");
971 return false;
972 }
973 }
974 BMCWEB_LOG_DEBUG("All client connections are terminated");
975 return true;
976 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000977};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530978} // namespace crow