• 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: 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         // Set SNI Hostname (many hosts need this to handshake successfully)
126         if(! SSL_set_tlsext_host_name(
127             ws_.next_layer().native_handle(),
128             host_.c_str()))
129         {
130           ec = beast::error_code(static_cast<int>(::ERR_get_error()),
131                                  net::error::get_ssl_category());
132           return fail(ec, "connect");
133         }
134 
135         // Perform the SSL handshake
136         ws_.next_layer().async_handshake(
137             ssl::stream_base::client,
138             beast::bind_front_handler(
139                 &session::on_ssl_handshake,
140                 shared_from_this()));
141     }
142 
143     void
on_ssl_handshake(beast::error_code ec)144     on_ssl_handshake(beast::error_code ec)
145     {
146         if(ec)
147             return fail(ec, "ssl_handshake");
148 
149         // Turn off the timeout on the tcp_stream, because
150         // the websocket stream has its own timeout system.
151         beast::get_lowest_layer(ws_).expires_never();
152 
153         // Set suggested timeout settings for the websocket
154         ws_.set_option(
155             websocket::stream_base::timeout::suggested(
156                 beast::role_type::client));
157 
158         // Set a decorator to change the User-Agent of the handshake
159         ws_.set_option(websocket::stream_base::decorator(
160             [](websocket::request_type& req)
161             {
162                 req.set(http::field::user_agent,
163                     std::string(BOOST_BEAST_VERSION_STRING) +
164                         " websocket-client-async-ssl");
165             }));
166 
167         // Perform the websocket handshake
168         ws_.async_handshake(host_, "/",
169             beast::bind_front_handler(
170                 &session::on_handshake,
171                 shared_from_this()));
172     }
173 
174     void
on_handshake(beast::error_code ec)175     on_handshake(beast::error_code ec)
176     {
177         if(ec)
178             return fail(ec, "handshake");
179 
180         // Send the message
181         ws_.async_write(
182             net::buffer(text_),
183             beast::bind_front_handler(
184                 &session::on_write,
185                 shared_from_this()));
186     }
187 
188     void
on_write(beast::error_code ec,std::size_t bytes_transferred)189     on_write(
190         beast::error_code ec,
191         std::size_t bytes_transferred)
192     {
193         boost::ignore_unused(bytes_transferred);
194 
195         if(ec)
196             return fail(ec, "write");
197 
198         // Read a message into our buffer
199         ws_.async_read(
200             buffer_,
201             beast::bind_front_handler(
202                 &session::on_read,
203                 shared_from_this()));
204     }
205 
206     void
on_read(beast::error_code ec,std::size_t bytes_transferred)207     on_read(
208         beast::error_code ec,
209         std::size_t bytes_transferred)
210     {
211         boost::ignore_unused(bytes_transferred);
212 
213         if(ec)
214             return fail(ec, "read");
215 
216         // Close the WebSocket connection
217         ws_.async_close(websocket::close_code::normal,
218             beast::bind_front_handler(
219                 &session::on_close,
220                 shared_from_this()));
221     }
222 
223     void
on_close(beast::error_code ec)224     on_close(beast::error_code ec)
225     {
226         if(ec)
227             return fail(ec, "close");
228 
229         // If we get here then the connection is closed gracefully
230 
231         // The make_printable() function helps print a ConstBufferSequence
232         std::cout << beast::make_printable(buffer_.data()) << std::endl;
233     }
234 };
235 
236 //------------------------------------------------------------------------------
237 
main(int argc,char ** argv)238 int main(int argc, char** argv)
239 {
240     // Check command line arguments.
241     if(argc != 4)
242     {
243         std::cerr <<
244             "Usage: websocket-client-async-ssl <host> <port> <text>\n" <<
245             "Example:\n" <<
246             "    websocket-client-async-ssl echo.websocket.org 443 \"Hello, world!\"\n";
247         return EXIT_FAILURE;
248     }
249     auto const host = argv[1];
250     auto const port = argv[2];
251     auto const text = argv[3];
252 
253 
254     // The SSL context is required, and holds certificates
255     ssl::context ctx{ssl::context::tlsv12_client};
256 
257     // This holds the root certificate used for verification
258     load_root_certificates(ctx);
259 
260     // Launch the asynchronous operation
261     std::make_shared<session>(ctx)->run(host, port, text);
262 
263     // The async operations will run on the system_executor.
264     // Because the main thread has nothing to do in this example, we just wait
265     // for the system_executor to run out of work.
266     net::query(net::system_executor(), net::execution::context).join();
267 
268     return EXIT_SUCCESS;
269 }
270