xref: /openbmc/bmcweb/http/http_client.hpp (revision bd030d0a)
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