blob: c455c7bfcfe8f0c0b8169cf90510fbef9ae22c93 [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
17#include <boost/asio/strand.hpp>
18#include <boost/beast/core.hpp>
19#include <boost/beast/http.hpp>
20#include <boost/beast/version.hpp>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050021
AppaRao Pulibd030d02020-03-20 03:34:29 +053022#include <cstdlib>
23#include <functional>
24#include <iostream>
25#include <memory>
AppaRao Puli2a5689a2020-04-29 15:24:31 +053026#include <queue>
AppaRao Pulibd030d02020-03-20 03:34:29 +053027#include <string>
28
29namespace crow
30{
31
AppaRao Puli2a5689a2020-04-29 15:24:31 +053032static constexpr uint8_t maxRequestQueueSize = 50;
33
AppaRao Pulibd030d02020-03-20 03:34:29 +053034enum class ConnState
35{
AppaRao Puli2a5689a2020-04-29 15:24:31 +053036 initialized,
37 connectInProgress,
38 connectFailed,
AppaRao Pulibd030d02020-03-20 03:34:29 +053039 connected,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053040 sendInProgress,
41 sendFailed,
42 recvFailed,
43 idle,
44 suspended,
Ayushi Smritife44eb02020-05-15 15:24:45 +053045 closed,
46 terminated
AppaRao Pulibd030d02020-03-20 03:34:29 +053047};
48
49class HttpClient : public std::enable_shared_from_this<HttpClient>
50{
51 private:
52 boost::beast::tcp_stream conn;
Ayushi Smritife44eb02020-05-15 15:24:45 +053053 boost::asio::steady_timer timer;
AppaRao Pulibd030d02020-03-20 03:34:29 +053054 boost::beast::flat_buffer buffer;
55 boost::beast::http::request<boost::beast::http::string_body> req;
56 boost::beast::http::response<boost::beast::http::string_body> res;
57 boost::asio::ip::tcp::resolver::results_type endpoint;
58 std::vector<std::pair<std::string, std::string>> headers;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053059 std::queue<std::string> requestDataQueue;
AppaRao Pulibd030d02020-03-20 03:34:29 +053060 ConnState state;
Ayushi Smritife44eb02020-05-15 15:24:45 +053061 std::string subId;
AppaRao Pulibd030d02020-03-20 03:34:29 +053062 std::string host;
63 std::string port;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053064 std::string uri;
Ayushi Smritife44eb02020-05-15 15:24:45 +053065 uint32_t retryCount;
66 uint32_t maxRetryAttempts;
67 uint32_t retryIntervalSecs;
68 std::string retryPolicyAction;
69 bool runningTimer;
AppaRao Pulibd030d02020-03-20 03:34:29 +053070
AppaRao Puli2a5689a2020-04-29 15:24:31 +053071 void doConnect()
AppaRao Pulibd030d02020-03-20 03:34:29 +053072 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +053073 if (state == ConnState::connectInProgress)
AppaRao Pulibd030d02020-03-20 03:34:29 +053074 {
AppaRao Pulibd030d02020-03-20 03:34:29 +053075 return;
76 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +053077 state = ConnState::connectInProgress;
78
79 BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":" << port;
80 // Set a timeout on the operation
81 conn.expires_after(std::chrono::seconds(30));
82 conn.async_connect(endpoint, [self(shared_from_this())](
83 const boost::beast::error_code& ec,
84 const boost::asio::ip::tcp::resolver::
85 results_type::endpoint_type& ep) {
86 if (ec)
87 {
88 BMCWEB_LOG_ERROR << "Connect " << ep
89 << " failed: " << ec.message();
90 self->state = ConnState::connectFailed;
91 self->checkQueue();
92 return;
93 }
94 self->state = ConnState::connected;
95 BMCWEB_LOG_DEBUG << "Connected to: " << ep;
96
97 self->checkQueue();
98 });
99 }
100
101 void sendMessage(const std::string& data)
102 {
103 if (state == ConnState::sendInProgress)
104 {
105 return;
106 }
107 state = ConnState::sendInProgress;
108
109 BMCWEB_LOG_DEBUG << __FUNCTION__ << "(): " << host << ":" << port;
110
111 req.version(static_cast<int>(11)); // HTTP 1.1
112 req.target(uri);
113 req.method(boost::beast::http::verb::post);
114
115 // Set headers
116 for (const auto& [key, value] : headers)
117 {
118 req.set(key, value);
119 }
120 req.set(boost::beast::http::field::host, host);
121 req.keep_alive(true);
122
123 req.body() = data;
124 req.prepare_payload();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530125
126 // Set a timeout on the operation
127 conn.expires_after(std::chrono::seconds(30));
128
129 // Send the HTTP request to the remote host
130 boost::beast::http::async_write(
131 conn, req,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530132 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530133 const std::size_t& bytesTransferred) {
134 if (ec)
135 {
136 BMCWEB_LOG_ERROR << "sendMessage() failed: "
137 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530138 self->state = ConnState::sendFailed;
139 self->checkQueue();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530140 return;
141 }
142 BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
143 << bytesTransferred;
144 boost::ignore_unused(bytesTransferred);
145
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530146 self->recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530147 });
148 }
149
150 void recvMessage()
151 {
AppaRao Pulibd030d02020-03-20 03:34:29 +0530152 // Receive the HTTP response
153 boost::beast::http::async_read(
154 conn, buffer, res,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530155 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530156 const std::size_t& bytesTransferred) {
157 if (ec)
158 {
159 BMCWEB_LOG_ERROR << "recvMessage() failed: "
160 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530161 self->state = ConnState::recvFailed;
162 self->checkQueue();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530163 return;
164 }
165 BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
166 << bytesTransferred;
167 boost::ignore_unused(bytesTransferred);
168
169 // Discard received data. We are not interested.
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530170 BMCWEB_LOG_DEBUG << "recvMessage() data: " << self->res;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530171
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530172 // Send is successful, Lets remove data from queue
173 // check for next request data in queue.
174 self->requestDataQueue.pop();
175 self->state = ConnState::idle;
176 self->checkQueue();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530177 });
178 }
179
180 void doClose()
181 {
182 boost::beast::error_code ec;
183 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
184
185 state = ConnState::closed;
186 // not_connected happens sometimes so don't bother reporting it.
187 if (ec && ec != boost::beast::errc::not_connected)
188 {
189 BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message();
190 return;
191 }
192 BMCWEB_LOG_DEBUG << "Connection closed gracefully";
193 }
194
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530195 void checkQueue(const bool newRecord = false)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530196 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530197 if (requestDataQueue.empty())
198 {
199 // TODO: Having issue in keeping connection alive. So lets close if
Gunnar Millscaa3ce32020-07-08 14:46:53 -0500200 // nothing to be transferred.
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530201 doClose();
202
203 BMCWEB_LOG_DEBUG << "requestDataQueue is empty\n";
204 return;
205 }
206
207 if (retryCount >= maxRetryAttempts)
208 {
209 BMCWEB_LOG_ERROR << "Maximum number of retries is reached.";
210
211 // Clear queue.
212 while (!requestDataQueue.empty())
213 {
214 requestDataQueue.pop();
215 }
216
Ayushi Smritife44eb02020-05-15 15:24:45 +0530217 BMCWEB_LOG_DEBUG << "Retry policy is set to " << retryPolicyAction;
218 if (retryPolicyAction == "TerminateAfterRetries")
219 {
220 // TODO: delete subscription
221 state = ConnState::terminated;
222 return;
223 }
224 else if (retryPolicyAction == "SuspendRetries")
225 {
226 state = ConnState::suspended;
227 return;
228 }
229 else
230 {
231 // keep retrying, reset count and continue.
232 retryCount = 0;
233 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530234 }
235
236 if ((state == ConnState::connectFailed) ||
237 (state == ConnState::sendFailed) ||
238 (state == ConnState::recvFailed))
239 {
240 if (newRecord)
241 {
242 // We are already running async wait and retry.
243 // Since record is added to queue, it gets the
244 // turn in FIFO.
245 return;
246 }
247
Ayushi Smritife44eb02020-05-15 15:24:45 +0530248 if (runningTimer)
249 {
250 BMCWEB_LOG_DEBUG << "Retry timer is already running.";
251 return;
252 }
253 runningTimer = true;
254
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530255 retryCount++;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530256
257 BMCWEB_LOG_DEBUG << "Attempt retry after " << retryIntervalSecs
258 << " seconds. RetryCount = " << retryCount;
259 timer.expires_after(std::chrono::seconds(retryIntervalSecs));
260 timer.async_wait([self = shared_from_this()](
261 const boost::system::error_code& ec) {
262 self->runningTimer = false;
263 self->connStateCheck();
264 });
265 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530266 }
267 else
268 {
269 // reset retry count.
270 retryCount = 0;
271 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530272 connStateCheck();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530273
Ayushi Smritife44eb02020-05-15 15:24:45 +0530274 return;
275 }
276
277 void connStateCheck()
278 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530279 switch (state)
280 {
281 case ConnState::connectInProgress:
282 case ConnState::sendInProgress:
283 case ConnState::suspended:
Ayushi Smritife44eb02020-05-15 15:24:45 +0530284 case ConnState::terminated:
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530285 // do nothing
286 break;
287 case ConnState::initialized:
288 case ConnState::closed:
289 case ConnState::connectFailed:
290 case ConnState::sendFailed:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530291 case ConnState::recvFailed:
292 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530293 // After establishing the connection, checkQueue() will
294 // get called and it will attempt to send data.
295 doConnect();
296 break;
297 }
298 case ConnState::connected:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530299 case ConnState::idle:
300 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530301 std::string data = requestDataQueue.front();
302 sendMessage(data);
303 break;
304 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530305 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530306 }
307
308 public:
Ayushi Smritife44eb02020-05-15 15:24:45 +0530309 explicit HttpClient(boost::asio::io_context& ioc, const std::string& id,
310 const std::string& destIP, const std::string& destPort,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530311 const std::string& destUri) :
AppaRao Pulibd030d02020-03-20 03:34:29 +0530312 conn(ioc),
Ayushi Smritife44eb02020-05-15 15:24:45 +0530313 timer(ioc), subId(id), host(destIP), port(destPort), uri(destUri),
314 retryCount(0), maxRetryAttempts(5),
315 retryPolicyAction("TerminateAfterRetries"), runningTimer(false)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530316 {
317 boost::asio::ip::tcp::resolver resolver(ioc);
318 endpoint = resolver.resolve(host, port);
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530319 state = ConnState::initialized;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530320 }
321
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530322 void sendData(const std::string& data)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530323 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530324 if (state == ConnState::suspended)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530325 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530326 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530327 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530328
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530329 if (requestDataQueue.size() <= maxRequestQueueSize)
330 {
331 requestDataQueue.push(data);
332 checkQueue(true);
333 }
334 else
335 {
336 BMCWEB_LOG_ERROR << "Request queue is full. So ignoring data.";
337 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530338
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530339 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530340 }
341
342 void setHeaders(
343 const std::vector<std::pair<std::string, std::string>>& httpHeaders)
344 {
345 headers = httpHeaders;
346 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530347
348 void setRetryConfig(const uint32_t retryAttempts,
349 const uint32_t retryTimeoutInterval)
350 {
351 maxRetryAttempts = retryAttempts;
352 retryIntervalSecs = retryTimeoutInterval;
353 }
354
355 void setRetryPolicy(const std::string& retryPolicy)
356 {
357 retryPolicyAction = retryPolicy;
358 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530359};
360
361} // namespace crow