changeset 4605:c8f444e8556d

new function in the plugin SDK: OrthancPluginCallRestApi()
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 30 Mar 2021 16:34:02 +0200
parents b7fe3494a53c
children d01702fb29a9 8661811abca3
files NEWS OrthancFramework/Sources/HttpServer/HttpToolbox.cpp OrthancFramework/Sources/HttpServer/IHttpHandler.cpp OrthancFramework/Sources/HttpServer/IHttpHandler.h OrthancFramework/Sources/HttpServer/StringHttpOutput.cpp OrthancFramework/Sources/HttpServer/StringHttpOutput.h OrthancServer/Plugins/Engine/OrthancPlugins.cpp OrthancServer/Plugins/Engine/OrthancPlugins.h OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h OrthancServer/Sources/LuaScripting.cpp
diffstat 10 files changed, 476 insertions(+), 136 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Wed Mar 17 15:48:31 2021 +0100
+++ b/NEWS	Tue Mar 30 16:34:02 2021 +0200
@@ -2,6 +2,13 @@
 ===============================
 
 
+Plugins
+-------
+
+* New functions in the SDK:
+  - OrthancPluginCallRestApi()
+
+
 Version 1.9.1 (2021-02-25)
 ==========================
 
--- a/OrthancFramework/Sources/HttpServer/HttpToolbox.cpp	Wed Mar 17 15:48:31 2021 +0100
+++ b/OrthancFramework/Sources/HttpServer/HttpToolbox.cpp	Tue Mar 30 16:34:02 2021 +0200
@@ -191,7 +191,7 @@
                               const std::string& uri,
                               const Arguments& httpHeaders)
   {
-    return IHttpHandler::SimpleGet(result, handler, origin, uri, httpHeaders);
+    return (IHttpHandler::SimpleGet(result, NULL, handler, origin, uri, httpHeaders) == HttpStatus_200_Ok);
   }
 
   bool HttpToolbox::SimplePost(std::string& result,
@@ -202,7 +202,8 @@
                                size_t bodySize,
                                const Arguments& httpHeaders)
   {
-    return IHttpHandler::SimplePost(result, handler, origin, uri, bodyData, bodySize, httpHeaders);
+    return (IHttpHandler::SimplePost(result, NULL, handler, origin, uri,
+                                     bodyData, bodySize, httpHeaders) == HttpStatus_200_Ok);
   }
 
   bool HttpToolbox::SimplePut(std::string& result,
@@ -213,7 +214,8 @@
                               size_t bodySize,
                               const Arguments& httpHeaders)
   {
-    return IHttpHandler::SimplePut(result, handler, origin, uri, bodyData, bodySize, httpHeaders);
+    return (IHttpHandler::SimplePut(result, NULL, handler, origin, uri,
+                                    bodyData, bodySize, httpHeaders) == HttpStatus_200_Ok);
   }
 
   bool HttpToolbox::SimpleDelete(IHttpHandler& handler,
@@ -221,7 +223,7 @@
                                  const std::string& uri,
                                  const Arguments& httpHeaders)
   {
-    return IHttpHandler::SimpleDelete(handler, origin, uri, httpHeaders);
+    return (IHttpHandler::SimpleDelete(NULL, handler, origin, uri, httpHeaders) == HttpStatus_200_Ok);
   }
 #endif
 }
--- a/OrthancFramework/Sources/HttpServer/IHttpHandler.cpp	Wed Mar 17 15:48:31 2021 +0100
+++ b/OrthancFramework/Sources/HttpServer/IHttpHandler.cpp	Tue Mar 30 16:34:02 2021 +0200
@@ -32,11 +32,12 @@
 
 namespace Orthanc
 {
-  bool IHttpHandler::SimpleGet(std::string& result,
-                               IHttpHandler& handler,
-                               RequestOrigin origin,
-                               const std::string& uri,
-                               const HttpToolbox::Arguments& httpHeaders)
+  HttpStatus IHttpHandler::SimpleGet(std::string& answerBody,
+                                     HttpToolbox::Arguments* answerHeaders,
+                                     IHttpHandler& handler,
+                                     RequestOrigin origin,
+                                     const std::string& uri,
+                                     const HttpToolbox::Arguments& httpHeaders)
   {
     UriComponents curi;
     HttpToolbox::GetArguments getArguments;
@@ -48,24 +49,31 @@
     if (handler.Handle(http, origin, LOCALHOST, "", HttpMethod_Get, curi, 
                        httpHeaders, getArguments, NULL /* no body for GET */, 0))
     {
-      stream.GetOutput(result);
-      return true;
+      stream.GetBody(answerBody);
+
+      if (answerHeaders != NULL)
+      {
+        stream.GetHeaders(*answerHeaders, true /* convert key to lower case */);
+      }
+      
+      return stream.GetStatus();
     }
     else
     {
-      return false;
+      return HttpStatus_404_NotFound;
     }
   }
 
 
-  static bool SimplePostOrPut(std::string& result,
-                              IHttpHandler& handler,
-                              RequestOrigin origin,
-                              HttpMethod method,
-                              const std::string& uri,
-                              const void* bodyData,
-                              size_t bodySize,
-                              const HttpToolbox::Arguments& httpHeaders)
+  static HttpStatus SimplePostOrPut(std::string& answerBody,
+                                    HttpToolbox::Arguments* answerHeaders,
+                                    IHttpHandler& handler,
+                                    RequestOrigin origin,
+                                    HttpMethod method,
+                                    const std::string& uri,
+                                    const void* bodyData,
+                                    size_t bodySize,
+                                    const HttpToolbox::Arguments& httpHeaders)
   {
     HttpToolbox::GetArguments getArguments;  // No GET argument for POST/PUT
 
@@ -78,44 +86,53 @@
     if (handler.Handle(http, origin, LOCALHOST, "", method, curi, 
                        httpHeaders, getArguments, bodyData, bodySize))
     {
-      stream.GetOutput(result);
-      return true;
+      stream.GetBody(answerBody);
+
+      if (answerHeaders != NULL)
+      {
+        stream.GetHeaders(*answerHeaders, true /* convert key to lower case */);
+      }
+      
+      return stream.GetStatus();
     }
     else
     {
-      return false;
+      return HttpStatus_404_NotFound;
     }
   }
 
 
-  bool IHttpHandler::SimplePost(std::string& result,
-                                IHttpHandler& handler,
-                                RequestOrigin origin,
-                                const std::string& uri,
-                                const void* bodyData,
-                                size_t bodySize,
-                                const HttpToolbox::Arguments& httpHeaders)
+  HttpStatus IHttpHandler::SimplePost(std::string& answerBody,
+                                      HttpToolbox::Arguments* answerHeaders,
+                                      IHttpHandler& handler,
+                                      RequestOrigin origin,
+                                      const std::string& uri,
+                                      const void* bodyData,
+                                      size_t bodySize,
+                                      const HttpToolbox::Arguments& httpHeaders)
   {
-    return SimplePostOrPut(result, handler, origin, HttpMethod_Post, uri, bodyData, bodySize, httpHeaders);
+    return SimplePostOrPut(answerBody, answerHeaders, handler, origin, HttpMethod_Post, uri, bodyData, bodySize, httpHeaders);
   }
 
 
-  bool IHttpHandler::SimplePut(std::string& result,
-                               IHttpHandler& handler,
-                               RequestOrigin origin,
-                               const std::string& uri,
-                               const void* bodyData,
-                               size_t bodySize,
-                               const HttpToolbox::Arguments& httpHeaders)
+  HttpStatus IHttpHandler::SimplePut(std::string& answerBody,
+                                     HttpToolbox::Arguments* answerHeaders,
+                                     IHttpHandler& handler,
+                                     RequestOrigin origin,
+                                     const std::string& uri,
+                                     const void* bodyData,
+                                     size_t bodySize,
+                                     const HttpToolbox::Arguments& httpHeaders)
   {
-    return SimplePostOrPut(result, handler, origin, HttpMethod_Put, uri, bodyData, bodySize, httpHeaders);
+    return SimplePostOrPut(answerBody, answerHeaders, handler, origin, HttpMethod_Put, uri, bodyData, bodySize, httpHeaders);
   }
 
 
-  bool IHttpHandler::SimpleDelete(IHttpHandler& handler,
-                                  RequestOrigin origin,
-                                  const std::string& uri,
-                                  const HttpToolbox::Arguments& httpHeaders)
+  HttpStatus IHttpHandler::SimpleDelete(HttpToolbox::Arguments* answerHeaders,
+                                        IHttpHandler& handler,
+                                        RequestOrigin origin,
+                                        const std::string& uri,
+                                        const HttpToolbox::Arguments& httpHeaders)
   {
     UriComponents curi;
     Toolbox::SplitUriComponents(curi, uri);
@@ -125,7 +142,19 @@
     StringHttpOutput stream;
     HttpOutput http(stream, false /* no keep alive */);
 
-    return handler.Handle(http, origin, LOCALHOST, "", HttpMethod_Delete, curi, 
-                          httpHeaders, getArguments, NULL /* no body for DELETE */, 0);
+    if (handler.Handle(http, origin, LOCALHOST, "", HttpMethod_Delete, curi, 
+                       httpHeaders, getArguments, NULL /* no body for DELETE */, 0))
+    {
+      if (answerHeaders != NULL)
+      {
+        stream.GetHeaders(*answerHeaders, true /* convert key to lower case */);
+      }
+      
+      return stream.GetStatus();
+    }
+    else
+    {
+      return HttpStatus_404_NotFound;
+    }
   }
 }
--- a/OrthancFramework/Sources/HttpServer/IHttpHandler.h	Wed Mar 17 15:48:31 2021 +0100
+++ b/OrthancFramework/Sources/HttpServer/IHttpHandler.h	Tue Mar 30 16:34:02 2021 +0200
@@ -83,31 +83,41 @@
                         const void* bodyData,
                         size_t bodySize) = 0;
 
-    static bool SimpleGet(std::string& result,
-                          IHttpHandler& handler,
-                          RequestOrigin origin,
-                          const std::string& uri,
-                          const HttpToolbox::Arguments& httpHeaders);
+
+    /**
+     * In the static functions below, "answerHeaders" can be set to
+     * NULL if the caller has no interest in HTTP headers of the
+     * answer (this avoids some computation).
+     **/
+    static HttpStatus SimpleGet(std::string& answerBody /* out */,
+                                HttpToolbox::Arguments* answerHeaders /* out */,
+                                IHttpHandler& handler,
+                                RequestOrigin origin,
+                                const std::string& uri,
+                                const HttpToolbox::Arguments& httpHeaders);
 
-    static bool SimplePost(std::string& result,
-                           IHttpHandler& handler,
-                           RequestOrigin origin,
-                           const std::string& uri,
-                           const void* bodyData,
-                           size_t bodySize,
-                           const HttpToolbox::Arguments& httpHeaders);
+    static HttpStatus SimplePost(std::string& answerBody /* out */,
+                                 HttpToolbox::Arguments* answerHeaders /* out */,
+                                 IHttpHandler& handler,
+                                 RequestOrigin origin,
+                                 const std::string& uri,
+                                 const void* bodyData,
+                                 size_t bodySize,
+                                 const HttpToolbox::Arguments& httpHeaders);
 
-    static bool SimplePut(std::string& result,
-                          IHttpHandler& handler,
-                          RequestOrigin origin,
-                          const std::string& uri,
-                          const void* bodyData,
-                          size_t bodySize,
-                          const HttpToolbox::Arguments& httpHeaders);
+    static HttpStatus SimplePut(std::string& answerBody /* out */,
+                                HttpToolbox::Arguments* answerHeaders /* out */,
+                                IHttpHandler& handler,
+                                RequestOrigin origin,
+                                const std::string& uri,
+                                const void* bodyData,
+                                size_t bodySize,
+                                const HttpToolbox::Arguments& httpHeaders);
 
-    static bool SimpleDelete(IHttpHandler& handler,
-                             RequestOrigin origin,
-                             const std::string& uri,
-                             const HttpToolbox::Arguments& httpHeaders);
+    static HttpStatus SimpleDelete(HttpToolbox::Arguments* answerHeaders /* out */,
+                                   IHttpHandler& handler,
+                                   RequestOrigin origin,
+                                   const std::string& uri,
+                                   const HttpToolbox::Arguments& httpHeaders);
   };
 }
--- a/OrthancFramework/Sources/HttpServer/StringHttpOutput.cpp	Wed Mar 17 15:48:31 2021 +0100
+++ b/OrthancFramework/Sources/HttpServer/StringHttpOutput.cpp	Tue Mar 30 16:34:02 2021 +0200
@@ -24,43 +24,97 @@
 #include "StringHttpOutput.h"
 
 #include "../OrthancException.h"
+#include "../Toolbox.h"
 
 namespace Orthanc
 {
-  void StringHttpOutput::OnHttpStatusReceived(HttpStatus status)
+  StringHttpOutput::StringHttpOutput() :
+    status_(HttpStatus_404_NotFound),
+    validBody_(true),
+    validHeaders_(true)
   {
-    switch (status)
+  }
+
+
+  void StringHttpOutput::Send(bool isHeader, const void* buffer, size_t length)
+  {
+    if (isHeader)
     {
-      case HttpStatus_200_Ok:
-        found_ = true;
-        break;
-
-      case HttpStatus_404_NotFound:
-        found_ = false;
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_BadRequest);
+      if (validHeaders_)
+      {
+        headers_.AddChunk(buffer, length);
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_BadSequenceOfCalls);
+      }
+    }
+    else
+    {
+      if (validBody_)
+      {
+        body_.AddChunk(buffer, length);
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_BadSequenceOfCalls);
+      }
     }
   }
 
-  void StringHttpOutput::Send(bool isHeader, const void* buffer, size_t length)
+  
+  void StringHttpOutput::GetBody(std::string& output)
   {
-    if (!isHeader)
+    if (!validBody_)
     {
-      buffer_.AddChunk(buffer, length);
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
     }
-  }
-
-  void StringHttpOutput::GetOutput(std::string& output)
-  {
-    if (found_)
+    else if (status_ == HttpStatus_200_Ok)
     {
-      buffer_.Flatten(output);
+      body_.Flatten(output);
+      validBody_ = false;
     }
     else
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
   }
+
+
+  void StringHttpOutput::GetHeaders(std::map<std::string, std::string>& target,
+                                    bool keyToLowerCase)
+  {
+    if (!validHeaders_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      std::string s;
+      headers_.Flatten(s);
+      validHeaders_ = false;
+
+      std::vector<std::string> lines;
+      Orthanc::Toolbox::TokenizeString(lines, s, '\n');
+
+      target.clear();
+
+      for (size_t i = 1 /* skip the HTTP status line */; i < lines.size(); i++)
+      {
+        size_t colon = lines[i].find(':');
+        if (colon != std::string::npos)
+        {
+          std::string key = lines[i].substr(0, colon);
+
+          if (keyToLowerCase)
+          {
+            Toolbox::ToLowerCase(key);
+          }
+          
+          const std::string value = lines[i].substr(colon + 1);
+          target[Toolbox::StripSpaces(key)] = Toolbox::StripSpaces(value);
+        }
+      }
+    }
+  }
 }
--- a/OrthancFramework/Sources/HttpServer/StringHttpOutput.h	Wed Mar 17 15:48:31 2021 +0100
+++ b/OrthancFramework/Sources/HttpServer/StringHttpOutput.h	Tue Mar 30 16:34:02 2021 +0200
@@ -27,27 +27,42 @@
 #include "../ChunkedBuffer.h"
 #include "../Compatibility.h"  // For ORTHANC_OVERRIDE
 
+#include <map>
+
+
 namespace Orthanc
 {
   class StringHttpOutput : public IHttpOutputStream
   {
   private:
-    bool          found_;
-    ChunkedBuffer buffer_;
+    HttpStatus    status_;
+    ChunkedBuffer body_;
+    ChunkedBuffer headers_;
+    bool          validBody_;
+    bool          validHeaders_;
 
   public:
-    StringHttpOutput() : found_(false)
+    StringHttpOutput();
+
+    virtual void OnHttpStatusReceived(HttpStatus status) ORTHANC_OVERRIDE
     {
+      status_ = status;
     }
 
-    virtual void OnHttpStatusReceived(HttpStatus status) ORTHANC_OVERRIDE;
-
     virtual void Send(bool isHeader, const void* buffer, size_t length) ORTHANC_OVERRIDE;
 
     virtual void DisableKeepAlive() ORTHANC_OVERRIDE
     {
     }
 
-    void GetOutput(std::string& output);
+    HttpStatus GetStatus() const
+    {
+      return status_;
+    }
+
+    void GetBody(std::string& output);
+
+    void GetHeaders(std::map<std::string, std::string>& target,
+                    bool keyToLowerCase);
   };
 }
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Wed Mar 17 15:48:31 2021 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Tue Mar 30 16:34:02 2021 +0200
@@ -153,6 +153,22 @@
   }
 
 
+  static void CopyDictionary(OrthancPluginMemoryBuffer& target,
+                             const std::map<std::string, std::string>& dictionary)
+  {
+    Json::Value json = Json::objectValue;
+
+    for (HttpClient::HttpHeaders::const_iterator 
+           it = dictionary.begin(); it != dictionary.end(); ++it)
+    {
+      json[it->first] = it->second;
+    }
+        
+    std::string s = json.toStyledString();
+    CopyToMemoryBuffer(target, s);
+  }
+
+
   namespace
   {
     class MemoryBufferRaii : public boost::noncopyable
@@ -2674,7 +2690,7 @@
     std::map<std::string, std::string> httpHeaders;
 
     std::string result;
-    if (IHttpHandler::SimpleGet(result, *handler, RequestOrigin_Plugins, p.uri, httpHeaders))
+    if (IHttpHandler::SimpleGet(result, NULL, *handler, RequestOrigin_Plugins, p.uri, httpHeaders) == HttpStatus_200_Ok)
     {
       CopyToMemoryBuffer(*p.target, result);
     }
@@ -2710,7 +2726,7 @@
     }
       
     std::string result;
-    if (IHttpHandler::SimpleGet(result, *handler, RequestOrigin_Plugins, p.uri, headers))
+    if (IHttpHandler::SimpleGet(result, NULL, *handler, RequestOrigin_Plugins, p.uri, headers) == HttpStatus_200_Ok)
     {
       CopyToMemoryBuffer(*p.target, result);
     }
@@ -2742,8 +2758,10 @@
 
     std::string result;
     if (isPost ? 
-        IHttpHandler::SimplePost(result, *handler, RequestOrigin_Plugins, p.uri, p.body, p.bodySize, httpHeaders) :
-        IHttpHandler::SimplePut (result, *handler, RequestOrigin_Plugins, p.uri, p.body, p.bodySize, httpHeaders))
+        IHttpHandler::SimplePost(result, NULL, *handler, RequestOrigin_Plugins, p.uri,
+                                 p.body, p.bodySize, httpHeaders) == HttpStatus_200_Ok :
+        IHttpHandler::SimplePut(result, NULL, *handler, RequestOrigin_Plugins, p.uri,
+                                p.body, p.bodySize, httpHeaders) == HttpStatus_200_Ok)
     {
       CopyToMemoryBuffer(*p.target, result);
     }
@@ -2770,7 +2788,7 @@
       
     std::map<std::string, std::string> httpHeaders;
 
-    if (!IHttpHandler::SimpleDelete(*handler, RequestOrigin_Plugins, uri, httpHeaders))
+    if (IHttpHandler::SimpleDelete(NULL, *handler, RequestOrigin_Plugins, uri, httpHeaders) != HttpStatus_200_Ok)
     {
       throw OrthancException(ErrorCode_UnknownResource);
     }
@@ -3329,11 +3347,6 @@
                                                   OrthancPluginMemoryBuffer* answerHeaders,
                                                   HttpClient& client)
   {
-    if (answerBody == NULL)
-    {
-      throw OrthancException(ErrorCode_NullPointer);
-    }
-
     std::string body;
     HttpClient::HttpHeaders headers;
 
@@ -3350,22 +3363,27 @@
     // Copy the HTTP headers of the answer, if the plugin requested them
     if (answerHeaders != NULL)
     {
-      Json::Value json = Json::objectValue;
-
-      for (HttpClient::HttpHeaders::const_iterator 
-             it = headers.begin(); it != headers.end(); ++it)
-      {
-        json[it->first] = it->second;
-      }
-        
-      std::string s = json.toStyledString();
-      CopyToMemoryBuffer(*answerHeaders, s);
+      CopyDictionary(*answerHeaders, headers);
     }
 
     // Copy the body of the answer if it makes sense
     if (client.GetMethod() != HttpMethod_Delete)
     {
-      CopyToMemoryBuffer(*answerBody, body);
+      try
+      {
+        if (answerBody != NULL)
+        {
+          CopyToMemoryBuffer(*answerBody, body);
+        }
+      }
+      catch (OrthancException&)
+      {
+        if (answerHeaders != NULL)
+        {
+          free(answerHeaders->data);
+        }
+        throw;
+      }
     }
   }
 
@@ -3481,6 +3499,112 @@
   }
 
 
+  void OrthancPlugins::CallRestApi(const void* parameters)
+  {
+    const _OrthancPluginCallRestApi& p = *reinterpret_cast<const _OrthancPluginCallRestApi*>(parameters);
+    
+    if (p.httpStatus == NULL)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+
+    const char* methodString;
+    switch (p.method)
+    {
+      case OrthancPluginHttpMethod_Get:
+        methodString = "GET";
+        break;
+
+      case OrthancPluginHttpMethod_Post:
+        methodString = "POST";
+        break;
+
+      case OrthancPluginHttpMethod_Put:
+        methodString = "PUT";
+        break;
+
+      case OrthancPluginHttpMethod_Delete:
+        methodString = "DELETE";
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    CLOG(INFO, PLUGINS) << "Plugin making REST " << methodString << " call to URI " << p.uri
+                        << (p.afterPlugins ? " (after plugins)" : " (built-in API)");
+
+    HttpToolbox::Arguments headers;
+
+    for (uint32_t i = 0; i < p.headersCount; i++)
+    {
+      std::string name(p.headersKeys[i]);
+      std::transform(name.begin(), name.end(), name.begin(), ::tolower);
+      headers[name] = p.headersValues[i];
+    }
+
+    IHttpHandler* handler;
+
+    {
+      PImpl::ServerContextLock lock(*pimpl_);
+      handler = &lock.GetContext().GetHttpHandler().RestrictToOrthancRestApi(!p.afterPlugins);
+    }
+    
+    std::string answerBody;
+    std::map<std::string, std::string> answerHeaders;
+    HttpStatus status;
+
+    switch (p.method)
+    {
+      case OrthancPluginHttpMethod_Get:
+        status = IHttpHandler::SimpleGet(
+          answerBody, &answerHeaders, *handler, RequestOrigin_Plugins, p.uri, headers);
+        break;
+
+      case OrthancPluginHttpMethod_Post:
+        status = IHttpHandler::SimplePost(
+          answerBody, &answerHeaders, *handler, RequestOrigin_Plugins, p.uri, p.body, p.bodySize, headers);
+        break;
+
+      case OrthancPluginHttpMethod_Put:
+        status = IHttpHandler::SimplePut(
+          answerBody, &answerHeaders, *handler, RequestOrigin_Plugins, p.uri, p.body, p.bodySize, headers);
+        break;
+
+      case OrthancPluginHttpMethod_Delete:
+        status = IHttpHandler::SimpleDelete(
+          &answerHeaders, *handler, RequestOrigin_Plugins, p.uri, headers);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    *p.httpStatus = static_cast<uint16_t>(status);
+
+    if (p.answerHeaders != NULL)
+    {
+      CopyDictionary(*p.answerHeaders, answerHeaders);
+    }
+
+    try
+    {
+      if (p.answerBody != NULL)
+      {
+        CopyToMemoryBuffer(*p.answerBody, answerBody);
+      }
+    }
+    catch (OrthancException&)
+    {
+      if (p.answerHeaders != NULL)
+      {
+        free(p.answerHeaders->data);
+      }
+      throw;
+    }
+  }
+
+
   void OrthancPlugins::CallPeerApi(const void* parameters)
   {
     const _OrthancPluginCallPeerApi& p = *reinterpret_cast<const _OrthancPluginCallPeerApi*>(parameters);
@@ -3545,22 +3669,27 @@
     // Copy the HTTP headers of the answer, if the plugin requested them
     if (p.answerHeaders != NULL)
     {
-      Json::Value json = Json::objectValue;
-
-      for (HttpClient::HttpHeaders::const_iterator 
-             it = headers.begin(); it != headers.end(); ++it)
-      {
-        json[it->first] = it->second;
-      }
-        
-      std::string s = json.toStyledString();
-      CopyToMemoryBuffer(*p.answerHeaders, s);
+      CopyDictionary(*p.answerHeaders, headers);
     }
 
     // Copy the body of the answer if it makes sense
     if (p.method != OrthancPluginHttpMethod_Delete)
     {
-      CopyToMemoryBuffer(*p.answerBody, body);
+      try
+      {
+        if (p.answerBody != NULL)
+        {
+          CopyToMemoryBuffer(*p.answerBody, body);
+        }
+      }
+      catch (OrthancException&)
+      {
+        if (p.answerHeaders != NULL)
+        {
+          free(p.answerHeaders->data);
+        }
+        throw;
+      }
     }
   }
 
@@ -4218,6 +4347,10 @@
         ChunkedHttpClient(parameters);
         return true;
 
+      case _OrthancPluginService_CallRestApi:
+        CallRestApi(parameters);
+        return true;
+
       case _OrthancPluginService_ConvertPixelFormat:
         ConvertPixelFormat(parameters);
         return true;
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.h	Wed Mar 17 15:48:31 2021 +0100
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.h	Tue Mar 30 16:34:02 2021 +0200
@@ -196,6 +196,8 @@
 
     void ChunkedHttpClient(const void* parameters);
 
+    void CallRestApi(const void* parameters);
+
     void CallPeerApi(const void* parameters);
   
     void GetFontInfo(const void* parameters);
--- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h	Wed Mar 17 15:48:31 2021 +0100
+++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h	Tue Mar 30 16:34:02 2021 +0200
@@ -117,7 +117,7 @@
 
 #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     1
 #define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     9
-#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  0
+#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  2
 
 
 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
@@ -493,6 +493,7 @@
     _OrthancPluginService_RestApiPutAfterPlugins = 3013,
     _OrthancPluginService_ReconstructMainDicomTags = 3014,
     _OrthancPluginService_RestApiGet2 = 3015,
+    _OrthancPluginService_CallRestApi = 3016,              /* New in Orthanc 1.9.2 */
 
     /* Access to DICOM instances */
     _OrthancPluginService_GetInstanceRemoteAet = 4000,
@@ -5756,6 +5757,7 @@
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
    * @param answerBody The target memory buffer (out argument).
    *        It must be freed with OrthancPluginFreeMemoryBuffer().
+   *        The value of this argument is ignored if the HTTP method is DELETE.
    * @param answerHeaders The target memory buffer for the HTTP headers in the answers (out argument). 
    *        The answer headers are formatted as a JSON object (associative array).
    *        The buffer must be freed with OrthancPluginFreeMemoryBuffer().
@@ -6560,6 +6562,7 @@
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
    * @param answerBody The target memory buffer (out argument).
    *        It must be freed with OrthancPluginFreeMemoryBuffer().
+   *        The value of this argument is ignored if the HTTP method is DELETE.
    * @param answerHeaders The target memory buffer for the HTTP headers in the answers (out argument). 
    *        The answer headers are formatted as a JSON object (associative array).
    *        The buffer must be freed with OrthancPluginFreeMemoryBuffer().
@@ -8483,6 +8486,89 @@
     return context->InvokeService(context, _OrthancPluginService_CreateDicom2, &params);
   }
 
+
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  answerBody;
+    OrthancPluginMemoryBuffer*  answerHeaders;
+    uint16_t*                   httpStatus;
+    OrthancPluginHttpMethod     method;
+    const char*                 uri;
+    uint32_t                    headersCount;
+    const char* const*          headersKeys;
+    const char* const*          headersValues;
+    const void*                 body;
+    uint32_t                    bodySize;
+    uint8_t                     afterPlugins;
+  } _OrthancPluginCallRestApi;
+
+  /**
+   * @brief Call the REST API of Orthanc with full flexibility.
+   * 
+   * Make a call to the given URI in the REST API of Orthanc. The
+   * result to the query is stored into a newly allocated memory
+   * buffer. This function is always granted full access to the REST
+   * API (no credentials, nor security token is needed).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answerBody The target memory buffer (out argument).
+   *        It must be freed with OrthancPluginFreeMemoryBuffer().
+   *        The value of this argument is ignored if the HTTP method is DELETE.
+   * @param answerHeaders The target memory buffer for the HTTP headers in the answer (out argument). 
+   *        The answer headers are formatted as a JSON object (associative array).
+   *        The buffer must be freed with OrthancPluginFreeMemoryBuffer().
+   *        This argument can be set to NULL if the plugin has no interest in the answer HTTP headers.
+   * @param httpStatus The HTTP status after the execution of the request (out argument).
+   * @param method HTTP method to be used.
+   * @param uri The URI of interest.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys Array containing the keys of the HTTP headers (can be <tt>NULL</tt> if no header).
+   * @param headersValues Array containing the values of the HTTP headers (can be <tt>NULL</tt> if no header).
+   * @param body The HTTP body for a POST or PUT request.
+   * @param bodySize The size of the body.
+   * @param afterPlugins If 0, the built-in API of Orthanc is used.
+   * If 1, the API is tainted by the plugins.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiGet2, OrthancPluginRestApiPost, OrthancPluginRestApiPut, OrthancPluginRestApiDelete
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginCallRestApi(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  answerBody,
+    OrthancPluginMemoryBuffer*  answerHeaders,
+    uint16_t*                   httpStatus,
+    OrthancPluginHttpMethod     method,
+    const char*                 uri,
+    uint32_t                    headersCount,
+    const char* const*          headersKeys,
+    const char* const*          headersValues,
+    const void*                 body,
+    uint32_t                    bodySize,
+    uint8_t                     afterPlugins)
+  {
+    _OrthancPluginCallRestApi params;
+    memset(&params, 0, sizeof(params));
+
+    params.answerBody = answerBody;
+    params.answerHeaders = answerHeaders;
+    params.httpStatus = httpStatus;
+    params.method = method;
+    params.uri = uri;
+    params.headersCount = headersCount;
+    params.headersKeys = headersKeys;
+    params.headersValues = headersValues;
+    params.body = body;
+    params.bodySize = bodySize;
+    params.afterPlugins = afterPlugins;
+
+    return context->InvokeService(context, _OrthancPluginService_CallRestApi, &params);
+  }
+
+
 #ifdef  __cplusplus
 }
 #endif
--- a/OrthancServer/Sources/LuaScripting.cpp	Wed Mar 17 15:48:31 2021 +0100
+++ b/OrthancServer/Sources/LuaScripting.cpp	Tue Mar 30 16:34:02 2021 +0200
@@ -405,8 +405,8 @@
     try
     {
       std::string result;
-      if (IHttpHandler::SimpleGet(result, serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), 
-                                  RequestOrigin_Lua, uri, headers))
+      if (IHttpHandler::SimpleGet(result, NULL, serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), 
+                                  RequestOrigin_Lua, uri, headers) == HttpStatus_200_Ok)
       {
         lua_pushlstring(state, result.c_str(), result.size());
         return 1;
@@ -458,10 +458,12 @@
     {
       std::string result;
       if (isPost ?
-          IHttpHandler::SimplePost(result, serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), 
-                                   RequestOrigin_Lua, uri, bodyData, bodySize, headers) :
-          IHttpHandler::SimplePut(result, serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), 
-                                  RequestOrigin_Lua, uri, bodyData, bodySize, headers))
+          IHttpHandler::SimplePost(result, NULL,
+                                   serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), 
+                                   RequestOrigin_Lua, uri, bodyData, bodySize, headers) == HttpStatus_200_Ok :
+          IHttpHandler::SimplePut(result, NULL,
+                                  serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), 
+                                  RequestOrigin_Lua, uri, bodyData, bodySize, headers) == HttpStatus_200_Ok)
       {
         lua_pushlstring(state, result.c_str(), result.size());
         return 1;
@@ -522,8 +524,8 @@
     
     try
     {
-      if (IHttpHandler::SimpleDelete(serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), 
-                                     RequestOrigin_Lua, uri, headers))
+      if (IHttpHandler::SimpleDelete(NULL, serverContext->GetHttpHandler().RestrictToOrthancRestApi(builtin), 
+                                     RequestOrigin_Lua, uri, headers) == HttpStatus_200_Ok)
       {
         lua_pushboolean(state, 1);
         return 1;