• 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         // 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