blob: 5c7b13fc1d7c9583aa5b550a5f8a029494ea0231 [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 }
Ed Tanous3174e4d2020-10-07 11:41:22 -0700225 if (retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530226 {
227 state = ConnState::suspended;
228 return;
229 }
Ed Tanous3174e4d2020-10-07 11:41:22 -0700230 // keep retrying, reset count and continue.
231 retryCount = 0;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530232 }
233
234 if ((state == ConnState::connectFailed) ||
235 (state == ConnState::sendFailed) ||
236 (state == ConnState::recvFailed))
237 {
238 if (newRecord)
239 {
240 // We are already running async wait and retry.
241 // Since record is added to queue, it gets the
242 // turn in FIFO.
243 return;
244 }
245
Ayushi Smritife44eb02020-05-15 15:24:45 +0530246 if (runningTimer)
247 {
248 BMCWEB_LOG_DEBUG << "Retry timer is already running.";
249 return;
250 }
251 runningTimer = true;
252
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530253 retryCount++;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530254
255 BMCWEB_LOG_DEBUG << "Attempt retry after " << retryIntervalSecs
256 << " seconds. RetryCount = " << retryCount;
257 timer.expires_after(std::chrono::seconds(retryIntervalSecs));
Ed Tanouscb13a392020-07-25 19:02:03 +0000258 timer.async_wait(
259 [self = shared_from_this()](const boost::system::error_code&) {
260 self->runningTimer = false;
261 self->connStateCheck();
262 });
Ayushi Smritife44eb02020-05-15 15:24:45 +0530263 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530264 }
Ed Tanous3174e4d2020-10-07 11:41:22 -0700265 // reset retry count.
266 retryCount = 0;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530267 connStateCheck();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530268
Ayushi Smritife44eb02020-05-15 15:24:45 +0530269 return;
270 }
271
272 void connStateCheck()
273 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530274 switch (state)
275 {
276 case ConnState::connectInProgress:
277 case ConnState::sendInProgress:
278 case ConnState::suspended:
Ayushi Smritife44eb02020-05-15 15:24:45 +0530279 case ConnState::terminated:
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530280 // do nothing
281 break;
282 case ConnState::initialized:
283 case ConnState::closed:
284 case ConnState::connectFailed:
285 case ConnState::sendFailed:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530286 case ConnState::recvFailed:
287 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530288 // After establishing the connection, checkQueue() will
289 // get called and it will attempt to send data.
290 doConnect();
291 break;
292 }
293 case ConnState::connected:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530294 case ConnState::idle:
295 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530296 std::string data = requestDataQueue.front();
297 sendMessage(data);
298 break;
299 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530300 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530301 }
302
303 public:
Ayushi Smritife44eb02020-05-15 15:24:45 +0530304 explicit HttpClient(boost::asio::io_context& ioc, const std::string& id,
305 const std::string& destIP, const std::string& destPort,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530306 const std::string& destUri) :
AppaRao Pulibd030d02020-03-20 03:34:29 +0530307 conn(ioc),
Ayushi Smritife44eb02020-05-15 15:24:45 +0530308 timer(ioc), subId(id), host(destIP), port(destPort), uri(destUri),
Ed Tanousf23b7292020-10-15 09:41:17 -0700309 retryCount(0), maxRetryAttempts(5), retryIntervalSecs(0),
Ayushi Smritife44eb02020-05-15 15:24:45 +0530310 retryPolicyAction("TerminateAfterRetries"), runningTimer(false)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530311 {
312 boost::asio::ip::tcp::resolver resolver(ioc);
313 endpoint = resolver.resolve(host, port);
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530314 state = ConnState::initialized;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530315 }
316
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530317 void sendData(const std::string& data)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530318 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530319 if (state == ConnState::suspended)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530320 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530321 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530322 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530323
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530324 if (requestDataQueue.size() <= maxRequestQueueSize)
325 {
326 requestDataQueue.push(data);
327 checkQueue(true);
328 }
329 else
330 {
331 BMCWEB_LOG_ERROR << "Request queue is full. So ignoring data.";
332 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530333
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530334 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530335 }
336
337 void setHeaders(
338 const std::vector<std::pair<std::string, std::string>>& httpHeaders)
339 {
340 headers = httpHeaders;
341 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530342
343 void setRetryConfig(const uint32_t retryAttempts,
344 const uint32_t retryTimeoutInterval)
345 {
346 maxRetryAttempts = retryAttempts;
347 retryIntervalSecs = retryTimeoutInterval;
348 }
349
350 void setRetryPolicy(const std::string& retryPolicy)
351 {
352 retryPolicyAction = retryPolicy;
353 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530354};
355
356} // namespace crow