From b7de6028c797177e8d8411f080307cc9faf936af Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Mon, 30 Jan 2017 14:08:36 +0000 Subject: Support for plugable error handlers --- icespider/core/core.cpp | 30 +++++++++++++++++- icespider/core/core.h | 12 +++++++ icespider/core/ihttpRequest.h | 5 +++ icespider/fcgi/cgiRequestBase.cpp | 36 +++++++++++++++++++++ icespider/fcgi/cgiRequestBase.h | 5 +++ icespider/testing/testRequest.cpp | 67 +++++++++++++++++++++++++++++++++------ icespider/testing/testRequest.h | 22 +++++++++---- icespider/unittests/testApp.cpp | 59 +++++++++++++++++++++++++++++++++- 8 files changed, 218 insertions(+), 18 deletions(-) diff --git a/icespider/core/core.cpp b/icespider/core/core.cpp index 9d187c9..3b3ec06 100644 --- a/icespider/core/core.cpp +++ b/icespider/core/core.cpp @@ -6,6 +6,7 @@ #include INSTANTIATEFACTORY(IceSpider::Plugin, Ice::CommunicatorPtr, Ice::PropertiesPtr); +INSTANTIATEPLUGINOF(IceSpider::ErrorHandler); namespace IceSpider { const boost::filesystem::path Core::defaultConfig("config/ice.properties"); @@ -69,13 +70,40 @@ namespace IceSpider { request->response(he.code, he.message); } catch (const std::exception & e) { - request->response(500, e.what()); + handleError(request, e); } catch (...) { request->response(500, "Unknown internal server error"); } } + void + Core::handleError(IHttpRequest * request, const std::exception & exception) const + { + auto errorHandlers = AdHoc::PluginManager::getDefault()->getAll(); + for (const auto & eh : errorHandlers) { + try { + switch (eh->implementation()->handleError(request, exception)) { + case ErrorHandlerResult_Handled: + return; + case ErrorHandlerResult_Unhandled: + continue; + case ErrorHandlerResult_Modified: + process(request, nullptr); + return; + } + } + catch (const HttpException & he) { + request->response(he.code, he.message); + return; + } + catch (...) { + std::cerr << "Error handler failed" << std::endl; + } + } + request->response(500, exception.what()); + } + Ice::ObjectPrx Core::getProxy(const char * type) const { diff --git a/icespider/core/core.h b/icespider/core/core.h index b1ea594..a5de08a 100644 --- a/icespider/core/core.h +++ b/icespider/core/core.h @@ -18,6 +18,7 @@ namespace IceSpider { virtual const IRouteHandler * findRoute(const IHttpRequest *) const = 0; void process(IHttpRequest *, const IRouteHandler * = nullptr) const; + void handleError(IHttpRequest *, const std::exception &) const; Ice::ObjectPrx getProxy(const char * type) const; @@ -49,6 +50,17 @@ namespace IceSpider { class DLL_PUBLIC Plugin : public virtual Ice::Object { }; typedef AdHoc::Factory PluginFactory; + + enum ErrorHandlerResult { + ErrorHandlerResult_Unhandled, + ErrorHandlerResult_Handled, + ErrorHandlerResult_Modified, + }; + class DLL_PUBLIC ErrorHandler : public AdHoc::AbstractPluginImplementation { + public: + virtual ErrorHandlerResult handleError(IHttpRequest * IHttpRequest, const std::exception &) const = 0; + }; + typedef AdHoc::PluginOf ErrorHandlerPlugin; } #endif diff --git a/icespider/core/ihttpRequest.h b/icespider/core/ihttpRequest.h index d92d549..b22406d 100644 --- a/icespider/core/ihttpRequest.h +++ b/icespider/core/ihttpRequest.h @@ -24,6 +24,7 @@ namespace IceSpider { Ice::Context getContext() const; virtual const PathElements & getRequestPath() const = 0; + virtual PathElements & getRequestPath() = 0; virtual HttpMethod getRequestMethod() const = 0; const std::string & getURLParam(unsigned int) const; @@ -31,6 +32,10 @@ namespace IceSpider { virtual OptionalString getHeaderParam(const std::string &) const = 0; virtual OptionalString getCookieParam(const std::string &) const = 0; virtual OptionalString getEnv(const std::string &) const = 0; + virtual void setQueryStringParam(const std::string &, const OptionalString &) = 0; + virtual void setHeaderParam(const std::string &, const OptionalString &) = 0; + virtual void setCookieParam(const std::string &, const OptionalString &) = 0; + virtual void setEnv(const std::string &, const OptionalString &) = 0; virtual Slicer::DeserializerPtr getDeserializer() const; virtual ContentTypeSerializer getSerializer(const IRouteHandler *) const; virtual std::istream & getInputStream() const = 0; diff --git a/icespider/fcgi/cgiRequestBase.cpp b/icespider/fcgi/cgiRequestBase.cpp index e820e05..f99bbf1 100644 --- a/icespider/fcgi/cgiRequestBase.cpp +++ b/icespider/fcgi/cgiRequestBase.cpp @@ -85,6 +85,12 @@ namespace IceSpider { return pathElements; } + PathElements & + CgiRequestBase::getRequestPath() + { + return pathElements; + } + HttpMethod CgiRequestBase::getRequestMethod() const { @@ -121,6 +127,36 @@ namespace IceSpider { return optionalLookup(("HTTP_" + boost::algorithm::to_upper_copy(key)).c_str(), envmap); } + void + CgiRequestBase::setQueryStringParam(const std::string & key, const OptionalString & val) + { + if (val) + qsmap[key] = val; + else + qsmap.erase(key); + } + + void + CgiRequestBase::setHeaderParam(const std::string &, const OptionalString &) + { + throw std::runtime_error("Changing the CGI environment is not supported."); + } + + void + CgiRequestBase::setCookieParam(const std::string & key, const OptionalString & val) + { + if (val) + cookiemap[key] = val; + else + cookiemap.erase(key); + } + + void + CgiRequestBase::setEnv(const std::string &, const OptionalString &) + { + throw std::runtime_error("Changing the CGI environment is not supported."); + } + void CgiRequestBase::response(short statusCode, const std::string & statusMsg) const { getOutputStream() diff --git a/icespider/fcgi/cgiRequestBase.h b/icespider/fcgi/cgiRequestBase.h index ec3d4dd..a7cc27d 100644 --- a/icespider/fcgi/cgiRequestBase.h +++ b/icespider/fcgi/cgiRequestBase.h @@ -22,11 +22,16 @@ namespace IceSpider { typedef std::map VarMap; const PathElements & getRequestPath() const override; + PathElements & getRequestPath() override; HttpMethod getRequestMethod() const override; OptionalString getQueryStringParam(const std::string & key) const override; OptionalString getHeaderParam(const std::string & key) const override; OptionalString getCookieParam(const std::string & key) const override; OptionalString getEnv(const std::string & key) const override; + void setQueryStringParam(const std::string &, const OptionalString &) override; + void setHeaderParam(const std::string &, const OptionalString &) override; + void setCookieParam(const std::string &, const OptionalString &) override; + void setEnv(const std::string &, const OptionalString &) override; void response(short, const std::string &) const override; void setHeader(const std::string &, const std::string &) const override; diff --git a/icespider/testing/testRequest.cpp b/icespider/testing/testRequest.cpp index 1645445..daecc49 100644 --- a/icespider/testing/testRequest.cpp +++ b/icespider/testing/testRequest.cpp @@ -14,40 +14,89 @@ namespace IceSpider { } } - const std::vector & + const PathElements & TestRequest::getRequestPath() const { return url; } + PathElements & + TestRequest::getRequestPath() + { + return url; + } + HttpMethod TestRequest::getRequestMethod() const { return method; } - IceUtil::Optional + OptionalString TestRequest::getEnv(const std::string & key) const { - return env.find(key) == env.end() ? IceUtil::Optional() : env.find(key)->second; + return get(key, env); } - IceUtil::Optional + OptionalString TestRequest::getQueryStringParam(const std::string & key) const { - return qs.find(key) == qs.end() ? IceUtil::Optional() : qs.find(key)->second; + return get(key, qs); } - IceUtil::Optional + OptionalString TestRequest::getCookieParam(const std::string & key) const { - return cookies.find(key) == cookies.end() ? IceUtil::Optional() : cookies.find(key)->second; + return get(key, cookies); } - IceUtil::Optional + OptionalString TestRequest::getHeaderParam(const std::string & key) const { - return hdr.find(key) == hdr.end() ? IceUtil::Optional() : hdr.find(key)->second; + return get(key, hdr); + } + + OptionalString + TestRequest::get(const std::string & key, const MapVars & vars) const + { + auto i = vars.find(key); + if (i == vars.end()) { + return IceUtil::None; + } + return i->second; + } + + void + TestRequest::setQueryStringParam(const std::string & key, const OptionalString & val) + { + set(key, val, qs); + } + + void + TestRequest::setHeaderParam(const std::string & key, const OptionalString & val) + { + set(key, val, hdr); + } + + void + TestRequest::setCookieParam(const std::string & key, const OptionalString & val) + { + set(key, val, cookies); + } + + void + TestRequest::setEnv(const std::string & key, const OptionalString & val) + { + set(key, val, env); + } + + void + TestRequest::set(const std::string & key, const OptionalString & val, MapVars & vars) + { + if (val) + vars[key] = *val; + else + vars.erase(key); } std::istream & diff --git a/icespider/testing/testRequest.h b/icespider/testing/testRequest.h index 3fd40f0..3278930 100644 --- a/icespider/testing/testRequest.h +++ b/icespider/testing/testRequest.h @@ -8,16 +8,20 @@ namespace IceSpider { class DLL_PUBLIC TestRequest : public IHttpRequest { public: typedef std::map MapVars; - typedef std::vector UrlVars; TestRequest(const Core * c, HttpMethod m, const std::string & p); - const std::vector & getRequestPath() const override; + const PathElements & getRequestPath() const override; + PathElements & getRequestPath() override; HttpMethod getRequestMethod() const override; - IceUtil::Optional getEnv(const std::string & key) const override; - IceUtil::Optional getQueryStringParam(const std::string & key) const override; - IceUtil::Optional getCookieParam(const std::string & key) const override; - IceUtil::Optional getHeaderParam(const std::string & key) const override; + OptionalString getEnv(const std::string & key) const override; + OptionalString getQueryStringParam(const std::string & key) const override; + OptionalString getCookieParam(const std::string & key) const override; + OptionalString getHeaderParam(const std::string & key) const override; + void setQueryStringParam(const std::string &, const OptionalString &) override; + void setHeaderParam(const std::string &, const OptionalString &) override; + void setCookieParam(const std::string &, const OptionalString &) override; + void setEnv(const std::string &, const OptionalString &) override; std::istream & getInputStream() const override; std::ostream & getOutputStream() const override; void response(short statusCode, const std::string & statusMsg) const override; @@ -25,7 +29,7 @@ namespace IceSpider { const MapVars & getResponseHeaders(); - UrlVars url; + PathElements url; MapVars qs; MapVars cookies; MapVars hdr; @@ -34,6 +38,10 @@ namespace IceSpider { mutable std::stringstream output; const HttpMethod method; + protected: + OptionalString get(const std::string &, const MapVars &) const; + void set(const std::string &, const OptionalString &, MapVars &); + private: MapVars responseHeaders; }; diff --git a/icespider/unittests/testApp.cpp b/icespider/unittests/testApp.cpp index 000f983..7a60875 100644 --- a/icespider/unittests/testApp.cpp +++ b/icespider/unittests/testApp.cpp @@ -154,6 +154,9 @@ class TestSerice : public TestIceSpider::TestApi { if (s == "error") { throw TestIceSpider::Ex("test error"); } + else if (s.length() == 3) { + throw TestIceSpider::Ex(s); + } BOOST_REQUIRE_EQUAL(s, "some value"); } @@ -491,7 +494,31 @@ BOOST_AUTO_TEST_CASE( testCookies ) BOOST_REQUIRE_EQUAL(h["Status"], "200 OK"); } -BOOST_AUTO_TEST_CASE( testErrorHandler ) +class DummyErrorHandler : public IceSpider::ErrorHandler { + public: + IceSpider::ErrorHandlerResult + handleError(IceSpider::IHttpRequest * request, const std::exception & ex) const + { + if (const auto * tex = dynamic_cast(&ex)) { + if (tex->message == "404") { + throw IceSpider::Http404_NotFound(); + } + if (tex->message == "304") { + request->getRequestPath().front() = "some value"; + return IceSpider::ErrorHandlerResult_Modified; + } + if (tex->message == "400") { + request->response(400, "Handled"); + return IceSpider::ErrorHandlerResult_Handled; + } + } + return IceSpider::ErrorHandlerResult_Unhandled; + } +}; + +PLUGIN(DummyErrorHandler, IceSpider::ErrorHandler); + +BOOST_AUTO_TEST_CASE( testErrorHandler_Unhandled ) { TestRequest requestDeleteItem(this, HttpMethod::DELETE, "/error"); process(&requestDeleteItem); @@ -501,5 +528,35 @@ BOOST_AUTO_TEST_CASE( testErrorHandler ) BOOST_REQUIRE(requestDeleteItem.output.eof()); } +BOOST_AUTO_TEST_CASE( testErrorHandler_Handled1 ) +{ + TestRequest requestDeleteItem(this, HttpMethod::DELETE, "/404"); + process(&requestDeleteItem); + auto h = requestDeleteItem.getResponseHeaders(); + BOOST_REQUIRE_EQUAL(h["Status"], "404 Not found"); + requestDeleteItem.output.get(); + BOOST_REQUIRE(requestDeleteItem.output.eof()); +} + +BOOST_AUTO_TEST_CASE( testErrorHandler_Handled2 ) +{ + TestRequest requestDeleteItem(this, HttpMethod::DELETE, "/400"); + process(&requestDeleteItem); + auto h = requestDeleteItem.getResponseHeaders(); + BOOST_REQUIRE_EQUAL(h["Status"], "400 Handled"); + requestDeleteItem.output.get(); + BOOST_REQUIRE(requestDeleteItem.output.eof()); +} + +BOOST_AUTO_TEST_CASE( testErrorHandler_Handled3 ) +{ + TestRequest requestDeleteItem(this, HttpMethod::DELETE, "/304"); + process(&requestDeleteItem); + auto h = requestDeleteItem.getResponseHeaders(); + BOOST_REQUIRE_EQUAL(h["Status"], "200 OK"); + requestDeleteItem.output.get(); + BOOST_REQUIRE(requestDeleteItem.output.eof()); +} + BOOST_AUTO_TEST_SUITE_END(); -- cgit v1.2.3