blob: db426d6b9bc03c7795541fa83099c323c557fbb8 [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
Ed Tanous38afdb92024-12-11 23:57:53 -0800321 parser_type& thisParser = parser.emplace();
Carson Labradod14a48f2023-02-22 00:24:54 +0000322
Ed Tanouse01d0c32023-06-30 13:21:32 -0700323 thisParser.body_limit(connPolicy->requestByteLimit);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530324
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800325 timer.expires_after(std::chrono::seconds(30));
326 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
327
AppaRao Pulibd030d02020-03-20 03:34:29 +0530328 // Receive the HTTP response
AppaRao Pulie38778a2022-06-27 23:09:03 +0000329 if (sslConn)
330 {
331 boost::beast::http::async_read(
Ed Tanouse01d0c32023-06-30 13:21:32 -0700332 *sslConn, buffer, thisParser,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000333 std::bind_front(&ConnectionInfo::afterRead, this,
334 shared_from_this()));
335 }
336 else
337 {
338 boost::beast::http::async_read(
Ed Tanouse01d0c32023-06-30 13:21:32 -0700339 conn, buffer, thisParser,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000340 std::bind_front(&ConnectionInfo::afterRead, this,
341 shared_from_this()));
342 }
343 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530344
AppaRao Pulie38778a2022-06-27 23:09:03 +0000345 void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/,
346 const boost::beast::error_code& ec,
347 const std::size_t& bytesTransferred)
348 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000349 // The operation already timed out. We don't want do continue down
350 // this branch
351 if (ec && ec == boost::asio::error::operation_aborted)
352 {
353 return;
354 }
355
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800356 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000357 if (ec && ec != boost::asio::ssl::error::stream_truncated)
358 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700359 BMCWEB_LOG_ERROR("recvMessage() failed: {} from {}", ec.message(),
360 host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000361 state = ConnState::recvFailed;
362 waitAndRetry();
363 return;
364 }
Ed Tanous62598e32023-07-17 17:06:25 -0700365 BMCWEB_LOG_DEBUG("recvMessage() bytes transferred: {}",
366 bytesTransferred);
Ed Tanouse01d0c32023-06-30 13:21:32 -0700367 if (!parser)
368 {
369 return;
370 }
Ed Tanous52e31622024-01-23 16:31:11 -0800371 BMCWEB_LOG_DEBUG("recvMessage() data: {}", parser->get().body().str());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000372
373 unsigned int respCode = parser->get().result_int();
Ed Tanous62598e32023-07-17 17:06:25 -0700374 BMCWEB_LOG_DEBUG("recvMessage() Header Response Code: {}", respCode);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000375
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600376 // Handle the case of stream_truncated. Some servers close the ssl
377 // connection uncleanly, so check to see if we got a full response
378 // before we handle this as an error.
379 if (!parser->is_done())
380 {
381 state = ConnState::recvFailed;
382 waitAndRetry();
383 return;
384 }
385
AppaRao Pulie38778a2022-06-27 23:09:03 +0000386 // Make sure the received response code is valid as defined by
387 // the associated retry policy
Carson Labradod14a48f2023-02-22 00:24:54 +0000388 if (connPolicy->invalidResp(respCode))
AppaRao Pulie38778a2022-06-27 23:09:03 +0000389 {
390 // The listener failed to receive the Sent-Event
Ed Tanous62598e32023-07-17 17:06:25 -0700391 BMCWEB_LOG_ERROR(
392 "recvMessage() Listener Failed to "
Ed Tanousa716aa72023-08-01 11:35:53 -0700393 "receive Sent-Event. Header Response Code: {} from {}",
394 respCode, host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000395 state = ConnState::recvFailed;
396 waitAndRetry();
397 return;
398 }
Ed Tanous002d39b2022-05-31 08:59:27 -0700399
AppaRao Pulie38778a2022-06-27 23:09:03 +0000400 // Send is successful
401 // Reset the counter just in case this was after retrying
402 retryCount = 0;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530403
AppaRao Pulie38778a2022-06-27 23:09:03 +0000404 // Keep the connection alive if server supports it
405 // Else close the connection
Ed Tanous62598e32023-07-17 17:06:25 -0700406 BMCWEB_LOG_DEBUG("recvMessage() keepalive : {}", parser->keep_alive());
AppaRao Pulibd030d02020-03-20 03:34:29 +0530407
AppaRao Pulie38778a2022-06-27 23:09:03 +0000408 // Copy the response into a Response object so that it can be
409 // processed by the callback function.
Ed Tanous27b0cf92023-08-07 12:02:40 -0700410 res.response = parser->release();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000411 callback(parser->keep_alive(), connId, res);
Carson Labrado513d1ff2022-07-19 00:38:15 +0000412 res.clear();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530413 }
414
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800415 static void onTimeout(const std::weak_ptr<ConnectionInfo>& weakSelf,
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800416 const boost::system::error_code& ec)
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800417 {
418 if (ec == boost::asio::error::operation_aborted)
419 {
Ed Tanous62598e32023-07-17 17:06:25 -0700420 BMCWEB_LOG_DEBUG(
421 "async_wait failed since the operation is aborted");
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800422 return;
423 }
424 if (ec)
425 {
Ed Tanous62598e32023-07-17 17:06:25 -0700426 BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
Ed Tanous27b0cf92023-08-07 12:02:40 -0700427 // If the timer fails, we need to close the socket anyway, same
428 // as if it expired.
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800429 }
430 std::shared_ptr<ConnectionInfo> self = weakSelf.lock();
431 if (self == nullptr)
432 {
433 return;
434 }
435 self->waitAndRetry();
436 }
437
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530438 void waitAndRetry()
AppaRao Pulibd030d02020-03-20 03:34:29 +0530439 {
Carson Labradod14a48f2023-02-22 00:24:54 +0000440 if ((retryCount >= connPolicy->maxRetryAttempts) ||
AppaRao Pulie38778a2022-06-27 23:09:03 +0000441 (state == ConnState::sslInitFailed))
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530442 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700443 BMCWEB_LOG_ERROR("Maximum number of retries reached. {}", host);
Ed Tanous62598e32023-07-17 17:06:25 -0700444 BMCWEB_LOG_DEBUG("Retry policy: {}", connPolicy->retryPolicyAction);
Carson Labrado039a47e2022-04-05 16:03:20 +0000445
Carson Labradod14a48f2023-02-22 00:24:54 +0000446 if (connPolicy->retryPolicyAction == "TerminateAfterRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530447 {
448 // TODO: delete subscription
449 state = ConnState::terminated;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530450 }
Carson Labradod14a48f2023-02-22 00:24:54 +0000451 if (connPolicy->retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530452 {
453 state = ConnState::suspended;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530454 }
Carson Labrado513d1ff2022-07-19 00:38:15 +0000455
456 // We want to return a 502 to indicate there was an error with
457 // the external server
458 res.result(boost::beast::http::status::bad_gateway);
459 callback(false, connId, res);
460 res.clear();
461
Ed Tanous27b0cf92023-08-07 12:02:40 -0700462 // Reset the retrycount to zero so that client can try
463 // connecting again if needed
Ed Tanous3174e4d2020-10-07 11:41:22 -0700464 retryCount = 0;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530465 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530466 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530467
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530468 retryCount++;
469
Ed Tanous62598e32023-07-17 17:06:25 -0700470 BMCWEB_LOG_DEBUG("Attempt retry after {} seconds. RetryCount = {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700471 connPolicy->retryIntervalSecs.count(), retryCount);
Carson Labradod14a48f2023-02-22 00:24:54 +0000472 timer.expires_after(connPolicy->retryIntervalSecs);
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700473 timer.async_wait(std::bind_front(&ConnectionInfo::onTimerDone, this,
474 shared_from_this()));
475 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530476
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700477 void onTimerDone(const std::shared_ptr<ConnectionInfo>& /*self*/,
478 const boost::system::error_code& ec)
479 {
480 if (ec == boost::asio::error::operation_aborted)
481 {
Ed Tanous62598e32023-07-17 17:06:25 -0700482 BMCWEB_LOG_DEBUG(
483 "async_wait failed since the operation is aborted{}",
484 ec.message());
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700485 }
486 else if (ec)
487 {
Ed Tanous62598e32023-07-17 17:06:25 -0700488 BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700489 // Ignore the error and continue the retry loop to attempt
490 // sending the event as per the retry policy
491 }
492
493 // Let's close the connection and restart from resolve.
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600494 shutdownConn(true);
495 }
496
497 void restartConnection()
498 {
499 BMCWEB_LOG_DEBUG("{}, id: {} restartConnection", host,
500 std::to_string(connId));
501 initializeConnection(host.scheme() == "https");
502 doResolve();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530503 }
504
AppaRao Pulie38778a2022-06-27 23:09:03 +0000505 void shutdownConn(bool retry)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530506 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000507 boost::beast::error_code ec;
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800508 conn.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
Carson Labradof52c03c2022-03-23 18:50:15 +0000509 conn.close();
510
511 // not_connected happens sometimes so don't bother reporting it.
512 if (ec && ec != boost::beast::errc::not_connected)
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530513 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700514 BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId,
Ed Tanous62598e32023-07-17 17:06:25 -0700515 ec.message());
Carson Labradof52c03c2022-03-23 18:50:15 +0000516 }
Carson Labrado5cab68f2022-07-11 22:26:21 +0000517 else
518 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700519 BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId);
Carson Labrado5cab68f2022-07-11 22:26:21 +0000520 }
Ed Tanousca723762022-06-28 19:40:39 -0700521
Carson Labrado513d1ff2022-07-19 00:38:15 +0000522 if (retry)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000523 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000524 // Now let's try to resend the data
525 state = ConnState::retry;
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600526 restartConnection();
Carson Labrado513d1ff2022-07-19 00:38:15 +0000527 }
528 else
529 {
530 state = ConnState::closed;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000531 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000532 }
533
AppaRao Pulie38778a2022-06-27 23:09:03 +0000534 void doClose(bool retry = false)
Carson Labradof52c03c2022-03-23 18:50:15 +0000535 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000536 if (!sslConn)
537 {
538 shutdownConn(retry);
539 return;
540 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000541
AppaRao Pulie38778a2022-06-27 23:09:03 +0000542 sslConn->async_shutdown(
543 std::bind_front(&ConnectionInfo::afterSslShutdown, this,
544 shared_from_this(), retry));
545 }
546
547 void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/,
548 bool retry, const boost::system::error_code& ec)
549 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000550 if (ec)
Carson Labradof52c03c2022-03-23 18:50:15 +0000551 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700552 BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId,
Ed Tanous62598e32023-07-17 17:06:25 -0700553 ec.message());
Carson Labradof52c03c2022-03-23 18:50:15 +0000554 }
Carson Labrado5cab68f2022-07-11 22:26:21 +0000555 else
556 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700557 BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId);
Carson Labrado5cab68f2022-07-11 22:26:21 +0000558 }
AppaRao Pulie38778a2022-06-27 23:09:03 +0000559 shutdownConn(retry);
560 }
Ed Tanousca723762022-06-28 19:40:39 -0700561
AppaRao Pulie38778a2022-06-27 23:09:03 +0000562 void setCipherSuiteTLSext()
563 {
564 if (!sslConn)
565 {
566 return;
567 }
Ravi Tejae7c29912023-07-31 09:39:32 -0500568
569 if (host.host_type() != boost::urls::host_type::name)
570 {
571 // Avoid setting SNI hostname if its IP address
572 return;
573 }
574 // Create a null terminated string for SSL
Ed Tanousa716aa72023-08-01 11:35:53 -0700575 std::string hostname(host.encoded_host_address());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000576 // NOTE: The SSL_set_tlsext_host_name is defined in tlsv1.h header
577 // file but its having old style casting (name is cast to void*).
578 // Since bmcweb compiler treats all old-style-cast as error, its
579 // causing the build failure. So replaced the same macro inline and
580 // did corrected the code by doing static_cast to viod*. This has to
581 // be fixed in openssl library in long run. Set SNI Hostname (many
582 // hosts need this to handshake successfully)
583 if (SSL_ctrl(sslConn->native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME,
584 TLSEXT_NAMETYPE_host_name,
Ed Tanousa716aa72023-08-01 11:35:53 -0700585 static_cast<void*>(hostname.data())) == 0)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000586
587 {
588 boost::beast::error_code ec{static_cast<int>(::ERR_get_error()),
589 boost::asio::error::get_ssl_category()};
590
Ed Tanousa716aa72023-08-01 11:35:53 -0700591 BMCWEB_LOG_ERROR("SSL_set_tlsext_host_name {}, id: {} failed: {}",
592 host, connId, ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000593 // Set state as sslInit failed so that we close the connection
594 // and take appropriate action as per retry configuration.
595 state = ConnState::sslInitFailed;
596 waitAndRetry();
597 return;
598 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530599 }
600
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600601 void initializeConnection(bool ssl)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000602 {
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600603 conn = boost::asio::ip::tcp::socket(ioc);
604 if (ssl)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000605 {
606 std::optional<boost::asio::ssl::context> sslCtx =
Ed Tanous19bb3622024-07-05 10:07:40 -0500607 ensuressl::getSSLClientContext(verifyCert);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000608
609 if (!sslCtx)
610 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700611 BMCWEB_LOG_ERROR("prepareSSLContext failed - {}, id: {}", host,
612 connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000613 // Don't retry if failure occurs while preparing SSL context
Ed Tanous27b0cf92023-08-07 12:02:40 -0700614 // such as certificate is invalid or set cipher failure or
615 // set host name failure etc... Setting conn state to
616 // sslInitFailed and connection state will be transitioned
617 // to next state depending on retry policy set by
618 // subscription.
AppaRao Pulie38778a2022-06-27 23:09:03 +0000619 state = ConnState::sslInitFailed;
620 waitAndRetry();
621 return;
622 }
623 sslConn.emplace(conn, *sslCtx);
624 setCipherSuiteTLSext();
625 }
626 }
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600627
628 public:
629 explicit ConnectionInfo(
630 boost::asio::io_context& iocIn, const std::string& idIn,
631 const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
Ed Tanous19bb3622024-07-05 10:07:40 -0500632 const boost::urls::url_view_base& hostIn,
633 ensuressl::VerifyCertificate verifyCertIn, unsigned int connIdIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400634 subId(idIn), connPolicy(connPolicyIn), host(hostIn),
635 verifyCert(verifyCertIn), connId(connIdIn), ioc(iocIn), resolver(iocIn),
636 conn(iocIn), timer(iocIn)
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600637 {
638 initializeConnection(host.scheme() == "https");
639 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000640};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530641
Carson Labradof52c03c2022-03-23 18:50:15 +0000642class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
643{
644 private:
645 boost::asio::io_context& ioc;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000646 std::string id;
Carson Labradod14a48f2023-02-22 00:24:54 +0000647 std::shared_ptr<ConnectionPolicy> connPolicy;
Ed Tanousa716aa72023-08-01 11:35:53 -0700648 boost::urls::url destIP;
Carson Labradof52c03c2022-03-23 18:50:15 +0000649 std::vector<std::shared_ptr<ConnectionInfo>> connections;
650 boost::container::devector<PendingRequest> requestQueue;
Ed Tanous19bb3622024-07-05 10:07:40 -0500651 ensuressl::VerifyCertificate verifyCert;
Carson Labradof52c03c2022-03-23 18:50:15 +0000652
653 friend class HttpClient;
654
Carson Labrado244256c2022-04-27 17:16:32 +0000655 // Configure a connections's request, callback, and retry info in
656 // preparation to begin sending the request
Carson Labradof52c03c2022-03-23 18:50:15 +0000657 void setConnProps(ConnectionInfo& conn)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530658 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000659 if (requestQueue.empty())
AppaRao Pulibd030d02020-03-20 03:34:29 +0530660 {
Ed Tanous62598e32023-07-17 17:06:25 -0700661 BMCWEB_LOG_ERROR(
662 "setConnProps() should not have been called when requestQueue is empty");
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530663 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530664 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530665
Ed Tanous52e31622024-01-23 16:31:11 -0800666 PendingRequest& nextReq = requestQueue.front();
Carson Labrado244256c2022-04-27 17:16:32 +0000667 conn.req = std::move(nextReq.req);
668 conn.callback = std::move(nextReq.callback);
Carson Labradof52c03c2022-03-23 18:50:15 +0000669
Ed Tanousa716aa72023-08-01 11:35:53 -0700670 BMCWEB_LOG_DEBUG("Setting properties for connection {}, id: {}",
671 conn.host, conn.connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000672
673 // We can remove the request from the queue at this point
674 requestQueue.pop_front();
675 }
676
Carson Labradof52c03c2022-03-23 18:50:15 +0000677 // Gets called as part of callback after request is sent
678 // Reuses the connection if there are any requests waiting to be sent
679 // Otherwise closes the connection if it is not a keep-alive
680 void sendNext(bool keepAlive, uint32_t connId)
681 {
682 auto conn = connections[connId];
Carson Labrado46a81462022-04-27 21:11:37 +0000683
684 // Allow the connection's handler to be deleted
685 // This is needed because of Redfish Aggregation passing an
686 // AsyncResponse shared_ptr to this callback
687 conn->callback = nullptr;
688
Carson Labradof52c03c2022-03-23 18:50:15 +0000689 // Reuse the connection to send the next request in the queue
690 if (!requestQueue.empty())
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530691 {
Ed Tanous62598e32023-07-17 17:06:25 -0700692 BMCWEB_LOG_DEBUG(
Ed Tanous8ece0e42024-01-02 13:16:50 -0800693 "{} requests remaining in queue for {}, reusing connection {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700694 requestQueue.size(), destIP, connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000695
696 setConnProps(*conn);
697
698 if (keepAlive)
699 {
700 conn->sendMessage();
701 }
702 else
703 {
704 // Server is not keep-alive enabled so we need to close the
705 // connection and then start over from resolve
706 conn->doClose();
Abhilash Raju2ecde742024-06-01 02:01:01 -0500707 conn->restartConnection();
Carson Labradof52c03c2022-03-23 18:50:15 +0000708 }
709 return;
710 }
711
712 // No more messages to send so close the connection if necessary
713 if (keepAlive)
714 {
715 conn->state = ConnState::idle;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530716 }
717 else
718 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000719 // Abort the connection since server is not keep-alive enabled
720 conn->state = ConnState::abortConnection;
721 conn->doClose();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530722 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530723 }
724
Ed Tanous4a7fbef2024-04-06 16:03:49 -0700725 void sendData(std::string&& data, const boost::urls::url_view_base& destUri,
Carson Labrado244256c2022-04-27 17:16:32 +0000726 const boost::beast::http::fields& httpHeader,
727 const boost::beast::http::verb verb,
Ed Tanous6b3db602022-06-28 19:41:44 -0700728 const std::function<void(Response&)>& resHandler)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530729 {
Carson Labrado244256c2022-04-27 17:16:32 +0000730 // Construct the request to be sent
Ed Tanousb2896142024-01-31 15:25:47 -0800731 boost::beast::http::request<bmcweb::HttpBody> thisReq(
Ed Tanousa716aa72023-08-01 11:35:53 -0700732 verb, destUri.encoded_target(), 11, "", httpHeader);
733 thisReq.set(boost::beast::http::field::host,
734 destUri.encoded_host_address());
Carson Labrado244256c2022-04-27 17:16:32 +0000735 thisReq.keep_alive(true);
Ed Tanous52e31622024-01-23 16:31:11 -0800736 thisReq.body().str() = std::move(data);
Carson Labrado244256c2022-04-27 17:16:32 +0000737 thisReq.prepare_payload();
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700738 auto cb = std::bind_front(&ConnectionPool::afterSendData,
739 weak_from_this(), resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000740 // Reuse an existing connection if one is available
741 for (unsigned int i = 0; i < connections.size(); i++)
742 {
743 auto conn = connections[i];
744 if ((conn->state == ConnState::idle) ||
745 (conn->state == ConnState::initialized) ||
746 (conn->state == ConnState::closed))
747 {
Carson Labrado244256c2022-04-27 17:16:32 +0000748 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000749 conn->callback = std::move(cb);
Ed Tanousa716aa72023-08-01 11:35:53 -0700750 std::string commonMsg = std::format("{} from pool {}", i, id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000751
752 if (conn->state == ConnState::idle)
753 {
Ed Tanous62598e32023-07-17 17:06:25 -0700754 BMCWEB_LOG_DEBUG("Grabbing idle connection {}", commonMsg);
Carson Labradof52c03c2022-03-23 18:50:15 +0000755 conn->sendMessage();
756 }
757 else
758 {
Ed Tanous62598e32023-07-17 17:06:25 -0700759 BMCWEB_LOG_DEBUG("Reusing existing connection {}",
760 commonMsg);
Abhilash Raju2ecde742024-06-01 02:01:01 -0500761 conn->restartConnection();
Carson Labradof52c03c2022-03-23 18:50:15 +0000762 }
763 return;
764 }
765 }
766
Ed Tanous27b0cf92023-08-07 12:02:40 -0700767 // All connections in use so create a new connection or add request
768 // to the queue
Carson Labradod14a48f2023-02-22 00:24:54 +0000769 if (connections.size() < connPolicy->maxConnections)
Carson Labradof52c03c2022-03-23 18:50:15 +0000770 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700771 BMCWEB_LOG_DEBUG("Adding new connection to pool {}", id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000772 auto conn = addConnection();
Carson Labrado244256c2022-04-27 17:16:32 +0000773 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000774 conn->callback = std::move(cb);
Carson Labradof52c03c2022-03-23 18:50:15 +0000775 conn->doResolve();
776 }
777 else if (requestQueue.size() < maxRequestQueueSize)
778 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700779 BMCWEB_LOG_DEBUG("Max pool size reached. Adding data to queue {}",
780 id);
Carson Labradod14a48f2023-02-22 00:24:54 +0000781 requestQueue.emplace_back(std::move(thisReq), std::move(cb));
Carson Labradof52c03c2022-03-23 18:50:15 +0000782 }
783 else
784 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700785 // If we can't buffer the request then we should let the
786 // callback handle a 429 Too Many Requests dummy response
Ed Tanous6ea90762024-04-07 08:38:44 -0700787 BMCWEB_LOG_ERROR("{} request queue full. Dropping request.", id);
Carson Labrado43e14d32022-11-09 00:25:20 +0000788 Response dummyRes;
789 dummyRes.result(boost::beast::http::status::too_many_requests);
790 resHandler(dummyRes);
Carson Labradof52c03c2022-03-23 18:50:15 +0000791 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530792 }
793
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700794 // Callback to be called once the request has been sent
795 static void afterSendData(const std::weak_ptr<ConnectionPool>& weakSelf,
796 const std::function<void(Response&)>& resHandler,
797 bool keepAlive, uint32_t connId, Response& res)
798 {
799 // Allow provided callback to perform additional processing of the
800 // request
801 resHandler(res);
802
803 // If requests remain in the queue then we want to reuse this
804 // connection to send the next request
805 std::shared_ptr<ConnectionPool> self = weakSelf.lock();
806 if (!self)
807 {
Ed Tanous62598e32023-07-17 17:06:25 -0700808 BMCWEB_LOG_CRITICAL("{} Failed to capture connection",
809 logPtr(self.get()));
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700810 return;
811 }
812
813 self->sendNext(keepAlive, connId);
814 }
815
Carson Labradof52c03c2022-03-23 18:50:15 +0000816 std::shared_ptr<ConnectionInfo>& addConnection()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530817 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000818 unsigned int newId = static_cast<unsigned int>(connections.size());
819
AppaRao Pulie38778a2022-06-27 23:09:03 +0000820 auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>(
Ed Tanous19bb3622024-07-05 10:07:40 -0500821 ioc, id, connPolicy, destIP, verifyCert, newId));
Carson Labradof52c03c2022-03-23 18:50:15 +0000822
Ed Tanousa716aa72023-08-01 11:35:53 -0700823 BMCWEB_LOG_DEBUG("Added connection {} to pool {}",
824 connections.size() - 1, id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000825
826 return ret;
827 }
828
829 public:
Carson Labradod14a48f2023-02-22 00:24:54 +0000830 explicit ConnectionPool(
831 boost::asio::io_context& iocIn, const std::string& idIn,
832 const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
Ed Tanous19bb3622024-07-05 10:07:40 -0500833 const boost::urls::url_view_base& destIPIn,
834 ensuressl::VerifyCertificate verifyCertIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400835 ioc(iocIn), id(idIn), connPolicy(connPolicyIn), destIP(destIPIn),
Ed Tanous19bb3622024-07-05 10:07:40 -0500836 verifyCert(verifyCertIn)
Carson Labradof52c03c2022-03-23 18:50:15 +0000837 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700838 BMCWEB_LOG_DEBUG("Initializing connection pool for {}", id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000839
840 // Initialize the pool with a single connection
841 addConnection();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530842 }
Myung Baea0969c72024-09-19 08:46:45 -0400843
844 // Check whether all connections are terminated
845 bool areAllConnectionsTerminated()
846 {
847 if (connections.empty())
848 {
849 BMCWEB_LOG_DEBUG("There are no connections for pool id:{}", id);
850 return false;
851 }
852 for (const auto& conn : connections)
853 {
854 if (conn != nullptr && conn->state != ConnState::terminated)
855 {
856 BMCWEB_LOG_DEBUG(
857 "Not all connections of pool id:{} are terminated", id);
858 return false;
859 }
860 }
861 BMCWEB_LOG_INFO("All connections of pool id:{} are terminated", id);
862 return true;
863 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530864};
865
Carson Labradof52c03c2022-03-23 18:50:15 +0000866class HttpClient
867{
868 private:
869 std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
870 connectionPools;
Ed Tanous4b712a22023-08-02 12:56:52 -0700871
872 // reference_wrapper here makes HttpClient movable
873 std::reference_wrapper<boost::asio::io_context> ioc;
Carson Labradod14a48f2023-02-22 00:24:54 +0000874 std::shared_ptr<ConnectionPolicy> connPolicy;
Carson Labradof52c03c2022-03-23 18:50:15 +0000875
Carson Labrado039a47e2022-04-05 16:03:20 +0000876 // Used as a dummy callback by sendData() in order to call
877 // sendDataWithCallback()
Ed Tanous02cad962022-06-30 16:50:15 -0700878 static void genericResHandler(const Response& res)
Carson Labrado039a47e2022-04-05 16:03:20 +0000879 {
Ed Tanous62598e32023-07-17 17:06:25 -0700880 BMCWEB_LOG_DEBUG("Response handled with return code: {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700881 res.resultInt());
Ed Tanous4ee8e212022-05-28 09:42:51 -0700882 }
Carson Labrado039a47e2022-04-05 16:03:20 +0000883
Carson Labradof52c03c2022-03-23 18:50:15 +0000884 public:
Carson Labradod14a48f2023-02-22 00:24:54 +0000885 HttpClient() = delete;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700886 explicit HttpClient(boost::asio::io_context& iocIn,
887 const std::shared_ptr<ConnectionPolicy>& connPolicyIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400888 ioc(iocIn), connPolicy(connPolicyIn)
Carson Labradod14a48f2023-02-22 00:24:54 +0000889 {}
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700890
Carson Labradof52c03c2022-03-23 18:50:15 +0000891 HttpClient(const HttpClient&) = delete;
892 HttpClient& operator=(const HttpClient&) = delete;
Ed Tanous4b712a22023-08-02 12:56:52 -0700893 HttpClient(HttpClient&& client) = default;
894 HttpClient& operator=(HttpClient&& client) = default;
Carson Labradof52c03c2022-03-23 18:50:15 +0000895 ~HttpClient() = default;
896
Ed Tanousa716aa72023-08-01 11:35:53 -0700897 // Send a request to destIP where additional processing of the
Carson Labrado039a47e2022-04-05 16:03:20 +0000898 // result is not required
Ed Tanous4a7fbef2024-04-06 16:03:49 -0700899 void sendData(std::string&& data, const boost::urls::url_view_base& destUri,
Ed Tanous19bb3622024-07-05 10:07:40 -0500900 ensuressl::VerifyCertificate verifyCert,
Carson Labradof52c03c2022-03-23 18:50:15 +0000901 const boost::beast::http::fields& httpHeader,
Carson Labradod14a48f2023-02-22 00:24:54 +0000902 const boost::beast::http::verb verb)
Carson Labradof52c03c2022-03-23 18:50:15 +0000903 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000904 const std::function<void(Response&)> cb = genericResHandler;
Ed Tanous19bb3622024-07-05 10:07:40 -0500905 sendDataWithCallback(std::move(data), destUri, verifyCert, httpHeader,
906 verb, cb);
Carson Labrado039a47e2022-04-05 16:03:20 +0000907 }
908
Ed Tanousa716aa72023-08-01 11:35:53 -0700909 // Send request to destIP and use the provided callback to
Carson Labrado039a47e2022-04-05 16:03:20 +0000910 // handle the response
Ed Tanous4a7fbef2024-04-06 16:03:49 -0700911 void sendDataWithCallback(std::string&& data,
912 const boost::urls::url_view_base& destUrl,
Ed Tanous19bb3622024-07-05 10:07:40 -0500913 ensuressl::VerifyCertificate verifyCert,
Carson Labrado039a47e2022-04-05 16:03:20 +0000914 const boost::beast::http::fields& httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000915 const boost::beast::http::verb verb,
Ed Tanous6b3db602022-06-28 19:41:44 -0700916 const std::function<void(Response&)>& resHandler)
Carson Labrado039a47e2022-04-05 16:03:20 +0000917 {
Ed Tanous19bb3622024-07-05 10:07:40 -0500918 std::string_view verify = "ssl_verify";
919 if (verifyCert == ensuressl::VerifyCertificate::NoVerify)
920 {
921 verify = "ssl no verify";
922 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400923 std::string clientKey =
924 std::format("{}{}://{}", verify, destUrl.scheme(),
925 destUrl.encoded_host_and_port());
Carson Labradod14a48f2023-02-22 00:24:54 +0000926 auto pool = connectionPools.try_emplace(clientKey);
927 if (pool.first->second == nullptr)
Carson Labradof52c03c2022-03-23 18:50:15 +0000928 {
Carson Labradod14a48f2023-02-22 00:24:54 +0000929 pool.first->second = std::make_shared<ConnectionPool>(
Ed Tanous19bb3622024-07-05 10:07:40 -0500930 ioc, clientKey, connPolicy, destUrl, verifyCert);
Carson Labradof52c03c2022-03-23 18:50:15 +0000931 }
Ed Tanous27b0cf92023-08-07 12:02:40 -0700932 // Send the data using either the existing connection pool or the
933 // newly created connection pool
Ed Tanousa716aa72023-08-01 11:35:53 -0700934 pool.first->second->sendData(std::move(data), destUrl, httpHeader, verb,
Carson Labradod14a48f2023-02-22 00:24:54 +0000935 resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000936 }
Myung Baea0969c72024-09-19 08:46:45 -0400937
938 // Test whether all connections are terminated (after MaxRetryAttempts)
939 bool isTerminated()
940 {
941 for (const auto& pool : connectionPools)
942 {
943 if (pool.second != nullptr &&
944 !pool.second->areAllConnectionsTerminated())
945 {
946 BMCWEB_LOG_DEBUG(
947 "Not all of client connections are terminated");
948 return false;
949 }
950 }
951 BMCWEB_LOG_DEBUG("All client connections are terminated");
952 return true;
953 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000954};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530955} // namespace crow