I couldn't figure out why my http server exits right after the first request. Could anyone shed some light why this is happening? It doesn't even say "Server has stopped running.". Just exits from within.
Any help will be appreciated.
main.cpp:
#include <iostream>
#include <unordered_map>
#include <memory>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include "..\include\server2.hpp"
using namespace std;
constexpr char ADDRESS[] = "0.0.0.0";
constexpr unsigned short PORT = 8080;
constexpr char DOC_ROOT[] = "frontend";
int main(int argc, char* argv[])
{
try
{
auto const address = boost::asio::ip::make_address(ADDRESS);
unsigned short port = PORT;
Server server(DOC_ROOT, address, port);
server.run();
cout << " hello" << endl;
}
catch (std::exception const& e)
{
std::cout << "Error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
}
server2.hpp:
#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <fstream>
#include <boost/asio.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/filesystem.hpp>
#include "toggle_button.hpp"
class Server {
const boost::asio::ip::address m_address;
const unsigned short m_port;
const std::string m_docroot;
boost::asio::io_context m_ioc;
boost::asio::ip::tcp::acceptor m_acceptor;
std::unordered_set<std::shared_ptr<boost::asio::ip::tcp::socket>> sse_clients;
int i;
public:
Server(const std::string& docroot, const boost::asio::ip::address& address, const unsigned short& port) :
m_docroot(docroot),
m_address(address),
m_port(port),
m_ioc(1),
m_acceptor(m_ioc, boost::asio::ip::tcp::endpoint(address, port)),
i(1) {}
void handle_accept() {
auto new_socket = std::make_shared<boost::asio::ip::tcp::socket>(m_ioc);
// Start asynchronous accept operation
m_acceptor.async_accept(*new_socket, [new_socket, this](boost::system::error_code ec) {
if (!ec) {
// Start a new session with the accepted socket
handle_session(std::move(new_socket));
}
else {
std::cerr << "Accept error: " << ec.message() << std::endl;
}
// Keep accepting new connections
std::cout << " session << " << i++ << std::endl;
handle_accept();
});
}
void run() {
std::cout << "Server is running and accepting connections..." << std::endl;
// Start accepting connections
handle_accept();
// Now run the io_context
m_ioc.run();
std::cout << "Server has stopped running." << std::endl;
}
private:
void handle_session(std::shared_ptr<boost::asio::ip::tcp::socket> socket) {
auto buffer = std::make_shared<boost::beast::flat_buffer>(100000);
auto request = std::make_shared<boost::beast::http::request<boost::beast::http::string_body>>();
boost::beast::http::async_read(*socket, *buffer, *request,
[this, socket, buffer, request](boost::system::error_code ec, std::size_t bytes_transferred) mutable {
if (!ec) {
boost::beast::http::response<boost::beast::http::dynamic_body> response;
response.result(boost::beast::http::status::ok);
response.set(boost::beast::http::field::content_type, "text/html");
response.version(request->version());
response.keep_alive(false);
response.set(boost::beast::http::field::server, "Beast");
boost::beast::ostream(response.body())
<< "<html>\n"
<< "<head><title>Hello</title></head>\n"
<< "<body>\n"
<< "<h1>Hello world</h1>\n"
<< "<p>It's just me</p>\n"
<< "</body>\n"
<< "</html>\n";
response.prepare_payload();
boost::beast::http::async_write(
*socket,
response,
[socket](boost::beast::error_code ec, std::size_t)
{
(*socket).shutdown(boost::asio::ip::tcp::socket::shutdown_send, ec);
});
}
else {
std::cerr << "Read error: " << ec.message() << std::endl;
}
});
}
};
I tried comparing this with the sample https://www.boost.org/doc/libs/1_85_0/libs/beast/example/http/server/small/http_server_small.cpp but I couldn't figure out why mine exits. They are almost similar, except for the way I structured my server class.
I am able to receive the first response, but right after server stops and the program exits.
It crashes:
Already, we can suspect you mishandled the lifetime of an object. To find which, let's use ASAN/UBSAN with -fsanitize=address,undefined
, e.g.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined")
Now we get a nice error novel:
Which, simplified says:
==3166915==ERROR: AddressSanitizer: heap-use-after-free on address 0x508000000050 at pc 0x00000061a052 bp 0x7ffea9d4c6a0 sp 0x7ffea9d4c698
READ of size 2 at 0x508000000050 thread T0
#0 0x61a051 in http::basic_fields<std::allocator<char> >::value_type::buffer() const include/boost/beast/http/impl/fields.hpp:287
...
#16 0x628db3 in http::serializer<false, http::basic_dynamic_body<beast::basic_multi_buffer<std::allocator<char> > >, ... >::consume(unsigned long) include/boost/beast/http/impl/serializer.hpp:295
#17 0x623cc8 in http::detail::write_some_op<http::detail::write_op<http::detail::write_msg_op<Server::handle_session(...)::{lambda(...)#1}::operator()(...)::{lambda(...)#1}, asio::basic_stream_socket<asio::ip::tcp, asio::any_io_executor>, false, http::basic_dynamic_body<beast::basic_multi_buffer<std::allocator<char> > >, http::basic_fields<std::allocator<char> > >, asio::basic_stream_socket<asio::ip::tcp, asio::any_io_executor>, http::detail::serializer_is_done, false, http::basic_dynamic_body<beast::basic_multi_buffer<std::allocator<char> > >, http::basic_fields<std::allocator<char> > >, asio::basic_stream_socket<asio::ip::tcp, asio::any_io_executor>, false, http::basic_dynamic_body<beast::basic_multi_buffer<std::allocator<char> > >, http::basic_fields<std::allocator<char> > >::operator()(boost::system::error_code, unsigned long) include/boost/beast/http/impl/write.hpp:135
0x508000000050 is located 48 bytes inside of 88-byte region [0x508000000020,0x508000000078)
freed by thread T0 here:
#0 0x7d137a2fe1f8 in operator delete(void*, unsigned long) (/nix/store/vf9kvaqln7nvqf41pphd5arvx95irgwv-gcc-14.2.0-lib/lib/libasan.so.8+0xfe1f8)
...
#9 0x474095 in Server::handle_session(...)::{lambda(...)#1}::operator()(...) /home/sehe/Projects/stackoverflow/server2.hpp:93
#11 0x5185f8 in http::detail::read_msg_op<...>::operator()(...) include/boost/beast/http/impl/read.hpp:114
previously allocated by thread T0 here:
#0 0x7d137a2fd2f8 in operator new(unsigned long) (/nix/store/vf9kvaqln7nvqf41pphd5arvx95irgwv-gcc-14.2.0-lib/lib/libasan.so.8+0xfd2f8)
#6 0x473c4d in Server::handle_session(...)::{lambda(...)#1}::operator()(...) /home/sehe/Projects/stackoverflow/server2.hpp:75
#8 0x5185f8 in http::detail::read_msg_op<asio::basic_stream_socket<asio::ip::tcp, asio::any_io_executor>, beast::basic_flat_buffer<std::allocator<char> >, true, http::basic_string_body<char, std::char_traits<char>, std::allocator<char> >, std::allocator<char>, Server::handle_session(std::shared_ptr<asio::basic_stream_socket<asio::ip::tcp, asio::any_io_executor> >, int)::{lambda(boost::system::error_code, unsigned long)#1}>::operator()(boost::system::error_code, unsigned long) include/boost/beast/http/impl/read.hpp:114
In English: the write operation is using buffers that have been destroyed. They're actually destroyed in handle_session
, line 93 which is when the stack object response
gets destructed.
The allocation spot is the same (but line 75).
The "obvious" fix is to make reponse
dynamically allocated as well and capture it in the completion handler:
void handle_session(std::shared_ptr<tcp::socket> socket, int session_id) {
namespace http = boost::beast::http;
using boost::beast::error_code;
std::cout << " session << " << session_id << std::endl;
auto buffer = std::make_shared<boost::beast::flat_buffer>(100000);
auto request = std::make_shared<http::request<http::string_body>>();
auto response = std::make_shared<http::response<http::dynamic_body>>();
http::async_read( //
*socket, *buffer, *request,
[/*this,*/ socket, buffer, request, response](error_code ec, size_t) mutable {
if (!ec) {
response->result(http::status::ok);
response->set(http::field::content_type, "text/html");
response->version(request->version());
response->keep_alive(false);
response->set(http::field::server, "Beast");
boost::beast::ostream(response->body()) << R"(<html>
<head><title>Hello</title></head>
<body>
<h1>Hello world</h1>
<p>It's just me</p>
</body>
</html>
)";
response->prepare_payload();
http::async_write(*socket, *response, [socket, response](error_code ec, size_t) {
socket->shutdown(tcp::socket::shutdown_send, ec);
});
} else {
std::cerr << "Read error: " << ec.message() << std::endl;
}
});
}
However please consider grouping the session related stuff together, instead of dynamically allocating and capturing everything separately. You'll get something like this:
File server2.hpp
#pragma once
#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <iostream>
class Server {
using error_code = boost::beast::error_code;
using tcp = boost::asio::ip::tcp;
boost::asio::ip::address const m_address;
[[maybe_unused]] uint16_t const m_port;
std::string const m_docroot;
boost::asio::io_context m_ioc{1};
boost::asio::ip::tcp::acceptor m_acceptor{m_ioc, {m_address, m_port}};
int i = 1;
public:
Server(std::string const& docroot, boost::asio::ip::address const& address, uint16_t port)
: m_address(address)
, m_port(port)
, m_docroot(docroot) {}
void handle_accept() {
// Start asynchronous accept operation
m_acceptor.async_accept(make_strand(m_acceptor.get_executor()), [this](error_code ec, tcp::socket s) {
if (!ec)
std::make_shared<Session>(std::move(s), i++)->start();
else
std::cerr << "Accept error: " << ec.message() << std::endl;
// Keep accepting new connections
handle_accept();
});
}
void run() {
std::cout << "Server is running and accepting connections..." << std::endl;
// Start accepting connections
handle_accept();
// Now run the io_context
m_ioc.run();
std::cout << "Server has stopped running." << std::endl;
}
private:
struct Session : std::enable_shared_from_this<Session> {
Session(tcp::socket s, int id) : socket(std::move(s)), id_(id) {}
void start() {
std::cout << " session id " << id_ << std::endl;
do_read();
}
private:
void do_read() {
namespace http = boost::beast::http;
http::async_read( //
socket, buffer, request, [this, self = shared_from_this()](error_code ec, size_t) mutable {
if (!ec)
do_response();
else
std::cerr << "Read error: " << ec.message() << std::endl;
});
}
void do_response() {
namespace http = boost::beast::http;
response = {};
response.result(http::status::ok);
response.set(http::field::content_type, "text/html");
response.version(request.version());
response.keep_alive(false);
response.set(http::field::server, "Beast");
boost::beast::ostream(response.body()) << R"(<html>
<head><title>Hello</title></head>
<body>
<h1>Hello world</h1>
<p>It's just me</p>
</body>
</html>
)";
response.prepare_payload();
http::async_write(socket, response, [this, self = shared_from_this()](error_code ec, size_t) {
socket.shutdown(tcp::socket::shutdown_send, ec);
});
}
tcp::socket socket;
int id_;
boost::beast::flat_buffer buffer{100000};
boost::beast::http::request<boost::beast::http::string_body> request;
boost::beast::http::response<boost::beast::http::dynamic_body> response;
};
};
File test.cpp
#include <iostream>
#include "server2.hpp"
constexpr char ADDRESS[] = "0.0.0.0";
constexpr uint16_t PORT = 8989;
constexpr char DOC_ROOT[] = "frontend";
int main() try {
Server server(DOC_ROOT, boost::asio::ip::make_address(ADDRESS), PORT);
server.run();
std::cout << " hello" << std::endl;
} catch (std::exception const& e) {
std::cout << "Error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
Which still works the same, but with much less bother.