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::query(net::system_executor(), net::execution::context).join();
244
245 return EXIT_SUCCESS;
246 }
247