diff Plugins/Samples/Common/OrthancPluginCppWrapper.cpp @ 3393:2cd0369a156f

support of chunked answers in HttpClient and in SDK
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 06 Jun 2019 16:12:55 +0200
parents ad434967a68c
children 2f82ef41bf5a
line wrap: on
line diff
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Thu Jun 06 10:35:16 2019 +0200
+++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Thu Jun 06 16:12:55 2019 +0200
@@ -2059,22 +2059,21 @@
 
 
 #if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1
-#if HAS_ORTHANC_PLUGIN_HTTP_CHUNKED_BODY == 1
-  class HttpClient::RequestChunkedBody : public boost::noncopyable
+  class HttpClient::RequestBodyWrapper : public boost::noncopyable
   {
   private:
-    static RequestChunkedBody& GetObject(void* body)
+    static RequestBodyWrapper& GetObject(void* body)
     {
       assert(body != NULL);
-      return *reinterpret_cast<RequestChunkedBody*>(body);
+      return *reinterpret_cast<RequestBodyWrapper*>(body);
     }
 
-    IRequestChunkedBody&  body_;
+    IRequestBody&  body_;
     bool           done_;
     std::string    chunk_;
 
   public:
-    RequestChunkedBody(IRequestChunkedBody& body) :
+    RequestBodyWrapper(IRequestBody& body) :
       body_(body),
       done_(false)
     {
@@ -2097,7 +2096,7 @@
 
     static OrthancPluginErrorCode Next(void* body)
     {
-      RequestChunkedBody& that = GetObject(body);
+      RequestBodyWrapper& that = GetObject(body);
         
       if (that.done_)
       {
@@ -2121,14 +2120,62 @@
       }
     }    
   };
+
+
+#if HAS_ORTHANC_PLUGIN_STREAMING_HTTP_CLIENT == 1
+  static OrthancPluginErrorCode AnswerAddHeaderCallback(void* answer,
+                                                        const char* key,
+                                                        const char* value)
+  {
+    assert(answer != NULL && key != NULL && value != NULL);
+
+    try
+    {
+      reinterpret_cast<HttpClient::IAnswer*>(answer)->AddHeader(key, value);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
 #endif
 
+
+#if HAS_ORTHANC_PLUGIN_STREAMING_HTTP_CLIENT == 1
+  static OrthancPluginErrorCode AnswerAddChunkCallback(void* answer,
+                                                       const void* data,
+                                                       uint32_t size)
+  {
+    assert(answer != NULL);
+
+    try
+    {
+      reinterpret_cast<HttpClient::IAnswer*>(answer)->AddChunk(data, size);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+#endif
+
+
   HttpClient::HttpClient() :
     httpStatus_(0),
     method_(OrthancPluginHttpMethod_Get),
     timeout_(0),
     pkcs11_(false),
-    chunkedBody_(NULL)
+    streamingBody_(NULL)
   {
   }
 
@@ -2169,108 +2216,356 @@
   void HttpClient::ClearBody()
   {
     body_.clear();
-    chunkedBody_ = NULL;
+    streamingBody_ = NULL;
   }
 
   
   void HttpClient::SwapBody(std::string& body)
   {
     body_.swap(body);
-    chunkedBody_ = NULL;
+    streamingBody_ = NULL;
   }
 
   
   void HttpClient::SetBody(const std::string& body)
   {
     body_ = body;
-    chunkedBody_ = NULL;
+    streamingBody_ = NULL;
   }
 
   
-#if HAS_ORTHANC_PLUGIN_HTTP_CHUNKED_BODY == 1
-  void HttpClient::SetBody(IRequestChunkedBody& body)
+  void HttpClient::SetBody(IRequestBody& body)
   {
     body_.clear();
-    chunkedBody_ = &body;
+    streamingBody_ = &body;
   }
-#endif
-
-  
-  void HttpClient::Execute()
+
+
+  namespace
   {
-    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)
+    class HeadersWrapper : public boost::noncopyable
+    {
+    private:
+      std::vector<const char*>  headersKeys_;
+      std::vector<const char*>  headersValues_;
+
+    public:
+      HeadersWrapper(const HttpClient::HttpHeaders& headers)
+      {
+        headersKeys_.reserve(headers.size());
+        headersValues_.reserve(headers.size());
+
+        for (HttpClient::HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it)
+        {
+          headersKeys_.push_back(it->first.c_str());
+          headersValues_.push_back(it->second.c_str());
+        }
+      }
+
+      uint32_t GetCount() const
+      {
+        return headersKeys_.size();
+      }
+
+      const char* const* GetKeys() const
+      {
+        return headersKeys_.empty() ? NULL : &headersKeys_[0];
+      }
+
+      const char* const* GetValues() const
+      {
+        return headersValues_.empty() ? NULL : &headersValues_[0];
+      }
+    };
+
+
+    class MemoryRequestBody : public HttpClient::IRequestBody
+    {
+    private:
+      std::string  body_;
+
+    public:
+      MemoryRequestBody(const std::string& body) :
+        body_(body)
+      {
+      }
+
+      virtual bool ReadNextChunk(std::string& chunk)
+      {
+        chunk.swap(body_);
+        return true;
+      }
+    };
+
+
+    // This class mimics Orthanc::ChunkedBuffer
+    class ChunkedBuffer : public boost::noncopyable
     {
-      headersKeys.push_back(it->first.c_str());
-      headersValues.push_back(it->second.c_str());
-    }
-
-    OrthancPluginErrorCode error;
-      
-    if (chunkedBody_ == NULL)
+    private:
+      typedef std::list<std::string*>  Content;
+
+      Content  content_;
+      size_t   size_;
+
+    public:
+      ChunkedBuffer() :
+        size_(0)
+      {
+      }
+
+      ~ChunkedBuffer()
+      {
+        Clear();
+      }
+
+      void Clear()
+      {
+        for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
+        {
+          assert(*it != NULL);
+          delete *it;
+        }
+
+        content_.clear();
+      }
+
+      void Flatten(std::string& target) const
+      {
+        target.resize(size_);
+
+        size_t pos = 0;
+
+        for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it)
+        {
+          assert(*it != NULL);
+          size_t s = (*it)->size();
+
+          if (s != 0)
+          {
+            memcpy(&target[pos], (*it)->c_str(), s);
+            pos += s;
+          }
+        }
+
+        assert(size_ == 0 ||
+               pos == target.size());
+      }
+
+      void AddChunk(const void* data,
+                    size_t size)
+      {
+        content_.push_back(new std::string(reinterpret_cast<const char*>(data), size));
+        size_ += size;
+      }
+
+      void AddChunk(const std::string& chunk)
+      {
+        content_.push_back(new std::string(chunk));
+        size_ += chunk.size();
+      }
+    };
+
+
+#if HAS_ORTHANC_PLUGIN_STREAMING_HTTP_CLIENT == 1
+    class MemoryAnswer : public HttpClient::IAnswer
     {
-      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);
+    private:
+      HttpClient::HttpHeaders  headers_;
+      ChunkedBuffer            body_;
+
+    public:
+      const HttpClient::HttpHeaders& GetHeaders() const
+      {
+        return headers_;
+      }
+
+      const ChunkedBuffer& GetBody() const
+      {
+        return body_;
+      }
+
+      virtual void AddHeader(const std::string& key,
+                             const std::string& value)
+      {
+        headers_[key] = value;
+      }
+
+      virtual void AddChunk(const void* data,
+                            size_t size)
+      {
+        body_.AddChunk(data, size);
+      }
+    };
+#endif
+  }
+
+
+#if HAS_ORTHANC_PLUGIN_STREAMING_HTTP_CLIENT == 1
+  void HttpClient::ExecuteWithStream(uint16_t& httpStatus,
+                                     IAnswer& answer,
+                                     IRequestBody& body) const
+  {
+    std::auto_ptr<HeadersWrapper> h;
+
+    // Automatically set the "Transfer-Encoding" header if absent
+    if (headers_.find("Transfer-Encoding") == headers_.end())
+    {
+      HttpHeaders tmp = headers_;
+      tmp["Transfer-Encoding"] = "chunked";
+      h.reset(new HeadersWrapper(tmp));
     }
     else
     {
-#if HAS_ORTHANC_PLUGIN_HTTP_CHUNKED_BODY != 1
-      error = OrthancPluginErrorCode_InternalError;
-#else
-      RequestChunkedBody wrapper(*chunkedBody_);
+      h.reset(new HeadersWrapper(headers_));
+    }
+
+    RequestBodyWrapper request(body);
         
-      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,
-        RequestChunkedBody::IsDone,
-        RequestChunkedBody::GetChunkData,
-        RequestChunkedBody::GetChunkSize,
-        RequestChunkedBody::Next);
-#endif
-    }
+    OrthancPluginErrorCode error = OrthancPluginStreamingHttpClient(
+      GetGlobalContext(),
+      &answer,
+      AnswerAddChunkCallback,
+      AnswerAddHeaderCallback,
+      &httpStatus,
+      method_,
+      url_.c_str(),
+      h->GetCount(),
+      h->GetKeys(),
+      h->GetValues(),
+      &request,
+      RequestBodyWrapper::IsDone,
+      RequestBodyWrapper::GetChunkData,
+      RequestBodyWrapper::GetChunkSize,
+      RequestBodyWrapper::Next,
+      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);
 
     if (error != OrthancPluginErrorCode_Success)
     {
       ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
     }
   }
+#endif    
+
+
+  void HttpClient::ExecuteWithoutStream(uint16_t& httpStatus,
+                                        HttpHeaders& answerHeaders,
+                                        std::string& answerBody,
+                                        const std::string& body) const
+  {
+    HeadersWrapper headers(headers_);
+
+    MemoryBuffer answerBodyBuffer, answerHeadersBuffer;
+
+    OrthancPluginErrorCode error = OrthancPluginHttpClient(
+      GetGlobalContext(),
+      *answerBodyBuffer,
+      *answerHeadersBuffer,
+      &httpStatus,
+      method_,
+      url_.c_str(),
+      headers.GetCount(),
+      headers.GetKeys(),
+      headers.GetValues(),
+      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);
+
+    if (error != OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
+    }
+
+    Json::Value v;
+    answerHeadersBuffer.ToJson(v);
+
+    if (v.type() != Json::objectValue)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    Json::Value::Members members = v.getMemberNames();
+    answerHeaders.clear();
+
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      const Json::Value& h = v[members[i]];
+      if (h.type() != Json::stringValue)
+      {
+        ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+      }
+      else
+      {
+        answerHeaders[members[i]] = h.asString();
+      }
+    }
+
+    answerBodyBuffer.ToString(answerBody);
+  }
+
+
+#if HAS_ORTHANC_PLUGIN_STREAMING_HTTP_CLIENT == 1
+  void HttpClient::Execute(IAnswer& answer)
+  {
+    if (streamingBody_ != NULL)
+    {
+      ExecuteWithStream(httpStatus_, answer, *streamingBody_);
+    }
+    else
+    {
+      MemoryRequestBody wrapper(body_);
+      ExecuteWithStream(httpStatus_, answer, wrapper);
+    }
+  }
+#endif
+
+
+  void HttpClient::Execute(HttpHeaders& answerHeaders /* out */,
+                           std::string& answerBody /* out */)
+  {
+#if HAS_ORTHANC_PLUGIN_STREAMING_HTTP_CLIENT == 1
+    MemoryAnswer answer;
+    Execute(answer);
+    answerHeaders = answer.GetHeaders();
+    answer.GetBody().Flatten(answerBody);
+
+#else
+    // Compatibility mode for Orthanc SDK <= 1.5.6. This results in
+    // higher memory usage (all chunks from the body request are sent
+    // at once)
+
+    if (streamingBody_ != NULL)
+    {
+      ChunkedBuffer buffer;
+      
+      std::string chunk;
+      while (streamingBody_->ReadNextChunk(chunk))
+      {
+        buffer.AddChunk(chunk);
+      }
+
+      std::string body;
+      buffer.Flatten(body);
+
+      ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, body);
+    }
+    else
+    {
+      ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, body_);
+    }
+#endif
+  }
+
 #endif  /* HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 */
 }