Catch exceptions thrown by handlers (#773)

This commit is contained in:
Martin Chang 2021-03-28 10:56:23 +08:00 committed by GitHub
parent 564fc67649
commit a19d0427ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 168 additions and 41 deletions

View File

@ -160,6 +160,7 @@ set(DROGON_SOURCES
lib/src/DrTemplateBase.cc
lib/src/FiltersFunction.cc
lib/src/HttpAppFrameworkImpl.cc
lib/src/HttpBinder.cc
lib/src/HttpClientImpl.cc
lib/src/HttpControllersRouter.cc
lib/src/HttpFileImpl.cc

View File

@ -205,6 +205,12 @@ int main()
int)>
func = std::bind(&A::handle, &tmp, _1, _2, _3, _4, _5, _6);
app().registerHandler("/api/v1/handle4/{4:p4}/{3:p3}/{1:p1}", func);
app().registerHandler(
"/api/v1/this_will_fail",
[](const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) {
throw std::runtime_error("this should fail");
});
app().setDocumentRoot("./");
app().enableSession(60);

View File

@ -1303,6 +1303,27 @@ void doTest(const HttpClientPtr &client,
exit(1);
}
});
req = HttpRequest::newHttpRequest();
req->setMethod(drogon::Get);
req->setPath("/api/v1/this_will_fail");
client->sendRequest(
req, [req, isHttps](ReqResult result, const HttpResponsePtr &resp) {
if (result == ReqResult::Ok)
{
if (resp->getStatusCode() != k500InternalServerError)
{
LOG_DEBUG << resp->getStatusCode();
LOG_ERROR << "Error!";
exit(1);
}
outputGood(req, isHttps);
}
else
{
LOG_ERROR << "Error!";
exit(1);
}
});
#ifdef __cpp_impl_coroutine
// Test coroutine requests

View File

@ -56,6 +56,10 @@ std::string getGitCommit();
class HttpControllerBase;
class HttpSimpleControllerBase;
class WebSocketControllerBase;
using ExceptionHandler =
std::function<void(const std::exception &,
const HttpRequestPtr &,
std::function<void(const HttpResponsePtr &)> &&)>;
class HttpAppFramework : public trantor::NonCopyable
{
@ -1293,6 +1297,16 @@ class HttpAppFramework : public trantor::NonCopyable
*/
virtual bool reusePort() const = 0;
/**
* @brief handler will be called upon an exception escapes a request handler
*/
virtual void setExceptionHandler(ExceptionHandler handler) = 0;
/**
* @brief returns the excaption handler
*/
virtual const ExceptionHandler &getExceptionHandler() const = 0;
private:
virtual void registerHttpController(
const std::string &pathPattern,

View File

@ -88,6 +88,10 @@ T &getControllerObj()
return obj;
}
void handleException(const std::exception &,
const HttpRequestPtr &,
std::function<void(const HttpResponsePtr &)> &&);
using HttpBinderBasePtr = std::shared_ptr<HttpBinderBase>;
template <typename FUNCTION>
class HttpBinder : public HttpBinderBase
@ -236,12 +240,13 @@ class HttpBinder : public HttpBinderBase
pathArguments.pop_front();
try
{
getHandlerArgumentValue(value, std::move(v));
if (v.empty() == false)
getHandlerArgumentValue(value, std::move(v));
}
catch (...)
catch (const std::exception &e)
{
LOG_ERROR << "Error converting string \"" << v << "\" to the "
<< sizeof...(Values) + 1 << "th argument";
handleException(e, req, std::move(callback));
return;
}
}
else
@ -250,10 +255,15 @@ class HttpBinder : public HttpBinderBase
{
value = req->as<ValueType>();
}
catch (const std::exception &)
catch (const std::exception &e)
{
LOG_ERROR << "Error converting HttpRequest to the "
<< sizeof...(Values) + 1 << "th argument";
handleException(e, req, std::move(callback));
return;
}
catch (...)
{
LOG_ERROR << "Exception not derived from std::exception";
return;
}
}
@ -273,7 +283,19 @@ class HttpBinder : public HttpBinderBase
std::function<void(const HttpResponsePtr &)> &&callback,
Values &&... values)
{
callFunction(req, std::move(callback), std::move(values)...);
try
{
callFunction(req, callback, std::move(values)...);
}
catch (const std::exception &except)
{
handleException(except, req, std::move(callback));
}
catch (...)
{
LOG_ERROR << "Exception not derived from std::exception";
return;
}
}
#ifdef __cpp_impl_coroutine
template <typename... Values,
@ -294,16 +316,12 @@ class HttpBinder : public HttpBinderBase
if constexpr (std::is_same_v<AsyncTask,
typename traits::return_type>)
{
callFunction(req,
std::move(callback),
std::move(values)...);
callFunction(req, callback, std::move(values)...);
}
else if constexpr (std::is_same_v<Task<>,
typename traits::return_type>)
{
co_await callFunction(req,
std::move(callback),
std::move(values)...);
co_await callFunction(req, callback, std::move(values)...);
}
else if constexpr (std::is_same_v<Task<HttpResponsePtr>,
typename traits::return_type>)
@ -313,14 +331,13 @@ class HttpBinder : public HttpBinderBase
callback(std::move(resp));
}
}
catch (const std::exception &e)
catch (const std::exception &except)
{
LOG_ERROR << "Uncaught exception in " << req->path()
<< " what(): " << e.what();
handleException(except, req, std::move(callback));
}
catch (...)
{
LOG_ERROR << "Uncaught unknown exception in " << req->path();
LOG_ERROR << "Exception not derived from std::exception";
}
}(req, std::move(callback), std::move(values)...);
}

View File

@ -118,6 +118,17 @@ HttpResponsePtr defaultErrorHandler(HttpStatusCode code)
return std::make_shared<HttpResponseImpl>(code, CT_TEXT_HTML);
}
void defaultExceptionHandler(
const std::exception &e,
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
LOG_ERROR << "Unhandled exception in " << req->query()
<< ", what():" << e.what();
const auto &handler = app().getCustomErrorHandler();
callback(handler(k500InternalServerError));
}
static void godaemon()
{
printf("Initializing daemon mode\n");

View File

@ -29,6 +29,9 @@
namespace drogon
{
HttpResponsePtr defaultErrorHandler(HttpStatusCode code);
void defaultExceptionHandler(const std::exception &,
const HttpRequestPtr &,
std::function<void(const HttpResponsePtr &)> &&);
struct InitBeforeMainFunction
{
@ -505,6 +508,16 @@ class HttpAppFrameworkImpl final : public HttpAppFramework
return reusePort_;
}
void setExceptionHandler(ExceptionHandler handler) override
{
exceptionHandler_ = std::move(handler);
}
const ExceptionHandler &getExceptionHandler() const override
{
return exceptionHandler_;
}
private:
void registerHttpController(const std::string &pathPattern,
const internal::HttpBinderBasePtr &binder,
@ -626,6 +639,7 @@ class HttpAppFrameworkImpl final : public HttpAppFramework
postRoutingObservers_;
std::vector<std::function<void(const HttpRequestPtr &)>>
preHandlingObservers_;
ExceptionHandler exceptionHandler_{defaultExceptionHandler};
};
} // namespace drogon

29
lib/src/HttpBinder.cc Normal file
View File

@ -0,0 +1,29 @@
/**
*
* HttpBinder.h
* Martin Chang
*
* Copyright 2021, Martin Chang. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include <drogon/HttpBinder.h>
#include <drogon/HttpAppFramework.h>
namespace drogon
{
namespace internal
{
void handleException(const std::exception& e,
const HttpRequestPtr& req,
std::function<void(const HttpResponsePtr&)>&& callback)
{
app().getExceptionHandler()(e, req, std::move(callback));
}
} // namespace internal
} // namespace drogon

View File

@ -225,32 +225,46 @@ void HttpSimpleControllersRouter::doControllerHandler(
}
}
controller->asyncHandleHttpRequest(
req,
[this, req, callback = std::move(callback), &ctrlBinderPtr](
const HttpResponsePtr &resp) {
auto newResp = resp;
if (resp->expiredTime() >= 0 &&
resp->statusCode() != k404NotFound)
{
// cache the response;
static_cast<HttpResponseImpl *>(resp.get())
->makeHeaderString();
auto loop = req->getLoop();
try
{
controller->asyncHandleHttpRequest(
req,
[this, req, callback, &ctrlBinderPtr](
const HttpResponsePtr &resp) {
auto newResp = resp;
if (resp->expiredTime() >= 0 &&
resp->statusCode() != k404NotFound)
{
// cache the response;
static_cast<HttpResponseImpl *>(resp.get())
->makeHeaderString();
auto loop = req->getLoop();
if (loop->isInLoopThread())
{
ctrlBinderPtr->responseCache_.setThreadData(resp);
}
else
{
loop->queueInLoop([resp, &ctrlBinderPtr]() {
if (loop->isInLoopThread())
{
ctrlBinderPtr->responseCache_.setThreadData(resp);
});
}
else
{
loop->queueInLoop([resp, &ctrlBinderPtr]() {
ctrlBinderPtr->responseCache_.setThreadData(
resp);
});
}
}
}
invokeCallback(callback, req, newResp);
});
invokeCallback(callback, req, newResp);
});
}
catch (const std::exception &e)
{
app().getExceptionHandler()(e, req, std::move(callback));
return;
}
catch (...)
{
LOG_ERROR << "Exception not derived from std::exception";
return;
}
return;
}