FETPAPI 0.3.0.0
This project provides C++ classes which facilitate the developement of ETP1.2 clients and servers.
Loading...
Searching...
No Matches
HttpsClientSession.h
1/*-----------------------------------------------------------------------
2Licensed to the Apache Software Foundation (ASF) under one
3or more contributor license agreements. See the NOTICE file
4distributed with this work for additional information
5regarding copyright ownership. The ASF licenses this file
6to you under the Apache License, Version 2.0 (the
7"License"; you may not use this file except in compliance
8with the License. You may obtain a copy of the License at
9
10http://www.apache.org/licenses/LICENSE-2.0
11
12Unless required by applicable law or agreed to in writing,
13software distributed under the License is distributed on an
14"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15KIND, either express or implied. See the License for the
16specific language governing permissions and limitations
17under the License.
18-----------------------------------------------------------------------*/
19#pragma once
20
21#include <iostream>
22
23#include <boost/beast/core.hpp>
24#include <boost/beast/http.hpp>
25#include <boost/beast/version.hpp>
26#include <boost/asio/connect.hpp>
27#include <boost/asio/ip/tcp.hpp>
28#include <boost/asio/ssl/error.hpp>
29#include <boost/asio/ssl/stream.hpp>
30
31using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
32namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
33namespace http = boost::beast::http; // from <boost/beast/http.hpp>
34
35namespace ETP_NS
36{
37 // Performs an HTTP GET and stores the response
38 class HttpsClientSession : public std::enable_shared_from_this<HttpsClientSession>
39 {
40 tcp::resolver resolver_;
41 ssl::stream<tcp::socket> stream_;
42 boost::beast::flat_buffer buffer_; // (Must persist between reads)
43 http::request<http::empty_body> req_;
44 http::request<http::empty_body> proxyHandshake;
45 http::response<http::string_body> res_;
46 http::response<http::empty_body> proxyHandshakeResponse;
47 // use own response parser
48 // NOTE: 200 response to a CONNECT request from a tunneling proxy do not carry a body
49 http::response_parser<http::empty_body> http_proxy_handshake_parser;
50
51 public:
52 // Resolver and stream require an io_context
53 explicit
54 HttpsClientSession(boost::asio::io_context& ioc, ssl::context& ctx)
55 : resolver_(ioc)
56 , stream_(ioc, ctx)
57 , http_proxy_handshake_parser(proxyHandshakeResponse)
58 {
59 }
60
61 // Start the asynchronous operation
62 void
63 run(
64 const std::string& etpServerHost,
65 uint16_t etpServerPort,
66 const std::string& etpServerTarget,
67 int version,
68 std::string authorization = "",
69 const std::string& proxyHost = "",
70 uint16_t proxyPort = 80,
71 const std::string& proxyAuthorization = "")
72 {
73 size_t hostSizeWithNullTermChar = etpServerHost.size() + 1;
74 char* copyHost = new char[hostSizeWithNullTermChar];
75 std::memcpy(copyHost, etpServerHost.c_str(), hostSizeWithNullTermChar); // Copy host because it must be non const in SSL_set_tlsext_host_name
76 // Set SNI Hostname (many hosts need this to handshake successfully)
77 if (!SSL_set_tlsext_host_name(stream_.native_handle(), copyHost))
78 {
79 boost::system::error_code ec{ static_cast<int>(::ERR_get_error()), boost::asio::error::get_ssl_category() };
80 std::cerr << ec.message() << "\n";
81 delete[] copyHost;
82 return;
83 }
84 delete[] copyHost;
85
86 // Set up an HTTP GET request message
87 req_.version(version);
88 req_.method(http::verb::get);
89 req_.target(etpServerTarget);
90 req_.set(http::field::host, etpServerHost + ':' + std::to_string(etpServerPort));
91 req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
92 if (!authorization.empty()) {
93 req_.set(http::field::authorization, authorization);
94 }
95
96 if (!proxyHost.empty()) {
97 proxyHandshake.version(version);
98 proxyHandshake.method(http::verb::connect);
99 proxyHandshake.target(etpServerHost + ':' + std::to_string(etpServerPort));
100 proxyHandshake.set(http::field::host, etpServerHost + ':' + std::to_string(etpServerPort));
101 if (!authorization.empty()) {
102 proxyHandshake.set(http::field::authorization, authorization);
103 }
104 if (!proxyAuthorization.empty()) {
105 proxyHandshake.set(http::field::proxy_authorization, proxyAuthorization);
106 }
107 }
108
109 // Look up the domain name
110 resolver_.async_resolve(
111 proxyHost.empty() ? etpServerHost : proxyHost,
112 std::to_string(proxyHost.empty() ? etpServerPort : proxyPort),
113 std::bind(
114 &HttpsClientSession::on_resolve,
115 shared_from_this(),
116 std::placeholders::_1,
117 std::placeholders::_2));
118 }
119
120 void
121 on_resolve(
122 boost::system::error_code ec,
123 tcp::resolver::results_type results)
124 {
125 if (ec) {
126 std::cerr << "resolve : " << ec.message() << std::endl;
127 return;
128 }
129
130 // Make the connection on the IP address we get from a lookup
131 boost::asio::async_connect(
132 stream_.next_layer(),
133 results.begin(),
134 results.end(),
135 std::bind(
136 &HttpsClientSession::on_connect,
137 shared_from_this(),
138 std::placeholders::_1));
139 }
140
141 void
142 on_connect(boost::system::error_code ec)
143 {
144 if (ec) {
145 std::cerr << "connect : " << ec.message() << std::endl;
146 return;
147 }
148
149 if (proxyHandshake.count(http::field::host) > 0) {
150 // Send the handshake to the proxy
151 http::async_write(stream_.next_layer(), proxyHandshake,
152 std::bind(
153 &HttpsClientSession::on_proxy_handshake_write,
154 shared_from_this(),
155 std::placeholders::_1,
156 std::placeholders::_2));
157 }
158 else {
159 // Perform the SSL handshake
160 stream_.async_handshake(
161 ssl::stream_base::client,
162 std::bind(
163 &HttpsClientSession::on_handshake,
164 shared_from_this(),
165 std::placeholders::_1));
166 }
167 }
168
169 void
171 boost::system::error_code ec,
172 std::size_t bytes_transferred)
173 {
174 boost::ignore_unused(bytes_transferred);
175
176 if (ec) {
177 std::cerr << "Proxy handshake write : " << ec.message() << std::endl;
178 return;
179 }
180
193 http_proxy_handshake_parser.skip(true); // see https://stackoverflow.com/a/49837467/10904212
194
195 // Receive the HTTP response
196 http::async_read(stream_.next_layer(), buffer_, http_proxy_handshake_parser,
197 std::bind(
198 &HttpsClientSession::on_proxy_handshake_read,
199 shared_from_this(),
200 std::placeholders::_1,
201 std::placeholders::_2));
202 }
203
204 void
205 on_proxy_handshake_read(
206 boost::system::error_code ec,
207 std::size_t bytes_transferred)
208 {
209 boost::ignore_unused(bytes_transferred);
210 if (ec) {
211 std::cerr << "read : " << ec.message() << std::endl;
212 return;
213 }
214
215 // Perform the SSL handshake
216 stream_.async_handshake(
217 ssl::stream_base::client,
218 std::bind(
219 &HttpsClientSession::on_handshake,
220 shared_from_this(),
221 std::placeholders::_1));
222 }
223
224 void
225 on_handshake(boost::system::error_code ec)
226 {
227 if (ec) {
228 std::cerr << "handshake : " << ec.message() << std::endl;
229 return;
230 }
231
232 // Send the HTTP request to the remote host
233 http::async_write(stream_, req_,
234 std::bind(
235 &HttpsClientSession::on_write,
236 shared_from_this(),
237 std::placeholders::_1,
238 std::placeholders::_2));
239 }
240
241 void
242 on_write(
243 boost::system::error_code ec,
244 std::size_t bytes_transferred)
245 {
246 boost::ignore_unused(bytes_transferred);
247
248 if (ec) {
249 std::cerr << "write : " << ec.message() << std::endl;
250 return;
251 }
252
253 // Receive the HTTP response
254 http::async_read(stream_, buffer_, res_,
255 std::bind(
256 &HttpsClientSession::on_read,
257 shared_from_this(),
258 std::placeholders::_1,
259 std::placeholders::_2));
260 }
261
262 void
263 on_read(
264 boost::system::error_code ec,
265 std::size_t bytes_transferred)
266 {
267 boost::ignore_unused(bytes_transferred);
268
269 if (ec) {
270 std::cerr << "read : " << ec.message() << std::endl;
271 return;
272 }
273
274 // Force close
275 boost::system::error_code closeEc;
276 stream_.next_layer().close(closeEc);
277 /*
278 // Gracefully close the stream_
279 stream_.async_shutdown(
280 std::bind(
281 &HttpsClientSession::on_shutdown,
282 shared_from_this(),
283 std::placeholders::_1));
284 */
285 }
286
287 void
288 on_shutdown(boost::system::error_code ec)
289 {
290 if (ec == boost::asio::error::eof)
291 {
292 // Rationale:
293 // http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error
294 ec.assign(0, ec.category());
295 }
296 if (ec) {
297 // Do nothing : https://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error
298 //std::cerr << "shutdown : " << ec.message() << std::endl;
299 return;
300 }
301
302 // If we get here then the connection is closed gracefully
303 }
304
305 const http::response<http::string_body>& getResponse() const {
306 return res_;
307 }
308 };
309}
Definition HttpsClientSession.h:39
void on_proxy_handshake_write(boost::system::error_code ec, std::size_t bytes_transferred)
Definition HttpsClientSession.h:170