changeset 1511:7962563129c9

starting support of deflate/gzip content types
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 10 Aug 2015 14:18:24 +0200
parents ffc9f36103b9
children 52dc56bcec7d
files Core/Compression/ZlibCompressor.cpp Core/Compression/ZlibCompressor.h Core/Enumerations.h Core/HttpServer/HttpOutput.cpp Core/HttpServer/HttpOutput.h Core/RestApi/RestApi.cpp Core/RestApi/RestApiOutput.cpp Core/RestApi/RestApiOutput.h
diffstat 8 files changed, 186 insertions(+), 54 deletions(-) [+]
line wrap: on
line diff
--- a/Core/Compression/ZlibCompressor.cpp	Mon Aug 10 10:27:05 2015 +0200
+++ b/Core/Compression/ZlibCompressor.cpp	Mon Aug 10 14:18:24 2015 +0200
@@ -61,25 +61,32 @@
       return;
     }
 
-    uLongf compressedSize = compressBound(uncompressedSize);
-    compressed.resize(compressedSize + sizeof(size_t));
+    uLongf compressedSize = compressBound(uncompressedSize) + 1024 /* security margin */;
+    if (compressedSize == 0)
+    {
+      compressedSize = 1;
+    }
 
-    int error = compress2
-      (reinterpret_cast<uint8_t*>(&compressed[0]) + sizeof(size_t),
-       &compressedSize,
-       const_cast<Bytef *>(static_cast<const Bytef *>(uncompressed)), 
-       uncompressedSize,
-       compressionLevel_);
-
-    memcpy(&compressed[0], &uncompressedSize, sizeof(size_t));
-  
-    if (error == Z_OK)
+    uint8_t* target;
+    if (prefixWithUncompressedSize_)
     {
-      compressed.resize(compressedSize + sizeof(size_t));
-      return;
+      compressed.resize(compressedSize + sizeof(uint64_t));
+      target = reinterpret_cast<uint8_t*>(&compressed[0]) + sizeof(uint64_t);
     }
     else
     {
+      compressed.resize(compressedSize);
+      target = reinterpret_cast<uint8_t*>(&compressed[0]);
+    }
+
+    int error = compress2(target,
+                          &compressedSize,
+                          const_cast<Bytef *>(static_cast<const Bytef *>(uncompressed)), 
+                          uncompressedSize,
+                          compressionLevel_);
+
+    if (error != Z_OK)
+    {
       compressed.clear();
 
       switch (error)
@@ -91,6 +98,18 @@
         throw OrthancException(ErrorCode_InternalError);
       }  
     }
+
+    // The compression was successful
+    if (prefixWithUncompressedSize_)
+    {
+      uint64_t s = static_cast<uint64_t>(uncompressedSize);
+      memcpy(&compressed[0], &s, sizeof(uint64_t));
+      compressed.resize(compressedSize + sizeof(uint64_t));
+    }
+    else
+    {
+      compressed.resize(compressedSize);
+    }
   }
 
 
@@ -104,29 +123,29 @@
       return;
     }
 
-    if (compressedSize < sizeof(size_t))
+    if (compressedSize < sizeof(uint64_t))
     {
       throw OrthancException("Zlib: The compressed buffer is ill-formed");
     }
 
-    size_t uncompressedLength;
-    memcpy(&uncompressedLength, compressed, sizeof(size_t));
+    uint64_t uncompressedSize;
+    memcpy(&uncompressedSize, compressed, sizeof(uint64_t));
     
     try
     {
-      uncompressed.resize(uncompressedLength);
+      uncompressed.resize(uncompressedSize);
     }
     catch (...)
     {
       throw OrthancException("Zlib: Corrupted compressed buffer");
     }
 
-    uLongf tmp = uncompressedLength;
+    uLongf tmp = uncompressedSize;
     int error = uncompress
       (reinterpret_cast<uint8_t*>(&uncompressed[0]), 
        &tmp,
-       reinterpret_cast<const uint8_t*>(compressed) + sizeof(size_t),
-       compressedSize - sizeof(size_t));
+       reinterpret_cast<const uint8_t*>(compressed) + sizeof(uint64_t),
+       compressedSize - sizeof(uint64_t));
 
     if (error != Z_OK)
     {
--- a/Core/Compression/ZlibCompressor.h	Mon Aug 10 10:27:05 2015 +0200
+++ b/Core/Compression/ZlibCompressor.h	Mon Aug 10 14:18:24 2015 +0200
@@ -40,17 +40,29 @@
   {
   private:
     uint8_t compressionLevel_;
+    bool    prefixWithUncompressedSize_;
 
   public:
     using BufferCompressor::Compress;
     using BufferCompressor::Uncompress;
 
-    ZlibCompressor()
+    ZlibCompressor() : 
+      compressionLevel_(6),
+      prefixWithUncompressedSize_(true)
     {
-      compressionLevel_ = 6;
     }
 
     void SetCompressionLevel(uint8_t level);
+    
+    void SetPrefixWithUncompressedSize(bool prefix)
+    {
+      prefixWithUncompressedSize_ = prefix;
+    }
+
+    bool HasPrefixWithUncompressedSize() const
+    {
+      return prefixWithUncompressedSize_;
+    }
 
     uint8_t GetCompressionLevel() const
     {
--- a/Core/Enumerations.h	Mon Aug 10 10:27:05 2015 +0200
+++ b/Core/Enumerations.h	Mon Aug 10 14:18:24 2015 +0200
@@ -239,6 +239,15 @@
   };
 
 
+  // https://en.wikipedia.org/wiki/HTTP_compression
+  enum HttpCompression
+  {
+    HttpCompression_None,
+    HttpCompression_Deflate,
+    HttpCompression_Gzip
+  };
+
+
   // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/
   enum Encoding
   {
@@ -300,7 +309,22 @@
 
   enum CompressionType
   {
+    /**
+     * Buffer/file that is stored as-is, in a raw fashion, without
+     * compression.
+     **/
     CompressionType_None = 1,
+
+    /**
+     * Buffer that is compressed using the "deflate" algorithm (RFC
+     * 1951), wrapped inside the zlib data format (RFC 1950), prefixed
+     * with a "uint64_t" (8 bytes) that encodes the size of the
+     * uncompressed buffer. If the compressed buffer is empty, its
+     * represents an empty uncompressed buffer. This format is
+     * internal to Orthanc. If the 8 first bytes are skipped AND the
+     * buffer is non-empty, the buffer is compatible with the
+     * "deflate" HTTP compression.
+     **/
     CompressionType_Zlib = 2
   };
 
--- a/Core/HttpServer/HttpOutput.cpp	Mon Aug 10 10:27:05 2015 +0200
+++ b/Core/HttpServer/HttpOutput.cpp	Mon Aug 10 14:18:24 2015 +0200
@@ -36,6 +36,7 @@
 #include "../Logging.h"
 #include "../OrthancException.h"
 #include "../Toolbox.h"
+#include "../Compression/ZlibCompressor.h"
 
 #include <iostream>
 #include <vector>
@@ -256,23 +257,61 @@
     stateMachine_.SendBody(NULL, 0);
   }
 
-  void HttpOutput::SendBody(const void* buffer, size_t length)
+  void HttpOutput::SendBody(const void* buffer, 
+                            size_t length,
+                            HttpCompression compression)
   {
-    stateMachine_.SendBody(buffer, length);
-  }
-
-  void HttpOutput::SendBody(const std::string& str)
-  {
-    if (str.size() == 0)
+    if (length == 0)
     {
       stateMachine_.SendBody(NULL, 0);
     }
     else
     {
-      stateMachine_.SendBody(str.c_str(), str.size());
+      switch (compression)
+      {
+        case HttpCompression_None:
+        {
+          stateMachine_.SendBody(buffer, length);
+          break;
+        }
+
+        case HttpCompression_Deflate:
+        {
+          LOG(TRACE) << "Compressing a HTTP answer using Deflate";
+          ZlibCompressor compressor;
+
+          // Do not prefix the buffer with its uncompressed size, to be compatible with "deflate"
+          compressor.SetPrefixWithUncompressedSize(false);  
+
+          std::string compressed;
+          compressor.Compress(compressed, buffer, length);
+
+          // The body is empty, do not use Deflate compression
+          if (compressed.size() == 0)
+          {
+            stateMachine_.SendBody(NULL, 0);
+          }
+          else
+          {
+            stateMachine_.AddHeader("Content-Encoding", "deflate");
+            stateMachine_.SendBody(compressed.c_str(), compressed.size());
+          }
+
+          break;
+        }
+
+        default:
+          throw OrthancException(ErrorCode_NotImplemented);
+      }
     }
   }
 
+  void HttpOutput::SendBody(const std::string& str,
+                            HttpCompression compression)
+  {
+    SendBody(str.size() == 0 ? NULL : str.c_str(), str.size(), compression);
+  }
+
   void HttpOutput::SendBody()
   {
     stateMachine_.SendBody(NULL, 0);
--- a/Core/HttpServer/HttpOutput.h	Mon Aug 10 10:27:05 2015 +0200
+++ b/Core/HttpServer/HttpOutput.h	Mon Aug 10 14:18:24 2015 +0200
@@ -146,9 +146,12 @@
       stateMachine_.AddHeader(key, value);
     }
 
-    void SendBody(const void* buffer, size_t length);
+    void SendBody(const void* buffer, 
+                  size_t length,
+                  HttpCompression compression = HttpCompression_None);
 
-    void SendBody(const std::string& str);
+    void SendBody(const std::string& str,
+                  HttpCompression compression = HttpCompression_None);
 
     void SendBody();
 
--- a/Core/RestApi/RestApi.cpp	Mon Aug 10 10:27:05 2015 +0200
+++ b/Core/RestApi/RestApi.cpp	Mon Aug 10 14:18:24 2015 +0200
@@ -170,28 +170,51 @@
     RestApiOutput wrappedOutput(output, method);
 
 #if ORTHANC_PUGIXML_ENABLED == 1
-    // Look if the user wishes XML answers instead of JSON
-    // http://www.w3.org/Protocols/HTTP/HTRQ_Headers.html#z3
-    Arguments::const_iterator it = headers.find("accept");
-    if (it != headers.end())
     {
-      std::vector<std::string> accepted;
-      Toolbox::TokenizeString(accepted, it->second, ';');
-      for (size_t i = 0; i < accepted.size(); i++)
+      // Look if the client wishes XML answers instead of JSON
+      // http://www.w3.org/Protocols/HTTP/HTRQ_Headers.html#z3
+      Arguments::const_iterator it = headers.find("accept");
+      if (it != headers.end())
       {
-        if (accepted[i] == "application/xml")
+        std::vector<std::string> accepted;
+        Toolbox::TokenizeString(accepted, it->second, ';');
+        for (size_t i = 0; i < accepted.size(); i++)
         {
-          wrappedOutput.SetConvertJsonToXml(true);
-        }
+          if (accepted[i] == "application/xml")
+          {
+            wrappedOutput.SetConvertJsonToXml(true);
+          }
 
-        if (accepted[i] == "application/json")
-        {
-          wrappedOutput.SetConvertJsonToXml(false);
+          if (accepted[i] == "application/json")
+          {
+            wrappedOutput.SetConvertJsonToXml(false);
+          }
         }
       }
     }
 #endif
 
+    {
+      // Look if the client wishes HTTP compression
+      // https://en.wikipedia.org/wiki/HTTP_compression
+      Arguments::const_iterator it = headers.find("accept-encoding");
+      if (it != headers.end())
+      {
+        std::vector<std::string> encodings;
+        Toolbox::TokenizeString(encodings, it->second, ',');
+        for (size_t i = 0; i < encodings.size(); i++)
+        {
+          std::string s = Toolbox::StripSpaces(encodings[i]);
+          if (s == "deflate")
+          {
+            wrappedOutput.SetHttpCompression(HttpCompression_Deflate);
+          }
+
+          // TODO HttpCompression_Gzip ?
+        }
+      }
+    }
+
     Arguments compiled;
     HttpToolbox::CompileGetArguments(compiled, getArguments);
 
--- a/Core/RestApi/RestApiOutput.cpp	Mon Aug 10 10:27:05 2015 +0200
+++ b/Core/RestApi/RestApiOutput.cpp	Mon Aug 10 14:18:24 2015 +0200
@@ -45,6 +45,7 @@
                                HttpMethod method) : 
     output_(output),
     method_(method),
+    compression_(HttpCompression_None),
     convertJsonToXml_(false)
   {
     alreadySent_ = false;
@@ -94,7 +95,7 @@
       std::string s;
       Toolbox::JsonToXml(s, value);
       output_.SetContentType("application/xml");
-      output_.SendBody(s);
+      output_.SendBody(s, compression_);
 #else
       LOG(ERROR) << "Orthanc was compiled without XML support";
       throw OrthancException(ErrorCode_InternalError);
@@ -104,7 +105,7 @@
     {
       Json::StyledWriter writer;
       output_.SetContentType("application/json");
-      output_.SendBody(writer.write(value));
+      output_.SendBody(writer.write(value), compression_);
     }
 
     alreadySent_ = true;
@@ -115,7 +116,7 @@
   {
     CheckStatus();
     output_.SetContentType(contentType.c_str());
-    output_.SendBody(buffer);
+    output_.SendBody(buffer, compression_);
     alreadySent_ = true;
   }
 
@@ -125,7 +126,7 @@
   {
     CheckStatus();
     output_.SetContentType(contentType.c_str());
-    output_.SendBody(buffer, length);
+    output_.SendBody(buffer, length, compression_);
     alreadySent_ = true;
   }
 
--- a/Core/RestApi/RestApiOutput.h	Mon Aug 10 10:27:05 2015 +0200
+++ b/Core/RestApi/RestApiOutput.h	Mon Aug 10 14:18:24 2015 +0200
@@ -42,10 +42,11 @@
   class RestApiOutput
   {
   private:
-    HttpOutput& output_;
-    HttpMethod  method_;
-    bool        alreadySent_;
-    bool        convertJsonToXml_;
+    HttpOutput&      output_;
+    HttpMethod       method_;
+    HttpCompression  compression_;
+    bool             alreadySent_;
+    bool             convertJsonToXml_;
 
     void CheckStatus();
 
@@ -75,6 +76,16 @@
       return convertJsonToXml_;
     }
 
+    void SetHttpCompression(HttpCompression compression)
+    {
+      compression_ = compression;
+    }
+
+    HttpCompression GetHttpCompression() const
+    {
+      return compression_;
+    }
+
     void AnswerFile(HttpFileSender& sender);
 
     void AnswerJson(const Json::Value& value);