How can i make Boost Beast Reply From A String Ins

2019-02-19 23:54发布

问题:

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.

回答1:

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;
}