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
29namespace beast = boost::beast; // from <boost/beast.hpp>
30namespace http = beast::http; // from <boost/beast/http.hpp>
31namespace net = boost::asio; // from <boost/asio.hpp>
32namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
33using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
34
35//------------------------------------------------------------------------------
36
37// Report a failure
38void
39fail(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
45class 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
53public:
54 explicit
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
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(value: version);
81 req_.method(v: http::verb::get);
82 req_.target(s: target);
83 req_.set(name: http::field::host, value: host);
84 req_.set(name: http::field::user_agent, BOOST_BEAST_VERSION_STRING);
85
86 // Look up the domain name
87 resolver_.async_resolve(
88 host,
89 service: port,
90 token: beast::bind_front_handler(
91 handler: &session::on_resolve,
92 args: shared_from_this()));
93 }
94
95 void
96 on_resolve(
97 beast::error_code ec,
98 tcp::resolver::results_type results)
99 {
100 if(ec)
101 return fail(ec, what: "resolve");
102
103 // Set a timeout on the operation
104 beast::get_lowest_layer(t&: stream_).expires_after(expiry_time: std::chrono::seconds(30));
105
106 // Make the connection on the IP address we get from a lookup
107 beast::get_lowest_layer(t&: stream_).async_connect(
108 endpoints: results,
109 handler: beast::bind_front_handler(
110 handler: &session::on_connect,
111 args: shared_from_this()));
112 }
113
114 void
115 on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type)
116 {
117 if(ec)
118 return fail(ec, what: "connect");
119
120 // Perform the SSL handshake
121 stream_.async_handshake(
122 type: ssl::stream_base::client,
123 handler: beast::bind_front_handler(
124 handler: &session::on_handshake,
125 args: shared_from_this()));
126 }
127
128 void
129 on_handshake(beast::error_code ec)
130 {
131 if(ec)
132 return fail(ec, what: "handshake");
133
134 // Set a timeout on the operation
135 beast::get_lowest_layer(t&: stream_).expires_after(expiry_time: std::chrono::seconds(30));
136
137 // Send the HTTP request to the remote host
138 http::async_write(stream&: stream_, msg: req_,
139 handler: beast::bind_front_handler(
140 handler: &session::on_write,
141 args: shared_from_this()));
142 }
143
144 void
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, what: "write");
153
154 // Receive the HTTP response
155 http::async_read(stream&: stream_, buffer&: buffer_, msg&: res_,
156 handler: beast::bind_front_handler(
157 handler: &session::on_read,
158 args: shared_from_this()));
159 }
160
161 void
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, what: "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(t&: stream_).expires_after(expiry_time: std::chrono::seconds(30));
176
177 // Gracefully close the stream
178 stream_.async_shutdown(
179 handler: beast::bind_front_handler(
180 handler: &session::on_shutdown,
181 args: shared_from_this()));
182 }
183
184 void
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, what: "shutdown");
195
196 // If we get here then the connection is closed gracefully
197 }
198};
199
200//------------------------------------------------------------------------------
201
202int 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(s1: "1.0", s2: 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 args: net::make_strand(ctx&: ioc),
236 args&: 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

source code of boost/libs/beast/example/http/client/async-ssl/http_client_async_ssl.cpp