changeset 1519:8bd0d897763f

refactoring: IHttpStreamAnswer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 11 Aug 2015 13:15:16 +0200
parents eb46cc06389a
children 4a503a8c7749
files CMakeLists.txt Core/HttpServer/BufferHttpSender.cpp Core/HttpServer/BufferHttpSender.h Core/HttpServer/FilesystemHttpHandler.cpp Core/HttpServer/FilesystemHttpSender.cpp Core/HttpServer/FilesystemHttpSender.h Core/HttpServer/HttpFileSender.cpp Core/HttpServer/HttpFileSender.h Core/HttpServer/HttpOutput.cpp Core/HttpServer/HttpOutput.h Core/HttpServer/IHttpStreamAnswer.h Core/HttpServer/MongooseServer.cpp Core/RestApi/RestApiOutput.cpp Core/RestApi/RestApiOutput.h OrthancServer/OrthancRestApi/OrthancRestArchive.cpp OrthancServer/ParsedDicomFile.cpp OrthancServer/ServerContext.cpp
diffstat 17 files changed, 432 insertions(+), 165 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Tue Aug 11 10:36:05 2015 +0200
+++ b/CMakeLists.txt	Tue Aug 11 13:15:16 2015 +0200
@@ -100,6 +100,7 @@
   Core/FileStorage/CompressedFileStorageAccessor.cpp
   Core/FileStorage/FileStorageAccessor.cpp
   Core/HttpClient.cpp
+  Core/HttpServer/BufferHttpSender.cpp
   Core/HttpServer/EmbeddedResourceHttpHandler.cpp
   Core/HttpServer/FilesystemHttpHandler.cpp
   Core/HttpServer/HttpToolbox.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/HttpServer/BufferHttpSender.cpp	Tue Aug 11 13:15:16 2015 +0200
@@ -0,0 +1,75 @@
+/**
+ * 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 "BufferHttpSender.h"
+
+#include "../OrthancException.h"
+
+namespace Orthanc
+{
+  bool BufferHttpSender::ReadNextChunk()
+  {
+    if (done_)
+    {
+      return false;
+    }
+    else
+    {
+      done_ = false;
+      return true;
+    }
+  }
+
+  const char* BufferHttpSender::GetChunkContent()
+  {
+    if (done_)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    else
+    {
+      return buffer_.c_str();
+    }
+  }
+
+  size_t BufferHttpSender::GetChunkSize()
+  {
+    if (done_)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    else
+    {
+      return buffer_.size();
+    }
+  }
+}
--- a/Core/HttpServer/BufferHttpSender.h	Tue Aug 11 10:36:05 2015 +0200
+++ b/Core/HttpServer/BufferHttpSender.h	Tue Aug 11 13:15:16 2015 +0200
@@ -39,24 +39,13 @@
   {
   private:
     std::string buffer_;
+    bool done_;
 
-  protected:
-    virtual uint64_t GetFileSize()
+  public:
+    BufferHttpSender() : done_(false)
     {
-      return buffer_.size();
     }
 
-    virtual bool SendData(HttpOutput& output)
-    {
-      if (buffer_.size())
-      {
-        output.SendBody(&buffer_[0], buffer_.size());
-      }
-
-      return true;
-    }
-
-  public:
     std::string& GetBuffer() 
     {
       return buffer_;
@@ -66,5 +55,21 @@
     {
       return buffer_;
     }
+
+
+    /**
+     * Implementation of the IHttpStreamAnswer interface.
+     **/
+
+    virtual uint64_t GetContentLength()
+    {
+      return buffer_.size();
+    }
+
+    virtual bool ReadNextChunk();
+
+    virtual const char* GetChunkContent();
+
+    virtual size_t GetChunkSize();
   };
 }
--- a/Core/HttpServer/FilesystemHttpHandler.cpp	Tue Aug 11 10:36:05 2015 +0200
+++ b/Core/HttpServer/FilesystemHttpHandler.cpp	Tue Aug 11 13:15:16 2015 +0200
@@ -155,9 +155,8 @@
 
     if (fs::exists(p) && fs::is_regular_file(p))
     {
-      FilesystemHttpSender(p).Send(output);   // TODO COMPRESSION
-
-      //output.AnswerFileAutodetectContentType(p.string());
+      FilesystemHttpSender sender(p);
+      output.Answer(sender);   // TODO COMPRESSION
     }
     else if (listDirectoryContent_ &&
              fs::exists(p) && 
--- a/Core/HttpServer/FilesystemHttpSender.cpp	Tue Aug 11 10:36:05 2015 +0200
+++ b/Core/HttpServer/FilesystemHttpSender.cpp	Tue Aug 11 13:15:16 2015 +0200
@@ -36,68 +36,50 @@
 
 #include <stdio.h>
 
+
+static const size_t  CHUNK_SIZE = 64 * 1024;   // Use 64KB chunks
+
 namespace Orthanc
 {
-  void FilesystemHttpSender::Setup()
-  {
-    //SetDownloadFilename(path_.filename().string());
-
-#if BOOST_HAS_FILESYSTEM_V3 == 1
-    SetContentType(Toolbox::AutodetectMimeType(path_.filename().string()));
-#else
-    SetContentType(Toolbox::AutodetectMimeType(path_.filename()));
-#endif
-  }
-
-  uint64_t FilesystemHttpSender::GetFileSize()
-  {
-    return Toolbox::GetFileSize(path_.string());
-  }
-
-  bool FilesystemHttpSender::SendData(HttpOutput& output)
+  void FilesystemHttpSender::Open()
   {
-    FILE* fp = fopen(path_.string().c_str(), "rb");
-    if (!fp)
-    {
-      return false;
-    }
-
-    std::vector<uint8_t> buffer(1024 * 1024);  // Chunks of 1MB
+    SetFilename(path_.filename().string());
+    file_.open(path_.string().c_str(), std::ifstream::binary);
 
-    for (;;)
-    {
-      size_t nbytes = fread(&buffer[0], 1, buffer.size(), fp);
-      if (nbytes == 0)
-      {
-        break;
-      }
-      else
-      {
-        output.SendBody(&buffer[0], nbytes);
-      }
-    }
+    file_.seekg(0, file_.end);
+    size_ = file_.tellg();
+    file_.seekg(0, file_.beg);
 
-    fclose(fp);
-
-    return true;
+    chunk_.resize(CHUNK_SIZE);
+    chunkSize_ = 0;
   }
 
   FilesystemHttpSender::FilesystemHttpSender(const char* path)
   {
     path_ = std::string(path);
-    Setup();
+    Open();
   }
 
   FilesystemHttpSender::FilesystemHttpSender(const boost::filesystem::path& path)
   {
     path_ = path;
-    Setup();
+    Open();
   }
 
   FilesystemHttpSender::FilesystemHttpSender(const FilesystemStorage& storage,
                                              const std::string& uuid)
   {
     path_ = storage.GetPath(uuid).string();
-    Setup();
+    Open();
+  }
+
+
+  bool FilesystemHttpSender::ReadNextChunk()
+  {
+    file_.read(&chunk_[0], chunk_.size());
+
+    chunkSize_ = file_.gcount();
+
+    return chunkSize_ > 0;
   }
 }
--- a/Core/HttpServer/FilesystemHttpSender.h	Tue Aug 11 10:36:05 2015 +0200
+++ b/Core/HttpServer/FilesystemHttpSender.h	Tue Aug 11 13:15:16 2015 +0200
@@ -34,19 +34,20 @@
 #include "HttpFileSender.h"
 #include "../FileStorage/FilesystemStorage.h"
 
+#include <fstream>
+
 namespace Orthanc
 {
   class FilesystemHttpSender : public HttpFileSender
   {
   private:
     boost::filesystem::path path_;
-
-    void Setup();
+    std::ifstream           file_;
+    uint64_t                size_;
+    std::string             chunk_;
+    size_t                  chunkSize_;
 
-  protected:
-    virtual uint64_t GetFileSize();
-
-    virtual bool SendData(HttpOutput& output);
+    void Open();
 
   public:
     FilesystemHttpSender(const char* path);
@@ -55,5 +56,27 @@
 
     FilesystemHttpSender(const FilesystemStorage& storage,
                          const std::string& uuid);
+
+
+    /**
+     * Implementation of the IHttpStreamAnswer interface.
+     **/
+
+    virtual uint64_t GetContentLength()
+    {
+      return size_;
+    }
+
+    virtual bool ReadNextChunk();
+
+    virtual const char* GetChunkContent()
+    {
+      return chunk_.c_str();
+    }
+
+    virtual size_t GetChunkSize()
+    {
+      return chunkSize_;
+    }
   };
 }
--- a/Core/HttpServer/HttpFileSender.cpp	Tue Aug 11 10:36:05 2015 +0200
+++ b/Core/HttpServer/HttpFileSender.cpp	Tue Aug 11 13:15:16 2015 +0200
@@ -34,34 +34,40 @@
 #include "HttpFileSender.h"
 
 #include "../OrthancException.h"
+#include "../Toolbox.h"
 
 #include <boost/lexical_cast.hpp>
 
 namespace Orthanc
 {
-  void HttpFileSender::SendHeader(HttpOutput& output)
+  void HttpFileSender::SetFilename(const std::string& filename)
   {
-    if (contentType_.size() > 0)
-    {
-      output.SetContentType(contentType_.c_str());
-    }
+    filename_ = filename;
 
-    if (downloadFilename_.size() > 0)
+    if (contentType_.empty())
     {
-      output.SetContentFilename(downloadFilename_.c_str());
+      contentType_ = Toolbox::AutodetectMimeType(filename);
     }
-
-    output.SetContentLength(GetFileSize());
   }
 
-  void HttpFileSender::Send(HttpOutput& output)
+
+  bool HttpFileSender::HasContentFilename(std::string& filename)
   {
-    SendHeader(output);
-
-    if (!SendData(output))
+    if (!filename_.empty())
     {
-      throw OrthancException(ErrorCode_InternalError);
-      //output.SendHeader(HttpStatus_500_InternalServerError);
+      filename = filename_;
+    }
+  }
+    
+  std::string HttpFileSender::GetContentType()
+  {
+    if (contentType_.empty())
+    {
+      return "application/octet-stream";
+    }
+    else
+    {
+      return contentType_;
     }
   }
 }
--- a/Core/HttpServer/HttpFileSender.h	Tue Aug 11 10:36:05 2015 +0200
+++ b/Core/HttpServer/HttpFileSender.h	Tue Aug 11 13:15:16 2015 +0200
@@ -36,29 +36,13 @@
 
 namespace Orthanc
 {
-  class HttpFileSender
+  class HttpFileSender : public IHttpStreamAnswer
   {
   private:
     std::string contentType_;
-    std::string downloadFilename_;
-
-    void SendHeader(HttpOutput& output);
-
-  protected:
-    virtual uint64_t GetFileSize() = 0;
-
-    virtual bool SendData(HttpOutput& output) = 0;
+    std::string filename_;
 
   public:
-    virtual ~HttpFileSender()
-    {
-    }
-
-    void ResetContentType()
-    {
-      contentType_.clear();
-    }
-
     void SetContentType(const std::string& contentType)
     {
       contentType_ = contentType;
@@ -69,21 +53,27 @@
       return contentType_;
     }
 
-    void ResetDownloadFilename()
+    void SetFilename(const std::string& filename);
+
+    const std::string& GetFilename() const
     {
-      downloadFilename_.clear();
+      return filename_;
     }
 
-    void SetDownloadFilename(const std::string& filename)
+
+    /**
+     * Implementation of the IHttpStreamAnswer interface. No
+     * compression is supported.
+     **/
+
+    virtual HttpCompression GetHttpCompression(bool /*gzipAllowed*/, 
+                                               bool /*deflateAllowed*/)
     {
-      downloadFilename_ = filename;
+      return HttpCompression_None;
     }
 
-    const std::string& GetDownloadFilename() const
-    {
-      return downloadFilename_;
-    }
-
-    void Send(HttpOutput& output);
+    virtual bool HasContentFilename(std::string& filename);
+    
+    virtual std::string GetContentType();
   };
 }
--- a/Core/HttpServer/HttpOutput.cpp	Tue Aug 11 10:36:05 2015 +0200
+++ b/Core/HttpServer/HttpOutput.cpp	Tue Aug 11 13:15:16 2015 +0200
@@ -215,6 +215,41 @@
   }
 
 
+  void HttpOutput::StateMachine::CloseBody()
+  {
+    switch (state_)
+    {
+      case State_WritingHeader:
+        LOG(ERROR) << "Closing the HTTP body, but the header has not been sent yet";
+        throw OrthancException(ErrorCode_BadSequenceOfCalls);
+
+      case State_WritingBody:
+        if (!hasContentLength_ ||
+            contentPosition_ == contentLength_)
+        {
+          state_ = State_Done;
+        }
+        else
+        {
+          LOG(ERROR) << "The body size has not reached what was declared with SetContentSize()";
+          throw OrthancException(ErrorCode_BadSequenceOfCalls);
+        }
+
+        break;
+
+      case State_WritingMultipart:
+        LOG(ERROR) << "Cannot invoke CloseBody() with multipart outputs";
+        throw OrthancException(ErrorCode_BadSequenceOfCalls);
+
+      case State_Done:
+        return;  // Ignore
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }      
+  }
+
+
   HttpCompression HttpOutput::GetPreferredCompression(size_t bodySize) const
   {
 #if 0
@@ -351,9 +386,11 @@
     SendBody(str.size() == 0 ? NULL : str.c_str(), str.size());
   }
 
-  void HttpOutput::SendBody()
+  void HttpOutput::SendEmptyBody()
   {
+    stateMachine_.SetContentLength(0);
     stateMachine_.SendBody(NULL, 0);
+    stateMachine_.CloseBody();
   }
 
 
@@ -460,4 +497,52 @@
       stateMachine_.SendMultipartItem(NULL, 0);
     }
   }
+
+
+  void HttpOutput::Answer(IHttpStreamAnswer& stream)
+  {
+    stateMachine_.SetContentLength(stream.GetContentLength());
+
+    std::string contentType = stream.GetContentType();
+    if (contentType.empty())
+    {
+      contentType = "application/octet-stream";
+    }
+
+    stateMachine_.SetContentType(contentType.c_str());
+
+    std::string filename;
+    if (stream.HasContentFilename(filename))
+    {
+      SetContentFilename(filename.c_str());
+    }
+
+    HttpCompression compression = stream.GetHttpCompression(isGzipAllowed_, isDeflateAllowed_);
+
+    switch (compression)
+    {
+      case HttpCompression_None:
+        break;
+
+      case HttpCompression_Gzip:
+        stateMachine_.AddHeader("Content-Encoding", "gzip");
+        break;
+
+      case HttpCompression_Deflate:
+        stateMachine_.AddHeader("Content-Encoding", "deflate");
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    while (stream.ReadNextChunk())
+    {
+      stateMachine_.SendBody(stream.GetChunkContent(),
+                             stream.GetChunkSize());
+    }
+
+    stateMachine_.CloseBody();
+  }
+
 }
--- a/Core/HttpServer/HttpOutput.h	Tue Aug 11 10:36:05 2015 +0200
+++ b/Core/HttpServer/HttpOutput.h	Tue Aug 11 13:15:16 2015 +0200
@@ -37,6 +37,7 @@
 #include <stdint.h>
 #include "../Enumerations.h"
 #include "IHttpOutputStream.h"
+#include "IHttpStreamAnswer.h"
 #include "../Uuid.h"
 
 namespace Orthanc
@@ -102,6 +103,8 @@
 
       void CloseMultipart();
 
+      void CloseBody();
+
       State GetState() const
       {
         return state_;
@@ -155,11 +158,6 @@
       stateMachine_.SetContentFilename(filename);
     }
 
-    void SetContentLength(uint64_t length)
-    {
-      stateMachine_.SetContentLength(length);
-    }
-
     void SetCookie(const std::string& cookie,
                    const std::string& value)
     {
@@ -177,7 +175,7 @@
 
     void SendBody(const std::string& str);
 
-    void SendBody();
+    void SendEmptyBody();
 
     void SendMethodNotAllowed(const std::string& allowed);
 
@@ -207,5 +205,7 @@
     {
       return stateMachine_.GetState() == StateMachine::State_WritingMultipart;
     }
+
+    void Answer(IHttpStreamAnswer& stream);
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/HttpServer/IHttpStreamAnswer.h	Tue Aug 11 13:15:16 2015 +0200
@@ -0,0 +1,62 @@
+/**
+ * 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 <stdint.h>
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class IHttpStreamAnswer : public boost::noncopyable
+  {
+  public:
+    virtual ~IHttpStreamAnswer()
+    {
+    }
+
+    virtual HttpCompression GetHttpCompression(bool gzipAllowed,
+                                               bool deflateAllowed) = 0;
+
+    virtual bool HasContentFilename(std::string& filename) = 0;
+
+    virtual std::string GetContentType() = 0;
+
+    virtual uint64_t GetContentLength() = 0;
+
+    virtual bool ReadNextChunk() = 0;
+
+    virtual const char* GetChunkContent() = 0;
+
+    virtual size_t GetChunkSize() = 0;
+  };
+}
--- a/Core/HttpServer/MongooseServer.cpp	Tue Aug 11 10:36:05 2015 +0200
+++ b/Core/HttpServer/MongooseServer.cpp	Tue Aug 11 13:15:16 2015 +0200
@@ -695,7 +695,7 @@
           return;
 
         case PostDataStatus_Pending:
-          output.SendBody();
+          output.SendEmptyBody();
           return;
 
         default:
--- a/Core/RestApi/RestApiOutput.cpp	Tue Aug 11 10:36:05 2015 +0200
+++ b/Core/RestApi/RestApiOutput.cpp	Tue Aug 11 13:15:16 2015 +0200
@@ -78,10 +78,10 @@
   }
 
 
-  void RestApiOutput::AnswerFile(HttpFileSender& sender)
+  void RestApiOutput::AnswerStream(IHttpStreamAnswer& stream)
   {
     CheckStatus();
-    sender.Send(output_);
+    output_.Answer(stream);
     alreadySent_ = true;
   }
 
--- a/Core/RestApi/RestApiOutput.h	Tue Aug 11 10:36:05 2015 +0200
+++ b/Core/RestApi/RestApiOutput.h	Tue Aug 11 13:15:16 2015 +0200
@@ -55,16 +55,6 @@
 
     ~RestApiOutput();
 
-    HttpOutput& GetLowLevelOutput()
-    {
-      return output_;
-    }
-
-    void MarkLowLevelOutputDone()
-    {
-      alreadySent_ = true;
-    }
-
     void SetConvertJsonToXml(bool convert)
     {
       convertJsonToXml_ = convert;
@@ -75,7 +65,7 @@
       return convertJsonToXml_;
     }
 
-    void AnswerFile(HttpFileSender& sender);
+    void AnswerStream(IHttpStreamAnswer& stream);
 
     void AnswerJson(const Json::Value& value);
 
--- a/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp	Tue Aug 11 10:36:05 2015 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp	Tue Aug 11 13:15:16 2015 +0200
@@ -298,10 +298,10 @@
     // Prepare the sending of the ZIP file
     FilesystemHttpSender sender(tmp.GetPath().c_str());
     sender.SetContentType("application/zip");
-    sender.SetDownloadFilename(id + ".zip");
+    sender.SetFilename(id + ".zip");
 
     // Send the ZIP
-    call.GetOutput().AnswerFile(sender);
+    call.GetOutput().AnswerStream(sender);
 
     // The temporary file is automatically removed thanks to the RAII
   }
@@ -359,10 +359,10 @@
     // Prepare the sending of the ZIP file
     FilesystemHttpSender sender(tmp.GetPath().c_str());
     sender.SetContentType("application/zip");
-    sender.SetDownloadFilename(id + ".zip");
+    sender.SetFilename(id + ".zip");
 
     // Send the ZIP
-    call.GetOutput().AnswerFile(sender);
+    call.GetOutput().AnswerStream(sender);
 
     // The temporary file is automatically removed thanks to the RAII
   }
--- a/OrthancServer/ParsedDicomFile.cpp	Tue Aug 11 10:36:05 2015 +0200
+++ b/OrthancServer/ParsedDicomFile.cpp	Tue Aug 11 13:15:16 2015 +0200
@@ -255,46 +255,93 @@
   }
 
 
-  static void AnswerDicomField(RestApiOutput& output,
-                               DcmElement& element,
-                               E_TransferSyntax transferSyntax)
+  namespace
   {
-    // This element is nor a sequence, neither a pixel-data
-    std::string buffer;
-    buffer.resize(65536);
-    Uint32 length = element.getLength(transferSyntax);
-    Uint32 offset = 0;
+    class DicomFieldStream : public IHttpStreamAnswer
+    {
+    private:
+      DcmElement&  element_;
+      uint32_t     length_;
+      uint32_t     offset_;
+      std::string  chunk_;
+      size_t       chunkSize_;
+      
+    public:
+      DicomFieldStream(DcmElement& element,
+                       E_TransferSyntax transferSyntax) :
+        element_(element),
+        length_(element.getLength(transferSyntax)),
+        offset_(0)
+      {
+        static const size_t CHUNK_SIZE = 64 * 1024;  // Use a 64KB chunk
+        chunk_.resize(CHUNK_SIZE);
+      }
 
-    output.GetLowLevelOutput().SetContentType(CONTENT_TYPE_OCTET_STREAM);
-    output.GetLowLevelOutput().SetContentLength(length);
+      virtual HttpCompression GetHttpCompression(bool /*gzipAllowed*/,
+                                                 bool /*deflateAllowed*/)
+      {
+        // No support for compression
+        return HttpCompression_None;
+      }
 
-    while (offset < length)
-    {
-      Uint32 nbytes;
-      if (length - offset < buffer.size())
+      virtual bool HasContentFilename(std::string& filename)
       {
-        nbytes = length - offset;
+        return false;
       }
-      else
+
+      virtual std::string GetContentType()
       {
-        nbytes = buffer.size();
+        return "";
       }
 
-      OFCondition cond = element.getPartialValue(&buffer[0], offset, nbytes);
-
-      if (cond.good())
+      virtual uint64_t  GetContentLength()
       {
-        output.GetLowLevelOutput().SendBody(&buffer[0], nbytes);
-        offset += nbytes;
+        return length_;
       }
-      else
+ 
+      virtual bool ReadNextChunk()
       {
-        LOG(ERROR) << "Error while sending a DICOM field: " << cond.text();
-        return;
+        assert(offset_ < length_);
+
+        if (offset_ == length_)
+        {
+          return false;
+        }
+        else
+        {
+          if (length_ - offset_ < chunk_.size())
+          {
+            chunkSize_ = length_ - offset_;
+          }
+          else
+          {
+            chunkSize_ = chunk_.size();
+          }
+
+          OFCondition cond = element_.getPartialValue(&chunk_[0], offset_, chunkSize_);
+
+          offset_ += chunkSize_;
+
+          if (!cond.good())
+          {
+            LOG(ERROR) << "Error while sending a DICOM field: " << cond.text();
+            throw OrthancException(ErrorCode_InternalError);
+          }
+
+          return true;
+        }
       }
-    }
-
-    output.MarkLowLevelOutputDone();
+ 
+      virtual const char *GetChunkContent()
+      {
+        return chunk_.c_str();
+      }
+ 
+      virtual size_t GetChunkSize()
+      {
+        return chunkSize_;
+      }
+    };
   }
 
 
@@ -365,7 +412,8 @@
         {
           // This is the case for raw, uncompressed image buffers
           assert(*blockUri == "0");
-          AnswerDicomField(output, *element, transferSyntax);
+          DicomFieldStream stream(*element, transferSyntax);
+          output.AnswerStream(stream);
         }
       }
     }
@@ -406,7 +454,8 @@
         //element->getVR() != EVR_UNKNOWN &&  // This would forbid private tags
         element->getVR() != EVR_SQ)
     {
-      AnswerDicomField(output, *element, transferSyntax);
+      DicomFieldStream stream(*element, transferSyntax);
+      output.AnswerStream(stream);
     }
   }
 
--- a/OrthancServer/ServerContext.cpp	Tue Aug 11 10:36:05 2015 +0200
+++ b/OrthancServer/ServerContext.cpp	Tue Aug 11 13:15:16 2015 +0200
@@ -317,8 +317,8 @@
 
     std::auto_ptr<HttpFileSender> sender(accessor_.ConstructHttpFileSender(attachment.GetUuid(), attachment.GetContentType()));
     sender->SetContentType(GetMimeType(content));
-    sender->SetDownloadFilename(instancePublicId + ".dcm");
-    output.AnswerFile(*sender);
+    sender->SetFilename(instancePublicId + ".dcm");
+    output.AnswerStream(*sender);
   }