blob: b48fc6ae099c61fc057980cc0f53624933a2837a [file] [log] [blame]
AppaRao Pulibd030d02020-03-20 03:34:29 +05301/*
Ed Tanous6be832e2024-09-10 11:44:48 -07002Copyright (c) 2020 Intel Corporation
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
AppaRao Pulibd030d02020-03-20 03:34:29 +053015*/
16#pragma once
Nan Zhou77665bd2022-10-12 20:28:58 +000017
18#include "async_resolve.hpp"
Ed Tanousb2896142024-01-31 15:25:47 -080019#include "http_body.hpp"
Nan Zhou77665bd2022-10-12 20:28:58 +000020#include "http_response.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080021#include "logging.hpp"
22#include "ssl_key_handler.hpp"
Nan Zhou77665bd2022-10-12 20:28:58 +000023
Ed Tanous0d5f5cf2022-03-12 15:30:55 -080024#include <boost/asio/connect.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070025#include <boost/asio/io_context.hpp>
Sunitha Harish29a82b02021-02-18 15:54:16 +053026#include <boost/asio/ip/address.hpp>
27#include <boost/asio/ip/basic_endpoint.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070028#include <boost/asio/ip/tcp.hpp>
AppaRao Pulie38778a2022-06-27 23:09:03 +000029#include <boost/asio/ssl/context.hpp>
30#include <boost/asio/ssl/error.hpp>
Ed Tanous003301a2024-04-16 09:59:19 -070031#include <boost/asio/ssl/stream.hpp>
Ed Tanousd43cd0c2020-09-30 20:46:53 -070032#include <boost/asio/steady_timer.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070033#include <boost/beast/core/flat_static_buffer.hpp>
Ed Tanousd43cd0c2020-09-30 20:46:53 -070034#include <boost/beast/http/message.hpp>
Ed Tanous4d698612024-02-06 14:57:24 -080035#include <boost/beast/http/message_generator.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070036#include <boost/beast/http/parser.hpp>
37#include <boost/beast/http/read.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070038#include <boost/beast/http/write.hpp>
Carson Labradof52c03c2022-03-23 18:50:15 +000039#include <boost/container/devector.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070040#include <boost/system/error_code.hpp>
Ed Tanous27b0cf92023-08-07 12:02:40 -070041#include <boost/url/format.hpp>
42#include <boost/url/url.hpp>
Ed Tanous4a7fbef2024-04-06 16:03:49 -070043#include <boost/url/url_view_base.hpp>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050044
AppaRao Pulibd030d02020-03-20 03:34:29 +053045#include <cstdlib>
46#include <functional>
AppaRao Pulibd030d02020-03-20 03:34:29 +053047#include <memory>
AppaRao Puli2a5689a2020-04-29 15:24:31 +053048#include <queue>
AppaRao Pulibd030d02020-03-20 03:34:29 +053049#include <string>
50
51namespace crow
52{
Ed Tanous27b0cf92023-08-07 12:02:40 -070053// With Redfish Aggregation it is assumed we will connect to another
54// instance of BMCWeb which can handle 100 simultaneous connections.
Carson Labrado66d90c22022-12-07 22:34:33 +000055constexpr size_t maxPoolSize = 20;
56constexpr size_t maxRequestQueueSize = 500;
Carson Labrado17dcc312022-07-28 22:17:28 +000057constexpr unsigned int httpReadBodyLimit = 131072;
Carson Labrado4d942722022-06-22 22:16:10 +000058constexpr unsigned int httpReadBufferSize = 4096;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053059
AppaRao Pulibd030d02020-03-20 03:34:29 +053060enum class ConnState
61{
AppaRao Puli2a5689a2020-04-29 15:24:31 +053062 initialized,
Sunitha Harish29a82b02021-02-18 15:54:16 +053063 resolveInProgress,
64 resolveFailed,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053065 connectInProgress,
66 connectFailed,
AppaRao Pulibd030d02020-03-20 03:34:29 +053067 connected,
AppaRao Pulie38778a2022-06-27 23:09:03 +000068 handshakeInProgress,
69 handshakeFailed,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053070 sendInProgress,
71 sendFailed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053072 recvInProgress,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053073 recvFailed,
74 idle,
Ayushi Smritife44eb02020-05-15 15:24:45 +053075 closed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053076 suspended,
77 terminated,
78 abortConnection,
AppaRao Pulie38778a2022-06-27 23:09:03 +000079 sslInitFailed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053080 retry
AppaRao Pulibd030d02020-03-20 03:34:29 +053081};
82
Ed Tanous4ff0f1f2024-09-04 17:27:37 -070083inline boost::system::error_code defaultRetryHandler(unsigned int respCode)
Carson Labradoa7a80292022-06-01 16:01:52 +000084{
85 // As a default, assume 200X is alright
Ed Tanous62598e32023-07-17 17:06:25 -070086 BMCWEB_LOG_DEBUG("Using default check for response code validity");
Carson Labradoa7a80292022-06-01 16:01:52 +000087 if ((respCode < 200) || (respCode >= 300))
88 {
89 return boost::system::errc::make_error_code(
90 boost::system::errc::result_out_of_range);
91 }
92
93 // Return 0 if the response code is valid
94 return boost::system::errc::make_error_code(boost::system::errc::success);
95};
96
Ed Tanous27b0cf92023-08-07 12:02:40 -070097// We need to allow retry information to be set before a message has been
98// sent and a connection pool has been created
Carson Labradod14a48f2023-02-22 00:24:54 +000099struct ConnectionPolicy
Carson Labradof52c03c2022-03-23 18:50:15 +0000100{
101 uint32_t maxRetryAttempts = 5;
Carson Labradod14a48f2023-02-22 00:24:54 +0000102
103 // the max size of requests in bytes. 0 for unlimited
104 boost::optional<uint64_t> requestByteLimit = httpReadBodyLimit;
105
106 size_t maxConnections = 1;
107
Carson Labradof52c03c2022-03-23 18:50:15 +0000108 std::string retryPolicyAction = "TerminateAfterRetries";
Carson Labradod14a48f2023-02-22 00:24:54 +0000109
110 std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0);
Carson Labradoa7a80292022-06-01 16:01:52 +0000111 std::function<boost::system::error_code(unsigned int respCode)>
112 invalidResp = defaultRetryHandler;
Carson Labradof52c03c2022-03-23 18:50:15 +0000113};
114
115struct PendingRequest
116{
Ed Tanousb2896142024-01-31 15:25:47 -0800117 boost::beast::http::request<bmcweb::HttpBody> req;
Carson Labrado039a47e2022-04-05 16:03:20 +0000118 std::function<void(bool, uint32_t, Response&)> callback;
Carson Labrado039a47e2022-04-05 16:03:20 +0000119 PendingRequest(
Ed Tanousb2896142024-01-31 15:25:47 -0800120 boost::beast::http::request<bmcweb::HttpBody>&& reqIn,
Carson Labradod14a48f2023-02-22 00:24:54 +0000121 const std::function<void(bool, uint32_t, Response&)>& callbackIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400122 req(std::move(reqIn)), callback(callbackIn)
Carson Labradof52c03c2022-03-23 18:50:15 +0000123 {}
124};
125
Ed Tanouse01d0c32023-06-30 13:21:32 -0700126namespace http = boost::beast::http;
Carson Labradof52c03c2022-03-23 18:50:15 +0000127class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo>
AppaRao Pulibd030d02020-03-20 03:34:29 +0530128{
129 private:
Carson Labradof52c03c2022-03-23 18:50:15 +0000130 ConnState state = ConnState::initialized;
131 uint32_t retryCount = 0;
Carson Labradof52c03c2022-03-23 18:50:15 +0000132 std::string subId;
Carson Labradod14a48f2023-02-22 00:24:54 +0000133 std::shared_ptr<ConnectionPolicy> connPolicy;
Ed Tanousa716aa72023-08-01 11:35:53 -0700134 boost::urls::url host;
Ed Tanous19bb3622024-07-05 10:07:40 -0500135 ensuressl::VerifyCertificate verifyCert;
Carson Labradof52c03c2022-03-23 18:50:15 +0000136 uint32_t connId;
Carson Labradof52c03c2022-03-23 18:50:15 +0000137 // Data buffers
Ed Tanousb2896142024-01-31 15:25:47 -0800138 http::request<bmcweb::HttpBody> req;
139 using parser_type = http::response_parser<bmcweb::HttpBody>;
Ed Tanouse01d0c32023-06-30 13:21:32 -0700140 std::optional<parser_type> parser;
Carson Labrado4d942722022-06-22 22:16:10 +0000141 boost::beast::flat_static_buffer<httpReadBufferSize> buffer;
Carson Labrado039a47e2022-04-05 16:03:20 +0000142 Response res;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530143
Carson Labradof52c03c2022-03-23 18:50:15 +0000144 // Ascync callables
Carson Labrado039a47e2022-04-05 16:03:20 +0000145 std::function<void(bool, uint32_t, Response&)> callback;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700146
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600147 boost::asio::io_context& ioc;
148
Ed Tanous25b54db2024-04-17 15:40:31 -0700149 using Resolver = std::conditional_t<BMCWEB_DNS_RESOLVER == "systemd-dbus",
150 async_resolve::Resolver,
151 boost::asio::ip::tcp::resolver>;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700152 Resolver resolver;
153
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800154 boost::asio::ip::tcp::socket conn;
Ed Tanous003301a2024-04-16 09:59:19 -0700155 std::optional<boost::asio::ssl::stream<boost::asio::ip::tcp::socket&>>
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800156 sslConn;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000157
Carson Labradof52c03c2022-03-23 18:50:15 +0000158 boost::asio::steady_timer timer;
Ed Tanous84b35602021-09-08 20:06:32 -0700159
Carson Labradof52c03c2022-03-23 18:50:15 +0000160 friend class ConnectionPool;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530161
Sunitha Harish29a82b02021-02-18 15:54:16 +0530162 void doResolve()
163 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530164 state = ConnState::resolveInProgress;
Ed Tanousa716aa72023-08-01 11:35:53 -0700165 BMCWEB_LOG_DEBUG("Trying to resolve: {}, id: {}", host, connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530166
Ed Tanousa716aa72023-08-01 11:35:53 -0700167 resolver.async_resolve(host.encoded_host_address(), host.port(),
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700168 std::bind_front(&ConnectionInfo::afterResolve,
169 this, shared_from_this()));
Sunitha Harish29a82b02021-02-18 15:54:16 +0530170 }
171
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700172 void afterResolve(const std::shared_ptr<ConnectionInfo>& /*self*/,
173 const boost::system::error_code& ec,
174 const Resolver::results_type& endpointList)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530175 {
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700176 if (ec || (endpointList.empty()))
177 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700178 BMCWEB_LOG_ERROR("Resolve failed: {} {}", ec.message(), host);
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700179 state = ConnState::resolveFailed;
180 waitAndRetry();
181 return;
182 }
Ed Tanousa716aa72023-08-01 11:35:53 -0700183 BMCWEB_LOG_DEBUG("Resolved {}, id: {}", host, connId);
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530184 state = ConnState::connectInProgress;
185
Ed Tanousa716aa72023-08-01 11:35:53 -0700186 BMCWEB_LOG_DEBUG("Trying to connect to: {}, id: {}", host, connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530187
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800188 timer.expires_after(std::chrono::seconds(30));
189 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
190
191 boost::asio::async_connect(
192 conn, endpointList,
193 std::bind_front(&ConnectionInfo::afterConnect, this,
194 shared_from_this()));
AppaRao Pulie38778a2022-06-27 23:09:03 +0000195 }
196
197 void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/,
Ed Tanous81c4e332023-05-18 10:30:34 -0700198 const boost::beast::error_code& ec,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000199 const boost::asio::ip::tcp::endpoint& endpoint)
200 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000201 // The operation already timed out. We don't want do continue down
202 // this branch
203 if (ec && ec == boost::asio::error::operation_aborted)
204 {
205 return;
206 }
207
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800208 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000209 if (ec)
210 {
Ed Tanous62598e32023-07-17 17:06:25 -0700211 BMCWEB_LOG_ERROR("Connect {}:{}, id: {} failed: {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700212 endpoint.address().to_string(), endpoint.port(),
213 connId, ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000214 state = ConnState::connectFailed;
215 waitAndRetry();
216 return;
217 }
Ed Tanousa716aa72023-08-01 11:35:53 -0700218 BMCWEB_LOG_DEBUG("Connected to: {}:{}, id: {}",
219 endpoint.address().to_string(), endpoint.port(),
220 connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000221 if (sslConn)
222 {
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800223 doSslHandshake();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000224 return;
225 }
226 state = ConnState::connected;
227 sendMessage();
228 }
229
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800230 void doSslHandshake()
AppaRao Pulie38778a2022-06-27 23:09:03 +0000231 {
232 if (!sslConn)
233 {
234 return;
235 }
236 state = ConnState::handshakeInProgress;
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800237 timer.expires_after(std::chrono::seconds(30));
238 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
AppaRao Pulie38778a2022-06-27 23:09:03 +0000239 sslConn->async_handshake(
240 boost::asio::ssl::stream_base::client,
241 std::bind_front(&ConnectionInfo::afterSslHandshake, this,
242 shared_from_this()));
243 }
244
245 void afterSslHandshake(const std::shared_ptr<ConnectionInfo>& /*self*/,
Ed Tanous81c4e332023-05-18 10:30:34 -0700246 const boost::beast::error_code& ec)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000247 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000248 // The operation already timed out. We don't want do continue down
249 // this branch
250 if (ec && ec == boost::asio::error::operation_aborted)
251 {
252 return;
253 }
254
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800255 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000256 if (ec)
257 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700258 BMCWEB_LOG_ERROR("SSL Handshake failed - id: {} error: {}", connId,
259 ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000260 state = ConnState::handshakeFailed;
261 waitAndRetry();
262 return;
263 }
Ed Tanousa716aa72023-08-01 11:35:53 -0700264 BMCWEB_LOG_DEBUG("SSL Handshake successful - id: {}", connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000265 state = ConnState::connected;
266 sendMessage();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530267 }
268
Carson Labradof52c03c2022-03-23 18:50:15 +0000269 void sendMessage()
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530270 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530271 state = ConnState::sendInProgress;
272
AppaRao Pulibd030d02020-03-20 03:34:29 +0530273 // Set a timeout on the operation
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800274 timer.expires_after(std::chrono::seconds(30));
275 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
AppaRao Pulibd030d02020-03-20 03:34:29 +0530276 // Send the HTTP request to the remote host
AppaRao Pulie38778a2022-06-27 23:09:03 +0000277 if (sslConn)
278 {
Ed Tanouscd504a92024-08-19 11:46:20 -0700279 boost::beast::http::async_write(
280 *sslConn, req,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000281 std::bind_front(&ConnectionInfo::afterWrite, this,
282 shared_from_this()));
283 }
284 else
285 {
Ed Tanouscd504a92024-08-19 11:46:20 -0700286 boost::beast::http::async_write(
287 conn, req,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000288 std::bind_front(&ConnectionInfo::afterWrite, this,
289 shared_from_this()));
290 }
291 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530292
AppaRao Pulie38778a2022-06-27 23:09:03 +0000293 void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/,
294 const boost::beast::error_code& ec, size_t bytesTransferred)
295 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000296 // The operation already timed out. We don't want do continue down
297 // this branch
298 if (ec && ec == boost::asio::error::operation_aborted)
299 {
300 return;
301 }
302
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800303 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000304 if (ec)
305 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700306 BMCWEB_LOG_ERROR("sendMessage() failed: {} {}", ec.message(), host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000307 state = ConnState::sendFailed;
308 waitAndRetry();
309 return;
310 }
Ed Tanous62598e32023-07-17 17:06:25 -0700311 BMCWEB_LOG_DEBUG("sendMessage() bytes transferred: {}",
312 bytesTransferred);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000313
314 recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530315 }
316
317 void recvMessage()
318 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530319 state = ConnState::recvInProgress;
320
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400321 parser_type& thisParser =
322 parser.emplace(std::piecewise_construct, std::make_tuple());
Carson Labradod14a48f2023-02-22 00:24:54 +0000323
Ed Tanouse01d0c32023-06-30 13:21:32 -0700324 thisParser.body_limit(connPolicy->requestByteLimit);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530325
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800326 timer.expires_after(std::chrono::seconds(30));
327 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
328
AppaRao Pulibd030d02020-03-20 03:34:29 +0530329 // Receive the HTTP response
AppaRao Pulie38778a2022-06-27 23:09:03 +0000330 if (sslConn)
331 {
332 boost::beast::http::async_read(
Ed Tanouse01d0c32023-06-30 13:21:32 -0700333 *sslConn, buffer, thisParser,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000334 std::bind_front(&ConnectionInfo::afterRead, this,
335 shared_from_this()));
336 }
337 else
338 {
339 boost::beast::http::async_read(
Ed Tanouse01d0c32023-06-30 13:21:32 -0700340 conn, buffer, thisParser,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000341 std::bind_front(&ConnectionInfo::afterRead, this,
342 shared_from_this()));
343 }
344 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530345
AppaRao Pulie38778a2022-06-27 23:09:03 +0000346 void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/,
347 const boost::beast::error_code& ec,
348 const std::size_t& bytesTransferred)
349 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000350 // The operation already timed out. We don't want do continue down
351 // this branch
352 if (ec && ec == boost::asio::error::operation_aborted)
353 {
354 return;
355 }
356
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800357 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000358 if (ec && ec != boost::asio::ssl::error::stream_truncated)
359 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700360 BMCWEB_LOG_ERROR("recvMessage() failed: {} from {}", ec.message(),
361 host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000362 state = ConnState::recvFailed;
363 waitAndRetry();
364 return;
365 }
Ed Tanous62598e32023-07-17 17:06:25 -0700366 BMCWEB_LOG_DEBUG("recvMessage() bytes transferred: {}",
367 bytesTransferred);
Ed Tanouse01d0c32023-06-30 13:21:32 -0700368 if (!parser)
369 {
370 return;
371 }
Ed Tanous52e31622024-01-23 16:31:11 -0800372 BMCWEB_LOG_DEBUG("recvMessage() data: {}", parser->get().body().str());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000373
374 unsigned int respCode = parser->get().result_int();
Ed Tanous62598e32023-07-17 17:06:25 -0700375 BMCWEB_LOG_DEBUG("recvMessage() Header Response Code: {}", respCode);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000376
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600377 // Handle the case of stream_truncated. Some servers close the ssl
378 // connection uncleanly, so check to see if we got a full response
379 // before we handle this as an error.
380 if (!parser->is_done())
381 {
382 state = ConnState::recvFailed;
383 waitAndRetry();
384 return;
385 }
386
AppaRao Pulie38778a2022-06-27 23:09:03 +0000387 // Make sure the received response code is valid as defined by
388 // the associated retry policy
Carson Labradod14a48f2023-02-22 00:24:54 +0000389 if (connPolicy->invalidResp(respCode))
AppaRao Pulie38778a2022-06-27 23:09:03 +0000390 {
391 // The listener failed to receive the Sent-Event
Ed Tanous62598e32023-07-17 17:06:25 -0700392 BMCWEB_LOG_ERROR(
393 "recvMessage() Listener Failed to "
Ed Tanousa716aa72023-08-01 11:35:53 -0700394 "receive Sent-Event. Header Response Code: {} from {}",
395 respCode, host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000396 state = ConnState::recvFailed;
397 waitAndRetry();
398 return;
399 }
Ed Tanous002d39b2022-05-31 08:59:27 -0700400
AppaRao Pulie38778a2022-06-27 23:09:03 +0000401 // Send is successful
402 // Reset the counter just in case this was after retrying
403 retryCount = 0;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530404
AppaRao Pulie38778a2022-06-27 23:09:03 +0000405 // Keep the connection alive if server supports it
406 // Else close the connection
Ed Tanous62598e32023-07-17 17:06:25 -0700407 BMCWEB_LOG_DEBUG("recvMessage() keepalive : {}", parser->keep_alive());
AppaRao Pulibd030d02020-03-20 03:34:29 +0530408
AppaRao Pulie38778a2022-06-27 23:09:03 +0000409 // Copy the response into a Response object so that it can be
410 // processed by the callback function.
Ed Tanous27b0cf92023-08-07 12:02:40 -0700411 res.response = parser->release();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000412 callback(parser->keep_alive(), connId, res);
Carson Labrado513d1ff2022-07-19 00:38:15 +0000413 res.clear();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530414 }
415
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800416 static void onTimeout(const std::weak_ptr<ConnectionInfo>& weakSelf,
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800417 const boost::system::error_code& ec)
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800418 {
419 if (ec == boost::asio::error::operation_aborted)
420 {
Ed Tanous62598e32023-07-17 17:06:25 -0700421 BMCWEB_LOG_DEBUG(
422 "async_wait failed since the operation is aborted");
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800423 return;
424 }
425 if (ec)
426 {
Ed Tanous62598e32023-07-17 17:06:25 -0700427 BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
Ed Tanous27b0cf92023-08-07 12:02:40 -0700428 // If the timer fails, we need to close the socket anyway, same
429 // as if it expired.
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800430 }
431 std::shared_ptr<ConnectionInfo> self = weakSelf.lock();
432 if (self == nullptr)
433 {
434 return;
435 }
436 self->waitAndRetry();
437 }
438
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530439 void waitAndRetry()
AppaRao Pulibd030d02020-03-20 03:34:29 +0530440 {
Carson Labradod14a48f2023-02-22 00:24:54 +0000441 if ((retryCount >= connPolicy->maxRetryAttempts) ||
AppaRao Pulie38778a2022-06-27 23:09:03 +0000442 (state == ConnState::sslInitFailed))
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530443 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700444 BMCWEB_LOG_ERROR("Maximum number of retries reached. {}", host);
Ed Tanous62598e32023-07-17 17:06:25 -0700445 BMCWEB_LOG_DEBUG("Retry policy: {}", connPolicy->retryPolicyAction);
Carson Labrado039a47e2022-04-05 16:03:20 +0000446
Carson Labradod14a48f2023-02-22 00:24:54 +0000447 if (connPolicy->retryPolicyAction == "TerminateAfterRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530448 {
449 // TODO: delete subscription
450 state = ConnState::terminated;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530451 }
Carson Labradod14a48f2023-02-22 00:24:54 +0000452 if (connPolicy->retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530453 {
454 state = ConnState::suspended;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530455 }
Carson Labrado513d1ff2022-07-19 00:38:15 +0000456
457 // We want to return a 502 to indicate there was an error with
458 // the external server
459 res.result(boost::beast::http::status::bad_gateway);
460 callback(false, connId, res);
461 res.clear();
462
Ed Tanous27b0cf92023-08-07 12:02:40 -0700463 // Reset the retrycount to zero so that client can try
464 // connecting again if needed
Ed Tanous3174e4d2020-10-07 11:41:22 -0700465 retryCount = 0;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530466 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530467 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530468
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530469 retryCount++;
470
Ed Tanous62598e32023-07-17 17:06:25 -0700471 BMCWEB_LOG_DEBUG("Attempt retry after {} seconds. RetryCount = {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700472 connPolicy->retryIntervalSecs.count(), retryCount);
Carson Labradod14a48f2023-02-22 00:24:54 +0000473 timer.expires_after(connPolicy->retryIntervalSecs);
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700474 timer.async_wait(std::bind_front(&ConnectionInfo::onTimerDone, this,
475 shared_from_this()));
476 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530477
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700478 void onTimerDone(const std::shared_ptr<ConnectionInfo>& /*self*/,
479 const boost::system::error_code& ec)
480 {
481 if (ec == boost::asio::error::operation_aborted)
482 {
Ed Tanous62598e32023-07-17 17:06:25 -0700483 BMCWEB_LOG_DEBUG(
484 "async_wait failed since the operation is aborted{}",
485 ec.message());
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700486 }
487 else if (ec)
488 {
Ed Tanous62598e32023-07-17 17:06:25 -0700489 BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700490 // Ignore the error and continue the retry loop to attempt
491 // sending the event as per the retry policy
492 }
493
494 // Let's close the connection and restart from resolve.
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600495 shutdownConn(true);
496 }
497
498 void restartConnection()
499 {
500 BMCWEB_LOG_DEBUG("{}, id: {} restartConnection", host,
501 std::to_string(connId));
502 initializeConnection(host.scheme() == "https");
503 doResolve();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530504 }
505
AppaRao Pulie38778a2022-06-27 23:09:03 +0000506 void shutdownConn(bool retry)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530507 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000508 boost::beast::error_code ec;
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800509 conn.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
Carson Labradof52c03c2022-03-23 18:50:15 +0000510 conn.close();
511
512 // not_connected happens sometimes so don't bother reporting it.
513 if (ec && ec != boost::beast::errc::not_connected)
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530514 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700515 BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId,
Ed Tanous62598e32023-07-17 17:06:25 -0700516 ec.message());
Carson Labradof52c03c2022-03-23 18:50:15 +0000517 }
Carson Labrado5cab68f2022-07-11 22:26:21 +0000518 else
519 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700520 BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId);
Carson Labrado5cab68f2022-07-11 22:26:21 +0000521 }
Ed Tanousca723762022-06-28 19:40:39 -0700522
Carson Labrado513d1ff2022-07-19 00:38:15 +0000523 if (retry)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000524 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000525 // Now let's try to resend the data
526 state = ConnState::retry;
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600527 restartConnection();
Carson Labrado513d1ff2022-07-19 00:38:15 +0000528 }
529 else
530 {
531 state = ConnState::closed;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000532 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000533 }
534
AppaRao Pulie38778a2022-06-27 23:09:03 +0000535 void doClose(bool retry = false)
Carson Labradof52c03c2022-03-23 18:50:15 +0000536 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000537 if (!sslConn)
538 {
539 shutdownConn(retry);
540 return;
541 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000542
AppaRao Pulie38778a2022-06-27 23:09:03 +0000543 sslConn->async_shutdown(
544 std::bind_front(&ConnectionInfo::afterSslShutdown, this,
545 shared_from_this(), retry));
546 }
547
548 void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/,
549 bool retry, const boost::system::error_code& ec)
550 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000551 if (ec)
Carson Labradof52c03c2022-03-23 18:50:15 +0000552 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700553 BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId,
Ed Tanous62598e32023-07-17 17:06:25 -0700554 ec.message());
Carson Labradof52c03c2022-03-23 18:50:15 +0000555 }
Carson Labrado5cab68f2022-07-11 22:26:21 +0000556 else
557 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700558 BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId);
Carson Labrado5cab68f2022-07-11 22:26:21 +0000559 }
AppaRao Pulie38778a2022-06-27 23:09:03 +0000560 shutdownConn(retry);
561 }
Ed Tanousca723762022-06-28 19:40:39 -0700562
AppaRao Pulie38778a2022-06-27 23:09:03 +0000563 void setCipherSuiteTLSext()
564 {
565 if (!sslConn)
566 {
567 return;
568 }
Ravi Tejae7c29912023-07-31 09:39:32 -0500569
570 if (host.host_type() != boost::urls::host_type::name)
571 {
572 // Avoid setting SNI hostname if its IP address
573 return;
574 }
575 // Create a null terminated string for SSL
Ed Tanousa716aa72023-08-01 11:35:53 -0700576 std::string hostname(host.encoded_host_address());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000577 // NOTE: The SSL_set_tlsext_host_name is defined in tlsv1.h header
578 // file but its having old style casting (name is cast to void*).
579 // Since bmcweb compiler treats all old-style-cast as error, its
580 // causing the build failure. So replaced the same macro inline and
581 // did corrected the code by doing static_cast to viod*. This has to
582 // be fixed in openssl library in long run. Set SNI Hostname (many
583 // hosts need this to handshake successfully)
584 if (SSL_ctrl(sslConn->native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME,
585 TLSEXT_NAMETYPE_host_name,
Ed Tanousa716aa72023-08-01 11:35:53 -0700586 static_cast<void*>(hostname.data())) == 0)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000587
588 {
589 boost::beast::error_code ec{static_cast<int>(::ERR_get_error()),
590 boost::asio::error::get_ssl_category()};
591
Ed Tanousa716aa72023-08-01 11:35:53 -0700592 BMCWEB_LOG_ERROR("SSL_set_tlsext_host_name {}, id: {} failed: {}",
593 host, connId, ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000594 // Set state as sslInit failed so that we close the connection
595 // and take appropriate action as per retry configuration.
596 state = ConnState::sslInitFailed;
597 waitAndRetry();
598 return;
599 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530600 }
601
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600602 void initializeConnection(bool ssl)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000603 {
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600604 conn = boost::asio::ip::tcp::socket(ioc);
605 if (ssl)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000606 {
607 std::optional<boost::asio::ssl::context> sslCtx =
Ed Tanous19bb3622024-07-05 10:07:40 -0500608 ensuressl::getSSLClientContext(verifyCert);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000609
610 if (!sslCtx)
611 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700612 BMCWEB_LOG_ERROR("prepareSSLContext failed - {}, id: {}", host,
613 connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000614 // Don't retry if failure occurs while preparing SSL context
Ed Tanous27b0cf92023-08-07 12:02:40 -0700615 // such as certificate is invalid or set cipher failure or
616 // set host name failure etc... Setting conn state to
617 // sslInitFailed and connection state will be transitioned
618 // to next state depending on retry policy set by
619 // subscription.
AppaRao Pulie38778a2022-06-27 23:09:03 +0000620 state = ConnState::sslInitFailed;
621 waitAndRetry();
622 return;
623 }
624 sslConn.emplace(conn, *sslCtx);
625 setCipherSuiteTLSext();
626 }
627 }
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600628
629 public:
630 explicit ConnectionInfo(
631 boost::asio::io_context& iocIn, const std::string& idIn,
632 const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
Ed Tanous19bb3622024-07-05 10:07:40 -0500633 const boost::urls::url_view_base& hostIn,
634 ensuressl::VerifyCertificate verifyCertIn, unsigned int connIdIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400635 subId(idIn), connPolicy(connPolicyIn), host(hostIn),
636 verifyCert(verifyCertIn), connId(connIdIn), ioc(iocIn), resolver(iocIn),
637 conn(iocIn), timer(iocIn)
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600638 {
639 initializeConnection(host.scheme() == "https");
640 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000641};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530642
Carson Labradof52c03c2022-03-23 18:50:15 +0000643class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
644{
645 private:
646 boost::asio::io_context& ioc;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000647 std::string id;
Carson Labradod14a48f2023-02-22 00:24:54 +0000648 std::shared_ptr<ConnectionPolicy> connPolicy;
Ed Tanousa716aa72023-08-01 11:35:53 -0700649 boost::urls::url destIP;
Carson Labradof52c03c2022-03-23 18:50:15 +0000650 std::vector<std::shared_ptr<ConnectionInfo>> connections;
651 boost::container::devector<PendingRequest> requestQueue;
Ed Tanous19bb3622024-07-05 10:07:40 -0500652 ensuressl::VerifyCertificate verifyCert;
Carson Labradof52c03c2022-03-23 18:50:15 +0000653
654 friend class HttpClient;
655
Carson Labrado244256c2022-04-27 17:16:32 +0000656 // Configure a connections's request, callback, and retry info in
657 // preparation to begin sending the request
Carson Labradof52c03c2022-03-23 18:50:15 +0000658 void setConnProps(ConnectionInfo& conn)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530659 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000660 if (requestQueue.empty())
AppaRao Pulibd030d02020-03-20 03:34:29 +0530661 {
Ed Tanous62598e32023-07-17 17:06:25 -0700662 BMCWEB_LOG_ERROR(
663 "setConnProps() should not have been called when requestQueue is empty");
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530664 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530665 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530666
Ed Tanous52e31622024-01-23 16:31:11 -0800667 PendingRequest& nextReq = requestQueue.front();
Carson Labrado244256c2022-04-27 17:16:32 +0000668 conn.req = std::move(nextReq.req);
669 conn.callback = std::move(nextReq.callback);
Carson Labradof52c03c2022-03-23 18:50:15 +0000670
Ed Tanousa716aa72023-08-01 11:35:53 -0700671 BMCWEB_LOG_DEBUG("Setting properties for connection {}, id: {}",
672 conn.host, conn.connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000673
674 // We can remove the request from the queue at this point
675 requestQueue.pop_front();
676 }
677
Carson Labradof52c03c2022-03-23 18:50:15 +0000678 // Gets called as part of callback after request is sent
679 // Reuses the connection if there are any requests waiting to be sent
680 // Otherwise closes the connection if it is not a keep-alive
681 void sendNext(bool keepAlive, uint32_t connId)
682 {
683 auto conn = connections[connId];
Carson Labrado46a81462022-04-27 21:11:37 +0000684
685 // Allow the connection's handler to be deleted
686 // This is needed because of Redfish Aggregation passing an
687 // AsyncResponse shared_ptr to this callback
688 conn->callback = nullptr;
689
Carson Labradof52c03c2022-03-23 18:50:15 +0000690 // Reuse the connection to send the next request in the queue
691 if (!requestQueue.empty())
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530692 {
Ed Tanous62598e32023-07-17 17:06:25 -0700693 BMCWEB_LOG_DEBUG(
Ed Tanous8ece0e42024-01-02 13:16:50 -0800694 "{} requests remaining in queue for {}, reusing connection {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700695 requestQueue.size(), destIP, connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000696
697 setConnProps(*conn);
698
699 if (keepAlive)
700 {
701 conn->sendMessage();
702 }
703 else
704 {
705 // Server is not keep-alive enabled so we need to close the
706 // connection and then start over from resolve
707 conn->doClose();
Abhilash Raju2ecde742024-06-01 02:01:01 -0500708 conn->restartConnection();
Carson Labradof52c03c2022-03-23 18:50:15 +0000709 }
710 return;
711 }
712
713 // No more messages to send so close the connection if necessary
714 if (keepAlive)
715 {
716 conn->state = ConnState::idle;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530717 }
718 else
719 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000720 // Abort the connection since server is not keep-alive enabled
721 conn->state = ConnState::abortConnection;
722 conn->doClose();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530723 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530724 }
725
Ed Tanous4a7fbef2024-04-06 16:03:49 -0700726 void sendData(std::string&& data, const boost::urls::url_view_base& destUri,
Carson Labrado244256c2022-04-27 17:16:32 +0000727 const boost::beast::http::fields& httpHeader,
728 const boost::beast::http::verb verb,
Ed Tanous6b3db602022-06-28 19:41:44 -0700729 const std::function<void(Response&)>& resHandler)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530730 {
Carson Labrado244256c2022-04-27 17:16:32 +0000731 // Construct the request to be sent
Ed Tanousb2896142024-01-31 15:25:47 -0800732 boost::beast::http::request<bmcweb::HttpBody> thisReq(
Ed Tanousa716aa72023-08-01 11:35:53 -0700733 verb, destUri.encoded_target(), 11, "", httpHeader);
734 thisReq.set(boost::beast::http::field::host,
735 destUri.encoded_host_address());
Carson Labrado244256c2022-04-27 17:16:32 +0000736 thisReq.keep_alive(true);
Ed Tanous52e31622024-01-23 16:31:11 -0800737 thisReq.body().str() = std::move(data);
Carson Labrado244256c2022-04-27 17:16:32 +0000738 thisReq.prepare_payload();
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700739 auto cb = std::bind_front(&ConnectionPool::afterSendData,
740 weak_from_this(), resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000741 // Reuse an existing connection if one is available
742 for (unsigned int i = 0; i < connections.size(); i++)
743 {
744 auto conn = connections[i];
745 if ((conn->state == ConnState::idle) ||
746 (conn->state == ConnState::initialized) ||
747 (conn->state == ConnState::closed))
748 {
Carson Labrado244256c2022-04-27 17:16:32 +0000749 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000750 conn->callback = std::move(cb);
Ed Tanousa716aa72023-08-01 11:35:53 -0700751 std::string commonMsg = std::format("{} from pool {}", i, id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000752
753 if (conn->state == ConnState::idle)
754 {
Ed Tanous62598e32023-07-17 17:06:25 -0700755 BMCWEB_LOG_DEBUG("Grabbing idle connection {}", commonMsg);
Carson Labradof52c03c2022-03-23 18:50:15 +0000756 conn->sendMessage();
757 }
758 else
759 {
Ed Tanous62598e32023-07-17 17:06:25 -0700760 BMCWEB_LOG_DEBUG("Reusing existing connection {}",
761 commonMsg);
Abhilash Raju2ecde742024-06-01 02:01:01 -0500762 conn->restartConnection();
Carson Labradof52c03c2022-03-23 18:50:15 +0000763 }
764 return;
765 }
766 }
767
Ed Tanous27b0cf92023-08-07 12:02:40 -0700768 // All connections in use so create a new connection or add request
769 // to the queue
Carson Labradod14a48f2023-02-22 00:24:54 +0000770 if (connections.size() < connPolicy->maxConnections)
Carson Labradof52c03c2022-03-23 18:50:15 +0000771 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700772 BMCWEB_LOG_DEBUG("Adding new connection to pool {}", id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000773 auto conn = addConnection();
Carson Labrado244256c2022-04-27 17:16:32 +0000774 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000775 conn->callback = std::move(cb);
Carson Labradof52c03c2022-03-23 18:50:15 +0000776 conn->doResolve();
777 }
778 else if (requestQueue.size() < maxRequestQueueSize)
779 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700780 BMCWEB_LOG_DEBUG("Max pool size reached. Adding data to queue {}",
781 id);
Carson Labradod14a48f2023-02-22 00:24:54 +0000782 requestQueue.emplace_back(std::move(thisReq), std::move(cb));
Carson Labradof52c03c2022-03-23 18:50:15 +0000783 }
784 else
785 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700786 // If we can't buffer the request then we should let the
787 // callback handle a 429 Too Many Requests dummy response
Ed Tanous6ea90762024-04-07 08:38:44 -0700788 BMCWEB_LOG_ERROR("{} request queue full. Dropping request.", id);
Carson Labrado43e14d32022-11-09 00:25:20 +0000789 Response dummyRes;
790 dummyRes.result(boost::beast::http::status::too_many_requests);
791 resHandler(dummyRes);
Carson Labradof52c03c2022-03-23 18:50:15 +0000792 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530793 }
794
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700795 // Callback to be called once the request has been sent
796 static void afterSendData(const std::weak_ptr<ConnectionPool>& weakSelf,
797 const std::function<void(Response&)>& resHandler,
798 bool keepAlive, uint32_t connId, Response& res)
799 {
800 // Allow provided callback to perform additional processing of the
801 // request
802 resHandler(res);
803
804 // If requests remain in the queue then we want to reuse this
805 // connection to send the next request
806 std::shared_ptr<ConnectionPool> self = weakSelf.lock();
807 if (!self)
808 {
Ed Tanous62598e32023-07-17 17:06:25 -0700809 BMCWEB_LOG_CRITICAL("{} Failed to capture connection",
810 logPtr(self.get()));
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700811 return;
812 }
813
814 self->sendNext(keepAlive, connId);
815 }
816
Carson Labradof52c03c2022-03-23 18:50:15 +0000817 std::shared_ptr<ConnectionInfo>& addConnection()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530818 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000819 unsigned int newId = static_cast<unsigned int>(connections.size());
820
AppaRao Pulie38778a2022-06-27 23:09:03 +0000821 auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>(
Ed Tanous19bb3622024-07-05 10:07:40 -0500822 ioc, id, connPolicy, destIP, verifyCert, newId));
Carson Labradof52c03c2022-03-23 18:50:15 +0000823
Ed Tanousa716aa72023-08-01 11:35:53 -0700824 BMCWEB_LOG_DEBUG("Added connection {} to pool {}",
825 connections.size() - 1, id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000826
827 return ret;
828 }
829
830 public:
Carson Labradod14a48f2023-02-22 00:24:54 +0000831 explicit ConnectionPool(
832 boost::asio::io_context& iocIn, const std::string& idIn,
833 const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
Ed Tanous19bb3622024-07-05 10:07:40 -0500834 const boost::urls::url_view_base& destIPIn,
835 ensuressl::VerifyCertificate verifyCertIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400836 ioc(iocIn), id(idIn), connPolicy(connPolicyIn), destIP(destIPIn),
Ed Tanous19bb3622024-07-05 10:07:40 -0500837 verifyCert(verifyCertIn)
Carson Labradof52c03c2022-03-23 18:50:15 +0000838 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700839 BMCWEB_LOG_DEBUG("Initializing connection pool for {}", id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000840
841 // Initialize the pool with a single connection
842 addConnection();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530843 }
Myung Baea0969c72024-09-19 08:46:45 -0400844
845 // Check whether all connections are terminated
846 bool areAllConnectionsTerminated()
847 {
848 if (connections.empty())
849 {
850 BMCWEB_LOG_DEBUG("There are no connections for pool id:{}", id);
851 return false;
852 }
853 for (const auto& conn : connections)
854 {
855 if (conn != nullptr && conn->state != ConnState::terminated)
856 {
857 BMCWEB_LOG_DEBUG(
858 "Not all connections of pool id:{} are terminated", id);
859 return false;
860 }
861 }
862 BMCWEB_LOG_INFO("All connections of pool id:{} are terminated", id);
863 return true;
864 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530865};
866
Carson Labradof52c03c2022-03-23 18:50:15 +0000867class HttpClient
868{
869 private:
870 std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
871 connectionPools;
Ed Tanous4b712a22023-08-02 12:56:52 -0700872
873 // reference_wrapper here makes HttpClient movable
874 std::reference_wrapper<boost::asio::io_context> ioc;
Carson Labradod14a48f2023-02-22 00:24:54 +0000875 std::shared_ptr<ConnectionPolicy> connPolicy;
Carson Labradof52c03c2022-03-23 18:50:15 +0000876
Carson Labrado039a47e2022-04-05 16:03:20 +0000877 // Used as a dummy callback by sendData() in order to call
878 // sendDataWithCallback()
Ed Tanous02cad962022-06-30 16:50:15 -0700879 static void genericResHandler(const Response& res)
Carson Labrado039a47e2022-04-05 16:03:20 +0000880 {
Ed Tanous62598e32023-07-17 17:06:25 -0700881 BMCWEB_LOG_DEBUG("Response handled with return code: {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700882 res.resultInt());
Ed Tanous4ee8e212022-05-28 09:42:51 -0700883 }
Carson Labrado039a47e2022-04-05 16:03:20 +0000884
Carson Labradof52c03c2022-03-23 18:50:15 +0000885 public:
Carson Labradod14a48f2023-02-22 00:24:54 +0000886 HttpClient() = delete;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700887 explicit HttpClient(boost::asio::io_context& iocIn,
888 const std::shared_ptr<ConnectionPolicy>& connPolicyIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400889 ioc(iocIn), connPolicy(connPolicyIn)
Carson Labradod14a48f2023-02-22 00:24:54 +0000890 {}
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700891
Carson Labradof52c03c2022-03-23 18:50:15 +0000892 HttpClient(const HttpClient&) = delete;
893 HttpClient& operator=(const HttpClient&) = delete;
Ed Tanous4b712a22023-08-02 12:56:52 -0700894 HttpClient(HttpClient&& client) = default;
895 HttpClient& operator=(HttpClient&& client) = default;
Carson Labradof52c03c2022-03-23 18:50:15 +0000896 ~HttpClient() = default;
897
Ed Tanousa716aa72023-08-01 11:35:53 -0700898 // Send a request to destIP where additional processing of the
Carson Labrado039a47e2022-04-05 16:03:20 +0000899 // result is not required
Ed Tanous4a7fbef2024-04-06 16:03:49 -0700900 void sendData(std::string&& data, const boost::urls::url_view_base& destUri,
Ed Tanous19bb3622024-07-05 10:07:40 -0500901 ensuressl::VerifyCertificate verifyCert,
Carson Labradof52c03c2022-03-23 18:50:15 +0000902 const boost::beast::http::fields& httpHeader,
Carson Labradod14a48f2023-02-22 00:24:54 +0000903 const boost::beast::http::verb verb)
Carson Labradof52c03c2022-03-23 18:50:15 +0000904 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000905 const std::function<void(Response&)> cb = genericResHandler;
Ed Tanous19bb3622024-07-05 10:07:40 -0500906 sendDataWithCallback(std::move(data), destUri, verifyCert, httpHeader,
907 verb, cb);
Carson Labrado039a47e2022-04-05 16:03:20 +0000908 }
909
Ed Tanousa716aa72023-08-01 11:35:53 -0700910 // Send request to destIP and use the provided callback to
Carson Labrado039a47e2022-04-05 16:03:20 +0000911 // handle the response
Ed Tanous4a7fbef2024-04-06 16:03:49 -0700912 void sendDataWithCallback(std::string&& data,
913 const boost::urls::url_view_base& destUrl,
Ed Tanous19bb3622024-07-05 10:07:40 -0500914 ensuressl::VerifyCertificate verifyCert,
Carson Labrado039a47e2022-04-05 16:03:20 +0000915 const boost::beast::http::fields& httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000916 const boost::beast::http::verb verb,
Ed Tanous6b3db602022-06-28 19:41:44 -0700917 const std::function<void(Response&)>& resHandler)
Carson Labrado039a47e2022-04-05 16:03:20 +0000918 {
Ed Tanous19bb3622024-07-05 10:07:40 -0500919 std::string_view verify = "ssl_verify";
920 if (verifyCert == ensuressl::VerifyCertificate::NoVerify)
921 {
922 verify = "ssl no verify";
923 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400924 std::string clientKey =
925 std::format("{}{}://{}", verify, destUrl.scheme(),
926 destUrl.encoded_host_and_port());
Carson Labradod14a48f2023-02-22 00:24:54 +0000927 auto pool = connectionPools.try_emplace(clientKey);
928 if (pool.first->second == nullptr)
Carson Labradof52c03c2022-03-23 18:50:15 +0000929 {
Carson Labradod14a48f2023-02-22 00:24:54 +0000930 pool.first->second = std::make_shared<ConnectionPool>(
Ed Tanous19bb3622024-07-05 10:07:40 -0500931 ioc, clientKey, connPolicy, destUrl, verifyCert);
Carson Labradof52c03c2022-03-23 18:50:15 +0000932 }
Ed Tanous27b0cf92023-08-07 12:02:40 -0700933 // Send the data using either the existing connection pool or the
934 // newly created connection pool
Ed Tanousa716aa72023-08-01 11:35:53 -0700935 pool.first->second->sendData(std::move(data), destUrl, httpHeader, verb,
Carson Labradod14a48f2023-02-22 00:24:54 +0000936 resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000937 }
Myung Baea0969c72024-09-19 08:46:45 -0400938
939 // Test whether all connections are terminated (after MaxRetryAttempts)
940 bool isTerminated()
941 {
942 for (const auto& pool : connectionPools)
943 {
944 if (pool.second != nullptr &&
945 !pool.second->areAllConnectionsTerminated())
946 {
947 BMCWEB_LOG_DEBUG(
948 "Not all of client connections are terminated");
949 return false;
950 }
951 }
952 BMCWEB_LOG_DEBUG("All client connections are terminated");
953 return true;
954 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000955};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530956} // namespace crow