From 0542fdb8e43930104b834f18e78a3441bbc75307 Mon Sep 17 00:00:00 2001 From: Nikolas Date: Thu, 28 Jan 2021 23:19:11 +0100 Subject: [PATCH] Add exception handler (#845) * Add exception handler * revert content reader changes * Add test for and fix exception handler * Fix warning in test * Readd exception test, improve readme note, don't rethrow errors, remove exception handler response --- README.md | 13 +++++++++++ httplib.h | 64 +++++++++++++++++++++++++++++++++------------------- test/test.cc | 36 +++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 4c3dc2f..45a0e12 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,19 @@ svr.set_error_handler([](const auto& req, auto& res) { }); ``` +### Exception handler +The exception handler gets called if a user routing handler throws an error. + +```cpp +svr.set_exception_handler([](const auto& req, auto& res, std::exception &e) { + res.status = 500; + auto fmt = "

Error 500

%s

"; + char buf[BUFSIZ]; + snprintf(buf, sizeof(buf), fmt, e.what()); + res.set_content(buf, "text/html"); +}); +``` + ### Pre routing handler ```cpp diff --git a/httplib.h b/httplib.h index bcc5d5f..3a65144 100644 --- a/httplib.h +++ b/httplib.h @@ -598,6 +598,9 @@ class Server { public: using Handler = std::function; + using ExceptionHandler = + std::function; + enum class HandlerResponse { Handled, Unhandled, @@ -652,6 +655,7 @@ public: Server &set_error_handler(HandlerWithResponse handler); Server &set_error_handler(Handler handler); + Server &set_exception_handler(ExceptionHandler handler); Server &set_pre_routing_handler(HandlerWithResponse handler); Server &set_post_routing_handler(Handler handler); @@ -762,6 +766,7 @@ private: HandlersForContentReader delete_handlers_for_content_reader_; Handlers options_handlers_; HandlerWithResponse error_handler_; + ExceptionHandler exception_handler_; HandlerWithResponse pre_routing_handler_; Handler post_routing_handler_; Logger logger_; @@ -4281,6 +4286,11 @@ inline Server &Server::set_error_handler(Handler handler) { return *this; } +inline Server &Server::set_exception_handler(ExceptionHandler handler) { + exception_handler_ = std::move(handler); + return *this; +} + inline Server &Server::set_pre_routing_handler(HandlerWithResponse handler) { pre_routing_handler_ = std::move(handler); return *this; @@ -4785,26 +4795,26 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm) { if (req.method == "POST") { if (dispatch_request_for_content_reader( - req, res, std::move(reader), - post_handlers_for_content_reader_)) { + req, res, std::move(reader), + post_handlers_for_content_reader_)) { return true; } } else if (req.method == "PUT") { if (dispatch_request_for_content_reader( - req, res, std::move(reader), - put_handlers_for_content_reader_)) { + req, res, std::move(reader), + put_handlers_for_content_reader_)) { return true; } } else if (req.method == "PATCH") { if (dispatch_request_for_content_reader( - req, res, std::move(reader), - patch_handlers_for_content_reader_)) { + req, res, std::move(reader), + patch_handlers_for_content_reader_)) { return true; } } else if (req.method == "DELETE") { if (dispatch_request_for_content_reader( - req, res, std::move(reader), - delete_handlers_for_content_reader_)) { + req, res, std::move(reader), + delete_handlers_for_content_reader_)) { return true; } } @@ -4835,22 +4845,14 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm) { inline bool Server::dispatch_request(Request &req, Response &res, const Handlers &handlers) { - try { - for (const auto &x : handlers) { - const auto &pattern = x.first; - const auto &handler = x.second; + for (const auto &x : handlers) { + const auto &pattern = x.first; + const auto &handler = x.second; - if (std::regex_match(req.path, req.matches, pattern)) { - handler(req, res); - return true; - } + if (std::regex_match(req.path, req.matches, pattern)) { + handler(req, res); + return true; } - } catch (const std::exception &ex) { - res.status = 500; - res.set_header("EXCEPTION_WHAT", ex.what()); - } catch (...) { - res.status = 500; - res.set_header("EXCEPTION_WHAT", "UNKNOWN"); } return false; } @@ -5064,7 +5066,23 @@ Server::process_request(Stream &strm, bool close_connection, } // Rounting - if (routing(req, res, strm)) { + bool routed = false; + try { + routed = routing(req, res, strm); + } catch (std::exception & e) { + if (exception_handler_) { + exception_handler_(req, res, e); + routed = true; + } else { + res.status = 500; + res.set_header("EXCEPTION_WHAT", e.what()); + } + } catch (...) { + res.status = 500; + res.set_header("EXCEPTION_WHAT", "UNKNOWN"); + } + + if (routed) { if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; } return write_response_with_content(strm, close_connection, req, res); } else { diff --git a/test/test.cc b/test/test.cc index 9b0b9a5..6325cdc 100644 --- a/test/test.cc +++ b/test/test.cc @@ -6,6 +6,7 @@ #include #include #include +#include #define SERVER_CERT_FILE "./cert.pem" #define SERVER_CERT2_FILE "./cert2.pem" @@ -978,6 +979,41 @@ TEST(ErrorHandlerTest, ContentLength) { ASSERT_FALSE(svr.is_running()); } +TEST(ExceptionHandlerTest, ContentLength) { + Server svr; + + svr.set_exception_handler([](const Request & /*req*/, Response &res, std::exception & /*e*/) { + res.status = 500; + res.set_content("abcdefghijklmnopqrstuvwxyz", + "text/html"); // <= Content-Length still 13 + }); + + svr.Get("/hi", [](const Request & /*req*/, Response &res) { + res.set_content("Hello World!\n", "text/plain"); + throw std::runtime_error("abc"); + }); + + auto thread = std::thread([&]() { svr.listen(HOST, PORT); }); + + // Give GET time to get a few messages. + std::this_thread::sleep_for(std::chrono::seconds(1)); + + { + Client cli(HOST, PORT); + + auto res = cli.Get("/hi"); + ASSERT_TRUE(res); + EXPECT_EQ(500, res->status); + EXPECT_EQ("text/html", res->get_header_value("Content-Type")); + EXPECT_EQ("26", res->get_header_value("Content-Length")); + EXPECT_EQ("abcdefghijklmnopqrstuvwxyz", res->body); + } + + svr.stop(); + thread.join(); + ASSERT_FALSE(svr.is_running()); +} + TEST(NoContentTest, ContentLength) { Server svr;