summaryrefslogtreecommitdiff
path: root/http/http_client.hpp
diff options
context:
space:
mode:
authorAppaRao Puli <apparao.puli@linux.intel.com>2020-03-20 01:04:29 +0300
committerAppaRao Puli <apparao.puli@linux.intel.com>2020-04-21 23:04:10 +0300
commitbd030d0a6796bf5c9e2db2ae7e2bcade5979516c (patch)
treeb3dadb79872f17c78929eea3a9c08dc0a7f4b5a6 /http/http_client.hpp
parent1d44c8083853494fc7a3334e39d74a89de64ea09 (diff)
downloadbmcweb-bd030d0a6796bf5c9e2db2ae7e2bcade5979516c.tar.xz
Http client support
Add Http client support to asynchronously open the TCP connection with client and send data(POST only). This doesn't have support to keep open connection alive and also re-attempt send message during fail cases. Tested: - Able to push data to HTTP server successfully. Signed-off-by: AppaRao Puli <apparao.puli@linux.intel.com> Change-Id: I2c1abf8ac4b002278377761d2dbc8b563808efcb
Diffstat (limited to 'http/http_client.hpp')
-rw-r--r--http/http_client.hpp190
1 files changed, 190 insertions, 0 deletions
diff --git a/http/http_client.hpp b/http/http_client.hpp
new file mode 100644
index 0000000000..64f70cbd74
--- /dev/null
+++ b/http/http_client.hpp
@@ -0,0 +1,190 @@
+/*
+// Copyright (c) 2020 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#pragma once
+#include <boost/asio/strand.hpp>
+#include <boost/beast/core.hpp>
+#include <boost/beast/http.hpp>
+#include <boost/beast/version.hpp>
+#include <cstdlib>
+#include <functional>
+#include <iostream>
+#include <memory>
+#include <string>
+
+namespace crow
+{
+
+enum class ConnState
+{
+ initializing,
+ connected,
+ closed
+};
+
+class HttpClient : public std::enable_shared_from_this<HttpClient>
+{
+ private:
+ boost::beast::tcp_stream conn;
+ boost::beast::flat_buffer buffer;
+ boost::beast::http::request<boost::beast::http::string_body> req;
+ boost::beast::http::response<boost::beast::http::string_body> res;
+ boost::asio::ip::tcp::resolver::results_type endpoint;
+ std::vector<std::pair<std::string, std::string>> headers;
+ ConnState state;
+ std::string host;
+ std::string port;
+
+ void sendMessage()
+ {
+ if (state != ConnState::connected)
+ {
+ BMCWEB_LOG_DEBUG << "Not connected to: " << host;
+ return;
+ }
+
+ // Set a timeout on the operation
+ conn.expires_after(std::chrono::seconds(30));
+
+ // Send the HTTP request to the remote host
+ boost::beast::http::async_write(
+ conn, req,
+ [this,
+ self(shared_from_this())](const boost::beast::error_code& ec,
+ const std::size_t& bytesTransferred) {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "sendMessage() failed: "
+ << ec.message();
+ this->doClose();
+ return;
+ }
+ BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
+ << bytesTransferred;
+ boost::ignore_unused(bytesTransferred);
+
+ this->recvMessage();
+ });
+ }
+
+ void recvMessage()
+ {
+ if (state != ConnState::connected)
+ {
+ BMCWEB_LOG_DEBUG << "Not connected to: " << host;
+ return;
+ }
+
+ // Receive the HTTP response
+ boost::beast::http::async_read(
+ conn, buffer, res,
+ [this,
+ self(shared_from_this())](const boost::beast::error_code& ec,
+ const std::size_t& bytesTransferred) {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "recvMessage() failed: "
+ << ec.message();
+ this->doClose();
+ return;
+ }
+ BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
+ << bytesTransferred;
+ boost::ignore_unused(bytesTransferred);
+
+ // Discard received data. We are not interested.
+ BMCWEB_LOG_DEBUG << "recvMessage() data: " << res;
+
+ this->doClose();
+ });
+ }
+
+ void doClose()
+ {
+ boost::beast::error_code ec;
+ conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
+
+ state = ConnState::closed;
+ // not_connected happens sometimes so don't bother reporting it.
+ if (ec && ec != boost::beast::errc::not_connected)
+ {
+ BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message();
+ return;
+ }
+ BMCWEB_LOG_DEBUG << "Connection closed gracefully";
+ }
+
+ ConnState getState()
+ {
+ return state;
+ }
+
+ public:
+ explicit HttpClient(boost::asio::io_context& ioc, const std::string& destIP,
+ const std::string& destPort) :
+ conn(ioc),
+ host(destIP), port(destPort)
+ {
+ boost::asio::ip::tcp::resolver resolver(ioc);
+ endpoint = resolver.resolve(host, port);
+ state = ConnState::initializing;
+ }
+
+ void doConnectAndSend(const std::string& path, const std::string& data)
+ {
+ BMCWEB_LOG_DEBUG << "doConnectAndSend " << host << ":" << port;
+
+ req.version(static_cast<int>(11)); // HTTP 1.1
+ req.target(path);
+ req.method(boost::beast::http::verb::post);
+
+ // Set headers
+ for (const auto& [key, value] : headers)
+ {
+ req.set(key, value);
+ }
+ req.set(boost::beast::http::field::host, host);
+ req.keep_alive(true);
+
+ req.body() = data;
+ req.prepare_payload();
+
+ // Set a timeout on the operation
+ conn.expires_after(std::chrono::seconds(30));
+ conn.async_connect(endpoint, [this, self(shared_from_this())](
+ const boost::beast::error_code& ec,
+ const boost::asio::ip::tcp::resolver::
+ results_type::endpoint_type& ep) {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "Connect " << ep
+ << " failed: " << ec.message();
+ return;
+ }
+ state = ConnState::connected;
+ BMCWEB_LOG_DEBUG << "Connected to: " << ep;
+
+ sendMessage();
+ });
+ }
+
+ void setHeaders(
+ const std::vector<std::pair<std::string, std::string>>& httpHeaders)
+ {
+ headers = httpHeaders;
+ }
+};
+
+} // namespace crow