1bd030d0aSAppaRao Puli /* 2bd030d0aSAppaRao Puli // Copyright (c) 2020 Intel Corporation 3bd030d0aSAppaRao Puli // 4bd030d0aSAppaRao Puli // Licensed under the Apache License, Version 2.0 (the "License"); 5bd030d0aSAppaRao Puli // you may not use this file except in compliance with the License. 6bd030d0aSAppaRao Puli // You may obtain a copy of the License at 7bd030d0aSAppaRao Puli // 8bd030d0aSAppaRao Puli // http://www.apache.org/licenses/LICENSE-2.0 9bd030d0aSAppaRao Puli // 10bd030d0aSAppaRao Puli // Unless required by applicable law or agreed to in writing, software 11bd030d0aSAppaRao Puli // distributed under the License is distributed on an "AS IS" BASIS, 12bd030d0aSAppaRao Puli // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13bd030d0aSAppaRao Puli // See the License for the specific language governing permissions and 14bd030d0aSAppaRao Puli // limitations under the License. 15bd030d0aSAppaRao Puli */ 16bd030d0aSAppaRao Puli #pragma once 17*d43cd0caSEd Tanous #include <boost/asio/steady_timer.hpp> 18*d43cd0caSEd Tanous #include <boost/beast/core/flat_buffer.hpp> 19*d43cd0caSEd Tanous #include <boost/beast/core/tcp_stream.hpp> 20*d43cd0caSEd Tanous #include <boost/beast/http/message.hpp> 21bd030d0aSAppaRao Puli #include <boost/beast/version.hpp> 221214b7e7SGunnar Mills 23bd030d0aSAppaRao Puli #include <cstdlib> 24bd030d0aSAppaRao Puli #include <functional> 25bd030d0aSAppaRao Puli #include <iostream> 26bd030d0aSAppaRao Puli #include <memory> 272a5689a7SAppaRao Puli #include <queue> 28bd030d0aSAppaRao Puli #include <string> 29bd030d0aSAppaRao Puli 30bd030d0aSAppaRao Puli namespace crow 31bd030d0aSAppaRao Puli { 32bd030d0aSAppaRao Puli 332a5689a7SAppaRao Puli static constexpr uint8_t maxRequestQueueSize = 50; 342a5689a7SAppaRao Puli 35bd030d0aSAppaRao Puli enum class ConnState 36bd030d0aSAppaRao Puli { 372a5689a7SAppaRao Puli initialized, 382a5689a7SAppaRao Puli connectInProgress, 392a5689a7SAppaRao Puli connectFailed, 40bd030d0aSAppaRao Puli connected, 412a5689a7SAppaRao Puli sendInProgress, 422a5689a7SAppaRao Puli sendFailed, 432a5689a7SAppaRao Puli recvFailed, 442a5689a7SAppaRao Puli idle, 452a5689a7SAppaRao Puli suspended, 46fe44eb0bSAyushi Smriti closed, 47fe44eb0bSAyushi Smriti terminated 48bd030d0aSAppaRao Puli }; 49bd030d0aSAppaRao Puli 50bd030d0aSAppaRao Puli class HttpClient : public std::enable_shared_from_this<HttpClient> 51bd030d0aSAppaRao Puli { 52bd030d0aSAppaRao Puli private: 53bd030d0aSAppaRao Puli boost::beast::tcp_stream conn; 54fe44eb0bSAyushi Smriti boost::asio::steady_timer timer; 55bd030d0aSAppaRao Puli boost::beast::flat_buffer buffer; 56bd030d0aSAppaRao Puli boost::beast::http::request<boost::beast::http::string_body> req; 57bd030d0aSAppaRao Puli boost::beast::http::response<boost::beast::http::string_body> res; 58bd030d0aSAppaRao Puli boost::asio::ip::tcp::resolver::results_type endpoint; 59bd030d0aSAppaRao Puli std::vector<std::pair<std::string, std::string>> headers; 602a5689a7SAppaRao Puli std::queue<std::string> requestDataQueue; 61bd030d0aSAppaRao Puli ConnState state; 62fe44eb0bSAyushi Smriti std::string subId; 63bd030d0aSAppaRao Puli std::string host; 64bd030d0aSAppaRao Puli std::string port; 652a5689a7SAppaRao Puli std::string uri; 66fe44eb0bSAyushi Smriti uint32_t retryCount; 67fe44eb0bSAyushi Smriti uint32_t maxRetryAttempts; 68fe44eb0bSAyushi Smriti uint32_t retryIntervalSecs; 69fe44eb0bSAyushi Smriti std::string retryPolicyAction; 70fe44eb0bSAyushi Smriti bool runningTimer; 71bd030d0aSAppaRao Puli 722a5689a7SAppaRao Puli void doConnect() 73bd030d0aSAppaRao Puli { 742a5689a7SAppaRao Puli if (state == ConnState::connectInProgress) 75bd030d0aSAppaRao Puli { 76bd030d0aSAppaRao Puli return; 77bd030d0aSAppaRao Puli } 782a5689a7SAppaRao Puli state = ConnState::connectInProgress; 792a5689a7SAppaRao Puli 802a5689a7SAppaRao Puli BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":" << port; 812a5689a7SAppaRao Puli // Set a timeout on the operation 822a5689a7SAppaRao Puli conn.expires_after(std::chrono::seconds(30)); 832a5689a7SAppaRao Puli conn.async_connect(endpoint, [self(shared_from_this())]( 842a5689a7SAppaRao Puli const boost::beast::error_code& ec, 852a5689a7SAppaRao Puli const boost::asio::ip::tcp::resolver:: 862a5689a7SAppaRao Puli results_type::endpoint_type& ep) { 872a5689a7SAppaRao Puli if (ec) 882a5689a7SAppaRao Puli { 892a5689a7SAppaRao Puli BMCWEB_LOG_ERROR << "Connect " << ep 902a5689a7SAppaRao Puli << " failed: " << ec.message(); 912a5689a7SAppaRao Puli self->state = ConnState::connectFailed; 922a5689a7SAppaRao Puli self->checkQueue(); 932a5689a7SAppaRao Puli return; 942a5689a7SAppaRao Puli } 952a5689a7SAppaRao Puli self->state = ConnState::connected; 962a5689a7SAppaRao Puli BMCWEB_LOG_DEBUG << "Connected to: " << ep; 972a5689a7SAppaRao Puli 982a5689a7SAppaRao Puli self->checkQueue(); 992a5689a7SAppaRao Puli }); 1002a5689a7SAppaRao Puli } 1012a5689a7SAppaRao Puli 1022a5689a7SAppaRao Puli void sendMessage(const std::string& data) 1032a5689a7SAppaRao Puli { 1042a5689a7SAppaRao Puli if (state == ConnState::sendInProgress) 1052a5689a7SAppaRao Puli { 1062a5689a7SAppaRao Puli return; 1072a5689a7SAppaRao Puli } 1082a5689a7SAppaRao Puli state = ConnState::sendInProgress; 1092a5689a7SAppaRao Puli 1102a5689a7SAppaRao Puli BMCWEB_LOG_DEBUG << __FUNCTION__ << "(): " << host << ":" << port; 1112a5689a7SAppaRao Puli 1122a5689a7SAppaRao Puli req.version(static_cast<int>(11)); // HTTP 1.1 1132a5689a7SAppaRao Puli req.target(uri); 1142a5689a7SAppaRao Puli req.method(boost::beast::http::verb::post); 1152a5689a7SAppaRao Puli 1162a5689a7SAppaRao Puli // Set headers 1172a5689a7SAppaRao Puli for (const auto& [key, value] : headers) 1182a5689a7SAppaRao Puli { 1192a5689a7SAppaRao Puli req.set(key, value); 1202a5689a7SAppaRao Puli } 1212a5689a7SAppaRao Puli req.set(boost::beast::http::field::host, host); 1222a5689a7SAppaRao Puli req.keep_alive(true); 1232a5689a7SAppaRao Puli 1242a5689a7SAppaRao Puli req.body() = data; 1252a5689a7SAppaRao Puli req.prepare_payload(); 126bd030d0aSAppaRao Puli 127bd030d0aSAppaRao Puli // Set a timeout on the operation 128bd030d0aSAppaRao Puli conn.expires_after(std::chrono::seconds(30)); 129bd030d0aSAppaRao Puli 130bd030d0aSAppaRao Puli // Send the HTTP request to the remote host 131bd030d0aSAppaRao Puli boost::beast::http::async_write( 132bd030d0aSAppaRao Puli conn, req, 1332a5689a7SAppaRao Puli [self(shared_from_this())](const boost::beast::error_code& ec, 134bd030d0aSAppaRao Puli const std::size_t& bytesTransferred) { 135bd030d0aSAppaRao Puli if (ec) 136bd030d0aSAppaRao Puli { 137bd030d0aSAppaRao Puli BMCWEB_LOG_ERROR << "sendMessage() failed: " 138bd030d0aSAppaRao Puli << ec.message(); 1392a5689a7SAppaRao Puli self->state = ConnState::sendFailed; 1402a5689a7SAppaRao Puli self->checkQueue(); 141bd030d0aSAppaRao Puli return; 142bd030d0aSAppaRao Puli } 143bd030d0aSAppaRao Puli BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: " 144bd030d0aSAppaRao Puli << bytesTransferred; 145bd030d0aSAppaRao Puli boost::ignore_unused(bytesTransferred); 146bd030d0aSAppaRao Puli 1472a5689a7SAppaRao Puli self->recvMessage(); 148bd030d0aSAppaRao Puli }); 149bd030d0aSAppaRao Puli } 150bd030d0aSAppaRao Puli 151bd030d0aSAppaRao Puli void recvMessage() 152bd030d0aSAppaRao Puli { 153bd030d0aSAppaRao Puli // Receive the HTTP response 154bd030d0aSAppaRao Puli boost::beast::http::async_read( 155bd030d0aSAppaRao Puli conn, buffer, res, 1562a5689a7SAppaRao Puli [self(shared_from_this())](const boost::beast::error_code& ec, 157bd030d0aSAppaRao Puli const std::size_t& bytesTransferred) { 158bd030d0aSAppaRao Puli if (ec) 159bd030d0aSAppaRao Puli { 160bd030d0aSAppaRao Puli BMCWEB_LOG_ERROR << "recvMessage() failed: " 161bd030d0aSAppaRao Puli << ec.message(); 1622a5689a7SAppaRao Puli self->state = ConnState::recvFailed; 1632a5689a7SAppaRao Puli self->checkQueue(); 164bd030d0aSAppaRao Puli return; 165bd030d0aSAppaRao Puli } 166bd030d0aSAppaRao Puli BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: " 167bd030d0aSAppaRao Puli << bytesTransferred; 168bd030d0aSAppaRao Puli boost::ignore_unused(bytesTransferred); 169bd030d0aSAppaRao Puli 170bd030d0aSAppaRao Puli // Discard received data. We are not interested. 1712a5689a7SAppaRao Puli BMCWEB_LOG_DEBUG << "recvMessage() data: " << self->res; 172bd030d0aSAppaRao Puli 1732a5689a7SAppaRao Puli // Send is successful, Lets remove data from queue 1742a5689a7SAppaRao Puli // check for next request data in queue. 1752a5689a7SAppaRao Puli self->requestDataQueue.pop(); 1762a5689a7SAppaRao Puli self->state = ConnState::idle; 1772a5689a7SAppaRao Puli self->checkQueue(); 178bd030d0aSAppaRao Puli }); 179bd030d0aSAppaRao Puli } 180bd030d0aSAppaRao Puli 181bd030d0aSAppaRao Puli void doClose() 182bd030d0aSAppaRao Puli { 183bd030d0aSAppaRao Puli boost::beast::error_code ec; 184bd030d0aSAppaRao Puli conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); 185bd030d0aSAppaRao Puli 186bd030d0aSAppaRao Puli state = ConnState::closed; 187bd030d0aSAppaRao Puli // not_connected happens sometimes so don't bother reporting it. 188bd030d0aSAppaRao Puli if (ec && ec != boost::beast::errc::not_connected) 189bd030d0aSAppaRao Puli { 190bd030d0aSAppaRao Puli BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message(); 191bd030d0aSAppaRao Puli return; 192bd030d0aSAppaRao Puli } 193bd030d0aSAppaRao Puli BMCWEB_LOG_DEBUG << "Connection closed gracefully"; 194bd030d0aSAppaRao Puli } 195bd030d0aSAppaRao Puli 1962a5689a7SAppaRao Puli void checkQueue(const bool newRecord = false) 197bd030d0aSAppaRao Puli { 1982a5689a7SAppaRao Puli if (requestDataQueue.empty()) 1992a5689a7SAppaRao Puli { 2002a5689a7SAppaRao Puli // TODO: Having issue in keeping connection alive. So lets close if 201caa3ce3cSGunnar Mills // nothing to be transferred. 2022a5689a7SAppaRao Puli doClose(); 2032a5689a7SAppaRao Puli 2042a5689a7SAppaRao Puli BMCWEB_LOG_DEBUG << "requestDataQueue is empty\n"; 2052a5689a7SAppaRao Puli return; 2062a5689a7SAppaRao Puli } 2072a5689a7SAppaRao Puli 2082a5689a7SAppaRao Puli if (retryCount >= maxRetryAttempts) 2092a5689a7SAppaRao Puli { 2102a5689a7SAppaRao Puli BMCWEB_LOG_ERROR << "Maximum number of retries is reached."; 2112a5689a7SAppaRao Puli 2122a5689a7SAppaRao Puli // Clear queue. 2132a5689a7SAppaRao Puli while (!requestDataQueue.empty()) 2142a5689a7SAppaRao Puli { 2152a5689a7SAppaRao Puli requestDataQueue.pop(); 2162a5689a7SAppaRao Puli } 2172a5689a7SAppaRao Puli 218fe44eb0bSAyushi Smriti BMCWEB_LOG_DEBUG << "Retry policy is set to " << retryPolicyAction; 219fe44eb0bSAyushi Smriti if (retryPolicyAction == "TerminateAfterRetries") 220fe44eb0bSAyushi Smriti { 221fe44eb0bSAyushi Smriti // TODO: delete subscription 222fe44eb0bSAyushi Smriti state = ConnState::terminated; 223fe44eb0bSAyushi Smriti return; 224fe44eb0bSAyushi Smriti } 225fe44eb0bSAyushi Smriti else if (retryPolicyAction == "SuspendRetries") 226fe44eb0bSAyushi Smriti { 2272a5689a7SAppaRao Puli state = ConnState::suspended; 2282a5689a7SAppaRao Puli return; 2292a5689a7SAppaRao Puli } 230fe44eb0bSAyushi Smriti else 231fe44eb0bSAyushi Smriti { 232fe44eb0bSAyushi Smriti // keep retrying, reset count and continue. 233fe44eb0bSAyushi Smriti retryCount = 0; 234fe44eb0bSAyushi Smriti } 235fe44eb0bSAyushi Smriti } 2362a5689a7SAppaRao Puli 2372a5689a7SAppaRao Puli if ((state == ConnState::connectFailed) || 2382a5689a7SAppaRao Puli (state == ConnState::sendFailed) || 2392a5689a7SAppaRao Puli (state == ConnState::recvFailed)) 2402a5689a7SAppaRao Puli { 2412a5689a7SAppaRao Puli if (newRecord) 2422a5689a7SAppaRao Puli { 2432a5689a7SAppaRao Puli // We are already running async wait and retry. 2442a5689a7SAppaRao Puli // Since record is added to queue, it gets the 2452a5689a7SAppaRao Puli // turn in FIFO. 2462a5689a7SAppaRao Puli return; 2472a5689a7SAppaRao Puli } 2482a5689a7SAppaRao Puli 249fe44eb0bSAyushi Smriti if (runningTimer) 250fe44eb0bSAyushi Smriti { 251fe44eb0bSAyushi Smriti BMCWEB_LOG_DEBUG << "Retry timer is already running."; 252fe44eb0bSAyushi Smriti return; 253fe44eb0bSAyushi Smriti } 254fe44eb0bSAyushi Smriti runningTimer = true; 255fe44eb0bSAyushi Smriti 2562a5689a7SAppaRao Puli retryCount++; 257fe44eb0bSAyushi Smriti 258fe44eb0bSAyushi Smriti BMCWEB_LOG_DEBUG << "Attempt retry after " << retryIntervalSecs 259fe44eb0bSAyushi Smriti << " seconds. RetryCount = " << retryCount; 260fe44eb0bSAyushi Smriti timer.expires_after(std::chrono::seconds(retryIntervalSecs)); 261cb13a392SEd Tanous timer.async_wait( 262cb13a392SEd Tanous [self = shared_from_this()](const boost::system::error_code&) { 263fe44eb0bSAyushi Smriti self->runningTimer = false; 264fe44eb0bSAyushi Smriti self->connStateCheck(); 265fe44eb0bSAyushi Smriti }); 266fe44eb0bSAyushi Smriti return; 2672a5689a7SAppaRao Puli } 2682a5689a7SAppaRao Puli else 2692a5689a7SAppaRao Puli { 2702a5689a7SAppaRao Puli // reset retry count. 2712a5689a7SAppaRao Puli retryCount = 0; 2722a5689a7SAppaRao Puli } 273fe44eb0bSAyushi Smriti connStateCheck(); 2742a5689a7SAppaRao Puli 275fe44eb0bSAyushi Smriti return; 276fe44eb0bSAyushi Smriti } 277fe44eb0bSAyushi Smriti 278fe44eb0bSAyushi Smriti void connStateCheck() 279fe44eb0bSAyushi Smriti { 2802a5689a7SAppaRao Puli switch (state) 2812a5689a7SAppaRao Puli { 2822a5689a7SAppaRao Puli case ConnState::connectInProgress: 2832a5689a7SAppaRao Puli case ConnState::sendInProgress: 2842a5689a7SAppaRao Puli case ConnState::suspended: 285fe44eb0bSAyushi Smriti case ConnState::terminated: 2862a5689a7SAppaRao Puli // do nothing 2872a5689a7SAppaRao Puli break; 2882a5689a7SAppaRao Puli case ConnState::initialized: 2892a5689a7SAppaRao Puli case ConnState::closed: 2902a5689a7SAppaRao Puli case ConnState::connectFailed: 2912a5689a7SAppaRao Puli case ConnState::sendFailed: 29292a74e56SAppaRao Puli case ConnState::recvFailed: 29392a74e56SAppaRao Puli { 2942a5689a7SAppaRao Puli // After establishing the connection, checkQueue() will 2952a5689a7SAppaRao Puli // get called and it will attempt to send data. 2962a5689a7SAppaRao Puli doConnect(); 2972a5689a7SAppaRao Puli break; 2982a5689a7SAppaRao Puli } 2992a5689a7SAppaRao Puli case ConnState::connected: 30092a74e56SAppaRao Puli case ConnState::idle: 30192a74e56SAppaRao Puli { 3022a5689a7SAppaRao Puli std::string data = requestDataQueue.front(); 3032a5689a7SAppaRao Puli sendMessage(data); 3042a5689a7SAppaRao Puli break; 3052a5689a7SAppaRao Puli } 3062a5689a7SAppaRao Puli } 307bd030d0aSAppaRao Puli } 308bd030d0aSAppaRao Puli 309bd030d0aSAppaRao Puli public: 310fe44eb0bSAyushi Smriti explicit HttpClient(boost::asio::io_context& ioc, const std::string& id, 311fe44eb0bSAyushi Smriti const std::string& destIP, const std::string& destPort, 3122a5689a7SAppaRao Puli const std::string& destUri) : 313bd030d0aSAppaRao Puli conn(ioc), 314fe44eb0bSAyushi Smriti timer(ioc), subId(id), host(destIP), port(destPort), uri(destUri), 315fe44eb0bSAyushi Smriti retryCount(0), maxRetryAttempts(5), 316fe44eb0bSAyushi Smriti retryPolicyAction("TerminateAfterRetries"), runningTimer(false) 317bd030d0aSAppaRao Puli { 318bd030d0aSAppaRao Puli boost::asio::ip::tcp::resolver resolver(ioc); 319bd030d0aSAppaRao Puli endpoint = resolver.resolve(host, port); 3202a5689a7SAppaRao Puli state = ConnState::initialized; 321bd030d0aSAppaRao Puli } 322bd030d0aSAppaRao Puli 3232a5689a7SAppaRao Puli void sendData(const std::string& data) 324bd030d0aSAppaRao Puli { 3252a5689a7SAppaRao Puli if (state == ConnState::suspended) 326bd030d0aSAppaRao Puli { 327bd030d0aSAppaRao Puli return; 328bd030d0aSAppaRao Puli } 329bd030d0aSAppaRao Puli 3302a5689a7SAppaRao Puli if (requestDataQueue.size() <= maxRequestQueueSize) 3312a5689a7SAppaRao Puli { 3322a5689a7SAppaRao Puli requestDataQueue.push(data); 3332a5689a7SAppaRao Puli checkQueue(true); 3342a5689a7SAppaRao Puli } 3352a5689a7SAppaRao Puli else 3362a5689a7SAppaRao Puli { 3372a5689a7SAppaRao Puli BMCWEB_LOG_ERROR << "Request queue is full. So ignoring data."; 3382a5689a7SAppaRao Puli } 3392a5689a7SAppaRao Puli 3402a5689a7SAppaRao Puli return; 341bd030d0aSAppaRao Puli } 342bd030d0aSAppaRao Puli 343bd030d0aSAppaRao Puli void setHeaders( 344bd030d0aSAppaRao Puli const std::vector<std::pair<std::string, std::string>>& httpHeaders) 345bd030d0aSAppaRao Puli { 346bd030d0aSAppaRao Puli headers = httpHeaders; 347bd030d0aSAppaRao Puli } 348fe44eb0bSAyushi Smriti 349fe44eb0bSAyushi Smriti void setRetryConfig(const uint32_t retryAttempts, 350fe44eb0bSAyushi Smriti const uint32_t retryTimeoutInterval) 351fe44eb0bSAyushi Smriti { 352fe44eb0bSAyushi Smriti maxRetryAttempts = retryAttempts; 353fe44eb0bSAyushi Smriti retryIntervalSecs = retryTimeoutInterval; 354fe44eb0bSAyushi Smriti } 355fe44eb0bSAyushi Smriti 356fe44eb0bSAyushi Smriti void setRetryPolicy(const std::string& retryPolicy) 357fe44eb0bSAyushi Smriti { 358fe44eb0bSAyushi Smriti retryPolicyAction = retryPolicy; 359fe44eb0bSAyushi Smriti } 360bd030d0aSAppaRao Puli }; 361bd030d0aSAppaRao Puli 362bd030d0aSAppaRao Puli } // namespace crow 363