Merge pull request #946 from oatpp/better_error_handling

Better error handling
This commit is contained in:
Leonid Stryzhevskyi 2024-06-03 17:53:35 -07:00 committed by GitHub
commit 39b4e00bdc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 441 additions and 245 deletions

View File

@ -13,6 +13,7 @@ Contents:
- [Remapper](#remapper)
- [oatpp::web::mime::ContentMappers](#oatppwebmimecontentmappers)
- [New OATPP_LOGx format](#new-oatpp_logx-format)
- [Better error handling](#better-error-handling)
- [Restructuring](#restructuring)
@ -172,6 +173,66 @@ Instead of old formatting "%s", "%d", "%f" use "{}" for any variable type:
OATPP_LOGd("MyController", "User: name={}, age={}", user->name, user->age)
```
## Better error handling
Fixed error handling in the `ApiController` and `ConnectionHandler`.
The new recommended way to implement custom error handler:
```cpp
/**
* Extend the DefaultErrorHandling class by overriding the renderError() method instead of the handleError() method.
* You can still override the handleError() method, but in most cases, it isn't necessary.
*/
class ErrorHandler : public oatpp::web::server::handler::DefaultErrorHandler {
private:
/* mappers to map response according to 'Accept' header and available mappers */
std::shared_ptr<oatpp::web::mime::ContentMappers> m_mappers;
public:
ErrorHandler(const std::shared_ptr<oatpp::web::mime::ContentMappers>& mappers)
: m_mappers(mappers)
{}
std::shared_ptr<OutgoingResponse> renderError(const HttpServerErrorStacktrace& stacktrace) override {
/* create ErrorDto */
auto error = ErrorDto::createShared();
error->code = stacktrace.status.code;
error->stack = {}; // initialize stack as empty list
/* push all items of stacktrace */
for(auto& s : stacktrace.stack) {
error->stack->push_back(s);
}
/* get all variants of acceptable mime-types from request */
std::vector<oatpp::String> acceptable;
if(stacktrace.request) {
acceptable = stacktrace.request->getHeaderValues("Accept");
}
/* select mapper based on provided preferences */
auto mapper = m_mappers->selectMapper(acceptable);
if(!mapper) {
mapper = m_mappers->getDefaultMapper();
}
/* create response object */
auto response = ResponseFactory::createResponse(stacktrace.status, error, mapper);
/* put all error related headers */
for(const auto& pair : stacktrace.headers.getAll()) {
response->putHeader(pair.first.toString(), pair.second.toString());
}
/* return response */
return response;
}
};
```
## Restructuring

View File

@ -289,6 +289,8 @@ add_library(oatpp
oatpp/web/server/HttpRequestHandler.hpp
oatpp/web/server/HttpRouter.cpp
oatpp/web/server/HttpRouter.hpp
oatpp/web/server/HttpServerError.cpp
oatpp/web/server/HttpServerError.hpp
oatpp/web/server/api/ApiController.cpp
oatpp/web/server/api/ApiController.hpp
oatpp/web/server/api/Endpoint.cpp

View File

@ -260,7 +260,7 @@ Action CoroutineHandle::takeAction(Action&& action) {
_CP = action.m_data.coroutine;
_FP = &AbstractCoroutine::act;
action.m_type = Action::TYPE_NONE;
return std::forward<oatpp::async::Action>(action);
return std::move(action);
}
case Action::TYPE_FINISH: {
@ -277,7 +277,7 @@ Action CoroutineHandle::takeAction(Action&& action) {
case Action::TYPE_YIELD_TO: {
_FP = action.m_data.fptr;
//break;
return std::forward<oatpp::async::Action>(action);
return std::move(action);
}
// case Action::TYPE_REPEAT: {
@ -289,7 +289,12 @@ Action CoroutineHandle::takeAction(Action&& action) {
// }
case Action::TYPE_ERROR: {
Action newAction = _CP->handleError(action.m_data.error);
Action newAction;
try {
newAction = _CP->handleError(action.m_data.error);
} catch (...) {
newAction = new Error(std::current_exception());
}
if (newAction.m_type == Action::TYPE_ERROR) {
AbstractCoroutine* savedCP = _CP;
@ -303,7 +308,7 @@ Action CoroutineHandle::takeAction(Action&& action) {
if(_CP == nullptr) {
delete action.m_data.error;
action.m_type = Action::TYPE_NONE;
return std::forward<oatpp::async::Action>(action);
return std::move(action);
}
} else {
action = std::move(newAction);
@ -313,7 +318,7 @@ Action CoroutineHandle::takeAction(Action&& action) {
}
default:
return std::forward<oatpp::async::Action>(action);
return std::move(action);
}
@ -329,10 +334,8 @@ Action CoroutineHandle::takeAction(Action&& action) {
Action CoroutineHandle::iterate() {
try {
return _CP->call(_FP);
} catch (std::exception& e) {
return new Error(e.what());
} catch (...) {
return new Error("[oatpp::async::CoroutineHandle::iterate()]: Error. Unknown Exception.");
return new Error(std::current_exception());
}
}

View File

@ -26,8 +26,23 @@
namespace oatpp { namespace async {
Error::Error(const std::string& what)
: runtime_error(what)
{}
Error::Error(const std::string& message)
: m_message(message)
{
}
Error::Error(const std::exception_ptr& exceptionPtr)
: m_message("exception_ptr")
, m_exceptionPtr(exceptionPtr)
{
}
const std::string& Error::what() const {
return m_message;
}
const std::exception_ptr& Error::getExceptionPtr() const {
return m_exceptionPtr;
}
}}

View File

@ -26,21 +26,24 @@
#define oatpp_async_Error_hpp
#include "oatpp/base/Countable.hpp"
#include <string>
namespace oatpp { namespace async {
/**
* Class to hold and communicate errors between Coroutines
*/
class Error : public std::runtime_error, public oatpp::base::Countable {
class Error : public oatpp::base::Countable {
private:
std::string m_message;
std::exception_ptr m_exceptionPtr;
public:
/**
* Constructor.
* @param what - error explanation.
*/
explicit Error(const std::string& what);
explicit Error(const std::string& message);
explicit Error(const std::exception_ptr& exceptionPtr);
const std::exception_ptr& getExceptionPtr() const;
const std::string& what() const;
/**
* Check if error belongs to specified class.

View File

@ -123,7 +123,7 @@ public:
* Get error info.
* @return - error info.
*/
Info getInfo() {
Info getInfo() const {
return m_info;
}

View File

@ -419,35 +419,32 @@ public:
/**
* Constructor.
* @param info
* @param message
* @param info - Status object
* @param message - Error message
* @param headers - additional headers recommended for error response.
*/
HttpError(const Info& info, const oatpp::String& message)
HttpError(const Info& info,
const oatpp::String& message,
const Headers& headers = {})
: protocol::ProtocolError<Status>(info, message)
, m_headers(headers)
{}
/**
* Constructor.
* @param status
* @param message
* @param status - Status object
* @param message - Error message
* @param headers - additional headers recommended for error response.
*/
HttpError(const Status& status, const oatpp::String& message)
: protocol::ProtocolError<Status>(Info(0, status), message)
{}
/**
* Constructor.
* @param status
* @param message
* @param headers
*/
HttpError(const Status& status, const oatpp::String& message, const Headers& headers)
HttpError(const Status& status,
const oatpp::String& message,
const Headers& headers = {})
: protocol::ProtocolError<Status>(Info(0, status), message)
, m_headers(headers)
{}
/**
* Get headers
* Get headers recommended for error response.
* @return
*/
const Headers& getHeaders() const {
@ -463,7 +460,7 @@ public:
* @param MESSAGE - String message.
*/
#define OATPP_ASSERT_HTTP(COND, STATUS, MESSAGE) \
if(!(COND)) { throw oatpp::web::protocol::http::HttpError(STATUS, MESSAGE); }
if(!(COND)) { throw oatpp::web::protocol::http::HttpError(STATUS, MESSAGE, {}); }
/**
* Collection of HTTP Header constants.

View File

@ -90,7 +90,7 @@ std::shared_ptr<AsyncHttpConnectionHandler> AsyncHttpConnectionHandler::createSh
void AsyncHttpConnectionHandler::setErrorHandler(const std::shared_ptr<handler::ErrorHandler>& errorHandler){
m_components->errorHandler = errorHandler;
if(!m_components->errorHandler) {
m_components->errorHandler = handler::DefaultErrorHandler::createShared();
m_components->errorHandler = std::make_shared<handler::DefaultErrorHandler>();
}
}

View File

@ -78,7 +78,7 @@ std::shared_ptr<HttpConnectionHandler> HttpConnectionHandler::createShared(const
void HttpConnectionHandler::setErrorHandler(const std::shared_ptr<handler::ErrorHandler>& errorHandler){
m_components->errorHandler = errorHandler;
if(!m_components->errorHandler) {
m_components->errorHandler = handler::DefaultErrorHandler::createShared();
m_components->errorHandler = std::make_shared<handler::DefaultErrorHandler>();
}
}

View File

@ -24,6 +24,7 @@
#include "HttpProcessor.hpp"
#include "oatpp/web/server/HttpServerError.hpp"
#include "oatpp/web/protocol/http/incoming/SimpleBodyDecoder.hpp"
#include "oatpp/data/stream/BufferStream.hpp"
@ -52,7 +53,7 @@ HttpProcessor::Components::Components(const std::shared_ptr<HttpRouter>& pRouter
: Components(pRouter,
nullptr,
std::make_shared<oatpp::web::protocol::http::incoming::SimpleBodyDecoder>(),
handler::DefaultErrorHandler::createShared(),
std::make_shared<handler::DefaultErrorHandler>(),
{},
{},
std::make_shared<Config>())
@ -62,7 +63,7 @@ HttpProcessor::Components::Components(const std::shared_ptr<HttpRouter>& pRouter
: Components(pRouter,
nullptr,
std::make_shared<oatpp::web::protocol::http::incoming::SimpleBodyDecoder>(),
handler::DefaultErrorHandler::createShared(),
std::make_shared<handler::DefaultErrorHandler>(),
{},
{},
pConfig)
@ -89,33 +90,33 @@ HttpProcessor::processNextRequest(ProcessingResources& resources,
std::shared_ptr<protocol::http::outgoing::Response> response;
try{
try {
try {
for(auto& interceptor : resources.components->requestInterceptors) {
for (auto &interceptor: resources.components->requestInterceptors) {
response = interceptor->intercept(request);
if(response) {
if (response) {
return response;
}
}
auto route = resources.components->router->getRoute(request->getStartingLine().method, request->getStartingLine().path);
if(!route) {
auto route = resources.components->router->getRoute(request->getStartingLine().method,
request->getStartingLine().path);
if (!route) {
data::stream::BufferOutputStream ss;
ss << "No mapping for HTTP-method: '" << request->getStartingLine().method.toString()
<< "', URL: '" << request->getStartingLine().path.toString() << "'";
connectionState = ConnectionState::CLOSING;
oatpp::web::protocol::http::HttpError error(protocol::http::Status::CODE_404, ss.toString());
auto ptr = std::make_exception_ptr(error);
return resources.components->errorHandler->handleError(ptr);
throw oatpp::web::protocol::http::HttpError(protocol::http::Status::CODE_404, ss.toString());
}
request->setPathVariables(route.getMatchMap());
return route.getEndpoint()->handle(request);
} catch (...) {
std::throw_with_nested(HttpServerError(request, "Error processing request"));
}
} catch (...) {
response = resources.components->errorHandler->handleError(std::current_exception());
connectionState = ConnectionState::CLOSING;
@ -139,9 +140,9 @@ HttpProcessor::ConnectionState HttpProcessor::processNextRequest(ProcessingResou
std::shared_ptr<protocol::http::outgoing::Response> response;
if(error.status.code != 0) {
oatpp::web::protocol::http::HttpError httpError(error.status, "Invalid Request Headers");
auto eptr = std::make_exception_ptr(httpError);
response = resources.components->errorHandler->handleError(eptr);
HttpServerError httpError(nullptr, "Invalid Request Headers");
auto ePtr = std::make_exception_ptr(httpError);
response = resources.components->errorHandler->handleError(ePtr);
connectionState = ConnectionState::CLOSING;
} else {
@ -153,18 +154,19 @@ HttpProcessor::ConnectionState HttpProcessor::processNextRequest(ProcessingResou
response = processNextRequest(resources, request, connectionState);
try {
try {
for (auto& interceptor : resources.components->responseInterceptors) {
for (auto &interceptor: resources.components->responseInterceptors) {
response = interceptor->intercept(request, response);
if (!response) {
oatpp::web::protocol::http::HttpError httpError(protocol::http::Status::CODE_500, "Response Interceptor returned an Invalid Response - 'null'");
auto eptr = std::make_exception_ptr(httpError);
response = resources.components->errorHandler->handleError(eptr);
connectionState = ConnectionState::CLOSING;
throw protocol::http::HttpError(protocol::http::Status::CODE_500, "Response Interceptor returned an Invalid Response - 'null'");
}
}
} catch (...) {
std::throw_with_nested(HttpServerError(request, "Error processing request during response interception"));
}
} catch (...) {
response = resources.components->errorHandler->handleError(std::current_exception());
connectionState = ConnectionState::CLOSING;
@ -284,6 +286,7 @@ HttpProcessor::Coroutine::Coroutine(const std::shared_ptr<Components>& component
, m_inStream(data::stream::InputStreamBufferedProxy::createShared(m_connection.object, std::make_shared<std::string>(data::buffer::IOBuffer::BUFFER_SIZE, 0)))
, m_connectionState(ConnectionState::ALIVE)
, m_taskListener(taskListener)
, m_shouldInterceptResponse(false)
{
m_taskListener->onTaskStart(m_connection);
}
@ -297,6 +300,7 @@ HttpProcessor::Coroutine::Action HttpProcessor::Coroutine::act() {
}
HttpProcessor::Coroutine::Action HttpProcessor::Coroutine::parseHeaders() {
m_shouldInterceptResponse = true;
return m_headersReader.readHeadersAsync(m_inStream).callbackTo(&HttpProcessor::Coroutine::onHeadersParsed);
}
@ -322,11 +326,11 @@ oatpp::async::Action HttpProcessor::Coroutine::onHeadersParsed(const RequestHead
data::stream::BufferOutputStream ss;
ss << "No mapping for HTTP-method: '" << headersReadResult.startingLine.method.toString()
<< "', URL: '" << headersReadResult.startingLine.path.toString() << "'";
oatpp::web::protocol::http::HttpError error(protocol::http::Status::CODE_404, ss.toString());
auto eptr = std::make_exception_ptr(error);
m_currentResponse = m_components->errorHandler->handleError(eptr);
m_connectionState = ConnectionState::CLOSING;
return yieldTo(&HttpProcessor::Coroutine::onResponseFormed);
throw oatpp::web::protocol::http::HttpError(protocol::http::Status::CODE_404, ss.toString());
// auto eptr = std::make_exception_ptr(error);
// m_currentResponse = m_components->errorHandler->handleError(eptr);
// m_connectionState = ConnectionState::CLOSING;
// return yieldTo(&HttpProcessor::Coroutine::onResponseFormed);
}
m_currentRequest->setPathVariables(m_currentRoute.getMatchMap());
@ -346,12 +350,16 @@ HttpProcessor::Coroutine::Action HttpProcessor::Coroutine::onResponse(const std:
HttpProcessor::Coroutine::Action HttpProcessor::Coroutine::onResponseFormed() {
for(auto& interceptor : m_components->responseInterceptors) {
if(m_shouldInterceptResponse) {
m_shouldInterceptResponse = false;
for (auto &interceptor: m_components->responseInterceptors) {
m_currentResponse = interceptor->intercept(m_currentRequest, m_currentResponse);
if(!m_currentResponse) {
oatpp::web::protocol::http::HttpError error(protocol::http::Status::CODE_500, "Response Interceptor returned an Invalid Response - 'null'");
auto eptr = std::make_exception_ptr(error);
m_currentResponse = m_components->errorHandler->handleError(eptr);
if (!m_currentResponse) {
throw oatpp::web::protocol::http::HttpError(protocol::http::Status::CODE_500,
"Response Interceptor returned an Invalid Response - 'null'");
//auto eptr = std::make_exception_ptr(error);
//m_currentResponse = m_components->errorHandler->handleError(eptr);
}
}
}
@ -424,15 +432,37 @@ HttpProcessor::Coroutine::Action HttpProcessor::Coroutine::handleError(Error* er
}
}
if(m_currentResponse) {
//OATPP_LOGe("[oatpp::web::server::HttpProcessor::Coroutine::handleError()]", "Unhandled error. '{}'. Dropping connection", error->what())
return error;
// if(m_currentResponse) {
// //OATPP_LOGe("[oatpp::web::server::HttpProcessor::Coroutine::handleError()]", "Unhandled error. '{}'. Dropping connection", error->what())
// return error;
// }
std::exception_ptr ePtr = error->getExceptionPtr();
if(!ePtr) {
ePtr = std::make_exception_ptr(*error);
}
oatpp::web::protocol::http::HttpError httpError(protocol::http::Status::CODE_500, error->what());
auto eptr = std::make_exception_ptr(httpError);
m_currentResponse = m_components->errorHandler->handleError(eptr);
try {
try {
std::rethrow_exception(ePtr);
} catch (...) {
std::throw_with_nested(HttpServerError(m_currentRequest, "Error processing async request"));
}
} catch (...) {
ePtr = std::current_exception();
m_currentResponse = m_components->errorHandler->handleError(ePtr);
if (m_currentResponse != nullptr) {
return yieldTo(&HttpProcessor::Coroutine::onResponseFormed);
}
}
return new async::Error(ePtr);
// oatpp::web::protocol::http::HttpError httpError(protocol::http::Status::CODE_500, error->what());
// auto eptr = std::make_exception_ptr(httpError);
// m_currentResponse = m_components->errorHandler->handleError(eptr);
// return yieldTo(&HttpProcessor::Coroutine::onResponseFormed);
}

View File

@ -264,6 +264,8 @@ public:
std::shared_ptr<protocol::http::incoming::Request> m_currentRequest;
std::shared_ptr<protocol::http::outgoing::Response> m_currentResponse;
TaskProcessingListener* m_taskListener;
private:
bool m_shouldInterceptResponse;
public:
/**

View File

@ -87,7 +87,7 @@ public:
*/
virtual std::shared_ptr<OutgoingResponse> handle(const std::shared_ptr<IncomingRequest>& request) {
(void)request;
throw HttpError(Status::CODE_501, "Endpoint not implemented.");
throw HttpError(Status::CODE_501, "Endpoint not implemented.", {});
}
/**
@ -99,7 +99,7 @@ public:
virtual oatpp::async::CoroutineStarterForResult<const std::shared_ptr<OutgoingResponse>&>
handleAsync(const std::shared_ptr<IncomingRequest>& request) {
(void)request;
throw HttpError(Status::CODE_501, "Asynchronous endpoint not implemented.");
throw HttpError(Status::CODE_501, "Asynchronous endpoint not implemented.", {});
}
/**

View File

@ -0,0 +1,38 @@
/***************************************************************************
*
* Project _____ __ ____ _ _
* ( _ ) /__\ (_ _)_| |_ _| |_
* )(_)( /(__)\ )( (_ _)(_ _)
* (_____)(__)(__)(__) |_| |_|
*
*
* Copyright 2018-present, Leonid Stryzhevskyi <lganzzzo@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************/
#include "HttpServerError.hpp"
namespace oatpp { namespace web { namespace server {
HttpServerError::HttpServerError(const std::shared_ptr<protocol::http::incoming::Request>& request, const std::string& message)
: std::runtime_error(message)
, m_request(request)
{}
const std::shared_ptr<protocol::http::incoming::Request>& HttpServerError::getRequest() const {
return m_request;
}
}}}

View File

@ -0,0 +1,57 @@
/***************************************************************************
*
* Project _____ __ ____ _ _
* ( _ ) /__\ (_ _)_| |_ _| |_
* )(_)( /(__)\ )( (_ _)(_ _)
* (_____)(__)(__)(__) |_| |_|
*
*
* Copyright 2018-present, Leonid Stryzhevskyi <lganzzzo@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************/
#ifndef oatpp_web_server_HttpServerError_hpp
#define oatpp_web_server_HttpServerError_hpp
#include "oatpp/web/protocol/http/incoming/Request.hpp"
namespace oatpp { namespace web { namespace server {
/**
* HttpServerError
*/
class HttpServerError : public std::runtime_error {
private:
std::shared_ptr<protocol::http::incoming::Request> m_request;
public:
/**
* Constructor
* @param request
* @param message
*/
HttpServerError(const std::shared_ptr<protocol::http::incoming::Request>& request, const std::string& message);
/**
* Get request
* @return
*/
const std::shared_ptr<protocol::http::incoming::Request>& getRequest() const;
};
}}}
#endif //oatpp_web_server_HttpServerError_hpp

View File

@ -51,7 +51,7 @@ std::shared_ptr<ApiController::RequestHandler> ApiController::getEndpointHandler
void ApiController::setErrorHandler(const std::shared_ptr<handler::ErrorHandler>& errorHandler){
m_errorHandler = errorHandler;
if(!m_errorHandler) {
m_errorHandler = handler::DefaultErrorHandler::createShared();
m_errorHandler = std::make_shared<handler::DefaultErrorHandler>();
}
}
@ -82,7 +82,7 @@ std::shared_ptr<handler::AuthorizationObject> ApiController::handleDefaultAuthor
return m_defaultAuthorizationHandler->handleAuthorization(authHeader);
}
// If Authorization is not setup on the server then it's 500
throw oatpp::web::protocol::http::HttpError(Status::CODE_500, "Authorization is not setup.");
throw oatpp::web::protocol::http::HttpError(Status::CODE_500, "Authorization is not setup.", {});
}
const std::shared_ptr<mime::ContentMappers>& ApiController::getContentMappers() const {
@ -91,8 +91,7 @@ const std::shared_ptr<mime::ContentMappers>& ApiController::getContentMappers()
// Helper methods
std::shared_ptr<ApiController::OutgoingResponse> ApiController::createResponse(const Status& status,
const oatpp::String& str) const {
std::shared_ptr<ApiController::OutgoingResponse> ApiController::createResponse(const Status& status, const oatpp::String& str) const {
return ResponseFactory::createResponse(status, str);
}

View File

@ -31,6 +31,7 @@
#include "oatpp/web/server/handler/AuthorizationHandler.hpp"
#include "oatpp/web/server/handler/ErrorHandler.hpp"
#include "oatpp/web/server/handler/AuthorizationHandler.hpp"
#include "oatpp/web/server/HttpServerError.hpp"
#include "oatpp/web/protocol/http/incoming/Response.hpp"
#include "oatpp/web/protocol/http/outgoing/Request.hpp"
#include "oatpp/web/protocol/http/outgoing/ResponseFactory.hpp"
@ -238,10 +239,29 @@ protected:
}
async::Action handleError(async::Error* error) override {
auto eptr = std::make_exception_ptr(*error);
auto response = m_handler->m_controller->m_errorHandler->handleError(eptr);
std::exception_ptr ePtr = error->getExceptionPtr();
if(!ePtr) {
ePtr = std::make_exception_ptr(*error);
}
try {
try {
std::rethrow_exception(ePtr);
} catch (...) {
std::throw_with_nested(HttpServerError(m_request, "[ApiController]: Error processing async request"));
}
} catch (...) {
ePtr = std::current_exception();
auto response = m_handler->m_controller->handleError(ePtr);
if (response != nullptr) {
return this->_return(response);
}
}
return new async::Error(ePtr);
}
};
@ -265,20 +285,24 @@ protected:
if(m_method == nullptr) {
if(m_methodAsync == nullptr) {
throw protocol::http::HttpError(Status::CODE_500, "[ApiController]: Error. Handler method is nullptr.");
throw HttpServerError(request, "[ApiController]: Error. Handler method is nullptr");
}
throw protocol::http::HttpError(Status::CODE_500, "[ApiController]: Error. Non-async call to async endpoint.");
throw HttpServerError(request, "[ApiController]: Error. Non-async call to async endpoint");
}
try {
try {
return (m_controller->*m_method)(request);
} catch (...) {
auto response = m_controller->handleError(std::current_exception());
if(response != nullptr) {
std::throw_with_nested(HttpServerError(request, "[ApiController]: Error processing request"));
}
} catch (...) {
auto ePtr = std::current_exception();
auto response = m_controller->handleError(ePtr);
if (response != nullptr) {
return response;
}
throw;
std::rethrow_exception(ePtr);
}
}
@ -288,16 +312,16 @@ protected:
if(m_methodAsync == nullptr) {
if(m_method == nullptr) {
throw oatpp::web::protocol::http::HttpError(Status::CODE_500, "[ApiController]: Error. Handler method is nullptr.");
throw HttpServerError(request, "[ApiController]: Error. Handler method is nullptr");
}
throw oatpp::web::protocol::http::HttpError(Status::CODE_500, "[ApiController]: Error. Async call to non-async endpoint.");
throw HttpServerError(request, "[ApiController]: Error. Async call to non-async endpoint");
}
if(m_controller->m_errorHandler) {
//if(m_controller->m_errorHandler) {
return ErrorHandlingCoroutine::startForResult(this, request);
}
//}
return (m_controller->*m_methodAsync)(request);
//return (m_controller->*m_methodAsync)(request);
}

View File

@ -23,58 +23,71 @@
***************************************************************************/
#include "./ErrorHandler.hpp"
#include "oatpp/web/server/HttpServerError.hpp"
#include "oatpp/web/protocol/http/outgoing/BufferBody.hpp"
namespace oatpp { namespace web { namespace server { namespace handler {
std::shared_ptr<protocol::http::outgoing::Response> ErrorHandler::handleError(const std::exception_ptr& exceptionPtr) {
void DefaultErrorHandler::unwrapErrorStack(HttpServerErrorStacktrace& stacktrace, const std::exception& e) {
std::shared_ptr<protocol::http::outgoing::Response> response;
stacktrace.stack.emplace_front(e.what());
if (auto httpServerError = dynamic_cast<const HttpServerError*>(std::addressof(e))) {
stacktrace.request = httpServerError->getRequest();
} else if (auto httpError = dynamic_cast<const protocol::http::HttpError*>(std::addressof(e))) {
for(auto& h : httpError->getHeaders().getAll_Unsafe()) {
stacktrace.headers.putIfNotExists(h.first.toString(), h.second.toString());
}
stacktrace.status = httpError->getInfo().status;
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
/* Default impl for backwards compatibility until the deprecated methods are removed */
try {
if(exceptionPtr) {
std::rethrow_exception(exceptionPtr);
}
} catch (protocol::http::HttpError& httpError) {
response = handleError(httpError.getInfo().status, httpError.getMessage(), httpError.getHeaders());
} catch (std::exception& error) {
response = handleError(protocol::http::Status::CODE_500, error.what());
std::rethrow_if_nested(e);
} catch (const std::exception& nestedException) {
unwrapErrorStack(stacktrace, nestedException);
} catch (...) {
response = handleError(protocol::http::Status::CODE_500, "An unknown error has occurred");
}
#pragma GCC diagnostic pop
return response;
}
std::shared_ptr<protocol::http::outgoing::Response> ErrorHandler::handleError(const protocol::http::Status& status, const oatpp::String& message) {
Headers headers;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
return handleError(status, message, headers);
#pragma GCC diagnostic pop
std::shared_ptr<protocol::http::outgoing::Response> DefaultErrorHandler::handleError(const std::exception_ptr& error) {
HttpServerErrorStacktrace stacktrace;
stacktrace.status = protocol::http::Status::CODE_500;
try {
if(error) {
std::rethrow_exception(error);
}
} catch (const std::exception& e) {
unwrapErrorStack(stacktrace, e);
} catch (...) {
}
return renderError(stacktrace);
}
std::shared_ptr<protocol::http::outgoing::Response>
DefaultErrorHandler::handleError(const oatpp::web::protocol::http::Status &status, const oatpp::String &message, const Headers& headers){
std::shared_ptr<protocol::http::outgoing::Response> DefaultErrorHandler::renderError(const HttpServerErrorStacktrace& stacktrace) {
data::stream::BufferOutputStream stream;
stream << "server=" << protocol::http::Header::Value::SERVER << "\n";
stream << "code=" << status.code << "\n";
stream << "description=" << status.description << "\n";
stream << "message=" << message << "\n";
stream << "code=" << stacktrace.status.code << "\n";
stream << "description=" << stacktrace.status.description << "\n";
stream << "stacktrace:\n";
for(auto& msg : stacktrace.stack) {
stream << " - " << msg << "\n";
}
auto response = protocol::http::outgoing::Response::createShared
(status, protocol::http::outgoing::BufferBody::createShared(stream.toString()));
(stacktrace.status, protocol::http::outgoing::BufferBody::createShared(stream.toString()));
response->putHeader(protocol::http::Header::SERVER, protocol::http::Header::Value::SERVER);
response->putHeader(protocol::http::Header::CONNECTION, protocol::http::Header::Value::CONNECTION_CLOSE);
response->putHeaderIfNotExists(protocol::http::Header::SERVER, protocol::http::Header::Value::SERVER);
response->putHeaderIfNotExists(protocol::http::Header::CONNECTION, protocol::http::Header::Value::CONNECTION_CLOSE);
for(const auto& pair : headers.getAll()) {
for(const auto& pair : stacktrace.headers.getAll()) {
response->putHeader_Unsafe(pair.first, pair.second);
}
@ -82,9 +95,4 @@ DefaultErrorHandler::handleError(const oatpp::web::protocol::http::Status &statu
}
std::shared_ptr<protocol::http::outgoing::Response>
DefaultErrorHandler::handleError(const std::exception_ptr& error) {
return ErrorHandler::handleError(error);
}
}}}}

View File

@ -25,6 +25,7 @@
#ifndef oatpp_web_server_handler_ErrorHandler_hpp
#define oatpp_web_server_handler_ErrorHandler_hpp
#include "oatpp/web/protocol/http/incoming/Request.hpp"
#include "oatpp/web/protocol/http/outgoing/Response.hpp"
#include "oatpp/web/protocol/http/Http.hpp"
@ -48,31 +49,10 @@ public:
/**
* Implement this method!
* @param error - &std::exception;.
* @param exceptionPtr - `std::exception_ptr`.
* @return - std::shared_ptr to &id:oatpp::web::protocol::http::outgoing::Response;.
*/
virtual std::shared_ptr<protocol::http::outgoing::Response> handleError(const std::exception_ptr& exceptionPtr);
/**
* Implement this method!
* @param status - &id:oatpp::web::protocol::http::Status;.
* @param message - &id:oatpp::String;.
* @param Headers - &id:oatpp::web::protocol::http::Headers;
* @return - std::shared_ptr to &id:oatpp::web::protocol::http::outgoing::Response;.
*/
[[deprecated]]
virtual
std::shared_ptr<protocol::http::outgoing::Response>
handleError(const protocol::http::Status& status, const oatpp::String& message, const Headers& headers) = 0;
/**
* Convenience method to call `handleError` method with no headers.
* @param status - &id:oatpp::web::protocol::http::Status;
* @param message - &id:oatpp::String;.
* @return - std::shared_ptr to &id:oatpp::web::protocol::http::outgoing::Response;.
*/
[[deprecated]]
std::shared_ptr<protocol::http::outgoing::Response> handleError(const protocol::http::Status& status, const oatpp::String& message);
virtual std::shared_ptr<protocol::http::outgoing::Response> handleError(const std::exception_ptr& exceptionPtr) = 0;
};
@ -81,30 +61,32 @@ public:
*/
class DefaultErrorHandler : public oatpp::base::Countable, public ErrorHandler {
public:
struct HttpServerErrorStacktrace {
std::shared_ptr<protocol::http::incoming::Request> request;
protocol::http::Status status;
Headers headers;
std::list<oatpp::String> stack;
};
private:
void unwrapErrorStack(HttpServerErrorStacktrace& stacktrace, const std::exception& e);
public:
/**
* Constructor.
*/
DefaultErrorHandler() = default;
public:
/**
* Create shared DefaultErrorHandler.
* @return - `std::shared_ptr` to DefaultErrorHandler.
*/
static std::shared_ptr<DefaultErrorHandler> createShared() {
return std::make_shared<DefaultErrorHandler>();
}
std::shared_ptr<protocol::http::outgoing::Response> handleError(const std::exception_ptr& error) override;
/**
* Implementation of &l:ErrorHandler::handleError ();
* @param status - &id:oatpp::web::protocol::http::Status;.
* @param message - &id:oatpp::String;.
* @return - &id:oatpp::web::protocol::http::outgoing::Response;.
* Reimplement this method for custom error rendering.
* Render error method.
* @param stacktrace
* @return
*/
std::shared_ptr<protocol::http::outgoing::Response>
handleError(const protocol::http::Status& status, const oatpp::String& message, const Headers& headers) override;
virtual std::shared_ptr<protocol::http::outgoing::Response> renderError(const HttpServerErrorStacktrace& stacktrace);
};

View File

@ -94,8 +94,6 @@ void runTests() {
OATPP_LOGd("Tests", "Map size={}", sizeof(std::unordered_map<oatpp::String, oatpp::String>))
OATPP_LOGd("Tests", "Tree size={}", sizeof(oatpp::data::mapping::Tree))
//return;
OATPP_LOGd("Tests", "coroutine handle size={}", sizeof(oatpp::async::CoroutineHandle))
OATPP_LOGd("Tests", "coroutine size={}", sizeof(oatpp::async::AbstractCoroutine))
OATPP_LOGd("Tests", "action size={}", sizeof(oatpp::async::Action))

View File

@ -347,10 +347,10 @@ void FullTest::onRun() {
OATPP_ASSERT(response->getStatusCode() == 400)
auto returnedData = response->readBodyToString();
OATPP_ASSERT(returnedData)
OATPP_ASSERT(returnedData == "server=oatpp/" OATPP_VERSION "\n"
"code=400\n"
"description=Bad Request\n"
"message=Missing valid body parameter 'body'\n")
// OATPP_ASSERT(returnedData == "server=oatpp/" OATPP_VERSION "\n"
// "code=400\n"
// "description=Bad Request\n"
// "message=Missing valid body parameter 'body'\n")
connection = client->getConnection();
}
@ -405,10 +405,10 @@ void FullTest::onRun() {
auto response = client->defaultBasicAuthorizationWithoutHeader();
OATPP_ASSERT(response->getStatusCode() == 401)
oatpp::String body = response->readBodyToString();
OATPP_ASSERT(body == "server=oatpp/" OATPP_VERSION "\n"
"code=401\n"
"description=Unauthorized\n"
"message=Authorization Required\n")
// OATPP_ASSERT(body == "server=oatpp/" OATPP_VERSION "\n"
// "code=401\n"
// "description=Unauthorized\n"
// "message=Authorization Required\n")
// should also add the WWW-Authenticate header when Authorization is missing
auto header = response->getHeader(oatpp::web::protocol::http::Header::WWW_AUTHENTICATE);
OATPP_ASSERT(header)
@ -424,10 +424,10 @@ void FullTest::onRun() {
auto response = client->customBasicAuthorizationWithoutHeader();
OATPP_ASSERT(response->getStatusCode() == 401)
oatpp::String body = response->readBodyToString();
OATPP_ASSERT(body == "server=oatpp/" OATPP_VERSION "\n"
"code=401\n"
"description=Unauthorized\n"
"message=Authorization Required\n")
// OATPP_ASSERT(body == "server=oatpp/" OATPP_VERSION "\n"
// "code=401\n"
// "description=Unauthorized\n"
// "message=Authorization Required\n")
// should also add the WWW-Authenticate header when Authorization is missing
auto header = response->getHeader(oatpp::web::protocol::http::Header::WWW_AUTHENTICATE);
OATPP_ASSERT(header)
@ -439,10 +439,10 @@ void FullTest::onRun() {
auto response = client->customBasicAuthorization("john:doe");
oatpp::String body = response->readBodyToString();
OATPP_ASSERT(response->getStatusCode() == 401)
OATPP_ASSERT(body == "server=oatpp/" OATPP_VERSION "\n"
"code=401\n"
"description=Unauthorized\n"
"message=Unauthorized\n")
// OATPP_ASSERT(body == "server=oatpp/" OATPP_VERSION "\n"
// "code=401\n"
// "description=Unauthorized\n"
// "message=Unauthorized\n")
// should also add the WWW-Authenticate header when Authorization is missing or wrong
auto header = response->getHeader(oatpp::web::protocol::http::Header::WWW_AUTHENTICATE);
OATPP_ASSERT(header)
@ -462,10 +462,10 @@ void FullTest::onRun() {
auto response = client->bearerAuthorization(token);
oatpp::String body = response->readBodyToString();
OATPP_ASSERT(response->getStatusCode() == 401)
OATPP_ASSERT(body == "server=oatpp/" OATPP_VERSION "\n"
"code=401\n"
"description=Unauthorized\n"
"message=Unauthorized\n")
// OATPP_ASSERT(body == "server=oatpp/" OATPP_VERSION "\n"
// "code=401\n"
// "description=Unauthorized\n"
// "message=Unauthorized\n")
// should also add the WWW-Authenticate header when Authorization is missing or wrong
auto header = response->getHeader(oatpp::web::protocol::http::Header::WWW_AUTHENTICATE);
OATPP_ASSERT(header)

View File

@ -95,6 +95,7 @@ public:
Action act() override {
auto param = request->getHeader("X-TEST-HEADER");
OATPP_ASSERT_HTTP(param, Status::CODE_400, "X-TEST-HEADER missing")
//OATPP_LOGv(TAG, "GET headers {X-TEST-HEADER: {}}", param)
auto dto = TestDto::createShared();
dto->testValue = param;

View File

@ -37,37 +37,13 @@ namespace oatpp { namespace test { namespace web { namespace app {
namespace http = oatpp::web::protocol::http;
/**
* Custom Error Handler.
*/
class CustomErrorHandler : public oatpp::base::Countable, public oatpp::web::server::handler::ErrorHandler {
class CustomErrorHandler : public oatpp::web::server::handler::DefaultErrorHandler {
public:
/**
* Constructor.
*/
CustomErrorHandler() = default;
public:
/**
* Create shared DefaultErrorHandler.
* @return - `std::shared_ptr` to DefaultErrorHandler.
*/
static std::shared_ptr<CustomErrorHandler> createShared() {
return std::make_shared<CustomErrorHandler>();
}
std::shared_ptr<http::outgoing::Response> handleError(const std::exception_ptr& error) override {
try {
std::rethrow_exception(error);
} catch(const std::runtime_error& e) {
return oatpp::web::protocol::http::outgoing::ResponseFactory::createResponse(http::Status::CODE_418, e.what());
} catch(...) {
throw;
}
}
std::shared_ptr<oatpp::web::protocol::http::outgoing::Response> handleError(const http::Status& status, const oatpp::String& message, const Headers& headers) override {
throw std::logic_error("Function not implemented");
std::shared_ptr<http::outgoing::Response> renderError(const HttpServerErrorStacktrace& stacktrace) override {
return oatpp::web::protocol::http::outgoing::ResponseFactory::createResponse(http::Status::CODE_418, stacktrace.stack.front());
}
};
@ -79,7 +55,7 @@ public:
explicit ControllerWithErrorHandler(const std::shared_ptr<ObjectMapper>& objectMapper)
: oatpp::web::server::api::ApiController(objectMapper)
{
setErrorHandler(CustomErrorHandler::createShared());
setErrorHandler(std::make_shared<CustomErrorHandler>());
}
public: