blob: f59438be8c3fea2f2c40ba9c43ebc6531d1bcb09 [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,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053066 closeInProgress,
Ayushi Smritife44eb02020-05-15 15:24:45 +053067 closed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053068 suspended,
69 terminated,
70 abortConnection,
71 retry
AppaRao Pulibd030d02020-03-20 03:34:29 +053072};
73
Carson Labradoa7a80292022-06-01 16:01:52 +000074static inline boost::system::error_code
75 defaultRetryHandler(unsigned int respCode)
76{
77 // As a default, assume 200X is alright
78 BMCWEB_LOG_DEBUG << "Using default check for response code validity";
79 if ((respCode < 200) || (respCode >= 300))
80 {
81 return boost::system::errc::make_error_code(
82 boost::system::errc::result_out_of_range);
83 }
84
85 // Return 0 if the response code is valid
86 return boost::system::errc::make_error_code(boost::system::errc::success);
87};
88
Carson Labradof52c03c2022-03-23 18:50:15 +000089// We need to allow retry information to be set before a message has been sent
90// and a connection pool has been created
91struct RetryPolicyData
92{
93 uint32_t maxRetryAttempts = 5;
94 std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0);
95 std::string retryPolicyAction = "TerminateAfterRetries";
Carson Labradoa7a80292022-06-01 16:01:52 +000096 std::function<boost::system::error_code(unsigned int respCode)>
97 invalidResp = defaultRetryHandler;
Carson Labradof52c03c2022-03-23 18:50:15 +000098};
99
100struct PendingRequest
101{
Carson Labrado244256c2022-04-27 17:16:32 +0000102 boost::beast::http::request<boost::beast::http::string_body> req;
Carson Labrado039a47e2022-04-05 16:03:20 +0000103 std::function<void(bool, uint32_t, Response&)> callback;
Carson Labradof52c03c2022-03-23 18:50:15 +0000104 RetryPolicyData retryPolicy;
Carson Labrado039a47e2022-04-05 16:03:20 +0000105 PendingRequest(
Ed Tanous8a592812022-06-04 09:06:59 -0700106 boost::beast::http::request<boost::beast::http::string_body>&& reqIn,
107 const std::function<void(bool, uint32_t, Response&)>& callbackIn,
108 const RetryPolicyData& retryPolicyIn) :
109 req(std::move(reqIn)),
110 callback(callbackIn), retryPolicy(retryPolicyIn)
Carson Labradof52c03c2022-03-23 18:50:15 +0000111 {}
112};
113
114class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo>
AppaRao Pulibd030d02020-03-20 03:34:29 +0530115{
116 private:
Carson Labradof52c03c2022-03-23 18:50:15 +0000117 ConnState state = ConnState::initialized;
118 uint32_t retryCount = 0;
119 bool runningTimer = false;
120 std::string subId;
121 std::string host;
122 uint16_t port;
123 uint32_t connId;
124
125 // Retry policy information
126 // This should be updated before each message is sent
127 RetryPolicyData retryPolicy;
128
129 // Data buffers
AppaRao Pulibd030d02020-03-20 03:34:29 +0530130 boost::beast::http::request<boost::beast::http::string_body> req;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530131 std::optional<
132 boost::beast::http::response_parser<boost::beast::http::string_body>>
133 parser;
Carson Labrado4d942722022-06-22 22:16:10 +0000134 boost::beast::flat_static_buffer<httpReadBufferSize> buffer;
Carson Labrado039a47e2022-04-05 16:03:20 +0000135 Response res;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530136
Carson Labradof52c03c2022-03-23 18:50:15 +0000137 // Ascync callables
Carson Labrado039a47e2022-04-05 16:03:20 +0000138 std::function<void(bool, uint32_t, Response&)> callback;
Carson Labradof52c03c2022-03-23 18:50:15 +0000139 crow::async_resolve::Resolver resolver;
140 boost::beast::tcp_stream conn;
141 boost::asio::steady_timer timer;
Ed Tanous84b35602021-09-08 20:06:32 -0700142
Carson Labradof52c03c2022-03-23 18:50:15 +0000143 friend class ConnectionPool;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530144
Sunitha Harish29a82b02021-02-18 15:54:16 +0530145 void doResolve()
146 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530147 state = ConnState::resolveInProgress;
Carson Labradof52c03c2022-03-23 18:50:15 +0000148 BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":"
149 << std::to_string(port)
150 << ", id: " << std::to_string(connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530151
152 auto respHandler =
153 [self(shared_from_this())](
154 const boost::beast::error_code ec,
155 const std::vector<boost::asio::ip::tcp::endpoint>&
156 endpointList) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700157 if (ec || (endpointList.empty()))
158 {
159 BMCWEB_LOG_ERROR << "Resolve failed: " << ec.message();
160 self->state = ConnState::resolveFailed;
161 self->waitAndRetry();
162 return;
163 }
164 BMCWEB_LOG_DEBUG << "Resolved " << self->host << ":"
165 << std::to_string(self->port)
166 << ", id: " << std::to_string(self->connId);
167 self->doConnect(endpointList);
168 };
Carson Labradof52c03c2022-03-23 18:50:15 +0000169
Sunitha Harish29a82b02021-02-18 15:54:16 +0530170 resolver.asyncResolve(host, port, std::move(respHandler));
171 }
172
173 void doConnect(
174 const std::vector<boost::asio::ip::tcp::endpoint>& endpointList)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530175 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530176 state = ConnState::connectInProgress;
177
Carson Labradof52c03c2022-03-23 18:50:15 +0000178 BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":"
179 << std::to_string(port)
180 << ", id: " << std::to_string(connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530181
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530182 conn.expires_after(std::chrono::seconds(30));
Ed Tanous002d39b2022-05-31 08:59:27 -0700183 conn.async_connect(endpointList,
184 [self(shared_from_this())](
185 const boost::beast::error_code ec,
186 const boost::asio::ip::tcp::endpoint& endpoint) {
187 if (ec)
188 {
189 BMCWEB_LOG_ERROR << "Connect " << endpoint.address().to_string()
190 << ":" << std::to_string(endpoint.port())
191 << ", id: " << std::to_string(self->connId)
192 << " failed: " << ec.message();
193 self->state = ConnState::connectFailed;
194 self->waitAndRetry();
195 return;
196 }
197 BMCWEB_LOG_DEBUG
198 << "Connected to: " << endpoint.address().to_string() << ":"
199 << std::to_string(endpoint.port())
200 << ", id: " << std::to_string(self->connId);
201 self->state = ConnState::connected;
202 self->sendMessage();
203 });
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530204 }
205
Carson Labradof52c03c2022-03-23 18:50:15 +0000206 void sendMessage()
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530207 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530208 state = ConnState::sendInProgress;
209
AppaRao Pulibd030d02020-03-20 03:34:29 +0530210 // Set a timeout on the operation
211 conn.expires_after(std::chrono::seconds(30));
212
213 // Send the HTTP request to the remote host
214 boost::beast::http::async_write(
215 conn, req,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530216 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530217 const std::size_t& bytesTransferred) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700218 if (ec)
219 {
220 BMCWEB_LOG_ERROR << "sendMessage() failed: " << ec.message();
221 self->state = ConnState::sendFailed;
222 self->waitAndRetry();
223 return;
224 }
225 BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
226 << bytesTransferred;
227 boost::ignore_unused(bytesTransferred);
AppaRao Pulibd030d02020-03-20 03:34:29 +0530228
Ed Tanous002d39b2022-05-31 08:59:27 -0700229 self->recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530230 });
231 }
232
233 void recvMessage()
234 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530235 state = ConnState::recvInProgress;
236
237 parser.emplace(std::piecewise_construct, std::make_tuple());
238 parser->body_limit(httpReadBodyLimit);
239
AppaRao Pulibd030d02020-03-20 03:34:29 +0530240 // Receive the HTTP response
241 boost::beast::http::async_read(
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530242 conn, buffer, *parser,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530243 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530244 const std::size_t& bytesTransferred) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700245 if (ec)
246 {
247 BMCWEB_LOG_ERROR << "recvMessage() failed: " << ec.message();
248 self->state = ConnState::recvFailed;
249 self->waitAndRetry();
250 return;
251 }
252 BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
253 << bytesTransferred;
254 BMCWEB_LOG_DEBUG << "recvMessage() data: "
255 << self->parser->get().body();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530256
Ed Tanous002d39b2022-05-31 08:59:27 -0700257 unsigned int respCode = self->parser->get().result_int();
258 BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: "
259 << respCode;
260
Carson Labradoa7a80292022-06-01 16:01:52 +0000261 // Make sure the received response code is valid as defined by
262 // the associated retry policy
263 if (self->retryPolicy.invalidResp(respCode))
Ed Tanous002d39b2022-05-31 08:59:27 -0700264 {
265 // The listener failed to receive the Sent-Event
266 BMCWEB_LOG_ERROR << "recvMessage() Listener Failed to "
267 "receive Sent-Event. Header Response Code: "
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530268 << respCode;
Ed Tanous002d39b2022-05-31 08:59:27 -0700269 self->state = ConnState::recvFailed;
270 self->waitAndRetry();
271 return;
272 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530273
Ed Tanous002d39b2022-05-31 08:59:27 -0700274 // Send is successful
275 // Reset the counter just in case this was after retrying
276 self->retryCount = 0;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530277
Ed Tanous002d39b2022-05-31 08:59:27 -0700278 // Keep the connection alive if server supports it
279 // Else close the connection
280 BMCWEB_LOG_DEBUG << "recvMessage() keepalive : "
281 << self->parser->keep_alive();
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530282
Ed Tanous002d39b2022-05-31 08:59:27 -0700283 // Copy the response into a Response object so that it can be
284 // processed by the callback function.
285 self->res.clear();
286 self->res.stringResponse = self->parser->release();
287 self->callback(self->parser->keep_alive(), self->connId, self->res);
AppaRao Pulibd030d02020-03-20 03:34:29 +0530288 });
289 }
290
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530291 void waitAndRetry()
AppaRao Pulibd030d02020-03-20 03:34:29 +0530292 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000293 if (retryCount >= retryPolicy.maxRetryAttempts)
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530294 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530295 BMCWEB_LOG_ERROR << "Maximum number of retries reached.";
Carson Labradof52c03c2022-03-23 18:50:15 +0000296 BMCWEB_LOG_DEBUG << "Retry policy: "
297 << retryPolicy.retryPolicyAction;
Carson Labrado039a47e2022-04-05 16:03:20 +0000298
299 // We want to return a 502 to indicate there was an error with the
300 // external server
301 res.clear();
Ed Tanous40d799e2022-06-28 12:07:22 -0700302 res.result(boost::beast::http::status::bad_gateway);
Carson Labrado039a47e2022-04-05 16:03:20 +0000303
Carson Labradof52c03c2022-03-23 18:50:15 +0000304 if (retryPolicy.retryPolicyAction == "TerminateAfterRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530305 {
306 // TODO: delete subscription
307 state = ConnState::terminated;
Carson Labrado039a47e2022-04-05 16:03:20 +0000308 callback(false, connId, res);
Ayushi Smritife44eb02020-05-15 15:24:45 +0530309 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000310 if (retryPolicy.retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530311 {
312 state = ConnState::suspended;
Carson Labrado039a47e2022-04-05 16:03:20 +0000313 callback(false, connId, res);
Ayushi Smritife44eb02020-05-15 15:24:45 +0530314 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530315 // Reset the retrycount to zero so that client can try connecting
316 // again if needed
Ed Tanous3174e4d2020-10-07 11:41:22 -0700317 retryCount = 0;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530318 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530319 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530320
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530321 if (runningTimer)
322 {
323 BMCWEB_LOG_DEBUG << "Retry timer is already running.";
324 return;
325 }
326 runningTimer = true;
327
328 retryCount++;
329
Carson Labradof52c03c2022-03-23 18:50:15 +0000330 BMCWEB_LOG_DEBUG << "Attempt retry after "
331 << std::to_string(
332 retryPolicy.retryIntervalSecs.count())
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530333 << " seconds. RetryCount = " << retryCount;
Carson Labradof52c03c2022-03-23 18:50:15 +0000334 timer.expires_after(retryPolicy.retryIntervalSecs);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530335 timer.async_wait(
Carson Labradof52c03c2022-03-23 18:50:15 +0000336 [self(shared_from_this())](const boost::system::error_code ec) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700337 if (ec == boost::asio::error::operation_aborted)
338 {
339 BMCWEB_LOG_DEBUG
340 << "async_wait failed since the operation is aborted"
341 << ec.message();
342 }
343 else if (ec)
344 {
345 BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message();
346 // Ignore the error and continue the retry loop to attempt
347 // sending the event as per the retry policy
348 }
349 self->runningTimer = false;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530350
Ed Tanous002d39b2022-05-31 08:59:27 -0700351 // Let's close the connection and restart from resolve.
352 self->doCloseAndRetry();
353 });
Ayushi Smritife44eb02020-05-15 15:24:45 +0530354 }
355
Carson Labradof52c03c2022-03-23 18:50:15 +0000356 void doClose()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530357 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000358 state = ConnState::closeInProgress;
359 boost::beast::error_code ec;
360 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
361 conn.close();
362
363 // not_connected happens sometimes so don't bother reporting it.
364 if (ec && ec != boost::beast::errc::not_connected)
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530365 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000366 BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
367 << ", id: " << std::to_string(connId)
368 << "shutdown failed: " << ec.message();
369 return;
370 }
371 BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
372 << ", id: " << std::to_string(connId)
373 << " closed gracefully";
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 {
380 state = ConnState::closeInProgress;
381 boost::beast::error_code ec;
382 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
383 conn.close();
384
385 // not_connected happens sometimes so don't bother reporting it.
386 if (ec && ec != boost::beast::errc::not_connected)
387 {
388 BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
389 << ", id: " << std::to_string(connId)
390 << "shutdown failed: " << ec.message();
391 return;
392 }
393 BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
394 << ", id: " << std::to_string(connId)
395 << " closed gracefully";
Ed Tanousca723762022-06-28 19:40:39 -0700396
397 // Now let's try to resend the data
398 state = ConnState::retry;
399 doResolve();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530400 }
401
402 public:
Ed Tanous8a592812022-06-04 09:06:59 -0700403 explicit ConnectionInfo(boost::asio::io_context& ioc,
404 const std::string& idIn, const std::string& destIP,
405 const uint16_t destPort,
406 const unsigned int connIdIn) :
407 subId(idIn),
408 host(destIP), port(destPort), connId(connIdIn), conn(ioc), timer(ioc)
Carson Labrado244256c2022-04-27 17:16:32 +0000409 {}
Carson Labradof52c03c2022-03-23 18:50:15 +0000410};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530411
Carson Labradof52c03c2022-03-23 18:50:15 +0000412class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
413{
414 private:
415 boost::asio::io_context& ioc;
416 const std::string id;
417 const std::string destIP;
418 const uint16_t destPort;
Carson Labradof52c03c2022-03-23 18:50:15 +0000419 std::vector<std::shared_ptr<ConnectionInfo>> connections;
420 boost::container::devector<PendingRequest> requestQueue;
421
422 friend class HttpClient;
423
Carson Labrado244256c2022-04-27 17:16:32 +0000424 // Configure a connections's request, callback, and retry info in
425 // preparation to begin sending the request
Carson Labradof52c03c2022-03-23 18:50:15 +0000426 void setConnProps(ConnectionInfo& conn)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530427 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000428 if (requestQueue.empty())
AppaRao Pulibd030d02020-03-20 03:34:29 +0530429 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000430 BMCWEB_LOG_ERROR
431 << "setConnProps() should not have been called when requestQueue is empty";
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530432 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530433 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530434
Carson Labrado244256c2022-04-27 17:16:32 +0000435 auto nextReq = requestQueue.front();
436 conn.retryPolicy = std::move(nextReq.retryPolicy);
437 conn.req = std::move(nextReq.req);
438 conn.callback = std::move(nextReq.callback);
Carson Labradof52c03c2022-03-23 18:50:15 +0000439
440 BMCWEB_LOG_DEBUG << "Setting properties for connection " << conn.host
441 << ":" << std::to_string(conn.port)
Carson Labradoa7a80292022-06-01 16:01:52 +0000442 << ", id: " << std::to_string(conn.connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000443
444 // We can remove the request from the queue at this point
445 requestQueue.pop_front();
446 }
447
448 // Configures a connection to use the specific retry policy.
449 inline void setConnRetryPolicy(ConnectionInfo& conn,
450 const RetryPolicyData& retryPolicy)
451 {
452 BMCWEB_LOG_DEBUG << destIP << ":" << std::to_string(destPort)
Carson Labradoa7a80292022-06-01 16:01:52 +0000453 << ", id: " << std::to_string(conn.connId);
Carson Labradof52c03c2022-03-23 18:50:15 +0000454
455 conn.retryPolicy = retryPolicy;
456 }
457
458 // Gets called as part of callback after request is sent
459 // Reuses the connection if there are any requests waiting to be sent
460 // Otherwise closes the connection if it is not a keep-alive
461 void sendNext(bool keepAlive, uint32_t connId)
462 {
463 auto conn = connections[connId];
464 // Reuse the connection to send the next request in the queue
465 if (!requestQueue.empty())
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530466 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000467 BMCWEB_LOG_DEBUG << std::to_string(requestQueue.size())
468 << " requests remaining in queue for " << destIP
469 << ":" << std::to_string(destPort)
470 << ", reusing connnection "
471 << std::to_string(connId);
472
473 setConnProps(*conn);
474
475 if (keepAlive)
476 {
477 conn->sendMessage();
478 }
479 else
480 {
481 // Server is not keep-alive enabled so we need to close the
482 // connection and then start over from resolve
483 conn->doClose();
484 conn->doResolve();
485 }
486 return;
487 }
488
489 // No more messages to send so close the connection if necessary
490 if (keepAlive)
491 {
492 conn->state = ConnState::idle;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530493 }
494 else
495 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000496 // Abort the connection since server is not keep-alive enabled
497 conn->state = ConnState::abortConnection;
498 conn->doClose();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530499 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530500 }
501
Carson Labrado244256c2022-04-27 17:16:32 +0000502 void sendData(std::string& data, const std::string& destUri,
503 const boost::beast::http::fields& httpHeader,
504 const boost::beast::http::verb verb,
505 const RetryPolicyData& retryPolicy,
Ed Tanous6b3db602022-06-28 19:41:44 -0700506 const std::function<void(Response&)>& resHandler)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530507 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000508 std::weak_ptr<ConnectionPool> weakSelf = weak_from_this();
509
510 // Callback to be called once the request has been sent
Carson Labrado039a47e2022-04-05 16:03:20 +0000511 auto cb = [weakSelf, resHandler](bool keepAlive, uint32_t connId,
512 Response& res) {
513 // Allow provided callback to perform additional processing of the
514 // request
515 resHandler(res);
516
Carson Labradof52c03c2022-03-23 18:50:15 +0000517 // If requests remain in the queue then we want to reuse this
518 // connection to send the next request
519 std::shared_ptr<ConnectionPool> self = weakSelf.lock();
520 if (!self)
521 {
522 BMCWEB_LOG_CRITICAL << self << " Failed to capture connection";
523 return;
524 }
525
526 self->sendNext(keepAlive, connId);
527 };
528
Carson Labrado244256c2022-04-27 17:16:32 +0000529 // Construct the request to be sent
530 boost::beast::http::request<boost::beast::http::string_body> thisReq(
531 verb, destUri, 11, "", httpHeader);
532 thisReq.set(boost::beast::http::field::host, destIP);
533 thisReq.keep_alive(true);
534 thisReq.body() = std::move(data);
535 thisReq.prepare_payload();
536
Carson Labradof52c03c2022-03-23 18:50:15 +0000537 // Reuse an existing connection if one is available
538 for (unsigned int i = 0; i < connections.size(); i++)
539 {
540 auto conn = connections[i];
541 if ((conn->state == ConnState::idle) ||
542 (conn->state == ConnState::initialized) ||
543 (conn->state == ConnState::closed))
544 {
Carson Labrado244256c2022-04-27 17:16:32 +0000545 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000546 conn->callback = std::move(cb);
Carson Labradof52c03c2022-03-23 18:50:15 +0000547 setConnRetryPolicy(*conn, retryPolicy);
548 std::string commonMsg = std::to_string(i) + " from pool " +
549 destIP + ":" + std::to_string(destPort);
550
551 if (conn->state == ConnState::idle)
552 {
553 BMCWEB_LOG_DEBUG << "Grabbing idle connection "
554 << commonMsg;
555 conn->sendMessage();
556 }
557 else
558 {
559 BMCWEB_LOG_DEBUG << "Reusing existing connection "
560 << commonMsg;
561 conn->doResolve();
562 }
563 return;
564 }
565 }
566
567 // All connections in use so create a new connection or add request to
568 // the queue
569 if (connections.size() < maxPoolSize)
570 {
571 BMCWEB_LOG_DEBUG << "Adding new connection to pool " << destIP
572 << ":" << std::to_string(destPort);
573 auto conn = addConnection();
Carson Labrado244256c2022-04-27 17:16:32 +0000574 conn->req = std::move(thisReq);
Carson Labradof52c03c2022-03-23 18:50:15 +0000575 conn->callback = std::move(cb);
576 setConnRetryPolicy(*conn, retryPolicy);
577 conn->doResolve();
578 }
579 else if (requestQueue.size() < maxRequestQueueSize)
580 {
581 BMCWEB_LOG_ERROR << "Max pool size reached. Adding data to queue.";
Carson Labrado244256c2022-04-27 17:16:32 +0000582 requestQueue.emplace_back(std::move(thisReq), std::move(cb),
Carson Labradof52c03c2022-03-23 18:50:15 +0000583 retryPolicy);
584 }
585 else
586 {
587 BMCWEB_LOG_ERROR << destIP << ":" << std::to_string(destPort)
588 << " request queue full. Dropping request.";
589 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530590 }
591
Carson Labradof52c03c2022-03-23 18:50:15 +0000592 std::shared_ptr<ConnectionInfo>& addConnection()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530593 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000594 unsigned int newId = static_cast<unsigned int>(connections.size());
595
Carson Labrado244256c2022-04-27 17:16:32 +0000596 auto& ret = connections.emplace_back(
597 std::make_shared<ConnectionInfo>(ioc, id, destIP, destPort, newId));
Carson Labradof52c03c2022-03-23 18:50:15 +0000598
599 BMCWEB_LOG_DEBUG << "Added connection "
600 << std::to_string(connections.size() - 1)
601 << " to pool " << destIP << ":"
602 << std::to_string(destPort);
603
604 return ret;
605 }
606
607 public:
Ed Tanous8a592812022-06-04 09:06:59 -0700608 explicit ConnectionPool(boost::asio::io_context& iocIn,
609 const std::string& idIn,
610 const std::string& destIPIn,
611 const uint16_t destPortIn) :
612 ioc(iocIn),
613 id(idIn), destIP(destIPIn), destPort(destPortIn)
Carson Labradof52c03c2022-03-23 18:50:15 +0000614 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000615 BMCWEB_LOG_DEBUG << "Initializing connection pool for " << destIP << ":"
616 << std::to_string(destPort);
617
618 // Initialize the pool with a single connection
619 addConnection();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530620 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530621};
622
Carson Labradof52c03c2022-03-23 18:50:15 +0000623class HttpClient
624{
625 private:
626 std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
627 connectionPools;
628 boost::asio::io_context& ioc =
629 crow::connections::systemBus->get_io_context();
630 std::unordered_map<std::string, RetryPolicyData> retryInfo;
631 HttpClient() = default;
632
Carson Labrado039a47e2022-04-05 16:03:20 +0000633 // Used as a dummy callback by sendData() in order to call
634 // sendDataWithCallback()
Ed Tanous02cad962022-06-30 16:50:15 -0700635 static void genericResHandler(const Response& res)
Carson Labrado039a47e2022-04-05 16:03:20 +0000636 {
637 BMCWEB_LOG_DEBUG << "Response handled with return code: "
638 << std::to_string(res.resultInt());
Ed Tanous4ee8e212022-05-28 09:42:51 -0700639 }
Carson Labrado039a47e2022-04-05 16:03:20 +0000640
Carson Labradof52c03c2022-03-23 18:50:15 +0000641 public:
642 HttpClient(const HttpClient&) = delete;
643 HttpClient& operator=(const HttpClient&) = delete;
644 HttpClient(HttpClient&&) = delete;
645 HttpClient& operator=(HttpClient&&) = delete;
646 ~HttpClient() = default;
647
648 static HttpClient& getInstance()
649 {
650 static HttpClient handler;
651 return handler;
652 }
653
Carson Labrado039a47e2022-04-05 16:03:20 +0000654 // Send a request to destIP:destPort where additional processing of the
655 // result is not required
Carson Labradof52c03c2022-03-23 18:50:15 +0000656 void sendData(std::string& data, const std::string& id,
657 const std::string& destIP, const uint16_t destPort,
658 const std::string& destUri,
659 const boost::beast::http::fields& httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000660 const boost::beast::http::verb verb,
661 const std::string& retryPolicyName)
Carson Labradof52c03c2022-03-23 18:50:15 +0000662 {
Ed Tanous02cad962022-06-30 16:50:15 -0700663 const std::function<void(const Response&)> cb = genericResHandler;
Carson Labrado039a47e2022-04-05 16:03:20 +0000664 sendDataWithCallback(data, id, destIP, destPort, destUri, httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000665 verb, retryPolicyName, cb);
Carson Labrado039a47e2022-04-05 16:03:20 +0000666 }
667
668 // Send request to destIP:destPort and use the provided callback to
669 // handle the response
670 void sendDataWithCallback(std::string& data, const std::string& id,
671 const std::string& destIP,
672 const uint16_t destPort,
673 const std::string& destUri,
674 const boost::beast::http::fields& httpHeader,
Carson Labrado244256c2022-04-27 17:16:32 +0000675 const boost::beast::http::verb verb,
676 const std::string& retryPolicyName,
Ed Tanous6b3db602022-06-28 19:41:44 -0700677 const std::function<void(Response&)>& resHandler)
Carson Labrado039a47e2022-04-05 16:03:20 +0000678 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000679 std::string clientKey = destIP + ":" + std::to_string(destPort);
680 // Use nullptr to avoid creating a ConnectionPool each time
681 auto result = connectionPools.try_emplace(clientKey, nullptr);
682 if (result.second)
683 {
684 // Now actually create the ConnectionPool shared_ptr since it does
685 // not already exist
Carson Labrado244256c2022-04-27 17:16:32 +0000686 result.first->second =
687 std::make_shared<ConnectionPool>(ioc, id, destIP, destPort);
Carson Labradof52c03c2022-03-23 18:50:15 +0000688 BMCWEB_LOG_DEBUG << "Created connection pool for " << clientKey;
689 }
690 else
691 {
692 BMCWEB_LOG_DEBUG << "Using existing connection pool for "
693 << clientKey;
694 }
695
696 // Get the associated retry policy
697 auto policy = retryInfo.try_emplace(retryPolicyName);
698 if (policy.second)
699 {
700 BMCWEB_LOG_DEBUG << "Creating retry policy \"" << retryPolicyName
701 << "\" with default values";
Carson Labradof52c03c2022-03-23 18:50:15 +0000702 }
703
704 // Send the data using either the existing connection pool or the newly
705 // created connection pool
Carson Labrado244256c2022-04-27 17:16:32 +0000706 result.first->second->sendData(data, destUri, httpHeader, verb,
707 policy.first->second, resHandler);
Carson Labradof52c03c2022-03-23 18:50:15 +0000708 }
709
Carson Labradoa7a80292022-06-01 16:01:52 +0000710 void setRetryConfig(
711 const uint32_t retryAttempts, const uint32_t retryTimeoutInterval,
712 const std::function<boost::system::error_code(unsigned int respCode)>&
713 invalidResp,
714 const std::string& retryPolicyName)
Carson Labradof52c03c2022-03-23 18:50:15 +0000715 {
716 // We need to create the retry policy if one does not already exist for
717 // the given retryPolicyName
718 auto result = retryInfo.try_emplace(retryPolicyName);
719 if (result.second)
720 {
721 BMCWEB_LOG_DEBUG << "setRetryConfig(): Creating new retry policy \""
722 << retryPolicyName << "\"";
Carson Labradof52c03c2022-03-23 18:50:15 +0000723 }
724 else
725 {
726 BMCWEB_LOG_DEBUG << "setRetryConfig(): Updating retry info for \""
727 << retryPolicyName << "\"";
728 }
729
730 result.first->second.maxRetryAttempts = retryAttempts;
731 result.first->second.retryIntervalSecs =
732 std::chrono::seconds(retryTimeoutInterval);
Carson Labradoa7a80292022-06-01 16:01:52 +0000733 result.first->second.invalidResp = invalidResp;
Carson Labradof52c03c2022-03-23 18:50:15 +0000734 }
735
736 void setRetryPolicy(const std::string& retryPolicy,
737 const std::string& retryPolicyName)
738 {
739 // We need to create the retry policy if one does not already exist for
740 // the given retryPolicyName
741 auto result = retryInfo.try_emplace(retryPolicyName);
742 if (result.second)
743 {
744 BMCWEB_LOG_DEBUG << "setRetryPolicy(): Creating new retry policy \""
745 << retryPolicyName << "\"";
Carson Labradof52c03c2022-03-23 18:50:15 +0000746 }
747 else
748 {
749 BMCWEB_LOG_DEBUG << "setRetryPolicy(): Updating retry policy for \""
750 << retryPolicyName << "\"";
751 }
752
753 result.first->second.retryPolicyAction = retryPolicy;
754 }
755};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530756} // namespace crow