Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Catchall route to Crow #126

Merged
merged 11 commits into from
Apr 14, 2021
5 changes: 4 additions & 1 deletion docs/guides/routes.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Alternatively, you can define the response in the body and return it (`#!cpp ([]

For more information on `crow::response` go [here](../../reference/structcrow_1_1response.html).<br><br>

###return statement
###Return statement
A `crow::response` is very strictly tied to a route. If you can have something in a response constructor, you can return it in a handler.<br><br>
The main return type is `std::string`. although you could also return a `crow::json::wvalue` or `crow::multipart::message` directly.<br><br>
For more information on the specific constructors for a `crow::response` go [here](../../reference/structcrow_1_1response.html).
Expand All @@ -71,3 +71,6 @@ class a : public crow::returnable
}
}
```

##Catchall routes
By default, any request that Crow can't find a route for will return a simple 404 response. You can change that to return a default route using the `CROW_CATCHALL_ROUTE(app)` macro. Defining it is identical to a normal route, even when it comes to the `const crow::request&` and `crow::response&` parameters being optional.
4 changes: 4 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ else ()
target_compile_options(example_chat PRIVATE "${compiler_options}")
target_link_libraries(example_chat PUBLIC ${REQUIRED_LIBRARIES})

add_executable(example_catchall example_catchall.cpp)
target_compile_options(example_catchall PRIVATE "${compiler_options}")
target_link_libraries(example_catchall PUBLIC ${REQUIRED_LIBRARIES})

add_custom_command(OUTPUT example_chat.html
COMMAND ${CMAKE_COMMAND} -E
copy ${PROJECT_SOURCE_DIR}/example_chat.html ${CMAKE_CURRENT_BINARY_DIR}/example_chat.html
Expand Down
20 changes: 20 additions & 0 deletions examples/example_catchall.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#define CROW_MAIN
#include <crow.h>



int main()
{
crow::SimpleApp app;

CROW_ROUTE(app, "/")([](){return "Hello";});

//Setting a custom route for any URL that isn't defined, instead of a simple 404.
CROW_CATCHALL_ROUTE(app)
([](crow::response& res) {
res.body = "The URL does not seem to be correct.";
res.end();
});

app.port(18080).run();
}
7 changes: 7 additions & 0 deletions include/crow/app.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#else
#define CROW_ROUTE(app, url) app.route<crow::black_magic::get_parameter_tag(url)>(url)
#endif
#define CROW_CATCHALL_ROUTE(app) app.catchall_route()

namespace crow
{
Expand Down Expand Up @@ -86,6 +87,12 @@ namespace crow
return router_.new_rule_tagged<Tag>(std::move(rule));
}

///Create a route for any requests without a proper route (**Use CROW_CATCHALL_ROUTE instead**)
CatchallRule& catchall_route()
{
return router_.catchall_rule();
}

self_t& signal_clear()
{
signals_.clear();
Expand Down
120 changes: 116 additions & 4 deletions include/crow/routing.h
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,101 @@ namespace crow
}
}


class CatchallRule
{
public:
CatchallRule(){}

template <typename Func>
typename std::enable_if<black_magic::CallHelper<Func, black_magic::S<>>::value, void>::type
operator()(Func&& f)
{
static_assert(!std::is_same<void, decltype(f())>::value,
"Handler function cannot have void return type; valid return types: string, int, crow::response, crow::returnable");

handler_ = (
#ifdef CROW_CAN_USE_CPP14
[f = std::move(f)]
#else
[f]
#endif
(const request&, response& res){
res = response(f());
res.end();
});

}

template <typename Func>
typename std::enable_if<
!black_magic::CallHelper<Func, black_magic::S<>>::value &&
black_magic::CallHelper<Func, black_magic::S<crow::request>>::value,
void>::type
operator()(Func&& f)
{
static_assert(!std::is_same<void, decltype(f(std::declval<crow::request>()))>::value,
"Handler function cannot have void return type; valid return types: string, int, crow::response, crow::returnable");

handler_ = (
#ifdef CROW_CAN_USE_CPP14
[f = std::move(f)]
#else
[f]
#endif
(const crow::request& req, crow::response& res){
res = response(f(req));
res.end();
});
}

template <typename Func>
typename std::enable_if<
!black_magic::CallHelper<Func, black_magic::S<>>::value &&
!black_magic::CallHelper<Func, black_magic::S<crow::request>>::value &&
black_magic::CallHelper<Func, black_magic::S<crow::response&>>::value,
void>::type
operator()(Func&& f)
{
static_assert(std::is_same<void, decltype(f(std::declval<crow::response&>()))>::value,
"Handler function with response argument should have void return type");
handler_ = (
#ifdef CROW_CAN_USE_CPP14
[f = std::move(f)]
#else
[f]
#endif
(const crow::request&, crow::response& res){
f(res);
});
}

template <typename Func>
typename std::enable_if<
!black_magic::CallHelper<Func, black_magic::S<>>::value &&
!black_magic::CallHelper<Func, black_magic::S<crow::request>>::value &&
!black_magic::CallHelper<Func, black_magic::S<crow::response&>>::value,
void>::type
operator()(Func&& f)
{
static_assert(std::is_same<void, decltype(f(std::declval<crow::request>(), std::declval<crow::response&>()))>::value,
"Handler function with response argument should have void return type");

handler_ = std::move(f);
}

bool has_handler()
{
return (handler_ != nullptr);
}

protected:
friend class Router;
private:
std::function<void(const crow::request&, crow::response&)> handler_;
};


/// A rule dealing with websockets.

/// Provides the interface for the user to put in the necessary handlers for a websocket to work.
Expand Down Expand Up @@ -487,7 +582,7 @@ namespace crow
black_magic::CallHelper<Func, black_magic::S<crow::request, Args...>>::value ,
"Handler type is mismatched with URL parameters");
static_assert(!std::is_same<void, decltype(f(std::declval<Args>()...))>::value,
"Handler function cannot have void return type; valid return types: string, int, crow::resposne, crow::json::wvalue");
"Handler function cannot have void return type; valid return types: string, int, crow::response, crow::returnable");

handler_ = (
#ifdef CROW_CAN_USE_CPP14
Expand All @@ -512,7 +607,7 @@ namespace crow
black_magic::CallHelper<Func, black_magic::S<crow::request, Args...>>::value,
"Handler type is mismatched with URL parameters");
static_assert(!std::is_same<void, decltype(f(std::declval<crow::request>(), std::declval<Args>()...))>::value,
"Handler function cannot have void return type; valid return types: string, int, crow::resposne, crow::json::wvalue");
"Handler function cannot have void return type; valid return types: string, int, crow::response, crow::returnable");

handler_ = (
#ifdef CROW_CAN_USE_CPP14
Expand Down Expand Up @@ -934,6 +1029,7 @@ namespace crow
std::vector<Node> nodes_;
};


/// Handles matching requests to existing rules and upgrade requests.
class Router
{
Expand Down Expand Up @@ -961,6 +1057,11 @@ namespace crow
return *ruleObject;
}

CatchallRule& catchall_rule()
{
return catchall_rule_;
}

void internal_add_rule_object(const std::string& rule, BaseRule* ruleObject)
{
bool has_trailing_slash = false;
Expand Down Expand Up @@ -1159,8 +1260,16 @@ namespace crow
}
}

CROW_LOG_DEBUG << "Cannot match rules " << req.url;
res = response(404);
if (catchall_rule_.has_handler())
{
CROW_LOG_DEBUG << "Cannot match rules " << req.url << ". Redirecting to Catchall rule";
catchall_rule_.handler_(req, res);
}
else
{
CROW_LOG_DEBUG << "Cannot match rules " << req.url;
res = response(404);
}
res.end();
return;
}
Expand Down Expand Up @@ -1219,6 +1328,8 @@ namespace crow
}

private:
CatchallRule catchall_rule_;

struct PerMethod
{
std::vector<BaseRule*> rules;
Expand All @@ -1229,5 +1340,6 @@ namespace crow
};
std::array<PerMethod, static_cast<int>(HTTPMethod::InternalMethodCount)> per_methods_;
std::vector<std::unique_ptr<BaseRule>> all_rules_;

};
}
60 changes: 60 additions & 0 deletions tests/unittest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1750,3 +1750,63 @@ TEST_CASE("zlib_compression")
app_deflate.stop();
app_gzip.stop();
}

TEST_CASE("catchall")
{
SimpleApp app;
SimpleApp app2;

CROW_ROUTE(app, "/place")([](){return "place";});

CROW_CATCHALL_ROUTE(app)([](){return "!place";});

CROW_ROUTE(app2, "/place")([](){return "place";});

app.validate();
app2.validate();

{
request req;
response res;

req.url = "/place";

app.handle(req, res);

CHECK(200 == res.code);
}

{
request req;
response res;

req.url = "/another_place";

app.handle(req, res);

CHECK(200 == res.code);
CHECK("!place" == res.body);
}

{
request req;
response res;

req.url = "/place";

app2.handle(req, res);

CHECK(200 == res.code);
}

{
request req;
response res;

req.url = "/another_place";

app2.handle(req, res);

CHECK(404 == res.code);
}
}