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