changeset 5338:78c59b02b121

accept parameters are now provided to HttpContentNegociation::IHandler::Handle()
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 28 Jun 2023 08:29:43 +0200
parents b376abae664a
children cb11e5ced4e3
files OrthancFramework/Sources/HttpServer/HttpContentNegociation.cpp OrthancFramework/Sources/HttpServer/HttpContentNegociation.h OrthancFramework/UnitTestsSources/RestApiTests.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp
diffstat 4 files changed, 139 insertions(+), 81 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancFramework/Sources/HttpServer/HttpContentNegociation.cpp	Tue Jun 27 17:55:09 2023 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpContentNegociation.cpp	Wed Jun 28 08:29:43 2023 +0200
@@ -49,28 +49,70 @@
     {
       return true;
     }
-        
-    if (subtype == "*" && type == type_)
+    else if (subtype == "*" && type == type_)
     {
       return true;
     }
-
-    return type == type_ && subtype == subtype_;
+    else
+    {
+      return type == type_ && subtype == subtype_;
+    }
   }
 
 
-  struct HttpContentNegociation::Reference : public boost::noncopyable
+  class HttpContentNegociation::Reference : public boost::noncopyable
   {
+  private:
     const Handler&  handler_;
     uint8_t         level_;
     float           quality_;
+    std::string     application_;
+    Dictionary      parameters_;
 
+    static float GetQuality(const Dictionary& parameters)
+    {
+      Dictionary::const_iterator found = parameters.find("q");
+
+      if (found != parameters.end())
+      {
+        float quality;
+        bool ok = false;
+
+        try
+        {
+          quality = boost::lexical_cast<float>(found->second);
+          ok = (quality >= 0.0f && quality <= 1.0f);
+        }
+        catch (boost::bad_lexical_cast&)
+        {
+        }
+
+        if (ok)
+        {
+          return quality;
+        }
+        else
+        {
+          throw OrthancException(
+            ErrorCode_BadRequest,
+            "Quality parameter out of range in a HTTP request (must be between 0 and 1): " + found->second);
+        }
+      }
+      else
+      {
+        return 1.0f;  // Default quality
+      }
+    }
+
+  public:
     Reference(const Handler& handler,
               const std::string& type,
               const std::string& subtype,
-              float quality) :
+              const Dictionary& parameters) :
       handler_(handler),
-      quality_(quality)
+      quality_(GetQuality(parameters)),
+      application_(type + "/" + subtype),
+      parameters_(parameters)
     {
       if (type == "*" && subtype == "*")
       {
@@ -85,6 +127,11 @@
         level_ = 2;
       }
     }
+
+    void Call() const
+    {
+      handler_.Call(parameters_);
+    }
       
     bool operator< (const Reference& other) const
     {
@@ -92,13 +139,14 @@
       {
         return true;
       }
-
-      if (level_ > other.level_)
+      else if (level_ > other.level_)
       {
         return false;
       }
-
-      return quality_ < other.quality_;
+      else
+      {
+        return quality_ < other.quality_;
+      }
     }
   };
 
@@ -123,58 +171,21 @@
   }
 
 
-  float HttpContentNegociation::GetQuality(const Tokens& parameters)
-  {
-    for (size_t i = 1; i < parameters.size(); i++)
-    {
-      std::string key, value;
-      if (SplitPair(key, value, parameters[i], '=') &&
-          key == "q")
-      {
-        float quality;
-        bool ok = false;
-
-        try
-        {
-          quality = boost::lexical_cast<float>(value);
-          ok = (quality >= 0.0f && quality <= 1.0f);
-        }
-        catch (boost::bad_lexical_cast&)
-        {
-        }
-
-        if (ok)
-        {
-          return quality;
-        }
-        else
-        {
-          throw OrthancException(
-            ErrorCode_BadRequest,
-            "Quality parameter out of range in a HTTP request (must be between 0 and 1): " + value);
-        }
-      }
-    }
-
-    return 1.0f;  // Default quality
-  }
-
-
-  void HttpContentNegociation::SelectBestMatch(std::unique_ptr<Reference>& best,
+  void HttpContentNegociation::SelectBestMatch(std::unique_ptr<Reference>& target,
                                                const Handler& handler,
                                                const std::string& type,
                                                const std::string& subtype,
-                                               float quality)
+                                               const Dictionary& parameters)
   {
-    std::unique_ptr<Reference> match(new Reference(handler, type, subtype, quality));
+    std::unique_ptr<Reference> match(new Reference(handler, type, subtype, parameters));
 
-    if (best.get() == NULL ||
-        *best < *match)
+    if (target.get() == NULL ||
+        *target < *match)
     {
 #if __cplusplus < 201103L
-      best.reset(match.release());
+      target.reset(match.release());
 #else
-      best = std::move(match);
+      target = std::move(match);
 #endif
     }
   }
@@ -198,9 +209,9 @@
   }
 
     
-  bool HttpContentNegociation::Apply(const HttpHeaders& headers)
+  bool HttpContentNegociation::Apply(const Dictionary& headers)
   {
-    HttpHeaders::const_iterator accept = headers.find("accept");
+    Dictionary::const_iterator accept = headers.find("accept");
     if (accept != headers.end())
     {
       return Apply(accept->second);
@@ -222,26 +233,39 @@
     Toolbox::TokenizeString(mediaRanges, accept, ',');
 
     std::unique_ptr<Reference> bestMatch;
+    Dictionary bestParameters;
 
     for (Tokens::const_iterator it = mediaRanges.begin();
          it != mediaRanges.end(); ++it)
     {
-      Tokens parameters;
-      Toolbox::TokenizeString(parameters, *it, ';');
+      Tokens tokens;
+      Toolbox::TokenizeString(tokens, *it, ';');
 
-      if (parameters.size() > 0)
+      if (tokens.size() > 0)
       {
-        float quality = GetQuality(parameters);
+        Dictionary parameters;
+        for (size_t i = 1; i < tokens.size(); i++)
+        {
+          std::string key, value;
+          
+          if (!SplitPair(key, value, tokens[i], '='))
+          {
+            key = Toolbox::StripSpaces(tokens[i]);
+            value = "";
+          }
 
+          parameters[key] = value;
+        }
+        
         std::string type, subtype;
-        if (SplitPair(type, subtype, parameters[0], '/'))
+        if (SplitPair(type, subtype, tokens[0], '/'))
         {
           for (Handlers::const_iterator it2 = handlers_.begin();
                it2 != handlers_.end(); ++it2)
           {
             if (it2->IsMatch(type, subtype))
             {
-              SelectBestMatch(bestMatch, *it2, type, subtype, quality);
+              SelectBestMatch(bestMatch, *it2, type, subtype, parameters);
             }
           }
         }
@@ -254,7 +278,7 @@
     }
     else
     {
-      bestMatch->handler_.Call();
+      bestMatch->Call();
       return true;
     }
   }
--- a/OrthancFramework/Sources/HttpServer/HttpContentNegociation.h	Tue Jun 27 17:55:09 2023 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpContentNegociation.h	Wed Jun 28 08:29:43 2023 +0200
@@ -39,7 +39,7 @@
   class ORTHANC_PUBLIC HttpContentNegociation : public boost::noncopyable
   {
   public:
-    typedef std::map<std::string, std::string>  HttpHeaders;
+    typedef std::map<std::string, std::string>  Dictionary;
 
     class IHandler : public boost::noncopyable
     {
@@ -49,7 +49,8 @@
       }
 
       virtual void Handle(const std::string& type,
-                          const std::string& subtype) = 0;
+                          const std::string& subtype,
+                          const Dictionary& parameters) = 0;
     };
 
   private:
@@ -66,9 +67,9 @@
       bool IsMatch(const std::string& type,
                    const std::string& subtype) const;
 
-      void Call() const
+      void Call(const Dictionary& parameters) const
       {
-        handler_.Handle(type_, subtype_);
+        handler_.Handle(type_, subtype_, parameters);
       }
    };
 
@@ -86,19 +87,17 @@
                           const std::string& source,
                           char separator);
 
-    static float GetQuality(const Tokens& parameters);
-
-    static void SelectBestMatch(std::unique_ptr<Reference>& best,
+    static void SelectBestMatch(std::unique_ptr<Reference>& target,
                                 const Handler& handler,
                                 const std::string& type,
                                 const std::string& subtype,
-                                float quality);
+                                const Dictionary& parameters);
 
   public:
     void Register(const std::string& mime,
                   IHandler& handler);
     
-    bool Apply(const HttpHeaders& headers);
+    bool Apply(const Dictionary& headers);
 
     bool Apply(const std::string& accept);
   };
--- a/OrthancFramework/UnitTestsSources/RestApiTests.cpp	Tue Jun 27 17:55:09 2023 +0200
+++ b/OrthancFramework/UnitTestsSources/RestApiTests.cpp	Wed Jun 28 08:29:43 2023 +0200
@@ -384,6 +384,7 @@
   private:
     std::string type_;
     std::string subtype_;
+    HttpContentNegociation::Dictionary parameters_;
 
   public:
     AcceptHandler()
@@ -393,7 +394,8 @@
 
     void Reset()
     {
-      Handle("nope", "nope");
+      HttpContentNegociation::Dictionary parameters;
+      Handle("nope", "nope", parameters);
     }
 
     const std::string& GetType() const
@@ -406,11 +408,18 @@
       return subtype_;
     }
 
+    HttpContentNegociation::Dictionary& GetParameters()
+    {
+      return parameters_;
+    }
+
     virtual void Handle(const std::string& type,
-                        const std::string& subtype) ORTHANC_OVERRIDE
+                        const std::string& subtype,
+                        const HttpContentNegociation::Dictionary& parameters) ORTHANC_OVERRIDE
     {
       type_ = type;
       subtype_ = subtype;
+      parameters_ = parameters;
     }
   };
 }
@@ -430,22 +439,29 @@
     ASSERT_TRUE(d.Apply("audio/*; q=0.2, audio/basic"));
     ASSERT_EQ("audio", h.GetType());
     ASSERT_EQ("basic", h.GetSubType());
+    ASSERT_EQ(0u, h.GetParameters().size());
 
-    ASSERT_TRUE(d.Apply("audio/*; q=0.2, audio/nope"));
+    ASSERT_TRUE(d.Apply("audio/*; q=0.2 ;  type =   test   ;  hello  , audio/nope"));
     ASSERT_EQ("audio", h.GetType());
     ASSERT_EQ("mp3", h.GetSubType());
+    ASSERT_EQ(3u, h.GetParameters().size());
+    ASSERT_EQ("0.2", h.GetParameters() ["q"]);
+    ASSERT_EQ("test", h.GetParameters() ["type"]);
+    ASSERT_EQ("", h.GetParameters() ["hello"]);
     
     ASSERT_FALSE(d.Apply("application/*; q=0.2, application/pdf"));
     
-    ASSERT_TRUE(d.Apply("*/*; application/*; q=0.2, application/pdf"));
+    ASSERT_TRUE(d.Apply("*/*; hello=world, application/*; q=0.2, application/pdf"));
     ASSERT_EQ("audio", h.GetType());
+    ASSERT_EQ(1u, h.GetParameters().size());
+    ASSERT_EQ("world", h.GetParameters() ["hello"]);
   }
 
   // "This would be interpreted as "text/html and text/x-c are the
   // preferred media types, but if they do not exist, then send the
   // text/x-dvi entity, and if that does not exist, send the
   // text/plain entity.""
-  const std::string T1 = "text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c";
+  const std::string T1 = "text/plain; q=0.5, text/html ;  hello  = world   , text/x-dvi; q=0.8, text/x-c";
   
   {
     HttpContentNegociation d;
@@ -455,6 +471,8 @@
     ASSERT_TRUE(d.Apply(T1));
     ASSERT_EQ("text", h.GetType());
     ASSERT_EQ("html", h.GetSubType());
+    ASSERT_EQ(1u, h.GetParameters().size());
+    ASSERT_EQ("world", h.GetParameters() ["hello"]);
   }
   
   {
@@ -465,6 +483,7 @@
     ASSERT_TRUE(d.Apply(T1));
     ASSERT_EQ("text", h.GetType());
     ASSERT_EQ("x-c", h.GetSubType());
+    ASSERT_EQ(0u, h.GetParameters().size());
   }
   
   {
@@ -476,6 +495,15 @@
     ASSERT_TRUE(d.Apply(T1));
     ASSERT_EQ("text", h.GetType());
     ASSERT_TRUE(h.GetSubType() == "x-c" || h.GetSubType() == "html");
+    if (h.GetSubType() == "x-c")
+    {
+      ASSERT_EQ(0u, h.GetParameters().size());
+    }
+    else
+    {
+      ASSERT_EQ(1u, h.GetParameters().size());
+      ASSERT_EQ("world", h.GetParameters() ["hello"]);
+    }
   }
   
   {
@@ -485,6 +513,8 @@
     ASSERT_TRUE(d.Apply(T1));
     ASSERT_EQ("text", h.GetType());
     ASSERT_EQ("x-dvi", h.GetSubType());
+    ASSERT_EQ(1u, h.GetParameters().size());
+    ASSERT_EQ("0.8", h.GetParameters() ["q"]);
   }
   
   {
@@ -493,6 +523,8 @@
     ASSERT_TRUE(d.Apply(T1));
     ASSERT_EQ("text", h.GetType());
     ASSERT_EQ("plain", h.GetSubType());
+    ASSERT_EQ(1u, h.GetParameters().size());
+    ASSERT_EQ("0.5", h.GetParameters() ["q"]);
   }
 }
 
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Tue Jun 27 17:55:09 2023 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Wed Jun 28 08:29:43 2023 +0200
@@ -639,7 +639,8 @@
       }
 
       virtual void Handle(const std::string& type,
-                          const std::string& subtype) ORTHANC_OVERRIDE
+                          const std::string& subtype,
+                          const HttpContentNegociation::Dictionary& parameters) ORTHANC_OVERRIDE
       {
         assert(type == "image");
         assert(subtype == "png");
@@ -658,7 +659,8 @@
       }
 
       virtual void Handle(const std::string& type,
-                          const std::string& subtype) ORTHANC_OVERRIDE
+                          const std::string& subtype,
+                          const HttpContentNegociation::Dictionary& parameters) ORTHANC_OVERRIDE
       {
         assert(type == "image");
         assert(subtype == "x-portable-arbitrarymap");
@@ -698,7 +700,8 @@
       }
 
       virtual void Handle(const std::string& type,
-                          const std::string& subtype) ORTHANC_OVERRIDE
+                          const std::string& subtype,
+                          const HttpContentNegociation::Dictionary& parameters) ORTHANC_OVERRIDE
       {
         assert(type == "image");
         assert(subtype == "jpeg");