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