blob: 992ac2bfefef7d422f747c19ed8924301e381bc4 [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;
37
AppaRao Pulibd030d02020-03-20 03:34:29 +053038enum class ConnState
39{
AppaRao Puli2a5689a2020-04-29 15:24:31 +053040 initialized,
Sunitha Harish29a82b02021-02-18 15:54:16 +053041 resolveInProgress,
42 resolveFailed,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053043 connectInProgress,
44 connectFailed,
AppaRao Pulibd030d02020-03-20 03:34:29 +053045 connected,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053046 sendInProgress,
47 sendFailed,
48 recvFailed,
49 idle,
50 suspended,
Ayushi Smritife44eb02020-05-15 15:24:45 +053051 closed,
52 terminated
AppaRao Pulibd030d02020-03-20 03:34:29 +053053};
54
55class HttpClient : public std::enable_shared_from_this<HttpClient>
56{
57 private:
Sunitha Harish29a82b02021-02-18 15:54:16 +053058 crow::async_resolve::Resolver resolver;
AppaRao Pulibd030d02020-03-20 03:34:29 +053059 boost::beast::tcp_stream conn;
Ayushi Smritife44eb02020-05-15 15:24:45 +053060 boost::asio::steady_timer timer;
AppaRao Pulibd030d02020-03-20 03:34:29 +053061 boost::beast::flat_buffer buffer;
62 boost::beast::http::request<boost::beast::http::string_body> req;
63 boost::beast::http::response<boost::beast::http::string_body> res;
AppaRao Pulibd030d02020-03-20 03:34:29 +053064 std::vector<std::pair<std::string, std::string>> headers;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053065 std::queue<std::string> requestDataQueue;
AppaRao Pulibd030d02020-03-20 03:34:29 +053066 ConnState state;
Ayushi Smritife44eb02020-05-15 15:24:45 +053067 std::string subId;
AppaRao Pulibd030d02020-03-20 03:34:29 +053068 std::string host;
69 std::string port;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053070 std::string uri;
Ayushi Smritife44eb02020-05-15 15:24:45 +053071 uint32_t retryCount;
72 uint32_t maxRetryAttempts;
73 uint32_t retryIntervalSecs;
74 std::string retryPolicyAction;
75 bool runningTimer;
AppaRao Pulibd030d02020-03-20 03:34:29 +053076
Sunitha Harish29a82b02021-02-18 15:54:16 +053077 void doResolve()
78 {
79 if (state == ConnState::resolveInProgress)
80 {
81 return;
82 }
83 state = ConnState::resolveInProgress;
84
85 BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":" << port;
86
87 auto respHandler =
88 [self(shared_from_this())](
89 const boost::beast::error_code ec,
90 const std::vector<boost::asio::ip::tcp::endpoint>&
91 endpointList) {
92 if (ec)
93 {
94 BMCWEB_LOG_ERROR << "Resolve failed: " << ec.message();
95 self->state = ConnState::resolveFailed;
96 self->checkQueue();
97 return;
98 }
99 BMCWEB_LOG_DEBUG << "Resolved";
100 self->doConnect(endpointList);
101 };
102 resolver.asyncResolve(host, port, std::move(respHandler));
103 }
104
105 void doConnect(
106 const std::vector<boost::asio::ip::tcp::endpoint>& endpointList)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530107 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530108 if (state == ConnState::connectInProgress)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530109 {
AppaRao Pulibd030d02020-03-20 03:34:29 +0530110 return;
111 }
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;
126 self->checkQueue();
127 return;
128 }
129 self->state = ConnState::connected;
130 BMCWEB_LOG_DEBUG << "Connected to: " << endpoint;
Ed Tanousb00dcc22021-02-23 12:52:50 -0800131
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530132 self->checkQueue();
Sunitha Harish29a82b02021-02-18 15:54:16 +0530133 });
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530134 }
135
136 void sendMessage(const std::string& data)
137 {
138 if (state == ConnState::sendInProgress)
139 {
140 return;
141 }
142 state = ConnState::sendInProgress;
143
144 BMCWEB_LOG_DEBUG << __FUNCTION__ << "(): " << host << ":" << port;
145
146 req.version(static_cast<int>(11)); // HTTP 1.1
147 req.target(uri);
148 req.method(boost::beast::http::verb::post);
149
150 // Set headers
151 for (const auto& [key, value] : headers)
152 {
153 req.set(key, value);
154 }
155 req.set(boost::beast::http::field::host, host);
156 req.keep_alive(true);
157
158 req.body() = data;
159 req.prepare_payload();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530160
161 // Set a timeout on the operation
162 conn.expires_after(std::chrono::seconds(30));
163
164 // Send the HTTP request to the remote host
165 boost::beast::http::async_write(
166 conn, req,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530167 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530168 const std::size_t& bytesTransferred) {
169 if (ec)
170 {
171 BMCWEB_LOG_ERROR << "sendMessage() failed: "
172 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530173 self->state = ConnState::sendFailed;
174 self->checkQueue();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530175 return;
176 }
177 BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
178 << bytesTransferred;
179 boost::ignore_unused(bytesTransferred);
180
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530181 self->recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530182 });
183 }
184
185 void recvMessage()
186 {
AppaRao Pulibd030d02020-03-20 03:34:29 +0530187 // Receive the HTTP response
188 boost::beast::http::async_read(
189 conn, buffer, res,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530190 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530191 const std::size_t& bytesTransferred) {
192 if (ec)
193 {
194 BMCWEB_LOG_ERROR << "recvMessage() failed: "
195 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530196 self->state = ConnState::recvFailed;
197 self->checkQueue();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530198 return;
199 }
200 BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
201 << bytesTransferred;
202 boost::ignore_unused(bytesTransferred);
203
204 // Discard received data. We are not interested.
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530205 BMCWEB_LOG_DEBUG << "recvMessage() data: " << self->res;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530206
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530207 // Send is successful, Lets remove data from queue
208 // check for next request data in queue.
209 self->requestDataQueue.pop();
210 self->state = ConnState::idle;
211 self->checkQueue();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530212 });
213 }
214
215 void doClose()
216 {
217 boost::beast::error_code ec;
218 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
219
220 state = ConnState::closed;
221 // not_connected happens sometimes so don't bother reporting it.
222 if (ec && ec != boost::beast::errc::not_connected)
223 {
224 BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message();
225 return;
226 }
227 BMCWEB_LOG_DEBUG << "Connection closed gracefully";
228 }
229
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530230 void checkQueue(const bool newRecord = false)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530231 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530232 if (requestDataQueue.empty())
233 {
234 // TODO: Having issue in keeping connection alive. So lets close if
Gunnar Millscaa3ce32020-07-08 14:46:53 -0500235 // nothing to be transferred.
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530236 doClose();
237
238 BMCWEB_LOG_DEBUG << "requestDataQueue is empty\n";
239 return;
240 }
241
242 if (retryCount >= maxRetryAttempts)
243 {
244 BMCWEB_LOG_ERROR << "Maximum number of retries is reached.";
245
246 // Clear queue.
247 while (!requestDataQueue.empty())
248 {
249 requestDataQueue.pop();
250 }
251
Ayushi Smritife44eb02020-05-15 15:24:45 +0530252 BMCWEB_LOG_DEBUG << "Retry policy is set to " << retryPolicyAction;
253 if (retryPolicyAction == "TerminateAfterRetries")
254 {
255 // TODO: delete subscription
256 state = ConnState::terminated;
257 return;
258 }
Ed Tanous3174e4d2020-10-07 11:41:22 -0700259 if (retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530260 {
261 state = ConnState::suspended;
262 return;
263 }
Ed Tanous3174e4d2020-10-07 11:41:22 -0700264 // keep retrying, reset count and continue.
265 retryCount = 0;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530266 }
267
268 if ((state == ConnState::connectFailed) ||
269 (state == ConnState::sendFailed) ||
270 (state == ConnState::recvFailed))
271 {
272 if (newRecord)
273 {
274 // We are already running async wait and retry.
275 // Since record is added to queue, it gets the
276 // turn in FIFO.
277 return;
278 }
279
Ayushi Smritife44eb02020-05-15 15:24:45 +0530280 if (runningTimer)
281 {
282 BMCWEB_LOG_DEBUG << "Retry timer is already running.";
283 return;
284 }
285 runningTimer = true;
286
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530287 retryCount++;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530288
289 BMCWEB_LOG_DEBUG << "Attempt retry after " << retryIntervalSecs
290 << " seconds. RetryCount = " << retryCount;
291 timer.expires_after(std::chrono::seconds(retryIntervalSecs));
Ed Tanouscb13a392020-07-25 19:02:03 +0000292 timer.async_wait(
293 [self = shared_from_this()](const boost::system::error_code&) {
294 self->runningTimer = false;
295 self->connStateCheck();
296 });
Ayushi Smritife44eb02020-05-15 15:24:45 +0530297 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530298 }
Ed Tanous3174e4d2020-10-07 11:41:22 -0700299 // reset retry count.
300 retryCount = 0;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530301 connStateCheck();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530302
Ayushi Smritife44eb02020-05-15 15:24:45 +0530303 return;
304 }
305
306 void connStateCheck()
307 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530308 switch (state)
309 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530310 case ConnState::resolveInProgress:
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530311 case ConnState::connectInProgress:
312 case ConnState::sendInProgress:
313 case ConnState::suspended:
Ayushi Smritife44eb02020-05-15 15:24:45 +0530314 case ConnState::terminated:
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530315 // do nothing
316 break;
317 case ConnState::initialized:
318 case ConnState::closed:
319 case ConnState::connectFailed:
320 case ConnState::sendFailed:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530321 case ConnState::recvFailed:
Sunitha Harish29a82b02021-02-18 15:54:16 +0530322 case ConnState::resolveFailed:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530323 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530324 doResolve();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530325 break;
326 }
327 case ConnState::connected:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530328 case ConnState::idle:
329 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530330 std::string data = requestDataQueue.front();
331 sendMessage(data);
332 break;
333 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530334 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530335 }
336
337 public:
Ayushi Smritife44eb02020-05-15 15:24:45 +0530338 explicit HttpClient(boost::asio::io_context& ioc, const std::string& id,
339 const std::string& destIP, const std::string& destPort,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530340 const std::string& destUri) :
AppaRao Pulibd030d02020-03-20 03:34:29 +0530341 conn(ioc),
Ayushi Smritife44eb02020-05-15 15:24:45 +0530342 timer(ioc), subId(id), host(destIP), port(destPort), uri(destUri),
Ed Tanousf23b7292020-10-15 09:41:17 -0700343 retryCount(0), maxRetryAttempts(5), retryIntervalSecs(0),
Ayushi Smritife44eb02020-05-15 15:24:45 +0530344 retryPolicyAction("TerminateAfterRetries"), runningTimer(false)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530345 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530346 state = ConnState::initialized;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530347 }
348
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530349 void sendData(const std::string& data)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530350 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530351 if (state == ConnState::suspended)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530352 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530353 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530354 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530355
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530356 if (requestDataQueue.size() <= maxRequestQueueSize)
357 {
358 requestDataQueue.push(data);
359 checkQueue(true);
360 }
361 else
362 {
363 BMCWEB_LOG_ERROR << "Request queue is full. So ignoring data.";
364 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530365
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530366 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530367 }
368
369 void setHeaders(
370 const std::vector<std::pair<std::string, std::string>>& httpHeaders)
371 {
372 headers = httpHeaders;
373 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530374
375 void setRetryConfig(const uint32_t retryAttempts,
376 const uint32_t retryTimeoutInterval)
377 {
378 maxRetryAttempts = retryAttempts;
379 retryIntervalSecs = retryTimeoutInterval;
380 }
381
382 void setRetryPolicy(const std::string& retryPolicy)
383 {
384 retryPolicyAction = retryPolicy;
385 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530386};
387
388} // namespace crow