]>
Commit | Line | Data |
---|---|---|
b32b8144 | 1 | // |
92f5a8d4 | 2 | // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) |
b32b8144 FG |
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, coroutine | |
13 | // | |
14 | //------------------------------------------------------------------------------ | |
15 | ||
16 | #include "example/common/root_certificates.hpp" | |
17 | ||
18 | #include <boost/beast/core.hpp> | |
92f5a8d4 | 19 | #include <boost/beast/ssl.hpp> |
b32b8144 FG |
20 | #include <boost/beast/websocket.hpp> |
21 | #include <boost/beast/websocket/ssl.hpp> | |
b32b8144 | 22 | #include <boost/asio/spawn.hpp> |
b32b8144 FG |
23 | #include <cstdlib> |
24 | #include <functional> | |
25 | #include <iostream> | |
26 | #include <string> | |
27 | ||
92f5a8d4 TL |
28 | namespace beast = boost::beast; // from <boost/beast.hpp> |
29 | namespace http = beast::http; // from <boost/beast/http.hpp> | |
30 | namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp> | |
31 | namespace net = boost::asio; // from <boost/asio.hpp> | |
32 | namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp> | |
33 | using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp> | |
b32b8144 FG |
34 | |
35 | //------------------------------------------------------------------------------ | |
36 | ||
37 | // Report a failure | |
38 | void | |
92f5a8d4 | 39 | fail(beast::error_code ec, char const* what) |
b32b8144 FG |
40 | { |
41 | std::cerr << what << ": " << ec.message() << "\n"; | |
42 | } | |
43 | ||
44 | // Sends a WebSocket message and prints the response | |
45 | void | |
46 | do_session( | |
f67539c2 | 47 | std::string host, |
b32b8144 FG |
48 | std::string const& port, |
49 | std::string const& text, | |
92f5a8d4 | 50 | net::io_context& ioc, |
b32b8144 | 51 | ssl::context& ctx, |
92f5a8d4 | 52 | net::yield_context yield) |
b32b8144 | 53 | { |
92f5a8d4 | 54 | beast::error_code ec; |
b32b8144 FG |
55 | |
56 | // These objects perform our I/O | |
92f5a8d4 TL |
57 | tcp::resolver resolver(ioc); |
58 | websocket::stream< | |
59 | beast::ssl_stream<beast::tcp_stream>> ws(ioc, ctx); | |
b32b8144 FG |
60 | |
61 | // Look up the domain name | |
62 | auto const results = resolver.async_resolve(host, port, yield[ec]); | |
63 | if(ec) | |
64 | return fail(ec, "resolve"); | |
65 | ||
92f5a8d4 TL |
66 | // Set a timeout on the operation |
67 | beast::get_lowest_layer(ws).expires_after(std::chrono::seconds(30)); | |
68 | ||
b32b8144 | 69 | // Make the connection on the IP address we get from a lookup |
f67539c2 | 70 | auto ep = beast::get_lowest_layer(ws).async_connect(results, yield[ec]); |
b32b8144 FG |
71 | if(ec) |
72 | return fail(ec, "connect"); | |
73 | ||
20effc67 TL |
74 | // Set SNI Hostname (many hosts need this to handshake successfully) |
75 | if(! SSL_set_tlsext_host_name( | |
76 | ws.next_layer().native_handle(), | |
77 | host.c_str())) | |
78 | { | |
79 | ec = beast::error_code(static_cast<int>(::ERR_get_error()), | |
80 | net::error::get_ssl_category()); | |
81 | return fail(ec, "connect"); | |
82 | } | |
83 | ||
84 | // Update the host string. This will provide the value of the | |
f67539c2 TL |
85 | // Host HTTP header during the WebSocket handshake. |
86 | // See https://tools.ietf.org/html/rfc7230#section-5.4 | |
87 | host += ':' + std::to_string(ep.port()); | |
88 | ||
92f5a8d4 TL |
89 | // Set a timeout on the operation |
90 | beast::get_lowest_layer(ws).expires_after(std::chrono::seconds(30)); | |
91 | ||
92 | // Set a decorator to change the User-Agent of the handshake | |
93 | ws.set_option(websocket::stream_base::decorator( | |
94 | [](websocket::request_type& req) | |
95 | { | |
96 | req.set(http::field::user_agent, | |
97 | std::string(BOOST_BEAST_VERSION_STRING) + | |
98 | " websocket-client-coro"); | |
99 | })); | |
100 | ||
b32b8144 FG |
101 | // Perform the SSL handshake |
102 | ws.next_layer().async_handshake(ssl::stream_base::client, yield[ec]); | |
103 | if(ec) | |
104 | return fail(ec, "ssl_handshake"); | |
105 | ||
92f5a8d4 TL |
106 | // Turn off the timeout on the tcp_stream, because |
107 | // the websocket stream has its own timeout system. | |
108 | beast::get_lowest_layer(ws).expires_never(); | |
109 | ||
110 | // Set suggested timeout settings for the websocket | |
111 | ws.set_option( | |
112 | websocket::stream_base::timeout::suggested( | |
113 | beast::role_type::client)); | |
114 | ||
b32b8144 FG |
115 | // Perform the websocket handshake |
116 | ws.async_handshake(host, "/", yield[ec]); | |
117 | if(ec) | |
118 | return fail(ec, "handshake"); | |
119 | ||
120 | // Send the message | |
92f5a8d4 | 121 | ws.async_write(net::buffer(std::string(text)), yield[ec]); |
b32b8144 FG |
122 | if(ec) |
123 | return fail(ec, "write"); | |
124 | ||
125 | // This buffer will hold the incoming message | |
92f5a8d4 | 126 | beast::flat_buffer buffer; |
b32b8144 FG |
127 | |
128 | // Read a message into our buffer | |
92f5a8d4 | 129 | ws.async_read(buffer, yield[ec]); |
b32b8144 FG |
130 | if(ec) |
131 | return fail(ec, "read"); | |
132 | ||
133 | // Close the WebSocket connection | |
134 | ws.async_close(websocket::close_code::normal, yield[ec]); | |
135 | if(ec) | |
136 | return fail(ec, "close"); | |
137 | ||
138 | // If we get here then the connection is closed gracefully | |
139 | ||
92f5a8d4 TL |
140 | // The make_printable() function helps print a ConstBufferSequence |
141 | std::cout << beast::make_printable(buffer.data()) << std::endl; | |
b32b8144 FG |
142 | } |
143 | ||
144 | //------------------------------------------------------------------------------ | |
145 | ||
146 | int main(int argc, char** argv) | |
147 | { | |
148 | // Check command line arguments. | |
149 | if(argc != 4) | |
150 | { | |
151 | std::cerr << | |
152 | "Usage: websocket-client-coro-ssl <host> <port> <text>\n" << | |
153 | "Example:\n" << | |
154 | " websocket-client-coro-ssl echo.websocket.org 443 \"Hello, world!\"\n"; | |
155 | return EXIT_FAILURE; | |
156 | } | |
157 | auto const host = argv[1]; | |
158 | auto const port = argv[2]; | |
159 | auto const text = argv[3]; | |
160 | ||
161 | // The io_context is required for all I/O | |
92f5a8d4 | 162 | net::io_context ioc; |
b32b8144 FG |
163 | |
164 | // The SSL context is required, and holds certificates | |
92f5a8d4 | 165 | ssl::context ctx{ssl::context::tlsv12_client}; |
b32b8144 FG |
166 | |
167 | // This holds the root certificate used for verification | |
168 | load_root_certificates(ctx); | |
169 | ||
170 | // Launch the asynchronous operation | |
171 | boost::asio::spawn(ioc, std::bind( | |
172 | &do_session, | |
173 | std::string(host), | |
174 | std::string(port), | |
175 | std::string(text), | |
176 | std::ref(ioc), | |
177 | std::ref(ctx), | |
178 | std::placeholders::_1)); | |
179 | ||
180 | // Run the I/O service. The call will return when | |
11fdf7f2 | 181 | // the socket is closed. |
b32b8144 FG |
182 | ioc.run(); |
183 | ||
184 | return EXIT_SUCCESS; | |
185 | } |