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