blob: 5f352d3674ae548c7f510861f0351121b1ec3212 [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 {
123 BMCWEB_LOG_ERROR << "Connect " << endpoint
124 << " failed: " << ec.message();
125 self->state = ConnState::connectFailed;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530126 self->handleConnState();
Sunitha Harish29a82b02021-02-18 15:54:16 +0530127 return;
128 }
Sunitha Harish29a82b02021-02-18 15:54:16 +0530129 BMCWEB_LOG_DEBUG << "Connected to: " << endpoint;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530130 self->state = ConnState::connected;
131 self->handleConnState();
Sunitha Harish29a82b02021-02-18 15:54:16 +0530132 });
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530133 }
134
135 void sendMessage(const std::string& data)
136 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530137 state = ConnState::sendInProgress;
138
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530139 req.body() = data;
140 req.prepare_payload();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530141
142 // Set a timeout on the operation
143 conn.expires_after(std::chrono::seconds(30));
144
145 // Send the HTTP request to the remote host
146 boost::beast::http::async_write(
147 conn, req,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530148 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530149 const std::size_t& bytesTransferred) {
150 if (ec)
151 {
152 BMCWEB_LOG_ERROR << "sendMessage() failed: "
153 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530154 self->state = ConnState::sendFailed;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530155 self->handleConnState();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530156 return;
157 }
158 BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
159 << bytesTransferred;
160 boost::ignore_unused(bytesTransferred);
161
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530162 self->recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530163 });
164 }
165
166 void recvMessage()
167 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530168 state = ConnState::recvInProgress;
169
170 parser.emplace(std::piecewise_construct, std::make_tuple());
171 parser->body_limit(httpReadBodyLimit);
172
AppaRao Pulibd030d02020-03-20 03:34:29 +0530173 // Receive the HTTP response
174 boost::beast::http::async_read(
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530175 conn, buffer, *parser,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530176 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530177 const std::size_t& bytesTransferred) {
178 if (ec)
179 {
180 BMCWEB_LOG_ERROR << "recvMessage() failed: "
181 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530182 self->state = ConnState::recvFailed;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530183 self->handleConnState();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530184 return;
185 }
186 BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
187 << bytesTransferred;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530188 BMCWEB_LOG_DEBUG << "recvMessage() data: "
189 << self->parser->get();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530190
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530191 unsigned int respCode = self->parser->get().result_int();
192 BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: "
193 << respCode;
194
195 // 2XX response is considered to be successful
196 if ((respCode < 200) || (respCode >= 300))
197 {
198 // The listener failed to receive the Sent-Event
Sunitha Harish7adb85a2021-10-26 03:10:04 -0500199 BMCWEB_LOG_ERROR
200 << "recvMessage() Listener Failed to "
201 "receive Sent-Event. Header Response Code: "
202 << respCode;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530203 self->state = ConnState::recvFailed;
204 self->handleConnState();
205 return;
206 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530207
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530208 // Send is successful, Lets remove data from queue
209 // check for next request data in queue.
Sunitha Harish7de9f812021-08-24 02:50:30 -0500210 if (!self->requestDataQueue.empty())
211 {
212 self->requestDataQueue.pop_front();
213 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530214 self->state = ConnState::idle;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530215
216 // Keep the connection alive if server supports it
217 // Else close the connection
218 BMCWEB_LOG_DEBUG << "recvMessage() keepalive : "
219 << self->parser->keep_alive();
220 if (!self->parser->keep_alive())
221 {
222 // Abort the connection since server is not keep-alive
223 // enabled
224 self->state = ConnState::abortConnection;
225 }
226
227 self->handleConnState();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530228 });
229 }
230
231 void doClose()
232 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530233 state = ConnState::closeInProgress;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530234 boost::beast::error_code ec;
235 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530236 conn.close();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530237
AppaRao Pulibd030d02020-03-20 03:34:29 +0530238 // not_connected happens sometimes so don't bother reporting it.
239 if (ec && ec != boost::beast::errc::not_connected)
240 {
241 BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message();
242 return;
243 }
244 BMCWEB_LOG_DEBUG << "Connection closed gracefully";
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530245 if ((state != ConnState::suspended) && (state != ConnState::terminated))
246 {
247 state = ConnState::closed;
248 handleConnState();
249 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530250 }
251
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530252 void waitAndRetry()
AppaRao Pulibd030d02020-03-20 03:34:29 +0530253 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530254 if (retryCount >= maxRetryAttempts)
255 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530256 BMCWEB_LOG_ERROR << "Maximum number of retries reached.";
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530257
258 // Clear queue.
259 while (!requestDataQueue.empty())
260 {
Sunitha Harish7de9f812021-08-24 02:50:30 -0500261 requestDataQueue.pop_front();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530262 }
263
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530264 BMCWEB_LOG_DEBUG << "Retry policy: " << retryPolicyAction;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530265 if (retryPolicyAction == "TerminateAfterRetries")
266 {
267 // TODO: delete subscription
268 state = ConnState::terminated;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530269 }
Ed Tanous3174e4d2020-10-07 11:41:22 -0700270 if (retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530271 {
272 state = ConnState::suspended;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530273 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530274 // Reset the retrycount to zero so that client can try connecting
275 // again if needed
Ed Tanous3174e4d2020-10-07 11:41:22 -0700276 retryCount = 0;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530277 handleConnState();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530278 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530279 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530280
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530281 if (runningTimer)
282 {
283 BMCWEB_LOG_DEBUG << "Retry timer is already running.";
284 return;
285 }
286 runningTimer = true;
287
288 retryCount++;
289
290 BMCWEB_LOG_DEBUG << "Attempt retry after " << retryIntervalSecs
291 << " seconds. RetryCount = " << retryCount;
292 timer.expires_after(std::chrono::seconds(retryIntervalSecs));
293 timer.async_wait(
294 [self = shared_from_this()](const boost::system::error_code ec) {
295 if (ec == boost::asio::error::operation_aborted)
296 {
297 BMCWEB_LOG_DEBUG
298 << "async_wait failed since the operation is aborted"
299 << ec.message();
300 }
301 else if (ec)
302 {
303 BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message();
304 // Ignore the error and continue the retry loop to attempt
305 // sending the event as per the retry policy
306 }
307 self->runningTimer = false;
308
309 // Lets close connection and start from resolve.
310 self->doClose();
311 });
Ayushi Smritife44eb02020-05-15 15:24:45 +0530312 }
313
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530314 void handleConnState()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530315 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530316 switch (state)
317 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530318 case ConnState::resolveInProgress:
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530319 case ConnState::connectInProgress:
320 case ConnState::sendInProgress:
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530321 case ConnState::recvInProgress:
322 case ConnState::closeInProgress:
323 {
324 BMCWEB_LOG_DEBUG << "Async operation is already in progress";
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530325 break;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530326 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530327 case ConnState::initialized:
328 case ConnState::closed:
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530329 {
330 if (requestDataQueue.empty())
331 {
332 BMCWEB_LOG_DEBUG << "requestDataQueue is empty";
333 return;
334 }
335 doResolve();
336 break;
337 }
338 case ConnState::suspended:
339 case ConnState::terminated:
340 {
341 doClose();
342 break;
343 }
344 case ConnState::resolveFailed:
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530345 case ConnState::connectFailed:
346 case ConnState::sendFailed:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530347 case ConnState::recvFailed:
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530348 case ConnState::retry:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530349 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530350 // In case of failures during connect and handshake
351 // the retry policy will be applied
352 waitAndRetry();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530353 break;
354 }
355 case ConnState::connected:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530356 case ConnState::idle:
357 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530358 // State idle means, previous attempt is successful
359 // State connected means, client connection is established
360 // successfully
361 if (requestDataQueue.empty())
362 {
363 BMCWEB_LOG_DEBUG << "requestDataQueue is empty";
364 return;
365 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530366 std::string data = requestDataQueue.front();
367 sendMessage(data);
368 break;
369 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530370 case ConnState::abortConnection:
371 {
372 // Server did not want to keep alive the session
373 doClose();
374 break;
375 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530376 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530377 }
378
379 public:
Ayushi Smritife44eb02020-05-15 15:24:45 +0530380 explicit HttpClient(boost::asio::io_context& ioc, const std::string& id,
381 const std::string& destIP, const std::string& destPort,
Ed Tanous4da04452021-09-08 19:57:44 -0700382 const std::string& destUri,
383 const boost::beast::http::fields& httpHeader) :
AppaRao Pulibd030d02020-03-20 03:34:29 +0530384 conn(ioc),
Ed Tanous4da04452021-09-08 19:57:44 -0700385 timer(ioc),
386 req(boost::beast::http::verb::post, destUri, 11, "", httpHeader),
Ed Tanous84b35602021-09-08 20:06:32 -0700387 subId(id), host(destIP), port(destPort)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530388 {
Ed Tanous4da04452021-09-08 19:57:44 -0700389 req.set(boost::beast::http::field::host, host);
390 req.keep_alive(true);
AppaRao Pulibd030d02020-03-20 03:34:29 +0530391 }
392
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530393 void sendData(const std::string& data)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530394 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530395 if ((state == ConnState::suspended) || (state == ConnState::terminated))
AppaRao Pulibd030d02020-03-20 03:34:29 +0530396 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530397 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530398 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530399
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530400 if (requestDataQueue.size() <= maxRequestQueueSize)
401 {
Sunitha Harish7de9f812021-08-24 02:50:30 -0500402 requestDataQueue.push_back(data);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530403 handleConnState();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530404 }
405 else
406 {
407 BMCWEB_LOG_ERROR << "Request queue is full. So ignoring data.";
408 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530409 }
410
Ayushi Smritife44eb02020-05-15 15:24:45 +0530411 void setRetryConfig(const uint32_t retryAttempts,
412 const uint32_t retryTimeoutInterval)
413 {
414 maxRetryAttempts = retryAttempts;
415 retryIntervalSecs = retryTimeoutInterval;
416 }
417
418 void setRetryPolicy(const std::string& retryPolicy)
419 {
420 retryPolicyAction = retryPolicy;
421 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530422};
423
424} // namespace crow