changeset 434:3a8bcc45c221 am-vsol-upgrade

moved the HTTP cache from OrthancApiClient to BaseWebService (not implemented yet in WASM)
author am@osimis.io
date Mon, 03 Dec 2018 18:03:30 +0100
parents 8999823db8b8
children e641d3978856
files Framework/Toolbox/BaseWebService.cpp Framework/Toolbox/BaseWebService.h Framework/Toolbox/IWebService.h Framework/Toolbox/OrthancApiClient.cpp Framework/Toolbox/OrthancApiClient.h Platforms/Generic/OracleWebService.cpp Platforms/Generic/OracleWebService.h Platforms/Wasm/WasmWebService.cpp Platforms/Wasm/WasmWebService.h Resources/CMake/OrthancStoneConfiguration.cmake
diffstat 10 files changed, 450 insertions(+), 197 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/BaseWebService.cpp	Mon Dec 03 18:03:30 2018 +0100
@@ -0,0 +1,143 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "BaseWebService.h"
+
+#include <Core/OrthancException.h>
+#include "Framework/Messages/IObservable.h"
+#include "Platforms/Generic/IOracleCommand.h"
+
+
+namespace OrthancStone
+{
+
+
+  class BaseWebService::BaseWebServicePayload : public Orthanc::IDynamicObject
+  {
+  private:
+    std::auto_ptr< MessageHandler<IWebService::HttpRequestSuccessMessage> >   userSuccessHandler_;
+    std::auto_ptr< MessageHandler<IWebService::HttpRequestErrorMessage> >     userFailureHandler_;
+    std::auto_ptr< Orthanc::IDynamicObject>                                   userPayload_;
+
+  public:
+    BaseWebServicePayload(MessageHandler<IWebService::HttpRequestSuccessMessage>* userSuccessHandler,
+                          MessageHandler<IWebService::HttpRequestErrorMessage>* userFailureHandler,
+                          Orthanc::IDynamicObject* userPayload) :
+      userSuccessHandler_(userSuccessHandler),
+      userFailureHandler_(userFailureHandler),
+      userPayload_(userPayload)
+    {
+    }
+
+    void HandleSuccess(const IWebService::HttpRequestSuccessMessage& message) const
+    {
+      if (userSuccessHandler_.get() != NULL)
+      {
+        // recreate a success message with the user payload
+        IWebService::HttpRequestSuccessMessage successMessage(message.GetUri(),
+                                                              message.GetAnswer(),
+                                                              message.GetAnswerSize(),
+                                                              message.GetAnswerHttpHeaders(),
+                                                              userPayload_.get());
+        userSuccessHandler_->Apply(successMessage);
+      }
+      else
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+    }
+
+    void HandleFailure(const IWebService::HttpRequestErrorMessage& message) const
+    {
+      if (userFailureHandler_.get() != NULL)
+      {
+        // recreate a failure message with the user payload
+        IWebService::HttpRequestErrorMessage failureMessage(message.GetUri(),
+                                                            userPayload_.get());
+
+        userFailureHandler_->Apply(failureMessage);
+      }
+    }
+
+  };
+
+
+  void BaseWebService::GetAsync(const std::string& uri,
+                                const HttpHeaders& headers,
+                                Orthanc::IDynamicObject* payload  /* takes ownership */,
+                                MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,
+                                MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback,
+                                unsigned int timeoutInSeconds)
+  {
+    if (cache_.find(uri) == cache_.end())
+    {
+      GetAsyncInternal(uri, headers,
+                       new BaseWebService::BaseWebServicePayload(successCallback, failureCallback, payload), // ownership is transfered
+                       new Callable<BaseWebService, IWebService::HttpRequestSuccessMessage>
+                       (*this, &BaseWebService::CacheAndNotifyHttpSuccess),
+                       new Callable<BaseWebService, IWebService::HttpRequestErrorMessage>
+                       (*this, &BaseWebService::NotifyHttpError),
+                       timeoutInSeconds);
+    }
+    else
+    {
+      // create a command and "post" it to the Oracle so it is executed and commited "later"
+      NotifyHttpSuccessLater(cache_[uri], payload, successCallback);
+    }
+
+  }
+
+
+
+  void BaseWebService::NotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message)
+  {
+    if (message.HasPayload())
+    {
+      dynamic_cast<const BaseWebServicePayload&>(message.GetPayload()).HandleSuccess(message);
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+  void BaseWebService::CacheAndNotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message)
+  {
+    cache_[message.GetUri()] = boost::shared_ptr<CachedHttpRequestSuccessMessage>(new CachedHttpRequestSuccessMessage(message));
+    NotifyHttpSuccess(message);
+  }
+
+  void BaseWebService::NotifyHttpError(const IWebService::HttpRequestErrorMessage& message)
+  {
+    if (message.HasPayload())
+    {
+      dynamic_cast<const BaseWebServicePayload&>(message.GetPayload()).HandleFailure(message);
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Toolbox/BaseWebService.h	Mon Dec 03 18:03:30 2018 +0100
@@ -0,0 +1,131 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IWebService.h"
+
+#include <string>
+#include <map>
+
+namespace OrthancStone
+{
+  // This is an intermediate of IWebService that implements some caching on
+  // the HTTP GET requests
+  class BaseWebService : public IWebService, public IObserver
+  {
+  protected:
+    class CachedHttpRequestSuccessMessage
+    {
+    protected:
+      std::string                    uri_;
+      void*                          answer_;
+      size_t                         answerSize_;
+      IWebService::HttpHeaders       answerHeaders_;
+
+    public:
+      CachedHttpRequestSuccessMessage(const IWebService::HttpRequestSuccessMessage& message) :
+        uri_(message.GetUri()),
+        answerSize_(message.GetAnswerSize()),
+        answerHeaders_(message.GetAnswerHttpHeaders())
+      {
+        answer_ =  malloc(answerSize_);
+        memcpy(answer_, message.GetAnswer(), answerSize_);
+      }
+
+      ~CachedHttpRequestSuccessMessage()
+      {
+        free(answer_);
+      }
+
+      const std::string& GetUri() const
+      {
+        return uri_;
+      }
+
+      const void* GetAnswer() const
+      {
+        return answer_;
+      }
+
+      size_t GetAnswerSize() const
+      {
+        return answerSize_;
+      }
+
+      const IWebService::HttpHeaders&  GetAnswerHttpHeaders() const
+      {
+        return answerHeaders_;
+      }
+
+    };
+
+    class BaseWebServicePayload;
+
+    bool          cacheEnabled_;
+    std::map<std::string, boost::shared_ptr<CachedHttpRequestSuccessMessage>> cache_;  // TODO: this is currently an infinite cache !
+
+  public:
+
+    BaseWebService(MessageBroker& broker) :
+      IWebService(broker),
+      IObserver(broker),
+      cacheEnabled_(true)
+    {
+    }
+
+    virtual ~BaseWebService()
+    {
+    }
+
+    virtual void EnableCache(bool enable)
+    {
+      cacheEnabled_ = enable;
+    }
+
+    virtual void GetAsync(const std::string& uri,
+                          const HttpHeaders& headers,
+                          Orthanc::IDynamicObject* payload  /* takes ownership */,
+                          MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,
+                          MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
+                          unsigned int timeoutInSeconds = 60);
+
+  protected:
+    virtual void GetAsyncInternal(const std::string& uri,
+                          const HttpHeaders& headers,
+                          Orthanc::IDynamicObject* payload  /* takes ownership */,
+                          MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,
+                          MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
+                          unsigned int timeoutInSeconds = 60) = 0;
+
+    virtual void NotifyHttpSuccessLater(boost::shared_ptr<BaseWebService::CachedHttpRequestSuccessMessage> cachedHttpMessage,
+                                        Orthanc::IDynamicObject* payload, // takes ownership
+                                        MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback) = 0;
+
+  private:
+    void NotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message);
+
+    void NotifyHttpError(const IWebService::HttpRequestErrorMessage& message);
+
+    void CacheAndNotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message);
+
+  };
+}
--- a/Framework/Toolbox/IWebService.h	Mon Dec 03 14:22:47 2018 +0100
+++ b/Framework/Toolbox/IWebService.h	Mon Dec 03 18:03:30 2018 +0100
@@ -135,6 +135,7 @@
     {
     }
 
+    virtual void EnableCache(bool enable) = 0;
     
     virtual void GetAsync(const std::string& uri,
                           const HttpHeaders& headers,
--- a/Framework/Toolbox/OrthancApiClient.cpp	Mon Dec 03 14:22:47 2018 +0100
+++ b/Framework/Toolbox/OrthancApiClient.cpp	Mon Dec 03 18:03:30 2018 +0100
@@ -167,60 +167,13 @@
   };
 
 
-  class OrthancApiClient::CachedHttpRequestSuccessMessage
-  {
-  protected:
-    std::string                    uri_;
-    void*                          answer_;
-    size_t                         answerSize_;
-    IWebService::HttpHeaders       answerHeaders_;
-
-  public:
-    CachedHttpRequestSuccessMessage(const IWebService::HttpRequestSuccessMessage& message) :
-      uri_(message.GetUri()),
-      answerSize_(message.GetAnswerSize()),
-      answerHeaders_(message.GetAnswerHttpHeaders())
-    {
-      answer_ =  malloc(answerSize_);
-      memcpy(answer_, message.GetAnswer(), answerSize_);
-    }
-
-    ~CachedHttpRequestSuccessMessage()
-    {
-      free(answer_);
-    }
-
-    const std::string& GetUri() const
-    {
-      return uri_;
-    }
-
-    const void* GetAnswer() const
-    {
-      return answer_;
-    }
-
-    size_t GetAnswerSize() const
-    {
-      return answerSize_;
-    }
-
-    const IWebService::HttpHeaders&  GetAnswerHttpHeaders() const
-    {
-      return answerHeaders_;
-    }
-
-  };
-
-
   OrthancApiClient::OrthancApiClient(MessageBroker& broker,
                                      IWebService& web,
                                      const std::string& baseUrl) :
     IObservable(broker),
     IObserver(broker),
     web_(web),
-    baseUrl_(baseUrl),
-    cacheEnabled_(true)
+    baseUrl_(baseUrl)
   {
   }
 
@@ -232,22 +185,13 @@
       Orthanc::IDynamicObject* payload)
   {
     IWebService::HttpHeaders emptyHeaders;
-    if (cacheEnabled_)
-    {
-      HandleFromCache(baseUrl_ + uri,
-                      emptyHeaders,
-                      new WebServicePayload(successCallback, failureCallback, payload));
-    }
-    else
-    {
-      web_.GetAsync(baseUrl_ + uri,
-                    emptyHeaders,
-                    new WebServicePayload(successCallback, failureCallback, payload),
-                    new Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage>
-                    (*this, &OrthancApiClient::NotifyHttpSuccess),
-                    new Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage>
-                    (*this, &OrthancApiClient::NotifyHttpError));
-    }
+    web_.GetAsync(baseUrl_ + uri,
+                  emptyHeaders,
+                  new WebServicePayload(successCallback, failureCallback, payload),
+                  new Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage>
+                  (*this, &OrthancApiClient::NotifyHttpSuccess),
+                  new Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage>
+                  (*this, &OrthancApiClient::NotifyHttpError));
   }
 
 
@@ -262,37 +206,6 @@
     headers["Accept"] = contentType;
     GetBinaryAsync(uri, headers, successCallback, failureCallback, payload);
   }
-  
-
-  void OrthancApiClient::HandleFromCache(const std::string& uri,
-                                         const IWebService::HttpHeaders& headers,
-                                         Orthanc::IDynamicObject* payload // takes ownership
-                                         )
-  {
-    if (cache_.find(uri) == cache_.end())
-    {
-      web_.GetAsync(uri, headers,
-                    payload, // ownership is transfered
-                    new Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage>
-                    (*this, &OrthancApiClient::CacheAndNotifyHttpSuccess),
-                    new Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage>
-                    (*this, &OrthancApiClient::NotifyHttpError));
-    }
-    else
-    {
-      std::auto_ptr<Orthanc::IDynamicObject> payloadRaii(payload); // make sure payload is deleted whatever happens
-
-      const OrthancApiClient::CachedHttpRequestSuccessMessage& cachedMessage = *(cache_[uri]);
-      IWebService::HttpRequestSuccessMessage successMessage(cachedMessage.GetUri(),
-                                                            cachedMessage.GetAnswer(),
-                                                            cachedMessage.GetAnswerSize(),
-                                                            cachedMessage.GetAnswerHttpHeaders(),
-                                                            payloadRaii.get());
-      NotifyHttpSuccess(successMessage);
-    }
-
-  }
-
 
   void OrthancApiClient::GetBinaryAsync(
       const std::string& uri,
@@ -303,21 +216,12 @@
   {
     // printf("GET [%s] [%s]\n", baseUrl_.c_str(), uri.c_str());
 
-    if (cacheEnabled_)
-    {
-      HandleFromCache(baseUrl_ + uri,
-                      headers,
-                      new WebServicePayload(successCallback, failureCallback, payload));
-    }
-    else
-    {
-      web_.GetAsync(baseUrl_ + uri, headers,
-                    new WebServicePayload(successCallback, failureCallback, payload),
-                    new Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage>
-                    (*this, &OrthancApiClient::NotifyHttpSuccess),
-                    new Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage>
-                    (*this, &OrthancApiClient::NotifyHttpError));
-    }
+    web_.GetAsync(baseUrl_ + uri, headers,
+                  new WebServicePayload(successCallback, failureCallback, payload),
+                  new Callable<OrthancApiClient, IWebService::HttpRequestSuccessMessage>
+                  (*this, &OrthancApiClient::NotifyHttpSuccess),
+                  new Callable<OrthancApiClient, IWebService::HttpRequestErrorMessage>
+                  (*this, &OrthancApiClient::NotifyHttpError));
   }
 
   
@@ -392,12 +296,6 @@
     }
   }
 
-  void OrthancApiClient::CacheAndNotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message)
-  {
-    cache_[message.GetUri()] = boost::shared_ptr<CachedHttpRequestSuccessMessage>(new CachedHttpRequestSuccessMessage(message));
-    NotifyHttpSuccess(message);
-  }
-
   void OrthancApiClient::NotifyHttpError(const IWebService::HttpRequestErrorMessage& message)
   {
     if (message.HasPayload())
--- a/Framework/Toolbox/OrthancApiClient.h	Mon Dec 03 14:22:47 2018 +0100
+++ b/Framework/Toolbox/OrthancApiClient.h	Mon Dec 03 18:03:30 2018 +0100
@@ -149,14 +149,11 @@
 
   private:
     class WebServicePayload;
-    class CachedHttpRequestSuccessMessage;
 
   protected:
     IWebService&  web_;
     std::string   baseUrl_;
 
-    std::map<std::string, boost::shared_ptr<CachedHttpRequestSuccessMessage>> cache_;  // TODO: this is currently an infinite cache !
-    bool          cacheEnabled_;
   public:
     OrthancApiClient(MessageBroker& broker,
                      IWebService& web,
@@ -166,11 +163,6 @@
     {
     }
 
-    void EnableCache(bool enable)
-    {
-      cacheEnabled_ = enable;
-    }
-
     // schedule a GET request expecting a JSON response.
     void GetJsonAsync(const std::string& uri,
                       MessageHandler<JsonResponseReadyMessage>* successCallback,
@@ -223,8 +215,6 @@
 
     void NotifyHttpError(const IWebService::HttpRequestErrorMessage& message);
 
-    void CacheAndNotifyHttpSuccess(const IWebService::HttpRequestSuccessMessage& message);
-
   private:
     void HandleFromCache(const std::string& uri,
                          const IWebService::HttpHeaders& headers,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Platforms/Generic/OracleWebService.cpp	Mon Dec 03 18:03:30 2018 +0100
@@ -0,0 +1,82 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "OracleWebService.h"
+#include "../../Framework/Toolbox/IWebService.h"
+
+namespace OrthancStone
+{
+
+
+  class OracleWebService::WebServiceCachedGetCommand : public IOracleCommand, IObservable
+  {
+  protected:
+    std::auto_ptr<MessageHandler<IWebService::HttpRequestSuccessMessage> >  successCallback_;
+    std::auto_ptr<Orthanc::IDynamicObject>                                  payload_;
+    boost::shared_ptr<BaseWebService::CachedHttpRequestSuccessMessage>      cachedMessage_;
+    NativeStoneApplicationContext&                                          context_;
+
+  public:
+    WebServiceCachedGetCommand(MessageBroker& broker,
+                               MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,  // takes ownership
+                               boost::shared_ptr<BaseWebService::CachedHttpRequestSuccessMessage> cachedMessage,
+                               Orthanc::IDynamicObject* payload /* takes ownership */,
+                               NativeStoneApplicationContext& context
+                               ) :
+      IObservable(broker),
+      successCallback_(successCallback),
+      payload_(payload),
+      cachedMessage_(cachedMessage),
+      context_(context)
+    {
+    }
+
+    virtual void Execute()
+    {
+      // nothing to do, everything is in the commit
+    }
+
+    virtual void Commit()
+    {
+      // We want to make sure that, i.e, the UpdateThread is not
+      // triggered while we are updating the "model" with the result of
+      // a WebServiceCommand
+      NativeStoneApplicationContext::GlobalMutexLocker lock(context_);
+
+      IWebService::HttpRequestSuccessMessage successMessage(cachedMessage_->GetUri(),
+                                                            cachedMessage_->GetAnswer(),
+                                                            cachedMessage_->GetAnswerSize(),
+                                                            cachedMessage_->GetAnswerHttpHeaders(),
+                                                            payload_.get());
+
+      successCallback_->Apply(successMessage);
+    }
+  };
+
+  void OracleWebService::NotifyHttpSuccessLater(boost::shared_ptr<BaseWebService::CachedHttpRequestSuccessMessage> cachedMessage,
+                                                Orthanc::IDynamicObject* payload, // takes ownership
+                                                MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback)
+  {
+    oracle_.Submit(new WebServiceCachedGetCommand(GetBroker(), successCallback, cachedMessage, payload, context_));
+  }
+
+
+}
--- a/Platforms/Generic/OracleWebService.h	Mon Dec 03 14:22:47 2018 +0100
+++ b/Platforms/Generic/OracleWebService.h	Mon Dec 03 18:03:30 2018 +0100
@@ -21,7 +21,7 @@
 
 #pragma once
 
-#include "../../Framework/Toolbox/IWebService.h"
+#include "../../Framework/Toolbox/BaseWebService.h"
 #include "Oracle.h"
 #include "WebServiceGetCommand.h"
 #include "WebServicePostCommand.h"
@@ -33,35 +33,27 @@
   // The OracleWebService performs HTTP requests in a native environment.
   // It uses a thread pool to handle multiple HTTP requests in a same time.
   // It works asynchronously to mimick the behaviour of the WebService running in a WASM environment.
-  class OracleWebService : public IWebService
+  class OracleWebService : public BaseWebService
   {
   private:
     Oracle&                        oracle_;
     NativeStoneApplicationContext& context_;
     Orthanc::WebServiceParameters  parameters_;
 
+    class WebServiceCachedGetCommand;
+
   public:
     OracleWebService(MessageBroker& broker,
                      Oracle& oracle,
                      const Orthanc::WebServiceParameters& parameters,
                      NativeStoneApplicationContext& context) :
-      IWebService(broker),
+      BaseWebService(broker),
       oracle_(oracle),
       context_(context),
       parameters_(parameters)
     {
     }
 
-    virtual void GetAsync(const std::string& uri,
-                          const HttpHeaders& headers,
-                          Orthanc::IDynamicObject* payload, // takes ownership
-                          MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,   // takes ownership
-                          MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,// takes ownership
-                          unsigned int timeoutInSeconds = 60)
-    {
-      oracle_.Submit(new WebServiceGetCommand(broker_, successCallback, failureCallback, parameters_, uri, headers, timeoutInSeconds, payload, context_));
-    }
-
     virtual void PostAsync(const std::string& uri,
                            const HttpHeaders& headers,
                            const std::string& body,
@@ -70,7 +62,7 @@
                            MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL, // takes ownership
                            unsigned int timeoutInSeconds = 60)
     {
-      oracle_.Submit(new WebServicePostCommand(broker_, successCallback, failureCallback, parameters_, uri, headers, timeoutInSeconds, body, payload, context_));
+      oracle_.Submit(new WebServicePostCommand(GetBroker(), successCallback, failureCallback, parameters_, uri, headers, timeoutInSeconds, body, payload, context_));
     }
 
     virtual void DeleteAsync(const std::string& uri,
@@ -80,7 +72,23 @@
                              MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,
                              unsigned int timeoutInSeconds = 60)
     {
-      oracle_.Submit(new WebServiceDeleteCommand(broker_, successCallback, failureCallback, parameters_, uri, headers, timeoutInSeconds, payload, context_));
+      oracle_.Submit(new WebServiceDeleteCommand(GetBroker(), successCallback, failureCallback, parameters_, uri, headers, timeoutInSeconds, payload, context_));
     }
+
+  protected:
+    virtual void GetAsyncInternal(const std::string& uri,
+                                  const HttpHeaders& headers,
+                                  Orthanc::IDynamicObject* payload, // takes ownership
+                                  MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback,   // takes ownership
+                                  MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallback = NULL,// takes ownership
+                                  unsigned int timeoutInSeconds = 60)
+    {
+      oracle_.Submit(new WebServiceGetCommand(GetBroker(), successCallback, failureCallback, parameters_, uri, headers, timeoutInSeconds, payload, context_));
+    }
+
+    virtual void NotifyHttpSuccessLater(boost::shared_ptr<BaseWebService::CachedHttpRequestSuccessMessage> cachedHttpMessage,
+                                        Orthanc::IDynamicObject* payload, // takes ownership
+                                        MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallback);
+
   };
 }
--- a/Platforms/Wasm/WasmWebService.cpp	Mon Dec 03 14:22:47 2018 +0100
+++ b/Platforms/Wasm/WasmWebService.cpp	Mon Dec 03 18:03:30 2018 +0100
@@ -122,12 +122,12 @@
                                payload, timeoutInSeconds);
   }
 
-  void WasmWebService::GetAsync(const std::string& relativeUri,
-                                const HttpHeaders& headers,
-                                Orthanc::IDynamicObject* payload,
-                                MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallable,
-                                MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallable,
-                                unsigned int timeoutInSeconds)
+  void WasmWebService::GetAsyncInternal(const std::string &relativeUri,
+                                        const HttpHeaders &headers,
+                                        Orthanc::IDynamicObject *payload,
+                                        MessageHandler<IWebService::HttpRequestSuccessMessage> *successCallable,
+                                        MessageHandler<IWebService::HttpRequestErrorMessage> *failureCallable,
+                                        unsigned int timeoutInSeconds)
   {
     std::string headersInJsonString;
     ToJsonString(headersInJsonString, headers);
--- a/Platforms/Wasm/WasmWebService.h	Mon Dec 03 14:22:47 2018 +0100
+++ b/Platforms/Wasm/WasmWebService.h	Mon Dec 03 18:03:30 2018 +0100
@@ -5,62 +5,61 @@
 
 namespace OrthancStone
 {
-  class WasmWebService : public IWebService
-  {
-  private:
-    static MessageBroker* broker_;
+class WasmWebService : public IWebService
+{
+private:
+  static MessageBroker *broker_;
 
-    // Private constructor => Singleton design pattern
-    WasmWebService(MessageBroker& broker) :
-      IWebService(broker)
-    {
-    }
+  // Private constructor => Singleton design pattern
+  WasmWebService(MessageBroker &broker) : IWebService(broker)
+  {
+  }
 
-  public:
-    static WasmWebService& GetInstance()
+public:
+  static WasmWebService &GetInstance()
+  {
+    if (broker_ == NULL)
     {
-      if (broker_ == NULL)
-      {
-        printf("WasmWebService::GetInstance(): broker not initialized\n");
-        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
-      }
-      static WasmWebService instance(*broker_);
-      return instance;
+      printf("WasmWebService::GetInstance(): broker not initialized\n");
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
     }
+    static WasmWebService instance(*broker_);
+    return instance;
+  }
 
-    static void SetBroker(MessageBroker& broker)
-    {
-      broker_ = &broker;
-    }
+  static void SetBroker(MessageBroker &broker)
+  {
+    broker_ = &broker;
+  }
 
-    virtual void GetAsync(const std::string& uri,
-                          const HttpHeaders& headers,
-                          Orthanc::IDynamicObject* payload,
-                          MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallable,
-                          MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallable = NULL,
-                          unsigned int timeoutInSeconds = 60);
+  virtual void GetAsyncInternal(const std::string &uri,
+                                const HttpHeaders &headers,
+                                Orthanc::IDynamicObject *payload,
+                                MessageHandler<IWebService::HttpRequestSuccessMessage> *successCallable,
+                                MessageHandler<IWebService::HttpRequestErrorMessage> *failureCallable = NULL,
+                                unsigned int timeoutInSeconds = 60);
 
-    virtual void PostAsync(const std::string& uri,
-                           const HttpHeaders& headers,
-                           const std::string& body,
-                           Orthanc::IDynamicObject* payload,
-                           MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallable,
-                           MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallable = NULL,
+  virtual void PostAsync(const std::string &uri,
+                         const HttpHeaders &headers,
+                         const std::string &body,
+                         Orthanc::IDynamicObject *payload,
+                         MessageHandler<IWebService::HttpRequestSuccessMessage> *successCallable,
+                         MessageHandler<IWebService::HttpRequestErrorMessage> *failureCallable = NULL,
+                         unsigned int timeoutInSeconds = 60);
+
+  virtual void DeleteAsync(const std::string &uri,
+                           const HttpHeaders &headers,
+                           Orthanc::IDynamicObject *payload,
+                           MessageHandler<IWebService::HttpRequestSuccessMessage> *successCallable,
+                           MessageHandler<IWebService::HttpRequestErrorMessage> *failureCallable = NULL,
                            unsigned int timeoutInSeconds = 60);
 
-    virtual void DeleteAsync(const std::string& uri,
-                             const HttpHeaders& headers,
-                             Orthanc::IDynamicObject* payload,
-                             MessageHandler<IWebService::HttpRequestSuccessMessage>* successCallable,
-                             MessageHandler<IWebService::HttpRequestErrorMessage>* failureCallable = NULL,
-                             unsigned int timeoutInSeconds = 60);
+  // virtual void Start()
+  // {
+  // }
 
-    // virtual void Start()
-    // {
-    // }
-    
-    // virtual void Stop()
-    // {
-    // }
-  };
-}
+  // virtual void Stop()
+  // {
+  // }
+};
+} // namespace OrthancStone
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Mon Dec 03 14:22:47 2018 +0100
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Mon Dec 03 18:03:30 2018 +0100
@@ -188,7 +188,7 @@
     ${ORTHANC_STONE_ROOT}/Platforms/Generic/WebServiceDeleteCommand.cpp
     ${ORTHANC_STONE_ROOT}/Platforms/Generic/DelayedCallCommand.cpp
     ${ORTHANC_STONE_ROOT}/Platforms/Generic/Oracle.cpp
-    ${ORTHANC_STONE_ROOT}/Platforms/Generic/OracleWebService.h
+    ${ORTHANC_STONE_ROOT}/Platforms/Generic/OracleWebService.cpp
     ${ORTHANC_STONE_ROOT}/Platforms/Generic/OracleDelayedCallExecutor.h
     )
 
@@ -271,6 +271,7 @@
   ${ORTHANC_STONE_ROOT}/Framework/StoneEnumerations.cpp
   ${ORTHANC_STONE_ROOT}/Framework/StoneException.h
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/AffineTransform2D.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Toolbox/BaseWebService.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/CoordinateSystem3D.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DicomFrameConverter.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/DicomStructureSet.cpp