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 }