summaryrefslogtreecommitdiff
path: root/http/http_client.hpp
diff options
context:
space:
mode:
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