Search code examples
c++boostboost-beast

How can i make Boost Beast Reply From A String Instead Of A File


I am trying to work with this example code boost beast advanced server example

It compiles and works nice. Now i want to make it read from a given string to reply a Get or Post request instead of reading from a file.

For example: Client sends a Get request for "www.xxxxxxxxxx.com/index.html" Program will reply the request from a string which is taken from a database, not file.

How can i do it? Thanks.


Solution

  • The sample already shows it. Look, e.g. at how error responses are generated:

    // Returns a not found response
    auto const not_found = [&req](boost::beast::string_view target) {
        http::response<http::string_body> res{ http::status::not_found, req.version() };
        res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
        res.set(http::field::content_type, "text/html");
        res.keep_alive(req.keep_alive());
        res.body() = "The resource '" + target.to_string() + "' was not found.";
        res.prepare_payload();
        return res;
    };
    

    Just set body() to something different.

    DEMO

    A full demo, basically just by stripping the sample from the unneeded code, and using prepare_payload to get content length/encoding automatically.

    #include <algorithm>
    #include <boost/asio/bind_executor.hpp>
    #include <boost/asio/ip/tcp.hpp>
    #include <boost/asio/steady_timer.hpp>
    #include <boost/asio/strand.hpp>
    #include <boost/beast/core.hpp>
    #include <boost/beast/http.hpp>
    #include <boost/beast/version.hpp>
    #include <boost/beast/websocket.hpp>
    #include <boost/config.hpp>
    #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 http = boost::beast::http;           // from <boost/beast/http.hpp>
    namespace websocket = boost::beast::websocket; // from <boost/beast/websocket.hpp>
    
    // This function produces an HTTP response for the given request.
    template <class Body, class Allocator, class Send>
    void handle_request(http::request<Body, http::basic_fields<Allocator> > &&req, Send &&send) {
        // Returns a bad request response
        auto const bad_request = [&req](boost::beast::string_view why) {
            http::response<http::string_body> res{ http::status::bad_request, req.version() };
            res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
            res.set(http::field::content_type, "text/html");
            res.keep_alive(req.keep_alive());
            res.body() = why.to_string();
            res.prepare_payload();
            return res;
        };
    
        // Make sure we can handle the method
        if (req.method() != http::verb::get)
            return send(bad_request("Unsupported HTTP-method"));
    
        // Request path must be absolute and not contain "..".
        auto target = req.target();
        if (target.empty() || target[0] != '/' || target.find("..") != boost::beast::string_view::npos)
            return send(bad_request("Illegal request-target"));
    
        http::response<http::string_body> res{ http::status::ok, req.version() };
        res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
        res.set(http::field::content_type, "text/html");
        res.body() = "You're looking at " + target.to_string();
        res.prepare_payload();
        res.keep_alive(req.keep_alive());
        return send(std::move(res));
    }
    
    // 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 websocket_session : public std::enable_shared_from_this<websocket_session> {
        websocket::stream<tcp::socket> ws_;
        boost::asio::strand<boost::asio::io_context::executor_type> strand_;
        boost::asio::steady_timer timer_;
        boost::beast::multi_buffer buffer_;
    
      public:
        // Take ownership of the socket
        explicit websocket_session(tcp::socket socket)
                : ws_(std::move(socket)), strand_(ws_.get_executor()),
                  timer_(ws_.get_executor().context(), (std::chrono::steady_clock::time_point::max)()) {}
    
        // Start the asynchronous operation
        template <class Body, class Allocator> void run(http::request<Body, http::basic_fields<Allocator> > req) {
            // Run the timer. The timer is operated
            // continuously, this simplifies the code.
            on_timer({});
    
            // Set the timer
            timer_.expires_after(std::chrono::seconds(15));
    
            // Accept the websocket handshake
            ws_.async_accept(req,
                             boost::asio::bind_executor(strand_, std::bind(&websocket_session::on_accept,
                                                                           shared_from_this(), std::placeholders::_1)));
        }
    
        // Called when the timer expires.
        void on_timer(boost::system::error_code ec) {
            if (ec && ec != boost::asio::error::operation_aborted)
                return fail(ec, "timer");
    
            // Verify that the timer really expired since the deadline may have moved.
            if (timer_.expiry() <= std::chrono::steady_clock::now()) {
                // Closing the socket cancels all outstanding operations. They
                // will complete with boost::asio::error::operation_aborted
                ws_.next_layer().shutdown(tcp::socket::shutdown_both, ec);
                ws_.next_layer().close(ec);
                return;
            }
    
            // Wait on the timer
            timer_.async_wait(boost::asio::bind_executor(
                strand_, std::bind(&websocket_session::on_timer, shared_from_this(), std::placeholders::_1)));
        }
    
        void on_accept(boost::system::error_code ec) {
            // Happens when the timer closes the socket
            if (ec == boost::asio::error::operation_aborted)
                return;
    
            if (ec)
                return fail(ec, "accept");
    
            // Read a message
            do_read();
        }
    
        void do_read() {
            // Set the timer
            timer_.expires_after(std::chrono::seconds(15));
    
            // Read a message into our buffer
            ws_.async_read(buffer_,
                           boost::asio::bind_executor(strand_, std::bind(&websocket_session::on_read, shared_from_this(),
                                                                         std::placeholders::_1, std::placeholders::_2)));
        }
    
        void on_read(boost::system::error_code ec, std::size_t bytes_transferred) {
            boost::ignore_unused(bytes_transferred);
    
            // Happens when the timer closes the socket
            if (ec == boost::asio::error::operation_aborted)
                return;
    
            // This indicates that the websocket_session was closed
            if (ec == websocket::error::closed)
                return;
    
            if (ec)
                fail(ec, "read");
    
            // Echo the message
            ws_.text(ws_.got_text());
            ws_.async_write(buffer_.data(),
                            boost::asio::bind_executor(strand_, std::bind(&websocket_session::on_write, shared_from_this(),
                                                                          std::placeholders::_1, std::placeholders::_2)));
        }
    
        void on_write(boost::system::error_code ec, std::size_t bytes_transferred) {
            boost::ignore_unused(bytes_transferred);
    
            // Happens when the timer closes the socket
            if (ec == boost::asio::error::operation_aborted)
                return;
    
            if (ec)
                return fail(ec, "write");
    
            // Clear the buffer
            buffer_.consume(buffer_.size());
    
            // Do another read
            do_read();
        }
    };
    
    // Handles an HTTP server connection
    class http_session : public std::enable_shared_from_this<http_session> {
        // This queue is used for HTTP pipelining.
        class queue {
            enum {
                // Maximum number of responses we will queue
                limit = 8
            };
    
            // The type-erased, saved work item
            struct work {
                virtual ~work() = default;
                virtual void operator()() = 0;
            };
    
            http_session &self_;
            std::vector<std::unique_ptr<work> > items_;
    
          public:
            explicit queue(http_session &self) : self_(self) {
                static_assert(limit > 0, "queue limit must be positive");
                items_.reserve(limit);
            }
    
            // Returns `true` if we have reached the queue limit
            bool is_full() const { return items_.size() >= limit; }
    
            // Called when a message finishes sending
            // Returns `true` if the caller should initiate a read
            bool on_write() {
                BOOST_ASSERT(!items_.empty());
                auto const was_full = is_full();
                items_.erase(items_.begin());
                if (!items_.empty())
                    (*items_.front())();
                return was_full;
            }
    
            // Called by the HTTP handler to send a response.
            template <bool isRequest, class Body, class Fields>
            void operator()(http::message<isRequest, Body, Fields> &&msg) {
                // This holds a work item
                struct work_impl : work {
                    http_session &self_;
                    http::message<isRequest, Body, Fields> msg_;
    
                    work_impl(http_session &self, http::message<isRequest, Body, Fields> &&msg)
                            : self_(self), msg_(std::move(msg)) {}
    
                    void operator()() {
                        http::async_write(self_.socket_, msg_,
                                          boost::asio::bind_executor(
                                              self_.strand_, std::bind(&http_session::on_write, self_.shared_from_this(),
                                                                       std::placeholders::_1, msg_.need_eof())));
                    }
                };
    
                // Allocate and store the work
                items_.emplace_back(new work_impl(self_, std::move(msg)));
    
                // If there was no previous work, start this one
                if (items_.size() == 1)
                    (*items_.front())();
            }
        };
    
        tcp::socket socket_;
        boost::asio::strand<boost::asio::io_context::executor_type> strand_;
        boost::asio::steady_timer timer_;
        boost::beast::flat_buffer buffer_;
        http::request<http::string_body> req_;
        queue queue_;
    
      public:
        // Take ownership of the socket
        explicit http_session(tcp::socket socket)
                : socket_(std::move(socket)), strand_(socket_.get_executor()),
                  timer_(socket_.get_executor().context(), (std::chrono::steady_clock::time_point::max)()), queue_(*this) {}
    
        // Start the asynchronous operation
        void run() {
            // Run the timer. The timer is operated
            // continuously, this simplifies the code.
            on_timer({});
    
            do_read();
        }
    
        void do_read() {
            // Set the timer
            timer_.expires_after(std::chrono::seconds(15));
    
            // Read a request
            http::async_read(socket_, buffer_, req_,
                             boost::asio::bind_executor(
                                 strand_, std::bind(&http_session::on_read, shared_from_this(), std::placeholders::_1)));
        }
    
        // Called when the timer expires.
        void on_timer(boost::system::error_code ec) {
            if (ec && ec != boost::asio::error::operation_aborted)
                return fail(ec, "timer");
    
            // Verify that the timer really expired since the deadline may have moved.
            if (timer_.expiry() <= std::chrono::steady_clock::now()) {
                // Closing the socket cancels all outstanding operations. They
                // will complete with boost::asio::error::operation_aborted
                socket_.shutdown(tcp::socket::shutdown_both, ec);
                socket_.close(ec);
                return;
            }
    
            // Wait on the timer
            timer_.async_wait(boost::asio::bind_executor(
                strand_, std::bind(&http_session::on_timer, shared_from_this(), std::placeholders::_1)));
        }
    
        void on_read(boost::system::error_code ec) {
            // Happens when the timer closes the socket
            if (ec == boost::asio::error::operation_aborted)
                return;
    
            // This means they closed the connection
            if (ec == http::error::end_of_stream)
                return do_close();
    
            if (ec)
                return fail(ec, "read");
    
            // See if it is a WebSocket Upgrade
            if (websocket::is_upgrade(req_)) {
                // Create a WebSocket websocket_session by transferring the socket
                std::make_shared<websocket_session>(std::move(socket_))->run(std::move(req_));
                return;
            }
    
            // Send the response
            handle_request(std::move(req_), queue_);
    
            // If we aren't at the queue limit, try to pipeline another request
            if (!queue_.is_full())
                do_read();
        }
    
        void on_write(boost::system::error_code ec, bool close) {
            // Happens when the timer closes the socket
            if (ec == boost::asio::error::operation_aborted)
                return;
    
            if (ec)
                return fail(ec, "write");
    
            if (close) {
                // This means we should close the connection, usually because
                // the response indicated the "Connection: close" semantic.
                return do_close();
            }
    
            // Inform the queue that a write completed
            if (queue_.on_write()) {
                // Read another request
                do_read();
            }
        }
    
        void do_close() {
            // Send a TCP shutdown
            boost::system::error_code ec;
            socket_.shutdown(tcp::socket::shutdown_send, ec);
    
            // At this point the connection is closed gracefully
        }
    };
    
    //------------------------------------------------------------------------------
    
    // Accepts incoming connections and launches the sessions
    class listener : public std::enable_shared_from_this<listener> {
        tcp::acceptor acceptor_;
        tcp::socket socket_;
    
      public:
        listener(boost::asio::io_context &ioc, tcp::endpoint endpoint) : acceptor_(ioc), socket_(ioc) {
            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_listen_connections, ec);
            if (ec) {
                fail(ec, "listen");
                return;
            }
        }
    
        // Start accepting incoming connections
        void run() {
            if (!acceptor_.is_open())
                return;
            do_accept();
        }
    
        void do_accept() {
            acceptor_.async_accept(socket_, std::bind(&listener::on_accept, shared_from_this(), std::placeholders::_1));
        }
    
        void on_accept(boost::system::error_code ec) {
            if (ec) {
                fail(ec, "accept");
            } else {
                // Create the http_session and run it
                std::make_shared<http_session>(std::move(socket_))->run();
            }
    
            // Accept another connection
            do_accept();
        }
    };
    
    //------------------------------------------------------------------------------
    
    int main(int argc, char *argv[]) {
        // Check command line arguments.
        if (argc != 4) {
            std::cerr << "Usage: advanced-server <address> <port> <threads>\n"
                      << "Example:\n"
                      << "    advanced-server 0.0.0.0 8080 1\n";
            return EXIT_FAILURE;
        }
        auto const address = boost::asio::ip::make_address(argv[1]);
        auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
        auto const threads = std::max<int>(1, std::atoi(argv[3]));
    
        // The io_context is required for all I/O
        boost::asio::io_context ioc{ threads };
    
        // Create and launch a listening port
        std::make_shared<listener>(ioc, 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([&ioc] { ioc.run(); });
        ioc.run();
    
        return EXIT_SUCCESS;
    }