Skip to content
Commits on Source (11)
repo: d5f45924411123cfd02d035fd50b8e37536eadef
node: ce4e5f0769c81584a8851cff3d1b0fd56283574f
branch: OrthancDicomWeb-0.6
node: 1e052095bb1d3e3ac5d0c3a5253699867d784579
branch: OrthancDicomWeb-1.0
latesttag: null
latesttagdistance: 250
changessincelatesttag: 264
latesttagdistance: 323
changessincelatesttag: 339
......@@ -21,13 +21,13 @@ cmake_minimum_required(VERSION 2.8)
project(OrthancDicomWeb)
set(ORTHANC_DICOM_WEB_VERSION "0.6")
set(ORTHANC_DICOM_WEB_VERSION "1.0")
if (ORTHANC_DICOM_WEB_VERSION STREQUAL "mainline")
set(ORTHANC_FRAMEWORK_VERSION "mainline")
set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg")
else()
set(ORTHANC_FRAMEWORK_VERSION "1.5.5")
set(ORTHANC_FRAMEWORK_VERSION "1.5.7")
set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "web")
endif()
......@@ -42,7 +42,11 @@ set(ORTHANC_FRAMEWORK_ROOT "" CACHE STRING "Path to the Orthanc source directory
# Advanced parameters to fine-tune linking against system libraries
set(USE_SYSTEM_GDCM ON CACHE BOOL "Use the system version of Grassroot DICOM (GDCM)")
set(USE_SYSTEM_ORTHANC_SDK ON CACHE BOOL "Use the system version of the Orthanc plugin SDK")
set(ORTHANC_SDK_VERSION "1.5.4" CACHE STRING "Version of the Orthanc plugin SDK to use, if not using the system version (can be \"1.5.4\", or \"framework\")")
set(ORTHANC_SDK_VERSION "1.5.7" CACHE STRING "Version of the Orthanc plugin SDK to use, if not using the system version (can be \"1.5.4\", \"1.5.7\", or \"framework\")")
set(BUILD_BOOTSTRAP_VUE OFF CACHE BOOL "Compile Bootstrap-Vue from sources")
set(BUILD_BABEL_POLYFILL OFF CACHE BOOL "Retrieve babel-polyfill from npm")
......@@ -60,13 +64,15 @@ set(USE_BOOST_ICONV ON)
include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkConfiguration.cmake)
include_directories(${ORTHANC_ROOT})
include(${CMAKE_SOURCE_DIR}/Resources/CMake/GdcmConfiguration.cmake)
include(${CMAKE_SOURCE_DIR}/Resources/CMake/JavaScriptLibraries.cmake)
if (STATIC_BUILD OR NOT USE_SYSTEM_ORTHANC_SDK)
if (ORTHANC_SDK_VERSION STREQUAL "1.5.4")
include_directories(${CMAKE_SOURCE_DIR}/Resources/Orthanc/Sdk-1.5.4)
elseif (ORTHANC_SDK_VERSION STREQUAL "1.5.7")
include_directories(${CMAKE_SOURCE_DIR}/Resources/Orthanc/Sdk-1.5.7)
elseif (ORTHANC_SDK_VERSION STREQUAL "framework")
include_directories(${ORTHANC_ROOT}/Plugins/Include)
else()
......@@ -110,11 +116,30 @@ if (APPLE)
endif()
if (STANDALONE_BUILD)
add_definitions(-DORTHANC_STANDALONE=1)
set(ADDITIONAL_RESOURCES
ORTHANC_EXPLORER ${CMAKE_SOURCE_DIR}/Plugin/OrthancExplorer.js
WEB_APPLICATION ${CMAKE_SOURCE_DIR}/WebApplication/
)
else()
add_definitions(-DORTHANC_STANDALONE=0)
endif()
EmbedResources(
--no-upcase-check
${ADDITIONAL_RESOURCES}
JAVASCRIPT_LIBS ${JAVASCRIPT_LIBS_DIR}
)
include_directories(${ORTHANC_ROOT}/Core) # To access "OrthancException.h"
add_definitions(
-DHAS_ORTHANC_EXCEPTION=1
-DORTHANC_ENABLE_LOGGING_PLUGIN=1
-DDICOMWEB_CLIENT_PATH="${CMAKE_SOURCE_DIR}/WebApplication/"
)
set(CORE_SOURCES
......
......@@ -2,6 +2,23 @@ Pending changes in the mainline
===============================
Version 1.0 (2019-06-26)
========================
=> Recommended SDK version: 1.5.7 <=
=> Minimum SDK version: 1.5.4 <=
* Web user interface to QIDO-RS, WADO-RS and STOW-RS client
* First implementation of WADO-RS "Retrieve Rendered Transaction"
* WADO-RS and STOW-RS client now create Orthanc jobs
* Support "Transfer-Encoding: chunked" to reduce memory consumption in STOW-RS
(provided the SDK version is above 1.5.7)
* New URI: /dicom-web/servers/.../qido
* New URI: /dicom-web/servers/.../delete
* Handling of the HTTP header "Forwarded" for WADO-RS
* Full refactoring of multipart parsing
Version 0.6 (2019-02-27)
========================
......
......@@ -21,15 +21,17 @@
#include "Configuration.h"
#include "DicomWebServers.h"
#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
#include <Core/Toolbox.h>
#include <fstream>
#include <json/reader.h>
#include <boost/regex.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include "DicomWebServers.h"
#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
#include <Core/Toolbox.h>
namespace OrthancPlugins
{
......@@ -85,220 +87,200 @@ namespace OrthancPlugins
}
static const boost::regex MULTIPART_HEADERS_ENDING("(.*?\r\n)\r\n(.*)");
static const boost::regex MULTIPART_HEADERS_LINE(".*?\r\n");
static void ParseMultipartHeaders(bool& hasLength /* out */,
size_t& length /* out */,
std::string& contentType /* out */,
const char* startHeaders,
const char* endHeaders)
{
hasLength = false;
contentType = "application/octet-stream";
// Loop over the HTTP headers of this multipart item
boost::cregex_token_iterator it(startHeaders, endHeaders, MULTIPART_HEADERS_LINE, 0);
boost::cregex_token_iterator iteratorEnd;
for (; it != iteratorEnd; ++it)
void ParseAssociativeArray(std::map<std::string, std::string>& target,
const Json::Value& value)
{
const std::string line(*it);
size_t colon = line.find(':');
size_t eol = line.find('\r');
if (colon != std::string::npos &&
eol != std::string::npos &&
colon < eol &&
eol + 2 == line.length())
if (value.type() != Json::objectValue)
{
std::string key = Orthanc::Toolbox::StripSpaces(line.substr(0, colon));
Orthanc::Toolbox::ToLowerCase(key);
throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
"The JSON object is not a JSON associative array as expected");
}
const std::string value = Orthanc::Toolbox::StripSpaces(line.substr(colon + 1, eol - colon - 1));
Json::Value::Members names = value.getMemberNames();
if (key == "content-length")
{
try
{
int tmp = boost::lexical_cast<int>(value);
if (tmp >= 0)
for (size_t i = 0; i < names.size(); i++)
{
hasLength = true;
length = tmp;
}
}
catch (boost::bad_lexical_cast&)
if (value[names[i]].type() != Json::stringValue)
{
LogWarning("Unable to parse the Content-Length of a multipart item");
}
throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
"Value \"" + names[i] + "\" in the associative array "
"is not a string as expected");
}
else if (key == "content-type")
else
{
contentType = value;
}
target[names[i]] = value[names[i]].asString();
}
}
}
static const char* ParseMultipartItem(std::vector<MultipartItem>& result,
const char* start,
const char* end,
const boost::regex& nextSeparator)
void ParseAssociativeArray(std::map<std::string, std::string>& target,
const Json::Value& value,
const std::string& key)
{
// Just before "start", it is guaranteed that "--[BOUNDARY]\r\n" is present
boost::cmatch what;
if (!boost::regex_match(start, end, what, MULTIPART_HEADERS_ENDING, boost::match_perl))
if (value.type() != Json::objectValue)
{
// Cannot find the HTTP headers of this multipart item
throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
"This is not a JSON object");
}
// Some aliases for more clarity
assert(what[1].first == start);
const char* startHeaders = what[1].first;
const char* endHeaders = what[1].second;
const char* startBody = what[2].first;
bool hasLength;
size_t length;
std::string contentType;
ParseMultipartHeaders(hasLength, length, contentType, startHeaders, endHeaders);
if (value.isMember(key))
{
ParseAssociativeArray(target, value[key]);
}
else
{
target.clear();
}
}
boost::cmatch separator;
if (hasLength)
bool ParseTag(Orthanc::DicomTag& target,
const std::string& name)
{
if (!boost::regex_match(startBody + length, end, separator, nextSeparator, boost::match_perl) ||
startBody + length != separator[1].first)
OrthancPluginDictionaryEntry entry;
if (OrthancPluginLookupDictionary(OrthancPlugins::GetGlobalContext(), &entry, name.c_str()) == OrthancPluginErrorCode_Success)
{
// Cannot find the separator after skipping the "Content-Length" bytes
throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
}
target = Orthanc::DicomTag(entry.group, entry.element);
return true;
}
else
{
if (!boost::regex_match(startBody, end, separator, nextSeparator, boost::match_perl))
{
// No more occurrence of the boundary separator
throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
return false;
}
}
MultipartItem item;
item.data_ = startBody;
item.size_ = separator[1].first - startBody;
item.contentType_ = contentType;
result.push_back(item);
return separator[1].second; // Return the end of the separator
void ParseJsonBody(Json::Value& target,
const OrthancPluginHttpRequest* request)
{
Json::Reader reader;
if (!reader.parse(reinterpret_cast<const char*>(request->body),
reinterpret_cast<const char*>(request->body) + request->bodySize, target))
{
throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
"A JSON file was expected");
}
}
void ParseMultipartBody(std::vector<MultipartItem>& result,
const char* body,
const uint64_t bodySize,
const std::string& boundary)
std::string RemoveMultipleSlashes(const std::string& source)
{
// Reference:
// https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
std::string target;
target.reserve(source.size());
result.clear();
size_t prefix = 0;
// Look for the first boundary separator in the body (note the "?"
// to request non-greedy search)
const boost::regex firstSeparator1("--" + boundary + "(--|\r\n).*");
const boost::regex firstSeparator2(".*?\r\n--" + boundary + "(--|\r\n).*");
if (boost::starts_with(source, "https://"))
{
prefix = 8;
}
else if (boost::starts_with(source, "http://"))
{
prefix = 7;
}
// Look for the next boundary separator in the body (note the "?"
// to request non-greedy search)
const boost::regex nextSeparator(".*?(\r\n--" + boundary + ").*");
for (size_t i = 0; i < prefix; i++)
{
target.push_back(source[i]);
}
const char* end = body + bodySize;
bool isLastSlash = false;
boost::cmatch what;
if (boost::regex_match(body, end, what, firstSeparator1, boost::match_perl | boost::match_single_line) ||
boost::regex_match(body, end, what, firstSeparator2, boost::match_perl | boost::match_single_line))
for (size_t i = prefix; i < source.size(); i++)
{
const char* current = what[1].first;
while (current != NULL &&
current + 2 < end)
if (source[i] == '/')
{
if (current[0] != '\r' ||
current[1] != '\n')
if (!isLastSlash)
{
// We reached a separator with a trailing "--", which
// means that reading the multipart body is done
break;
target.push_back('/');
isLastSlash = true;
}
}
else
{
current = ParseMultipartItem(result, current + 2, end, nextSeparator);
}
target.push_back(source[i]);
isLastSlash = false;
}
}
return target;
}
void ParseAssociativeArray(std::map<std::string, std::string>& target,
const Json::Value& value,
bool LookupStringValue(std::string& target,
const Json::Value& json,
const std::string& key)
{
if (value.type() != Json::objectValue)
if (json.type() != Json::objectValue)
{
throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
"This is not a JSON object");
throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
}
if (!value.isMember(key))
else if (!json.isMember(key))
{
return;
return false;
}
const Json::Value& tmp = value[key];
if (tmp.type() != Json::objectValue)
else if (json[key].type() != Json::stringValue)
{
throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
"The field \"" + key + "\" of a JSON object is "
"not a JSON associative array as expected");
throw Orthanc::OrthancException(
Orthanc::ErrorCode_BadFileFormat,
"The field \"" + key + "\" in a JSON object should be a string");
}
else
{
target = json[key].asString();
return true;
}
}
Json::Value::Members names = tmp.getMemberNames();
for (size_t i = 0; i < names.size(); i++)
bool LookupIntegerValue(int& target,
const Json::Value& json,
const std::string& key)
{
if (tmp[names[i]].type() != Json::stringValue)
if (json.type() != Json::objectValue)
{
throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
"Some value in the associative array \"" + key +
"\" is not a string as expected");
throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
}
else
else if (!json.isMember(key))
{
target[names[i]] = tmp[names[i]].asString();
return false;
}
else if (json[key].type() != Json::intValue &&
json[key].type() != Json::uintValue)
{
throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
}
else
{
target = json[key].asInt();
return true;
}
}
bool ParseTag(Orthanc::DicomTag& target,
const std::string& name)
bool LookupBooleanValue(bool& target,
const Json::Value& json,
const std::string& key)
{
OrthancPluginDictionaryEntry entry;
if (OrthancPluginLookupDictionary(OrthancPlugins::GetGlobalContext(), &entry, name.c_str()) == OrthancPluginErrorCode_Success)
if (json.type() != Json::objectValue)
{
target = Orthanc::DicomTag(entry.group, entry.element);
return true;
throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
}
else
else if (!json.isMember(key))
{
return false;
}
else if (json[key].type() != Json::booleanValue)
{
throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
}
else
{
target = json[key].asBool();
return true;
}
}
......@@ -352,7 +334,7 @@ namespace OrthancPlugins
}
std::string GetRoot()
std::string GetDicomWebRoot()
{
assert(configuration_.get() != NULL);
std::string root = configuration_->GetStringValue("Root", "/dicom-web/");
......@@ -373,6 +355,40 @@ namespace OrthancPlugins
}
std::string GetOrthancApiRoot()
{
std::string root = OrthancPlugins::Configuration::GetDicomWebRoot();
std::vector<std::string> tokens;
Orthanc::Toolbox::TokenizeString(tokens, root, '/');
int depth = 0;
for (size_t i = 0; i < tokens.size(); i++)
{
if (tokens[i].empty() ||
tokens[i] == ".")
{
// Don't change the depth
}
else if (tokens[i] == "..")
{
depth--;
}
else
{
depth++;
}
}
std::string orthancRoot = "./";
for (int i = 0; i < depth; i++)
{
orthancRoot += "../";
}
return orthancRoot;
}
std::string GetWadoRoot()
{
assert(configuration_.get() != NULL);
......@@ -395,21 +411,116 @@ namespace OrthancPlugins
}
std::string GetBaseUrl(const OrthancPluginHttpRequest* request)
static bool IsHttpsProto(const std::string& proto,
bool defaultValue)
{
if (proto == "http")
{
return false;
}
else if (proto == "https")
{
return true;
}
else
{
return defaultValue;
}
}
static bool LookupHttpHeader2(std::string& value,
const OrthancPlugins::HttpClient::HttpHeaders& headers,
const std::string& name)
{
for (OrthancPlugins::HttpClient::HttpHeaders::const_iterator
it = headers.begin(); it != headers.end(); ++it)
{
if (boost::iequals(it->first, name))
{
value = it->second;
return false;
}
}
return false;
}
std::string GetBaseUrl(const OrthancPlugins::HttpClient::HttpHeaders& headers)
{
assert(configuration_.get() != NULL);
std::string host = configuration_->GetStringValue("Host", "");
bool ssl = configuration_->GetBooleanValue("Ssl", false);
bool https = configuration_->GetBooleanValue("Ssl", false);
std::string forwarded;
if (host.empty() &&
!LookupHttpHeader(host, request, "host"))
LookupHttpHeader2(forwarded, headers, "forwarded"))
{
// There is a "Forwarded" HTTP header in the query
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded
std::vector<std::string> forwarders;
Orthanc::Toolbox::TokenizeString(forwarders, forwarded, ',');
// Only consider the first forwarder, if any
if (!forwarders.empty())
{
std::vector<std::string> tokens;
Orthanc::Toolbox::TokenizeString(tokens, forwarders[0], ';');
for (size_t j = 0; j < tokens.size(); j++)
{
std::vector<std::string> args;
Orthanc::Toolbox::TokenizeString(args, tokens[j], '=');
if (args.size() == 2)
{
std::string key = Orthanc::Toolbox::StripSpaces(args[0]);
std::string value = Orthanc::Toolbox::StripSpaces(args[1]);
Orthanc::Toolbox::ToLowerCase(key);
if (key == "host")
{
host = value;
}
else if (key == "proto")
{
https = IsHttpsProto(value, https);
}
}
}
}
}
if (host.empty() &&
!LookupHttpHeader2(host, headers, "host"))
{
// Should never happen: The "host" header should always be present
// in HTTP requests. Provide a default value anyway.
host = "localhost:8042";
}
return (ssl ? "https://" : "http://") + host + GetRoot();
return (https ? "https://" : "http://") + host + GetDicomWebRoot();
}
std::string GetBaseUrl(const OrthancPluginHttpRequest* request)
{
OrthancPlugins::HttpClient::HttpHeaders headers;
std::string value;
if (LookupHttpHeader(value, request, "forwarded"))
{
headers["Forwarded"] = value;
}
if (LookupHttpHeader(value, request, "host"))
{
headers["Host"] = value;
}
return GetBaseUrl(headers);
}
......@@ -438,5 +549,61 @@ namespace OrthancPlugins
{
return defaultEncoding_;
}
static bool IsXmlExpected(const std::string& acceptHeader)
{
std::string accept;
Orthanc::Toolbox::ToLowerCase(accept, acceptHeader);
if (accept == "application/dicom+json" ||
accept == "application/json" ||
accept == "*/*")
{
return false;
}
else if (accept == "application/dicom+xml" ||
accept == "application/xml" ||
accept == "text/xml")
{
return true;
}
else
{
OrthancPlugins::LogError("Unsupported return MIME type: " + accept +
", will return DICOM+JSON");
return false;
}
}
bool IsXmlExpected(const std::map<std::string, std::string>& headers)
{
std::map<std::string, std::string>::const_iterator found = headers.find("accept");
if (found == headers.end())
{
return false; // By default, return DICOM+JSON
}
else
{
return IsXmlExpected(found->second);
}
}
bool IsXmlExpected(const OrthancPluginHttpRequest* request)
{
std::string accept;
if (OrthancPlugins::LookupHttpHeader(accept, request, "accept"))
{
return IsXmlExpected(accept);
}
else
{
return false; // By default, return DICOM+JSON
}
}
}
}
......@@ -45,13 +45,6 @@ namespace OrthancPlugins
static const Orthanc::DicomTag DICOM_TAG_REFERENCED_SOP_CLASS_UID(0x0008, 0x1150);
static const Orthanc::DicomTag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID(0x0008, 0x1155);
struct MultipartItem
{
const char* data_;
size_t size_;
std::string contentType_;
};
bool LookupHttpHeader(std::string& value,
const OrthancPluginHttpRequest* request,
const std::string& header);
......@@ -60,18 +53,33 @@ namespace OrthancPlugins
std::map<std::string, std::string>& attributes,
const std::string& header);
void ParseMultipartBody(std::vector<MultipartItem>& result,
const char* body,
const uint64_t bodySize,
const std::string& boundary);
void ParseAssociativeArray(std::map<std::string, std::string>& target,
const Json::Value& value,
const std::string& key);
void ParseAssociativeArray(std::map<std::string, std::string>& target,
const Json::Value& value);
bool ParseTag(Orthanc::DicomTag& target,
const std::string& name);
void ParseJsonBody(Json::Value& target,
const OrthancPluginHttpRequest* request);
std::string RemoveMultipleSlashes(const std::string& source);
bool LookupStringValue(std::string& target,
const Json::Value& json,
const std::string& key);
bool LookupIntegerValue(int& target,
const Json::Value& json,
const std::string& key);
bool LookupBooleanValue(bool& target,
const Json::Value& json,
const std::string& key);
namespace Configuration
{
void Initialize();
......@@ -85,10 +93,15 @@ namespace OrthancPlugins
unsigned int GetUnsignedIntegerValue(const std::string& key,
unsigned int defaultValue);
std::string GetRoot();
std::string GetDicomWebRoot();
std::string GetOrthancApiRoot();
std::string GetWadoRoot();
std::string GetBaseUrl(const std::map<std::string, std::string>& headers);
// TODO => REMOVE
std::string GetBaseUrl(const OrthancPluginHttpRequest* request);
std::string GetWadoUrl(const std::string& wadoBase,
......@@ -97,5 +110,10 @@ namespace OrthancPlugins
const std::string& sopInstanceUid);
Orthanc::Encoding GetDefaultEncoding();
bool IsXmlExpected(const std::map<std::string, std::string>& headers);
// TODO => REMOVE
bool IsXmlExpected(const OrthancPluginHttpRequest* request);
}
}
This diff is collapsed.
......@@ -32,6 +32,14 @@ void GetFromServer(OrthancPluginRestOutput* output,
const char* /*url*/,
const OrthancPluginHttpRequest* request);
void GetFromServer(Json::Value& result,
const OrthancPluginHttpRequest* request);
// TODO => Mark as deprecated
void RetrieveFromServer(OrthancPluginRestOutput* output,
const char* /*url*/,
const OrthancPluginHttpRequest* request);
void WadoRetrieveClient(OrthancPluginRestOutput* output,
const char* url,
const OrthancPluginHttpRequest* request);
......@@ -25,6 +25,8 @@
#include <Core/Toolbox.h>
#include <boost/algorithm/string/predicate.hpp>
namespace OrthancPlugins
{
void DicomWebServers::Clear()
......@@ -116,6 +118,67 @@ namespace OrthancPlugins
}
void DicomWebServers::ConfigureHttpClient(HttpClient& client,
const std::string& name,
const std::string& uri)
{
static const char* HAS_CHUNKED_TRANSFERS = "ChunkedTransfers";
const Orthanc::WebServiceParameters parameters = GetServer(name);
client.SetUrl(RemoveMultipleSlashes(parameters.GetUrl() + "/" + uri));
client.SetHeaders(parameters.GetHttpHeaders());
if (!parameters.GetUsername().empty())
{
client.SetCredentials(parameters.GetUsername(), parameters.GetPassword());
}
// By default, enable chunked transfers
client.SetChunkedTransfersAllowed(
parameters.GetBooleanUserProperty(HAS_CHUNKED_TRANSFERS, true));
}
void DicomWebServers::DeleteServer(const std::string& name)
{
boost::mutex::scoped_lock lock(mutex_);
Servers::iterator found = servers_.find(name);
if (found == servers_.end())
{
throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
"Unknown DICOMweb server: " + name);
}
else
{
assert(found->second != NULL);
delete found->second;
servers_.erase(found);
}
}
void DicomWebServers::SetServer(const std::string& name,
const Orthanc::WebServiceParameters& parameters)
{
boost::mutex::scoped_lock lock(mutex_);
Servers::iterator found = servers_.find(name);
if (found != servers_.end())
{
assert(found->second != NULL);
delete found->second;
servers_.erase(found);
}
servers_[name] = new Orthanc::WebServiceParameters(parameters);
}
static const char* ConvertToCString(const std::string& s)
{
if (s.empty())
......@@ -252,7 +315,7 @@ namespace OrthancPlugins
}
void UriEncode(std::string& uri,
void DicomWebServers::UriEncode(std::string& uri,
const std::string& resource,
const std::map<std::string, std::string>& getArguments)
{
......
......@@ -45,6 +45,10 @@ namespace OrthancPlugins
}
public:
static void UriEncode(std::string& uri,
const std::string& resource,
const std::map<std::string, std::string>& getArguments);
void Load(const Json::Value& configuration);
~DicomWebServers()
......@@ -57,6 +61,15 @@ namespace OrthancPlugins
Orthanc::WebServiceParameters GetServer(const std::string& name);
void ListServers(std::list<std::string>& servers);
void ConfigureHttpClient(HttpClient& client,
const std::string& name,
const std::string& uri);
void DeleteServer(const std::string& name);
void SetServer(const std::string& name,
const Orthanc::WebServiceParameters& parameters);
};
......@@ -67,8 +80,4 @@ namespace OrthancPlugins
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);
}
function ChooseDicomWebServer(callback)
{
var clickedModality = '';
var clickedPeer = '';
var items = $('<ul>')
.attr('data-divider-theme', 'd')
.attr('data-role', 'listview');
$.ajax({
url: '../${DICOMWEB_ROOT}/servers',
type: 'GET',
dataType: 'json',
async: false,
cache: false,
success: function(servers) {
var name, item;
if (servers.length > 0)
{
items.append('<li data-role="list-divider">DICOMweb servers</li>');
for (var i = 0; i < servers.length; i++) {
name = servers[i];
item = $('<li>')
.html('<a href="#" rel="close">' + name + '</a>')
.attr('name', name)
.click(function() {
clickedModality = $(this).attr('name');
});
items.append(item);
}
}
// Launch the dialog
$(document).simpledialog2({
mode: 'blank',
animate: false,
headerText: 'Choose target',
headerClose: true,
forceInput: false,
width: '100%',
blankContent: items,
callbackClose: function() {
var timer;
function WaitForDialogToClose() {
if (!$('#dialog').is(':visible')) {
clearInterval(timer);
callback(clickedModality, clickedPeer);
}
}
timer = setInterval(WaitForDialogToClose, 100);
}
});
}
});
}
function ConfigureDicomWebStowClient(resourceId, buttonId, positionOnPage)
{
$('#' + buttonId).remove();
var b = $('<a>')
.attr('id', buttonId)
.attr('data-role', 'button')
.attr('href', '#')
.attr('data-icon', 'forward')
.attr('data-theme', 'e')
.text('Send to DICOMweb server')
.button();
b.insertAfter($('#' + positionOnPage));
b.click(function() {
if ($.mobile.pageData) {
ChooseDicomWebServer(function(server) {
if (server != '' && resourceId != '') {
var query = {
'Resources' : [ resourceId ],
'Synchronous' : false
};
$.ajax({
url: '../${DICOMWEB_ROOT}/servers/' + server + '/stow',
type: 'POST',
dataType: 'json',
data: JSON.stringify(query),
async: false,
error: function() {
alert('Cannot submit job');
},
success: function(job) {
}
});
}
});
}
});
}
$('#patient').live('pagebeforeshow', function() {
ConfigureDicomWebStowClient($.mobile.pageData.uuid, 'stow-patient', 'patient-info');
});
$('#study').live('pagebeforeshow', function() {
ConfigureDicomWebStowClient($.mobile.pageData.uuid, 'stow-study', 'study-info');
});
$('#series').live('pagebeforeshow', function() {
ConfigureDicomWebStowClient($.mobile.pageData.uuid, 'stow-series', 'series-info');
});
$('#instance').live('pagebeforeshow', function() {
ConfigureDicomWebStowClient($.mobile.pageData.uuid, 'stow-instance', 'instance-info');
});
$('#lookup').live('pagebeforeshow', function() {
$('#open-dicomweb-client').remove();
var b = $('<fieldset>')
.attr('id', 'open-dicomweb-client')
.addClass('ui-grid-b')
.append($('<div>')
.addClass('ui-block-a'))
.append($('<div>')
.addClass('ui-block-b')
.append($('<a>')
.attr('id', 'coucou')
.attr('data-role', 'button')
.attr('href', '#')
.attr('data-icon', 'forward')
.attr('data-theme', 'a')
.text('Open DICOMweb client')
.button()
.click(function(e) {
window.open('../${DICOMWEB_ROOT}/app/client/index.html');
})));
b.insertAfter($('#lookup-result'));
});
This diff is collapsed.
......@@ -21,7 +21,6 @@
#include "QidoRs.h"
#include "StowRs.h" // For IsXmlExpected()
#include "Configuration.h"
#include "DicomWebFormatter.h"
......@@ -494,7 +493,8 @@ static void ApplyMatcher(OrthancPluginRestOutput* output,
std::string wadoBase = OrthancPlugins::Configuration::GetBaseUrl(request);
OrthancPlugins::DicomWebFormatter::HttpWriter writer(output, IsXmlExpected(request));
OrthancPlugins::DicomWebFormatter::HttpWriter writer(
output, OrthancPlugins::Configuration::IsXmlExpected(request));
// Fix of issue #13
for (ResourcesAndInstances::const_iterator
......
......@@ -24,135 +24,67 @@
#include "Configuration.h"
#include "DicomWebFormatter.h"
#include <Core/Toolbox.h>
#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
bool IsXmlExpected(const OrthancPluginHttpRequest* request)
namespace OrthancPlugins
{
std::string accept;
if (!OrthancPlugins::LookupHttpHeader(accept, request, "accept"))
{
return false; // By default, return DICOM+JSON
}
Orthanc::Toolbox::ToLowerCase(accept);
if (accept == "application/dicom+json" ||
accept == "application/json" ||
accept == "*/*")
StowServer::StowServer(OrthancPluginContext* context,
const std::map<std::string, std::string>& headers,
const std::string& expectedStudy) :
context_(context),
xml_(Configuration::IsXmlExpected(headers)),
wadoBase_(Configuration::GetBaseUrl(headers)),
expectedStudy_(expectedStudy),
isFirst_(true),
result_(Json::objectValue),
success_(Json::arrayValue),
failed_(Json::arrayValue)
{
return false;
}
else if (accept == "application/dicom+xml" ||
accept == "application/xml" ||
accept == "text/xml")
std::string tmp, contentType, subType, boundary;
if (!Orthanc::MultipartStreamReader::GetMainContentType(tmp, headers) ||
!Orthanc::MultipartStreamReader::ParseMultipartContentType(contentType, subType, boundary, tmp))
{
return true;
throw Orthanc::OrthancException(Orthanc::ErrorCode_UnsupportedMediaType,
"The STOW-RS server expects a multipart body in its request");
}
else
{
OrthancPlugins::LogError("Unsupported return MIME type: " + accept +
", will return DICOM+JSON");
return false;
}
}
void StowCallback(OrthancPluginRestOutput* output,
const char* url,
const OrthancPluginHttpRequest* request)
{
OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
const std::string wadoBase = OrthancPlugins::Configuration::GetBaseUrl(request);
if (request->method != OrthancPluginHttpMethod_Post)
if (contentType != "multipart/related")
{
OrthancPluginSendMethodNotAllowed(context, output, "POST");
return;
throw Orthanc::OrthancException(Orthanc::ErrorCode_UnsupportedMediaType,
"The Content-Type of a STOW-RS request must be \"multipart/related\"");
}
std::string expectedStudy;
if (request->groupsCount == 1)
if (subType != "application/dicom")
{
expectedStudy = request->groups[0];
throw Orthanc::OrthancException(Orthanc::ErrorCode_UnsupportedMediaType,
"The STOW-RS plugin currently only supports \"application/dicom\" subtype");
}
if (expectedStudy.empty())
{
OrthancPlugins::LogInfo("STOW-RS request without study");
}
else
{
OrthancPlugins::LogInfo("STOW-RS request restricted to study UID " + expectedStudy);
}
std::string header;
if (!OrthancPlugins::LookupHttpHeader(header, request, "content-type"))
{
OrthancPlugins::LogError("No content type in the HTTP header of a STOW-RS request");
OrthancPluginSendHttpStatusCode(context, output, 400 /* Bad request */);
return;
}
std::string application;
std::map<std::string, std::string> attributes;
OrthancPlugins::ParseContentType(application, attributes, header);
if (application != "multipart/related" ||
attributes.find("type") == attributes.end() ||
attributes.find("boundary") == attributes.end())
{
OrthancPlugins::LogError("Unable to parse the content type of a STOW-RS request (" + application + ")");
OrthancPluginSendHttpStatusCode(context, output, 400 /* Bad request */);
return;
parser_.reset(new Orthanc::MultipartStreamReader(boundary));
parser_->SetHandler(*this);
}
std::string boundary = attributes["boundary"];
if (attributes["type"] != "application/dicom")
void StowServer::HandlePart(const Orthanc::MultipartStreamReader::HttpHeaders& headers,
const void* part,
size_t size)
{
OrthancPlugins::LogError("The STOW-RS plugin currently only supports application/dicom");
OrthancPluginSendHttpStatusCode(context, output, 415 /* Unsupported media type */);
return;
}
bool isFirst = true;
Json::Value result = Json::objectValue;
Json::Value success = Json::arrayValue;
Json::Value failed = Json::arrayValue;
std::vector<OrthancPlugins::MultipartItem> items;
OrthancPlugins::ParseMultipartBody(items, request->body, request->bodySize, boundary);
std::string contentType;
for (size_t i = 0; i < items.size(); i++)
if (!Orthanc::MultipartStreamReader::GetMainContentType(contentType, headers) ||
contentType != "application/dicom")
{
OrthancPlugins::LogInfo("Detected multipart item with content type \"" +
items[i].contentType_ + "\" of size " +
boost::lexical_cast<std::string>(items[i].size_));
}
for (size_t i = 0; i < items.size(); i++)
{
if (!items[i].contentType_.empty() &&
items[i].contentType_ != "application/dicom")
{
OrthancPlugins::LogError("The STOW-RS request contains a part that is not "
"\"application/dicom\" (it is: \"" + items[i].contentType_ + "\")");
OrthancPluginSendHttpStatusCode(context, output, 415 /* Unsupported media type */);
return;
throw Orthanc::OrthancException(
Orthanc::ErrorCode_UnsupportedMediaType,
"The STOW-RS request contains a part that is not "
"\"application/dicom\" (it is: \"" + contentType + "\")");
}
Json::Value dicom;
try
{
OrthancPlugins::OrthancString s;
s.Assign(OrthancPluginDicomBufferToJson(context, items[i].data_, items[i].size_,
OrthancString s;
s.Assign(OrthancPluginDicomBufferToJson(context_, part, size,
OrthancPluginDicomToJsonFormat_Short,
OrthancPluginDicomToJsonFlags_None, 256));
s.ToJson(dicom);
......@@ -160,8 +92,8 @@ void StowCallback(OrthancPluginRestOutput* output,
catch (Orthanc::OrthancException&)
{
// Bad DICOM file => TODO add to error
OrthancPlugins::LogWarning("STOW-RS cannot parse an incoming DICOM file");
continue;
LogWarning("STOW-RS cannot parse an incoming DICOM file");
return;
}
if (dicom.type() != Json::objectValue ||
......@@ -174,8 +106,8 @@ void StowCallback(OrthancPluginRestOutput* output,
dicom[Orthanc::DICOM_TAG_SOP_INSTANCE_UID.Format()].type() != Json::stringValue ||
dicom[Orthanc::DICOM_TAG_STUDY_INSTANCE_UID.Format()].type() != Json::stringValue)
{
OrthancPlugins::LogWarning("STOW-RS: Missing a mandatory tag in incoming DICOM file");
continue;
LogWarning("STOW-RS: Missing a mandatory tag in incoming DICOM file");
return;
}
const std::string seriesInstanceUid = dicom[Orthanc::DICOM_TAG_SERIES_INSTANCE_UID.Format()].asString();
......@@ -184,63 +116,112 @@ void StowCallback(OrthancPluginRestOutput* output,
const std::string studyInstanceUid = dicom[Orthanc::DICOM_TAG_STUDY_INSTANCE_UID.Format()].asString();
Json::Value item = Json::objectValue;
item[OrthancPlugins::DICOM_TAG_REFERENCED_SOP_CLASS_UID.Format()] = sopClassUid;
item[OrthancPlugins::DICOM_TAG_REFERENCED_SOP_INSTANCE_UID.Format()] = sopInstanceUid;
item[DICOM_TAG_REFERENCED_SOP_CLASS_UID.Format()] = sopClassUid;
item[DICOM_TAG_REFERENCED_SOP_INSTANCE_UID.Format()] = sopInstanceUid;
if (!expectedStudy.empty() &&
studyInstanceUid != expectedStudy)
if (!expectedStudy_.empty() &&
studyInstanceUid != expectedStudy_)
{
OrthancPlugins::LogInfo("STOW-RS request restricted to study [" + expectedStudy +
LogInfo("STOW-RS request restricted to study [" + expectedStudy_ +
"]: Ignoring instance from study [" + studyInstanceUid + "]");
/*item[OrthancPlugins::DICOM_TAG_WARNING_REASON.Format()] =
/*item[DICOM_TAG_WARNING_REASON.Format()] =
boost::lexical_cast<std::string>(0xB006); // Elements discarded
success.append(item);*/
}
else
{
if (isFirst)
if (isFirst_)
{
std::string url = wadoBase + "studies/" + studyInstanceUid;
result[OrthancPlugins::DICOM_TAG_RETRIEVE_URL.Format()] = url;
isFirst = false;
std::string url = wadoBase_ + "studies/" + studyInstanceUid;
result_[DICOM_TAG_RETRIEVE_URL.Format()] = url;
isFirst_ = false;
}
OrthancPlugins::MemoryBuffer tmp;
bool ok = tmp.RestApiPost("/instances", items[i].data_, items[i].size_, false);
MemoryBuffer tmp;
bool ok = tmp.RestApiPost("/instances", part, size, false);
tmp.Clear();
if (ok)
{
std::string url = (wadoBase +
std::string url = (wadoBase_ +
"studies/" + studyInstanceUid +
"/series/" + seriesInstanceUid +
"/instances/" + sopInstanceUid);
item[OrthancPlugins::DICOM_TAG_RETRIEVE_URL.Format()] = url;
success.append(item);
item[DICOM_TAG_RETRIEVE_URL.Format()] = url;
success_.append(item);
}
else
{
OrthancPlugins::LogError("Orthanc was unable to store instance through STOW-RS request");
item[OrthancPlugins::DICOM_TAG_FAILURE_REASON.Format()] =
LogError("Orthanc was unable to store one instance in a STOW-RS request");
item[DICOM_TAG_FAILURE_REASON.Format()] =
boost::lexical_cast<std::string>(0x0110); // Processing failure
failed.append(item);
failed_.append(item);
}
}
}
void StowServer::AddChunk(const void* data,
size_t size)
{
assert(parser_.get() != NULL);
parser_->AddChunk(data, size);
}
result[OrthancPlugins::DICOM_TAG_FAILED_SOP_SEQUENCE.Format()] = failed;
result[OrthancPlugins::DICOM_TAG_REFERENCED_SOP_SEQUENCE.Format()] = success;
const bool isXml = IsXmlExpected(request);
void StowServer::Execute(OrthancPluginRestOutput* output)
{
assert(parser_.get() != NULL);
parser_->CloseStream();
result_[DICOM_TAG_FAILED_SOP_SEQUENCE.Format()] = failed_;
result_[DICOM_TAG_REFERENCED_SOP_SEQUENCE.Format()] = success_;
std::string answer;
{
OrthancPlugins::DicomWebFormatter::Locker locker(OrthancPluginDicomWebBinaryMode_Ignore, "");
locker.Apply(answer, context, result, isXml);
DicomWebFormatter::Locker locker(OrthancPluginDicomWebBinaryMode_Ignore, "");
locker.Apply(answer, context_, result_, xml_);
}
OrthancPluginAnswerBuffer(context_, output, answer.c_str(), answer.size(),
xml_ ? "application/dicom+xml" : "application/dicom+json");
};
IChunkedRequestReader* StowServer::PostCallback(const char* url,
const OrthancPluginHttpRequest* request)
{
OrthancPluginContext* context = GetGlobalContext();
if (request->method != OrthancPluginHttpMethod_Post)
{
throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
}
std::map<std::string, std::string> headers;
for (uint32_t i = 0; i < request->headersCount; i++)
{
headers[request->headersKeys[i]] = request->headersValues[i];
}
std::string expectedStudy;
if (request->groupsCount == 1)
{
expectedStudy = request->groups[0];
}
if (expectedStudy.empty())
{
LogInfo("STOW-RS request without study");
}
else
{
LogInfo("STOW-RS request restricted to study UID " + expectedStudy);
}
OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(),
isXml ? "application/dicom+xml" : "application/dicom+json");
return new StowServer(context, headers, expectedStudy);
}
}
......@@ -21,10 +21,42 @@
#pragma once
#include "Configuration.h"
#include <Core/HttpServer/MultipartStreamReader.h>
#include <Plugins/Samples/Common/OrthancPluginCppWrapper.h>
bool IsXmlExpected(const OrthancPluginHttpRequest* request);
namespace OrthancPlugins
{
class StowServer :
public IChunkedRequestReader,
private Orthanc::MultipartStreamReader::IHandler
{
private:
OrthancPluginContext* context_;
bool xml_;
std::string wadoBase_;
std::string expectedStudy_;
bool isFirst_;
Json::Value result_;
Json::Value success_;
Json::Value failed_;
void StowCallback(OrthancPluginRestOutput* output,
const char* url,
std::auto_ptr<Orthanc::MultipartStreamReader> parser_;
virtual void HandlePart(const Orthanc::MultipartStreamReader::HttpHeaders& headers,
const void* part,
size_t size);
public:
StowServer(OrthancPluginContext* context,
const std::map<std::string, std::string>& headers,
const std::string& expectedStudy);
virtual void AddChunk(const void* data,
size_t size);
virtual void Execute(OrthancPluginRestOutput* output);
static IChunkedRequestReader* PostCallback(const char* url,
const OrthancPluginHttpRequest* request);
};
}
......@@ -439,9 +439,9 @@ bool LocateInstance(OrthancPluginRestOutput* output,
series["MainDicomTags"]["SeriesInstanceUID"].asString() != std::string(request->groups[1]))
{
throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem,
"No instance " + std::string(request->groups[2]) +
" in study " + std::string(request->groups[0]) +
" or in series " + std::string(request->groups[1]));
"Instance " + std::string(request->groups[2]) +
" is not both in study " + std::string(request->groups[0]) +
" and in series " + std::string(request->groups[1]));
}
else
{
......
# Orthanc - A Lightweight, RESTful DICOM Store
# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
# Department, University Hospital of Liege, Belgium
# Copyright (C) 2017-2019 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/>.
set(BASE_URL "http://orthanc.osimis.io/ThirdPartyDownloads/dicom-web")
DownloadPackage(
"da0189f7c33bf9f652ea65401e0a3dc9"
"${BASE_URL}/bootstrap-4.3.1.zip"
"${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1")
DownloadPackage(
"8242afdc5bd44105d9dc9e6535315484"
"${BASE_URL}/vuejs-2.6.10.tar.gz"
"${CMAKE_CURRENT_BINARY_DIR}/vue-2.6.10")
DownloadPackage(
"3e2b4e1522661f7fcf8ad49cb933296c"
"${BASE_URL}/axios-0.19.0.tar.gz"
"${CMAKE_CURRENT_BINARY_DIR}/axios-0.19.0")
DownloadPackage(
"a6145901f233f7d54165d8ade779082e"
"${BASE_URL}/Font-Awesome-4.7.0.tar.gz"
"${CMAKE_CURRENT_BINARY_DIR}/Font-Awesome-4.7.0")
set(BOOTSTRAP_VUE_SOURCES_DIR ${CMAKE_CURRENT_BINARY_DIR}/bootstrap-vue-2.0.0-rc.24)
if (BUILD_BOOTSTRAP_VUE OR
BUILD_BABEL_POLYFILL)
find_program(NPM_EXECUTABLE npm)
if (${NPM_EXECUTABLE} MATCHES "NPM_EXECUTABLE-NOTFOUND")
message(FATAL_ERROR "Please install the 'npm' standard command-line tool")
endif()
endif()
if (BUILD_BOOTSTRAP_VUE)
DownloadPackage(
"36ab31495ab94162e159619532e8def5"
"${BASE_URL}/bootstrap-vue-2.0.0-rc.24.tar.gz"
"${BOOTSTRAP_VUE_SOURCES_DIR}")
if (NOT IS_DIRECTORY "${BOOTSTRAP_VUE_SOURCES_DIR}/node_modules")
execute_process(
COMMAND ${NPM_EXECUTABLE} install
WORKING_DIRECTORY ${BOOTSTRAP_VUE_SOURCES_DIR}
RESULT_VARIABLE Failure
OUTPUT_QUIET
)
if (Failure)
message(FATAL_ERROR "Error while running 'npm install' on Bootstrap-Vue")
endif()
endif()
if (NOT IS_DIRECTORY "${BOOTSTRAP_VUE_SOURCES_DIR}/dist")
execute_process(
COMMAND ${NPM_EXECUTABLE} run build
WORKING_DIRECTORY ${BOOTSTRAP_VUE_SOURCES_DIR}
RESULT_VARIABLE Failure
OUTPUT_QUIET
)
if (Failure)
message(FATAL_ERROR "Error while running 'npm build' on Bootstrap-Vue")
endif()
endif()
else()
##
## Generation of the precompiled Bootstrap-Vue package:
##
## Possibility 1 (build from sources):
## $ cmake -DBUILD_BOOTSTRAP_VUE=ON .
## $ tar cvfz bootstrap-vue-2.0.0-rc.24-dist.tar.gz bootstrap-vue-2.0.0-rc.24/dist/
##
## Possibility 2 (download from CDN):
## $ mkdir /tmp/i && cd /tmp/i
## $ wget -r --no-parent https://unpkg.com/bootstrap-vue@2.0.0-rc.24/dist/
## $ mv unpkg.com/bootstrap-vue@2.0.0-rc.24/ bootstrap-vue-2.0.0-rc.24
## $ rm bootstrap-vue-2.0.0-rc.24/dist/index.html
## $ tar cvfz bootstrap-vue-2.0.0-rc.24-dist.tar.gz bootstrap-vue-2.0.0-rc.24/dist/
DownloadPackage(
"ba0e67b1f0b4ce64e072b42b17f6c578"
"${BASE_URL}/bootstrap-vue-2.0.0-rc.24-dist.tar.gz"
"${BOOTSTRAP_VUE_SOURCES_DIR}")
endif()
if (BUILD_BABEL_POLYFILL)
set(BABEL_POLYFILL_SOURCES_DIR ${CMAKE_CURRENT_BINARY_DIR}/node_modules/babel-polyfill/dist)
if (NOT IS_DIRECTORY "${BABEL_POLYFILL_SOURCES_DIR}")
execute_process(
COMMAND ${NPM_EXECUTABLE} install babel-polyfill
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
RESULT_VARIABLE Failure
OUTPUT_QUIET
)
if (Failure)
message(FATAL_ERROR "Error while running 'npm install' on Bootstrap-Vue")
endif()
endif()
else()
## curl -L https://unpkg.com/babel-polyfill@6.26.0/dist/polyfill.min.js | gzip > babel-polyfill-6.26.0.min.js.gz
set(BABEL_POLYFILL_SOURCES_DIR ${CMAKE_CURRENT_BINARY_DIR})
DownloadCompressedFile(
"49f7bad4176d715ce145e75c903988ef"
"${BASE_URL}/babel-polyfill-6.26.0.min.js.gz"
"${CMAKE_CURRENT_BINARY_DIR}/polyfill.min.js")
endif()
set(JAVASCRIPT_LIBS_DIR ${CMAKE_CURRENT_BINARY_DIR}/javascript-libs)
file(MAKE_DIRECTORY ${JAVASCRIPT_LIBS_DIR})
file(COPY
${BABEL_POLYFILL_SOURCES_DIR}/polyfill.min.js
${BOOTSTRAP_VUE_SOURCES_DIR}/dist/bootstrap-vue.min.js
${BOOTSTRAP_VUE_SOURCES_DIR}/dist/bootstrap-vue.min.js.map
${CMAKE_CURRENT_BINARY_DIR}/axios-0.19.0/dist/axios.min.js
${CMAKE_CURRENT_BINARY_DIR}/axios-0.19.0/dist/axios.min.map
${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1/dist/js/bootstrap.min.js
${CMAKE_CURRENT_BINARY_DIR}/vue-2.6.10/dist/vue.min.js
DESTINATION
${JAVASCRIPT_LIBS_DIR}/js
)
file(COPY
${BOOTSTRAP_VUE_SOURCES_DIR}/dist/bootstrap-vue.min.css
${BOOTSTRAP_VUE_SOURCES_DIR}/dist/bootstrap-vue.min.css.map
${CMAKE_CURRENT_BINARY_DIR}/Font-Awesome-4.7.0/css/font-awesome.min.css
${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1/dist/css/bootstrap.min.css
${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1/dist/css/bootstrap.min.css.map
DESTINATION
${JAVASCRIPT_LIBS_DIR}/css
)
file(COPY
${CMAKE_CURRENT_BINARY_DIR}/Font-Awesome-4.7.0/fonts/FontAwesome.otf
${CMAKE_CURRENT_BINARY_DIR}/Font-Awesome-4.7.0/fonts/fontawesome-webfont.eot
${CMAKE_CURRENT_BINARY_DIR}/Font-Awesome-4.7.0/fonts/fontawesome-webfont.svg
${CMAKE_CURRENT_BINARY_DIR}/Font-Awesome-4.7.0/fonts/fontawesome-webfont.ttf
${CMAKE_CURRENT_BINARY_DIR}/Font-Awesome-4.7.0/fonts/fontawesome-webfont.woff
${CMAKE_CURRENT_BINARY_DIR}/Font-Awesome-4.7.0/fonts/fontawesome-webfont.woff2
DESTINATION
${JAVASCRIPT_LIBS_DIR}/fonts
)
file(COPY
${ORTHANC_ROOT}/Resources/OrthancLogo.png
DESTINATION
${JAVASCRIPT_LIBS_DIR}/img
)
......@@ -66,6 +66,9 @@ if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg" OR
if (NOT DEFINED ORTHANC_FRAMEWORK_BRANCH)
if (ORTHANC_FRAMEWORK_VERSION STREQUAL "mainline")
set(ORTHANC_FRAMEWORK_BRANCH "default")
set(ORTHANC_FRAMEWORK_MAJOR 999)
set(ORTHANC_FRAMEWORK_MINOR 999)
set(ORTHANC_FRAMEWORK_REVISION 999)
else()
set(ORTHANC_FRAMEWORK_BRANCH "Orthanc-${ORTHANC_FRAMEWORK_VERSION}")
......@@ -103,9 +106,18 @@ if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "hg" OR
set(ORTHANC_FRAMEWORK_MD5 "404baef5d4c43e7c5d9410edda8ef5a5")
elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.5")
set(ORTHANC_FRAMEWORK_MD5 "cfc437e0687ae4bd725fd93dc1f08bc4")
elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.6")
set(ORTHANC_FRAMEWORK_MD5 "3c29de1e289b5472342947168f0105c0")
elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.7")
set(ORTHANC_FRAMEWORK_MD5 "e1b76f01116d9b5d4ac8cc39980560e3")
endif()
endif()
endif()
else()
message("Using the Orthanc framework from a path of the filesystem. Assuming mainline version.")
set(ORTHANC_FRAMEWORK_MAJOR 999)
set(ORTHANC_FRAMEWORK_MINOR 999)
set(ORTHANC_FRAMEWORK_REVISION 999)
endif()
......
This diff is collapsed.
This is a sample configuration file for nginx to test the DICOMweb
plugin behind a HTTP proxy. To start the proxy as a regular user:
$ nginx -c ./nginx.local.conf -p $PWD
References about "Forwarded" header in nginx:
https://onefeed.xyz/posts/x-forwarded-for-vs-x-real-ip.html
https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/
worker_processes 1;
error_log stderr;
daemon off;
pid nginx.pid;
# `events` section is mandatory
events {
worker_connections 1024; # Default: 1024
}
http {
# prevent nginx sync issues on OSX
proxy_buffering off;
access_log off;
server {
listen 9977 default_server;
client_max_body_size 4G;
# location may have to be adjusted depending on your OS and nginx install
include /etc/nginx/mime.types;
# if not in your system mime.types, add this line to support WASM:
# types {
# application/wasm wasm;
# }
# reverse proxy orthanc
location /orthanc/ {
rewrite /orthanc(.*) $1 break;
proxy_pass http://127.0.0.1:8042;
proxy_set_header Host $http_host;
proxy_set_header my-auth-header good-token;
#proxy_request_buffering off;
#proxy_max_temp_file_size 0;
#client_max_body_size 0;
}
}
}