1 //
2 // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/boostorg/beast
8 //
9 
10 //------------------------------------------------------------------------------
11 //
12 // Example: HTTP SSL client, asynchronous, using system_executor
13 //
14 //------------------------------------------------------------------------------
15 
16 #include "example/common/root_certificates.hpp"
17 
18 #include <boost/beast/core.hpp>
19 #include <boost/beast/http.hpp>
20 #include <boost/beast/ssl.hpp>
21 #include <boost/beast/version.hpp>
22 #include <boost/asio/strand.hpp>
23 #include <boost/asio/system_executor.hpp>
24 #include <cstdlib>
25 #include <functional>
26 #include <iostream>
27 #include <memory>
28 #include <string>
29 
30 namespace beast = boost::beast;         // from <boost/beast.hpp>
31 namespace http = beast::http;           // from <boost/beast/http.hpp>
32 namespace net = boost::asio;            // from <boost/asio.hpp>
33 namespace ssl = boost::asio::ssl;       // from <boost/asio/ssl.hpp>
34 using tcp = boost::asio::ip::tcp;       // from <boost/asio/ip/tcp.hpp>
35 
36 //------------------------------------------------------------------------------
37 
38 // Report a failure
39 void
fail(beast::error_code ec,char const * what)40 fail(beast::error_code ec, char const* what)
41 {
42     std::cerr << what << ": " << ec.message() << "\n";
43 }
44 
45 // Performs an HTTP GET and prints the response
46 class session : public std::enable_shared_from_this<session>
47 {
48     tcp::resolver resolver_;
49     beast::ssl_stream<beast::tcp_stream> stream_;
50     beast::flat_buffer buffer_; // (Must persist between reads)
51     http::request<http::empty_body> req_;
52     http::response<http::string_body> res_;
53 
54     // Objects are constructed with a strand to
55     // ensure that handlers do not execute concurrently.
session(net::strand<net::system_executor> strand,ssl::context & ctx)56     session(net::strand<net::system_executor> strand, ssl::context& ctx)
57         : resolver_(strand)
58         , stream_(strand, ctx)
59     {
60     }
61 
62 public:
63     // Delegate construction to a prive constructor to be able to use
64     // the same strand for both I/O object.
65     explicit
session(ssl::context & ctx)66     session(ssl::context& ctx)
67         : session(net::make_strand(net::system_executor()), ctx)
68     {
69     }
70 
71     // Start the asynchronous operation
72     void
run(char const * host,char const * port,char const * target,int version)73     run(
74         char const* host,
75         char const* port,
76         char const* target,
77         int version)
78     {
79         // Set SNI Hostname (many hosts need this to handshake successfully)
80         if(! SSL_set_tlsext_host_name(stream_.native_handle(), host))
81         {
82             beast::error_code ec{static_cast<int>(::ERR_get_error()), net::error::get_ssl_category()};
83             std::cerr << ec.message() << "\n";
84             return;
85         }
86 
87         // Set up an HTTP GET request message
88         req_.version(version);
89         req_.method(http::verb::get);
90         req_.target(target);
91         req_.set(http::field::host, host);
92         req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
93 
94         // Look up the domain name
95         resolver_.async_resolve(
96             host,
97             port,
98             beast::bind_front_handler(
99                 &session::on_resolve,
100                 shared_from_this()));
101     }
102 
103     void
on_resolve(beast::error_code ec,tcp::resolver::results_type results)104     on_resolve(
105         beast::error_code ec,
106         tcp::resolver::results_type results)
107     {
108         if(ec)
109             return fail(ec, "resolve");
110 
111         // Set a timeout on the operation
112         beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
113 
114         // Make the connection on the IP address we get from a lookup
115         beast::get_lowest_layer(stream_).async_connect(
116             results,
117             beast::bind_front_handler(
118                 &session::on_connect,
119                 shared_from_this()));
120     }
121 
122     void
on_connect(beast::error_code ec,tcp::resolver::results_type::endpoint_type)123     on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type)
124     {
125         if(ec)
126             return fail(ec, "connect");
127 
128         // Perform the SSL handshake
129         stream_.async_handshake(
130             ssl::stream_base::client,
131             beast::bind_front_handler(
132                 &session::on_handshake,
133                 shared_from_this()));
134     }
135 
136     void
on_handshake(beast::error_code ec)137     on_handshake(beast::error_code ec)
138     {
139         if(ec)
140             return fail(ec, "handshake");
141 
142         // Set a timeout on the operation
143         beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
144 
145         // Send the HTTP request to the remote host
146         http::async_write(stream_, req_,
147             beast::bind_front_handler(
148                 &session::on_write,
149                 shared_from_this()));
150     }
151 
152     void
on_write(beast::error_code ec,std::size_t bytes_transferred)153     on_write(
154         beast::error_code ec,
155         std::size_t bytes_transferred)
156     {
157         boost::ignore_unused(bytes_transferred);
158 
159         if(ec)
160             return fail(ec, "write");
161 
162         // Receive the HTTP response
163         http::async_read(stream_, buffer_, res_,
164             beast::bind_front_handler(
165                 &session::on_read,
166                 shared_from_this()));
167     }
168 
169     void
on_read(beast::error_code ec,std::size_t bytes_transferred)170     on_read(
171         beast::error_code ec,
172         std::size_t bytes_transferred)
173     {
174         boost::ignore_unused(bytes_transferred);
175 
176         if(ec)
177             return fail(ec, "read");
178 
179         // Write the message to standard out
180         std::cout << res_ << std::endl;
181 
182         // Set a timeout on the operation
183         beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
184 
185         // Gracefully close the stream
186         stream_.async_shutdown(
187             beast::bind_front_handler(
188                 &session::on_shutdown,
189                 shared_from_this()));
190     }
191 
192     void
on_shutdown(beast::error_code ec)193     on_shutdown(beast::error_code ec)
194     {
195         if(ec == net::error::eof)
196         {
197             // Rationale:
198             // http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error
199             ec = {};
200         }
201         if(ec)
202             return fail(ec, "shutdown");
203 
204         // If we get here then the connection is closed gracefully
205     }
206 };
207 
208 //------------------------------------------------------------------------------
209 
main(int argc,char ** argv)210 int main(int argc, char** argv)
211 {
212     // Check command line arguments.
213     if(argc != 4 && argc != 5)
214     {
215         std::cerr <<
216             "Usage: http-client-async-ssl-system-executor <host> <port> <target> [<HTTP version: 1.0 or 1.1(default)>]\n" <<
217             "Example:\n" <<
218             "    http-client-async-ssl-system-executor www.example.com 443 /\n" <<
219             "    http-client-async-ssl-system-executor www.example.com 443 / 1.0\n";
220         return EXIT_FAILURE;
221     }
222     auto const host = argv[1];
223     auto const port = argv[2];
224     auto const target = argv[3];
225     int version = argc == 5 && !std::strcmp("1.0", argv[4]) ? 10 : 11;
226 
227     // The SSL context is required, and holds certificates
228     ssl::context ctx{ssl::context::tlsv12_client};
229 
230     // This holds the root certificate used for verification
231     load_root_certificates(ctx);
232 
233     // Verify the remote server's certificate
234     ctx.set_verify_mode(ssl::verify_peer);
235 
236     // Launch the asynchronous operation
237     std::make_shared<session>(ctx)->run(host, port, target, version);
238 
239 
240     // The async operations will run on the system_executor.
241     // Because the main thread has nothing to do in this example, we just wait
242     // for the system_executor to run out of work.
243     net::system_executor().context().join();
244 
245     return EXIT_SUCCESS;
246 }
247