blob: 59804789ee39c563c070572914bd7714b92dd32a [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 Tanousd43cd0c2020-09-30 20:46:53 -070031#include <boost/asio/steady_timer.hpp>
32#include <boost/beast/core/flat_buffer.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 Tanousbb49eb52022-06-28 12:02:42 -070035#include <boost/beast/http/parser.hpp>
36#include <boost/beast/http/read.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070037#include <boost/beast/http/write.hpp>
AppaRao Pulie38778a2022-06-27 23:09:03 +000038#include <boost/beast/ssl/ssl_stream.hpp>
AppaRao Pulibd030d02020-03-20 03:34:29 +053039#include <boost/beast/version.hpp>
Carson Labradof52c03c2022-03-23 18:50:15 +000040#include <boost/container/devector.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070041#include <boost/system/error_code.hpp>
Ed Tanous27b0cf92023-08-07 12:02:40 -070042#include <boost/url/format.hpp>
43#include <boost/url/url.hpp>
Ed Tanousa716aa72023-08-01 11:35:53 -070044#include <boost/url/url_view.hpp>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050045
AppaRao Pulibd030d02020-03-20 03:34:29 +053046#include <cstdlib>
47#include <functional>
48#include <iostream>
49#include <memory>
AppaRao Puli2a5689a2020-04-29 15:24:31 +053050#include <queue>
AppaRao Pulibd030d02020-03-20 03:34:29 +053051#include <string>
52
53namespace crow
54{
Ed Tanous27b0cf92023-08-07 12:02:40 -070055// With Redfish Aggregation it is assumed we will connect to another
56// instance of BMCWeb which can handle 100 simultaneous connections.
Carson Labrado66d90c22022-12-07 22:34:33 +000057constexpr size_t maxPoolSize = 20;
58constexpr size_t maxRequestQueueSize = 500;
Carson Labrado17dcc312022-07-28 22:17:28 +000059constexpr unsigned int httpReadBodyLimit = 131072;
Carson Labrado4d942722022-06-22 22:16:10 +000060constexpr unsigned int httpReadBufferSize = 4096;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053061
AppaRao Pulibd030d02020-03-20 03:34:29 +053062enum class ConnState
63{
AppaRao Puli2a5689a2020-04-29 15:24:31 +053064 initialized,
Sunitha Harish29a82b02021-02-18 15:54:16 +053065 resolveInProgress,
66 resolveFailed,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053067 connectInProgress,
68 connectFailed,
AppaRao Pulibd030d02020-03-20 03:34:29 +053069 connected,
AppaRao Pulie38778a2022-06-27 23:09:03 +000070 handshakeInProgress,
71 handshakeFailed,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053072 sendInProgress,
73 sendFailed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053074 recvInProgress,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053075 recvFailed,
76 idle,
Ayushi Smritife44eb02020-05-15 15:24:45 +053077 closed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053078 suspended,
79 terminated,
80 abortConnection,
AppaRao Pulie38778a2022-06-27 23:09:03 +000081 sslInitFailed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053082 retry
AppaRao Pulibd030d02020-03-20 03:34:29 +053083};
84
Carson Labradoa7a80292022-06-01 16:01:52 +000085static inline boost::system::error_code
86 defaultRetryHandler(unsigned int respCode)
87{
88 // As a default, assume 200X is alright
Ed Tanous62598e32023-07-17 17:06:25 -070089 BMCWEB_LOG_DEBUG("Using default check for response code validity");
Carson Labradoa7a80292022-06-01 16:01:52 +000090 if ((respCode < 200) || (respCode >= 300))
91 {
92 return boost::system::errc::make_error_code(
93 boost::system::errc::result_out_of_range);
94 }
95
96 // Return 0 if the response code is valid
97 return boost::system::errc::make_error_code(boost::system::errc::success);
98};
99
Ed Tanous27b0cf92023-08-07 12:02:40 -0700100// We need to allow retry information to be set before a message has been
101// sent and a connection pool has been created
Carson Labradod14a48f2023-02-22 00:24:54 +0000102struct ConnectionPolicy
Carson Labradof52c03c2022-03-23 18:50:15 +0000103{
104 uint32_t maxRetryAttempts = 5;
Carson Labradod14a48f2023-02-22 00:24:54 +0000105
106 // the max size of requests in bytes. 0 for unlimited
107 boost::optional<uint64_t> requestByteLimit = httpReadBodyLimit;
108
109 size_t maxConnections = 1;
110
Carson Labradof52c03c2022-03-23 18:50:15 +0000111 std::string retryPolicyAction = "TerminateAfterRetries";
Carson Labradod14a48f2023-02-22 00:24:54 +0000112
113 std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0);
Carson Labradoa7a80292022-06-01 16:01:52 +0000114 std::function<boost::system::error_code(unsigned int respCode)>
115 invalidResp = defaultRetryHandler;
Carson Labradof52c03c2022-03-23 18:50:15 +0000116};
117
118struct PendingRequest
119{
Ed Tanousb2896142024-01-31 15:25:47 -0800120 boost::beast::http::request<bmcweb::HttpBody> req;
Carson Labrado039a47e2022-04-05 16:03:20 +0000121 std::function<void(bool, uint32_t, Response&)> callback;
Carson Labrado039a47e2022-04-05 16:03:20 +0000122 PendingRequest(
Ed Tanousb2896142024-01-31 15:25:47 -0800123 boost::beast::http::request<bmcweb::HttpBody>&& reqIn,
Carson Labradod14a48f2023-02-22 00:24:54 +0000124 const std::function<void(bool, uint32_t, Response&)>& callbackIn) :
Ed Tanous8a592812022-06-04 09:06:59 -0700125 req(std::move(reqIn)),
Carson Labradod14a48f2023-02-22 00:24:54 +0000126 callback(callbackIn)
Carson Labradof52c03c2022-03-23 18:50:15 +0000127 {}
128};
129
Ed Tanouse01d0c32023-06-30 13:21:32 -0700130namespace http = boost::beast::http;
Carson Labradof52c03c2022-03-23 18:50:15 +0000131class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo>
AppaRao Pulibd030d02020-03-20 03:34:29 +0530132{
133 private:
Carson Labradof52c03c2022-03-23 18:50:15 +0000134 ConnState state = ConnState::initialized;
135 uint32_t retryCount = 0;
Carson Labradof52c03c2022-03-23 18:50:15 +0000136 std::string subId;
Carson Labradod14a48f2023-02-22 00:24:54 +0000137 std::shared_ptr<ConnectionPolicy> connPolicy;
Ed Tanousa716aa72023-08-01 11:35:53 -0700138 boost::urls::url host;
Carson Labradof52c03c2022-03-23 18:50:15 +0000139 uint32_t connId;
140
Carson Labradof52c03c2022-03-23 18:50:15 +0000141 // Data buffers
Ed Tanousb2896142024-01-31 15:25:47 -0800142 http::request<bmcweb::HttpBody> req;
143 using parser_type = http::response_parser<bmcweb::HttpBody>;
Ed Tanouse01d0c32023-06-30 13:21:32 -0700144 std::optional<parser_type> parser;
Carson Labrado4d942722022-06-22 22:16:10 +0000145 boost::beast::flat_static_buffer<httpReadBufferSize> buffer;
Carson Labrado039a47e2022-04-05 16:03:20 +0000146 Response res;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530147
Carson Labradof52c03c2022-03-23 18:50:15 +0000148 // Ascync callables
Carson Labrado039a47e2022-04-05 16:03:20 +0000149 std::function<void(bool, uint32_t, Response&)> callback;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700150
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600151 boost::asio::io_context& ioc;
152
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700153#ifdef BMCWEB_DBUS_DNS_RESOLVER
Ed Tanouse1452be2021-10-04 17:03:52 -0700154 using Resolver = async_resolve::Resolver;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700155#else
156 using Resolver = boost::asio::ip::tcp::resolver;
157#endif
158 Resolver resolver;
159
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800160 boost::asio::ip::tcp::socket conn;
161 std::optional<boost::beast::ssl_stream<boost::asio::ip::tcp::socket&>>
162 sslConn;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000163
Carson Labradof52c03c2022-03-23 18:50:15 +0000164 boost::asio::steady_timer timer;
Ed Tanous84b35602021-09-08 20:06:32 -0700165
Carson Labradof52c03c2022-03-23 18:50:15 +0000166 friend class ConnectionPool;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530167
Sunitha Harish29a82b02021-02-18 15:54:16 +0530168 void doResolve()
169 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530170 state = ConnState::resolveInProgress;
Ed Tanousa716aa72023-08-01 11:35:53 -0700171 BMCWEB_LOG_DEBUG("Trying to resolve: {}, id: {}", host, connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530172
Ed Tanousa716aa72023-08-01 11:35:53 -0700173 resolver.async_resolve(host.encoded_host_address(), host.port(),
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700174 std::bind_front(&ConnectionInfo::afterResolve,
175 this, shared_from_this()));
Sunitha Harish29a82b02021-02-18 15:54:16 +0530176 }
177
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700178 void afterResolve(const std::shared_ptr<ConnectionInfo>& /*self*/,
179 const boost::system::error_code& ec,
180 const Resolver::results_type& endpointList)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530181 {
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700182 if (ec || (endpointList.empty()))
183 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700184 BMCWEB_LOG_ERROR("Resolve failed: {} {}", ec.message(), host);
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700185 state = ConnState::resolveFailed;
186 waitAndRetry();
187 return;
188 }
Ed Tanousa716aa72023-08-01 11:35:53 -0700189 BMCWEB_LOG_DEBUG("Resolved {}, id: {}", host, connId);
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530190 state = ConnState::connectInProgress;
191
Ed Tanousa716aa72023-08-01 11:35:53 -0700192 BMCWEB_LOG_DEBUG("Trying to connect to: {}, id: {}", host, connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530193
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800194 timer.expires_after(std::chrono::seconds(30));
195 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
196
197 boost::asio::async_connect(
198 conn, endpointList,
199 std::bind_front(&ConnectionInfo::afterConnect, this,
200 shared_from_this()));
AppaRao Pulie38778a2022-06-27 23:09:03 +0000201 }
202
203 void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/,
Ed Tanous81c4e332023-05-18 10:30:34 -0700204 const boost::beast::error_code& ec,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000205 const boost::asio::ip::tcp::endpoint& endpoint)
206 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000207 // The operation already timed out. We don't want do continue down
208 // this branch
209 if (ec && ec == boost::asio::error::operation_aborted)
210 {
211 return;
212 }
213
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800214 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000215 if (ec)
216 {
Ed Tanous62598e32023-07-17 17:06:25 -0700217 BMCWEB_LOG_ERROR("Connect {}:{}, id: {} failed: {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700218 endpoint.address().to_string(), endpoint.port(),
219 connId, ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000220 state = ConnState::connectFailed;
221 waitAndRetry();
222 return;
223 }
Ed Tanousa716aa72023-08-01 11:35:53 -0700224 BMCWEB_LOG_DEBUG("Connected to: {}:{}, id: {}",
225 endpoint.address().to_string(), endpoint.port(),
226 connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000227 if (sslConn)
228 {
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800229 doSslHandshake();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000230 return;
231 }
232 state = ConnState::connected;
233 sendMessage();
234 }
235
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800236 void doSslHandshake()
AppaRao Pulie38778a2022-06-27 23:09:03 +0000237 {
238 if (!sslConn)
239 {
240 return;
241 }
242 state = ConnState::handshakeInProgress;
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800243 timer.expires_after(std::chrono::seconds(30));
244 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
AppaRao Pulie38778a2022-06-27 23:09:03 +0000245 sslConn->async_handshake(
246 boost::asio::ssl::stream_base::client,
247 std::bind_front(&ConnectionInfo::afterSslHandshake, this,
248 shared_from_this()));
249 }
250
251 void afterSslHandshake(const std::shared_ptr<ConnectionInfo>& /*self*/,
Ed Tanous81c4e332023-05-18 10:30:34 -0700252 const boost::beast::error_code& ec)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000253 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000254 // The operation already timed out. We don't want do continue down
255 // this branch
256 if (ec && ec == boost::asio::error::operation_aborted)
257 {
258 return;
259 }
260
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800261 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000262 if (ec)
263 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700264 BMCWEB_LOG_ERROR("SSL Handshake failed - id: {} error: {}", connId,
265 ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000266 state = ConnState::handshakeFailed;
267 waitAndRetry();
268 return;
269 }
Ed Tanousa716aa72023-08-01 11:35:53 -0700270 BMCWEB_LOG_DEBUG("SSL Handshake successful - id: {}", connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000271 state = ConnState::connected;
272 sendMessage();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530273 }
274
Carson Labradof52c03c2022-03-23 18:50:15 +0000275 void sendMessage()
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530276 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530277 state = ConnState::sendInProgress;
278
AppaRao Pulibd030d02020-03-20 03:34:29 +0530279 // Set a timeout on the operation
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800280 timer.expires_after(std::chrono::seconds(30));
281 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
AppaRao Pulibd030d02020-03-20 03:34:29 +0530282
283 // Send the HTTP request to the remote host
AppaRao Pulie38778a2022-06-27 23:09:03 +0000284 if (sslConn)
285 {
286 boost::beast::http::async_write(
287 *sslConn, req,
288 std::bind_front(&ConnectionInfo::afterWrite, this,
289 shared_from_this()));
290 }
291 else
292 {
293 boost::beast::http::async_write(
294 conn, req,
295 std::bind_front(&ConnectionInfo::afterWrite, this,
296 shared_from_this()));
297 }
298 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530299
AppaRao Pulie38778a2022-06-27 23:09:03 +0000300 void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/,
301 const boost::beast::error_code& ec, size_t bytesTransferred)
302 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000303 // The operation already timed out. We don't want do continue down
304 // this branch
305 if (ec && ec == boost::asio::error::operation_aborted)
306 {
307 return;
308 }
309
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800310 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000311 if (ec)
312 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700313 BMCWEB_LOG_ERROR("sendMessage() failed: {} {}", ec.message(), host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000314 state = ConnState::sendFailed;
315 waitAndRetry();
316 return;
317 }
Ed Tanous62598e32023-07-17 17:06:25 -0700318 BMCWEB_LOG_DEBUG("sendMessage() bytes transferred: {}",
319 bytesTransferred);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000320
321 recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530322 }
323
324 void recvMessage()
325 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530326 state = ConnState::recvInProgress;
327
Ed Tanouse01d0c32023-06-30 13:21:32 -0700328 parser_type& thisParser = parser.emplace(std::piecewise_construct,
329 std::make_tuple());
Carson Labradod14a48f2023-02-22 00:24:54 +0000330
Ed Tanouse01d0c32023-06-30 13:21:32 -0700331 thisParser.body_limit(connPolicy->requestByteLimit);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530332
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800333 timer.expires_after(std::chrono::seconds(30));
334 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
335
AppaRao Pulibd030d02020-03-20 03:34:29 +0530336 // Receive the HTTP response
AppaRao Pulie38778a2022-06-27 23:09:03 +0000337 if (sslConn)
338 {
339 boost::beast::http::async_read(
Ed Tanouse01d0c32023-06-30 13:21:32 -0700340 *sslConn, buffer, thisParser,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000341 std::bind_front(&ConnectionInfo::afterRead, this,
342 shared_from_this()));
343 }
344 else
345 {
346 boost::beast::http::async_read(
Ed Tanouse01d0c32023-06-30 13:21:32 -0700347 conn, buffer, thisParser,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000348 std::bind_front(&ConnectionInfo::afterRead, this,
349 shared_from_this()));
350 }
351 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530352
AppaRao Pulie38778a2022-06-27 23:09:03 +0000353 void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/,
354 const boost::beast::error_code& ec,
355 const std::size_t& bytesTransferred)
356 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000357 // The operation already timed out. We don't want do continue down
358 // this branch
359 if (ec && ec == boost::asio::error::operation_aborted)
360 {
361 return;
362 }
363
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800364 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000365 if (ec && ec != boost::asio::ssl::error::stream_truncated)
366 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700367 BMCWEB_LOG_ERROR("recvMessage() failed: {} from {}", ec.message(),
368 host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000369 state = ConnState::recvFailed;
370 waitAndRetry();
371 return;
372 }
Ed Tanous62598e32023-07-17 17:06:25 -0700373 BMCWEB_LOG_DEBUG("recvMessage() bytes transferred: {}",
374 bytesTransferred);
Ed Tanouse01d0c32023-06-30 13:21:32 -0700375 if (!parser)
376 {
377 return;
378 }
Ed Tanous52e31622024-01-23 16:31:11 -0800379 BMCWEB_LOG_DEBUG("recvMessage() data: {}", parser->get().body().str());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000380
381 unsigned int respCode = parser->get().result_int();
Ed Tanous62598e32023-07-17 17:06:25 -0700382 BMCWEB_LOG_DEBUG("recvMessage() Header Response Code: {}", respCode);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000383
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600384 // Handle the case of stream_truncated. Some servers close the ssl
385 // connection uncleanly, so check to see if we got a full response
386 // before we handle this as an error.
387 if (!parser->is_done())
388 {
389 state = ConnState::recvFailed;
390 waitAndRetry();
391 return;
392 }
393
AppaRao Pulie38778a2022-06-27 23:09:03 +0000394 // Make sure the received response code is valid as defined by
395 // the associated retry policy
Carson Labradod14a48f2023-02-22 00:24:54 +0000396 if (connPolicy->invalidResp(respCode))
AppaRao Pulie38778a2022-06-27 23:09:03 +0000397 {
398 // The listener failed to receive the Sent-Event
Ed Tanous62598e32023-07-17 17:06:25 -0700399 BMCWEB_LOG_ERROR(
400 "recvMessage() Listener Failed to "
Ed Tanousa716aa72023-08-01 11:35:53 -0700401 "receive Sent-Event. Header Response Code: {} from {}",
402 respCode, host);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000403 state = ConnState::recvFailed;
404 waitAndRetry();
405 return;
406 }
Ed Tanous002d39b2022-05-31 08:59:27 -0700407
AppaRao Pulie38778a2022-06-27 23:09:03 +0000408 // Send is successful
409 // Reset the counter just in case this was after retrying
410 retryCount = 0;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530411
AppaRao Pulie38778a2022-06-27 23:09:03 +0000412 // Keep the connection alive if server supports it
413 // Else close the connection
Ed Tanous62598e32023-07-17 17:06:25 -0700414 BMCWEB_LOG_DEBUG("recvMessage() keepalive : {}", parser->keep_alive());
AppaRao Pulibd030d02020-03-20 03:34:29 +0530415
AppaRao Pulie38778a2022-06-27 23:09:03 +0000416 // Copy the response into a Response object so that it can be
417 // processed by the callback function.
Ed Tanous27b0cf92023-08-07 12:02:40 -0700418 res.response = parser->release();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000419 callback(parser->keep_alive(), connId, res);
Carson Labrado513d1ff2022-07-19 00:38:15 +0000420 res.clear();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530421 }
422
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800423 static void onTimeout(const std::weak_ptr<ConnectionInfo>& weakSelf,
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800424 const boost::system::error_code& ec)
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800425 {
426 if (ec == boost::asio::error::operation_aborted)
427 {
Ed Tanous62598e32023-07-17 17:06:25 -0700428 BMCWEB_LOG_DEBUG(
429 "async_wait failed since the operation is aborted");
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800430 return;
431 }
432 if (ec)
433 {
Ed Tanous62598e32023-07-17 17:06:25 -0700434 BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
Ed Tanous27b0cf92023-08-07 12:02:40 -0700435 // If the timer fails, we need to close the socket anyway, same
436 // as if it expired.
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800437 }
438 std::shared_ptr<ConnectionInfo> self = weakSelf.lock();
439 if (self == nullptr)
440 {
441 return;
442 }
443 self->waitAndRetry();
444 }
445
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530446 void waitAndRetry()
AppaRao Pulibd030d02020-03-20 03:34:29 +0530447 {
Carson Labradod14a48f2023-02-22 00:24:54 +0000448 if ((retryCount >= connPolicy->maxRetryAttempts) ||
AppaRao Pulie38778a2022-06-27 23:09:03 +0000449 (state == ConnState::sslInitFailed))
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530450 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700451 BMCWEB_LOG_ERROR("Maximum number of retries reached. {}", host);
Ed Tanous62598e32023-07-17 17:06:25 -0700452 BMCWEB_LOG_DEBUG("Retry policy: {}", connPolicy->retryPolicyAction);
Carson Labrado039a47e2022-04-05 16:03:20 +0000453
Carson Labradod14a48f2023-02-22 00:24:54 +0000454 if (connPolicy->retryPolicyAction == "TerminateAfterRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530455 {
456 // TODO: delete subscription
457 state = ConnState::terminated;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530458 }
Carson Labradod14a48f2023-02-22 00:24:54 +0000459 if (connPolicy->retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530460 {
461 state = ConnState::suspended;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530462 }
Carson Labrado513d1ff2022-07-19 00:38:15 +0000463
464 // We want to return a 502 to indicate there was an error with
465 // the external server
466 res.result(boost::beast::http::status::bad_gateway);
467 callback(false, connId, res);
468 res.clear();
469
Ed Tanous27b0cf92023-08-07 12:02:40 -0700470 // Reset the retrycount to zero so that client can try
471 // connecting again if needed
Ed Tanous3174e4d2020-10-07 11:41:22 -0700472 retryCount = 0;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530473 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530474 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530475
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530476 retryCount++;
477
Ed Tanous62598e32023-07-17 17:06:25 -0700478 BMCWEB_LOG_DEBUG("Attempt retry after {} seconds. RetryCount = {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700479 connPolicy->retryIntervalSecs.count(), retryCount);
Carson Labradod14a48f2023-02-22 00:24:54 +0000480 timer.expires_after(connPolicy->retryIntervalSecs);
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700481 timer.async_wait(std::bind_front(&ConnectionInfo::onTimerDone, this,
482 shared_from_this()));
483 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530484
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700485 void onTimerDone(const std::shared_ptr<ConnectionInfo>& /*self*/,
486 const boost::system::error_code& ec)
487 {
488 if (ec == boost::asio::error::operation_aborted)
489 {
Ed Tanous62598e32023-07-17 17:06:25 -0700490 BMCWEB_LOG_DEBUG(
491 "async_wait failed since the operation is aborted{}",
492 ec.message());
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700493 }
494 else if (ec)
495 {
Ed Tanous62598e32023-07-17 17:06:25 -0700496 BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700497 // Ignore the error and continue the retry loop to attempt
498 // sending the event as per the retry policy
499 }
500
501 // Let's close the connection and restart from resolve.
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600502 shutdownConn(true);
503 }
504
505 void restartConnection()
506 {
507 BMCWEB_LOG_DEBUG("{}, id: {} restartConnection", host,
508 std::to_string(connId));
509 initializeConnection(host.scheme() == "https");
510 doResolve();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530511 }
512
AppaRao Pulie38778a2022-06-27 23:09:03 +0000513 void shutdownConn(bool retry)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530514 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000515 boost::beast::error_code ec;
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800516 conn.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
Carson Labradof52c03c2022-03-23 18:50:15 +0000517 conn.close();
518
519 // not_connected happens sometimes so don't bother reporting it.
520 if (ec && ec != boost::beast::errc::not_connected)
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530521 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700522 BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId,
Ed Tanous62598e32023-07-17 17:06:25 -0700523 ec.message());
Carson Labradof52c03c2022-03-23 18:50:15 +0000524 }
Carson Labrado5cab68f2022-07-11 22:26:21 +0000525 else
526 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700527 BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId);
Carson Labrado5cab68f2022-07-11 22:26:21 +0000528 }
Ed Tanousca723762022-06-28 19:40:39 -0700529
Carson Labrado513d1ff2022-07-19 00:38:15 +0000530 if (retry)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000531 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000532 // Now let's try to resend the data
533 state = ConnState::retry;
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600534 restartConnection();
Carson Labrado513d1ff2022-07-19 00:38:15 +0000535 }
536 else
537 {
538 state = ConnState::closed;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000539 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000540 }
541
AppaRao Pulie38778a2022-06-27 23:09:03 +0000542 void doClose(bool retry = false)
Carson Labradof52c03c2022-03-23 18:50:15 +0000543 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000544 if (!sslConn)
545 {
546 shutdownConn(retry);
547 return;
548 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000549
AppaRao Pulie38778a2022-06-27 23:09:03 +0000550 sslConn->async_shutdown(
551 std::bind_front(&ConnectionInfo::afterSslShutdown, this,
552 shared_from_this(), retry));
553 }
554
555 void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/,
556 bool retry, const boost::system::error_code& ec)
557 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000558 if (ec)
Carson Labradof52c03c2022-03-23 18:50:15 +0000559 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700560 BMCWEB_LOG_ERROR("{}, id: {} shutdown failed: {}", host, connId,
Ed Tanous62598e32023-07-17 17:06:25 -0700561 ec.message());
Carson Labradof52c03c2022-03-23 18:50:15 +0000562 }
Carson Labrado5cab68f2022-07-11 22:26:21 +0000563 else
564 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700565 BMCWEB_LOG_DEBUG("{}, id: {} closed gracefully", host, connId);
Carson Labrado5cab68f2022-07-11 22:26:21 +0000566 }
AppaRao Pulie38778a2022-06-27 23:09:03 +0000567 shutdownConn(retry);
568 }
Ed Tanousca723762022-06-28 19:40:39 -0700569
AppaRao Pulie38778a2022-06-27 23:09:03 +0000570 void setCipherSuiteTLSext()
571 {
572 if (!sslConn)
573 {
574 return;
575 }
Ravi Tejae7c29912023-07-31 09:39:32 -0500576
577 if (host.host_type() != boost::urls::host_type::name)
578 {
579 // Avoid setting SNI hostname if its IP address
580 return;
581 }
582 // Create a null terminated string for SSL
Ed Tanousa716aa72023-08-01 11:35:53 -0700583 std::string hostname(host.encoded_host_address());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000584 // NOTE: The SSL_set_tlsext_host_name is defined in tlsv1.h header
585 // file but its having old style casting (name is cast to void*).
586 // Since bmcweb compiler treats all old-style-cast as error, its
587 // causing the build failure. So replaced the same macro inline and
588 // did corrected the code by doing static_cast to viod*. This has to
589 // be fixed in openssl library in long run. Set SNI Hostname (many
590 // hosts need this to handshake successfully)
591 if (SSL_ctrl(sslConn->native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME,
592 TLSEXT_NAMETYPE_host_name,
Ed Tanousa716aa72023-08-01 11:35:53 -0700593 static_cast<void*>(hostname.data())) == 0)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000594
595 {
596 boost::beast::error_code ec{static_cast<int>(::ERR_get_error()),
597 boost::asio::error::get_ssl_category()};
598
Ed Tanousa716aa72023-08-01 11:35:53 -0700599 BMCWEB_LOG_ERROR("SSL_set_tlsext_host_name {}, id: {} failed: {}",
600 host, connId, ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000601 // Set state as sslInit failed so that we close the connection
602 // and take appropriate action as per retry configuration.
603 state = ConnState::sslInitFailed;
604 waitAndRetry();
605 return;
606 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530607 }
608
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600609 void initializeConnection(bool ssl)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000610 {
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600611 conn = boost::asio::ip::tcp::socket(ioc);
612 if (ssl)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000613 {
614 std::optional<boost::asio::ssl::context> sslCtx =
615 ensuressl::getSSLClientContext();
616
617 if (!sslCtx)
618 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700619 BMCWEB_LOG_ERROR("prepareSSLContext failed - {}, id: {}", host,
620 connId);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000621 // Don't retry if failure occurs while preparing SSL context
Ed Tanous27b0cf92023-08-07 12:02:40 -0700622 // such as certificate is invalid or set cipher failure or
623 // set host name failure etc... Setting conn state to
624 // sslInitFailed and connection state will be transitioned
625 // to next state depending on retry policy set by
626 // subscription.
AppaRao Pulie38778a2022-06-27 23:09:03 +0000627 state = ConnState::sslInitFailed;
628 waitAndRetry();
629 return;
630 }
631 sslConn.emplace(conn, *sslCtx);
632 setCipherSuiteTLSext();
633 }
634 }
Abhilash Rajuf3cb5df2023-11-30 03:54:11 -0600635
636 public:
637 explicit ConnectionInfo(
638 boost::asio::io_context& iocIn, const std::string& idIn,
639 const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
640 boost::urls::url_view hostIn, unsigned int connIdIn) :
641 subId(idIn),
642 connPolicy(connPolicyIn), host(hostIn), connId(connIdIn), ioc(iocIn),
643 resolver(iocIn), conn(iocIn), timer(iocIn)
644 {
645 initializeConnection(host.scheme() == "https");
646 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000647};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530648
Carson Labradof52c03c2022-03-23 18:50:15 +0000649class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
650{
651 private:
652 boost::asio::io_context& ioc;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000653 std::string id;
Carson Labradod14a48f2023-02-22 00:24:54 +0000654 std::shared_ptr<ConnectionPolicy> connPolicy;
Ed Tanousa716aa72023-08-01 11:35:53 -0700655 boost::urls::url destIP;
Carson Labradof52c03c2022-03-23 18:50:15 +0000656 std::vector<std::shared_ptr<ConnectionInfo>> connections;
657 boost::container::devector<PendingRequest> requestQueue;
658
659 friend class HttpClient;
660
Carson Labrado244256c2022-04-27 17:16:32 +0000661 // Configure a connections's request, callback, and retry info in
662 // preparation to begin sending the request
Carson Labradof52c03c2022-03-23 18:50:15 +0000663 void setConnProps(ConnectionInfo& conn)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530664 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000665 if (requestQueue.empty())
AppaRao Pulibd030d02020-03-20 03:34:29 +0530666 {
Ed Tanous62598e32023-07-17 17:06:25 -0700667 BMCWEB_LOG_ERROR(
668 "setConnProps() should not have been called when requestQueue is empty");
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530669 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530670 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530671
Ed Tanous52e31622024-01-23 16:31:11 -0800672 PendingRequest& nextReq = requestQueue.front();
Carson Labrado244256c2022-04-27 17:16:32 +0000673 conn.req = std::move(nextReq.req);
674 conn.callback = std::move(nextReq.callback);
Carson Labradof52c03c2022-03-23 18:50:15 +0000675
Ed Tanousa716aa72023-08-01 11:35:53 -0700676 BMCWEB_LOG_DEBUG("Setting properties for connection {}, id: {}",
677 conn.host, conn.connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000678
679 // We can remove the request from the queue at this point
680 requestQueue.pop_front();
681 }
682
Carson Labradof52c03c2022-03-23 18:50:15 +0000683 // Gets called as part of callback after request is sent
684 // Reuses the connection if there are any requests waiting to be sent
685 // Otherwise closes the connection if it is not a keep-alive
686 void sendNext(bool keepAlive, uint32_t connId)
687 {
688 auto conn = connections[connId];
Carson Labrado46a81462022-04-27 21:11:37 +0000689
690 // Allow the connection's handler to be deleted
691 // This is needed because of Redfish Aggregation passing an
692 // AsyncResponse shared_ptr to this callback
693 conn->callback = nullptr;
694
Carson Labradof52c03c2022-03-23 18:50:15 +0000695 // Reuse the connection to send the next request in the queue
696 if (!requestQueue.empty())
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530697 {
Ed Tanous62598e32023-07-17 17:06:25 -0700698 BMCWEB_LOG_DEBUG(
Ed Tanous8ece0e42024-01-02 13:16:50 -0800699 "{} requests remaining in queue for {}, reusing connection {}",
Ed Tanousa716aa72023-08-01 11:35:53 -0700700 requestQueue.size(), destIP, connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000701
702 setConnProps(*conn);
703
704 if (keepAlive)
705 {
706 conn->sendMessage();
707 }
708 else
709 {
710 // Server is not keep-alive enabled so we need to close the
711 // connection and then start over from resolve
712 conn->doClose();
713 conn->doResolve();
714 }
715 return;
716 }
717
718 // No more messages to send so close the connection if necessary
719 if (keepAlive)
720 {
721 conn->state = ConnState::idle;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530722 }
723 else
724 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000725 // Abort the connection since server is not keep-alive enabled
726 conn->state = ConnState::abortConnection;
727 conn->doClose();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530728 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530729 }
730
Ed Tanousa716aa72023-08-01 11:35:53 -0700731 void sendData(std::string&& data, boost::urls::url_view destUri,
Carson Labrado244256c2022-04-27 17:16:32 +0000732 const boost::beast::http::fields& httpHeader,
733 const boost::beast::http::verb verb,
Ed Tanous6b3db602022-06-28 19:41:44 -0700734 const std::function<void(Response&)>& resHandler)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530735 {
Carson Labrado244256c2022-04-27 17:16:32 +0000736 // Construct the request to be sent
Ed Tanousb2896142024-01-31 15:25:47 -0800737 boost::beast::http::request<bmcweb::HttpBody> thisReq(
Ed Tanousa716aa72023-08-01 11:35:53 -0700738 verb, destUri.encoded_target(), 11, "", httpHeader);
739 thisReq.set(boost::beast::http::field::host,
740 destUri.encoded_host_address());
Carson Labrado244256c2022-04-27 17:16:32 +0000741 thisReq.keep_alive(true);
Ed Tanous52e31622024-01-23 16:31:11 -0800742 thisReq.body().str() = std::move(data);
Carson Labrado244256c2022-04-27 17:16:32 +0000743 thisReq.prepare_payload();
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700744 auto cb = std::bind_front(&ConnectionPool::afterSendData,
745 weak_from_this(), resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000746 // Reuse an existing connection if one is available
747 for (unsigned int i = 0; i < connections.size(); i++)
748 {
749 auto conn = connections[i];
750 if ((conn->state == ConnState::idle) ||
751 (conn->state == ConnState::initialized) ||
752 (conn->state == ConnState::closed))
753 {
Carson Labrado244256c2022-04-27 17:16:32 +0000754 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000755 conn->callback = std::move(cb);
Ed Tanousa716aa72023-08-01 11:35:53 -0700756 std::string commonMsg = std::format("{} from pool {}", i, id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000757
758 if (conn->state == ConnState::idle)
759 {
Ed Tanous62598e32023-07-17 17:06:25 -0700760 BMCWEB_LOG_DEBUG("Grabbing idle connection {}", commonMsg);
Carson Labradof52c03c2022-03-23 18:50:15 +0000761 conn->sendMessage();
762 }
763 else
764 {
Ed Tanous62598e32023-07-17 17:06:25 -0700765 BMCWEB_LOG_DEBUG("Reusing existing connection {}",
766 commonMsg);
Carson Labradof52c03c2022-03-23 18:50:15 +0000767 conn->doResolve();
768 }
769 return;
770 }
771 }
772
Ed Tanous27b0cf92023-08-07 12:02:40 -0700773 // All connections in use so create a new connection or add request
774 // to the queue
Carson Labradod14a48f2023-02-22 00:24:54 +0000775 if (connections.size() < connPolicy->maxConnections)
Carson Labradof52c03c2022-03-23 18:50:15 +0000776 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700777 BMCWEB_LOG_DEBUG("Adding new connection to pool {}", id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000778 auto conn = addConnection();
Carson Labrado244256c2022-04-27 17:16:32 +0000779 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000780 conn->callback = std::move(cb);
Carson Labradof52c03c2022-03-23 18:50:15 +0000781 conn->doResolve();
782 }
783 else if (requestQueue.size() < maxRequestQueueSize)
784 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700785 BMCWEB_LOG_DEBUG("Max pool size reached. Adding data to queue {}",
786 id);
Carson Labradod14a48f2023-02-22 00:24:54 +0000787 requestQueue.emplace_back(std::move(thisReq), std::move(cb));
Carson Labradof52c03c2022-03-23 18:50:15 +0000788 }
789 else
790 {
Ed Tanous27b0cf92023-08-07 12:02:40 -0700791 // If we can't buffer the request then we should let the
792 // callback handle a 429 Too Many Requests dummy response
Ed Tanous6ea90762024-04-07 08:38:44 -0700793 BMCWEB_LOG_ERROR("{} request queue full. Dropping request.", id);
Carson Labrado43e14d32022-11-09 00:25:20 +0000794 Response dummyRes;
795 dummyRes.result(boost::beast::http::status::too_many_requests);
796 resHandler(dummyRes);
Carson Labradof52c03c2022-03-23 18:50:15 +0000797 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530798 }
799
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700800 // Callback to be called once the request has been sent
801 static void afterSendData(const std::weak_ptr<ConnectionPool>& weakSelf,
802 const std::function<void(Response&)>& resHandler,
803 bool keepAlive, uint32_t connId, Response& res)
804 {
805 // Allow provided callback to perform additional processing of the
806 // request
807 resHandler(res);
808
809 // If requests remain in the queue then we want to reuse this
810 // connection to send the next request
811 std::shared_ptr<ConnectionPool> self = weakSelf.lock();
812 if (!self)
813 {
Ed Tanous62598e32023-07-17 17:06:25 -0700814 BMCWEB_LOG_CRITICAL("{} Failed to capture connection",
815 logPtr(self.get()));
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700816 return;
817 }
818
819 self->sendNext(keepAlive, connId);
820 }
821
Carson Labradof52c03c2022-03-23 18:50:15 +0000822 std::shared_ptr<ConnectionInfo>& addConnection()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530823 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000824 unsigned int newId = static_cast<unsigned int>(connections.size());
825
AppaRao Pulie38778a2022-06-27 23:09:03 +0000826 auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>(
Ed Tanousa716aa72023-08-01 11:35:53 -0700827 ioc, id, connPolicy, destIP, newId));
Carson Labradof52c03c2022-03-23 18:50:15 +0000828
Ed Tanousa716aa72023-08-01 11:35:53 -0700829 BMCWEB_LOG_DEBUG("Added connection {} to pool {}",
830 connections.size() - 1, id);
Carson Labradof52c03c2022-03-23 18:50:15 +0000831
832 return ret;
833 }
834
835 public:
Carson Labradod14a48f2023-02-22 00:24:54 +0000836 explicit ConnectionPool(
837 boost::asio::io_context& iocIn, const std::string& idIn,
838 const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
Ed Tanousa716aa72023-08-01 11:35:53 -0700839 boost::urls::url_view destIPIn) :
Ed Tanous8a592812022-06-04 09:06:59 -0700840 ioc(iocIn),
Ed Tanousa716aa72023-08-01 11:35:53 -0700841 id(idIn), connPolicy(connPolicyIn), destIP(destIPIn)
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 Tanousa716aa72023-08-01 11:35:53 -0700882 void sendData(std::string&& data, boost::urls::url_view destUri,
Carson Labradof52c03c2022-03-23 18:50:15 +0000883 const boost::beast::http::fields& httpHeader,
Carson Labradod14a48f2023-02-22 00:24:54 +0000884 const boost::beast::http::verb verb)
Carson Labradof52c03c2022-03-23 18:50:15 +0000885 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000886 const std::function<void(Response&)> cb = genericResHandler;
Ed Tanousa716aa72023-08-01 11:35:53 -0700887 sendDataWithCallback(std::move(data), destUri, httpHeader, verb, cb);
Carson Labrado039a47e2022-04-05 16:03:20 +0000888 }
889
Ed Tanousa716aa72023-08-01 11:35:53 -0700890 // Send request to destIP and use the provided callback to
Carson Labrado039a47e2022-04-05 16:03:20 +0000891 // handle the response
Ed Tanousa716aa72023-08-01 11:35:53 -0700892 void sendDataWithCallback(std::string&& data, boost::urls::url_view destUrl,
Carson Labrado039a47e2022-04-05 16:03:20 +0000893 const boost::beast::http::fields& httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000894 const boost::beast::http::verb verb,
Ed Tanous6b3db602022-06-28 19:41:44 -0700895 const std::function<void(Response&)>& resHandler)
Carson Labrado039a47e2022-04-05 16:03:20 +0000896 {
Ed Tanousa716aa72023-08-01 11:35:53 -0700897 std::string clientKey = std::format("{}://{}", destUrl.scheme(),
898 destUrl.encoded_host_and_port());
Carson Labradod14a48f2023-02-22 00:24:54 +0000899 auto pool = connectionPools.try_emplace(clientKey);
900 if (pool.first->second == nullptr)
Carson Labradof52c03c2022-03-23 18:50:15 +0000901 {
Carson Labradod14a48f2023-02-22 00:24:54 +0000902 pool.first->second = std::make_shared<ConnectionPool>(
Ed Tanousa716aa72023-08-01 11:35:53 -0700903 ioc, clientKey, connPolicy, destUrl);
Carson Labradof52c03c2022-03-23 18:50:15 +0000904 }
Ed Tanous27b0cf92023-08-07 12:02:40 -0700905 // Send the data using either the existing connection pool or the
906 // newly created connection pool
Ed Tanousa716aa72023-08-01 11:35:53 -0700907 pool.first->second->sendData(std::move(data), destUrl, httpHeader, verb,
Carson Labradod14a48f2023-02-22 00:24:54 +0000908 resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000909 }
910};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530911} // namespace crow