blob: ab20eb034bbffb6a9cbf7f194fb5c95154a661c2 [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;
AppaRao Pulibd030d02020-03-20 03:34:29 +053071 std::vector<std::pair<std::string, std::string>> headers;
Sunitha Harish7de9f812021-08-24 02:50:30 -050072 boost::circular_buffer_space_optimized<std::string> requestDataQueue{};
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053073
AppaRao Pulibd030d02020-03-20 03:34:29 +053074 ConnState state;
Ayushi Smritife44eb02020-05-15 15:24:45 +053075 std::string subId;
AppaRao Pulibd030d02020-03-20 03:34:29 +053076 std::string host;
77 std::string port;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053078 std::string uri;
Ayushi Smritife44eb02020-05-15 15:24:45 +053079 uint32_t retryCount;
80 uint32_t maxRetryAttempts;
81 uint32_t retryIntervalSecs;
82 std::string retryPolicyAction;
83 bool runningTimer;
AppaRao Pulibd030d02020-03-20 03:34:29 +053084
Sunitha Harish29a82b02021-02-18 15:54:16 +053085 void doResolve()
86 {
Sunitha Harish29a82b02021-02-18 15:54:16 +053087 state = ConnState::resolveInProgress;
Sunitha Harish29a82b02021-02-18 15:54:16 +053088 BMCWEB_LOG_DEBUG << "Trying to resolve: " << host << ":" << port;
89
90 auto respHandler =
91 [self(shared_from_this())](
92 const boost::beast::error_code ec,
93 const std::vector<boost::asio::ip::tcp::endpoint>&
94 endpointList) {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053095 if (ec || (endpointList.size() == 0))
Sunitha Harish29a82b02021-02-18 15:54:16 +053096 {
97 BMCWEB_LOG_ERROR << "Resolve failed: " << ec.message();
98 self->state = ConnState::resolveFailed;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +053099 self->handleConnState();
Sunitha Harish29a82b02021-02-18 15:54:16 +0530100 return;
101 }
102 BMCWEB_LOG_DEBUG << "Resolved";
103 self->doConnect(endpointList);
104 };
105 resolver.asyncResolve(host, port, std::move(respHandler));
106 }
107
108 void doConnect(
109 const std::vector<boost::asio::ip::tcp::endpoint>& endpointList)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530110 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530111 state = ConnState::connectInProgress;
112
113 BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":" << port;
Sunitha Harish29a82b02021-02-18 15:54:16 +0530114
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530115 conn.expires_after(std::chrono::seconds(30));
Sunitha Harish29a82b02021-02-18 15:54:16 +0530116 conn.async_connect(
117 endpointList, [self(shared_from_this())](
118 const boost::beast::error_code ec,
119 const boost::asio::ip::tcp::endpoint& endpoint) {
120 if (ec)
121 {
122 BMCWEB_LOG_ERROR << "Connect " << endpoint
123 << " failed: " << ec.message();
124 self->state = ConnState::connectFailed;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530125 self->handleConnState();
Sunitha Harish29a82b02021-02-18 15:54:16 +0530126 return;
127 }
Sunitha Harish29a82b02021-02-18 15:54:16 +0530128 BMCWEB_LOG_DEBUG << "Connected to: " << endpoint;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530129 self->state = ConnState::connected;
130 self->handleConnState();
Sunitha Harish29a82b02021-02-18 15:54:16 +0530131 });
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530132 }
133
134 void sendMessage(const std::string& data)
135 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530136 state = ConnState::sendInProgress;
137
138 BMCWEB_LOG_DEBUG << __FUNCTION__ << "(): " << host << ":" << port;
139
140 req.version(static_cast<int>(11)); // HTTP 1.1
141 req.target(uri);
142 req.method(boost::beast::http::verb::post);
143
144 // Set headers
145 for (const auto& [key, value] : headers)
146 {
147 req.set(key, value);
148 }
149 req.set(boost::beast::http::field::host, host);
150 req.keep_alive(true);
151
152 req.body() = data;
153 req.prepare_payload();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530154
155 // Set a timeout on the operation
156 conn.expires_after(std::chrono::seconds(30));
157
158 // Send the HTTP request to the remote host
159 boost::beast::http::async_write(
160 conn, req,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530161 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530162 const std::size_t& bytesTransferred) {
163 if (ec)
164 {
165 BMCWEB_LOG_ERROR << "sendMessage() failed: "
166 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530167 self->state = ConnState::sendFailed;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530168 self->handleConnState();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530169 return;
170 }
171 BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
172 << bytesTransferred;
173 boost::ignore_unused(bytesTransferred);
174
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530175 self->recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530176 });
177 }
178
179 void recvMessage()
180 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530181 state = ConnState::recvInProgress;
182
183 parser.emplace(std::piecewise_construct, std::make_tuple());
184 parser->body_limit(httpReadBodyLimit);
185
186 // Check only for the response header
187 parser->skip(true);
188
AppaRao Pulibd030d02020-03-20 03:34:29 +0530189 // Receive the HTTP response
190 boost::beast::http::async_read(
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530191 conn, buffer, *parser,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530192 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530193 const std::size_t& bytesTransferred) {
194 if (ec)
195 {
196 BMCWEB_LOG_ERROR << "recvMessage() failed: "
197 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530198 self->state = ConnState::recvFailed;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530199 self->handleConnState();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530200 return;
201 }
202 BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
203 << bytesTransferred;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530204 BMCWEB_LOG_DEBUG << "recvMessage() data: "
205 << self->parser->get();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530206
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530207 unsigned int respCode = self->parser->get().result_int();
208 BMCWEB_LOG_DEBUG << "recvMessage() Header Response Code: "
209 << respCode;
210
211 // 2XX response is considered to be successful
212 if ((respCode < 200) || (respCode >= 300))
213 {
214 // The listener failed to receive the Sent-Event
215 BMCWEB_LOG_ERROR << "recvMessage() Listener Failed to "
216 "receive Sent-Event";
217 self->state = ConnState::recvFailed;
218 self->handleConnState();
219 return;
220 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530221
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530222 // Send is successful, Lets remove data from queue
223 // check for next request data in queue.
Sunitha Harish7de9f812021-08-24 02:50:30 -0500224 if (!self->requestDataQueue.empty())
225 {
226 self->requestDataQueue.pop_front();
227 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530228 self->state = ConnState::idle;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530229
230 // Keep the connection alive if server supports it
231 // Else close the connection
232 BMCWEB_LOG_DEBUG << "recvMessage() keepalive : "
233 << self->parser->keep_alive();
234 if (!self->parser->keep_alive())
235 {
236 // Abort the connection since server is not keep-alive
237 // enabled
238 self->state = ConnState::abortConnection;
239 }
240
241 self->handleConnState();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530242 });
243 }
244
245 void doClose()
246 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530247 state = ConnState::closeInProgress;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530248 boost::beast::error_code ec;
249 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530250 conn.close();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530251
AppaRao Pulibd030d02020-03-20 03:34:29 +0530252 // not_connected happens sometimes so don't bother reporting it.
253 if (ec && ec != boost::beast::errc::not_connected)
254 {
255 BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message();
256 return;
257 }
258 BMCWEB_LOG_DEBUG << "Connection closed gracefully";
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530259 if ((state != ConnState::suspended) && (state != ConnState::terminated))
260 {
261 state = ConnState::closed;
262 handleConnState();
263 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530264 }
265
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530266 void waitAndRetry()
AppaRao Pulibd030d02020-03-20 03:34:29 +0530267 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530268 if (retryCount >= maxRetryAttempts)
269 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530270 BMCWEB_LOG_ERROR << "Maximum number of retries reached.";
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530271
272 // Clear queue.
273 while (!requestDataQueue.empty())
274 {
Sunitha Harish7de9f812021-08-24 02:50:30 -0500275 requestDataQueue.pop_front();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530276 }
277
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530278 BMCWEB_LOG_DEBUG << "Retry policy: " << retryPolicyAction;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530279 if (retryPolicyAction == "TerminateAfterRetries")
280 {
281 // TODO: delete subscription
282 state = ConnState::terminated;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530283 }
Ed Tanous3174e4d2020-10-07 11:41:22 -0700284 if (retryPolicyAction == "SuspendRetries")
Ayushi Smritife44eb02020-05-15 15:24:45 +0530285 {
286 state = ConnState::suspended;
Ayushi Smritife44eb02020-05-15 15:24:45 +0530287 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530288 // Reset the retrycount to zero so that client can try connecting
289 // again if needed
Ed Tanous3174e4d2020-10-07 11:41:22 -0700290 retryCount = 0;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530291 handleConnState();
Ayushi Smritife44eb02020-05-15 15:24:45 +0530292 return;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530293 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530294
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530295 if (runningTimer)
296 {
297 BMCWEB_LOG_DEBUG << "Retry timer is already running.";
298 return;
299 }
300 runningTimer = true;
301
302 retryCount++;
303
304 BMCWEB_LOG_DEBUG << "Attempt retry after " << retryIntervalSecs
305 << " seconds. RetryCount = " << retryCount;
306 timer.expires_after(std::chrono::seconds(retryIntervalSecs));
307 timer.async_wait(
308 [self = shared_from_this()](const boost::system::error_code ec) {
309 if (ec == boost::asio::error::operation_aborted)
310 {
311 BMCWEB_LOG_DEBUG
312 << "async_wait failed since the operation is aborted"
313 << ec.message();
314 }
315 else if (ec)
316 {
317 BMCWEB_LOG_ERROR << "async_wait failed: " << ec.message();
318 // Ignore the error and continue the retry loop to attempt
319 // sending the event as per the retry policy
320 }
321 self->runningTimer = false;
322
323 // Lets close connection and start from resolve.
324 self->doClose();
325 });
Ayushi Smritife44eb02020-05-15 15:24:45 +0530326 return;
327 }
328
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530329 void handleConnState()
Ayushi Smritife44eb02020-05-15 15:24:45 +0530330 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530331 switch (state)
332 {
Sunitha Harish29a82b02021-02-18 15:54:16 +0530333 case ConnState::resolveInProgress:
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530334 case ConnState::connectInProgress:
335 case ConnState::sendInProgress:
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530336 case ConnState::recvInProgress:
337 case ConnState::closeInProgress:
338 {
339 BMCWEB_LOG_DEBUG << "Async operation is already in progress";
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530340 break;
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530341 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530342 case ConnState::initialized:
343 case ConnState::closed:
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530344 {
345 if (requestDataQueue.empty())
346 {
347 BMCWEB_LOG_DEBUG << "requestDataQueue is empty";
348 return;
349 }
350 doResolve();
351 break;
352 }
353 case ConnState::suspended:
354 case ConnState::terminated:
355 {
356 doClose();
357 break;
358 }
359 case ConnState::resolveFailed:
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530360 case ConnState::connectFailed:
361 case ConnState::sendFailed:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530362 case ConnState::recvFailed:
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530363 case ConnState::retry:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530364 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530365 // In case of failures during connect and handshake
366 // the retry policy will be applied
367 waitAndRetry();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530368 break;
369 }
370 case ConnState::connected:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530371 case ConnState::idle:
372 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530373 // State idle means, previous attempt is successful
374 // State connected means, client connection is established
375 // successfully
376 if (requestDataQueue.empty())
377 {
378 BMCWEB_LOG_DEBUG << "requestDataQueue is empty";
379 return;
380 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530381 std::string data = requestDataQueue.front();
382 sendMessage(data);
383 break;
384 }
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530385 case ConnState::abortConnection:
386 {
387 // Server did not want to keep alive the session
388 doClose();
389 break;
390 }
391 default:
392 break;
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530393 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530394 }
395
396 public:
Ayushi Smritife44eb02020-05-15 15:24:45 +0530397 explicit HttpClient(boost::asio::io_context& ioc, const std::string& id,
398 const std::string& destIP, const std::string& destPort,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530399 const std::string& destUri) :
AppaRao Pulibd030d02020-03-20 03:34:29 +0530400 conn(ioc),
Ayushi Smritife44eb02020-05-15 15:24:45 +0530401 timer(ioc), subId(id), host(destIP), port(destPort), uri(destUri),
Ed Tanousf23b7292020-10-15 09:41:17 -0700402 retryCount(0), maxRetryAttempts(5), retryIntervalSecs(0),
Ayushi Smritife44eb02020-05-15 15:24:45 +0530403 retryPolicyAction("TerminateAfterRetries"), runningTimer(false)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530404 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530405 state = ConnState::initialized;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530406 }
407
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530408 void sendData(const std::string& data)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530409 {
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530410 if ((state == ConnState::suspended) || (state == ConnState::terminated))
AppaRao Pulibd030d02020-03-20 03:34:29 +0530411 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530412 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530413 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530414
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530415 if (requestDataQueue.size() <= maxRequestQueueSize)
416 {
Sunitha Harish7de9f812021-08-24 02:50:30 -0500417 requestDataQueue.push_back(data);
Sunitha Harish6eaa1d22021-02-19 13:38:31 +0530418 handleConnState();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530419 }
420 else
421 {
422 BMCWEB_LOG_ERROR << "Request queue is full. So ignoring data.";
423 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530424
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530425 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530426 }
427
428 void setHeaders(
429 const std::vector<std::pair<std::string, std::string>>& httpHeaders)
430 {
431 headers = httpHeaders;
432 }
Ayushi Smritife44eb02020-05-15 15:24:45 +0530433
434 void setRetryConfig(const uint32_t retryAttempts,
435 const uint32_t retryTimeoutInterval)
436 {
437 maxRetryAttempts = retryAttempts;
438 retryIntervalSecs = retryTimeoutInterval;
439 }
440
441 void setRetryPolicy(const std::string& retryPolicy)
442 {
443 retryPolicyAction = retryPolicy;
444 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530445};
446
447} // namespace crow