Mercurial > hg > orthanc
annotate PalantirServer/PalantirRestApi.cpp @ 34:96e57b863dd9
option to disallow remote access
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 30 Aug 2012 11:22:21 +0200 |
parents | 3a584803783e |
children | f6d12037f886 |
rev | line source |
---|---|
0 | 1 /** |
2 * Palantir - A Lightweight, RESTful DICOM Store | |
3 * Copyright (C) 2012 Medical Physics Department, CHU of Liege, | |
4 * Belgium | |
5 * | |
6 * This program is free software: you can redistribute it and/or | |
7 * modify it under the terms of the GNU General Public License as | |
8 * published by the Free Software Foundation, either version 3 of the | |
9 * License, or (at your option) any later version. | |
10 * | |
11 * This program is distributed in the hope that it will be useful, but | |
12 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 * General Public License for more details. | |
15 * | |
16 * You should have received a copy of the GNU General Public License | |
17 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
18 **/ | |
19 | |
20 | |
21 #include "PalantirRestApi.h" | |
22 | |
23 #include "PalantirInitialization.h" | |
24 #include "FromDcmtkBridge.h" | |
25 #include "../Core/Uuid.h" | |
26 | |
27 #include <dcmtk/dcmdata/dcistrmb.h> | |
28 #include <dcmtk/dcmdata/dcfilefo.h> | |
29 #include <boost/lexical_cast.hpp> | |
30 | |
31 namespace Palantir | |
32 { | |
33 static void SendJson(HttpOutput& output, | |
34 const Json::Value& value) | |
35 { | |
36 Json::StyledWriter writer; | |
37 std::string s = writer.write(value); | |
38 output.AnswerBufferWithContentType(s, "application/json"); | |
39 } | |
40 | |
41 bool PalantirRestApi::Store(Json::Value& result, | |
42 const std::string& postData) | |
43 { | |
44 // Prepare an input stream for the memory buffer | |
45 DcmInputBufferStream is; | |
46 if (postData.size() > 0) | |
47 { | |
48 is.setBuffer(&postData[0], postData.size()); | |
49 } | |
50 is.setEos(); | |
51 | |
34
96e57b863dd9
option to disallow remote access
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
8
diff
changeset
|
52 //printf("[%d]\n", postData.size()); |
0 | 53 |
54 DcmFileFormat dicomFile; | |
55 if (dicomFile.read(is).good()) | |
56 { | |
57 DicomMap dicomSummary; | |
58 FromDcmtkBridge::Convert(dicomSummary, *dicomFile.getDataset()); | |
59 | |
60 Json::Value dicomJson; | |
61 FromDcmtkBridge::ToJson(dicomJson, *dicomFile.getDataset()); | |
62 | |
63 std::string instanceUuid; | |
64 StoreStatus status = StoreStatus_Failure; | |
65 if (postData.size() > 0) | |
66 { | |
67 status = index_.Store | |
68 (instanceUuid, storage_, reinterpret_cast<const char*>(&postData[0]), | |
69 postData.size(), dicomSummary, dicomJson, ""); | |
70 } | |
71 | |
72 switch (status) | |
73 { | |
74 case StoreStatus_Success: | |
75 result["ID"] = instanceUuid; | |
76 result["Path"] = "/instances/" + instanceUuid; | |
77 result["Status"] = "Success"; | |
78 return true; | |
79 | |
80 case StoreStatus_AlreadyStored: | |
81 result["ID"] = instanceUuid; | |
82 result["Path"] = "/instances/" + instanceUuid; | |
83 result["Status"] = "AlreadyStored"; | |
84 return true; | |
85 | |
86 default: | |
87 return false; | |
88 } | |
89 } | |
90 | |
91 return false; | |
92 } | |
93 | |
94 void PalantirRestApi::ConnectToModality(DicomUserConnection& c, | |
95 const std::string& name) | |
96 { | |
97 std::string aet, address; | |
98 int port; | |
99 GetDicomModality(name, aet, address, port); | |
100 c.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "PALANTIR")); | |
101 c.SetDistantApplicationEntityTitle(aet); | |
102 c.SetDistantHost(address); | |
103 c.SetDistantPort(port); | |
104 c.Open(); | |
105 } | |
106 | |
107 bool PalantirRestApi::MergeQueryAndTemplate(DicomMap& result, | |
108 const std::string& postData) | |
109 { | |
110 Json::Value query; | |
111 Json::Reader reader; | |
112 | |
113 if (!reader.parse(postData, query) || | |
8 | 114 query.type() != Json::objectValue) |
0 | 115 { |
116 return false; | |
117 } | |
118 | |
119 Json::Value::Members members = query.getMemberNames(); | |
120 for (size_t i = 0; i < members.size(); i++) | |
121 { | |
122 DicomTag t = FromDcmtkBridge::FindTag(members[i]); | |
123 result.SetValue(t, query[members[i]].asString()); | |
124 } | |
125 | |
126 return true; | |
127 } | |
128 | |
129 bool PalantirRestApi::DicomFindPatient(Json::Value& result, | |
130 DicomUserConnection& c, | |
131 const std::string& postData) | |
132 { | |
133 DicomMap m; | |
134 DicomMap::SetupFindPatientTemplate(m); | |
135 if (!MergeQueryAndTemplate(m, postData)) | |
136 { | |
137 return false; | |
138 } | |
139 | |
140 DicomFindAnswers answers; | |
141 c.FindPatient(answers, m); | |
142 answers.ToJson(result); | |
143 return true; | |
144 } | |
145 | |
146 bool PalantirRestApi::DicomFindStudy(Json::Value& result, | |
147 DicomUserConnection& c, | |
148 const std::string& postData) | |
149 { | |
150 DicomMap m; | |
151 DicomMap::SetupFindStudyTemplate(m); | |
152 if (!MergeQueryAndTemplate(m, postData)) | |
153 { | |
154 return false; | |
155 } | |
156 | |
157 if (m.GetValue(DicomTag::ACCESSION_NUMBER).AsString().size() <= 2 && | |
158 m.GetValue(DicomTag::PATIENT_ID).AsString().size() <= 2) | |
159 { | |
160 return false; | |
161 } | |
162 | |
163 DicomFindAnswers answers; | |
164 c.FindStudy(answers, m); | |
165 answers.ToJson(result); | |
166 return true; | |
167 } | |
168 | |
169 bool PalantirRestApi::DicomFindSeries(Json::Value& result, | |
170 DicomUserConnection& c, | |
171 const std::string& postData) | |
172 { | |
173 DicomMap m; | |
174 DicomMap::SetupFindSeriesTemplate(m); | |
175 if (!MergeQueryAndTemplate(m, postData)) | |
176 { | |
177 return false; | |
178 } | |
179 | |
180 if ((m.GetValue(DicomTag::ACCESSION_NUMBER).AsString().size() <= 2 && | |
181 m.GetValue(DicomTag::PATIENT_ID).AsString().size() <= 2) || | |
182 m.GetValue(DicomTag::STUDY_UID).AsString().size() <= 2) | |
183 { | |
184 return false; | |
185 } | |
186 | |
187 DicomFindAnswers answers; | |
188 c.FindSeries(answers, m); | |
189 answers.ToJson(result); | |
190 return true; | |
191 } | |
192 | |
193 bool PalantirRestApi::DicomFind(Json::Value& result, | |
194 DicomUserConnection& c, | |
195 const std::string& postData) | |
196 { | |
197 DicomMap m; | |
198 DicomMap::SetupFindPatientTemplate(m); | |
199 if (!MergeQueryAndTemplate(m, postData)) | |
200 { | |
201 return false; | |
202 } | |
203 | |
204 DicomFindAnswers patients; | |
205 c.FindPatient(patients, m); | |
206 | |
207 // Loop over the found patients | |
208 result = Json::arrayValue; | |
209 for (size_t i = 0; i < patients.GetSize(); i++) | |
210 { | |
211 Json::Value patient(Json::objectValue); | |
212 FromDcmtkBridge::ToJson(patient, patients.GetAnswer(i)); | |
213 | |
214 DicomMap::SetupFindStudyTemplate(m); | |
215 if (!MergeQueryAndTemplate(m, postData)) | |
216 { | |
217 return false; | |
218 } | |
219 m.CopyTagIfExists(patients.GetAnswer(i), DicomTag::PATIENT_ID); | |
220 | |
221 DicomFindAnswers studies; | |
222 c.FindStudy(studies, m); | |
223 | |
224 patient["Studies"] = Json::arrayValue; | |
225 | |
226 // Loop over the found studies | |
227 for (size_t j = 0; j < studies.GetSize(); j++) | |
228 { | |
229 Json::Value study(Json::objectValue); | |
230 FromDcmtkBridge::ToJson(study, studies.GetAnswer(j)); | |
231 | |
232 DicomMap::SetupFindSeriesTemplate(m); | |
233 if (!MergeQueryAndTemplate(m, postData)) | |
234 { | |
235 return false; | |
236 } | |
237 m.CopyTagIfExists(studies.GetAnswer(j), DicomTag::PATIENT_ID); | |
238 m.CopyTagIfExists(studies.GetAnswer(j), DicomTag::STUDY_UID); | |
239 | |
240 DicomFindAnswers series; | |
241 c.FindSeries(series, m); | |
242 | |
243 // Loop over the found series | |
244 study["Series"] = Json::arrayValue; | |
245 for (size_t k = 0; k < series.GetSize(); k++) | |
246 { | |
247 Json::Value series2(Json::objectValue); | |
248 FromDcmtkBridge::ToJson(series2, series.GetAnswer(k)); | |
249 study["Series"].append(series2); | |
250 } | |
251 | |
252 patient["Studies"].append(study); | |
253 } | |
254 | |
255 result.append(patient); | |
256 } | |
257 | |
258 return true; | |
259 } | |
260 | |
261 | |
262 | |
263 bool PalantirRestApi::DicomStore(Json::Value& result, | |
264 DicomUserConnection& c, | |
265 const std::string& postData) | |
266 { | |
267 Json::Value found(Json::objectValue); | |
268 | |
269 if (!Toolbox::IsUuid(postData)) | |
270 { | |
271 // This is not a UUID, assume this is a DICOM instance | |
272 c.Store(postData); | |
273 } | |
274 else if (index_.GetSeries(found, postData)) | |
275 { | |
276 // The UUID corresponds to a series | |
277 for (size_t i = 0; i < found["Instances"].size(); i++) | |
278 { | |
279 std::string uuid = found["Instances"][i].asString(); | |
280 Json::Value instance(Json::objectValue); | |
281 if (index_.GetInstance(instance, uuid)) | |
282 { | |
283 std::string content; | |
284 storage_.ReadFile(content, instance["FileUuid"].asString()); | |
285 c.Store(content); | |
286 } | |
287 else | |
288 { | |
289 return false; | |
290 } | |
291 } | |
292 } | |
293 else if (index_.GetInstance(found, postData)) | |
294 { | |
295 // The UUID corresponds to an instance | |
296 std::string content; | |
297 storage_.ReadFile(content, found["FileUuid"].asString()); | |
298 c.Store(content); | |
299 } | |
300 else | |
301 { | |
302 return false; | |
303 } | |
304 | |
305 return true; | |
306 } | |
307 | |
308 | |
309 PalantirRestApi::PalantirRestApi(ServerIndex& index, | |
310 const std::string& path) : | |
311 index_(index), | |
312 storage_(path) | |
313 { | |
314 GetListOfDicomModalities(modalities_); | |
315 } | |
316 | |
317 | |
318 void PalantirRestApi::Handle( | |
319 HttpOutput& output, | |
320 const std::string& method, | |
321 const UriComponents& uri, | |
322 const Arguments& headers, | |
323 const Arguments& arguments, | |
324 const std::string& postData) | |
325 { | |
326 if (uri.size() == 0) | |
327 { | |
328 if (method == "GET") | |
329 { | |
330 output.Redirect("/app/explorer.html"); | |
331 } | |
332 else | |
333 { | |
334 output.SendMethodNotAllowedError("GET"); | |
335 } | |
336 | |
337 return; | |
338 } | |
339 | |
340 bool existingResource = false; | |
341 Json::Value result(Json::objectValue); | |
342 | |
343 | |
344 // List all the instances --------------------------------------------------- | |
345 | |
346 if (uri.size() == 1 && uri[0] == "instances") | |
347 { | |
348 if (method == "GET") | |
349 { | |
350 result = Json::Value(Json::arrayValue); | |
351 index_.GetAllUuids(result, "Instances"); | |
352 existingResource = true; | |
353 } | |
354 else if (method == "POST") | |
355 { | |
356 // Add a new instance to the storage | |
357 if (Store(result, postData)) | |
358 { | |
359 SendJson(output, result); | |
360 return; | |
361 } | |
362 else | |
363 { | |
364 output.SendHeader(HttpStatus_415_UnsupportedMediaType); | |
365 return; | |
366 } | |
367 } | |
368 else | |
369 { | |
370 output.SendMethodNotAllowedError("GET,POST"); | |
371 return; | |
372 } | |
373 } | |
374 | |
375 | |
376 // List all the patients, studies or series --------------------------------- | |
377 | |
378 if (uri.size() == 1 && | |
379 (uri[0] == "series" || | |
380 uri[0] == "studies" || | |
381 uri[0] == "patients")) | |
382 { | |
383 if (method == "GET") | |
384 { | |
385 result = Json::Value(Json::arrayValue); | |
386 | |
387 if (uri[0] == "instances") | |
388 index_.GetAllUuids(result, "Instances"); | |
389 else if (uri[0] == "series") | |
390 index_.GetAllUuids(result, "Series"); | |
391 else if (uri[0] == "studies") | |
392 index_.GetAllUuids(result, "Studies"); | |
393 else if (uri[0] == "patients") | |
394 index_.GetAllUuids(result, "Patients"); | |
395 | |
396 existingResource = true; | |
397 } | |
398 else | |
399 { | |
400 output.SendMethodNotAllowedError("GET"); | |
401 return; | |
402 } | |
403 } | |
404 | |
405 | |
406 // Information about a single object ---------------------------------------- | |
407 | |
408 else if (uri.size() == 2 && | |
409 (uri[0] == "instances" || | |
410 uri[0] == "series" || | |
411 uri[0] == "studies" || | |
412 uri[0] == "patients")) | |
413 { | |
414 if (method == "GET") | |
415 { | |
416 if (uri[0] == "patients") | |
417 { | |
418 existingResource = index_.GetPatient(result, uri[1]); | |
419 } | |
420 else if (uri[0] == "studies") | |
421 { | |
422 existingResource = index_.GetStudy(result, uri[1]); | |
423 } | |
424 else if (uri[0] == "series") | |
425 { | |
426 existingResource = index_.GetSeries(result, uri[1]); | |
427 } | |
428 else if (uri[0] == "instances") | |
429 { | |
430 existingResource = index_.GetInstance(result, uri[1]); | |
431 } | |
432 } | |
433 else if (method == "DELETE") | |
434 { | |
435 if (uri[0] == "patients") | |
436 { | |
437 existingResource = index_.DeletePatient(result, uri[1]); | |
438 } | |
439 else if (uri[0] == "studies") | |
440 { | |
441 existingResource = index_.DeleteStudy(result, uri[1]); | |
442 } | |
443 else if (uri[0] == "series") | |
444 { | |
445 existingResource = index_.DeleteSeries(result, uri[1]); | |
446 } | |
447 else if (uri[0] == "instances") | |
448 { | |
449 existingResource = index_.DeleteInstance(result, uri[1]); | |
450 } | |
451 | |
452 if (existingResource) | |
453 { | |
454 result["Status"] = "Success"; | |
455 } | |
456 } | |
457 else | |
458 { | |
459 output.SendMethodNotAllowedError("GET,DELETE"); | |
460 return; | |
461 } | |
462 } | |
463 | |
464 | |
465 // Get the DICOM or the JSON file of one instance --------------------------- | |
466 | |
467 else if (uri.size() == 3 && | |
468 uri[0] == "instances" && | |
469 (uri[2] == "file" || | |
34
96e57b863dd9
option to disallow remote access
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
8
diff
changeset
|
470 uri[2] == "tags" || |
96e57b863dd9
option to disallow remote access
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
8
diff
changeset
|
471 uri[2] == "named-tags")) |
0 | 472 { |
473 std::string fileUuid, contentType; | |
474 if (uri[2] == "file") | |
475 { | |
476 existingResource = index_.GetDicomFile(fileUuid, uri[1]); | |
477 contentType = "application/dicom"; | |
478 } | |
34
96e57b863dd9
option to disallow remote access
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
8
diff
changeset
|
479 else if (uri[2] == "tags" || |
96e57b863dd9
option to disallow remote access
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
8
diff
changeset
|
480 uri[2] == "named-tags") |
0 | 481 { |
482 existingResource = index_.GetJsonFile(fileUuid, uri[1]); | |
483 contentType = "application/json"; | |
484 } | |
485 | |
486 if (existingResource) | |
487 { | |
488 output.AnswerFile(storage_, fileUuid, contentType); | |
489 return; | |
490 } | |
491 } | |
492 | |
493 | |
494 else if (uri.size() == 3 && | |
495 uri[0] == "instances" && | |
496 uri[2] == "normalized-image") | |
497 { | |
498 std::string uuid; | |
499 existingResource = index_.GetDicomFile(uuid, uri[1]); | |
500 | |
501 if (existingResource) | |
502 { | |
503 std::string dicomContent, png; | |
504 storage_.ReadFile(dicomContent, uuid); | |
505 try | |
506 { | |
507 FromDcmtkBridge::ExtractNormalizedImage(png, dicomContent); | |
508 output.AnswerBufferWithContentType(png, "image/png"); | |
509 return; | |
510 } | |
511 catch (PalantirException&) | |
512 { | |
513 output.Redirect("/app/images/Unsupported.png"); | |
514 return; | |
515 } | |
516 } | |
517 } | |
518 | |
519 | |
520 | |
521 // Changes API -------------------------------------------------------------- | |
522 | |
523 if (uri.size() == 1 && uri[0] == "changes") | |
524 { | |
525 if (method == "GET") | |
526 { | |
527 const static unsigned int MAX_RESULTS = 100; | |
528 | |
529 std::string filter = GetArgument(arguments, "filter", ""); | |
530 int64_t since; | |
531 unsigned int limit; | |
532 try | |
533 { | |
534 since = boost::lexical_cast<int64_t>(GetArgument(arguments, "since", "0")); | |
535 limit = boost::lexical_cast<unsigned int>(GetArgument(arguments, "limit", "0")); | |
536 } | |
537 catch (boost::bad_lexical_cast) | |
538 { | |
539 output.SendHeader(HttpStatus_400_BadRequest); | |
540 return; | |
541 } | |
542 | |
543 if (limit == 0 || limit > MAX_RESULTS) | |
544 { | |
545 limit = MAX_RESULTS; | |
546 } | |
547 | |
548 if (!index_.GetChanges(result, since, filter, limit)) | |
549 { | |
550 output.SendHeader(HttpStatus_400_BadRequest); | |
551 return; | |
552 } | |
553 | |
554 existingResource = true; | |
555 } | |
556 else | |
557 { | |
558 output.SendMethodNotAllowedError("GET"); | |
559 return; | |
560 } | |
561 } | |
562 | |
563 | |
564 // DICOM bridge ------------------------------------------------------------- | |
565 | |
566 if (uri.size() == 1 && | |
567 uri[0] == "modalities") | |
568 { | |
569 if (method == "GET") | |
570 { | |
571 result = Json::Value(Json::arrayValue); | |
572 existingResource = true; | |
573 | |
574 for (Modalities::const_iterator it = modalities_.begin(); | |
575 it != modalities_.end(); it++) | |
576 { | |
577 result.append(*it); | |
578 } | |
579 } | |
580 else | |
581 { | |
582 output.SendMethodNotAllowedError("GET"); | |
583 return; | |
584 } | |
585 } | |
586 | |
587 if ((uri.size() == 2 || | |
588 uri.size() == 3) && | |
589 uri[0] == "modalities") | |
590 { | |
591 if (modalities_.find(uri[1]) == modalities_.end()) | |
592 { | |
593 // Unknown modality | |
594 } | |
595 else if (uri.size() == 2) | |
596 { | |
597 if (method != "GET") | |
598 { | |
599 output.SendMethodNotAllowedError("POST"); | |
600 return; | |
601 } | |
602 else | |
603 { | |
604 existingResource = true; | |
605 result = Json::arrayValue; | |
606 result.append("find-patient"); | |
607 result.append("find-study"); | |
608 result.append("find-series"); | |
609 result.append("find"); | |
610 result.append("store"); | |
611 } | |
612 } | |
613 else if (uri.size() == 3) | |
614 { | |
615 if (uri[2] != "find-patient" && | |
616 uri[2] != "find-study" && | |
617 uri[2] != "find-series" && | |
618 uri[2] != "find" && | |
619 uri[2] != "store") | |
620 { | |
621 // Unknown request | |
622 } | |
623 else if (method != "POST") | |
624 { | |
625 output.SendMethodNotAllowedError("POST"); | |
626 return; | |
627 } | |
628 else | |
629 { | |
630 DicomUserConnection connection; | |
631 ConnectToModality(connection, uri[1]); | |
632 existingResource = true; | |
633 | |
634 if ((uri[2] == "find-patient" && !DicomFindPatient(result, connection, postData)) || | |
635 (uri[2] == "find-study" && !DicomFindStudy(result, connection, postData)) || | |
636 (uri[2] == "find-series" && !DicomFindSeries(result, connection, postData)) || | |
637 (uri[2] == "find" && !DicomFind(result, connection, postData)) || | |
638 (uri[2] == "store" && !DicomStore(result, connection, postData))) | |
639 { | |
640 output.SendHeader(HttpStatus_400_BadRequest); | |
641 return; | |
642 } | |
643 } | |
644 } | |
645 } | |
646 | |
647 | |
648 if (existingResource) | |
649 { | |
650 SendJson(output, result); | |
651 } | |
652 else | |
653 { | |
654 output.SendHeader(HttpStatus_404_NotFound); | |
655 } | |
656 } | |
657 } |