diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp	Wed Jun 10 20:30:34 2020 +0200
@@ -0,0 +1,333 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersServer.h"
+#include "OrthancRestApi.h"
+
+#include "../../Core/Compression/GzipCompressor.h"
+#include "../../Core/Logging.h"
+#include "../../Core/MetricsRegistry.h"
+#include "../../Core/SerializationToolbox.h"
+#include "../ServerContext.h"
+
+#include <boost/algorithm/string/predicate.hpp>
+
+namespace Orthanc
+{
+  static void SetupResourceAnswer(Json::Value& result,
+                                  const std::string& publicId,
+                                  ResourceType resourceType,
+                                  StoreStatus status)
+  {
+    result = Json::objectValue;
+
+    if (status != StoreStatus_Failure)
+    {
+      result["ID"] = publicId;
+      result["Path"] = GetBasePath(resourceType, publicId);
+    }
+    
+    result["Status"] = EnumerationToString(status);
+  }
+
+
+  void OrthancRestApi::AnswerStoredInstance(RestApiPostCall& call,
+                                            DicomInstanceToStore& instance,
+                                            StoreStatus status,
+                                            const std::string& instanceId) const
+  {
+    Json::Value result;
+    SetupResourceAnswer(result, instanceId, ResourceType_Instance, status);
+
+    result["ParentPatient"] = instance.GetHasher().HashPatient();
+    result["ParentStudy"] = instance.GetHasher().HashStudy();
+    result["ParentSeries"] = instance.GetHasher().HashSeries();
+
+    call.GetOutput().AnswerJson(result);
+  }
+
+
+  void OrthancRestApi::AnswerStoredResource(RestApiPostCall& call,
+                                            const std::string& publicId,
+                                            ResourceType resourceType,
+                                            StoreStatus status) const
+  {
+    Json::Value result;
+    SetupResourceAnswer(result, publicId, resourceType, status);
+    call.GetOutput().AnswerJson(result);
+  }
+
+
+  void OrthancRestApi::ResetOrthanc(RestApiPostCall& call)
+  {
+    OrthancRestApi::GetApi(call).leaveBarrier_ = true;
+    OrthancRestApi::GetApi(call).resetRequestReceived_ = true;
+    call.GetOutput().AnswerBuffer("{}", MimeType_Json);
+  }
+
+
+  void OrthancRestApi::ShutdownOrthanc(RestApiPostCall& call)
+  {
+    OrthancRestApi::GetApi(call).leaveBarrier_ = true;
+    call.GetOutput().AnswerBuffer("{}", MimeType_Json);
+    LOG(WARNING) << "Shutdown request received";
+  }
+
+
+
+
+
+  // Upload of DICOM files through HTTP ---------------------------------------
+
+  static void UploadDicomFile(RestApiPostCall& call)
+  {
+    ServerContext& context = OrthancRestApi::GetContext(call);
+
+    LOG(INFO) << "Receiving a DICOM file of " << call.GetBodySize() << " bytes through HTTP";
+
+    if (call.GetBodySize() == 0)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat,
+                             "Received an empty DICOM file");
+    }
+
+    // The lifetime of "dicom" must be longer than "toStore", as the
+    // latter can possibly store a reference to the former (*)
+    std::string dicom;
+
+    DicomInstanceToStore toStore;
+    toStore.SetOrigin(DicomInstanceOrigin::FromRest(call));
+
+    if (boost::iequals(call.GetHttpHeader("content-encoding", ""), "gzip"))
+    {
+      GzipCompressor compressor;
+      compressor.Uncompress(dicom, call.GetBodyData(), call.GetBodySize());
+      toStore.SetBuffer(dicom.c_str(), dicom.size());  // (*)
+    }
+    else
+    {
+      toStore.SetBuffer(call.GetBodyData(), call.GetBodySize());
+    }    
+
+    std::string publicId;
+    StoreStatus status = context.Store(publicId, toStore, StoreInstanceMode_Default);
+
+    OrthancRestApi::GetApi(call).AnswerStoredInstance(call, toStore, status, publicId);
+  }
+
+
+
+  // Registration of the various REST handlers --------------------------------
+
+  OrthancRestApi::OrthancRestApi(ServerContext& context) : 
+    context_(context),
+    leaveBarrier_(false),
+    resetRequestReceived_(false),
+    activeRequests_(context.GetMetricsRegistry(), 
+                    "orthanc_rest_api_active_requests", 
+                    MetricsType_MaxOver10Seconds)
+  {
+    RegisterSystem();
+
+    RegisterChanges();
+    RegisterResources();
+    RegisterModalities();
+    RegisterAnonymizeModify();
+    RegisterArchive();
+
+    Register("/instances", UploadDicomFile);
+
+    // Auto-generated directories
+    Register("/tools", RestApi::AutoListChildren);
+    Register("/tools/reset", ResetOrthanc);
+    Register("/tools/shutdown", ShutdownOrthanc);
+    Register("/instances/{id}/frames/{frame}", RestApi::AutoListChildren);
+  }
+
+
+  bool OrthancRestApi::Handle(HttpOutput& output,
+                              RequestOrigin origin,
+                              const char* remoteIp,
+                              const char* username,
+                              HttpMethod method,
+                              const UriComponents& uri,
+                              const Arguments& headers,
+                              const GetArguments& getArguments,
+                              const void* bodyData,
+                              size_t bodySize)
+  {
+    MetricsRegistry::Timer timer(context_.GetMetricsRegistry(), "orthanc_rest_api_duration_ms");
+    MetricsRegistry::ActiveCounter counter(activeRequests_);
+
+    return RestApi::Handle(output, origin, remoteIp, username, method,
+                           uri, headers, getArguments, bodyData, bodySize);
+  }
+
+
+  ServerContext& OrthancRestApi::GetContext(RestApiCall& call)
+  {
+    return GetApi(call).context_;
+  }
+
+
+  ServerIndex& OrthancRestApi::GetIndex(RestApiCall& call)
+  {
+    return GetContext(call).GetIndex();
+  }
+
+
+
+  static const char* KEY_PERMISSIVE = "Permissive";
+  static const char* KEY_PRIORITY = "Priority";
+  static const char* KEY_SYNCHRONOUS = "Synchronous";
+  static const char* KEY_ASYNCHRONOUS = "Asynchronous";
+
+  
+  bool OrthancRestApi::IsSynchronousJobRequest(bool isDefaultSynchronous,
+                                               const Json::Value& body)
+  {
+    if (body.type() != Json::objectValue)
+    {
+      return isDefaultSynchronous;
+    }
+    else if (body.isMember(KEY_SYNCHRONOUS))
+    {
+      return SerializationToolbox::ReadBoolean(body, KEY_SYNCHRONOUS);
+    }
+    else if (body.isMember(KEY_ASYNCHRONOUS))
+    {
+      return !SerializationToolbox::ReadBoolean(body, KEY_ASYNCHRONOUS);
+    }
+    else
+    {
+      return isDefaultSynchronous;
+    }
+  }
+
+  
+  unsigned int OrthancRestApi::GetJobRequestPriority(const Json::Value& body)
+  {
+    if (body.type() != Json::objectValue ||
+        !body.isMember(KEY_PRIORITY))
+    {
+      return 0;   // Default priority
+    }
+    else 
+    {
+      return SerializationToolbox::ReadInteger(body, KEY_PRIORITY);
+    }
+  }
+  
+
+  void OrthancRestApi::SubmitGenericJob(RestApiOutput& output,
+                                        ServerContext& context,
+                                        IJob* job,
+                                        bool synchronous,
+                                        int priority)
+  {
+    std::unique_ptr<IJob> raii(job);
+    
+    if (job == NULL)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+
+    if (synchronous)
+    {
+      Json::Value successContent;
+      context.GetJobsEngine().GetRegistry().SubmitAndWait
+        (successContent, raii.release(), priority);
+
+      // Success in synchronous execution
+      output.AnswerJson(successContent);
+    }
+    else
+    {
+      // Asynchronous mode: Submit the job, but don't wait for its completion
+      std::string id;
+      context.GetJobsEngine().GetRegistry().Submit
+        (id, raii.release(), priority);
+
+      Json::Value v;
+      v["ID"] = id;
+      v["Path"] = "/jobs/" + id;
+      output.AnswerJson(v);
+    }
+  }
+
+  
+  void OrthancRestApi::SubmitGenericJob(RestApiPostCall& call,
+                                        IJob* job,
+                                        bool isDefaultSynchronous,
+                                        const Json::Value& body) const
+  {
+    std::unique_ptr<IJob> raii(job);
+
+    if (body.type() != Json::objectValue)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    bool synchronous = IsSynchronousJobRequest(isDefaultSynchronous, body);
+    int priority = GetJobRequestPriority(body);
+
+    SubmitGenericJob(call.GetOutput(), context_, raii.release(), synchronous, priority);
+  }
+
+  
+  void OrthancRestApi::SubmitCommandsJob(RestApiPostCall& call,
+                                         SetOfCommandsJob* job,
+                                         bool isDefaultSynchronous,
+                                         const Json::Value& body) const
+  {
+    std::unique_ptr<SetOfCommandsJob> raii(job);
+    
+    if (body.type() != Json::objectValue)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    job->SetDescription("REST API");
+    
+    if (body.isMember(KEY_PERMISSIVE))
+    {
+      job->SetPermissive(SerializationToolbox::ReadBoolean(body, KEY_PERMISSIVE));
+    }
+    else
+    {
+      job->SetPermissive(false);
+    }
+
+    SubmitGenericJob(call, raii.release(), isDefaultSynchronous, body);
+  }
+}