Mercurial > hg > orthanc
comparison OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp @ 4044:d25f4c0fa160 framework
splitting code into OrthancFramework and OrthancServer
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 10 Jun 2020 20:30:34 +0200 |
parents | OrthancServer/OrthancRestApi/OrthancRestApi.cpp@7610af1532c3 |
children | 05b8fd21089c |
comparison
equal
deleted
inserted
replaced
4043:6c6239aec462 | 4044:d25f4c0fa160 |
---|---|
1 /** | |
2 * Orthanc - A Lightweight, RESTful DICOM Store | |
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 General Public License as | |
9 * published by the Free Software Foundation, either version 3 of the | |
10 * License, or (at your option) any later version. | |
11 * | |
12 * In addition, as a special exception, the copyright holders of this | |
13 * program give permission to link the code of its release with the | |
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it | |
15 * that use the same license as the "OpenSSL" library), and distribute | |
16 * the linked executables. You must obey the GNU General Public License | |
17 * in all respects for all of the code used other than "OpenSSL". If you | |
18 * modify file(s) with this exception, you may extend this exception to | |
19 * your version of the file(s), but you are not obligated to do so. If | |
20 * you do not wish to do so, delete this exception statement from your | |
21 * version. If you delete this exception statement from all source files | |
22 * in the program, then also delete it here. | |
23 * | |
24 * This program is distributed in the hope that it will be useful, but | |
25 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
27 * General Public License for more details. | |
28 * | |
29 * You should have received a copy of the GNU General Public License | |
30 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
31 **/ | |
32 | |
33 | |
34 #include "../PrecompiledHeadersServer.h" | |
35 #include "OrthancRestApi.h" | |
36 | |
37 #include "../../Core/Compression/GzipCompressor.h" | |
38 #include "../../Core/Logging.h" | |
39 #include "../../Core/MetricsRegistry.h" | |
40 #include "../../Core/SerializationToolbox.h" | |
41 #include "../ServerContext.h" | |
42 | |
43 #include <boost/algorithm/string/predicate.hpp> | |
44 | |
45 namespace Orthanc | |
46 { | |
47 static void SetupResourceAnswer(Json::Value& result, | |
48 const std::string& publicId, | |
49 ResourceType resourceType, | |
50 StoreStatus status) | |
51 { | |
52 result = Json::objectValue; | |
53 | |
54 if (status != StoreStatus_Failure) | |
55 { | |
56 result["ID"] = publicId; | |
57 result["Path"] = GetBasePath(resourceType, publicId); | |
58 } | |
59 | |
60 result["Status"] = EnumerationToString(status); | |
61 } | |
62 | |
63 | |
64 void OrthancRestApi::AnswerStoredInstance(RestApiPostCall& call, | |
65 DicomInstanceToStore& instance, | |
66 StoreStatus status, | |
67 const std::string& instanceId) const | |
68 { | |
69 Json::Value result; | |
70 SetupResourceAnswer(result, instanceId, ResourceType_Instance, status); | |
71 | |
72 result["ParentPatient"] = instance.GetHasher().HashPatient(); | |
73 result["ParentStudy"] = instance.GetHasher().HashStudy(); | |
74 result["ParentSeries"] = instance.GetHasher().HashSeries(); | |
75 | |
76 call.GetOutput().AnswerJson(result); | |
77 } | |
78 | |
79 | |
80 void OrthancRestApi::AnswerStoredResource(RestApiPostCall& call, | |
81 const std::string& publicId, | |
82 ResourceType resourceType, | |
83 StoreStatus status) const | |
84 { | |
85 Json::Value result; | |
86 SetupResourceAnswer(result, publicId, resourceType, status); | |
87 call.GetOutput().AnswerJson(result); | |
88 } | |
89 | |
90 | |
91 void OrthancRestApi::ResetOrthanc(RestApiPostCall& call) | |
92 { | |
93 OrthancRestApi::GetApi(call).leaveBarrier_ = true; | |
94 OrthancRestApi::GetApi(call).resetRequestReceived_ = true; | |
95 call.GetOutput().AnswerBuffer("{}", MimeType_Json); | |
96 } | |
97 | |
98 | |
99 void OrthancRestApi::ShutdownOrthanc(RestApiPostCall& call) | |
100 { | |
101 OrthancRestApi::GetApi(call).leaveBarrier_ = true; | |
102 call.GetOutput().AnswerBuffer("{}", MimeType_Json); | |
103 LOG(WARNING) << "Shutdown request received"; | |
104 } | |
105 | |
106 | |
107 | |
108 | |
109 | |
110 // Upload of DICOM files through HTTP --------------------------------------- | |
111 | |
112 static void UploadDicomFile(RestApiPostCall& call) | |
113 { | |
114 ServerContext& context = OrthancRestApi::GetContext(call); | |
115 | |
116 LOG(INFO) << "Receiving a DICOM file of " << call.GetBodySize() << " bytes through HTTP"; | |
117 | |
118 if (call.GetBodySize() == 0) | |
119 { | |
120 throw OrthancException(ErrorCode_BadFileFormat, | |
121 "Received an empty DICOM file"); | |
122 } | |
123 | |
124 // The lifetime of "dicom" must be longer than "toStore", as the | |
125 // latter can possibly store a reference to the former (*) | |
126 std::string dicom; | |
127 | |
128 DicomInstanceToStore toStore; | |
129 toStore.SetOrigin(DicomInstanceOrigin::FromRest(call)); | |
130 | |
131 if (boost::iequals(call.GetHttpHeader("content-encoding", ""), "gzip")) | |
132 { | |
133 GzipCompressor compressor; | |
134 compressor.Uncompress(dicom, call.GetBodyData(), call.GetBodySize()); | |
135 toStore.SetBuffer(dicom.c_str(), dicom.size()); // (*) | |
136 } | |
137 else | |
138 { | |
139 toStore.SetBuffer(call.GetBodyData(), call.GetBodySize()); | |
140 } | |
141 | |
142 std::string publicId; | |
143 StoreStatus status = context.Store(publicId, toStore, StoreInstanceMode_Default); | |
144 | |
145 OrthancRestApi::GetApi(call).AnswerStoredInstance(call, toStore, status, publicId); | |
146 } | |
147 | |
148 | |
149 | |
150 // Registration of the various REST handlers -------------------------------- | |
151 | |
152 OrthancRestApi::OrthancRestApi(ServerContext& context) : | |
153 context_(context), | |
154 leaveBarrier_(false), | |
155 resetRequestReceived_(false), | |
156 activeRequests_(context.GetMetricsRegistry(), | |
157 "orthanc_rest_api_active_requests", | |
158 MetricsType_MaxOver10Seconds) | |
159 { | |
160 RegisterSystem(); | |
161 | |
162 RegisterChanges(); | |
163 RegisterResources(); | |
164 RegisterModalities(); | |
165 RegisterAnonymizeModify(); | |
166 RegisterArchive(); | |
167 | |
168 Register("/instances", UploadDicomFile); | |
169 | |
170 // Auto-generated directories | |
171 Register("/tools", RestApi::AutoListChildren); | |
172 Register("/tools/reset", ResetOrthanc); | |
173 Register("/tools/shutdown", ShutdownOrthanc); | |
174 Register("/instances/{id}/frames/{frame}", RestApi::AutoListChildren); | |
175 } | |
176 | |
177 | |
178 bool OrthancRestApi::Handle(HttpOutput& output, | |
179 RequestOrigin origin, | |
180 const char* remoteIp, | |
181 const char* username, | |
182 HttpMethod method, | |
183 const UriComponents& uri, | |
184 const Arguments& headers, | |
185 const GetArguments& getArguments, | |
186 const void* bodyData, | |
187 size_t bodySize) | |
188 { | |
189 MetricsRegistry::Timer timer(context_.GetMetricsRegistry(), "orthanc_rest_api_duration_ms"); | |
190 MetricsRegistry::ActiveCounter counter(activeRequests_); | |
191 | |
192 return RestApi::Handle(output, origin, remoteIp, username, method, | |
193 uri, headers, getArguments, bodyData, bodySize); | |
194 } | |
195 | |
196 | |
197 ServerContext& OrthancRestApi::GetContext(RestApiCall& call) | |
198 { | |
199 return GetApi(call).context_; | |
200 } | |
201 | |
202 | |
203 ServerIndex& OrthancRestApi::GetIndex(RestApiCall& call) | |
204 { | |
205 return GetContext(call).GetIndex(); | |
206 } | |
207 | |
208 | |
209 | |
210 static const char* KEY_PERMISSIVE = "Permissive"; | |
211 static const char* KEY_PRIORITY = "Priority"; | |
212 static const char* KEY_SYNCHRONOUS = "Synchronous"; | |
213 static const char* KEY_ASYNCHRONOUS = "Asynchronous"; | |
214 | |
215 | |
216 bool OrthancRestApi::IsSynchronousJobRequest(bool isDefaultSynchronous, | |
217 const Json::Value& body) | |
218 { | |
219 if (body.type() != Json::objectValue) | |
220 { | |
221 return isDefaultSynchronous; | |
222 } | |
223 else if (body.isMember(KEY_SYNCHRONOUS)) | |
224 { | |
225 return SerializationToolbox::ReadBoolean(body, KEY_SYNCHRONOUS); | |
226 } | |
227 else if (body.isMember(KEY_ASYNCHRONOUS)) | |
228 { | |
229 return !SerializationToolbox::ReadBoolean(body, KEY_ASYNCHRONOUS); | |
230 } | |
231 else | |
232 { | |
233 return isDefaultSynchronous; | |
234 } | |
235 } | |
236 | |
237 | |
238 unsigned int OrthancRestApi::GetJobRequestPriority(const Json::Value& body) | |
239 { | |
240 if (body.type() != Json::objectValue || | |
241 !body.isMember(KEY_PRIORITY)) | |
242 { | |
243 return 0; // Default priority | |
244 } | |
245 else | |
246 { | |
247 return SerializationToolbox::ReadInteger(body, KEY_PRIORITY); | |
248 } | |
249 } | |
250 | |
251 | |
252 void OrthancRestApi::SubmitGenericJob(RestApiOutput& output, | |
253 ServerContext& context, | |
254 IJob* job, | |
255 bool synchronous, | |
256 int priority) | |
257 { | |
258 std::unique_ptr<IJob> raii(job); | |
259 | |
260 if (job == NULL) | |
261 { | |
262 throw OrthancException(ErrorCode_NullPointer); | |
263 } | |
264 | |
265 if (synchronous) | |
266 { | |
267 Json::Value successContent; | |
268 context.GetJobsEngine().GetRegistry().SubmitAndWait | |
269 (successContent, raii.release(), priority); | |
270 | |
271 // Success in synchronous execution | |
272 output.AnswerJson(successContent); | |
273 } | |
274 else | |
275 { | |
276 // Asynchronous mode: Submit the job, but don't wait for its completion | |
277 std::string id; | |
278 context.GetJobsEngine().GetRegistry().Submit | |
279 (id, raii.release(), priority); | |
280 | |
281 Json::Value v; | |
282 v["ID"] = id; | |
283 v["Path"] = "/jobs/" + id; | |
284 output.AnswerJson(v); | |
285 } | |
286 } | |
287 | |
288 | |
289 void OrthancRestApi::SubmitGenericJob(RestApiPostCall& call, | |
290 IJob* job, | |
291 bool isDefaultSynchronous, | |
292 const Json::Value& body) const | |
293 { | |
294 std::unique_ptr<IJob> raii(job); | |
295 | |
296 if (body.type() != Json::objectValue) | |
297 { | |
298 throw OrthancException(ErrorCode_BadFileFormat); | |
299 } | |
300 | |
301 bool synchronous = IsSynchronousJobRequest(isDefaultSynchronous, body); | |
302 int priority = GetJobRequestPriority(body); | |
303 | |
304 SubmitGenericJob(call.GetOutput(), context_, raii.release(), synchronous, priority); | |
305 } | |
306 | |
307 | |
308 void OrthancRestApi::SubmitCommandsJob(RestApiPostCall& call, | |
309 SetOfCommandsJob* job, | |
310 bool isDefaultSynchronous, | |
311 const Json::Value& body) const | |
312 { | |
313 std::unique_ptr<SetOfCommandsJob> raii(job); | |
314 | |
315 if (body.type() != Json::objectValue) | |
316 { | |
317 throw OrthancException(ErrorCode_BadFileFormat); | |
318 } | |
319 | |
320 job->SetDescription("REST API"); | |
321 | |
322 if (body.isMember(KEY_PERMISSIVE)) | |
323 { | |
324 job->SetPermissive(SerializationToolbox::ReadBoolean(body, KEY_PERMISSIVE)); | |
325 } | |
326 else | |
327 { | |
328 job->SetPermissive(false); | |
329 } | |
330 | |
331 SubmitGenericJob(call, raii.release(), isDefaultSynchronous, body); | |
332 } | |
333 } |