blob: e1c4d374ebf44591709ae4e9d19654e1d8ef9054 [file] [log] [blame]
AppaRao Pulibd030d02020-03-20 03:34:29 +05301/*
2// Copyright (c) 2020 Intel Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
16#pragma once
Nan Zhou77665bd2022-10-12 20:28:58 +000017
18#include "async_resolve.hpp"
19#include "http_response.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080020#include "logging.hpp"
21#include "ssl_key_handler.hpp"
Nan Zhou77665bd2022-10-12 20:28:58 +000022
Ed Tanous0d5f5cf2022-03-12 15:30:55 -080023#include <boost/asio/connect.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070024#include <boost/asio/io_context.hpp>
Sunitha Harish29a82b02021-02-18 15:54:16 +053025#include <boost/asio/ip/address.hpp>
26#include <boost/asio/ip/basic_endpoint.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070027#include <boost/asio/ip/tcp.hpp>
AppaRao Pulie38778a2022-06-27 23:09:03 +000028#include <boost/asio/ssl/context.hpp>
29#include <boost/asio/ssl/error.hpp>
Ed Tanousd43cd0c2020-09-30 20:46:53 -070030#include <boost/asio/steady_timer.hpp>
31#include <boost/beast/core/flat_buffer.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070032#include <boost/beast/core/flat_static_buffer.hpp>
Ed Tanousd43cd0c2020-09-30 20:46:53 -070033#include <boost/beast/http/message.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070034#include <boost/beast/http/parser.hpp>
35#include <boost/beast/http/read.hpp>
36#include <boost/beast/http/string_body.hpp>
37#include <boost/beast/http/write.hpp>
AppaRao Pulie38778a2022-06-27 23:09:03 +000038#include <boost/beast/ssl/ssl_stream.hpp>
AppaRao Pulibd030d02020-03-20 03:34:29 +053039#include <boost/beast/version.hpp>
Carson Labradof52c03c2022-03-23 18:50:15 +000040#include <boost/container/devector.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070041#include <boost/system/error_code.hpp>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050042
AppaRao Pulibd030d02020-03-20 03:34:29 +053043#include <cstdlib>
44#include <functional>
45#include <iostream>
46#include <memory>
AppaRao Puli2a5689a2020-04-29 15:24:31 +053047#include <queue>
AppaRao Pulibd030d02020-03-20 03:34:29 +053048#include <string>
49
50namespace crow
51{
52
Carson Labrado66d90c22022-12-07 22:34:33 +000053// With Redfish Aggregation it is assumed we will connect to another instance
54// of BMCWeb which can handle 100 simultaneous connections.
55constexpr 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
Carson Labradof52c03c2022-03-23 18:50:15 +000098// We need to allow retry information to be set before a message has been sent
99// 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{
Carson Labrado244256c2022-04-27 17:16:32 +0000118 boost::beast::http::request<boost::beast::http::string_body> 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 Tanous8a592812022-06-04 09:06:59 -0700121 boost::beast::http::request<boost::beast::http::string_body>&& 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;
Carson Labradof52c03c2022-03-23 18:50:15 +0000136 std::string host;
137 uint16_t port;
138 uint32_t connId;
139
Carson Labradof52c03c2022-03-23 18:50:15 +0000140 // Data buffers
Ed Tanouse01d0c32023-06-30 13:21:32 -0700141 http::request<http::string_body> req;
142 using parser_type = http::response_parser<http::string_body>;
143 std::optional<parser_type> parser;
Carson Labrado4d942722022-06-22 22:16:10 +0000144 boost::beast::flat_static_buffer<httpReadBufferSize> buffer;
Carson Labrado039a47e2022-04-05 16:03:20 +0000145 Response res;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530146
Carson Labradof52c03c2022-03-23 18:50:15 +0000147 // Ascync callables
Carson Labrado039a47e2022-04-05 16:03:20 +0000148 std::function<void(bool, uint32_t, Response&)> callback;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700149
150#ifdef BMCWEB_DBUS_DNS_RESOLVER
Ed Tanouse1452be2021-10-04 17:03:52 -0700151 using Resolver = async_resolve::Resolver;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700152#else
153 using Resolver = boost::asio::ip::tcp::resolver;
154#endif
155 Resolver resolver;
156
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800157 boost::asio::ip::tcp::socket conn;
158 std::optional<boost::beast::ssl_stream<boost::asio::ip::tcp::socket&>>
159 sslConn;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000160
Carson Labradof52c03c2022-03-23 18:50:15 +0000161 boost::asio::steady_timer timer;
Ed Tanous84b35602021-09-08 20:06:32 -0700162
Carson Labradof52c03c2022-03-23 18:50:15 +0000163 friend class ConnectionPool;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530164
Sunitha Harish29a82b02021-02-18 15:54:16 +0530165 void doResolve()
166 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530167 state = ConnState::resolveInProgress;
Ed Tanous62598e32023-07-17 17:06:25 -0700168 BMCWEB_LOG_DEBUG("Trying to resolve: {}:{}, id: {}", host,
169 std::to_string(port), std::to_string(connId));
Sunitha Harish29a82b02021-02-18 15:54:16 +0530170
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700171 resolver.async_resolve(host, std::to_string(port),
172 std::bind_front(&ConnectionInfo::afterResolve,
173 this, shared_from_this()));
Sunitha Harish29a82b02021-02-18 15:54:16 +0530174 }
175
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700176 void afterResolve(const std::shared_ptr<ConnectionInfo>& /*self*/,
177 const boost::system::error_code& ec,
178 const Resolver::results_type& endpointList)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530179 {
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700180 if (ec || (endpointList.empty()))
181 {
Ed Tanous62598e32023-07-17 17:06:25 -0700182 BMCWEB_LOG_ERROR("Resolve failed: {} {}:{}", ec.message(), host,
183 std::to_string(port));
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700184 state = ConnState::resolveFailed;
185 waitAndRetry();
186 return;
187 }
Ed Tanous62598e32023-07-17 17:06:25 -0700188 BMCWEB_LOG_DEBUG("Resolved {}:{}, id: {}", host, std::to_string(port),
189 std::to_string(connId));
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530190 state = ConnState::connectInProgress;
191
Ed Tanous62598e32023-07-17 17:06:25 -0700192 BMCWEB_LOG_DEBUG("Trying to connect to: {}:{}, id: {}", host,
193 std::to_string(port), std::to_string(connId));
Sunitha Harish29a82b02021-02-18 15:54:16 +0530194
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800195 timer.expires_after(std::chrono::seconds(30));
196 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
197
198 boost::asio::async_connect(
199 conn, endpointList,
200 std::bind_front(&ConnectionInfo::afterConnect, this,
201 shared_from_this()));
AppaRao Pulie38778a2022-06-27 23:09:03 +0000202 }
203
204 void afterConnect(const std::shared_ptr<ConnectionInfo>& /*self*/,
Ed Tanous81c4e332023-05-18 10:30:34 -0700205 const boost::beast::error_code& ec,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000206 const boost::asio::ip::tcp::endpoint& endpoint)
207 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000208 // The operation already timed out. We don't want do continue down
209 // this branch
210 if (ec && ec == boost::asio::error::operation_aborted)
211 {
212 return;
213 }
214
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800215 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000216 if (ec)
217 {
Ed Tanous62598e32023-07-17 17:06:25 -0700218 BMCWEB_LOG_ERROR("Connect {}:{}, id: {} failed: {}",
219 endpoint.address().to_string(),
220 std::to_string(endpoint.port()),
221 std::to_string(connId), ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000222 state = ConnState::connectFailed;
223 waitAndRetry();
224 return;
225 }
Ed Tanous62598e32023-07-17 17:06:25 -0700226 BMCWEB_LOG_DEBUG(
227 "Connected to: {}:{}, id: {}", endpoint.address().to_string(),
228 std::to_string(endpoint.port()), std::to_string(connId));
AppaRao Pulie38778a2022-06-27 23:09:03 +0000229 if (sslConn)
230 {
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800231 doSslHandshake();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000232 return;
233 }
234 state = ConnState::connected;
235 sendMessage();
236 }
237
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800238 void doSslHandshake()
AppaRao Pulie38778a2022-06-27 23:09:03 +0000239 {
240 if (!sslConn)
241 {
242 return;
243 }
244 state = ConnState::handshakeInProgress;
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800245 timer.expires_after(std::chrono::seconds(30));
246 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
AppaRao Pulie38778a2022-06-27 23:09:03 +0000247 sslConn->async_handshake(
248 boost::asio::ssl::stream_base::client,
249 std::bind_front(&ConnectionInfo::afterSslHandshake, this,
250 shared_from_this()));
251 }
252
253 void afterSslHandshake(const std::shared_ptr<ConnectionInfo>& /*self*/,
Ed Tanous81c4e332023-05-18 10:30:34 -0700254 const boost::beast::error_code& ec)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000255 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000256 // The operation already timed out. We don't want do continue down
257 // this branch
258 if (ec && ec == boost::asio::error::operation_aborted)
259 {
260 return;
261 }
262
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800263 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000264 if (ec)
265 {
Ed Tanous62598e32023-07-17 17:06:25 -0700266 BMCWEB_LOG_ERROR("SSL Handshake failed - id: {} error: {}",
267 std::to_string(connId), ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000268 state = ConnState::handshakeFailed;
269 waitAndRetry();
270 return;
271 }
Ed Tanous62598e32023-07-17 17:06:25 -0700272 BMCWEB_LOG_DEBUG("SSL Handshake successful - id: {}",
273 std::to_string(connId));
AppaRao Pulie38778a2022-06-27 23:09:03 +0000274 state = ConnState::connected;
275 sendMessage();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530276 }
277
Carson Labradof52c03c2022-03-23 18:50:15 +0000278 void sendMessage()
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530279 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530280 state = ConnState::sendInProgress;
281
AppaRao Pulibd030d02020-03-20 03:34:29 +0530282 // Set a timeout on the operation
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800283 timer.expires_after(std::chrono::seconds(30));
284 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
AppaRao Pulibd030d02020-03-20 03:34:29 +0530285
286 // Send the HTTP request to the remote host
AppaRao Pulie38778a2022-06-27 23:09:03 +0000287 if (sslConn)
288 {
289 boost::beast::http::async_write(
290 *sslConn, req,
291 std::bind_front(&ConnectionInfo::afterWrite, this,
292 shared_from_this()));
293 }
294 else
295 {
296 boost::beast::http::async_write(
297 conn, req,
298 std::bind_front(&ConnectionInfo::afterWrite, this,
299 shared_from_this()));
300 }
301 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530302
AppaRao Pulie38778a2022-06-27 23:09:03 +0000303 void afterWrite(const std::shared_ptr<ConnectionInfo>& /*self*/,
304 const boost::beast::error_code& ec, size_t bytesTransferred)
305 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000306 // The operation already timed out. We don't want do continue down
307 // this branch
308 if (ec && ec == boost::asio::error::operation_aborted)
309 {
310 return;
311 }
312
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800313 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000314 if (ec)
315 {
Ed Tanous62598e32023-07-17 17:06:25 -0700316 BMCWEB_LOG_ERROR("sendMessage() failed: {} {}:{}", ec.message(),
317 host, std::to_string(port));
AppaRao Pulie38778a2022-06-27 23:09:03 +0000318 state = ConnState::sendFailed;
319 waitAndRetry();
320 return;
321 }
Ed Tanous62598e32023-07-17 17:06:25 -0700322 BMCWEB_LOG_DEBUG("sendMessage() bytes transferred: {}",
323 bytesTransferred);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000324
325 recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530326 }
327
328 void recvMessage()
329 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530330 state = ConnState::recvInProgress;
331
Ed Tanouse01d0c32023-06-30 13:21:32 -0700332 parser_type& thisParser = parser.emplace(std::piecewise_construct,
333 std::make_tuple());
Carson Labradod14a48f2023-02-22 00:24:54 +0000334
Ed Tanouse01d0c32023-06-30 13:21:32 -0700335 thisParser.body_limit(connPolicy->requestByteLimit);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530336
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800337 timer.expires_after(std::chrono::seconds(30));
338 timer.async_wait(std::bind_front(onTimeout, weak_from_this()));
339
AppaRao Pulibd030d02020-03-20 03:34:29 +0530340 // Receive the HTTP response
AppaRao Pulie38778a2022-06-27 23:09:03 +0000341 if (sslConn)
342 {
343 boost::beast::http::async_read(
Ed Tanouse01d0c32023-06-30 13:21:32 -0700344 *sslConn, buffer, thisParser,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000345 std::bind_front(&ConnectionInfo::afterRead, this,
346 shared_from_this()));
347 }
348 else
349 {
350 boost::beast::http::async_read(
Ed Tanouse01d0c32023-06-30 13:21:32 -0700351 conn, buffer, thisParser,
AppaRao Pulie38778a2022-06-27 23:09:03 +0000352 std::bind_front(&ConnectionInfo::afterRead, this,
353 shared_from_this()));
354 }
355 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530356
AppaRao Pulie38778a2022-06-27 23:09:03 +0000357 void afterRead(const std::shared_ptr<ConnectionInfo>& /*self*/,
358 const boost::beast::error_code& ec,
359 const std::size_t& bytesTransferred)
360 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000361 // The operation already timed out. We don't want do continue down
362 // this branch
363 if (ec && ec == boost::asio::error::operation_aborted)
364 {
365 return;
366 }
367
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800368 timer.cancel();
AppaRao Pulie38778a2022-06-27 23:09:03 +0000369 if (ec && ec != boost::asio::ssl::error::stream_truncated)
370 {
Ed Tanous62598e32023-07-17 17:06:25 -0700371 BMCWEB_LOG_ERROR("recvMessage() failed: {} from {}:{}",
372 ec.message(), host, std::to_string(port));
AppaRao Pulie38778a2022-06-27 23:09:03 +0000373 state = ConnState::recvFailed;
374 waitAndRetry();
375 return;
376 }
Ed Tanous62598e32023-07-17 17:06:25 -0700377 BMCWEB_LOG_DEBUG("recvMessage() bytes transferred: {}",
378 bytesTransferred);
Ed Tanouse01d0c32023-06-30 13:21:32 -0700379 if (!parser)
380 {
381 return;
382 }
Ed Tanous62598e32023-07-17 17:06:25 -0700383 BMCWEB_LOG_DEBUG("recvMessage() data: {}", parser->get().body());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000384
385 unsigned int respCode = parser->get().result_int();
Ed Tanous62598e32023-07-17 17:06:25 -0700386 BMCWEB_LOG_DEBUG("recvMessage() Header Response Code: {}", respCode);
AppaRao Pulie38778a2022-06-27 23:09:03 +0000387
388 // Make sure the received response code is valid as defined by
389 // the associated retry policy
Carson Labradod14a48f2023-02-22 00:24:54 +0000390 if (connPolicy->invalidResp(respCode))
AppaRao Pulie38778a2022-06-27 23:09:03 +0000391 {
392 // The listener failed to receive the Sent-Event
Ed Tanous62598e32023-07-17 17:06:25 -0700393 BMCWEB_LOG_ERROR(
394 "recvMessage() Listener Failed to "
395 "receive Sent-Event. Header Response Code: {} from {}:{}",
396 respCode, host, std::to_string(port));
AppaRao Pulie38778a2022-06-27 23:09:03 +0000397 state = ConnState::recvFailed;
398 waitAndRetry();
399 return;
400 }
Ed Tanous002d39b2022-05-31 08:59:27 -0700401
AppaRao Pulie38778a2022-06-27 23:09:03 +0000402 // Send is successful
403 // Reset the counter just in case this was after retrying
404 retryCount = 0;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530405
AppaRao Pulie38778a2022-06-27 23:09:03 +0000406 // Keep the connection alive if server supports it
407 // Else close the connection
Ed Tanous62598e32023-07-17 17:06:25 -0700408 BMCWEB_LOG_DEBUG("recvMessage() keepalive : {}", parser->keep_alive());
AppaRao Pulibd030d02020-03-20 03:34:29 +0530409
AppaRao Pulie38778a2022-06-27 23:09:03 +0000410 // Copy the response into a Response object so that it can be
411 // processed by the callback function.
AppaRao Pulie38778a2022-06-27 23:09:03 +0000412 res.stringResponse = parser->release();
413 callback(parser->keep_alive(), connId, res);
Carson Labrado513d1ff2022-07-19 00:38:15 +0000414 res.clear();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530415 }
416
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800417 static void onTimeout(const std::weak_ptr<ConnectionInfo>& weakSelf,
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800418 const boost::system::error_code& ec)
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800419 {
420 if (ec == boost::asio::error::operation_aborted)
421 {
Ed Tanous62598e32023-07-17 17:06:25 -0700422 BMCWEB_LOG_DEBUG(
423 "async_wait failed since the operation is aborted");
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800424 return;
425 }
426 if (ec)
427 {
Ed Tanous62598e32023-07-17 17:06:25 -0700428 BMCWEB_LOG_ERROR("async_wait failed: {}", ec.message());
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800429 // If the timer fails, we need to close the socket anyway, same as
430 // if it expired.
431 }
432 std::shared_ptr<ConnectionInfo> self = weakSelf.lock();
433 if (self == nullptr)
434 {
435 return;
436 }
437 self->waitAndRetry();
438 }
439
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530440 void waitAndRetry()
AppaRao Pulibd030d02020-03-20 03:34:29 +0530441 {
Carson Labradod14a48f2023-02-22 00:24:54 +0000442 if ((retryCount >= connPolicy->maxRetryAttempts) ||
AppaRao Pulie38778a2022-06-27 23:09:03 +0000443 (state == ConnState::sslInitFailed))
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530444 {
Ed Tanous62598e32023-07-17 17:06:25 -0700445 BMCWEB_LOG_ERROR("Maximum number of retries reached. {}:{}", host,
446 std::to_string(port));
447 BMCWEB_LOG_DEBUG("Retry policy: {}", connPolicy->retryPolicyAction);
Carson Labrado039a47e2022-04-05 16:03:20 +0000448
Carson Labradod14a48f2023-02-22 00:24:54 +0000449 if (connPolicy->retryPolicyAction == "TerminateAfterRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530450 {
451 // TODO: delete subscription
452 state = ConnState::terminated;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530453 }
Carson Labradod14a48f2023-02-22 00:24:54 +0000454 if (connPolicy->retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530455 {
456 state = ConnState::suspended;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530457 }
Carson Labrado513d1ff2022-07-19 00:38:15 +0000458
459 // We want to return a 502 to indicate there was an error with
460 // the external server
461 res.result(boost::beast::http::status::bad_gateway);
462 callback(false, connId, res);
463 res.clear();
464
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530465 // Reset the retrycount to zero so that client can try connecting
466 // again if needed
Ed Tanous3174e4d2020-10-07 11:41:22 -0700467 retryCount = 0;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530468 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530469 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530470
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530471 retryCount++;
472
Ed Tanous62598e32023-07-17 17:06:25 -0700473 BMCWEB_LOG_DEBUG("Attempt retry after {} seconds. RetryCount = {}",
474 std::to_string(connPolicy->retryIntervalSecs.count()),
475 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.
498 doClose(true);
Ayushi Smritife44eb02020-05-15 15:24:45 +0530499 }
500
AppaRao Pulie38778a2022-06-27 23:09:03 +0000501 void shutdownConn(bool retry)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530502 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000503 boost::beast::error_code ec;
Ed Tanous0d5f5cf2022-03-12 15:30:55 -0800504 conn.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
Carson Labradof52c03c2022-03-23 18:50:15 +0000505 conn.close();
506
507 // not_connected happens sometimes so don't bother reporting it.
508 if (ec && ec != boost::beast::errc::not_connected)
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530509 {
Ed Tanous62598e32023-07-17 17:06:25 -0700510 BMCWEB_LOG_ERROR("{}:{}, id: {} shutdown failed: {}", host,
511 std::to_string(port), std::to_string(connId),
512 ec.message());
Carson Labradof52c03c2022-03-23 18:50:15 +0000513 }
Carson Labrado5cab68f2022-07-11 22:26:21 +0000514 else
515 {
Ed Tanous62598e32023-07-17 17:06:25 -0700516 BMCWEB_LOG_DEBUG("{}:{}, id: {} closed gracefully", host,
517 std::to_string(port), std::to_string(connId));
Carson Labrado5cab68f2022-07-11 22:26:21 +0000518 }
Ed Tanousca723762022-06-28 19:40:39 -0700519
Carson Labrado513d1ff2022-07-19 00:38:15 +0000520 if (retry)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000521 {
Carson Labrado513d1ff2022-07-19 00:38:15 +0000522 // Now let's try to resend the data
523 state = ConnState::retry;
524 doResolve();
525 }
526 else
527 {
528 state = ConnState::closed;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000529 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000530 }
531
AppaRao Pulie38778a2022-06-27 23:09:03 +0000532 void doClose(bool retry = false)
Carson Labradof52c03c2022-03-23 18:50:15 +0000533 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000534 if (!sslConn)
535 {
536 shutdownConn(retry);
537 return;
538 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000539
AppaRao Pulie38778a2022-06-27 23:09:03 +0000540 sslConn->async_shutdown(
541 std::bind_front(&ConnectionInfo::afterSslShutdown, this,
542 shared_from_this(), retry));
543 }
544
545 void afterSslShutdown(const std::shared_ptr<ConnectionInfo>& /*self*/,
546 bool retry, const boost::system::error_code& ec)
547 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000548 if (ec)
Carson Labradof52c03c2022-03-23 18:50:15 +0000549 {
Ed Tanous62598e32023-07-17 17:06:25 -0700550 BMCWEB_LOG_ERROR("{}:{}, id: {} shutdown failed: {}", host,
551 std::to_string(port), std::to_string(connId),
552 ec.message());
Carson Labradof52c03c2022-03-23 18:50:15 +0000553 }
Carson Labrado5cab68f2022-07-11 22:26:21 +0000554 else
555 {
Ed Tanous62598e32023-07-17 17:06:25 -0700556 BMCWEB_LOG_DEBUG("{}:{}, id: {} closed gracefully", host,
557 std::to_string(port), std::to_string(connId));
Carson Labrado5cab68f2022-07-11 22:26:21 +0000558 }
AppaRao Pulie38778a2022-06-27 23:09:03 +0000559 shutdownConn(retry);
560 }
Ed Tanousca723762022-06-28 19:40:39 -0700561
AppaRao Pulie38778a2022-06-27 23:09:03 +0000562 void setCipherSuiteTLSext()
563 {
564 if (!sslConn)
565 {
566 return;
567 }
568 // NOTE: The SSL_set_tlsext_host_name is defined in tlsv1.h header
569 // file but its having old style casting (name is cast to void*).
570 // Since bmcweb compiler treats all old-style-cast as error, its
571 // causing the build failure. So replaced the same macro inline and
572 // did corrected the code by doing static_cast to viod*. This has to
573 // be fixed in openssl library in long run. Set SNI Hostname (many
574 // hosts need this to handshake successfully)
575 if (SSL_ctrl(sslConn->native_handle(), SSL_CTRL_SET_TLSEXT_HOSTNAME,
576 TLSEXT_NAMETYPE_host_name,
577 static_cast<void*>(&host.front())) == 0)
578
579 {
580 boost::beast::error_code ec{static_cast<int>(::ERR_get_error()),
581 boost::asio::error::get_ssl_category()};
582
Ed Tanous62598e32023-07-17 17:06:25 -0700583 BMCWEB_LOG_ERROR(
584 "SSL_set_tlsext_host_name {}:{}, id: {} failed: {}", host, port,
585 std::to_string(connId), ec.message());
AppaRao Pulie38778a2022-06-27 23:09:03 +0000586 // Set state as sslInit failed so that we close the connection
587 // and take appropriate action as per retry configuration.
588 state = ConnState::sslInitFailed;
589 waitAndRetry();
590 return;
591 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530592 }
593
594 public:
Carson Labradod14a48f2023-02-22 00:24:54 +0000595 explicit ConnectionInfo(
596 boost::asio::io_context& iocIn, const std::string& idIn,
597 const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
598 const std::string& destIPIn, uint16_t destPortIn, bool useSSL,
599 unsigned int connIdIn) :
Ed Tanous8a592812022-06-04 09:06:59 -0700600 subId(idIn),
Carson Labradod14a48f2023-02-22 00:24:54 +0000601 connPolicy(connPolicyIn), host(destIPIn), port(destPortIn),
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700602 connId(connIdIn), resolver(iocIn), conn(iocIn), timer(iocIn)
AppaRao Pulie38778a2022-06-27 23:09:03 +0000603 {
604 if (useSSL)
605 {
606 std::optional<boost::asio::ssl::context> sslCtx =
607 ensuressl::getSSLClientContext();
608
609 if (!sslCtx)
610 {
Ed Tanous62598e32023-07-17 17:06:25 -0700611 BMCWEB_LOG_ERROR("prepareSSLContext failed - {}:{}, id: {}",
612 host, port, std::to_string(connId));
AppaRao Pulie38778a2022-06-27 23:09:03 +0000613 // Don't retry if failure occurs while preparing SSL context
614 // such as certificate is invalid or set cipher failure or set
615 // host name failure etc... Setting conn state to sslInitFailed
616 // and connection state will be transitioned to next state
617 // depending on retry policy set by subscription.
618 state = ConnState::sslInitFailed;
619 waitAndRetry();
620 return;
621 }
622 sslConn.emplace(conn, *sslCtx);
623 setCipherSuiteTLSext();
624 }
625 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000626};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530627
Carson Labradof52c03c2022-03-23 18:50:15 +0000628class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
629{
630 private:
631 boost::asio::io_context& ioc;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000632 std::string id;
Carson Labradod14a48f2023-02-22 00:24:54 +0000633 std::shared_ptr<ConnectionPolicy> connPolicy;
AppaRao Pulie38778a2022-06-27 23:09:03 +0000634 std::string destIP;
635 uint16_t destPort;
636 bool useSSL;
Carson Labradof52c03c2022-03-23 18:50:15 +0000637 std::vector<std::shared_ptr<ConnectionInfo>> connections;
638 boost::container::devector<PendingRequest> requestQueue;
639
640 friend class HttpClient;
641
Carson Labrado244256c2022-04-27 17:16:32 +0000642 // Configure a connections's request, callback, and retry info in
643 // preparation to begin sending the request
Carson Labradof52c03c2022-03-23 18:50:15 +0000644 void setConnProps(ConnectionInfo& conn)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530645 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000646 if (requestQueue.empty())
AppaRao Pulibd030d02020-03-20 03:34:29 +0530647 {
Ed Tanous62598e32023-07-17 17:06:25 -0700648 BMCWEB_LOG_ERROR(
649 "setConnProps() should not have been called when requestQueue is empty");
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530650 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530651 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530652
Carson Labrado244256c2022-04-27 17:16:32 +0000653 auto nextReq = requestQueue.front();
Carson Labrado244256c2022-04-27 17:16:32 +0000654 conn.req = std::move(nextReq.req);
655 conn.callback = std::move(nextReq.callback);
Carson Labradof52c03c2022-03-23 18:50:15 +0000656
Ed Tanous62598e32023-07-17 17:06:25 -0700657 BMCWEB_LOG_DEBUG("Setting properties for connection {}:{}, id: {}",
658 conn.host, std::to_string(conn.port),
659 std::to_string(conn.connId));
Carson Labradof52c03c2022-03-23 18:50:15 +0000660
661 // We can remove the request from the queue at this point
662 requestQueue.pop_front();
663 }
664
Carson Labradof52c03c2022-03-23 18:50:15 +0000665 // Gets called as part of callback after request is sent
666 // Reuses the connection if there are any requests waiting to be sent
667 // Otherwise closes the connection if it is not a keep-alive
668 void sendNext(bool keepAlive, uint32_t connId)
669 {
670 auto conn = connections[connId];
Carson Labrado46a81462022-04-27 21:11:37 +0000671
672 // Allow the connection's handler to be deleted
673 // This is needed because of Redfish Aggregation passing an
674 // AsyncResponse shared_ptr to this callback
675 conn->callback = nullptr;
676
Carson Labradof52c03c2022-03-23 18:50:15 +0000677 // Reuse the connection to send the next request in the queue
678 if (!requestQueue.empty())
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530679 {
Ed Tanous62598e32023-07-17 17:06:25 -0700680 BMCWEB_LOG_DEBUG(
681 "{} requests remaining in queue for {}:{}, reusing connnection {}",
682 std::to_string(requestQueue.size()), destIP,
683 std::to_string(destPort), std::to_string(connId));
Carson Labradof52c03c2022-03-23 18:50:15 +0000684
685 setConnProps(*conn);
686
687 if (keepAlive)
688 {
689 conn->sendMessage();
690 }
691 else
692 {
693 // Server is not keep-alive enabled so we need to close the
694 // connection and then start over from resolve
695 conn->doClose();
696 conn->doResolve();
697 }
698 return;
699 }
700
701 // No more messages to send so close the connection if necessary
702 if (keepAlive)
703 {
704 conn->state = ConnState::idle;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530705 }
706 else
707 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000708 // Abort the connection since server is not keep-alive enabled
709 conn->state = ConnState::abortConnection;
710 conn->doClose();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530711 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530712 }
713
AppaRao Puli5e44e3d2021-03-16 15:37:24 +0000714 void sendData(std::string&& data, const std::string& destUri,
Carson Labrado244256c2022-04-27 17:16:32 +0000715 const boost::beast::http::fields& httpHeader,
716 const boost::beast::http::verb verb,
Ed Tanous6b3db602022-06-28 19:41:44 -0700717 const std::function<void(Response&)>& resHandler)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530718 {
Carson Labrado244256c2022-04-27 17:16:32 +0000719 // Construct the request to be sent
720 boost::beast::http::request<boost::beast::http::string_body> thisReq(
721 verb, destUri, 11, "", httpHeader);
722 thisReq.set(boost::beast::http::field::host, destIP);
723 thisReq.keep_alive(true);
724 thisReq.body() = std::move(data);
725 thisReq.prepare_payload();
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700726 auto cb = std::bind_front(&ConnectionPool::afterSendData,
727 weak_from_this(), resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000728 // Reuse an existing connection if one is available
729 for (unsigned int i = 0; i < connections.size(); i++)
730 {
731 auto conn = connections[i];
732 if ((conn->state == ConnState::idle) ||
733 (conn->state == ConnState::initialized) ||
734 (conn->state == ConnState::closed))
735 {
Carson Labrado244256c2022-04-27 17:16:32 +0000736 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000737 conn->callback = std::move(cb);
Carson Labradof52c03c2022-03-23 18:50:15 +0000738 std::string commonMsg = std::to_string(i) + " from pool " +
739 destIP + ":" + std::to_string(destPort);
740
741 if (conn->state == ConnState::idle)
742 {
Ed Tanous62598e32023-07-17 17:06:25 -0700743 BMCWEB_LOG_DEBUG("Grabbing idle connection {}", commonMsg);
Carson Labradof52c03c2022-03-23 18:50:15 +0000744 conn->sendMessage();
745 }
746 else
747 {
Ed Tanous62598e32023-07-17 17:06:25 -0700748 BMCWEB_LOG_DEBUG("Reusing existing connection {}",
749 commonMsg);
Carson Labradof52c03c2022-03-23 18:50:15 +0000750 conn->doResolve();
751 }
752 return;
753 }
754 }
755
756 // All connections in use so create a new connection or add request to
757 // the queue
Carson Labradod14a48f2023-02-22 00:24:54 +0000758 if (connections.size() < connPolicy->maxConnections)
Carson Labradof52c03c2022-03-23 18:50:15 +0000759 {
Ed Tanous62598e32023-07-17 17:06:25 -0700760 BMCWEB_LOG_DEBUG("Adding new connection to pool {}:{}", destIP,
761 std::to_string(destPort));
Carson Labradof52c03c2022-03-23 18:50:15 +0000762 auto conn = addConnection();
Carson Labrado244256c2022-04-27 17:16:32 +0000763 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000764 conn->callback = std::move(cb);
Carson Labradof52c03c2022-03-23 18:50:15 +0000765 conn->doResolve();
766 }
767 else if (requestQueue.size() < maxRequestQueueSize)
768 {
Carson Labradobf2dded2023-08-10 00:37:06 +0000769 BMCWEB_LOG_DEBUG(
Ed Tanous62598e32023-07-17 17:06:25 -0700770 "Max pool size reached. Adding data to queue.{}:{}", destIP,
771 std::to_string(destPort));
Carson Labradod14a48f2023-02-22 00:24:54 +0000772 requestQueue.emplace_back(std::move(thisReq), std::move(cb));
Carson Labradof52c03c2022-03-23 18:50:15 +0000773 }
774 else
775 {
Carson Labrado43e14d32022-11-09 00:25:20 +0000776 // If we can't buffer the request then we should let the callback
777 // handle a 429 Too Many Requests dummy response
Ed Tanous62598e32023-07-17 17:06:25 -0700778 BMCWEB_LOG_ERROR("{}:{} request queue full. Dropping request.",
779 destIP, std::to_string(destPort));
Carson Labrado43e14d32022-11-09 00:25:20 +0000780 Response dummyRes;
781 dummyRes.result(boost::beast::http::status::too_many_requests);
782 resHandler(dummyRes);
Carson Labradof52c03c2022-03-23 18:50:15 +0000783 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530784 }
785
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700786 // Callback to be called once the request has been sent
787 static void afterSendData(const std::weak_ptr<ConnectionPool>& weakSelf,
788 const std::function<void(Response&)>& resHandler,
789 bool keepAlive, uint32_t connId, Response& res)
790 {
791 // Allow provided callback to perform additional processing of the
792 // request
793 resHandler(res);
794
795 // If requests remain in the queue then we want to reuse this
796 // connection to send the next request
797 std::shared_ptr<ConnectionPool> self = weakSelf.lock();
798 if (!self)
799 {
Ed Tanous62598e32023-07-17 17:06:25 -0700800 BMCWEB_LOG_CRITICAL("{} Failed to capture connection",
801 logPtr(self.get()));
Ed Tanous3d36e3a2022-08-19 15:54:04 -0700802 return;
803 }
804
805 self->sendNext(keepAlive, connId);
806 }
807
Carson Labradof52c03c2022-03-23 18:50:15 +0000808 std::shared_ptr<ConnectionInfo>& addConnection()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530809 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000810 unsigned int newId = static_cast<unsigned int>(connections.size());
811
AppaRao Pulie38778a2022-06-27 23:09:03 +0000812 auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>(
Carson Labradod14a48f2023-02-22 00:24:54 +0000813 ioc, id, connPolicy, destIP, destPort, useSSL, newId));
Carson Labradof52c03c2022-03-23 18:50:15 +0000814
Ed Tanous62598e32023-07-17 17:06:25 -0700815 BMCWEB_LOG_DEBUG("Added connection {} to pool {}:{}",
816 std::to_string(connections.size() - 1), destIP,
817 std::to_string(destPort));
Carson Labradof52c03c2022-03-23 18:50:15 +0000818
819 return ret;
820 }
821
822 public:
Carson Labradod14a48f2023-02-22 00:24:54 +0000823 explicit ConnectionPool(
824 boost::asio::io_context& iocIn, const std::string& idIn,
825 const std::shared_ptr<ConnectionPolicy>& connPolicyIn,
826 const std::string& destIPIn, uint16_t destPortIn, bool useSSLIn) :
Ed Tanous8a592812022-06-04 09:06:59 -0700827 ioc(iocIn),
Carson Labradod14a48f2023-02-22 00:24:54 +0000828 id(idIn), connPolicy(connPolicyIn), destIP(destIPIn),
829 destPort(destPortIn), useSSL(useSSLIn)
Carson Labradof52c03c2022-03-23 18:50:15 +0000830 {
Ed Tanous62598e32023-07-17 17:06:25 -0700831 BMCWEB_LOG_DEBUG("Initializing connection pool for {}:{}", destIP,
832 std::to_string(destPort));
Carson Labradof52c03c2022-03-23 18:50:15 +0000833
834 // Initialize the pool with a single connection
835 addConnection();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530836 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530837};
838
Carson Labradof52c03c2022-03-23 18:50:15 +0000839class HttpClient
840{
841 private:
842 std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
843 connectionPools;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700844 boost::asio::io_context& ioc;
Carson Labradod14a48f2023-02-22 00:24:54 +0000845 std::shared_ptr<ConnectionPolicy> connPolicy;
Carson Labradof52c03c2022-03-23 18:50:15 +0000846
Carson Labrado039a47e2022-04-05 16:03:20 +0000847 // Used as a dummy callback by sendData() in order to call
848 // sendDataWithCallback()
Ed Tanous02cad962022-06-30 16:50:15 -0700849 static void genericResHandler(const Response& res)
Carson Labrado039a47e2022-04-05 16:03:20 +0000850 {
Ed Tanous62598e32023-07-17 17:06:25 -0700851 BMCWEB_LOG_DEBUG("Response handled with return code: {}",
852 std::to_string(res.resultInt()));
Ed Tanous4ee8e212022-05-28 09:42:51 -0700853 }
Carson Labrado039a47e2022-04-05 16:03:20 +0000854
Carson Labradof52c03c2022-03-23 18:50:15 +0000855 public:
Carson Labradod14a48f2023-02-22 00:24:54 +0000856 HttpClient() = delete;
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700857 explicit HttpClient(boost::asio::io_context& iocIn,
858 const std::shared_ptr<ConnectionPolicy>& connPolicyIn) :
859 ioc(iocIn),
Carson Labradod14a48f2023-02-22 00:24:54 +0000860 connPolicy(connPolicyIn)
861 {}
Ed Tanousf8ca6d72022-06-28 12:12:03 -0700862
Carson Labradof52c03c2022-03-23 18:50:15 +0000863 HttpClient(const HttpClient&) = delete;
864 HttpClient& operator=(const HttpClient&) = delete;
865 HttpClient(HttpClient&&) = delete;
866 HttpClient& operator=(HttpClient&&) = delete;
867 ~HttpClient() = default;
868
Carson Labrado039a47e2022-04-05 16:03:20 +0000869 // Send a request to destIP:destPort where additional processing of the
870 // result is not required
AppaRao Puli5e44e3d2021-03-16 15:37:24 +0000871 void sendData(std::string&& data, const std::string& destIP,
Carson Labradod14a48f2023-02-22 00:24:54 +0000872 uint16_t destPort, const std::string& destUri, bool useSSL,
Carson Labradof52c03c2022-03-23 18:50:15 +0000873 const boost::beast::http::fields& httpHeader,
Carson Labradod14a48f2023-02-22 00:24:54 +0000874 const boost::beast::http::verb verb)
Carson Labradof52c03c2022-03-23 18:50:15 +0000875 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000876 const std::function<void(Response&)> cb = genericResHandler;
AppaRao Puli5e44e3d2021-03-16 15:37:24 +0000877 sendDataWithCallback(std::move(data), destIP, destPort, destUri, useSSL,
Carson Labradod14a48f2023-02-22 00:24:54 +0000878 httpHeader, verb, cb);
Carson Labrado039a47e2022-04-05 16:03:20 +0000879 }
880
881 // Send request to destIP:destPort and use the provided callback to
882 // handle the response
AppaRao Puli5e44e3d2021-03-16 15:37:24 +0000883 void sendDataWithCallback(std::string&& data, const std::string& destIP,
Carson Labradod14a48f2023-02-22 00:24:54 +0000884 uint16_t destPort, const std::string& destUri,
885 bool useSSL,
Carson Labrado039a47e2022-04-05 16:03:20 +0000886 const boost::beast::http::fields& httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000887 const boost::beast::http::verb verb,
Ed Tanous6b3db602022-06-28 19:41:44 -0700888 const std::function<void(Response&)>& resHandler)
Carson Labrado039a47e2022-04-05 16:03:20 +0000889 {
AppaRao Pulie38778a2022-06-27 23:09:03 +0000890 std::string clientKey = useSSL ? "https" : "http";
891 clientKey += destIP;
892 clientKey += ":";
893 clientKey += std::to_string(destPort);
Carson Labradod14a48f2023-02-22 00:24:54 +0000894 auto pool = connectionPools.try_emplace(clientKey);
895 if (pool.first->second == nullptr)
Carson Labradof52c03c2022-03-23 18:50:15 +0000896 {
Carson Labradod14a48f2023-02-22 00:24:54 +0000897 pool.first->second = std::make_shared<ConnectionPool>(
898 ioc, clientKey, connPolicy, destIP, destPort, useSSL);
Carson Labradof52c03c2022-03-23 18:50:15 +0000899 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000900 // Send the data using either the existing connection pool or the newly
901 // created connection pool
AppaRao Puli5e44e3d2021-03-16 15:37:24 +0000902 pool.first->second->sendData(std::move(data), destUri, httpHeader, verb,
Carson Labradod14a48f2023-02-22 00:24:54 +0000903 resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000904 }
905};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530906} // namespace crow