changeset 1525:f9b0169eb6bb

testing
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 11 Aug 2015 17:50:38 +0200
parents 4a0c2eedceb6
children 096a8af528c9
files CMakeLists.txt Core/HttpServer/BufferHttpSender.cpp Core/HttpServer/BufferHttpSender.h Core/HttpServer/FilesystemHttpSender.cpp Core/HttpServer/FilesystemHttpSender.h Core/HttpServer/HttpFileSender.h Core/HttpServer/HttpStreamTranscoder.cpp Core/HttpServer/HttpStreamTranscoder.h Core/HttpServer/IHttpStreamAnswer.h OrthancServer/ServerContext.cpp UnitTestsSources/StreamTests.cpp UnitTestsSources/UnitTestsMain.cpp
diffstat 12 files changed, 726 insertions(+), 291 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Tue Aug 11 16:09:24 2015 +0200
+++ b/CMakeLists.txt	Tue Aug 11 17:50:38 2015 +0200
@@ -109,6 +109,7 @@
   Core/HttpServer/MongooseServer.cpp
   Core/HttpServer/HttpFileSender.cpp
   Core/HttpServer/FilesystemHttpSender.cpp
+  Core/HttpServer/HttpStreamTranscoder.cpp
   Core/Logging.cpp
   Core/RestApi/RestApiCall.cpp
   Core/RestApi/RestApiGetCall.cpp
@@ -214,6 +215,7 @@
   UnitTestsSources/ImageProcessingTests.cpp
   UnitTestsSources/JpegLosslessTests.cpp
   UnitTestsSources/PluginsTests.cpp
+  UnitTestsSources/StreamTests.cpp
   )
 
 
--- a/Core/HttpServer/BufferHttpSender.cpp	Tue Aug 11 16:09:24 2015 +0200
+++ b/Core/HttpServer/BufferHttpSender.cpp	Tue Aug 11 17:50:38 2015 +0200
@@ -34,28 +34,51 @@
 
 #include "../OrthancException.h"
 
+#include <cassert>
+
 namespace Orthanc
 {
+  BufferHttpSender::BufferHttpSender() :
+    position_(0), 
+    chunkSize_(0),
+    currentChunkSize_(0)
+  {
+  }
+
+
   bool BufferHttpSender::ReadNextChunk()
   {
-    if (done_)
+    assert(position_ + currentChunkSize_ <= buffer_.size());
+
+    position_ += currentChunkSize_;
+
+    if (position_ == buffer_.size())
     {
       return false;
     }
     else
     {
-      done_ = true;
+      currentChunkSize_ = buffer_.size() - position_;
+
+      if (chunkSize_ != 0 &&
+          currentChunkSize_ > chunkSize_)
+      {
+        currentChunkSize_ = chunkSize_;
+      }
+
       return true;
     }
   }
 
+
   const char* BufferHttpSender::GetChunkContent()
   {
-    return buffer_.c_str();
+    return buffer_.c_str() + position_;
   }
 
+
   size_t BufferHttpSender::GetChunkSize()
   {
-    return buffer_.size();
+    return currentChunkSize_;
   }
 }
--- a/Core/HttpServer/BufferHttpSender.h	Tue Aug 11 16:09:24 2015 +0200
+++ b/Core/HttpServer/BufferHttpSender.h	Tue Aug 11 17:50:38 2015 +0200
@@ -38,13 +38,13 @@
   class BufferHttpSender : public HttpFileSender
   {
   private:
-    std::string buffer_;
-    bool done_;
+    std::string  buffer_;
+    size_t       position_;
+    size_t       chunkSize_;
+    size_t       currentChunkSize_;
 
   public:
-    BufferHttpSender() : done_(false)
-    {
-    }
+    BufferHttpSender();
 
     std::string& GetBuffer() 
     {
@@ -56,18 +56,18 @@
       return buffer_;
     }
 
+    // This is for test purpose. If "chunkSize" is set to "0" (the
+    // default), the entire buffer is consumed at once.
+    void SetChunkSize(size_t chunkSize)
+    {
+      chunkSize_ = chunkSize;
+    }
+
 
     /**
      * Implementation of the IHttpStreamAnswer interface.
      **/
 
-    virtual HttpCompression SetupHttpCompression(bool /*gzipAllowed*/, 
-                                                 bool /*deflateAllowed*/)
-    {
-      // No compression is supported
-      return HttpCompression_None;
-    }
-
     virtual uint64_t GetContentLength()
     {
       return buffer_.size();
--- a/Core/HttpServer/FilesystemHttpSender.cpp	Tue Aug 11 16:09:24 2015 +0200
+++ b/Core/HttpServer/FilesystemHttpSender.cpp	Tue Aug 11 17:50:38 2015 +0200
@@ -32,12 +32,7 @@
 #include "../PrecompiledHeaders.h"
 #include "FilesystemHttpSender.h"
 
-#include "../Toolbox.h"
 #include "../OrthancException.h"
-#include "../Compression/ZlibCompressor.h"
-
-#include <stdio.h>
-
 
 static const size_t  CHUNK_SIZE = 64 * 1024;   // Use 64KB chunks
 
@@ -45,9 +40,6 @@
 {
   void FilesystemHttpSender::Initialize(const boost::filesystem::path& path)
   {
-    sourceCompression_ = CompressionType_None;
-    targetCompression_ = HttpCompression_None;
-
     SetContentFilename(path.filename().string());
     file_.open(path.string().c_str(), std::ifstream::binary);
 
@@ -62,129 +54,23 @@
   }
 
 
-  HttpCompression FilesystemHttpSender::SetupHttpCompression(bool gzipAllowed, 
-                                                             bool deflateAllowed)
-  {
-    switch (sourceCompression_)
-    {
-      case CompressionType_None:
-      {
-        return HttpCompression_None;
-      }
-
-      case CompressionType_ZlibWithSize:
-      {
-        if (size_ == 0)
-        {
-          return HttpCompression_None;
-        }
-
-        if (size_ < sizeof(uint64_t))
-        {
-          throw OrthancException(ErrorCode_CorruptedFile);
-        }
-
-        if (deflateAllowed)
-        {
-          file_.seekg(sizeof(uint64_t), file_.beg);
-          size_ -= sizeof(uint64_t);
-          return HttpCompression_Deflate;
-        }
-        else
-        {
-          uncompressed_.reset(new BufferHttpSender);
-
-          // TODO Stream-based uncompression
-          assert(size_ != 0);
-          std::string compressed;
-          compressed.resize(size_);
-
-          file_.read(&compressed[0], size_);
-          if ((file_.flags() & std::istream::failbit) ||
-              !(file_.flags() & std::istream::eofbit) ||
-              file_.gcount() < 0 ||
-              static_cast<uint64_t>(file_.gcount()) != size_)
-          {
-            throw OrthancException(ErrorCode_CorruptedFile);
-          }
-          
-          ZlibCompressor compressor;
-          IBufferCompressor::Uncompress(uncompressed_->GetBuffer(), compressor, compressed);
-
-          return HttpCompression_None;
-        }
-
-        break;
-      }
-
-      default:
-        throw OrthancException(ErrorCode_NotImplemented);
-    }
-  }
-
-
-  uint64_t FilesystemHttpSender::GetContentLength()
-  {
-    if (uncompressed_.get() != NULL)
-    {
-      return uncompressed_->GetContentLength();
-    }
-    else
-    {
-      return size_;
-    }
-  }
-
-
   bool FilesystemHttpSender::ReadNextChunk()
   {
-    if (uncompressed_.get() != NULL)
+    if (chunk_.size() == 0)
     {
-      return uncompressed_->ReadNextChunk();
+      chunk_.resize(CHUNK_SIZE);
     }
-    else
-    {
-      if (chunk_.size() == 0)
-      {
-        chunk_.resize(CHUNK_SIZE);
-      }
 
-      file_.read(&chunk_[0], chunk_.size());
-
-      if (file_.flags() & std::istream::failbit)
-      {
-        throw OrthancException(ErrorCode_CorruptedFile);
-      }
-
-      chunkSize_ = file_.gcount();
-
-      return chunkSize_ > 0;
-    }
-  }
+    file_.read(&chunk_[0], chunk_.size());
 
-
-  const char* FilesystemHttpSender::GetChunkContent()
-  {
-    if (uncompressed_.get() != NULL)
+    if ((file_.flags() & std::istream::failbit) ||
+        file_.gcount() < 0)
     {
-      return uncompressed_->GetChunkContent();
-    }
-    else
-    {
-      return chunk_.c_str();
+      throw OrthancException(ErrorCode_CorruptedFile);
     }
-  }
 
-  
-  size_t FilesystemHttpSender::GetChunkSize()
-  {
-    if (uncompressed_.get() != NULL)
-    {
-      return uncompressed_->GetChunkSize();
-    }
-    else
-    {
-      return chunkSize_;
-    }
+    chunkSize_ = file_.gcount();
+
+    return chunkSize_ > 0;
   }
 }
--- a/Core/HttpServer/FilesystemHttpSender.h	Tue Aug 11 16:09:24 2015 +0200
+++ b/Core/HttpServer/FilesystemHttpSender.h	Tue Aug 11 17:50:38 2015 +0200
@@ -46,10 +46,6 @@
     uint64_t         size_;
     std::string      chunk_;
     size_t           chunkSize_;
-    CompressionType  sourceCompression_;
-    HttpCompression  targetCompression_;
-
-    std::auto_ptr<BufferHttpSender>  uncompressed_;
 
     void Initialize(const boost::filesystem::path& path);
 
@@ -70,25 +66,25 @@
       Initialize(storage.GetPath(uuid));
     }
 
-    void SetSourceCompression(CompressionType compression)
-    {
-      sourceCompression_ = compression;
-    }
-
-
     /**
      * Implementation of the IHttpStreamAnswer interface.
      **/
 
-    virtual HttpCompression SetupHttpCompression(bool /*gzipAllowed*/, 
-                                                 bool /*deflateAllowed*/);
-
-    virtual uint64_t GetContentLength();
+    virtual uint64_t GetContentLength()
+    {
+      return size_;
+    }
 
     virtual bool ReadNextChunk();
 
-    virtual const char* GetChunkContent();
+    virtual const char* GetChunkContent()
+    {
+      return chunk_.c_str();
+    }
 
-    virtual size_t GetChunkSize();
+    virtual size_t GetChunkSize()
+    {
+      return chunkSize_;
+    }
   };
 }
--- a/Core/HttpServer/HttpFileSender.h	Tue Aug 11 16:09:24 2015 +0200
+++ b/Core/HttpServer/HttpFileSender.h	Tue Aug 11 17:50:38 2015 +0200
@@ -65,6 +65,12 @@
      * Implementation of the IHttpStreamAnswer interface.
      **/
 
+    virtual HttpCompression SetupHttpCompression(bool /*gzipAllowed*/, 
+                                                 bool /*deflateAllowed*/)
+    {
+      return HttpCompression_None;
+    }
+
     virtual bool HasContentFilename(std::string& filename);
     
     virtual std::string GetContentType();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/HttpServer/HttpStreamTranscoder.cpp	Tue Aug 11 17:50:38 2015 +0200
@@ -0,0 +1,223 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "HttpStreamTranscoder.h"
+
+#include "../OrthancException.h"
+#include "../Compression/ZlibCompressor.h"
+
+#include <string.h>   // For memcpy()
+#include <cassert>
+
+#include <stdio.h>
+
+namespace Orthanc
+{
+  void HttpStreamTranscoder::ReadSource(std::string& buffer)
+  {
+    if (source_.SetupHttpCompression(false, false) != HttpCompression_None)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    
+    uint64_t size = source_.GetContentLength();
+    if (static_cast<uint64_t>(static_cast<size_t>(size)) != size)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+
+    buffer.resize(static_cast<size_t>(size));
+    size_t offset = 0;
+
+    while (source_.ReadNextChunk())
+    {
+      size_t chunkSize = static_cast<size_t>(source_.GetChunkSize());
+      memcpy(&buffer[offset], source_.GetChunkContent(), chunkSize);
+      offset += chunkSize;
+    }
+
+    if (offset != size)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
+  HttpCompression HttpStreamTranscoder::SetupHttpCompression(bool gzipAllowed,
+                                                             bool deflateAllowed)
+  {
+    switch (sourceCompression_)
+    {
+      case CompressionType_None:
+      {
+        return HttpCompression_None;
+      }
+
+      case CompressionType_ZlibWithSize:
+      {
+        uint64_t size = source_.GetContentLength();
+
+        if (size == 0)
+        {
+          return HttpCompression_None;
+        }
+
+        if (size < sizeof(uint64_t))
+        {
+          throw OrthancException(ErrorCode_CorruptedFile);
+        }
+
+        if (deflateAllowed)
+        {
+          bytesToSkip_ = sizeof(uint64_t);
+          return HttpCompression_Deflate;
+        }
+        else
+        {
+          std::string compressed;
+          ReadSource(compressed);
+
+          uncompressed_.reset(new BufferHttpSender);
+
+          ZlibCompressor compressor;
+          IBufferCompressor::Uncompress(uncompressed_->GetBuffer(), compressor, compressed);
+
+          return HttpCompression_None;
+        }
+
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  uint64_t HttpStreamTranscoder::GetContentLength()
+  {
+    if (uncompressed_.get() != NULL)
+    {
+      return uncompressed_->GetContentLength();
+    }
+    else
+    {
+      uint64_t length = source_.GetContentLength();
+      if (length < bytesToSkip_)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      return length - bytesToSkip_;
+    }
+  }
+
+
+  bool HttpStreamTranscoder::ReadNextChunk()
+  {
+    if (uncompressed_.get() != NULL)
+    {
+      return uncompressed_->ReadNextChunk();
+    }
+
+    assert(skipped_ <= bytesToSkip_);
+    if (skipped_ == bytesToSkip_)
+    {
+      // We have already skipped the first bytes of the stream
+      currentChunkOffset_ = 0;
+      return source_.ReadNextChunk();
+    }
+
+    // This condition can only be true on the first call to "ReadNextChunk()"
+    for (;;)
+    {
+      assert(skipped_ < bytesToSkip_);
+      printf("[%d %d]  ", skipped_, bytesToSkip_); fflush(stdout);
+
+      bool ok = source_.ReadNextChunk();
+      if (!ok)
+      {
+        throw OrthancException(ErrorCode_CorruptedFile);
+      }
+
+      size_t remaining = bytesToSkip_ - skipped_;
+      size_t s = source_.GetChunkSize();
+
+      if (s < remaining)
+      {
+        skipped_ += s;
+      }
+      else if (s == remaining)
+      {
+        // We have skipped enough bytes, but we must read a new chunk
+        currentChunkOffset_ = 0;            
+        skipped_ = bytesToSkip_;
+        return source_.GetChunkSize();
+      }
+      else
+      {
+        assert(s > remaining);
+            
+        // We have skipped enough bytes, and we have enough data in the current chunk
+        currentChunkOffset_ = s - remaining;
+        skipped_ = bytesToSkip_;
+        return true;
+      }
+    }
+  }
+
+
+  const char* HttpStreamTranscoder::GetChunkContent()
+  {
+    if (uncompressed_.get() != NULL)
+    {
+      return uncompressed_->GetChunkContent();
+    }
+    else
+    {
+      return source_.GetChunkContent() + currentChunkOffset_;
+    }
+  }
+
+  size_t HttpStreamTranscoder::GetChunkSize()
+  {
+    if (uncompressed_.get() != NULL)
+    {
+      return uncompressed_->GetChunkSize();
+    }
+    else
+    {
+      return source_.GetChunkSize() - currentChunkOffset_;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/HttpServer/HttpStreamTranscoder.h	Tue Aug 11 17:50:38 2015 +0200
@@ -0,0 +1,86 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "BufferHttpSender.h"
+
+#include <memory>  // For std::auto_ptr
+
+namespace Orthanc
+{
+  class HttpStreamTranscoder : public IHttpStreamAnswer
+  {
+  private:
+    IHttpStreamAnswer& source_;
+    CompressionType    sourceCompression_;
+    uint64_t           bytesToSkip_;
+    uint64_t           skipped_;
+    uint64_t           currentChunkOffset_;
+
+    std::auto_ptr<BufferHttpSender>  uncompressed_;
+
+    void ReadSource(std::string& buffer);
+
+  public:
+    HttpStreamTranscoder(IHttpStreamAnswer& source,
+                         CompressionType compression) : 
+      source_(source),
+      sourceCompression_(compression),
+      bytesToSkip_(0),
+      skipped_(0)
+    {
+    }
+
+    // This is the first method to be called
+    virtual HttpCompression SetupHttpCompression(bool gzipAllowed,
+                                                 bool deflateAllowed);
+
+    virtual bool HasContentFilename(std::string& filename)
+    {
+      return source_.HasContentFilename(filename);
+    }
+
+    virtual std::string GetContentType()
+    {
+      return source_.GetContentType();
+    }
+
+    virtual uint64_t GetContentLength();
+
+    virtual bool ReadNextChunk();
+
+    virtual const char* GetChunkContent();
+
+    virtual size_t GetChunkSize();
+  };
+}
--- a/Core/HttpServer/IHttpStreamAnswer.h	Tue Aug 11 16:09:24 2015 +0200
+++ b/Core/HttpServer/IHttpStreamAnswer.h	Tue Aug 11 17:50:38 2015 +0200
@@ -32,8 +32,11 @@
 
 #pragma once
 
+#include "../Enumerations.h"
+
 #include <stdint.h>
 #include <boost/noncopyable.hpp>
+#include <string>
 
 namespace Orthanc
 {
--- a/OrthancServer/ServerContext.cpp	Tue Aug 11 16:09:24 2015 +0200
+++ b/OrthancServer/ServerContext.cpp	Tue Aug 11 17:50:38 2015 +0200
@@ -34,6 +34,7 @@
 #include "ServerContext.h"
 
 #include "../Core/HttpServer/FilesystemHttpSender.h"
+#include "../Core/HttpServer/HttpStreamTranscoder.h"
 #include "../Core/Logging.h"
 #include "FromDcmtkBridge.h"
 #include "ServerToolbox.h"
@@ -318,16 +319,17 @@
 
     std::auto_ptr<HttpFileSender> sender(accessor_.ConstructHttpFileSender(attachment.GetUuid(), attachment.GetContentType()));
     sender->SetContentType(GetMimeType(content));
-    sender->SetContentFilename(instancePublicId + ".dcm");  // TODO ".dcm" => ToMimeType(content)
+    sender->SetContentFilename(attachment.GetUuid() + ".dcm");  // TODO ".dcm" => ToMimeType(content)
     output.AnswerStream(*sender);
 #else
     const FilesystemStorage& a = dynamic_cast<FilesystemStorage&>(accessor_.GetStorageArea());
     
     FilesystemHttpSender sender(a, attachment.GetUuid());
-    sender.SetSourceCompression(attachment.GetCompressionType());
     sender.SetContentType(GetMimeType(content));
-    sender.SetContentFilename(instancePublicId + ".dcm");
-    output.AnswerStream(sender);
+    sender.SetContentFilename(attachment.GetUuid() + ".dcm");
+
+    HttpStreamTranscoder transcoder(sender, attachment.GetCompressionType());
+    output.AnswerStream(transcoder);
 #endif
   }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/StreamTests.cpp	Tue Aug 11 17:50:38 2015 +0200
@@ -0,0 +1,340 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PrecompiledHeadersUnitTests.h"
+#include "gtest/gtest.h"
+
+#include "../Core/Toolbox.h"
+#include "../Core/OrthancException.h"
+#include "../Core/Uuid.h"
+#include "../Core/HttpServer/BufferHttpSender.h"
+#include "../Core/HttpServer/FilesystemHttpSender.h"
+#include "../Core/HttpServer/HttpStreamTranscoder.h"
+#include "../Core/Compression/ZlibCompressor.h"
+#include "../Core/Compression/GzipCompressor.h"
+
+
+using namespace Orthanc;
+
+
+TEST(Gzip, Basic)
+{
+  std::string s = "Hello world";
+ 
+  std::string compressed;
+  GzipCompressor c;
+  ASSERT_FALSE(c.HasPrefixWithUncompressedSize());
+  IBufferCompressor::Compress(compressed, c, s);
+
+  std::string uncompressed;
+  IBufferCompressor::Uncompress(uncompressed, c, compressed);
+  ASSERT_EQ(s.size(), uncompressed.size());
+  ASSERT_EQ(0, memcmp(&s[0], &uncompressed[0], s.size()));
+}
+
+
+TEST(Gzip, Empty)
+{
+  std::string s;
+ 
+  std::string compressed;
+  GzipCompressor c;
+  ASSERT_FALSE(c.HasPrefixWithUncompressedSize());
+  c.SetPrefixWithUncompressedSize(false);
+  IBufferCompressor::Compress(compressed, c, s);
+
+  std::string uncompressed;
+  IBufferCompressor::Uncompress(uncompressed, c, compressed);
+  ASSERT_EQ(0, uncompressed.size());
+}
+
+
+TEST(Gzip, BasicWithPrefix)
+{
+  std::string s = "Hello world";
+ 
+  std::string compressed;
+  GzipCompressor c;
+  c.SetPrefixWithUncompressedSize(true);
+  ASSERT_TRUE(c.HasPrefixWithUncompressedSize());
+  IBufferCompressor::Compress(compressed, c, s);
+
+  std::string uncompressed;
+  IBufferCompressor::Uncompress(uncompressed, c, compressed);
+  ASSERT_EQ(s.size(), uncompressed.size());
+  ASSERT_EQ(0, memcmp(&s[0], &uncompressed[0], s.size()));
+}
+
+
+TEST(Gzip, EmptyWithPrefix)
+{
+  std::string s;
+ 
+  std::string compressed;
+  GzipCompressor c;
+  c.SetPrefixWithUncompressedSize(true);
+  ASSERT_TRUE(c.HasPrefixWithUncompressedSize());
+  IBufferCompressor::Compress(compressed, c, s);
+
+  std::string uncompressed;
+  IBufferCompressor::Uncompress(uncompressed, c, compressed);
+  ASSERT_EQ(0, uncompressed.size());
+}
+
+
+TEST(Zlib, Basic)
+{
+  std::string s = Toolbox::GenerateUuid();
+  s = s + s + s + s;
+ 
+  std::string compressed, compressed2;
+  ZlibCompressor c;
+  ASSERT_TRUE(c.HasPrefixWithUncompressedSize());
+  IBufferCompressor::Compress(compressed, c, s);
+
+  std::string uncompressed;
+  IBufferCompressor::Uncompress(uncompressed, c, compressed);
+  ASSERT_EQ(s.size(), uncompressed.size());
+  ASSERT_EQ(0, memcmp(&s[0], &uncompressed[0], s.size()));
+}
+
+
+TEST(Zlib, Level)
+{
+  std::string s = Toolbox::GenerateUuid();
+  s = s + s + s + s;
+ 
+  std::string compressed, compressed2;
+  ZlibCompressor c;
+  c.SetCompressionLevel(9);
+  IBufferCompressor::Compress(compressed, c, s);
+
+  c.SetCompressionLevel(0);
+  IBufferCompressor::Compress(compressed2, c, s);
+
+  ASSERT_TRUE(compressed.size() < compressed2.size());
+}
+
+
+TEST(Zlib, DISABLED_Corrupted)  // Disabled because it may result in a crash
+{
+  std::string s = Toolbox::GenerateUuid();
+  s = s + s + s + s;
+ 
+  std::string compressed;
+  ZlibCompressor c;
+  IBufferCompressor::Compress(compressed, c, s);
+
+  compressed[compressed.size() - 1] = 'a';
+  std::string u;
+
+  ASSERT_THROW(IBufferCompressor::Uncompress(u, c, compressed), OrthancException);
+}
+
+
+TEST(Zlib, Empty)
+{
+  std::string s = "";
+ 
+  std::string compressed, compressed2;
+  ZlibCompressor c;
+  IBufferCompressor::Compress(compressed, c, s);
+  ASSERT_EQ(compressed, compressed2);
+
+  std::string uncompressed;
+  IBufferCompressor::Uncompress(uncompressed, c, compressed);
+  ASSERT_EQ(0u, uncompressed.size());
+}
+
+
+static bool ReadAllStream(std::string& result,
+                          IHttpStreamAnswer& stream,
+                          bool allowGzip = false,
+                          bool allowDeflate = false)
+{
+  result.resize(stream.GetContentLength());
+
+  stream.SetupHttpCompression(allowGzip, allowDeflate);
+
+  size_t pos = 0;
+  while (stream.ReadNextChunk())
+  {
+    size_t s = stream.GetChunkSize();
+    if (pos + s > result.size())
+    {
+      return false;
+    }
+
+    memcpy(&result[pos], stream.GetChunkContent(), s);
+    pos += s;
+  }
+
+  return pos == result.size();
+}
+
+
+TEST(BufferHttpSender, Basic)
+{
+  const std::string s = "Hello world";
+  std::string t;
+
+  {
+    BufferHttpSender sender;
+    sender.SetChunkSize(0);
+    sender.GetBuffer() = s;
+    ASSERT_TRUE(ReadAllStream(t, sender));
+    ASSERT_EQ(s, t);
+  }
+
+  {
+    BufferHttpSender sender;
+    sender.SetChunkSize(1);
+    sender.GetBuffer() = s;
+    ASSERT_TRUE(ReadAllStream(t, sender));
+    ASSERT_EQ(s, t);
+  }
+
+  {
+    BufferHttpSender sender;
+    sender.SetChunkSize(1);
+    ASSERT_TRUE(ReadAllStream(t, sender));
+    ASSERT_EQ(0u, t.size());
+  }
+
+  {
+    BufferHttpSender sender;
+    sender.SetChunkSize(3);
+    sender.GetBuffer() = s;
+    ASSERT_TRUE(ReadAllStream(t, sender));
+    ASSERT_EQ(s, t);
+  }
+
+  {
+    BufferHttpSender sender;
+    sender.SetChunkSize(300);
+    sender.GetBuffer() = s;
+    ASSERT_TRUE(ReadAllStream(t, sender));
+    ASSERT_EQ(s, t);
+  }
+}
+
+
+TEST(FilesystemHttpSender, Basic)
+{
+  const std::string& path = "UnitTestsResults/stream";
+  const std::string s = "Hello world";
+  std::string t;
+
+  {
+    Toolbox::WriteFile(s, path);
+    FilesystemHttpSender sender(path);
+    ASSERT_TRUE(ReadAllStream(t, sender));
+    ASSERT_EQ(s, t);
+  }
+
+  {
+    Toolbox::WriteFile("", path);
+    FilesystemHttpSender sender(path);
+    ASSERT_TRUE(ReadAllStream(t, sender));
+    ASSERT_EQ(0u, t.size());
+  }
+}
+
+
+TEST(HttpStreamTranscoder, Basic)
+{
+  ZlibCompressor compressor;
+
+  const std::string s = "Hello world " + Toolbox::GenerateUuid();
+
+  std::string t;
+  IBufferCompressor::Compress(t, compressor, s);
+
+  for (int cs = 0; cs < 3; cs++)
+  {
+    BufferHttpSender sender;
+    sender.SetChunkSize(cs);
+    sender.GetBuffer() = t;
+    std::string u;
+    ASSERT_TRUE(ReadAllStream(u, sender));
+
+    std::string v;
+    IBufferCompressor::Uncompress(v, compressor, u);
+    ASSERT_EQ(s, v);
+  }
+
+  // Pass-through test, no decompression occurs
+  for (int cs = 0; cs < 3; cs++)
+  {
+    BufferHttpSender sender;
+    sender.SetChunkSize(cs);
+    sender.GetBuffer() = t;
+
+    HttpStreamTranscoder transcode(sender, CompressionType_None);
+    
+    std::string u;
+    ASSERT_TRUE(ReadAllStream(u, transcode));
+    
+    ASSERT_EQ(t, u);
+  }
+
+  // Pass-through test, decompression occurs
+  for (int cs = 0; cs < 3; cs++)
+  {
+    BufferHttpSender sender;
+    sender.SetChunkSize(cs);
+    sender.GetBuffer() = t;
+
+    HttpStreamTranscoder transcode(sender, CompressionType_ZlibWithSize);
+    
+    std::string u;
+    ASSERT_TRUE(ReadAllStream(u, transcode, false, false));
+    
+    ASSERT_EQ(s, u);
+  }
+
+  // Pass-through test with zlib, no decompression occurs but deflate is sent
+  for (int cs = 0; cs < 3; cs++)
+  {
+    BufferHttpSender sender;
+    sender.SetChunkSize(cs);
+    sender.GetBuffer() = t;
+
+    HttpStreamTranscoder transcode(sender, CompressionType_ZlibWithSize);
+    
+    std::string u;
+    ASSERT_TRUE(ReadAllStream(u, transcode, false, true));
+    
+    ASSERT_EQ(t.size() - sizeof(uint64_t), u.size());
+    ASSERT_EQ(t.substr(sizeof(uint64_t)), u);
+  }
+}
--- a/UnitTestsSources/UnitTestsMain.cpp	Tue Aug 11 16:09:24 2015 +0200
+++ b/UnitTestsSources/UnitTestsMain.cpp	Tue Aug 11 17:50:38 2015 +0200
@@ -37,8 +37,6 @@
 
 #include <ctype.h>
 
-#include "../Core/Compression/ZlibCompressor.h"
-#include "../Core/Compression/GzipCompressor.h"
 #include "../Core/DicomFormat/DicomTag.h"
 #include "../Core/HttpServer/HttpToolbox.h"
 #include "../Core/Logging.h"
@@ -100,136 +98,6 @@
 }
 
 
-TEST(Gzip, Basic)
-{
-  std::string s = "Hello world";
- 
-  std::string compressed;
-  GzipCompressor c;
-  ASSERT_FALSE(c.HasPrefixWithUncompressedSize());
-  IBufferCompressor::Compress(compressed, c, s);
-
-  std::string uncompressed;
-  IBufferCompressor::Uncompress(uncompressed, c, compressed);
-  ASSERT_EQ(s.size(), uncompressed.size());
-  ASSERT_EQ(0, memcmp(&s[0], &uncompressed[0], s.size()));
-}
-
-
-TEST(Gzip, Empty)
-{
-  std::string s;
- 
-  std::string compressed;
-  GzipCompressor c;
-  ASSERT_FALSE(c.HasPrefixWithUncompressedSize());
-  c.SetPrefixWithUncompressedSize(false);
-  IBufferCompressor::Compress(compressed, c, s);
-
-  std::string uncompressed;
-  IBufferCompressor::Uncompress(uncompressed, c, compressed);
-  ASSERT_EQ(0, uncompressed.size());
-}
-
-
-TEST(Gzip, BasicWithPrefix)
-{
-  std::string s = "Hello world";
- 
-  std::string compressed;
-  GzipCompressor c;
-  c.SetPrefixWithUncompressedSize(true);
-  ASSERT_TRUE(c.HasPrefixWithUncompressedSize());
-  IBufferCompressor::Compress(compressed, c, s);
-
-  std::string uncompressed;
-  IBufferCompressor::Uncompress(uncompressed, c, compressed);
-  ASSERT_EQ(s.size(), uncompressed.size());
-  ASSERT_EQ(0, memcmp(&s[0], &uncompressed[0], s.size()));
-}
-
-
-TEST(Gzip, EmptyWithPrefix)
-{
-  std::string s;
- 
-  std::string compressed;
-  GzipCompressor c;
-  c.SetPrefixWithUncompressedSize(true);
-  ASSERT_TRUE(c.HasPrefixWithUncompressedSize());
-  IBufferCompressor::Compress(compressed, c, s);
-
-  std::string uncompressed;
-  IBufferCompressor::Uncompress(uncompressed, c, compressed);
-  ASSERT_EQ(0, uncompressed.size());
-}
-
-
-TEST(Zlib, Basic)
-{
-  std::string s = Toolbox::GenerateUuid();
-  s = s + s + s + s;
- 
-  std::string compressed, compressed2;
-  ZlibCompressor c;
-  ASSERT_TRUE(c.HasPrefixWithUncompressedSize());
-  IBufferCompressor::Compress(compressed, c, s);
-
-  std::string uncompressed;
-  IBufferCompressor::Uncompress(uncompressed, c, compressed);
-  ASSERT_EQ(s.size(), uncompressed.size());
-  ASSERT_EQ(0, memcmp(&s[0], &uncompressed[0], s.size()));
-}
-
-
-TEST(Zlib, Level)
-{
-  std::string s = Toolbox::GenerateUuid();
-  s = s + s + s + s;
- 
-  std::string compressed, compressed2;
-  ZlibCompressor c;
-  c.SetCompressionLevel(9);
-  IBufferCompressor::Compress(compressed, c, s);
-
-  c.SetCompressionLevel(0);
-  IBufferCompressor::Compress(compressed2, c, s);
-
-  ASSERT_TRUE(compressed.size() < compressed2.size());
-}
-
-
-TEST(Zlib, DISABLED_Corrupted)  // Disabled because it may result in a crash
-{
-  std::string s = Toolbox::GenerateUuid();
-  s = s + s + s + s;
- 
-  std::string compressed;
-  ZlibCompressor c;
-  IBufferCompressor::Compress(compressed, c, s);
-
-  compressed[compressed.size() - 1] = 'a';
-  std::string u;
-
-  ASSERT_THROW(IBufferCompressor::Uncompress(u, c, compressed), OrthancException);
-}
-
-
-TEST(Zlib, Empty)
-{
-  std::string s = "";
- 
-  std::string compressed, compressed2;
-  ZlibCompressor c;
-  IBufferCompressor::Compress(compressed, c, s);
-  ASSERT_EQ(compressed, compressed2);
-
-  std::string uncompressed;
-  IBufferCompressor::Uncompress(uncompressed, c, compressed);
-  ASSERT_EQ(0u, uncompressed.size());
-}
-
-
 TEST(ParseGetArguments, Basic)
 {
   IHttpHandler::GetArguments b;