changeset 3387:a48d652f1500

new function OrthancPluginHttpClientChunkedBody(), new class OrthancPlugins::HttpClient
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 05 Jun 2019 17:17:48 +0200
parents af9432e46c07
children 18cd4951fccc
files Core/HttpClient.cpp Core/HttpClient.h NEWS Plugins/Engine/OrthancPlugins.cpp Plugins/Engine/OrthancPlugins.h Plugins/Include/orthanc/OrthancCPlugin.h Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Plugins/Samples/Common/OrthancPluginCppWrapper.h
diffstat 8 files changed, 755 insertions(+), 210 deletions(-) [+]
line wrap: on
line diff
--- a/Core/HttpClient.cpp	Wed Jun 05 14:40:14 2019 +0200
+++ b/Core/HttpClient.cpp	Wed Jun 05 17:17:48 2019 +0200
@@ -201,16 +201,16 @@
   };
 
 
-  class HttpClient::CurlBodyStream : public boost::noncopyable
+  class HttpClient::CurlChunkedBody : public boost::noncopyable
   {
   private:
-    HttpClient::IBodyStream*  stream_;
-    std::string               buffer_;
+    HttpClient::IChunkedBody*  body_;
+    std::string                buffer_;
 
     size_t CallbackInternal(char* curlBuffer,
                             size_t curlBufferSize)
     {
-      if (stream_ == NULL)
+      if (body_ == NULL)
       {
         throw OrthancException(ErrorCode_BadSequenceOfCalls);
       }
@@ -220,11 +220,11 @@
         throw OrthancException(ErrorCode_InternalError);
       }
 
-      // Read chunks from the stream so as to fill the target buffer
+      // Read chunks from the body stream so as to fill the target buffer
       std::string chunk;
       
       while (buffer_.size() < curlBufferSize &&
-             stream_->ReadNextChunk(chunk))
+             body_->ReadNextChunk(chunk))
       {
         buffer_ += chunk;
       }
@@ -243,26 +243,26 @@
     }
     
   public:
-    CurlBodyStream() :
-      stream_(NULL)
+    CurlChunkedBody() :
+      body_(NULL)
     {
     }
 
-    void SetStream(HttpClient::IBodyStream& stream)
+    void SetBody(HttpClient::IChunkedBody& body)
     {
-      stream_ = &stream;
+      body_ = &body;
       buffer_.clear();
     }
 
     void Clear()
     {
-      stream_ = NULL;
+      body_ = NULL;
       buffer_.clear();
     }
 
     bool IsValid() const
     {
-      return stream_ != NULL;
+      return body_ != NULL;
     }
 
     static size_t Callback(char *buffer,
@@ -272,15 +272,15 @@
     {
       try
       {
-        HttpClient::CurlBodyStream* stream = reinterpret_cast<HttpClient::CurlBodyStream*>(userdata);
+        HttpClient::CurlChunkedBody* body = reinterpret_cast<HttpClient::CurlChunkedBody*>(userdata);
 
-        if (stream == NULL)
+        if (body == NULL)
         {
           throw OrthancException(ErrorCode_NullPointer);
         }
         else
         {
-          return stream->CallbackInternal(buffer, size * nitems);
+          return body->CallbackInternal(buffer, size * nitems);
         }
       }
       catch (OrthancException& e)
@@ -404,7 +404,7 @@
     CurlHeaders defaultPostHeaders_;
     CurlHeaders defaultChunkedHeaders_;
     CurlHeaders userHeaders_;
-    CurlBodyStream  bodyStream_;
+    CurlChunkedBody chunkedBody_;
   };
 
 
@@ -604,19 +604,21 @@
   void HttpClient::SetBody(const std::string& data)
   {
     body_ = data;
-    pimpl_->bodyStream_.Clear();
+    pimpl_->chunkedBody_.Clear();
   }
 
 
-  void HttpClient::SetBodyStream(IBodyStream& stream)
+  void HttpClient::SetBody(IChunkedBody& body)
   {
-    pimpl_->bodyStream_.SetStream(stream);
+    body_.clear();
+    pimpl_->chunkedBody_.SetBody(body);
   }
 
   
-  void HttpClient::ClearBodyStream()
+  void HttpClient::ClearBody()
   {
-    pimpl_->bodyStream_.Clear();
+    body_.clear();
+    pimpl_->chunkedBody_.Clear();
   }
 
 
@@ -825,10 +827,10 @@
         LOG(INFO) << "For performance, the HTTP header \"Expect\" should be set to empty string in POST/PUT requests";
       }
 
-      if (pimpl_->bodyStream_.IsValid())
+      if (pimpl_->chunkedBody_.IsValid())
       {
-        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_READFUNCTION, CurlBodyStream::Callback));
-        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_READDATA, &pimpl_->bodyStream_));
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_READFUNCTION, CurlChunkedBody::Callback));
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_READDATA, &pimpl_->chunkedBody_));
         CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 1L));
         CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, -1L));
     
--- a/Core/HttpClient.h	Wed Jun 05 14:40:14 2019 +0200
+++ b/Core/HttpClient.h	Wed Jun 05 17:17:48 2019 +0200
@@ -57,10 +57,10 @@
   public:
     typedef std::map<std::string, std::string>  HttpHeaders;
 
-    class IBodyStream : public boost::noncopyable
+    class IChunkedBody : public boost::noncopyable
     {
     public:
-      virtual ~IBodyStream()
+      virtual ~IChunkedBody()
       {
       }
       
@@ -69,7 +69,7 @@
 
   private:
     class CurlHeaders;
-    class CurlBodyStream;
+    class CurlChunkedBody;
     class GlobalParameters;
 
     struct PImpl;
@@ -158,9 +158,9 @@
       return body_;
     }
 
-    void SetBodyStream(IBodyStream& stream);
+    void SetBody(IChunkedBody& body);
 
-    void ClearBodyStream();
+    void ClearBody();
 
     void SetVerbose(bool isVerbose);
 
--- a/NEWS	Wed Jun 05 14:40:14 2019 +0200
+++ b/NEWS	Wed Jun 05 17:17:48 2019 +0200
@@ -9,6 +9,12 @@
   to bypass the automated correction of outgoing C-FIND queries
 * Reporting of "ParentResources" in "DicomModalityStore" and "DicomModalityStore" jobs
 
+Plugins
+-------
+
+* New functions in the SDK:
+  - OrthancPluginHttpClientChunkedBody(): POST/PUT query with a chunked body
+
 Maintenance
 -----------
 
--- a/Plugins/Engine/OrthancPlugins.cpp	Wed Jun 05 14:40:14 2019 +0200
+++ b/Plugins/Engine/OrthancPlugins.cpp	Wed Jun 05 17:17:48 2019 +0200
@@ -973,7 +973,55 @@
       return new Driver(driver, size, params_.applyMove, params_.freeMove);
     }
   };
-  
+
+
+
+  class OrthancPlugins::ChunkedBody : public HttpClient::IChunkedBody
+  {
+  private:
+    const _OrthancPluginHttpClientChunkedBody&  params_;
+    PluginsErrorDictionary&                     errorDictionary_;
+
+  public:
+    ChunkedBody(const _OrthancPluginHttpClientChunkedBody& params,
+               PluginsErrorDictionary&  errorDictionary) :
+      params_(params),
+      errorDictionary_(errorDictionary)
+    {
+    }
+
+    virtual bool ReadNextChunk(std::string& chunk)
+    {
+      if (params_.bodyDone(params_.body))
+      {
+        return false;
+      }
+      else
+      {
+        size_t size = params_.bodyChunkSize(params_.body);
+
+        chunk.resize(size);
+        
+        if (size != 0)
+        {
+          const void* data = params_.bodyChunkData(params_.body);
+          memcpy(&chunk[0], data, size);
+        }
+
+        OrthancPluginErrorCode error = params_.bodyNext(params_.body);
+        
+        if (error != OrthancPluginErrorCode_Success)
+        {
+          errorDictionary_.LogError(error, true);
+          throw OrthancException(static_cast<ErrorCode>(error));
+        }
+        else
+        {
+          return true;
+        }
+      }
+    }
+  };
 
 
   OrthancPlugins::OrthancPlugins()
@@ -2037,20 +2085,55 @@
   }
 
 
-  void OrthancPlugins::CallHttpClient(const void* parameters)
+  static void RunHttpClient(HttpClient& client,
+                            const _OrthancPluginCallHttpClient2& parameters)
   {
-    const _OrthancPluginCallHttpClient& p = *reinterpret_cast<const _OrthancPluginCallHttpClient*>(parameters);
-
-    HttpClient client;
-    client.SetUrl(p.url);
-
-    if (p.username != NULL && 
-        p.password != NULL)
+    client.SetUrl(parameters.url);
+    client.SetConvertHeadersToLowerCase(false);
+
+    if (parameters.timeout != 0)
+    {
+      client.SetTimeout(parameters.timeout);
+    }
+
+    if (parameters.username != NULL && 
+        parameters.password != NULL)
+    {
+      client.SetCredentials(parameters.username, parameters.password);
+    }
+
+    if (parameters.certificateFile != NULL)
     {
-      client.SetCredentials(p.username, p.password);
+      std::string certificate(parameters.certificateFile);
+      std::string key, password;
+
+      if (parameters.certificateKeyFile)
+      {
+        key.assign(parameters.certificateKeyFile);
+      }
+
+      if (parameters.certificateKeyPassword)
+      {
+        password.assign(parameters.certificateKeyPassword);
+      }
+
+      client.SetClientCertificate(certificate, key, password);
     }
 
-    switch (p.method)
+    client.SetPkcs11Enabled(parameters.pkcs11 ? true : false);
+
+    for (uint32_t i = 0; i < parameters.headersCount; i++)
+    {
+      if (parameters.headersKeys[i] == NULL ||
+          parameters.headersValues[i] == NULL)
+      {
+        throw OrthancException(ErrorCode_NullPointer);
+      }
+
+      client.AddHeader(parameters.headersKeys[i], parameters.headersValues[i]);
+    }
+
+    switch (parameters.method)
     {
       case OrthancPluginHttpMethod_Get:
         client.SetMethod(HttpMethod_Get);
@@ -2058,96 +2141,10 @@
 
       case OrthancPluginHttpMethod_Post:
         client.SetMethod(HttpMethod_Post);
-        client.GetBody().assign(p.body, p.bodySize);
         break;
 
       case OrthancPluginHttpMethod_Put:
         client.SetMethod(HttpMethod_Put);
-        client.GetBody().assign(p.body, p.bodySize);
-        break;
-
-      case OrthancPluginHttpMethod_Delete:
-        client.SetMethod(HttpMethod_Delete);
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    std::string s;
-    client.ApplyAndThrowException(s);
-
-    if (p.method != OrthancPluginHttpMethod_Delete)
-    {
-      CopyToMemoryBuffer(*p.target, s);
-    }
-  }
-
-
-  void OrthancPlugins::CallHttpClient2(const void* parameters)
-  {
-    const _OrthancPluginCallHttpClient2& p = *reinterpret_cast<const _OrthancPluginCallHttpClient2*>(parameters);
-
-    HttpClient client;
-    client.SetUrl(p.url);
-    client.SetConvertHeadersToLowerCase(false);
-
-    if (p.timeout != 0)
-    {
-      client.SetTimeout(p.timeout);
-    }
-
-    if (p.username != NULL && 
-        p.password != NULL)
-    {
-      client.SetCredentials(p.username, p.password);
-    }
-
-    if (p.certificateFile != NULL)
-    {
-      std::string certificate(p.certificateFile);
-      std::string key, password;
-
-      if (p.certificateKeyFile)
-      {
-        key.assign(p.certificateKeyFile);
-      }
-
-      if (p.certificateKeyPassword)
-      {
-        password.assign(p.certificateKeyPassword);
-      }
-
-      client.SetClientCertificate(certificate, key, password);
-    }
-
-    client.SetPkcs11Enabled(p.pkcs11 ? true : false);
-
-    for (uint32_t i = 0; i < p.headersCount; i++)
-    {
-      if (p.headersKeys[i] == NULL ||
-          p.headersValues[i] == NULL)
-      {
-        throw OrthancException(ErrorCode_NullPointer);
-      }
-
-      client.AddHeader(p.headersKeys[i], p.headersValues[i]);
-    }
-
-    switch (p.method)
-    {
-      case OrthancPluginHttpMethod_Get:
-        client.SetMethod(HttpMethod_Get);
-        break;
-
-      case OrthancPluginHttpMethod_Post:
-        client.SetMethod(HttpMethod_Post);
-        client.GetBody().assign(p.body, p.bodySize);
-        break;
-
-      case OrthancPluginHttpMethod_Put:
-        client.SetMethod(HttpMethod_Put);
-        client.GetBody().assign(p.body, p.bodySize);
         break;
 
       case OrthancPluginHttpMethod_Delete:
@@ -2164,7 +2161,7 @@
     bool success = client.Apply(body, headers);
 
     // The HTTP request has succeeded
-    *p.httpStatus = static_cast<uint16_t>(client.GetLastStatus());
+    *parameters.httpStatus = static_cast<uint16_t>(client.GetLastStatus());
 
     if (!success)
     {
@@ -2172,7 +2169,7 @@
     }
 
     // Copy the HTTP headers of the answer, if the plugin requested them
-    if (p.answerHeaders != NULL)
+    if (parameters.answerHeaders != NULL)
     {
       Json::Value json = Json::objectValue;
 
@@ -2183,17 +2180,107 @@
       }
         
       std::string s = json.toStyledString();
-      CopyToMemoryBuffer(*p.answerHeaders, s);
+      CopyToMemoryBuffer(*parameters.answerHeaders, s);
     }
 
     // Copy the body of the answer if it makes sense
-    if (p.method != OrthancPluginHttpMethod_Delete)
+    if (parameters.method != OrthancPluginHttpMethod_Delete)
     {
-      CopyToMemoryBuffer(*p.answerBody, body);
+      CopyToMemoryBuffer(*parameters.answerBody, body);
     }
   }
 
 
+  void OrthancPlugins::CallHttpClient(const void* parameters)
+  {
+    const _OrthancPluginCallHttpClient& p = *reinterpret_cast<const _OrthancPluginCallHttpClient*>(parameters);
+    
+    _OrthancPluginCallHttpClient2 converted;
+    memset(&converted, 0, sizeof(converted));
+
+    uint16_t httpStatus;
+
+    converted.answerBody = p.target;
+    converted.answerHeaders = NULL;
+    converted.httpStatus = &httpStatus;
+    converted.method = p.method;
+    converted.url = p.url;
+    converted.headersCount = 0;
+    converted.headersKeys = NULL;
+    converted.headersValues = NULL;
+    converted.body = p.body;
+    converted.bodySize = p.bodySize;
+    converted.username = p.username;
+    converted.password = p.password;
+    converted.timeout = 0;  // Use default timeout
+    converted.certificateFile = NULL;
+    converted.certificateKeyFile = NULL;
+    converted.certificateKeyPassword = NULL;
+    converted.pkcs11 = false;
+
+    HttpClient client;
+    RunHttpClient(client, converted);
+  }
+
+
+  void OrthancPlugins::CallHttpClient2(const void* parameters)
+  {
+    const _OrthancPluginCallHttpClient2& p = *reinterpret_cast<const _OrthancPluginCallHttpClient2*>(parameters);
+    
+    HttpClient client;
+
+    if (p.method == OrthancPluginHttpMethod_Post ||
+        p.method == OrthancPluginHttpMethod_Put)
+    {
+        client.GetBody().assign(p.body, p.bodySize);
+    }
+    
+    RunHttpClient(client, p);
+  }
+
+
+  void OrthancPlugins::HttpClientChunkedBody(const void* parameters)
+  {
+    const _OrthancPluginHttpClientChunkedBody& p =
+      *reinterpret_cast<const _OrthancPluginHttpClientChunkedBody*>(parameters);
+    
+    if (p.method != OrthancPluginHttpMethod_Post &&
+        p.method != OrthancPluginHttpMethod_Put)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange,
+                             "This plugin service is only allowed for PUT and POST HTTP requests");
+    }
+
+    ChunkedBody body(p, pimpl_->dictionary_);
+
+    HttpClient client;
+    client.SetBody(body);
+    
+    _OrthancPluginCallHttpClient2 converted;
+    memset(&converted, 0, sizeof(converted));
+
+    converted.answerBody = p.answerBody;
+    converted.answerHeaders = p.answerHeaders;
+    converted.httpStatus = p.httpStatus;
+    converted.method = p.method;
+    converted.url = p.url;
+    converted.headersCount = p.headersCount;
+    converted.headersKeys = p.headersKeys;
+    converted.headersValues = p.headersValues;
+    converted.body = NULL;
+    converted.bodySize = 0;
+    converted.username = p.username;
+    converted.password = p.password;
+    converted.timeout = p.timeout;
+    converted.certificateFile = p.certificateFile;
+    converted.certificateKeyFile = p.certificateKeyFile;
+    converted.certificateKeyPassword = p.certificateKeyPassword;
+    converted.pkcs11 = p.pkcs11;
+    
+    RunHttpClient(client, converted);
+  }
+
+
   void OrthancPlugins::CallPeerApi(const void* parameters)
   {
     const _OrthancPluginCallPeerApi& p = *reinterpret_cast<const _OrthancPluginCallPeerApi*>(parameters);
@@ -2872,6 +2959,10 @@
         CallHttpClient2(parameters);
         return true;
 
+      case _OrthancPluginService_HttpClientChunkedBody:
+        HttpClientChunkedBody(parameters);
+        return true;
+
       case _OrthancPluginService_ConvertPixelFormat:
         ConvertPixelFormat(parameters);
         return true;
--- a/Plugins/Engine/OrthancPlugins.h	Wed Jun 05 14:40:14 2019 +0200
+++ b/Plugins/Engine/OrthancPlugins.h	Wed Jun 05 17:17:48 2019 +0200
@@ -89,7 +89,8 @@
     class WorklistHandler;
     class FindHandler;
     class MoveHandler;
-
+    class ChunkedBody;
+    
     void RegisterRestCallback(const void* parameters,
                               bool lock);
 
@@ -164,6 +165,8 @@
 
     void CallHttpClient2(const void* parameters);
 
+    void HttpClientChunkedBody(const void* parameters);
+
     void CallPeerApi(const void* parameters);
   
     void GetFontInfo(const void* parameters);
--- a/Plugins/Include/orthanc/OrthancCPlugin.h	Wed Jun 05 14:40:14 2019 +0200
+++ b/Plugins/Include/orthanc/OrthancCPlugin.h	Wed Jun 05 17:17:48 2019 +0200
@@ -120,7 +120,7 @@
 
 #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     1
 #define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     5
-#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  4
+#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  7
 
 
 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
@@ -429,6 +429,7 @@
     _OrthancPluginService_SetMetricsValue = 31,
     _OrthancPluginService_EncodeDicomWebJson = 32,
     _OrthancPluginService_EncodeDicomWebXml = 33,
+    _OrthancPluginService_HttpClientChunkedBody = 34,   /* New in Orthanc 1.5.7 */
     
     /* Registration of callbacks */
     _OrthancPluginService_RegisterRestCallback = 1000,
@@ -6794,6 +6795,110 @@
   }
   
 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+  typedef uint8_t (*OrthancPluginChunkedBodyIsDone) (void* body);
+
+  typedef OrthancPluginErrorCode (*OrthancPluginChunkedBodyNext) (void* body);
+
+  typedef const void* (*OrthancPluginChunkedBodyGetChunkData) (void* body);
+
+  typedef uint32_t (*OrthancPluginChunkedBodyGetChunkSize) (void* body);
+
+  
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*            answerBody;
+    OrthancPluginMemoryBuffer*            answerHeaders;
+    uint16_t*                             httpStatus;
+    OrthancPluginHttpMethod               method;
+    const char*                           url;
+    uint32_t                              headersCount;
+    const char* const*                    headersKeys;
+    const char* const*                    headersValues;
+    const char*                           username;
+    const char*                           password;
+    uint32_t                              timeout;
+    const char*                           certificateFile;
+    const char*                           certificateKeyFile;
+    const char*                           certificateKeyPassword;
+    uint8_t                               pkcs11;
+    void*                                 body;
+    OrthancPluginChunkedBodyIsDone        bodyDone;
+    OrthancPluginChunkedBodyGetChunkData  bodyChunkData;
+    OrthancPluginChunkedBodyGetChunkSize  bodyChunkSize;
+    OrthancPluginChunkedBodyNext          bodyNext;
+  } _OrthancPluginHttpClientChunkedBody;
+
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpClientChunkedBody(
+    OrthancPluginContext*                 context,
+    OrthancPluginMemoryBuffer*            answerBody,
+    OrthancPluginMemoryBuffer*            answerHeaders,
+    uint16_t*                             httpStatus,
+    OrthancPluginHttpMethod               method,
+    const char*                           url,
+    uint32_t                              headersCount,
+    const char* const*                    headersKeys,
+    const char* const*                    headersValues,
+    const char*                           username,
+    const char*                           password,
+    uint32_t                              timeout,
+    const char*                           certificateFile,
+    const char*                           certificateKeyFile,
+    const char*                           certificateKeyPassword,
+    uint8_t                               pkcs11,
+    void*                                 body,
+    OrthancPluginChunkedBodyIsDone        bodyDone,
+    OrthancPluginChunkedBodyGetChunkData  bodyChunkData,
+    OrthancPluginChunkedBodyGetChunkSize  bodyChunkSize,
+    OrthancPluginChunkedBodyNext          bodyNext)
+  {
+    _OrthancPluginHttpClientChunkedBody params;
+    memset(&params, 0, sizeof(params));
+
+    /* In common with OrthancPluginHttpClient() */
+    params.answerBody = answerBody;
+    params.answerHeaders = answerHeaders;
+    params.httpStatus = httpStatus;
+    params.method = method;
+    params.url = url;
+    params.headersCount = headersCount;
+    params.headersKeys = headersKeys;
+    params.headersValues = headersValues;
+    params.username = username;
+    params.password = password;
+    params.timeout = timeout;
+    params.certificateFile = certificateFile;
+    params.certificateKeyFile = certificateKeyFile;
+    params.certificateKeyPassword = certificateKeyPassword;
+    params.pkcs11 = pkcs11;
+
+    /* For body stream */
+    params.body = body;
+    params.bodyDone = bodyDone;
+    params.bodyChunkData = bodyChunkData;
+    params.bodyChunkSize = bodyChunkSize;
+    params.bodyNext = bodyNext;
+
+    return context->InvokeService(context, _OrthancPluginService_HttpClientChunkedBody, &params);
+  }
+
+
+  
 #ifdef  __cplusplus
 }
 #endif
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Wed Jun 05 14:40:14 2019 +0200
+++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Wed Jun 05 17:17:48 2019 +0200
@@ -440,9 +440,9 @@
                   const std::string& password)
   {
     OrthancPluginErrorCode error = OrthancPluginHttpDelete
-        (GetGlobalContext(), url.c_str(),
-         username.empty() ? NULL : username.c_str(),
-         password.empty() ? NULL : password.c_str());
+      (GetGlobalContext(), url.c_str(),
+       username.empty() ? NULL : username.c_str(),
+       password.empty() ? NULL : password.c_str());
 
     if (error == OrthancPluginErrorCode_Success)
     {
@@ -591,19 +591,19 @@
 
     switch (configuration_[key].type())
     {
-    case Json::intValue:
-      target = configuration_[key].asInt();
-      return true;
+      case Json::intValue:
+        target = configuration_[key].asInt();
+        return true;
 
-    case Json::uintValue:
-      target = configuration_[key].asUInt();
-      return true;
+      case Json::uintValue:
+        target = configuration_[key].asUInt();
+        return true;
 
-    default:
-      LogError("The configuration option \"" + GetPath(key) +
-               "\" is not an integer as expected");
+      default:
+        LogError("The configuration option \"" + GetPath(key) +
+                 "\" is not an integer as expected");
 
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
     }
   }
 
@@ -667,23 +667,23 @@
 
     switch (configuration_[key].type())
     {
-    case Json::realValue:
-      target = configuration_[key].asFloat();
-      return true;
+      case Json::realValue:
+        target = configuration_[key].asFloat();
+        return true;
 
-    case Json::intValue:
-      target = static_cast<float>(configuration_[key].asInt());
-      return true;
+      case Json::intValue:
+        target = static_cast<float>(configuration_[key].asInt());
+        return true;
 
-    case Json::uintValue:
-      target = static_cast<float>(configuration_[key].asUInt());
-      return true;
+      case Json::uintValue:
+        target = static_cast<float>(configuration_[key].asUInt());
+        return true;
 
-    default:
-      LogError("The configuration option \"" + GetPath(key) +
-               "\" is not an integer as expected");
+      default:
+        LogError("The configuration option \"" + GetPath(key) +
+                 "\" is not an integer as expected");
 
-      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
     }
   }
 
@@ -703,41 +703,41 @@
 
     switch (configuration_[key].type())
     {
-    case Json::arrayValue:
-    {
-      bool ok = true;
-
-      for (Json::Value::ArrayIndex i = 0; ok && i < configuration_[key].size(); i++)
+      case Json::arrayValue:
       {
-        if (configuration_[key][i].type() == Json::stringValue)
+        bool ok = true;
+
+        for (Json::Value::ArrayIndex i = 0; ok && i < configuration_[key].size(); i++)
         {
-          target.push_back(configuration_[key][i].asString());
+          if (configuration_[key][i].type() == Json::stringValue)
+          {
+            target.push_back(configuration_[key][i].asString());
+          }
+          else
+          {
+            ok = false;
+          }
         }
-        else
+
+        if (ok)
         {
-          ok = false;
+          return true;
         }
+
+        break;
       }
 
-      if (ok)
-      {
-        return true;
-      }
-
-      break;
-    }
+      case Json::stringValue:
+        if (allowSingleString)
+        {
+          target.push_back(configuration_[key].asString());
+          return true;
+        }
 
-    case Json::stringValue:
-      if (allowSingleString)
-      {
-        target.push_back(configuration_[key].asString());
-        return true;
-      }
+        break;
 
-      break;
-
-    default:
-      break;
+      default:
+        break;
     }
 
     LogError("The configuration option \"" + GetPath(key) +
@@ -758,7 +758,7 @@
       target.clear();
 
       for (std::list<std::string>::const_iterator
-           it = lst.begin(); it != lst.end(); ++it)
+             it = lst.begin(); it != lst.end(); ++it)
       {
         target.insert(*it);
       }
@@ -941,7 +941,7 @@
                              void*                     buffer)
   {
     image_ = OrthancPluginCreateImageAccessor
-        (GetGlobalContext(), format, width, height, pitch, buffer);
+      (GetGlobalContext(), format, width, height, pitch, buffer);
 
     if (image_ == NULL)
     {
@@ -1143,7 +1143,7 @@
 
   void AnswerJson(const Json::Value& value,
                   OrthancPluginRestOutput* output
-                  )
+    )
   {
     Json::StyledWriter writer;
     std::string bodyString = writer.write(value);
@@ -1154,7 +1154,7 @@
   void AnswerString(const std::string& answer,
                     const char* mimeType,
                     OrthancPluginRestOutput* output
-                    )
+    )
   {
     OrthancPluginAnswerBuffer(GetGlobalContext(), output, answer.c_str(), answer.size(), mimeType);
   }
@@ -1354,15 +1354,15 @@
     // Parse the version of the Orthanc core
     int aa, bb, cc;
     if (
-    #ifdef _MSC_VER
-        sscanf_s
-    #else
-        sscanf
-    #endif
-        (GetGlobalContext()->orthancVersion, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 ||
-        aa < 0 ||
-        bb < 0 ||
-        cc < 0)
+#ifdef _MSC_VER
+      sscanf_s
+#else
+      sscanf
+#endif
+      (GetGlobalContext()->orthancVersion, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 ||
+      aa < 0 ||
+      bb < 0 ||
+      cc < 0)
     {
       return false;
     }
@@ -1590,9 +1590,9 @@
     OrthancPlugins::MemoryBuffer answer;
     uint16_t status;
     OrthancPluginErrorCode code = OrthancPluginCallPeerApi
-        (GetGlobalContext(), *answer, NULL, &status, peers_,
-         static_cast<uint32_t>(index), OrthancPluginHttpMethod_Get, uri.c_str(),
-         0, NULL, NULL, NULL, 0, timeout_);
+      (GetGlobalContext(), *answer, NULL, &status, peers_,
+       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Get, uri.c_str(),
+       0, NULL, NULL, NULL, 0, timeout_);
 
     if (code == OrthancPluginErrorCode_Success)
     {
@@ -1714,9 +1714,9 @@
     OrthancPlugins::MemoryBuffer answer;
     uint16_t status;
     OrthancPluginErrorCode code = OrthancPluginCallPeerApi
-        (GetGlobalContext(), *answer, NULL, &status, peers_,
-         static_cast<uint32_t>(index), OrthancPluginHttpMethod_Post, uri.c_str(),
-         0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_);
+      (GetGlobalContext(), *answer, NULL, &status, peers_,
+       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Post, uri.c_str(),
+       0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_);
 
     if (code == OrthancPluginErrorCode_Success)
     {
@@ -1742,9 +1742,9 @@
     OrthancPlugins::MemoryBuffer answer;
     uint16_t status;
     OrthancPluginErrorCode code = OrthancPluginCallPeerApi
-        (GetGlobalContext(), *answer, NULL, &status, peers_,
-         static_cast<uint32_t>(index), OrthancPluginHttpMethod_Put, uri.c_str(),
-         0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_);
+      (GetGlobalContext(), *answer, NULL, &status, peers_,
+       static_cast<uint32_t>(index), OrthancPluginHttpMethod_Put, uri.c_str(),
+       0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_);
 
     if (code == OrthancPluginErrorCode_Success)
     {
@@ -2000,9 +2000,9 @@
     }
 
     OrthancPluginJob* orthanc = OrthancPluginCreateJob(
-          GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(),
-          CallbackGetProgress, CallbackGetContent, CallbackGetSerialized,
-          CallbackStep, CallbackStop, CallbackReset);
+      GetGlobalContext(), job, CallbackFinalize, job->jobType_.c_str(),
+      CallbackGetProgress, CallbackGetContent, CallbackGetSerialized,
+      CallbackStep, CallbackStop, CallbackReset);
 
     if (orthanc == NULL)
     {
@@ -2055,4 +2055,220 @@
                                  OrthancPluginMetricsType_Timer);
   }
 #endif
+
+
+
+#if HAS_ORTHANC_PLUGIN_HTTP_CHUNKED_BODY == 1
+  class HttpClient::ChunkedBody : public boost::noncopyable
+  {
+  private:
+    static ChunkedBody& GetObject(void* body)
+    {
+      assert(body != NULL);
+      return *reinterpret_cast<ChunkedBody*>(body);
+    }
+
+    IChunkedBody&  body_;
+    bool           done_;
+    std::string    chunk_;
+
+  public:
+    ChunkedBody(IChunkedBody& body) :
+      body_(body),
+      done_(false)
+    {
+    }      
+
+    static uint8_t IsDone(void* body)
+    {
+      return GetObject(body).done_;
+    }
+    
+    static const void* GetChunkData(void* body)
+    {
+      return GetObject(body).chunk_.c_str();
+    }
+    
+    static uint32_t GetChunkSize(void* body)
+    {
+      return static_cast<uint32_t>(GetObject(body).chunk_.size());
+    }
+
+    static OrthancPluginErrorCode Next(void* body)
+    {
+      ChunkedBody& that = GetObject(body);
+        
+      if (that.done_)
+      {
+        return OrthancPluginErrorCode_BadSequenceOfCalls;
+      }
+      else
+      {
+        try
+        {
+          that.done_ = !that.body_.ReadNextChunk(that.chunk_);
+          return OrthancPluginErrorCode_Success;
+        }
+        catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+        {
+          return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+        }
+        catch (...)
+        {
+          return OrthancPluginErrorCode_InternalError;
+        }
+      }
+    }    
+  };
+#endif
+
+  HttpClient::HttpClient() :
+    httpStatus_(0),
+    method_(OrthancPluginHttpMethod_Get),
+    timeout_(0),
+    pkcs11_(false),
+    chunkedBody_(NULL)
+  {
+  }
+
+  
+  void HttpClient::SetCredentials(const std::string& username,
+                                  const std::string& password)
+  {
+    username_ = username;
+    password_ = password;
+  }
+
+  
+  void HttpClient::ClearCredentials()
+  {
+    username_.empty();
+    password_.empty();
+  }
+
+
+  void HttpClient::SetCertificate(const std::string& certificateFile,
+                                  const std::string& keyFile,
+                                  const std::string& keyPassword)
+  {
+    certificateFile_ = certificateFile;
+    certificateKeyFile_ = keyFile;
+    certificateKeyPassword_ = keyPassword;
+  }
+
+  
+  void HttpClient::ClearCertificate()
+  {
+    certificateFile_.clear();
+    certificateKeyFile_.clear();
+    certificateKeyPassword_.clear();
+  }
+
+
+  void HttpClient::ClearBody()
+  {
+    body_.clear();
+    chunkedBody_ = NULL;
+  }
+
+  
+  void HttpClient::SwapBody(std::string& body)
+  {
+    body_.swap(body);
+    chunkedBody_ = NULL;
+  }
+
+  
+  void HttpClient::SetBody(const std::string& body)
+  {
+    body_ = body;
+    chunkedBody_ = NULL;
+  }
+
+  
+#if HAS_ORTHANC_PLUGIN_HTTP_CHUNKED_BODY == 1
+  void HttpClient::SetBody(IChunkedBody& body)
+  {
+    body_.clear();
+    chunkedBody_ = &body;
+  }
+#endif
+
+  
+  void HttpClient::Execute()
+  {
+    std::vector<const char*> headersKeys;
+    std::vector<const char*> headersValues;
+
+    headersKeys.reserve(headers_.size());
+    headersValues.reserve(headers_.size());
+
+    for (HttpHeaders::const_iterator it = headers_.begin();
+         it != headers_.end(); ++it)
+    {
+      headersKeys.push_back(it->first.c_str());
+      headersValues.push_back(it->second.c_str());
+    }
+
+    OrthancPluginErrorCode error;
+      
+    if (chunkedBody_ == NULL)
+    {
+      error = OrthancPluginHttpClient(
+        GetGlobalContext(),
+        *answerBody_,
+        *answerHeaders_,
+        &httpStatus_,
+        method_,
+        url_.c_str(),
+        headersKeys.size(),
+        headersKeys.empty() ? NULL : &headersKeys[0],
+        headersValues.empty() ? NULL : &headersValues[0],
+        body_.empty() ? NULL : body_.c_str(),
+        body_.size(),
+        username_.empty() ? NULL : username_.c_str(),
+        password_.empty() ? NULL : password_.c_str(),
+        timeout_,
+        certificateFile_.empty() ? NULL : certificateFile_.c_str(),
+        certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(),
+        certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(),
+        pkcs11_ ? 1 : 0);
+    }
+    else
+    {
+#if HAS_ORTHANC_PLUGIN_HTTP_CHUNKED_BODY != 1
+      error = OrthancPluginErrorCode_InternalError;
+#else
+      ChunkedBody wrapper(*chunkedBody_);
+        
+      error = OrthancPluginHttpClientChunkedBody(
+        GetGlobalContext(),
+        *answerBody_,
+        *answerHeaders_,
+        &httpStatus_,
+        method_,
+        url_.c_str(),
+        headersKeys.size(),
+        headersKeys.empty() ? NULL : &headersKeys[0],
+        headersValues.empty() ? NULL : &headersValues[0],
+        username_.empty() ? NULL : username_.c_str(),
+        password_.empty() ? NULL : password_.c_str(),
+        timeout_,
+        certificateFile_.empty() ? NULL : certificateFile_.c_str(),
+        certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(),
+        certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(),
+        pkcs11_ ? 1 : 0,
+        &wrapper,
+        ChunkedBody::IsDone,
+        ChunkedBody::GetChunkData,
+        ChunkedBody::GetChunkSize,
+        ChunkedBody::Next);
+#endif
+    }
+
+    if (error != OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
+    }
+  }
 }
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Wed Jun 05 14:40:14 2019 +0200
+++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Wed Jun 05 17:17:48 2019 +0200
@@ -85,6 +85,12 @@
 #  define HAS_ORTHANC_PLUGIN_METRICS  0
 #endif
 
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7)
+#  define HAS_ORTHANC_PLUGIN_HTTP_CHUNKED_BODY  1
+#else
+#  define HAS_ORTHANC_PLUGIN_HTTP_CHUNKED_BODY  0
+#endif
+
 
 
 namespace OrthancPlugins
@@ -771,4 +777,120 @@
     ~MetricsTimer();
   };
 #endif
+
+
+  class HttpClient : public boost::noncopyable
+  {
+  public:
+#if HAS_ORTHANC_PLUGIN_HTTP_CHUNKED_BODY == 1
+    class IChunkedBody : public boost::noncopyable
+    {
+    public:
+      virtual ~IChunkedBody()
+      {
+      }
+
+      virtual bool ReadNextChunk(std::string& chunk) = 0;
+    };
+#endif
+  
+
+  private:
+    typedef std::map<std::string, std::string>  HttpHeaders;
+    
+    MemoryBuffer             answerBody_;
+    MemoryBuffer             answerHeaders_;
+    uint16_t                 httpStatus_;
+    OrthancPluginHttpMethod  method_;
+    std::string              url_;
+    HttpHeaders              headers_;
+    std::string              username_;
+    std::string              password_;
+    uint32_t                 timeout_;
+    std::string              certificateFile_;
+    std::string              certificateKeyFile_;
+    std::string              certificateKeyPassword_;
+    bool                     pkcs11_;
+    std::string              body_;
+
+#if HAS_ORTHANC_PLUGIN_HTTP_CHUNKED_BODY == 1
+    class ChunkedBody;
+    IChunkedBody*            chunkedBody_;
+#else
+    // Dummy variable for backward compatibility
+    void*                    chunkedBody_;
+#endif
+
+  public:
+    HttpClient();
+
+    const MemoryBuffer& GetAnswerBody() const
+    {
+      return answerBody_;
+    }
+
+    const MemoryBuffer& GetAnswerHeaders() const
+    {
+      return answerHeaders_;
+    }
+
+    uint16_t GetHttpStatus() const
+    {
+      return httpStatus_;
+    }
+
+    void SetMethod(OrthancPluginHttpMethod method)
+    {
+      method_ = method;
+    }
+
+    const std::string& GetUrl() const
+    {
+      return url_;
+    }
+
+    void SetUrl(const std::string& url)
+    {
+      url_ = url;
+    }
+
+    void AddHeader(const std::string& key,
+                   const std::string& value)
+    {
+      headers_[key] = value;
+    }
+
+    void SetCredentials(const std::string& username,
+                        const std::string& password);
+
+    void ClearCredentials();
+
+    void SetTimeout(unsigned int timeout)  // 0 for default timeout
+    {
+      timeout_ = timeout;
+    }
+
+    void SetCertificate(const std::string& certificateFile,
+                        const std::string& keyFile,
+                        const std::string& keyPassword);
+
+    void ClearCertificate();
+
+    void SetPkcs11(bool pkcs11)
+    {
+      pkcs11_ = pkcs11;
+    }
+
+    void ClearBody();
+
+    void SwapBody(std::string& body);
+
+    void SetBody(const std::string& body);
+
+#if HAS_ORTHANC_PLUGIN_HTTP_CHUNKED_BODY == 1
+    void SetBody(IChunkedBody& body);
+#endif
+
+    void Execute();
+  };
 }