From 049490c2c99c6986ec7ae1530232ddb40d38d7b0 Mon Sep 17 00:00:00 2001 From: Corentin Schreiber Date: Sun, 10 Mar 2024 12:52:13 +0000 Subject: [PATCH] Add configurable exception handler (#637) * Added exception_handler() * Fixed worker crash if exception thrown in catch-all handler --- include/crow/app.h | 18 +++++++++++ include/crow/routing.h | 65 ++++++++++++++++++++++++------------- tests/unittest.cpp | 73 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 22 deletions(-) diff --git a/include/crow/app.h b/include/crow/app.h index d38697ed8..a91e992a6 100644 --- a/include/crow/app.h +++ b/include/crow/app.h @@ -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 + self_t& exception_handler(Func&& f) + { + router_.exception_handler() = std::forward(f); + return *this; + } + + std::function& exception_handler() + { + return router_.exception_handler(); + } + /// Set a custom duration and function to run on every tick template self_t& tick(Duration d, Func f) diff --git a/include/crow/routing.h b/include/crow/routing.h index e9ca5e560..3ec90b959 100644 --- a/include/crow/routing.h +++ b/include/crow/routing.h @@ -1461,22 +1461,13 @@ namespace crow CROW_LOG_DEBUG << "Matched rule (upgrade) '" << rules[rule_index]->rule_ << "' " << static_cast(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 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(req.method) << " / " << rules[rule_index]->get_methods(); - // any uncaught exceptions become 500s try { auto& rule = rules[rule_index]; handle_rule(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& 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(HTTPMethod::InternalMethodCount)> per_methods_; std::vector> all_rules_; std::vector blueprints_; + std::function exception_handler_ = &default_exception_handler; }; } // namespace crow diff --git a/tests/unittest.cpp b/tests/unittest.cpp index cded190bd..44e40573c 100644 --- a/tests/unittest.cpp +++ b/tests/unittest.cpp @@ -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};