changeset 1987:ce90d109bb64

new plugin functions: OrthancPluginHttpClient and OrthancPluginGenerateUuid
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 26 Apr 2016 17:40:55 +0200
parents 99b249867052
children e29aea2349b9
files Core/HttpClient.cpp Core/HttpClient.h Core/Lua/LuaContext.h NEWS OrthancServer/LuaScripting.cpp OrthancServer/OrthancInitialization.cpp OrthancServer/Scheduler/StorePeerCommand.cpp OrthancServer/main.cpp Plugins/Engine/OrthancPlugins.cpp Plugins/Engine/OrthancPlugins.h Plugins/Include/orthanc/OrthancCPlugin.h
diffstat 11 files changed, 322 insertions(+), 68 deletions(-) [+]
line wrap: on
line diff
--- a/Core/HttpClient.cpp	Mon Apr 25 17:23:07 2016 +0200
+++ b/Core/HttpClient.cpp	Tue Apr 26 17:40:55 2016 +0200
@@ -42,10 +42,6 @@
 #include <boost/algorithm/string/predicate.hpp>
 
 
-static std::string globalCACertificates_;
-static bool globalVerifyPeers_ = true;
-static long globalTimeout_ = 0;
-
 extern "C"
 {
   static CURLcode GetHttpStatus(CURLcode code, CURL* curl, long* status)
@@ -77,6 +73,79 @@
 
 namespace Orthanc
 {
+  class HttpClient::GlobalParameters
+  {
+  private:
+    boost::mutex    mutex_;
+    bool            httpsVerifyPeers_;
+    std::string     httpsCACertificates_;
+    std::string     proxy_;
+    long            timeout_;
+
+    GlobalParameters() : 
+      httpsVerifyPeers_(true),
+      timeout_(0)
+    {
+    }
+
+  public:
+    // Singleton pattern
+    static GlobalParameters& GetInstance()
+    {
+      static GlobalParameters parameters;
+      return parameters;
+    }
+
+    void ConfigureSsl(bool httpsVerifyPeers,
+                      const std::string& httpsCACertificates)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      httpsVerifyPeers_ = httpsVerifyPeers;
+      httpsCACertificates_ = httpsCACertificates;
+    }
+
+    void GetSslConfiguration(bool& httpsVerifyPeers,
+                             std::string& httpsCACertificates)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      httpsVerifyPeers = httpsVerifyPeers_;
+      httpsCACertificates = httpsCACertificates_;
+    }
+
+    void SetDefaultProxy(const std::string& proxy)
+    {
+      LOG(INFO) << "Setting the default proxy for HTTP client connections: " << proxy;
+
+      {
+        boost::mutex::scoped_lock lock(mutex_);
+        proxy_ = proxy;
+      }
+    }
+
+    void GetDefaultProxy(std::string& target)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      target = proxy_;
+    }
+
+    void SetDefaultTimeout(long seconds)
+    {
+      LOG(INFO) << "Setting the default timeout for HTTP client connections: " << seconds << " seconds";
+
+      {
+        boost::mutex::scoped_lock lock(mutex_);
+        timeout_ = seconds;
+      }
+    }
+
+    long GetDefaultTimeout()
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      return timeout_;
+    }
+  };
+
+
   struct HttpClient::PImpl
   {
     CURL* curl_;
@@ -93,6 +162,7 @@
         throw OrthancException(ErrorCode_BadRequest);
 
       case HttpStatus_401_Unauthorized:
+      case HttpStatus_403_Forbidden:
         throw OrthancException(ErrorCode_Unauthorized);
 
       case HttpStatus_404_NotFound:
@@ -163,8 +233,9 @@
     method_ = HttpMethod_Get;
     lastStatus_ = HttpStatus_200_Ok;
     isVerbose_ = false;
-    timeout_ = globalTimeout_;
-    verifyPeers_ = globalVerifyPeers_;
+    timeout_ = GlobalParameters::GetInstance().GetDefaultTimeout();
+    GlobalParameters::GetInstance().GetDefaultProxy(proxy_);
+    GlobalParameters::GetInstance().GetSslConfiguration(verifyPeers_, caCertificates_);
   }
 
 
@@ -174,22 +245,6 @@
   }
 
 
-  HttpClient::HttpClient(const HttpClient& other) : pimpl_(new PImpl)
-  {
-    Setup();
-
-    if (other.IsVerbose())
-    {
-      SetVerbose(true);
-    }
-
-    if (other.credentials_.size() != 0)
-    {
-      credentials_ = other.credentials_;
-    }
-  }
-
-
   HttpClient::~HttpClient()
   {
     curl_easy_cleanup(pimpl_->curl_);
@@ -248,9 +303,9 @@
 
     // Setup HTTPS-related options
 #if ORTHANC_SSL_ENABLED == 1
-    if (IsHttpsVerifyPeers())
+    if (verifyPeers_)
     {
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CAINFO, GetHttpsCACertificates().c_str()));
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CAINFO, caCertificates_.c_str()));
       CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYHOST, 2));  // libcurl default is strict verifyhost
       CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 1)); 
     }
@@ -375,7 +430,15 @@
       lastStatus_ = static_cast<HttpStatus>(status);
     }
 
-    return (status >= 200 && status < 300);
+    bool success = (status >= 200 && status < 300);
+
+    if (!success)
+    {
+      LOG(INFO) << "Error in HTTP request, received HTTP status " << status 
+                << " (" << EnumerationToString(lastStatus_) << ")";
+    }
+
+    return success;
   }
 
 
@@ -400,59 +463,54 @@
     credentials_ = std::string(username) + ":" + std::string(password);
   }
 
-  
-  const std::string& HttpClient::GetHttpsCACertificates() const
+
+  void HttpClient::ConfigureSsl(bool httpsVerifyPeers,
+                                const std::string& httpsVerifyCertificates)
   {
-    if (caCertificates_.empty())
-    {
-      return globalCACertificates_;
-    }
-    else
-    {
-      return caCertificates_;
-    }
-  }
-
-
-  void HttpClient::GlobalInitialize(bool httpsVerifyPeers,
-                                    const std::string& httpsVerifyCertificates)
-  {
-    globalVerifyPeers_ = httpsVerifyPeers;
-    globalCACertificates_ = httpsVerifyCertificates;
-
 #if ORTHANC_SSL_ENABLED == 1
     if (httpsVerifyPeers)
     {
-      if (globalCACertificates_.empty())
+      if (httpsVerifyCertificates.empty())
       {
         LOG(WARNING) << "No certificates are provided to validate peers, "
                      << "set \"HttpsCACertificates\" if you need to do HTTPS requests";
       }
       else
       {
-        LOG(WARNING) << "HTTPS will use the CA certificates from this file: " << globalCACertificates_;
+        LOG(WARNING) << "HTTPS will use the CA certificates from this file: " << httpsVerifyCertificates;
       }
     }
     else
     {
-      LOG(WARNING) << "The verification of the peers in HTTPS requests is disabled!";
+      LOG(WARNING) << "The verification of the peers in HTTPS requests is disabled";
     }
 #endif
 
+    GlobalParameters::GetInstance().ConfigureSsl(httpsVerifyPeers, httpsVerifyCertificates);
+  }
+
+  
+  void HttpClient::GlobalInitialize()
+  {
     CheckCode(curl_global_init(CURL_GLOBAL_DEFAULT));
   }
 
-  
+
   void HttpClient::GlobalFinalize()
   {
     curl_global_cleanup();
   }
+  
 
-  
+  void HttpClient::SetDefaultProxy(const std::string& proxy)
+  {
+    GlobalParameters::GetInstance().SetDefaultProxy(proxy);
+  }
+
+
   void HttpClient::SetDefaultTimeout(long timeout)
   {
-    LOG(INFO) << "Setting the default timeout for HTTP client connections: " << timeout << " seconds";
-    globalTimeout_ = timeout;
+    GlobalParameters::GetInstance().SetDefaultTimeout(timeout);
   }
 
 
--- a/Core/HttpClient.h	Mon Apr 25 17:23:07 2016 +0200
+++ b/Core/HttpClient.h	Tue Apr 26 17:40:55 2016 +0200
@@ -43,6 +43,8 @@
   class HttpClient
   {
   private:
+    class GlobalParameters;
+
     struct PImpl;
     boost::shared_ptr<PImpl> pimpl_;
 
@@ -59,11 +61,10 @@
 
     void Setup();
 
-    void operator= (const HttpClient&);  // Forbidden
+    void operator= (const HttpClient&);  // Assignment forbidden
+    HttpClient(const HttpClient& base);  // Copy forbidden
 
   public:
-    HttpClient(const HttpClient& base);
-
     HttpClient();
 
     ~HttpClient();
@@ -162,13 +163,20 @@
       caCertificates_ = certificates;
     }
 
-    const std::string& GetHttpsCACertificates() const;
+    const std::string& GetHttpsCACertificates() const
+    {
+      return caCertificates_;
+    }
 
-    static void GlobalInitialize(bool httpsVerifyPeers,
-                                 const std::string& httpsCACertificates);
+    static void GlobalInitialize();
   
     static void GlobalFinalize();
 
+    static void ConfigureSsl(bool httpsVerifyPeers,
+                             const std::string& httpsCACertificates);
+
+    static void SetDefaultProxy(const std::string& proxy);
+
     static void SetDefaultTimeout(long timeout);
 
     void ApplyAndThrowException(std::string& answer);
--- a/Core/Lua/LuaContext.h	Mon Apr 25 17:23:07 2016 +0200
+++ b/Core/Lua/LuaContext.h	Tue Apr 26 17:40:55 2016 +0200
@@ -104,11 +104,6 @@
       httpClient_.SetCredentials(username, password);
     }
 
-    void SetHttpProxy(const std::string& proxy)
-    {
-      httpClient_.SetProxy(proxy);
-    }
-
     void RegisterFunction(const char* name,
                           lua_CFunction func);
 
--- a/NEWS	Mon Apr 25 17:23:07 2016 +0200
+++ b/NEWS	Tue Apr 26 17:40:55 2016 +0200
@@ -22,6 +22,8 @@
 -------
 
 * New callback to filter incoming HTTP requests: OrthancPluginRegisterIncomingHttpRequestFilter()
+* New function: "OrthancPluginHttpClient()" to do HTTP requests with full control
+* New function: "OrthancPluginGenerateUuid()" to generate a UUID
 
 Lua
 ---
--- a/OrthancServer/LuaScripting.cpp	Mon Apr 25 17:23:07 2016 +0200
+++ b/OrthancServer/LuaScripting.cpp	Tue Apr 26 17:40:55 2016 +0200
@@ -391,7 +391,6 @@
     lua_.RegisterFunction("GetOrthancConfiguration", GetOrthancConfiguration);
 
     lua_.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX);
-    lua_.SetHttpProxy(Configuration::GetGlobalStringParameter("HttpProxy", ""));
   }
 
 
--- a/OrthancServer/OrthancInitialization.cpp	Mon Apr 25 17:23:07 2016 +0200
+++ b/OrthancServer/OrthancInitialization.cpp	Tue Apr 26 17:40:55 2016 +0200
@@ -411,8 +411,7 @@
     ReadGlobalConfiguration(configurationFile);
     ValidateGlobalConfiguration();
 
-    HttpClient::GlobalInitialize(GetGlobalBoolParameterInternal("HttpsVerifyPeers", true),
-                                 GetGlobalStringParameterInternal("HttpsCACertificates", ""));
+    HttpClient::GlobalInitialize();
 
     RegisterUserMetadata();
     RegisterUserContentType();
--- a/OrthancServer/Scheduler/StorePeerCommand.cpp	Mon Apr 25 17:23:07 2016 +0200
+++ b/OrthancServer/Scheduler/StorePeerCommand.cpp	Tue Apr 26 17:40:55 2016 +0200
@@ -52,7 +52,6 @@
   {
     // Configure the HTTP client
     HttpClient client;
-    client.SetProxy(Configuration::GetGlobalStringParameter("HttpProxy", ""));
     if (peer_.GetUsername().size() != 0 && 
         peer_.GetPassword().size() != 0)
     {
--- a/OrthancServer/main.cpp	Mon Apr 25 17:23:07 2016 +0200
+++ b/OrthancServer/main.cpp	Tue Apr 26 17:40:55 2016 +0200
@@ -892,7 +892,10 @@
 {
   ServerContext context(database, storageArea);
 
+  HttpClient::ConfigureSsl(Configuration::GetGlobalBoolParameter("HttpsVerifyPeers", true),
+                           Configuration::GetGlobalStringParameter("HttpsCACertificates", ""));
   HttpClient::SetDefaultTimeout(Configuration::GetGlobalIntegerParameter("HttpTimeout", 0));
+  HttpClient::SetDefaultProxy(Configuration::GetGlobalStringParameter("HttpProxy", ""));
   context.SetCompressionEnabled(Configuration::GetGlobalBoolParameter("StorageCompression", false));
   context.SetStoreMD5ForAttachments(Configuration::GetGlobalBoolParameter("StoreMD5ForAttachments", true));
 
--- a/Plugins/Engine/OrthancPlugins.cpp	Mon Apr 25 17:23:07 2016 +0200
+++ b/Plugins/Engine/OrthancPlugins.cpp	Tue Apr 26 17:40:55 2016 +0200
@@ -1415,6 +1415,76 @@
   }
 
 
+  void OrthancPlugins::CallHttpClient2(const void* parameters)
+  {
+    const _OrthancPluginCallHttpClient2& p = *reinterpret_cast<const _OrthancPluginCallHttpClient2*>(parameters);
+
+    HttpClient client;
+    client.SetUrl(p.url);
+
+    if (p.timeout != 0)
+    {
+      client.SetTimeout(p.timeout);
+    }
+
+    if (p.username != NULL && 
+        p.password != NULL)
+    {
+      client.SetCredentials(p.username, p.password);
+    }
+
+    for (uint32_t i = 0; i < p.headersCount; i++)
+    {
+      if (p.headersKeys[i] == NULL ||
+          p.headersValues[i] == NULL)
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+
+      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:
+        client.SetMethod(HttpMethod_Delete);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    std::string s;
+
+    if (!client.Apply(s))
+    {
+      *p.httpStatus = 0;
+      throw OrthancException(ErrorCode_NetworkProtocol);
+    }
+
+    *p.httpStatus = static_cast<uint16_t>(client.GetLastStatus());
+
+    if (p.method != OrthancPluginHttpMethod_Delete)
+    {
+      CopyToMemoryBuffer(*p.target, s);
+    }
+  }
+
+
   void OrthancPlugins::ConvertPixelFormat(const void* parameters)
   {
     const _OrthancPluginConvertPixelFormat& p = *reinterpret_cast<const _OrthancPluginConvertPixelFormat*>(parameters);
@@ -2115,6 +2185,10 @@
         CallHttpClient(parameters);
         return true;
 
+      case _OrthancPluginService_CallHttpClient2:
+        CallHttpClient2(parameters);
+        return true;
+
       case _OrthancPluginService_ConvertPixelFormat:
         ConvertPixelFormat(parameters);
         return true;
@@ -2255,6 +2329,13 @@
         ApplyLookupDictionary(parameters);
         return true;
 
+      case _OrthancPluginService_GenerateUuid:
+      {
+        *reinterpret_cast<const _OrthancPluginRetrieveDynamicString*>(parameters)->result = 
+          CopyString(Toolbox::GenerateUuid());
+        return true;
+      }
+
       default:
       {
         // This service is unknown to the Orthanc plugin engine
--- a/Plugins/Engine/OrthancPlugins.h	Mon Apr 25 17:23:07 2016 +0200
+++ b/Plugins/Engine/OrthancPlugins.h	Tue Apr 26 17:40:55 2016 +0200
@@ -139,6 +139,8 @@
 
     void CallHttpClient(const void* parameters);
 
+    void CallHttpClient2(const void* parameters);
+
     void GetFontInfo(const void* parameters);
 
     void DrawText(const void* parameters);
--- a/Plugins/Include/orthanc/OrthancCPlugin.h	Mon Apr 25 17:23:07 2016 +0200
+++ b/Plugins/Include/orthanc/OrthancCPlugin.h	Tue Apr 26 17:40:55 2016 +0200
@@ -403,6 +403,8 @@
     _OrthancPluginService_ComputeMd5 = 24,
     _OrthancPluginService_ComputeSha1 = 25,
     _OrthancPluginService_LookupDictionary = 26,
+    _OrthancPluginService_CallHttpClient2 = 27,
+    _OrthancPluginService_GenerateUuid = 28,
 
     /* Registration of callbacks */
     _OrthancPluginService_RegisterRestCallback = 1000,
@@ -4148,8 +4150,8 @@
    * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
    * @param uri The URI in the built-in Orthanc API.
    * @param headersCount The number of HTTP headers.
-   * @param headersKeys Array containing the keys of the HTTP headers.
-   * @param headersValues Array containing the values of the 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 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.
@@ -4789,6 +4791,112 @@
   }
   
 
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    uint16_t*                   httpStatus;
+    OrthancPluginHttpMethod     method;
+    const char*                 url;
+    uint32_t                    headersCount;
+    const char* const*          headersKeys;
+    const char* const*          headersValues;
+    const char*                 body;
+    uint32_t                    bodySize;
+    const char*                 username;
+    const char*                 password;
+    uint32_t                    timeout;
+  } _OrthancPluginCallHttpClient2;
+
+
+
+  /**
+   * @brief Issue a HTTP call with full flexibility.
+   * 
+   * Make a HTTP call to the given URL. The result to the query is
+   * stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer() (out argument).
+   * @param httpStatus The HTTP status after the execution of the request (out argument).
+   * @param method HTTP method to be used.
+   * @param url The URL 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 username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @param body The body of the POST request.
+   * @param bodySize The size of the body.
+   * @param timeout Timeout in seconds (0 for default timeout).
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpClient(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    uint16_t*                   httpStatus,
+    OrthancPluginHttpMethod     method,
+    const char*                 url,
+    uint32_t                    headersCount,
+    const char* const*          headersKeys,
+    const char* const*          headersValues,
+    const char*                 body,
+    uint32_t                    bodySize,
+    const char*                 username,
+    const char*                 password,
+    uint32_t                    timeout)
+  {
+    _OrthancPluginCallHttpClient2 params;
+    memset(&params, 0, sizeof(params));
+
+    params.target = target;
+    params.httpStatus = httpStatus;
+    params.method = method;
+    params.url = url;
+    params.headersCount = headersCount;
+    params.headersKeys = headersKeys;
+    params.headersValues = headersValues;
+    params.body = body;
+    params.bodySize = bodySize;
+    params.username = username;
+    params.password = password;
+    params.timeout = timeout;
+
+    return context->InvokeService(context, _OrthancPluginService_CallHttpClient2, &params);
+  }
+
+
+  /**
+   * @brief Generate an UUID.
+   *
+   * Generate a random GUID/UUID (globally unique identifier).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the UUID. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGenerateUuid(
+    OrthancPluginContext*  context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GenerateUuid, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
 #ifdef  __cplusplus
 }
 #endif