Mercurial > hg > orthanc-dicomweb
changeset 582:d32e224e1758
merge
author | Alain Mazy <am@osimis.io> |
---|---|
date | Wed, 28 Jun 2023 14:38:47 +0200 |
parents | 0354c92bbc63 (current diff) 1a5b52c8e785 (diff) |
children | 13d932c8a743 |
files | NEWS Plugin/WadoRs.cpp |
diffstat | 6 files changed, 318 insertions(+), 339 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Wed Jun 28 14:29:17 2023 +0200 +++ b/NEWS Wed Jun 28 14:38:47 2023 +0200 @@ -4,6 +4,7 @@ * Speeded up instance metadata retrieval using OrthancPluginLoadDicomInstance() from SDK 1.12.1 * Support "X-Forwarded-Host" and "X-Forwarded-Proto" headers to compute BulkDataURI. * Small speed-up the studies/../series/../metadata route when in "MainDicomTags" mode. +* Fix issue #216 (Requests fail due to bad parsing of the accept HTTP header (semi-colons)) Version 1.13 (2023-02-03)
--- a/Plugin/Configuration.cpp Wed Jun 28 14:29:17 2023 +0200 +++ b/Plugin/Configuration.cpp Wed Jun 28 14:38:47 2023 +0200 @@ -67,37 +67,6 @@ } - void ParseContentType(std::string& application, - std::map<std::string, std::string>& attributes, - const std::string& header) - { - application.clear(); - attributes.clear(); - - std::vector<std::string> tokens; - Orthanc::Toolbox::TokenizeString(tokens, header, ';'); - - assert(tokens.size() > 0); - application = tokens[0]; - Orthanc::Toolbox::StripSpaces(application); - Orthanc::Toolbox::ToLowerCase(application); - - boost::regex pattern("\\s*([^=]+)\\s*=\\s*(([^=\"]+)|\"([^=\"]+)\")\\s*"); - - for (size_t i = 1; i < tokens.size(); i++) - { - boost::cmatch what; - if (boost::regex_match(tokens[i].c_str(), what, pattern)) - { - std::string key(what[1]); - std::string value(what.length(3) != 0 ? what[3] : what[4]); - Orthanc::Toolbox::ToLowerCase(key); - attributes[key] = value; - } - } - } - - void ParseAssociativeArray(std::map<std::string, std::string>& target, const Json::Value& value) {
--- a/Plugin/Configuration.h Wed Jun 28 14:29:17 2023 +0200 +++ b/Plugin/Configuration.h Wed Jun 28 14:38:47 2023 +0200 @@ -58,10 +58,6 @@ const OrthancPluginHttpRequest* request, const std::string& header); - void ParseContentType(std::string& application, - std::map<std::string, std::string>& attributes, - const std::string& header); - void ParseAssociativeArray(std::map<std::string, std::string>& target, const Json::Value& value, const std::string& key);
--- a/Plugin/WadoRs.cpp Wed Jun 28 14:29:17 2023 +0200 +++ b/Plugin/WadoRs.cpp Wed Jun 28 14:38:47 2023 +0200 @@ -24,8 +24,9 @@ #include "Configuration.h" #include "DicomWebFormatter.h" +#include <ChunkedBuffer.h> #include <Compatibility.h> -#include <ChunkedBuffer.h> +#include <HttpServer/HttpContentNegociation.h> #include <Logging.h> #include <Toolbox.h> #include <MultiThreading/SharedMessageQueue.h> @@ -64,6 +65,71 @@ +namespace +{ + class MultipartDicomNegotiation : public Orthanc::HttpContentNegociation::IHandler + { + private: + bool& transcode_; + Orthanc::DicomTransferSyntax& targetSyntax_; + + public: + MultipartDicomNegotiation( + bool& transcode, + Orthanc::DicomTransferSyntax& targetSyntax /* set only if transcoding */) : + transcode_(transcode), + targetSyntax_(targetSyntax) + { + } + + virtual void Handle(const std::string& type, + const std::string& subtype, + const Orthanc::HttpContentNegociation::Dictionary& parameters) ORTHANC_OVERRIDE + { + assert(type == "multipart" && + subtype == "related"); + + Orthanc::HttpContentNegociation::Dictionary::const_iterator found = parameters.find("type"); + + if (found != parameters.end()) + { + std::string s = found->second; + Orthanc::Toolbox::ToLowerCase(s); + if (s != "application/dicom") + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, + "This WADO-RS plugin only supports application/dicom " + "return type for DICOM retrieval (" + found->second + ")"); + } + } + + found = parameters.find("transfer-syntax"); + if (found != parameters.end()) + { + /** + * The "*" case below is related to Google Healthcare API: + * https://groups.google.com/d/msg/orthanc-users/w1Ekrsc6-U8/T2a_DoQ5CwAJ + **/ + if (found->second == "*") + { + transcode_ = false; + } + else + { + transcode_ = true; + + if (!Orthanc::LookupTransferSyntax(targetSyntax_, found->second)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, + "Unsupported transfer syntax in WADO-RS: " + found->second); + } + } + } + } + }; +} + + static void AcceptMultipartDicom(bool& transcode, Orthanc::DicomTransferSyntax& targetSyntax /* only if transcoding */, const OrthancPluginHttpRequest* request) @@ -91,177 +157,177 @@ * convention is used by the Google Cloud Platform: * https://cloud.google.com/healthcare/docs/dicom **/ + + // By default, return "multipart/related; type=application/dicom; transfer-syntax=1.2.840.10008.1.2.1" transcode = true; targetSyntax = Orthanc::DicomTransferSyntax_LittleEndianExplicit; std::string accept; - - if (!OrthancPlugins::LookupHttpHeader(accept, request, "accept")) + if (OrthancPlugins::LookupHttpHeader(accept, request, "accept")) { - return; // By default, return "multipart/related; type=application/dicom;" - } - - std::string application; - std::map<std::string, std::string> attributes; - OrthancPlugins::ParseContentType(application, attributes, accept); + Orthanc::HttpContentNegociation negotiation; - if (application != "multipart/related" && - application != "*/*") - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, - "This WADO-RS plugin cannot generate the following content type: " + accept); - } + MultipartDicomNegotiation dicom(transcode, targetSyntax); + negotiation.Register("multipart/related", dicom); - if (attributes.find("type") != attributes.end()) - { - std::string s = attributes["type"]; - Orthanc::Toolbox::ToLowerCase(s); - if (s != "application/dicom") + if (!negotiation.Apply(accept)) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, - "This WADO-RS plugin only supports application/dicom " - "return type for DICOM retrieval (" + accept + ")"); + "This WADO-RS plugin cannot generate the following content type: " + accept); } } +} - static const char* const TRANSFER_SYNTAX = "transfer-syntax"; - std::map<std::string, std::string>::const_iterator found = attributes.find(TRANSFER_SYNTAX); - if (found != attributes.end()) +namespace +{ + class AcceptMetadataJson : public Orthanc::HttpContentNegociation::IHandler + { + public: + virtual void Handle(const std::string& type, + const std::string& subtype, + const Orthanc::HttpContentNegociation::Dictionary& parameters) ORTHANC_OVERRIDE + { + assert(type == "application"); + assert(subtype == "json" || subtype == "dicom+json"); + } + }; + + class AcceptMetadataMultipart : public Orthanc::HttpContentNegociation::IHandler { - /** - * The "*" case below is related to Google Healthcare API: - * https://groups.google.com/d/msg/orthanc-users/w1Ekrsc6-U8/T2a_DoQ5CwAJ - **/ - if (found->second == "*") + private: + bool& isXml_; + + public: + AcceptMetadataMultipart(bool& isXml /* out */) : + isXml_(isXml) + { + } + + virtual void Handle(const std::string& type, + const std::string& subtype, + const Orthanc::HttpContentNegociation::Dictionary& parameters) ORTHANC_OVERRIDE { - transcode = false; - } - else - { - transcode = true; + assert(type == "multipart" && + subtype == "related"); + + Orthanc::HttpContentNegociation::Dictionary::const_iterator found = parameters.find("type"); - if (!Orthanc::LookupTransferSyntax(targetSyntax, found->second)) + if (found != parameters.end()) + { + if (found->second == "application/dicom+xml") + { + isXml_ = true; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, + "This WADO-RS plugin only supports application/dicom+xml " + "type for multipart/related accept (" + found->second + ")"); + } + } + else { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, - "Unsupported transfer syntax in WADO-RS: " + found->second); + "Missing \"type\" in multipart/related accept type"); + } + + found = parameters.find("transfer-syntax"); + if (found != parameters.end()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, + "This WADO-RS plugin cannot change the transfer syntax to " + + found->second); } } + }; +} + + +static void AcceptMetadata(const OrthancPluginHttpRequest* request, + bool& isXml) +{ + isXml = false; // By default, return application/dicom+json + + std::string accept; + if (OrthancPlugins::LookupHttpHeader(accept, request, "accept")) + { + Orthanc::HttpContentNegociation negotiation; + + AcceptMetadataJson json; + negotiation.Register("application/json", json); + negotiation.Register("application/dicom+json", json); + + AcceptMetadataMultipart multipart(isXml); + negotiation.Register("multipart/related", multipart); + + if (!negotiation.Apply(accept)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, + "This WADO-RS plugin cannot generate the following content type: " + accept); + } } } -static bool AcceptMetadata(const OrthancPluginHttpRequest* request, - bool& isXml) +namespace { - isXml = false; // By default, return application/dicom+json - - std::string accept; - if (!OrthancPlugins::LookupHttpHeader(accept, request, "accept")) - { - return true; - } - - std::string application; - std::map<std::string, std::string> attributes; - OrthancPlugins::ParseContentType(application, attributes, accept); - - std::vector<std::string> applicationTokens; - Orthanc::Toolbox::TokenizeString(applicationTokens, application, ','); - - for (size_t i = 0; i < applicationTokens.size(); i++) - { - std::string token = Orthanc::Toolbox::StripSpaces(applicationTokens[i]); - - if (token == "application/json" || - token == "application/dicom+json" || - token == "*/*") - { - return true; - } - } - - if (application != "multipart/related") + class BulkDataNegotiation : public Orthanc::HttpContentNegociation::IHandler { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, - "This WADO-RS plugin cannot generate the following content type: " + accept); - } - - if (attributes.find("type") != attributes.end()) - { - std::string s = attributes["type"]; - Orthanc::Toolbox::ToLowerCase(s); - if (s == "application/dicom+xml") - { - isXml = true; - } - else + public: + virtual void Handle(const std::string& type, + const std::string& subtype, + const Orthanc::HttpContentNegociation::Dictionary& parameters) ORTHANC_OVERRIDE { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, - "This WADO-RS plugin only supports application/dicom+xml " - "type for multipart/related accept (" + accept + ")"); + assert(type == "multipart" && + subtype == "related"); + + Orthanc::HttpContentNegociation::Dictionary::const_iterator found = parameters.find("type"); + + if (found != parameters.end()) + { + std::string s = found->second; + Orthanc::Toolbox::ToLowerCase(s); + if (s != "application/octet-stream") + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, + "This WADO-RS plugin only supports application/octet-stream " + "return type for bulk data retrieval (" + found->second + ")"); + } + } + + if (parameters.find("range") != parameters.end()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, + "This WADO-RS plugin does not support Range retrieval, " + "it can only return entire bulk data object"); + } } - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, - "Missing \"type\" in multipart/related accept type (" + accept + ")"); - } - - if (attributes.find("transfer-syntax") != attributes.end()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, - "This WADO-RS plugin cannot change the transfer syntax to " + - attributes["transfer-syntax"]); - } - - return true; + }; } +static void AcceptBulkData(const OrthancPluginHttpRequest* request) +{ + // By default, return "multipart/related; type=application/octet-stream;" -static bool AcceptBulkData(const OrthancPluginHttpRequest* request) -{ std::string accept; - if (!OrthancPlugins::LookupHttpHeader(accept, request, "accept")) + if (OrthancPlugins::LookupHttpHeader(accept, request, "accept")) { - return true; // By default, return "multipart/related; type=application/octet-stream;" - } + Orthanc::HttpContentNegociation negotiation; - std::string application; - std::map<std::string, std::string> attributes; - OrthancPlugins::ParseContentType(application, attributes, accept); + BulkDataNegotiation bulk; + negotiation.Register("multipart/related", bulk); - if (application != "multipart/related" && - application != "*/*") - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, + if (!negotiation.Apply(accept)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "This WADO-RS plugin cannot generate the following " "bulk data type: " + accept); - } - - if (attributes.find("type") != attributes.end()) - { - std::string s = attributes["type"]; - Orthanc::Toolbox::ToLowerCase(s); - if (s != "application/octet-stream") - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, - "This WADO-RS plugin only supports application/octet-stream " - "return type for bulk data retrieval (" + accept + ")"); } } - - if (attributes.find("ra,ge") != attributes.end()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, - "This WADO-RS plugin does not support Range retrieval, " - "it can only return entire bulk data object"); - } - - return true; } @@ -1127,41 +1193,36 @@ const OrthancPluginHttpRequest* request) { bool isXml; - if (!AcceptMetadata(request, isXml)) - { - OrthancPluginSendHttpStatusCode(OrthancPlugins::GetGlobalContext(), output, 400 /* Bad request */); - } - else + AcceptMetadata(request, isXml); + + const OrthancPlugins::MetadataMode mode = + OrthancPlugins::Configuration::GetMetadataMode(Orthanc::ResourceType_Study); + + MainDicomTagsCache cache; + + std::string studyOrthancId, studyInstanceUid; + if (LocateStudy(output, studyOrthancId, studyInstanceUid, request)) { - const OrthancPlugins::MetadataMode mode = - OrthancPlugins::Configuration::GetMetadataMode(Orthanc::ResourceType_Study); - - MainDicomTagsCache cache; - - std::string studyOrthancId, studyInstanceUid; - if (LocateStudy(output, studyOrthancId, studyInstanceUid, request)) - { - OrthancPlugins::DicomWebFormatter::HttpWriter writer(output, isXml); + OrthancPlugins::DicomWebFormatter::HttpWriter writer(output, isXml); - std::list<std::string> series; - std::string studyDicomUid; - GetChildrenIdentifiers(series, studyDicomUid, Orthanc::ResourceType_Study, studyOrthancId); - - for (std::list<std::string>::const_iterator s = series.begin(); s != series.end(); ++s) - { - std::list<std::string> instances; - std::string seriesDicomUid; - GetChildrenIdentifiers(instances, seriesDicomUid, Orthanc::ResourceType_Series, *s); + std::list<std::string> series; + std::string studyDicomUid; + GetChildrenIdentifiers(series, studyDicomUid, Orthanc::ResourceType_Study, studyOrthancId); - for (std::list<std::string>::const_iterator i = instances.begin(); i != instances.end(); ++i) - { - WriteInstanceMetadata(writer, mode, cache, *i, studyInstanceUid, seriesDicomUid, - OrthancPlugins::Configuration::GetBasePublicUrl(request)); - } + for (std::list<std::string>::const_iterator s = series.begin(); s != series.end(); ++s) + { + std::list<std::string> instances; + std::string seriesDicomUid; + GetChildrenIdentifiers(instances, seriesDicomUid, Orthanc::ResourceType_Series, *s); + + for (std::list<std::string>::const_iterator i = instances.begin(); i != instances.end(); ++i) + { + WriteInstanceMetadata(writer, mode, cache, *i, studyInstanceUid, seriesDicomUid, + OrthancPlugins::Configuration::GetBasePublicUrl(request)); } + } - writer.Send(); - } + writer.Send(); } } @@ -1278,120 +1339,112 @@ const char* url, const OrthancPluginHttpRequest* request) { - OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); - boost::posix_time::ptime start = boost::posix_time::microsec_clock::universal_time(); boost::posix_time::ptime stop = boost::posix_time::microsec_clock::universal_time(); // stop = boost::posix_time::microsec_clock::universal_time(); LOG(WARNING) << "start " << (stop-start).total_milliseconds(); bool isXml; - if (!AcceptMetadata(request, isXml)) + AcceptMetadata(request, isXml); + + const OrthancPlugins::MetadataMode mode = + OrthancPlugins::Configuration::GetMetadataMode(Orthanc::ResourceType_Series); + + MainDicomTagsCache cache; + + std::string seriesOrthancId, studyInstanceUid, seriesInstanceUid; + + if (LocateSeries(output, seriesOrthancId, studyInstanceUid, seriesInstanceUid, request)) { - OrthancPluginSendHttpStatusCode(context, output, 400 /* Bad request */); - } - else - { - const OrthancPlugins::MetadataMode mode = - OrthancPlugins::Configuration::GetMetadataMode(Orthanc::ResourceType_Series); - - MainDicomTagsCache cache; + OrthancPlugins::DicomWebFormatter::HttpWriter writer(output, isXml); + + ChildrenMainDicomMaps instancesDicomMaps; + std::list<std::string> instancesIds; + std::string seriesDicomUid; + + size_t threadCount = 4; + bool oneLargeQuery = false; // we keep this code here for future use once we'll have optimized Orthanc API /series/.../instances?full to minimize the SQL queries + // right now, it is faster to call /instances/..?full in each worker but, later, it should be more efficient with a large SQL query in Orthanc - std::string seriesOrthancId, studyInstanceUid, seriesInstanceUid; - - if (LocateSeries(output, seriesOrthancId, studyInstanceUid, seriesInstanceUid, request)) + if (oneLargeQuery) { - OrthancPlugins::DicomWebFormatter::HttpWriter writer(output, isXml); + GetChildrenMainDicomTags(instancesDicomMaps, seriesDicomUid, Orthanc::ResourceType_Series, seriesOrthancId); + } + else + { + GetChildrenIdentifiers(instancesIds, seriesDicomUid, Orthanc::ResourceType_Series, seriesOrthancId); + } - ChildrenMainDicomMaps instancesDicomMaps; - std::list<std::string> instancesIds; - std::string seriesDicomUid; + if (threadCount > 1) + { + // span a few workers to get the tags from the core and serialize them + Orthanc::SharedMessageQueue instancesQueue; + std::vector<boost::shared_ptr<boost::thread> > instancesWorkers; + boost::mutex writerMutex; + std::vector<boost::shared_ptr<InstanceWorkerData> > instancesWorkersData; + std::string wadoBase = OrthancPlugins::Configuration::GetBasePublicUrl(request); - size_t threadCount = 4; - bool oneLargeQuery = false; // we keep this code here for future use once we'll have optimized Orthanc API /series/.../instances?full to minimize the SQL queries - // right now, it is faster to call /instances/..?full in each worker but, later, it should be more efficient with a large SQL query in Orthanc + for (size_t t; t < threadCount; t++) + { + InstanceWorkerData* threadData = new InstanceWorkerData(boost::lexical_cast<std::string>(t), &instancesQueue, wadoBase); + instancesWorkersData.push_back(boost::shared_ptr<InstanceWorkerData>(threadData)); + instancesWorkers.push_back(boost::shared_ptr<boost::thread>(new boost::thread(InstanceWorkerThread, threadData))); + } if (oneLargeQuery) { - GetChildrenMainDicomTags(instancesDicomMaps, seriesDicomUid, Orthanc::ResourceType_Series, seriesOrthancId); + for (ChildrenMainDicomMaps::const_iterator i = instancesDicomMaps.begin(); i != instancesDicomMaps.end(); ++i) + { + std::string bulkRoot = (wadoBase + + "studies/" + studyInstanceUid + + "/series/" + seriesInstanceUid + + "/instances/" + i->second->GetStringValue(Orthanc::DICOM_TAG_SOP_INSTANCE_UID, "", false) + "/bulk"); + + instancesQueue.Enqueue(new InstanceToLoad(i->first, bulkRoot, writerMutex, writer, isXml, OrthancPluginDicomWebBinaryMode_BulkDataUri, studyInstanceUid, seriesInstanceUid)); + } } else { - GetChildrenIdentifiers(instancesIds, seriesDicomUid, Orthanc::ResourceType_Series, seriesOrthancId); - } - - if (threadCount > 1) - { - // span a few workers to get the tags from the core and serialize them - Orthanc::SharedMessageQueue instancesQueue; - std::vector<boost::shared_ptr<boost::thread> > instancesWorkers; - boost::mutex writerMutex; - std::vector<boost::shared_ptr<InstanceWorkerData> > instancesWorkersData; - std::string wadoBase = OrthancPlugins::Configuration::GetBasePublicUrl(request); - - for (size_t t; t < threadCount; t++) - { - InstanceWorkerData* threadData = new InstanceWorkerData(boost::lexical_cast<std::string>(t), &instancesQueue, wadoBase); - instancesWorkersData.push_back(boost::shared_ptr<InstanceWorkerData>(threadData)); - instancesWorkers.push_back(boost::shared_ptr<boost::thread>(new boost::thread(InstanceWorkerThread, threadData))); - } - - if (oneLargeQuery) - { - for (ChildrenMainDicomMaps::const_iterator i = instancesDicomMaps.begin(); i != instancesDicomMaps.end(); ++i) - { - std::string bulkRoot = (wadoBase + - "studies/" + studyInstanceUid + - "/series/" + seriesInstanceUid + - "/instances/" + i->second->GetStringValue(Orthanc::DICOM_TAG_SOP_INSTANCE_UID, "", false) + "/bulk"); - - instancesQueue.Enqueue(new InstanceToLoad(i->first, bulkRoot, writerMutex, writer, isXml, OrthancPluginDicomWebBinaryMode_BulkDataUri, studyInstanceUid, seriesInstanceUid)); - } - } - else + for (std::list<std::string>::const_iterator i = instancesIds.begin(); i != instancesIds.end(); ++i) { - for (std::list<std::string>::const_iterator i = instancesIds.begin(); i != instancesIds.end(); ++i) - { - instancesQueue.Enqueue(new InstanceToLoad(*i, "", writerMutex, writer, isXml, OrthancPluginDicomWebBinaryMode_BulkDataUri, studyInstanceUid, seriesInstanceUid)); - } - } - - stop = boost::posix_time::microsec_clock::universal_time(); LOG(WARNING) << "enqueued " << (stop-start).total_milliseconds(); - - // send a dummy "exit" message to all workers such that they stop waiting for messages on the queue - for (size_t i = 0; i < instancesWorkers.size(); i++) - { - instancesQueue.Enqueue(new InstanceToLoad(EXIT_WORKER_MESSAGE, "", writerMutex, writer, isXml, OrthancPluginDicomWebBinaryMode_BulkDataUri, studyInstanceUid, seriesInstanceUid)); - } - - stop = boost::posix_time::microsec_clock::universal_time(); LOG(WARNING) << "waiting " << (stop-start).total_milliseconds(); - - for (size_t i = 0; i < instancesWorkers.size(); i++) - { - if (instancesWorkers[i]->joinable()) - { - instancesWorkers[i]->join(); - } - } - - instancesWorkers.clear(); - } - else - { // old single threaded code - std::list<std::string> instances; - std::string seriesDicomUid; // not used - - GetChildrenIdentifiers(instances, seriesDicomUid, Orthanc::ResourceType_Series, seriesOrthancId); - - for (std::list<std::string>::const_iterator i = instances.begin(); i != instances.end(); ++i) - { - WriteInstanceMetadata(writer, mode, cache, *i, studyInstanceUid, seriesInstanceUid, - OrthancPlugins::Configuration::GetBasePublicUrl(request)); + instancesQueue.Enqueue(new InstanceToLoad(*i, "", writerMutex, writer, isXml, OrthancPluginDicomWebBinaryMode_BulkDataUri, studyInstanceUid, seriesInstanceUid)); } } + stop = boost::posix_time::microsec_clock::universal_time(); LOG(WARNING) << "enqueued " << (stop-start).total_milliseconds(); - writer.Send(); + // send a dummy "exit" message to all workers such that they stop waiting for messages on the queue + for (size_t i = 0; i < instancesWorkers.size(); i++) + { + instancesQueue.Enqueue(new InstanceToLoad(EXIT_WORKER_MESSAGE, "", writerMutex, writer, isXml, OrthancPluginDicomWebBinaryMode_BulkDataUri, studyInstanceUid, seriesInstanceUid)); + } + + stop = boost::posix_time::microsec_clock::universal_time(); LOG(WARNING) << "waiting " << (stop-start).total_milliseconds(); + + for (size_t i = 0; i < instancesWorkers.size(); i++) + { + if (instancesWorkers[i]->joinable()) + { + instancesWorkers[i]->join(); + } + } + + instancesWorkers.clear(); } + else + { // old single threaded code + std::list<std::string> instances; + std::string seriesDicomUid; // not used + + GetChildrenIdentifiers(instances, seriesDicomUid, Orthanc::ResourceType_Series, seriesOrthancId); + + for (std::list<std::string>::const_iterator i = instances.begin(); i != instances.end(); ++i) + { + WriteInstanceMetadata(writer, mode, cache, *i, studyInstanceUid, seriesInstanceUid, + OrthancPlugins::Configuration::GetBasePublicUrl(request)); + } + } + + writer.Send(); } } @@ -1401,22 +1454,17 @@ const OrthancPluginHttpRequest* request) { bool isXml; - if (!AcceptMetadata(request, isXml)) - { - OrthancPluginSendHttpStatusCode(OrthancPlugins::GetGlobalContext(), output, 400 /* Bad request */); - } - else - { - MainDicomTagsCache cache; + AcceptMetadata(request, isXml); + + MainDicomTagsCache cache; - std::string orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid; - if (LocateInstance(output, orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid, request)) - { - OrthancPlugins::DicomWebFormatter::HttpWriter writer(output, isXml); - WriteInstanceMetadata(writer, OrthancPlugins::MetadataMode_Full, cache, orthancId, studyInstanceUid, - seriesInstanceUid, OrthancPlugins::Configuration::GetBasePublicUrl(request)); - writer.Send(); - } + std::string orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid; + if (LocateInstance(output, orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid, request)) + { + OrthancPlugins::DicomWebFormatter::HttpWriter writer(output, isXml); + WriteInstanceMetadata(writer, OrthancPlugins::MetadataMode_Full, cache, orthancId, studyInstanceUid, + seriesInstanceUid, OrthancPlugins::Configuration::GetBasePublicUrl(request)); + writer.Send(); } } @@ -1427,11 +1475,7 @@ { OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); - if (!AcceptBulkData(request)) - { - OrthancPluginSendHttpStatusCode(context, output, 400 /* Bad request */); - return; - } + AcceptBulkData(request); std::string orthancId, studyInstanceUid, seriesInstanceUid, sopInstanceUid; OrthancPlugins::MemoryBuffer content;
--- a/Plugin/WadoRsRetrieveRendered.cpp Wed Jun 28 14:29:17 2023 +0200 +++ b/Plugin/WadoRsRetrieveRendered.cpp Wed Jun 28 14:38:47 2023 +0200 @@ -935,7 +935,7 @@ tags[PHOTOMETRIC_INTERPRETATION].type() == Json::stringValue) { std::string s = tags[PHOTOMETRIC_INTERPRETATION].asString(); - Orthanc::Toolbox::StripSpaces(s); + s = Orthanc::Toolbox::StripSpaces(s); if (s == "MONOCHROME1") { invert = true;
--- a/UnitTestsSources/UnitTestsMain.cpp Wed Jun 28 14:29:17 2023 +0200 +++ b/UnitTestsSources/UnitTestsMain.cpp Wed Jun 28 14:38:47 2023 +0200 @@ -32,37 +32,6 @@ OrthancPluginContext* context_ = NULL; -// TODO => Remove this test (now in Orthanc core) -TEST(ContentType, Parse) -{ - std::string c; - std::map<std::string, std::string> a; - - ParseContentType(c, a, "Multipart/Related; TYPE=Application/Dicom; Boundary=heLLO"); - ASSERT_EQ(c, "multipart/related"); - ASSERT_EQ(2u, a.size()); - ASSERT_EQ(a["type"], "Application/Dicom"); - ASSERT_EQ(a["boundary"], "heLLO"); - - // The WADO-RS client must support the case where the WADO-RS server - // escapes the "type" subfield in the Content-Type header - // cf. https://tools.ietf.org/html/rfc7231#section-3.1.1.1 - ParseContentType(c, a, "Multipart/Related; TYPE=\"Application/Dicom\" ; Boundary=heLLO"); - ASSERT_EQ(c, "multipart/related"); - ASSERT_EQ(2u, a.size()); - ASSERT_EQ(a["type"], "Application/Dicom"); - ASSERT_EQ(a["boundary"], "heLLO"); - - ParseContentType(c, a, ""); - ASSERT_TRUE(c.empty()); - ASSERT_EQ(0u, a.size()); - - ParseContentType(c, a, "multipart/related"); - ASSERT_EQ(c, "multipart/related"); - ASSERT_EQ(0u, a.size()); -} - - TEST(DicomWebServers, Serialization) { std::list<std::string> servers;