blob: 0c9e38728038f28dd44deaa808a56973d2adcee5 [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>
Sunitha Harish29a82b02021-02-18 15:54:16 +053024#include <include/async_resolve.hpp>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050025
AppaRao Pulibd030d02020-03-20 03:34:29 +053026#include <cstdlib>
27#include <functional>
28#include <iostream>
29#include <memory>
AppaRao Puli2a5689a2020-04-29 15:24:31 +053030#include <queue>
AppaRao Pulibd030d02020-03-20 03:34:29 +053031#include <string>
32
33namespace crow
34{
35
AppaRao Puli2a5689a2020-04-29 15:24:31 +053036static constexpr uint8_t maxRequestQueueSize = 50;
Sunitha Harish7de9f812021-08-24 02:50:30 -050037static constexpr unsigned int httpReadBodyLimit = 8192;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053038
AppaRao Pulibd030d02020-03-20 03:34:29 +053039enum class ConnState
40{
AppaRao Puli2a5689a2020-04-29 15:24:31 +053041 initialized,
Sunitha Harish29a82b02021-02-18 15:54:16 +053042 resolveInProgress,
43 resolveFailed,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053044 connectInProgress,
45 connectFailed,
AppaRao Pulibd030d02020-03-20 03:34:29 +053046 connected,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053047 sendInProgress,
48 sendFailed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053049 recvInProgress,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053050 recvFailed,
51 idle,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053052 closeInProgress,
Ayushi Smritife44eb02020-05-15 15:24:45 +053053 closed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053054 suspended,
55 terminated,
56 abortConnection,
57 retry
AppaRao Pulibd030d02020-03-20 03:34:29 +053058};
59
60class HttpClient : public std::enable_shared_from_this<HttpClient>
61{
62 private:
Sunitha Harish29a82b02021-02-18 15:54:16 +053063 crow::async_resolve::Resolver resolver;
AppaRao Pulibd030d02020-03-20 03:34:29 +053064 boost::beast::tcp_stream conn;
Ayushi Smritife44eb02020-05-15 15:24:45 +053065 boost::asio::steady_timer timer;
Sunitha Harish7de9f812021-08-24 02:50:30 -050066 boost::beast::flat_static_buffer<httpReadBodyLimit> buffer;
AppaRao Pulibd030d02020-03-20 03:34:29 +053067 boost::beast::http::request<boost::beast::http::string_body> req;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053068 std::optional<
69 boost::beast::http::response_parser<boost::beast::http::string_body>>
70 parser;
Sunitha Harish7de9f812021-08-24 02:50:30 -050071 boost::circular_buffer_space_optimized<std::string> requestDataQueue{};
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053072
Ed Tanous84b35602021-09-08 20:06:32 -070073 ConnState state = ConnState::initialized;
74
Ayushi Smritife44eb02020-05-15 15:24:45 +053075 std::string subId;
AppaRao Pulibd030d02020-03-20 03:34:29 +053076 std::string host;
77 std::string port;
Ed Tanous84b35602021-09-08 20:06:32 -070078 uint32_t retryCount = 0;
79 uint32_t maxRetryAttempts = 5;
80 uint32_t retryIntervalSecs = 0;
81 std::string retryPolicyAction = "TerminateAfterRetries";
82 bool runningTimer = false;
AppaRao Pulibd030d02020-03-20 03:34:29 +053083
Sunitha Harish29a82b02021-02-18 15:54:16 +053084 void doResolve()
85 {
Sunitha Harish29a82b02021-02-18 15:54:16 +053086 state = ConnState::resolveInProgress;
Sunitha Harish29a82b02021-02-18 15:54:16 +053087 BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":" << port;
88
89 auto respHandler =
90 [self(shared_from_this())](
91 const boost::beast::error_code ec,
92 const std::vector<boost::asio::ip::tcp::endpoint>&
93 endpointList) {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053094 if (ec || (endpointList.size() == 0))
Sunitha Harish29a82b02021-02-18 15:54:16 +053095 {
96 BMCWEB_LOG_ERROR << "Resolve failed: " << ec.message();
97 self->state = ConnState::resolveFailed;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053098 self->handleConnState();
Sunitha Harish29a82b02021-02-18 15:54:16 +053099 return;
100 }
101 BMCWEB_LOG_DEBUG << "Resolved";
102 self->doConnect(endpointList);
103 };
104 resolver.asyncResolve(host, port, std::move(respHandler));
105 }
106
107 void doConnect(
108 const std::vector<boost::asio::ip::tcp::endpoint>& endpointList)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530109 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530110 state = ConnState::connectInProgress;
111
112 BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":" << port;
Sunitha Harish29a82b02021-02-18 15:54:16 +0530113
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530114 conn.expires_after(std::chrono::seconds(30));
Sunitha Harish29a82b02021-02-18 15:54:16 +0530115 conn.async_connect(
116 endpointList, [self(shared_from_this())](
117 const boost::beast::error_code ec,
118 const boost::asio::ip::tcp::endpoint& endpoint) {
119 if (ec)
120 {
121 BMCWEB_LOG_ERROR << "Connect " << endpoint
122 << " failed: " << ec.message();
123 self->state = ConnState::connectFailed;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530124 self->handleConnState();
Sunitha Harish29a82b02021-02-18 15:54:16 +0530125 return;
126 }
Sunitha Harish29a82b02021-02-18 15:54:16 +0530127 BMCWEB_LOG_DEBUG << "Connected to: " << endpoint;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530128 self->state = ConnState::connected;
129 self->handleConnState();
Sunitha Harish29a82b02021-02-18 15:54:16 +0530130 });
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530131 }
132
133 void sendMessage(const std::string& data)
134 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530135 state = ConnState::sendInProgress;
136
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530137 req.body() = data;
138 req.prepare_payload();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530139
140 // Set a timeout on the operation
141 conn.expires_after(std::chrono::seconds(30));
142
143 // Send the HTTP request to the remote host
144 boost::beast::http::async_write(
145 conn, req,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530146 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530147 const std::size_t& bytesTransferred) {
148 if (ec)
149 {
150 BMCWEB_LOG_ERROR << "sendMessage() failed: "
151 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530152 self->state = ConnState::sendFailed;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530153 self->handleConnState();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530154 return;
155 }
156 BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
157 << bytesTransferred;
158 boost::ignore_unused(bytesTransferred);
159
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530160 self->recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530161 });
162 }
163
164 void recvMessage()
165 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530166 state = ConnState::recvInProgress;
167
168 parser.emplace(std::piecewise_construct, std::make_tuple());
169 parser->body_limit(httpReadBodyLimit);
170
171 // Check only for the response header
172 parser->skip(true);
173
AppaRao Pulibd030d02020-03-20 03:34:29 +0530174 // Receive the HTTP response
175 boost::beast::http::async_read(
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530176 conn, buffer, *parser,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530177 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530178 const std::size_t& bytesTransferred) {
179 if (ec)
180 {
181 BMCWEB_LOG_ERROR << "recvMessage() failed: "
182 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530183 self->state = ConnState::recvFailed;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530184 self->handleConnState();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530185 return;
186 }
187 BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
188 << bytesTransferred;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530189 BMCWEB_LOG_DEBUG << "recvMessage() data: "
190 << self->parser->get();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530191
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530192 unsigned int respCode = self->parser->get().result_int();
193 BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: "
194 << respCode;
195
196 // 2XX response is considered to be successful
197 if ((respCode < 200) || (respCode >= 300))
198 {
199 // The listener failed to receive the Sent-Event
Sunitha Harish7adb85a2021-10-26 03:10:04 -0500200 BMCWEB_LOG_ERROR
201 << "recvMessage() Listener Failed to "
202 "receive Sent-Event. Header Response Code: "
203 << respCode;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530204 self->state = ConnState::recvFailed;
205 self->handleConnState();
206 return;
207 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530208
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530209 // Send is successful, Lets remove data from queue
210 // check for next request data in queue.
Sunitha Harish7de9f812021-08-24 02:50:30 -0500211 if (!self->requestDataQueue.empty())
212 {
213 self->requestDataQueue.pop_front();
214 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530215 self->state = ConnState::idle;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530216
217 // Keep the connection alive if server supports it
218 // Else close the connection
219 BMCWEB_LOG_DEBUG << "recvMessage() keepalive : "
220 << self->parser->keep_alive();
221 if (!self->parser->keep_alive())
222 {
223 // Abort the connection since server is not keep-alive
224 // enabled
225 self->state = ConnState::abortConnection;
226 }
227
228 self->handleConnState();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530229 });
230 }
231
232 void doClose()
233 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530234 state = ConnState::closeInProgress;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530235 boost::beast::error_code ec;
236 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530237 conn.close();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530238
AppaRao Pulibd030d02020-03-20 03:34:29 +0530239 // not_connected happens sometimes so don't bother reporting it.
240 if (ec && ec != boost::beast::errc::not_connected)
241 {
242 BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message();
243 return;
244 }
245 BMCWEB_LOG_DEBUG << "Connection closed gracefully";
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530246 if ((state != ConnState::suspended) && (state != ConnState::terminated))
247 {
248 state = ConnState::closed;
249 handleConnState();
250 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530251 }
252
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530253 void waitAndRetry()
AppaRao Pulibd030d02020-03-20 03:34:29 +0530254 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530255 if (retryCount >= maxRetryAttempts)
256 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530257 BMCWEB_LOG_ERROR << "Maximum number of retries reached.";
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530258
259 // Clear queue.
260 while (!requestDataQueue.empty())
261 {
Sunitha Harish7de9f812021-08-24 02:50:30 -0500262 requestDataQueue.pop_front();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530263 }
264
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530265 BMCWEB_LOG_DEBUG << "Retry policy: " << retryPolicyAction;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530266 if (retryPolicyAction == "TerminateAfterRetries")
267 {
268 // TODO: delete subscription
269 state = ConnState::terminated;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530270 }
Ed Tanous3174e4d2020-10-07 11:41:22 -0700271 if (retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530272 {
273 state = ConnState::suspended;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530274 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530275 // Reset the retrycount to zero so that client can try connecting
276 // again if needed
Ed Tanous3174e4d2020-10-07 11:41:22 -0700277 retryCount = 0;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530278 handleConnState();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530279 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530280 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530281
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530282 if (runningTimer)
283 {
284 BMCWEB_LOG_DEBUG << "Retry timer is already running.";
285 return;
286 }
287 runningTimer = true;
288
289 retryCount++;
290
291 BMCWEB_LOG_DEBUG << "Attempt retry after " << retryIntervalSecs
292 << " seconds. RetryCount = " << retryCount;
293 timer.expires_after(std::chrono::seconds(retryIntervalSecs));
294 timer.async_wait(
295 [self = shared_from_this()](const boost::system::error_code ec) {
296 if (ec == boost::asio::error::operation_aborted)
297 {
298 BMCWEB_LOG_DEBUG
299 << "async_wait failed since the operation is aborted"
300 << ec.message();
301 }
302 else if (ec)
303 {
304 BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message();
305 // Ignore the error and continue the retry loop to attempt
306 // sending the event as per the retry policy
307 }
308 self->runningTimer = false;
309
310 // Lets close connection and start from resolve.
311 self->doClose();
312 });
Ayushi Smritife44eb02020-05-15 15:24:45 +0530313 return;
314 }
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
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530412 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530413 }
414
Ayushi Smritife44eb02020-05-15 15:24:45 +0530415 void setRetryConfig(const uint32_t retryAttempts,
416 const uint32_t retryTimeoutInterval)
417 {
418 maxRetryAttempts = retryAttempts;
419 retryIntervalSecs = retryTimeoutInterval;
420 }
421
422 void setRetryPolicy(const std::string& retryPolicy)
423 {
424 retryPolicyAction = retryPolicy;
425 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530426};
427
428} // namespace crow