blob: b1359991d54178e8f410aaff9d38c801ec42d192 [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,
49 recvFailed,
50 idle,
51 suspended,
Ayushi Smritife44eb02020-05-15 15:24:45 +053052 closed,
53 terminated
AppaRao Pulibd030d02020-03-20 03:34:29 +053054};
55
56class HttpClient : public std::enable_shared_from_this<HttpClient>
57{
58 private:
Sunitha Harish29a82b02021-02-18 15:54:16 +053059 crow::async_resolve::Resolver resolver;
AppaRao Pulibd030d02020-03-20 03:34:29 +053060 boost::beast::tcp_stream conn;
Ayushi Smritife44eb02020-05-15 15:24:45 +053061 boost::asio::steady_timer timer;
Sunitha Harish7de9f812021-08-24 02:50:30 -050062 boost::beast::flat_static_buffer<httpReadBodyLimit> buffer;
AppaRao Pulibd030d02020-03-20 03:34:29 +053063 boost::beast::http::request<boost::beast::http::string_body> req;
64 boost::beast::http::response<boost::beast::http::string_body> res;
AppaRao Pulibd030d02020-03-20 03:34:29 +053065 std::vector<std::pair<std::string, std::string>> headers;
Sunitha Harish7de9f812021-08-24 02:50:30 -050066 boost::circular_buffer_space_optimized<std::string> requestDataQueue{};
AppaRao Pulibd030d02020-03-20 03:34:29 +053067 ConnState state;
Ayushi Smritife44eb02020-05-15 15:24:45 +053068 std::string subId;
AppaRao Pulibd030d02020-03-20 03:34:29 +053069 std::string host;
70 std::string port;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053071 std::string uri;
Ayushi Smritife44eb02020-05-15 15:24:45 +053072 uint32_t retryCount;
73 uint32_t maxRetryAttempts;
74 uint32_t retryIntervalSecs;
75 std::string retryPolicyAction;
76 bool runningTimer;
AppaRao Pulibd030d02020-03-20 03:34:29 +053077
Sunitha Harish29a82b02021-02-18 15:54:16 +053078 void doResolve()
79 {
80 if (state == ConnState::resolveInProgress)
81 {
82 return;
83 }
84 state = ConnState::resolveInProgress;
85
86 BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":" << port;
87
88 auto respHandler =
89 [self(shared_from_this())](
90 const boost::beast::error_code ec,
91 const std::vector<boost::asio::ip::tcp::endpoint>&
92 endpointList) {
93 if (ec)
94 {
95 BMCWEB_LOG_ERROR << "Resolve failed: " << ec.message();
96 self->state = ConnState::resolveFailed;
97 self->checkQueue();
98 return;
99 }
100 BMCWEB_LOG_DEBUG << "Resolved";
101 self->doConnect(endpointList);
102 };
103 resolver.asyncResolve(host, port, std::move(respHandler));
104 }
105
106 void doConnect(
107 const std::vector<boost::asio::ip::tcp::endpoint>& endpointList)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530108 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530109 if (state == ConnState::connectInProgress)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530110 {
AppaRao Pulibd030d02020-03-20 03:34:29 +0530111 return;
112 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530113 state = ConnState::connectInProgress;
114
115 BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":" << port;
Sunitha Harish29a82b02021-02-18 15:54:16 +0530116
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530117 conn.expires_after(std::chrono::seconds(30));
Sunitha Harish29a82b02021-02-18 15:54:16 +0530118 conn.async_connect(
119 endpointList, [self(shared_from_this())](
120 const boost::beast::error_code ec,
121 const boost::asio::ip::tcp::endpoint& endpoint) {
122 if (ec)
123 {
124 BMCWEB_LOG_ERROR << "Connect " << endpoint
125 << " failed: " << ec.message();
126 self->state = ConnState::connectFailed;
127 self->checkQueue();
128 return;
129 }
130 self->state = ConnState::connected;
131 BMCWEB_LOG_DEBUG << "Connected to: " << endpoint;
Ed Tanousb00dcc22021-02-23 12:52:50 -0800132
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530133 self->checkQueue();
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 {
139 if (state == ConnState::sendInProgress)
140 {
141 return;
142 }
143 state = ConnState::sendInProgress;
144
145 BMCWEB_LOG_DEBUG << __FUNCTION__ << "(): " << host << ":" << port;
146
147 req.version(static_cast<int>(11)); // HTTP 1.1
148 req.target(uri);
149 req.method(boost::beast::http::verb::post);
150
151 // Set headers
152 for (const auto& [key, value] : headers)
153 {
154 req.set(key, value);
155 }
156 req.set(boost::beast::http::field::host, host);
157 req.keep_alive(true);
158
159 req.body() = data;
160 req.prepare_payload();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530161
162 // Set a timeout on the operation
163 conn.expires_after(std::chrono::seconds(30));
164
165 // Send the HTTP request to the remote host
166 boost::beast::http::async_write(
167 conn, req,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530168 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530169 const std::size_t& bytesTransferred) {
170 if (ec)
171 {
172 BMCWEB_LOG_ERROR << "sendMessage() failed: "
173 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530174 self->state = ConnState::sendFailed;
175 self->checkQueue();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530176 return;
177 }
178 BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
179 << bytesTransferred;
180 boost::ignore_unused(bytesTransferred);
181
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530182 self->recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530183 });
184 }
185
186 void recvMessage()
187 {
AppaRao Pulibd030d02020-03-20 03:34:29 +0530188 // Receive the HTTP response
189 boost::beast::http::async_read(
190 conn, buffer, res,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530191 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530192 const std::size_t& bytesTransferred) {
193 if (ec)
194 {
195 BMCWEB_LOG_ERROR << "recvMessage() failed: "
196 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530197 self->state = ConnState::recvFailed;
198 self->checkQueue();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530199 return;
200 }
201 BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
202 << bytesTransferred;
203 boost::ignore_unused(bytesTransferred);
204
205 // Discard received data. We are not interested.
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530206 BMCWEB_LOG_DEBUG << "recvMessage() data: " << self->res;
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;
215 self->checkQueue();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530216 });
217 }
218
219 void doClose()
220 {
221 boost::beast::error_code ec;
222 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
223
224 state = ConnState::closed;
225 // not_connected happens sometimes so don't bother reporting it.
226 if (ec && ec != boost::beast::errc::not_connected)
227 {
228 BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message();
229 return;
230 }
231 BMCWEB_LOG_DEBUG << "Connection closed gracefully";
232 }
233
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530234 void checkQueue(const bool newRecord = false)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530235 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530236 if (requestDataQueue.empty())
237 {
238 // TODO: Having issue in keeping connection alive. So lets close if
Gunnar Millscaa3ce32020-07-08 14:46:53 -0500239 // nothing to be transferred.
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530240 doClose();
241
242 BMCWEB_LOG_DEBUG << "requestDataQueue is empty\n";
243 return;
244 }
245
246 if (retryCount >= maxRetryAttempts)
247 {
248 BMCWEB_LOG_ERROR << "Maximum number of retries is reached.";
249
250 // Clear queue.
251 while (!requestDataQueue.empty())
252 {
Sunitha Harish7de9f812021-08-24 02:50:30 -0500253 requestDataQueue.pop_front();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530254 }
255
Ayushi Smritife44eb02020-05-15 15:24:45 +0530256 BMCWEB_LOG_DEBUG << "Retry policy is set to " << retryPolicyAction;
257 if (retryPolicyAction == "TerminateAfterRetries")
258 {
259 // TODO: delete subscription
260 state = ConnState::terminated;
261 return;
262 }
Ed Tanous3174e4d2020-10-07 11:41:22 -0700263 if (retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530264 {
265 state = ConnState::suspended;
266 return;
267 }
Ed Tanous3174e4d2020-10-07 11:41:22 -0700268 // keep retrying, reset count and continue.
269 retryCount = 0;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530270 }
271
272 if ((state == ConnState::connectFailed) ||
273 (state == ConnState::sendFailed) ||
274 (state == ConnState::recvFailed))
275 {
276 if (newRecord)
277 {
278 // We are already running async wait and retry.
279 // Since record is added to queue, it gets the
280 // turn in FIFO.
281 return;
282 }
283
Ayushi Smritife44eb02020-05-15 15:24:45 +0530284 if (runningTimer)
285 {
286 BMCWEB_LOG_DEBUG << "Retry timer is already running.";
287 return;
288 }
289 runningTimer = true;
290
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530291 retryCount++;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530292
293 BMCWEB_LOG_DEBUG << "Attempt retry after " << retryIntervalSecs
294 << " seconds. RetryCount = " << retryCount;
295 timer.expires_after(std::chrono::seconds(retryIntervalSecs));
Ed Tanouscb13a392020-07-25 19:02:03 +0000296 timer.async_wait(
297 [self = shared_from_this()](const boost::system::error_code&) {
298 self->runningTimer = false;
299 self->connStateCheck();
300 });
Ayushi Smritife44eb02020-05-15 15:24:45 +0530301 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530302 }
Ed Tanous3174e4d2020-10-07 11:41:22 -0700303 // reset retry count.
304 retryCount = 0;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530305 connStateCheck();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530306
Ayushi Smritife44eb02020-05-15 15:24:45 +0530307 return;
308 }
309
310 void connStateCheck()
311 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530312 switch (state)
313 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530314 case ConnState::resolveInProgress:
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530315 case ConnState::connectInProgress:
316 case ConnState::sendInProgress:
317 case ConnState::suspended:
Ayushi Smritife44eb02020-05-15 15:24:45 +0530318 case ConnState::terminated:
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530319 // do nothing
320 break;
321 case ConnState::initialized:
322 case ConnState::closed:
323 case ConnState::connectFailed:
324 case ConnState::sendFailed:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530325 case ConnState::recvFailed:
Sunitha Harish29a82b02021-02-18 15:54:16 +0530326 case ConnState::resolveFailed:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530327 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530328 doResolve();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530329 break;
330 }
331 case ConnState::connected:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530332 case ConnState::idle:
333 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530334 std::string data = requestDataQueue.front();
335 sendMessage(data);
336 break;
337 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530338 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530339 }
340
341 public:
Ayushi Smritife44eb02020-05-15 15:24:45 +0530342 explicit HttpClient(boost::asio::io_context& ioc, const std::string& id,
343 const std::string& destIP, const std::string& destPort,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530344 const std::string& destUri) :
AppaRao Pulibd030d02020-03-20 03:34:29 +0530345 conn(ioc),
Ayushi Smritife44eb02020-05-15 15:24:45 +0530346 timer(ioc), subId(id), host(destIP), port(destPort), uri(destUri),
Ed Tanousf23b7292020-10-15 09:41:17 -0700347 retryCount(0), maxRetryAttempts(5), retryIntervalSecs(0),
Ayushi Smritife44eb02020-05-15 15:24:45 +0530348 retryPolicyAction("TerminateAfterRetries"), runningTimer(false)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530349 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530350 state = ConnState::initialized;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530351 }
352
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530353 void sendData(const std::string& data)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530354 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530355 if (state == ConnState::suspended)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530356 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530357 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530358 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530359
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530360 if (requestDataQueue.size() <= maxRequestQueueSize)
361 {
Sunitha Harish7de9f812021-08-24 02:50:30 -0500362 requestDataQueue.push_back(data);
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530363 checkQueue(true);
364 }
365 else
366 {
367 BMCWEB_LOG_ERROR << "Request queue is full. So ignoring data.";
368 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530369
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530370 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530371 }
372
373 void setHeaders(
374 const std::vector<std::pair<std::string, std::string>>& httpHeaders)
375 {
376 headers = httpHeaders;
377 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530378
379 void setRetryConfig(const uint32_t retryAttempts,
380 const uint32_t retryTimeoutInterval)
381 {
382 maxRetryAttempts = retryAttempts;
383 retryIntervalSecs = retryTimeoutInterval;
384 }
385
386 void setRetryPolicy(const std::string& retryPolicy)
387 {
388 retryPolicyAction = retryPolicy;
389 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530390};
391
392} // namespace crow