1*bd030d0aSAppaRao Puli /* 2*bd030d0aSAppaRao Puli // Copyright (c) 2020 Intel Corporation 3*bd030d0aSAppaRao Puli // 4*bd030d0aSAppaRao Puli // Licensed under the Apache License, Version 2.0 (the "License"); 5*bd030d0aSAppaRao Puli // you may not use this file except in compliance with the License. 6*bd030d0aSAppaRao Puli // You may obtain a copy of the License at 7*bd030d0aSAppaRao Puli // 8*bd030d0aSAppaRao Puli // http://www.apache.org/licenses/LICENSE-2.0 9*bd030d0aSAppaRao Puli // 10*bd030d0aSAppaRao Puli // Unless required by applicable law or agreed to in writing, software 11*bd030d0aSAppaRao Puli // distributed under the License is distributed on an "AS IS" BASIS, 12*bd030d0aSAppaRao Puli // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*bd030d0aSAppaRao Puli // See the License for the specific language governing permissions and 14*bd030d0aSAppaRao Puli // limitations under the License. 15*bd030d0aSAppaRao Puli */ 16*bd030d0aSAppaRao Puli #pragma once 17*bd030d0aSAppaRao Puli #include <boost/asio/strand.hpp> 18*bd030d0aSAppaRao Puli #include <boost/beast/core.hpp> 19*bd030d0aSAppaRao Puli #include <boost/beast/http.hpp> 20*bd030d0aSAppaRao Puli #include <boost/beast/version.hpp> 21*bd030d0aSAppaRao Puli #include <cstdlib> 22*bd030d0aSAppaRao Puli #include <functional> 23*bd030d0aSAppaRao Puli #include <iostream> 24*bd030d0aSAppaRao Puli #include <memory> 25*bd030d0aSAppaRao Puli #include <string> 26*bd030d0aSAppaRao Puli 27*bd030d0aSAppaRao Puli namespace crow 28*bd030d0aSAppaRao Puli { 29*bd030d0aSAppaRao Puli 30*bd030d0aSAppaRao Puli enum class ConnState 31*bd030d0aSAppaRao Puli { 32*bd030d0aSAppaRao Puli initializing, 33*bd030d0aSAppaRao Puli connected, 34*bd030d0aSAppaRao Puli closed 35*bd030d0aSAppaRao Puli }; 36*bd030d0aSAppaRao Puli 37*bd030d0aSAppaRao Puli class HttpClient : public std::enable_shared_from_this<HttpClient> 38*bd030d0aSAppaRao Puli { 39*bd030d0aSAppaRao Puli private: 40*bd030d0aSAppaRao Puli boost::beast::tcp_stream conn; 41*bd030d0aSAppaRao Puli boost::beast::flat_buffer buffer; 42*bd030d0aSAppaRao Puli boost::beast::http::request<boost::beast::http::string_body> req; 43*bd030d0aSAppaRao Puli boost::beast::http::response<boost::beast::http::string_body> res; 44*bd030d0aSAppaRao Puli boost::asio::ip::tcp::resolver::results_type endpoint; 45*bd030d0aSAppaRao Puli std::vector<std::pair<std::string, std::string>> headers; 46*bd030d0aSAppaRao Puli ConnState state; 47*bd030d0aSAppaRao Puli std::string host; 48*bd030d0aSAppaRao Puli std::string port; 49*bd030d0aSAppaRao Puli 50*bd030d0aSAppaRao Puli void sendMessage() 51*bd030d0aSAppaRao Puli { 52*bd030d0aSAppaRao Puli if (state != ConnState::connected) 53*bd030d0aSAppaRao Puli { 54*bd030d0aSAppaRao Puli BMCWEB_LOG_DEBUG << "Not connected to: " << host; 55*bd030d0aSAppaRao Puli return; 56*bd030d0aSAppaRao Puli } 57*bd030d0aSAppaRao Puli 58*bd030d0aSAppaRao Puli // Set a timeout on the operation 59*bd030d0aSAppaRao Puli conn.expires_after(std::chrono::seconds(30)); 60*bd030d0aSAppaRao Puli 61*bd030d0aSAppaRao Puli // Send the HTTP request to the remote host 62*bd030d0aSAppaRao Puli boost::beast::http::async_write( 63*bd030d0aSAppaRao Puli conn, req, 64*bd030d0aSAppaRao Puli [this, 65*bd030d0aSAppaRao Puli self(shared_from_this())](const boost::beast::error_code& ec, 66*bd030d0aSAppaRao Puli const std::size_t& bytesTransferred) { 67*bd030d0aSAppaRao Puli if (ec) 68*bd030d0aSAppaRao Puli { 69*bd030d0aSAppaRao Puli BMCWEB_LOG_ERROR << "sendMessage() failed: " 70*bd030d0aSAppaRao Puli << ec.message(); 71*bd030d0aSAppaRao Puli this->doClose(); 72*bd030d0aSAppaRao Puli return; 73*bd030d0aSAppaRao Puli } 74*bd030d0aSAppaRao Puli BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: " 75*bd030d0aSAppaRao Puli << bytesTransferred; 76*bd030d0aSAppaRao Puli boost::ignore_unused(bytesTransferred); 77*bd030d0aSAppaRao Puli 78*bd030d0aSAppaRao Puli this->recvMessage(); 79*bd030d0aSAppaRao Puli }); 80*bd030d0aSAppaRao Puli } 81*bd030d0aSAppaRao Puli 82*bd030d0aSAppaRao Puli void recvMessage() 83*bd030d0aSAppaRao Puli { 84*bd030d0aSAppaRao Puli if (state != ConnState::connected) 85*bd030d0aSAppaRao Puli { 86*bd030d0aSAppaRao Puli BMCWEB_LOG_DEBUG << "Not connected to: " << host; 87*bd030d0aSAppaRao Puli return; 88*bd030d0aSAppaRao Puli } 89*bd030d0aSAppaRao Puli 90*bd030d0aSAppaRao Puli // Receive the HTTP response 91*bd030d0aSAppaRao Puli boost::beast::http::async_read( 92*bd030d0aSAppaRao Puli conn, buffer, res, 93*bd030d0aSAppaRao Puli [this, 94*bd030d0aSAppaRao Puli self(shared_from_this())](const boost::beast::error_code& ec, 95*bd030d0aSAppaRao Puli const std::size_t& bytesTransferred) { 96*bd030d0aSAppaRao Puli if (ec) 97*bd030d0aSAppaRao Puli { 98*bd030d0aSAppaRao Puli BMCWEB_LOG_ERROR << "recvMessage() failed: " 99*bd030d0aSAppaRao Puli << ec.message(); 100*bd030d0aSAppaRao Puli this->doClose(); 101*bd030d0aSAppaRao Puli return; 102*bd030d0aSAppaRao Puli } 103*bd030d0aSAppaRao Puli BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: " 104*bd030d0aSAppaRao Puli << bytesTransferred; 105*bd030d0aSAppaRao Puli boost::ignore_unused(bytesTransferred); 106*bd030d0aSAppaRao Puli 107*bd030d0aSAppaRao Puli // Discard received data. We are not interested. 108*bd030d0aSAppaRao Puli BMCWEB_LOG_DEBUG << "recvMessage() data: " << res; 109*bd030d0aSAppaRao Puli 110*bd030d0aSAppaRao Puli this->doClose(); 111*bd030d0aSAppaRao Puli }); 112*bd030d0aSAppaRao Puli } 113*bd030d0aSAppaRao Puli 114*bd030d0aSAppaRao Puli void doClose() 115*bd030d0aSAppaRao Puli { 116*bd030d0aSAppaRao Puli boost::beast::error_code ec; 117*bd030d0aSAppaRao Puli conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); 118*bd030d0aSAppaRao Puli 119*bd030d0aSAppaRao Puli state = ConnState::closed; 120*bd030d0aSAppaRao Puli // not_connected happens sometimes so don't bother reporting it. 121*bd030d0aSAppaRao Puli if (ec && ec != boost::beast::errc::not_connected) 122*bd030d0aSAppaRao Puli { 123*bd030d0aSAppaRao Puli BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message(); 124*bd030d0aSAppaRao Puli return; 125*bd030d0aSAppaRao Puli } 126*bd030d0aSAppaRao Puli BMCWEB_LOG_DEBUG << "Connection closed gracefully"; 127*bd030d0aSAppaRao Puli } 128*bd030d0aSAppaRao Puli 129*bd030d0aSAppaRao Puli ConnState getState() 130*bd030d0aSAppaRao Puli { 131*bd030d0aSAppaRao Puli return state; 132*bd030d0aSAppaRao Puli } 133*bd030d0aSAppaRao Puli 134*bd030d0aSAppaRao Puli public: 135*bd030d0aSAppaRao Puli explicit HttpClient(boost::asio::io_context& ioc, const std::string& destIP, 136*bd030d0aSAppaRao Puli const std::string& destPort) : 137*bd030d0aSAppaRao Puli conn(ioc), 138*bd030d0aSAppaRao Puli host(destIP), port(destPort) 139*bd030d0aSAppaRao Puli { 140*bd030d0aSAppaRao Puli boost::asio::ip::tcp::resolver resolver(ioc); 141*bd030d0aSAppaRao Puli endpoint = resolver.resolve(host, port); 142*bd030d0aSAppaRao Puli state = ConnState::initializing; 143*bd030d0aSAppaRao Puli } 144*bd030d0aSAppaRao Puli 145*bd030d0aSAppaRao Puli void doConnectAndSend(const std::string& path, const std::string& data) 146*bd030d0aSAppaRao Puli { 147*bd030d0aSAppaRao Puli BMCWEB_LOG_DEBUG << "doConnectAndSend " << host << ":" << port; 148*bd030d0aSAppaRao Puli 149*bd030d0aSAppaRao Puli req.version(static_cast<int>(11)); // HTTP 1.1 150*bd030d0aSAppaRao Puli req.target(path); 151*bd030d0aSAppaRao Puli req.method(boost::beast::http::verb::post); 152*bd030d0aSAppaRao Puli 153*bd030d0aSAppaRao Puli // Set headers 154*bd030d0aSAppaRao Puli for (const auto& [key, value] : headers) 155*bd030d0aSAppaRao Puli { 156*bd030d0aSAppaRao Puli req.set(key, value); 157*bd030d0aSAppaRao Puli } 158*bd030d0aSAppaRao Puli req.set(boost::beast::http::field::host, host); 159*bd030d0aSAppaRao Puli req.keep_alive(true); 160*bd030d0aSAppaRao Puli 161*bd030d0aSAppaRao Puli req.body() = data; 162*bd030d0aSAppaRao Puli req.prepare_payload(); 163*bd030d0aSAppaRao Puli 164*bd030d0aSAppaRao Puli // Set a timeout on the operation 165*bd030d0aSAppaRao Puli conn.expires_after(std::chrono::seconds(30)); 166*bd030d0aSAppaRao Puli conn.async_connect(endpoint, [this, self(shared_from_this())]( 167*bd030d0aSAppaRao Puli const boost::beast::error_code& ec, 168*bd030d0aSAppaRao Puli const boost::asio::ip::tcp::resolver:: 169*bd030d0aSAppaRao Puli results_type::endpoint_type& ep) { 170*bd030d0aSAppaRao Puli if (ec) 171*bd030d0aSAppaRao Puli { 172*bd030d0aSAppaRao Puli BMCWEB_LOG_ERROR << "Connect " << ep 173*bd030d0aSAppaRao Puli << " failed: " << ec.message(); 174*bd030d0aSAppaRao Puli return; 175*bd030d0aSAppaRao Puli } 176*bd030d0aSAppaRao Puli state = ConnState::connected; 177*bd030d0aSAppaRao Puli BMCWEB_LOG_DEBUG << "Connected to: " << ep; 178*bd030d0aSAppaRao Puli 179*bd030d0aSAppaRao Puli sendMessage(); 180*bd030d0aSAppaRao Puli }); 181*bd030d0aSAppaRao Puli } 182*bd030d0aSAppaRao Puli 183*bd030d0aSAppaRao Puli void setHeaders( 184*bd030d0aSAppaRao Puli const std::vector<std::pair<std::string, std::string>>& httpHeaders) 185*bd030d0aSAppaRao Puli { 186*bd030d0aSAppaRao Puli headers = httpHeaders; 187*bd030d0aSAppaRao Puli } 188*bd030d0aSAppaRao Puli }; 189*bd030d0aSAppaRao Puli 190*bd030d0aSAppaRao Puli } // namespace crow 191