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