blob: 8b1fb5c12cca0593e933b70fa773c8a86c54da43 [file] [log] [blame]
AppaRao Pulibd030d02020-03-20 03:34:29 +05301/*
2// Copyright (c) 2020 Intel Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
16#pragma once
17#include <boost/asio/strand.hpp>
18#include <boost/beast/core.hpp>
19#include <boost/beast/http.hpp>
20#include <boost/beast/version.hpp>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050021
AppaRao Pulibd030d02020-03-20 03:34:29 +053022#include <cstdlib>
23#include <functional>
24#include <iostream>
25#include <memory>
AppaRao Puli2a5689a2020-04-29 15:24:31 +053026#include <queue>
AppaRao Pulibd030d02020-03-20 03:34:29 +053027#include <string>
28
29namespace crow
30{
31
AppaRao Puli2a5689a2020-04-29 15:24:31 +053032static constexpr uint8_t maxRequestQueueSize = 50;
33
AppaRao Pulibd030d02020-03-20 03:34:29 +053034enum class ConnState
35{
AppaRao Puli2a5689a2020-04-29 15:24:31 +053036 initialized,
37 connectInProgress,
38 connectFailed,
AppaRao Pulibd030d02020-03-20 03:34:29 +053039 connected,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053040 sendInProgress,
41 sendFailed,
42 recvFailed,
43 idle,
44 suspended,
AppaRao Pulibd030d02020-03-20 03:34:29 +053045 closed
46};
47
48class HttpClient : public std::enable_shared_from_this<HttpClient>
49{
50 private:
51 boost::beast::tcp_stream conn;
52 boost::beast::flat_buffer buffer;
53 boost::beast::http::request<boost::beast::http::string_body> req;
54 boost::beast::http::response<boost::beast::http::string_body> res;
55 boost::asio::ip::tcp::resolver::results_type endpoint;
56 std::vector<std::pair<std::string, std::string>> headers;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053057 std::queue<std::string> requestDataQueue;
AppaRao Pulibd030d02020-03-20 03:34:29 +053058 ConnState state;
59 std::string host;
60 std::string port;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053061 std::string uri;
62 int retryCount;
63 int maxRetryAttempts;
AppaRao Pulibd030d02020-03-20 03:34:29 +053064
AppaRao Puli2a5689a2020-04-29 15:24:31 +053065 void doConnect()
AppaRao Pulibd030d02020-03-20 03:34:29 +053066 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +053067 if (state == ConnState::connectInProgress)
AppaRao Pulibd030d02020-03-20 03:34:29 +053068 {
AppaRao Pulibd030d02020-03-20 03:34:29 +053069 return;
70 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +053071 state = ConnState::connectInProgress;
72
73 BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":" << port;
74 // Set a timeout on the operation
75 conn.expires_after(std::chrono::seconds(30));
76 conn.async_connect(endpoint, [self(shared_from_this())](
77 const boost::beast::error_code& ec,
78 const boost::asio::ip::tcp::resolver::
79 results_type::endpoint_type& ep) {
80 if (ec)
81 {
82 BMCWEB_LOG_ERROR << "Connect " << ep
83 << " failed: " << ec.message();
84 self->state = ConnState::connectFailed;
85 self->checkQueue();
86 return;
87 }
88 self->state = ConnState::connected;
89 BMCWEB_LOG_DEBUG << "Connected to: " << ep;
90
91 self->checkQueue();
92 });
93 }
94
95 void sendMessage(const std::string& data)
96 {
97 if (state == ConnState::sendInProgress)
98 {
99 return;
100 }
101 state = ConnState::sendInProgress;
102
103 BMCWEB_LOG_DEBUG << __FUNCTION__ << "(): " << host << ":" << port;
104
105 req.version(static_cast<int>(11)); // HTTP 1.1
106 req.target(uri);
107 req.method(boost::beast::http::verb::post);
108
109 // Set headers
110 for (const auto& [key, value] : headers)
111 {
112 req.set(key, value);
113 }
114 req.set(boost::beast::http::field::host, host);
115 req.keep_alive(true);
116
117 req.body() = data;
118 req.prepare_payload();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530119
120 // Set a timeout on the operation
121 conn.expires_after(std::chrono::seconds(30));
122
123 // Send the HTTP request to the remote host
124 boost::beast::http::async_write(
125 conn, req,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530126 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530127 const std::size_t& bytesTransferred) {
128 if (ec)
129 {
130 BMCWEB_LOG_ERROR << "sendMessage() failed: "
131 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530132 self->state = ConnState::sendFailed;
133 self->checkQueue();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530134 return;
135 }
136 BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
137 << bytesTransferred;
138 boost::ignore_unused(bytesTransferred);
139
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530140 self->recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530141 });
142 }
143
144 void recvMessage()
145 {
AppaRao Pulibd030d02020-03-20 03:34:29 +0530146 // Receive the HTTP response
147 boost::beast::http::async_read(
148 conn, buffer, res,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530149 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530150 const std::size_t& bytesTransferred) {
151 if (ec)
152 {
153 BMCWEB_LOG_ERROR << "recvMessage() failed: "
154 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530155 self->state = ConnState::recvFailed;
156 self->checkQueue();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530157 return;
158 }
159 BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
160 << bytesTransferred;
161 boost::ignore_unused(bytesTransferred);
162
163 // Discard received data. We are not interested.
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530164 BMCWEB_LOG_DEBUG << "recvMessage() data: " << self->res;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530165
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530166 // Send is successful, Lets remove data from queue
167 // check for next request data in queue.
168 self->requestDataQueue.pop();
169 self->state = ConnState::idle;
170 self->checkQueue();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530171 });
172 }
173
174 void doClose()
175 {
176 boost::beast::error_code ec;
177 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
178
179 state = ConnState::closed;
180 // not_connected happens sometimes so don't bother reporting it.
181 if (ec && ec != boost::beast::errc::not_connected)
182 {
183 BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message();
184 return;
185 }
186 BMCWEB_LOG_DEBUG << "Connection closed gracefully";
187 }
188
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530189 void checkQueue(const bool newRecord = false)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530190 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530191 if (requestDataQueue.empty())
192 {
193 // TODO: Having issue in keeping connection alive. So lets close if
194 // nothing to be trasferred.
195 doClose();
196
197 BMCWEB_LOG_DEBUG << "requestDataQueue is empty\n";
198 return;
199 }
200
201 if (retryCount >= maxRetryAttempts)
202 {
203 BMCWEB_LOG_ERROR << "Maximum number of retries is reached.";
204
205 // Clear queue.
206 while (!requestDataQueue.empty())
207 {
208 requestDataQueue.pop();
209 }
210
211 // TODO: Take 'DeliveryRetryPolicy' action.
212 // For now, doing 'SuspendRetries' action.
213 state = ConnState::suspended;
214 return;
215 }
216
217 if ((state == ConnState::connectFailed) ||
218 (state == ConnState::sendFailed) ||
219 (state == ConnState::recvFailed))
220 {
221 if (newRecord)
222 {
223 // We are already running async wait and retry.
224 // Since record is added to queue, it gets the
225 // turn in FIFO.
226 return;
227 }
228
229 retryCount++;
230 // TODO: Perform async wait for retryTimeoutInterval before proceed.
231 }
232 else
233 {
234 // reset retry count.
235 retryCount = 0;
236 }
237
238 switch (state)
239 {
240 case ConnState::connectInProgress:
241 case ConnState::sendInProgress:
242 case ConnState::suspended:
243 // do nothing
244 break;
245 case ConnState::initialized:
246 case ConnState::closed:
247 case ConnState::connectFailed:
248 case ConnState::sendFailed:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530249 case ConnState::recvFailed:
250 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530251 // After establishing the connection, checkQueue() will
252 // get called and it will attempt to send data.
253 doConnect();
254 break;
255 }
256 case ConnState::connected:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530257 case ConnState::idle:
258 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530259 std::string data = requestDataQueue.front();
260 sendMessage(data);
261 break;
262 }
263 default:
264 break;
265 }
266
267 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530268 }
269
270 public:
271 explicit HttpClient(boost::asio::io_context& ioc, const std::string& destIP,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530272 const std::string& destPort,
273 const std::string& destUri) :
AppaRao Pulibd030d02020-03-20 03:34:29 +0530274 conn(ioc),
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530275 host(destIP), port(destPort), uri(destUri)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530276 {
277 boost::asio::ip::tcp::resolver resolver(ioc);
278 endpoint = resolver.resolve(host, port);
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530279 state = ConnState::initialized;
280 retryCount = 0;
281 maxRetryAttempts = 5;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530282 }
283
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530284 void sendData(const std::string& data)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530285 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530286 if (state == ConnState::suspended)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530287 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530288 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530289 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530290
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530291 if (requestDataQueue.size() <= maxRequestQueueSize)
292 {
293 requestDataQueue.push(data);
294 checkQueue(true);
295 }
296 else
297 {
298 BMCWEB_LOG_ERROR << "Request queue is full. So ignoring data.";
299 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530300
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530301 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530302 }
303
304 void setHeaders(
305 const std::vector<std::pair<std::string, std::string>>& httpHeaders)
306 {
307 headers = httpHeaders;
308 }
309};
310
311} // namespace crow