Add configurable exception handler (#637)

* Added exception_handler()

* Fixed worker crash if exception thrown in catch-all handler
This commit is contained in:
Corentin Schreiber 2024-03-10 12:52:13 +00:00 committed by GitHub
parent c95e33ac0a
commit 049490c2c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 134 additions and 22 deletions

View File

@ -252,6 +252,24 @@ namespace crow
return *this;
}
/// Set the function to call to handle uncaught exceptions generated in routes (Default generates error 500).
///
/// The function must have the following signature: void(crow::response&).
/// It must set the response passed in argument to the function, which will be sent back to the client.
/// See Router::default_exception_handler() for the default implementation.
template<typename Func>
self_t& exception_handler(Func&& f)
{
router_.exception_handler() = std::forward<Func>(f);
return *this;
}
std::function<void(crow::response&)>& exception_handler()
{
return router_.exception_handler();
}
/// Set a custom duration and function to run on every tick
template<typename Duration, typename Func>
self_t& tick(Duration d, Func f)

View File

@ -1461,22 +1461,13 @@ namespace crow
CROW_LOG_DEBUG << "Matched rule (upgrade) '" << rules[rule_index]->rule_ << "' " << static_cast<uint32_t>(req.method) << " / " << rules[rule_index]->get_methods();
// any uncaught exceptions become 500s
try
{
rules[rule_index]->handle_upgrade(req, res, std::move(adaptor));
}
catch (std::exception& e)
{
CROW_LOG_ERROR << "An uncaught exception occurred: " << e.what();
res = response(500);
res.end();
return;
}
catch (...)
{
CROW_LOG_ERROR << "An uncaught exception occurred. The type was unknown so no information was available.";
res = response(500);
exception_handler_(res);
res.end();
return;
}
@ -1534,7 +1525,14 @@ namespace crow
std::vector<uint16_t> bpi = found.blueprint_indices;
if (bps_found[i]->catchall_rule().has_handler())
{
bps_found[i]->catchall_rule().handler_(req, res);
try
{
bps_found[i]->catchall_rule().handler_(req, res);
}
catch (...)
{
exception_handler_(res);
}
#ifdef CROW_ENABLE_DEBUG
return std::string("Redirected to Blueprint \"" + bps_found[i]->prefix() + "\" Catchall rule");
#else
@ -1544,7 +1542,14 @@ namespace crow
}
if (catchall_rule_.has_handler())
{
catchall_rule_.handler_(req, res);
try
{
catchall_rule_.handler_(req, res);
}
catch (...)
{
exception_handler_(res);
}
#ifdef CROW_ENABLE_DEBUG
return std::string("Redirected to global Catchall rule");
#else
@ -1704,23 +1709,14 @@ namespace crow
CROW_LOG_DEBUG << "Matched rule '" << rules[rule_index]->rule_ << "' " << static_cast<uint32_t>(req.method) << " / " << rules[rule_index]->get_methods();
// any uncaught exceptions become 500s
try
{
auto& rule = rules[rule_index];
handle_rule<App>(rule, req, res, found.r_params);
}
catch (std::exception& e)
{
CROW_LOG_ERROR << "An uncaught exception occurred: " << e.what();
res = response(500);
res.end();
return;
}
catch (...)
{
CROW_LOG_ERROR << "An uncaught exception occurred. The type was unknown so no information was available.";
res = response(500);
exception_handler_(res);
res.end();
return;
}
@ -1787,6 +1783,30 @@ namespace crow
return blueprints_;
}
std::function<void(crow::response&)>& exception_handler()
{
return exception_handler_;
}
static void default_exception_handler(response& res)
{
// any uncaught exceptions become 500s
res = response(500);
try
{
throw;
}
catch (const std::exception& e)
{
CROW_LOG_ERROR << "An uncaught exception occurred: " << e.what();
}
catch (...)
{
CROW_LOG_ERROR << "An uncaught exception occurred. The type was unknown so no information was available.";
}
}
private:
CatchallRule catchall_rule_;
@ -1802,5 +1822,6 @@ namespace crow
std::array<PerMethod, static_cast<int>(HTTPMethod::InternalMethodCount)> per_methods_;
std::vector<std::unique_ptr<BaseRule>> all_rules_;
std::vector<Blueprint*> blueprints_;
std::function<void(crow::response&)> exception_handler_ = &default_exception_handler;
};
} // namespace crow

View File

@ -3261,6 +3261,79 @@ TEST_CASE("blueprint")
}
} // blueprint
TEST_CASE("exception_handler")
{
SimpleApp app;
CROW_ROUTE(app, "/get_error")
([&]() -> std::string {
throw std::runtime_error("some error occurred");
});
CROW_ROUTE(app, "/get_no_error")
([&]() {
return "Hello world";
});
app.validate();
{
request req;
response res;
req.url = "/get_error";
app.handle_full(req, res);
CHECK(500 == res.code);
CHECK(res.body.empty());
}
{
request req;
response res;
req.url = "/get_no_error";
app.handle_full(req, res);
CHECK(200 == res.code);
CHECK(res.body.find("Hello world") != std::string::npos);
}
app.exception_handler([](crow::response& res) {
try
{
throw;
}
catch (const std::exception& e)
{
res = response(501, e.what());
}
});
{
request req;
response res;
req.url = "/get_error";
app.handle_full(req, res);
CHECK(501 == res.code);
CHECK(res.body.find("some error occurred") != std::string::npos);
}
{
request req;
response res;
req.url = "/get_no_error";
app.handle_full(req, res);
CHECK(200 == res.code);
CHECK(res.body.find("some error occurred") == std::string::npos);
CHECK(res.body.find("Hello world") != std::string::npos);
}
} // exception_handler
TEST_CASE("base64")
{
unsigned char sample_bin[] = {0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e};