diff --git a/example/http/client/async-ssl-system-executor/CMakeLists.txt b/example/http/client/async-ssl-system-executor/CMakeLists.txt new file mode 100644 index 00000000..049a2fea --- /dev/null +++ b/example/http/client/async-ssl-system-executor/CMakeLists.txt @@ -0,0 +1,31 @@ +# +# Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# +# Official repository: https://github.com/boostorg/beast +# + +if (OPENSSL_FOUND) + GroupSources(include/boost/beast beast) + GroupSources(example/common common) + GroupSources(example/http/client/async-ssl-system-executor "/") + + add_executable (http-client-async-ssl-system-executor + ${BOOST_BEAST_FILES} + ${PROJECT_SOURCE_DIR}/example/common/root_certificates.hpp + Jamfile + http_client_async_ssl_system_executor.cpp + ) + + set_property(TARGET http-client-async-ssl-system-executor PROPERTY FOLDER "example-http-client") + + target_link_libraries (http-client-async-ssl-system-executor + OpenSSL::SSL OpenSSL::Crypto + lib-asio + lib-asio-ssl + lib-beast + ) + +endif() diff --git a/example/http/client/async-ssl-system-executor/Jamfile b/example/http/client/async-ssl-system-executor/Jamfile new file mode 100644 index 00000000..732da863 --- /dev/null +++ b/example/http/client/async-ssl-system-executor/Jamfile @@ -0,0 +1,22 @@ +# +# Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# +# Official repository: https://github.com/boostorg/beast +# + +import ac ; + +project + : requirements + [ ac.check-library /boost/beast//lib-asio-ssl : /boost/beast//lib-asio-ssl/static : no ] + ; + +exe http-client-async-ssl-system-executor : + http_client_async_ssl_system_executor.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/http/client/async-ssl-system-executor/http_client_async_ssl_system_executor.cpp b/example/http/client/async-ssl-system-executor/http_client_async_ssl_system_executor.cpp new file mode 100644 index 00000000..b9fe5dfe --- /dev/null +++ b/example/http/client/async-ssl-system-executor/http_client_async_ssl_system_executor.cpp @@ -0,0 +1,245 @@ +// +// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: HTTP SSL client, asynchronous, using system_executor +// +//------------------------------------------------------------------------------ + +#include "example/common/root_certificates.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast = boost::beast; // from +namespace http = beast::http; // from +namespace net = boost::asio; // from +namespace ssl = boost::asio::ssl; // from +using tcp = boost::asio::ip::tcp; // from + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(beast::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Performs an HTTP GET and prints the response +class session : public std::enable_shared_from_this +{ + tcp::resolver resolver_; + beast::ssl_stream stream_; + beast::flat_buffer buffer_; // (Must persist between reads) + http::request req_; + http::response res_; + + // Objects are constructed with a strand to + // ensure that handlers do not execute concurrently. + session(net::strand strand, ssl::context& ctx) + : resolver_(strand) + , stream_(strand, ctx) + { + } + +public: + // Delegate construction to a prive constructor to be able to use + // the same strand for both I/O object. + explicit + session(ssl::context& ctx) + : session(net::make_strand(net::system_executor()), ctx) + { + } + + // Start the asynchronous operation + void + run( + char const* host, + char const* port, + char const* target, + int version) + { + // Set SNI Hostname (many hosts need this to handshake successfully) + if(! SSL_set_tlsext_host_name(stream_.native_handle(), host)) + { + beast::error_code ec{static_cast(::ERR_get_error()), net::error::get_ssl_category()}; + std::cerr << ec.message() << "\n"; + return; + } + + // Set up an HTTP GET request message + req_.version(version); + req_.method(http::verb::get); + req_.target(target); + req_.set(http::field::host, host); + req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); + + // Look up the domain name + resolver_.async_resolve( + host, + port, + beast::bind_front_handler( + &session::on_resolve, + shared_from_this())); + } + + void + on_resolve( + beast::error_code ec, + tcp::resolver::results_type results) + { + if(ec) + return fail(ec, "resolve"); + + // Set a timeout on the operation + beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30)); + + // Make the connection on the IP address we get from a lookup + beast::get_lowest_layer(stream_).async_connect( + results, + beast::bind_front_handler( + &session::on_connect, + shared_from_this())); + } + + void + on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type) + { + if(ec) + return fail(ec, "connect"); + + // Perform the SSL handshake + stream_.async_handshake( + ssl::stream_base::client, + beast::bind_front_handler( + &session::on_handshake, + shared_from_this())); + } + + void + on_handshake(beast::error_code ec) + { + if(ec) + return fail(ec, "handshake"); + + // Set a timeout on the operation + beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30)); + + // Send the HTTP request to the remote host + http::async_write(stream_, req_, + beast::bind_front_handler( + &session::on_write, + shared_from_this())); + } + + void + on_write( + beast::error_code ec, + std::size_t bytes_transferred) + { + boost::ignore_unused(bytes_transferred); + + if(ec) + return fail(ec, "write"); + + // Receive the HTTP response + http::async_read(stream_, buffer_, res_, + beast::bind_front_handler( + &session::on_read, + shared_from_this())); + } + + void + on_read( + beast::error_code ec, + std::size_t bytes_transferred) + { + boost::ignore_unused(bytes_transferred); + + if(ec) + return fail(ec, "read"); + + // Write the message to standard out + std::cout << res_ << std::endl; + + // Set a timeout on the operation + beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30)); + + // Gracefully close the stream + stream_.async_shutdown( + beast::bind_front_handler( + &session::on_shutdown, + shared_from_this())); + } + + void + on_shutdown(beast::error_code ec) + { + if(ec == net::error::eof) + { + // Rationale: + // http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error + ec = {}; + } + if(ec) + return fail(ec, "shutdown"); + + // If we get here then the connection is closed gracefully + } +}; + +//------------------------------------------------------------------------------ + +int main(int argc, char** argv) +{ + // Check command line arguments. + if(argc != 4 && argc != 5) + { + std::cerr << + "Usage: http-client-async-ssl-system-executor []\n" << + "Example:\n" << + " http-client-async-ssl-system-executor www.example.com 443 /\n" << + " http-client-async-ssl-system-executor www.example.com 443 / 1.0\n"; + return EXIT_FAILURE; + } + auto const host = argv[1]; + auto const port = argv[2]; + auto const target = argv[3]; + int version = argc == 5 && !std::strcmp("1.0", argv[4]) ? 10 : 11; + + // The SSL context is required, and holds certificates + ssl::context ctx{ssl::context::tlsv12_client}; + + // This holds the root certificate used for verification + load_root_certificates(ctx); + + // Verify the remote server's certificate + ctx.set_verify_mode(ssl::verify_peer); + + // Launch the asynchronous operation + std::make_shared(ctx)->run(host, port, target, version); + + + // The async operations will run on the system_executor. + // Because the main thread has nothing to do in this example, we just wait + // for the system_executor to run out of work. + net::system_executor().context().join(); + + return EXIT_SUCCESS; +}