blob: caaaccbc6500adaf78088137857fa2fa60b6cd88 [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>
21#include <cstdlib>
22#include <functional>
23#include <iostream>
24#include <memory>
AppaRao Puli2a5689a2020-04-29 15:24:31 +053025#include <queue>
AppaRao Pulibd030d02020-03-20 03:34:29 +053026#include <string>
27
28namespace crow
29{
30
AppaRao Puli2a5689a2020-04-29 15:24:31 +053031static constexpr uint8_t maxRequestQueueSize = 50;
32
AppaRao Pulibd030d02020-03-20 03:34:29 +053033enum class ConnState
34{
AppaRao Puli2a5689a2020-04-29 15:24:31 +053035 initialized,
36 connectInProgress,
37 connectFailed,
AppaRao Pulibd030d02020-03-20 03:34:29 +053038 connected,
AppaRao Puli2a5689a2020-04-29 15:24:31 +053039 sendInProgress,
40 sendFailed,
41 recvFailed,
42 idle,
43 suspended,
AppaRao Pulibd030d02020-03-20 03:34:29 +053044 closed
45};
46
47class HttpClient : public std::enable_shared_from_this<HttpClient>
48{
49 private:
50 boost::beast::tcp_stream conn;
51 boost::beast::flat_buffer buffer;
52 boost::beast::http::request<boost::beast::http::string_body> req;
53 boost::beast::http::response<boost::beast::http::string_body> res;
54 boost::asio::ip::tcp::resolver::results_type endpoint;
55 std::vector<std::pair<std::string, std::string>> headers;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053056 std::queue<std::string> requestDataQueue;
AppaRao Pulibd030d02020-03-20 03:34:29 +053057 ConnState state;
58 std::string host;
59 std::string port;
AppaRao Puli2a5689a2020-04-29 15:24:31 +053060 std::string uri;
61 int retryCount;
62 int maxRetryAttempts;
AppaRao Pulibd030d02020-03-20 03:34:29 +053063
AppaRao Puli2a5689a2020-04-29 15:24:31 +053064 void doConnect()
AppaRao Pulibd030d02020-03-20 03:34:29 +053065 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +053066 if (state == ConnState::connectInProgress)
AppaRao Pulibd030d02020-03-20 03:34:29 +053067 {
AppaRao Pulibd030d02020-03-20 03:34:29 +053068 return;
69 }
AppaRao Puli2a5689a2020-04-29 15:24:31 +053070 state = ConnState::connectInProgress;
71
72 BMCWEB_LOG_DEBUG << "Trying to connect to: " << host << ":" << port;
73 // Set a timeout on the operation
74 conn.expires_after(std::chrono::seconds(30));
75 conn.async_connect(endpoint, [self(shared_from_this())](
76 const boost::beast::error_code& ec,
77 const boost::asio::ip::tcp::resolver::
78 results_type::endpoint_type& ep) {
79 if (ec)
80 {
81 BMCWEB_LOG_ERROR << "Connect " << ep
82 << " failed: " << ec.message();
83 self->state = ConnState::connectFailed;
84 self->checkQueue();
85 return;
86 }
87 self->state = ConnState::connected;
88 BMCWEB_LOG_DEBUG << "Connected to: " << ep;
89
90 self->checkQueue();
91 });
92 }
93
94 void sendMessage(const std::string& data)
95 {
96 if (state == ConnState::sendInProgress)
97 {
98 return;
99 }
100 state = ConnState::sendInProgress;
101
102 BMCWEB_LOG_DEBUG << __FUNCTION__ << "(): " << host << ":" << port;
103
104 req.version(static_cast<int>(11)); // HTTP 1.1
105 req.target(uri);
106 req.method(boost::beast::http::verb::post);
107
108 // Set headers
109 for (const auto& [key, value] : headers)
110 {
111 req.set(key, value);
112 }
113 req.set(boost::beast::http::field::host, host);
114 req.keep_alive(true);
115
116 req.body() = data;
117 req.prepare_payload();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530118
119 // Set a timeout on the operation
120 conn.expires_after(std::chrono::seconds(30));
121
122 // Send the HTTP request to the remote host
123 boost::beast::http::async_write(
124 conn, req,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530125 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530126 const std::size_t& bytesTransferred) {
127 if (ec)
128 {
129 BMCWEB_LOG_ERROR << "sendMessage() failed: "
130 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530131 self->state = ConnState::sendFailed;
132 self->checkQueue();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530133 return;
134 }
135 BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
136 << bytesTransferred;
137 boost::ignore_unused(bytesTransferred);
138
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530139 self->recvMessage();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530140 });
141 }
142
143 void recvMessage()
144 {
AppaRao Pulibd030d02020-03-20 03:34:29 +0530145 // Receive the HTTP response
146 boost::beast::http::async_read(
147 conn, buffer, res,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530148 [self(shared_from_this())](const boost::beast::error_code& ec,
AppaRao Pulibd030d02020-03-20 03:34:29 +0530149 const std::size_t& bytesTransferred) {
150 if (ec)
151 {
152 BMCWEB_LOG_ERROR << "recvMessage() failed: "
153 << ec.message();
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530154 self->state = ConnState::recvFailed;
155 self->checkQueue();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530156 return;
157 }
158 BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
159 << bytesTransferred;
160 boost::ignore_unused(bytesTransferred);
161
162 // Discard received data. We are not interested.
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530163 BMCWEB_LOG_DEBUG << "recvMessage() data: " << self->res;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530164
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530165 // Send is successful, Lets remove data from queue
166 // check for next request data in queue.
167 self->requestDataQueue.pop();
168 self->state = ConnState::idle;
169 self->checkQueue();
AppaRao Pulibd030d02020-03-20 03:34:29 +0530170 });
171 }
172
173 void doClose()
174 {
175 boost::beast::error_code ec;
176 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
177
178 state = ConnState::closed;
179 // not_connected happens sometimes so don't bother reporting it.
180 if (ec && ec != boost::beast::errc::not_connected)
181 {
182 BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message();
183 return;
184 }
185 BMCWEB_LOG_DEBUG << "Connection closed gracefully";
186 }
187
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530188 void checkQueue(const bool newRecord = false)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530189 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530190 if (requestDataQueue.empty())
191 {
192 // TODO: Having issue in keeping connection alive. So lets close if
193 // nothing to be trasferred.
194 doClose();
195
196 BMCWEB_LOG_DEBUG << "requestDataQueue is empty\n";
197 return;
198 }
199
200 if (retryCount >= maxRetryAttempts)
201 {
202 BMCWEB_LOG_ERROR << "Maximum number of retries is reached.";
203
204 // Clear queue.
205 while (!requestDataQueue.empty())
206 {
207 requestDataQueue.pop();
208 }
209
210 // TODO: Take 'DeliveryRetryPolicy' action.
211 // For now, doing 'SuspendRetries' action.
212 state = ConnState::suspended;
213 return;
214 }
215
216 if ((state == ConnState::connectFailed) ||
217 (state == ConnState::sendFailed) ||
218 (state == ConnState::recvFailed))
219 {
220 if (newRecord)
221 {
222 // We are already running async wait and retry.
223 // Since record is added to queue, it gets the
224 // turn in FIFO.
225 return;
226 }
227
228 retryCount++;
229 // TODO: Perform async wait for retryTimeoutInterval before proceed.
230 }
231 else
232 {
233 // reset retry count.
234 retryCount = 0;
235 }
236
237 switch (state)
238 {
239 case ConnState::connectInProgress:
240 case ConnState::sendInProgress:
241 case ConnState::suspended:
242 // do nothing
243 break;
244 case ConnState::initialized:
245 case ConnState::closed:
246 case ConnState::connectFailed:
247 case ConnState::sendFailed:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530248 case ConnState::recvFailed:
249 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530250 // After establishing the connection, checkQueue() will
251 // get called and it will attempt to send data.
252 doConnect();
253 break;
254 }
255 case ConnState::connected:
AppaRao Puli92a74e52020-06-04 11:12:28 +0530256 case ConnState::idle:
257 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530258 std::string data = requestDataQueue.front();
259 sendMessage(data);
260 break;
261 }
262 default:
263 break;
264 }
265
266 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530267 }
268
269 public:
270 explicit HttpClient(boost::asio::io_context& ioc, const std::string& destIP,
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530271 const std::string& destPort,
272 const std::string& destUri) :
AppaRao Pulibd030d02020-03-20 03:34:29 +0530273 conn(ioc),
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530274 host(destIP), port(destPort), uri(destUri)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530275 {
276 boost::asio::ip::tcp::resolver resolver(ioc);
277 endpoint = resolver.resolve(host, port);
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530278 state = ConnState::initialized;
279 retryCount = 0;
280 maxRetryAttempts = 5;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530281 }
282
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530283 void sendData(const std::string& data)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530284 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530285 if (state == ConnState::suspended)
AppaRao Pulibd030d02020-03-20 03:34:29 +0530286 {
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530287 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530288 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530289
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530290 if (requestDataQueue.size() <= maxRequestQueueSize)
291 {
292 requestDataQueue.push(data);
293 checkQueue(true);
294 }
295 else
296 {
297 BMCWEB_LOG_ERROR << "Request queue is full. So ignoring data.";
298 }
AppaRao Pulibd030d02020-03-20 03:34:29 +0530299
AppaRao Puli2a5689a2020-04-29 15:24:31 +0530300 return;
AppaRao Pulibd030d02020-03-20 03:34:29 +0530301 }
302
303 void setHeaders(
304 const std::vector<std::pair<std::string, std::string>>& httpHeaders)
305 {
306 headers = httpHeaders;
307 }
308};
309
310} // namespace crow