blob: 58078c4b0ac24bb5483882c6d39649a10696d91a [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
6#include "async_resolve.hpp"
Ed Tanousb2896142024-01-31 15:25:47 -08007#include "http_body.hpp"
Nan Zhou77665bd2022-10-12 20:28:58 +00008#include "http_response.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08009#include "logging.hpp"
10#include "ssl_key_handler.hpp"
Nan Zhou77665bd2022-10-12 20:28:58 +000011
Ed Tanous0d5f5cf2022-03-12 15:30:55 -080012#include <boost/asio/connect.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070013#include <boost/asio/io_context.hpp>
Sunitha Harish29a82b02021-02-18 15:54:16 +053014#include <boost/asio/ip/address.hpp>
15#include <boost/asio/ip/basic_endpoint.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070016#include <boost/asio/ip/tcp.hpp>
AppaRao Pulie38778a2022-06-27 23:09:03 +000017#include <boost/asio/ssl/context.hpp>
18#include <boost/asio/ssl/error.hpp>
Ed Tanous003301a2024-04-16 09:59:19 -070019#include <boost/asio/ssl/stream.hpp>
Ed Tanousd43cd0c2020-09-30 20:46:53 -070020#include <boost/asio/steady_timer.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070021#include <boost/beast/core/flat_static_buffer.hpp>
Ed Tanousd43cd0c2020-09-30 20:46:53 -070022#include <boost/beast/http/message.hpp>
Ed Tanous4d698612024-02-06 14:57:24 -080023#include <boost/beast/http/message_generator.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070024#include <boost/beast/http/parser.hpp>
25#include <boost/beast/http/read.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070026#include <boost/beast/http/write.hpp>
Carson Labradof52c03c2022-03-23 18:50:15 +000027#include <boost/container/devector.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070028#include <boost/system/error_code.hpp>
Ed Tanous27b0cf92023-08-07 12:02:40 -070029#include <boost/url/format.hpp>
30#include <boost/url/url.hpp>
Ed Tanous4a7fbef2024-04-06 16:03:49 -070031#include <boost/url/url_view_base.hpp>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050032
AppaRao Pulibd030d02020-03-20 03:34:29 +053033#include <cstdlib>
34#include <functional>
AppaRao Pulibd030d02020-03-20 03:34:29 +053035#include <memory>
AppaRao Puli2a5689a2020-04-29 15:24:31 +053036#include <queue>
AppaRao Pulibd030d02020-03-20 03:34:29 +053037#include <string>
38
39namespace crow
40{
Ed Tanous27b0cf92023-08-07 12:02:40 -070041// With Redfish Aggregation it is assumed we will connect to another
42// instance of BMCWeb which can handle 100 simultaneous connections.
Carson Labrado66d90c22022-12-07 22:34:33 +000043constexpr size_t maxPoolSize = 20;
44constexpr size_t maxRequestQueueSize = 500;
Carson Labrado17dcc312022-07-28 22:17:28 +000045constexpr unsigned int httpReadBodyLimit = 131072;
Carson Labrado4d942722022-06-22 22:16:10 +000046constexpr unsigned int httpReadBufferSize = 4096;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053047
AppaRao Pulibd030d02020-03-20 03:34:29 +053048enum class ConnState
49{
AppaRao Puli2a5689a2020-04-29 15:24:31 +053050 initialized,
Sunitha Harish29a82b02021-02-18 15:54:16 +053051 resolveInProgress,
52 resolveFailed,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053053 connectInProgress,
54 connectFailed,
AppaRao Pulibd030d02020-03-20 03:34:29 +053055 connected,
AppaRao Pulie38778a2022-06-27 23:09:03 +000056 handshakeInProgress,
57 handshakeFailed,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053058 sendInProgress,
59 sendFailed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053060 recvInProgress,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053061 recvFailed,
62 idle,
Ayushi Smritife44eb02020-05-15 15:24:45 +053063 closed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053064 suspended,
65 terminated,
66 abortConnection,
AppaRao Pulie38778a2022-06-27 23:09:03 +000067 sslInitFailed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053068 retry
AppaRao Pulibd030d02020-03-20 03:34:29 +053069};
70
Ed Tanous4ff0f1f2024-09-04 17:27:37 -070071inline boost::system::error_code defaultRetryHandler(unsigned int respCode)
Carson Labradoa7a80292022-06-01 16:01:52 +000072{
73 // As a default, assume 200X is alright
Ed Tanous62598e32023-07-17 17:06:25 -070074 BMCWEB_LOG_DEBUG("Using default check for response code validity");
Carson Labradoa7a80292022-06-01 16:01:52 +000075 if ((respCode < 200) || (respCode >= 300))
76 {
77 return boost::system::errc::make_error_code(
78 boost::system::errc::result_out_of_range);
79 }
80
81 // Return 0 if the response code is valid
82 return boost::system::errc::make_error_code(boost::system::errc::success);
83};
84
Ed Tanous27b0cf92023-08-07 12:02:40 -070085// We need to allow retry information to be set before a message has been
86// sent and a connection pool has been created
Carson Labradod14a48f2023-02-22 00:24:54 +000087struct ConnectionPolicy
Carson Labradof52c03c2022-03-23 18:50:15 +000088{
89 uint32_t maxRetryAttempts = 5;
Carson Labradod14a48f2023-02-22 00:24:54 +000090
91 // the max size of requests in bytes. 0 for unlimited
92 boost::optional<uint64_t> requestByteLimit = httpReadBodyLimit;
93
94 size_t maxConnections = 1;
95
Carson Labradof52c03c2022-03-23 18:50:15 +000096 std::string retryPolicyAction = "TerminateAfterRetries";
Carson Labradod14a48f2023-02-22 00:24:54 +000097
98 std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0);
Carson Labradoa7a80292022-06-01 16:01:52 +000099 std::function<boost::system::error_code(unsigned int respCode)>
100 invalidResp = defaultRetryHandler;
Carson Labradof52c03c2022-03-23 18:50:15 +0000101};
102
103struct PendingRequest
104{
Ed Tanousb2896142024-01-31 15:25:47 -0800105 boost::beast::http::request<bmcweb::HttpBody> req;
Carson Labrado039a47e2022-04-05 16:03:20 +0000106 std::function<void(bool, uint32_t, Response&)> callback;
Carson Labrado039a47e2022-04-05 16:03:20 +0000107 PendingRequest(
Ed Tanousb2896142024-01-31 15:25:47 -0800108 boost::beast::http::request<bmcweb::HttpBody>&& reqIn,
Carson Labradod14a48f2023-02-22 00:24:54 +0000109 const std::function<void(bool, uint32_t, Response&)>& callbackIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400110 req(std::move(reqIn)), callback(callbackIn)
Carson Labradof52c03c2022-03-23 18:50:15 +0000111 {}
112};
113
Ed Tanouse01d0c32023-06-30 13:21:32 -0700114namespace http = boost::beast::http;
Carson Labradof52c03c2022-03-23 18:50:15 +0000115class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo>
AppaRao Pulibd030d02020-03-20 03:34:29 +0530116{
117 private:
Carson Labradof52c03c2022-03-23 18:50:15 +0000118 ConnState state = ConnState::initialized;
119 uint32_t retryCount = 0;
Carson Labradof52c03c2022-03-23 18:50:15 +0000120 std::string subId;
Carson Labradod14a48f2023-02-22 00:24:54 +0000121 std::shared_ptr<ConnectionPolicy> connPolicy;
Ed Tanousa716aa72023-08-01 11:35:53 -0700122 boost::urls::url host;
Ed Tanous19bb3622024-07-05 10:07:40 -0500123 ensuressl::VerifyCertificate verifyCert;
Carson Labradof52c03c2022-03-23 18:50:15 +0000124 uint32_t connId;
Carson Labradof52c03c2022-03-23 18:50:15 +0000125 // Data buffers
Ed Tanousb2896142024-01-31 15:25:47 -0800126 http::request<bmcweb::HttpBody> req;
127 using parser_type = http::response_parser<bmcweb::HttpBody>;
Ed Tanouse01d0c32023-06-30 13:21:32 -0700128 std::optional<parser_type> parser;
Carson Labrado4d942722022-06-22 22:16:10 +0000129 boost::beast::flat_static_buffer<httpReadBufferSize> buffer;
Carson Labrado039a47e2022-04-05 16:03:20 +0000130 Response res;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530131
Carson Labradof52c03c2022-03-23 18:50:15 +0000132 // Ascync callables
Carson Labrado039a47e2022-04-05 16:03:20 +0000133 std::function<void(bool, uint32_t, Response&)> callback;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700134
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600135 boost::asio::io_context& ioc;
136
Ed Tanous25b54db2024-04-17 15:40:31 -0700137 using Resolver = std::conditional_t<BMCWEB_DNS_RESOLVER == "systemd-dbus",
138 async_resolve::Resolver,
139 boost::asio::ip::tcp::resolver>;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700140 Resolver resolver;
141
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800142 boost::asio::ip::tcp::socket conn;
Ed Tanous003301a2024-04-16 09:59:19 -0700143 std::optional<boost::asio::ssl::stream<boost::asio::ip::tcp::socket&>>
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800144 sslConn;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000145
Carson Labradof52c03c2022-03-23 18:50:15 +0000146 boost::asio::steady_timer timer;
Ed Tanous84b35602021-09-08 20:06:32 -0700147
Carson Labradof52c03c2022-03-23 18:50:15 +0000148 friend class ConnectionPool;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530149
Sunitha Harish29a82b02021-02-18 15:54:16 +0530150 void doResolve()
151 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530152 state = ConnState::resolveInProgress;
Ed Tanousa716aa72023-08-01 11:35:53 -0700153 BMCWEB_LOG_DEBUG("Trying to resolve: {}, id: {}", host, connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530154
Ed Tanousa716aa72023-08-01 11:35:53 -0700155 resolver.async_resolve(host.encoded_host_address(), host.port(),
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700156 std::bind_front(&ConnectionInfo::afterResolve,
157 this, shared_from_this()));
Sunitha Harish29a82b02021-02-18 15:54:16 +0530158 }
159
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700160 void afterResolve(const std::shared_ptr<ConnectionInfo>& /*self*/,
161 const boost::system::error_code& ec,
162 const Resolver::results_type& endpointList)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530163 {
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700164 if (ec || (endpointList.empty()))
165 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700166 BMCWEB_LOG_ERROR("Resolve failed: {} {}", ec.message(), host);
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700167 state = ConnState::resolveFailed;
168 waitAndRetry();
169 return;
170 }
Ed Tanousa716aa72023-08-01 11:35:53 -0700171 BMCWEB_LOG_DEBUG("Resolved {}, id: {}", host, connId);
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530172 state = ConnState::connectInProgress;
173
Ed Tanousa716aa72023-08-01 11:35:53 -0700174 BMCWEB_LOG_DEBUG("Trying to connect to: {}, id: {}", host, connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530175
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800176 timer.expires_after(std::chrono::seconds(30));
177 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
178
179 boost::asio::async_connect(
180 conn, endpointList,
181 std::bind_front(&ConnectionInfo::afterConnect, this,
182 shared_from_this()));
AppaRao Pulie38778a2022-06-27 23:09:03 +0000183 }
184
185 void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/,
Ed Tanous81c4e332023-05-18 10:30:34 -0700186 const boost::beast::error_code& ec,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000187 const boost::asio::ip::tcp::endpoint& endpoint)
188 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000189 // The operation already timed out. We don't want do continue down
190 // this branch
191 if (ec && ec == boost::asio::error::operation_aborted)
192 {
193 return;
194 }
195
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800196 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000197 if (ec)
198 {
Ed Tanous62598e32023-07-17 17:06:25 -0700199 BMCWEB_LOG_ERROR("Connect {}:{}, id: {} failed: {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700200 endpoint.address().to_string(), endpoint.port(),
201 connId, ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000202 state = ConnState::connectFailed;
203 waitAndRetry();
204 return;
205 }
Ed Tanousa716aa72023-08-01 11:35:53 -0700206 BMCWEB_LOG_DEBUG("Connected to: {}:{}, id: {}",
207 endpoint.address().to_string(), endpoint.port(),
208 connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000209 if (sslConn)
210 {
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800211 doSslHandshake();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000212 return;
213 }
214 state = ConnState::connected;
215 sendMessage();
216 }
217
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800218 void doSslHandshake()
AppaRao Pulie38778a2022-06-27 23:09:03 +0000219 {
220 if (!sslConn)
221 {
222 return;
223 }
224 state = ConnState::handshakeInProgress;
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800225 timer.expires_after(std::chrono::seconds(30));
226 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
AppaRao Pulie38778a2022-06-27 23:09:03 +0000227 sslConn->async_handshake(
228 boost::asio::ssl::stream_base::client,
229 std::bind_front(&ConnectionInfo::afterSslHandshake, this,
230 shared_from_this()));
231 }
232
233 void afterSslHandshake(const std::shared_ptr<ConnectionInfo>& /*self*/,
Ed Tanous81c4e332023-05-18 10:30:34 -0700234 const boost::beast::error_code& ec)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000235 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000236 // The operation already timed out. We don't want do continue down
237 // this branch
238 if (ec && ec == boost::asio::error::operation_aborted)
239 {
240 return;
241 }
242
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800243 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000244 if (ec)
245 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700246 BMCWEB_LOG_ERROR("SSL Handshake failed - id: {} error: {}", connId,
247 ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000248 state = ConnState::handshakeFailed;
249 waitAndRetry();
250 return;
251 }
Ed Tanousa716aa72023-08-01 11:35:53 -0700252 BMCWEB_LOG_DEBUG("SSL Handshake successful - id: {}", connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000253 state = ConnState::connected;
254 sendMessage();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530255 }
256
Carson Labradof52c03c2022-03-23 18:50:15 +0000257 void sendMessage()
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530258 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530259 state = ConnState::sendInProgress;
260
AppaRao Pulibd030d02020-03-20 03:34:29 +0530261 // Set a timeout on the operation
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800262 timer.expires_after(std::chrono::seconds(30));
263 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
AppaRao Pulibd030d02020-03-20 03:34:29 +0530264 // Send the HTTP request to the remote host
AppaRao Pulie38778a2022-06-27 23:09:03 +0000265 if (sslConn)
266 {
Ed Tanouscd504a92024-08-19 11:46:20 -0700267 boost::beast::http::async_write(
268 *sslConn, req,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000269 std::bind_front(&ConnectionInfo::afterWrite, this,
270 shared_from_this()));
271 }
272 else
273 {
Ed Tanouscd504a92024-08-19 11:46:20 -0700274 boost::beast::http::async_write(
275 conn, req,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000276 std::bind_front(&ConnectionInfo::afterWrite, this,
277 shared_from_this()));
278 }
279 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530280
AppaRao Pulie38778a2022-06-27 23:09:03 +0000281 void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/,
282 const boost::beast::error_code& ec, size_t bytesTransferred)
283 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000284 // The operation already timed out. We don't want do continue down
285 // this branch
286 if (ec && ec == boost::asio::error::operation_aborted)
287 {
288 return;
289 }
290
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800291 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000292 if (ec)
293 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700294 BMCWEB_LOG_ERROR("sendMessage() failed: {} {}", ec.message(), host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000295 state = ConnState::sendFailed;
296 waitAndRetry();
297 return;
298 }
Ed Tanous62598e32023-07-17 17:06:25 -0700299 BMCWEB_LOG_DEBUG("sendMessage() bytes transferred: {}",
300 bytesTransferred);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000301
302 recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530303 }
304
305 void recvMessage()
306 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530307 state = ConnState::recvInProgress;
308
Ed Tanous38afdb92024-12-11 23:57:53 -0800309 parser_type& thisParser = parser.emplace();
Carson Labradod14a48f2023-02-22 00:24:54 +0000310
Ed Tanouse01d0c32023-06-30 13:21:32 -0700311 thisParser.body_limit(connPolicy->requestByteLimit);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530312
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800313 timer.expires_after(std::chrono::seconds(30));
314 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
315
AppaRao Pulibd030d02020-03-20 03:34:29 +0530316 // Receive the HTTP response
AppaRao Pulie38778a2022-06-27 23:09:03 +0000317 if (sslConn)
318 {
319 boost::beast::http::async_read(
Ed Tanouse01d0c32023-06-30 13:21:32 -0700320 *sslConn, buffer, thisParser,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000321 std::bind_front(&ConnectionInfo::afterRead, this,
322 shared_from_this()));
323 }
324 else
325 {
326 boost::beast::http::async_read(
Ed Tanouse01d0c32023-06-30 13:21:32 -0700327 conn, buffer, thisParser,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000328 std::bind_front(&ConnectionInfo::afterRead, this,
329 shared_from_this()));
330 }
331 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530332
AppaRao Pulie38778a2022-06-27 23:09:03 +0000333 void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/,
334 const boost::beast::error_code& ec,
335 const std::size_t& bytesTransferred)
336 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000337 // The operation already timed out. We don't want do continue down
338 // this branch
339 if (ec && ec == boost::asio::error::operation_aborted)
340 {
341 return;
342 }
343
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800344 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000345 if (ec && ec != boost::asio::ssl::error::stream_truncated)
346 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700347 BMCWEB_LOG_ERROR("recvMessage() failed: {} from {}", ec.message(),
348 host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000349 state = ConnState::recvFailed;
350 waitAndRetry();
351 return;
352 }
Ed Tanous62598e32023-07-17 17:06:25 -0700353 BMCWEB_LOG_DEBUG("recvMessage() bytes transferred: {}",
354 bytesTransferred);
Ed Tanouse01d0c32023-06-30 13:21:32 -0700355 if (!parser)
356 {
357 return;
358 }
Ed Tanous52e31622024-01-23 16:31:11 -0800359 BMCWEB_LOG_DEBUG("recvMessage() data: {}", parser->get().body().str());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000360
361 unsigned int respCode = parser->get().result_int();
Ed Tanous62598e32023-07-17 17:06:25 -0700362 BMCWEB_LOG_DEBUG("recvMessage() Header Response Code: {}", respCode);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000363
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600364 // Handle the case of stream_truncated. Some servers close the ssl
365 // connection uncleanly, so check to see if we got a full response
366 // before we handle this as an error.
367 if (!parser->is_done())
368 {
369 state = ConnState::recvFailed;
370 waitAndRetry();
371 return;
372 }
373
AppaRao Pulie38778a2022-06-27 23:09:03 +0000374 // Make sure the received response code is valid as defined by
375 // the associated retry policy
Carson Labradod14a48f2023-02-22 00:24:54 +0000376 if (connPolicy->invalidResp(respCode))
AppaRao Pulie38778a2022-06-27 23:09:03 +0000377 {
378 // The listener failed to receive the Sent-Event
Ed Tanous62598e32023-07-17 17:06:25 -0700379 BMCWEB_LOG_ERROR(
380 "recvMessage() Listener Failed to "
Ed Tanousa716aa72023-08-01 11:35:53 -0700381 "receive Sent-Event. Header Response Code: {} from {}",
382 respCode, host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000383 state = ConnState::recvFailed;
384 waitAndRetry();
385 return;
386 }
Ed Tanous002d39b2022-05-31 08:59:27 -0700387
AppaRao Pulie38778a2022-06-27 23:09:03 +0000388 // Send is successful
389 // Reset the counter just in case this was after retrying
390 retryCount = 0;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530391
AppaRao Pulie38778a2022-06-27 23:09:03 +0000392 // Keep the connection alive if server supports it
393 // Else close the connection
Ed Tanous62598e32023-07-17 17:06:25 -0700394 BMCWEB_LOG_DEBUG("recvMessage() keepalive : {}", parser->keep_alive());
AppaRao Pulibd030d02020-03-20 03:34:29 +0530395
AppaRao Pulie38778a2022-06-27 23:09:03 +0000396 // Copy the response into a Response object so that it can be
397 // processed by the callback function.
Ed Tanous27b0cf92023-08-07 12:02:40 -0700398 res.response = parser->release();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000399 callback(parser->keep_alive(), connId, res);
Carson Labrado513d1ff2022-07-19 00:38:15 +0000400 res.clear();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530401 }
402
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800403 static void onTimeout(const std::weak_ptr<ConnectionInfo>& weakSelf,
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800404 const boost::system::error_code& ec)
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800405 {
406 if (ec == boost::asio::error::operation_aborted)
407 {
Ed Tanous62598e32023-07-17 17:06:25 -0700408 BMCWEB_LOG_DEBUG(
409 "async_wait failed since the operation is aborted");
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800410 return;
411 }
412 if (ec)
413 {
Ed Tanous62598e32023-07-17 17:06:25 -0700414 BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
Ed Tanous27b0cf92023-08-07 12:02:40 -0700415 // If the timer fails, we need to close the socket anyway, same
416 // as if it expired.
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800417 }
418 std::shared_ptr<ConnectionInfo> self = weakSelf.lock();
419 if (self == nullptr)
420 {
421 return;
422 }
423 self->waitAndRetry();
424 }
425
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530426 void waitAndRetry()
AppaRao Pulibd030d02020-03-20 03:34:29 +0530427 {
Carson Labradod14a48f2023-02-22 00:24:54 +0000428 if ((retryCount >= connPolicy->maxRetryAttempts) ||
AppaRao Pulie38778a2022-06-27 23:09:03 +0000429 (state == ConnState::sslInitFailed))
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530430 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700431 BMCWEB_LOG_ERROR("Maximum number of retries reached. {}", host);
Ed Tanous62598e32023-07-17 17:06:25 -0700432 BMCWEB_LOG_DEBUG("Retry policy: {}", connPolicy->retryPolicyAction);
Carson Labrado039a47e2022-04-05 16:03:20 +0000433
Carson Labradod14a48f2023-02-22 00:24:54 +0000434 if (connPolicy->retryPolicyAction == "TerminateAfterRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530435 {
436 // TODO: delete subscription
437 state = ConnState::terminated;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530438 }
Carson Labradod14a48f2023-02-22 00:24:54 +0000439 if (connPolicy->retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530440 {
441 state = ConnState::suspended;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530442 }
Carson Labrado513d1ff2022-07-19 00:38:15 +0000443
444 // We want to return a 502 to indicate there was an error with
445 // the external server
446 res.result(boost::beast::http::status::bad_gateway);
447 callback(false, connId, res);
448 res.clear();
449
Ed Tanous27b0cf92023-08-07 12:02:40 -0700450 // Reset the retrycount to zero so that client can try
451 // connecting again if needed
Ed Tanous3174e4d2020-10-07 11:41:22 -0700452 retryCount = 0;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530453 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530454 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530455
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530456 retryCount++;
457
Ed Tanous62598e32023-07-17 17:06:25 -0700458 BMCWEB_LOG_DEBUG("Attempt retry after {} seconds. RetryCount = {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700459 connPolicy->retryIntervalSecs.count(), retryCount);
Carson Labradod14a48f2023-02-22 00:24:54 +0000460 timer.expires_after(connPolicy->retryIntervalSecs);
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700461 timer.async_wait(std::bind_front(&ConnectionInfo::onTimerDone, this,
462 shared_from_this()));
463 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530464
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700465 void onTimerDone(const std::shared_ptr<ConnectionInfo>& /*self*/,
466 const boost::system::error_code& ec)
467 {
468 if (ec == boost::asio::error::operation_aborted)
469 {
Ed Tanous62598e32023-07-17 17:06:25 -0700470 BMCWEB_LOG_DEBUG(
471 "async_wait failed since the operation is aborted{}",
472 ec.message());
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700473 }
474 else if (ec)
475 {
Ed Tanous62598e32023-07-17 17:06:25 -0700476 BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700477 // Ignore the error and continue the retry loop to attempt
478 // sending the event as per the retry policy
479 }
480
481 // Let's close the connection and restart from resolve.
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600482 shutdownConn(true);
483 }
484
485 void restartConnection()
486 {
487 BMCWEB_LOG_DEBUG("{}, id: {} restartConnection", host,
488 std::to_string(connId));
489 initializeConnection(host.scheme() == "https");
490 doResolve();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530491 }
492
AppaRao Pulie38778a2022-06-27 23:09:03 +0000493 void shutdownConn(bool retry)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530494 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000495 boost::beast::error_code ec;
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800496 conn.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
Carson Labradof52c03c2022-03-23 18:50:15 +0000497 conn.close();
498
499 // not_connected happens sometimes so don't bother reporting it.
500 if (ec && ec != boost::beast::errc::not_connected)
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530501 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700502 BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId,
Ed Tanous62598e32023-07-17 17:06:25 -0700503 ec.message());
Carson Labradof52c03c2022-03-23 18:50:15 +0000504 }
Carson Labrado5cab68f2022-07-11 22:26:21 +0000505 else
506 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700507 BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId);
Carson Labrado5cab68f2022-07-11 22:26:21 +0000508 }
Ed Tanousca723762022-06-28 19:40:39 -0700509
Carson Labrado513d1ff2022-07-19 00:38:15 +0000510 if (retry)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000511 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000512 // Now let's try to resend the data
513 state = ConnState::retry;
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600514 restartConnection();
Carson Labrado513d1ff2022-07-19 00:38:15 +0000515 }
516 else
517 {
518 state = ConnState::closed;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000519 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000520 }
521
AppaRao Pulie38778a2022-06-27 23:09:03 +0000522 void doClose(bool retry = false)
Carson Labradof52c03c2022-03-23 18:50:15 +0000523 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000524 if (!sslConn)
525 {
526 shutdownConn(retry);
527 return;
528 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000529
AppaRao Pulie38778a2022-06-27 23:09:03 +0000530 sslConn->async_shutdown(
531 std::bind_front(&ConnectionInfo::afterSslShutdown, this,
532 shared_from_this(), retry));
533 }
534
535 void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/,
536 bool retry, const boost::system::error_code& ec)
537 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000538 if (ec)
Carson Labradof52c03c2022-03-23 18:50:15 +0000539 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700540 BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId,
Ed Tanous62598e32023-07-17 17:06:25 -0700541 ec.message());
Carson Labradof52c03c2022-03-23 18:50:15 +0000542 }
Carson Labrado5cab68f2022-07-11 22:26:21 +0000543 else
544 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700545 BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId);
Carson Labrado5cab68f2022-07-11 22:26:21 +0000546 }
AppaRao Pulie38778a2022-06-27 23:09:03 +0000547 shutdownConn(retry);
548 }
Ed Tanousca723762022-06-28 19:40:39 -0700549
AppaRao Pulie38778a2022-06-27 23:09:03 +0000550 void setCipherSuiteTLSext()
551 {
552 if (!sslConn)
553 {
554 return;
555 }
Ravi Tejae7c29912023-07-31 09:39:32 -0500556
557 if (host.host_type() != boost::urls::host_type::name)
558 {
559 // Avoid setting SNI hostname if its IP address
560 return;
561 }
562 // Create a null terminated string for SSL
Ed Tanousa716aa72023-08-01 11:35:53 -0700563 std::string hostname(host.encoded_host_address());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000564 // NOTE: The SSL_set_tlsext_host_name is defined in tlsv1.h header
565 // file but its having old style casting (name is cast to void*).
566 // Since bmcweb compiler treats all old-style-cast as error, its
567 // causing the build failure. So replaced the same macro inline and
568 // did corrected the code by doing static_cast to viod*. This has to
569 // be fixed in openssl library in long run. Set SNI Hostname (many
570 // hosts need this to handshake successfully)
571 if (SSL_ctrl(sslConn->native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME,
572 TLSEXT_NAMETYPE_host_name,
Ed Tanousa716aa72023-08-01 11:35:53 -0700573 static_cast<void*>(hostname.data())) == 0)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000574
575 {
576 boost::beast::error_code ec{static_cast<int>(::ERR_get_error()),
577 boost::asio::error::get_ssl_category()};
578
Ed Tanousa716aa72023-08-01 11:35:53 -0700579 BMCWEB_LOG_ERROR("SSL_set_tlsext_host_name {}, id: {} failed: {}",
580 host, connId, ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000581 // Set state as sslInit failed so that we close the connection
582 // and take appropriate action as per retry configuration.
583 state = ConnState::sslInitFailed;
584 waitAndRetry();
585 return;
586 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530587 }
588
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600589 void initializeConnection(bool ssl)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000590 {
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600591 conn = boost::asio::ip::tcp::socket(ioc);
592 if (ssl)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000593 {
594 std::optional<boost::asio::ssl::context> sslCtx =
Ed Tanous19bb3622024-07-05 10:07:40 -0500595 ensuressl::getSSLClientContext(verifyCert);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000596
597 if (!sslCtx)
598 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700599 BMCWEB_LOG_ERROR("prepareSSLContext failed - {}, id: {}", host,
600 connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000601 // Don't retry if failure occurs while preparing SSL context
Ed Tanous27b0cf92023-08-07 12:02:40 -0700602 // such as certificate is invalid or set cipher failure or
603 // set host name failure etc... Setting conn state to
604 // sslInitFailed and connection state will be transitioned
605 // to next state depending on retry policy set by
606 // subscription.
AppaRao Pulie38778a2022-06-27 23:09:03 +0000607 state = ConnState::sslInitFailed;
608 waitAndRetry();
609 return;
610 }
611 sslConn.emplace(conn, *sslCtx);
612 setCipherSuiteTLSext();
613 }
614 }
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600615
616 public:
617 explicit ConnectionInfo(
618 boost::asio::io_context& iocIn, const std::string& idIn,
619 const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
Ed Tanous19bb3622024-07-05 10:07:40 -0500620 const boost::urls::url_view_base& hostIn,
621 ensuressl::VerifyCertificate verifyCertIn, unsigned int connIdIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400622 subId(idIn), connPolicy(connPolicyIn), host(hostIn),
623 verifyCert(verifyCertIn), connId(connIdIn), ioc(iocIn), resolver(iocIn),
624 conn(iocIn), timer(iocIn)
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600625 {
626 initializeConnection(host.scheme() == "https");
627 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000628};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530629
Carson Labradof52c03c2022-03-23 18:50:15 +0000630class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
631{
632 private:
633 boost::asio::io_context& ioc;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000634 std::string id;
Carson Labradod14a48f2023-02-22 00:24:54 +0000635 std::shared_ptr<ConnectionPolicy> connPolicy;
Ed Tanousa716aa72023-08-01 11:35:53 -0700636 boost::urls::url destIP;
Carson Labradof52c03c2022-03-23 18:50:15 +0000637 std::vector<std::shared_ptr<ConnectionInfo>> connections;
638 boost::container::devector<PendingRequest> requestQueue;
Ed Tanous19bb3622024-07-05 10:07:40 -0500639 ensuressl::VerifyCertificate verifyCert;
Carson Labradof52c03c2022-03-23 18:50:15 +0000640
641 friend class HttpClient;
642
Carson Labrado244256c2022-04-27 17:16:32 +0000643 // Configure a connections's request, callback, and retry info in
644 // preparation to begin sending the request
Carson Labradof52c03c2022-03-23 18:50:15 +0000645 void setConnProps(ConnectionInfo& conn)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530646 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000647 if (requestQueue.empty())
AppaRao Pulibd030d02020-03-20 03:34:29 +0530648 {
Ed Tanous62598e32023-07-17 17:06:25 -0700649 BMCWEB_LOG_ERROR(
650 "setConnProps() should not have been called when requestQueue is empty");
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530651 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530652 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530653
Ed Tanous52e31622024-01-23 16:31:11 -0800654 PendingRequest& nextReq = requestQueue.front();
Carson Labrado244256c2022-04-27 17:16:32 +0000655 conn.req = std::move(nextReq.req);
656 conn.callback = std::move(nextReq.callback);
Carson Labradof52c03c2022-03-23 18:50:15 +0000657
Ed Tanousa716aa72023-08-01 11:35:53 -0700658 BMCWEB_LOG_DEBUG("Setting properties for connection {}, id: {}",
659 conn.host, conn.connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000660
661 // We can remove the request from the queue at this point
662 requestQueue.pop_front();
663 }
664
Carson Labradof52c03c2022-03-23 18:50:15 +0000665 // Gets called as part of callback after request is sent
666 // Reuses the connection if there are any requests waiting to be sent
667 // Otherwise closes the connection if it is not a keep-alive
668 void sendNext(bool keepAlive, uint32_t connId)
669 {
670 auto conn = connections[connId];
Carson Labrado46a81462022-04-27 21:11:37 +0000671
672 // Allow the connection's handler to be deleted
673 // This is needed because of Redfish Aggregation passing an
674 // AsyncResponse shared_ptr to this callback
675 conn->callback = nullptr;
676
Carson Labradof52c03c2022-03-23 18:50:15 +0000677 // Reuse the connection to send the next request in the queue
678 if (!requestQueue.empty())
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530679 {
Ed Tanous62598e32023-07-17 17:06:25 -0700680 BMCWEB_LOG_DEBUG(
Ed Tanous8ece0e42024-01-02 13:16:50 -0800681 "{} requests remaining in queue for {}, reusing connection {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700682 requestQueue.size(), destIP, connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000683
684 setConnProps(*conn);
685
686 if (keepAlive)
687 {
688 conn->sendMessage();
689 }
690 else
691 {
692 // Server is not keep-alive enabled so we need to close the
693 // connection and then start over from resolve
694 conn->doClose();
Abhilash Raju2ecde742024-06-01 02:01:01 -0500695 conn->restartConnection();
Carson Labradof52c03c2022-03-23 18:50:15 +0000696 }
697 return;
698 }
699
700 // No more messages to send so close the connection if necessary
701 if (keepAlive)
702 {
703 conn->state = ConnState::idle;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530704 }
705 else
706 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000707 // Abort the connection since server is not keep-alive enabled
708 conn->state = ConnState::abortConnection;
709 conn->doClose();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530710 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530711 }
712
Ed Tanous4a7fbef2024-04-06 16:03:49 -0700713 void sendData(std::string&& data, const boost::urls::url_view_base& destUri,
Carson Labrado244256c2022-04-27 17:16:32 +0000714 const boost::beast::http::fields& httpHeader,
715 const boost::beast::http::verb verb,
Ed Tanous6b3db602022-06-28 19:41:44 -0700716 const std::function<void(Response&)>& resHandler)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530717 {
Carson Labrado244256c2022-04-27 17:16:32 +0000718 // Construct the request to be sent
Ed Tanousb2896142024-01-31 15:25:47 -0800719 boost::beast::http::request<bmcweb::HttpBody> thisReq(
Ed Tanousa716aa72023-08-01 11:35:53 -0700720 verb, destUri.encoded_target(), 11, "", httpHeader);
721 thisReq.set(boost::beast::http::field::host,
722 destUri.encoded_host_address());
Carson Labrado244256c2022-04-27 17:16:32 +0000723 thisReq.keep_alive(true);
Ed Tanous52e31622024-01-23 16:31:11 -0800724 thisReq.body().str() = std::move(data);
Carson Labrado244256c2022-04-27 17:16:32 +0000725 thisReq.prepare_payload();
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700726 auto cb = std::bind_front(&ConnectionPool::afterSendData,
727 weak_from_this(), resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000728 // Reuse an existing connection if one is available
729 for (unsigned int i = 0; i < connections.size(); i++)
730 {
731 auto conn = connections[i];
732 if ((conn->state == ConnState::idle) ||
733 (conn->state == ConnState::initialized) ||
734 (conn->state == ConnState::closed))
735 {
Carson Labrado244256c2022-04-27 17:16:32 +0000736 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000737 conn->callback = std::move(cb);
Ed Tanousa716aa72023-08-01 11:35:53 -0700738 std::string commonMsg = std::format("{} from pool {}", i, id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000739
740 if (conn->state == ConnState::idle)
741 {
Ed Tanous62598e32023-07-17 17:06:25 -0700742 BMCWEB_LOG_DEBUG("Grabbing idle connection {}", commonMsg);
Carson Labradof52c03c2022-03-23 18:50:15 +0000743 conn->sendMessage();
744 }
745 else
746 {
Ed Tanous62598e32023-07-17 17:06:25 -0700747 BMCWEB_LOG_DEBUG("Reusing existing connection {}",
748 commonMsg);
Abhilash Raju2ecde742024-06-01 02:01:01 -0500749 conn->restartConnection();
Carson Labradof52c03c2022-03-23 18:50:15 +0000750 }
751 return;
752 }
753 }
754
Ed Tanous27b0cf92023-08-07 12:02:40 -0700755 // All connections in use so create a new connection or add request
756 // to the queue
Carson Labradod14a48f2023-02-22 00:24:54 +0000757 if (connections.size() < connPolicy->maxConnections)
Carson Labradof52c03c2022-03-23 18:50:15 +0000758 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700759 BMCWEB_LOG_DEBUG("Adding new connection to pool {}", id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000760 auto conn = addConnection();
Carson Labrado244256c2022-04-27 17:16:32 +0000761 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000762 conn->callback = std::move(cb);
Carson Labradof52c03c2022-03-23 18:50:15 +0000763 conn->doResolve();
764 }
765 else if (requestQueue.size() < maxRequestQueueSize)
766 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700767 BMCWEB_LOG_DEBUG("Max pool size reached. Adding data to queue {}",
768 id);
Carson Labradod14a48f2023-02-22 00:24:54 +0000769 requestQueue.emplace_back(std::move(thisReq), std::move(cb));
Carson Labradof52c03c2022-03-23 18:50:15 +0000770 }
771 else
772 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700773 // If we can't buffer the request then we should let the
774 // callback handle a 429 Too Many Requests dummy response
Ed Tanous6ea90762024-04-07 08:38:44 -0700775 BMCWEB_LOG_ERROR("{} request queue full. Dropping request.", id);
Carson Labrado43e14d32022-11-09 00:25:20 +0000776 Response dummyRes;
777 dummyRes.result(boost::beast::http::status::too_many_requests);
778 resHandler(dummyRes);
Carson Labradof52c03c2022-03-23 18:50:15 +0000779 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530780 }
781
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700782 // Callback to be called once the request has been sent
783 static void afterSendData(const std::weak_ptr<ConnectionPool>& weakSelf,
784 const std::function<void(Response&)>& resHandler,
785 bool keepAlive, uint32_t connId, Response& res)
786 {
787 // Allow provided callback to perform additional processing of the
788 // request
789 resHandler(res);
790
791 // If requests remain in the queue then we want to reuse this
792 // connection to send the next request
793 std::shared_ptr<ConnectionPool> self = weakSelf.lock();
794 if (!self)
795 {
Ed Tanous62598e32023-07-17 17:06:25 -0700796 BMCWEB_LOG_CRITICAL("{} Failed to capture connection",
797 logPtr(self.get()));
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700798 return;
799 }
800
801 self->sendNext(keepAlive, connId);
802 }
803
Carson Labradof52c03c2022-03-23 18:50:15 +0000804 std::shared_ptr<ConnectionInfo>& addConnection()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530805 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000806 unsigned int newId = static_cast<unsigned int>(connections.size());
807
AppaRao Pulie38778a2022-06-27 23:09:03 +0000808 auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>(
Ed Tanous19bb3622024-07-05 10:07:40 -0500809 ioc, id, connPolicy, destIP, verifyCert, newId));
Carson Labradof52c03c2022-03-23 18:50:15 +0000810
Ed Tanousa716aa72023-08-01 11:35:53 -0700811 BMCWEB_LOG_DEBUG("Added connection {} to pool {}",
812 connections.size() - 1, id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000813
814 return ret;
815 }
816
817 public:
Carson Labradod14a48f2023-02-22 00:24:54 +0000818 explicit ConnectionPool(
819 boost::asio::io_context& iocIn, const std::string& idIn,
820 const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
Ed Tanous19bb3622024-07-05 10:07:40 -0500821 const boost::urls::url_view_base& destIPIn,
822 ensuressl::VerifyCertificate verifyCertIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400823 ioc(iocIn), id(idIn), connPolicy(connPolicyIn), destIP(destIPIn),
Ed Tanous19bb3622024-07-05 10:07:40 -0500824 verifyCert(verifyCertIn)
Carson Labradof52c03c2022-03-23 18:50:15 +0000825 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700826 BMCWEB_LOG_DEBUG("Initializing connection pool for {}", id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000827
828 // Initialize the pool with a single connection
829 addConnection();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530830 }
Myung Baea0969c72024-09-19 08:46:45 -0400831
832 // Check whether all connections are terminated
833 bool areAllConnectionsTerminated()
834 {
835 if (connections.empty())
836 {
837 BMCWEB_LOG_DEBUG("There are no connections for pool id:{}", id);
838 return false;
839 }
840 for (const auto& conn : connections)
841 {
842 if (conn != nullptr && conn->state != ConnState::terminated)
843 {
844 BMCWEB_LOG_DEBUG(
845 "Not all connections of pool id:{} are terminated", id);
846 return false;
847 }
848 }
849 BMCWEB_LOG_INFO("All connections of pool id:{} are terminated", id);
850 return true;
851 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530852};
853
Carson Labradof52c03c2022-03-23 18:50:15 +0000854class HttpClient
855{
856 private:
857 std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
858 connectionPools;
Ed Tanous4b712a22023-08-02 12:56:52 -0700859
860 // reference_wrapper here makes HttpClient movable
861 std::reference_wrapper<boost::asio::io_context> ioc;
Carson Labradod14a48f2023-02-22 00:24:54 +0000862 std::shared_ptr<ConnectionPolicy> connPolicy;
Carson Labradof52c03c2022-03-23 18:50:15 +0000863
Carson Labrado039a47e2022-04-05 16:03:20 +0000864 // Used as a dummy callback by sendData() in order to call
865 // sendDataWithCallback()
Ed Tanous02cad962022-06-30 16:50:15 -0700866 static void genericResHandler(const Response& res)
Carson Labrado039a47e2022-04-05 16:03:20 +0000867 {
Ed Tanous62598e32023-07-17 17:06:25 -0700868 BMCWEB_LOG_DEBUG("Response handled with return code: {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700869 res.resultInt());
Ed Tanous4ee8e212022-05-28 09:42:51 -0700870 }
Carson Labrado039a47e2022-04-05 16:03:20 +0000871
Carson Labradof52c03c2022-03-23 18:50:15 +0000872 public:
Carson Labradod14a48f2023-02-22 00:24:54 +0000873 HttpClient() = delete;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700874 explicit HttpClient(boost::asio::io_context& iocIn,
875 const std::shared_ptr<ConnectionPolicy>& connPolicyIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400876 ioc(iocIn), connPolicy(connPolicyIn)
Carson Labradod14a48f2023-02-22 00:24:54 +0000877 {}
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700878
Carson Labradof52c03c2022-03-23 18:50:15 +0000879 HttpClient(const HttpClient&) = delete;
880 HttpClient& operator=(const HttpClient&) = delete;
Ed Tanous4b712a22023-08-02 12:56:52 -0700881 HttpClient(HttpClient&& client) = default;
882 HttpClient& operator=(HttpClient&& client) = default;
Carson Labradof52c03c2022-03-23 18:50:15 +0000883 ~HttpClient() = default;
884
Ed Tanousa716aa72023-08-01 11:35:53 -0700885 // Send a request to destIP where additional processing of the
Carson Labrado039a47e2022-04-05 16:03:20 +0000886 // result is not required
Ed Tanous4a7fbef2024-04-06 16:03:49 -0700887 void sendData(std::string&& data, const boost::urls::url_view_base& destUri,
Ed Tanous19bb3622024-07-05 10:07:40 -0500888 ensuressl::VerifyCertificate verifyCert,
Carson Labradof52c03c2022-03-23 18:50:15 +0000889 const boost::beast::http::fields& httpHeader,
Carson Labradod14a48f2023-02-22 00:24:54 +0000890 const boost::beast::http::verb verb)
Carson Labradof52c03c2022-03-23 18:50:15 +0000891 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000892 const std::function<void(Response&)> cb = genericResHandler;
Ed Tanous19bb3622024-07-05 10:07:40 -0500893 sendDataWithCallback(std::move(data), destUri, verifyCert, httpHeader,
894 verb, cb);
Carson Labrado039a47e2022-04-05 16:03:20 +0000895 }
896
Ed Tanousa716aa72023-08-01 11:35:53 -0700897 // Send request to destIP and use the provided callback to
Carson Labrado039a47e2022-04-05 16:03:20 +0000898 // handle the response
Ed Tanous4a7fbef2024-04-06 16:03:49 -0700899 void sendDataWithCallback(std::string&& data,
900 const boost::urls::url_view_base& destUrl,
Ed Tanous19bb3622024-07-05 10:07:40 -0500901 ensuressl::VerifyCertificate verifyCert,
Carson Labrado039a47e2022-04-05 16:03:20 +0000902 const boost::beast::http::fields& httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000903 const boost::beast::http::verb verb,
Ed Tanous6b3db602022-06-28 19:41:44 -0700904 const std::function<void(Response&)>& resHandler)
Carson Labrado039a47e2022-04-05 16:03:20 +0000905 {
Ed Tanous19bb3622024-07-05 10:07:40 -0500906 std::string_view verify = "ssl_verify";
907 if (verifyCert == ensuressl::VerifyCertificate::NoVerify)
908 {
909 verify = "ssl no verify";
910 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400911 std::string clientKey =
912 std::format("{}{}://{}", verify, destUrl.scheme(),
913 destUrl.encoded_host_and_port());
Carson Labradod14a48f2023-02-22 00:24:54 +0000914 auto pool = connectionPools.try_emplace(clientKey);
915 if (pool.first->second == nullptr)
Carson Labradof52c03c2022-03-23 18:50:15 +0000916 {
Carson Labradod14a48f2023-02-22 00:24:54 +0000917 pool.first->second = std::make_shared<ConnectionPool>(
Ed Tanous19bb3622024-07-05 10:07:40 -0500918 ioc, clientKey, connPolicy, destUrl, verifyCert);
Carson Labradof52c03c2022-03-23 18:50:15 +0000919 }
Ed Tanous27b0cf92023-08-07 12:02:40 -0700920 // Send the data using either the existing connection pool or the
921 // newly created connection pool
Ed Tanousa716aa72023-08-01 11:35:53 -0700922 pool.first->second->sendData(std::move(data), destUrl, httpHeader, verb,
Carson Labradod14a48f2023-02-22 00:24:54 +0000923 resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000924 }
Myung Baea0969c72024-09-19 08:46:45 -0400925
926 // Test whether all connections are terminated (after MaxRetryAttempts)
927 bool isTerminated()
928 {
929 for (const auto& pool : connectionPools)
930 {
931 if (pool.second != nullptr &&
932 !pool.second->areAllConnectionsTerminated())
933 {
934 BMCWEB_LOG_DEBUG(
935 "Not all of client connections are terminated");
936 return false;
937 }
938 }
939 BMCWEB_LOG_DEBUG("All client connections are terminated");
940 return true;
941 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000942};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530943} // namespace crow