Mercurial > hg > orthanc-stone
annotate Framework/Oracle/WebAssemblyOracle.cpp @ 931:fc38c4ab17e3 toa2019072401
Merge
author | Benjamin Golinvaux <bgo@osimis.io> |
---|---|
date | Wed, 24 Jul 2019 18:46:06 +0200 |
parents | 67f9c27214c5 |
children | f75f6cb69c1b |
rev | line source |
---|---|
825 | 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-2019 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 #include "WebAssemblyOracle.h" | |
23 | |
24 #include "SleepOracleCommand.h" | |
25 | |
26 #include <Core/OrthancException.h> | |
27 #include <Core/Toolbox.h> | |
28 | |
29 #include <emscripten.h> | |
30 #include <emscripten/html5.h> | |
31 #include <emscripten/fetch.h> | |
32 | |
33 | |
34 namespace OrthancStone | |
35 { | |
36 class WebAssemblyOracle::TimeoutContext | |
37 { | |
38 private: | |
39 WebAssemblyOracle& oracle_; | |
40 const IObserver& receiver_; | |
41 std::auto_ptr<SleepOracleCommand> command_; | |
42 | |
43 public: | |
44 TimeoutContext(WebAssemblyOracle& oracle, | |
45 const IObserver& receiver, | |
46 IOracleCommand* command) : | |
47 oracle_(oracle), | |
48 receiver_(receiver) | |
49 { | |
50 if (command == NULL) | |
51 { | |
52 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
53 } | |
54 else | |
55 { | |
56 command_.reset(dynamic_cast<SleepOracleCommand*>(command)); | |
57 } | |
58 } | |
59 | |
60 void EmitMessage() | |
61 { | |
62 SleepOracleCommand::TimeoutMessage message(*command_); | |
63 oracle_.EmitMessage(receiver_, message); | |
64 } | |
65 | |
66 static void Callback(void *userData) | |
67 { | |
68 std::auto_ptr<TimeoutContext> context(reinterpret_cast<TimeoutContext*>(userData)); | |
69 context->EmitMessage(); | |
70 } | |
71 }; | |
72 | |
73 | |
74 class WebAssemblyOracle::Emitter : public IMessageEmitter | |
75 { | |
76 private: | |
77 WebAssemblyOracle& oracle_; | |
78 | |
79 public: | |
80 Emitter(WebAssemblyOracle& oracle) : | |
81 oracle_(oracle) | |
82 { | |
83 } | |
84 | |
85 virtual void EmitMessage(const IObserver& receiver, | |
86 const IMessage& message) | |
87 { | |
88 oracle_.EmitMessage(receiver, message); | |
89 } | |
90 }; | |
91 | |
92 | |
93 class WebAssemblyOracle::FetchContext : public boost::noncopyable | |
94 { | |
95 private: | |
96 Emitter emitter_; | |
97 const IObserver& receiver_; | |
98 std::auto_ptr<IOracleCommand> command_; | |
99 std::string expectedContentType_; | |
100 | |
101 public: | |
102 FetchContext(WebAssemblyOracle& oracle, | |
103 const IObserver& receiver, | |
104 IOracleCommand* command, | |
105 const std::string& expectedContentType) : | |
106 emitter_(oracle), | |
107 receiver_(receiver), | |
108 command_(command), | |
109 expectedContentType_(expectedContentType) | |
110 { | |
111 if (command == NULL) | |
112 { | |
113 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
114 } | |
115 } | |
116 | |
117 const std::string& GetExpectedContentType() const | |
118 { | |
119 return expectedContentType_; | |
120 } | |
121 | |
122 void EmitMessage(const IMessage& message) | |
123 { | |
124 emitter_.EmitMessage(receiver_, message); | |
125 } | |
126 | |
127 IMessageEmitter& GetEmitter() | |
128 { | |
129 return emitter_; | |
130 } | |
131 | |
132 const IObserver& GetReceiver() const | |
133 { | |
134 return receiver_; | |
135 } | |
136 | |
137 IOracleCommand& GetCommand() const | |
138 { | |
139 return *command_; | |
140 } | |
141 | |
142 template <typename T> | |
143 const T& GetTypedCommand() const | |
144 { | |
145 return dynamic_cast<T&>(*command_); | |
146 } | |
147 | |
148 static void SuccessCallback(emscripten_fetch_t *fetch) | |
149 { | |
150 /** | |
151 * Firstly, make a local copy of the fetched information, and | |
152 * free data associated with the fetch. | |
153 **/ | |
154 | |
155 std::auto_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData)); | |
156 | |
157 std::string answer; | |
158 if (fetch->numBytes > 0) | |
159 { | |
160 answer.assign(fetch->data, fetch->numBytes); | |
161 } | |
162 | |
163 /** | |
164 * TODO - HACK - As of emscripten-1.38.31, the fetch API does | |
165 * not contain a way to retrieve the HTTP headers of the | |
166 * answer. We make the assumption that the "Content-Type" header | |
167 * of the response is the same as the "Accept" header of the | |
168 * query. This should be fixed in future versions of emscripten. | |
169 * https://github.com/emscripten-core/emscripten/pull/8486 | |
170 **/ | |
171 | |
172 HttpHeaders headers; | |
173 if (!context->GetExpectedContentType().empty()) | |
174 { | |
175 headers["Content-Type"] = context->GetExpectedContentType(); | |
176 } | |
177 | |
178 | |
179 emscripten_fetch_close(fetch); | |
180 | |
181 | |
182 /** | |
183 * Secondly, use the retrieved data. | |
184 **/ | |
185 | |
186 try | |
187 { | |
188 if (context.get() == NULL) | |
189 { | |
190 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
191 } | |
192 else | |
193 { | |
194 switch (context->GetCommand().GetType()) | |
195 { | |
196 case IOracleCommand::Type_OrthancRestApi: | |
197 { | |
198 OrthancRestApiCommand::SuccessMessage message | |
199 (context->GetTypedCommand<OrthancRestApiCommand>(), headers, answer); | |
200 context->EmitMessage(message); | |
201 break; | |
202 } | |
203 | |
204 case IOracleCommand::Type_GetOrthancImage: | |
205 { | |
206 context->GetTypedCommand<GetOrthancImageCommand>().ProcessHttpAnswer | |
207 (context->GetEmitter(), context->GetReceiver(), answer, headers); | |
208 break; | |
209 } | |
210 | |
211 case IOracleCommand::Type_GetOrthancWebViewerJpeg: | |
212 { | |
213 context->GetTypedCommand<GetOrthancWebViewerJpegCommand>().ProcessHttpAnswer | |
214 (context->GetEmitter(), context->GetReceiver(), answer); | |
215 break; | |
216 } | |
217 | |
218 default: | |
219 LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle: " | |
220 << context->GetCommand().GetType(); | |
221 } | |
222 } | |
223 } | |
224 catch (Orthanc::OrthancException& e) | |
225 { | |
226 LOG(ERROR) << "Error while processing a fetch answer in the oracle: " << e.What(); | |
227 } | |
228 } | |
229 | |
230 static void FailureCallback(emscripten_fetch_t *fetch) | |
231 { | |
232 std::auto_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData)); | |
233 | |
234 LOG(ERROR) << "Fetching " << fetch->url << " failed, HTTP failure status code: " << fetch->status; | |
235 | |
236 /** | |
237 * TODO - The following code leads to an infinite recursion, at | |
238 * least with Firefox running on incognito mode => WHY? | |
239 **/ | |
240 //emscripten_fetch_close(fetch); // Also free data on failure. | |
241 } | |
242 }; | |
243 | |
244 | |
245 | |
246 class WebAssemblyOracle::FetchCommand : public boost::noncopyable | |
247 { | |
248 private: | |
249 WebAssemblyOracle& oracle_; | |
250 const IObserver& receiver_; | |
251 std::auto_ptr<IOracleCommand> command_; | |
252 Orthanc::HttpMethod method_; | |
253 std::string uri_; | |
254 std::string body_; | |
255 HttpHeaders headers_; | |
256 unsigned int timeout_; | |
257 std::string expectedContentType_; | |
258 | |
259 public: | |
260 FetchCommand(WebAssemblyOracle& oracle, | |
261 const IObserver& receiver, | |
262 IOracleCommand* command) : | |
263 oracle_(oracle), | |
264 receiver_(receiver), | |
265 command_(command), | |
266 method_(Orthanc::HttpMethod_Get), | |
267 timeout_(0) | |
268 { | |
269 if (command == NULL) | |
270 { | |
271 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
272 } | |
273 } | |
274 | |
275 void SetMethod(Orthanc::HttpMethod method) | |
276 { | |
277 method_ = method; | |
278 } | |
279 | |
280 void SetUri(const std::string& uri) | |
281 { | |
831
d71cf8504159
handling of GET arguments
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
825
diff
changeset
|
282 uri_ = oracle_.orthancRoot_ + uri; |
825 | 283 } |
284 | |
285 void SetBody(std::string& body /* will be swapped */) | |
286 { | |
287 body_.swap(body); | |
288 } | |
289 | |
290 void SetHttpHeaders(const HttpHeaders& headers) | |
291 { | |
292 headers_ = headers; | |
293 } | |
294 | |
295 void SetTimeout(unsigned int timeout) | |
296 { | |
297 timeout_ = timeout; | |
298 } | |
299 | |
300 void Execute() | |
301 { | |
302 if (command_.get() == NULL) | |
303 { | |
304 // Cannot call Execute() twice | |
305 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
306 } | |
307 | |
308 emscripten_fetch_attr_t attr; | |
309 emscripten_fetch_attr_init(&attr); | |
310 | |
311 const char* method; | |
312 | |
313 switch (method_) | |
314 { | |
315 case Orthanc::HttpMethod_Get: | |
316 method = "GET"; | |
317 break; | |
318 | |
319 case Orthanc::HttpMethod_Post: | |
320 method = "POST"; | |
321 break; | |
322 | |
323 case Orthanc::HttpMethod_Delete: | |
324 method = "DELETE"; | |
325 break; | |
326 | |
327 case Orthanc::HttpMethod_Put: | |
328 method = "PUT"; | |
329 break; | |
330 | |
331 default: | |
332 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
333 } | |
334 | |
335 strcpy(attr.requestMethod, method); | |
336 | |
337 attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; | |
338 attr.onsuccess = FetchContext::SuccessCallback; | |
339 attr.onerror = FetchContext::FailureCallback; | |
340 attr.timeoutMSecs = timeout_ * 1000; | |
341 | |
342 std::vector<const char*> headers; | |
343 headers.reserve(2 * headers_.size() + 1); | |
344 | |
345 std::string expectedContentType; | |
346 | |
347 for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it) | |
348 { | |
349 std::string key; | |
350 Orthanc::Toolbox::ToLowerCase(key, it->first); | |
351 | |
352 if (key == "accept") | |
353 { | |
354 expectedContentType = it->second; | |
355 } | |
356 | |
357 if (key != "accept-encoding") // Web browsers forbid the modification of this HTTP header | |
358 { | |
359 headers.push_back(it->first.c_str()); | |
360 headers.push_back(it->second.c_str()); | |
361 } | |
362 } | |
363 | |
364 headers.push_back(NULL); // Termination of the array of HTTP headers | |
365 | |
366 attr.requestHeaders = &headers[0]; | |
367 | |
841
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
368 char* requestData = NULL; |
825 | 369 if (!body_.empty()) |
841
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
370 requestData = reinterpret_cast<char*>(malloc(body_.size())); |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
371 |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
372 try |
825 | 373 { |
841
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
374 if (!body_.empty()) |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
375 { |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
376 memcpy(requestData, &(body_[0]), body_.size()); |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
377 attr.requestDataSize = body_.size(); |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
378 attr.requestData = requestData; |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
379 } |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
380 attr.userData = new FetchContext(oracle_, receiver_, command_.release(), expectedContentType); |
825 | 381 |
841
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
382 // Must be the last call to prevent memory leak on error |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
383 emscripten_fetch(&attr, uri_.c_str()); |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
384 } |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
385 catch(...) |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
386 { |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
387 if(requestData != NULL) |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
388 free(requestData); |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
389 throw; |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
390 } |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
391 } |
825 | 392 }; |
393 | |
394 void WebAssemblyOracle::Execute(const IObserver& receiver, | |
395 OrthancRestApiCommand* command) | |
396 { | |
397 FetchCommand fetch(*this, receiver, command); | |
398 | |
399 fetch.SetMethod(command->GetMethod()); | |
400 fetch.SetUri(command->GetUri()); | |
401 fetch.SetHttpHeaders(command->GetHttpHeaders()); | |
402 fetch.SetTimeout(command->GetTimeout()); | |
403 | |
841
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
404 if (command->GetMethod() == Orthanc::HttpMethod_Post || |
825 | 405 command->GetMethod() == Orthanc::HttpMethod_Put) |
406 { | |
407 std::string body; | |
408 command->SwapBody(body); | |
409 fetch.SetBody(body); | |
410 } | |
841
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
411 |
825 | 412 fetch.Execute(); |
413 } | |
414 | |
415 | |
416 void WebAssemblyOracle::Execute(const IObserver& receiver, | |
417 GetOrthancImageCommand* command) | |
418 { | |
419 FetchCommand fetch(*this, receiver, command); | |
420 | |
421 fetch.SetUri(command->GetUri()); | |
422 fetch.SetHttpHeaders(command->GetHttpHeaders()); | |
423 fetch.SetTimeout(command->GetTimeout()); | |
424 | |
425 fetch.Execute(); | |
426 } | |
427 | |
428 | |
429 void WebAssemblyOracle::Execute(const IObserver& receiver, | |
430 GetOrthancWebViewerJpegCommand* command) | |
431 { | |
432 FetchCommand fetch(*this, receiver, command); | |
433 | |
434 fetch.SetUri(command->GetUri()); | |
435 fetch.SetHttpHeaders(command->GetHttpHeaders()); | |
436 fetch.SetTimeout(command->GetTimeout()); | |
437 | |
438 fetch.Execute(); | |
439 } | |
440 | |
441 | |
442 | |
443 void WebAssemblyOracle::Schedule(const IObserver& receiver, | |
444 IOracleCommand* command) | |
445 { | |
446 std::auto_ptr<IOracleCommand> protection(command); | |
447 | |
448 if (command == NULL) | |
449 { | |
450 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
451 } | |
452 | |
453 switch (command->GetType()) | |
454 { | |
455 case IOracleCommand::Type_OrthancRestApi: | |
456 Execute(receiver, dynamic_cast<OrthancRestApiCommand*>(protection.release())); | |
457 break; | |
458 | |
459 case IOracleCommand::Type_GetOrthancImage: | |
460 Execute(receiver, dynamic_cast<GetOrthancImageCommand*>(protection.release())); | |
461 break; | |
462 | |
463 case IOracleCommand::Type_GetOrthancWebViewerJpeg: | |
464 Execute(receiver, dynamic_cast<GetOrthancWebViewerJpegCommand*>(protection.release())); | |
465 break; | |
466 | |
467 case IOracleCommand::Type_Sleep: | |
468 { | |
469 unsigned int timeoutMS = dynamic_cast<SleepOracleCommand*>(command)->GetDelay(); | |
470 emscripten_set_timeout(TimeoutContext::Callback, timeoutMS, | |
471 new TimeoutContext(*this, receiver, protection.release())); | |
472 break; | |
473 } | |
474 | |
475 default: | |
476 LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle: " << command->GetType(); | |
477 } | |
478 } | |
479 } |