restbed
restbed copied to clipboard
Resources are published by path ascending order
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}
@arteniioleg fantastic bug 👍 Thank you also for the in-depth detail. We will deploy a fix shortly.
@ben-crowhurst We will deploy a fix shortly.

If you have PR ready for review we'd be happy to take a look.
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/...
- ...
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.
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.
Hi, I created a pull request for this feature https://github.com/Corvusoft/restbed/pull/298
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!)