changeset 5406:aaf7c49a9ddc am-http-compression

tentative to implement smart HTTP compression with detection of transfer syntax
author Alain Mazy <am@osimis.io>
date Sat, 04 Nov 2023 13:42:30 +0100
parents 62bb63346185
children
files NEWS OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp OrthancFramework/Sources/Enumerations.cpp OrthancFramework/Sources/Enumerations.h OrthancFramework/Sources/FileStorage/StorageAccessor.cpp OrthancFramework/Sources/FileStorage/StorageAccessor.h OrthancFramework/Sources/HttpServer/FilesystemHttpHandler.cpp OrthancFramework/Sources/HttpServer/FilesystemHttpSender.cpp OrthancFramework/Sources/HttpServer/FilesystemHttpSender.h OrthancFramework/Sources/HttpServer/HttpFileSender.cpp OrthancFramework/Sources/HttpServer/HttpFileSender.h OrthancFramework/Sources/HttpServer/HttpOutput.cpp OrthancFramework/Sources/HttpServer/HttpOutput.h OrthancFramework/Sources/RestApi/RestApiOutput.cpp OrthancFramework/Sources/RestApi/RestApiOutput.h OrthancFramework/Sources/SystemToolbox.cpp OrthancFramework/Sources/SystemToolbox.h OrthancServer/Plugins/Engine/OrthancPlugins.cpp OrthancServer/Sources/EmbeddedResourceHttpHandler.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp OrthancServer/Sources/ServerContext.cpp
diffstat 21 files changed, 308 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Tue Oct 17 15:06:11 2023 +0200
+++ b/NEWS	Sat Nov 04 13:42:30 2023 +0100
@@ -17,6 +17,9 @@
 * All 'expand' GET arguments now accepts expand=true and expand=false values.
   The /studies/../instances and sibling routes are the only whose expand is true if not specified.
   These routes now accepts expand=false to simply list the child resources ids.
+* Whatever the value of the "HttpCompressionEnabled" configuration, only the contents 
+  that are clearly identified as compressible (mainly JSON, XML or a raw DICOM file) are compressed.
+  - For contents provided by plugins, 
 
 
 Maintenance
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Tue Oct 17 15:06:11 2023 +0200
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp	Sat Nov 04 13:42:30 2023 +0100
@@ -360,14 +360,14 @@
             {
               if (pixelItem->getLength() == 0)
               {
-                output.AnswerBuffer(NULL, 0, MimeType_Binary);
+                output.AnswerBuffer(NULL, 0, MimeType_Binary, ContentCompression_AlreadyCompressed);
                 return true;
               }
 
               Uint8* buffer = NULL;
               if (pixelItem->getUint8Array(buffer).good() && buffer)
               {
-                output.AnswerBuffer(buffer, pixelItem->getLength(), MimeType_Binary);
+                output.AnswerBuffer(buffer, pixelItem->getLength(), MimeType_Binary, ContentCompression_AlreadyCompressed);
                 return true;
               }
             }
@@ -378,7 +378,7 @@
           // This is the case for raw, uncompressed image buffers
           assert(*blockUri == "0");
           DicomFieldStream stream(*element, transferSyntax);
-          output.AnswerStream(stream);
+          output.AnswerStream(stream, ContentCompression_NotCompressed);
         }
       }
     }
@@ -838,7 +838,17 @@
     std::string serialized;
     if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, *GetDcmtkObjectConst().getDataset()))
     {
-      output.AnswerBuffer(serialized, MimeType_Dicom);
+      ContentCompression contentCompression = ContentCompression_Unknown;
+      DicomTransferSyntax transferSyntax;
+
+      if (LookupTransferSyntax(transferSyntax))
+      {
+        contentCompression = (IsCompressedTransferSyntax(transferSyntax) ? ContentCompression_AlreadyCompressed : ContentCompression_NotCompressed);
+      }
+      
+      output.AnswerBuffer(serialized, 
+                          MimeType_Dicom, 
+                          contentCompression);
     }
   }
 #endif
--- a/OrthancFramework/Sources/Enumerations.cpp	Tue Oct 17 15:06:11 2023 +0200
+++ b/OrthancFramework/Sources/Enumerations.cpp	Sat Nov 04 13:42:30 2023 +0100
@@ -35,26 +35,6 @@
 
 namespace Orthanc
 {
-  static const char* const MIME_CSS = "text/css";
-  static const char* const MIME_DICOM = "application/dicom";
-  static const char* const MIME_GIF = "image/gif";
-  static const char* const MIME_GZIP = "application/gzip";
-  static const char* const MIME_HTML = "text/html";
-  static const char* const MIME_JAVASCRIPT = "application/javascript";
-  static const char* const MIME_JPEG2000 = "image/jp2";
-  static const char* const MIME_NACL = "application/x-nacl";
-  static const char* const MIME_PLAIN_TEXT = "text/plain";
-  static const char* const MIME_PNACL = "application/x-pnacl";
-  static const char* const MIME_SVG = "image/svg+xml";
-  static const char* const MIME_WEB_ASSEMBLY = "application/wasm";
-  static const char* const MIME_WOFF = "application/x-font-woff";
-  static const char* const MIME_WOFF2 = "font/woff2";
-  static const char* const MIME_XML_2 = "text/xml";
-  static const char* const MIME_ZIP = "application/zip";
-  static const char* const MIME_DICOM_WEB_JSON = "application/dicom+json";
-  static const char* const MIME_DICOM_WEB_XML = "application/dicom+xml";
-  static const char* const MIME_ICO = "image/x-icon";
-
   // This function is autogenerated by the script
   // "Resources/CodeGeneration/GenerateErrorCodes.py"
   const char* EnumerationToString(ErrorCode error)
@@ -2488,6 +2468,19 @@
       throw OrthancException(ErrorCode_InternalError);
     }
   }
+
+  bool IsCompressedTransferSyntax(DicomTransferSyntax syntax)
+  {
+    switch (syntax)
+    {
+      case DicomTransferSyntax_LittleEndianImplicit:
+      case DicomTransferSyntax_LittleEndianExplicit:
+      case DicomTransferSyntax_BigEndianExplicit:
+        return false;
+      default:
+        return true;
+    }
+  }
 }
 
 
--- a/OrthancFramework/Sources/Enumerations.h	Tue Oct 17 15:06:11 2023 +0200
+++ b/OrthancFramework/Sources/Enumerations.h	Sat Nov 04 13:42:30 2023 +0100
@@ -47,6 +47,27 @@
   static const char* const MIME_MTL = "model/mtl";
   static const char* const MIME_STL = "model/stl";
 
+  static const char* const MIME_CSS = "text/css";
+  static const char* const MIME_DICOM = "application/dicom";
+  static const char* const MIME_GIF = "image/gif";
+  static const char* const MIME_GZIP = "application/gzip";
+  static const char* const MIME_HTML = "text/html";
+  static const char* const MIME_JAVASCRIPT = "application/javascript";
+  static const char* const MIME_JPEG2000 = "image/jp2";
+  static const char* const MIME_NACL = "application/x-nacl";
+  static const char* const MIME_PLAIN_TEXT = "text/plain";
+  static const char* const MIME_PNACL = "application/x-pnacl";
+  static const char* const MIME_SVG = "image/svg+xml";
+  static const char* const MIME_WEB_ASSEMBLY = "application/wasm";
+  static const char* const MIME_WOFF = "application/x-font-woff";
+  static const char* const MIME_WOFF2 = "font/woff2";
+  static const char* const MIME_XML_2 = "text/xml";
+  static const char* const MIME_ZIP = "application/zip";
+  static const char* const MIME_DICOM_WEB_JSON = "application/dicom+json";
+  static const char* const MIME_DICOM_WEB_XML = "application/dicom+xml";
+  static const char* const MIME_ICO = "image/x-icon";
+
+
   /**
    * "No Internet Media Type (aka MIME type, content type) for PBM has
    * been registered with IANA, but the unofficial value
@@ -460,6 +481,15 @@
     HttpCompression_Gzip
   };
 
+  // Used to know if the content of an HTTP response is already compressed or not.
+  // If the content is already compressed, using HTTP compression on top of it is meaningless
+  // and consumes a lot of time.
+  enum ContentCompression
+  {
+    ContentCompression_Unknown,
+    ContentCompression_AlreadyCompressed,
+    ContentCompression_NotCompressed,
+  };
 
   // Specific Character Sets
   // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.12.1.1.2
@@ -920,4 +950,7 @@
 
   ORTHANC_PUBLIC
   void GetAllDicomTransferSyntaxes(std::set<DicomTransferSyntax>& target);
+
+  ORTHANC_PUBLIC
+  bool IsCompressedTransferSyntax(DicomTransferSyntax syntax);
 }
--- a/OrthancFramework/Sources/FileStorage/StorageAccessor.cpp	Tue Oct 17 15:06:11 2023 +0200
+++ b/OrthancFramework/Sources/FileStorage/StorageAccessor.cpp	Sat Nov 04 13:42:30 2023 +0100
@@ -400,11 +400,19 @@
                                    const FileInfo& info,
                                    const std::string& mime)
   {
+    AnswerFile(output, info, mime, ContentCompression_Unknown);
+  }
+
+  void StorageAccessor::AnswerFile(RestApiOutput& output,
+                                   const FileInfo& info,
+                                   const std::string& mime,
+                                   ContentCompression contentCompression)
+  {
     BufferHttpSender sender;
     SetupSender(sender, info, mime);
   
     HttpStreamTranscoder transcoder(sender, CompressionType_None); // since 1.11.2, the storage accessor only returns uncompressed buffers
-    output.AnswerStream(transcoder);
+    output.AnswerStream(transcoder, contentCompression);
   }
 #endif
 }
--- a/OrthancFramework/Sources/FileStorage/StorageAccessor.h	Tue Oct 17 15:06:11 2023 +0200
+++ b/OrthancFramework/Sources/FileStorage/StorageAccessor.h	Sat Nov 04 13:42:30 2023 +0100
@@ -133,6 +133,12 @@
     void AnswerFile(RestApiOutput& output,
                     const FileInfo& info,
                     const std::string& mime);
+
+    void AnswerFile(RestApiOutput& output,
+                    const FileInfo& info,
+                    const std::string& mime,
+                    ContentCompression contentCompression);
+
 #endif
   };
 }
--- a/OrthancFramework/Sources/HttpServer/FilesystemHttpHandler.cpp	Tue Oct 17 15:06:11 2023 +0200
+++ b/OrthancFramework/Sources/HttpServer/FilesystemHttpHandler.cpp	Sat Nov 04 13:42:30 2023 +0100
@@ -98,6 +98,7 @@
     s += "</html>";
 
     output.SetContentType(MimeType_Html);
+    output.SetContentCompression(SystemToolbox::GuessContentCompression(MimeType_Html));
     output.Answer(s);
   }
 
@@ -152,8 +153,12 @@
     if (SystemToolbox::IsRegularFile(p.string()))
     {
       FilesystemHttpSender sender(p);
-      sender.SetContentType(SystemToolbox::AutodetectMimeType(p.string()));
-      output.Answer(sender);   // TODO COMPRESSION
+      MimeType mimeType = SystemToolbox::AutodetectMimeType(p.string());
+      ContentCompression contentCompression = SystemToolbox::GuessContentCompression(mimeType);
+
+      sender.SetContentType(mimeType);
+      sender.SetContentCompression(contentCompression);
+      output.Answer(sender);
     }
     else if (listDirectoryContent_ &&
              fs::exists(p) && 
--- a/OrthancFramework/Sources/HttpServer/FilesystemHttpSender.cpp	Tue Oct 17 15:06:11 2023 +0200
+++ b/OrthancFramework/Sources/HttpServer/FilesystemHttpSender.cpp	Sat Nov 04 13:42:30 2023 +0100
@@ -22,6 +22,7 @@
 
 #include "../PrecompiledHeaders.h"
 #include "FilesystemHttpSender.h"
+#include "../SystemToolbox.h"
 
 #include "../OrthancException.h"
 
@@ -58,6 +59,16 @@
                                              MimeType contentType)
   {
     SetContentType(contentType);
+    SetContentCompression(SystemToolbox::GuessContentCompression(contentType));
+    Initialize(path);
+  }
+
+  FilesystemHttpSender::FilesystemHttpSender(const std::string& path,
+                                             MimeType contentType,
+                                             ContentCompression contentCompression)
+  {
+    SetContentType(contentType);
+    SetContentCompression(SystemToolbox::GuessContentCompression(contentType));
     Initialize(path);
   }
 
--- a/OrthancFramework/Sources/HttpServer/FilesystemHttpSender.h	Tue Oct 17 15:06:11 2023 +0200
+++ b/OrthancFramework/Sources/HttpServer/FilesystemHttpSender.h	Sat Nov 04 13:42:30 2023 +0100
@@ -48,6 +48,10 @@
     FilesystemHttpSender(const std::string& path,
                          MimeType contentType);
 
+    FilesystemHttpSender(const std::string& path,
+                         MimeType contentType,
+                         ContentCompression contentCompression);
+
     FilesystemHttpSender(const FilesystemStorage& storage,
                          const std::string& uuid);
 
--- a/OrthancFramework/Sources/HttpServer/HttpFileSender.cpp	Tue Oct 17 15:06:11 2023 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpFileSender.cpp	Sat Nov 04 13:42:30 2023 +0100
@@ -32,6 +32,11 @@
 
 namespace Orthanc
 {
+  void HttpFileSender::SetContentCompression(ContentCompression contentCompression)
+  {
+    contentCompression_ = contentCompression;
+  }
+
   void HttpFileSender::SetContentType(MimeType contentType)
   {
     contentType_ = EnumerationToString(contentType);
@@ -47,13 +52,20 @@
     return contentType_;
   }
 
+  ContentCompression HttpFileSender::GuessContentCompression() const
+  {
+    return contentCompression_;
+  }
+
   void HttpFileSender::SetContentFilename(const std::string& filename)
   {
     filename_ = filename;
 
     if (contentType_.empty())
     {
-      contentType_ = SystemToolbox::AutodetectMimeType(filename);
+      MimeType mimeType = SystemToolbox::AutodetectMimeType(filename);
+      contentType_ = EnumerationToString(mimeType);
+      contentCompression_ = SystemToolbox::GuessContentCompression(mimeType);
     }
   }
 
--- a/OrthancFramework/Sources/HttpServer/HttpFileSender.h	Tue Oct 17 15:06:11 2023 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpFileSender.h	Sat Nov 04 13:42:30 2023 +0100
@@ -32,6 +32,7 @@
   {
   private:
     std::string contentType_;
+    ContentCompression contentCompression_;
     std::string filename_;
 
   public:
@@ -39,8 +40,12 @@
 
     void SetContentType(const std::string& contentType);
 
+    void SetContentCompression(ContentCompression contentCompression);
+
     const std::string& GetContentType() const;
 
+    ContentCompression GuessContentCompression() const;
+
     void SetContentFilename(const std::string& filename);
 
     const std::string& GetContentFilename() const;
--- a/OrthancFramework/Sources/HttpServer/HttpOutput.cpp	Tue Oct 17 15:06:11 2023 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpOutput.cpp	Sat Nov 04 13:42:30 2023 +0100
@@ -51,6 +51,7 @@
                                          unsigned int keepAliveTimeout) : 
     stream_(stream),
     state_(State_WritingHeader),
+    contentCompression_(ContentCompression_Unknown),
     status_(HttpStatus_200_Ok),
     hasContentLength_(false),
     contentLength_(0),
@@ -102,6 +103,20 @@
     AddHeader("Content-Type", contentType);
   }
 
+  void HttpOutput::StateMachine::SetContentCompression(ContentCompression contentCompression)
+  {
+    contentCompression_ = contentCompression;
+  }
+
+  bool HttpOutput::StateMachine::IsContentCompressible() const
+  {
+    // We assume that all files that compress correctly (mainly JSON, XML) are clearly identified.
+    // Therefore, only the content identified as NotCompressed are compressed again.
+    // We consider that the content whose compression is Unknown, likely DICOM file whose transfer syntax
+    // could not be determined, must not be compressed either. 
+    return contentCompression_ == ContentCompression_NotCompressed;
+  }
+
   void HttpOutput::StateMachine::SetContentFilename(const char* filename)
   {
     // TODO Escape double quotes
@@ -275,13 +290,11 @@
 
   HttpCompression HttpOutput::GetPreferredCompression(size_t bodySize) const
   {
-#if 0
-    // TODO Do not compress small files?
-    if (bodySize < 512)
+    // Do not compress small files since there is no real size benefit.
+    if (bodySize < 2048)
     {
       return HttpCompression_None;
     }
-#endif
 
     // Prefer "gzip" over "deflate" if the choice is offered
 
@@ -375,6 +388,11 @@
     stateMachine_.SetContentType(contentType.c_str());
   }
 
+  void HttpOutput::SetContentCompression(ContentCompression contentCompression)
+  {
+    stateMachine_.SetContentCompression(contentCompression);
+  }
+
   void HttpOutput::SetContentFilename(const char *filename)
   {
     stateMachine_.SetContentFilename(filename);
@@ -442,7 +460,7 @@
 
     HttpCompression compression = GetPreferredCompression(length);
 
-    if (compression == HttpCompression_None)
+    if (compression == HttpCompression_None || !IsContentCompressible())
     {
       stateMachine_.SetContentLength(length);
       stateMachine_.SendBody(buffer, length);
@@ -816,6 +834,11 @@
   {
     HttpCompression compression = stream.SetupHttpCompression(isGzipAllowed_, isDeflateAllowed_);
 
+    if (!IsContentCompressible())
+    {
+      compression = HttpCompression_None;
+    }
+
     switch (compression)
     {
       case HttpCompression_None:
--- a/OrthancFramework/Sources/HttpServer/HttpOutput.h	Tue Oct 17 15:06:11 2023 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpOutput.h	Sat Nov 04 13:42:30 2023 +0100
@@ -56,6 +56,7 @@
       IHttpOutputStream& stream_;
       State state_;
 
+      ContentCompression contentCompression_;
       HttpStatus status_;
       bool hasContentLength_;
       uint64_t contentLength_;
@@ -82,6 +83,8 @@
 
       void SetContentType(const char* contentType);
 
+      void SetContentCompression(ContentCompression contentCompression);
+
       void SetContentFilename(const char* filename);
 
       void SetCookie(const std::string& cookie,
@@ -110,6 +113,8 @@
         return state_;
       }
 
+      bool IsContentCompressible() const;
+
       void CheckHeadersCompatibilityWithMultipart() const;
 
       void StartStream(const std::string& contentType);
@@ -139,6 +144,11 @@
 
     bool IsGzipAllowed() const;
 
+    bool IsContentCompressible() const
+    {
+      return stateMachine_.IsContentCompressible();
+    }
+
     void SendStatus(HttpStatus status,
 		    const char* message,
 		    size_t messageSize);
@@ -152,6 +162,8 @@
     
     void SetContentType(const std::string& contentType);
 
+    void SetContentCompression(ContentCompression contentCompression);
+
     void SetContentFilename(const char* filename);
 
     void SetCookie(const std::string& cookie,
--- a/OrthancFramework/Sources/RestApi/RestApiOutput.cpp	Tue Oct 17 15:06:11 2023 +0200
+++ b/OrthancFramework/Sources/RestApi/RestApiOutput.cpp	Sat Nov 04 13:42:30 2023 +0100
@@ -27,6 +27,7 @@
 #include "../Logging.h"
 #include "../OrthancException.h"
 #include "../Toolbox.h"
+#include "../SystemToolbox.h"
 
 #include <boost/lexical_cast.hpp>
 
@@ -72,7 +73,13 @@
 
   void RestApiOutput::AnswerStream(IHttpStreamAnswer& stream)
   {
+    AnswerStream(stream, ContentCompression_Unknown);
+  }
+
+  void RestApiOutput::AnswerStream(IHttpStreamAnswer& stream, ContentCompression contentCompression)
+  {
     CheckStatus();
+    output_.SetContentCompression(contentCompression);
     output_.Answer(stream);
     alreadySent_ = true;
   }
@@ -80,7 +87,13 @@
 
   void RestApiOutput::AnswerWithoutBuffering(IHttpStreamAnswer& stream)
   {
+    AnswerWithoutBuffering(stream, ContentCompression_Unknown);
+  }
+
+  void RestApiOutput::AnswerWithoutBuffering(IHttpStreamAnswer& stream, ContentCompression contentCompression)
+  {
     CheckStatus();
+    output_.SetContentCompression(contentCompression);
     output_.AnswerWithoutBuffering(stream);
     alreadySent_ = true;
   }
@@ -97,6 +110,7 @@
       Toolbox::JsonToXml(s, value);
 
       output_.SetContentType(MIME_XML_UTF8);
+      output_.SetContentCompression(ContentCompression_NotCompressed);
       output_.Answer(s);
 #else
       throw OrthancException(ErrorCode_InternalError,
@@ -107,7 +121,8 @@
     {
       std::string s;
       Toolbox::WriteStyledJson(s, value);
-      output_.SetContentType(MIME_JSON_UTF8);      
+      output_.SetContentType(MIME_JSON_UTF8);
+      output_.SetContentCompression(ContentCompression_NotCompressed);
       output_.Answer(s);
     }
 
@@ -117,14 +132,30 @@
   void RestApiOutput::AnswerBuffer(const std::string& buffer,
                                    MimeType contentType)
   {
+    AnswerBuffer(buffer, contentType, SystemToolbox::GuessContentCompression(contentType));
+  }
+
+
+  void RestApiOutput::AnswerBuffer(const std::string& buffer,
+                                   MimeType contentType,
+                                   ContentCompression contentCompression)
+  {
     AnswerBuffer(buffer.size() == 0 ? NULL : buffer.c_str(),
-                 buffer.size(), contentType);
+                 buffer.size(), contentType, contentCompression);
   }
 
   void RestApiOutput::AnswerBuffer(const void* buffer,
                                    size_t length,
                                    MimeType contentType)
   {
+    AnswerBuffer(buffer, length, contentType, SystemToolbox::GuessContentCompression(contentType));
+  }
+
+  void RestApiOutput::AnswerBuffer(const void* buffer,
+                                   size_t length,
+                                   MimeType contentType,
+                                   ContentCompression contentCompression)
+  {
     CheckStatus();
 
     if (convertJsonToXml_ &&
@@ -144,6 +175,7 @@
     else
     {
       output_.SetContentType(contentType);
+      output_.SetContentCompression(contentCompression);
       output_.Answer(buffer, length);
       alreadySent_ = true;
     }
--- a/OrthancFramework/Sources/RestApi/RestApiOutput.h	Tue Oct 17 15:06:11 2023 +0200
+++ b/OrthancFramework/Sources/RestApi/RestApiOutput.h	Sat Nov 04 13:42:30 2023 +0100
@@ -67,17 +67,32 @@
 
     void AnswerStream(IHttpStreamAnswer& stream);
 
+    void AnswerStream(IHttpStreamAnswer& stream,
+                      ContentCompression contentCompression);
+
     void AnswerWithoutBuffering(IHttpStreamAnswer& stream);
 
+    void AnswerWithoutBuffering(IHttpStreamAnswer& stream,
+                                ContentCompression contentCompression);
+
     void AnswerJson(const Json::Value& value);
 
     void AnswerBuffer(const std::string& buffer,
                       MimeType contentType);
 
+    void AnswerBuffer(const std::string& buffer,
+                      MimeType contentType,
+                      ContentCompression contentCompression);
+
     void AnswerBuffer(const void* buffer,
                       size_t length,
                       MimeType contentType);
 
+    void AnswerBuffer(const void* buffer,
+                      size_t length,
+                      MimeType contentType,
+                      ContentCompression contentCompression);
+
     void SetContentFilename(const char* filename);
 
     void SignalError(HttpStatus status);
--- a/OrthancFramework/Sources/SystemToolbox.cpp	Tue Oct 17 15:06:11 2023 +0200
+++ b/OrthancFramework/Sources/SystemToolbox.cpp	Sat Nov 04 13:42:30 2023 +0100
@@ -725,6 +725,80 @@
     }
   }
 
+  ContentCompression SystemToolbox::GuessContentCompression(MimeType mime)
+  {
+    switch (mime)
+    {
+      case MimeType_Css:
+      case MimeType_Html:
+      case MimeType_JavaScript:
+      case MimeType_Json:
+      case MimeType_Pam:
+      case MimeType_Pdf:
+      case MimeType_PlainText:
+      case MimeType_WebAssembly:
+      case MimeType_Xml:
+      case MimeType_PrometheusText:
+      case MimeType_DicomWebJson:
+      case MimeType_DicomWebXml:
+        return ContentCompression_NotCompressed;
+      case MimeType_Gif:
+      case MimeType_Jpeg:
+      case MimeType_Jpeg2000:
+      case MimeType_Png:
+      case MimeType_Svg:
+      case MimeType_Woff:
+      case MimeType_Woff2:
+      case MimeType_Zip:
+        return ContentCompression_AlreadyCompressed;
+      default: // for all other (DICOM, binary, ...) we actually don't know
+        return ContentCompression_Unknown;
+    }
+  }
+  
+  ContentCompression SystemToolbox::GuessContentCompression(const std::string& contentType)
+  {
+    if (contentType.empty())
+    {
+      return ContentCompression_Unknown;
+    }
+
+    if (contentType.find(MIME_JSON) != std::string::npos ||
+        contentType.find(MIME_XML) != std::string::npos ||
+        contentType.find(MIME_DICOM_WEB_JSON) != std::string::npos ||
+        contentType.find(MIME_DICOM_WEB_XML) != std::string::npos ||
+        contentType.find(MIME_PDF) != std::string::npos ||
+        contentType.find(MIME_CSS) != std::string::npos ||
+        contentType.find(MIME_HTML) != std::string::npos ||
+        contentType.find(MIME_JAVASCRIPT) != std::string::npos ||
+        contentType.find(MIME_PLAIN_TEXT) != std::string::npos ||
+        contentType.find(MIME_WEB_ASSEMBLY) != std::string::npos ||
+        contentType.find(MIME_XML_2) != std::string::npos ||
+        contentType.find(MIME_ICO) != std::string::npos)
+    {
+      return ContentCompression_NotCompressed;
+    }
+    else if (contentType.find(MIME_DICOM) != std::string::npos || // this must happen after the test for MIME_DICOM_WEB_JSON since application/dicom is inside application/dicom+json
+             contentType.find(MIME_BINARY) != std::string::npos)
+    {
+      // For DICOM, it is impossible to know the transfer syntax at this point so we don't know if the data is compressed or not
+      return ContentCompression_Unknown;
+    }
+    else if (contentType.find(MIME_JPEG) != std::string::npos ||
+             contentType.find(MIME_PNG) != std::string::npos ||
+             contentType.find(MIME_GIF) != std::string::npos ||
+             contentType.find(MIME_JPEG2000) != std::string::npos ||
+             contentType.find(MIME_GZIP) != std::string::npos ||
+             contentType.find(MIME_ZIP) != std::string::npos ||
+             contentType.find(MIME_SVG) != std::string::npos ||
+             contentType.find(MIME_WOFF) != std::string::npos ||
+             contentType.find(MIME_WOFF2) != std::string::npos)
+    {
+      return ContentCompression_AlreadyCompressed;
+    }
+
+    return ContentCompression_Unknown;
+  }
 
   MimeType SystemToolbox::AutodetectMimeType(const std::string& path)
   {
--- a/OrthancFramework/Sources/SystemToolbox.h	Tue Oct 17 15:06:11 2023 +0200
+++ b/OrthancFramework/Sources/SystemToolbox.h	Sat Nov 04 13:42:30 2023 +0100
@@ -108,6 +108,10 @@
 
     static unsigned int GetHardwareConcurrency();
 
+    static ContentCompression GuessContentCompression(MimeType mime);
+
+    static ContentCompression GuessContentCompression(const std::string& contentType);
+
     static MimeType AutodetectMimeType(const std::string& path);
 
     static void GetEnvironmentVariables(std::map<std::string, std::string>& env);
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Tue Oct 17 15:06:11 2023 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Sat Nov 04 13:42:30 2023 +0100
@@ -2974,6 +2974,7 @@
 
     HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
     translatedOutput.SetContentType(p.mimeType);
+    translatedOutput.SetContentCompression(SystemToolbox::GuessContentCompression(p.mimeType));
     translatedOutput.Answer(p.answer, p.answerSize);
   }
 
@@ -3107,6 +3108,7 @@
         PngWriter writer;
         IImageWriter::WriteToMemory(writer, compressed, accessor);
         translatedOutput.SetContentType(MimeType_Png);
+        translatedOutput.SetContentCompression(ContentCompression_AlreadyCompressed);
         break;
       }
 
@@ -3116,6 +3118,7 @@
         writer.SetQuality(p.quality);
         IImageWriter::WriteToMemory(writer, compressed, accessor);
         translatedOutput.SetContentType(MimeType_Jpeg);
+        translatedOutput.SetContentCompression(ContentCompression_AlreadyCompressed);
         break;
       }
 
--- a/OrthancServer/Sources/EmbeddedResourceHttpHandler.cpp	Tue Oct 17 15:06:11 2023 +0200
+++ b/OrthancServer/Sources/EmbeddedResourceHttpHandler.cpp	Sat Nov 04 13:42:30 2023 +0100
@@ -73,6 +73,7 @@
       size_t size = ServerResources::GetDirectoryResourceSize(resourceId_, resourcePath.c_str());
 
       output.SetContentType(contentType);
+      output.SetContentCompression(SystemToolbox::GuessContentCompression(contentType));
       output.Answer(buffer, size);
     }
     catch (OrthancException&)
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Tue Oct 17 15:06:11 2023 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Sat Nov 04 13:42:30 2023 +0100
@@ -219,7 +219,9 @@
       if (context.Transcode(transcoded, source, s, true))
       {      
         call.GetOutput().AnswerBuffer(transcoded.GetBufferData(),
-                                      transcoded.GetBufferSize(), MimeType_Dicom);
+                                      transcoded.GetBufferSize(), 
+                                      MimeType_Dicom, 
+                                      (IsCompressedTransferSyntax(targetSyntax) ? ContentCompression_AlreadyCompressed : ContentCompression_NotCompressed));
       }
       else
       {
--- a/OrthancServer/Sources/ServerContext.cpp	Tue Oct 17 15:06:11 2023 +0200
+++ b/OrthancServer/Sources/ServerContext.cpp	Sat Nov 04 13:42:30 2023 +0100
@@ -898,8 +898,23 @@
     }
     else
     {
+      ContentCompression contentCompression = ContentCompression_Unknown;
+
+      // try to avoid compressing a large DICOM file ( > 1MB) that is already compressed (For large files, this could take multiple seconds)
+      std::string transferSyntaxString;
+      DicomTransferSyntax transferSyntax;
+      int64_t revision;
+
+      if (content == FileContentType_Dicom &&
+          attachment.GetUncompressedSize() > 1*1024*1024 &&  // don't even waste time reading the TransferSyntax from DB for "small" DICOM files
+          index_.LookupMetadata(transferSyntaxString, revision, resourceId, ResourceType_Instance, MetadataType_Instance_TransferSyntax) &&
+          LookupTransferSyntax(transferSyntax, transferSyntaxString))
+      {
+        contentCompression = (IsCompressedTransferSyntax(transferSyntax) ? ContentCompression_AlreadyCompressed : ContentCompression_NotCompressed);
+      }
+
       StorageAccessor accessor(area_, storageCache_, GetMetricsRegistry());
-      accessor.AnswerFile(output, attachment, GetFileContentMime(content));
+      accessor.AnswerFile(output, attachment, GetFileContentMime(content), contentCompression);
     }
   }