blob: 16378232cbbd4a7131c53c48a132d56f8a647c1a [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
Sunitha Harish29a82b02021-02-18 15:54:16 +053017#include <boost/asio/ip/address.hpp>
18#include <boost/asio/ip/basic_endpoint.hpp>
Ed Tanousd43cd0c2020-09-30 20:46:53 -070019#include <boost/asio/steady_timer.hpp>
20#include <boost/beast/core/flat_buffer.hpp>
21#include <boost/beast/core/tcp_stream.hpp>
22#include <boost/beast/http/message.hpp>
AppaRao Pulibd030d02020-03-20 03:34:29 +053023#include <boost/beast/version.hpp>
Carson Labradof52c03c2022-03-23 18:50:15 +000024#include <boost/container/devector.hpp>
Sunitha Harish29a82b02021-02-18 15:54:16 +053025#include <include/async_resolve.hpp>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050026
AppaRao Pulibd030d02020-03-20 03:34:29 +053027#include <cstdlib>
28#include <functional>
29#include <iostream>
30#include <memory>
AppaRao Puli2a5689a2020-04-29 15:24:31 +053031#include <queue>
AppaRao Pulibd030d02020-03-20 03:34:29 +053032#include <string>
33
34namespace crow
35{
36
Carson Labradof52c03c2022-03-23 18:50:15 +000037// It is assumed that the BMC should be able to handle 4 parallel connections
38constexpr uint8_t maxPoolSize = 4;
39constexpr uint8_t maxRequestQueueSize = 50;
40constexpr unsigned int httpReadBodyLimit = 8192;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053041
AppaRao Pulibd030d02020-03-20 03:34:29 +053042enum class ConnState
43{
AppaRao Puli2a5689a2020-04-29 15:24:31 +053044 initialized,
Sunitha Harish29a82b02021-02-18 15:54:16 +053045 resolveInProgress,
46 resolveFailed,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053047 connectInProgress,
48 connectFailed,
AppaRao Pulibd030d02020-03-20 03:34:29 +053049 connected,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053050 sendInProgress,
51 sendFailed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053052 recvInProgress,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053053 recvFailed,
54 idle,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053055 closeInProgress,
Ayushi Smritife44eb02020-05-15 15:24:45 +053056 closed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053057 suspended,
58 terminated,
59 abortConnection,
60 retry
AppaRao Pulibd030d02020-03-20 03:34:29 +053061};
62
Carson Labradof52c03c2022-03-23 18:50:15 +000063// We need to allow retry information to be set before a message has been sent
64// and a connection pool has been created
65struct RetryPolicyData
66{
67 uint32_t maxRetryAttempts = 5;
68 std::chrono::seconds retryIntervalSecs = std::chrono::seconds(0);
69 std::string retryPolicyAction = "TerminateAfterRetries";
70 std::string name;
71};
72
73struct PendingRequest
74{
75 std::string requestData;
76 std::function<void(bool, uint32_t)> callback;
77 RetryPolicyData retryPolicy;
78 PendingRequest(const std::string& requestData,
79 const std::function<void(bool, uint32_t)>& callback,
80 const RetryPolicyData& retryPolicy) :
81 requestData(requestData),
82 callback(callback), retryPolicy(retryPolicy)
83 {}
84};
85
86class ConnectionInfo : public std::enable_shared_from_this<ConnectionInfo>
AppaRao Pulibd030d02020-03-20 03:34:29 +053087{
88 private:
Carson Labradof52c03c2022-03-23 18:50:15 +000089 ConnState state = ConnState::initialized;
90 uint32_t retryCount = 0;
91 bool runningTimer = false;
92 std::string subId;
93 std::string host;
94 uint16_t port;
95 uint32_t connId;
96
97 // Retry policy information
98 // This should be updated before each message is sent
99 RetryPolicyData retryPolicy;
100
101 // Data buffers
102 std::string data;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530103 boost::beast::http::request<boost::beast::http::string_body> req;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530104 std::optional<
105 boost::beast::http::response_parser<boost::beast::http::string_body>>
106 parser;
Carson Labradof52c03c2022-03-23 18:50:15 +0000107 boost::beast::flat_static_buffer<httpReadBodyLimit> buffer;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530108
Carson Labradof52c03c2022-03-23 18:50:15 +0000109 // Ascync callables
110 std::function<void(bool, uint32_t)> callback;
111 crow::async_resolve::Resolver resolver;
112 boost::beast::tcp_stream conn;
113 boost::asio::steady_timer timer;
Ed Tanous84b35602021-09-08 20:06:32 -0700114
Carson Labradof52c03c2022-03-23 18:50:15 +0000115 friend class ConnectionPool;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530116
Sunitha Harish29a82b02021-02-18 15:54:16 +0530117 void doResolve()
118 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530119 state = ConnState::resolveInProgress;
Carson Labradof52c03c2022-03-23 18:50:15 +0000120 BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":"
121 << std::to_string(port)
122 << ", id: " << std::to_string(connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530123
124 auto respHandler =
125 [self(shared_from_this())](
126 const boost::beast::error_code ec,
127 const std::vector<boost::asio::ip::tcp::endpoint>&
128 endpointList) {
Ed Tanous26f69762022-01-25 09:49:11 -0800129 if (ec || (endpointList.empty()))
Sunitha Harish29a82b02021-02-18 15:54:16 +0530130 {
131 BMCWEB_LOG_ERROR << "Resolve failed: " << ec.message();
132 self->state = ConnState::resolveFailed;
Carson Labradof52c03c2022-03-23 18:50:15 +0000133 self->waitAndRetry();
Sunitha Harish29a82b02021-02-18 15:54:16 +0530134 return;
135 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000136 BMCWEB_LOG_DEBUG << "Resolved " << self->host << ":"
137 << std::to_string(self->port)
138 << ", id: " << std::to_string(self->connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530139 self->doConnect(endpointList);
140 };
Carson Labradof52c03c2022-03-23 18:50:15 +0000141
Sunitha Harish29a82b02021-02-18 15:54:16 +0530142 resolver.asyncResolve(host, port, std::move(respHandler));
143 }
144
145 void doConnect(
146 const std::vector<boost::asio::ip::tcp::endpoint>& endpointList)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530147 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530148 state = ConnState::connectInProgress;
149
Carson Labradof52c03c2022-03-23 18:50:15 +0000150 BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":"
151 << std::to_string(port)
152 << ", id: " << std::to_string(connId);
Sunitha Harish29a82b02021-02-18 15:54:16 +0530153
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530154 conn.expires_after(std::chrono::seconds(30));
Sunitha Harish29a82b02021-02-18 15:54:16 +0530155 conn.async_connect(
156 endpointList, [self(shared_from_this())](
157 const boost::beast::error_code ec,
158 const boost::asio::ip::tcp::endpoint& endpoint) {
159 if (ec)
160 {
Ed Tanous8cc8ede2022-02-28 10:20:59 -0800161 BMCWEB_LOG_ERROR << "Connect "
Carson Labradof52c03c2022-03-23 18:50:15 +0000162 << endpoint.address().to_string() << ":"
163 << std::to_string(endpoint.port())
164 << ", id: " << std::to_string(self->connId)
Sunitha Harish29a82b02021-02-18 15:54:16 +0530165 << " failed: " << ec.message();
166 self->state = ConnState::connectFailed;
Carson Labradof52c03c2022-03-23 18:50:15 +0000167 self->waitAndRetry();
Sunitha Harish29a82b02021-02-18 15:54:16 +0530168 return;
169 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000170 BMCWEB_LOG_DEBUG
171 << "Connected to: " << endpoint.address().to_string() << ":"
172 << std::to_string(endpoint.port())
173 << ", id: " << std::to_string(self->connId);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530174 self->state = ConnState::connected;
Carson Labradof52c03c2022-03-23 18:50:15 +0000175 self->sendMessage();
Sunitha Harish29a82b02021-02-18 15:54:16 +0530176 });
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530177 }
178
Carson Labradof52c03c2022-03-23 18:50:15 +0000179 void sendMessage()
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530180 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530181 state = ConnState::sendInProgress;
182
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530183 req.body() = data;
184 req.prepare_payload();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530185
186 // Set a timeout on the operation
187 conn.expires_after(std::chrono::seconds(30));
188
189 // Send the HTTP request to the remote host
190 boost::beast::http::async_write(
191 conn, req,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530192 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530193 const std::size_t& bytesTransferred) {
194 if (ec)
195 {
196 BMCWEB_LOG_ERROR << "sendMessage() failed: "
197 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530198 self->state = ConnState::sendFailed;
Carson Labradof52c03c2022-03-23 18:50:15 +0000199 self->waitAndRetry();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530200 return;
201 }
202 BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
203 << bytesTransferred;
204 boost::ignore_unused(bytesTransferred);
205
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530206 self->recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530207 });
208 }
209
210 void recvMessage()
211 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530212 state = ConnState::recvInProgress;
213
214 parser.emplace(std::piecewise_construct, std::make_tuple());
215 parser->body_limit(httpReadBodyLimit);
216
AppaRao Pulibd030d02020-03-20 03:34:29 +0530217 // Receive the HTTP response
218 boost::beast::http::async_read(
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530219 conn, buffer, *parser,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530220 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530221 const std::size_t& bytesTransferred) {
222 if (ec)
223 {
224 BMCWEB_LOG_ERROR << "recvMessage() failed: "
225 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530226 self->state = ConnState::recvFailed;
Carson Labradof52c03c2022-03-23 18:50:15 +0000227 self->waitAndRetry();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530228 return;
229 }
230 BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
231 << bytesTransferred;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530232 BMCWEB_LOG_DEBUG << "recvMessage() data: "
Ed Tanous8cc8ede2022-02-28 10:20:59 -0800233 << self->parser->get().body();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530234
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530235 unsigned int respCode = self->parser->get().result_int();
236 BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: "
237 << respCode;
238
239 // 2XX response is considered to be successful
240 if ((respCode < 200) || (respCode >= 300))
241 {
242 // The listener failed to receive the Sent-Event
Sunitha Harish7adb85a2021-10-26 03:10:04 -0500243 BMCWEB_LOG_ERROR
244 << "recvMessage() Listener Failed to "
245 "receive Sent-Event. Header Response Code: "
246 << respCode;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530247 self->state = ConnState::recvFailed;
Carson Labradof52c03c2022-03-23 18:50:15 +0000248 self->waitAndRetry();
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530249 return;
250 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530251
Carson Labradof52c03c2022-03-23 18:50:15 +0000252 // Send is successful
253 // Reset the counter just in case this was after retrying
254 self->retryCount = 0;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530255
256 // Keep the connection alive if server supports it
257 // Else close the connection
258 BMCWEB_LOG_DEBUG << "recvMessage() keepalive : "
259 << self->parser->keep_alive();
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530260
Carson Labradof52c03c2022-03-23 18:50:15 +0000261 self->callback(self->parser->keep_alive(), self->connId);
AppaRao Pulibd030d02020-03-20 03:34:29 +0530262 });
263 }
264
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530265 void waitAndRetry()
AppaRao Pulibd030d02020-03-20 03:34:29 +0530266 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000267 if (retryCount >= retryPolicy.maxRetryAttempts)
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530268 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530269 BMCWEB_LOG_ERROR << "Maximum number of retries reached.";
Carson Labradof52c03c2022-03-23 18:50:15 +0000270 BMCWEB_LOG_DEBUG << "Retry policy: "
271 << retryPolicy.retryPolicyAction;
272 if (retryPolicy.retryPolicyAction == "TerminateAfterRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530273 {
274 // TODO: delete subscription
275 state = ConnState::terminated;
Carson Labradof52c03c2022-03-23 18:50:15 +0000276 callback(false, connId);
Ayushi Smritife44eb02020-05-15 15:24:45 +0530277 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000278 if (retryPolicy.retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530279 {
280 state = ConnState::suspended;
Carson Labradof52c03c2022-03-23 18:50:15 +0000281 callback(false, connId);
Ayushi Smritife44eb02020-05-15 15:24:45 +0530282 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530283 // Reset the retrycount to zero so that client can try connecting
284 // again if needed
Ed Tanous3174e4d2020-10-07 11:41:22 -0700285 retryCount = 0;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530286 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530287 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530288
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530289 if (runningTimer)
290 {
291 BMCWEB_LOG_DEBUG << "Retry timer is already running.";
292 return;
293 }
294 runningTimer = true;
295
296 retryCount++;
297
Carson Labradof52c03c2022-03-23 18:50:15 +0000298 BMCWEB_LOG_DEBUG << "Attempt retry after "
299 << std::to_string(
300 retryPolicy.retryIntervalSecs.count())
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530301 << " seconds. RetryCount = " << retryCount;
Carson Labradof52c03c2022-03-23 18:50:15 +0000302 timer.expires_after(retryPolicy.retryIntervalSecs);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530303 timer.async_wait(
Carson Labradof52c03c2022-03-23 18:50:15 +0000304 [self(shared_from_this())](const boost::system::error_code ec) {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530305 if (ec == boost::asio::error::operation_aborted)
306 {
307 BMCWEB_LOG_DEBUG
308 << "async_wait failed since the operation is aborted"
309 << ec.message();
310 }
311 else if (ec)
312 {
313 BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message();
314 // Ignore the error and continue the retry loop to attempt
315 // sending the event as per the retry policy
316 }
317 self->runningTimer = false;
318
Carson Labradof52c03c2022-03-23 18:50:15 +0000319 // Let's close the connection and restart from resolve.
320 self->doCloseAndRetry();
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530321 });
Ayushi Smritife44eb02020-05-15 15:24:45 +0530322 }
323
Carson Labradof52c03c2022-03-23 18:50:15 +0000324 void doClose()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530325 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000326 state = ConnState::closeInProgress;
327 boost::beast::error_code ec;
328 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
329 conn.close();
330
331 // not_connected happens sometimes so don't bother reporting it.
332 if (ec && ec != boost::beast::errc::not_connected)
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530333 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000334 BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
335 << ", id: " << std::to_string(connId)
336 << "shutdown failed: " << ec.message();
337 return;
338 }
339 BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
340 << ", id: " << std::to_string(connId)
341 << " closed gracefully";
342 if ((state != ConnState::suspended) && (state != ConnState::terminated))
343 {
344 state = ConnState::closed;
345 }
346 }
347
348 void doCloseAndRetry()
349 {
350 state = ConnState::closeInProgress;
351 boost::beast::error_code ec;
352 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
353 conn.close();
354
355 // not_connected happens sometimes so don't bother reporting it.
356 if (ec && ec != boost::beast::errc::not_connected)
357 {
358 BMCWEB_LOG_ERROR << host << ":" << std::to_string(port)
359 << ", id: " << std::to_string(connId)
360 << "shutdown failed: " << ec.message();
361 return;
362 }
363 BMCWEB_LOG_DEBUG << host << ":" << std::to_string(port)
364 << ", id: " << std::to_string(connId)
365 << " closed gracefully";
366 if ((state != ConnState::suspended) && (state != ConnState::terminated))
367 {
368 // Now let's try to resend the data
369 state = ConnState::retry;
370 this->doResolve();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530371 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530372 }
373
374 public:
Carson Labradof52c03c2022-03-23 18:50:15 +0000375 explicit ConnectionInfo(boost::asio::io_context& ioc, const std::string& id,
376 const std::string& destIP, const uint16_t destPort,
377 const std::string& destUri,
378 const boost::beast::http::fields& httpHeader,
379 const unsigned int connId) :
380 subId(id),
381 host(destIP), port(destPort), connId(connId),
Ed Tanous4da04452021-09-08 19:57:44 -0700382 req(boost::beast::http::verb::post, destUri, 11, "", httpHeader),
Carson Labradof52c03c2022-03-23 18:50:15 +0000383 conn(ioc), timer(ioc)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530384 {
Ed Tanous4da04452021-09-08 19:57:44 -0700385 req.set(boost::beast::http::field::host, host);
386 req.keep_alive(true);
AppaRao Pulibd030d02020-03-20 03:34:29 +0530387 }
Carson Labradof52c03c2022-03-23 18:50:15 +0000388};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530389
Carson Labradof52c03c2022-03-23 18:50:15 +0000390class ConnectionPool : public std::enable_shared_from_this<ConnectionPool>
391{
392 private:
393 boost::asio::io_context& ioc;
394 const std::string id;
395 const std::string destIP;
396 const uint16_t destPort;
397 const std::string destUri;
398 const boost::beast::http::fields httpHeader;
399 std::vector<std::shared_ptr<ConnectionInfo>> connections;
400 boost::container::devector<PendingRequest> requestQueue;
401
402 friend class HttpClient;
403
404 // Configure a connections's data, callback, and retry info in preparation
405 // to begin sending a request
406 void setConnProps(ConnectionInfo& conn)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530407 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000408 if (requestQueue.empty())
AppaRao Pulibd030d02020-03-20 03:34:29 +0530409 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000410 BMCWEB_LOG_ERROR
411 << "setConnProps() should not have been called when requestQueue is empty";
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530412 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530413 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530414
Carson Labradof52c03c2022-03-23 18:50:15 +0000415 auto req = requestQueue.front();
416 conn.retryPolicy = std::move(req.retryPolicy);
417 conn.data = std::move(req.requestData);
418 conn.callback = std::move(req.callback);
419
420 BMCWEB_LOG_DEBUG << "Setting properties for connection " << conn.host
421 << ":" << std::to_string(conn.port)
422 << ", id: " << std::to_string(conn.connId)
423 << ", retry policy is \"" << conn.retryPolicy.name
424 << "\"";
425
426 // We can remove the request from the queue at this point
427 requestQueue.pop_front();
428 }
429
430 // Configures a connection to use the specific retry policy.
431 inline void setConnRetryPolicy(ConnectionInfo& conn,
432 const RetryPolicyData& retryPolicy)
433 {
434 BMCWEB_LOG_DEBUG << destIP << ":" << std::to_string(destPort)
435 << ", id: " << std::to_string(conn.connId)
436 << " using retry policy \"" << retryPolicy.name
437 << "\"";
438
439 conn.retryPolicy = retryPolicy;
440 }
441
442 // Gets called as part of callback after request is sent
443 // Reuses the connection if there are any requests waiting to be sent
444 // Otherwise closes the connection if it is not a keep-alive
445 void sendNext(bool keepAlive, uint32_t connId)
446 {
447 auto conn = connections[connId];
448 // Reuse the connection to send the next request in the queue
449 if (!requestQueue.empty())
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530450 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000451 BMCWEB_LOG_DEBUG << std::to_string(requestQueue.size())
452 << " requests remaining in queue for " << destIP
453 << ":" << std::to_string(destPort)
454 << ", reusing connnection "
455 << std::to_string(connId);
456
457 setConnProps(*conn);
458
459 if (keepAlive)
460 {
461 conn->sendMessage();
462 }
463 else
464 {
465 // Server is not keep-alive enabled so we need to close the
466 // connection and then start over from resolve
467 conn->doClose();
468 conn->doResolve();
469 }
470 return;
471 }
472
473 // No more messages to send so close the connection if necessary
474 if (keepAlive)
475 {
476 conn->state = ConnState::idle;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530477 }
478 else
479 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000480 // Abort the connection since server is not keep-alive enabled
481 conn->state = ConnState::abortConnection;
482 conn->doClose();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530483 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530484 }
485
Carson Labradof52c03c2022-03-23 18:50:15 +0000486 void sendData(std::string& data, const RetryPolicyData& retryPolicy)
Ayushi Smritife44eb02020-05-15 15:24:45 +0530487 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000488 std::weak_ptr<ConnectionPool> weakSelf = weak_from_this();
489
490 // Callback to be called once the request has been sent
491 auto cb = [weakSelf](bool keepAlive, uint32_t connId) {
492 // If requests remain in the queue then we want to reuse this
493 // connection to send the next request
494 std::shared_ptr<ConnectionPool> self = weakSelf.lock();
495 if (!self)
496 {
497 BMCWEB_LOG_CRITICAL << self << " Failed to capture connection";
498 return;
499 }
500
501 self->sendNext(keepAlive, connId);
502 };
503
504 // Reuse an existing connection if one is available
505 for (unsigned int i = 0; i < connections.size(); i++)
506 {
507 auto conn = connections[i];
508 if ((conn->state == ConnState::idle) ||
509 (conn->state == ConnState::initialized) ||
510 (conn->state == ConnState::closed))
511 {
512 conn->data = std::move(data);
513 conn->callback = std::move(cb);
514 conn->retryPolicy = retryPolicy;
515 setConnRetryPolicy(*conn, retryPolicy);
516 std::string commonMsg = std::to_string(i) + " from pool " +
517 destIP + ":" + std::to_string(destPort);
518
519 if (conn->state == ConnState::idle)
520 {
521 BMCWEB_LOG_DEBUG << "Grabbing idle connection "
522 << commonMsg;
523 conn->sendMessage();
524 }
525 else
526 {
527 BMCWEB_LOG_DEBUG << "Reusing existing connection "
528 << commonMsg;
529 conn->doResolve();
530 }
531 return;
532 }
533 }
534
535 // All connections in use so create a new connection or add request to
536 // the queue
537 if (connections.size() < maxPoolSize)
538 {
539 BMCWEB_LOG_DEBUG << "Adding new connection to pool " << destIP
540 << ":" << std::to_string(destPort);
541 auto conn = addConnection();
542 conn->data = std::move(data);
543 conn->callback = std::move(cb);
544 setConnRetryPolicy(*conn, retryPolicy);
545 conn->doResolve();
546 }
547 else if (requestQueue.size() < maxRequestQueueSize)
548 {
549 BMCWEB_LOG_ERROR << "Max pool size reached. Adding data to queue.";
550 requestQueue.emplace_back(std::move(data), std::move(cb),
551 retryPolicy);
552 }
553 else
554 {
555 BMCWEB_LOG_ERROR << destIP << ":" << std::to_string(destPort)
556 << " request queue full. Dropping request.";
557 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530558 }
559
Carson Labradof52c03c2022-03-23 18:50:15 +0000560 std::shared_ptr<ConnectionInfo>& addConnection()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530561 {
Carson Labradof52c03c2022-03-23 18:50:15 +0000562 unsigned int newId = static_cast<unsigned int>(connections.size());
563
564 auto& ret = connections.emplace_back(std::make_shared<ConnectionInfo>(
565 ioc, id, destIP, destPort, destUri, httpHeader, newId));
566
567 BMCWEB_LOG_DEBUG << "Added connection "
568 << std::to_string(connections.size() - 1)
569 << " to pool " << destIP << ":"
570 << std::to_string(destPort);
571
572 return ret;
573 }
574
575 public:
576 explicit ConnectionPool(boost::asio::io_context& ioc, const std::string& id,
577 const std::string& destIP, const uint16_t destPort,
578 const std::string& destUri,
579 const boost::beast::http::fields& httpHeader) :
580 ioc(ioc),
581 id(id), destIP(destIP), destPort(destPort), destUri(destUri),
582 httpHeader(httpHeader)
583 {
584 std::string clientKey = destIP + ":" + std::to_string(destPort);
585 BMCWEB_LOG_DEBUG << "Initializing connection pool for " << destIP << ":"
586 << std::to_string(destPort);
587
588 // Initialize the pool with a single connection
589 addConnection();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530590 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530591};
592
Carson Labradof52c03c2022-03-23 18:50:15 +0000593class HttpClient
594{
595 private:
596 std::unordered_map<std::string, std::shared_ptr<ConnectionPool>>
597 connectionPools;
598 boost::asio::io_context& ioc =
599 crow::connections::systemBus->get_io_context();
600 std::unordered_map<std::string, RetryPolicyData> retryInfo;
601 HttpClient() = default;
602
603 public:
604 HttpClient(const HttpClient&) = delete;
605 HttpClient& operator=(const HttpClient&) = delete;
606 HttpClient(HttpClient&&) = delete;
607 HttpClient& operator=(HttpClient&&) = delete;
608 ~HttpClient() = default;
609
610 static HttpClient& getInstance()
611 {
612 static HttpClient handler;
613 return handler;
614 }
615
616 void sendData(std::string& data, const std::string& id,
617 const std::string& destIP, const uint16_t destPort,
618 const std::string& destUri,
619 const boost::beast::http::fields& httpHeader,
620 std::string& retryPolicyName)
621 {
622 std::string clientKey = destIP + ":" + std::to_string(destPort);
623 // Use nullptr to avoid creating a ConnectionPool each time
624 auto result = connectionPools.try_emplace(clientKey, nullptr);
625 if (result.second)
626 {
627 // Now actually create the ConnectionPool shared_ptr since it does
628 // not already exist
629 result.first->second = std::make_shared<ConnectionPool>(
630 ioc, id, destIP, destPort, destUri, httpHeader);
631 BMCWEB_LOG_DEBUG << "Created connection pool for " << clientKey;
632 }
633 else
634 {
635 BMCWEB_LOG_DEBUG << "Using existing connection pool for "
636 << clientKey;
637 }
638
639 // Get the associated retry policy
640 auto policy = retryInfo.try_emplace(retryPolicyName);
641 if (policy.second)
642 {
643 BMCWEB_LOG_DEBUG << "Creating retry policy \"" << retryPolicyName
644 << "\" with default values";
645 policy.first->second.name = retryPolicyName;
646 }
647
648 // Send the data using either the existing connection pool or the newly
649 // created connection pool
650 result.first->second->sendData(data, policy.first->second);
651 }
652
653 void setRetryConfig(const uint32_t retryAttempts,
654 const uint32_t retryTimeoutInterval,
655 const std::string& retryPolicyName)
656 {
657 // We need to create the retry policy if one does not already exist for
658 // the given retryPolicyName
659 auto result = retryInfo.try_emplace(retryPolicyName);
660 if (result.second)
661 {
662 BMCWEB_LOG_DEBUG << "setRetryConfig(): Creating new retry policy \""
663 << retryPolicyName << "\"";
664 result.first->second.name = retryPolicyName;
665 }
666 else
667 {
668 BMCWEB_LOG_DEBUG << "setRetryConfig(): Updating retry info for \""
669 << retryPolicyName << "\"";
670 }
671
672 result.first->second.maxRetryAttempts = retryAttempts;
673 result.first->second.retryIntervalSecs =
674 std::chrono::seconds(retryTimeoutInterval);
675 }
676
677 void setRetryPolicy(const std::string& retryPolicy,
678 const std::string& retryPolicyName)
679 {
680 // We need to create the retry policy if one does not already exist for
681 // the given retryPolicyName
682 auto result = retryInfo.try_emplace(retryPolicyName);
683 if (result.second)
684 {
685 BMCWEB_LOG_DEBUG << "setRetryPolicy(): Creating new retry policy \""
686 << retryPolicyName << "\"";
687 result.first->second.name = retryPolicyName;
688 }
689 else
690 {
691 BMCWEB_LOG_DEBUG << "setRetryPolicy(): Updating retry policy for \""
692 << retryPolicyName << "\"";
693 }
694
695 result.first->second.retryPolicyAction = retryPolicy;
696 }
697};
AppaRao Pulibd030d02020-03-20 03:34:29 +0530698} // namespace crow