comparison Applications/Platforms/WebAssembly/WebAssemblyOracle.cpp @ 1591:5887a4f8594b

moving platform-specific files out of the "OrthancStone" folder
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 23 Oct 2020 13:15:03 +0200
parents OrthancStone/Sources/Oracle/WebAssemblyOracle.cpp@1b3039384972
children 4fb8fdf03314
comparison
equal deleted inserted replaced
1590:7b963bccafef 1591:5887a4f8594b
1 /**
2 * Stone of Orthanc
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2020 Osimis S.A., Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU Affero General Public License
9 * as published by the Free Software Foundation, either version 3 of
10 * the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 **/
20
21
22 #if defined(ORTHANC_BUILDING_STONE_LIBRARY) && ORTHANC_BUILDING_STONE_LIBRARY == 1
23 # include "WebAssemblyOracle_Includes.h"
24 #else
25 // This is the case when using the WebAssembly side module, and this
26 // source file must be compiled within the WebAssembly main module
27 # include <Oracle/WebAssemblyOracle_Includes.h>
28 #endif
29
30 #include <OrthancException.h>
31 #include <Toolbox.h>
32
33 #include <emscripten.h>
34 #include <emscripten/html5.h>
35 #include <emscripten/fetch.h>
36
37
38 #if ORTHANC_ENABLE_DCMTK == 1
39 static unsigned int BUCKET_SOP = 1;
40 #endif
41
42
43 namespace OrthancStone
44 {
45 class WebAssemblyOracle::TimeoutContext
46 {
47 private:
48 WebAssemblyOracle& oracle_;
49 boost::weak_ptr<IObserver> receiver_;
50 std::unique_ptr<SleepOracleCommand> command_;
51
52 public:
53 TimeoutContext(WebAssemblyOracle& oracle,
54 boost::weak_ptr<IObserver> receiver,
55 IOracleCommand* command) :
56 oracle_(oracle),
57 receiver_(receiver)
58 {
59 if (command == NULL)
60 {
61 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
62 }
63 else
64 {
65 command_.reset(dynamic_cast<SleepOracleCommand*>(command));
66 }
67 }
68
69 void EmitMessage()
70 {
71 assert(command_.get() != NULL);
72
73 SleepOracleCommand::TimeoutMessage message(*command_);
74 oracle_.EmitMessage(receiver_, message);
75 }
76
77 static void Callback(void *userData)
78 {
79 std::unique_ptr<TimeoutContext> context(reinterpret_cast<TimeoutContext*>(userData));
80 context->EmitMessage();
81 }
82 };
83
84
85 /**
86 This object is created on the heap for every http request.
87 It is deleted in the success (or error) callbacks.
88
89 This object references the receiver of the request. Since this is a raw
90 reference, we need additional checks to make sure we send the response to
91 the same object, for the object can be deleted and a new one recreated at the
92 same address (it often happens in the [single-threaded] browser context).
93 */
94 class WebAssemblyOracle::FetchContext : public boost::noncopyable
95 {
96 private:
97 WebAssemblyOracle& oracle_;
98 boost::weak_ptr<IObserver> receiver_;
99 std::unique_ptr<IOracleCommand> command_;
100 std::string expectedContentType_;
101
102 public:
103 FetchContext(WebAssemblyOracle& oracle,
104 boost::weak_ptr<IObserver> receiver,
105 IOracleCommand* command,
106 const std::string& expectedContentType) :
107 oracle_(oracle),
108 receiver_(receiver),
109 command_(command),
110 expectedContentType_(expectedContentType)
111 {
112 if (Orthanc::Logging::IsTraceLevelEnabled())
113 {
114 // Calling "receiver.lock()" is expensive, hence the quick check if TRACE is enabled
115 LOG(TRACE) << "WebAssemblyOracle::FetchContext::FetchContext() | "
116 << "receiver address = " << std::hex << receiver.lock().get();
117 }
118
119 if (command == NULL)
120 {
121 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
122 }
123 }
124
125 const std::string& GetExpectedContentType() const
126 {
127 return expectedContentType_;
128 }
129
130 IMessageEmitter& GetEmitter() const
131 {
132 return oracle_;
133 }
134
135 boost::weak_ptr<IObserver> GetReceiver() const
136 {
137 return receiver_;
138 }
139
140 void EmitMessage(const IMessage& message)
141 {
142 if (Orthanc::Logging::IsTraceLevelEnabled())
143 {
144 // Calling "receiver_.lock()" is expensive, hence the quick check if TRACE is enabled
145 LOG(TRACE) << "WebAssemblyOracle::FetchContext::EmitMessage receiver_ = "
146 << std::hex << receiver_.lock().get() << std::dec;
147 }
148
149 oracle_.EmitMessage(receiver_, message);
150 }
151
152 IOracleCommand& GetCommand() const
153 {
154 return *command_;
155 }
156
157 template <typename T>
158 const T& GetTypedCommand() const
159 {
160 return dynamic_cast<T&>(*command_);
161 }
162
163 #if ORTHANC_ENABLE_DCMTK == 1
164 void StoreInCache(const std::string& sopInstanceUid,
165 std::unique_ptr<Orthanc::ParsedDicomFile>& dicom,
166 size_t fileSize)
167 {
168 if (oracle_.dicomCache_.get())
169 {
170 // Store it into the cache for future use
171 oracle_.dicomCache_->Acquire(BUCKET_SOP, sopInstanceUid,
172 dicom.release(), fileSize, true);
173 }
174 }
175 #endif
176
177 static void SuccessCallback(emscripten_fetch_t *fetch)
178 {
179 /**
180 * Firstly, make a local copy of the fetched information, and
181 * free data associated with the fetch.
182 **/
183
184 if (fetch->userData == NULL)
185 {
186 LOG(ERROR) << "WebAssemblyOracle::FetchContext::SuccessCallback fetch->userData is NULL!!!!!!!";
187 return;
188 }
189
190 std::unique_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData));
191
192 std::string answer;
193 if (fetch->numBytes > 0)
194 {
195 answer.assign(fetch->data, fetch->numBytes);
196 }
197
198
199 /**
200 * Retrieving the headers of the HTTP answer.
201 **/
202 HttpHeaders headers;
203
204 #if (__EMSCRIPTEN_major__ < 1 || \
205 (__EMSCRIPTEN_major__ == 1 && __EMSCRIPTEN_minor__ < 38) || \
206 (__EMSCRIPTEN_major__ == 1 && __EMSCRIPTEN_minor__ == 38 && __EMSCRIPTEN_tiny__ < 37))
207 # warning Consider upgrading Emscripten to a version above 1.38.37, incomplete support of Fetch API
208
209 /**
210 * HACK - If emscripten < 1.38.37, the fetch API does not
211 * contain a way to retrieve the HTTP headers of the answer. We
212 * make the assumption that the "Content-Type" header of the
213 * response is the same as the "Accept" header of the
214 * query. This is fixed thanks to the
215 * "emscripten_fetch_get_response_headers()" function that was
216 * added to "fetch.h" at emscripten-1.38.37 on 2019-06-26.
217 *
218 * https://github.com/emscripten-core/emscripten/blob/1.38.37/system/include/emscripten/fetch.h
219 * https://github.com/emscripten-core/emscripten/pull/8486
220 **/
221 if (fetch->userData != NULL)
222 {
223 if (!context->GetExpectedContentType().empty())
224 {
225 headers["Content-Type"] = context->GetExpectedContentType();
226 }
227 }
228 #else
229 {
230 size_t size = emscripten_fetch_get_response_headers_length(fetch);
231
232 std::string plainHeaders(size + 1, '\0');
233 emscripten_fetch_get_response_headers(fetch, &plainHeaders[0], size + 1);
234
235 std::vector<std::string> tokens;
236 Orthanc::Toolbox::TokenizeString(tokens, plainHeaders, '\n');
237
238 for (size_t i = 0; i < tokens.size(); i++)
239 {
240 size_t p = tokens[i].find(':');
241 if (p != std::string::npos)
242 {
243 std::string key = Orthanc::Toolbox::StripSpaces(tokens[i].substr(0, p));
244 std::string value = Orthanc::Toolbox::StripSpaces(tokens[i].substr(p + 1));
245 headers[key] = value;
246 }
247 }
248 }
249 #endif
250
251 LOG(TRACE) << "About to call emscripten_fetch_close";
252 emscripten_fetch_close(fetch);
253 LOG(TRACE) << "Successfully called emscripten_fetch_close";
254
255 /**
256 * Secondly, use the retrieved data.
257 * IMPORTANT NOTE: the receiver might be dead. This is prevented
258 * by the object responsible for zombie check, later on.
259 **/
260 try
261 {
262 if (context.get() == NULL)
263 {
264 LOG(ERROR) << "WebAssemblyOracle::FetchContext::SuccessCallback: (context.get() == NULL)";
265 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
266 }
267 else
268 {
269 switch (context->GetCommand().GetType())
270 {
271 case IOracleCommand::Type_Http:
272 {
273 HttpCommand::SuccessMessage message(context->GetTypedCommand<HttpCommand>(), headers, answer);
274 context->EmitMessage(message);
275 break;
276 }
277
278 case IOracleCommand::Type_OrthancRestApi:
279 {
280 LOG(TRACE) << "WebAssemblyOracle::FetchContext::SuccessCallback. About to call context->EmitMessage(message);";
281 OrthancRestApiCommand::SuccessMessage message
282 (context->GetTypedCommand<OrthancRestApiCommand>(), headers, answer);
283 context->EmitMessage(message);
284 break;
285 }
286
287 case IOracleCommand::Type_GetOrthancImage:
288 {
289 context->GetTypedCommand<GetOrthancImageCommand>().ProcessHttpAnswer
290 (context->GetReceiver(), context->GetEmitter(), answer, headers);
291 break;
292 }
293
294 case IOracleCommand::Type_GetOrthancWebViewerJpeg:
295 {
296 context->GetTypedCommand<GetOrthancWebViewerJpegCommand>().ProcessHttpAnswer
297 (context->GetReceiver(), context->GetEmitter(), answer);
298 break;
299 }
300
301 case IOracleCommand::Type_ParseDicomFromWado:
302 {
303 #if ORTHANC_ENABLE_DCMTK == 1
304 const ParseDicomFromWadoCommand& command =
305 context->GetTypedCommand<ParseDicomFromWadoCommand>();
306
307 size_t fileSize;
308 std::unique_ptr<Orthanc::ParsedDicomFile> dicom
309 (ParseDicomSuccessMessage::ParseWadoAnswer(fileSize, answer, headers));
310
311 {
312 ParseDicomSuccessMessage message(command, command.GetSource(), *dicom, fileSize, true);
313 context->EmitMessage(message);
314 }
315
316 context->StoreInCache(command.GetSopInstanceUid(), dicom, fileSize);
317 #else
318 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
319 #endif
320 break;
321 }
322
323 default:
324 LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in SuccessCallback): "
325 << context->GetCommand().GetType();
326 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
327 }
328 }
329 }
330 catch (Orthanc::OrthancException& e)
331 {
332 LOG(INFO) << "Error while processing a fetch answer in the oracle: " << e.What();
333
334 {
335 OracleCommandExceptionMessage message(context->GetCommand(), e);
336 context->EmitMessage(message);
337 }
338 }
339 }
340
341 static void FailureCallback(emscripten_fetch_t *fetch)
342 {
343 std::unique_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData));
344
345 #if 0
346 {
347 const size_t kEmscriptenStatusTextSize = sizeof(emscripten_fetch_t::statusText);
348 char message[kEmscriptenStatusTextSize + 1];
349 memcpy(message, fetch->statusText, kEmscriptenStatusTextSize);
350 message[kEmscriptenStatusTextSize] = 0;
351
352 LOG(ERROR) << "Fetching " << fetch->url
353 << " failed, HTTP failure status code: " << fetch->status
354 << " | statusText = " << message
355 << " | numBytes = " << fetch->numBytes
356 << " | totalBytes = " << fetch->totalBytes
357 << " | readyState = " << fetch->readyState;
358 }
359 #endif
360
361 {
362 OracleCommandExceptionMessage message
363 (context->GetCommand(), Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol));
364 context->EmitMessage(message);
365 }
366
367 /**
368 * TODO - The following code leads to an infinite recursion, at
369 * least with Firefox running on incognito mode => WHY?
370 **/
371 emscripten_fetch_close(fetch); // Also free data on failure.
372 }
373 };
374
375
376
377 class WebAssemblyOracle::FetchCommand : public boost::noncopyable
378 {
379 private:
380 WebAssemblyOracle& oracle_;
381 boost::weak_ptr<IObserver> receiver_;
382 std::unique_ptr<IOracleCommand> command_;
383 Orthanc::HttpMethod method_;
384 std::string url_;
385 std::string body_;
386 HttpHeaders headers_;
387 unsigned int timeout_;
388 std::string expectedContentType_;
389 bool hasCredentials_;
390 std::string username_;
391 std::string password_;
392
393 public:
394 FetchCommand(WebAssemblyOracle& oracle,
395 boost::weak_ptr<IObserver> receiver,
396 IOracleCommand* command) :
397 oracle_(oracle),
398 receiver_(receiver),
399 command_(command),
400 method_(Orthanc::HttpMethod_Get),
401 timeout_(0),
402 hasCredentials_(false)
403 {
404 if (command == NULL)
405 {
406 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
407 }
408 }
409
410 void SetMethod(Orthanc::HttpMethod method)
411 {
412 method_ = method;
413 }
414
415 void SetUrl(const std::string& url)
416 {
417 url_ = url;
418 }
419
420 void SetBody(std::string& body /* will be swapped */)
421 {
422 body_.swap(body);
423 }
424
425 void AddHttpHeaders(const HttpHeaders& headers)
426 {
427 for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it)
428 {
429 headers_[it->first] = it->second;
430 }
431 }
432
433 void SetTimeout(unsigned int timeout)
434 {
435 timeout_ = timeout;
436 }
437
438 void SetCredentials(const std::string& username,
439 const std::string& password)
440 {
441 hasCredentials_ = true;
442 username_ = username;
443 password_ = password;
444 }
445
446 void Execute()
447 {
448 if (command_.get() == NULL)
449 {
450 // Cannot call Execute() twice
451 LOG(ERROR) << "WebAssemblyOracle::Execute(): (command_.get() == NULL)";
452 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
453 }
454
455 emscripten_fetch_attr_t attr;
456 emscripten_fetch_attr_init(&attr);
457
458 const char* method;
459
460 switch (method_)
461 {
462 case Orthanc::HttpMethod_Get:
463 method = "GET";
464 break;
465
466 case Orthanc::HttpMethod_Post:
467 method = "POST";
468 break;
469
470 case Orthanc::HttpMethod_Delete:
471 method = "DELETE";
472 break;
473
474 case Orthanc::HttpMethod_Put:
475 method = "PUT";
476 break;
477
478 default:
479 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
480 }
481
482 strcpy(attr.requestMethod, method);
483
484 attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_REPLACE;
485 attr.onsuccess = FetchContext::SuccessCallback;
486 attr.onerror = FetchContext::FailureCallback;
487 attr.timeoutMSecs = timeout_ * 1000;
488
489 if (hasCredentials_)
490 {
491 attr.withCredentials = EM_TRUE;
492 attr.userName = username_.c_str();
493 attr.password = password_.c_str();
494 }
495
496 std::vector<const char*> headers;
497 headers.reserve(2 * headers_.size() + 1);
498
499 std::string expectedContentType;
500
501 for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it)
502 {
503 std::string key;
504 Orthanc::Toolbox::ToLowerCase(key, it->first);
505
506 if (key == "accept")
507 {
508 expectedContentType = it->second;
509 }
510
511 if (key != "accept-encoding") // Web browsers forbid the modification of this HTTP header
512 {
513 headers.push_back(it->first.c_str());
514 headers.push_back(it->second.c_str());
515 }
516 }
517
518 headers.push_back(NULL); // Termination of the array of HTTP headers
519
520 attr.requestHeaders = &headers[0];
521
522 char* requestData = NULL;
523 if (!body_.empty())
524 requestData = reinterpret_cast<char*>(malloc(body_.size()));
525
526 try
527 {
528 if (!body_.empty())
529 {
530 memcpy(requestData, &(body_[0]), body_.size());
531 attr.requestDataSize = body_.size();
532 attr.requestData = requestData;
533 }
534 attr.userData = new FetchContext(oracle_, receiver_, command_.release(), expectedContentType);
535
536 // Must be the last call to prevent memory leak on error
537 emscripten_fetch(&attr, url_.c_str());
538 }
539 catch(...)
540 {
541 if(requestData != NULL)
542 free(requestData);
543 throw;
544 }
545 }
546 };
547
548
549 void WebAssemblyOracle::SetOrthancUrl(FetchCommand& command,
550 const std::string& uri) const
551 {
552 if (isLocalOrthanc_)
553 {
554 command.SetUrl(localOrthancRoot_ + uri);
555 }
556 else
557 {
558 command.SetUrl(remoteOrthanc_.GetUrl() + uri);
559 command.AddHttpHeaders(remoteOrthanc_.GetHttpHeaders());
560
561 if (!remoteOrthanc_.GetUsername().empty())
562 {
563 command.SetCredentials(remoteOrthanc_.GetUsername(), remoteOrthanc_.GetPassword());
564 }
565 }
566 }
567
568
569 void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
570 HttpCommand* command)
571 {
572 FetchCommand fetch(*this, receiver, command);
573
574 fetch.SetMethod(command->GetMethod());
575 fetch.SetUrl(command->GetUrl());
576 fetch.AddHttpHeaders(command->GetHttpHeaders());
577 fetch.SetTimeout(command->GetTimeout());
578
579 if (command->GetMethod() == Orthanc::HttpMethod_Post ||
580 command->GetMethod() == Orthanc::HttpMethod_Put)
581 {
582 std::string body;
583 command->SwapBody(body);
584 fetch.SetBody(body);
585 }
586
587 fetch.Execute();
588 }
589
590
591 void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
592 OrthancRestApiCommand* command)
593 {
594 try
595 {
596 //LOG(TRACE) << "*********** WebAssemblyOracle::Execute.";
597 //LOG(TRACE) << "WebAssemblyOracle::Execute | command = " << command;
598 FetchCommand fetch(*this, receiver, command);
599
600 fetch.SetMethod(command->GetMethod());
601 SetOrthancUrl(fetch, command->GetUri());
602 fetch.AddHttpHeaders(command->GetHttpHeaders());
603 fetch.SetTimeout(command->GetTimeout());
604
605 if (command->GetMethod() == Orthanc::HttpMethod_Post ||
606 command->GetMethod() == Orthanc::HttpMethod_Put)
607 {
608 std::string body;
609 command->SwapBody(body);
610 fetch.SetBody(body);
611 }
612
613 fetch.Execute();
614 //LOG(TRACE) << "*********** successful end of WebAssemblyOracle::Execute.";
615 }
616 catch (const Orthanc::OrthancException& e)
617 {
618 if (e.HasDetails())
619 {
620 LOG(ERROR) << "OrthancException in WebAssemblyOracle::Execute: " << e.What() << " Details: " << e.GetDetails();
621 }
622 else
623 {
624 LOG(ERROR) << "OrthancException in WebAssemblyOracle::Execute: " << e.What();
625 }
626 //LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute.";
627 throw;
628 }
629 catch (const std::exception& e)
630 {
631 LOG(ERROR) << "std::exception in WebAssemblyOracle::Execute: " << e.what();
632 // LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute.";
633 throw;
634 }
635 catch (...)
636 {
637 LOG(ERROR) << "Unknown exception in WebAssemblyOracle::Execute";
638 // LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute.";
639 throw;
640 }
641 }
642
643
644 void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
645 GetOrthancImageCommand* command)
646 {
647 FetchCommand fetch(*this, receiver, command);
648
649 SetOrthancUrl(fetch, command->GetUri());
650 fetch.AddHttpHeaders(command->GetHttpHeaders());
651 fetch.SetTimeout(command->GetTimeout());
652
653 fetch.Execute();
654 }
655
656
657 void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
658 GetOrthancWebViewerJpegCommand* command)
659 {
660 FetchCommand fetch(*this, receiver, command);
661
662 SetOrthancUrl(fetch, command->GetUri());
663 fetch.AddHttpHeaders(command->GetHttpHeaders());
664 fetch.SetTimeout(command->GetTimeout());
665
666 fetch.Execute();
667 }
668
669
670 void WebAssemblyOracle::Execute(boost::weak_ptr<IObserver> receiver,
671 ParseDicomFromWadoCommand* command)
672 {
673 std::unique_ptr<ParseDicomFromWadoCommand> protection(command);
674
675 #if ORTHANC_ENABLE_DCMTK == 1
676 if (dicomCache_.get())
677 {
678 ParsedDicomCache::Reader reader(*dicomCache_, BUCKET_SOP, protection->GetSopInstanceUid());
679 if (reader.IsValid() &&
680 reader.HasPixelData())
681 {
682 // Reuse the DICOM file from the cache
683 ParseDicomSuccessMessage message(*protection, protection->GetSource(), reader.GetDicom(),
684 reader.GetFileSize(), reader.HasPixelData());
685 EmitMessage(receiver, message);
686 return;
687 }
688 }
689 #endif
690
691 switch (command->GetRestCommand().GetType())
692 {
693 case IOracleCommand::Type_Http:
694 {
695 const HttpCommand& rest =
696 dynamic_cast<const HttpCommand&>(protection->GetRestCommand());
697
698 FetchCommand fetch(*this, receiver, protection.release());
699
700 fetch.SetMethod(rest.GetMethod());
701 fetch.SetUrl(rest.GetUrl());
702 fetch.AddHttpHeaders(rest.GetHttpHeaders());
703 fetch.SetTimeout(rest.GetTimeout());
704
705 if (rest.GetMethod() == Orthanc::HttpMethod_Post ||
706 rest.GetMethod() == Orthanc::HttpMethod_Put)
707 {
708 std::string body = rest.GetBody();
709 fetch.SetBody(body);
710 }
711
712 fetch.Execute();
713 break;
714 }
715
716 case IOracleCommand::Type_OrthancRestApi:
717 {
718 const OrthancRestApiCommand& rest =
719 dynamic_cast<const OrthancRestApiCommand&>(protection->GetRestCommand());
720
721 FetchCommand fetch(*this, receiver, protection.release());
722
723 fetch.SetMethod(rest.GetMethod());
724 SetOrthancUrl(fetch, rest.GetUri());
725 fetch.AddHttpHeaders(rest.GetHttpHeaders());
726 fetch.SetTimeout(rest.GetTimeout());
727
728 if (rest.GetMethod() == Orthanc::HttpMethod_Post ||
729 rest.GetMethod() == Orthanc::HttpMethod_Put)
730 {
731 std::string body = rest.GetBody();
732 fetch.SetBody(body);
733 }
734
735 fetch.Execute();
736 break;
737 }
738
739 default:
740 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
741 }
742 }
743
744
745 bool WebAssemblyOracle::Schedule(boost::shared_ptr<IObserver> receiver,
746 IOracleCommand* command)
747 {
748 LOG(TRACE) << "WebAssemblyOracle::Schedule : receiver = "
749 << std::hex << receiver.get();
750
751 std::unique_ptr<IOracleCommand> protection(command);
752
753 if (command == NULL)
754 {
755 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
756 }
757
758 switch (command->GetType())
759 {
760 case IOracleCommand::Type_Http:
761 Execute(receiver, dynamic_cast<HttpCommand*>(protection.release()));
762 break;
763
764 case IOracleCommand::Type_OrthancRestApi:
765 Execute(receiver, dynamic_cast<OrthancRestApiCommand*>(protection.release()));
766 break;
767
768 case IOracleCommand::Type_GetOrthancImage:
769 Execute(receiver, dynamic_cast<GetOrthancImageCommand*>(protection.release()));
770 break;
771
772 case IOracleCommand::Type_GetOrthancWebViewerJpeg:
773 Execute(receiver, dynamic_cast<GetOrthancWebViewerJpegCommand*>(protection.release()));
774 break;
775
776 case IOracleCommand::Type_Sleep:
777 {
778 unsigned int timeoutMS = dynamic_cast<SleepOracleCommand*>(command)->GetDelay();
779 emscripten_set_timeout(TimeoutContext::Callback, timeoutMS,
780 new TimeoutContext(*this, receiver, protection.release()));
781 break;
782 }
783
784 case IOracleCommand::Type_ParseDicomFromWado:
785 #if ORTHANC_ENABLE_DCMTK == 1
786 Execute(receiver, dynamic_cast<ParseDicomFromWadoCommand*>(protection.release()));
787 #else
788 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented,
789 "DCMTK must be enabled to parse DICOM files");
790 #endif
791 break;
792
793 default:
794 LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in Schedule): "
795 << command->GetType();
796 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
797 }
798
799 return true;
800 }
801
802
803 void WebAssemblyOracle::SetDicomCacheSize(size_t size)
804 {
805 #if ORTHANC_ENABLE_DCMTK == 1
806 if (size == 0)
807 {
808 dicomCache_.reset();
809 }
810 else
811 {
812 dicomCache_.reset(new ParsedDicomCache(size));
813 }
814 #else
815 LOG(INFO) << "DCMTK support is disabled, the DICOM cache is disabled";
816 #endif
817 }
818 }