# HG changeset patch # User Sebastien Jodogne # Date 1439209104 -7200 # Node ID 7962563129c97e62b85d4218f34ccf58ca4c757b # Parent ffc9f36103b9ab9eed07c89337306dae6180f7e9 starting support of deflate/gzip content types diff -r ffc9f36103b9 -r 7962563129c9 Core/Compression/ZlibCompressor.cpp --- 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(&compressed[0]) + sizeof(size_t), - &compressedSize, - const_cast(static_cast(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(&compressed[0]) + sizeof(uint64_t); } else { + compressed.resize(compressedSize); + target = reinterpret_cast(&compressed[0]); + } + + int error = compress2(target, + &compressedSize, + const_cast(static_cast(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(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(&uncompressed[0]), &tmp, - reinterpret_cast(compressed) + sizeof(size_t), - compressedSize - sizeof(size_t)); + reinterpret_cast(compressed) + sizeof(uint64_t), + compressedSize - sizeof(uint64_t)); if (error != Z_OK) { diff -r ffc9f36103b9 -r 7962563129c9 Core/Compression/ZlibCompressor.h --- 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 { diff -r ffc9f36103b9 -r 7962563129c9 Core/Enumerations.h --- 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 }; diff -r ffc9f36103b9 -r 7962563129c9 Core/HttpServer/HttpOutput.cpp --- 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 #include @@ -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); diff -r ffc9f36103b9 -r 7962563129c9 Core/HttpServer/HttpOutput.h --- 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(); diff -r ffc9f36103b9 -r 7962563129c9 Core/RestApi/RestApi.cpp --- 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 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 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 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); diff -r ffc9f36103b9 -r 7962563129c9 Core/RestApi/RestApiOutput.cpp --- 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; } diff -r ffc9f36103b9 -r 7962563129c9 Core/RestApi/RestApiOutput.h --- 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);