diff --git a/docs/guides/blueprints.md b/docs/guides/blueprints.md
new file mode 100644
index 000000000..2a0637611
--- /dev/null
+++ b/docs/guides/blueprints.md
@@ -0,0 +1,43 @@
+!!!Warning
+
+ This feature is currently only available on the "master" branch.
+
+Crow supports flask style blueprints.
+A blueprint is a limited app. It cannot handle networking. But it can handle routes.
+Blueprints allow developers to compartmentalize their Crow applications, making them a lot more modular.
+
+In order for a blueprint to work, it has to be registered with a Crow app before the app is run. This can be done using `#!cpp app.register_blueprint(blueprint);`.
+
+Blueprints let you do the following:
+
+### Define Routes
+You can define routes in a blueprint, similarly to how `#!cpp CROW_ROUTE(app, "/xyz")` works, you can use `#!cpp CROW_BP_ROUTE(blueprint, "/xyz")` to define a blueprint route.
+
+### Define a Prefix
+Blueprints can have a prefix assigned to them. This can be done when creating a new blueprint as in `#!cpp crow::blueprint bp("prefix");`. This prefix will be applied to all routes belonging to the blueprint. truning a route such as `/crow/rocks` into `/prefix/crow/rocks`.
+
+!!!Warning
+
+ Unlike routes, blueprint prefixes should contain no slashes.
+
+
+### Use a custom Static directory
+Blueprints let you define a custom static directory (relative to your working directory). This can be done by initializing a blueprint as `#!cpp crow::blueprint bp("prefix", "custom_static");`. This does not have an effect on `#!cpp set_static_file_info()`, it's only for when you want direct access to a file.
+
+!!!note
+
+ Currently changing which endpoint the blueprint uses isn't possible, so whatever you've set in `CROW_STATIC_ENDPOINT` (default is "static") will be used. Making your final route `/prefix/static/filename`.
+
+
+### Use a custom Templates directory
+Similar to static directories, You can set a custom templates directory (relative to your working directory). To do this you initialize the blueprint as `#!cpp crow::blueprint bp("prefix", "custom_static", "custom_templates");`. Any routes defined for the blueprint will use that directory when calling `#!cpp crow::mustache::load("filename.html")`.
+
+!!!note
+
+ If you want to define a custom templates directory without defining a custom static directory, you can pass the static directory as an empty string. Making your constructor `#!cpp crow::blueprint bp("prefix", "", "custom_templates");`.
+
+### Define a custom Catchall route
+You can define a custom catchall route for a blueprint by calling `#!cpp CROW_BP_CATCHALL_ROUTE(blueprint)`. This causes any requests with a URL starting with `/prefix` and no route found to call the blueprint's catchall route. if no catchall route is defined, Crow will default to either the parent blueprint or the app's catchall route.
+
+### Register other Blueprints
+Blueprints can also register other blueprints. This is done through `#!cpp blueprint.register_blueprint(blueprint_2);`. The child blueprint's routes become `/prefix/prefix_2/abc/xyz`.
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index ded51c771..4841e37ff 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -77,6 +77,10 @@ else ()
target_compile_options(example_json_map PRIVATE "${compiler_options}")
target_link_libraries(example_json_map PUBLIC ${REQUIRED_LIBRARIES})
+ add_executable(example_blueprint example_blueprint.cpp)
+ target_compile_options(example_blueprint PRIVATE "${compiler_options}")
+ target_link_libraries(example_blueprint 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
diff --git a/examples/example_blueprint.cpp b/examples/example_blueprint.cpp
new file mode 100644
index 000000000..af007bd09
--- /dev/null
+++ b/examples/example_blueprint.cpp
@@ -0,0 +1,35 @@
+#define CROW_MAIN
+#include "crow.h"
+
+int main()
+{
+ crow::SimpleApp app;
+
+ crow::Blueprint bp("bp_prefix", "cstat", "ctemplate");
+
+
+ crow::Blueprint sub_bp("bp2", "csstat", "cstemplate");
+
+ CROW_BP_ROUTE(sub_bp, "/")
+ ([]() {
+ return "Hello world!";
+ });
+
+/* CROW_BP_ROUTE(bp, "/templatt")
+ ([]() {
+ crow::mustache::context ctxdat;
+ ctxdat["messg"] = "fifty five!!";
+
+ auto page = crow::mustache::load("indks.html");
+
+ return page.render(ctxdat);
+ });
+*/
+ CROW_BP_CATCHALL_ROUTE(sub_bp)([](){return "WRONG!!";});
+
+
+ bp.register_blueprint(sub_bp);
+ app.register_blueprint(bp);
+
+ app.loglevel(crow::LogLevel::DEBUG).port(18080).run();
+}
diff --git a/include/crow/app.h b/include/crow/app.h
index e0f8b14d4..2491ebc6a 100644
--- a/include/crow/app.h
+++ b/include/crow/app.h
@@ -25,10 +25,13 @@
#ifdef CROW_MSVC_WORKAROUND
#define CROW_ROUTE(app, url) app.route_dynamic(url)
+#define CROW_BP_ROUTE(blueprint, url) blueprint.new_rule_dynamic(url)
#else
#define CROW_ROUTE(app, url) app.route(url)
+#define CROW_BP_ROUTE(blueprint, url) blueprint.new_rule_tagged(url)
#endif
#define CROW_CATCHALL_ROUTE(app) app.catchall_route()
+#define CROW_BP_CATCHALL_ROUTE(blueprint) blueprint.catchall_rule()
namespace crow
{
@@ -159,12 +162,18 @@ namespace crow
/// crow::LogLevel::Warning (2)
/// crow::LogLevel::Error (3)
/// crow::LogLevel::Critical (4)
- self_t& loglevel(crow::LogLevel level)
+ self_t& loglevel(LogLevel level)
{
crow::logger::setLogLevel(level);
return *this;
}
+ self_t& register_blueprint(Blueprint& blueprint)
+ {
+ router_.register_blueprint(blueprint);
+ return *this;
+ }
+
///Set a custom duration and function to run on every tick
template
self_t& tick(Duration d, Func f) {
@@ -192,7 +201,34 @@ namespace crow
///Go through the rules, upgrade them if possible, and add them to the list of rules
void validate()
{
- router_.validate();
+ if (!validated_)
+ {
+
+#ifndef CROW_DISABLE_STATIC_DIR
+ route(CROW_STATIC_ENDPOINT)
+ ([](crow::response& res, std::string file_path_partial)
+ {
+ res.set_static_file_info(CROW_STATIC_DIRECTORY + file_path_partial);
+ res.end();
+ });
+
+ for (auto& bp : router_.blueprints())
+ {
+ if (!bp->static_dir().empty())
+ {
+ bp->new_rule_tagged(CROW_STATIC_ENDPOINT)
+ ([bp](crow::response& res, std::string file_path_partial)
+ {
+ res.set_static_file_info(bp->static_dir() + '/' + file_path_partial);
+ res.end();
+ });
+ }
+ }
+#endif
+
+ router_.validate();
+ validated_ = true;
+ }
}
///Notify anything using `wait_for_server_start()` to proceed
@@ -206,14 +242,7 @@ namespace crow
///Run the server
void run()
{
-#ifndef CROW_DISABLE_STATIC_DIR
- route(CROW_STATIC_ENDPOINT)
- ([](crow::response& res, std::string file_path_partial)
- {
- res.set_static_file_info(CROW_STATIC_DIRECTORY + file_path_partial);
- res.end();
- });
-#endif
+
validate();
#ifdef CROW_ENABLE_SSL
@@ -361,6 +390,7 @@ namespace crow
private:
uint16_t port_ = 80;
uint16_t concurrency_ = 1;
+ bool validated_ = false;
std::string server_name_ = std::string("Crow/") + VERSION;
std::string bindaddr_ = "0.0.0.0";
Router router_;
diff --git a/include/crow/common.h b/include/crow/common.h
index f38a3c767..2aa95a388 100644
--- a/include/crow/common.h
+++ b/include/crow/common.h
@@ -8,7 +8,7 @@
namespace crow
{
- enum class HTTPMethod
+ enum class HTTPMethod : char
{
#ifndef DELETE
DELETE = 0,
@@ -69,7 +69,7 @@ namespace crow
return "invalid";
}
- enum class ParamType
+ enum class ParamType : char
{
INT,
UINT,
diff --git a/include/crow/routing.h b/include/crow/routing.h
index 6de9f90c2..64e1de09a 100644
--- a/include/crow/routing.h
+++ b/include/crow/routing.h
@@ -14,9 +14,16 @@
#include "crow/utility.h"
#include "crow/logging.h"
#include "crow/websocket.h"
+#include "crow/mustache.h"
namespace crow
{
+
+#ifdef CROW_MAIN
+ uint16_t INVALID_BP_ID{0xFFFF};
+#else
+ extern uint16_t INVALID_BP_ID{0xFFFF};
+#endif
/// A base class for all rules.
/// Used to provide a common interface for code dealing with different types of rules.
@@ -70,6 +77,9 @@ namespace crow
}
}
+
+ std::string custom_templates_base;
+
const std::string& rule() { return rule_; }
protected:
@@ -81,6 +91,7 @@ namespace crow
std::unique_ptr rule_to_upgrade_;
friend class Router;
+ friend class Blueprint;
template
friend struct RuleParameterTraits;
};
@@ -499,6 +510,10 @@ namespace crow
void handle(const request& req, response& res, const routing_params& params) override
{
+ if (!custom_templates_base.empty())
+ mustache::set_base(custom_templates_base);
+ else if (mustache::detail::get_template_base_directory_ref() != "templates")
+ mustache::set_base("templates");
erased_handler_(req, res, params);
}
@@ -674,6 +689,11 @@ namespace crow
void handle(const request& req, response& res, const routing_params& params) override
{
+ if (!custom_templates_base.empty())
+ mustache::set_base(custom_templates_base);
+ else if (mustache::detail::get_template_base_directory_ref() != "templates")
+ mustache::set_base("templates");
+
detail::routing_handler_call_helper::call<
detail::routing_handler_call_helper::call_params<
decltype(handler_)>,
@@ -694,13 +714,16 @@ namespace crow
const int RULE_SPECIAL_REDIRECT_SLASH = 1;
+
/// A search tree.
class Trie
{
public:
struct Node
{
- unsigned rule_index{};
+ uint16_t rule_index{};
+ // Assign the index to the maximum 32 unsigned integer value by default so that any other number (specifically 0) is a valid BP id.
+ uint16_t blueprint_index{INVALID_BP_ID};
std::string key;
ParamType param = ParamType::MAX; // MAX = No param.
std::vector children;
@@ -709,6 +732,7 @@ namespace crow
{
return
!rule_index &&
+ blueprint_index == INVALID_BP_ID &&
children.size() < 2 &&
param == ParamType::MAX &&
std::all_of(std::begin(children), std::end(children), [](Node* x){ return x->param == ParamType::MAX; });
@@ -736,6 +760,8 @@ namespace crow
private:
+
+
void optimizeNode(Node* node)
{
if (node->children.empty())
@@ -745,6 +771,7 @@ namespace crow
Node* child_temp = node->children[0];
node->key = node->key + child_temp->key;
node->rule_index = child_temp->rule_index;
+ node->blueprint_index = child_temp->blueprint_index;
node->children = std::move(child_temp->children);
delete(child_temp);
optimizeNode(node);
@@ -808,33 +835,44 @@ namespace crow
optimize();
}
- std::pair find(const std::string& req_url, const Node* node = nullptr, unsigned pos = 0, routing_params* params = nullptr) const
+ //Rule_index, Blueprint_index, routing_params
+ std::tuple, routing_params> find(const std::string& req_url, const Node* node = nullptr, unsigned pos = 0, routing_params* params = nullptr, std::vector* blueprints = nullptr) const
{
//start params as an empty struct
routing_params empty;
if (params == nullptr)
params = ∅
+ //same for blueprint vector
+ std::vector MT;
+ if (blueprints == nullptr)
+ blueprints = &MT;
- unsigned found{}; //The rule index to be found
+ uint16_t found{}; //The rule index to be found
+ std::vector found_BP; //The Blueprint indices to be found
routing_params match_params; //supposedly the final matched parameters
//start from the head node
if (node == nullptr)
node = &head_;
- //if the function was called on a node at the end of the string (the last recursion), return the nodes rule index, and whatever params were passed to the function
- if (pos == req_url.size())
- return {node->rule_index, *params};
-
- auto update_found = [&found, &match_params](std::pair& ret)
+ auto update_found = [&found, &found_BP, &match_params](std::tuple, routing_params>& ret)
{
- if (ret.first && (!found || found > ret.first))
+ found_BP = std::move(std::get<1>(ret));
+ if (std::get<0>(ret) && (!found || found > std::get<0>(ret)))
{
- found = ret.first;
- match_params = std::move(ret.second);
+ found = std::get<0>(ret);
+ match_params = std::move(std::get<2>(ret));
}
};
+ //if the function was called on a node at the end of the string (the last recursion), return the nodes rule index, and whatever params were passed to the function
+ if (pos == req_url.size())
+ {
+ found_BP = std::move(*blueprints);
+ return {node->rule_index, *blueprints, *params};
+ }
+
+ bool found_fragment = false;
for(auto& child : node->children)
{
@@ -850,10 +888,13 @@ namespace crow
long long int value = strtoll(req_url.data()+pos, &eptr, 10);
if (errno != ERANGE && eptr != req_url.data()+pos)
{
+ found_fragment = true;
params->int_params.push_back(value);
- auto ret = find(req_url, child, eptr - req_url.data(), params);
+ if (child->blueprint_index != INVALID_BP_ID) blueprints->push_back(child->blueprint_index);
+ auto ret = find(req_url, child, eptr - req_url.data(), params, blueprints);
update_found(ret);
params->int_params.pop_back();
+ blueprints->pop_back();
}
}
}
@@ -868,10 +909,13 @@ namespace crow
unsigned long long int value = strtoull(req_url.data()+pos, &eptr, 10);
if (errno != ERANGE && eptr != req_url.data()+pos)
{
+ found_fragment = true;
params->uint_params.push_back(value);
- auto ret = find(req_url, child, eptr - req_url.data(), params);
+ if (child->blueprint_index != INVALID_BP_ID) blueprints->push_back(child->blueprint_index);
+ auto ret = find(req_url, child, eptr - req_url.data(), params, blueprints);
update_found(ret);
params->uint_params.pop_back();
+ blueprints->pop_back();
}
}
}
@@ -886,10 +930,13 @@ namespace crow
double value = strtod(req_url.data()+pos, &eptr);
if (errno != ERANGE && eptr != req_url.data()+pos)
{
+ found_fragment = true;
params->double_params.push_back(value);
- auto ret = find(req_url, child, eptr - req_url.data(), params);
+ if (child->blueprint_index != INVALID_BP_ID) blueprints->push_back(child->blueprint_index);
+ auto ret = find(req_url, child, eptr - req_url.data(), params, blueprints);
update_found(ret);
params->double_params.pop_back();
+ blueprints->pop_back();
}
}
}
@@ -905,10 +952,13 @@ namespace crow
if (epos != pos)
{
+ found_fragment = true;
params->string_params.push_back(req_url.substr(pos, epos-pos));
- auto ret = find(req_url, child, epos, params);
+ if (child->blueprint_index != INVALID_BP_ID) blueprints->push_back(child->blueprint_index);
+ auto ret = find(req_url, child, epos, params, blueprints);
update_found(ret);
params->string_params.pop_back();
+ blueprints->pop_back();
}
}
@@ -918,10 +968,13 @@ namespace crow
if (epos != pos)
{
+ found_fragment = true;
params->string_params.push_back(req_url.substr(pos, epos-pos));
- auto ret = find(req_url, child, epos, params);
+ if (child->blueprint_index != INVALID_BP_ID) blueprints->push_back(child->blueprint_index);
+ auto ret = find(req_url, child, epos, params, blueprints);
update_found(ret);
params->string_params.pop_back();
+ blueprints->pop_back();
}
}
}
@@ -931,18 +984,28 @@ namespace crow
const std::string& fragment = child->key;
if (req_url.compare(pos, fragment.size(), fragment) == 0)
{
- auto ret = find(req_url, child, pos + fragment.size(), params);
+ found_fragment = true;
+ if (child->blueprint_index != INVALID_BP_ID) blueprints->push_back(child->blueprint_index);
+ auto ret = find(req_url, child, pos + fragment.size(), params, blueprints);
update_found(ret);
+ blueprints->pop_back();
}
}
}
- return {found, match_params}; //Called after all the recursions have been done
+
+ if (!found_fragment)
+ found_BP = std::move(*blueprints);
+
+ return {found, found_BP, match_params}; //Called after all the recursions have been done
}
- void add(const std::string& url, unsigned rule_index)
+ //This functions assumes any blueprint info passed is valid
+ void add(const std::string& url, uint16_t rule_index, unsigned bp_prefix_length = 0, uint16_t blueprint_index = INVALID_BP_ID)
{
Node* idx = &head_;
+ bool has_blueprint = bp_prefix_length != 0 && blueprint_index != INVALID_BP_ID;
+
for(unsigned i = 0; i < url.size(); i ++)
{
char c = url[i];
@@ -1008,6 +1071,9 @@ namespace crow
{
auto new_node_idx = new_node(idx);
new_node_idx->key = c;
+ //The assumption here is that you'd only need to add a blueprint index if the tree didn't have the BP prefix.
+ if (has_blueprint && i == bp_prefix_length)
+ new_node_idx->blueprint_index = blueprint_index;
idx = new_node_idx;
}
}
@@ -1026,7 +1092,7 @@ namespace crow
size_t get_size(Node* node)
{
- unsigned size = 8; //rule_index and param
+ unsigned size = 5 ; //rule_index, blueprint_index, and param
size += (node->key.size()); //each character in the key is 1 byte
for (auto child: node->children)
{
@@ -1046,9 +1112,146 @@ namespace crow
return children[children.size()-1];
}
+
Node head_;
};
+ /// A blueprint can be considered a smaller section of a Crow app, specifically where the router is conecerned.
+ ///
+ /// You can use blueprints to assign a common prefix to rules' prefix, set custom static and template folders, and set a custom catchall route.
+ /// You can also assign nest blueprints for maximum Compartmentalization.
+ class Blueprint
+ {
+ public:
+
+ Blueprint(const std::string& prefix):
+ prefix_(prefix){};
+
+ Blueprint(const std::string& prefix, const std::string& static_dir):
+ prefix_(prefix), static_dir_(static_dir){};
+
+ Blueprint(const std::string& prefix, const std::string& static_dir, const std::string& templates_dir):
+ prefix_(prefix), static_dir_(static_dir), templates_dir_(templates_dir){};
+
+/*
+ Blueprint(Blueprint& other)
+ {
+ prefix_ = std::move(other.prefix_);
+ all_rules_ = std::move(other.all_rules_);
+ }
+
+ Blueprint(const Blueprint& other)
+ {
+ prefix_ = other.prefix_;
+ all_rules_ = other.all_rules_;
+ }
+*/
+ Blueprint(Blueprint&& value)
+ {
+ *this = std::move(value);
+ }
+
+ Blueprint& operator = (const Blueprint& value) = delete;
+
+ Blueprint& operator = (Blueprint&& value) noexcept
+ {
+ prefix_ = std::move(value.prefix_);
+ all_rules_ = std::move(value.all_rules_);
+ catchall_rule_ = std::move(value.catchall_rule_);
+ return *this;
+ }
+
+ bool operator == (const Blueprint& value)
+ {
+ return value.prefix() == prefix_;
+ }
+
+ bool operator != (const Blueprint& value)
+ {
+ return value.prefix() != prefix_;
+ }
+
+ std::string prefix() const
+ {
+ return prefix_;
+ }
+
+ std::string static_dir() const
+ {
+ return static_dir_;
+ }
+
+ DynamicRule& new_rule_dynamic(std::string&& rule)
+ {
+ std::string new_rule = std::move(rule);
+ new_rule = '/' + prefix_ + new_rule;
+ auto ruleObject = new DynamicRule(new_rule);
+ ruleObject->custom_templates_base = templates_dir_;
+ all_rules_.emplace_back(ruleObject);
+
+ return *ruleObject;
+ }
+
+ template
+ typename black_magic::arguments::type::template rebind& new_rule_tagged(std::string&& rule)
+ {
+ std::string new_rule = std::move(rule);
+ new_rule = '/' + prefix_ + new_rule;
+ using RuleT = typename black_magic::arguments::type::template rebind;
+
+ auto ruleObject = new RuleT(new_rule);
+ ruleObject->custom_templates_base = templates_dir_;
+ all_rules_.emplace_back(ruleObject);
+
+ return *ruleObject;
+ }
+
+ void register_blueprint(Blueprint& blueprint)
+ {
+ if (blueprints_.empty() || std::find(blueprints_.begin(), blueprints_.end(), &blueprint) == blueprints_.end())
+ {
+ apply_blueprint(blueprint);
+ blueprints_.emplace_back(&blueprint);
+ }
+ else
+ throw std::runtime_error("blueprint \"" + blueprint.prefix_ + "\" already exists in blueprint \"" + prefix_ + '\"');
+ }
+
+
+ CatchallRule& catchall_rule()
+ {
+ return catchall_rule_;
+ }
+
+ private:
+
+ void apply_blueprint(Blueprint& blueprint)
+ {
+
+ blueprint.prefix_ = prefix_ + '/' + blueprint.prefix_;
+ blueprint.static_dir_ = static_dir_ + '/' + blueprint.static_dir_;
+ blueprint.templates_dir_ = templates_dir_ + '/' + blueprint.templates_dir_;
+ for (auto& rule : blueprint.all_rules_)
+ {
+ std::string new_rule = '/' + prefix_ + rule->rule_;
+ rule->rule_ = new_rule;
+ }
+ for (Blueprint* bp_child : blueprint.blueprints_)
+ {
+ Blueprint& bp_ref = *bp_child;
+ apply_blueprint(bp_ref);
+ }
+ }
+
+ std::string prefix_;
+ std::string static_dir_;
+ std::string templates_dir_;
+ std::vector> all_rules_;
+ CatchallRule catchall_rule_;
+ std::vector blueprints_;
+
+ friend class Router;
+ };
/// Handles matching requests to existing rules and upgrade requests.
class Router
@@ -1082,7 +1285,7 @@ namespace crow
return catchall_rule_;
}
- void internal_add_rule_object(const std::string& rule, BaseRule* ruleObject)
+ void internal_add_rule_object(const std::string& rule, BaseRule* ruleObject, const uint16_t& BP_index, std::vector& blueprints)
{
bool has_trailing_slash = false;
std::string rule_without_trailing_slash;
@@ -1096,20 +1299,85 @@ namespace crow
ruleObject->foreach_method([&](int method)
{
per_methods_[method].rules.emplace_back(ruleObject);
- per_methods_[method].trie.add(rule, per_methods_[method].rules.size() - 1);
+ per_methods_[method].trie.add(rule, per_methods_[method].rules.size() - 1, BP_index != INVALID_BP_ID ? blueprints[BP_index]->prefix().length() : 0, BP_index);
// directory case:
// request to '/about' url matches '/about/' rule
if (has_trailing_slash)
{
- per_methods_[method].trie.add(rule_without_trailing_slash, RULE_SPECIAL_REDIRECT_SLASH);
+ per_methods_[method].trie.add(rule_without_trailing_slash, RULE_SPECIAL_REDIRECT_SLASH, BP_index != INVALID_BP_ID ? blueprints_[BP_index]->prefix().length() : 0, BP_index);
}
});
}
+ void register_blueprint(Blueprint& blueprint)
+ {
+ if (std::find(blueprints_.begin(), blueprints_.end(), &blueprint) == blueprints_.end())
+ {
+ blueprints_.emplace_back(&blueprint);
+ }
+ else
+ throw std::runtime_error("blueprint \"" + blueprint.prefix_ + "\" already exists in router");
+ }
+
+ void get_recursive_child_methods(Blueprint* blueprint, std::vector& methods)
+ {
+ //we only need to deal with children if the blueprint has absolutely no methods (meaning its index won't be added to the trie)
+ if (blueprint->static_dir_.empty() && blueprint->all_rules_.empty())
+ {
+ for(Blueprint* bp : blueprint->blueprints_)
+ {
+ get_recursive_child_methods(bp, methods);
+ }
+ }
+ else if (!blueprint->static_dir_.empty())
+ methods.emplace_back(HTTPMethod::GET);
+ for (auto& rule: blueprint->all_rules_)
+ {
+ rule->foreach_method([&methods](unsigned method){
+ HTTPMethod method_final = static_cast(method);
+ if (std::find(methods.begin(), methods.end(), method_final) == methods.end())
+ methods.emplace_back(method_final);
+ });
+ }
+ }
+
+ void validate_bp(std::vector blueprints)
+ {
+ for (unsigned i = 0; i < blueprints.size(); i++)
+ {
+ Blueprint* blueprint = blueprints[i];
+ if (blueprint->static_dir_ == "" && blueprint->all_rules_.empty())
+ {
+ std::vector methods;
+ get_recursive_child_methods(blueprint, methods);
+ for (HTTPMethod x : methods)
+ {
+ int i = static_cast(x);
+ per_methods_[i].trie.add(blueprint->prefix(), 0, blueprint->prefix().length(), i);
+ }
+ }
+ for (auto& rule: blueprint->all_rules_)
+ {
+ if (rule)
+ {
+ auto upgraded = rule->upgrade();
+ if (upgraded)
+ rule = std::move(upgraded);
+ rule->validate();
+ internal_add_rule_object(rule->rule(), rule.get(), i, blueprints);
+ }
+ }
+ validate_bp(blueprint->blueprints_);
+ }
+ }
+
void validate()
{
+ //Take all the routes from the registered blueprints and add them to `all_rules_` to be processed.
+ validate_bp(blueprints_);
+
for(auto& rule:all_rules_)
{
if (rule)
@@ -1118,7 +1386,7 @@ namespace crow
if (upgraded)
rule = std::move(upgraded);
rule->validate();
- internal_add_rule_object(rule->rule(), rule.get());
+ internal_add_rule_object(rule->rule(), rule.get(), INVALID_BP_ID, blueprints_);
}
}
for(auto& per_method:per_methods_)
@@ -1136,13 +1404,13 @@ namespace crow
auto& per_method = per_methods_[static_cast(req.method)];
auto& rules = per_method.rules;
- unsigned rule_index = per_method.trie.find(req.url).first;
+ unsigned rule_index = std::get<0>(per_method.trie.find(req.url));
if (!rule_index)
{
for (auto& per_method: per_methods_)
{
- if (per_method.trie.find(req.url).first)
+ if (std::get<0>(per_method.trie.find(req.url)))
{
CROW_LOG_DEBUG << "Cannot match method " << req.url << " " << method_name(req.method);
res = response(405);
@@ -1201,6 +1469,51 @@ namespace crow
}
}
+ void get_found_bp(std::vector& bp_i, std::vector& blueprints, std::vector& found_bps, uint16_t index = 0)
+ {
+ // This statement makes 3 assertions:
+ // 1. The index is above 0.
+ // 2. The index does not lie outside the given blueprint list.
+ // 3. The next blueprint we're adding has a prefix that starts the same as the already added blueprint + a slash (the rest is irrelevant).
+ //
+ // This is done to prevent a blueprint that has a prefix of "bp_prefix2" to be assumed as a child of one that has "bp_prefix".
+ //
+ // If any of the assertions is untrue, we delete the last item added, and continue using the blueprint list of the blueprint found before, the topmost being the router's list
+ auto verify_prefix = [&bp_i, &index, &blueprints, &found_bps]()
+ {
+ return index > 0 &&
+ bp_i[index] < blueprints.size() &&
+ blueprints[bp_i[index]]->prefix().substr(0,found_bps[index-1]->prefix().length()+1)
+ .compare(std::string(found_bps[index-1]->prefix()+'/')) == 0;
+ };
+ if (index < bp_i.size())
+ {
+
+ if (verify_prefix())
+ {
+ found_bps.push_back(blueprints[bp_i[index]]);
+ get_found_bp(bp_i, found_bps.back()->blueprints_, found_bps, ++index);
+ }
+ else
+ {
+ if (!found_bps.empty())
+ found_bps.pop_back();
+
+ if (found_bps.empty())
+ {
+ found_bps.push_back(blueprints_[bp_i[index]]);
+ get_found_bp(bp_i, found_bps.back()->blueprints_, found_bps, ++index);
+ }
+ else
+ {
+ Blueprint* last_element = found_bps.back();
+ found_bps.push_back(last_element->blueprints_[bp_i[index]]);
+ get_found_bp(bp_i, found_bps.back()->blueprints_, found_bps, ++index);
+ }
+ }
+ }
+ }
+
void handle(const request& req, response& res)
{
HTTPMethod method_actual = req.method;
@@ -1235,7 +1548,7 @@ namespace crow
{
for(int i = 0; i < static_cast(HTTPMethod::InternalMethodCount); i ++)
{
- if (per_methods_[i].trie.find(req.url).first)
+ if (std::get<0>(per_methods_[i].trie.find(req.url)))
{
allow += method_name(static_cast(i)) + ", ";
}
@@ -1265,13 +1578,13 @@ namespace crow
auto found = trie.find(req.url);
- unsigned rule_index = found.first;
+ unsigned rule_index = std::get<0>(found);
if (!rule_index)
{
for (auto& per_method: per_methods_)
{
- if (per_method.trie.find(req.url).first)
+ if (std::get<0>(per_method.trie.find(req.url)))
{
CROW_LOG_DEBUG << "Cannot match method " << req.url << " " << method_name(method_actual);
res = response(405);
@@ -1280,15 +1593,32 @@ namespace crow
}
}
- if (catchall_rule_.has_handler())
+ std::vector bps_found;
+ get_found_bp(std::get<1>(found), blueprints_, bps_found);
+ bool no_bp_catchall = true;
+ for (int i = bps_found.size()-1; i > 0; i--)
{
- CROW_LOG_DEBUG << "Cannot match rules " << req.url << ". Redirecting to Catchall rule";
- catchall_rule_.handler_(req, res);
+ std::vector bpi = std::get<1>(found);
+ if (bps_found[i]->catchall_rule().has_handler())
+ {
+ no_bp_catchall = false;
+ CROW_LOG_DEBUG << "Cannot match rules " << req.url << ". Redirecting to Blueprint \"" << bps_found[i]->prefix() << "\" Catchall rule";
+ bps_found[i]->catchall_rule().handler_(req, res);
+ break;
+ }
}
- else
+ if (no_bp_catchall)
{
- 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 global Catchall rule";
+ catchall_rule_.handler_(req, res);
+ }
+ else
+ {
+ CROW_LOG_DEBUG << "Cannot match rules " << req.url;
+ res = response(404);
+ }
}
res.end();
return;
@@ -1320,7 +1650,7 @@ namespace crow
// any uncaught exceptions become 500s
try
{
- rules[rule_index]->handle(req, res, found.second);
+ rules[rule_index]->handle(req, res, std::get<2>(found));
}
catch(std::exception& e)
{
@@ -1347,6 +1677,11 @@ namespace crow
}
}
+ std::vector& blueprints()
+ {
+ return blueprints_;
+ }
+
private:
CatchallRule catchall_rule_;
@@ -1360,6 +1695,7 @@ namespace crow
};
std::array(HTTPMethod::InternalMethodCount)> per_methods_;
std::vector> all_rules_;
+ std::vector blueprints_;
};
}
diff --git a/mkdocs.yml b/mkdocs.yml
index d3507a839..adac457e4 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -1,9 +1,9 @@
site_name: Crow
# Repository
-repo_name: crowcpp/crow
-repo_url: https://github.com/crowcpp/crow
-site_url: https://crowcpp.github.io/crow
+repo_name: CrowCpp/Crow
+repo_url: https://github.com/CrowCpp/Crow
+site_url: https://crowcpp.org
edit_uri: ""
theme:
@@ -41,6 +41,7 @@ nav:
- Middleware: guides/middleware.md
- SSL: guides/ssl.md
- Static Files: guides/static.md
+ - Blueprints: guides/blueprints.md
- Compression: guides/compression.md
- Websockets: guides/websockets.md
- Writing Tests: guides/testing.md
diff --git a/tests/img/cat.jpg b/tests/img/cat.jpg
index 2cd578ec1..b5fcec3bd 100644
Binary files a/tests/img/cat.jpg and b/tests/img/cat.jpg differ
diff --git a/tests/unittest.cpp b/tests/unittest.cpp
index 70d9fab58..22d2e038d 100644
--- a/tests/unittest.cpp
+++ b/tests/unittest.cpp
@@ -1418,14 +1418,15 @@ TEST_CASE("stream_response")
SimpleApp app;
+
+ std::string keyword_ = "hello";
+ std::string key_response;
+ for (unsigned int i = 0; i<250000; i++)
+ key_response += keyword_;
+
CROW_ROUTE(app, "/test")
- ([](const crow::request&, crow::response& res)
+ ([&key_response](const crow::request&, crow::response& res)
{
- std::string keyword_ = "hello";
- std::string key_response;
- for (unsigned int i = 0; i<250000; i++)
- key_response += keyword_;
-
res.body = key_response;
res.end();
});
@@ -1433,7 +1434,7 @@ TEST_CASE("stream_response")
app.validate();
//running the test on a separate thread to allow the client to sleep
- std::thread runTest([&app](){
+ std::thread runTest([&app, &key_response](){
auto _ = async(launch::async,
[&] { app.bindaddr(LOCALHOST_ADDRESS).port(45451).run(); });
@@ -1447,9 +1448,6 @@ TEST_CASE("stream_response")
{
asio::streambuf b;
- std::string keyword_ = "hello";
- std::string key_response;
-
asio::ip::tcp::socket c(is);
c.connect(asio::ip::tcp::endpoint(
asio::ip::address::from_string(LOCALHOST_ADDRESS), 45451));
@@ -1459,10 +1457,6 @@ TEST_CASE("stream_response")
static char buf[2048];
c.receive(asio::buffer(buf, 2048));
- //creating the string to compare against
- for (unsigned int i = 0; i<250000; i++)
- key_response += keyword_;
-
//"hello" is 5 bytes, (5*250000)/16384 = 76.2939
for (unsigned int i = 0; i<76; i++)
{
@@ -1887,3 +1881,115 @@ TEST_CASE("catchall")
CHECK(404 == res.code);
}
}
+
+TEST_CASE("blueprint")
+{
+ SimpleApp app;
+ crow::Blueprint bp("bp_prefix", "cstat", "ctemplate");
+ crow::Blueprint bp_not_sub("bp_prefix_second");
+ crow::Blueprint sub_bp("bp2", "csstat", "cstemplate");
+ crow::Blueprint sub_sub_bp("bp3");
+
+ CROW_BP_ROUTE(sub_bp, "/hello")
+ ([]() {
+ return "Hello world!";
+ });
+
+ CROW_BP_ROUTE(bp_not_sub, "/hello")
+ ([]() {
+ return "Hello world!";
+ });
+
+ CROW_BP_ROUTE(sub_sub_bp, "/hi")
+ ([]() {
+ return "Hi world!";
+ });
+
+ CROW_BP_CATCHALL_ROUTE(sub_bp)([](){return response(200, "WRONG!!");});
+
+ app. register_blueprint(bp);
+ app. register_blueprint(bp_not_sub);
+ bp. register_blueprint(sub_bp);
+ sub_bp.register_blueprint(sub_sub_bp);
+
+ app.validate();
+
+ {
+ request req;
+ response res;
+
+ req.url = "/bp_prefix/bp2/hello";
+
+ app.handle(req, res);
+
+ CHECK("Hello world!" == res.body);
+ }
+
+ {
+ request req;
+ response res;
+
+ req.url = "/bp_prefix_second/hello";
+
+ app.handle(req, res);
+
+ CHECK("Hello world!" == res.body);
+ }
+
+ {
+ request req;
+ response res;
+
+ req.url = "/bp_prefix/bp2/bp3/hi";
+
+ app.handle(req, res);
+
+ CHECK("Hi world!" == res.body);
+ }
+
+ {
+ request req;
+ response res;
+
+ req.url = "/bp_prefix/nonexistent";
+
+ app.handle(req, res);
+
+ CHECK(404 == res.code);
+ }
+
+ {
+ request req;
+ response res;
+
+ req.url = "/bp_prefix_second/nonexistent";
+
+ app.handle(req, res);
+
+ CHECK(404 == res.code);
+ }
+
+ {
+ request req;
+ response res;
+
+ req.url = "/bp_prefix/bp2/nonexistent";
+
+ app.handle(req, res);
+
+ CHECK(200 == res.code);
+ CHECK("WRONG!!" == res.body);
+ }
+
+ {
+ request req;
+ response res;
+
+ req.url = "/bp_prefix/bp2/bp3/nonexistent";
+
+ app.handle(req, res);
+
+ CHECK(200 == res.code);
+ CHECK("WRONG!!" == res.body);
+ }
+}