Search code examples
c++boostserverasioboost-beast

C++ simple http server, the program closes on the second request and does not send the contents of the string


this is what my fileCollector looks like, it just stores a "cache" of files (so that you don't read the file during each request)

class FileCollector
    {
    public:
        FileCollector();
        std::string get_file_data(std::string s);
    private:
        std::map<std::string, std::string> fileData;
    };

The error is most likely here, but due to inexperience I can't find it.

http_connection::http_connection(tcp::socket socket, std::shared_ptr<Collector::FileCollector> fc)
        : socket_(std::move(socket)),
        filecollector(fc)
    {}

    void http_connection::start(void)
    {
        read_request();
    }

    void http_connection::read_request(void)
    {
        auto self = shared_from_this();

        http::async_read(
            socket_,
            buffer_,
            request_,
            [self](beast::error_code ec,
                std::size_t bytes_transferred)
            {
                boost::ignore_unused(bytes_transferred);
                if(!ec)
                    self->process_request();
            }
        );
    }

    void http_connection::process_request(void)
    {
        response_.version(request_.version());
        response_.keep_alive(false);

        switch (request_.method())
        {
            case (http::verb::get):
                response_.result(http::status::ok);
                response_.set(http::field::server, "Beast");
                create_response();
                break;
            default:
                response_.result(http::status::bad_request);
                response_.set(http::field::content_type, "text/plain");
                beast::ostream(response_.body())
                    << "Invalid request-method '"
                    << std::string(request_.method_string())
                    << "'";
                break;
        }
        write_response();
    }

    void http_connection::create_response(void)
    {
        std::string_view sv = request_.target().substr(1);
        std::string targetfile = {sv.data(), sv.size()};
        
        beast::ostream(response_.body())
            << filecollector->get_file_data(targetfile);
    }

    void http_connection::write_response(void)
    {
        auto self = shared_from_this();
        response_.content_length(response_.body().size());
        http::async_write(
            socket_,
            response_,
            [self](beast::error_code er, std::size_t bytes_trans)
            {
                self->socket_.shutdown(tcp::socket::shutdown_send, er);
            }
        );
    }

the way I start connections

void ownHTTPServer::start_acceptor(tcp::acceptor& accepter, tcp::socket& sock,
    std::shared_ptr<Collector::FileCollector> &fc)
{
    accepter.async_accept(sock,
        [&](beast::error_code er){
            if (!er)
            {
                std::make_shared<ownHTTPServer::http_connection>
                    (std::move(sock), std::move(fc))->start();
            }
            start_acceptor(accepter, sock, fc);
        });
}

g++ -IC:\boost\include\boost-1_84 -lws2_32 -lwsock32 ... compliler x86_64-w64-mingw32

the code works if you don't use filecollector and the response content is const char*

If it's not difficult for someone, I would like to read articles on how boost.asio works


Solution

  • In start_acceptor(), each time a new client is accepted, you are creating a new http_connection object and moving (not sharing) ownership of your shared_ptr<FileCollector> fc object into that connection:

    std::make_shared<ownHTTPServer::http_connection>
                        (std::move(sock), std::move(fc))->start();
                                          ^^^^^^^^^^^^^
    

    Thus, fc loses access to the FileCollector object in memory, making it unavailable when subsequent connections transfer fc again and again.

    You should not be using std::move() to transfer ownership of a std::shared_ptr, that defeats the whole point of using shared ownership.

    Your http_connection constructor takes the shared_ptr by value, as it should. So, simply do not use std::move() when passing fc to that constructor, let fc be copied so the FileCollector object is properly shared across multiple shared_ptr instances:

    std::make_shared<ownHTTPServer::http_connection>
                        (std::move(sock), fc)->start();
                                          ^^
    

    Along those same lines, you should also be capturing fc by value, not by reference (just as you do with self in http_connection::read_request()), eg:

    accepter.async_accept(sock,
        [&,fc](beast::error_code er){
           ^^
            if (!er)
            {
                std::make_shared<ownHTTPServer::http_connection>
                    (std::move(sock), fc)->start();
            }
            start_acceptor(accepter, sock, fc);
        });
    

    That way, the FileCollector object is shared properly while the async_accept() handler is waiting to be called.