blob: 1ac890ce7d866edd13c1deb062d8cc4a33c34d45 [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));
Ed Tanousb00dcc22021-02-23 12:52:50 -080083
AppaRao Puli2a5689a2020-04-29 15:24:31 +053084 conn.async_connect(endpoint, [self(shared_from_this())](
85 const boost::beast::error_code& ec,
86 const boost::asio::ip::tcp::resolver::
87 results_type::endpoint_type& ep) {
88 if (ec)
89 {
90 BMCWEB_LOG_ERROR << "Connect " << ep
91 << " failed: " << ec.message();
92 self->state = ConnState::connectFailed;
93 self->checkQueue();
94 return;
95 }
96 self->state = ConnState::connected;
97 BMCWEB_LOG_DEBUG << "Connected to: " << ep;
98
99 self->checkQueue();
100 });
101 }
102
103 void sendMessage(const std::string& data)
104 {
105 if (state == ConnState::sendInProgress)
106 {
107 return;
108 }
109 state = ConnState::sendInProgress;
110
111 BMCWEB_LOG_DEBUG << __FUNCTION__ << "(): " << host << ":" << port;
112
113 req.version(static_cast<int>(11)); // HTTP 1.1
114 req.target(uri);
115 req.method(boost::beast::http::verb::post);
116
117 // Set headers
118 for (const auto& [key, value] : headers)
119 {
120 req.set(key, value);
121 }
122 req.set(boost::beast::http::field::host, host);
123 req.keep_alive(true);
124
125 req.body() = data;
126 req.prepare_payload();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530127
128 // Set a timeout on the operation
129 conn.expires_after(std::chrono::seconds(30));
130
131 // Send the HTTP request to the remote host
132 boost::beast::http::async_write(
133 conn, req,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530134 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530135 const std::size_t& bytesTransferred) {
136 if (ec)
137 {
138 BMCWEB_LOG_ERROR << "sendMessage() failed: "
139 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530140 self->state = ConnState::sendFailed;
141 self->checkQueue();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530142 return;
143 }
144 BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
145 << bytesTransferred;
146 boost::ignore_unused(bytesTransferred);
147
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530148 self->recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530149 });
150 }
151
152 void recvMessage()
153 {
AppaRao Pulibd030d02020-03-20 03:34:29 +0530154 // Receive the HTTP response
155 boost::beast::http::async_read(
156 conn, buffer, res,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530157 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530158 const std::size_t& bytesTransferred) {
159 if (ec)
160 {
161 BMCWEB_LOG_ERROR << "recvMessage() failed: "
162 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530163 self->state = ConnState::recvFailed;
164 self->checkQueue();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530165 return;
166 }
167 BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
168 << bytesTransferred;
169 boost::ignore_unused(bytesTransferred);
170
171 // Discard received data. We are not interested.
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530172 BMCWEB_LOG_DEBUG << "recvMessage() data: " << self->res;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530173
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530174 // Send is successful, Lets remove data from queue
175 // check for next request data in queue.
176 self->requestDataQueue.pop();
177 self->state = ConnState::idle;
178 self->checkQueue();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530179 });
180 }
181
182 void doClose()
183 {
184 boost::beast::error_code ec;
185 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
186
187 state = ConnState::closed;
188 // not_connected happens sometimes so don't bother reporting it.
189 if (ec && ec != boost::beast::errc::not_connected)
190 {
191 BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message();
192 return;
193 }
194 BMCWEB_LOG_DEBUG << "Connection closed gracefully";
195 }
196
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530197 void checkQueue(const bool newRecord = false)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530198 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530199 if (requestDataQueue.empty())
200 {
201 // TODO: Having issue in keeping connection alive. So lets close if
Gunnar Millscaa3ce32020-07-08 14:46:53 -0500202 // nothing to be transferred.
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530203 doClose();
204
205 BMCWEB_LOG_DEBUG << "requestDataQueue is empty\n";
206 return;
207 }
208
209 if (retryCount >= maxRetryAttempts)
210 {
211 BMCWEB_LOG_ERROR << "Maximum number of retries is reached.";
212
213 // Clear queue.
214 while (!requestDataQueue.empty())
215 {
216 requestDataQueue.pop();
217 }
218
Ayushi Smritife44eb02020-05-15 15:24:45 +0530219 BMCWEB_LOG_DEBUG << "Retry policy is set to " << retryPolicyAction;
220 if (retryPolicyAction == "TerminateAfterRetries")
221 {
222 // TODO: delete subscription
223 state = ConnState::terminated;
224 return;
225 }
Ed Tanous3174e4d2020-10-07 11:41:22 -0700226 if (retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530227 {
228 state = ConnState::suspended;
229 return;
230 }
Ed Tanous3174e4d2020-10-07 11:41:22 -0700231 // keep retrying, reset count and continue.
232 retryCount = 0;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530233 }
234
235 if ((state == ConnState::connectFailed) ||
236 (state == ConnState::sendFailed) ||
237 (state == ConnState::recvFailed))
238 {
239 if (newRecord)
240 {
241 // We are already running async wait and retry.
242 // Since record is added to queue, it gets the
243 // turn in FIFO.
244 return;
245 }
246
Ayushi Smritife44eb02020-05-15 15:24:45 +0530247 if (runningTimer)
248 {
249 BMCWEB_LOG_DEBUG << "Retry timer is already running.";
250 return;
251 }
252 runningTimer = true;
253
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530254 retryCount++;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530255
256 BMCWEB_LOG_DEBUG << "Attempt retry after " << retryIntervalSecs
257 << " seconds. RetryCount = " << retryCount;
258 timer.expires_after(std::chrono::seconds(retryIntervalSecs));
Ed Tanouscb13a392020-07-25 19:02:03 +0000259 timer.async_wait(
260 [self = shared_from_this()](const boost::system::error_code&) {
261 self->runningTimer = false;
262 self->connStateCheck();
263 });
Ayushi Smritife44eb02020-05-15 15:24:45 +0530264 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530265 }
Ed Tanous3174e4d2020-10-07 11:41:22 -0700266 // reset retry count.
267 retryCount = 0;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530268 connStateCheck();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530269
Ayushi Smritife44eb02020-05-15 15:24:45 +0530270 return;
271 }
272
273 void connStateCheck()
274 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530275 switch (state)
276 {
277 case ConnState::connectInProgress:
278 case ConnState::sendInProgress:
279 case ConnState::suspended:
Ayushi Smritife44eb02020-05-15 15:24:45 +0530280 case ConnState::terminated:
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530281 // do nothing
282 break;
283 case ConnState::initialized:
284 case ConnState::closed:
285 case ConnState::connectFailed:
286 case ConnState::sendFailed:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530287 case ConnState::recvFailed:
288 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530289 // After establishing the connection, checkQueue() will
290 // get called and it will attempt to send data.
291 doConnect();
292 break;
293 }
294 case ConnState::connected:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530295 case ConnState::idle:
296 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530297 std::string data = requestDataQueue.front();
298 sendMessage(data);
299 break;
300 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530301 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530302 }
303
304 public:
Ayushi Smritife44eb02020-05-15 15:24:45 +0530305 explicit HttpClient(boost::asio::io_context& ioc, const std::string& id,
306 const std::string& destIP, const std::string& destPort,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530307 const std::string& destUri) :
AppaRao Pulibd030d02020-03-20 03:34:29 +0530308 conn(ioc),
Ayushi Smritife44eb02020-05-15 15:24:45 +0530309 timer(ioc), subId(id), host(destIP), port(destPort), uri(destUri),
Ed Tanousf23b7292020-10-15 09:41:17 -0700310 retryCount(0), maxRetryAttempts(5), retryIntervalSecs(0),
Ayushi Smritife44eb02020-05-15 15:24:45 +0530311 retryPolicyAction("TerminateAfterRetries"), runningTimer(false)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530312 {
313 boost::asio::ip::tcp::resolver resolver(ioc);
314 endpoint = resolver.resolve(host, port);
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530315 state = ConnState::initialized;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530316 }
317
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530318 void sendData(const std::string& data)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530319 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530320 if (state == ConnState::suspended)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530321 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530322 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530323 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530324
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530325 if (requestDataQueue.size() <= maxRequestQueueSize)
326 {
327 requestDataQueue.push(data);
328 checkQueue(true);
329 }
330 else
331 {
332 BMCWEB_LOG_ERROR << "Request queue is full. So ignoring data.";
333 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530334
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530335 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530336 }
337
338 void setHeaders(
339 const std::vector<std::pair<std::string, std::string>>& httpHeaders)
340 {
341 headers = httpHeaders;
342 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530343
344 void setRetryConfig(const uint32_t retryAttempts,
345 const uint32_t retryTimeoutInterval)
346 {
347 maxRetryAttempts = retryAttempts;
348 retryIntervalSecs = retryTimeoutInterval;
349 }
350
351 void setRetryPolicy(const std::string& retryPolicy)
352 {
353 retryPolicyAction = retryPolicy;
354 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530355};
356
357} // namespace crow