blob: 64f70cbd744639d2ea523dcf553d3a9deb5ac40a [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>
25#include <string>
26
27namespace crow
28{
29
30enum class ConnState
31{
32 initializing,
33 connected,
34 closed
35};
36
37class HttpClient : public std::enable_shared_from_this<HttpClient>
38{
39 private:
40 boost::beast::tcp_stream conn;
41 boost::beast::flat_buffer buffer;
42 boost::beast::http::request<boost::beast::http::string_body> req;
43 boost::beast::http::response<boost::beast::http::string_body> res;
44 boost::asio::ip::tcp::resolver::results_type endpoint;
45 std::vector<std::pair<std::string, std::string>> headers;
46 ConnState state;
47 std::string host;
48 std::string port;
49
50 void sendMessage()
51 {
52 if (state != ConnState::connected)
53 {
54 BMCWEB_LOG_DEBUG << "Not connected to: " << host;
55 return;
56 }
57
58 // Set a timeout on the operation
59 conn.expires_after(std::chrono::seconds(30));
60
61 // Send the HTTP request to the remote host
62 boost::beast::http::async_write(
63 conn, req,
64 [this,
65 self(shared_from_this())](const boost::beast::error_code& ec,
66 const std::size_t& bytesTransferred) {
67 if (ec)
68 {
69 BMCWEB_LOG_ERROR << "sendMessage() failed: "
70 << ec.message();
71 this->doClose();
72 return;
73 }
74 BMCWEB_LOG_DEBUG << "sendMessage() bytes transferred: "
75 << bytesTransferred;
76 boost::ignore_unused(bytesTransferred);
77
78 this->recvMessage();
79 });
80 }
81
82 void recvMessage()
83 {
84 if (state != ConnState::connected)
85 {
86 BMCWEB_LOG_DEBUG << "Not connected to: " << host;
87 return;
88 }
89
90 // Receive the HTTP response
91 boost::beast::http::async_read(
92 conn, buffer, res,
93 [this,
94 self(shared_from_this())](const boost::beast::error_code& ec,
95 const std::size_t& bytesTransferred) {
96 if (ec)
97 {
98 BMCWEB_LOG_ERROR << "recvMessage() failed: "
99 << ec.message();
100 this->doClose();
101 return;
102 }
103 BMCWEB_LOG_DEBUG << "recvMessage() bytes transferred: "
104 << bytesTransferred;
105 boost::ignore_unused(bytesTransferred);
106
107 // Discard received data. We are not interested.
108 BMCWEB_LOG_DEBUG << "recvMessage() data: " << res;
109
110 this->doClose();
111 });
112 }
113
114 void doClose()
115 {
116 boost::beast::error_code ec;
117 conn.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
118
119 state = ConnState::closed;
120 // not_connected happens sometimes so don't bother reporting it.
121 if (ec && ec != boost::beast::errc::not_connected)
122 {
123 BMCWEB_LOG_ERROR << "shutdown failed: " << ec.message();
124 return;
125 }
126 BMCWEB_LOG_DEBUG << "Connection closed gracefully";
127 }
128
129 ConnState getState()
130 {
131 return state;
132 }
133
134 public:
135 explicit HttpClient(boost::asio::io_context& ioc, const std::string& destIP,
136 const std::string& destPort) :
137 conn(ioc),
138 host(destIP), port(destPort)
139 {
140 boost::asio::ip::tcp::resolver resolver(ioc);
141 endpoint = resolver.resolve(host, port);
142 state = ConnState::initializing;
143 }
144
145 void doConnectAndSend(const std::string& path, const std::string& data)
146 {
147 BMCWEB_LOG_DEBUG << "doConnectAndSend " << host << ":" << port;
148
149 req.version(static_cast<int>(11)); // HTTP 1.1
150 req.target(path);
151 req.method(boost::beast::http::verb::post);
152
153 // Set headers
154 for (const auto& [key, value] : headers)
155 {
156 req.set(key, value);
157 }
158 req.set(boost::beast::http::field::host, host);
159 req.keep_alive(true);
160
161 req.body() = data;
162 req.prepare_payload();
163
164 // Set a timeout on the operation
165 conn.expires_after(std::chrono::seconds(30));
166 conn.async_connect(endpoint, [this, self(shared_from_this())](
167 const boost::beast::error_code& ec,
168 const boost::asio::ip::tcp::resolver::
169 results_type::endpoint_type& ep) {
170 if (ec)
171 {
172 BMCWEB_LOG_ERROR << "Connect " << ep
173 << " failed: " << ec.message();
174 return;
175 }
176 state = ConnState::connected;
177 BMCWEB_LOG_DEBUG << "Connected to: " << ep;
178
179 sendMessage();
180 });
181 }
182
183 void setHeaders(
184 const std::vector<std::pair<std::string, std::string>>& httpHeaders)
185 {
186 headers = httpHeaders;
187 }
188};
189
190} // namespace crow