restbed icon indicating copy to clipboard operation
restbed copied to clipboard

Resources are published by path ascending order

Open 01e9 opened this issue 7 years ago • 8 comments

For example we have the following paths/resources

POST /users/register (self registered users)
POST /users (admins will create users)
GET  /users
GET  /users/{id: .+}

We register/publish them in the above order, so the first match will be executed and we will get the correct/expected behavior. But restbed sorts them ascending by path before publishing, so /users/register will never be executed.

/users
/users/{id: .+}
/users/register (call me maybe)

Any workarounds (require numeric {id: \\d+} or length bigger than the register word {id: .{9,}}), are not accepted. We need to control the publishing order.

Working code for testing

main.cpp
#include <iostream>
#include <memory>
#include <restbed>
#include "restbed-logger.h"

using namespace std;
using namespace restbed;

int main(const int, const char **) {

    Service service;

    service.publish([]() {
        cout << "[app] publishing /users/register" << endl;

        auto resource = make_shared<Resource>();

        resource->set_path("/users/register");
        resource->set_method_handler("POST", [](const shared_ptr<Session> session) -> void {
            session->close(OK, "POST /users/register");
        });

        return resource;
    }());
    service.publish([]() {
        cout << "[app] publishing /users" << endl;

        auto resource = make_shared<Resource>();

        resource->set_path("/users");
        resource->set_method_handler("POST", [](const shared_ptr<Session> session) -> void {
            session->close(OK, "POST /users");
        });
        resource->set_method_handler("GET", [](const shared_ptr<Session> session) -> void {
            session->close(OK, "GET /users");
        });

        return resource;
    }());
    service.publish([]() {
        cout << "[app] publishing /users/{id}" << endl;

        auto resource = make_shared<Resource>();

        resource->set_path("/users/{id: .+}");
        resource->set_method_handler("GET", [](const shared_ptr<Session> session) -> void {
            session->close(OK, "GET /users/{id}");
        });

        return resource;
    }());

    service.set_logger(make_shared<RestbedLogger>());

    service.start([]() {
        auto settings = make_shared<Settings>();

        settings->set_port(1984);
        settings->set_default_header("Connection", "close");

        return settings;
    }());

    return 0;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.9)
project(restbed_publish_order)

set(EXTERNAL_PROJECTS_PREFIX ${CMAKE_BINARY_DIR}/external-projects)
set(EXTERNAL_PROJECTS_INSTALL_PREFIX ${EXTERNAL_PROJECTS_PREFIX}/installed)

include(GNUInstallDirs)
include(ExternalProject)

link_directories(${EXTERNAL_PROJECTS_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR})

ExternalProject_Add(externalRestbed
    PREFIX "${EXTERNAL_PROJECTS_PREFIX}"
    GIT_REPOSITORY "https://github.com/Corvusoft/restbed.git"
    GIT_TAG "master"
    UPDATE_DISCONNECTED ON
    CMAKE_ARGS
    -DCMAKE_INSTALL_PREFIX=${EXTERNAL_PROJECTS_INSTALL_PREFIX}
    -DCMAKE_INSTALL_LIBDIR=${CMAKE_INSTALL_LIBDIR}
    )

add_executable(${PROJECT_NAME} main.cpp)
set_target_properties(${PROJECT_NAME} PROPERTIES
    CXX_STANDARD 11
    CXX_STANDARD_REQUIRED ON
    CXX_EXTENSIONS OFF
    )
target_include_directories(${PROJECT_NAME}
    PRIVATE
    $<BUILD_INTERFACE:${EXTERNAL_PROJECTS_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}>
    )

add_dependencies(${PROJECT_NAME} externalRestbed)
find_package(Threads REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE
        restbed ssl crypto
        Threads::Threads # workaround for https://github.com/Corvusoft/restbed/issues/251
        )
restbed-logger.h
#pragma once

#include <restbed>
#include <cstdarg>
#include <iostream>

class RestbedLogger : public restbed::Logger
{
public:
    using restbed::Logger::Logger;

    void stop(void) override
    {
        mStarted = false;
    }

    void start(const std::shared_ptr<const restbed::Settings>& settings) override
    {
        mStarted = true;
    }

    void log(const Level level, const char *format, ...) override
    {
        if (!mStarted) {
            return;
        }

        std::va_list arguments;
        va_start(arguments, format);
        vprintf(format, arguments);
        std::cout << std::endl;
        va_end(arguments);
    }

    void log_if(bool expression, const Level level, const char *format, ...) override
    {
        if (!mStarted || !expression) {
            return;
        }

        std::va_list arguments;
        va_start(arguments, format);
        vprintf(format, arguments);
        std::cout << std::endl;
        va_end(arguments);
    }

private:
    bool mStarted {true};
};

Output

[app] publishing /users/register
[app] publishing /users
[app] publishing /users/{id: .+}
Service accepting HTTP connections at 'http://[::]:1984'.
Resource published on route '/users'.
Resource published on route '/users/{id: .+}'.
Resource published on route '/users/register'.
curl -w'\n' -v -XPOST 'http://localhost:1984/users/register'
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 1984 (#0)
> POST /users/register HTTP/1.1
> Host: localhost:1984
> User-Agent: curl/7.55.1
> Accept: */*
> 
< HTTP/1.1 405 Method Not Allowed
< Connection: close
< 
* Closing connection 0
curl -w'\n' -v -XGET 'http://localhost:1984/users/register'
Note: Unnecessary use of -X or --request, GET is already inferred.
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 1984 (#0)
> GET /users/register HTTP/1.1
> Host: localhost:1984
> User-Agent: curl/7.55.1
> Accept: */*
> 
< HTTP/1.1 200 OK
< Connection: close
< 
* Closing connection 0
GET /users/{id}

01e9 avatar Dec 28 '17 13:12 01e9

@arteniioleg fantastic bug 👍 Thank you also for the in-depth detail. We will deploy a fix shortly.

ben-crowhurst avatar Jan 06 '18 05:01 ben-crowhurst

@ben-crowhurst We will deploy a fix shortly.

ezgif com-optimize

01e9 avatar Jan 10 '18 07:01 01e9

If you have PR ready for review we'd be happy to take a look.

ben-crowhurst avatar Jan 15 '18 10:01 ben-crowhurst

Routes are sorted via std::map< std::string, std::shared_ptr< const Resource > > default operator<.

This would require a sizeable change to the codebase.

are not accepted. We need to control the publishing order.

What is the issue with the myriad of workarounds?

  • Less generic id structure .*
  • /api/users/register, /resources/users/...
  • ...

ben-crowhurst avatar Apr 02 '18 01:04 ben-crowhurst

We will take the publishing order into consideration for the 5.0 release. However such heavy lifting will not take place within the 4.* stream.

ben-crowhurst avatar Apr 02 '18 01:04 ben-crowhurst

I saw those maps from the beginning. That's why I opened an issue for you to fix it because it's too complicated for me to understand the consequences of migrating to another container type.

01e9 avatar Apr 02 '18 05:04 01e9

Hi, I created a pull request for this feature https://github.com/Corvusoft/restbed/pull/298

CheyenneForbes avatar Apr 12 '18 18:04 CheyenneForbes

As a temp fix we use (?!PREFIX)

/users
/users/{id: (?!register).+}    (skip if starts with: register)
/users/register                (finally I am called, thank you for skipping!)

01e9 avatar Jun 04 '18 11:06 01e9