FETPAPI 0.6.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
29#include <boost/version.hpp>
30#if USE_WINTLS_INSTEAD_OF_OPENSSL
31#include <wintls.hpp>
32#elif BOOST_VERSION < 107000
33#include <boost/asio/ssl/error.hpp>
34#include <boost/asio/ssl/rfc2818_verification.hpp>
35#include <boost/asio/ssl/stream.hpp>
36#elif BOOST_VERSION < 108600
37#if BOOST_VERSION < 107300
38#include <boost/asio/ssl/rfc2818_verification.hpp>
39#else
40#include <boost/asio/ssl/host_name_verification.hpp>
41#endif
42#include <boost/beast/ssl.hpp>
43#else
44#include <boost/asio/ssl.hpp>
45#endif
46
47namespace beast = boost::beast; // from <boost/beast.hpp>
48namespace http = beast::http; // from <boost/beast/http.hpp>
49namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
50using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
51
52namespace ETP_NS
53{
54 // Performs an HTTP GET and stores the response
55 class HttpsClientSession : public std::enable_shared_from_this<HttpsClientSession>
56 {
57 tcp::resolver resolver_;
58#if USE_WINTLS_INSTEAD_OF_OPENSSL
59 wintls::stream<beast::tcp_stream> stream_;
60#elif BOOST_VERSION < 107000
61 ssl::stream<tcp::socket> stream_;
62#elif BOOST_VERSION < 108600
63 beast::ssl_stream<beast::tcp_stream> stream_;
64#else
65 ssl::stream<beast::tcp_stream> stream_;
66#endif
67 boost::beast::flat_buffer buffer_; // (Must persist between reads)
68 http::request<http::empty_body> req_;
69 http::request<http::empty_body> proxyHandshake;
70 http::response<http::string_body> res_;
71 http::response<http::empty_body> proxyHandshakeResponse;
72 // use own response parser
73 // NOTE: 200 response to a CONNECT request from a tunneling proxy do not carry a body
74 http::response_parser<http::empty_body> http_proxy_handshake_parser;
75
76 public:
77 // Resolver and stream require an io_context
78 explicit
79 HttpsClientSession(boost::asio::io_context& ioc,
80#if USE_WINTLS_INSTEAD_OF_OPENSSL
81 wintls::context& ctx
82#else
83 ssl::context& ctx
84#endif
85 )
86 : resolver_(ioc)
87 , stream_(ioc, ctx)
88 , http_proxy_handshake_parser(proxyHandshakeResponse)
89 {
90 }
91
92 // Start the asynchronous operation
93 void
94 run(
95 const std::string& etpServerHost,
96 uint16_t etpServerPort,
97 const std::string& etpServerTarget,
98 uint32_t version,
99 std::string authorization = "",
100 const std::string& proxyHost = "",
101 uint16_t proxyPort = 80,
102 const std::string& proxyAuthorization = "")
103 {
104#if USE_WINTLS_INSTEAD_OF_OPENSSL
105 // Set SNI hostname (many hosts need this to handshake successfully)
106 stream_.set_server_hostname(etpServerHost);
107
108 // Enable Check whether the Server Certificate was revoked
109 stream_.set_certificate_revocation_check(true);
110#else
111 // Set SNI Hostname (many hosts need this to handshake successfully)
112 if (!SSL_set_tlsext_host_name(stream_.native_handle(), etpServerHost.data()))
113 {
114 boost::system::error_code ec{ static_cast<int>(::ERR_get_error()), boost::asio::error::get_ssl_category() };
115 std::cerr << "HTTPS on connect (SNI): " << ec.message() << "\n";
116 return;
117 }
118
119 // Set the expected hostname in the peer certificate for verification
120#if BOOST_VERSION < 107300
121 stream_.set_verify_callback(ssl::rfc2818_verification(etpServerHost));
122#else
123 stream_.set_verify_callback(ssl::host_name_verification(etpServerHost));
124#endif
125#endif
126
127 // Set up an HTTP GET request message
128 req_.version(version);
129 req_.method(http::verb::get);
130 req_.target(etpServerTarget);
131 req_.set(http::field::host, etpServerHost + ':' + std::to_string(etpServerPort));
132 req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
133 if (!authorization.empty()) {
134 req_.set(http::field::authorization, authorization);
135 }
136
137 if (!proxyHost.empty()) {
138 proxyHandshake.version(version);
139 proxyHandshake.method(http::verb::connect);
140 proxyHandshake.target(etpServerHost + ':' + std::to_string(etpServerPort));
141 proxyHandshake.set(http::field::host, etpServerHost + ':' + std::to_string(etpServerPort));
142 if (!authorization.empty()) {
143 proxyHandshake.set(http::field::authorization, authorization);
144 }
145 if (!proxyAuthorization.empty()) {
146 proxyHandshake.set(http::field::proxy_authorization, proxyAuthorization);
147 }
148 }
149
150 // Look up the domain name
151 resolver_.async_resolve(
152 proxyHost.empty() ? etpServerHost : proxyHost,
153 std::to_string(proxyHost.empty() ? etpServerPort : proxyPort),
154 std::bind(
155 &HttpsClientSession::on_resolve,
156 shared_from_this(),
157 std::placeholders::_1,
158 std::placeholders::_2));
159 }
160
161 void
162 on_resolve(
163 boost::system::error_code ec,
164 tcp::resolver::results_type results)
165 {
166 if (ec) {
167 std::cerr << "HTTP over SSL resolve : " << ec.message() << std::endl;
168 return;
169 }
170
171 // Reality check: IPv6 is unlikely to be available yet
172 std::vector<tcp::endpoint> endpoints(results.begin(), results.end());;
173 std::stable_partition(endpoints.begin(), endpoints.end(), [](auto entry) {return entry.protocol() == tcp::v4(); });
174
175 // Make the connection on the IP address we get from a lookup
176#if BOOST_VERSION < 107000
177 boost::asio::async_connect(
178 stream_.next_layer(),
179 endpoints.begin(),
180 endpoints.end(),
181 std::bind(
182 &HttpsClientSession::on_connect,
183 shared_from_this(),
184 std::placeholders::_1));
185#else
186 beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(3));
187 beast::get_lowest_layer(stream_).async_connect(
188 endpoints,
189 beast::bind_front_handler(
190 &HttpsClientSession::on_connect,
191 std::static_pointer_cast<HttpsClientSession>(shared_from_this())));
192#endif
193 }
194
195#if BOOST_VERSION < 107000
196 void on_connect(boost::system::error_code ec)
197#else
198 void on_connect(boost::beast::error_code ec, tcp::resolver::results_type::endpoint_type)
199#endif
200 {
201 if (ec) {
202 std::cerr << "HTTP over SSL connect : " << ec.message() << std::endl;
203 return;
204 }
205
206 if (proxyHandshake.count(http::field::host) > 0) {
207 // Send the handshake to the proxy
208 http::async_write(stream_.next_layer(), proxyHandshake,
209 std::bind(
211 shared_from_this(),
212 std::placeholders::_1,
213 std::placeholders::_2));
214 }
215 else {
216 // Perform the SSL handshake
217 stream_.async_handshake(
218#if USE_WINTLS_INSTEAD_OF_OPENSSL
219 wintls::handshake_type::client,
220#else
221 ssl::stream_base::client,
222#endif
223 std::bind(
224 &HttpsClientSession::on_handshake,
225 shared_from_this(),
226 std::placeholders::_1));
227 }
228 }
229
230 void
232 boost::system::error_code ec,
233 std::size_t bytes_transferred)
234 {
235 boost::ignore_unused(bytes_transferred);
236
237 if (ec) {
238 std::cerr << "HTTP over SSL Proxy handshake write : " << ec.message() << std::endl;
239 return;
240 }
241
254 http_proxy_handshake_parser.skip(true); // see https://stackoverflow.com/a/49837467/10904212
255
256 // Receive the HTTP response
257 http::async_read(stream_.next_layer(), buffer_, http_proxy_handshake_parser,
258 std::bind(
259 &HttpsClientSession::on_proxy_handshake_read,
260 shared_from_this(),
261 std::placeholders::_1,
262 std::placeholders::_2));
263 }
264
265 void
266 on_proxy_handshake_read(
267 boost::system::error_code ec,
268 std::size_t bytes_transferred)
269 {
270 boost::ignore_unused(bytes_transferred);
271 if (ec) {
272 std::cerr << "HTTP over SSL read : " << ec.message() << std::endl;
273 return;
274 }
275
276 // Perform the SSL handshake
277 stream_.async_handshake(
278#if USE_WINTLS_INSTEAD_OF_OPENSSL
279 wintls::handshake_type::client,
280#else
281 ssl::stream_base::client,
282#endif
283 std::bind(
284 &HttpsClientSession::on_handshake,
285 shared_from_this(),
286 std::placeholders::_1));
287 }
288
289 void
290 on_handshake(boost::system::error_code ec)
291 {
292 if (ec) {
293 std::cerr << "HTTP over SSL handshake : " << ec.message() << std::endl;
294 return;
295 }
296
297 // Send the HTTP request to the remote host
298 http::async_write(stream_, req_,
299 std::bind(
300 &HttpsClientSession::on_write,
301 shared_from_this(),
302 std::placeholders::_1,
303 std::placeholders::_2));
304 }
305
306 void
307 on_write(
308 boost::system::error_code ec,
309 std::size_t bytes_transferred)
310 {
311 boost::ignore_unused(bytes_transferred);
312
313 if (ec) {
314 std::cerr << "HTTP over SSL write : " << ec.message() << std::endl;
315 return;
316 }
317
318 // Receive the HTTP response
319 http::async_read(stream_, buffer_, res_,
320 std::bind(
321 &HttpsClientSession::on_read,
322 shared_from_this(),
323 std::placeholders::_1,
324 std::placeholders::_2));
325 }
326
327 void
328 on_read(
329 boost::system::error_code ec,
330 std::size_t bytes_transferred)
331 {
332 boost::ignore_unused(bytes_transferred);
333
334 if (ec) {
335 std::cerr << "HTTP over SSL read : " << ec.message() << std::endl;
336 return;
337 }
338
339 // Gracefully close the stream_
340 stream_.async_shutdown(
341 std::bind(
342 &HttpsClientSession::on_shutdown,
343 shared_from_this(),
344 std::placeholders::_1));
345 }
346
347 void
348 on_shutdown(boost::system::error_code ec)
349 {
350 if (ec == boost::asio::error::eof)
351 {
352 // Rationale:
353 // http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error
354 ec.assign(0, ec.category());
355 }
356 if (ec) {
357 // Do nothing : https://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error
358 //std::cerr << "shutdown : " << ec.message() << std::endl;
359 return;
360 }
361
362 // If we get here then the connection is closed gracefully
363 }
364
365 const http::response<http::string_body>& getResponse() const {
366 return res_;
367 }
368 };
369}
void on_proxy_handshake_write(boost::system::error_code ec, std::size_t bytes_transferred)
Definition HttpsClientSession.h:231