changeset 129:1aad2bf98d8c dev

QIDO-RS and WADO-RS client with URI "/{dicom-web}/servers/{id}/get"
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 24 Jun 2016 10:12:25 +0200
parents ffe7bfbe370e
children f5d135633484
files NEWS Plugin/Configuration.cpp Plugin/DicomWebServers.cpp Plugin/DicomWebServers.h Plugin/Plugin.cpp Plugin/StowRsClient.cpp
diffstat 6 files changed, 264 insertions(+), 187 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Thu Jun 23 18:04:35 2016 +0200
+++ b/NEWS	Fri Jun 24 10:12:25 2016 +0200
@@ -3,7 +3,8 @@
 
 => Minimum SDK version: 1.0.1 <=
 
-* STOW-RS client
+* STOW-RS client with URI "/{dicom-web}/servers/{id}/stow"
+* QIDO-RS and WADO-RS client with URI "/{dicom-web}/servers/{id}/get"
 * Improved robustness in the STOW-RS server
 * Fix issue #13 (QIDO-RS study-level query is slow)
 * Fix issue #14 (Aggregate fields empty for QIDO-RS study/series-level queries)
--- a/Plugin/Configuration.cpp	Thu Jun 23 18:04:35 2016 +0200
+++ b/Plugin/Configuration.cpp	Fri Jun 24 10:12:25 2016 +0200
@@ -313,8 +313,6 @@
                              const Json::Value& value,
                              const std::string& key)
   {
-    target.clear();
-
     if (value.type() != Json::objectValue)
     {
       OrthancPlugins::Configuration::LogError("This is not a JSON object");
--- a/Plugin/DicomWebServers.cpp	Thu Jun 23 18:04:35 2016 +0200
+++ b/Plugin/DicomWebServers.cpp	Fri Jun 24 10:12:25 2016 +0200
@@ -22,6 +22,7 @@
 
 #include "Configuration.h"
 #include "../Orthanc/Core/OrthancException.h"
+#include "../Orthanc/Core/Toolbox.h"
 
 namespace OrthancPlugins
 {
@@ -111,4 +112,167 @@
       servers.push_back(it->first);
     }
   }
+
+
+  static const char* ConvertToCString(const std::string& s)
+  {
+    if (s.empty())
+    {
+      return NULL;
+    }
+    else
+    {
+      return s.c_str();
+    }
+  }
+
+
+
+  void CallServer(OrthancPlugins::MemoryBuffer& answerBody /* out */,
+                  std::map<std::string, std::string>& answerHeaders /* out */,
+                  const Orthanc::WebServiceParameters& server,
+                  OrthancPluginHttpMethod method,
+                  const std::map<std::string, std::string>& httpHeaders,
+                  const std::string& uri,
+                  const std::string& body)
+  {
+    answerBody.Clear();
+    answerHeaders.clear();
+
+    std::string url = server.GetUrl();
+    assert(!url.empty() && url[url.size() - 1] == '/');
+
+    // Remove the leading "/" in the URI if need be
+    std::string tmp;
+    if (!uri.empty() &&
+        uri[0] == '/')
+    {
+      url += uri.substr(1);
+    }
+    else
+    {
+      url += uri;
+    }
+
+    std::vector<const char*> httpHeadersKeys(httpHeaders.size());
+    std::vector<const char*> httpHeadersValues(httpHeaders.size());
+
+    {
+      size_t pos = 0;
+      for (std::map<std::string, std::string>::const_iterator
+             it = httpHeaders.begin(); it != httpHeaders.end(); ++it)
+      {
+        httpHeadersKeys[pos] = it->first.c_str();
+        httpHeadersValues[pos] = it->second.c_str();
+        pos += 1;
+      }
+    }
+
+    const char* bodyContent = NULL;
+    size_t bodySize = 0;
+
+    if ((method == OrthancPluginHttpMethod_Put ||
+         method == OrthancPluginHttpMethod_Post) &&
+        !body.empty())
+    {
+      bodyContent = body.c_str();
+      bodySize = body.size();
+    }
+
+    uint16_t status = 0;
+    MemoryBuffer answerHeadersTmp(OrthancPlugins::Configuration::GetContext());
+    OrthancPluginErrorCode code = OrthancPluginHttpClient(
+      OrthancPlugins::Configuration::GetContext(), 
+      /* Outputs */
+      *answerBody, *answerHeadersTmp, &status, 
+      method,
+      url.c_str(), 
+      /* HTTP headers*/
+      httpHeaders.size(),
+      httpHeadersKeys.empty() ? NULL : &httpHeadersKeys[0],
+      httpHeadersValues.empty() ? NULL : &httpHeadersValues[0],
+      bodyContent, bodySize,
+      ConvertToCString(server.GetUsername()), /* Authentication */
+      ConvertToCString(server.GetPassword()), 
+      0,                                      /* Timeout */
+      ConvertToCString(server.GetCertificateFile()),
+      ConvertToCString(server.GetCertificateKeyFile()),
+      ConvertToCString(server.GetCertificateKeyPassword()),
+      server.IsPkcs11Enabled() ? 1 : 0);
+
+    if (code != OrthancPluginErrorCode_Success ||
+        (status < 200 || status >= 300))
+    {
+      OrthancPlugins::Configuration::LogError("Cannot issue an HTTP query to " + url + 
+                                              " (HTTP status: " + boost::lexical_cast<std::string>(status) + ")");
+      throw PluginException(code);
+    }
+
+    Json::Value json;
+    answerHeadersTmp.ToJson(json);
+    answerHeadersTmp.Clear();
+
+    if (json.type() != Json::objectValue)
+    {
+      throw PluginException(OrthancPluginErrorCode_InternalError);
+    }
+
+    Json::Value::Members members = json.getMemberNames();
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      const std::string& key = members[i];
+
+      if (json[key].type() != Json::stringValue)
+      {
+        throw PluginException(OrthancPluginErrorCode_InternalError);
+      }
+      else
+      {
+        answerHeaders[key] = json[key].asString();        
+      }
+    }
+  }
+
+
+  void UriEncode(std::string& uri,
+                 const std::string& resource,
+                 const std::map<std::string, std::string>& getArguments)
+  {
+    if (resource.find('?') != std::string::npos)
+    {
+      OrthancPlugins::Configuration::LogError("The GET arguments must be provided in a separate field "
+                                              "(explicit \"?\" is disallowed): " + resource);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
+    }
+
+    uri = resource;
+
+    bool isFirst = true;
+    for (std::map<std::string, std::string>::const_iterator
+           it = getArguments.begin(); it != getArguments.end(); ++it)
+    {
+      if (isFirst)
+      {
+        uri += '?';
+        isFirst = false;
+      }
+      else
+      {
+        uri += '&';
+      }
+
+      std::string key, value;
+      Orthanc::Toolbox::UriEncode(key, it->first);
+      Orthanc::Toolbox::UriEncode(value, it->second);
+
+      if (value.empty())
+      {
+        uri += key;
+      }
+      else
+      {
+        uri += key + "=" + value;
+      }
+    }
+  }
 }
--- a/Plugin/DicomWebServers.h	Thu Jun 23 18:04:35 2016 +0200
+++ b/Plugin/DicomWebServers.h	Fri Jun 24 10:12:25 2016 +0200
@@ -58,9 +58,16 @@
     void ListServers(std::list<std::string>& servers);
   };
 
-  void QueryServer(std::string& result,
-                   const Orthanc::WebServiceParameters& server,
-                   const std::map<std::string, std::string>& httpHeaders,
-                   const std::string& uri,
-                   const std::string& body);
+
+  void CallServer(OrthancPlugins::MemoryBuffer& answerBody /* out */,
+                  std::map<std::string, std::string>& answerHeaders /* out */,
+                  const Orthanc::WebServiceParameters& server,
+                  OrthancPluginHttpMethod method,
+                  const std::map<std::string, std::string>& httpHeaders,
+                  const std::string& uri,
+                  const std::string& body);
+
+  void UriEncode(std::string& uri,
+                 const std::string& resource,
+                 const std::map<std::string, std::string>& getArguments);
 }
--- a/Plugin/Plugin.cpp	Thu Jun 23 18:04:35 2016 +0200
+++ b/Plugin/Plugin.cpp	Fri Jun 24 10:12:25 2016 +0200
@@ -139,72 +139,14 @@
 }
 
 
-
-static const char* GET_ARGUMENTS = "Arguments";
-
-
-static void UrlEncode(std::string& url,
-                      const Orthanc::WebServiceParameters& server,
-                      const std::string& uri,
-                      const std::map<std::string, std::string>& getArguments)
-{
-  url = server.GetUrl();
-  assert(!url.empty() && url[url.size() - 1] == '/');
-
-  if (uri.find('?') != std::string::npos)
-  {
-    OrthancPlugins::Configuration::LogError("The GET arguments must be provided in the \"" + 
-                                            std::string(GET_ARGUMENTS) + "\" field (\"?\" is disallowed): " + uri);
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
-  }
-
-  // Remove the leading "/" in the URI if need be
-  std::string tmp;
-  if (!uri.empty() &&
-      uri[0] == '/')
-  {
-    url += uri.substr(1);
-  }
-  else
-  {
-    url += uri;
-  }
-
-  bool isFirst = true;
-  for (std::map<std::string, std::string>::const_iterator
-         it = getArguments.begin(); it != getArguments.end(); ++it)
-  {
-    if (isFirst)
-    {
-      url += '?';
-      isFirst = false;
-    }
-    else
-    {
-      url += '&';
-    }
-
-    std::string key, value;
-    Orthanc::Toolbox::UriEncode(key, it->first);
-    Orthanc::Toolbox::UriEncode(value, it->second);
-
-    if (value.empty())
-    {
-      url += key;
-    }
-    else
-    {
-      url += key + "=" + value;
-    }
-  }
-}
-                      
-
-
 void GetFromServer(OrthancPluginRestOutput* output,
                    const char* /*url*/,
                    const OrthancPluginHttpRequest* request)
 {
+  static const char* URI = "Uri";
+  static const char* HTTP_HEADERS = "HttpHeaders";
+  static const char* GET_ARGUMENTS = "Arguments";
+
   if (request->method != OrthancPluginHttpMethod_Post)
   {
     OrthancPluginSendMethodNotAllowed(OrthancPlugins::Configuration::GetContext(), output, "POST");
@@ -213,9 +155,6 @@
 
   Orthanc::WebServiceParameters server(OrthancPlugins::DicomWebServers::GetInstance().GetServer(request->groups[0]));
 
-  static const char* URI = "Uri";
-  static const char* HTTP_HEADERS = "HttpHeaders";
-
   Json::Value body;
   Json::Reader reader;
   if (!reader.parse(request->body, request->body + request->bodySize, body) ||
@@ -228,14 +167,44 @@
     throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
   }
 
-  std::map<std::string, std::string> httpHeaders, getArguments;
-  OrthancPlugins::ParseAssociativeArray(httpHeaders, body, HTTP_HEADERS);
+  std::map<std::string, std::string> getArguments;
   OrthancPlugins::ParseAssociativeArray(getArguments, body, GET_ARGUMENTS);
 
-  std::string url; 
-  UrlEncode(url, server, body[URI].asString(), getArguments);
+  std::string uri; 
+  OrthancPlugins::UriEncode(uri, body[URI].asString(), getArguments);
+
+  std::map<std::string, std::string> httpHeaders;
+  OrthancPlugins::ParseAssociativeArray(httpHeaders, body, HTTP_HEADERS);
+
+  OrthancPlugins::MemoryBuffer answerBody(OrthancPlugins::Configuration::GetContext());
+  std::map<std::string, std::string> answerHeaders;
+  OrthancPlugins::CallServer(answerBody, answerHeaders, server, OrthancPluginHttpMethod_Get, httpHeaders, uri, "");
+
+  std::string contentType = "application/octet-stream";
+
+  for (std::map<std::string, std::string>::const_iterator
+         it = answerHeaders.begin(); it != answerHeaders.end(); ++it)
+  {
+    std::string key = it->first;
+    Orthanc::Toolbox::ToLowerCase(key);
 
-  printf("URL: [%s]\n", url.c_str());
+    if (key == "content-type")
+    {
+      contentType = it->second;
+    }
+    else if (key == "transfer-encoding")
+    {
+      // Do not include this header
+    }
+    else
+    {
+      OrthancPluginSetHttpHeader(OrthancPlugins::Configuration::GetContext(), output, it->first.c_str(), it->second.c_str());
+    }
+  }
+
+  OrthancPluginAnswerBuffer(OrthancPlugins::Configuration::GetContext(), output, 
+                            reinterpret_cast<const char*>(answerBody.GetData()),
+                            answerBody.GetSize(), contentType.c_str());
 }
 
 
--- a/Plugin/StowRsClient.cpp	Thu Jun 23 18:04:35 2016 +0200
+++ b/Plugin/StowRsClient.cpp	Fri Jun 24 10:12:25 2016 +0200
@@ -93,114 +93,6 @@
 
 
 
-static const char* ConvertToCString(const std::string& s)
-{
-  if (s.empty())
-  {
-    return NULL;
-  }
-  else
-  {
-    return s.c_str();
-  }
-}
-
-
-
-static void SendStowRequest(const Orthanc::WebServiceParameters& server,
-                            const std::map<std::string, std::string>& httpHeaders,
-                            const std::string& body,
-                            size_t countInstances)
-{
-  std::vector<const char*> httpHeadersKeys(httpHeaders.size());
-  std::vector<const char*> httpHeadersValues(httpHeaders.size());
-
-  {
-    size_t pos = 0;
-    for (std::map<std::string, std::string>::const_iterator
-           it = httpHeaders.begin(); it != httpHeaders.end(); ++it)
-    {
-      httpHeadersKeys[pos] = it->first.c_str();
-      httpHeadersValues[pos] = it->second.c_str();
-      pos += 1;
-    }
-  }
-
-  std::string url = server.GetUrl() + "studies";
-
-  uint16_t status = 0;
-  OrthancPluginMemoryBuffer answerBody;
-  OrthancPluginErrorCode code = OrthancPluginHttpClient(
-    OrthancPlugins::Configuration::GetContext(), &answerBody, 
-    NULL,                                   /* No interest in the HTTP headers of the answer */
-    &status, 
-    OrthancPluginHttpMethod_Post,
-    url.c_str(), 
-    /* HTTP headers*/
-    httpHeaders.size(),
-    httpHeadersKeys.empty() ? NULL : &httpHeadersKeys[0],
-    httpHeadersValues.empty() ? NULL : &httpHeadersValues[0],
-    body.c_str(), body.size(),              /* POST body */
-    ConvertToCString(server.GetUsername()), /* Authentication */
-    ConvertToCString(server.GetPassword()), 
-    0,                                      /* Timeout */
-    ConvertToCString(server.GetCertificateFile()),
-    ConvertToCString(server.GetCertificateKeyFile()),
-    ConvertToCString(server.GetCertificateKeyPassword()),
-    server.IsPkcs11Enabled() ? 1 : 0);
-
-  if (code != OrthancPluginErrorCode_Success ||
-      (status != 200 && status != 202))
-  {
-    OrthancPlugins::Configuration::LogError("Cannot send DICOM images through STOW-RS to DICOMweb server " + server.GetUrl() + 
-                                            " (HTTP status: " + boost::lexical_cast<std::string>(status) + ")");
-    throw Orthanc::OrthancException(static_cast<Orthanc::ErrorCode>(code));
-  }
-
-  Json::Value response;
-  Json::Reader reader;
-  bool success = reader.parse(reinterpret_cast<const char*>(answerBody.data),
-                              reinterpret_cast<const char*>(answerBody.data) + answerBody.size, response);
-  OrthancPluginFreeMemoryBuffer(OrthancPlugins::Configuration::GetContext(), &answerBody);
-
-  if (!success ||
-      response.type() != Json::objectValue ||
-      !response.isMember("00081199"))
-  {
-    OrthancPlugins::Configuration::LogError("Unable to parse STOW-RS JSON response from DICOMweb server " + server.GetUrl());
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-  }
-
-  size_t size;
-  if (!GetSequenceSize(size, response, "00081199", true, server.GetUrl()) ||
-      size != countInstances)
-  {
-    OrthancPlugins::Configuration::LogError("The STOW-RS server was only able to receive " + 
-                                            boost::lexical_cast<std::string>(size) + " instances out of " +
-                                            boost::lexical_cast<std::string>(countInstances));
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
-  }
-
-  if (GetSequenceSize(size, response, "00081198", false, server.GetUrl()) &&
-      size != 0)
-  {
-    OrthancPlugins::Configuration::LogError("The response from the STOW-RS server contains " + 
-                                            boost::lexical_cast<std::string>(size) + 
-                                            " items in its Failed SOP Sequence (0008,1198) tag");
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);    
-  }
-
-  if (GetSequenceSize(size, response, "0008119A", false, server.GetUrl()) &&
-      size != 0)
-  {
-    OrthancPlugins::Configuration::LogError("The response from the STOW-RS server contains " + 
-                                            boost::lexical_cast<std::string>(size) + 
-                                            " items in its Other Failures Sequence (0008,119A) tag");
-    throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);    
-  }
-}
-
-
 static void ParseRestRequest(std::list<std::string>& instances /* out */,
                              std::map<std::string, std::string>& httpHeaders /* out */,
                              const OrthancPluginHttpRequest* request /* in */)
@@ -285,7 +177,53 @@
     std::string body;
     chunks.Flatten(body);
 
-    SendStowRequest(server, httpHeaders, body, countInstances);
+    OrthancPlugins::MemoryBuffer answerBody(OrthancPlugins::Configuration::GetContext());
+    std::map<std::string, std::string> answerHeaders;
+    OrthancPlugins::CallServer(answerBody, answerHeaders, server, OrthancPluginHttpMethod_Post,
+                               httpHeaders, "studies", body);
+
+    Json::Value response;
+    Json::Reader reader;
+    bool success = reader.parse(reinterpret_cast<const char*>((*answerBody)->data),
+                                reinterpret_cast<const char*>((*answerBody)->data) + (*answerBody)->size, response);
+    answerBody.Clear();
+
+    if (!success ||
+        response.type() != Json::objectValue ||
+        !response.isMember("00081199"))
+    {
+      OrthancPlugins::Configuration::LogError("Unable to parse STOW-RS JSON response from DICOMweb server " + server.GetUrl());
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+
+    size_t size;
+    if (!GetSequenceSize(size, response, "00081199", true, server.GetUrl()) ||
+        size != countInstances)
+    {
+      OrthancPlugins::Configuration::LogError("The STOW-RS server was only able to receive " + 
+                                              boost::lexical_cast<std::string>(size) + " instances out of " +
+                                              boost::lexical_cast<std::string>(countInstances));
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+
+    if (GetSequenceSize(size, response, "00081198", false, server.GetUrl()) &&
+        size != 0)
+    {
+      OrthancPlugins::Configuration::LogError("The response from the STOW-RS server contains " + 
+                                              boost::lexical_cast<std::string>(size) + 
+                                              " items in its Failed SOP Sequence (0008,1198) tag");
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);    
+    }
+
+    if (GetSequenceSize(size, response, "0008119A", false, server.GetUrl()) &&
+        size != 0)
+    {
+      OrthancPlugins::Configuration::LogError("The response from the STOW-RS server contains " + 
+                                              boost::lexical_cast<std::string>(size) + 
+                                              " items in its Other Failures Sequence (0008,119A) tag");
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);    
+    }
+
     countInstances = 0;
   }
 }