blob: 8c27d6d61aa601ad9f0d80a694037de57a2270da [file] [log] [blame]
AppaRao Pulibd030d02020-03-20 03:34:29 +05301/*
2// Copyright (c) 2020 Intel Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
16#pragma once
Nan Zhou77665bd2022-10-12 20:28:58 +000017
18#include "async_resolve.hpp"
19#include "http_response.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080020#include "logging.hpp"
21#include "ssl_key_handler.hpp"
Nan Zhou77665bd2022-10-12 20:28:58 +000022
Ed Tanous0d5f5cf2022-03-12 15:30:55 -080023#include <boost/asio/connect.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070024#include <boost/asio/io_context.hpp>
Sunitha Harish29a82b02021-02-18 15:54:16 +053025#include <boost/asio/ip/address.hpp>
26#include <boost/asio/ip/basic_endpoint.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070027#include <boost/asio/ip/tcp.hpp>
AppaRao Pulie38778a2022-06-27 23:09:03 +000028#include <boost/asio/ssl/context.hpp>
29#include <boost/asio/ssl/error.hpp>
Ed Tanousd43cd0c2020-09-30 20:46:53 -070030#include <boost/asio/steady_timer.hpp>
31#include <boost/beast/core/flat_buffer.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070032#include <boost/beast/core/flat_static_buffer.hpp>
Ed Tanousd43cd0c2020-09-30 20:46:53 -070033#include <boost/beast/http/message.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070034#include <boost/beast/http/parser.hpp>
35#include <boost/beast/http/read.hpp>
36#include <boost/beast/http/string_body.hpp>
37#include <boost/beast/http/write.hpp>
AppaRao Pulie38778a2022-06-27 23:09:03 +000038#include <boost/beast/ssl/ssl_stream.hpp>
AppaRao Pulibd030d02020-03-20 03:34:29 +053039#include <boost/beast/version.hpp>
Carson Labradof52c03c2022-03-23 18:50:15 +000040#include <boost/container/devector.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070041#include <boost/system/error_code.hpp>
Ed Tanous27b0cf92023-08-07 12:02:40 -070042#include <boost/url/format.hpp>
43#include <boost/url/url.hpp>
Ed Tanousa716aa72023-08-01 11:35:53 -070044#include <boost/url/url_view.hpp>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050045
AppaRao Pulibd030d02020-03-20 03:34:29 +053046#include <cstdlib>
47#include <functional>
48#include <iostream>
49#include <memory>
AppaRao Puli2a5689a2020-04-29 15:24:31 +053050#include <queue>
AppaRao Pulibd030d02020-03-20 03:34:29 +053051#include <string>
52
53namespace crow
54{
Ed Tanous27b0cf92023-08-07 12:02:40 -070055// With Redfish Aggregation it is assumed we will connect to another
56// instance of BMCWeb which can handle 100 simultaneous connections.
Carson Labrado66d90c22022-12-07 22:34:33 +000057constexpr size_t maxPoolSize = 20;
58constexpr size_t maxRequestQueueSize = 500;
Carson Labrado17dcc312022-07-28 22:17:28 +000059constexpr unsigned int httpReadBodyLimit = 131072;
Carson Labrado4d942722022-06-22 22:16:10 +000060constexpr unsigned int httpReadBufferSize = 4096;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053061
AppaRao Pulibd030d02020-03-20 03:34:29 +053062enum class ConnState
63{
AppaRao Puli2a5689a2020-04-29 15:24:31 +053064 initialized,
Sunitha Harish29a82b02021-02-18 15:54:16 +053065 resolveInProgress,
66 resolveFailed,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053067 connectInProgress,
68 connectFailed,
AppaRao Pulibd030d02020-03-20 03:34:29 +053069 connected,
AppaRao Pulie38778a2022-06-27 23:09:03 +000070 handshakeInProgress,
71 handshakeFailed,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053072 sendInProgress,
73 sendFailed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053074 recvInProgress,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053075 recvFailed,
76 idle,
Ayushi Smritife44eb02020-05-15 15:24:45 +053077 closed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053078 suspended,
79 terminated,
80 abortConnection,
AppaRao Pulie38778a2022-06-27 23:09:03 +000081 sslInitFailed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053082 retry
AppaRao Pulibd030d02020-03-20 03:34:29 +053083};
84
Carson Labradoa7a80292022-06-01 16:01:52 +000085static inline boost::system::error_code
86 defaultRetryHandler(unsigned int respCode)
87{
88 // As a default, assume 200X is alright
Ed Tanous62598e32023-07-17 17:06:25 -070089 BMCWEB_LOG_DEBUG("Using default check for response code validity");
Carson Labradoa7a80292022-06-01 16:01:52 +000090 if ((respCode < 200) || (respCode >= 300))
91 {
92 return boost::system::errc::make_error_code(
93 boost::system::errc::result_out_of_range);
94 }
95
96 // Return 0 if the response code is valid
97 return boost::system::errc::make_error_code(boost::system::errc::success);
98};
99
Ed Tanous27b0cf92023-08-07 12:02:40 -0700100// We need to allow retry information to be set before a message has been
101// sent and a connection pool has been created
Carson Labradod14a48f2023-02-22 00:24:54 +0000102struct ConnectionPolicy
Carson Labradof52c03c2022-03-23 18:50:15 +0000103{
104 uint32_t maxRetryAttempts = 5;
Carson Labradod14a48f2023-02-22 00:24:54 +0000105
106 // the max size of requests in bytes. 0 for unlimited
107 boost::optional<uint64_t> requestByteLimit = httpReadBodyLimit;
108
109 size_t maxConnections = 1;
110
Carson Labradof52c03c2022-03-23 18:50:15 +0000111 std::string retryPolicyAction = "TerminateAfterRetries";
Carson Labradod14a48f2023-02-22 00:24:54 +0000112
113 std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0);
Carson Labradoa7a80292022-06-01 16:01:52 +0000114 std::function<boost::system::error_code(unsigned int respCode)>
115 invalidResp = defaultRetryHandler;
Carson Labradof52c03c2022-03-23 18:50:15 +0000116};
117
118struct PendingRequest
119{
Carson Labrado244256c2022-04-27 17:16:32 +0000120 boost::beast::http::request<boost::beast::http::string_body> req;
Carson Labrado039a47e2022-04-05 16:03:20 +0000121 std::function<void(bool, uint32_t, Response&)> callback;
Carson Labrado039a47e2022-04-05 16:03:20 +0000122 PendingRequest(
Ed Tanous8a592812022-06-04 09:06:59 -0700123 boost::beast::http::request<boost::beast::http::string_body>&& reqIn,
Carson Labradod14a48f2023-02-22 00:24:54 +0000124 const std::function<void(bool, uint32_t, Response&)>& callbackIn) :
Ed Tanous8a592812022-06-04 09:06:59 -0700125 req(std::move(reqIn)),
Carson Labradod14a48f2023-02-22 00:24:54 +0000126 callback(callbackIn)
Carson Labradof52c03c2022-03-23 18:50:15 +0000127 {}
128};
129
Ed Tanouse01d0c32023-06-30 13:21:32 -0700130namespace http = boost::beast::http;
Carson Labradof52c03c2022-03-23 18:50:15 +0000131class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo>
AppaRao Pulibd030d02020-03-20 03:34:29 +0530132{
133 private:
Carson Labradof52c03c2022-03-23 18:50:15 +0000134 ConnState state = ConnState::initialized;
135 uint32_t retryCount = 0;
Carson Labradof52c03c2022-03-23 18:50:15 +0000136 std::string subId;
Carson Labradod14a48f2023-02-22 00:24:54 +0000137 std::shared_ptr<ConnectionPolicy> connPolicy;
Ed Tanousa716aa72023-08-01 11:35:53 -0700138 boost::urls::url host;
Carson Labradof52c03c2022-03-23 18:50:15 +0000139 uint32_t connId;
140
Carson Labradof52c03c2022-03-23 18:50:15 +0000141 // Data buffers
Ed Tanouse01d0c32023-06-30 13:21:32 -0700142 http::request<http::string_body> req;
143 using parser_type = http::response_parser<http::string_body>;
144 std::optional<parser_type> parser;
Carson Labrado4d942722022-06-22 22:16:10 +0000145 boost::beast::flat_static_buffer<httpReadBufferSize> buffer;
Carson Labrado039a47e2022-04-05 16:03:20 +0000146 Response res;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530147
Carson Labradof52c03c2022-03-23 18:50:15 +0000148 // Ascync callables
Carson Labrado039a47e2022-04-05 16:03:20 +0000149 std::function<void(bool, uint32_t, Response&)> callback;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700150
151#ifdef BMCWEB_DBUS_DNS_RESOLVER
Ed Tanouse1452be2021-10-04 17:03:52 -0700152 using Resolver = async_resolve::Resolver;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700153#else
154 using Resolver = boost::asio::ip::tcp::resolver;
155#endif
156 Resolver resolver;
157
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800158 boost::asio::ip::tcp::socket conn;
159 std::optional<boost::beast::ssl_stream<boost::asio::ip::tcp::socket&>>
160 sslConn;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000161
Carson Labradof52c03c2022-03-23 18:50:15 +0000162 boost::asio::steady_timer timer;
Ed Tanous84b35602021-09-08 20:06:32 -0700163
Carson Labradof52c03c2022-03-23 18:50:15 +0000164 friend class ConnectionPool;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530165
Sunitha Harish29a82b02021-02-18 15:54:16 +0530166 void doResolve()
167 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530168 state = ConnState::resolveInProgress;
Ed Tanousa716aa72023-08-01 11:35:53 -0700169 BMCWEB_LOG_DEBUG("Trying to resolve: {}, id: {}", host, connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530170
Ed Tanousa716aa72023-08-01 11:35:53 -0700171 resolver.async_resolve(host.encoded_host_address(), host.port(),
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700172 std::bind_front(&ConnectionInfo::afterResolve,
173 this, shared_from_this()));
Sunitha Harish29a82b02021-02-18 15:54:16 +0530174 }
175
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700176 void afterResolve(const std::shared_ptr<ConnectionInfo>& /*self*/,
177 const boost::system::error_code& ec,
178 const Resolver::results_type& endpointList)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530179 {
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700180 if (ec || (endpointList.empty()))
181 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700182 BMCWEB_LOG_ERROR("Resolve failed: {} {}", ec.message(), host);
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700183 state = ConnState::resolveFailed;
184 waitAndRetry();
185 return;
186 }
Ed Tanousa716aa72023-08-01 11:35:53 -0700187 BMCWEB_LOG_DEBUG("Resolved {}, id: {}", host, connId);
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530188 state = ConnState::connectInProgress;
189
Ed Tanousa716aa72023-08-01 11:35:53 -0700190 BMCWEB_LOG_DEBUG("Trying to connect to: {}, id: {}", host, connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530191
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800192 timer.expires_after(std::chrono::seconds(30));
193 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
194
195 boost::asio::async_connect(
196 conn, endpointList,
197 std::bind_front(&ConnectionInfo::afterConnect, this,
198 shared_from_this()));
AppaRao Pulie38778a2022-06-27 23:09:03 +0000199 }
200
201 void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/,
Ed Tanous81c4e332023-05-18 10:30:34 -0700202 const boost::beast::error_code& ec,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000203 const boost::asio::ip::tcp::endpoint& endpoint)
204 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000205 // The operation already timed out. We don't want do continue down
206 // this branch
207 if (ec && ec == boost::asio::error::operation_aborted)
208 {
209 return;
210 }
211
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800212 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000213 if (ec)
214 {
Ed Tanous62598e32023-07-17 17:06:25 -0700215 BMCWEB_LOG_ERROR("Connect {}:{}, id: {} failed: {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700216 endpoint.address().to_string(), endpoint.port(),
217 connId, ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000218 state = ConnState::connectFailed;
219 waitAndRetry();
220 return;
221 }
Ed Tanousa716aa72023-08-01 11:35:53 -0700222 BMCWEB_LOG_DEBUG("Connected to: {}:{}, id: {}",
223 endpoint.address().to_string(), endpoint.port(),
224 connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000225 if (sslConn)
226 {
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800227 doSslHandshake();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000228 return;
229 }
230 state = ConnState::connected;
231 sendMessage();
232 }
233
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800234 void doSslHandshake()
AppaRao Pulie38778a2022-06-27 23:09:03 +0000235 {
236 if (!sslConn)
237 {
238 return;
239 }
240 state = ConnState::handshakeInProgress;
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800241 timer.expires_after(std::chrono::seconds(30));
242 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
AppaRao Pulie38778a2022-06-27 23:09:03 +0000243 sslConn->async_handshake(
244 boost::asio::ssl::stream_base::client,
245 std::bind_front(&ConnectionInfo::afterSslHandshake, this,
246 shared_from_this()));
247 }
248
249 void afterSslHandshake(const std::shared_ptr<ConnectionInfo>& /*self*/,
Ed Tanous81c4e332023-05-18 10:30:34 -0700250 const boost::beast::error_code& ec)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000251 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000252 // The operation already timed out. We don't want do continue down
253 // this branch
254 if (ec && ec == boost::asio::error::operation_aborted)
255 {
256 return;
257 }
258
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800259 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000260 if (ec)
261 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700262 BMCWEB_LOG_ERROR("SSL Handshake failed - id: {} error: {}", connId,
263 ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000264 state = ConnState::handshakeFailed;
265 waitAndRetry();
266 return;
267 }
Ed Tanousa716aa72023-08-01 11:35:53 -0700268 BMCWEB_LOG_DEBUG("SSL Handshake successful - id: {}", connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000269 state = ConnState::connected;
270 sendMessage();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530271 }
272
Carson Labradof52c03c2022-03-23 18:50:15 +0000273 void sendMessage()
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530274 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530275 state = ConnState::sendInProgress;
276
AppaRao Pulibd030d02020-03-20 03:34:29 +0530277 // Set a timeout on the operation
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800278 timer.expires_after(std::chrono::seconds(30));
279 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
AppaRao Pulibd030d02020-03-20 03:34:29 +0530280
281 // Send the HTTP request to the remote host
AppaRao Pulie38778a2022-06-27 23:09:03 +0000282 if (sslConn)
283 {
284 boost::beast::http::async_write(
285 *sslConn, req,
286 std::bind_front(&ConnectionInfo::afterWrite, this,
287 shared_from_this()));
288 }
289 else
290 {
291 boost::beast::http::async_write(
292 conn, req,
293 std::bind_front(&ConnectionInfo::afterWrite, this,
294 shared_from_this()));
295 }
296 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530297
AppaRao Pulie38778a2022-06-27 23:09:03 +0000298 void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/,
299 const boost::beast::error_code& ec, size_t bytesTransferred)
300 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000301 // The operation already timed out. We don't want do continue down
302 // this branch
303 if (ec && ec == boost::asio::error::operation_aborted)
304 {
305 return;
306 }
307
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800308 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000309 if (ec)
310 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700311 BMCWEB_LOG_ERROR("sendMessage() failed: {} {}", ec.message(), host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000312 state = ConnState::sendFailed;
313 waitAndRetry();
314 return;
315 }
Ed Tanous62598e32023-07-17 17:06:25 -0700316 BMCWEB_LOG_DEBUG("sendMessage() bytes transferred: {}",
317 bytesTransferred);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000318
319 recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530320 }
321
322 void recvMessage()
323 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530324 state = ConnState::recvInProgress;
325
Ed Tanouse01d0c32023-06-30 13:21:32 -0700326 parser_type& thisParser = parser.emplace(std::piecewise_construct,
327 std::make_tuple());
Carson Labradod14a48f2023-02-22 00:24:54 +0000328
Ed Tanouse01d0c32023-06-30 13:21:32 -0700329 thisParser.body_limit(connPolicy->requestByteLimit);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530330
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800331 timer.expires_after(std::chrono::seconds(30));
332 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
333
AppaRao Pulibd030d02020-03-20 03:34:29 +0530334 // Receive the HTTP response
AppaRao Pulie38778a2022-06-27 23:09:03 +0000335 if (sslConn)
336 {
337 boost::beast::http::async_read(
Ed Tanouse01d0c32023-06-30 13:21:32 -0700338 *sslConn, buffer, thisParser,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000339 std::bind_front(&ConnectionInfo::afterRead, this,
340 shared_from_this()));
341 }
342 else
343 {
344 boost::beast::http::async_read(
Ed Tanouse01d0c32023-06-30 13:21:32 -0700345 conn, buffer, thisParser,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000346 std::bind_front(&ConnectionInfo::afterRead, this,
347 shared_from_this()));
348 }
349 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530350
AppaRao Pulie38778a2022-06-27 23:09:03 +0000351 void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/,
352 const boost::beast::error_code& ec,
353 const std::size_t& bytesTransferred)
354 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000355 // The operation already timed out. We don't want do continue down
356 // this branch
357 if (ec && ec == boost::asio::error::operation_aborted)
358 {
359 return;
360 }
361
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800362 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000363 if (ec && ec != boost::asio::ssl::error::stream_truncated)
364 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700365 BMCWEB_LOG_ERROR("recvMessage() failed: {} from {}", ec.message(),
366 host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000367 state = ConnState::recvFailed;
368 waitAndRetry();
369 return;
370 }
Ed Tanous62598e32023-07-17 17:06:25 -0700371 BMCWEB_LOG_DEBUG("recvMessage() bytes transferred: {}",
372 bytesTransferred);
Ed Tanouse01d0c32023-06-30 13:21:32 -0700373 if (!parser)
374 {
375 return;
376 }
Ed Tanous62598e32023-07-17 17:06:25 -0700377 BMCWEB_LOG_DEBUG("recvMessage() data: {}", parser->get().body());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000378
379 unsigned int respCode = parser->get().result_int();
Ed Tanous62598e32023-07-17 17:06:25 -0700380 BMCWEB_LOG_DEBUG("recvMessage() Header Response Code: {}", respCode);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000381
382 // Make sure the received response code is valid as defined by
383 // the associated retry policy
Carson Labradod14a48f2023-02-22 00:24:54 +0000384 if (connPolicy->invalidResp(respCode))
AppaRao Pulie38778a2022-06-27 23:09:03 +0000385 {
386 // The listener failed to receive the Sent-Event
Ed Tanous62598e32023-07-17 17:06:25 -0700387 BMCWEB_LOG_ERROR(
388 "recvMessage() Listener Failed to "
Ed Tanousa716aa72023-08-01 11:35:53 -0700389 "receive Sent-Event. Header Response Code: {} from {}",
390 respCode, host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000391 state = ConnState::recvFailed;
392 waitAndRetry();
393 return;
394 }
Ed Tanous002d39b2022-05-31 08:59:27 -0700395
AppaRao Pulie38778a2022-06-27 23:09:03 +0000396 // Send is successful
397 // Reset the counter just in case this was after retrying
398 retryCount = 0;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530399
AppaRao Pulie38778a2022-06-27 23:09:03 +0000400 // Keep the connection alive if server supports it
401 // Else close the connection
Ed Tanous62598e32023-07-17 17:06:25 -0700402 BMCWEB_LOG_DEBUG("recvMessage() keepalive : {}", parser->keep_alive());
AppaRao Pulibd030d02020-03-20 03:34:29 +0530403
AppaRao Pulie38778a2022-06-27 23:09:03 +0000404 // Copy the response into a Response object so that it can be
405 // processed by the callback function.
Ed Tanous27b0cf92023-08-07 12:02:40 -0700406 res.response = parser->release();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000407 callback(parser->keep_alive(), connId, res);
Carson Labrado513d1ff2022-07-19 00:38:15 +0000408 res.clear();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530409 }
410
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800411 static void onTimeout(const std::weak_ptr<ConnectionInfo>& weakSelf,
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800412 const boost::system::error_code& ec)
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800413 {
414 if (ec == boost::asio::error::operation_aborted)
415 {
Ed Tanous62598e32023-07-17 17:06:25 -0700416 BMCWEB_LOG_DEBUG(
417 "async_wait failed since the operation is aborted");
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800418 return;
419 }
420 if (ec)
421 {
Ed Tanous62598e32023-07-17 17:06:25 -0700422 BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
Ed Tanous27b0cf92023-08-07 12:02:40 -0700423 // If the timer fails, we need to close the socket anyway, same
424 // as if it expired.
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800425 }
426 std::shared_ptr<ConnectionInfo> self = weakSelf.lock();
427 if (self == nullptr)
428 {
429 return;
430 }
431 self->waitAndRetry();
432 }
433
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530434 void waitAndRetry()
AppaRao Pulibd030d02020-03-20 03:34:29 +0530435 {
Carson Labradod14a48f2023-02-22 00:24:54 +0000436 if ((retryCount >= connPolicy->maxRetryAttempts) ||
AppaRao Pulie38778a2022-06-27 23:09:03 +0000437 (state == ConnState::sslInitFailed))
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530438 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700439 BMCWEB_LOG_ERROR("Maximum number of retries reached. {}", host);
Ed Tanous62598e32023-07-17 17:06:25 -0700440 BMCWEB_LOG_DEBUG("Retry policy: {}", connPolicy->retryPolicyAction);
Carson Labrado039a47e2022-04-05 16:03:20 +0000441
Carson Labradod14a48f2023-02-22 00:24:54 +0000442 if (connPolicy->retryPolicyAction == "TerminateAfterRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530443 {
444 // TODO: delete subscription
445 state = ConnState::terminated;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530446 }
Carson Labradod14a48f2023-02-22 00:24:54 +0000447 if (connPolicy->retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530448 {
449 state = ConnState::suspended;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530450 }
Carson Labrado513d1ff2022-07-19 00:38:15 +0000451
452 // We want to return a 502 to indicate there was an error with
453 // the external server
454 res.result(boost::beast::http::status::bad_gateway);
455 callback(false, connId, res);
456 res.clear();
457
Ed Tanous27b0cf92023-08-07 12:02:40 -0700458 // Reset the retrycount to zero so that client can try
459 // connecting again if needed
Ed Tanous3174e4d2020-10-07 11:41:22 -0700460 retryCount = 0;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530461 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530462 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530463
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530464 retryCount++;
465
Ed Tanous62598e32023-07-17 17:06:25 -0700466 BMCWEB_LOG_DEBUG("Attempt retry after {} seconds. RetryCount = {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700467 connPolicy->retryIntervalSecs.count(), retryCount);
Carson Labradod14a48f2023-02-22 00:24:54 +0000468 timer.expires_after(connPolicy->retryIntervalSecs);
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700469 timer.async_wait(std::bind_front(&ConnectionInfo::onTimerDone, this,
470 shared_from_this()));
471 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530472
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700473 void onTimerDone(const std::shared_ptr<ConnectionInfo>& /*self*/,
474 const boost::system::error_code& ec)
475 {
476 if (ec == boost::asio::error::operation_aborted)
477 {
Ed Tanous62598e32023-07-17 17:06:25 -0700478 BMCWEB_LOG_DEBUG(
479 "async_wait failed since the operation is aborted{}",
480 ec.message());
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700481 }
482 else if (ec)
483 {
Ed Tanous62598e32023-07-17 17:06:25 -0700484 BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700485 // Ignore the error and continue the retry loop to attempt
486 // sending the event as per the retry policy
487 }
488
489 // Let's close the connection and restart from resolve.
490 doClose(true);
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;
514 doResolve();
515 }
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
589 public:
Carson Labradod14a48f2023-02-22 00:24:54 +0000590 explicit ConnectionInfo(
591 boost::asio::io_context& iocIn, const std::string& idIn,
592 const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
Ed Tanousa716aa72023-08-01 11:35:53 -0700593 boost::urls::url_view hostIn, unsigned int connIdIn) :
Ed Tanous8a592812022-06-04 09:06:59 -0700594 subId(idIn),
Ed Tanousa716aa72023-08-01 11:35:53 -0700595 connPolicy(connPolicyIn), host(hostIn), connId(connIdIn),
596 resolver(iocIn), conn(iocIn), timer(iocIn)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000597 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700598 if (host.scheme() == "https")
AppaRao Pulie38778a2022-06-27 23:09:03 +0000599 {
600 std::optional<boost::asio::ssl::context> sslCtx =
601 ensuressl::getSSLClientContext();
602
603 if (!sslCtx)
604 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700605 BMCWEB_LOG_ERROR("prepareSSLContext failed - {}, id: {}", host,
606 connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000607 // Don't retry if failure occurs while preparing SSL context
Ed Tanous27b0cf92023-08-07 12:02:40 -0700608 // such as certificate is invalid or set cipher failure or
609 // set host name failure etc... Setting conn state to
610 // sslInitFailed and connection state will be transitioned
611 // to next state depending on retry policy set by
612 // subscription.
AppaRao Pulie38778a2022-06-27 23:09:03 +0000613 state = ConnState::sslInitFailed;
614 waitAndRetry();
615 return;
616 }
617 sslConn.emplace(conn, *sslCtx);
618 setCipherSuiteTLSext();
619 }
620 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000621};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530622
Carson Labradof52c03c2022-03-23 18:50:15 +0000623class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
624{
625 private:
626 boost::asio::io_context& ioc;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000627 std::string id;
Carson Labradod14a48f2023-02-22 00:24:54 +0000628 std::shared_ptr<ConnectionPolicy> connPolicy;
Ed Tanousa716aa72023-08-01 11:35:53 -0700629 boost::urls::url destIP;
Carson Labradof52c03c2022-03-23 18:50:15 +0000630 std::vector<std::shared_ptr<ConnectionInfo>> connections;
631 boost::container::devector<PendingRequest> requestQueue;
632
633 friend class HttpClient;
634
Carson Labrado244256c2022-04-27 17:16:32 +0000635 // Configure a connections's request, callback, and retry info in
636 // preparation to begin sending the request
Carson Labradof52c03c2022-03-23 18:50:15 +0000637 void setConnProps(ConnectionInfo& conn)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530638 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000639 if (requestQueue.empty())
AppaRao Pulibd030d02020-03-20 03:34:29 +0530640 {
Ed Tanous62598e32023-07-17 17:06:25 -0700641 BMCWEB_LOG_ERROR(
642 "setConnProps() should not have been called when requestQueue is empty");
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530643 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530644 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530645
Carson Labrado244256c2022-04-27 17:16:32 +0000646 auto nextReq = requestQueue.front();
Carson Labrado244256c2022-04-27 17:16:32 +0000647 conn.req = std::move(nextReq.req);
648 conn.callback = std::move(nextReq.callback);
Carson Labradof52c03c2022-03-23 18:50:15 +0000649
Ed Tanousa716aa72023-08-01 11:35:53 -0700650 BMCWEB_LOG_DEBUG("Setting properties for connection {}, id: {}",
651 conn.host, conn.connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000652
653 // We can remove the request from the queue at this point
654 requestQueue.pop_front();
655 }
656
Carson Labradof52c03c2022-03-23 18:50:15 +0000657 // Gets called as part of callback after request is sent
658 // Reuses the connection if there are any requests waiting to be sent
659 // Otherwise closes the connection if it is not a keep-alive
660 void sendNext(bool keepAlive, uint32_t connId)
661 {
662 auto conn = connections[connId];
Carson Labrado46a81462022-04-27 21:11:37 +0000663
664 // Allow the connection's handler to be deleted
665 // This is needed because of Redfish Aggregation passing an
666 // AsyncResponse shared_ptr to this callback
667 conn->callback = nullptr;
668
Carson Labradof52c03c2022-03-23 18:50:15 +0000669 // Reuse the connection to send the next request in the queue
670 if (!requestQueue.empty())
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530671 {
Ed Tanous62598e32023-07-17 17:06:25 -0700672 BMCWEB_LOG_DEBUG(
Ed Tanousa716aa72023-08-01 11:35:53 -0700673 "{} requests remaining in queue for {}, reusing connnection {}",
674 requestQueue.size(), destIP, connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000675
676 setConnProps(*conn);
677
678 if (keepAlive)
679 {
680 conn->sendMessage();
681 }
682 else
683 {
684 // Server is not keep-alive enabled so we need to close the
685 // connection and then start over from resolve
686 conn->doClose();
687 conn->doResolve();
688 }
689 return;
690 }
691
692 // No more messages to send so close the connection if necessary
693 if (keepAlive)
694 {
695 conn->state = ConnState::idle;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530696 }
697 else
698 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000699 // Abort the connection since server is not keep-alive enabled
700 conn->state = ConnState::abortConnection;
701 conn->doClose();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530702 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530703 }
704
Ed Tanousa716aa72023-08-01 11:35:53 -0700705 void sendData(std::string&& data, boost::urls::url_view destUri,
Carson Labrado244256c2022-04-27 17:16:32 +0000706 const boost::beast::http::fields& httpHeader,
707 const boost::beast::http::verb verb,
Ed Tanous6b3db602022-06-28 19:41:44 -0700708 const std::function<void(Response&)>& resHandler)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530709 {
Carson Labrado244256c2022-04-27 17:16:32 +0000710 // Construct the request to be sent
711 boost::beast::http::request<boost::beast::http::string_body> thisReq(
Ed Tanousa716aa72023-08-01 11:35:53 -0700712 verb, destUri.encoded_target(), 11, "", httpHeader);
713 thisReq.set(boost::beast::http::field::host,
714 destUri.encoded_host_address());
Carson Labrado244256c2022-04-27 17:16:32 +0000715 thisReq.keep_alive(true);
716 thisReq.body() = std::move(data);
717 thisReq.prepare_payload();
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700718 auto cb = std::bind_front(&ConnectionPool::afterSendData,
719 weak_from_this(), resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000720 // Reuse an existing connection if one is available
721 for (unsigned int i = 0; i < connections.size(); i++)
722 {
723 auto conn = connections[i];
724 if ((conn->state == ConnState::idle) ||
725 (conn->state == ConnState::initialized) ||
726 (conn->state == ConnState::closed))
727 {
Carson Labrado244256c2022-04-27 17:16:32 +0000728 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000729 conn->callback = std::move(cb);
Ed Tanousa716aa72023-08-01 11:35:53 -0700730 std::string commonMsg = std::format("{} from pool {}", i, id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000731
732 if (conn->state == ConnState::idle)
733 {
Ed Tanous62598e32023-07-17 17:06:25 -0700734 BMCWEB_LOG_DEBUG("Grabbing idle connection {}", commonMsg);
Carson Labradof52c03c2022-03-23 18:50:15 +0000735 conn->sendMessage();
736 }
737 else
738 {
Ed Tanous62598e32023-07-17 17:06:25 -0700739 BMCWEB_LOG_DEBUG("Reusing existing connection {}",
740 commonMsg);
Carson Labradof52c03c2022-03-23 18:50:15 +0000741 conn->doResolve();
742 }
743 return;
744 }
745 }
746
Ed Tanous27b0cf92023-08-07 12:02:40 -0700747 // All connections in use so create a new connection or add request
748 // to the queue
Carson Labradod14a48f2023-02-22 00:24:54 +0000749 if (connections.size() < connPolicy->maxConnections)
Carson Labradof52c03c2022-03-23 18:50:15 +0000750 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700751 BMCWEB_LOG_DEBUG("Adding new connection to pool {}", id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000752 auto conn = addConnection();
Carson Labrado244256c2022-04-27 17:16:32 +0000753 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000754 conn->callback = std::move(cb);
Carson Labradof52c03c2022-03-23 18:50:15 +0000755 conn->doResolve();
756 }
757 else if (requestQueue.size() < maxRequestQueueSize)
758 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700759 BMCWEB_LOG_DEBUG("Max pool size reached. Adding data to queue {}",
760 id);
Carson Labradod14a48f2023-02-22 00:24:54 +0000761 requestQueue.emplace_back(std::move(thisReq), std::move(cb));
Carson Labradof52c03c2022-03-23 18:50:15 +0000762 }
763 else
764 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700765 // If we can't buffer the request then we should let the
766 // callback handle a 429 Too Many Requests dummy response
Ed Tanous62598e32023-07-17 17:06:25 -0700767 BMCWEB_LOG_ERROR("{}:{} request queue full. Dropping request.",
Ed Tanousa716aa72023-08-01 11:35:53 -0700768 id);
Carson Labrado43e14d32022-11-09 00:25:20 +0000769 Response dummyRes;
770 dummyRes.result(boost::beast::http::status::too_many_requests);
771 resHandler(dummyRes);
Carson Labradof52c03c2022-03-23 18:50:15 +0000772 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530773 }
774
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700775 // Callback to be called once the request has been sent
776 static void afterSendData(const std::weak_ptr<ConnectionPool>& weakSelf,
777 const std::function<void(Response&)>& resHandler,
778 bool keepAlive, uint32_t connId, Response& res)
779 {
780 // Allow provided callback to perform additional processing of the
781 // request
782 resHandler(res);
783
784 // If requests remain in the queue then we want to reuse this
785 // connection to send the next request
786 std::shared_ptr<ConnectionPool> self = weakSelf.lock();
787 if (!self)
788 {
Ed Tanous62598e32023-07-17 17:06:25 -0700789 BMCWEB_LOG_CRITICAL("{} Failed to capture connection",
790 logPtr(self.get()));
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700791 return;
792 }
793
794 self->sendNext(keepAlive, connId);
795 }
796
Carson Labradof52c03c2022-03-23 18:50:15 +0000797 std::shared_ptr<ConnectionInfo>& addConnection()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530798 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000799 unsigned int newId = static_cast<unsigned int>(connections.size());
800
AppaRao Pulie38778a2022-06-27 23:09:03 +0000801 auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>(
Ed Tanousa716aa72023-08-01 11:35:53 -0700802 ioc, id, connPolicy, destIP, newId));
Carson Labradof52c03c2022-03-23 18:50:15 +0000803
Ed Tanousa716aa72023-08-01 11:35:53 -0700804 BMCWEB_LOG_DEBUG("Added connection {} to pool {}",
805 connections.size() - 1, id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000806
807 return ret;
808 }
809
810 public:
Carson Labradod14a48f2023-02-22 00:24:54 +0000811 explicit ConnectionPool(
812 boost::asio::io_context& iocIn, const std::string& idIn,
813 const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
Ed Tanousa716aa72023-08-01 11:35:53 -0700814 boost::urls::url_view destIPIn) :
Ed Tanous8a592812022-06-04 09:06:59 -0700815 ioc(iocIn),
Ed Tanousa716aa72023-08-01 11:35:53 -0700816 id(idIn), connPolicy(connPolicyIn), destIP(destIPIn)
Carson Labradof52c03c2022-03-23 18:50:15 +0000817 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700818 BMCWEB_LOG_DEBUG("Initializing connection pool for {}", id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000819
820 // Initialize the pool with a single connection
821 addConnection();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530822 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530823};
824
Carson Labradof52c03c2022-03-23 18:50:15 +0000825class HttpClient
826{
827 private:
828 std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
829 connectionPools;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700830 boost::asio::io_context& ioc;
Carson Labradod14a48f2023-02-22 00:24:54 +0000831 std::shared_ptr<ConnectionPolicy> connPolicy;
Carson Labradof52c03c2022-03-23 18:50:15 +0000832
Carson Labrado039a47e2022-04-05 16:03:20 +0000833 // Used as a dummy callback by sendData() in order to call
834 // sendDataWithCallback()
Ed Tanous02cad962022-06-30 16:50:15 -0700835 static void genericResHandler(const Response& res)
Carson Labrado039a47e2022-04-05 16:03:20 +0000836 {
Ed Tanous62598e32023-07-17 17:06:25 -0700837 BMCWEB_LOG_DEBUG("Response handled with return code: {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700838 res.resultInt());
Ed Tanous4ee8e212022-05-28 09:42:51 -0700839 }
Carson Labrado039a47e2022-04-05 16:03:20 +0000840
Carson Labradof52c03c2022-03-23 18:50:15 +0000841 public:
Carson Labradod14a48f2023-02-22 00:24:54 +0000842 HttpClient() = delete;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700843 explicit HttpClient(boost::asio::io_context& iocIn,
844 const std::shared_ptr<ConnectionPolicy>& connPolicyIn) :
845 ioc(iocIn),
Carson Labradod14a48f2023-02-22 00:24:54 +0000846 connPolicy(connPolicyIn)
847 {}
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700848
Carson Labradof52c03c2022-03-23 18:50:15 +0000849 HttpClient(const HttpClient&) = delete;
850 HttpClient& operator=(const HttpClient&) = delete;
851 HttpClient(HttpClient&&) = delete;
852 HttpClient& operator=(HttpClient&&) = delete;
853 ~HttpClient() = default;
854
Ed Tanousa716aa72023-08-01 11:35:53 -0700855 // Send a request to destIP where additional processing of the
Carson Labrado039a47e2022-04-05 16:03:20 +0000856 // result is not required
Ed Tanousa716aa72023-08-01 11:35:53 -0700857 void sendData(std::string&& data, boost::urls::url_view destUri,
Carson Labradof52c03c2022-03-23 18:50:15 +0000858 const boost::beast::http::fields& httpHeader,
Carson Labradod14a48f2023-02-22 00:24:54 +0000859 const boost::beast::http::verb verb)
Carson Labradof52c03c2022-03-23 18:50:15 +0000860 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000861 const std::function<void(Response&)> cb = genericResHandler;
Ed Tanousa716aa72023-08-01 11:35:53 -0700862 sendDataWithCallback(std::move(data), destUri, httpHeader, verb, cb);
Carson Labrado039a47e2022-04-05 16:03:20 +0000863 }
864
Ed Tanousa716aa72023-08-01 11:35:53 -0700865 // Send request to destIP and use the provided callback to
Carson Labrado039a47e2022-04-05 16:03:20 +0000866 // handle the response
Ed Tanousa716aa72023-08-01 11:35:53 -0700867 void sendDataWithCallback(std::string&& data, boost::urls::url_view destUrl,
Carson Labrado039a47e2022-04-05 16:03:20 +0000868 const boost::beast::http::fields& httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000869 const boost::beast::http::verb verb,
Ed Tanous6b3db602022-06-28 19:41:44 -0700870 const std::function<void(Response&)>& resHandler)
Carson Labrado039a47e2022-04-05 16:03:20 +0000871 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700872 std::string clientKey = std::format("{}://{}", destUrl.scheme(),
873 destUrl.encoded_host_and_port());
Carson Labradod14a48f2023-02-22 00:24:54 +0000874 auto pool = connectionPools.try_emplace(clientKey);
875 if (pool.first->second == nullptr)
Carson Labradof52c03c2022-03-23 18:50:15 +0000876 {
Carson Labradod14a48f2023-02-22 00:24:54 +0000877 pool.first->second = std::make_shared<ConnectionPool>(
Ed Tanousa716aa72023-08-01 11:35:53 -0700878 ioc, clientKey, connPolicy, destUrl);
Carson Labradof52c03c2022-03-23 18:50:15 +0000879 }
Ed Tanous27b0cf92023-08-07 12:02:40 -0700880 // Send the data using either the existing connection pool or the
881 // newly created connection pool
Ed Tanousa716aa72023-08-01 11:35:53 -0700882 pool.first->second->sendData(std::move(data), destUrl, httpHeader, verb,
Carson Labradod14a48f2023-02-22 00:24:54 +0000883 resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000884 }
885};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530886} // namespace crow