blob: 62e7df61bfef0da8878993bbd88e7bbeebfb81ba [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
Carson Labradoa7a80292022-06-01 16:01:52 +000083static inline boost::system::error_code
84 defaultRetryHandler(unsigned int respCode)
85{
86 // As a default, assume 200X is alright
Ed Tanous62598e32023-07-17 17:06:25 -070087 BMCWEB_LOG_DEBUG("Using default check for response code validity");
Carson Labradoa7a80292022-06-01 16:01:52 +000088 if ((respCode < 200) || (respCode >= 300))
89 {
90 return boost::system::errc::make_error_code(
91 boost::system::errc::result_out_of_range);
92 }
93
94 // Return 0 if the response code is valid
95 return boost::system::errc::make_error_code(boost::system::errc::success);
96};
97
Ed Tanous27b0cf92023-08-07 12:02:40 -070098// We need to allow retry information to be set before a message has been
99// sent and a connection pool has been created
Carson Labradod14a48f2023-02-22 00:24:54 +0000100struct ConnectionPolicy
Carson Labradof52c03c2022-03-23 18:50:15 +0000101{
102 uint32_t maxRetryAttempts = 5;
Carson Labradod14a48f2023-02-22 00:24:54 +0000103
104 // the max size of requests in bytes. 0 for unlimited
105 boost::optional<uint64_t> requestByteLimit = httpReadBodyLimit;
106
107 size_t maxConnections = 1;
108
Carson Labradof52c03c2022-03-23 18:50:15 +0000109 std::string retryPolicyAction = "TerminateAfterRetries";
Carson Labradod14a48f2023-02-22 00:24:54 +0000110
111 std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0);
Carson Labradoa7a80292022-06-01 16:01:52 +0000112 std::function<boost::system::error_code(unsigned int respCode)>
113 invalidResp = defaultRetryHandler;
Carson Labradof52c03c2022-03-23 18:50:15 +0000114};
115
116struct PendingRequest
117{
Ed Tanousb2896142024-01-31 15:25:47 -0800118 boost::beast::http::request<bmcweb::HttpBody> req;
Carson Labrado039a47e2022-04-05 16:03:20 +0000119 std::function<void(bool, uint32_t, Response&)> callback;
Carson Labrado039a47e2022-04-05 16:03:20 +0000120 PendingRequest(
Ed Tanousb2896142024-01-31 15:25:47 -0800121 boost::beast::http::request<bmcweb::HttpBody>&& reqIn,
Carson Labradod14a48f2023-02-22 00:24:54 +0000122 const std::function<void(bool, uint32_t, Response&)>& callbackIn) :
Ed Tanous8a592812022-06-04 09:06:59 -0700123 req(std::move(reqIn)),
Carson Labradod14a48f2023-02-22 00:24:54 +0000124 callback(callbackIn)
Carson Labradof52c03c2022-03-23 18:50:15 +0000125 {}
126};
127
Ed Tanouse01d0c32023-06-30 13:21:32 -0700128namespace http = boost::beast::http;
Carson Labradof52c03c2022-03-23 18:50:15 +0000129class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo>
AppaRao Pulibd030d02020-03-20 03:34:29 +0530130{
131 private:
Carson Labradof52c03c2022-03-23 18:50:15 +0000132 ConnState state = ConnState::initialized;
133 uint32_t retryCount = 0;
Carson Labradof52c03c2022-03-23 18:50:15 +0000134 std::string subId;
Carson Labradod14a48f2023-02-22 00:24:54 +0000135 std::shared_ptr<ConnectionPolicy> connPolicy;
Ed Tanousa716aa72023-08-01 11:35:53 -0700136 boost::urls::url host;
Ed Tanous19bb3622024-07-05 10:07:40 -0500137 ensuressl::VerifyCertificate verifyCert;
Carson Labradof52c03c2022-03-23 18:50:15 +0000138 uint32_t connId;
Carson Labradof52c03c2022-03-23 18:50:15 +0000139 // Data buffers
Ed Tanousb2896142024-01-31 15:25:47 -0800140 http::request<bmcweb::HttpBody> req;
141 using parser_type = http::response_parser<bmcweb::HttpBody>;
Ed Tanouse01d0c32023-06-30 13:21:32 -0700142 std::optional<parser_type> parser;
Carson Labrado4d942722022-06-22 22:16:10 +0000143 boost::beast::flat_static_buffer<httpReadBufferSize> buffer;
Carson Labrado039a47e2022-04-05 16:03:20 +0000144 Response res;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530145
Carson Labradof52c03c2022-03-23 18:50:15 +0000146 // Ascync callables
Carson Labrado039a47e2022-04-05 16:03:20 +0000147 std::function<void(bool, uint32_t, Response&)> callback;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700148
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600149 boost::asio::io_context& ioc;
150
Ed Tanous25b54db2024-04-17 15:40:31 -0700151 using Resolver = std::conditional_t<BMCWEB_DNS_RESOLVER == "systemd-dbus",
152 async_resolve::Resolver,
153 boost::asio::ip::tcp::resolver>;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700154 Resolver resolver;
155
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800156 boost::asio::ip::tcp::socket conn;
Ed Tanous003301a2024-04-16 09:59:19 -0700157 std::optional<boost::asio::ssl::stream<boost::asio::ip::tcp::socket&>>
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800158 sslConn;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000159
Carson Labradof52c03c2022-03-23 18:50:15 +0000160 boost::asio::steady_timer timer;
Ed Tanous84b35602021-09-08 20:06:32 -0700161
Carson Labradof52c03c2022-03-23 18:50:15 +0000162 friend class ConnectionPool;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530163
Sunitha Harish29a82b02021-02-18 15:54:16 +0530164 void doResolve()
165 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530166 state = ConnState::resolveInProgress;
Ed Tanousa716aa72023-08-01 11:35:53 -0700167 BMCWEB_LOG_DEBUG("Trying to resolve: {}, id: {}", host, connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530168
Ed Tanousa716aa72023-08-01 11:35:53 -0700169 resolver.async_resolve(host.encoded_host_address(), host.port(),
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700170 std::bind_front(&ConnectionInfo::afterResolve,
171 this, shared_from_this()));
Sunitha Harish29a82b02021-02-18 15:54:16 +0530172 }
173
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700174 void afterResolve(const std::shared_ptr<ConnectionInfo>& /*self*/,
175 const boost::system::error_code& ec,
176 const Resolver::results_type& endpointList)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530177 {
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700178 if (ec || (endpointList.empty()))
179 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700180 BMCWEB_LOG_ERROR("Resolve failed: {} {}", ec.message(), host);
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700181 state = ConnState::resolveFailed;
182 waitAndRetry();
183 return;
184 }
Ed Tanousa716aa72023-08-01 11:35:53 -0700185 BMCWEB_LOG_DEBUG("Resolved {}, id: {}", host, connId);
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530186 state = ConnState::connectInProgress;
187
Ed Tanousa716aa72023-08-01 11:35:53 -0700188 BMCWEB_LOG_DEBUG("Trying to connect to: {}, id: {}", host, connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530189
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800190 timer.expires_after(std::chrono::seconds(30));
191 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
192
193 boost::asio::async_connect(
194 conn, endpointList,
195 std::bind_front(&ConnectionInfo::afterConnect, this,
196 shared_from_this()));
AppaRao Pulie38778a2022-06-27 23:09:03 +0000197 }
198
199 void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/,
Ed Tanous81c4e332023-05-18 10:30:34 -0700200 const boost::beast::error_code& ec,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000201 const boost::asio::ip::tcp::endpoint& endpoint)
202 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000203 // The operation already timed out. We don't want do continue down
204 // this branch
205 if (ec && ec == boost::asio::error::operation_aborted)
206 {
207 return;
208 }
209
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800210 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000211 if (ec)
212 {
Ed Tanous62598e32023-07-17 17:06:25 -0700213 BMCWEB_LOG_ERROR("Connect {}:{}, id: {} failed: {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700214 endpoint.address().to_string(), endpoint.port(),
215 connId, ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000216 state = ConnState::connectFailed;
217 waitAndRetry();
218 return;
219 }
Ed Tanousa716aa72023-08-01 11:35:53 -0700220 BMCWEB_LOG_DEBUG("Connected to: {}:{}, id: {}",
221 endpoint.address().to_string(), endpoint.port(),
222 connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000223 if (sslConn)
224 {
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800225 doSslHandshake();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000226 return;
227 }
228 state = ConnState::connected;
229 sendMessage();
230 }
231
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800232 void doSslHandshake()
AppaRao Pulie38778a2022-06-27 23:09:03 +0000233 {
234 if (!sslConn)
235 {
236 return;
237 }
238 state = ConnState::handshakeInProgress;
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800239 timer.expires_after(std::chrono::seconds(30));
240 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
AppaRao Pulie38778a2022-06-27 23:09:03 +0000241 sslConn->async_handshake(
242 boost::asio::ssl::stream_base::client,
243 std::bind_front(&ConnectionInfo::afterSslHandshake, this,
244 shared_from_this()));
245 }
246
247 void afterSslHandshake(const std::shared_ptr<ConnectionInfo>& /*self*/,
Ed Tanous81c4e332023-05-18 10:30:34 -0700248 const boost::beast::error_code& ec)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000249 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000250 // The operation already timed out. We don't want do continue down
251 // this branch
252 if (ec && ec == boost::asio::error::operation_aborted)
253 {
254 return;
255 }
256
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800257 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000258 if (ec)
259 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700260 BMCWEB_LOG_ERROR("SSL Handshake failed - id: {} error: {}", connId,
261 ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000262 state = ConnState::handshakeFailed;
263 waitAndRetry();
264 return;
265 }
Ed Tanousa716aa72023-08-01 11:35:53 -0700266 BMCWEB_LOG_DEBUG("SSL Handshake successful - id: {}", connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000267 state = ConnState::connected;
268 sendMessage();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530269 }
270
Carson Labradof52c03c2022-03-23 18:50:15 +0000271 void sendMessage()
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530272 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530273 state = ConnState::sendInProgress;
274
AppaRao Pulibd030d02020-03-20 03:34:29 +0530275 // Set a timeout on the operation
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800276 timer.expires_after(std::chrono::seconds(30));
277 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
Ed Tanous4d698612024-02-06 14:57:24 -0800278 boost::beast::http::message_generator messageGenerator(std::move(req));
AppaRao Pulibd030d02020-03-20 03:34:29 +0530279 // Send the HTTP request to the remote host
AppaRao Pulie38778a2022-06-27 23:09:03 +0000280 if (sslConn)
281 {
Ed Tanous4d698612024-02-06 14:57:24 -0800282 boost::beast::async_write(
283 *sslConn, std::move(messageGenerator),
AppaRao Pulie38778a2022-06-27 23:09:03 +0000284 std::bind_front(&ConnectionInfo::afterWrite, this,
285 shared_from_this()));
286 }
287 else
288 {
Ed Tanous4d698612024-02-06 14:57:24 -0800289 boost::beast::async_write(
290 conn, std::move(messageGenerator),
AppaRao Pulie38778a2022-06-27 23:09:03 +0000291 std::bind_front(&ConnectionInfo::afterWrite, this,
292 shared_from_this()));
293 }
294 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530295
AppaRao Pulie38778a2022-06-27 23:09:03 +0000296 void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/,
297 const boost::beast::error_code& ec, size_t bytesTransferred)
298 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000299 // The operation already timed out. We don't want do continue down
300 // this branch
301 if (ec && ec == boost::asio::error::operation_aborted)
302 {
303 return;
304 }
305
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800306 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000307 if (ec)
308 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700309 BMCWEB_LOG_ERROR("sendMessage() failed: {} {}", ec.message(), host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000310 state = ConnState::sendFailed;
311 waitAndRetry();
312 return;
313 }
Ed Tanous62598e32023-07-17 17:06:25 -0700314 BMCWEB_LOG_DEBUG("sendMessage() bytes transferred: {}",
315 bytesTransferred);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000316
317 recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530318 }
319
320 void recvMessage()
321 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530322 state = ConnState::recvInProgress;
323
Ed Tanouse01d0c32023-06-30 13:21:32 -0700324 parser_type& thisParser = parser.emplace(std::piecewise_construct,
325 std::make_tuple());
Carson Labradod14a48f2023-02-22 00:24:54 +0000326
Ed Tanouse01d0c32023-06-30 13:21:32 -0700327 thisParser.body_limit(connPolicy->requestByteLimit);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530328
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800329 timer.expires_after(std::chrono::seconds(30));
330 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
331
AppaRao Pulibd030d02020-03-20 03:34:29 +0530332 // Receive the HTTP response
AppaRao Pulie38778a2022-06-27 23:09:03 +0000333 if (sslConn)
334 {
335 boost::beast::http::async_read(
Ed Tanouse01d0c32023-06-30 13:21:32 -0700336 *sslConn, buffer, thisParser,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000337 std::bind_front(&ConnectionInfo::afterRead, this,
338 shared_from_this()));
339 }
340 else
341 {
342 boost::beast::http::async_read(
Ed Tanouse01d0c32023-06-30 13:21:32 -0700343 conn, buffer, thisParser,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000344 std::bind_front(&ConnectionInfo::afterRead, this,
345 shared_from_this()));
346 }
347 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530348
AppaRao Pulie38778a2022-06-27 23:09:03 +0000349 void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/,
350 const boost::beast::error_code& ec,
351 const std::size_t& bytesTransferred)
352 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000353 // The operation already timed out. We don't want do continue down
354 // this branch
355 if (ec && ec == boost::asio::error::operation_aborted)
356 {
357 return;
358 }
359
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800360 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000361 if (ec && ec != boost::asio::ssl::error::stream_truncated)
362 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700363 BMCWEB_LOG_ERROR("recvMessage() failed: {} from {}", ec.message(),
364 host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000365 state = ConnState::recvFailed;
366 waitAndRetry();
367 return;
368 }
Ed Tanous62598e32023-07-17 17:06:25 -0700369 BMCWEB_LOG_DEBUG("recvMessage() bytes transferred: {}",
370 bytesTransferred);
Ed Tanouse01d0c32023-06-30 13:21:32 -0700371 if (!parser)
372 {
373 return;
374 }
Ed Tanous52e31622024-01-23 16:31:11 -0800375 BMCWEB_LOG_DEBUG("recvMessage() data: {}", parser->get().body().str());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000376
377 unsigned int respCode = parser->get().result_int();
Ed Tanous62598e32023-07-17 17:06:25 -0700378 BMCWEB_LOG_DEBUG("recvMessage() Header Response Code: {}", respCode);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000379
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600380 // Handle the case of stream_truncated. Some servers close the ssl
381 // connection uncleanly, so check to see if we got a full response
382 // before we handle this as an error.
383 if (!parser->is_done())
384 {
385 state = ConnState::recvFailed;
386 waitAndRetry();
387 return;
388 }
389
AppaRao Pulie38778a2022-06-27 23:09:03 +0000390 // Make sure the received response code is valid as defined by
391 // the associated retry policy
Carson Labradod14a48f2023-02-22 00:24:54 +0000392 if (connPolicy->invalidResp(respCode))
AppaRao Pulie38778a2022-06-27 23:09:03 +0000393 {
394 // The listener failed to receive the Sent-Event
Ed Tanous62598e32023-07-17 17:06:25 -0700395 BMCWEB_LOG_ERROR(
396 "recvMessage() Listener Failed to "
Ed Tanousa716aa72023-08-01 11:35:53 -0700397 "receive Sent-Event. Header Response Code: {} from {}",
398 respCode, host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000399 state = ConnState::recvFailed;
400 waitAndRetry();
401 return;
402 }
Ed Tanous002d39b2022-05-31 08:59:27 -0700403
AppaRao Pulie38778a2022-06-27 23:09:03 +0000404 // Send is successful
405 // Reset the counter just in case this was after retrying
406 retryCount = 0;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530407
AppaRao Pulie38778a2022-06-27 23:09:03 +0000408 // Keep the connection alive if server supports it
409 // Else close the connection
Ed Tanous62598e32023-07-17 17:06:25 -0700410 BMCWEB_LOG_DEBUG("recvMessage() keepalive : {}", parser->keep_alive());
AppaRao Pulibd030d02020-03-20 03:34:29 +0530411
AppaRao Pulie38778a2022-06-27 23:09:03 +0000412 // Copy the response into a Response object so that it can be
413 // processed by the callback function.
Ed Tanous27b0cf92023-08-07 12:02:40 -0700414 res.response = parser->release();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000415 callback(parser->keep_alive(), connId, res);
Carson Labrado513d1ff2022-07-19 00:38:15 +0000416 res.clear();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530417 }
418
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800419 static void onTimeout(const std::weak_ptr<ConnectionInfo>& weakSelf,
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800420 const boost::system::error_code& ec)
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800421 {
422 if (ec == boost::asio::error::operation_aborted)
423 {
Ed Tanous62598e32023-07-17 17:06:25 -0700424 BMCWEB_LOG_DEBUG(
425 "async_wait failed since the operation is aborted");
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800426 return;
427 }
428 if (ec)
429 {
Ed Tanous62598e32023-07-17 17:06:25 -0700430 BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
Ed Tanous27b0cf92023-08-07 12:02:40 -0700431 // If the timer fails, we need to close the socket anyway, same
432 // as if it expired.
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800433 }
434 std::shared_ptr<ConnectionInfo> self = weakSelf.lock();
435 if (self == nullptr)
436 {
437 return;
438 }
439 self->waitAndRetry();
440 }
441
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530442 void waitAndRetry()
AppaRao Pulibd030d02020-03-20 03:34:29 +0530443 {
Carson Labradod14a48f2023-02-22 00:24:54 +0000444 if ((retryCount >= connPolicy->maxRetryAttempts) ||
AppaRao Pulie38778a2022-06-27 23:09:03 +0000445 (state == ConnState::sslInitFailed))
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530446 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700447 BMCWEB_LOG_ERROR("Maximum number of retries reached. {}", host);
Ed Tanous62598e32023-07-17 17:06:25 -0700448 BMCWEB_LOG_DEBUG("Retry policy: {}", connPolicy->retryPolicyAction);
Carson Labrado039a47e2022-04-05 16:03:20 +0000449
Carson Labradod14a48f2023-02-22 00:24:54 +0000450 if (connPolicy->retryPolicyAction == "TerminateAfterRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530451 {
452 // TODO: delete subscription
453 state = ConnState::terminated;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530454 }
Carson Labradod14a48f2023-02-22 00:24:54 +0000455 if (connPolicy->retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530456 {
457 state = ConnState::suspended;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530458 }
Carson Labrado513d1ff2022-07-19 00:38:15 +0000459
460 // We want to return a 502 to indicate there was an error with
461 // the external server
462 res.result(boost::beast::http::status::bad_gateway);
463 callback(false, connId, res);
464 res.clear();
465
Ed Tanous27b0cf92023-08-07 12:02:40 -0700466 // Reset the retrycount to zero so that client can try
467 // connecting again if needed
Ed Tanous3174e4d2020-10-07 11:41:22 -0700468 retryCount = 0;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530469 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530470 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530471
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530472 retryCount++;
473
Ed Tanous62598e32023-07-17 17:06:25 -0700474 BMCWEB_LOG_DEBUG("Attempt retry after {} seconds. RetryCount = {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700475 connPolicy->retryIntervalSecs.count(), retryCount);
Carson Labradod14a48f2023-02-22 00:24:54 +0000476 timer.expires_after(connPolicy->retryIntervalSecs);
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700477 timer.async_wait(std::bind_front(&ConnectionInfo::onTimerDone, this,
478 shared_from_this()));
479 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530480
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700481 void onTimerDone(const std::shared_ptr<ConnectionInfo>& /*self*/,
482 const boost::system::error_code& ec)
483 {
484 if (ec == boost::asio::error::operation_aborted)
485 {
Ed Tanous62598e32023-07-17 17:06:25 -0700486 BMCWEB_LOG_DEBUG(
487 "async_wait failed since the operation is aborted{}",
488 ec.message());
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700489 }
490 else if (ec)
491 {
Ed Tanous62598e32023-07-17 17:06:25 -0700492 BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700493 // Ignore the error and continue the retry loop to attempt
494 // sending the event as per the retry policy
495 }
496
497 // Let's close the connection and restart from resolve.
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600498 shutdownConn(true);
499 }
500
501 void restartConnection()
502 {
503 BMCWEB_LOG_DEBUG("{}, id: {} restartConnection", host,
504 std::to_string(connId));
505 initializeConnection(host.scheme() == "https");
506 doResolve();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530507 }
508
AppaRao Pulie38778a2022-06-27 23:09:03 +0000509 void shutdownConn(bool retry)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530510 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000511 boost::beast::error_code ec;
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800512 conn.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
Carson Labradof52c03c2022-03-23 18:50:15 +0000513 conn.close();
514
515 // not_connected happens sometimes so don't bother reporting it.
516 if (ec && ec != boost::beast::errc::not_connected)
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530517 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700518 BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId,
Ed Tanous62598e32023-07-17 17:06:25 -0700519 ec.message());
Carson Labradof52c03c2022-03-23 18:50:15 +0000520 }
Carson Labrado5cab68f2022-07-11 22:26:21 +0000521 else
522 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700523 BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId);
Carson Labrado5cab68f2022-07-11 22:26:21 +0000524 }
Ed Tanousca723762022-06-28 19:40:39 -0700525
Carson Labrado513d1ff2022-07-19 00:38:15 +0000526 if (retry)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000527 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000528 // Now let's try to resend the data
529 state = ConnState::retry;
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600530 restartConnection();
Carson Labrado513d1ff2022-07-19 00:38:15 +0000531 }
532 else
533 {
534 state = ConnState::closed;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000535 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000536 }
537
AppaRao Pulie38778a2022-06-27 23:09:03 +0000538 void doClose(bool retry = false)
Carson Labradof52c03c2022-03-23 18:50:15 +0000539 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000540 if (!sslConn)
541 {
542 shutdownConn(retry);
543 return;
544 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000545
AppaRao Pulie38778a2022-06-27 23:09:03 +0000546 sslConn->async_shutdown(
547 std::bind_front(&ConnectionInfo::afterSslShutdown, this,
548 shared_from_this(), retry));
549 }
550
551 void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/,
552 bool retry, const boost::system::error_code& ec)
553 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000554 if (ec)
Carson Labradof52c03c2022-03-23 18:50:15 +0000555 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700556 BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId,
Ed Tanous62598e32023-07-17 17:06:25 -0700557 ec.message());
Carson Labradof52c03c2022-03-23 18:50:15 +0000558 }
Carson Labrado5cab68f2022-07-11 22:26:21 +0000559 else
560 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700561 BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId);
Carson Labrado5cab68f2022-07-11 22:26:21 +0000562 }
AppaRao Pulie38778a2022-06-27 23:09:03 +0000563 shutdownConn(retry);
564 }
Ed Tanousca723762022-06-28 19:40:39 -0700565
AppaRao Pulie38778a2022-06-27 23:09:03 +0000566 void setCipherSuiteTLSext()
567 {
568 if (!sslConn)
569 {
570 return;
571 }
Ravi Tejae7c29912023-07-31 09:39:32 -0500572
573 if (host.host_type() != boost::urls::host_type::name)
574 {
575 // Avoid setting SNI hostname if its IP address
576 return;
577 }
578 // Create a null terminated string for SSL
Ed Tanousa716aa72023-08-01 11:35:53 -0700579 std::string hostname(host.encoded_host_address());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000580 // NOTE: The SSL_set_tlsext_host_name is defined in tlsv1.h header
581 // file but its having old style casting (name is cast to void*).
582 // Since bmcweb compiler treats all old-style-cast as error, its
583 // causing the build failure. So replaced the same macro inline and
584 // did corrected the code by doing static_cast to viod*. This has to
585 // be fixed in openssl library in long run. Set SNI Hostname (many
586 // hosts need this to handshake successfully)
587 if (SSL_ctrl(sslConn->native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME,
588 TLSEXT_NAMETYPE_host_name,
Ed Tanousa716aa72023-08-01 11:35:53 -0700589 static_cast<void*>(hostname.data())) == 0)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000590
591 {
592 boost::beast::error_code ec{static_cast<int>(::ERR_get_error()),
593 boost::asio::error::get_ssl_category()};
594
Ed Tanousa716aa72023-08-01 11:35:53 -0700595 BMCWEB_LOG_ERROR("SSL_set_tlsext_host_name {}, id: {} failed: {}",
596 host, connId, ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000597 // Set state as sslInit failed so that we close the connection
598 // and take appropriate action as per retry configuration.
599 state = ConnState::sslInitFailed;
600 waitAndRetry();
601 return;
602 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530603 }
604
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600605 void initializeConnection(bool ssl)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000606 {
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600607 conn = boost::asio::ip::tcp::socket(ioc);
608 if (ssl)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000609 {
610 std::optional<boost::asio::ssl::context> sslCtx =
Ed Tanous19bb3622024-07-05 10:07:40 -0500611 ensuressl::getSSLClientContext(verifyCert);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000612
613 if (!sslCtx)
614 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700615 BMCWEB_LOG_ERROR("prepareSSLContext failed - {}, id: {}", host,
616 connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000617 // Don't retry if failure occurs while preparing SSL context
Ed Tanous27b0cf92023-08-07 12:02:40 -0700618 // such as certificate is invalid or set cipher failure or
619 // set host name failure etc... Setting conn state to
620 // sslInitFailed and connection state will be transitioned
621 // to next state depending on retry policy set by
622 // subscription.
AppaRao Pulie38778a2022-06-27 23:09:03 +0000623 state = ConnState::sslInitFailed;
624 waitAndRetry();
625 return;
626 }
627 sslConn.emplace(conn, *sslCtx);
628 setCipherSuiteTLSext();
629 }
630 }
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600631
632 public:
633 explicit ConnectionInfo(
634 boost::asio::io_context& iocIn, const std::string& idIn,
635 const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
Ed Tanous19bb3622024-07-05 10:07:40 -0500636 const boost::urls::url_view_base& hostIn,
637 ensuressl::VerifyCertificate verifyCertIn, unsigned int connIdIn) :
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600638 subId(idIn),
Ed Tanous19bb3622024-07-05 10:07:40 -0500639 connPolicy(connPolicyIn), host(hostIn), verifyCert(verifyCertIn),
640 connId(connIdIn), ioc(iocIn), resolver(iocIn), conn(iocIn), timer(iocIn)
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600641 {
642 initializeConnection(host.scheme() == "https");
643 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000644};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530645
Carson Labradof52c03c2022-03-23 18:50:15 +0000646class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
647{
648 private:
649 boost::asio::io_context& ioc;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000650 std::string id;
Carson Labradod14a48f2023-02-22 00:24:54 +0000651 std::shared_ptr<ConnectionPolicy> connPolicy;
Ed Tanousa716aa72023-08-01 11:35:53 -0700652 boost::urls::url destIP;
Carson Labradof52c03c2022-03-23 18:50:15 +0000653 std::vector<std::shared_ptr<ConnectionInfo>> connections;
654 boost::container::devector<PendingRequest> requestQueue;
Ed Tanous19bb3622024-07-05 10:07:40 -0500655 ensuressl::VerifyCertificate verifyCert;
Carson Labradof52c03c2022-03-23 18:50:15 +0000656
657 friend class HttpClient;
658
Carson Labrado244256c2022-04-27 17:16:32 +0000659 // Configure a connections's request, callback, and retry info in
660 // preparation to begin sending the request
Carson Labradof52c03c2022-03-23 18:50:15 +0000661 void setConnProps(ConnectionInfo& conn)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530662 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000663 if (requestQueue.empty())
AppaRao Pulibd030d02020-03-20 03:34:29 +0530664 {
Ed Tanous62598e32023-07-17 17:06:25 -0700665 BMCWEB_LOG_ERROR(
666 "setConnProps() should not have been called when requestQueue is empty");
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530667 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530668 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530669
Ed Tanous52e31622024-01-23 16:31:11 -0800670 PendingRequest& nextReq = requestQueue.front();
Carson Labrado244256c2022-04-27 17:16:32 +0000671 conn.req = std::move(nextReq.req);
672 conn.callback = std::move(nextReq.callback);
Carson Labradof52c03c2022-03-23 18:50:15 +0000673
Ed Tanousa716aa72023-08-01 11:35:53 -0700674 BMCWEB_LOG_DEBUG("Setting properties for connection {}, id: {}",
675 conn.host, conn.connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000676
677 // We can remove the request from the queue at this point
678 requestQueue.pop_front();
679 }
680
Carson Labradof52c03c2022-03-23 18:50:15 +0000681 // Gets called as part of callback after request is sent
682 // Reuses the connection if there are any requests waiting to be sent
683 // Otherwise closes the connection if it is not a keep-alive
684 void sendNext(bool keepAlive, uint32_t connId)
685 {
686 auto conn = connections[connId];
Carson Labrado46a81462022-04-27 21:11:37 +0000687
688 // Allow the connection's handler to be deleted
689 // This is needed because of Redfish Aggregation passing an
690 // AsyncResponse shared_ptr to this callback
691 conn->callback = nullptr;
692
Carson Labradof52c03c2022-03-23 18:50:15 +0000693 // Reuse the connection to send the next request in the queue
694 if (!requestQueue.empty())
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530695 {
Ed Tanous62598e32023-07-17 17:06:25 -0700696 BMCWEB_LOG_DEBUG(
Ed Tanous8ece0e42024-01-02 13:16:50 -0800697 "{} requests remaining in queue for {}, reusing connection {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700698 requestQueue.size(), destIP, connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000699
700 setConnProps(*conn);
701
702 if (keepAlive)
703 {
704 conn->sendMessage();
705 }
706 else
707 {
708 // Server is not keep-alive enabled so we need to close the
709 // connection and then start over from resolve
710 conn->doClose();
Abhilash Raju2ecde742024-06-01 02:01:01 -0500711 conn->restartConnection();
Carson Labradof52c03c2022-03-23 18:50:15 +0000712 }
713 return;
714 }
715
716 // No more messages to send so close the connection if necessary
717 if (keepAlive)
718 {
719 conn->state = ConnState::idle;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530720 }
721 else
722 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000723 // Abort the connection since server is not keep-alive enabled
724 conn->state = ConnState::abortConnection;
725 conn->doClose();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530726 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530727 }
728
Ed Tanous4a7fbef2024-04-06 16:03:49 -0700729 void sendData(std::string&& data, const boost::urls::url_view_base& destUri,
Carson Labrado244256c2022-04-27 17:16:32 +0000730 const boost::beast::http::fields& httpHeader,
731 const boost::beast::http::verb verb,
Ed Tanous6b3db602022-06-28 19:41:44 -0700732 const std::function<void(Response&)>& resHandler)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530733 {
Carson Labrado244256c2022-04-27 17:16:32 +0000734 // Construct the request to be sent
Ed Tanousb2896142024-01-31 15:25:47 -0800735 boost::beast::http::request<bmcweb::HttpBody> thisReq(
Ed Tanousa716aa72023-08-01 11:35:53 -0700736 verb, destUri.encoded_target(), 11, "", httpHeader);
737 thisReq.set(boost::beast::http::field::host,
738 destUri.encoded_host_address());
Carson Labrado244256c2022-04-27 17:16:32 +0000739 thisReq.keep_alive(true);
Ed Tanous52e31622024-01-23 16:31:11 -0800740 thisReq.body().str() = std::move(data);
Carson Labrado244256c2022-04-27 17:16:32 +0000741 thisReq.prepare_payload();
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700742 auto cb = std::bind_front(&ConnectionPool::afterSendData,
743 weak_from_this(), resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000744 // Reuse an existing connection if one is available
745 for (unsigned int i = 0; i < connections.size(); i++)
746 {
747 auto conn = connections[i];
748 if ((conn->state == ConnState::idle) ||
749 (conn->state == ConnState::initialized) ||
750 (conn->state == ConnState::closed))
751 {
Carson Labrado244256c2022-04-27 17:16:32 +0000752 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000753 conn->callback = std::move(cb);
Ed Tanousa716aa72023-08-01 11:35:53 -0700754 std::string commonMsg = std::format("{} from pool {}", i, id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000755
756 if (conn->state == ConnState::idle)
757 {
Ed Tanous62598e32023-07-17 17:06:25 -0700758 BMCWEB_LOG_DEBUG("Grabbing idle connection {}", commonMsg);
Carson Labradof52c03c2022-03-23 18:50:15 +0000759 conn->sendMessage();
760 }
761 else
762 {
Ed Tanous62598e32023-07-17 17:06:25 -0700763 BMCWEB_LOG_DEBUG("Reusing existing connection {}",
764 commonMsg);
Abhilash Raju2ecde742024-06-01 02:01:01 -0500765 conn->restartConnection();
Carson Labradof52c03c2022-03-23 18:50:15 +0000766 }
767 return;
768 }
769 }
770
Ed Tanous27b0cf92023-08-07 12:02:40 -0700771 // All connections in use so create a new connection or add request
772 // to the queue
Carson Labradod14a48f2023-02-22 00:24:54 +0000773 if (connections.size() < connPolicy->maxConnections)
Carson Labradof52c03c2022-03-23 18:50:15 +0000774 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700775 BMCWEB_LOG_DEBUG("Adding new connection to pool {}", id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000776 auto conn = addConnection();
Carson Labrado244256c2022-04-27 17:16:32 +0000777 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000778 conn->callback = std::move(cb);
Carson Labradof52c03c2022-03-23 18:50:15 +0000779 conn->doResolve();
780 }
781 else if (requestQueue.size() < maxRequestQueueSize)
782 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700783 BMCWEB_LOG_DEBUG("Max pool size reached. Adding data to queue {}",
784 id);
Carson Labradod14a48f2023-02-22 00:24:54 +0000785 requestQueue.emplace_back(std::move(thisReq), std::move(cb));
Carson Labradof52c03c2022-03-23 18:50:15 +0000786 }
787 else
788 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700789 // If we can't buffer the request then we should let the
790 // callback handle a 429 Too Many Requests dummy response
Ed Tanous6ea90762024-04-07 08:38:44 -0700791 BMCWEB_LOG_ERROR("{} request queue full. Dropping request.", id);
Carson Labrado43e14d32022-11-09 00:25:20 +0000792 Response dummyRes;
793 dummyRes.result(boost::beast::http::status::too_many_requests);
794 resHandler(dummyRes);
Carson Labradof52c03c2022-03-23 18:50:15 +0000795 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530796 }
797
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700798 // Callback to be called once the request has been sent
799 static void afterSendData(const std::weak_ptr<ConnectionPool>& weakSelf,
800 const std::function<void(Response&)>& resHandler,
801 bool keepAlive, uint32_t connId, Response& res)
802 {
803 // Allow provided callback to perform additional processing of the
804 // request
805 resHandler(res);
806
807 // If requests remain in the queue then we want to reuse this
808 // connection to send the next request
809 std::shared_ptr<ConnectionPool> self = weakSelf.lock();
810 if (!self)
811 {
Ed Tanous62598e32023-07-17 17:06:25 -0700812 BMCWEB_LOG_CRITICAL("{} Failed to capture connection",
813 logPtr(self.get()));
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700814 return;
815 }
816
817 self->sendNext(keepAlive, connId);
818 }
819
Carson Labradof52c03c2022-03-23 18:50:15 +0000820 std::shared_ptr<ConnectionInfo>& addConnection()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530821 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000822 unsigned int newId = static_cast<unsigned int>(connections.size());
823
AppaRao Pulie38778a2022-06-27 23:09:03 +0000824 auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>(
Ed Tanous19bb3622024-07-05 10:07:40 -0500825 ioc, id, connPolicy, destIP, verifyCert, newId));
Carson Labradof52c03c2022-03-23 18:50:15 +0000826
Ed Tanousa716aa72023-08-01 11:35:53 -0700827 BMCWEB_LOG_DEBUG("Added connection {} to pool {}",
828 connections.size() - 1, id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000829
830 return ret;
831 }
832
833 public:
Carson Labradod14a48f2023-02-22 00:24:54 +0000834 explicit ConnectionPool(
835 boost::asio::io_context& iocIn, const std::string& idIn,
836 const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
Ed Tanous19bb3622024-07-05 10:07:40 -0500837 const boost::urls::url_view_base& destIPIn,
838 ensuressl::VerifyCertificate verifyCertIn) :
Ed Tanous8a592812022-06-04 09:06:59 -0700839 ioc(iocIn),
Ed Tanous19bb3622024-07-05 10:07:40 -0500840 id(idIn), connPolicy(connPolicyIn), destIP(destIPIn),
841 verifyCert(verifyCertIn)
Carson Labradof52c03c2022-03-23 18:50:15 +0000842 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700843 BMCWEB_LOG_DEBUG("Initializing connection pool for {}", id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000844
845 // Initialize the pool with a single connection
846 addConnection();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530847 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530848};
849
Carson Labradof52c03c2022-03-23 18:50:15 +0000850class HttpClient
851{
852 private:
853 std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
854 connectionPools;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700855 boost::asio::io_context& ioc;
Carson Labradod14a48f2023-02-22 00:24:54 +0000856 std::shared_ptr<ConnectionPolicy> connPolicy;
Carson Labradof52c03c2022-03-23 18:50:15 +0000857
Carson Labrado039a47e2022-04-05 16:03:20 +0000858 // Used as a dummy callback by sendData() in order to call
859 // sendDataWithCallback()
Ed Tanous02cad962022-06-30 16:50:15 -0700860 static void genericResHandler(const Response& res)
Carson Labrado039a47e2022-04-05 16:03:20 +0000861 {
Ed Tanous62598e32023-07-17 17:06:25 -0700862 BMCWEB_LOG_DEBUG("Response handled with return code: {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700863 res.resultInt());
Ed Tanous4ee8e212022-05-28 09:42:51 -0700864 }
Carson Labrado039a47e2022-04-05 16:03:20 +0000865
Carson Labradof52c03c2022-03-23 18:50:15 +0000866 public:
Carson Labradod14a48f2023-02-22 00:24:54 +0000867 HttpClient() = delete;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700868 explicit HttpClient(boost::asio::io_context& iocIn,
869 const std::shared_ptr<ConnectionPolicy>& connPolicyIn) :
870 ioc(iocIn),
Carson Labradod14a48f2023-02-22 00:24:54 +0000871 connPolicy(connPolicyIn)
872 {}
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700873
Carson Labradof52c03c2022-03-23 18:50:15 +0000874 HttpClient(const HttpClient&) = delete;
875 HttpClient& operator=(const HttpClient&) = delete;
876 HttpClient(HttpClient&&) = delete;
877 HttpClient& operator=(HttpClient&&) = delete;
878 ~HttpClient() = default;
879
Ed Tanousa716aa72023-08-01 11:35:53 -0700880 // Send a request to destIP where additional processing of the
Carson Labrado039a47e2022-04-05 16:03:20 +0000881 // result is not required
Ed Tanous4a7fbef2024-04-06 16:03:49 -0700882 void sendData(std::string&& data, const boost::urls::url_view_base& destUri,
Ed Tanous19bb3622024-07-05 10:07:40 -0500883 ensuressl::VerifyCertificate verifyCert,
Carson Labradof52c03c2022-03-23 18:50:15 +0000884 const boost::beast::http::fields& httpHeader,
Carson Labradod14a48f2023-02-22 00:24:54 +0000885 const boost::beast::http::verb verb)
Carson Labradof52c03c2022-03-23 18:50:15 +0000886 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000887 const std::function<void(Response&)> cb = genericResHandler;
Ed Tanous19bb3622024-07-05 10:07:40 -0500888 sendDataWithCallback(std::move(data), destUri, verifyCert, httpHeader,
889 verb, cb);
Carson Labrado039a47e2022-04-05 16:03:20 +0000890 }
891
Ed Tanousa716aa72023-08-01 11:35:53 -0700892 // Send request to destIP and use the provided callback to
Carson Labrado039a47e2022-04-05 16:03:20 +0000893 // handle the response
Ed Tanous4a7fbef2024-04-06 16:03:49 -0700894 void sendDataWithCallback(std::string&& data,
895 const boost::urls::url_view_base& destUrl,
Ed Tanous19bb3622024-07-05 10:07:40 -0500896 ensuressl::VerifyCertificate verifyCert,
Carson Labrado039a47e2022-04-05 16:03:20 +0000897 const boost::beast::http::fields& httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000898 const boost::beast::http::verb verb,
Ed Tanous6b3db602022-06-28 19:41:44 -0700899 const std::function<void(Response&)>& resHandler)
Carson Labrado039a47e2022-04-05 16:03:20 +0000900 {
Ed Tanous19bb3622024-07-05 10:07:40 -0500901 std::string_view verify = "ssl_verify";
902 if (verifyCert == ensuressl::VerifyCertificate::NoVerify)
903 {
904 verify = "ssl no verify";
905 }
906 std::string clientKey = std::format("{}{}://{}", verify,
907 destUrl.scheme(),
Ed Tanousa716aa72023-08-01 11:35:53 -0700908 destUrl.encoded_host_and_port());
Carson Labradod14a48f2023-02-22 00:24:54 +0000909 auto pool = connectionPools.try_emplace(clientKey);
910 if (pool.first->second == nullptr)
Carson Labradof52c03c2022-03-23 18:50:15 +0000911 {
Carson Labradod14a48f2023-02-22 00:24:54 +0000912 pool.first->second = std::make_shared<ConnectionPool>(
Ed Tanous19bb3622024-07-05 10:07:40 -0500913 ioc, clientKey, connPolicy, destUrl, verifyCert);
Carson Labradof52c03c2022-03-23 18:50:15 +0000914 }
Ed Tanous27b0cf92023-08-07 12:02:40 -0700915 // Send the data using either the existing connection pool or the
916 // newly created connection pool
Ed Tanousa716aa72023-08-01 11:35:53 -0700917 pool.first->second->sendData(std::move(data), destUrl, httpHeader, verb,
Carson Labradod14a48f2023-02-22 00:24:54 +0000918 resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000919 }
920};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530921} // namespace crow