From 09e33a20696b83d0367ebb6c9122d946173df6e8 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Tue, 16 Aug 2016 22:30:03 +0100 Subject: Add basic content negotiation --- icespider/compile/routeCompiler.cpp | 2 +- icespider/core/acceptable.cpp | 12 ++++++++++++ icespider/core/acceptable.h | 18 ++++++++++++++++++ icespider/core/core.cpp | 1 + icespider/core/ihttpRequest.cpp | 35 ++++++++++++++++++++++++++++++++--- icespider/core/ihttpRequest.h | 17 ++++++++++++----- icespider/core/irouteHandler.cpp | 28 ++++++++++++++++++++++++++++ icespider/core/irouteHandler.h | 9 +++++++++ icespider/unittests/Jamfile.jam | 2 ++ icespider/unittests/testApp.cpp | 18 ++++++++++++++++-- 10 files changed, 131 insertions(+), 11 deletions(-) create mode 100644 icespider/core/acceptable.cpp create mode 100644 icespider/core/acceptable.h diff --git a/icespider/compile/routeCompiler.cpp b/icespider/compile/routeCompiler.cpp index 6f5ded0..c190a86 100644 --- a/icespider/compile/routeCompiler.cpp +++ b/icespider/compile/routeCompiler.cpp @@ -277,7 +277,7 @@ namespace IceSpider { } fprintbf(4, output, "auto prx = getProxy<%s>(request);\n", proxyName); if (o->returnsData()) { - fprintbf(4, output, "request->response(prx->%s(", operation); + fprintbf(4, output, "request->response(this, prx->%s(", operation); } else { fprintbf(4, output, "prx->%s(", operation); diff --git a/icespider/core/acceptable.cpp b/icespider/core/acceptable.cpp new file mode 100644 index 0000000..d7a2c58 --- /dev/null +++ b/icespider/core/acceptable.cpp @@ -0,0 +1,12 @@ +#include "acceptable.h" +#include +#include + +namespace IceSpider { + void + Acceptable::free() + { + ::free(grp); + ::free(type); + } +} diff --git a/icespider/core/acceptable.h b/icespider/core/acceptable.h new file mode 100644 index 0000000..20b6f09 --- /dev/null +++ b/icespider/core/acceptable.h @@ -0,0 +1,18 @@ +#ifndef ICESPIDER_ACCEPTABLE_H +#define ICESPIDER_ACCEPTABLE_H + +#include + +namespace IceSpider { + class DLL_PUBLIC Acceptable { + public: + void free(); + + char * grp; + char * type; + float pri; + }; +} + +#endif + diff --git a/icespider/core/core.cpp b/icespider/core/core.cpp index b21a805..85a9bfa 100644 --- a/icespider/core/core.cpp +++ b/icespider/core/core.cpp @@ -12,6 +12,7 @@ namespace IceSpider { // Initialize routes for (const auto & rp : AdHoc::PluginManager::getDefault()->getAll()) { auto r = rp->implementation(); + r->initialize(); auto & mroutes = routes[r->method]; if (mroutes.size() <= r->pathElementCount()) { mroutes.resize(r->pathElementCount() + 1); diff --git a/icespider/core/ihttpRequest.cpp b/icespider/core/ihttpRequest.cpp index a255de0..8e9b8f4 100644 --- a/icespider/core/ihttpRequest.cpp +++ b/icespider/core/ihttpRequest.cpp @@ -1,4 +1,5 @@ #include "ihttpRequest.h" +#include "irouteHandler.h" #include "util.h" #include @@ -24,10 +25,38 @@ namespace IceSpider { } Slicer::SerializerPtr - IHttpRequest::getSerializer() const + IHttpRequest::getSerializer(const IRouteHandler * handler) const { - return Slicer::StreamSerializerFactory::createNew( - "application/json", getOutputStream()); + auto acceptHdr = getHeaderParam("Accept"); + if (acceptHdr) { + auto accept = acceptHdr->c_str(); + std::vector accepts; + accepts.reserve(5); + char * grp = NULL, * type = NULL; + float pri = 0.0f; + int chars, v; + while ((v = sscanf(accept, " %m[^/] / %m[^;,] %n ; q = %f , %n", &grp, &type, &chars, &pri, &chars)) >= 2) { + accepts.push_back( { grp, type, (v < 3 ? 1.0f : pri) } ); + grp = NULL; + type = NULL; + accept += chars; + } + free(grp); + free(type); + std::stable_sort(accepts.begin(), accepts.end(), [](const auto & a, const auto & b) { return a.pri < b.pri; }); + Slicer::SerializerPtr serializer; + auto & strm = getOutputStream(); + for(auto & a : accepts) { + if (!serializer) { + serializer = handler->getSerializer(a.grp, a.type, strm); + } + a.free(); + } + return serializer; + } + else { + return handler->defaultSerializer(getOutputStream()); + } } const std::string & diff --git a/icespider/core/ihttpRequest.h b/icespider/core/ihttpRequest.h index cdc2161..45bcd11 100644 --- a/icespider/core/ihttpRequest.h +++ b/icespider/core/ihttpRequest.h @@ -8,9 +8,11 @@ #include #include #include +#include "acceptable.h" namespace IceSpider { class Core; + class IRouteHandler; typedef std::vector PathElements; typedef IceUtil::Optional OptionalString; @@ -27,7 +29,7 @@ namespace IceSpider { virtual OptionalString getQueryStringParam(const std::string &) const = 0; virtual OptionalString getHeaderParam(const std::string &) const = 0; virtual Slicer::DeserializerPtr getDeserializer() const; - virtual Slicer::SerializerPtr getSerializer() const; + virtual Slicer::SerializerPtr getSerializer(const IRouteHandler *) const; virtual std::istream & getInputStream() const = 0; virtual std::ostream & getOutputStream() const = 0; @@ -44,11 +46,16 @@ namespace IceSpider { IceUtil::Optional getHeaderParam(const std::string & key) const; void response(short, const std::string &) const; template - void response(const T & t) const + void response(const IRouteHandler * route, const T & t) const { - auto s = getSerializer(); - response(200, "OK"); - Slicer::SerializeAnyWith(t, s); + auto s = getSerializer(route); + if (s) { + response(200, "OK"); + Slicer::SerializeAnyWith(t, s); + } + else { + response(406, "Unacceptable"); + } } const Core * core; diff --git a/icespider/core/irouteHandler.cpp b/icespider/core/irouteHandler.cpp index 4d78397..11d18e8 100644 --- a/icespider/core/irouteHandler.cpp +++ b/icespider/core/irouteHandler.cpp @@ -11,10 +11,38 @@ namespace IceSpider { { } + void + IRouteHandler::initialize() + { + auto globalSerializers = AdHoc::PluginManager::getDefault()->getAll(); + for (const auto & gs : globalSerializers) { + auto slash = gs->name.find('/'); + routeSerializers.insert({ { gs->name.substr(0, slash), gs->name.substr(slash + 1) }, gs }); + } + } + Ice::ObjectPrx IRouteHandler::getProxy(IHttpRequest * request, const char * type) const { return request->core->getProxy(type); } + + Slicer::SerializerPtr + IRouteHandler::getSerializer(const char * grp, const char * type, std::ostream & strm) const + { + for (const auto & rs : routeSerializers) { + if ((!grp || rs.first.first == grp) && (!type || rs.first.second == type)) { + return rs.second->implementation()->create(strm); + } + } + return nullptr; + } + + Slicer::SerializerPtr + IRouteHandler::defaultSerializer(std::ostream & strm) const + { + return Slicer::StreamSerializerFactory::createNew( + "application/json", strm); + } } diff --git a/icespider/core/irouteHandler.h b/icespider/core/irouteHandler.h index 1334bee..0417775 100644 --- a/icespider/core/irouteHandler.h +++ b/icespider/core/irouteHandler.h @@ -13,11 +13,20 @@ namespace IceSpider { class DLL_PUBLIC IRouteHandler : public AdHoc::AbstractPluginImplementation, public Path { public: IRouteHandler(HttpMethod, const std::string & path); + virtual void execute(IHttpRequest * request) const = 0; + virtual void initialize(); + virtual Slicer::SerializerPtr getSerializer(const char *, const char *, std::ostream &) const; + virtual Slicer::SerializerPtr defaultSerializer(std::ostream &) const; const HttpMethod method; protected: + typedef boost::shared_ptr> StreamSerializerFactoryPtr; + typedef std::pair ContentType; + typedef std::map RouteSerializers; + RouteSerializers routeSerializers; + template inline T requiredParameterNotFound(const char *, const K & key) const { diff --git a/icespider/unittests/Jamfile.jam b/icespider/unittests/Jamfile.jam index a10d718..234e715 100644 --- a/icespider/unittests/Jamfile.jam +++ b/icespider/unittests/Jamfile.jam @@ -20,6 +20,7 @@ actions routes2cpp bind ICESPIDER lib adhocutil : : : : /usr/include/adhocutil ; lib slicer : : : : /usr/include/slicer ; lib slicer-json : : : : /usr/include/slicer ; +lib slicer-xml : : : : /usr/include/slicer ; lib boost_utf : : boost_unit_test_framework ; lib dl ; path-constant me : . ; @@ -63,6 +64,7 @@ run adhocutil slicer slicer-json + slicer-xml ../common//icespider-common ../core//icespider-core ../common//icespider-common diff --git a/icespider/unittests/testApp.cpp b/icespider/unittests/testApp.cpp index 80dd6c1..1031eaf 100644 --- a/icespider/unittests/testApp.cpp +++ b/icespider/unittests/testApp.cpp @@ -68,7 +68,7 @@ class TestRequest : public IHttpRequest { IceUtil::Optional getHeaderParam(const std::string & key) const override { - return AdHoc::safeMapLookup(hdr, key); + return hdr.find(key) == hdr.end() ? IceUtil::Optional() : hdr.find(key)->second; } std::istream & getInputStream() const override @@ -161,7 +161,6 @@ BOOST_AUTO_TEST_CASE( testCallMethods ) auto adp = communicator->createObjectAdapterWithEndpoints("test", "default"); auto obj = adp->addWithUUID(new TestSerice()); adp->activate(); - fprintf(stderr, "%s\n", obj->ice_id().c_str()); communicator->getProperties()->setProperty("TestIceSpider::TestApi", communicator->proxyToString(obj)); TestRequest requestGetIndex(this, HttpMethod::GET, "/"); @@ -190,6 +189,21 @@ BOOST_AUTO_TEST_CASE( testCallMethods ) process(&requestUpdateItem); BOOST_REQUIRE_EQUAL(requestDeleteItem.output.str(), "Status: 200 OK\r\n\r\n"); + TestRequest requestJson(this, HttpMethod::GET, "/"); + requestJson.hdr["Accept"] = "application/json"; + process(&requestJson); + BOOST_REQUIRE_EQUAL(requestJson.output.str(), "Status: 200 OK\r\n\r\n{\"value\":\"index\"}"); + + TestRequest requestXml(this, HttpMethod::GET, "/"); + requestXml.hdr["Accept"] = "application/xml"; + process(&requestXml); + BOOST_REQUIRE_EQUAL(requestXml.output.str(), "Status: 200 OK\r\n\r\n\nindex\n"); + + TestRequest requestBadAccept(this, HttpMethod::GET, "/"); + requestBadAccept.hdr["Accept"] = "not/supported"; + process(&requestBadAccept); + BOOST_REQUIRE_EQUAL(requestBadAccept.output.str(), "Status: 406 Unacceptable\r\n\r\n"); + adp->deactivate(); } -- cgit v1.2.3