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