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: WebSocket 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/ssl.hpp>
20 #include <boost/beast/websocket.hpp>
21 #include <boost/beast/websocket/ssl.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 websocket = beast::websocket; // from <boost/beast/websocket.hpp>
33 namespace net = boost::asio; // from <boost/asio.hpp>
34 namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
35 using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
36
37 //------------------------------------------------------------------------------
38
39 // Report a failure
40 void
fail(beast::error_code ec,char const * what)41 fail(beast::error_code ec, char const* what)
42 {
43 std::cerr << what << ": " << ec.message() << "\n";
44 }
45
46 // Sends a WebSocket message and prints the response
47 class session : public std::enable_shared_from_this<session>
48 {
49 tcp::resolver resolver_;
50 websocket::stream<
51 beast::ssl_stream<beast::tcp_stream>> ws_;
52 beast::flat_buffer buffer_;
53 std::string host_;
54 std::string text_;
55
56 // Objects are constructed with a strand to
57 // ensure that handlers do not execute concurrently.
session(net::strand<net::system_executor> ex,ssl::context & ctx)58 session(net::strand<net::system_executor> ex, ssl::context& ctx)
59 : resolver_(ex)
60 , ws_(ex, ctx)
61 {
62 }
63 public:
64 // Delegate construction to a prive constructor to be able to use
65 // the same strand for both I/O objects.
66 explicit
session(ssl::context & ctx)67 session(ssl::context& ctx)
68 : session(net::make_strand(net::system_executor{}), ctx)
69 {
70 }
71
72 // Start the asynchronous operation
73 void
run(char const * host,char const * port,char const * text)74 run(
75 char const* host,
76 char const* port,
77 char const* text)
78 {
79 // Save these for later
80 host_ = host;
81 text_ = text;
82
83 // Look up the domain name
84 resolver_.async_resolve(
85 host,
86 port,
87 beast::bind_front_handler(
88 &session::on_resolve,
89 shared_from_this()));
90 }
91
92 void
on_resolve(beast::error_code ec,tcp::resolver::results_type results)93 on_resolve(
94 beast::error_code ec,
95 tcp::resolver::results_type results)
96 {
97 if(ec)
98 return fail(ec, "resolve");
99
100 // Set a timeout on the operation
101 beast::get_lowest_layer(ws_).expires_after(std::chrono::seconds(30));
102
103 // Make the connection on the IP address we get from a lookup
104 beast::get_lowest_layer(ws_).async_connect(
105 results,
106 beast::bind_front_handler(
107 &session::on_connect,
108 shared_from_this()));
109 }
110
111 void
on_connect(beast::error_code ec,tcp::resolver::results_type::endpoint_type ep)112 on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type ep)
113 {
114 if(ec)
115 return fail(ec, "connect");
116
117 // Update the host_ string. This will provide the value of the
118 // Host HTTP header during the WebSocket handshake.
119 // See https://tools.ietf.org/html/rfc7230#section-5.4
120 host_ += ':' + std::to_string(ep.port());
121
122 // Set a timeout on the operation
123 beast::get_lowest_layer(ws_).expires_after(std::chrono::seconds(30));
124
125 // Perform the SSL handshake
126 ws_.next_layer().async_handshake(
127 ssl::stream_base::client,
128 beast::bind_front_handler(
129 &session::on_ssl_handshake,
130 shared_from_this()));
131 }
132
133 void
on_ssl_handshake(beast::error_code ec)134 on_ssl_handshake(beast::error_code ec)
135 {
136 if(ec)
137 return fail(ec, "ssl_handshake");
138
139 // Turn off the timeout on the tcp_stream, because
140 // the websocket stream has its own timeout system.
141 beast::get_lowest_layer(ws_).expires_never();
142
143 // Set suggested timeout settings for the websocket
144 ws_.set_option(
145 websocket::stream_base::timeout::suggested(
146 beast::role_type::client));
147
148 // Set a decorator to change the User-Agent of the handshake
149 ws_.set_option(websocket::stream_base::decorator(
150 [](websocket::request_type& req)
151 {
152 req.set(http::field::user_agent,
153 std::string(BOOST_BEAST_VERSION_STRING) +
154 " websocket-client-async-ssl");
155 }));
156
157 // Perform the websocket handshake
158 ws_.async_handshake(host_, "/",
159 beast::bind_front_handler(
160 &session::on_handshake,
161 shared_from_this()));
162 }
163
164 void
on_handshake(beast::error_code ec)165 on_handshake(beast::error_code ec)
166 {
167 if(ec)
168 return fail(ec, "handshake");
169
170 // Send the message
171 ws_.async_write(
172 net::buffer(text_),
173 beast::bind_front_handler(
174 &session::on_write,
175 shared_from_this()));
176 }
177
178 void
on_write(beast::error_code ec,std::size_t bytes_transferred)179 on_write(
180 beast::error_code ec,
181 std::size_t bytes_transferred)
182 {
183 boost::ignore_unused(bytes_transferred);
184
185 if(ec)
186 return fail(ec, "write");
187
188 // Read a message into our buffer
189 ws_.async_read(
190 buffer_,
191 beast::bind_front_handler(
192 &session::on_read,
193 shared_from_this()));
194 }
195
196 void
on_read(beast::error_code ec,std::size_t bytes_transferred)197 on_read(
198 beast::error_code ec,
199 std::size_t bytes_transferred)
200 {
201 boost::ignore_unused(bytes_transferred);
202
203 if(ec)
204 return fail(ec, "read");
205
206 // Close the WebSocket connection
207 ws_.async_close(websocket::close_code::normal,
208 beast::bind_front_handler(
209 &session::on_close,
210 shared_from_this()));
211 }
212
213 void
on_close(beast::error_code ec)214 on_close(beast::error_code ec)
215 {
216 if(ec)
217 return fail(ec, "close");
218
219 // If we get here then the connection is closed gracefully
220
221 // The make_printable() function helps print a ConstBufferSequence
222 std::cout << beast::make_printable(buffer_.data()) << std::endl;
223 }
224 };
225
226 //------------------------------------------------------------------------------
227
main(int argc,char ** argv)228 int main(int argc, char** argv)
229 {
230 // Check command line arguments.
231 if(argc != 4)
232 {
233 std::cerr <<
234 "Usage: websocket-client-async-ssl <host> <port> <text>\n" <<
235 "Example:\n" <<
236 " websocket-client-async-ssl echo.websocket.org 443 \"Hello, world!\"\n";
237 return EXIT_FAILURE;
238 }
239 auto const host = argv[1];
240 auto const port = argv[2];
241 auto const text = argv[3];
242
243
244 // The SSL context is required, and holds certificates
245 ssl::context ctx{ssl::context::tlsv12_client};
246
247 // This holds the root certificate used for verification
248 load_root_certificates(ctx);
249
250 // Launch the asynchronous operation
251 std::make_shared<session>(ctx)->run(host, port, text);
252
253 // The async operations will run on the system_executor.
254 // Because the main thread has nothing to do in this example, we just wait
255 // for the system_executor to run out of work.
256 net::query(net::system_executor(), net::execution::context).join();
257
258 return EXIT_SUCCESS;
259 }
260