Mercurial > hg > orthanc-dicomweb
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; } }