dimanche 28 novembre 2021

Why my cpp-netlib server stuck at std::condition_variable::wait when process POST request?

I'm writing an async HTTP server and client using cpp-netlib based on the given "Hello World" example. The server can process GET request, but can't get the request body of POST request. Then I saw the Issue-823 of the cpp-netlib and add an async connection handler. The struct as follows(Changed from the "file upload" example):

///
/// Custom exception type
///
struct file_uploader_exception : public std::runtime_error {
    file_uploader_exception(const std::string err) :
        std::runtime_error(err) {
    }
};

///
/// Encapsulates request & connection
///
struct file_uploader : std::enable_shared_from_this<file_uploader> {
    const server::request& req;
    server::connection_ptr  conn;

    std::mutex              mtx;
    std::condition_variable condvar;

    //FILE* fp = NULL;
    std::string body;

public:
    file_uploader(const server::request& req, const server::connection_ptr& conn)
        : req(req)
        , conn(conn) {
        /*const std::string dest = destination(req);

        if (dest.find("/upload") != std::string::npos) {
            auto queries = get_queries(dest);
            auto fname = queries.find("filename");
            if (fname != queries.end()) {
                fp = ::fopen(fname->second.c_str(), "wb");
                if (!fp) {
                    throw file_uploader_exception("Failed to open file to write");
                }
            }
            else {
                throw file_uploader_exception("'filename' cannot be empty");
            }
        }*/
    }

    ~file_uploader() {
        /*if (fp) {
            ::fflush(fp);
            ::fclose(fp);
        }*/
    }

    ///
    /// Non blocking call to initiate the data transfer
    ///
    void async_recv() {
        std::cout << "async_recv()" << std::endl;
        std::size_t content_length = 0;
        auto const& headers = req.headers;
        for (auto item : headers) {
            if (boost::to_lower_copy(item.name) == "content-length") {
                content_length = std::stoll(item.value);
                break;
            }
        }

        read_chunk(conn, content_length);
    }

    ///
    /// The client shall wait by calling this until the transfer is done by
    /// the IO threadpool
    ///
    void wait_for_completion() {
        std::cout << "wait_for_completion()" << std::endl;
        std::unique_lock<std::mutex> _(mtx);
        condvar.wait(_);
    }

private:
    ///
    /// Parses the string and gets the query as a key-value pair
    ///
    /// @param [in] dest String containing the path and the queries, without the fragment,
    ///                  of the form "/path?key1=value1&key2=value2"
    ///
    std::map<std::string, std::string> get_queries(const std::string dest) {
        std::cout << "get_queries()" << std::endl;

        std::size_t pos = dest.find_first_of("?");

        std::map<std::string, std::string> queries;
        if (pos != std::string::npos) {
            std::string query_string = dest.substr(pos + 1);

            // Replace '&' with space
            for (pos = 0; pos < query_string.size(); pos++) {
                if (query_string[pos] == '&') {
                    query_string[pos] = ' ';
                }
            }

            std::istringstream sin(query_string);
            while (sin >> query_string) {

                pos = query_string.find_first_of("=");

                if (pos != std::string::npos) {
                    const std::string key = query_string.substr(0, pos);
                    const std::string value = query_string.substr(pos + 1);
                    queries[key] = value;
                }
            }
        }

        return queries;
    }

    ///
    /// Reads a chunk of data
    ///
    /// @param [in] conn        Connection to read from
    /// @param [in] left2read   Size to read
    ///
    void read_chunk(server::connection_ptr conn, std::size_t left2read) {
        std::cout << "read_chunk()" << std::endl;
        conn->read(boost::bind(&file_uploader::on_data_ready,
            file_uploader::shared_from_this(),
            _1, _2, _3, conn, left2read));
    }

    ///
    /// Callback that gets called when the data is ready to be consumed
    ///
    void on_data_ready(server::connection::input_range range,
        boost::system::error_code error,
        std::size_t size,
        server::connection_ptr conn,
        std::size_t left2read) {
        std::cout << "on_data_ready()" << std::endl;
        if (!error) {
            //::fwrite(boost::begin(range), size, 1, fp);
            body.append(boost::begin(range), boost::begin(range) + size);
            std::size_t left = left2read - size;
            if (left > 0)
                read_chunk(conn, left);
            else
                wakeup();
        }
    }

    ///
    /// Wakesup the waiting thread
    ///
    void wakeup() {
        std::cout << "wakeup()" << std::endl;
        std::unique_lock<std::mutex> _(mtx);
        condvar.notify_one();
    }
};

When I use my client or curl to perform a POST request, the server outputs as follows:

PS F:\CBPLibary\x64\Release> .\ServerTest.exe 0.0.0.0 40000
async_recv()
read_chunk()
wait_for_completion()

Then it stucks.The server does not respose to any other request, and the client stucks too. I want to know why this happened and how to solve it. My server code:

struct hello_world {
    void operator()(server::request const& request, server::connection_ptr connection) {
        std::shared_ptr<file_uploader> uploader(new file_uploader(request, connection));
        uploader->async_recv();
        uploader->wait_for_completion();
        std::cout << uploader->body << std::endl;
        server::string_type ip = source(request);
        server::response_header headers[] = { {"Connection","close"} ,{"Content-Type", "text/plain"} };
        unsigned int port = request.source_port;
        std::ostringstream data;
        data << "Hello, " << ip << '[' << port << "]!";
        connection->set_headers(boost::make_iterator_range(headers, headers + 2));
        connection->set_status(server::connection::ok);
        connection->write(data.str());
    }
};
int main(int argc, char* argv[]) {

    if (argc != 3) {
        std::cerr << "Usage: " << argv[0] << " address port" << std::endl;
        return 1;
    }

    try {
        hello_world handler;
        server::options options(handler);
        options.thread_pool(std::make_shared<boost::network::utils::thread_pool>());
        server server_(options.address(argv[1]).port(argv[2]));
        std::thread t_server([&server_] { server_.run(); });
        //server_.run();
        t_server.detach();
        char ch;
        do
        {
            ch = getchar();
        } while (ch != '0');
        server_.stop();
    }
    catch (std::exception& e) {
        std::cerr << e.what() << std::endl;
        return 1;
    }

    return 0;
}

My client code:

namespace http = boost::network::http;
using header = std::pair<std::string, std::string>;
int main(int argc, char* argv[]) {
    if (argc != 3) {
        std::cerr << "Usage: " << argv[0] << " address port" << std::endl;
        return 1;
    }

    try {
        std::string post_body = "test";
        http::client client;
        std::ostringstream url;
        url << "http://" << argv[1] << ":" << argv[2] << "/command";
        http::client::request request(url.str());
        request.add_header(header("Connection", "keep-alive"));
        request.add_header(header("Content-Type", "text/plain"));
        //http::client::response response = client.get(request);
        http::client::response response = client.post(request, post_body);
        std::cout << body(response) << std::endl;
    }
    catch (std::exception& e) {
        std::cerr << e.what() << std::endl;
        return 1;
    }
    return 0;
}

The curl command I use:

curl.exe -d "test" -X POST http://127.0.0.1:40000/command

My develop environment:

OS: Windows 11 build 22504
boost library version: 1.77.0
cpp-netlib version: 0.13.0
IDE: Visual Studio 2022 (MSVC17,Build tool v143)

Aucun commentaire:

Enregistrer un commentaire