blob: 93c2a60d0875982aa41fb2100567ffbaf8beb4f1 [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
Ed Tanousd43cd0c2020-09-30 20:46:53 -070017#include <boost/asio/steady_timer.hpp>
18#include <boost/beast/core/flat_buffer.hpp>
19#include <boost/beast/core/tcp_stream.hpp>
20#include <boost/beast/http/message.hpp>
AppaRao Pulibd030d02020-03-20 03:34:29 +053021#include <boost/beast/version.hpp>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050022
AppaRao Pulibd030d02020-03-20 03:34:29 +053023#include <cstdlib>
24#include <functional>
25#include <iostream>
26#include <memory>
AppaRao Puli2a5689a2020-04-29 15:24:31 +053027#include <queue>
AppaRao Pulibd030d02020-03-20 03:34:29 +053028#include <string>
29
30namespace crow
31{
32
AppaRao Puli2a5689a2020-04-29 15:24:31 +053033static constexpr uint8_t maxRequestQueueSize = 50;
34
AppaRao Pulibd030d02020-03-20 03:34:29 +053035enum class ConnState
36{
AppaRao Puli2a5689a2020-04-29 15:24:31 +053037 initialized,
38 connectInProgress,
39 connectFailed,
AppaRao Pulibd030d02020-03-20 03:34:29 +053040 connected,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053041 sendInProgress,
42 sendFailed,
43 recvFailed,
44 idle,
45 suspended,
Ayushi Smritife44eb02020-05-15 15:24:45 +053046 closed,
47 terminated
AppaRao Pulibd030d02020-03-20 03:34:29 +053048};
49
50class HttpClient : public std::enable_shared_from_this<HttpClient>
51{
52 private:
53 boost::beast::tcp_stream conn;
Ayushi Smritife44eb02020-05-15 15:24:45 +053054 boost::asio::steady_timer timer;
AppaRao Pulibd030d02020-03-20 03:34:29 +053055 boost::beast::flat_buffer buffer;
56 boost::beast::http::request<boost::beast::http::string_body> req;
57 boost::beast::http::response<boost::beast::http::string_body> res;
58 boost::asio::ip::tcp::resolver::results_type endpoint;
59 std::vector<std::pair<std::string, std::string>> headers;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053060 std::queue<std::string> requestDataQueue;
AppaRao Pulibd030d02020-03-20 03:34:29 +053061 ConnState state;
Ayushi Smritife44eb02020-05-15 15:24:45 +053062 std::string subId;
AppaRao Pulibd030d02020-03-20 03:34:29 +053063 std::string host;
64 std::string port;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053065 std::string uri;
Ayushi Smritife44eb02020-05-15 15:24:45 +053066 uint32_t retryCount;
67 uint32_t maxRetryAttempts;
68 uint32_t retryIntervalSecs;
69 std::string retryPolicyAction;
70 bool runningTimer;
AppaRao Pulibd030d02020-03-20 03:34:29 +053071
AppaRao Puli2a5689a2020-04-29 15:24:31 +053072 void doConnect()
AppaRao Pulibd030d02020-03-20 03:34:29 +053073 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +053074 if (state == ConnState::connectInProgress)
AppaRao Pulibd030d02020-03-20 03:34:29 +053075 {
AppaRao Pulibd030d02020-03-20 03:34:29 +053076 return;
77 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +053078 state = ConnState::connectInProgress;
79
80 BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":" << port;
81 // Set a timeout on the operation
82 conn.expires_after(std::chrono::seconds(30));
83 conn.async_connect(endpoint, [self(shared_from_this())](
84 const boost::beast::error_code& ec,
85 const boost::asio::ip::tcp::resolver::
86 results_type::endpoint_type& ep) {
87 if (ec)
88 {
89 BMCWEB_LOG_ERROR << "Connect " << ep
90 << " failed: " << ec.message();
91 self->state = ConnState::connectFailed;
92 self->checkQueue();
93 return;
94 }
95 self->state = ConnState::connected;
96 BMCWEB_LOG_DEBUG << "Connected to: " << ep;
97
98 self->checkQueue();
99 });
100 }
101
102 void sendMessage(const std::string& data)
103 {
104 if (state == ConnState::sendInProgress)
105 {
106 return;
107 }
108 state = ConnState::sendInProgress;
109
110 BMCWEB_LOG_DEBUG << __FUNCTION__ << "(): " << host << ":" << port;
111
112 req.version(static_cast<int>(11)); // HTTP 1.1
113 req.target(uri);
114 req.method(boost::beast::http::verb::post);
115
116 // Set headers
117 for (const auto& [key, value] : headers)
118 {
119 req.set(key, value);
120 }
121 req.set(boost::beast::http::field::host, host);
122 req.keep_alive(true);
123
124 req.body() = data;
125 req.prepare_payload();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530126
127 // Set a timeout on the operation
128 conn.expires_after(std::chrono::seconds(30));
129
130 // Send the HTTP request to the remote host
131 boost::beast::http::async_write(
132 conn, req,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530133 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530134 const std::size_t& bytesTransferred) {
135 if (ec)
136 {
137 BMCWEB_LOG_ERROR << "sendMessage() failed: "
138 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530139 self->state = ConnState::sendFailed;
140 self->checkQueue();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530141 return;
142 }
143 BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
144 << bytesTransferred;
145 boost::ignore_unused(bytesTransferred);
146
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530147 self->recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530148 });
149 }
150
151 void recvMessage()
152 {
AppaRao Pulibd030d02020-03-20 03:34:29 +0530153 // Receive the HTTP response
154 boost::beast::http::async_read(
155 conn, buffer, res,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530156 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530157 const std::size_t& bytesTransferred) {
158 if (ec)
159 {
160 BMCWEB_LOG_ERROR << "recvMessage() failed: "
161 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530162 self->state = ConnState::recvFailed;
163 self->checkQueue();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530164 return;
165 }
166 BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
167 << bytesTransferred;
168 boost::ignore_unused(bytesTransferred);
169
170 // Discard received data. We are not interested.
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530171 BMCWEB_LOG_DEBUG << "recvMessage() data: " << self->res;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530172
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530173 // Send is successful, Lets remove data from queue
174 // check for next request data in queue.
175 self->requestDataQueue.pop();
176 self->state = ConnState::idle;
177 self->checkQueue();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530178 });
179 }
180
181 void doClose()
182 {
183 boost::beast::error_code ec;
184 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
185
186 state = ConnState::closed;
187 // not_connected happens sometimes so don't bother reporting it.
188 if (ec && ec != boost::beast::errc::not_connected)
189 {
190 BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message();
191 return;
192 }
193 BMCWEB_LOG_DEBUG << "Connection closed gracefully";
194 }
195
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530196 void checkQueue(const bool newRecord = false)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530197 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530198 if (requestDataQueue.empty())
199 {
200 // TODO: Having issue in keeping connection alive. So lets close if
Gunnar Millscaa3ce32020-07-08 14:46:53 -0500201 // nothing to be transferred.
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530202 doClose();
203
204 BMCWEB_LOG_DEBUG << "requestDataQueue is empty\n";
205 return;
206 }
207
208 if (retryCount >= maxRetryAttempts)
209 {
210 BMCWEB_LOG_ERROR << "Maximum number of retries is reached.";
211
212 // Clear queue.
213 while (!requestDataQueue.empty())
214 {
215 requestDataQueue.pop();
216 }
217
Ayushi Smritife44eb02020-05-15 15:24:45 +0530218 BMCWEB_LOG_DEBUG << "Retry policy is set to " << retryPolicyAction;
219 if (retryPolicyAction == "TerminateAfterRetries")
220 {
221 // TODO: delete subscription
222 state = ConnState::terminated;
223 return;
224 }
225 else if (retryPolicyAction == "SuspendRetries")
226 {
227 state = ConnState::suspended;
228 return;
229 }
230 else
231 {
232 // keep retrying, reset count and continue.
233 retryCount = 0;
234 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530235 }
236
237 if ((state == ConnState::connectFailed) ||
238 (state == ConnState::sendFailed) ||
239 (state == ConnState::recvFailed))
240 {
241 if (newRecord)
242 {
243 // We are already running async wait and retry.
244 // Since record is added to queue, it gets the
245 // turn in FIFO.
246 return;
247 }
248
Ayushi Smritife44eb02020-05-15 15:24:45 +0530249 if (runningTimer)
250 {
251 BMCWEB_LOG_DEBUG << "Retry timer is already running.";
252 return;
253 }
254 runningTimer = true;
255
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530256 retryCount++;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530257
258 BMCWEB_LOG_DEBUG << "Attempt retry after " << retryIntervalSecs
259 << " seconds. RetryCount = " << retryCount;
260 timer.expires_after(std::chrono::seconds(retryIntervalSecs));
Ed Tanouscb13a392020-07-25 19:02:03 +0000261 timer.async_wait(
262 [self = shared_from_this()](const boost::system::error_code&) {
263 self->runningTimer = false;
264 self->connStateCheck();
265 });
Ayushi Smritife44eb02020-05-15 15:24:45 +0530266 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530267 }
268 else
269 {
270 // reset retry count.
271 retryCount = 0;
272 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530273 connStateCheck();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530274
Ayushi Smritife44eb02020-05-15 15:24:45 +0530275 return;
276 }
277
278 void connStateCheck()
279 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530280 switch (state)
281 {
282 case ConnState::connectInProgress:
283 case ConnState::sendInProgress:
284 case ConnState::suspended:
Ayushi Smritife44eb02020-05-15 15:24:45 +0530285 case ConnState::terminated:
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530286 // do nothing
287 break;
288 case ConnState::initialized:
289 case ConnState::closed:
290 case ConnState::connectFailed:
291 case ConnState::sendFailed:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530292 case ConnState::recvFailed:
293 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530294 // After establishing the connection, checkQueue() will
295 // get called and it will attempt to send data.
296 doConnect();
297 break;
298 }
299 case ConnState::connected:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530300 case ConnState::idle:
301 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530302 std::string data = requestDataQueue.front();
303 sendMessage(data);
304 break;
305 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530306 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530307 }
308
309 public:
Ayushi Smritife44eb02020-05-15 15:24:45 +0530310 explicit HttpClient(boost::asio::io_context& ioc, const std::string& id,
311 const std::string& destIP, const std::string& destPort,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530312 const std::string& destUri) :
AppaRao Pulibd030d02020-03-20 03:34:29 +0530313 conn(ioc),
Ayushi Smritife44eb02020-05-15 15:24:45 +0530314 timer(ioc), subId(id), host(destIP), port(destPort), uri(destUri),
315 retryCount(0), maxRetryAttempts(5),
316 retryPolicyAction("TerminateAfterRetries"), runningTimer(false)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530317 {
318 boost::asio::ip::tcp::resolver resolver(ioc);
319 endpoint = resolver.resolve(host, port);
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530320 state = ConnState::initialized;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530321 }
322
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530323 void sendData(const std::string& data)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530324 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530325 if (state == ConnState::suspended)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530326 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530327 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530328 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530329
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530330 if (requestDataQueue.size() <= maxRequestQueueSize)
331 {
332 requestDataQueue.push(data);
333 checkQueue(true);
334 }
335 else
336 {
337 BMCWEB_LOG_ERROR << "Request queue is full. So ignoring data.";
338 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530339
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530340 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530341 }
342
343 void setHeaders(
344 const std::vector<std::pair<std::string, std::string>>& httpHeaders)
345 {
346 headers = httpHeaders;
347 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530348
349 void setRetryConfig(const uint32_t retryAttempts,
350 const uint32_t retryTimeoutInterval)
351 {
352 maxRetryAttempts = retryAttempts;
353 retryIntervalSecs = retryTimeoutInterval;
354 }
355
356 void setRetryPolicy(const std::string& retryPolicy)
357 {
358 retryPolicyAction = retryPolicy;
359 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530360};
361
362} // namespace crow