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 17bd030d0aSAppaRao Puli #include <boost/asio/strand.hpp> 18bd030d0aSAppaRao Puli #include <boost/beast/core.hpp> 19bd030d0aSAppaRao Puli #include <boost/beast/http.hpp> 20bd030d0aSAppaRao Puli #include <boost/beast/version.hpp> 21bd030d0aSAppaRao Puli #include <cstdlib> 22bd030d0aSAppaRao Puli #include <functional> 23bd030d0aSAppaRao Puli #include <iostream> 24bd030d0aSAppaRao Puli #include <memory> 252a5689a7SAppaRao Puli #include <queue> 26bd030d0aSAppaRao Puli #include <string> 27bd030d0aSAppaRao Puli 28bd030d0aSAppaRao Puli namespace crow 29bd030d0aSAppaRao Puli { 30bd030d0aSAppaRao Puli 312a5689a7SAppaRao Puli static constexpr uint8_t maxRequestQueueSize = 50; 322a5689a7SAppaRao Puli 33bd030d0aSAppaRao Puli enum class ConnState 34bd030d0aSAppaRao Puli { 352a5689a7SAppaRao Puli initialized, 362a5689a7SAppaRao Puli connectInProgress, 372a5689a7SAppaRao Puli connectFailed, 38bd030d0aSAppaRao Puli connected, 392a5689a7SAppaRao Puli sendInProgress, 402a5689a7SAppaRao Puli sendFailed, 412a5689a7SAppaRao Puli recvFailed, 422a5689a7SAppaRao Puli idle, 432a5689a7SAppaRao Puli suspended, 44bd030d0aSAppaRao Puli closed 45bd030d0aSAppaRao Puli }; 46bd030d0aSAppaRao Puli 47bd030d0aSAppaRao Puli class HttpClient : public std::enable_shared_from_this<HttpClient> 48bd030d0aSAppaRao Puli { 49bd030d0aSAppaRao Puli private: 50bd030d0aSAppaRao Puli boost::beast::tcp_stream conn; 51bd030d0aSAppaRao Puli boost::beast::flat_buffer buffer; 52bd030d0aSAppaRao Puli boost::beast::http::request<boost::beast::http::string_body> req; 53bd030d0aSAppaRao Puli boost::beast::http::response<boost::beast::http::string_body> res; 54bd030d0aSAppaRao Puli boost::asio::ip::tcp::resolver::results_type endpoint; 55bd030d0aSAppaRao Puli std::vector<std::pair<std::string, std::string>> headers; 562a5689a7SAppaRao Puli std::queue<std::string> requestDataQueue; 57bd030d0aSAppaRao Puli ConnState state; 58bd030d0aSAppaRao Puli std::string host; 59bd030d0aSAppaRao Puli std::string port; 602a5689a7SAppaRao Puli std::string uri; 612a5689a7SAppaRao Puli int retryCount; 622a5689a7SAppaRao Puli int maxRetryAttempts; 63bd030d0aSAppaRao Puli 642a5689a7SAppaRao Puli void doConnect() 65bd030d0aSAppaRao Puli { 662a5689a7SAppaRao Puli if (state == ConnState::connectInProgress) 67bd030d0aSAppaRao Puli { 68bd030d0aSAppaRao Puli return; 69bd030d0aSAppaRao Puli } 702a5689a7SAppaRao Puli state = ConnState::connectInProgress; 712a5689a7SAppaRao Puli 722a5689a7SAppaRao Puli BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":" << port; 732a5689a7SAppaRao Puli // Set a timeout on the operation 742a5689a7SAppaRao Puli conn.expires_after(std::chrono::seconds(30)); 752a5689a7SAppaRao Puli conn.async_connect(endpoint, [self(shared_from_this())]( 762a5689a7SAppaRao Puli const boost::beast::error_code& ec, 772a5689a7SAppaRao Puli const boost::asio::ip::tcp::resolver:: 782a5689a7SAppaRao Puli results_type::endpoint_type& ep) { 792a5689a7SAppaRao Puli if (ec) 802a5689a7SAppaRao Puli { 812a5689a7SAppaRao Puli BMCWEB_LOG_ERROR << "Connect " << ep 822a5689a7SAppaRao Puli << " failed: " << ec.message(); 832a5689a7SAppaRao Puli self->state = ConnState::connectFailed; 842a5689a7SAppaRao Puli self->checkQueue(); 852a5689a7SAppaRao Puli return; 862a5689a7SAppaRao Puli } 872a5689a7SAppaRao Puli self->state = ConnState::connected; 882a5689a7SAppaRao Puli BMCWEB_LOG_DEBUG << "Connected to: " << ep; 892a5689a7SAppaRao Puli 902a5689a7SAppaRao Puli self->checkQueue(); 912a5689a7SAppaRao Puli }); 922a5689a7SAppaRao Puli } 932a5689a7SAppaRao Puli 942a5689a7SAppaRao Puli void sendMessage(const std::string& data) 952a5689a7SAppaRao Puli { 962a5689a7SAppaRao Puli if (state == ConnState::sendInProgress) 972a5689a7SAppaRao Puli { 982a5689a7SAppaRao Puli return; 992a5689a7SAppaRao Puli } 1002a5689a7SAppaRao Puli state = ConnState::sendInProgress; 1012a5689a7SAppaRao Puli 1022a5689a7SAppaRao Puli BMCWEB_LOG_DEBUG << __FUNCTION__ << "(): " << host << ":" << port; 1032a5689a7SAppaRao Puli 1042a5689a7SAppaRao Puli req.version(static_cast<int>(11)); // HTTP 1.1 1052a5689a7SAppaRao Puli req.target(uri); 1062a5689a7SAppaRao Puli req.method(boost::beast::http::verb::post); 1072a5689a7SAppaRao Puli 1082a5689a7SAppaRao Puli // Set headers 1092a5689a7SAppaRao Puli for (const auto& [key, value] : headers) 1102a5689a7SAppaRao Puli { 1112a5689a7SAppaRao Puli req.set(key, value); 1122a5689a7SAppaRao Puli } 1132a5689a7SAppaRao Puli req.set(boost::beast::http::field::host, host); 1142a5689a7SAppaRao Puli req.keep_alive(true); 1152a5689a7SAppaRao Puli 1162a5689a7SAppaRao Puli req.body() = data; 1172a5689a7SAppaRao Puli req.prepare_payload(); 118bd030d0aSAppaRao Puli 119bd030d0aSAppaRao Puli // Set a timeout on the operation 120bd030d0aSAppaRao Puli conn.expires_after(std::chrono::seconds(30)); 121bd030d0aSAppaRao Puli 122bd030d0aSAppaRao Puli // Send the HTTP request to the remote host 123bd030d0aSAppaRao Puli boost::beast::http::async_write( 124bd030d0aSAppaRao Puli conn, req, 1252a5689a7SAppaRao Puli [self(shared_from_this())](const boost::beast::error_code& ec, 126bd030d0aSAppaRao Puli const std::size_t& bytesTransferred) { 127bd030d0aSAppaRao Puli if (ec) 128bd030d0aSAppaRao Puli { 129bd030d0aSAppaRao Puli BMCWEB_LOG_ERROR << "sendMessage() failed: " 130bd030d0aSAppaRao Puli << ec.message(); 1312a5689a7SAppaRao Puli self->state = ConnState::sendFailed; 1322a5689a7SAppaRao Puli self->checkQueue(); 133bd030d0aSAppaRao Puli return; 134bd030d0aSAppaRao Puli } 135bd030d0aSAppaRao Puli BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: " 136bd030d0aSAppaRao Puli << bytesTransferred; 137bd030d0aSAppaRao Puli boost::ignore_unused(bytesTransferred); 138bd030d0aSAppaRao Puli 1392a5689a7SAppaRao Puli self->recvMessage(); 140bd030d0aSAppaRao Puli }); 141bd030d0aSAppaRao Puli } 142bd030d0aSAppaRao Puli 143bd030d0aSAppaRao Puli void recvMessage() 144bd030d0aSAppaRao Puli { 145bd030d0aSAppaRao Puli // Receive the HTTP response 146bd030d0aSAppaRao Puli boost::beast::http::async_read( 147bd030d0aSAppaRao Puli conn, buffer, res, 1482a5689a7SAppaRao Puli [self(shared_from_this())](const boost::beast::error_code& ec, 149bd030d0aSAppaRao Puli const std::size_t& bytesTransferred) { 150bd030d0aSAppaRao Puli if (ec) 151bd030d0aSAppaRao Puli { 152bd030d0aSAppaRao Puli BMCWEB_LOG_ERROR << "recvMessage() failed: " 153bd030d0aSAppaRao Puli << ec.message(); 1542a5689a7SAppaRao Puli self->state = ConnState::recvFailed; 1552a5689a7SAppaRao Puli self->checkQueue(); 156bd030d0aSAppaRao Puli return; 157bd030d0aSAppaRao Puli } 158bd030d0aSAppaRao Puli BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: " 159bd030d0aSAppaRao Puli << bytesTransferred; 160bd030d0aSAppaRao Puli boost::ignore_unused(bytesTransferred); 161bd030d0aSAppaRao Puli 162bd030d0aSAppaRao Puli // Discard received data. We are not interested. 1632a5689a7SAppaRao Puli BMCWEB_LOG_DEBUG << "recvMessage() data: " << self->res; 164bd030d0aSAppaRao Puli 1652a5689a7SAppaRao Puli // Send is successful, Lets remove data from queue 1662a5689a7SAppaRao Puli // check for next request data in queue. 1672a5689a7SAppaRao Puli self->requestDataQueue.pop(); 1682a5689a7SAppaRao Puli self->state = ConnState::idle; 1692a5689a7SAppaRao Puli self->checkQueue(); 170bd030d0aSAppaRao Puli }); 171bd030d0aSAppaRao Puli } 172bd030d0aSAppaRao Puli 173bd030d0aSAppaRao Puli void doClose() 174bd030d0aSAppaRao Puli { 175bd030d0aSAppaRao Puli boost::beast::error_code ec; 176bd030d0aSAppaRao Puli conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); 177bd030d0aSAppaRao Puli 178bd030d0aSAppaRao Puli state = ConnState::closed; 179bd030d0aSAppaRao Puli // not_connected happens sometimes so don't bother reporting it. 180bd030d0aSAppaRao Puli if (ec && ec != boost::beast::errc::not_connected) 181bd030d0aSAppaRao Puli { 182bd030d0aSAppaRao Puli BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message(); 183bd030d0aSAppaRao Puli return; 184bd030d0aSAppaRao Puli } 185bd030d0aSAppaRao Puli BMCWEB_LOG_DEBUG << "Connection closed gracefully"; 186bd030d0aSAppaRao Puli } 187bd030d0aSAppaRao Puli 1882a5689a7SAppaRao Puli void checkQueue(const bool newRecord = false) 189bd030d0aSAppaRao Puli { 1902a5689a7SAppaRao Puli if (requestDataQueue.empty()) 1912a5689a7SAppaRao Puli { 1922a5689a7SAppaRao Puli // TODO: Having issue in keeping connection alive. So lets close if 1932a5689a7SAppaRao Puli // nothing to be trasferred. 1942a5689a7SAppaRao Puli doClose(); 1952a5689a7SAppaRao Puli 1962a5689a7SAppaRao Puli BMCWEB_LOG_DEBUG << "requestDataQueue is empty\n"; 1972a5689a7SAppaRao Puli return; 1982a5689a7SAppaRao Puli } 1992a5689a7SAppaRao Puli 2002a5689a7SAppaRao Puli if (retryCount >= maxRetryAttempts) 2012a5689a7SAppaRao Puli { 2022a5689a7SAppaRao Puli BMCWEB_LOG_ERROR << "Maximum number of retries is reached."; 2032a5689a7SAppaRao Puli 2042a5689a7SAppaRao Puli // Clear queue. 2052a5689a7SAppaRao Puli while (!requestDataQueue.empty()) 2062a5689a7SAppaRao Puli { 2072a5689a7SAppaRao Puli requestDataQueue.pop(); 2082a5689a7SAppaRao Puli } 2092a5689a7SAppaRao Puli 2102a5689a7SAppaRao Puli // TODO: Take 'DeliveryRetryPolicy' action. 2112a5689a7SAppaRao Puli // For now, doing 'SuspendRetries' action. 2122a5689a7SAppaRao Puli state = ConnState::suspended; 2132a5689a7SAppaRao Puli return; 2142a5689a7SAppaRao Puli } 2152a5689a7SAppaRao Puli 2162a5689a7SAppaRao Puli if ((state == ConnState::connectFailed) || 2172a5689a7SAppaRao Puli (state == ConnState::sendFailed) || 2182a5689a7SAppaRao Puli (state == ConnState::recvFailed)) 2192a5689a7SAppaRao Puli { 2202a5689a7SAppaRao Puli if (newRecord) 2212a5689a7SAppaRao Puli { 2222a5689a7SAppaRao Puli // We are already running async wait and retry. 2232a5689a7SAppaRao Puli // Since record is added to queue, it gets the 2242a5689a7SAppaRao Puli // turn in FIFO. 2252a5689a7SAppaRao Puli return; 2262a5689a7SAppaRao Puli } 2272a5689a7SAppaRao Puli 2282a5689a7SAppaRao Puli retryCount++; 2292a5689a7SAppaRao Puli // TODO: Perform async wait for retryTimeoutInterval before proceed. 2302a5689a7SAppaRao Puli } 2312a5689a7SAppaRao Puli else 2322a5689a7SAppaRao Puli { 2332a5689a7SAppaRao Puli // reset retry count. 2342a5689a7SAppaRao Puli retryCount = 0; 2352a5689a7SAppaRao Puli } 2362a5689a7SAppaRao Puli 2372a5689a7SAppaRao Puli switch (state) 2382a5689a7SAppaRao Puli { 2392a5689a7SAppaRao Puli case ConnState::connectInProgress: 2402a5689a7SAppaRao Puli case ConnState::sendInProgress: 2412a5689a7SAppaRao Puli case ConnState::suspended: 2422a5689a7SAppaRao Puli // do nothing 2432a5689a7SAppaRao Puli break; 2442a5689a7SAppaRao Puli case ConnState::initialized: 2452a5689a7SAppaRao Puli case ConnState::closed: 2462a5689a7SAppaRao Puli case ConnState::connectFailed: 2472a5689a7SAppaRao Puli case ConnState::sendFailed: 248*92a74e56SAppaRao Puli case ConnState::recvFailed: 249*92a74e56SAppaRao Puli { 2502a5689a7SAppaRao Puli // After establishing the connection, checkQueue() will 2512a5689a7SAppaRao Puli // get called and it will attempt to send data. 2522a5689a7SAppaRao Puli doConnect(); 2532a5689a7SAppaRao Puli break; 2542a5689a7SAppaRao Puli } 2552a5689a7SAppaRao Puli case ConnState::connected: 256*92a74e56SAppaRao Puli case ConnState::idle: 257*92a74e56SAppaRao Puli { 2582a5689a7SAppaRao Puli std::string data = requestDataQueue.front(); 2592a5689a7SAppaRao Puli sendMessage(data); 2602a5689a7SAppaRao Puli break; 2612a5689a7SAppaRao Puli } 2622a5689a7SAppaRao Puli default: 2632a5689a7SAppaRao Puli break; 2642a5689a7SAppaRao Puli } 2652a5689a7SAppaRao Puli 2662a5689a7SAppaRao Puli return; 267bd030d0aSAppaRao Puli } 268bd030d0aSAppaRao Puli 269bd030d0aSAppaRao Puli public: 270bd030d0aSAppaRao Puli explicit HttpClient(boost::asio::io_context& ioc, const std::string& destIP, 2712a5689a7SAppaRao Puli const std::string& destPort, 2722a5689a7SAppaRao Puli const std::string& destUri) : 273bd030d0aSAppaRao Puli conn(ioc), 2742a5689a7SAppaRao Puli host(destIP), port(destPort), uri(destUri) 275bd030d0aSAppaRao Puli { 276bd030d0aSAppaRao Puli boost::asio::ip::tcp::resolver resolver(ioc); 277bd030d0aSAppaRao Puli endpoint = resolver.resolve(host, port); 2782a5689a7SAppaRao Puli state = ConnState::initialized; 2792a5689a7SAppaRao Puli retryCount = 0; 2802a5689a7SAppaRao Puli maxRetryAttempts = 5; 281bd030d0aSAppaRao Puli } 282bd030d0aSAppaRao Puli 2832a5689a7SAppaRao Puli void sendData(const std::string& data) 284bd030d0aSAppaRao Puli { 2852a5689a7SAppaRao Puli if (state == ConnState::suspended) 286bd030d0aSAppaRao Puli { 287bd030d0aSAppaRao Puli return; 288bd030d0aSAppaRao Puli } 289bd030d0aSAppaRao Puli 2902a5689a7SAppaRao Puli if (requestDataQueue.size() <= maxRequestQueueSize) 2912a5689a7SAppaRao Puli { 2922a5689a7SAppaRao Puli requestDataQueue.push(data); 2932a5689a7SAppaRao Puli checkQueue(true); 2942a5689a7SAppaRao Puli } 2952a5689a7SAppaRao Puli else 2962a5689a7SAppaRao Puli { 2972a5689a7SAppaRao Puli BMCWEB_LOG_ERROR << "Request queue is full. So ignoring data."; 2982a5689a7SAppaRao Puli } 2992a5689a7SAppaRao Puli 3002a5689a7SAppaRao Puli return; 301bd030d0aSAppaRao Puli } 302bd030d0aSAppaRao Puli 303bd030d0aSAppaRao Puli void setHeaders( 304bd030d0aSAppaRao Puli const std::vector<std::pair<std::string, std::string>>& httpHeaders) 305bd030d0aSAppaRao Puli { 306bd030d0aSAppaRao Puli headers = httpHeaders; 307bd030d0aSAppaRao Puli } 308bd030d0aSAppaRao Puli }; 309bd030d0aSAppaRao Puli 310bd030d0aSAppaRao Puli } // namespace crow 311