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