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

77 views Asked by At

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

1

There are 1 answers

0
Remy Lebeau On BEST ANSWER

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.