feat(WADO) Allow WADO / QIDO requests to originate from client (#222)
This commit is contained in:
1 parent
92408d4c86
commit
ebe0a40f3e
43 files changed
+519
-571
No files matched your search
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* Returns the specified element as a dicom attribute group/element.
|
||||
*
|
||||
* @param element - The group/element of the element (e.g. '00280009')
|
||||
* @param [defaultValue] - The value to return if the element is not present
|
||||
* @returns {*}
|
||||
*/
|
||||
DICOMWeb.getAttribute = function(element, defaultValue) {
|
||||
if (!element) {
|
||||
return defaultValue;
|
||||
}
|
||||
// Value is not present if the attribute has a zero length value
|
||||
if (!element.Value) {
|
||||
return defaultValue;
|
||||
}
|
||||
// Sanity check to make sure we have at least one entry in the array.
|
||||
if (!element.Value.length) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return convertToInt(element.Value);
|
||||
};
|
||||
|
||||
function convertToInt(input) {
|
||||
function padFour(input) {
|
||||
var l = input.length;
|
||||
|
||||
if (l == 0) return '0000';
|
||||
if (l == 1) return '000' + input;
|
||||
if (l == 2) return '00' + input;
|
||||
if (l == 3) return '0' + input;
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
var output = '';
|
||||
for (var i = 0; i < input.length; i++) {
|
||||
for (var j = 0; j < input[i].length; j++) {
|
||||
output += padFour(input[i].charCodeAt(j).toString(16));
|
||||
}
|
||||
}
|
||||
|
||||
return parseInt(output, 16);
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
|
||||
const ASCII = 'ascii';
|
||||
const http = Npm.require('http')
|
||||
const url = Npm.require('url');
|
||||
|
||||
function getMultipartContentInfo(headers) {
|
||||
|
||||
let dict = null,
|
||||
multipartRegex = /^\s*multipart\/[^;]+/g,
|
||||
contentType = headers['content-type'];
|
||||
|
||||
if (typeof contentType === 'string' && multipartRegex.test(contentType)) {
|
||||
let match,
|
||||
pairRegex = /;\s*([^=]+)=([^;\r\n]+)/g;
|
||||
pairRegex.lastIndex = multipartRegex.lastIndex;
|
||||
while ((match = pairRegex.exec(contentType)) !== null) {
|
||||
let key = match[1], value = match[2];
|
||||
if (dict === null) {
|
||||
dict = {};
|
||||
}
|
||||
dict[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return dict;
|
||||
|
||||
}
|
||||
|
||||
function parseContentHeader(data, offset) {
|
||||
|
||||
let endOfHeader = data.indexOf('\r\n\r\n', offset, ASCII);
|
||||
|
||||
if (endOfHeader < 0 || endOfHeader <= offset || endOfHeader > data.length) {
|
||||
throw {
|
||||
name: 'DICOMWebContentParsingError',
|
||||
message: 'End of content header cannot be determined...'
|
||||
};
|
||||
}
|
||||
|
||||
let match,
|
||||
header = {
|
||||
start: offset,
|
||||
end: endOfHeader + 4,
|
||||
fields: {}
|
||||
},
|
||||
headerRegex = /([\w\-]+):\s*([^\r\n]+)(?:\r\n)?/g,
|
||||
headerContentRegex = /\t([^\r\n]+)(?:\r\n)?/g,
|
||||
headerString = data.toString('ascii', offset, endOfHeader);
|
||||
|
||||
while ((match = headerRegex.exec(headerString)) !== null) {
|
||||
let key = match[1].toLowerCase(), value = match[2];
|
||||
while (headerString.charAt(headerRegex.lastIndex) === '\t') {
|
||||
headerContentRegex.lastIndex = headerRegex.lastIndex;
|
||||
if ((match = headerContentRegex.exec(headerString)) !== null) {
|
||||
headerRegex.lastIndex = headerContentRegex.lastIndex;
|
||||
value += ' ' + match[1];
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
header.fields[key] = value;
|
||||
}
|
||||
|
||||
return header;
|
||||
|
||||
}
|
||||
|
||||
function parseResponse(headers, data) {
|
||||
|
||||
let contentInfo = getMultipartContentInfo(headers);
|
||||
|
||||
if (!contentInfo || !contentInfo.boundary) {
|
||||
throw {
|
||||
name: 'DICOMWebContentParsingError',
|
||||
message: 'Content boundary not specified...'
|
||||
};
|
||||
}
|
||||
|
||||
let boundary = contentInfo.boundary,
|
||||
delimiter = '--' + boundary + '\r\n',
|
||||
index = data.indexOf(delimiter, 0, ASCII);
|
||||
|
||||
if (index < 0) {
|
||||
throw {
|
||||
name: 'DICOMWebContentParsingError',
|
||||
message: 'The specified boundary could not be found...'
|
||||
};
|
||||
}
|
||||
|
||||
let closingIndex, closingDelimiter = Buffer.from('\r\n--' + boundary + '--', ASCII);
|
||||
|
||||
index += delimiter.length;
|
||||
if (data[index] !== 0x0D || data[index + 1] !== 0x0A) {
|
||||
// multipart content headers are present, so let's parse them...
|
||||
let contentHeader = parseContentHeader(data, index);
|
||||
if (contentHeader && contentHeader.end > index) {
|
||||
if (contentHeader.fields['content-length'] > 0) {
|
||||
let endOfData = contentHeader.end + parseInt(contentHeader.fields['content-length'], 10);
|
||||
if (endOfData === data.indexOf(closingDelimiter, endOfData)) {
|
||||
// content-length is valid...
|
||||
return data.slice(contentHeader.end, endOfData);
|
||||
}
|
||||
}
|
||||
index = contentHeader.end;
|
||||
}
|
||||
} else {
|
||||
// no headers, the content comes right after...
|
||||
index += 2;
|
||||
}
|
||||
|
||||
closingIndex = data.indexOf(closingDelimiter, index);
|
||||
if (closingIndex < 0 || closingIndex < index) {
|
||||
throw {
|
||||
name: 'DICOMWebContentParsingError',
|
||||
message: 'The end of the content could not be determined...'
|
||||
};
|
||||
}
|
||||
|
||||
return data.slice(index, closingIndex);
|
||||
|
||||
}
|
||||
|
||||
function makeRequest(geturl, options, callback) {
|
||||
const headers = 'multipart/related; type=application/octet-stream';
|
||||
const parsed = url.parse(geturl);
|
||||
|
||||
let requestOpt = {
|
||||
hostname: parsed.hostname,
|
||||
headers: {
|
||||
Accept: headers
|
||||
},
|
||||
path: parsed.path,
|
||||
method: 'GET',
|
||||
};
|
||||
|
||||
let requester;
|
||||
if (parsed.protocol === 'https:') {
|
||||
requester = https.request;
|
||||
|
||||
const allowUnauthorizedAgent = new https.Agent({ rejectUnauthorized: false });
|
||||
requestOpt.agent = allowUnauthorizedAgent
|
||||
} else {
|
||||
requester = http.request;
|
||||
}
|
||||
|
||||
if (parsed.port) {
|
||||
requestOpt.port = parsed.port;
|
||||
}
|
||||
|
||||
if (options.auth) {
|
||||
requestOpt.auth = options.auth;
|
||||
}
|
||||
|
||||
let req = requester(requestOpt, function(resp) {
|
||||
|
||||
let data = [];
|
||||
|
||||
if (resp.statusCode !== 200) {
|
||||
callback({
|
||||
name: 'DICOMWebRequestError',
|
||||
message: `Unexpected status code for DICOMWeb response (${resp.statusCode})...`
|
||||
}, null);
|
||||
}
|
||||
|
||||
resp.on('data', function(chunk) {
|
||||
data.push(chunk);
|
||||
});
|
||||
|
||||
resp.on('error', function (responseError) {
|
||||
OHIF.log.error('There was an error in the DICOMWeb Server');
|
||||
OHIF.log.error(responseError.stack);
|
||||
OHIF.log.trace();
|
||||
|
||||
callback(responseError, null);
|
||||
});
|
||||
|
||||
resp.on('end', function() {
|
||||
try {
|
||||
callback(null, parseResponse(resp.headers, Buffer.concat(data)));
|
||||
} catch (error) {
|
||||
callback(error, null);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
req.on('error', function (requestError) {
|
||||
OHIF.log.error('Couldn\'t connect to DICOMWeb server.');
|
||||
OHIF.log.error('Make sure you are trying to connect to the right server and that it is up and running.');
|
||||
OHIF.log.error(requestError.stack);
|
||||
OHIF.log.trace();
|
||||
|
||||
callback(requestError, null);
|
||||
});
|
||||
|
||||
req.end();
|
||||
|
||||
}
|
||||
|
||||
const makeRequestSync = Meteor.wrapAsync(makeRequest);
|
||||
|
||||
// TODO: Unify this stuff with the getJSON code
|
||||
DICOMWeb.getBulkData = function(geturl, options) {
|
||||
|
||||
if (options && options.logRequests) {
|
||||
OHIF.log.info(geturl);
|
||||
}
|
||||
|
||||
if (options && options.logTiming) {
|
||||
console.time(geturl);
|
||||
}
|
||||
|
||||
var result = makeRequestSync(geturl, options);
|
||||
|
||||
if (options && options.logTiming) {
|
||||
console.timeEnd(geturl);
|
||||
}
|
||||
|
||||
if (options && options.logResponses) {
|
||||
OHIF.log.info(result);
|
||||
}
|
||||
|
||||
if (!Buffer.isBuffer(result)) {
|
||||
throw result;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
};
|
||||
@@ -1,110 +0,0 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
|
||||
const http = Npm.require('http');
|
||||
const https = Npm.require('https');
|
||||
const url = Npm.require('url');
|
||||
|
||||
function makeRequest(geturl, options, callback) {
|
||||
const parsed = url.parse(geturl);
|
||||
const jsonHeaders = ['application/json', 'application/dicom+json'];
|
||||
|
||||
let requestOpt = {
|
||||
hostname: parsed.hostname,
|
||||
headers: {
|
||||
Accept: 'application/json'
|
||||
},
|
||||
path: parsed.path,
|
||||
method: 'GET'
|
||||
};
|
||||
|
||||
let requester;
|
||||
if (parsed.protocol === 'https:') {
|
||||
requester = https.request;
|
||||
|
||||
const allowUnauthorizedAgent = new https.Agent({ rejectUnauthorized: false });
|
||||
requestOpt.agent = allowUnauthorizedAgent;
|
||||
} else {
|
||||
requester = http.request;
|
||||
}
|
||||
|
||||
if (parsed.port) {
|
||||
requestOpt.port = parsed.port;
|
||||
}
|
||||
|
||||
if (options.auth) {
|
||||
requestOpt.auth = options.auth;
|
||||
}
|
||||
|
||||
if (options.headers) {
|
||||
Object.keys(options.headers).forEach(key => {
|
||||
const value = options.headers[key];
|
||||
requestOpt.headers[key] = value;
|
||||
});
|
||||
}
|
||||
|
||||
const req = requester(requestOpt, function(resp) {
|
||||
// TODO: handle errors with 400+ code
|
||||
const contentType = (resp.headers['content-type'] || '').split(';')[0];
|
||||
if (jsonHeaders.indexOf(contentType) === -1) {
|
||||
const errorMessage = `We only support json but "${contentType}" was sent by the server`;
|
||||
callback(new Error(errorMessage), null);
|
||||
return;
|
||||
}
|
||||
|
||||
let output = '';
|
||||
|
||||
resp.setEncoding('utf8');
|
||||
|
||||
resp.on('data', function(chunk){
|
||||
output += chunk;
|
||||
});
|
||||
|
||||
resp.on('error', function (responseError) {
|
||||
OHIF.log.error('There was an error in the DICOMWeb Server')
|
||||
OHIF.log.error(error.stack);
|
||||
OHIF.log.trace();
|
||||
|
||||
callback(new Meteor.Error('server-internal-error', responseError.message), null);
|
||||
});
|
||||
|
||||
resp.on('end', function(){
|
||||
callback(null, { data: JSON.parse(output) });
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', function (requestError) {
|
||||
OHIF.log.error('Couldn\'t connect to DICOMWeb server.');
|
||||
OHIF.log.error('Make sure you are trying to connect to the right server and that it is up and running.');
|
||||
OHIF.log.error(requestError.stack);
|
||||
OHIF.log.trace();
|
||||
|
||||
callback(new Meteor.Error('server-connection-error', requestError.message), null);
|
||||
});
|
||||
|
||||
req.end();
|
||||
}
|
||||
|
||||
const makeRequestSync = Meteor.wrapAsync(makeRequest);
|
||||
|
||||
DICOMWeb.getJSON = function(geturl, options) {
|
||||
if (options && options.logRequests) {
|
||||
OHIF.log.info(geturl);
|
||||
}
|
||||
|
||||
if (options && options.logTiming) {
|
||||
console.time(geturl);
|
||||
}
|
||||
|
||||
const result = makeRequestSync(geturl, options);
|
||||
|
||||
if (options && options.logTiming) {
|
||||
console.timeEnd(geturl);
|
||||
}
|
||||
|
||||
if (options && options.logResponses) {
|
||||
OHIF.log.info(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
DICOMWeb.getModalities = function(modality, modalitiesInStudy) {
|
||||
var modalities = {};
|
||||
if (modality) {
|
||||
modalities = modality;
|
||||
}
|
||||
|
||||
if (modalitiesInStudy) {
|
||||
// Find vr in modalities
|
||||
if (modalities.vr && modalities.vr === modalitiesInStudy.vr) {
|
||||
for (var i = 0; i < modalitiesInStudy.Value.length; i++) {
|
||||
var value = modalitiesInStudy.Value[i];
|
||||
if (modalities.Value.indexOf(value) === -1) {
|
||||
modalities.Value.push(value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
modalities = modalitiesInStudy;
|
||||
}
|
||||
}
|
||||
return modalities;
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Returns the Alphabetic version of a PN
|
||||
*
|
||||
* @param element - The group/element of the element (e.g. '00200013')
|
||||
* @param [defaultValue] - The default value to return if the element is not found
|
||||
* @returns {*}
|
||||
*/
|
||||
DICOMWeb.getName = function(element, defaultValue) {
|
||||
if (!element) {
|
||||
return defaultValue;
|
||||
}
|
||||
// Value is not present if the attribute has a zero length value
|
||||
if (!element.Value) {
|
||||
return defaultValue;
|
||||
}
|
||||
// Sanity check to make sure we have at least one entry in the array.
|
||||
if (!element.Value.length) {
|
||||
return defaultValue;
|
||||
}
|
||||
// Return the Alphabetic component group
|
||||
if (element.Value[0].Alphabetic) {
|
||||
return element.Value[0].Alphabetic;
|
||||
}
|
||||
// Orthanc does not return PN properly so this is a temporary workaround
|
||||
return element.Value[0];
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Returns the first string value as a Javascript Number
|
||||
* @param element - The group/element of the element (e.g. '00200013')
|
||||
* @param [defaultValue] - The default value to return if the element does not exist
|
||||
* @returns {*}
|
||||
*/
|
||||
DICOMWeb.getNumber = function(element, defaultValue) {
|
||||
if (!element) {
|
||||
return defaultValue;
|
||||
}
|
||||
// Value is not present if the attribute has a zero length value
|
||||
if (!element.Value) {
|
||||
return defaultValue;
|
||||
}
|
||||
// Sanity check to make sure we have at least one entry in the array.
|
||||
if (!element.Value.length) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return parseFloat(element.Value[0]);
|
||||
};
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* Returns the specified element as a string. Multi-valued elements will be separated by a backslash
|
||||
*
|
||||
* @param element - The group/element of the element (e.g. '00200013')
|
||||
* @param [defaultValue] - The value to return if the element is not present
|
||||
* @returns {*}
|
||||
*/
|
||||
DICOMWeb.getString = function(element, defaultValue) {
|
||||
if (!element) {
|
||||
return defaultValue;
|
||||
}
|
||||
// Value is not present if the attribute has a zero length value
|
||||
if (!element.Value) {
|
||||
return defaultValue;
|
||||
}
|
||||
// Sanity check to make sure we have at least one entry in the array.
|
||||
if (!element.Value.length) {
|
||||
return defaultValue;
|
||||
}
|
||||
// Join the array together separated by backslash
|
||||
// NOTE: Orthanc does not correctly split values into an array so the join is a no-op
|
||||
return element.Value.join('\\');
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
DICOMWeb = {};
|
||||
Reference in new issue
Block a user