blob: 1664d01fa2c6bda0acf2e94b44637a3527048f07 [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>
Ed Tanous5dfb5b22021-12-03 11:24:53 -080024#include <boost/circular_buffer.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
AppaRao Puli2a5689a2020-04-29 15:24:31 +053037static constexpr uint8_t maxRequestQueueSize = 50;
Sunitha Harish7de9f812021-08-24 02:50:30 -050038static constexpr unsigned int httpReadBodyLimit = 8192;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053039
AppaRao Pulibd030d02020-03-20 03:34:29 +053040enum class ConnState
41{
AppaRao Puli2a5689a2020-04-29 15:24:31 +053042 initialized,
Sunitha Harish29a82b02021-02-18 15:54:16 +053043 resolveInProgress,
44 resolveFailed,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053045 connectInProgress,
46 connectFailed,
AppaRao Pulibd030d02020-03-20 03:34:29 +053047 connected,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053048 sendInProgress,
49 sendFailed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053050 recvInProgress,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053051 recvFailed,
52 idle,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053053 closeInProgress,
Ayushi Smritife44eb02020-05-15 15:24:45 +053054 closed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053055 suspended,
56 terminated,
57 abortConnection,
58 retry
AppaRao Pulibd030d02020-03-20 03:34:29 +053059};
60
61class HttpClient : public std::enable_shared_from_this<HttpClient>
62{
63 private:
Sunitha Harish29a82b02021-02-18 15:54:16 +053064 crow::async_resolve::Resolver resolver;
AppaRao Pulibd030d02020-03-20 03:34:29 +053065 boost::beast::tcp_stream conn;
Ayushi Smritife44eb02020-05-15 15:24:45 +053066 boost::asio::steady_timer timer;
Sunitha Harish7de9f812021-08-24 02:50:30 -050067 boost::beast::flat_static_buffer<httpReadBodyLimit> buffer;
AppaRao Pulibd030d02020-03-20 03:34:29 +053068 boost::beast::http::request<boost::beast::http::string_body> req;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053069 std::optional<
70 boost::beast::http::response_parser<boost::beast::http::string_body>>
71 parser;
Krzysztof Grobelny116c1842021-12-06 11:56:37 +010072 boost::circular_buffer_space_optimized<std::string> requestDataQueue{
73 maxRequestQueueSize};
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053074
Ed Tanous84b35602021-09-08 20:06:32 -070075 ConnState state = ConnState::initialized;
76
Ayushi Smritife44eb02020-05-15 15:24:45 +053077 std::string subId;
AppaRao Pulibd030d02020-03-20 03:34:29 +053078 std::string host;
79 std::string port;
Ed Tanous84b35602021-09-08 20:06:32 -070080 uint32_t retryCount = 0;
81 uint32_t maxRetryAttempts = 5;
82 uint32_t retryIntervalSecs = 0;
83 std::string retryPolicyAction = "TerminateAfterRetries";
84 bool runningTimer = false;
AppaRao Pulibd030d02020-03-20 03:34:29 +053085
Sunitha Harish29a82b02021-02-18 15:54:16 +053086 void doResolve()
87 {
Sunitha Harish29a82b02021-02-18 15:54:16 +053088 state = ConnState::resolveInProgress;
Sunitha Harish29a82b02021-02-18 15:54:16 +053089 BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":" << port;
90
91 auto respHandler =
92 [self(shared_from_this())](
93 const boost::beast::error_code ec,
94 const std::vector<boost::asio::ip::tcp::endpoint>&
95 endpointList) {
Ed Tanous26f69762022-01-25 09:49:11 -080096 if (ec || (endpointList.empty()))
Sunitha Harish29a82b02021-02-18 15:54:16 +053097 {
98 BMCWEB_LOG_ERROR << "Resolve failed: " << ec.message();
99 self->state = ConnState::resolveFailed;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530100 self->handleConnState();
Sunitha Harish29a82b02021-02-18 15:54:16 +0530101 return;
102 }
103 BMCWEB_LOG_DEBUG << "Resolved";
104 self->doConnect(endpointList);
105 };
106 resolver.asyncResolve(host, port, std::move(respHandler));
107 }
108
109 void doConnect(
110 const std::vector<boost::asio::ip::tcp::endpoint>& endpointList)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530111 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530112 state = ConnState::connectInProgress;
113
114 BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":" << port;
Sunitha Harish29a82b02021-02-18 15:54:16 +0530115
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530116 conn.expires_after(std::chrono::seconds(30));
Sunitha Harish29a82b02021-02-18 15:54:16 +0530117 conn.async_connect(
118 endpointList, [self(shared_from_this())](
119 const boost::beast::error_code ec,
120 const boost::asio::ip::tcp::endpoint& endpoint) {
121 if (ec)
122 {
Ed Tanous8cc8ede2022-02-28 10:20:59 -0800123 BMCWEB_LOG_ERROR << "Connect "
124 << endpoint.address().to_string()
Sunitha Harish29a82b02021-02-18 15:54:16 +0530125 << " failed: " << ec.message();
126 self->state = ConnState::connectFailed;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530127 self->handleConnState();
Sunitha Harish29a82b02021-02-18 15:54:16 +0530128 return;
129 }
Ed Tanous8cc8ede2022-02-28 10:20:59 -0800130 BMCWEB_LOG_DEBUG << "Connected to: "
131 << endpoint.address().to_string();
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530132 self->state = ConnState::connected;
133 self->handleConnState();
Sunitha Harish29a82b02021-02-18 15:54:16 +0530134 });
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530135 }
136
137 void sendMessage(const std::string& data)
138 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530139 state = ConnState::sendInProgress;
140
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530141 req.body() = data;
142 req.prepare_payload();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530143
144 // Set a timeout on the operation
145 conn.expires_after(std::chrono::seconds(30));
146
147 // Send the HTTP request to the remote host
148 boost::beast::http::async_write(
149 conn, req,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530150 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530151 const std::size_t& bytesTransferred) {
152 if (ec)
153 {
154 BMCWEB_LOG_ERROR << "sendMessage() failed: "
155 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530156 self->state = ConnState::sendFailed;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530157 self->handleConnState();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530158 return;
159 }
160 BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
161 << bytesTransferred;
162 boost::ignore_unused(bytesTransferred);
163
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530164 self->recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530165 });
166 }
167
168 void recvMessage()
169 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530170 state = ConnState::recvInProgress;
171
172 parser.emplace(std::piecewise_construct, std::make_tuple());
173 parser->body_limit(httpReadBodyLimit);
174
AppaRao Pulibd030d02020-03-20 03:34:29 +0530175 // Receive the HTTP response
176 boost::beast::http::async_read(
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530177 conn, buffer, *parser,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530178 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530179 const std::size_t& bytesTransferred) {
180 if (ec)
181 {
182 BMCWEB_LOG_ERROR << "recvMessage() failed: "
183 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530184 self->state = ConnState::recvFailed;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530185 self->handleConnState();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530186 return;
187 }
188 BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
189 << bytesTransferred;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530190 BMCWEB_LOG_DEBUG << "recvMessage() data: "
Ed Tanous8cc8ede2022-02-28 10:20:59 -0800191 << self->parser->get().body();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530192
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530193 unsigned int respCode = self->parser->get().result_int();
194 BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: "
195 << respCode;
196
197 // 2XX response is considered to be successful
198 if ((respCode < 200) || (respCode >= 300))
199 {
200 // The listener failed to receive the Sent-Event
Sunitha Harish7adb85a2021-10-26 03:10:04 -0500201 BMCWEB_LOG_ERROR
202 << "recvMessage() Listener Failed to "
203 "receive Sent-Event. Header Response Code: "
204 << respCode;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530205 self->state = ConnState::recvFailed;
206 self->handleConnState();
207 return;
208 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530209
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530210 // Send is successful, Lets remove data from queue
211 // check for next request data in queue.
Sunitha Harish7de9f812021-08-24 02:50:30 -0500212 if (!self->requestDataQueue.empty())
213 {
214 self->requestDataQueue.pop_front();
215 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530216 self->state = ConnState::idle;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530217
218 // Keep the connection alive if server supports it
219 // Else close the connection
220 BMCWEB_LOG_DEBUG << "recvMessage() keepalive : "
221 << self->parser->keep_alive();
222 if (!self->parser->keep_alive())
223 {
224 // Abort the connection since server is not keep-alive
225 // enabled
226 self->state = ConnState::abortConnection;
227 }
228
229 self->handleConnState();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530230 });
231 }
232
233 void doClose()
234 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530235 state = ConnState::closeInProgress;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530236 boost::beast::error_code ec;
237 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530238 conn.close();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530239
AppaRao Pulibd030d02020-03-20 03:34:29 +0530240 // not_connected happens sometimes so don't bother reporting it.
241 if (ec && ec != boost::beast::errc::not_connected)
242 {
243 BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message();
244 return;
245 }
246 BMCWEB_LOG_DEBUG << "Connection closed gracefully";
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530247 if ((state != ConnState::suspended) && (state != ConnState::terminated))
248 {
249 state = ConnState::closed;
250 handleConnState();
251 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530252 }
253
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530254 void waitAndRetry()
AppaRao Pulibd030d02020-03-20 03:34:29 +0530255 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530256 if (retryCount >= maxRetryAttempts)
257 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530258 BMCWEB_LOG_ERROR << "Maximum number of retries reached.";
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530259
260 // Clear queue.
261 while (!requestDataQueue.empty())
262 {
Sunitha Harish7de9f812021-08-24 02:50:30 -0500263 requestDataQueue.pop_front();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530264 }
265
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530266 BMCWEB_LOG_DEBUG << "Retry policy: " << retryPolicyAction;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530267 if (retryPolicyAction == "TerminateAfterRetries")
268 {
269 // TODO: delete subscription
270 state = ConnState::terminated;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530271 }
Ed Tanous3174e4d2020-10-07 11:41:22 -0700272 if (retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530273 {
274 state = ConnState::suspended;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530275 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530276 // Reset the retrycount to zero so that client can try connecting
277 // again if needed
Ed Tanous3174e4d2020-10-07 11:41:22 -0700278 retryCount = 0;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530279 handleConnState();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530280 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530281 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530282
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530283 if (runningTimer)
284 {
285 BMCWEB_LOG_DEBUG << "Retry timer is already running.";
286 return;
287 }
288 runningTimer = true;
289
290 retryCount++;
291
292 BMCWEB_LOG_DEBUG << "Attempt retry after " << retryIntervalSecs
293 << " seconds. RetryCount = " << retryCount;
294 timer.expires_after(std::chrono::seconds(retryIntervalSecs));
295 timer.async_wait(
296 [self = shared_from_this()](const boost::system::error_code ec) {
297 if (ec == boost::asio::error::operation_aborted)
298 {
299 BMCWEB_LOG_DEBUG
300 << "async_wait failed since the operation is aborted"
301 << ec.message();
302 }
303 else if (ec)
304 {
305 BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message();
306 // Ignore the error and continue the retry loop to attempt
307 // sending the event as per the retry policy
308 }
309 self->runningTimer = false;
310
311 // Lets close connection and start from resolve.
312 self->doClose();
313 });
Ayushi Smritife44eb02020-05-15 15:24:45 +0530314 }
315
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530316 void handleConnState()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530317 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530318 switch (state)
319 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530320 case ConnState::resolveInProgress:
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530321 case ConnState::connectInProgress:
322 case ConnState::sendInProgress:
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530323 case ConnState::recvInProgress:
324 case ConnState::closeInProgress:
325 {
326 BMCWEB_LOG_DEBUG << "Async operation is already in progress";
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530327 break;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530328 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530329 case ConnState::initialized:
330 case ConnState::closed:
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530331 {
332 if (requestDataQueue.empty())
333 {
334 BMCWEB_LOG_DEBUG << "requestDataQueue is empty";
335 return;
336 }
337 doResolve();
338 break;
339 }
340 case ConnState::suspended:
341 case ConnState::terminated:
342 {
343 doClose();
344 break;
345 }
346 case ConnState::resolveFailed:
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530347 case ConnState::connectFailed:
348 case ConnState::sendFailed:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530349 case ConnState::recvFailed:
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530350 case ConnState::retry:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530351 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530352 // In case of failures during connect and handshake
353 // the retry policy will be applied
354 waitAndRetry();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530355 break;
356 }
357 case ConnState::connected:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530358 case ConnState::idle:
359 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530360 // State idle means, previous attempt is successful
361 // State connected means, client connection is established
362 // successfully
363 if (requestDataQueue.empty())
364 {
365 BMCWEB_LOG_DEBUG << "requestDataQueue is empty";
366 return;
367 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530368 std::string data = requestDataQueue.front();
369 sendMessage(data);
370 break;
371 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530372 case ConnState::abortConnection:
373 {
374 // Server did not want to keep alive the session
375 doClose();
376 break;
377 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530378 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530379 }
380
381 public:
Ayushi Smritife44eb02020-05-15 15:24:45 +0530382 explicit HttpClient(boost::asio::io_context& ioc, const std::string& id,
383 const std::string& destIP, const std::string& destPort,
Ed Tanous4da04452021-09-08 19:57:44 -0700384 const std::string& destUri,
385 const boost::beast::http::fields& httpHeader) :
AppaRao Pulibd030d02020-03-20 03:34:29 +0530386 conn(ioc),
Ed Tanous4da04452021-09-08 19:57:44 -0700387 timer(ioc),
388 req(boost::beast::http::verb::post, destUri, 11, "", httpHeader),
Ed Tanous84b35602021-09-08 20:06:32 -0700389 subId(id), host(destIP), port(destPort)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530390 {
Ed Tanous4da04452021-09-08 19:57:44 -0700391 req.set(boost::beast::http::field::host, host);
392 req.keep_alive(true);
AppaRao Pulibd030d02020-03-20 03:34:29 +0530393 }
394
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530395 void sendData(const std::string& data)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530396 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530397 if ((state == ConnState::suspended) || (state == ConnState::terminated))
AppaRao Pulibd030d02020-03-20 03:34:29 +0530398 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530399 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530400 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530401
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530402 if (requestDataQueue.size() <= maxRequestQueueSize)
403 {
Sunitha Harish7de9f812021-08-24 02:50:30 -0500404 requestDataQueue.push_back(data);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530405 handleConnState();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530406 }
407 else
408 {
409 BMCWEB_LOG_ERROR << "Request queue is full. So ignoring data.";
410 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530411 }
412
Ayushi Smritife44eb02020-05-15 15:24:45 +0530413 void setRetryConfig(const uint32_t retryAttempts,
414 const uint32_t retryTimeoutInterval)
415 {
416 maxRetryAttempts = retryAttempts;
417 retryIntervalSecs = retryTimeoutInterval;
418 }
419
420 void setRetryPolicy(const std::string& retryPolicy)
421 {
422 retryPolicyAction = retryPolicy;
423 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530424};
425
426} // namespace crow