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
This commit is contained in:
parent
78c474c744
commit
0542fdb8e4
13
README.md
13
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 = "<h1>Error 500</h1><p>%s</p>";
|
||||||
|
char buf[BUFSIZ];
|
||||||
|
snprintf(buf, sizeof(buf), fmt, e.what());
|
||||||
|
res.set_content(buf, "text/html");
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
### Pre routing handler
|
### Pre routing handler
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
|
64
httplib.h
64
httplib.h
@ -598,6 +598,9 @@ class Server {
|
|||||||
public:
|
public:
|
||||||
using Handler = std::function<void(const Request &, Response &)>;
|
using Handler = std::function<void(const Request &, Response &)>;
|
||||||
|
|
||||||
|
using ExceptionHandler =
|
||||||
|
std::function<void(const Request &, Response &, std::exception &e)>;
|
||||||
|
|
||||||
enum class HandlerResponse {
|
enum class HandlerResponse {
|
||||||
Handled,
|
Handled,
|
||||||
Unhandled,
|
Unhandled,
|
||||||
@ -652,6 +655,7 @@ public:
|
|||||||
|
|
||||||
Server &set_error_handler(HandlerWithResponse handler);
|
Server &set_error_handler(HandlerWithResponse handler);
|
||||||
Server &set_error_handler(Handler handler);
|
Server &set_error_handler(Handler handler);
|
||||||
|
Server &set_exception_handler(ExceptionHandler handler);
|
||||||
Server &set_pre_routing_handler(HandlerWithResponse handler);
|
Server &set_pre_routing_handler(HandlerWithResponse handler);
|
||||||
Server &set_post_routing_handler(Handler handler);
|
Server &set_post_routing_handler(Handler handler);
|
||||||
|
|
||||||
@ -762,6 +766,7 @@ private:
|
|||||||
HandlersForContentReader delete_handlers_for_content_reader_;
|
HandlersForContentReader delete_handlers_for_content_reader_;
|
||||||
Handlers options_handlers_;
|
Handlers options_handlers_;
|
||||||
HandlerWithResponse error_handler_;
|
HandlerWithResponse error_handler_;
|
||||||
|
ExceptionHandler exception_handler_;
|
||||||
HandlerWithResponse pre_routing_handler_;
|
HandlerWithResponse pre_routing_handler_;
|
||||||
Handler post_routing_handler_;
|
Handler post_routing_handler_;
|
||||||
Logger logger_;
|
Logger logger_;
|
||||||
@ -4281,6 +4286,11 @@ inline Server &Server::set_error_handler(Handler handler) {
|
|||||||
return *this;
|
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) {
|
inline Server &Server::set_pre_routing_handler(HandlerWithResponse handler) {
|
||||||
pre_routing_handler_ = std::move(handler);
|
pre_routing_handler_ = std::move(handler);
|
||||||
return *this;
|
return *this;
|
||||||
@ -4785,26 +4795,26 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm) {
|
|||||||
|
|
||||||
if (req.method == "POST") {
|
if (req.method == "POST") {
|
||||||
if (dispatch_request_for_content_reader(
|
if (dispatch_request_for_content_reader(
|
||||||
req, res, std::move(reader),
|
req, res, std::move(reader),
|
||||||
post_handlers_for_content_reader_)) {
|
post_handlers_for_content_reader_)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (req.method == "PUT") {
|
} else if (req.method == "PUT") {
|
||||||
if (dispatch_request_for_content_reader(
|
if (dispatch_request_for_content_reader(
|
||||||
req, res, std::move(reader),
|
req, res, std::move(reader),
|
||||||
put_handlers_for_content_reader_)) {
|
put_handlers_for_content_reader_)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (req.method == "PATCH") {
|
} else if (req.method == "PATCH") {
|
||||||
if (dispatch_request_for_content_reader(
|
if (dispatch_request_for_content_reader(
|
||||||
req, res, std::move(reader),
|
req, res, std::move(reader),
|
||||||
patch_handlers_for_content_reader_)) {
|
patch_handlers_for_content_reader_)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (req.method == "DELETE") {
|
} else if (req.method == "DELETE") {
|
||||||
if (dispatch_request_for_content_reader(
|
if (dispatch_request_for_content_reader(
|
||||||
req, res, std::move(reader),
|
req, res, std::move(reader),
|
||||||
delete_handlers_for_content_reader_)) {
|
delete_handlers_for_content_reader_)) {
|
||||||
return true;
|
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,
|
inline bool Server::dispatch_request(Request &req, Response &res,
|
||||||
const Handlers &handlers) {
|
const Handlers &handlers) {
|
||||||
try {
|
for (const auto &x : handlers) {
|
||||||
for (const auto &x : handlers) {
|
const auto &pattern = x.first;
|
||||||
const auto &pattern = x.first;
|
const auto &handler = x.second;
|
||||||
const auto &handler = x.second;
|
|
||||||
|
|
||||||
if (std::regex_match(req.path, req.matches, pattern)) {
|
if (std::regex_match(req.path, req.matches, pattern)) {
|
||||||
handler(req, res);
|
handler(req, res);
|
||||||
return true;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
@ -5064,7 +5066,23 @@ Server::process_request(Stream &strm, bool close_connection,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Rounting
|
// 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; }
|
if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; }
|
||||||
return write_response_with_content(strm, close_connection, req, res);
|
return write_response_with_content(strm, close_connection, req, res);
|
||||||
} else {
|
} else {
|
||||||
|
36
test/test.cc
36
test/test.cc
@ -6,6 +6,7 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <future>
|
#include <future>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
#define SERVER_CERT_FILE "./cert.pem"
|
#define SERVER_CERT_FILE "./cert.pem"
|
||||||
#define SERVER_CERT2_FILE "./cert2.pem"
|
#define SERVER_CERT2_FILE "./cert2.pem"
|
||||||
@ -978,6 +979,41 @@ TEST(ErrorHandlerTest, ContentLength) {
|
|||||||
ASSERT_FALSE(svr.is_running());
|
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) {
|
TEST(NoContentTest, ContentLength) {
|
||||||
Server svr;
|
Server svr;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user