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