diff options
| author | Dan Goodliffe <dan@randomdan.homeip.net> | 2017-01-30 14:08:36 +0000 | 
|---|---|---|
| committer | Dan Goodliffe <dan@randomdan.homeip.net> | 2017-01-30 14:08:36 +0000 | 
| commit | b7de6028c797177e8d8411f080307cc9faf936af (patch) | |
| tree | 96837e1b5a2d7ec0b28a487b66e5cbf598d2e023 | |
| parent | Fix case where operation returns void but has declared exception list (return... (diff) | |
| download | icespider-b7de6028c797177e8d8411f080307cc9faf936af.tar.bz2 icespider-b7de6028c797177e8d8411f080307cc9faf936af.tar.xz icespider-b7de6028c797177e8d8411f080307cc9faf936af.zip | |
Support for plugable error handlers
| -rw-r--r-- | icespider/core/core.cpp | 30 | ||||
| -rw-r--r-- | icespider/core/core.h | 12 | ||||
| -rw-r--r-- | icespider/core/ihttpRequest.h | 5 | ||||
| -rw-r--r-- | icespider/fcgi/cgiRequestBase.cpp | 36 | ||||
| -rw-r--r-- | icespider/fcgi/cgiRequestBase.h | 5 | ||||
| -rw-r--r-- | icespider/testing/testRequest.cpp | 67 | ||||
| -rw-r--r-- | icespider/testing/testRequest.h | 22 | ||||
| -rw-r--r-- | 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 <factory.impl.h>  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<ErrorHandler>(); +		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<Plugin, Ice::CommunicatorPtr, Ice::PropertiesPtr> 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<ErrorHandler> 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<const char *, Env, cmp_str> 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<std::string> & +	const PathElements &  	TestRequest::getRequestPath() const  	{  		return url;  	} +	PathElements & +	TestRequest::getRequestPath() +	{ +		return url; +	} +  	HttpMethod  	TestRequest::getRequestMethod() const  	{  		return method;  	} -	IceUtil::Optional<std::string> +	OptionalString  	TestRequest::getEnv(const std::string & key) const  	{ -		return env.find(key) == env.end() ? IceUtil::Optional<std::string>() : env.find(key)->second; +		return get(key, env);  	} -	IceUtil::Optional<std::string> +	OptionalString  	TestRequest::getQueryStringParam(const std::string & key) const  	{ -		return qs.find(key) == qs.end() ? IceUtil::Optional<std::string>() : qs.find(key)->second; +		return get(key, qs);  	} -	IceUtil::Optional<std::string> +	OptionalString  	TestRequest::getCookieParam(const std::string & key) const  	{ -		return cookies.find(key) == cookies.end() ? IceUtil::Optional<std::string>() : cookies.find(key)->second; +		return get(key, cookies);  	} -	IceUtil::Optional<std::string> +	OptionalString  	TestRequest::getHeaderParam(const std::string & key) const  	{ -		return hdr.find(key) == hdr.end() ? IceUtil::Optional<std::string>() : 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<std::string, std::string> MapVars; -			typedef std::vector<std::string> UrlVars;  			TestRequest(const Core * c, HttpMethod m, const std::string & p); -			const std::vector<std::string> & getRequestPath() const override; +			const PathElements & getRequestPath() const override; +			PathElements & getRequestPath() override;  			HttpMethod getRequestMethod() const override; -			IceUtil::Optional<std::string> getEnv(const std::string & key) const override; -			IceUtil::Optional<std::string> getQueryStringParam(const std::string & key) const override; -			IceUtil::Optional<std::string> getCookieParam(const std::string & key) const override; -			IceUtil::Optional<std::string> 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<const TestIceSpider::Ex *>(&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(); | 
