blob: 53040aeec7e530c5091048b076a77856c4322aa4 [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
137 BMCWEB_LOG_DEBUG << __FUNCTION__ << "(): " << host << ":" << port;
138
139 req.version(static_cast<int>(11)); // HTTP 1.1
140 req.target(uri);
141 req.method(boost::beast::http::verb::post);
142
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530143 req.set(boost::beast::http::field::host, host);
144 req.keep_alive(true);
145
146 req.body() = data;
147 req.prepare_payload();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530148
149 // Set a timeout on the operation
150 conn.expires_after(std::chrono::seconds(30));
151
152 // Send the HTTP request to the remote host
153 boost::beast::http::async_write(
154 conn, req,
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 << "sendMessage() failed: "
160 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530161 self->state = ConnState::sendFailed;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530162 self->handleConnState();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530163 return;
164 }
165 BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
166 << bytesTransferred;
167 boost::ignore_unused(bytesTransferred);
168
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530169 self->recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530170 });
171 }
172
173 void recvMessage()
174 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530175 state = ConnState::recvInProgress;
176
177 parser.emplace(std::piecewise_construct, std::make_tuple());
178 parser->body_limit(httpReadBodyLimit);
179
180 // Check only for the response header
181 parser->skip(true);
182
AppaRao Pulibd030d02020-03-20 03:34:29 +0530183 // Receive the HTTP response
184 boost::beast::http::async_read(
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530185 conn, buffer, *parser,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530186 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530187 const std::size_t& bytesTransferred) {
188 if (ec)
189 {
190 BMCWEB_LOG_ERROR << "recvMessage() failed: "
191 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530192 self->state = ConnState::recvFailed;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530193 self->handleConnState();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530194 return;
195 }
196 BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
197 << bytesTransferred;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530198 BMCWEB_LOG_DEBUG << "recvMessage() data: "
199 << self->parser->get();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530200
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530201 unsigned int respCode = self->parser->get().result_int();
202 BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: "
203 << respCode;
204
205 // 2XX response is considered to be successful
206 if ((respCode < 200) || (respCode >= 300))
207 {
208 // The listener failed to receive the Sent-Event
209 BMCWEB_LOG_ERROR << "recvMessage() Listener Failed to "
210 "receive Sent-Event";
211 self->state = ConnState::recvFailed;
212 self->handleConnState();
213 return;
214 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530215
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530216 // Send is successful, Lets remove data from queue
217 // check for next request data in queue.
Sunitha Harish7de9f812021-08-24 02:50:30 -0500218 if (!self->requestDataQueue.empty())
219 {
220 self->requestDataQueue.pop_front();
221 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530222 self->state = ConnState::idle;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530223
224 // Keep the connection alive if server supports it
225 // Else close the connection
226 BMCWEB_LOG_DEBUG << "recvMessage() keepalive : "
227 << self->parser->keep_alive();
228 if (!self->parser->keep_alive())
229 {
230 // Abort the connection since server is not keep-alive
231 // enabled
232 self->state = ConnState::abortConnection;
233 }
234
235 self->handleConnState();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530236 });
237 }
238
239 void doClose()
240 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530241 state = ConnState::closeInProgress;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530242 boost::beast::error_code ec;
243 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530244 conn.close();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530245
AppaRao Pulibd030d02020-03-20 03:34:29 +0530246 // not_connected happens sometimes so don't bother reporting it.
247 if (ec && ec != boost::beast::errc::not_connected)
248 {
249 BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message();
250 return;
251 }
252 BMCWEB_LOG_DEBUG << "Connection closed gracefully";
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530253 if ((state != ConnState::suspended) && (state != ConnState::terminated))
254 {
255 state = ConnState::closed;
256 handleConnState();
257 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530258 }
259
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530260 void waitAndRetry()
AppaRao Pulibd030d02020-03-20 03:34:29 +0530261 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530262 if (retryCount >= maxRetryAttempts)
263 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530264 BMCWEB_LOG_ERROR << "Maximum number of retries reached.";
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530265
266 // Clear queue.
267 while (!requestDataQueue.empty())
268 {
Sunitha Harish7de9f812021-08-24 02:50:30 -0500269 requestDataQueue.pop_front();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530270 }
271
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530272 BMCWEB_LOG_DEBUG << "Retry policy: " << retryPolicyAction;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530273 if (retryPolicyAction == "TerminateAfterRetries")
274 {
275 // TODO: delete subscription
276 state = ConnState::terminated;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530277 }
Ed Tanous3174e4d2020-10-07 11:41:22 -0700278 if (retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530279 {
280 state = ConnState::suspended;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530281 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530282 // Reset the retrycount to zero so that client can try connecting
283 // again if needed
Ed Tanous3174e4d2020-10-07 11:41:22 -0700284 retryCount = 0;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530285 handleConnState();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530286 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530287 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530288
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530289 if (runningTimer)
290 {
291 BMCWEB_LOG_DEBUG << "Retry timer is already running.";
292 return;
293 }
294 runningTimer = true;
295
296 retryCount++;
297
298 BMCWEB_LOG_DEBUG << "Attempt retry after " << retryIntervalSecs
299 << " seconds. RetryCount = " << retryCount;
300 timer.expires_after(std::chrono::seconds(retryIntervalSecs));
301 timer.async_wait(
302 [self = shared_from_this()](const boost::system::error_code ec) {
303 if (ec == boost::asio::error::operation_aborted)
304 {
305 BMCWEB_LOG_DEBUG
306 << "async_wait failed since the operation is aborted"
307 << ec.message();
308 }
309 else if (ec)
310 {
311 BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message();
312 // Ignore the error and continue the retry loop to attempt
313 // sending the event as per the retry policy
314 }
315 self->runningTimer = false;
316
317 // Lets close connection and start from resolve.
318 self->doClose();
319 });
Ayushi Smritife44eb02020-05-15 15:24:45 +0530320 return;
321 }
322
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530323 void handleConnState()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530324 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530325 switch (state)
326 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530327 case ConnState::resolveInProgress:
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530328 case ConnState::connectInProgress:
329 case ConnState::sendInProgress:
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530330 case ConnState::recvInProgress:
331 case ConnState::closeInProgress:
332 {
333 BMCWEB_LOG_DEBUG << "Async operation is already in progress";
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530334 break;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530335 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530336 case ConnState::initialized:
337 case ConnState::closed:
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530338 {
339 if (requestDataQueue.empty())
340 {
341 BMCWEB_LOG_DEBUG << "requestDataQueue is empty";
342 return;
343 }
344 doResolve();
345 break;
346 }
347 case ConnState::suspended:
348 case ConnState::terminated:
349 {
350 doClose();
351 break;
352 }
353 case ConnState::resolveFailed:
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530354 case ConnState::connectFailed:
355 case ConnState::sendFailed:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530356 case ConnState::recvFailed:
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530357 case ConnState::retry:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530358 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530359 // In case of failures during connect and handshake
360 // the retry policy will be applied
361 waitAndRetry();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530362 break;
363 }
364 case ConnState::connected:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530365 case ConnState::idle:
366 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530367 // State idle means, previous attempt is successful
368 // State connected means, client connection is established
369 // successfully
370 if (requestDataQueue.empty())
371 {
372 BMCWEB_LOG_DEBUG << "requestDataQueue is empty";
373 return;
374 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530375 std::string data = requestDataQueue.front();
376 sendMessage(data);
377 break;
378 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530379 case ConnState::abortConnection:
380 {
381 // Server did not want to keep alive the session
382 doClose();
383 break;
384 }
385 default:
386 break;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530387 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530388 }
389
390 public:
Ayushi Smritife44eb02020-05-15 15:24:45 +0530391 explicit HttpClient(boost::asio::io_context& ioc, const std::string& id,
392 const std::string& destIP, const std::string& destPort,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530393 const std::string& destUri) :
AppaRao Pulibd030d02020-03-20 03:34:29 +0530394 conn(ioc),
Ayushi Smritife44eb02020-05-15 15:24:45 +0530395 timer(ioc), subId(id), host(destIP), port(destPort), uri(destUri),
Ed Tanousf23b7292020-10-15 09:41:17 -0700396 retryCount(0), maxRetryAttempts(5), retryIntervalSecs(0),
Ayushi Smritife44eb02020-05-15 15:24:45 +0530397 retryPolicyAction("TerminateAfterRetries"), runningTimer(false)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530398 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530399 state = ConnState::initialized;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530400 }
401
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530402 void sendData(const std::string& data)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530403 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530404 if ((state == ConnState::suspended) || (state == ConnState::terminated))
AppaRao Pulibd030d02020-03-20 03:34:29 +0530405 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530406 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530407 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530408
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530409 if (requestDataQueue.size() <= maxRequestQueueSize)
410 {
Sunitha Harish7de9f812021-08-24 02:50:30 -0500411 requestDataQueue.push_back(data);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530412 handleConnState();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530413 }
414 else
415 {
416 BMCWEB_LOG_ERROR << "Request queue is full. So ignoring data.";
417 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530418
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530419 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530420 }
421
Ed Tanous601c71a2021-09-08 16:40:12 -0700422 void setHeaders(const boost::beast::http::fields& httpHeaders)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530423 {
Ed Tanous601c71a2021-09-08 16:40:12 -0700424 req.base() = boost::beast::http::header<true>(httpHeaders);
AppaRao Pulibd030d02020-03-20 03:34:29 +0530425 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530426
427 void setRetryConfig(const uint32_t retryAttempts,
428 const uint32_t retryTimeoutInterval)
429 {
430 maxRetryAttempts = retryAttempts;
431 retryIntervalSecs = retryTimeoutInterval;
432 }
433
434 void setRetryPolicy(const std::string& retryPolicy)
435 {
436 retryPolicyAction = retryPolicy;
437 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530438};
439
440} // namespace crow