blob: cd6739843e553c071573cb4397b2f17a7c0b26e0 [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;
Sunitha Harish7de9f812021-08-24 02:50:30 -050037static constexpr unsigned int httpReadBodyLimit = 8192;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053038
AppaRao Pulibd030d02020-03-20 03:34:29 +053039enum class ConnState
40{
AppaRao Puli2a5689a2020-04-29 15:24:31 +053041 initialized,
Sunitha Harish29a82b02021-02-18 15:54:16 +053042 resolveInProgress,
43 resolveFailed,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053044 connectInProgress,
45 connectFailed,
AppaRao Pulibd030d02020-03-20 03:34:29 +053046 connected,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053047 sendInProgress,
48 sendFailed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053049 recvInProgress,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053050 recvFailed,
51 idle,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053052 closeInProgress,
Ayushi Smritife44eb02020-05-15 15:24:45 +053053 closed,
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053054 suspended,
55 terminated,
56 abortConnection,
57 retry
AppaRao Pulibd030d02020-03-20 03:34:29 +053058};
59
60class HttpClient : public std::enable_shared_from_this<HttpClient>
61{
62 private:
Sunitha Harish29a82b02021-02-18 15:54:16 +053063 crow::async_resolve::Resolver resolver;
AppaRao Pulibd030d02020-03-20 03:34:29 +053064 boost::beast::tcp_stream conn;
Ayushi Smritife44eb02020-05-15 15:24:45 +053065 boost::asio::steady_timer timer;
Sunitha Harish7de9f812021-08-24 02:50:30 -050066 boost::beast::flat_static_buffer<httpReadBodyLimit> buffer;
AppaRao Pulibd030d02020-03-20 03:34:29 +053067 boost::beast::http::request<boost::beast::http::string_body> req;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053068 std::optional<
69 boost::beast::http::response_parser<boost::beast::http::string_body>>
70 parser;
Sunitha Harish7de9f812021-08-24 02:50:30 -050071 boost::circular_buffer_space_optimized<std::string> requestDataQueue{};
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053072
AppaRao Pulibd030d02020-03-20 03:34:29 +053073 ConnState state;
Ayushi Smritife44eb02020-05-15 15:24:45 +053074 std::string subId;
AppaRao Pulibd030d02020-03-20 03:34:29 +053075 std::string host;
76 std::string port;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053077 std::string uri;
Ayushi Smritife44eb02020-05-15 15:24:45 +053078 uint32_t retryCount;
79 uint32_t maxRetryAttempts;
80 uint32_t retryIntervalSecs;
81 std::string retryPolicyAction;
82 bool runningTimer;
AppaRao Pulibd030d02020-03-20 03:34:29 +053083
Sunitha Harish29a82b02021-02-18 15:54:16 +053084 void doResolve()
85 {
Sunitha Harish29a82b02021-02-18 15:54:16 +053086 state = ConnState::resolveInProgress;
Sunitha Harish29a82b02021-02-18 15:54:16 +053087 BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":" << port;
88
89 auto respHandler =
90 [self(shared_from_this())](
91 const boost::beast::error_code ec,
92 const std::vector<boost::asio::ip::tcp::endpoint>&
93 endpointList) {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053094 if (ec || (endpointList.size() == 0))
Sunitha Harish29a82b02021-02-18 15:54:16 +053095 {
96 BMCWEB_LOG_ERROR << "Resolve failed: " << ec.message();
97 self->state = ConnState::resolveFailed;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053098 self->handleConnState();
Sunitha Harish29a82b02021-02-18 15:54:16 +053099 return;
100 }
101 BMCWEB_LOG_DEBUG << "Resolved";
102 self->doConnect(endpointList);
103 };
104 resolver.asyncResolve(host, port, std::move(respHandler));
105 }
106
107 void doConnect(
108 const std::vector<boost::asio::ip::tcp::endpoint>& endpointList)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530109 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530110 state = ConnState::connectInProgress;
111
112 BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":" << port;
Sunitha Harish29a82b02021-02-18 15:54:16 +0530113
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530114 conn.expires_after(std::chrono::seconds(30));
Sunitha Harish29a82b02021-02-18 15:54:16 +0530115 conn.async_connect(
116 endpointList, [self(shared_from_this())](
117 const boost::beast::error_code ec,
118 const boost::asio::ip::tcp::endpoint& endpoint) {
119 if (ec)
120 {
121 BMCWEB_LOG_ERROR << "Connect " << endpoint
122 << " failed: " << ec.message();
123 self->state = ConnState::connectFailed;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530124 self->handleConnState();
Sunitha Harish29a82b02021-02-18 15:54:16 +0530125 return;
126 }
Sunitha Harish29a82b02021-02-18 15:54:16 +0530127 BMCWEB_LOG_DEBUG << "Connected to: " << endpoint;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530128 self->state = ConnState::connected;
129 self->handleConnState();
Sunitha Harish29a82b02021-02-18 15:54:16 +0530130 });
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530131 }
132
133 void sendMessage(const std::string& data)
134 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530135 state = ConnState::sendInProgress;
136
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530137 req.body() = data;
138 req.prepare_payload();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530139
140 // Set a timeout on the operation
141 conn.expires_after(std::chrono::seconds(30));
142
143 // Send the HTTP request to the remote host
144 boost::beast::http::async_write(
145 conn, req,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530146 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530147 const std::size_t& bytesTransferred) {
148 if (ec)
149 {
150 BMCWEB_LOG_ERROR << "sendMessage() failed: "
151 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530152 self->state = ConnState::sendFailed;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530153 self->handleConnState();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530154 return;
155 }
156 BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
157 << bytesTransferred;
158 boost::ignore_unused(bytesTransferred);
159
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530160 self->recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530161 });
162 }
163
164 void recvMessage()
165 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530166 state = ConnState::recvInProgress;
167
168 parser.emplace(std::piecewise_construct, std::make_tuple());
169 parser->body_limit(httpReadBodyLimit);
170
171 // Check only for the response header
172 parser->skip(true);
173
AppaRao Pulibd030d02020-03-20 03:34:29 +0530174 // Receive the HTTP response
175 boost::beast::http::async_read(
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530176 conn, buffer, *parser,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530177 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530178 const std::size_t& bytesTransferred) {
179 if (ec)
180 {
181 BMCWEB_LOG_ERROR << "recvMessage() failed: "
182 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530183 self->state = ConnState::recvFailed;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530184 self->handleConnState();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530185 return;
186 }
187 BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
188 << bytesTransferred;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530189 BMCWEB_LOG_DEBUG << "recvMessage() data: "
190 << self->parser->get();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530191
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530192 unsigned int respCode = self->parser->get().result_int();
193 BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: "
194 << respCode;
195
196 // 2XX response is considered to be successful
197 if ((respCode < 200) || (respCode >= 300))
198 {
199 // The listener failed to receive the Sent-Event
200 BMCWEB_LOG_ERROR << "recvMessage() Listener Failed to "
201 "receive Sent-Event";
202 self->state = ConnState::recvFailed;
203 self->handleConnState();
204 return;
205 }
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.
Sunitha Harish7de9f812021-08-24 02:50:30 -0500209 if (!self->requestDataQueue.empty())
210 {
211 self->requestDataQueue.pop_front();
212 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530213 self->state = ConnState::idle;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530214
215 // Keep the connection alive if server supports it
216 // Else close the connection
217 BMCWEB_LOG_DEBUG << "recvMessage() keepalive : "
218 << self->parser->keep_alive();
219 if (!self->parser->keep_alive())
220 {
221 // Abort the connection since server is not keep-alive
222 // enabled
223 self->state = ConnState::abortConnection;
224 }
225
226 self->handleConnState();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530227 });
228 }
229
230 void doClose()
231 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530232 state = ConnState::closeInProgress;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530233 boost::beast::error_code ec;
234 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530235 conn.close();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530236
AppaRao Pulibd030d02020-03-20 03:34:29 +0530237 // not_connected happens sometimes so don't bother reporting it.
238 if (ec && ec != boost::beast::errc::not_connected)
239 {
240 BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message();
241 return;
242 }
243 BMCWEB_LOG_DEBUG << "Connection closed gracefully";
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530244 if ((state != ConnState::suspended) && (state != ConnState::terminated))
245 {
246 state = ConnState::closed;
247 handleConnState();
248 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530249 }
250
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530251 void waitAndRetry()
AppaRao Pulibd030d02020-03-20 03:34:29 +0530252 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530253 if (retryCount >= maxRetryAttempts)
254 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530255 BMCWEB_LOG_ERROR << "Maximum number of retries reached.";
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530256
257 // Clear queue.
258 while (!requestDataQueue.empty())
259 {
Sunitha Harish7de9f812021-08-24 02:50:30 -0500260 requestDataQueue.pop_front();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530261 }
262
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530263 BMCWEB_LOG_DEBUG << "Retry policy: " << retryPolicyAction;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530264 if (retryPolicyAction == "TerminateAfterRetries")
265 {
266 // TODO: delete subscription
267 state = ConnState::terminated;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530268 }
Ed Tanous3174e4d2020-10-07 11:41:22 -0700269 if (retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530270 {
271 state = ConnState::suspended;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530272 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530273 // Reset the retrycount to zero so that client can try connecting
274 // again if needed
Ed Tanous3174e4d2020-10-07 11:41:22 -0700275 retryCount = 0;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530276 handleConnState();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530277 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530278 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530279
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530280 if (runningTimer)
281 {
282 BMCWEB_LOG_DEBUG << "Retry timer is already running.";
283 return;
284 }
285 runningTimer = true;
286
287 retryCount++;
288
289 BMCWEB_LOG_DEBUG << "Attempt retry after " << retryIntervalSecs
290 << " seconds. RetryCount = " << retryCount;
291 timer.expires_after(std::chrono::seconds(retryIntervalSecs));
292 timer.async_wait(
293 [self = shared_from_this()](const boost::system::error_code ec) {
294 if (ec == boost::asio::error::operation_aborted)
295 {
296 BMCWEB_LOG_DEBUG
297 << "async_wait failed since the operation is aborted"
298 << ec.message();
299 }
300 else if (ec)
301 {
302 BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message();
303 // Ignore the error and continue the retry loop to attempt
304 // sending the event as per the retry policy
305 }
306 self->runningTimer = false;
307
308 // Lets close connection and start from resolve.
309 self->doClose();
310 });
Ayushi Smritife44eb02020-05-15 15:24:45 +0530311 return;
312 }
313
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530314 void handleConnState()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530315 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530316 switch (state)
317 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530318 case ConnState::resolveInProgress:
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530319 case ConnState::connectInProgress:
320 case ConnState::sendInProgress:
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530321 case ConnState::recvInProgress:
322 case ConnState::closeInProgress:
323 {
324 BMCWEB_LOG_DEBUG << "Async operation is already in progress";
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530325 break;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530326 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530327 case ConnState::initialized:
328 case ConnState::closed:
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530329 {
330 if (requestDataQueue.empty())
331 {
332 BMCWEB_LOG_DEBUG << "requestDataQueue is empty";
333 return;
334 }
335 doResolve();
336 break;
337 }
338 case ConnState::suspended:
339 case ConnState::terminated:
340 {
341 doClose();
342 break;
343 }
344 case ConnState::resolveFailed:
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530345 case ConnState::connectFailed:
346 case ConnState::sendFailed:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530347 case ConnState::recvFailed:
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530348 case ConnState::retry:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530349 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530350 // In case of failures during connect and handshake
351 // the retry policy will be applied
352 waitAndRetry();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530353 break;
354 }
355 case ConnState::connected:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530356 case ConnState::idle:
357 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530358 // State idle means, previous attempt is successful
359 // State connected means, client connection is established
360 // successfully
361 if (requestDataQueue.empty())
362 {
363 BMCWEB_LOG_DEBUG << "requestDataQueue is empty";
364 return;
365 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530366 std::string data = requestDataQueue.front();
367 sendMessage(data);
368 break;
369 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530370 case ConnState::abortConnection:
371 {
372 // Server did not want to keep alive the session
373 doClose();
374 break;
375 }
376 default:
377 break;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530378 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530379 }
380
381 public:
Ayushi Smritife44eb02020-05-15 15:24:45 +0530382 explicit HttpClient(boost::asio::io_context& ioc, const std::string& id,
383 const std::string& destIP, const std::string& destPort,
Ed Tanous4da04452021-09-08 19:57:44 -0700384 const std::string& destUri,
385 const boost::beast::http::fields& httpHeader) :
AppaRao Pulibd030d02020-03-20 03:34:29 +0530386 conn(ioc),
Ed Tanous4da04452021-09-08 19:57:44 -0700387 timer(ioc),
388 req(boost::beast::http::verb::post, destUri, 11, "", httpHeader),
389 state(ConnState::initialized), subId(id), host(destIP), port(destPort),
390 uri(destUri), retryCount(0), maxRetryAttempts(5), retryIntervalSecs(0),
Ayushi Smritife44eb02020-05-15 15:24:45 +0530391 retryPolicyAction("TerminateAfterRetries"), runningTimer(false)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530392 {
Ed Tanous4da04452021-09-08 19:57:44 -0700393 req.set(boost::beast::http::field::host, host);
394 req.keep_alive(true);
AppaRao Pulibd030d02020-03-20 03:34:29 +0530395 }
396
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530397 void sendData(const std::string& data)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530398 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530399 if ((state == ConnState::suspended) || (state == ConnState::terminated))
AppaRao Pulibd030d02020-03-20 03:34:29 +0530400 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530401 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530402 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530403
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530404 if (requestDataQueue.size() <= maxRequestQueueSize)
405 {
Sunitha Harish7de9f812021-08-24 02:50:30 -0500406 requestDataQueue.push_back(data);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530407 handleConnState();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530408 }
409 else
410 {
411 BMCWEB_LOG_ERROR << "Request queue is full. So ignoring data.";
412 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530413
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530414 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530415 }
416
Ayushi Smritife44eb02020-05-15 15:24:45 +0530417 void setRetryConfig(const uint32_t retryAttempts,
418 const uint32_t retryTimeoutInterval)
419 {
420 maxRetryAttempts = retryAttempts;
421 retryIntervalSecs = retryTimeoutInterval;
422 }
423
424 void setRetryPolicy(const std::string& retryPolicy)
425 {
426 retryPolicyAction = retryPolicy;
427 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530428};
429
430} // namespace crow