blob: c9526bbaf4c9fa571caffce5b04aca980d1890de [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
Ed Tanousbb49eb52022-06-28 12:02:42 -070017#include <boost/asio/io_context.hpp>
Sunitha Harish29a82b02021-02-18 15:54:16 +053018#include <boost/asio/ip/address.hpp>
19#include <boost/asio/ip/basic_endpoint.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070020#include <boost/asio/ip/tcp.hpp>
Ed Tanousd43cd0c2020-09-30 20:46:53 -070021#include <boost/asio/steady_timer.hpp>
22#include <boost/beast/core/flat_buffer.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070023#include <boost/beast/core/flat_static_buffer.hpp>
Ed Tanousd43cd0c2020-09-30 20:46:53 -070024#include <boost/beast/core/tcp_stream.hpp>
25#include <boost/beast/http/message.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070026#include <boost/beast/http/parser.hpp>
27#include <boost/beast/http/read.hpp>
28#include <boost/beast/http/string_body.hpp>
29#include <boost/beast/http/write.hpp>
AppaRao Pulibd030d02020-03-20 03:34:29 +053030#include <boost/beast/version.hpp>
Carson Labradof52c03c2022-03-23 18:50:15 +000031#include <boost/container/devector.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070032#include <boost/system/error_code.hpp>
33#include <http/http_response.hpp>
Sunitha Harish29a82b02021-02-18 15:54:16 +053034#include <include/async_resolve.hpp>
Ed Tanousbb49eb52022-06-28 12:02:42 -070035#include <logging.hpp>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050036
AppaRao Pulibd030d02020-03-20 03:34:29 +053037#include <cstdlib>
38#include <functional>
39#include <iostream>
40#include <memory>
AppaRao Puli2a5689a2020-04-29 15:24:31 +053041#include <queue>
AppaRao Pulibd030d02020-03-20 03:34:29 +053042#include <string>
43
44namespace crow
45{
46
Carson Labradof52c03c2022-03-23 18:50:15 +000047// It is assumed that the BMC should be able to handle 4 parallel connections
48constexpr uint8_t maxPoolSize = 4;
49constexpr uint8_t maxRequestQueueSize = 50;
Carson Labrado4d942722022-06-22 22:16:10 +000050constexpr unsigned int httpReadBodyLimit = 16384;
51constexpr unsigned int httpReadBufferSize = 4096;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053052
AppaRao Pulibd030d02020-03-20 03:34:29 +053053enum class ConnState
54{
AppaRao Puli2a5689a2020-04-29 15:24:31 +053055 initialized,
Sunitha Harish29a82b02021-02-18 15:54:16 +053056 resolveInProgress,
57 resolveFailed,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053058 connectInProgress,
59 connectFailed,
AppaRao Pulibd030d02020-03-20 03:34:29 +053060 connected,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053061 sendInProgress,
62 sendFailed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053063 recvInProgress,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053064 recvFailed,
65 idle,
Ayushi Smritife44eb02020-05-15 15:24:45 +053066 closed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053067 suspended,
68 terminated,
69 abortConnection,
70 retry
AppaRao Pulibd030d02020-03-20 03:34:29 +053071};
72
Carson Labradoa7a80292022-06-01 16:01:52 +000073static inline boost::system::error_code
74 defaultRetryHandler(unsigned int respCode)
75{
76 // As a default, assume 200X is alright
77 BMCWEB_LOG_DEBUG << "Using default check for response code validity";
78 if ((respCode < 200) || (respCode >= 300))
79 {
80 return boost::system::errc::make_error_code(
81 boost::system::errc::result_out_of_range);
82 }
83
84 // Return 0 if the response code is valid
85 return boost::system::errc::make_error_code(boost::system::errc::success);
86};
87
Carson Labradof52c03c2022-03-23 18:50:15 +000088// We need to allow retry information to be set before a message has been sent
89// and a connection pool has been created
90struct RetryPolicyData
91{
92 uint32_t maxRetryAttempts = 5;
93 std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0);
94 std::string retryPolicyAction = "TerminateAfterRetries";
Carson Labradoa7a80292022-06-01 16:01:52 +000095 std::function<boost::system::error_code(unsigned int respCode)>
96 invalidResp = defaultRetryHandler;
Carson Labradof52c03c2022-03-23 18:50:15 +000097};
98
99struct PendingRequest
100{
Carson Labrado244256c2022-04-27 17:16:32 +0000101 boost::beast::http::request<boost::beast::http::string_body> req;
Carson Labrado039a47e2022-04-05 16:03:20 +0000102 std::function<void(bool, uint32_t, Response&)> callback;
Carson Labradof52c03c2022-03-23 18:50:15 +0000103 RetryPolicyData retryPolicy;
Carson Labrado039a47e2022-04-05 16:03:20 +0000104 PendingRequest(
Ed Tanous8a592812022-06-04 09:06:59 -0700105 boost::beast::http::request<boost::beast::http::string_body>&& reqIn,
106 const std::function<void(bool, uint32_t, Response&)>& callbackIn,
107 const RetryPolicyData& retryPolicyIn) :
108 req(std::move(reqIn)),
109 callback(callbackIn), retryPolicy(retryPolicyIn)
Carson Labradof52c03c2022-03-23 18:50:15 +0000110 {}
111};
112
113class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo>
AppaRao Pulibd030d02020-03-20 03:34:29 +0530114{
115 private:
Carson Labradof52c03c2022-03-23 18:50:15 +0000116 ConnState state = ConnState::initialized;
117 uint32_t retryCount = 0;
118 bool runningTimer = false;
119 std::string subId;
120 std::string host;
121 uint16_t port;
122 uint32_t connId;
123
124 // Retry policy information
125 // This should be updated before each message is sent
126 RetryPolicyData retryPolicy;
127
128 // Data buffers
AppaRao Pulibd030d02020-03-20 03:34:29 +0530129 boost::beast::http::request<boost::beast::http::string_body> req;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530130 std::optional<
131 boost::beast::http::response_parser<boost::beast::http::string_body>>
132 parser;
Carson Labrado4d942722022-06-22 22:16:10 +0000133 boost::beast::flat_static_buffer<httpReadBufferSize> buffer;
Carson Labrado039a47e2022-04-05 16:03:20 +0000134 Response res;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530135
Carson Labradof52c03c2022-03-23 18:50:15 +0000136 // Ascync callables
Carson Labrado039a47e2022-04-05 16:03:20 +0000137 std::function<void(bool, uint32_t, Response&)> callback;
Carson Labradof52c03c2022-03-23 18:50:15 +0000138 crow::async_resolve::Resolver resolver;
139 boost::beast::tcp_stream conn;
140 boost::asio::steady_timer timer;
Ed Tanous84b35602021-09-08 20:06:32 -0700141
Carson Labradof52c03c2022-03-23 18:50:15 +0000142 friend class ConnectionPool;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530143
Sunitha Harish29a82b02021-02-18 15:54:16 +0530144 void doResolve()
145 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530146 state = ConnState::resolveInProgress;
Carson Labradof52c03c2022-03-23 18:50:15 +0000147 BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":"
148 << std::to_string(port)
149 << ", id: " << std::to_string(connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530150
151 auto respHandler =
152 [self(shared_from_this())](
153 const boost::beast::error_code ec,
154 const std::vector<boost::asio::ip::tcp::endpoint>&
155 endpointList) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700156 if (ec || (endpointList.empty()))
157 {
158 BMCWEB_LOG_ERROR << "Resolve failed: " << ec.message();
159 self->state = ConnState::resolveFailed;
160 self->waitAndRetry();
161 return;
162 }
163 BMCWEB_LOG_DEBUG << "Resolved " << self->host << ":"
164 << std::to_string(self->port)
165 << ", id: " << std::to_string(self->connId);
166 self->doConnect(endpointList);
167 };
Carson Labradof52c03c2022-03-23 18:50:15 +0000168
Sunitha Harish29a82b02021-02-18 15:54:16 +0530169 resolver.asyncResolve(host, port, std::move(respHandler));
170 }
171
172 void doConnect(
173 const std::vector<boost::asio::ip::tcp::endpoint>& endpointList)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530174 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530175 state = ConnState::connectInProgress;
176
Carson Labradof52c03c2022-03-23 18:50:15 +0000177 BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":"
178 << std::to_string(port)
179 << ", id: " << std::to_string(connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530180
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530181 conn.expires_after(std::chrono::seconds(30));
Ed Tanous002d39b2022-05-31 08:59:27 -0700182 conn.async_connect(endpointList,
183 [self(shared_from_this())](
184 const boost::beast::error_code ec,
185 const boost::asio::ip::tcp::endpoint& endpoint) {
186 if (ec)
187 {
188 BMCWEB_LOG_ERROR << "Connect " << endpoint.address().to_string()
189 << ":" << std::to_string(endpoint.port())
190 << ", id: " << std::to_string(self->connId)
191 << " failed: " << ec.message();
192 self->state = ConnState::connectFailed;
193 self->waitAndRetry();
194 return;
195 }
196 BMCWEB_LOG_DEBUG
197 << "Connected to: " << endpoint.address().to_string() << ":"
198 << std::to_string(endpoint.port())
199 << ", id: " << std::to_string(self->connId);
200 self->state = ConnState::connected;
201 self->sendMessage();
202 });
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530203 }
204
Carson Labradof52c03c2022-03-23 18:50:15 +0000205 void sendMessage()
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530206 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530207 state = ConnState::sendInProgress;
208
AppaRao Pulibd030d02020-03-20 03:34:29 +0530209 // Set a timeout on the operation
210 conn.expires_after(std::chrono::seconds(30));
211
212 // Send the HTTP request to the remote host
213 boost::beast::http::async_write(
214 conn, req,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530215 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530216 const std::size_t& bytesTransferred) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700217 if (ec)
218 {
219 BMCWEB_LOG_ERROR << "sendMessage() failed: " << ec.message();
220 self->state = ConnState::sendFailed;
221 self->waitAndRetry();
222 return;
223 }
224 BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
225 << bytesTransferred;
226 boost::ignore_unused(bytesTransferred);
AppaRao Pulibd030d02020-03-20 03:34:29 +0530227
Ed Tanous002d39b2022-05-31 08:59:27 -0700228 self->recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530229 });
230 }
231
232 void recvMessage()
233 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530234 state = ConnState::recvInProgress;
235
236 parser.emplace(std::piecewise_construct, std::make_tuple());
237 parser->body_limit(httpReadBodyLimit);
238
AppaRao Pulibd030d02020-03-20 03:34:29 +0530239 // Receive the HTTP response
240 boost::beast::http::async_read(
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530241 conn, buffer, *parser,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530242 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530243 const std::size_t& bytesTransferred) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700244 if (ec)
245 {
246 BMCWEB_LOG_ERROR << "recvMessage() failed: " << ec.message();
247 self->state = ConnState::recvFailed;
248 self->waitAndRetry();
249 return;
250 }
251 BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
252 << bytesTransferred;
253 BMCWEB_LOG_DEBUG << "recvMessage() data: "
254 << self->parser->get().body();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530255
Ed Tanous002d39b2022-05-31 08:59:27 -0700256 unsigned int respCode = self->parser->get().result_int();
257 BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: "
258 << respCode;
259
Carson Labradoa7a80292022-06-01 16:01:52 +0000260 // Make sure the received response code is valid as defined by
261 // the associated retry policy
262 if (self->retryPolicy.invalidResp(respCode))
Ed Tanous002d39b2022-05-31 08:59:27 -0700263 {
264 // The listener failed to receive the Sent-Event
265 BMCWEB_LOG_ERROR << "recvMessage() Listener Failed to "
266 "receive Sent-Event. Header Response Code: "
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530267 << respCode;
Ed Tanous002d39b2022-05-31 08:59:27 -0700268 self->state = ConnState::recvFailed;
269 self->waitAndRetry();
270 return;
271 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530272
Ed Tanous002d39b2022-05-31 08:59:27 -0700273 // Send is successful
274 // Reset the counter just in case this was after retrying
275 self->retryCount = 0;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530276
Ed Tanous002d39b2022-05-31 08:59:27 -0700277 // Keep the connection alive if server supports it
278 // Else close the connection
279 BMCWEB_LOG_DEBUG << "recvMessage() keepalive : "
280 << self->parser->keep_alive();
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530281
Ed Tanous002d39b2022-05-31 08:59:27 -0700282 // Copy the response into a Response object so that it can be
283 // processed by the callback function.
284 self->res.clear();
285 self->res.stringResponse = self->parser->release();
286 self->callback(self->parser->keep_alive(), self->connId, self->res);
AppaRao Pulibd030d02020-03-20 03:34:29 +0530287 });
288 }
289
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530290 void waitAndRetry()
AppaRao Pulibd030d02020-03-20 03:34:29 +0530291 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000292 if (retryCount >= retryPolicy.maxRetryAttempts)
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530293 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530294 BMCWEB_LOG_ERROR << "Maximum number of retries reached.";
Carson Labradof52c03c2022-03-23 18:50:15 +0000295 BMCWEB_LOG_DEBUG << "Retry policy: "
296 << retryPolicy.retryPolicyAction;
Carson Labrado039a47e2022-04-05 16:03:20 +0000297
298 // We want to return a 502 to indicate there was an error with the
299 // external server
300 res.clear();
Ed Tanous40d799e2022-06-28 12:07:22 -0700301 res.result(boost::beast::http::status::bad_gateway);
Carson Labrado039a47e2022-04-05 16:03:20 +0000302
Carson Labradof52c03c2022-03-23 18:50:15 +0000303 if (retryPolicy.retryPolicyAction == "TerminateAfterRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530304 {
305 // TODO: delete subscription
306 state = ConnState::terminated;
Carson Labrado039a47e2022-04-05 16:03:20 +0000307 callback(false, connId, res);
Ayushi Smritife44eb02020-05-15 15:24:45 +0530308 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000309 if (retryPolicy.retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530310 {
311 state = ConnState::suspended;
Carson Labrado039a47e2022-04-05 16:03:20 +0000312 callback(false, connId, res);
Ayushi Smritife44eb02020-05-15 15:24:45 +0530313 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530314 // Reset the retrycount to zero so that client can try connecting
315 // again if needed
Ed Tanous3174e4d2020-10-07 11:41:22 -0700316 retryCount = 0;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530317 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530318 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530319
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530320 if (runningTimer)
321 {
322 BMCWEB_LOG_DEBUG << "Retry timer is already running.";
323 return;
324 }
325 runningTimer = true;
326
327 retryCount++;
328
Carson Labradof52c03c2022-03-23 18:50:15 +0000329 BMCWEB_LOG_DEBUG << "Attempt retry after "
330 << std::to_string(
331 retryPolicy.retryIntervalSecs.count())
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530332 << " seconds. RetryCount = " << retryCount;
Carson Labradof52c03c2022-03-23 18:50:15 +0000333 timer.expires_after(retryPolicy.retryIntervalSecs);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530334 timer.async_wait(
Carson Labradof52c03c2022-03-23 18:50:15 +0000335 [self(shared_from_this())](const boost::system::error_code ec) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700336 if (ec == boost::asio::error::operation_aborted)
337 {
338 BMCWEB_LOG_DEBUG
339 << "async_wait failed since the operation is aborted"
340 << ec.message();
341 }
342 else if (ec)
343 {
344 BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message();
345 // Ignore the error and continue the retry loop to attempt
346 // sending the event as per the retry policy
347 }
348 self->runningTimer = false;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530349
Ed Tanous002d39b2022-05-31 08:59:27 -0700350 // Let's close the connection and restart from resolve.
351 self->doCloseAndRetry();
352 });
Ayushi Smritife44eb02020-05-15 15:24:45 +0530353 }
354
Carson Labradof52c03c2022-03-23 18:50:15 +0000355 void doClose()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530356 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000357 boost::beast::error_code ec;
358 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
359 conn.close();
360
361 // not_connected happens sometimes so don't bother reporting it.
362 if (ec && ec != boost::beast::errc::not_connected)
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530363 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000364 BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
365 << ", id: " << std::to_string(connId)
366 << "shutdown failed: " << ec.message();
Carson Labradof52c03c2022-03-23 18:50:15 +0000367 }
Carson Labrado5cab68f2022-07-11 22:26:21 +0000368 else
369 {
370 BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
371 << ", id: " << std::to_string(connId)
372 << " closed gracefully";
373 }
Ed Tanousca723762022-06-28 19:40:39 -0700374
375 state = ConnState::closed;
Carson Labradof52c03c2022-03-23 18:50:15 +0000376 }
377
378 void doCloseAndRetry()
379 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000380 boost::beast::error_code ec;
381 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
382 conn.close();
383
384 // not_connected happens sometimes so don't bother reporting it.
385 if (ec && ec != boost::beast::errc::not_connected)
386 {
387 BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
388 << ", id: " << std::to_string(connId)
389 << "shutdown failed: " << ec.message();
Carson Labradof52c03c2022-03-23 18:50:15 +0000390 }
Carson Labrado5cab68f2022-07-11 22:26:21 +0000391 else
392 {
393 BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
394 << ", id: " << std::to_string(connId)
395 << " closed gracefully";
396 }
Ed Tanousca723762022-06-28 19:40:39 -0700397
398 // Now let's try to resend the data
399 state = ConnState::retry;
400 doResolve();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530401 }
402
403 public:
Ed Tanous8a592812022-06-04 09:06:59 -0700404 explicit ConnectionInfo(boost::asio::io_context& ioc,
405 const std::string& idIn, const std::string& destIP,
406 const uint16_t destPort,
407 const unsigned int connIdIn) :
408 subId(idIn),
409 host(destIP), port(destPort), connId(connIdIn), conn(ioc), timer(ioc)
Carson Labrado244256c2022-04-27 17:16:32 +0000410 {}
Carson Labradof52c03c2022-03-23 18:50:15 +0000411};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530412
Carson Labradof52c03c2022-03-23 18:50:15 +0000413class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
414{
415 private:
416 boost::asio::io_context& ioc;
417 const std::string id;
418 const std::string destIP;
419 const uint16_t destPort;
Carson Labradof52c03c2022-03-23 18:50:15 +0000420 std::vector<std::shared_ptr<ConnectionInfo>> connections;
421 boost::container::devector<PendingRequest> requestQueue;
422
423 friend class HttpClient;
424
Carson Labrado244256c2022-04-27 17:16:32 +0000425 // Configure a connections's request, callback, and retry info in
426 // preparation to begin sending the request
Carson Labradof52c03c2022-03-23 18:50:15 +0000427 void setConnProps(ConnectionInfo& conn)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530428 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000429 if (requestQueue.empty())
AppaRao Pulibd030d02020-03-20 03:34:29 +0530430 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000431 BMCWEB_LOG_ERROR
432 << "setConnProps() should not have been called when requestQueue is empty";
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530433 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530434 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530435
Carson Labrado244256c2022-04-27 17:16:32 +0000436 auto nextReq = requestQueue.front();
437 conn.retryPolicy = std::move(nextReq.retryPolicy);
438 conn.req = std::move(nextReq.req);
439 conn.callback = std::move(nextReq.callback);
Carson Labradof52c03c2022-03-23 18:50:15 +0000440
441 BMCWEB_LOG_DEBUG << "Setting properties for connection " << conn.host
442 << ":" << std::to_string(conn.port)
Carson Labradoa7a80292022-06-01 16:01:52 +0000443 << ", id: " << std::to_string(conn.connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000444
445 // We can remove the request from the queue at this point
446 requestQueue.pop_front();
447 }
448
449 // Configures a connection to use the specific retry policy.
450 inline void setConnRetryPolicy(ConnectionInfo& conn,
451 const RetryPolicyData& retryPolicy)
452 {
453 BMCWEB_LOG_DEBUG << destIP << ":" << std::to_string(destPort)
Carson Labradoa7a80292022-06-01 16:01:52 +0000454 << ", id: " << std::to_string(conn.connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000455
456 conn.retryPolicy = retryPolicy;
457 }
458
459 // Gets called as part of callback after request is sent
460 // Reuses the connection if there are any requests waiting to be sent
461 // Otherwise closes the connection if it is not a keep-alive
462 void sendNext(bool keepAlive, uint32_t connId)
463 {
464 auto conn = connections[connId];
465 // Reuse the connection to send the next request in the queue
466 if (!requestQueue.empty())
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530467 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000468 BMCWEB_LOG_DEBUG << std::to_string(requestQueue.size())
469 << " requests remaining in queue for " << destIP
470 << ":" << std::to_string(destPort)
471 << ", reusing connnection "
472 << std::to_string(connId);
473
474 setConnProps(*conn);
475
476 if (keepAlive)
477 {
478 conn->sendMessage();
479 }
480 else
481 {
482 // Server is not keep-alive enabled so we need to close the
483 // connection and then start over from resolve
484 conn->doClose();
485 conn->doResolve();
486 }
487 return;
488 }
489
490 // No more messages to send so close the connection if necessary
491 if (keepAlive)
492 {
493 conn->state = ConnState::idle;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530494 }
495 else
496 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000497 // Abort the connection since server is not keep-alive enabled
498 conn->state = ConnState::abortConnection;
499 conn->doClose();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530500 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530501 }
502
Carson Labrado244256c2022-04-27 17:16:32 +0000503 void sendData(std::string& data, const std::string& destUri,
504 const boost::beast::http::fields& httpHeader,
505 const boost::beast::http::verb verb,
506 const RetryPolicyData& retryPolicy,
Ed Tanous6b3db602022-06-28 19:41:44 -0700507 const std::function<void(Response&)>& resHandler)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530508 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000509 std::weak_ptr<ConnectionPool> weakSelf = weak_from_this();
510
511 // Callback to be called once the request has been sent
Carson Labrado039a47e2022-04-05 16:03:20 +0000512 auto cb = [weakSelf, resHandler](bool keepAlive, uint32_t connId,
513 Response& res) {
514 // Allow provided callback to perform additional processing of the
515 // request
516 resHandler(res);
517
Carson Labradof52c03c2022-03-23 18:50:15 +0000518 // If requests remain in the queue then we want to reuse this
519 // connection to send the next request
520 std::shared_ptr<ConnectionPool> self = weakSelf.lock();
521 if (!self)
522 {
523 BMCWEB_LOG_CRITICAL << self << " Failed to capture connection";
524 return;
525 }
526
527 self->sendNext(keepAlive, connId);
528 };
529
Carson Labrado244256c2022-04-27 17:16:32 +0000530 // Construct the request to be sent
531 boost::beast::http::request<boost::beast::http::string_body> thisReq(
532 verb, destUri, 11, "", httpHeader);
533 thisReq.set(boost::beast::http::field::host, destIP);
534 thisReq.keep_alive(true);
535 thisReq.body() = std::move(data);
536 thisReq.prepare_payload();
537
Carson Labradof52c03c2022-03-23 18:50:15 +0000538 // Reuse an existing connection if one is available
539 for (unsigned int i = 0; i < connections.size(); i++)
540 {
541 auto conn = connections[i];
542 if ((conn->state == ConnState::idle) ||
543 (conn->state == ConnState::initialized) ||
544 (conn->state == ConnState::closed))
545 {
Carson Labrado244256c2022-04-27 17:16:32 +0000546 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000547 conn->callback = std::move(cb);
Carson Labradof52c03c2022-03-23 18:50:15 +0000548 setConnRetryPolicy(*conn, retryPolicy);
549 std::string commonMsg = std::to_string(i) + " from pool " +
550 destIP + ":" + std::to_string(destPort);
551
552 if (conn->state == ConnState::idle)
553 {
554 BMCWEB_LOG_DEBUG << "Grabbing idle connection "
555 << commonMsg;
556 conn->sendMessage();
557 }
558 else
559 {
560 BMCWEB_LOG_DEBUG << "Reusing existing connection "
561 << commonMsg;
562 conn->doResolve();
563 }
564 return;
565 }
566 }
567
568 // All connections in use so create a new connection or add request to
569 // the queue
570 if (connections.size() < maxPoolSize)
571 {
572 BMCWEB_LOG_DEBUG << "Adding new connection to pool " << destIP
573 << ":" << std::to_string(destPort);
574 auto conn = addConnection();
Carson Labrado244256c2022-04-27 17:16:32 +0000575 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000576 conn->callback = std::move(cb);
577 setConnRetryPolicy(*conn, retryPolicy);
578 conn->doResolve();
579 }
580 else if (requestQueue.size() < maxRequestQueueSize)
581 {
582 BMCWEB_LOG_ERROR << "Max pool size reached. Adding data to queue.";
Carson Labrado244256c2022-04-27 17:16:32 +0000583 requestQueue.emplace_back(std::move(thisReq), std::move(cb),
Carson Labradof52c03c2022-03-23 18:50:15 +0000584 retryPolicy);
585 }
586 else
587 {
588 BMCWEB_LOG_ERROR << destIP << ":" << std::to_string(destPort)
589 << " request queue full. Dropping request.";
590 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530591 }
592
Carson Labradof52c03c2022-03-23 18:50:15 +0000593 std::shared_ptr<ConnectionInfo>& addConnection()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530594 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000595 unsigned int newId = static_cast<unsigned int>(connections.size());
596
Carson Labrado244256c2022-04-27 17:16:32 +0000597 auto& ret = connections.emplace_back(
598 std::make_shared<ConnectionInfo>(ioc, id, destIP, destPort, newId));
Carson Labradof52c03c2022-03-23 18:50:15 +0000599
600 BMCWEB_LOG_DEBUG << "Added connection "
601 << std::to_string(connections.size() - 1)
602 << " to pool " << destIP << ":"
603 << std::to_string(destPort);
604
605 return ret;
606 }
607
608 public:
Ed Tanous8a592812022-06-04 09:06:59 -0700609 explicit ConnectionPool(boost::asio::io_context& iocIn,
610 const std::string& idIn,
611 const std::string& destIPIn,
612 const uint16_t destPortIn) :
613 ioc(iocIn),
614 id(idIn), destIP(destIPIn), destPort(destPortIn)
Carson Labradof52c03c2022-03-23 18:50:15 +0000615 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000616 BMCWEB_LOG_DEBUG << "Initializing connection pool for " << destIP << ":"
617 << std::to_string(destPort);
618
619 // Initialize the pool with a single connection
620 addConnection();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530621 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530622};
623
Carson Labradof52c03c2022-03-23 18:50:15 +0000624class HttpClient
625{
626 private:
627 std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
628 connectionPools;
629 boost::asio::io_context& ioc =
630 crow::connections::systemBus->get_io_context();
631 std::unordered_map<std::string, RetryPolicyData> retryInfo;
632 HttpClient() = default;
633
Carson Labrado039a47e2022-04-05 16:03:20 +0000634 // Used as a dummy callback by sendData() in order to call
635 // sendDataWithCallback()
Ed Tanous02cad962022-06-30 16:50:15 -0700636 static void genericResHandler(const Response& res)
Carson Labrado039a47e2022-04-05 16:03:20 +0000637 {
638 BMCWEB_LOG_DEBUG << "Response handled with return code: "
639 << std::to_string(res.resultInt());
Ed Tanous4ee8e212022-05-28 09:42:51 -0700640 }
Carson Labrado039a47e2022-04-05 16:03:20 +0000641
Carson Labradof52c03c2022-03-23 18:50:15 +0000642 public:
643 HttpClient(const HttpClient&) = delete;
644 HttpClient& operator=(const HttpClient&) = delete;
645 HttpClient(HttpClient&&) = delete;
646 HttpClient& operator=(HttpClient&&) = delete;
647 ~HttpClient() = default;
648
649 static HttpClient& getInstance()
650 {
651 static HttpClient handler;
652 return handler;
653 }
654
Carson Labrado039a47e2022-04-05 16:03:20 +0000655 // Send a request to destIP:destPort where additional processing of the
656 // result is not required
Carson Labradof52c03c2022-03-23 18:50:15 +0000657 void sendData(std::string& data, const std::string& id,
658 const std::string& destIP, const uint16_t destPort,
659 const std::string& destUri,
660 const boost::beast::http::fields& httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000661 const boost::beast::http::verb verb,
662 const std::string& retryPolicyName)
Carson Labradof52c03c2022-03-23 18:50:15 +0000663 {
Ed Tanous02cad962022-06-30 16:50:15 -0700664 const std::function<void(const Response&)> cb = genericResHandler;
Carson Labrado039a47e2022-04-05 16:03:20 +0000665 sendDataWithCallback(data, id, destIP, destPort, destUri, httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000666 verb, retryPolicyName, cb);
Carson Labrado039a47e2022-04-05 16:03:20 +0000667 }
668
669 // Send request to destIP:destPort and use the provided callback to
670 // handle the response
671 void sendDataWithCallback(std::string& data, const std::string& id,
672 const std::string& destIP,
673 const uint16_t destPort,
674 const std::string& destUri,
675 const boost::beast::http::fields& httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000676 const boost::beast::http::verb verb,
677 const std::string& retryPolicyName,
Ed Tanous6b3db602022-06-28 19:41:44 -0700678 const std::function<void(Response&)>& resHandler)
Carson Labrado039a47e2022-04-05 16:03:20 +0000679 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000680 std::string clientKey = destIP + ":" + std::to_string(destPort);
681 // Use nullptr to avoid creating a ConnectionPool each time
682 auto result = connectionPools.try_emplace(clientKey, nullptr);
683 if (result.second)
684 {
685 // Now actually create the ConnectionPool shared_ptr since it does
686 // not already exist
Carson Labrado244256c2022-04-27 17:16:32 +0000687 result.first->second =
688 std::make_shared<ConnectionPool>(ioc, id, destIP, destPort);
Carson Labradof52c03c2022-03-23 18:50:15 +0000689 BMCWEB_LOG_DEBUG << "Created connection pool for " << clientKey;
690 }
691 else
692 {
693 BMCWEB_LOG_DEBUG << "Using existing connection pool for "
694 << clientKey;
695 }
696
697 // Get the associated retry policy
698 auto policy = retryInfo.try_emplace(retryPolicyName);
699 if (policy.second)
700 {
701 BMCWEB_LOG_DEBUG << "Creating retry policy \"" << retryPolicyName
702 << "\" with default values";
Carson Labradof52c03c2022-03-23 18:50:15 +0000703 }
704
705 // Send the data using either the existing connection pool or the newly
706 // created connection pool
Carson Labrado244256c2022-04-27 17:16:32 +0000707 result.first->second->sendData(data, destUri, httpHeader, verb,
708 policy.first->second, resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000709 }
710
Carson Labradoa7a80292022-06-01 16:01:52 +0000711 void setRetryConfig(
712 const uint32_t retryAttempts, const uint32_t retryTimeoutInterval,
713 const std::function<boost::system::error_code(unsigned int respCode)>&
714 invalidResp,
715 const std::string& retryPolicyName)
Carson Labradof52c03c2022-03-23 18:50:15 +0000716 {
717 // We need to create the retry policy if one does not already exist for
718 // the given retryPolicyName
719 auto result = retryInfo.try_emplace(retryPolicyName);
720 if (result.second)
721 {
722 BMCWEB_LOG_DEBUG << "setRetryConfig(): Creating new retry policy \""
723 << retryPolicyName << "\"";
Carson Labradof52c03c2022-03-23 18:50:15 +0000724 }
725 else
726 {
727 BMCWEB_LOG_DEBUG << "setRetryConfig(): Updating retry info for \""
728 << retryPolicyName << "\"";
729 }
730
731 result.first->second.maxRetryAttempts = retryAttempts;
732 result.first->second.retryIntervalSecs =
733 std::chrono::seconds(retryTimeoutInterval);
Carson Labradoa7a80292022-06-01 16:01:52 +0000734 result.first->second.invalidResp = invalidResp;
Carson Labradof52c03c2022-03-23 18:50:15 +0000735 }
736
737 void setRetryPolicy(const std::string& retryPolicy,
738 const std::string& retryPolicyName)
739 {
740 // We need to create the retry policy if one does not already exist for
741 // the given retryPolicyName
742 auto result = retryInfo.try_emplace(retryPolicyName);
743 if (result.second)
744 {
745 BMCWEB_LOG_DEBUG << "setRetryPolicy(): Creating new retry policy \""
746 << retryPolicyName << "\"";
Carson Labradof52c03c2022-03-23 18:50:15 +0000747 }
748 else
749 {
750 BMCWEB_LOG_DEBUG << "setRetryPolicy(): Updating retry policy for \""
751 << retryPolicyName << "\"";
752 }
753
754 result.first->second.retryPolicyAction = retryPolicy;
755 }
756};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530757} // namespace crow