• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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