Mercurial > hg > orthanc-stone
annotate Framework/Oracle/WebAssemblyOracle.cpp @ 841:266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
in WebAssemblyOracle + support for LookupTableTextureSceneLayer in OpenGL (NOT
using shaders!) (2 new files) + a few small non-functional changes
author | Benjamin Golinvaux <bgo@osimis.io> |
---|---|
date | Tue, 11 Jun 2019 15:41:21 +0200 |
parents | d71cf8504159 |
children | 67f9c27214c5 |
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 { | |
841
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
287 if (body != "") |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
288 { |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
289 LOG(ERROR) << "Setting non-empty body. body size = " << body.size() << " body = " << body; |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
290 } |
825 | 291 body_.swap(body); |
841
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
292 LOG(ERROR) << "After setting non-empty body. body_ size = " << body_.size() << " body_ = " << body_; |
825 | 293 } |
294 | |
295 void SetHttpHeaders(const HttpHeaders& headers) | |
296 { | |
297 headers_ = headers; | |
298 } | |
299 | |
300 void SetTimeout(unsigned int timeout) | |
301 { | |
302 timeout_ = timeout; | |
303 } | |
304 | |
305 void Execute() | |
306 { | |
307 if (command_.get() == NULL) | |
308 { | |
309 // Cannot call Execute() twice | |
310 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
311 } | |
312 | |
313 emscripten_fetch_attr_t attr; | |
314 emscripten_fetch_attr_init(&attr); | |
315 | |
316 const char* method; | |
317 | |
318 switch (method_) | |
319 { | |
320 case Orthanc::HttpMethod_Get: | |
321 method = "GET"; | |
322 break; | |
323 | |
324 case Orthanc::HttpMethod_Post: | |
325 method = "POST"; | |
326 break; | |
327 | |
328 case Orthanc::HttpMethod_Delete: | |
329 method = "DELETE"; | |
330 break; | |
331 | |
332 case Orthanc::HttpMethod_Put: | |
333 method = "PUT"; | |
334 break; | |
335 | |
336 default: | |
337 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
338 } | |
339 | |
340 strcpy(attr.requestMethod, method); | |
341 | |
342 attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; | |
343 attr.onsuccess = FetchContext::SuccessCallback; | |
344 attr.onerror = FetchContext::FailureCallback; | |
345 attr.timeoutMSecs = timeout_ * 1000; | |
346 | |
347 std::vector<const char*> headers; | |
348 headers.reserve(2 * headers_.size() + 1); | |
349 | |
350 std::string expectedContentType; | |
351 | |
352 for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it) | |
353 { | |
354 std::string key; | |
355 Orthanc::Toolbox::ToLowerCase(key, it->first); | |
356 | |
357 if (key == "accept") | |
358 { | |
359 expectedContentType = it->second; | |
360 } | |
361 | |
362 if (key != "accept-encoding") // Web browsers forbid the modification of this HTTP header | |
363 { | |
364 headers.push_back(it->first.c_str()); | |
365 headers.push_back(it->second.c_str()); | |
366 } | |
367 } | |
368 | |
369 headers.push_back(NULL); // Termination of the array of HTTP headers | |
370 | |
371 attr.requestHeaders = &headers[0]; | |
372 | |
841
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
373 char* requestData = NULL; |
825 | 374 if (!body_.empty()) |
841
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
375 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
|
376 |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
377 try |
825 | 378 { |
841
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
379 if (!body_.empty()) |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
380 { |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
381 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
|
382 attr.requestDataSize = body_.size(); |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
383 attr.requestData = requestData; |
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 attr.userData = new FetchContext(oracle_, receiver_, command_.release(), expectedContentType); |
825 | 386 |
841
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
387 // 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
|
388 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
|
389 } |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
390 catch(...) |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
391 { |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
392 if(requestData != NULL) |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
393 free(requestData); |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
394 throw; |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
395 } |
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
396 } |
825 | 397 }; |
398 | |
399 void WebAssemblyOracle::Execute(const IObserver& receiver, | |
400 OrthancRestApiCommand* command) | |
401 { | |
402 FetchCommand fetch(*this, receiver, command); | |
403 | |
404 fetch.SetMethod(command->GetMethod()); | |
405 fetch.SetUri(command->GetUri()); | |
406 fetch.SetHttpHeaders(command->GetHttpHeaders()); | |
407 fetch.SetTimeout(command->GetTimeout()); | |
408 | |
841
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
409 if (command->GetMethod() == Orthanc::HttpMethod_Post || |
825 | 410 command->GetMethod() == Orthanc::HttpMethod_Put) |
411 { | |
412 std::string body; | |
413 command->SwapBody(body); | |
414 fetch.SetBody(body); | |
415 } | |
841
266e2b0b9abc
better error reporting in DicomStructureSetLoader + fixed POST request logic
Benjamin Golinvaux <bgo@osimis.io>
parents:
831
diff
changeset
|
416 |
825 | 417 fetch.Execute(); |
418 } | |
419 | |
420 | |
421 void WebAssemblyOracle::Execute(const IObserver& receiver, | |
422 GetOrthancImageCommand* command) | |
423 { | |
424 FetchCommand fetch(*this, receiver, command); | |
425 | |
426 fetch.SetUri(command->GetUri()); | |
427 fetch.SetHttpHeaders(command->GetHttpHeaders()); | |
428 fetch.SetTimeout(command->GetTimeout()); | |
429 | |
430 fetch.Execute(); | |
431 } | |
432 | |
433 | |
434 void WebAssemblyOracle::Execute(const IObserver& receiver, | |
435 GetOrthancWebViewerJpegCommand* command) | |
436 { | |
437 FetchCommand fetch(*this, receiver, command); | |
438 | |
439 fetch.SetUri(command->GetUri()); | |
440 fetch.SetHttpHeaders(command->GetHttpHeaders()); | |
441 fetch.SetTimeout(command->GetTimeout()); | |
442 | |
443 fetch.Execute(); | |
444 } | |
445 | |
446 | |
447 | |
448 void WebAssemblyOracle::Schedule(const IObserver& receiver, | |
449 IOracleCommand* command) | |
450 { | |
451 std::auto_ptr<IOracleCommand> protection(command); | |
452 | |
453 if (command == NULL) | |
454 { | |
455 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
456 } | |
457 | |
458 switch (command->GetType()) | |
459 { | |
460 case IOracleCommand::Type_OrthancRestApi: | |
461 Execute(receiver, dynamic_cast<OrthancRestApiCommand*>(protection.release())); | |
462 break; | |
463 | |
464 case IOracleCommand::Type_GetOrthancImage: | |
465 Execute(receiver, dynamic_cast<GetOrthancImageCommand*>(protection.release())); | |
466 break; | |
467 | |
468 case IOracleCommand::Type_GetOrthancWebViewerJpeg: | |
469 Execute(receiver, dynamic_cast<GetOrthancWebViewerJpegCommand*>(protection.release())); | |
470 break; | |
471 | |
472 case IOracleCommand::Type_Sleep: | |
473 { | |
474 unsigned int timeoutMS = dynamic_cast<SleepOracleCommand*>(command)->GetDelay(); | |
475 emscripten_set_timeout(TimeoutContext::Callback, timeoutMS, | |
476 new TimeoutContext(*this, receiver, protection.release())); | |
477 break; | |
478 } | |
479 | |
480 default: | |
481 LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle: " << command->GetType(); | |
482 } | |
483 } | |
484 } |