Corvusoft / restbed

Corvusoft's Restbed framework brings asynchronous RESTful functionality to C++14 applications.
http://www.corvusoft.co.uk
Other
1.93k stars 379 forks source link

Centos 7 http post request always has an empty body #480

Open RVelichko opened 3 years ago

RVelichko commented 3 years ago

Code example in project:

...
        auto request = _session->get_request();
        size_t length = 0;
        request->get_header("Content-Length", length);
        _session->fetch(length, [](const PRestSession session, const restbed::Bytes &body) {
                                  std::cout << "### BODY:  " << std::string(body.begin(), body.end()) 
                                            << "\n" << std::flush;
                                  session->close(200, "", {{"Content-Length", "0"}});
        });
...

Testing: curl -X POST http://127.0.0.1:8881/api/save_projects -d'[["1","Docs",""]]'

Result in server: Body is empty, but pcapdum contains a line "[["1","Docs",""]]" in the post request.

ben-crowhurst commented 3 years ago

What is the verbose output of the client?

Is the fetch callback invoked?

RVelichko commented 3 years ago

What is the verbose output of the client?

Instead of the client, I checked with standard curl curl -X POST http://127.0.0.1:8881/api/save_projects -d'[["1","Docs",""]]'

Is the fetch callback invoked?

*. hpp


#pragma once

include

include

include

namespace rest_srv {

struct IncapsulateData {};

using DataBuffer = std::vector; using ReadDataFn = std::function<bool(const DataBuffer &)>;

class RestSessionData { public: virtual void status(int status) = 0; virtual void send(const DataBuffer &data) = 0; virtual void read(const ReadDataFn &rd_data_fn) = 0; virtual std::multimap<std::string, std::string> parameters() = 0; };

using PRestSessionData = std::shared_ptr; using RestSessionDataFn = std::function<void(RestSessionData *)>; using ServiceReadyHndlFn = std::function<void()>;

class RestServer { using PIncapsulateData = std::unique_ptr;

int _port;
std::string _crt_path;
std::string _key_path;

PIncapsulateData _in_data;

public: RestServer(int port, const std::string &crt, const std::string &key, const ServiceReadyHndlFn &ready_fn);

void initHanler(const std::string &path, const std::string &method,
                const RestSessionDataFn &handler_fn);
void start();
void stop();

}; } // namespace rest_srv


> *.cpp

include

include

include

include "RestServer.hpp"

namespace ph = std::placeholders; using namespace rest_srv;

using RestSession = restbed::Session; using RestResource = restbed::Resource; using RestSettings = restbed::Settings; using RestService = restbed::Service; using PRestSession = std::shared_ptr; using PRestResource = std::shared_ptr; using PRestSettings = std::shared_ptr; using PRestService = std::unique_ptr; ////////////////////////////////////////////////////////////////////////////////////////////////////

class SessionData : public RestSessionData { RestSessionDataFn _callback_fn; RestSession *_session;

public: SessionData(const RestSessionDataFn &callback_fn) : _callback_fn(callback_fn) {}

void call(const PRestSession &session) {
    if (_callback_fn) {
        _session = session.get();
        _callback_fn(this);
    }
}

virtual void status(int status) { _session->close(status); }

virtual void send(const DataBuffer &data) {
    const auto request = _session->get_request();
    int content_length = 0;
    request->get_header("Content-Length", content_length);
    _session->fetch(
        content_length, [data](const PRestSession session, const restbed::Bytes &body) {
            std::string content_length = std::to_string(data.size());
            session->set_header("Access-Control-Allow-Origin", "*");
            session->set_header("Content-type", "application/json; charset=utf-8");
            session->set_header("Access-Control-Allow-Methods",
                                "GET,PUT,POST,DELETE,PATCH,OPTIONS");
            session->close(restbed::OK, data, {{"Content-Length", content_length.c_str()}});
        });
}

virtual void read(const ReadDataFn &rd_data_fn) {
    auto request = _session->get_request();
    size_t length = 0;
    request->get_header("Content-Length", length);
    _session->fetch(length, [](const PRestSession session, const restbed::Bytes &body) {
        std::cout << "### BODY " << std::string(body.begin(), body.end()) << "\n" << std::flush;
        session->close(200, "", {{"Content-Length", "0"}});
        //[rd_data_fn](const PRestSession session, const restbed::Bytes &body) {
        //            if (rd_data_fn(body)) {
        //                                session->close(200, "",
        //                                {{"Content-Length", "0"}});
        //                            } else {
        //                                session->close(500, "ERROR",
        //                                {{"Content-Length", "5"}});
        //                            }
    });
}

virtual std::multimap<std::string, std::string> parameters() {
    const auto request = _session->get_request();
    return request->get_query_parameters();
}

};

void SessionHandler(const PRestSession &session, const PRestSessionData &sdata) { static_cast<SessionData *>(sdata.get())->call(session); }

void ReadyHandler(RestService &srv, const ServiceReadyHndlFn &ready_fn) { if (ready_fn) { ready_fn(); } }

struct IncapsulateService : IncapsulateData { RestService srv; }; ////////////////////////////////////////////////////////////////////////////////////////////////////

void RestServer::initHanler(const std::string &path, const std::string &method, const RestSessionDataFn &handler_fn) { PRestSessionData sdata = std::make_shared(handler_fn); auto resource = std::make_shared(); resource->set_path(path); resource->set_method_handler(method, std::bind(&SessionHandler, ph::_1, sdata)); static_cast<IncapsulateService *>(_in_data.get())->srv.publish(resource); }

RestServer::RestServer(int port, const std::string &crt, const std::string &key, const ServiceReadyHndlFn &ready_fn) : _port(port), _crt_path(crt), _key_path(key), _in_data(std::make_unique()) { static_cast<IncapsulateService *>(_in_data.get()) ->srv.set_ready_handler(std::bind(&ReadyHandler, ph::_1, ready_fn)); }

void RestServer::start() { auto settings = std::make_shared(); settings->set_port(_port); static_cast<IncapsulateService *>(_in_data.get())->srv.start(settings); }

void RestServer::stop() { static_cast<IncapsulateService *>(_in_data.get())->srv.stop(); }

ben-crowhurst commented 3 years ago

Please read one of the supplied examples to validate your implementation.

RVelichko commented 3 years ago

Your examples don't compile at all.

int content_length = request-> get_header ("Content-Length", 0); no such function!

Code I It works for you on Ubuntu 20.02, but does not work on Centos 7.

ben-crowhurst commented 3 years ago

That issue is related to

  template <typename Type, std::enable_if_t< std::is_arithmetic< std::remove_reference_t< Type > >::value > * = nullptr> inline
  Type get_header( const std::string& name, const Type default_value ) const
  {
      return Common::parse_parameter( get_header( name ), default_value );
  }
RVelichko commented 3 years ago

I tried this: int content_length = 10000; request-> get_header ("Content-Length", content_length);

nothing has changed. On Centos7 - the body is always empty.

RVelichko commented 3 years ago

Speaking of examples: https://stackoverflow.com/questions/39275257/restbed-http-client-empty-body

ben-crowhurst commented 3 years ago

I suspect your content_length is empty.

void post_method_handler( const shared_ptr< Session > session )
{
    const auto request = session->get_request( );

    size_t content_length = request->get_header( "Content-Length", 1024 );

    session->fetch( content_length, [ request ]( const shared_ptr< Session > session, const Bytes & body )
    {
        fprintf( stdout, "%.*s\n", ( int ) body.size( ), body.data( ) );
        session->close( OK, "Hello, World!", { { "Content-Length", "13" }, { "Connection", "close" } } );
    } );
}
ben-crowhurst commented 3 years ago

Speaking of examples: https://stackoverflow.com/questions/39275257/restbed-http-client-empty-body

This was caused by user error as can be seen in the top answer.

RVelichko commented 3 years ago

This link is in support of the fact that the example on the github is not correct.

RVelichko commented 3 years ago

Screenshot_20210608_105631

ben-crowhurst commented 3 years ago

OK, so you have no direct issue with restbed you just require the documentation updated?

If this is the case we have an outstanding ticket for this and it will be done in the 4.8 release cycle.

Please also note the issue is one of compiler implementation.

RVelichko commented 3 years ago

I answered your recommendation to see examples. I looked at them - they don't compile. And the error has not been fixed. I am the second in whom it manifested itself. But the repost bug was simply closed. Once again, it works on Ubuntu, but not on Centos7.

ben-crowhurst commented 3 years ago

So if you hardcode the content_length to a known value you are still receiving no body?

auto request = _session->get_request();
       size_t length = 5
       _session->fetch(length, [](const PRestSession session, const restbed::Bytes &body) {
                                 fprintf( stderr, "%*s\n", 5, body.data( ));
                                 session->close(200, "", {{"Content-Length", "0"}});
       });
RVelichko commented 3 years ago

That's the strangeness - under Centos 7 - the body is always empty.

RVelichko commented 3 years ago

I can roll back the version of my server a little later and try it more carefully, look for this problem with you. So far I rewrote everything on POCO.

ben-crowhurst commented 3 years ago

That's the strangeness - under Centos 7 - the body is always empty.

What version of the compiler are you using? We don't see this on PopOS, FreeBSD, Ubuntu, MacOSX, Windows with GCC, Clang and MSVC.

RVelichko commented 3 years ago

On Centos 7 /opt/rh/devtoolset-9/root/usr/bin/g++ --version g++ (GCC) 9.3.1 20200408 (Red Hat 9.3.1-2) Copyright (C) 2019 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE

On Ubuntu g++ --version g++ (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0 Copyright (C) 2019 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.