xref: /openbmc/bmcweb/http/http_client.hpp (revision caa3ce3c)
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>
211214b7e7SGunnar Mills 
22bd030d0aSAppaRao Puli #include <cstdlib>
23bd030d0aSAppaRao Puli #include <functional>
24bd030d0aSAppaRao Puli #include <iostream>
25bd030d0aSAppaRao Puli #include <memory>
262a5689a7SAppaRao Puli #include <queue>
27bd030d0aSAppaRao Puli #include <string>
28bd030d0aSAppaRao Puli 
29bd030d0aSAppaRao Puli namespace crow
30bd030d0aSAppaRao Puli {
31bd030d0aSAppaRao Puli 
322a5689a7SAppaRao Puli static constexpr uint8_t maxRequestQueueSize = 50;
332a5689a7SAppaRao Puli 
34bd030d0aSAppaRao Puli enum class ConnState
35bd030d0aSAppaRao Puli {
362a5689a7SAppaRao Puli     initialized,
372a5689a7SAppaRao Puli     connectInProgress,
382a5689a7SAppaRao Puli     connectFailed,
39bd030d0aSAppaRao Puli     connected,
402a5689a7SAppaRao Puli     sendInProgress,
412a5689a7SAppaRao Puli     sendFailed,
422a5689a7SAppaRao Puli     recvFailed,
432a5689a7SAppaRao Puli     idle,
442a5689a7SAppaRao Puli     suspended,
45fe44eb0bSAyushi Smriti     closed,
46fe44eb0bSAyushi Smriti     terminated
47bd030d0aSAppaRao Puli };
48bd030d0aSAppaRao Puli 
49bd030d0aSAppaRao Puli class HttpClient : public std::enable_shared_from_this<HttpClient>
50bd030d0aSAppaRao Puli {
51bd030d0aSAppaRao Puli   private:
52bd030d0aSAppaRao Puli     boost::beast::tcp_stream conn;
53fe44eb0bSAyushi Smriti     boost::asio::steady_timer timer;
54bd030d0aSAppaRao Puli     boost::beast::flat_buffer buffer;
55bd030d0aSAppaRao Puli     boost::beast::http::request<boost::beast::http::string_body> req;
56bd030d0aSAppaRao Puli     boost::beast::http::response<boost::beast::http::string_body> res;
57bd030d0aSAppaRao Puli     boost::asio::ip::tcp::resolver::results_type endpoint;
58bd030d0aSAppaRao Puli     std::vector<std::pair<std::string, std::string>> headers;
592a5689a7SAppaRao Puli     std::queue<std::string> requestDataQueue;
60bd030d0aSAppaRao Puli     ConnState state;
61fe44eb0bSAyushi Smriti     std::string subId;
62bd030d0aSAppaRao Puli     std::string host;
63bd030d0aSAppaRao Puli     std::string port;
642a5689a7SAppaRao Puli     std::string uri;
65fe44eb0bSAyushi Smriti     uint32_t retryCount;
66fe44eb0bSAyushi Smriti     uint32_t maxRetryAttempts;
67fe44eb0bSAyushi Smriti     uint32_t retryIntervalSecs;
68fe44eb0bSAyushi Smriti     std::string retryPolicyAction;
69fe44eb0bSAyushi Smriti     bool runningTimer;
70bd030d0aSAppaRao Puli 
712a5689a7SAppaRao Puli     void doConnect()
72bd030d0aSAppaRao Puli     {
732a5689a7SAppaRao Puli         if (state == ConnState::connectInProgress)
74bd030d0aSAppaRao Puli         {
75bd030d0aSAppaRao Puli             return;
76bd030d0aSAppaRao Puli         }
772a5689a7SAppaRao Puli         state = ConnState::connectInProgress;
782a5689a7SAppaRao Puli 
792a5689a7SAppaRao Puli         BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":" << port;
802a5689a7SAppaRao Puli         // Set a timeout on the operation
812a5689a7SAppaRao Puli         conn.expires_after(std::chrono::seconds(30));
822a5689a7SAppaRao Puli         conn.async_connect(endpoint, [self(shared_from_this())](
832a5689a7SAppaRao Puli                                          const boost::beast::error_code& ec,
842a5689a7SAppaRao Puli                                          const boost::asio::ip::tcp::resolver::
852a5689a7SAppaRao Puli                                              results_type::endpoint_type& ep) {
862a5689a7SAppaRao Puli             if (ec)
872a5689a7SAppaRao Puli             {
882a5689a7SAppaRao Puli                 BMCWEB_LOG_ERROR << "Connect " << ep
892a5689a7SAppaRao Puli                                  << " failed: " << ec.message();
902a5689a7SAppaRao Puli                 self->state = ConnState::connectFailed;
912a5689a7SAppaRao Puli                 self->checkQueue();
922a5689a7SAppaRao Puli                 return;
932a5689a7SAppaRao Puli             }
942a5689a7SAppaRao Puli             self->state = ConnState::connected;
952a5689a7SAppaRao Puli             BMCWEB_LOG_DEBUG << "Connected to: " << ep;
962a5689a7SAppaRao Puli 
972a5689a7SAppaRao Puli             self->checkQueue();
982a5689a7SAppaRao Puli         });
992a5689a7SAppaRao Puli     }
1002a5689a7SAppaRao Puli 
1012a5689a7SAppaRao Puli     void sendMessage(const std::string& data)
1022a5689a7SAppaRao Puli     {
1032a5689a7SAppaRao Puli         if (state == ConnState::sendInProgress)
1042a5689a7SAppaRao Puli         {
1052a5689a7SAppaRao Puli             return;
1062a5689a7SAppaRao Puli         }
1072a5689a7SAppaRao Puli         state = ConnState::sendInProgress;
1082a5689a7SAppaRao Puli 
1092a5689a7SAppaRao Puli         BMCWEB_LOG_DEBUG << __FUNCTION__ << "(): " << host << ":" << port;
1102a5689a7SAppaRao Puli 
1112a5689a7SAppaRao Puli         req.version(static_cast<int>(11)); // HTTP 1.1
1122a5689a7SAppaRao Puli         req.target(uri);
1132a5689a7SAppaRao Puli         req.method(boost::beast::http::verb::post);
1142a5689a7SAppaRao Puli 
1152a5689a7SAppaRao Puli         // Set headers
1162a5689a7SAppaRao Puli         for (const auto& [key, value] : headers)
1172a5689a7SAppaRao Puli         {
1182a5689a7SAppaRao Puli             req.set(key, value);
1192a5689a7SAppaRao Puli         }
1202a5689a7SAppaRao Puli         req.set(boost::beast::http::field::host, host);
1212a5689a7SAppaRao Puli         req.keep_alive(true);
1222a5689a7SAppaRao Puli 
1232a5689a7SAppaRao Puli         req.body() = data;
1242a5689a7SAppaRao Puli         req.prepare_payload();
125bd030d0aSAppaRao Puli 
126bd030d0aSAppaRao Puli         // Set a timeout on the operation
127bd030d0aSAppaRao Puli         conn.expires_after(std::chrono::seconds(30));
128bd030d0aSAppaRao Puli 
129bd030d0aSAppaRao Puli         // Send the HTTP request to the remote host
130bd030d0aSAppaRao Puli         boost::beast::http::async_write(
131bd030d0aSAppaRao Puli             conn, req,
1322a5689a7SAppaRao Puli             [self(shared_from_this())](const boost::beast::error_code& ec,
133bd030d0aSAppaRao Puli                                        const std::size_t& bytesTransferred) {
134bd030d0aSAppaRao Puli                 if (ec)
135bd030d0aSAppaRao Puli                 {
136bd030d0aSAppaRao Puli                     BMCWEB_LOG_ERROR << "sendMessage() failed: "
137bd030d0aSAppaRao Puli                                      << ec.message();
1382a5689a7SAppaRao Puli                     self->state = ConnState::sendFailed;
1392a5689a7SAppaRao Puli                     self->checkQueue();
140bd030d0aSAppaRao Puli                     return;
141bd030d0aSAppaRao Puli                 }
142bd030d0aSAppaRao Puli                 BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
143bd030d0aSAppaRao Puli                                  << bytesTransferred;
144bd030d0aSAppaRao Puli                 boost::ignore_unused(bytesTransferred);
145bd030d0aSAppaRao Puli 
1462a5689a7SAppaRao Puli                 self->recvMessage();
147bd030d0aSAppaRao Puli             });
148bd030d0aSAppaRao Puli     }
149bd030d0aSAppaRao Puli 
150bd030d0aSAppaRao Puli     void recvMessage()
151bd030d0aSAppaRao Puli     {
152bd030d0aSAppaRao Puli         // Receive the HTTP response
153bd030d0aSAppaRao Puli         boost::beast::http::async_read(
154bd030d0aSAppaRao Puli             conn, buffer, res,
1552a5689a7SAppaRao Puli             [self(shared_from_this())](const boost::beast::error_code& ec,
156bd030d0aSAppaRao Puli                                        const std::size_t& bytesTransferred) {
157bd030d0aSAppaRao Puli                 if (ec)
158bd030d0aSAppaRao Puli                 {
159bd030d0aSAppaRao Puli                     BMCWEB_LOG_ERROR << "recvMessage() failed: "
160bd030d0aSAppaRao Puli                                      << ec.message();
1612a5689a7SAppaRao Puli                     self->state = ConnState::recvFailed;
1622a5689a7SAppaRao Puli                     self->checkQueue();
163bd030d0aSAppaRao Puli                     return;
164bd030d0aSAppaRao Puli                 }
165bd030d0aSAppaRao Puli                 BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
166bd030d0aSAppaRao Puli                                  << bytesTransferred;
167bd030d0aSAppaRao Puli                 boost::ignore_unused(bytesTransferred);
168bd030d0aSAppaRao Puli 
169bd030d0aSAppaRao Puli                 // Discard received data. We are not interested.
1702a5689a7SAppaRao Puli                 BMCWEB_LOG_DEBUG << "recvMessage() data: " << self->res;
171bd030d0aSAppaRao Puli 
1722a5689a7SAppaRao Puli                 // Send is successful, Lets remove data from queue
1732a5689a7SAppaRao Puli                 // check for next request data in queue.
1742a5689a7SAppaRao Puli                 self->requestDataQueue.pop();
1752a5689a7SAppaRao Puli                 self->state = ConnState::idle;
1762a5689a7SAppaRao Puli                 self->checkQueue();
177bd030d0aSAppaRao Puli             });
178bd030d0aSAppaRao Puli     }
179bd030d0aSAppaRao Puli 
180bd030d0aSAppaRao Puli     void doClose()
181bd030d0aSAppaRao Puli     {
182bd030d0aSAppaRao Puli         boost::beast::error_code ec;
183bd030d0aSAppaRao Puli         conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
184bd030d0aSAppaRao Puli 
185bd030d0aSAppaRao Puli         state = ConnState::closed;
186bd030d0aSAppaRao Puli         // not_connected happens sometimes so don't bother reporting it.
187bd030d0aSAppaRao Puli         if (ec && ec != boost::beast::errc::not_connected)
188bd030d0aSAppaRao Puli         {
189bd030d0aSAppaRao Puli             BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message();
190bd030d0aSAppaRao Puli             return;
191bd030d0aSAppaRao Puli         }
192bd030d0aSAppaRao Puli         BMCWEB_LOG_DEBUG << "Connection closed gracefully";
193bd030d0aSAppaRao Puli     }
194bd030d0aSAppaRao Puli 
1952a5689a7SAppaRao Puli     void checkQueue(const bool newRecord = false)
196bd030d0aSAppaRao Puli     {
1972a5689a7SAppaRao Puli         if (requestDataQueue.empty())
1982a5689a7SAppaRao Puli         {
1992a5689a7SAppaRao Puli             // TODO: Having issue in keeping connection alive. So lets close if
200*caa3ce3cSGunnar Mills             // nothing to be transferred.
2012a5689a7SAppaRao Puli             doClose();
2022a5689a7SAppaRao Puli 
2032a5689a7SAppaRao Puli             BMCWEB_LOG_DEBUG << "requestDataQueue is empty\n";
2042a5689a7SAppaRao Puli             return;
2052a5689a7SAppaRao Puli         }
2062a5689a7SAppaRao Puli 
2072a5689a7SAppaRao Puli         if (retryCount >= maxRetryAttempts)
2082a5689a7SAppaRao Puli         {
2092a5689a7SAppaRao Puli             BMCWEB_LOG_ERROR << "Maximum number of retries is reached.";
2102a5689a7SAppaRao Puli 
2112a5689a7SAppaRao Puli             // Clear queue.
2122a5689a7SAppaRao Puli             while (!requestDataQueue.empty())
2132a5689a7SAppaRao Puli             {
2142a5689a7SAppaRao Puli                 requestDataQueue.pop();
2152a5689a7SAppaRao Puli             }
2162a5689a7SAppaRao Puli 
217fe44eb0bSAyushi Smriti             BMCWEB_LOG_DEBUG << "Retry policy is set to " << retryPolicyAction;
218fe44eb0bSAyushi Smriti             if (retryPolicyAction == "TerminateAfterRetries")
219fe44eb0bSAyushi Smriti             {
220fe44eb0bSAyushi Smriti                 // TODO: delete subscription
221fe44eb0bSAyushi Smriti                 state = ConnState::terminated;
222fe44eb0bSAyushi Smriti                 return;
223fe44eb0bSAyushi Smriti             }
224fe44eb0bSAyushi Smriti             else if (retryPolicyAction == "SuspendRetries")
225fe44eb0bSAyushi Smriti             {
2262a5689a7SAppaRao Puli                 state = ConnState::suspended;
2272a5689a7SAppaRao Puli                 return;
2282a5689a7SAppaRao Puli             }
229fe44eb0bSAyushi Smriti             else
230fe44eb0bSAyushi Smriti             {
231fe44eb0bSAyushi Smriti                 // keep retrying, reset count and continue.
232fe44eb0bSAyushi Smriti                 retryCount = 0;
233fe44eb0bSAyushi Smriti             }
234fe44eb0bSAyushi Smriti         }
2352a5689a7SAppaRao Puli 
2362a5689a7SAppaRao Puli         if ((state == ConnState::connectFailed) ||
2372a5689a7SAppaRao Puli             (state == ConnState::sendFailed) ||
2382a5689a7SAppaRao Puli             (state == ConnState::recvFailed))
2392a5689a7SAppaRao Puli         {
2402a5689a7SAppaRao Puli             if (newRecord)
2412a5689a7SAppaRao Puli             {
2422a5689a7SAppaRao Puli                 // We are already running async wait and retry.
2432a5689a7SAppaRao Puli                 // Since record is added to queue, it gets the
2442a5689a7SAppaRao Puli                 // turn in FIFO.
2452a5689a7SAppaRao Puli                 return;
2462a5689a7SAppaRao Puli             }
2472a5689a7SAppaRao Puli 
248fe44eb0bSAyushi Smriti             if (runningTimer)
249fe44eb0bSAyushi Smriti             {
250fe44eb0bSAyushi Smriti                 BMCWEB_LOG_DEBUG << "Retry timer is already running.";
251fe44eb0bSAyushi Smriti                 return;
252fe44eb0bSAyushi Smriti             }
253fe44eb0bSAyushi Smriti             runningTimer = true;
254fe44eb0bSAyushi Smriti 
2552a5689a7SAppaRao Puli             retryCount++;
256fe44eb0bSAyushi Smriti 
257fe44eb0bSAyushi Smriti             BMCWEB_LOG_DEBUG << "Attempt retry after " << retryIntervalSecs
258fe44eb0bSAyushi Smriti                              << " seconds. RetryCount = " << retryCount;
259fe44eb0bSAyushi Smriti             timer.expires_after(std::chrono::seconds(retryIntervalSecs));
260fe44eb0bSAyushi Smriti             timer.async_wait([self = shared_from_this()](
261fe44eb0bSAyushi Smriti                                  const boost::system::error_code& ec) {
262fe44eb0bSAyushi Smriti                 self->runningTimer = false;
263fe44eb0bSAyushi Smriti                 self->connStateCheck();
264fe44eb0bSAyushi Smriti             });
265fe44eb0bSAyushi Smriti             return;
2662a5689a7SAppaRao Puli         }
2672a5689a7SAppaRao Puli         else
2682a5689a7SAppaRao Puli         {
2692a5689a7SAppaRao Puli             // reset retry count.
2702a5689a7SAppaRao Puli             retryCount = 0;
2712a5689a7SAppaRao Puli         }
272fe44eb0bSAyushi Smriti         connStateCheck();
2732a5689a7SAppaRao Puli 
274fe44eb0bSAyushi Smriti         return;
275fe44eb0bSAyushi Smriti     }
276fe44eb0bSAyushi Smriti 
277fe44eb0bSAyushi Smriti     void connStateCheck()
278fe44eb0bSAyushi Smriti     {
2792a5689a7SAppaRao Puli         switch (state)
2802a5689a7SAppaRao Puli         {
2812a5689a7SAppaRao Puli             case ConnState::connectInProgress:
2822a5689a7SAppaRao Puli             case ConnState::sendInProgress:
2832a5689a7SAppaRao Puli             case ConnState::suspended:
284fe44eb0bSAyushi Smriti             case ConnState::terminated:
2852a5689a7SAppaRao Puli                 // do nothing
2862a5689a7SAppaRao Puli                 break;
2872a5689a7SAppaRao Puli             case ConnState::initialized:
2882a5689a7SAppaRao Puli             case ConnState::closed:
2892a5689a7SAppaRao Puli             case ConnState::connectFailed:
2902a5689a7SAppaRao Puli             case ConnState::sendFailed:
29192a74e56SAppaRao Puli             case ConnState::recvFailed:
29292a74e56SAppaRao Puli             {
2932a5689a7SAppaRao Puli                 // After establishing the connection, checkQueue() will
2942a5689a7SAppaRao Puli                 // get called and it will attempt to send data.
2952a5689a7SAppaRao Puli                 doConnect();
2962a5689a7SAppaRao Puli                 break;
2972a5689a7SAppaRao Puli             }
2982a5689a7SAppaRao Puli             case ConnState::connected:
29992a74e56SAppaRao Puli             case ConnState::idle:
30092a74e56SAppaRao Puli             {
3012a5689a7SAppaRao Puli                 std::string data = requestDataQueue.front();
3022a5689a7SAppaRao Puli                 sendMessage(data);
3032a5689a7SAppaRao Puli                 break;
3042a5689a7SAppaRao Puli             }
3052a5689a7SAppaRao Puli             default:
3062a5689a7SAppaRao Puli                 break;
3072a5689a7SAppaRao Puli         }
308bd030d0aSAppaRao Puli     }
309bd030d0aSAppaRao Puli 
310bd030d0aSAppaRao Puli   public:
311fe44eb0bSAyushi Smriti     explicit HttpClient(boost::asio::io_context& ioc, const std::string& id,
312fe44eb0bSAyushi Smriti                         const std::string& destIP, const std::string& destPort,
3132a5689a7SAppaRao Puli                         const std::string& destUri) :
314bd030d0aSAppaRao Puli         conn(ioc),
315fe44eb0bSAyushi Smriti         timer(ioc), subId(id), host(destIP), port(destPort), uri(destUri),
316fe44eb0bSAyushi Smriti         retryCount(0), maxRetryAttempts(5),
317fe44eb0bSAyushi Smriti         retryPolicyAction("TerminateAfterRetries"), runningTimer(false)
318bd030d0aSAppaRao Puli     {
319bd030d0aSAppaRao Puli         boost::asio::ip::tcp::resolver resolver(ioc);
320bd030d0aSAppaRao Puli         endpoint = resolver.resolve(host, port);
3212a5689a7SAppaRao Puli         state = ConnState::initialized;
322bd030d0aSAppaRao Puli     }
323bd030d0aSAppaRao Puli 
3242a5689a7SAppaRao Puli     void sendData(const std::string& data)
325bd030d0aSAppaRao Puli     {
3262a5689a7SAppaRao Puli         if (state == ConnState::suspended)
327bd030d0aSAppaRao Puli         {
328bd030d0aSAppaRao Puli             return;
329bd030d0aSAppaRao Puli         }
330bd030d0aSAppaRao Puli 
3312a5689a7SAppaRao Puli         if (requestDataQueue.size() <= maxRequestQueueSize)
3322a5689a7SAppaRao Puli         {
3332a5689a7SAppaRao Puli             requestDataQueue.push(data);
3342a5689a7SAppaRao Puli             checkQueue(true);
3352a5689a7SAppaRao Puli         }
3362a5689a7SAppaRao Puli         else
3372a5689a7SAppaRao Puli         {
3382a5689a7SAppaRao Puli             BMCWEB_LOG_ERROR << "Request queue is full. So ignoring data.";
3392a5689a7SAppaRao Puli         }
3402a5689a7SAppaRao Puli 
3412a5689a7SAppaRao Puli         return;
342bd030d0aSAppaRao Puli     }
343bd030d0aSAppaRao Puli 
344bd030d0aSAppaRao Puli     void setHeaders(
345bd030d0aSAppaRao Puli         const std::vector<std::pair<std::string, std::string>>& httpHeaders)
346bd030d0aSAppaRao Puli     {
347bd030d0aSAppaRao Puli         headers = httpHeaders;
348bd030d0aSAppaRao Puli     }
349fe44eb0bSAyushi Smriti 
350fe44eb0bSAyushi Smriti     void setRetryConfig(const uint32_t retryAttempts,
351fe44eb0bSAyushi Smriti                         const uint32_t retryTimeoutInterval)
352fe44eb0bSAyushi Smriti     {
353fe44eb0bSAyushi Smriti         maxRetryAttempts = retryAttempts;
354fe44eb0bSAyushi Smriti         retryIntervalSecs = retryTimeoutInterval;
355fe44eb0bSAyushi Smriti     }
356fe44eb0bSAyushi Smriti 
357fe44eb0bSAyushi Smriti     void setRetryPolicy(const std::string& retryPolicy)
358fe44eb0bSAyushi Smriti     {
359fe44eb0bSAyushi Smriti         retryPolicyAction = retryPolicy;
360fe44eb0bSAyushi Smriti     }
361bd030d0aSAppaRao Puli };
362bd030d0aSAppaRao Puli 
363bd030d0aSAppaRao Puli } // namespace crow
364