forked from boostorg/beast
		
	fix #575, fix #604, fix #608, fix #634, fix #712 All examples are rewritten: * Using Best Practices * Mostly self-contained * New examples to complete the feature matrix * The server-framework example is removed
		
			
				
	
	
		
			264 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			264 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| //
 | |
| // 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()
 | |
|     {
 | |
|         loop();
 | |
|     }
 | |
| 
 | |
| #include <boost/asio/yield.hpp>
 | |
|     void
 | |
|     loop(boost::system::error_code ec = {})
 | |
|     {
 | |
|         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(),
 | |
|                     std::placeholders::_1)));
 | |
|             if(ec)
 | |
|                 return fail(ec, "handshake");
 | |
| 
 | |
|             // Accept the websocket handshake
 | |
|             yield ws_.async_accept(
 | |
|                 strand_.wrap(std::bind(
 | |
|                     &session::loop,
 | |
|                     shared_from_this(),
 | |
|                     std::placeholders::_1)));
 | |
|             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(),
 | |
|                         std::placeholders::_1)));
 | |
|                 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(),
 | |
|                         std::placeholders::_1)));
 | |
|                 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_;
 | |
|     boost::asio::io_service::strand strand_;
 | |
|     tcp::acceptor acceptor_;
 | |
|     tcp::socket socket_;
 | |
| 
 | |
| public:
 | |
|     listener(
 | |
|         boost::asio::io_service& ios,
 | |
|         ssl::context& ctx,
 | |
|         tcp::endpoint endpoint)
 | |
|         : ctx_(ctx)
 | |
|         , strand_(ios)
 | |
|         , 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;
 | |
| }
 |