blob: 046df2e0f051fc0e11b8b5a42710ce8c50703700 [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 Tanousa716aa72023-08-01 11:35:53 -070042#include <boost/url/url_view.hpp>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050043
AppaRao Pulibd030d02020-03-20 03:34:29 +053044#include <cstdlib>
45#include <functional>
46#include <iostream>
47#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{
53
Carson Labrado66d90c22022-12-07 22:34:33 +000054// With Redfish Aggregation it is assumed we will connect to another instance
55// of BMCWeb which can handle 100 simultaneous connections.
56constexpr size_t maxPoolSize = 20;
57constexpr size_t maxRequestQueueSize = 500;
Carson Labrado17dcc312022-07-28 22:17:28 +000058constexpr unsigned int httpReadBodyLimit = 131072;
Carson Labrado4d942722022-06-22 22:16:10 +000059constexpr unsigned int httpReadBufferSize = 4096;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053060
AppaRao Pulibd030d02020-03-20 03:34:29 +053061enum class ConnState
62{
AppaRao Puli2a5689a2020-04-29 15:24:31 +053063 initialized,
Sunitha Harish29a82b02021-02-18 15:54:16 +053064 resolveInProgress,
65 resolveFailed,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053066 connectInProgress,
67 connectFailed,
AppaRao Pulibd030d02020-03-20 03:34:29 +053068 connected,
AppaRao Pulie38778a2022-06-27 23:09:03 +000069 handshakeInProgress,
70 handshakeFailed,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053071 sendInProgress,
72 sendFailed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053073 recvInProgress,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053074 recvFailed,
75 idle,
Ayushi Smritife44eb02020-05-15 15:24:45 +053076 closed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053077 suspended,
78 terminated,
79 abortConnection,
AppaRao Pulie38778a2022-06-27 23:09:03 +000080 sslInitFailed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053081 retry
AppaRao Pulibd030d02020-03-20 03:34:29 +053082};
83
Carson Labradoa7a80292022-06-01 16:01:52 +000084static inline boost::system::error_code
85 defaultRetryHandler(unsigned int respCode)
86{
87 // As a default, assume 200X is alright
Ed Tanous62598e32023-07-17 17:06:25 -070088 BMCWEB_LOG_DEBUG("Using default check for response code validity");
Carson Labradoa7a80292022-06-01 16:01:52 +000089 if ((respCode < 200) || (respCode >= 300))
90 {
91 return boost::system::errc::make_error_code(
92 boost::system::errc::result_out_of_range);
93 }
94
95 // Return 0 if the response code is valid
96 return boost::system::errc::make_error_code(boost::system::errc::success);
97};
98
Carson Labradof52c03c2022-03-23 18:50:15 +000099// We need to allow retry information to be set before a message has been sent
100// and a connection pool has been created
Carson Labradod14a48f2023-02-22 00:24:54 +0000101struct ConnectionPolicy
Carson Labradof52c03c2022-03-23 18:50:15 +0000102{
103 uint32_t maxRetryAttempts = 5;
Carson Labradod14a48f2023-02-22 00:24:54 +0000104
105 // the max size of requests in bytes. 0 for unlimited
106 boost::optional<uint64_t> requestByteLimit = httpReadBodyLimit;
107
108 size_t maxConnections = 1;
109
Carson Labradof52c03c2022-03-23 18:50:15 +0000110 std::string retryPolicyAction = "TerminateAfterRetries";
Carson Labradod14a48f2023-02-22 00:24:54 +0000111
112 std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0);
Carson Labradoa7a80292022-06-01 16:01:52 +0000113 std::function<boost::system::error_code(unsigned int respCode)>
114 invalidResp = defaultRetryHandler;
Carson Labradof52c03c2022-03-23 18:50:15 +0000115};
116
117struct PendingRequest
118{
Carson Labrado244256c2022-04-27 17:16:32 +0000119 boost::beast::http::request<boost::beast::http::string_body> req;
Carson Labrado039a47e2022-04-05 16:03:20 +0000120 std::function<void(bool, uint32_t, Response&)> callback;
Carson Labrado039a47e2022-04-05 16:03:20 +0000121 PendingRequest(
Ed Tanous8a592812022-06-04 09:06:59 -0700122 boost::beast::http::request<boost::beast::http::string_body>&& reqIn,
Carson Labradod14a48f2023-02-22 00:24:54 +0000123 const std::function<void(bool, uint32_t, Response&)>& callbackIn) :
Ed Tanous8a592812022-06-04 09:06:59 -0700124 req(std::move(reqIn)),
Carson Labradod14a48f2023-02-22 00:24:54 +0000125 callback(callbackIn)
Carson Labradof52c03c2022-03-23 18:50:15 +0000126 {}
127};
128
Ed Tanouse01d0c32023-06-30 13:21:32 -0700129namespace http = boost::beast::http;
Carson Labradof52c03c2022-03-23 18:50:15 +0000130class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo>
AppaRao Pulibd030d02020-03-20 03:34:29 +0530131{
132 private:
Carson Labradof52c03c2022-03-23 18:50:15 +0000133 ConnState state = ConnState::initialized;
134 uint32_t retryCount = 0;
Carson Labradof52c03c2022-03-23 18:50:15 +0000135 std::string subId;
Carson Labradod14a48f2023-02-22 00:24:54 +0000136 std::shared_ptr<ConnectionPolicy> connPolicy;
Ed Tanousa716aa72023-08-01 11:35:53 -0700137 boost::urls::url host;
Carson Labradof52c03c2022-03-23 18:50:15 +0000138 uint32_t connId;
139
Carson Labradof52c03c2022-03-23 18:50:15 +0000140 // Data buffers
Ed Tanouse01d0c32023-06-30 13:21:32 -0700141 http::request<http::string_body> req;
142 using parser_type = http::response_parser<http::string_body>;
143 std::optional<parser_type> parser;
Carson Labrado4d942722022-06-22 22:16:10 +0000144 boost::beast::flat_static_buffer<httpReadBufferSize> buffer;
Carson Labrado039a47e2022-04-05 16:03:20 +0000145 Response res;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530146
Carson Labradof52c03c2022-03-23 18:50:15 +0000147 // Ascync callables
Carson Labrado039a47e2022-04-05 16:03:20 +0000148 std::function<void(bool, uint32_t, Response&)> callback;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700149
150#ifdef BMCWEB_DBUS_DNS_RESOLVER
Ed Tanouse1452be2021-10-04 17:03:52 -0700151 using Resolver = async_resolve::Resolver;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700152#else
153 using Resolver = boost::asio::ip::tcp::resolver;
154#endif
155 Resolver resolver;
156
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800157 boost::asio::ip::tcp::socket conn;
158 std::optional<boost::beast::ssl_stream<boost::asio::ip::tcp::socket&>>
159 sslConn;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000160
Carson Labradof52c03c2022-03-23 18:50:15 +0000161 boost::asio::steady_timer timer;
Ed Tanous84b35602021-09-08 20:06:32 -0700162
Carson Labradof52c03c2022-03-23 18:50:15 +0000163 friend class ConnectionPool;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530164
Sunitha Harish29a82b02021-02-18 15:54:16 +0530165 void doResolve()
166 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530167 state = ConnState::resolveInProgress;
Ed Tanousa716aa72023-08-01 11:35:53 -0700168 BMCWEB_LOG_DEBUG("Trying to resolve: {}, id: {}", host, connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530169
Ed Tanousa716aa72023-08-01 11:35:53 -0700170 resolver.async_resolve(host.encoded_host_address(), host.port(),
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700171 std::bind_front(&ConnectionInfo::afterResolve,
172 this, shared_from_this()));
Sunitha Harish29a82b02021-02-18 15:54:16 +0530173 }
174
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700175 void afterResolve(const std::shared_ptr<ConnectionInfo>& /*self*/,
176 const boost::system::error_code& ec,
177 const Resolver::results_type& endpointList)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530178 {
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700179 if (ec || (endpointList.empty()))
180 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700181 BMCWEB_LOG_ERROR("Resolve failed: {} {}", ec.message(), host);
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700182 state = ConnState::resolveFailed;
183 waitAndRetry();
184 return;
185 }
Ed Tanousa716aa72023-08-01 11:35:53 -0700186 BMCWEB_LOG_DEBUG("Resolved {}, id: {}", host, connId);
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530187 state = ConnState::connectInProgress;
188
Ed Tanousa716aa72023-08-01 11:35:53 -0700189 BMCWEB_LOG_DEBUG("Trying to connect to: {}, id: {}", host, connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530190
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800191 timer.expires_after(std::chrono::seconds(30));
192 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
193
194 boost::asio::async_connect(
195 conn, endpointList,
196 std::bind_front(&ConnectionInfo::afterConnect, this,
197 shared_from_this()));
AppaRao Pulie38778a2022-06-27 23:09:03 +0000198 }
199
200 void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/,
Ed Tanous81c4e332023-05-18 10:30:34 -0700201 const boost::beast::error_code& ec,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000202 const boost::asio::ip::tcp::endpoint& endpoint)
203 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000204 // The operation already timed out. We don't want do continue down
205 // this branch
206 if (ec && ec == boost::asio::error::operation_aborted)
207 {
208 return;
209 }
210
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800211 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000212 if (ec)
213 {
Ed Tanous62598e32023-07-17 17:06:25 -0700214 BMCWEB_LOG_ERROR("Connect {}:{}, id: {} failed: {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700215 endpoint.address().to_string(), endpoint.port(),
216 connId, ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000217 state = ConnState::connectFailed;
218 waitAndRetry();
219 return;
220 }
Ed Tanousa716aa72023-08-01 11:35:53 -0700221 BMCWEB_LOG_DEBUG("Connected to: {}:{}, id: {}",
222 endpoint.address().to_string(), endpoint.port(),
223 connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000224 if (sslConn)
225 {
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800226 doSslHandshake();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000227 return;
228 }
229 state = ConnState::connected;
230 sendMessage();
231 }
232
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800233 void doSslHandshake()
AppaRao Pulie38778a2022-06-27 23:09:03 +0000234 {
235 if (!sslConn)
236 {
237 return;
238 }
239 state = ConnState::handshakeInProgress;
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800240 timer.expires_after(std::chrono::seconds(30));
241 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
AppaRao Pulie38778a2022-06-27 23:09:03 +0000242 sslConn->async_handshake(
243 boost::asio::ssl::stream_base::client,
244 std::bind_front(&ConnectionInfo::afterSslHandshake, this,
245 shared_from_this()));
246 }
247
248 void afterSslHandshake(const std::shared_ptr<ConnectionInfo>& /*self*/,
Ed Tanous81c4e332023-05-18 10:30:34 -0700249 const boost::beast::error_code& ec)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000250 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000251 // The operation already timed out. We don't want do continue down
252 // this branch
253 if (ec && ec == boost::asio::error::operation_aborted)
254 {
255 return;
256 }
257
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800258 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000259 if (ec)
260 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700261 BMCWEB_LOG_ERROR("SSL Handshake failed - id: {} error: {}", connId,
262 ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000263 state = ConnState::handshakeFailed;
264 waitAndRetry();
265 return;
266 }
Ed Tanousa716aa72023-08-01 11:35:53 -0700267 BMCWEB_LOG_DEBUG("SSL Handshake successful - id: {}", connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000268 state = ConnState::connected;
269 sendMessage();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530270 }
271
Carson Labradof52c03c2022-03-23 18:50:15 +0000272 void sendMessage()
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530273 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530274 state = ConnState::sendInProgress;
275
AppaRao Pulibd030d02020-03-20 03:34:29 +0530276 // Set a timeout on the operation
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800277 timer.expires_after(std::chrono::seconds(30));
278 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
AppaRao Pulibd030d02020-03-20 03:34:29 +0530279
280 // Send the HTTP request to the remote host
AppaRao Pulie38778a2022-06-27 23:09:03 +0000281 if (sslConn)
282 {
283 boost::beast::http::async_write(
284 *sslConn, req,
285 std::bind_front(&ConnectionInfo::afterWrite, this,
286 shared_from_this()));
287 }
288 else
289 {
290 boost::beast::http::async_write(
291 conn, req,
292 std::bind_front(&ConnectionInfo::afterWrite, this,
293 shared_from_this()));
294 }
295 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530296
AppaRao Pulie38778a2022-06-27 23:09:03 +0000297 void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/,
298 const boost::beast::error_code& ec, size_t bytesTransferred)
299 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000300 // The operation already timed out. We don't want do continue down
301 // this branch
302 if (ec && ec == boost::asio::error::operation_aborted)
303 {
304 return;
305 }
306
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800307 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000308 if (ec)
309 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700310 BMCWEB_LOG_ERROR("sendMessage() failed: {} {}", ec.message(), host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000311 state = ConnState::sendFailed;
312 waitAndRetry();
313 return;
314 }
Ed Tanous62598e32023-07-17 17:06:25 -0700315 BMCWEB_LOG_DEBUG("sendMessage() bytes transferred: {}",
316 bytesTransferred);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000317
318 recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530319 }
320
321 void recvMessage()
322 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530323 state = ConnState::recvInProgress;
324
Ed Tanouse01d0c32023-06-30 13:21:32 -0700325 parser_type& thisParser = parser.emplace(std::piecewise_construct,
326 std::make_tuple());
Carson Labradod14a48f2023-02-22 00:24:54 +0000327
Ed Tanouse01d0c32023-06-30 13:21:32 -0700328 thisParser.body_limit(connPolicy->requestByteLimit);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530329
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800330 timer.expires_after(std::chrono::seconds(30));
331 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
332
AppaRao Pulibd030d02020-03-20 03:34:29 +0530333 // Receive the HTTP response
AppaRao Pulie38778a2022-06-27 23:09:03 +0000334 if (sslConn)
335 {
336 boost::beast::http::async_read(
Ed Tanouse01d0c32023-06-30 13:21:32 -0700337 *sslConn, buffer, thisParser,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000338 std::bind_front(&ConnectionInfo::afterRead, this,
339 shared_from_this()));
340 }
341 else
342 {
343 boost::beast::http::async_read(
Ed Tanouse01d0c32023-06-30 13:21:32 -0700344 conn, buffer, thisParser,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000345 std::bind_front(&ConnectionInfo::afterRead, this,
346 shared_from_this()));
347 }
348 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530349
AppaRao Pulie38778a2022-06-27 23:09:03 +0000350 void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/,
351 const boost::beast::error_code& ec,
352 const std::size_t& bytesTransferred)
353 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000354 // The operation already timed out. We don't want do continue down
355 // this branch
356 if (ec && ec == boost::asio::error::operation_aborted)
357 {
358 return;
359 }
360
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800361 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000362 if (ec && ec != boost::asio::ssl::error::stream_truncated)
363 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700364 BMCWEB_LOG_ERROR("recvMessage() failed: {} from {}", ec.message(),
365 host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000366 state = ConnState::recvFailed;
367 waitAndRetry();
368 return;
369 }
Ed Tanous62598e32023-07-17 17:06:25 -0700370 BMCWEB_LOG_DEBUG("recvMessage() bytes transferred: {}",
371 bytesTransferred);
Ed Tanouse01d0c32023-06-30 13:21:32 -0700372 if (!parser)
373 {
374 return;
375 }
Ed Tanous62598e32023-07-17 17:06:25 -0700376 BMCWEB_LOG_DEBUG("recvMessage() data: {}", parser->get().body());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000377
378 unsigned int respCode = parser->get().result_int();
Ed Tanous62598e32023-07-17 17:06:25 -0700379 BMCWEB_LOG_DEBUG("recvMessage() Header Response Code: {}", respCode);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000380
381 // Make sure the received response code is valid as defined by
382 // the associated retry policy
Carson Labradod14a48f2023-02-22 00:24:54 +0000383 if (connPolicy->invalidResp(respCode))
AppaRao Pulie38778a2022-06-27 23:09:03 +0000384 {
385 // The listener failed to receive the Sent-Event
Ed Tanous62598e32023-07-17 17:06:25 -0700386 BMCWEB_LOG_ERROR(
387 "recvMessage() Listener Failed to "
Ed Tanousa716aa72023-08-01 11:35:53 -0700388 "receive Sent-Event. Header Response Code: {} from {}",
389 respCode, host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000390 state = ConnState::recvFailed;
391 waitAndRetry();
392 return;
393 }
Ed Tanous002d39b2022-05-31 08:59:27 -0700394
AppaRao Pulie38778a2022-06-27 23:09:03 +0000395 // Send is successful
396 // Reset the counter just in case this was after retrying
397 retryCount = 0;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530398
AppaRao Pulie38778a2022-06-27 23:09:03 +0000399 // Keep the connection alive if server supports it
400 // Else close the connection
Ed Tanous62598e32023-07-17 17:06:25 -0700401 BMCWEB_LOG_DEBUG("recvMessage() keepalive : {}", parser->keep_alive());
AppaRao Pulibd030d02020-03-20 03:34:29 +0530402
AppaRao Pulie38778a2022-06-27 23:09:03 +0000403 // Copy the response into a Response object so that it can be
404 // processed by the callback function.
AppaRao Pulie38778a2022-06-27 23:09:03 +0000405 res.stringResponse = parser->release();
406 callback(parser->keep_alive(), connId, res);
Carson Labrado513d1ff2022-07-19 00:38:15 +0000407 res.clear();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530408 }
409
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800410 static void onTimeout(const std::weak_ptr<ConnectionInfo>& weakSelf,
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800411 const boost::system::error_code& ec)
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800412 {
413 if (ec == boost::asio::error::operation_aborted)
414 {
Ed Tanous62598e32023-07-17 17:06:25 -0700415 BMCWEB_LOG_DEBUG(
416 "async_wait failed since the operation is aborted");
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800417 return;
418 }
419 if (ec)
420 {
Ed Tanous62598e32023-07-17 17:06:25 -0700421 BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800422 // If the timer fails, we need to close the socket anyway, same as
423 // if it expired.
424 }
425 std::shared_ptr<ConnectionInfo> self = weakSelf.lock();
426 if (self == nullptr)
427 {
428 return;
429 }
430 self->waitAndRetry();
431 }
432
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530433 void waitAndRetry()
AppaRao Pulibd030d02020-03-20 03:34:29 +0530434 {
Carson Labradod14a48f2023-02-22 00:24:54 +0000435 if ((retryCount >= connPolicy->maxRetryAttempts) ||
AppaRao Pulie38778a2022-06-27 23:09:03 +0000436 (state == ConnState::sslInitFailed))
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530437 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700438 BMCWEB_LOG_ERROR("Maximum number of retries reached. {}", host);
Ed Tanous62598e32023-07-17 17:06:25 -0700439 BMCWEB_LOG_DEBUG("Retry policy: {}", connPolicy->retryPolicyAction);
Carson Labrado039a47e2022-04-05 16:03:20 +0000440
Carson Labradod14a48f2023-02-22 00:24:54 +0000441 if (connPolicy->retryPolicyAction == "TerminateAfterRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530442 {
443 // TODO: delete subscription
444 state = ConnState::terminated;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530445 }
Carson Labradod14a48f2023-02-22 00:24:54 +0000446 if (connPolicy->retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530447 {
448 state = ConnState::suspended;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530449 }
Carson Labrado513d1ff2022-07-19 00:38:15 +0000450
451 // We want to return a 502 to indicate there was an error with
452 // the external server
453 res.result(boost::beast::http::status::bad_gateway);
454 callback(false, connId, res);
455 res.clear();
456
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530457 // Reset the retrycount to zero so that client can try connecting
458 // again if needed
Ed Tanous3174e4d2020-10-07 11:41:22 -0700459 retryCount = 0;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530460 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530461 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530462
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530463 retryCount++;
464
Ed Tanous62598e32023-07-17 17:06:25 -0700465 BMCWEB_LOG_DEBUG("Attempt retry after {} seconds. RetryCount = {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700466 connPolicy->retryIntervalSecs.count(), retryCount);
Carson Labradod14a48f2023-02-22 00:24:54 +0000467 timer.expires_after(connPolicy->retryIntervalSecs);
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700468 timer.async_wait(std::bind_front(&ConnectionInfo::onTimerDone, this,
469 shared_from_this()));
470 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530471
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700472 void onTimerDone(const std::shared_ptr<ConnectionInfo>& /*self*/,
473 const boost::system::error_code& ec)
474 {
475 if (ec == boost::asio::error::operation_aborted)
476 {
Ed Tanous62598e32023-07-17 17:06:25 -0700477 BMCWEB_LOG_DEBUG(
478 "async_wait failed since the operation is aborted{}",
479 ec.message());
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700480 }
481 else if (ec)
482 {
Ed Tanous62598e32023-07-17 17:06:25 -0700483 BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700484 // Ignore the error and continue the retry loop to attempt
485 // sending the event as per the retry policy
486 }
487
488 // Let's close the connection and restart from resolve.
489 doClose(true);
Ayushi Smritife44eb02020-05-15 15:24:45 +0530490 }
491
AppaRao Pulie38778a2022-06-27 23:09:03 +0000492 void shutdownConn(bool retry)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530493 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000494 boost::beast::error_code ec;
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800495 conn.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
Carson Labradof52c03c2022-03-23 18:50:15 +0000496 conn.close();
497
498 // not_connected happens sometimes so don't bother reporting it.
499 if (ec && ec != boost::beast::errc::not_connected)
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530500 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700501 BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId,
Ed Tanous62598e32023-07-17 17:06:25 -0700502 ec.message());
Carson Labradof52c03c2022-03-23 18:50:15 +0000503 }
Carson Labrado5cab68f2022-07-11 22:26:21 +0000504 else
505 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700506 BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId);
Carson Labrado5cab68f2022-07-11 22:26:21 +0000507 }
Ed Tanousca723762022-06-28 19:40:39 -0700508
Carson Labrado513d1ff2022-07-19 00:38:15 +0000509 if (retry)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000510 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000511 // Now let's try to resend the data
512 state = ConnState::retry;
513 doResolve();
514 }
515 else
516 {
517 state = ConnState::closed;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000518 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000519 }
520
AppaRao Pulie38778a2022-06-27 23:09:03 +0000521 void doClose(bool retry = false)
Carson Labradof52c03c2022-03-23 18:50:15 +0000522 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000523 if (!sslConn)
524 {
525 shutdownConn(retry);
526 return;
527 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000528
AppaRao Pulie38778a2022-06-27 23:09:03 +0000529 sslConn->async_shutdown(
530 std::bind_front(&ConnectionInfo::afterSslShutdown, this,
531 shared_from_this(), retry));
532 }
533
534 void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/,
535 bool retry, const boost::system::error_code& ec)
536 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000537 if (ec)
Carson Labradof52c03c2022-03-23 18:50:15 +0000538 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700539 BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId,
Ed Tanous62598e32023-07-17 17:06:25 -0700540 ec.message());
Carson Labradof52c03c2022-03-23 18:50:15 +0000541 }
Carson Labrado5cab68f2022-07-11 22:26:21 +0000542 else
543 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700544 BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId);
Carson Labrado5cab68f2022-07-11 22:26:21 +0000545 }
AppaRao Pulie38778a2022-06-27 23:09:03 +0000546 shutdownConn(retry);
547 }
Ed Tanousca723762022-06-28 19:40:39 -0700548
AppaRao Pulie38778a2022-06-27 23:09:03 +0000549 void setCipherSuiteTLSext()
550 {
551 if (!sslConn)
552 {
553 return;
554 }
Ravi Tejae7c29912023-07-31 09:39:32 -0500555
556 if (host.host_type() != boost::urls::host_type::name)
557 {
558 // Avoid setting SNI hostname if its IP address
559 return;
560 }
561 // Create a null terminated string for SSL
Ed Tanousa716aa72023-08-01 11:35:53 -0700562 std::string hostname(host.encoded_host_address());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000563 // NOTE: The SSL_set_tlsext_host_name is defined in tlsv1.h header
564 // file but its having old style casting (name is cast to void*).
565 // Since bmcweb compiler treats all old-style-cast as error, its
566 // causing the build failure. So replaced the same macro inline and
567 // did corrected the code by doing static_cast to viod*. This has to
568 // be fixed in openssl library in long run. Set SNI Hostname (many
569 // hosts need this to handshake successfully)
570 if (SSL_ctrl(sslConn->native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME,
571 TLSEXT_NAMETYPE_host_name,
Ed Tanousa716aa72023-08-01 11:35:53 -0700572 static_cast<void*>(hostname.data())) == 0)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000573
574 {
575 boost::beast::error_code ec{static_cast<int>(::ERR_get_error()),
576 boost::asio::error::get_ssl_category()};
577
Ed Tanousa716aa72023-08-01 11:35:53 -0700578 BMCWEB_LOG_ERROR("SSL_set_tlsext_host_name {}, id: {} failed: {}",
579 host, connId, ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000580 // Set state as sslInit failed so that we close the connection
581 // and take appropriate action as per retry configuration.
582 state = ConnState::sslInitFailed;
583 waitAndRetry();
584 return;
585 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530586 }
587
588 public:
Carson Labradod14a48f2023-02-22 00:24:54 +0000589 explicit ConnectionInfo(
590 boost::asio::io_context& iocIn, const std::string& idIn,
591 const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
Ed Tanousa716aa72023-08-01 11:35:53 -0700592 boost::urls::url_view hostIn, unsigned int connIdIn) :
Ed Tanous8a592812022-06-04 09:06:59 -0700593 subId(idIn),
Ed Tanousa716aa72023-08-01 11:35:53 -0700594 connPolicy(connPolicyIn), host(hostIn), connId(connIdIn),
595 resolver(iocIn), conn(iocIn), timer(iocIn)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000596 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700597 if (host.scheme() == "https")
AppaRao Pulie38778a2022-06-27 23:09:03 +0000598 {
599 std::optional<boost::asio::ssl::context> sslCtx =
600 ensuressl::getSSLClientContext();
601
602 if (!sslCtx)
603 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700604 BMCWEB_LOG_ERROR("prepareSSLContext failed - {}, id: {}", host,
605 connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000606 // Don't retry if failure occurs while preparing SSL context
607 // such as certificate is invalid or set cipher failure or set
608 // host name failure etc... Setting conn state to sslInitFailed
609 // and connection state will be transitioned to next state
610 // depending on retry policy set by subscription.
611 state = ConnState::sslInitFailed;
612 waitAndRetry();
613 return;
614 }
615 sslConn.emplace(conn, *sslCtx);
616 setCipherSuiteTLSext();
617 }
618 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000619};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530620
Carson Labradof52c03c2022-03-23 18:50:15 +0000621class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
622{
623 private:
624 boost::asio::io_context& ioc;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000625 std::string id;
Carson Labradod14a48f2023-02-22 00:24:54 +0000626 std::shared_ptr<ConnectionPolicy> connPolicy;
Ed Tanousa716aa72023-08-01 11:35:53 -0700627 boost::urls::url destIP;
Carson Labradof52c03c2022-03-23 18:50:15 +0000628 std::vector<std::shared_ptr<ConnectionInfo>> connections;
629 boost::container::devector<PendingRequest> requestQueue;
630
631 friend class HttpClient;
632
Carson Labrado244256c2022-04-27 17:16:32 +0000633 // Configure a connections's request, callback, and retry info in
634 // preparation to begin sending the request
Carson Labradof52c03c2022-03-23 18:50:15 +0000635 void setConnProps(ConnectionInfo& conn)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530636 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000637 if (requestQueue.empty())
AppaRao Pulibd030d02020-03-20 03:34:29 +0530638 {
Ed Tanous62598e32023-07-17 17:06:25 -0700639 BMCWEB_LOG_ERROR(
640 "setConnProps() should not have been called when requestQueue is empty");
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530641 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530642 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530643
Carson Labrado244256c2022-04-27 17:16:32 +0000644 auto nextReq = requestQueue.front();
Carson Labrado244256c2022-04-27 17:16:32 +0000645 conn.req = std::move(nextReq.req);
646 conn.callback = std::move(nextReq.callback);
Carson Labradof52c03c2022-03-23 18:50:15 +0000647
Ed Tanousa716aa72023-08-01 11:35:53 -0700648 BMCWEB_LOG_DEBUG("Setting properties for connection {}, id: {}",
649 conn.host, conn.connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000650
651 // We can remove the request from the queue at this point
652 requestQueue.pop_front();
653 }
654
Carson Labradof52c03c2022-03-23 18:50:15 +0000655 // Gets called as part of callback after request is sent
656 // Reuses the connection if there are any requests waiting to be sent
657 // Otherwise closes the connection if it is not a keep-alive
658 void sendNext(bool keepAlive, uint32_t connId)
659 {
660 auto conn = connections[connId];
Carson Labrado46a81462022-04-27 21:11:37 +0000661
662 // Allow the connection's handler to be deleted
663 // This is needed because of Redfish Aggregation passing an
664 // AsyncResponse shared_ptr to this callback
665 conn->callback = nullptr;
666
Carson Labradof52c03c2022-03-23 18:50:15 +0000667 // Reuse the connection to send the next request in the queue
668 if (!requestQueue.empty())
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530669 {
Ed Tanous62598e32023-07-17 17:06:25 -0700670 BMCWEB_LOG_DEBUG(
Ed Tanousa716aa72023-08-01 11:35:53 -0700671 "{} requests remaining in queue for {}, reusing connnection {}",
672 requestQueue.size(), destIP, connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000673
674 setConnProps(*conn);
675
676 if (keepAlive)
677 {
678 conn->sendMessage();
679 }
680 else
681 {
682 // Server is not keep-alive enabled so we need to close the
683 // connection and then start over from resolve
684 conn->doClose();
685 conn->doResolve();
686 }
687 return;
688 }
689
690 // No more messages to send so close the connection if necessary
691 if (keepAlive)
692 {
693 conn->state = ConnState::idle;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530694 }
695 else
696 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000697 // Abort the connection since server is not keep-alive enabled
698 conn->state = ConnState::abortConnection;
699 conn->doClose();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530700 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530701 }
702
Ed Tanousa716aa72023-08-01 11:35:53 -0700703 void sendData(std::string&& data, boost::urls::url_view destUri,
Carson Labrado244256c2022-04-27 17:16:32 +0000704 const boost::beast::http::fields& httpHeader,
705 const boost::beast::http::verb verb,
Ed Tanous6b3db602022-06-28 19:41:44 -0700706 const std::function<void(Response&)>& resHandler)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530707 {
Carson Labrado244256c2022-04-27 17:16:32 +0000708 // Construct the request to be sent
709 boost::beast::http::request<boost::beast::http::string_body> thisReq(
Ed Tanousa716aa72023-08-01 11:35:53 -0700710 verb, destUri.encoded_target(), 11, "", httpHeader);
711 thisReq.set(boost::beast::http::field::host,
712 destUri.encoded_host_address());
Carson Labrado244256c2022-04-27 17:16:32 +0000713 thisReq.keep_alive(true);
714 thisReq.body() = std::move(data);
715 thisReq.prepare_payload();
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700716 auto cb = std::bind_front(&ConnectionPool::afterSendData,
717 weak_from_this(), resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000718 // Reuse an existing connection if one is available
719 for (unsigned int i = 0; i < connections.size(); i++)
720 {
721 auto conn = connections[i];
722 if ((conn->state == ConnState::idle) ||
723 (conn->state == ConnState::initialized) ||
724 (conn->state == ConnState::closed))
725 {
Carson Labrado244256c2022-04-27 17:16:32 +0000726 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000727 conn->callback = std::move(cb);
Ed Tanousa716aa72023-08-01 11:35:53 -0700728 std::string commonMsg = std::format("{} from pool {}", i, id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000729
730 if (conn->state == ConnState::idle)
731 {
Ed Tanous62598e32023-07-17 17:06:25 -0700732 BMCWEB_LOG_DEBUG("Grabbing idle connection {}", commonMsg);
Carson Labradof52c03c2022-03-23 18:50:15 +0000733 conn->sendMessage();
734 }
735 else
736 {
Ed Tanous62598e32023-07-17 17:06:25 -0700737 BMCWEB_LOG_DEBUG("Reusing existing connection {}",
738 commonMsg);
Carson Labradof52c03c2022-03-23 18:50:15 +0000739 conn->doResolve();
740 }
741 return;
742 }
743 }
744
745 // All connections in use so create a new connection or add request to
746 // the queue
Carson Labradod14a48f2023-02-22 00:24:54 +0000747 if (connections.size() < connPolicy->maxConnections)
Carson Labradof52c03c2022-03-23 18:50:15 +0000748 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700749 BMCWEB_LOG_DEBUG("Adding new connection to pool {}", id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000750 auto conn = addConnection();
Carson Labrado244256c2022-04-27 17:16:32 +0000751 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000752 conn->callback = std::move(cb);
Carson Labradof52c03c2022-03-23 18:50:15 +0000753 conn->doResolve();
754 }
755 else if (requestQueue.size() < maxRequestQueueSize)
756 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700757 BMCWEB_LOG_DEBUG("Max pool size reached. Adding data to queue {}",
758 id);
Carson Labradod14a48f2023-02-22 00:24:54 +0000759 requestQueue.emplace_back(std::move(thisReq), std::move(cb));
Carson Labradof52c03c2022-03-23 18:50:15 +0000760 }
761 else
762 {
Carson Labrado43e14d32022-11-09 00:25:20 +0000763 // If we can't buffer the request then we should let the callback
764 // handle a 429 Too Many Requests dummy response
Ed Tanous62598e32023-07-17 17:06:25 -0700765 BMCWEB_LOG_ERROR("{}:{} request queue full. Dropping request.",
Ed Tanousa716aa72023-08-01 11:35:53 -0700766 id);
Carson Labrado43e14d32022-11-09 00:25:20 +0000767 Response dummyRes;
768 dummyRes.result(boost::beast::http::status::too_many_requests);
769 resHandler(dummyRes);
Carson Labradof52c03c2022-03-23 18:50:15 +0000770 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530771 }
772
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700773 // Callback to be called once the request has been sent
774 static void afterSendData(const std::weak_ptr<ConnectionPool>& weakSelf,
775 const std::function<void(Response&)>& resHandler,
776 bool keepAlive, uint32_t connId, Response& res)
777 {
778 // Allow provided callback to perform additional processing of the
779 // request
780 resHandler(res);
781
782 // If requests remain in the queue then we want to reuse this
783 // connection to send the next request
784 std::shared_ptr<ConnectionPool> self = weakSelf.lock();
785 if (!self)
786 {
Ed Tanous62598e32023-07-17 17:06:25 -0700787 BMCWEB_LOG_CRITICAL("{} Failed to capture connection",
788 logPtr(self.get()));
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700789 return;
790 }
791
792 self->sendNext(keepAlive, connId);
793 }
794
Carson Labradof52c03c2022-03-23 18:50:15 +0000795 std::shared_ptr<ConnectionInfo>& addConnection()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530796 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000797 unsigned int newId = static_cast<unsigned int>(connections.size());
798
AppaRao Pulie38778a2022-06-27 23:09:03 +0000799 auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>(
Ed Tanousa716aa72023-08-01 11:35:53 -0700800 ioc, id, connPolicy, destIP, newId));
Carson Labradof52c03c2022-03-23 18:50:15 +0000801
Ed Tanousa716aa72023-08-01 11:35:53 -0700802 BMCWEB_LOG_DEBUG("Added connection {} to pool {}",
803 connections.size() - 1, id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000804
805 return ret;
806 }
807
808 public:
Carson Labradod14a48f2023-02-22 00:24:54 +0000809 explicit ConnectionPool(
810 boost::asio::io_context& iocIn, const std::string& idIn,
811 const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
Ed Tanousa716aa72023-08-01 11:35:53 -0700812 boost::urls::url_view destIPIn) :
Ed Tanous8a592812022-06-04 09:06:59 -0700813 ioc(iocIn),
Ed Tanousa716aa72023-08-01 11:35:53 -0700814 id(idIn), connPolicy(connPolicyIn), destIP(destIPIn)
Carson Labradof52c03c2022-03-23 18:50:15 +0000815 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700816 BMCWEB_LOG_DEBUG("Initializing connection pool for {}", id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000817
818 // Initialize the pool with a single connection
819 addConnection();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530820 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530821};
822
Carson Labradof52c03c2022-03-23 18:50:15 +0000823class HttpClient
824{
825 private:
826 std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
827 connectionPools;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700828 boost::asio::io_context& ioc;
Carson Labradod14a48f2023-02-22 00:24:54 +0000829 std::shared_ptr<ConnectionPolicy> connPolicy;
Carson Labradof52c03c2022-03-23 18:50:15 +0000830
Carson Labrado039a47e2022-04-05 16:03:20 +0000831 // Used as a dummy callback by sendData() in order to call
832 // sendDataWithCallback()
Ed Tanous02cad962022-06-30 16:50:15 -0700833 static void genericResHandler(const Response& res)
Carson Labrado039a47e2022-04-05 16:03:20 +0000834 {
Ed Tanous62598e32023-07-17 17:06:25 -0700835 BMCWEB_LOG_DEBUG("Response handled with return code: {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700836 res.resultInt());
Ed Tanous4ee8e212022-05-28 09:42:51 -0700837 }
Carson Labrado039a47e2022-04-05 16:03:20 +0000838
Carson Labradof52c03c2022-03-23 18:50:15 +0000839 public:
Carson Labradod14a48f2023-02-22 00:24:54 +0000840 HttpClient() = delete;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700841 explicit HttpClient(boost::asio::io_context& iocIn,
842 const std::shared_ptr<ConnectionPolicy>& connPolicyIn) :
843 ioc(iocIn),
Carson Labradod14a48f2023-02-22 00:24:54 +0000844 connPolicy(connPolicyIn)
845 {}
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700846
Carson Labradof52c03c2022-03-23 18:50:15 +0000847 HttpClient(const HttpClient&) = delete;
848 HttpClient& operator=(const HttpClient&) = delete;
849 HttpClient(HttpClient&&) = delete;
850 HttpClient& operator=(HttpClient&&) = delete;
851 ~HttpClient() = default;
852
Ed Tanousa716aa72023-08-01 11:35:53 -0700853 // Send a request to destIP where additional processing of the
Carson Labrado039a47e2022-04-05 16:03:20 +0000854 // result is not required
Ed Tanousa716aa72023-08-01 11:35:53 -0700855 void sendData(std::string&& data, boost::urls::url_view destUri,
Carson Labradof52c03c2022-03-23 18:50:15 +0000856 const boost::beast::http::fields& httpHeader,
Carson Labradod14a48f2023-02-22 00:24:54 +0000857 const boost::beast::http::verb verb)
Carson Labradof52c03c2022-03-23 18:50:15 +0000858 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000859 const std::function<void(Response&)> cb = genericResHandler;
Ed Tanousa716aa72023-08-01 11:35:53 -0700860 sendDataWithCallback(std::move(data), destUri, httpHeader, verb, cb);
Carson Labrado039a47e2022-04-05 16:03:20 +0000861 }
862
Ed Tanousa716aa72023-08-01 11:35:53 -0700863 // Send request to destIP and use the provided callback to
Carson Labrado039a47e2022-04-05 16:03:20 +0000864 // handle the response
Ed Tanousa716aa72023-08-01 11:35:53 -0700865 void sendDataWithCallback(std::string&& data, boost::urls::url_view destUrl,
Carson Labrado039a47e2022-04-05 16:03:20 +0000866 const boost::beast::http::fields& httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000867 const boost::beast::http::verb verb,
Ed Tanous6b3db602022-06-28 19:41:44 -0700868 const std::function<void(Response&)>& resHandler)
Carson Labrado039a47e2022-04-05 16:03:20 +0000869 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700870 std::string clientKey = std::format("{}://{}", destUrl.scheme(),
871 destUrl.encoded_host_and_port());
Carson Labradod14a48f2023-02-22 00:24:54 +0000872 auto pool = connectionPools.try_emplace(clientKey);
873 if (pool.first->second == nullptr)
Carson Labradof52c03c2022-03-23 18:50:15 +0000874 {
Carson Labradod14a48f2023-02-22 00:24:54 +0000875 pool.first->second = std::make_shared<ConnectionPool>(
Ed Tanousa716aa72023-08-01 11:35:53 -0700876 ioc, clientKey, connPolicy, destUrl);
Carson Labradof52c03c2022-03-23 18:50:15 +0000877 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000878 // Send the data using either the existing connection pool or the newly
879 // created connection pool
Ed Tanousa716aa72023-08-01 11:35:53 -0700880 pool.first->second->sendData(std::move(data), destUrl, httpHeader, verb,
Carson Labradod14a48f2023-02-22 00:24:54 +0000881 resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000882 }
883};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530884} // namespace crow