# HG changeset patch # User Alain Mazy # Date 1699101750 -3600 # Node ID aaf7c49a9ddc88068b06b9daf2b9daff0307d46e # Parent 62bb63346185877e902c8f7ca9dc029c0325cee2 tentative to implement smart HTTP compression with detection of transfer syntax diff -r 62bb63346185 -r aaf7c49a9ddc NEWS --- 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 diff -r 62bb63346185 -r aaf7c49a9ddc OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp --- 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 diff -r 62bb63346185 -r aaf7c49a9ddc OrthancFramework/Sources/Enumerations.cpp --- 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; + } + } } diff -r 62bb63346185 -r aaf7c49a9ddc OrthancFramework/Sources/Enumerations.h --- 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& target); + + ORTHANC_PUBLIC + bool IsCompressedTransferSyntax(DicomTransferSyntax syntax); } diff -r 62bb63346185 -r aaf7c49a9ddc OrthancFramework/Sources/FileStorage/StorageAccessor.cpp --- 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 } diff -r 62bb63346185 -r aaf7c49a9ddc OrthancFramework/Sources/FileStorage/StorageAccessor.h --- 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 }; } diff -r 62bb63346185 -r aaf7c49a9ddc OrthancFramework/Sources/HttpServer/FilesystemHttpHandler.cpp --- 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 += ""; 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) && diff -r 62bb63346185 -r aaf7c49a9ddc OrthancFramework/Sources/HttpServer/FilesystemHttpSender.cpp --- 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); } diff -r 62bb63346185 -r aaf7c49a9ddc OrthancFramework/Sources/HttpServer/FilesystemHttpSender.h --- 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); diff -r 62bb63346185 -r aaf7c49a9ddc OrthancFramework/Sources/HttpServer/HttpFileSender.cpp --- 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); } } diff -r 62bb63346185 -r aaf7c49a9ddc OrthancFramework/Sources/HttpServer/HttpFileSender.h --- 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; diff -r 62bb63346185 -r aaf7c49a9ddc OrthancFramework/Sources/HttpServer/HttpOutput.cpp --- 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: diff -r 62bb63346185 -r aaf7c49a9ddc OrthancFramework/Sources/HttpServer/HttpOutput.h --- 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, diff -r 62bb63346185 -r aaf7c49a9ddc OrthancFramework/Sources/RestApi/RestApiOutput.cpp --- 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 @@ -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; } diff -r 62bb63346185 -r aaf7c49a9ddc OrthancFramework/Sources/RestApi/RestApiOutput.h --- 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); diff -r 62bb63346185 -r aaf7c49a9ddc OrthancFramework/Sources/SystemToolbox.cpp --- 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) { diff -r 62bb63346185 -r aaf7c49a9ddc OrthancFramework/Sources/SystemToolbox.h --- 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& env); diff -r 62bb63346185 -r aaf7c49a9ddc OrthancServer/Plugins/Engine/OrthancPlugins.cpp --- 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(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; } diff -r 62bb63346185 -r aaf7c49a9ddc OrthancServer/Sources/EmbeddedResourceHttpHandler.cpp --- 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&) diff -r 62bb63346185 -r aaf7c49a9ddc OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp --- 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 { diff -r 62bb63346185 -r aaf7c49a9ddc OrthancServer/Sources/ServerContext.cpp --- 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); } }