Mercurial > hg > orthanc-dicomweb
changeset 135:36be09e49ad1 dev
reorganization
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 24 Jun 2016 12:06:03 +0200 |
parents | 4d2a2e39c25a |
children | 66ff02eefca6 |
files | Plugin/DicomWebClient.cpp Plugin/DicomWebClient.h Plugin/Plugin.cpp Plugin/WadoRs.cpp |
diffstat | 4 files changed, 309 insertions(+), 305 deletions(-) [+] |
line wrap: on
line diff
--- a/Plugin/DicomWebClient.cpp Fri Jun 24 12:01:33 2016 +0200 +++ b/Plugin/DicomWebClient.cpp Fri Jun 24 12:06:03 2016 +0200 @@ -25,6 +25,7 @@ #include <json/reader.h> #include <list> +#include <set> #include <boost/lexical_cast.hpp> #include "../Orthanc/Core/ChunkedBuffer.h" @@ -301,3 +302,302 @@ std::string answer = "{}\n"; OrthancPluginAnswerBuffer(OrthancPlugins::Configuration::GetContext(), output, answer.c_str(), answer.size(), "application/json"); } + + +static bool GetStringValue(std::string& target, + const Json::Value& json, + const std::string& key) +{ + if (json.type() != Json::objectValue) + { + throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat); + } + else if (!json.isMember(key)) + { + target.clear(); + return false; + } + else if (json[key].type() != Json::stringValue) + { + OrthancPlugins::Configuration::LogError("The field \"" + key + "\" in a JSON object should be a string"); + throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat); + } + else + { + target = json[key].asString(); + return true; + } +} + + +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"); + return; + } + + Orthanc::WebServiceParameters server(OrthancPlugins::DicomWebServers::GetInstance().GetServer(request->groups[0])); + + std::string tmp; + Json::Value body; + Json::Reader reader; + if (!reader.parse(request->body, request->body + request->bodySize, body) || + body.type() != Json::objectValue || + !GetStringValue(tmp, body, URI)) + { + OrthancPlugins::Configuration::LogError("A request to the DICOMweb STOW-RS client must provide a JSON object " + "with the field \"Uri\" containing the URI of interest"); + throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat); + } + + std::map<std::string, std::string> getArguments; + OrthancPlugins::ParseAssociativeArray(getArguments, body, GET_ARGUMENTS); + + std::string uri; + OrthancPlugins::UriEncode(uri, tmp, 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); + + if (key == "content-type") + { + contentType = it->second; + } + else if (key == "transfer-encoding") + { + // Do not forward 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()); +} + + + +static void RetrieveFromServerInternal(std::set<std::string>& instances, + const Orthanc::WebServiceParameters& server, + const std::map<std::string, std::string>& httpHeaders, + const Json::Value& resource) +{ + static const std::string STUDY = "Study"; + static const std::string SERIES = "Series"; + static const std::string INSTANCE = "Instance"; + static const std::string MULTIPART_RELATED = "multipart/related"; + static const std::string APPLICATION_DICOM = "application/dicom"; + + if (resource.type() != Json::objectValue) + { + OrthancPlugins::Configuration::LogError("Resources of interest for the DICOMweb WADO-RS Retrieve client " + "must be provided as a JSON object"); + throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat); + } + + std::string study, series, instance; + if (!GetStringValue(study, resource, STUDY) || + study.empty()) + { + OrthancPlugins::Configuration::LogError("A non-empty \"" + STUDY + "\" field is mandatory for the " + "DICOMweb WADO-RS Retrieve client"); + throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat); + } + + GetStringValue(series, resource, SERIES); + GetStringValue(instance, resource, INSTANCE); + + if (series.empty() && + !instance.empty()) + { + OrthancPlugins::Configuration::LogError("When specifying a \"" + INSTANCE + "\" field in a call to DICOMweb " + "WADO-RS Retrieve client, the \"" + SERIES + "\" field is mandatory"); + throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat); + } + + std::string uri = "studies/" + study; + if (!series.empty()) + { + uri += "/" + series; + if (!instance.empty()) + { + uri += "/" + instance; + } + } + + OrthancPlugins::MemoryBuffer answerBody(OrthancPlugins::Configuration::GetContext()); + std::map<std::string, std::string> answerHeaders; + OrthancPlugins::CallServer(answerBody, answerHeaders, server, OrthancPluginHttpMethod_Get, httpHeaders, uri, ""); + + std::vector<std::string> contentType; + for (std::map<std::string, std::string>::const_iterator + it = answerHeaders.begin(); it != answerHeaders.end(); ++it) + { + std::string s = Orthanc::Toolbox::StripSpaces(it->first); + Orthanc::Toolbox::ToLowerCase(s); + if (s == "content-type") + { + Orthanc::Toolbox::TokenizeString(contentType, it->second, ';'); + break; + } + } + + if (contentType.empty()) + { + OrthancPlugins::Configuration::LogError("No Content-Type provided by the remote WADO-RS server"); + throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol); + } + + Orthanc::Toolbox::ToLowerCase(contentType[0]); + if (Orthanc::Toolbox::StripSpaces(contentType[0]) != MULTIPART_RELATED) + { + OrthancPlugins::Configuration::LogError("The remote WADO-RS server answers with a \"" + contentType[0] + + "\" Content-Type, but \"" + MULTIPART_RELATED + "\" is expected"); + throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol); + } + + std::string type, boundary; + for (size_t i = 1; i < contentType.size(); i++) + { + std::vector<std::string> tokens; + Orthanc::Toolbox::TokenizeString(tokens, contentType[i], '='); + + if (tokens.size() == 2) + { + std::string s = Orthanc::Toolbox::StripSpaces(tokens[0]); + Orthanc::Toolbox::ToLowerCase(s); + + if (s == "type") + { + type = Orthanc::Toolbox::StripSpaces(tokens[1]); + Orthanc::Toolbox::ToLowerCase(type); + } + else if (s == "boundary") + { + boundary = Orthanc::Toolbox::StripSpaces(tokens[1]); + } + } + } + + if (type != APPLICATION_DICOM) + { + OrthancPlugins::Configuration::LogError("The remote WADO-RS server answers with a \"" + type + + "\" multipart Content-Type, but \"" + APPLICATION_DICOM + "\" is expected"); + throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol); + } + + if (boundary.empty()) + { + OrthancPlugins::Configuration::LogError("The remote WADO-RS server does not provide a boundary for its multipart answer"); + throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol); + } + + std::vector<OrthancPlugins::MultipartItem> parts; + OrthancPlugins::ParseMultipartBody(parts, OrthancPlugins::Configuration::GetContext(), + reinterpret_cast<const char*>(answerBody.GetData()), + answerBody.GetSize(), boundary); + + OrthancPlugins::Configuration::LogInfo("The remote WADO-RS server has provided " + + boost::lexical_cast<std::string>(parts.size()) + + " DICOM instances"); + + for (size_t i = 0; i < parts.size(); i++) + { + if (parts[i].contentType_ != APPLICATION_DICOM) + { + OrthancPlugins::Configuration::LogError("The remote WADO-RS server has provided a non-DICOM file in its multipart answer"); + throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol); + } + + OrthancPlugins::MemoryBuffer tmp(OrthancPlugins::Configuration::GetContext()); + tmp.RestApiPost("/instances", parts[i].data_, parts[i].size_, false); + + Json::Value result; + tmp.ToJson(result); + + if (result.type() != Json::objectValue || + !result.isMember("ID") || + result["ID"].type() != Json::stringValue) + { + throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError); + } + else + { + instances.insert(result["ID"].asString()); + } + } +} + + + +void RetrieveFromServer(OrthancPluginRestOutput* output, + const char* /*url*/, + const OrthancPluginHttpRequest* request) +{ + static const std::string RESOURCES("Resources"); + static const char* HTTP_HEADERS = "HttpHeaders"; + + if (request->method != OrthancPluginHttpMethod_Post) + { + OrthancPluginSendMethodNotAllowed(OrthancPlugins::Configuration::GetContext(), output, "POST"); + return; + } + + Orthanc::WebServiceParameters server(OrthancPlugins::DicomWebServers::GetInstance().GetServer(request->groups[0])); + + Json::Value body; + Json::Reader reader; + if (!reader.parse(request->body, request->body + request->bodySize, body) || + body.type() != Json::objectValue || + !body.isMember(RESOURCES) || + body[RESOURCES].type() != Json::arrayValue) + { + OrthancPlugins::Configuration::LogError("A request to the DICOMweb WADO-RS Retrieve client must provide a JSON object " + "with the field \"" + RESOURCES + "\" containing an array of resources"); + throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat); + } + + std::map<std::string, std::string> httpHeaders; + OrthancPlugins::ParseAssociativeArray(httpHeaders, body, HTTP_HEADERS); + + std::set<std::string> instances; + for (Json::Value::ArrayIndex i = 0; i < body[RESOURCES].size(); i++) + { + RetrieveFromServerInternal(instances, server, httpHeaders, body[RESOURCES][i]); + } + + Json::Value status = Json::objectValue; + status["Instances"] = Json::arrayValue; + + for (std::set<std::string>::const_iterator + it = instances.begin(); it != instances.end(); ++it) + { + status["Instances"].append(*it); + } + + std::string s = status.toStyledString(); + OrthancPluginAnswerBuffer(OrthancPlugins::Configuration::GetContext(), output, s.c_str(), s.size(), "application/json"); +}
--- a/Plugin/DicomWebClient.h Fri Jun 24 12:01:33 2016 +0200 +++ b/Plugin/DicomWebClient.h Fri Jun 24 12:06:03 2016 +0200 @@ -26,3 +26,11 @@ void StowClient(OrthancPluginRestOutput* output, const char* url, const OrthancPluginHttpRequest* request); + +void GetFromServer(OrthancPluginRestOutput* output, + const char* /*url*/, + const OrthancPluginHttpRequest* request); + +void RetrieveFromServer(OrthancPluginRestOutput* output, + const char* /*url*/, + const OrthancPluginHttpRequest* request);
--- a/Plugin/Plugin.cpp Fri Jun 24 12:01:33 2016 +0200 +++ b/Plugin/Plugin.cpp Fri Jun 24 12:06:03 2016 +0200 @@ -41,9 +41,6 @@ const gdcm::Dict* dictionary_ = NULL; -#include <boost/lexical_cast.hpp> - - void SwitchStudies(OrthancPluginRestOutput* output, const char* url, const OrthancPluginHttpRequest* request) @@ -130,6 +127,7 @@ Json::Value json = Json::arrayValue; json.append("get"); + json.append("retrieve"); json.append("stow"); std::string answer = json.toStyledString(); @@ -138,307 +136,6 @@ } - -static bool GetStringValue(std::string& target, - const Json::Value& json, - const std::string& key) -{ - if (json.type() != Json::objectValue) - { - throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat); - } - else if (!json.isMember(key)) - { - target.clear(); - return false; - } - else if (json[key].type() != Json::stringValue) - { - OrthancPlugins::Configuration::LogError("The field \"" + key + "\" in a JSON object should be a string"); - throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat); - } - else - { - target = json[key].asString(); - return true; - } -} - - -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"); - return; - } - - Orthanc::WebServiceParameters server(OrthancPlugins::DicomWebServers::GetInstance().GetServer(request->groups[0])); - - std::string tmp; - Json::Value body; - Json::Reader reader; - if (!reader.parse(request->body, request->body + request->bodySize, body) || - body.type() != Json::objectValue || - !GetStringValue(tmp, body, URI)) - { - OrthancPlugins::Configuration::LogError("A request to the DICOMweb STOW-RS client must provide a JSON object " - "with the field \"Uri\" containing the URI of interest"); - throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat); - } - - std::map<std::string, std::string> getArguments; - OrthancPlugins::ParseAssociativeArray(getArguments, body, GET_ARGUMENTS); - - std::string uri; - OrthancPlugins::UriEncode(uri, tmp, 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); - - if (key == "content-type") - { - contentType = it->second; - } - else if (key == "transfer-encoding") - { - // Do not forward 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()); -} - - - -static void RetrieveFromServerInternal(std::set<std::string>& instances, - const Orthanc::WebServiceParameters& server, - const std::map<std::string, std::string>& httpHeaders, - const Json::Value& resource) -{ - static const std::string STUDY = "Study"; - static const std::string SERIES = "Series"; - static const std::string INSTANCE = "Instance"; - static const std::string MULTIPART_RELATED = "multipart/related"; - static const std::string APPLICATION_DICOM = "application/dicom"; - - if (resource.type() != Json::objectValue) - { - OrthancPlugins::Configuration::LogError("Resources of interest for the DICOMweb WADO-RS Retrieve client " - "must be provided as a JSON object"); - throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat); - } - - std::string study, series, instance; - if (!GetStringValue(study, resource, STUDY) || - study.empty()) - { - OrthancPlugins::Configuration::LogError("A non-empty \"" + STUDY + "\" field is mandatory for the " - "DICOMweb WADO-RS Retrieve client"); - throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat); - } - - GetStringValue(series, resource, SERIES); - GetStringValue(instance, resource, INSTANCE); - - if (series.empty() && - !instance.empty()) - { - OrthancPlugins::Configuration::LogError("When specifying a \"" + INSTANCE + "\" field in a call to DICOMweb " - "WADO-RS Retrieve client, the \"" + SERIES + "\" field is mandatory"); - throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat); - } - - std::string uri = "studies/" + study; - if (!series.empty()) - { - uri += "/" + series; - if (!instance.empty()) - { - uri += "/" + instance; - } - } - - OrthancPlugins::MemoryBuffer answerBody(OrthancPlugins::Configuration::GetContext()); - std::map<std::string, std::string> answerHeaders; - OrthancPlugins::CallServer(answerBody, answerHeaders, server, OrthancPluginHttpMethod_Get, httpHeaders, uri, ""); - - std::vector<std::string> contentType; - for (std::map<std::string, std::string>::const_iterator - it = answerHeaders.begin(); it != answerHeaders.end(); ++it) - { - std::string s = Orthanc::Toolbox::StripSpaces(it->first); - Orthanc::Toolbox::ToLowerCase(s); - if (s == "content-type") - { - Orthanc::Toolbox::TokenizeString(contentType, it->second, ';'); - break; - } - } - - if (contentType.empty()) - { - OrthancPlugins::Configuration::LogError("No Content-Type provided by the remote WADO-RS server"); - throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol); - } - - Orthanc::Toolbox::ToLowerCase(contentType[0]); - if (Orthanc::Toolbox::StripSpaces(contentType[0]) != MULTIPART_RELATED) - { - OrthancPlugins::Configuration::LogError("The remote WADO-RS server answers with a \"" + contentType[0] + - "\" Content-Type, but \"" + MULTIPART_RELATED + "\" is expected"); - throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol); - } - - std::string type, boundary; - for (size_t i = 1; i < contentType.size(); i++) - { - std::vector<std::string> tokens; - Orthanc::Toolbox::TokenizeString(tokens, contentType[i], '='); - - if (tokens.size() == 2) - { - std::string s = Orthanc::Toolbox::StripSpaces(tokens[0]); - Orthanc::Toolbox::ToLowerCase(s); - - if (s == "type") - { - type = Orthanc::Toolbox::StripSpaces(tokens[1]); - Orthanc::Toolbox::ToLowerCase(type); - } - else if (s == "boundary") - { - boundary = Orthanc::Toolbox::StripSpaces(tokens[1]); - } - } - } - - if (type != APPLICATION_DICOM) - { - OrthancPlugins::Configuration::LogError("The remote WADO-RS server answers with a \"" + type + - "\" multipart Content-Type, but \"" + APPLICATION_DICOM + "\" is expected"); - throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol); - } - - if (boundary.empty()) - { - OrthancPlugins::Configuration::LogError("The remote WADO-RS server does not provide a boundary for its multipart answer"); - throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol); - } - - std::vector<OrthancPlugins::MultipartItem> parts; - OrthancPlugins::ParseMultipartBody(parts, OrthancPlugins::Configuration::GetContext(), - reinterpret_cast<const char*>(answerBody.GetData()), - answerBody.GetSize(), boundary); - - OrthancPlugins::Configuration::LogInfo("The remote WADO-RS server has provided " + - boost::lexical_cast<std::string>(parts.size()) + - " DICOM instances"); - - for (size_t i = 0; i < parts.size(); i++) - { - if (parts[i].contentType_ != APPLICATION_DICOM) - { - OrthancPlugins::Configuration::LogError("The remote WADO-RS server has provided a non-DICOM file in its multipart answer"); - throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol); - } - - OrthancPlugins::MemoryBuffer tmp(OrthancPlugins::Configuration::GetContext()); - tmp.RestApiPost("/instances", parts[i].data_, parts[i].size_, false); - - Json::Value result; - tmp.ToJson(result); - - if (result.type() != Json::objectValue || - !result.isMember("ID") || - result["ID"].type() != Json::stringValue) - { - throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError); - } - else - { - instances.insert(result["ID"].asString()); - } - } -} - - - -void RetrieveFromServer(OrthancPluginRestOutput* output, - const char* /*url*/, - const OrthancPluginHttpRequest* request) -{ - static const std::string RESOURCES("Resources"); - static const char* HTTP_HEADERS = "HttpHeaders"; - - if (request->method != OrthancPluginHttpMethod_Post) - { - OrthancPluginSendMethodNotAllowed(OrthancPlugins::Configuration::GetContext(), output, "POST"); - return; - } - - Orthanc::WebServiceParameters server(OrthancPlugins::DicomWebServers::GetInstance().GetServer(request->groups[0])); - - Json::Value body; - Json::Reader reader; - if (!reader.parse(request->body, request->body + request->bodySize, body) || - body.type() != Json::objectValue || - !body.isMember(RESOURCES) || - body[RESOURCES].type() != Json::arrayValue) - { - OrthancPlugins::Configuration::LogError("A request to the DICOMweb WADO-RS Retrieve client must provide a JSON object " - "with the field \"" + RESOURCES + "\" containing an array of resources"); - throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat); - } - - std::map<std::string, std::string> httpHeaders; - OrthancPlugins::ParseAssociativeArray(httpHeaders, body, HTTP_HEADERS); - - std::set<std::string> instances; - for (Json::Value::ArrayIndex i = 0; i < body[RESOURCES].size(); i++) - { - RetrieveFromServerInternal(instances, server, httpHeaders, body[RESOURCES][i]); - } - - Json::Value status = Json::objectValue; - status["Instances"] = Json::arrayValue; - - for (std::set<std::string>::const_iterator - it = instances.begin(); it != instances.end(); ++it) - { - status["Instances"].append(*it); - } - - std::string s = status.toStyledString(); - OrthancPluginAnswerBuffer(OrthancPlugins::Configuration::GetContext(), output, s.c_str(), s.size(), "application/json"); -} - - - extern "C" { ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
--- a/Plugin/WadoRs.cpp Fri Jun 24 12:01:33 2016 +0200 +++ b/Plugin/WadoRs.cpp Fri Jun 24 12:06:03 2016 +0200 @@ -25,7 +25,6 @@ #include "DicomResults.h" #include "../Orthanc/Core/Toolbox.h" -#include <boost/lexical_cast.hpp> #include <memory> static bool AcceptMultipartDicom(const OrthancPluginHttpRequest* request)