ksl
ksl

Reputation: 4709

POST request body is empty (cpp-netlib 0.13.0)

I have upgraded cpp-netlib from v0.11.0 to 0.13.0 and run into some difficulties.

Previously, when a request was sent to the server, the body of the request could be read from the request object.

The request body is now empty when I send the same request to a server using v0.13.0.

The rest of the request object appears to be correct - only the body is empty.

Is there something I need to do differently? I can't find any examples on the site that show how the body is extracted.

I have confirmed the same behaviour from the hello world example.

#include <boost/network/protocol/http/server.hpp>
#include <iostream>

namespace http = boost::network::http;

struct hello_world;
typedef http::server<hello_world> server;

struct hello_world
{
    void operator()(const server::request  &request, server::connection_ptr connection)
    {
        ///////////////////////////////////
        // request.body is empty
        ///////////////////////////////////

        server::string_type ip = source(request);
        unsigned int port = request.source_port;

        std::ostringstream data;
        data << "Hello, " << ip << ':' << port << '!';
        connection->set_status(server::connection::ok);
        connection->write(data.str());
    }
};

int main(int argc, char *argv[]) {

    try {
        hello_world handler;
        server::options options(handler);
        server server_(options.address("192.168.0.19").port("9999"));

        server_.run();
    }
    catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
        return 1;
    }

    return 0;
}

Here is the request I'm sending:

curl -v -X POST http://192.168.0.19:9999/my-app/rest/foo/1.0/bar -H 'Content-Type: application/x-www-form-urlencoded' --data key=value

Upvotes: 2

Views: 826

Answers (3)

Martin Trenkmann
Martin Trenkmann

Reputation: 548

In older versions of cpp-netlib you could choose between a sync_server and a async_server class. Since version 0.12 only the async_server class is available. This class does not read body data of a POST request into request.body automatically, but requires the user to read the data in an asynchronous way using connection->read(callback).

Long story short, I've compiled a minimal echo server example that shows how to do this correctly. It also explains how to deal with the not well known Expect: 100-continue header that might be involved.

Please check out echo_async_server.cpp which has been added to the repo recently.

Upvotes: 3

vitakot
vitakot

Reputation: 3844

#include <vector>

#include <boost/config/warning_disable.hpp>
#include <boost/network/include/http/server.hpp>
#include <boost/network/utils/thread_pool.hpp>
#include <boost/range/algorithm/find_if.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/thread.hpp>

namespace net = boost::network;
namespace http = boost::network::http;
namespace utils = boost::network::utils;

struct async_hello_world;
typedef http::async_server<async_hello_world> server;

struct connection_handler : boost::enable_shared_from_this<connection_handler> {

    connection_handler(server::request const &request)
        :req(request), body("") {}

    ~connection_handler() {
        std::cout << "connection_handler dctor called!" << std::endl;
    }

    void operator()(server::connection_ptr conn) {
        int cl;
        server::request::headers_container_type const &hs = req.headers;
        for(server::request::headers_container_type::const_iterator it = hs.begin(); it!=hs.end(); ++it) {
            if(boost::to_lower_copy(it->name)=="content-length") {
                cl = boost::lexical_cast<int>(it->value);
                break;
            }
        }

        read_chunk(cl, conn);
    }

    void read_chunk(size_t left2read, server::connection_ptr conn) {
        std::cout << "left2read: " << left2read << std::endl;
        conn->read(
            boost::bind(
                &connection_handler::handle_post_read,
                connection_handler::shared_from_this(),
                _1, _2, _3, conn, left2read
                )
            );
    }

    void handle_post_read(
        server::connection::input_range range, boost::system::error_code error, size_t size, server::connection_ptr conn, size_t left2read) {
        if(!error) {
            std::cout << "read size: " << size << std::endl;
            body.append(boost::begin(range), size);
            size_t left = left2read - size;
            if(left>0) {
                read_chunk(left, conn);
            } else {
                //std::cout << "FINISHED at " << body.size()<< std::endl;
            }

        }
        std::cout << "error: " << error.message() << std::endl;
    }

    void handle_post_request(server::connection_ptr conn)
    {
        std::cout << "handle request..." << std::endl;
        std::cout << "post size: " << body.size() << std::endl;
    }

    server::request const &req;
    std::string body;
};


struct async_hello_world {

    void operator()(server::request const &request, server::connection_ptr conn) {
    boost::shared_ptr<connection_handler> h(new connection_handler(request));
    (*h)(conn);
    }

    void error(boost::system::error_code const & ec) {
        // do nothing here.
    std::cout << "async error: " << ec.message() << std::endl;
    }
};

int main(int argc, char * argv[]) {
    utils::thread_pool thread_pool(4); 
    async_hello_world handler;
    server instance("0.0.0.0", "1935", handler, thread_pool);
    instance.run();
    return 0;
}

Upvotes: 2

Germ&#225;n Diago
Germ&#225;n Diago

Reputation: 7673

You need to read manually the body. Now a connection_ptr object is used and a handler must be attached for doing the read.

So it must look something like this:

if (r.method == "POST") {
            auto foundIt = std::find_if(r.headers.begin(), r.headers.end(),
                                        [](auto const & h) { return h.name == "Content-Length"; });
            if (foundIt == r.headers.end())
                throw std::logic_error("No Content-Length header found in POST");

            auto handleReadFunc = [this](auto &&... args) {
                this->handleReadBody(args...);
            };

            //This attaches a callback to read the body
            connection->read(handleReadFunc);
        }

Upvotes: 1

Related Questions