changeset 3414:b9cba6a91780

simplification
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 11 Jun 2019 19:44:10 +0200
parents f09bfdea3fc3
children 2a821deece64
files Core/HttpServer/EmbeddedResourceHttpHandler.h Core/HttpServer/FilesystemHttpHandler.h Core/HttpServer/HttpServer.cpp Core/HttpServer/IHttpHandler.h Core/RestApi/RestApi.h OrthancServer/OrthancHttpHandler.cpp OrthancServer/OrthancHttpHandler.h Plugins/Engine/OrthancPlugins.cpp Plugins/Engine/OrthancPlugins.h Plugins/Include/orthanc/OrthancCPlugin.h Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Plugins/Samples/Common/OrthancPluginCppWrapper.h
diffstat 12 files changed, 660 insertions(+), 727 deletions(-) [+]
line wrap: on
line diff
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.h	Tue Jun 11 14:02:57 2019 +0200
+++ b/Core/HttpServer/EmbeddedResourceHttpHandler.h	Tue Jun 11 19:44:10 2019 +0200
@@ -51,26 +51,26 @@
       const std::string& baseUri,
       EmbeddedResources::DirectoryResourceId resourceId);
 
-    virtual IStream* CreateStreamHandler(RequestOrigin origin,
-                                         const char* remoteIp,
-                                         const char* username,
-                                         HttpMethod method,
-                                         const UriComponents& uri,
-                                         const Arguments& headers)
+    virtual bool CreateChunkedRequestReader(std::auto_ptr<IChunkedRequestReader>& target,
+                                            RequestOrigin origin,
+                                            const char* remoteIp,
+                                            const char* username,
+                                            HttpMethod method,
+                                            const UriComponents& uri,
+                                            const Arguments& headers)
     {
-      return NULL;
+      return false;
     }
 
-    virtual bool Handle(
-      HttpOutput& output,
-      RequestOrigin origin,
-      const char* remoteIp,
-      const char* username,
-      HttpMethod method,
-      const UriComponents& uri,
-      const Arguments& headers,
-      const GetArguments& arguments,
-      const void* /*bodyData*/,
-      size_t /*bodySize*/);
+    virtual bool Handle(HttpOutput& output,
+                        RequestOrigin origin,
+                        const char* remoteIp,
+                        const char* username,
+                        HttpMethod method,
+                        const UriComponents& uri,
+                        const Arguments& headers,
+                        const GetArguments& arguments,
+                        const void* /*bodyData*/,
+                        size_t /*bodySize*/);
   };
 }
--- a/Core/HttpServer/FilesystemHttpHandler.h	Tue Jun 11 14:02:57 2019 +0200
+++ b/Core/HttpServer/FilesystemHttpHandler.h	Tue Jun 11 19:44:10 2019 +0200
@@ -53,14 +53,15 @@
     FilesystemHttpHandler(const std::string& baseUri,
                           const std::string& root);
 
-    virtual IStream* CreateStreamHandler(RequestOrigin origin,
-                                         const char* remoteIp,
-                                         const char* username,
-                                         HttpMethod method,
-                                         const UriComponents& uri,
-                                         const Arguments& headers)
+    virtual bool CreateChunkedRequestReader(std::auto_ptr<IChunkedRequestReader>& target,
+                                            RequestOrigin origin,
+                                            const char* remoteIp,
+                                            const char* username,
+                                            HttpMethod method,
+                                            const UriComponents& uri,
+                                            const Arguments& headers)
     {
-      return NULL;
+      return false;
     }
 
     virtual bool Handle(
--- a/Core/HttpServer/HttpServer.cpp	Tue Jun 11 14:02:57 2019 +0200
+++ b/Core/HttpServer/HttpServer.cpp	Tue Jun 11 19:44:10 2019 +0200
@@ -384,7 +384,7 @@
   }
 
 
-  static PostDataStatus ReadBodyToStream(IHttpHandler::IStream& stream,
+  static PostDataStatus ReadBodyToStream(IHttpHandler::IChunkedRequestReader& stream,
                                          struct mg_connection *connection,
                                          const IHttpHandler::Arguments& headers)
   {
@@ -847,16 +847,21 @@
 
       if (!isMultipartForm)
       {
-        std::auto_ptr<IHttpHandler::IStream> stream;
+        std::auto_ptr<IHttpHandler::IChunkedRequestReader> stream;
 
         if (server.HasHandler())
         {
-          stream.reset(server.GetHandler().CreateStreamHandler
-                       (RequestOrigin_RestApi, remoteIp, username.c_str(), method, uri, headers));
+          found = server.GetHandler().CreateChunkedRequestReader
+            (stream, RequestOrigin_RestApi, remoteIp, username.c_str(), method, uri, headers);
         }
+        
+        if (found)
+        {
+          if (stream.get() == NULL)
+          {
+            throw OrthancException(ErrorCode_InternalError);
+          }
 
-        if (stream.get() != NULL)
-        {
           status = ReadBodyToStream(*stream, connection, headers);
 
           if (status == PostDataStatus_Success)
--- a/Core/HttpServer/IHttpHandler.h	Tue Jun 11 14:02:57 2019 +0200
+++ b/Core/HttpServer/IHttpHandler.h	Tue Jun 11 19:44:10 2019 +0200
@@ -40,6 +40,7 @@
 #include <set>
 #include <vector>
 #include <string>
+#include <memory>
 
 namespace Orthanc
 {
@@ -50,10 +51,10 @@
     typedef std::vector< std::pair<std::string, std::string> >  GetArguments;
 
 
-    class IStream : public boost::noncopyable
+    class IChunkedRequestReader : public boost::noncopyable
     {
     public:
-      virtual ~IStream()
+      virtual ~IChunkedRequestReader()
       {
       }
 
@@ -68,14 +69,17 @@
     {
     }
 
-    // This function is called for each incomding POST/PUT request
-    // (new in Orthanc 1.5.7). It allows to deal with chunked transfers.
-    virtual IStream* CreateStreamHandler(RequestOrigin origin,
-                                         const char* remoteIp,
-                                         const char* username,
-                                         HttpMethod method,
-                                         const UriComponents& uri,
-                                         const Arguments& headers) = 0;
+    /**
+     * This function allows to deal with chunked transfers (new in
+     * Orthanc 1.5.7). It is only called if "method" is POST or PUT.
+     **/
+    virtual bool CreateChunkedRequestReader(std::auto_ptr<IChunkedRequestReader>& target,
+                                            RequestOrigin origin,
+                                            const char* remoteIp,
+                                            const char* username,
+                                            HttpMethod method,
+                                            const UriComponents& uri,
+                                            const Arguments& headers) = 0;
 
     virtual bool Handle(HttpOutput& output,
                         RequestOrigin origin,
--- a/Core/RestApi/RestApi.h	Tue Jun 11 14:02:57 2019 +0200
+++ b/Core/RestApi/RestApi.h	Tue Jun 11 19:44:10 2019 +0200
@@ -47,14 +47,15 @@
   public:
     static void AutoListChildren(RestApiGetCall& call);
 
-    virtual IStream* CreateStreamHandler(RequestOrigin origin,
-                                         const char* remoteIp,
-                                         const char* username,
-                                         HttpMethod method,
-                                         const UriComponents& uri,
-                                         const Arguments& headers)
+    virtual bool CreateChunkedRequestReader(std::auto_ptr<IChunkedRequestReader>& target,
+                                            RequestOrigin origin,
+                                            const char* remoteIp,
+                                            const char* username,
+                                            HttpMethod method,
+                                            const UriComponents& uri,
+                                            const Arguments& headers)
     {
-      return NULL;
+      return false;
     }
 
     virtual bool Handle(HttpOutput& output,
--- a/OrthancServer/OrthancHttpHandler.cpp	Tue Jun 11 14:02:57 2019 +0200
+++ b/OrthancServer/OrthancHttpHandler.cpp	Tue Jun 11 19:44:10 2019 +0200
@@ -39,25 +39,36 @@
 
 namespace Orthanc
 {
-  IHttpHandler::IStream* OrthancHttpHandler::CreateStreamHandler(RequestOrigin origin,
-                                                                 const char* remoteIp,
-                                                                 const char* username,
-                                                                 HttpMethod method,
-                                                                 const UriComponents& uri,
-                                                                 const Arguments& headers)
+  bool OrthancHttpHandler::CreateChunkedRequestReader(
+    std::auto_ptr<IHttpHandler::IChunkedRequestReader>& target,
+    RequestOrigin origin,
+    const char* remoteIp,
+    const char* username,
+    HttpMethod method,
+    const UriComponents& uri,
+    const Arguments& headers)
   {
+    if (method != HttpMethod_Post &&
+        method != HttpMethod_Put)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
     for (Handlers::const_iterator it = handlers_.begin(); it != handlers_.end(); ++it) 
     {
-      IHttpHandler::IStream* stream = (*it)->CreateStreamHandler(
-        origin, remoteIp, username, method, uri, headers);
+      if ((*it)->CreateChunkedRequestReader
+          (target, origin, remoteIp, username, method, uri, headers))
+      {
+        if (target.get() == NULL)
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
 
-      if (stream != NULL)
-      {
-        return stream;
+        return true;
       }
     }
 
-    return NULL;
+    return false;
   }
 
 
--- a/OrthancServer/OrthancHttpHandler.h	Tue Jun 11 14:02:57 2019 +0200
+++ b/OrthancServer/OrthancHttpHandler.h	Tue Jun 11 19:44:10 2019 +0200
@@ -50,12 +50,13 @@
     {
     }
 
-    virtual IStream* CreateStreamHandler(RequestOrigin origin,
-                                         const char* remoteIp,
-                                         const char* username,
-                                         HttpMethod method,
-                                         const UriComponents& uri,
-                                         const Arguments& headers);
+    virtual bool CreateChunkedRequestReader(std::auto_ptr<IChunkedRequestReader>& target,
+                                            RequestOrigin origin,
+                                            const char* remoteIp,
+                                            const char* username,
+                                            HttpMethod method,
+                                            const UriComponents& uri,
+                                            const Arguments& headers);
 
     virtual bool Handle(HttpOutput& output,
                         RequestOrigin origin,
--- a/Plugins/Engine/OrthancPlugins.cpp	Tue Jun 11 14:02:57 2019 +0200
+++ b/Plugins/Engine/OrthancPlugins.cpp	Tue Jun 11 19:44:10 2019 +0200
@@ -51,7 +51,6 @@
 #include "../../Core/DicomParsing/Internals/DicomImageDecoder.h"
 #include "../../Core/DicomParsing/ToDcmtkBridge.h"
 #include "../../Core/HttpServer/HttpToolbox.h"
-#include "../../Core/HttpServer/MultipartStreamReader.h"
 #include "../../Core/Images/Image.h"
 #include "../../Core/Images/ImageProcessing.h"
 #include "../../Core/Images/JpegReader.h"
@@ -76,12 +75,19 @@
 #include <dcmtk/dcmdata/dcdict.h>
 #include <dcmtk/dcmdata/dcdicent.h>
 
+#define ERROR_MESSAGE_64BIT "A 64bit version of the Orthanc API is necessary"
+
 namespace Orthanc
 {
   static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer& target,
                                  const void* data,
                                  size_t size)
   {
+    if (static_cast<uint32_t>(size) != size)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory, ERROR_MESSAGE_64BIT);
+    }
+
     target.size = size;
 
     if (size == 0)
@@ -120,6 +126,11 @@
 
   static char* CopyString(const std::string& str)
   {
+    if (static_cast<uint32_t>(str.size()) != str.size())
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory, ERROR_MESSAGE_64BIT);
+    }
+
     char *result = reinterpret_cast<char*>(malloc(str.size() + 1));
     if (result == NULL)
     {
@@ -539,16 +550,16 @@
     };
 
 
-    class MultipartRestCallback : public boost::noncopyable
+    class ChunkedRestCallback : public boost::noncopyable
     {
     private:
-      _OrthancPluginMultipartRestCallback parameters_;
+      _OrthancPluginChunkedRestCallback parameters_;
       boost::regex                        regex_;
 
     public:
-      MultipartRestCallback(_OrthancPluginMultipartRestCallback parameters) :
-        parameters_(parameters),
-        regex_(parameters.pathRegularExpression)
+      ChunkedRestCallback(_OrthancPluginChunkedRestCallback parameters) :
+      parameters_(parameters),
+      regex_(parameters.pathRegularExpression)
       {
       }
 
@@ -557,7 +568,7 @@
         return regex_;
       }
 
-      const _OrthancPluginMultipartRestCallback& GetParameters() const
+      const _OrthancPluginChunkedRestCallback& GetParameters() const
       {
         return parameters_;
       }
@@ -598,7 +609,7 @@
 
     typedef std::pair<std::string, _OrthancPluginProperty>  Property;
     typedef std::list<RestCallback*>  RestCallbacks;
-    typedef std::list<MultipartRestCallback*>  MultipartRestCallbacks;
+    typedef std::list<ChunkedRestCallback*>  ChunkedRestCallbacks;
     typedef std::list<OrthancPluginOnStoredInstanceCallback>  OnStoredCallbacks;
     typedef std::list<OrthancPluginOnChangeCallback>  OnChangeCallbacks;
     typedef std::list<OrthancPluginIncomingHttpRequestFilter>  IncomingHttpRequestFilters;
@@ -611,7 +622,7 @@
     PluginsManager manager_;
 
     RestCallbacks restCallbacks_;
-    MultipartRestCallbacks multipartRestCallbacks_;
+    ChunkedRestCallbacks chunkedRestCallbacks_;
     OnStoredCallbacks  onStoredCallbacks_;
     OnChangeCallbacks  onChangeCallbacks_;
     OrthancPluginFindCallback  findCallback_;
@@ -1030,15 +1041,15 @@
 
 
 
-  class OrthancPlugins::ChunkedHttpRequest : public HttpClient::IRequestBody
+  class OrthancPlugins::HttpClientChunkedRequest : public HttpClient::IRequestBody
   {
   private:
     const _OrthancPluginChunkedHttpClient&  params_;
     PluginsErrorDictionary&                 errorDictionary_;
 
   public:
-    ChunkedHttpRequest(const _OrthancPluginChunkedHttpClient& params,
-                         PluginsErrorDictionary&  errorDictionary) :
+    HttpClientChunkedRequest(const _OrthancPluginChunkedHttpClient& params,
+                             PluginsErrorDictionary&  errorDictionary) :
       params_(params),
       errorDictionary_(errorDictionary)
     {
@@ -1078,15 +1089,15 @@
   };
 
 
-  class OrthancPlugins::ChunkedHttpAnswer : public HttpClient::IAnswer
+  class OrthancPlugins::HttpClientChunkedAnswer : public HttpClient::IAnswer
   {
   private:
     const _OrthancPluginChunkedHttpClient&  params_;
     PluginsErrorDictionary&                 errorDictionary_;
 
   public:
-    ChunkedHttpAnswer(const _OrthancPluginChunkedHttpClient& params,
-                        PluginsErrorDictionary&  errorDictionary) :
+    HttpClientChunkedAnswer(const _OrthancPluginChunkedHttpClient& params,
+                            PluginsErrorDictionary&  errorDictionary) :
       params_(params),
       errorDictionary_(errorDictionary)
     {
@@ -1180,8 +1191,8 @@
       delete *it;
     }
 
-    for (PImpl::MultipartRestCallbacks::iterator it = pimpl_->multipartRestCallbacks_.begin(); 
-         it != pimpl_->multipartRestCallbacks_.end(); ++it)
+    for (PImpl::ChunkedRestCallbacks::iterator it = pimpl_->chunkedRestCallbacks_.begin(); 
+         it != pimpl_->chunkedRestCallbacks_.end(); ++it)
     {
       delete *it;
     }
@@ -1232,7 +1243,7 @@
       
     public:
       RestCallbackMatcher(const UriComponents& uri) :
-        flatUri_(Toolbox::FlattenUri(uri))
+      flatUri_(Toolbox::FlattenUri(uri))
       {
       }
 
@@ -1280,6 +1291,133 @@
         return flatUri_;
       }
     };
+
+
+    // WARNING - The lifetime of this internal object must be smaller
+    // than "matcher", "headers" and "getArguments" objects
+    class HttpRequestConverter
+    {
+    private:
+      std::vector<const char*>    getKeys_;
+      std::vector<const char*>    getValues_;
+      std::vector<const char*>    headersKeys_;
+      std::vector<const char*>    headersValues_;
+      OrthancPluginHttpRequest    converted_;
+
+    public:
+      HttpRequestConverter(const RestCallbackMatcher& matcher,
+                           HttpMethod method,
+                           const IHttpHandler::Arguments& headers)
+      {
+        memset(&converted_, 0, sizeof(OrthancPluginHttpRequest));
+
+        ArgumentsToPlugin(headersKeys_, headersValues_, headers);
+        assert(headersKeys_.size() == headersValues_.size());
+
+        switch (method)
+        {
+          case HttpMethod_Get:
+            converted_.method = OrthancPluginHttpMethod_Get;
+            break;
+
+          case HttpMethod_Post:
+            converted_.method = OrthancPluginHttpMethod_Post;
+            break;
+
+          case HttpMethod_Delete:
+            converted_.method = OrthancPluginHttpMethod_Delete;
+            break;
+
+          case HttpMethod_Put:
+            converted_.method = OrthancPluginHttpMethod_Put;
+            break;
+
+          default:
+            throw OrthancException(ErrorCode_InternalError);
+        }
+
+        converted_.groups = matcher.GetGroups();
+        converted_.groupsCount = matcher.GetGroupsCount();
+        converted_.getCount = 0;
+        converted_.getKeys = NULL;
+        converted_.getValues = NULL;
+        converted_.body = NULL;
+        converted_.bodySize = 0;
+        converted_.headersCount = headers.size();
+       
+        if (headers.size() > 0)
+        {
+          converted_.headersKeys = &headersKeys_[0];
+          converted_.headersValues = &headersValues_[0];
+        }
+      }
+
+      void SetGetArguments(const IHttpHandler::GetArguments& getArguments)
+      {
+        ArgumentsToPlugin(getKeys_, getValues_, getArguments);
+        assert(getKeys_.size() == getValues_.size());
+
+        converted_.getCount = getArguments.size();
+
+        if (getArguments.size() > 0)
+        {
+          converted_.getKeys = &getKeys_[0];
+          converted_.getValues = &getValues_[0];
+        }
+      }
+
+      OrthancPluginHttpRequest& GetRequest()
+      {
+        return converted_;
+      }
+    };
+  }
+
+
+  bool OrthancPlugins::HandleChunkedGetDelete(HttpOutput& output,
+                                              HttpMethod method,
+                                              const UriComponents& uri,
+                                              const Arguments& headers,
+                                              const GetArguments& getArguments)
+  {
+    if (method == HttpMethod_Get ||
+        method == HttpMethod_Delete)
+    {
+      RestCallbackMatcher matcher(uri);
+
+      PImpl::ChunkedRestCallback* callback = NULL;
+
+      // Loop over the callbacks registered by the plugins
+      for (PImpl::ChunkedRestCallbacks::const_iterator it = pimpl_->chunkedRestCallbacks_.begin(); 
+           it != pimpl_->chunkedRestCallbacks_.end(); ++it)
+      {
+        if (matcher.IsMatch((*it)->GetRegularExpression()))
+        {
+          callback = *it;
+          break;
+        }
+      }
+
+      if (callback != NULL)
+      {
+        LOG(INFO) << "Delegating HTTP request to plugin for URI: " << matcher.GetFlatUri();
+
+        HttpRequestConverter converter(matcher, method, headers);
+        converter.SetGetArguments(getArguments);
+        
+        PImpl::PluginHttpOutput pluginOutput(output);
+        
+        assert(callback != NULL);
+        OrthancPluginErrorCode error = callback->GetParameters().handler
+          (reinterpret_cast<OrthancPluginRestOutput*>(&pluginOutput), 
+           NULL /* no reader */, matcher.GetFlatUri().c_str(), &converter.GetRequest());
+        
+        pluginOutput.Close(error, GetErrorDictionary());
+        return true;
+      }
+    }
+
+    return false;
   }
 
 
@@ -1311,69 +1449,22 @@
 
     if (callback == NULL)
     {
-      // Callback not found
-      return false;
+      // Callback not found, try to find a chunked callback
+      return HandleChunkedGetDelete(output, method, uri, headers, getArguments);
     }
 
     LOG(INFO) << "Delegating HTTP request to plugin for URI: " << matcher.GetFlatUri();
 
-    std::vector<const char*> getKeys, getValues, headersKeys, headersValues;
-
-    OrthancPluginHttpRequest request;
-    memset(&request, 0, sizeof(OrthancPluginHttpRequest));
-
-    ArgumentsToPlugin(headersKeys, headersValues, headers);
-    assert(headersKeys.size() == headersValues.size());
-
-    switch (method)
-    {
-      case HttpMethod_Get:
-        request.method = OrthancPluginHttpMethod_Get;
-        ArgumentsToPlugin(getKeys, getValues, getArguments);
-        assert(getKeys.size() == getValues.size());
-        break;
-
-      case HttpMethod_Post:
-        request.method = OrthancPluginHttpMethod_Post;
-        break;
-
-      case HttpMethod_Delete:
-        request.method = OrthancPluginHttpMethod_Delete;
-        break;
-
-      case HttpMethod_Put:
-        request.method = OrthancPluginHttpMethod_Put;
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_InternalError);
-    }
-
-    request.groups = matcher.GetGroups();
-    request.groupsCount = matcher.GetGroupsCount();
-    request.getCount = getArguments.size();
-    request.body = bodyData;
-    request.bodySize = bodySize;
-    request.headersCount = headers.size();
-    
-    if (getArguments.size() > 0)
-    {
-      request.getKeys = &getKeys[0];
-      request.getValues = &getValues[0];
-    }
-    
-    if (headers.size() > 0)
-    {
-      request.headersKeys = &headersKeys[0];
-      request.headersValues = &headersValues[0];
-    }
+    HttpRequestConverter converter(matcher, method, headers);
+    converter.SetGetArguments(getArguments);
+    converter.GetRequest().body = bodyData;
+    converter.GetRequest().bodySize = bodySize;
+
+    PImpl::PluginHttpOutput pluginOutput(output);
 
     assert(callback != NULL);
-
-    PImpl::PluginHttpOutput pluginOutput(output);
-
     OrthancPluginErrorCode error = callback->Invoke
-      (pimpl_->restCallbackMutex_, pluginOutput, matcher.GetFlatUri(), request);
+      (pimpl_->restCallbackMutex_, pluginOutput, matcher.GetFlatUri(), converter.GetRequest());
 
     pluginOutput.Close(error, GetErrorDictionary());
     return true;
@@ -1450,15 +1541,15 @@
   }
 
 
-  void OrthancPlugins::RegisterMultipartRestCallback(const void* parameters)
+  void OrthancPlugins::RegisterChunkedRestCallback(const void* parameters)
   {
-    const _OrthancPluginMultipartRestCallback& p = 
-      *reinterpret_cast<const _OrthancPluginMultipartRestCallback*>(parameters);
-
-    LOG(INFO) << "Plugin has registered a REST callback for multipart streams on: " 
+    const _OrthancPluginChunkedRestCallback& p = 
+      *reinterpret_cast<const _OrthancPluginChunkedRestCallback*>(parameters);
+
+    LOG(INFO) << "Plugin has registered a REST callback for chunked streams on: " 
               << p.pathRegularExpression;
 
-    pimpl_->multipartRestCallbacks_.push_back(new PImpl::MultipartRestCallback(p));
+    pimpl_->chunkedRestCallbacks_.push_back(new PImpl::ChunkedRestCallback(p));
   }
 
 
@@ -2432,10 +2523,10 @@
       SetupHttpClient(client, converted);
     }
     
-    ChunkedHttpRequest body(p, pimpl_->dictionary_);
+    HttpClientChunkedRequest body(p, pimpl_->dictionary_);
     client.SetBody(body);
 
-    ChunkedHttpAnswer answer(p, pimpl_->dictionary_);
+    HttpClientChunkedAnswer answer(p, pimpl_->dictionary_);
 
     bool success = client.Apply(answer);
 
@@ -3546,8 +3637,8 @@
         RegisterRestCallback(parameters, false);
         return true;
 
-      case _OrthancPluginService_RegisterMultipartRestCallback:
-        RegisterMultipartRestCallback(parameters);
+      case _OrthancPluginService_RegisterChunkedRestCallback:
+        RegisterChunkedRestCallback(parameters);
         return true;
 
       case _OrthancPluginService_RegisterOnStoredInstanceCallback:
@@ -4110,170 +4201,115 @@
   }
 
 
-  class OrthancPlugins::MultipartStream : 
-    public IHttpHandler::IStream,
-    private MultipartStreamReader::IHandler
+  class OrthancPlugins::HttpServerChunkedReader : public IHttpHandler::IChunkedRequestReader
   {
   private:
-    OrthancPluginMultipartRestHandler*   handler_;
-    _OrthancPluginMultipartRestCallback  parameters_;
-    MultipartStreamReader                reader_;
-    PluginsErrorDictionary&              errorDictionary_;
-
-    virtual void HandlePart(const MultipartStreamReader::HttpHeaders& headers,
-                            const void* part,
-                            size_t size)
-    {
-      assert(handler_ != NULL);
-
-      std::string contentType;
-      MultipartStreamReader::GetMainContentType(contentType, headers);
-      Orthanc::Toolbox::ToLowerCase(contentType);
-
-      std::vector<const char*>  headersKeys, headersValues;
-      ArgumentsToPlugin(headersKeys, headersValues, headers);
-      assert(headersKeys.size() == headersValues.size());
-
-      OrthancPluginErrorCode error = parameters_.addPart(
-        handler_, contentType.c_str(), headersKeys.size(),
-        headersKeys.empty() ? NULL : &headersKeys[0],
-        headersValues.empty() ? NULL : &headersValues[0],
-        part, size);
-
-      if (error != OrthancPluginErrorCode_Success)
-      {
-        errorDictionary_.LogError(error, true);
-        throw OrthancException(static_cast<ErrorCode>(error));
-      }
-    }
+    OrthancPluginServerChunkedRequestReader*  reader_;
+    _OrthancPluginChunkedRestCallback         parameters_;
+    PluginsErrorDictionary&                   errorDictionary_;
 
   public:
-    MultipartStream(OrthancPluginMultipartRestHandler* handler,
-                    const _OrthancPluginMultipartRestCallback& parameters,
-                    const std::string& boundary,
-                    PluginsErrorDictionary& errorDictionary) :
-      handler_(handler),
+    HttpServerChunkedReader(OrthancPluginServerChunkedRequestReader* reader,
+                            const _OrthancPluginChunkedRestCallback& parameters,
+                            PluginsErrorDictionary& errorDictionary) :
+      reader_(reader),
       parameters_(parameters),
-      reader_(boundary),
       errorDictionary_(errorDictionary)
     {
-      if (handler_ == NULL)
-      {
-        throw OrthancException(ErrorCode_Plugin, "The plugin has not created a multipart stream handler");
-      }
-
-      reader_.SetHandler(*this);
+      assert(reader_ != NULL);
     }
 
-    virtual ~MultipartStream()
+    virtual ~HttpServerChunkedReader()
     {
-      if (handler_ != NULL)
-      {
-        parameters_.finalize(handler_);
-      }
+      assert(reader_ != NULL);
+      parameters_.finalize(reader_);
     }
 
     virtual void AddBodyChunk(const void* data,
                               size_t size)
     {
-      reader_.AddChunk(data, size);
-    }
+      if (static_cast<uint32_t>(size) != size)
+      {
+        throw OrthancException(ErrorCode_NotEnoughMemory, ERROR_MESSAGE_64BIT);
+      }
+
+      assert(reader_ != NULL);
+      parameters_.addChunk(reader_, data, size);
+    }    
 
     virtual void Execute(HttpOutput& output)
     {
-      assert(handler_ != NULL);
-
-      reader_.CloseStream();
+      assert(reader_ != NULL);
 
       PImpl::PluginHttpOutput pluginOutput(output);
 
       OrthancPluginErrorCode error = parameters_.execute(
-        handler_, reinterpret_cast<OrthancPluginRestOutput*>(&pluginOutput));
+        reader_, reinterpret_cast<OrthancPluginRestOutput*>(&pluginOutput));
 
       pluginOutput.Close(error, errorDictionary_);
     }
   };
 
 
-  IHttpHandler::IStream* OrthancPlugins::CreateStreamHandler(RequestOrigin origin,
-                                                             const char* remoteIp,
-                                                             const char* username,
-                                                             HttpMethod method,
-                                                             const UriComponents& uri,
-                                                             const Arguments& headers)
+  bool OrthancPlugins::CreateChunkedRequestReader(std::auto_ptr<IChunkedRequestReader>& target,
+                                                  RequestOrigin origin,
+                                                  const char* remoteIp,
+                                                  const char* username,
+                                                  HttpMethod method,
+                                                  const UriComponents& uri,
+                                                  const Arguments& headers)
   {
+    if (method != HttpMethod_Post &&
+        method != HttpMethod_Put)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
     RestCallbackMatcher matcher(uri);
 
+    PImpl::ChunkedRestCallback* callback = NULL;
+
     // Loop over the callbacks registered by the plugins
-    for (PImpl::MultipartRestCallbacks::const_iterator it = pimpl_->multipartRestCallbacks_.begin(); 
-         it != pimpl_->multipartRestCallbacks_.end(); ++it)
+    for (PImpl::ChunkedRestCallbacks::const_iterator it = pimpl_->chunkedRestCallbacks_.begin(); 
+         it != pimpl_->chunkedRestCallbacks_.end(); ++it)
     {
       if (matcher.IsMatch((*it)->GetRegularExpression()))
       {
-        LOG(INFO) << "Delegating HTTP multipart request to plugin for URI: " << matcher.GetFlatUri();
-
-        std::vector<const char*> headersKeys, headersValues;
-        ArgumentsToPlugin(headersKeys, headersValues, headers);
-        assert(headersKeys.size() == headersValues.size());
-
-        OrthancPluginHttpMethod convertedMethod;
-        switch (method)
-        {
-          case HttpMethod_Post:
-            convertedMethod = OrthancPluginHttpMethod_Post;
-            break;
-
-          case HttpMethod_Put:
-            convertedMethod = OrthancPluginHttpMethod_Put;
-            break;
-
-          default:
-            throw OrthancException(ErrorCode_ParameterOutOfRange);
-        }
-
-        std::string mainContentType;
-        if (!MultipartStreamReader::GetMainContentType(mainContentType, headers))
-        {
-          LOG(INFO) << "Missing Content-Type HTTP header, prevents streaming the body";
-          continue;
-        }
-
-        std::string contentType, subType, boundary;
-        if (!MultipartStreamReader::ParseMultipartContentType
-            (contentType, subType, boundary, mainContentType))
-        {
-          LOG(INFO) << "Invalid Content-Type HTTP header, "
-                    << "prevents streaming the body: \"" << mainContentType << "\"";
-          continue;
-        }
-
-        OrthancPluginErrorCode errorCode = OrthancPluginErrorCode_Plugin;
-
-        OrthancPluginMultipartRestHandler* handler = (*it)->GetParameters().createHandler(
-          (*it)->GetParameters().factory, &errorCode,
-          convertedMethod, matcher.GetFlatUri().c_str(), contentType.c_str(), subType.c_str(),
-          matcher.GetGroupsCount(), matcher.GetGroups(), headers.size(),
-          headers.empty() ? NULL : &headersKeys[0],
-          headers.empty() ? NULL : &headersValues[0]);
-
-        if (handler == NULL)
-        {
-          if (errorCode == OrthancPluginErrorCode_Success)
-          {
-            // Ignore: The factory cannot create a handler for this request
-          }
-          else
-          {
-            throw OrthancException(static_cast<ErrorCode>(errorCode));
-          }          
-        }
-        else
-        {
-          return new MultipartStream(handler, (*it)->GetParameters(), boundary, GetErrorDictionary());
-        }
+        callback = *it;
+        break;
       }
     }
 
-    return NULL;
+    if (callback == NULL)
+    {
+      // Callback not found
+      return false;
+    }
+
+    LOG(INFO) << "Delegating chunked HTTP request to plugin for URI: " << matcher.GetFlatUri();
+
+    HttpRequestConverter converter(matcher, method, headers);
+    converter.GetRequest().body = NULL;
+    converter.GetRequest().bodySize = 0;
+
+    OrthancPluginServerChunkedRequestReader* reader = NULL;
+    
+    OrthancPluginErrorCode errorCode = callback->GetParameters().handler(
+      NULL /* no HTTP output */, &reader, matcher.GetFlatUri().c_str(), &converter.GetRequest());
+    
+    if (reader == NULL)
+    {
+      // The plugin has not created a reader for chunked body
+      return false;
+    }
+    else if (errorCode != OrthancPluginErrorCode_Success)
+    {
+      throw OrthancException(static_cast<ErrorCode>(errorCode));
+    }
+    else
+    {
+      target.reset(new HttpServerChunkedReader(reader, callback->GetParameters(), GetErrorDictionary()));
+      return true;
+    }
   }
 }
--- a/Plugins/Engine/OrthancPlugins.h	Tue Jun 11 14:02:57 2019 +0200
+++ b/Plugins/Engine/OrthancPlugins.h	Tue Jun 11 19:44:10 2019 +0200
@@ -89,14 +89,20 @@
     class WorklistHandler;
     class FindHandler;
     class MoveHandler;
-    class ChunkedHttpRequest;
-    class ChunkedHttpAnswer;
-    class MultipartStream;
+    class HttpClientChunkedRequest;
+    class HttpClientChunkedAnswer;
+    class HttpServerChunkedReader;
     
     void RegisterRestCallback(const void* parameters,
                               bool lock);
 
-    void RegisterMultipartRestCallback(const void* parameters);
+    void RegisterChunkedRestCallback(const void* parameters);
+
+    bool HandleChunkedGetDelete(HttpOutput& output,
+                                HttpMethod method,
+                                const UriComponents& uri,
+                                const Arguments& headers,
+                                const GetArguments& getArguments);
 
     void RegisterOnStoredInstanceCallback(const void* parameters);
 
@@ -326,12 +332,13 @@
     void RefreshMetrics();
 
     // New in Orthanc 1.5.7
-    virtual IStream* CreateStreamHandler(RequestOrigin origin,
-                                         const char* remoteIp,
-                                         const char* username,
-                                         HttpMethod method,
-                                         const UriComponents& uri,
-                                         const Arguments& headers);
+    virtual bool CreateChunkedRequestReader(std::auto_ptr<IChunkedRequestReader>& target,
+                                            RequestOrigin origin,
+                                            const char* remoteIp,
+                                            const char* username,
+                                            HttpMethod method,
+                                            const UriComponents& uri,
+                                            const Arguments& headers);
   };
 }
 
--- a/Plugins/Include/orthanc/OrthancCPlugin.h	Tue Jun 11 14:02:57 2019 +0200
+++ b/Plugins/Include/orthanc/OrthancCPlugin.h	Tue Jun 11 19:44:10 2019 +0200
@@ -445,7 +445,7 @@
     _OrthancPluginService_RegisterMoveCallback = 1009,
     _OrthancPluginService_RegisterIncomingHttpRequestFilter2 = 1010,
     _OrthancPluginService_RegisterRefreshMetricsCallback = 1011,
-    _OrthancPluginService_RegisterMultipartRestCallback = 1012,  /* New in Orthanc 1.5.7 */
+    _OrthancPluginService_RegisterChunkedRestCallback = 1012,  /* New in Orthanc 1.5.7 */
 
     /* Sending answers to REST calls */
     _OrthancPluginService_AnswerBuffer = 2000,
@@ -6810,72 +6810,72 @@
 
 
 
-  typedef OrthancPluginErrorCode (*OrthancPluginChunkedHttpAnswerAddHeader) (void* answer,
+  typedef OrthancPluginErrorCode (*OrthancPluginChunkedClientAnswerAddHeader) (void* answer,
                                                                              const char* key,
                                                                              const char* value);
 
-  typedef OrthancPluginErrorCode (*OrthancPluginChunkedHttpAnswerAddChunk) (void* answer,
+  typedef OrthancPluginErrorCode (*OrthancPluginChunkedClientAnswerAddChunk) (void* answer,
                                                                             const void* data,
                                                                             uint32_t size);
 
-  typedef uint8_t (*OrthancPluginChunkedHttpRequestIsDone) (void* request);
-
-  typedef OrthancPluginErrorCode (*OrthancPluginChunkedHttpRequestNext) (void* request);
-
-  typedef const void* (*OrthancPluginChunkedHttpRequestGetChunkData) (void* request);
-
-  typedef uint32_t (*OrthancPluginChunkedHttpRequestGetChunkSize) (void* request);
+  typedef uint8_t (*OrthancPluginChunkedClientRequestIsDone) (void* request);
+
+  typedef OrthancPluginErrorCode (*OrthancPluginChunkedClientRequestNext) (void* request);
+
+  typedef const void* (*OrthancPluginChunkedClientRequestGetChunkData) (void* request);
+
+  typedef uint32_t (*OrthancPluginChunkedClientRequestGetChunkSize) (void* request);
 
   
 
   typedef struct
   {
-    void*                                        answer;
-    OrthancPluginChunkedHttpAnswerAddChunk       answerAddChunk;
-    OrthancPluginChunkedHttpAnswerAddHeader      answerAddHeader;
-    uint16_t*                                    httpStatus;
-    OrthancPluginHttpMethod                      method;
-    const char*                                  url;
-    uint32_t                                     headersCount;
-    const char* const*                           headersKeys;
-    const char* const*                           headersValues;
-    void*                                        request;
-    OrthancPluginChunkedHttpRequestIsDone        requestIsDone;
-    OrthancPluginChunkedHttpRequestGetChunkData  requestChunkData;
-    OrthancPluginChunkedHttpRequestGetChunkSize  requestChunkSize;
-    OrthancPluginChunkedHttpRequestNext          requestNext;
-    const char*                                  username;
-    const char*                                  password;
-    uint32_t                                     timeout;
-    const char*                                  certificateFile;
-    const char*                                  certificateKeyFile;
-    const char*                                  certificateKeyPassword;
-    uint8_t                                      pkcs11;
+    void*                                          answer;
+    OrthancPluginChunkedClientAnswerAddChunk       answerAddChunk;
+    OrthancPluginChunkedClientAnswerAddHeader      answerAddHeader;
+    uint16_t*                                      httpStatus;
+    OrthancPluginHttpMethod                        method;
+    const char*                                    url;
+    uint32_t                                       headersCount;
+    const char* const*                             headersKeys;
+    const char* const*                             headersValues;
+    void*                                          request;
+    OrthancPluginChunkedClientRequestIsDone        requestIsDone;
+    OrthancPluginChunkedClientRequestGetChunkData  requestChunkData;
+    OrthancPluginChunkedClientRequestGetChunkSize  requestChunkSize;
+    OrthancPluginChunkedClientRequestNext          requestNext;
+    const char*                                    username;
+    const char*                                    password;
+    uint32_t                                       timeout;
+    const char*                                    certificateFile;
+    const char*                                    certificateKeyFile;
+    const char*                                    certificateKeyPassword;
+    uint8_t                                        pkcs11;
   } _OrthancPluginChunkedHttpClient;
 
   ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginChunkedHttpClient(
-    OrthancPluginContext*                        context,
-    void*                                        answer,
-    OrthancPluginChunkedHttpAnswerAddChunk       answerAddChunk,
-    OrthancPluginChunkedHttpAnswerAddHeader      answerAddHeader,
-    uint16_t*                                    httpStatus,
-    OrthancPluginHttpMethod                      method,
-    const char*                                  url,
-    uint32_t                                     headersCount,
-    const char* const*                           headersKeys,
-    const char* const*                           headersValues,
-    void*                                        request,
-    OrthancPluginChunkedHttpRequestIsDone        requestIsDone,
-    OrthancPluginChunkedHttpRequestGetChunkData  requestChunkData,
-    OrthancPluginChunkedHttpRequestGetChunkSize  requestChunkSize,
-    OrthancPluginChunkedHttpRequestNext          requestNext,
-    const char*                                  username,
-    const char*                                  password,
-    uint32_t                                     timeout,
-    const char*                                  certificateFile,
-    const char*                                  certificateKeyFile,
-    const char*                                  certificateKeyPassword,
-    uint8_t                                      pkcs11)
+    OrthancPluginContext*                          context,
+    void*                                          answer,
+    OrthancPluginChunkedClientAnswerAddChunk       answerAddChunk,
+    OrthancPluginChunkedClientAnswerAddHeader      answerAddHeader,
+    uint16_t*                                      httpStatus,
+    OrthancPluginHttpMethod                        method,
+    const char*                                    url,
+    uint32_t                                       headersCount,
+    const char* const*                             headersKeys,
+    const char* const*                             headersValues,
+    void*                                          request,
+    OrthancPluginChunkedClientRequestIsDone        requestIsDone,
+    OrthancPluginChunkedClientRequestGetChunkData  requestChunkData,
+    OrthancPluginChunkedClientRequestGetChunkSize  requestChunkSize,
+    OrthancPluginChunkedClientRequestNext          requestNext,
+    const char*                                    username,
+    const char*                                    password,
+    uint32_t                                       timeout,
+    const char*                                    certificateFile,
+    const char*                                    certificateKeyFile,
+    const char*                                    certificateKeyPassword,
+    uint8_t                                        pkcs11)
   {
     _OrthancPluginChunkedHttpClient params;
     memset(&params, 0, sizeof(params));
@@ -6910,67 +6910,51 @@
 
 
 
-  typedef struct _OrthancPluginMultipartRestHandler_t OrthancPluginMultipartRestHandler;
-
-  typedef struct _OrthancPluginMultipartRestFactory_t OrthancPluginMultipartRestFactory;
-
-  typedef OrthancPluginMultipartRestHandler* (*OrthancPluginMultipartRestCreateHandler) (
-    OrthancPluginMultipartRestFactory* factory,
-    OrthancPluginErrorCode*            errorCode,   /* out: to report an exception on handler creation */
-    OrthancPluginHttpMethod            method,
-    const char*                        url,
-    const char*                        contentType,
-    const char*                        subType,
-    uint32_t                           groupsCount,
-    const char* const*                 groups,
-    uint32_t                           headersCount,
-    const char* const*                 headersKeys,
-    const char* const*                 headersValues);
-
-  typedef OrthancPluginErrorCode (*OrthancPluginMultipartHandlerAddPart) (
-    OrthancPluginMultipartRestHandler* handler,
-    const char*                        contentType,
-    uint32_t                           headersCount,
-    const char* const*                 headersKeys,
-    const char* const*                 headersValues,
-    const void*                        data,
-    uint32_t                           size);
+  typedef struct _OrthancPluginServerChunkedRequestReader_t OrthancPluginServerChunkedRequestReader;
+
+  typedef OrthancPluginErrorCode (*OrthancPluginServerChunkedRequestHandler) (
+    OrthancPluginRestOutput*                   output,  /* out, for GET/DELETE only, NULL if POST/PUT */
+    OrthancPluginServerChunkedRequestReader**  reader, /* out, for POST/PUT only, NULL if GET/DELETE */
+    const char*                                url,
+    const OrthancPluginHttpRequest*            request); /* body and bodySize are not used */
+
+  typedef OrthancPluginErrorCode (*OrthancPluginServerChunkedRequestReaderAddChunk) (
+    OrthancPluginServerChunkedRequestReader* reader,
+    const void*                              data,
+    uint32_t                                 size);
     
-  typedef OrthancPluginErrorCode (*OrthancPluginMultipartHandlerExecute) (
-    OrthancPluginMultipartRestHandler* handler,
-    OrthancPluginRestOutput*           output);
-  
-  typedef void (*OrthancPluginMultipartHandlerFinalize) (
-    OrthancPluginMultipartRestHandler* handler);
+  typedef OrthancPluginErrorCode (*OrthancPluginServerChunkedRequestReaderExecute) (
+    OrthancPluginServerChunkedRequestReader* reader,
+    OrthancPluginRestOutput*                 output);
+    
+  typedef void (*OrthancPluginServerChunkedRequestReaderFinalize) (
+    OrthancPluginServerChunkedRequestReader* reader);
   
   typedef struct
   {
-    const char*                              pathRegularExpression;
-    OrthancPluginMultipartRestFactory*       factory;
-    OrthancPluginMultipartRestCreateHandler  createHandler;
-    OrthancPluginMultipartHandlerAddPart     addPart;
-    OrthancPluginMultipartHandlerExecute     execute;
-   OrthancPluginMultipartHandlerFinalize     finalize;
-  } _OrthancPluginMultipartRestCallback;
-
-  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterMultipartRestCallback(
-    OrthancPluginContext*                    context,
-    const char*                              pathRegularExpression,
-    OrthancPluginMultipartRestFactory*       factory,
-    OrthancPluginMultipartRestCreateHandler  createHandler,
-    OrthancPluginMultipartHandlerAddPart     addPart,
-    OrthancPluginMultipartHandlerExecute     execute,
-    OrthancPluginMultipartHandlerFinalize    finalize)
-  {
-    _OrthancPluginMultipartRestCallback params;
+    const char*                                      pathRegularExpression;
+    OrthancPluginServerChunkedRequestHandler         handler;
+    OrthancPluginServerChunkedRequestReaderAddChunk  addChunk;
+    OrthancPluginServerChunkedRequestReaderExecute   execute;
+    OrthancPluginServerChunkedRequestReaderFinalize  finalize;
+  } _OrthancPluginChunkedRestCallback;
+
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterChunkedRestCallback(
+    OrthancPluginContext*                            context,
+    const char*                                      pathRegularExpression,
+    OrthancPluginServerChunkedRequestHandler         handler,
+    OrthancPluginServerChunkedRequestReaderAddChunk  addChunk,
+    OrthancPluginServerChunkedRequestReaderExecute   execute,
+    OrthancPluginServerChunkedRequestReaderFinalize  finalize)
+  {
+    _OrthancPluginChunkedRestCallback params;
     params.pathRegularExpression = pathRegularExpression;
-    params.factory = factory;
-    params.createHandler = createHandler;
-    params.addPart = addPart;
+    params.handler = handler;
+    params.addChunk = addChunk;
     params.execute = execute;
     params.finalize = finalize;
 
-    context->InvokeService(context, _OrthancPluginService_RegisterMultipartRestCallback, &params);
+    context->InvokeService(context, _OrthancPluginService_RegisterChunkedRestCallback, &params);
   }
 
 #ifdef  __cplusplus
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Tue Jun 11 14:02:57 2019 +0200
+++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Tue Jun 11 19:44:10 2019 +0200
@@ -33,17 +33,6 @@
 
 #include "OrthancPluginCppWrapper.h"
 
-#if HAS_ORTHANC_PLUGIN_HTTP_MULTIPART_SERVER == 0
-#  if ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(1, 5, 7)
-#    define HAS_ORTHANC_FRAMEWORK_MULTIPART_READER 1
-#    include "../../../Core/HttpServer/MultipartStreamReader.h"
-#    include <boost/thread/shared_mutex.hpp>
-#  else
-#    define HAS_ORTHANC_FRAMEWORK_MULTIPART_READER 0
-#  endif
-#endif
-
-
 #include <boost/algorithm/string/predicate.hpp>
 #include <json/reader.h>
 #include <json/writer.h>
@@ -2615,283 +2604,4 @@
   }
 
 #endif  /* HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 */
-
-
-
-
-
-  /******************************************************************
-   ** MULTIPART REST SERVER
-   ******************************************************************/
-
-#if HAS_ORTHANC_PLUGIN_HTTP_MULTIPART_SERVER == 1
-  static OrthancPluginMultipartRestHandler* MultipartRestFactory(
-    OrthancPluginMultipartRestFactory* factory,
-    OrthancPluginErrorCode*            errorCode,
-    OrthancPluginHttpMethod            method,
-    const char*                        url,
-    const char*                        contentType,
-    const char*                        subType,
-    uint32_t                           groupsCount,
-    const char* const*                 groups,
-    uint32_t                           headersCount,
-    const char* const*                 headersKeys,
-    const char* const*                 headersValues)
-  {
-    *errorCode = OrthancPluginErrorCode_Success;
-
-    try
-    {
-      assert(factory != NULL);
-      MultipartRestCallback& that = *reinterpret_cast<MultipartRestCallback*>(factory);
-
-      std::vector<std::string> g;
-      g.resize(groupsCount);
-
-      for (uint32_t i = 0; i < groupsCount; i++)
-      {
-        g[i] = groups[i];
-      }
-
-      std::map<std::string, std::string> headers;
-      for (uint32_t i = 0; i < headersCount; i++)
-      {
-        headers[headersKeys[i]] = headersValues[i];
-      }
-
-      return reinterpret_cast<OrthancPluginMultipartRestHandler*>(
-        that.CreateHandler(method, url, contentType, 
-                           subType == NULL ? "" : subType, g, headers));
-    }
-    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-    {
-      LogError("Exception while creating a multipart handler");
-      *errorCode = static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-      return NULL;
-    }
-    catch (...)
-    {
-      LogError("Native exception while creating a multipart handler");
-      *errorCode = OrthancPluginErrorCode_Plugin;
-      return NULL;
-    }
-  }
-
-  static OrthancPluginErrorCode MultipartRestAddPart(
-    OrthancPluginMultipartRestHandler* handler,
-    const char*                        contentType,
-    uint32_t                           headersCount,
-    const char* const*                 headersKeys,
-    const char* const*                 headersValues,
-    const void*                        data,
-    uint32_t                           size)
-  {
-    try
-    {
-      assert(handler != NULL);
-      std::map<std::string, std::string> headers;
-      for (uint32_t i = 0; i < headersCount; i++)
-      {
-        headers[headersKeys[i]] = headersValues[i];
-      }
-
-      return reinterpret_cast<MultipartRestCallback::IHandler*>(handler)->
-        AddPart(contentType, headers, data, size);
-    }
-    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-    {
-      LogError("Exception while adding a part to a multipart handler");
-      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-    }
-    catch (...)
-    {
-      LogError("Native exception while adding a part to a multipart handler");
-      return OrthancPluginErrorCode_Plugin;
-    }
-  }
-    
-  static OrthancPluginErrorCode MultipartRestExecute(
-    OrthancPluginMultipartRestHandler* handler,
-    OrthancPluginRestOutput*           output)
-  {
-    try
-    {
-      assert(handler != NULL);
-      return reinterpret_cast<MultipartRestCallback::IHandler*>(handler)->Execute(output);
-    }
-    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-    {
-      LogError("Exception while executing a multipart handler");
-      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
-    }
-    catch (...)
-    {
-      LogError("Native exception while executing a multipart handler");
-      return OrthancPluginErrorCode_Plugin;
-    }
-  }
-  
-  static void MultipartRestFinalize(OrthancPluginMultipartRestHandler* handler)
-  {
-    try
-    {
-      assert(handler != NULL);
-      delete reinterpret_cast<MultipartRestCallback::IHandler*>(handler);
-    }
-    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
-    {
-      LogError("Exception while finalizing a multipart handler");
-    }
-    catch (...)
-    {
-      LogError("Native exception while finalizing a multipart handler");
-    }
-  }  
-
-  void MultipartRestCallback::Register(const std::string& regularExpression)
-  {
-    OrthancPluginRegisterMultipartRestCallback(
-      GetGlobalContext(), regularExpression.c_str(), 
-      reinterpret_cast<OrthancPluginMultipartRestFactory*>(this), 
-      MultipartRestFactory, MultipartRestAddPart, MultipartRestExecute, MultipartRestFinalize);
-  }
-
-#elif HAS_ORTHANC_FRAMEWORK_MULTIPART_READER == 1
-
-  namespace
-  {
-    class CompatibilityPartHandler : public Orthanc::MultipartStreamReader::IHandler
-    {
-    private:
-      MultipartRestCallback::IHandler&  handler_;
-
-    public:
-      CompatibilityPartHandler(MultipartRestCallback::IHandler& handler) :
-        handler_(handler)
-      {
-      }
-
-      virtual void HandlePart(const Orthanc::MultipartStreamReader::HttpHeaders& headers,
-                              const void* part,
-                              size_t size)
-      {
-        std::string contentType;
-        if (!Orthanc::MultipartStreamReader::GetMainContentType(contentType, headers))
-        {
-          contentType.clear();
-        }        
-
-        OrthancPluginErrorCode code = handler_.AddPart(contentType, headers, part, size);
-
-        if (code != OrthancPluginErrorCode_Success)
-        {
-          ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
-        }
-      }
-    };
-  }
-
-  static boost::shared_mutex  compatibilityMultipartMutex_;
-  static std::set<MultipartRestCallback*> compatibilityMultipartCallbacks_;
-    
-
-  namespace
-  {
-    void CompatibilityMultipartRestCallback(OrthancPluginRestOutput* output,
-                                            const char* url,
-                                            const OrthancPluginHttpRequest* request)
-    {
-      std::map<std::string, std::string> headers;
-      for (uint32_t i = 0; i < request->headersCount; i++)
-      {
-        headers[request->headersKeys[i]] = request->headersValues[i];
-      }
-
-      std::string mainContentType;
-      if (!Orthanc::MultipartStreamReader::GetMainContentType(mainContentType, headers))
-      {
-        LogError("Missing Content-Type HTTP header, prevents streaming the body");
-        ORTHANC_PLUGINS_THROW_EXCEPTION(UnsupportedMediaType);
-      }
-
-      std::string contentType, subType, boundary;
-      if (!Orthanc::MultipartStreamReader::ParseMultipartContentType
-          (contentType, subType, boundary, mainContentType))
-      {
-        LogError("Invalid Content-Type HTTP header, prevents streaming the body: \"" + 
-                 mainContentType + "\"");
-        ORTHANC_PLUGINS_THROW_EXCEPTION(UnsupportedMediaType);
-      }
-
-      std::vector<std::string> groups;
-      groups.reserve(request->groupsCount);
-      for (uint32_t i = 0; i < request->groupsCount; i++)
-      {
-        groups[i] = request->groups[i];
-      }
-
-      std::auto_ptr<MultipartRestCallback::IHandler> handler;
-
-      {
-        boost::shared_lock<boost::shared_mutex> lock(compatibilityMultipartMutex_);
-
-        for (std::set<MultipartRestCallback*>::const_iterator 
-               it = compatibilityMultipartCallbacks_.begin();
-             it != compatibilityMultipartCallbacks_.end(); ++it)
-        {
-          assert(*it != NULL);
-          handler.reset((*it)->CreateHandler(request->method, url, contentType, subType, groups, headers));
-          if (handler.get() != NULL)
-          {
-            break;
-          }
-        }
-      }
-
-      if (handler.get() != NULL)
-      {
-        {
-          CompatibilityPartHandler wrapper(*handler);
-          Orthanc::MultipartStreamReader reader(boundary);
-          reader.SetHandler(wrapper);
-          reader.AddChunk(request->body, request->bodySize);
-          reader.CloseStream();
-        }
-
-        handler->Execute(output);
-      }
-    }
-  }
-
-  void MultipartRestCallback::Register(const std::string& regularExpression)
-  {
-    LogWarning("Performance warning: The plugin was compiled against a pre-1.5.7 version "
-               "of the Orthanc SDK. Multipart transfers will be entirely stored in RAM.");
-
-    {
-      boost::unique_lock<boost::shared_mutex> lock(compatibilityMultipartMutex_);
-      compatibilityMultipartCallbacks_.insert(this);
-    }
-
-    OrthancPluginRegisterRestCallback(GetGlobalContext(), regularExpression.c_str(), 
-                                      Internals::Protect<CompatibilityMultipartRestCallback>);
-  }
-
-#else
-
-  void MultipartRestCallback::Register(const std::string& regularExpression)
-  {
-    /**
-     * Version >= 1.5.7 of the Orthanc framework must be
-     * available. Make sure that macros "HAS_ORTHANC_FRAMEWORK=1",
-     * "ORTHANC_FRAMEWORK_VERSION_MAJOR",
-     * "ORTHANC_FRAMEWORK_VERSION_MINOR" and
-     * "ORTHANC_FRAMEWORK_VERSION_REVISION" are properly set.
-     **/
-    LogError("The compatibility wrappers for multipart transfers is not available, stopping now.");
-    ORTHANC_PLUGINS_THROW_EXCEPTION(NotImplemented);
-  }  
-
-#endif
-
 }
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Tue Jun 11 14:02:57 2019 +0200
+++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Tue Jun 11 19:44:10 2019 +0200
@@ -57,21 +57,6 @@
 #endif
 
 
-#if !defined(ORTHANC_FRAMEWORK_VERSION_IS_ABOVE)
-#define ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(major, minor, revision)      \
-  (defined(HAS_ORTHANC_FRAMEWORK) &&                                    \
-   defined(ORTHANC_FRAMEWORK_VERSION_MAJOR) &&                          \
-   defined(ORTHANC_FRAMEWORK_VERSION_MINOR) &&                          \
-   defined(ORTHANC_FRAMEWORK_VERSION_REVISION) &&                       \
-   HAS_ORTHANC_FRAMEWORK == 1 &&                                        \
-   ORTHANC_FRAMEWORK_VERSION_MAJOR > major ||                           \
-   (ORTHANC_FRAMEWORK_VERSION_MAJOR == major &&                         \
-    (ORTHANC_FRAMEWORK_VERSION_MINOR > minor ||                         \
-     (ORTHANC_FRAMEWORK_VERSION_MINOR == minor &&                       \
-      ORTHANC_FRAMEWORK_VERSION_REVISION >= revision))))
-#endif
-
-
 #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0)
 // The "OrthancPluginFindMatcher()" primitive was introduced in Orthanc 1.2.0
 #  define HAS_ORTHANC_PLUGIN_FIND_MATCHER  1
@@ -113,9 +98,9 @@
 #endif
 
 #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7)
-#  define HAS_ORTHANC_PLUGIN_HTTP_MULTIPART_SERVER  1
+#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER  1
 #else
-#  define HAS_ORTHANC_PLUGIN_HTTP_MULTIPART_SERVER  0
+#  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER  0
 #endif
 
 
@@ -126,7 +111,6 @@
                                 const char* url,
                                 const OrthancPluginHttpRequest* request);
 
-
   void SetGlobalContext(OrthancPluginContext* context);
 
   bool HasGlobalContext();
@@ -936,36 +920,225 @@
 
 
 
-  class MultipartRestCallback : public boost::noncopyable
+  class IChunkedRequestReader : public boost::noncopyable
   {
   public:
-    class IHandler : public boost::noncopyable
-    {
-    public:
-      virtual ~IHandler()
-      {
-      }
-
-      virtual OrthancPluginErrorCode AddPart(const std::string& contentType,
-                                             const std::map<std::string, std::string>& headers,
-                                             const void* data,
-                                             size_t size) = 0;
-
-      virtual OrthancPluginErrorCode Execute(OrthancPluginRestOutput* output) = 0;
-    };
-
-
-    virtual ~MultipartRestCallback()
+    virtual ~IChunkedRequestReader()
     {
     }
 
-    virtual IHandler* CreateHandler(OrthancPluginHttpMethod method,
-                                    const std::string& url,
-                                    const std::string& contentType,
-                                    const std::string& subType,  // Possibly empty
-                                    const std::vector<std::string>& groups,
-                                    const std::map<std::string, std::string>& headers) = 0;
+    virtual void AddChunk(const void* data,
+                          size_t size) = 0;
+
+    virtual void Execute(OrthancPluginRestOutput* output) = 0;
+  };
+
+
+  typedef IChunkedRequestReader* (*ChunkedRestCallback) (OrthancPluginRestOutput* output,
+                                                         const char* url,
+                                                         const OrthancPluginHttpRequest* request);
+
+
+  namespace Internals
+  {
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1
+    template <ChunkedRestCallback Callback>
+    OrthancPluginErrorCode Protect(OrthancPluginRestOutput* output,
+                                   OrthancPluginServerChunkedRequestReader** reader,
+                                   const char* url,
+                                   const OrthancPluginHttpRequest* request)
+    {
+      try
+      {
+        switch (request->method)
+        {
+          case OrthancPluginHttpMethod_Get:
+          case OrthancPluginHttpMethod_Delete:
+            if (output == NULL ||
+                reader != NULL ||
+                Callback(output, url, request) != NULL)
+            {
+              return OrthancPluginErrorCode_InternalError;
+            }
+
+            return OrthancPluginErrorCode_Success;
+
+          case OrthancPluginHttpMethod_Post:
+          case OrthancPluginHttpMethod_Put:
+            if (output != NULL ||
+                reader == NULL)
+            {
+              return OrthancPluginErrorCode_InternalError;
+            }
+            else
+            {
+              *reader = reinterpret_cast<OrthancPluginServerChunkedRequestReader*>(Callback(NULL, url, request));
+              if (*reader == NULL)
+              {
+                return OrthancPluginErrorCode_Plugin;
+              }
+            }
+
+            return OrthancPluginErrorCode_Success;
+
+          default:
+            return OrthancPluginErrorCode_InternalError;
+        }
+      }
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+      {
+#if HAS_ORTHANC_EXCEPTION == 1 && HAS_ORTHANC_PLUGIN_EXCEPTION_DETAILS == 1
+        if (HasGlobalContext() &&
+            e.HasDetails())
+        {
+          // The "false" instructs Orthanc not to log the detailed
+          // error message. This is to avoid duplicating the details,
+          // because "OrthancException" already does it on construction.
+          OrthancPluginSetHttpErrorDetails
+            (GetGlobalContext(), output, e.GetDetails(), false);
+        }
+#endif
+
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+
+    static OrthancPluginErrorCode ChunkedRequestReaderAddChunk(
+      OrthancPluginServerChunkedRequestReader* reader,
+      const void*                              data,
+      uint32_t                                 size)
+    {
+      try
+      {
+        if (reader == NULL)
+        {
+          return OrthancPluginErrorCode_InternalError;
+        }
 
-    void Register(const std::string& regularExpression);
-  };
+        reinterpret_cast<IChunkedRequestReader*>(reader)->AddChunk(data, size);
+        return OrthancPluginErrorCode_Success;
+      }
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+      {
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+
+    static OrthancPluginErrorCode ChunkedRequestReaderExecute(
+      OrthancPluginServerChunkedRequestReader* reader,
+      OrthancPluginRestOutput*                 output)
+    {
+      try
+      {
+        if (reader == NULL)
+        {
+          return OrthancPluginErrorCode_InternalError;
+        }
+
+        reinterpret_cast<IChunkedRequestReader*>(reader)->Execute(output);
+        return OrthancPluginErrorCode_Success;
+      }
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+      {
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+
+    static void ChunkedRequestReaderFinalize(
+      OrthancPluginServerChunkedRequestReader* reader)
+    {
+      if (reader != NULL)
+      {
+        delete reinterpret_cast<IChunkedRequestReader*>(reader);
+      }
+    }
+
+#else
+
+    template <ChunkedRestCallback Callback>
+    void ChunkedRestCompatibility(OrthancPluginRestOutput* output,
+                                  const char* url,
+                                  const OrthancPluginHttpRequest* request)
+    {
+      switch (request->method)
+      {
+        case OrthancPluginHttpMethod_Get:
+        case OrthancPluginHttpMethod_Delete:
+          if (Callback(output, url, request) != NULL)
+          {
+            ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+          }
+          else
+          {
+            return;
+          }
+
+        case OrthancPluginHttpMethod_Post:
+        case OrthancPluginHttpMethod_Put:
+        {
+          std::auto_ptr<IChunkedRequestReader> reader(Callback(NULL, url, request));
+          if (reader.get() == NULL)
+          {
+            ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+          }
+          else
+          {
+            if (request->bodySize != 0)
+            {
+              reader->AddChunk(request->body, request->bodySize);
+            }
+            reader->Execute(output);
+          }
+          return;
+        }
+
+        default:
+          ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+      }
+    }
+#endif
+  }
+
+
+  template <ChunkedRestCallback Callback>
+  void RegisterChunkedRestCallback(const std::string& uri)
+  {
+#if HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER == 1
+    OrthancPluginRegisterChunkedRestCallback(
+      GetGlobalContext(), uri.c_str(), Internals::Protect<Callback>,
+      Internals::ChunkedRequestReaderAddChunk,
+      Internals::ChunkedRequestReaderExecute,
+      Internals::ChunkedRequestReaderFinalize);
+#else
+    LogWarning("Performance warning: The plugin was compiled against a pre-1.5.7 version "
+               "of the Orthanc SDK. Multipart transfers will be entirely stored in RAM.");
+    OrthancPluginRegisterRestCallback(
+      GetGlobalContext(), uri.c_str(), 
+      Internals::Protect< Internals::ChunkedRestCompatibility<Callback> >);
+#endif
+  }
 }