| 
									
										
										
										
											2017-08-04 19:55:28 -07:00
										 |  |  | //
 | 
					
						
							|  |  |  | // Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // Distributed under the Boost Software License, Version 1.0. (See accompanying
 | 
					
						
							|  |  |  | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // Official repository: https://github.com/boostorg/beast
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //------------------------------------------------------------------------------
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // Example: WebSocket SSL server, stackless coroutine
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | //------------------------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "example/common/server_certificate.hpp"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <boost/beast/core.hpp>
 | 
					
						
							|  |  |  | #include <boost/beast/websocket.hpp>
 | 
					
						
							|  |  |  | #include <boost/beast/websocket/ssl.hpp>
 | 
					
						
							|  |  |  | #include <boost/asio/coroutine.hpp>
 | 
					
						
							|  |  |  | #include <boost/asio/ip/tcp.hpp>
 | 
					
						
							|  |  |  | #include <boost/asio/ssl/stream.hpp>
 | 
					
						
							|  |  |  | #include <boost/asio/strand.hpp>
 | 
					
						
							|  |  |  | #include <algorithm>
 | 
					
						
							|  |  |  | #include <cstdlib>
 | 
					
						
							|  |  |  | #include <functional>
 | 
					
						
							|  |  |  | #include <iostream>
 | 
					
						
							|  |  |  | #include <memory>
 | 
					
						
							|  |  |  | #include <string>
 | 
					
						
							|  |  |  | #include <thread>
 | 
					
						
							|  |  |  | #include <vector>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | using tcp = boost::asio::ip::tcp;               // from <boost/asio/ip/tcp.hpp>
 | 
					
						
							|  |  |  | namespace ssl = boost::asio::ssl;               // from <boost/asio/ssl.hpp>
 | 
					
						
							|  |  |  | namespace websocket = boost::beast::websocket;  // from <boost/beast/websocket.hpp>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //------------------------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Report a failure
 | 
					
						
							|  |  |  | void | 
					
						
							|  |  |  | fail(boost::system::error_code ec, char const* what) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     std::cerr << what << ": " << ec.message() << "\n"; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Echoes back all received WebSocket messages
 | 
					
						
							|  |  |  | class session | 
					
						
							|  |  |  |     : public boost::asio::coroutine | 
					
						
							|  |  |  |     , public std::enable_shared_from_this<session> | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     tcp::socket socket_; | 
					
						
							|  |  |  |     websocket::stream<ssl::stream<tcp::socket&>> ws_; | 
					
						
							|  |  |  |     boost::asio::io_service::strand strand_; | 
					
						
							|  |  |  |     boost::beast::multi_buffer buffer_; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | public: | 
					
						
							|  |  |  |     // Take ownership of the socket
 | 
					
						
							|  |  |  |     session(tcp::socket socket, ssl::context& ctx) | 
					
						
							|  |  |  |         : socket_(std::move(socket)) | 
					
						
							|  |  |  |         , ws_(socket_, ctx) | 
					
						
							|  |  |  |         , strand_(ws_.get_io_service()) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Start the asynchronous operation
 | 
					
						
							|  |  |  |     void | 
					
						
							|  |  |  |     run() | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2017-09-03 06:18:07 -07:00
										 |  |  |         loop({}, 0); | 
					
						
							| 
									
										
										
										
											2017-08-04 19:55:28 -07:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <boost/asio/yield.hpp>
 | 
					
						
							|  |  |  |     void | 
					
						
							| 
									
										
										
										
											2017-09-03 06:18:07 -07:00
										 |  |  |     loop( | 
					
						
							|  |  |  |         boost::system::error_code ec, | 
					
						
							|  |  |  |         std::size_t bytes_transferred) | 
					
						
							| 
									
										
										
										
											2017-08-04 19:55:28 -07:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2017-09-03 06:18:07 -07:00
										 |  |  |         boost::ignore_unused(bytes_transferred); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-04 19:55:28 -07:00
										 |  |  |         reenter(*this) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             // Perform the SSL handshake
 | 
					
						
							|  |  |  |             yield ws_.next_layer().async_handshake( | 
					
						
							|  |  |  |                 ssl::stream_base::server, | 
					
						
							|  |  |  |                 strand_.wrap(std::bind( | 
					
						
							|  |  |  |                     &session::loop, | 
					
						
							|  |  |  |                     shared_from_this(), | 
					
						
							| 
									
										
										
										
											2017-09-03 06:18:07 -07:00
										 |  |  |                     std::placeholders::_1, | 
					
						
							|  |  |  |                     0))); | 
					
						
							| 
									
										
										
										
											2017-08-04 19:55:28 -07:00
										 |  |  |             if(ec) | 
					
						
							|  |  |  |                 return fail(ec, "handshake"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Accept the websocket handshake
 | 
					
						
							|  |  |  |             yield ws_.async_accept( | 
					
						
							|  |  |  |                 strand_.wrap(std::bind( | 
					
						
							|  |  |  |                     &session::loop, | 
					
						
							|  |  |  |                     shared_from_this(), | 
					
						
							| 
									
										
										
										
											2017-09-03 06:18:07 -07:00
										 |  |  |                     std::placeholders::_1, | 
					
						
							|  |  |  |                     0))); | 
					
						
							| 
									
										
										
										
											2017-08-04 19:55:28 -07:00
										 |  |  |             if(ec) | 
					
						
							|  |  |  |                 return fail(ec, "accept"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             for(;;) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 // Read a message into our buffer
 | 
					
						
							|  |  |  |                 yield ws_.async_read( | 
					
						
							|  |  |  |                     buffer_, | 
					
						
							|  |  |  |                     strand_.wrap(std::bind( | 
					
						
							|  |  |  |                         &session::loop, | 
					
						
							|  |  |  |                         shared_from_this(), | 
					
						
							| 
									
										
										
										
											2017-09-03 06:18:07 -07:00
										 |  |  |                         std::placeholders::_1, | 
					
						
							|  |  |  |                         std::placeholders::_2))); | 
					
						
							| 
									
										
										
										
											2017-08-04 19:55:28 -07:00
										 |  |  |                 if(ec == websocket::error::closed) | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     // This indicates that the session was closed
 | 
					
						
							|  |  |  |                     return; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 if(ec) | 
					
						
							|  |  |  |                     fail(ec, "read"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // Echo the message
 | 
					
						
							|  |  |  |                 ws_.text(ws_.got_text()); | 
					
						
							|  |  |  |                 yield ws_.async_write( | 
					
						
							|  |  |  |                     buffer_.data(), | 
					
						
							|  |  |  |                     strand_.wrap(std::bind( | 
					
						
							|  |  |  |                         &session::loop, | 
					
						
							|  |  |  |                         shared_from_this(), | 
					
						
							| 
									
										
										
										
											2017-09-03 06:18:07 -07:00
										 |  |  |                         std::placeholders::_1, | 
					
						
							|  |  |  |                         std::placeholders::_2))); | 
					
						
							| 
									
										
										
										
											2017-08-04 19:55:28 -07:00
										 |  |  |                 if(ec) | 
					
						
							|  |  |  |                     return fail(ec, "write"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // Clear the buffer
 | 
					
						
							|  |  |  |                 buffer_.consume(buffer_.size()); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | #include <boost/asio/unyield.hpp>
 | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //------------------------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Accepts incoming connections and launches the sessions
 | 
					
						
							|  |  |  | class listener | 
					
						
							|  |  |  |     : public boost::asio::coroutine | 
					
						
							|  |  |  |     , public std::enable_shared_from_this<listener> | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     ssl::context& ctx_; | 
					
						
							|  |  |  |     tcp::acceptor acceptor_; | 
					
						
							|  |  |  |     tcp::socket socket_; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | public: | 
					
						
							|  |  |  |     listener( | 
					
						
							|  |  |  |         boost::asio::io_service& ios, | 
					
						
							|  |  |  |         ssl::context& ctx, | 
					
						
							|  |  |  |         tcp::endpoint endpoint) | 
					
						
							|  |  |  |         : ctx_(ctx) | 
					
						
							|  |  |  |         , acceptor_(ios) | 
					
						
							|  |  |  |         , socket_(ios) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         boost::system::error_code ec; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Open the acceptor
 | 
					
						
							|  |  |  |         acceptor_.open(endpoint.protocol(), ec); | 
					
						
							|  |  |  |         if(ec) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             fail(ec, "open"); | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Bind to the server address
 | 
					
						
							|  |  |  |         acceptor_.bind(endpoint, ec); | 
					
						
							|  |  |  |         if(ec) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             fail(ec, "bind"); | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Start listening for connections
 | 
					
						
							|  |  |  |         acceptor_.listen( | 
					
						
							|  |  |  |             boost::asio::socket_base::max_connections, ec); | 
					
						
							|  |  |  |         if(ec) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             fail(ec, "listen"); | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Start accepting incoming connections
 | 
					
						
							|  |  |  |     void | 
					
						
							|  |  |  |     run() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         if(! acceptor_.is_open()) | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         loop(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <boost/asio/yield.hpp>
 | 
					
						
							|  |  |  |     void | 
					
						
							|  |  |  |     loop(boost::system::error_code ec = {}) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         reenter(*this) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             for(;;) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 yield acceptor_.async_accept( | 
					
						
							|  |  |  |                     socket_, | 
					
						
							|  |  |  |                     std::bind( | 
					
						
							|  |  |  |                         &listener::loop, | 
					
						
							|  |  |  |                         shared_from_this(), | 
					
						
							|  |  |  |                         std::placeholders::_1)); | 
					
						
							|  |  |  |                 if(ec) | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     fail(ec, "accept"); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 else | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     // Create the session and run it
 | 
					
						
							|  |  |  |                     std::make_shared<session>(std::move(socket_), ctx_)->run(); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | #include <boost/asio/unyield.hpp>
 | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //------------------------------------------------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int main(int argc, char* argv[]) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     // Check command line arguments.
 | 
					
						
							|  |  |  |     if (argc != 4) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         std::cerr << | 
					
						
							|  |  |  |             "Usage: websocket-server-async-ssl <address> <port> <threads>\n" << | 
					
						
							|  |  |  |             "Example:\n" << | 
					
						
							|  |  |  |             "    websocket-server-async-ssl 0.0.0.0 8080 1\n"; | 
					
						
							|  |  |  |         return EXIT_FAILURE; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     auto const address = boost::asio::ip::address::from_string(argv[1]); | 
					
						
							|  |  |  |     auto const port = static_cast<unsigned short>(std::atoi(argv[2])); | 
					
						
							|  |  |  |     auto const threads = std::max<std::size_t>(1, std::atoi(argv[3])); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // The io_service is required for all I/O
 | 
					
						
							|  |  |  |     boost::asio::io_service ios{threads}; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // The SSL context is required, and holds certificates
 | 
					
						
							|  |  |  |     ssl::context ctx{ssl::context::sslv23}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // This holds the self-signed certificate used by the server
 | 
					
						
							|  |  |  |     load_server_certificate(ctx); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Create and launch a listening port
 | 
					
						
							|  |  |  |     std::make_shared<listener>(ios, ctx, tcp::endpoint{address, port})->run(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Run the I/O service on the requested number of threads
 | 
					
						
							|  |  |  |     std::vector<std::thread> v; | 
					
						
							|  |  |  |     v.reserve(threads - 1); | 
					
						
							|  |  |  |     for(auto i = threads - 1; i > 0; --i) | 
					
						
							|  |  |  |         v.emplace_back( | 
					
						
							|  |  |  |         [&ios] | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             ios.run(); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     ios.run(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return EXIT_SUCCESS; | 
					
						
							|  |  |  | } |