changeset 2957:ccf61f6e22ef

New function in the SDK: "OrthancPluginSetHttpErrorDetails()"
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 03 Dec 2018 17:14:55 +0100
parents bfee0b9f3209
children bb7a66efbeb1
files NEWS Plugins/Engine/OrthancPlugins.cpp Plugins/Engine/OrthancPlugins.h Plugins/Include/orthanc/OrthancCPlugin.h Plugins/Samples/Basic/Plugin.c
diffstat 5 files changed, 191 insertions(+), 56 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Mon Dec 03 15:11:42 2018 +0100
+++ b/NEWS	Mon Dec 03 17:14:55 2018 +0100
@@ -28,6 +28,11 @@
 * New options to URI "/queries/.../answers": "?expand" and "?simplify"
 * New "Details" field in HTTP answers on error (cf. "HttpDescribeErrors" option)
 
+Plugins
+-------
+
+* New function in the SDK: "OrthancPluginSetHttpErrorDetails()"
+
 Maintenance
 -----------
 
--- a/Plugins/Engine/OrthancPlugins.cpp	Mon Dec 03 15:11:42 2018 +0100
+++ b/Plugins/Engine/OrthancPlugins.cpp	Mon Dec 03 17:14:55 2018 +0100
@@ -319,6 +319,47 @@
     
 
   public:
+    class PluginHttpOutput : public boost::noncopyable
+    {
+    private:
+      HttpOutput&                 output_;
+      std::auto_ptr<std::string>  errorDetails_;
+
+    public:
+      PluginHttpOutput(HttpOutput& output) :
+        output_(output)
+      {
+      }
+
+      HttpOutput& GetOutput()
+      {
+        return output_;
+      }
+
+      void SetErrorDetails(const std::string& details)
+      {
+        errorDetails_.reset(new std::string(details));
+      }
+
+      bool HasErrorDetails() const
+      {
+        return errorDetails_.get() != NULL;
+      }
+
+      const std::string& GetErrorDetails() const
+      {
+        if (errorDetails_.get() == NULL)
+        {
+          throw OrthancException(ErrorCode_BadSequenceOfCalls);
+        }
+        else
+        {
+          return *errorDetails_;
+        }
+      }
+    };
+
+    
     class RestCallback : public boost::noncopyable
     {
     private:
@@ -326,7 +367,7 @@
       OrthancPluginRestCallback callback_;
       bool                      lock_;
 
-      OrthancPluginErrorCode InvokeInternal(HttpOutput& output,
+      OrthancPluginErrorCode InvokeInternal(PluginHttpOutput& output,
                                             const std::string& flatUri,
                                             const OrthancPluginHttpRequest& request)
       {
@@ -351,7 +392,7 @@
       }
 
       OrthancPluginErrorCode Invoke(boost::recursive_mutex& restCallbackMutex,
-                                    HttpOutput& output,
+                                    PluginHttpOutput& output,
                                     const std::string& flatUri,
                                     const OrthancPluginHttpRequest& request)
       {
@@ -1025,7 +1066,10 @@
     }
 
     assert(callback != NULL);
-    OrthancPluginErrorCode error = callback->Invoke(pimpl_->restCallbackMutex_, output, flatUri, request);
+
+    PImpl::PluginHttpOutput pluginOutput(output);
+
+    OrthancPluginErrorCode error = callback->Invoke(pimpl_->restCallbackMutex_, pluginOutput, flatUri, request);
 
     if (error == OrthancPluginErrorCode_Success && 
         output.IsWritingMultipart())
@@ -1040,7 +1084,15 @@
     else
     {
       GetErrorDictionary().LogError(error, false);
-      throw OrthancException(static_cast<ErrorCode>(error));
+
+      if (pluginOutput.HasErrorDetails())
+      {
+        throw OrthancException(static_cast<ErrorCode>(error), pluginOutput.GetErrorDetails());
+      }
+      else
+      {
+        throw OrthancException(static_cast<ErrorCode>(error));
+      }
     }
   }
 
@@ -1247,9 +1299,9 @@
     const _OrthancPluginAnswerBuffer& p = 
       *reinterpret_cast<const _OrthancPluginAnswerBuffer*>(parameters);
 
-    HttpOutput* translatedOutput = reinterpret_cast<HttpOutput*>(p.output);
-    translatedOutput->SetContentType(p.mimeType);
-    translatedOutput->Answer(p.answer, p.answerSize);
+    HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
+    translatedOutput.SetContentType(p.mimeType);
+    translatedOutput.Answer(p.answer, p.answerSize);
   }
 
 
@@ -1258,8 +1310,8 @@
     const _OrthancPluginOutputPlusArgument& p = 
       *reinterpret_cast<const _OrthancPluginOutputPlusArgument*>(parameters);
 
-    HttpOutput* translatedOutput = reinterpret_cast<HttpOutput*>(p.output);
-    translatedOutput->Redirect(p.argument);
+    HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
+    translatedOutput.Redirect(p.argument);
   }
 
 
@@ -1268,8 +1320,8 @@
     const _OrthancPluginSendHttpStatusCode& p = 
       *reinterpret_cast<const _OrthancPluginSendHttpStatusCode*>(parameters);
 
-    HttpOutput* translatedOutput = reinterpret_cast<HttpOutput*>(p.output);
-    translatedOutput->SendStatus(static_cast<HttpStatus>(p.status));
+    HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
+    translatedOutput.SendStatus(static_cast<HttpStatus>(p.status));
   }
 
 
@@ -1278,16 +1330,16 @@
     const _OrthancPluginSendHttpStatus& p = 
       *reinterpret_cast<const _OrthancPluginSendHttpStatus*>(parameters);
 
-    HttpOutput* translatedOutput = reinterpret_cast<HttpOutput*>(p.output);
+    HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
     HttpStatus status = static_cast<HttpStatus>(p.status);
 
     if (p.bodySize > 0 && p.body != NULL)
     {
-      translatedOutput->SendStatus(status, p.body, p.bodySize);
+      translatedOutput.SendStatus(status, p.body, p.bodySize);
     }
     else
     {
-      translatedOutput->SendStatus(status);
+      translatedOutput.SendStatus(status);
     }
   }
 
@@ -1297,8 +1349,8 @@
     const _OrthancPluginOutputPlusArgument& p = 
       *reinterpret_cast<const _OrthancPluginOutputPlusArgument*>(parameters);
 
-    HttpOutput* translatedOutput = reinterpret_cast<HttpOutput*>(p.output);
-    translatedOutput->SendUnauthorized(p.argument);
+    HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
+    translatedOutput.SendUnauthorized(p.argument);
   }
 
 
@@ -1307,8 +1359,8 @@
     const _OrthancPluginOutputPlusArgument& p = 
       *reinterpret_cast<const _OrthancPluginOutputPlusArgument*>(parameters);
 
-    HttpOutput* translatedOutput = reinterpret_cast<HttpOutput*>(p.output);
-    translatedOutput->SendMethodNotAllowed(p.argument);
+    HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
+    translatedOutput.SendMethodNotAllowed(p.argument);
   }
 
 
@@ -1317,8 +1369,8 @@
     const _OrthancPluginSetHttpHeader& p = 
       *reinterpret_cast<const _OrthancPluginSetHttpHeader*>(parameters);
 
-    HttpOutput* translatedOutput = reinterpret_cast<HttpOutput*>(p.output);
-    translatedOutput->SetCookie(p.key, p.value);
+    HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
+    translatedOutput.SetCookie(p.key, p.value);
   }
 
 
@@ -1327,8 +1379,18 @@
     const _OrthancPluginSetHttpHeader& p = 
       *reinterpret_cast<const _OrthancPluginSetHttpHeader*>(parameters);
 
-    HttpOutput* translatedOutput = reinterpret_cast<HttpOutput*>(p.output);
-    translatedOutput->AddHeader(p.key, p.value);
+    HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
+    translatedOutput.AddHeader(p.key, p.value);
+  }
+
+
+  void OrthancPlugins::SetHttpErrorDetails(const void* parameters)
+  {
+    const _OrthancPluginSetHttpErrorDetails& p = 
+      *reinterpret_cast<const _OrthancPluginSetHttpErrorDetails*>(parameters);
+
+    PImpl::PluginHttpOutput* output = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output);
+    output->SetErrorDetails(p.details);
   }
 
 
@@ -1357,7 +1419,7 @@
     const _OrthancPluginCompressAndAnswerImage& p = 
       *reinterpret_cast<const _OrthancPluginCompressAndAnswerImage*>(parameters);
 
-    HttpOutput* translatedOutput = reinterpret_cast<HttpOutput*>(p.output);
+    HttpOutput& translatedOutput = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
 
     ImageAccessor accessor;
     accessor.AssignReadOnly(Plugins::Convert(p.pixelFormat), p.width, p.height, p.pitch, p.buffer);
@@ -1370,7 +1432,7 @@
       {
         PngWriter writer;
         writer.WriteToMemory(compressed, accessor);
-        translatedOutput->SetContentType(MimeType_Png);
+        translatedOutput.SetContentType(MimeType_Png);
         break;
       }
 
@@ -1379,7 +1441,7 @@
         JpegWriter writer;
         writer.SetQuality(p.quality);
         writer.WriteToMemory(compressed, accessor);
-        translatedOutput->SetContentType(MimeType_Jpeg);
+        translatedOutput.SetContentType(MimeType_Jpeg);
         break;
       }
 
@@ -1387,7 +1449,7 @@
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
-    translatedOutput->Answer(compressed);
+    translatedOutput.Answer(compressed);
   }
 
 
@@ -2285,10 +2347,10 @@
     const _OrthancPluginAnswerBuffer& p =
       *reinterpret_cast<const _OrthancPluginAnswerBuffer*>(parameters);
 
-    HttpOutput* output = reinterpret_cast<HttpOutput*>(p.output);
+    HttpOutput& output = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
 
     std::map<std::string, std::string> headers;  // No custom headers
-    output->SendMultipartItem(p.answer, p.answerSize, headers);
+    output.SendMultipartItem(p.answer, p.answerSize, headers);
   }
 
 
@@ -2298,7 +2360,7 @@
     // connection was closed by the HTTP client.
     const _OrthancPluginSendMultipartItem2& p =
       *reinterpret_cast<const _OrthancPluginSendMultipartItem2*>(parameters);
-    HttpOutput* output = reinterpret_cast<HttpOutput*>(p.output);
+    HttpOutput& output = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
     
     std::map<std::string, std::string> headers;
     for (uint32_t i = 0; i < p.headersCount; i++)
@@ -2306,7 +2368,7 @@
       headers[p.headersKeys[i]] = p.headersValues[i];
     }
     
-    output->SendMultipartItem(p.answer, p.answerSize, headers);
+    output.SendMultipartItem(p.answer, p.answerSize, headers);
   }
       
 
@@ -2510,6 +2572,10 @@
         SetHttpHeader(parameters);
         return true;
 
+      case _OrthancPluginService_SetHttpErrorDetails:
+        SetHttpErrorDetails(parameters);
+        return true;
+
       case _OrthancPluginService_LookupPatient:
       case _OrthancPluginService_LookupStudy:
       case _OrthancPluginService_LookupStudyWithAccessionNumber:
@@ -2573,8 +2639,8 @@
       {
         const _OrthancPluginStartMultipartAnswer& p =
           *reinterpret_cast<const _OrthancPluginStartMultipartAnswer*>(parameters);
-        HttpOutput* output = reinterpret_cast<HttpOutput*>(p.output);
-        output->StartMultipart(p.subType, p.contentType);
+        HttpOutput& output = reinterpret_cast<PImpl::PluginHttpOutput*>(p.output)->GetOutput();
+        output.StartMultipart(p.subType, p.contentType);
         return true;
       }
 
--- a/Plugins/Engine/OrthancPlugins.h	Mon Dec 03 15:11:42 2018 +0100
+++ b/Plugins/Engine/OrthancPlugins.h	Mon Dec 03 17:14:55 2018 +0100
@@ -148,6 +148,8 @@
 
     void SetHttpHeader(const void* parameters);
 
+    void SetHttpErrorDetails(const void* parameters);
+
     void BufferCompression(const void* parameters);
 
     void UncompressImage(const void* parameters);
--- a/Plugins/Include/orthanc/OrthancCPlugin.h	Mon Dec 03 15:11:42 2018 +0100
+++ b/Plugins/Include/orthanc/OrthancCPlugin.h	Mon Dec 03 17:14:55 2018 +0100
@@ -119,7 +119,7 @@
 
 #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     1
 #define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     4
-#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  2
+#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  3
 
 
 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
@@ -451,6 +451,7 @@
     _OrthancPluginService_SendHttpStatus = 2010,
     _OrthancPluginService_CompressAndAnswerImage = 2011,
     _OrthancPluginService_SendMultipartItem2 = 2012,
+    _OrthancPluginService_SetHttpErrorDetails = 2013,
 
     /* Access to the Orthanc database and API */
     _OrthancPluginService_GetDicomForInstance = 3000,
@@ -6427,6 +6428,42 @@
   }
 
 
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              details;
+  } _OrthancPluginSetHttpErrorDetails;
+
+  /**
+   * @brief Provide a detailed description for an HTTP error.
+   *
+   * This function sets the detailed description associated with an
+   * HTTP error. This description will be displayed in the "Details"
+   * field of the JSON body of the HTTP answer. It is only taken into
+   * consideration if the REST callback returns an error code that is
+   * different from "OrthancPluginErrorCode_Success", and if the
+   * "HttpDescribeErrors" configuration option of Orthanc is set to
+   * "true".
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param details The details of the error message.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpErrorDetails(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              details)
+  {
+    _OrthancPluginSetHttpErrorDetails params;
+    params.output = output;
+    params.details = details;
+    context->InvokeService(context, _OrthancPluginService_SetHttpErrorDetails, &params);
+  }
+
+
+
 #ifdef  __cplusplus
 }
 #endif
--- a/Plugins/Samples/Basic/Plugin.c	Mon Dec 03 15:11:42 2018 +0100
+++ b/Plugins/Samples/Basic/Plugin.c	Mon Dec 03 17:14:55 2018 +0100
@@ -36,6 +36,14 @@
   char buffer[1024];
   uint32_t i;
 
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    // NB: Calling "OrthancPluginSendMethodNotAllowed(context, output, "GET");"
+    // is preferable. This is a sample to demonstrate "OrthancPluginSetHttpErrorDetails()".
+    OrthancPluginSetHttpErrorDetails(context, output, "This Callback1() can only be used by a GET call");
+    return OrthancPluginErrorCode_ParameterOutOfRange;
+  }
+  
   sprintf(buffer, "Callback on URL [%s] with body [%s]\n", url, request->body);
   OrthancPluginLogWarning(context, buffer);
 
@@ -184,7 +192,7 @@
   if (request->method != OrthancPluginHttpMethod_Get)
   {
     OrthancPluginSendMethodNotAllowed(context, output, "GET");
-    return 0;
+    return OrthancPluginErrorCode_Success;
   }
 
   isBuiltIn = strcmp("plugins", request->groups[0]);
@@ -301,22 +309,51 @@
                                                             OrthancPluginResourceType resourceType,
                                                             const char* resourceId)
 {
+  OrthancPluginMemoryBuffer tmp;
+  memset(&tmp, 0, sizeof(tmp));
+
   char info[1024];
-  OrthancPluginMemoryBuffer tmp;
-
-  sprintf(info, "Change %d on resource %s of type %d", changeType, resourceId, resourceType);
+  sprintf(info, "Change %d on resource %s of type %d", changeType,
+          (resourceId == NULL ? "<none>" : resourceId), resourceType);
   OrthancPluginLogWarning(context, info);
 
-  if (changeType == OrthancPluginChangeType_NewInstance)
+  switch (changeType)
   {
-    sprintf(info, "/instances/%s/metadata/AnonymizedFrom", resourceId);
-    if (OrthancPluginRestApiGet(context, &tmp, info) == 0)
+    case OrthancPluginChangeType_NewInstance:
+    {
+      sprintf(info, "/instances/%s/metadata/AnonymizedFrom", resourceId);
+      if (OrthancPluginRestApiGet(context, &tmp, info) == 0)
+      {
+        sprintf(info, "  Instance %s comes from the anonymization of instance", resourceId);
+        strncat(info, (const char*) tmp.data, tmp.size);
+        OrthancPluginLogWarning(context, info);
+        OrthancPluginFreeMemoryBuffer(context, &tmp);
+      }
+
+      break;
+    }
+
+    case OrthancPluginChangeType_OrthancStarted:
     {
-      sprintf(info, "  Instance %s comes from the anonymization of instance", resourceId);
-      strncat(info, (const char*) tmp.data, tmp.size);
-      OrthancPluginLogWarning(context, info);
+      /* Make REST requests to the built-in Orthanc API */
+      OrthancPluginRestApiGet(context, &tmp, "/changes");
+      OrthancPluginFreeMemoryBuffer(context, &tmp);
+      OrthancPluginRestApiGet(context, &tmp, "/changes?limit=1");
       OrthancPluginFreeMemoryBuffer(context, &tmp);
+
+      /* Play with PUT by defining a new target modality. */
+      sprintf(info, "[ \"STORESCP\", \"localhost\", 2000 ]");
+      OrthancPluginRestApiPut(context, &tmp, "/modalities/demo", info, strlen(info));
+
+      break;
     }
+
+    case OrthancPluginChangeType_OrthancStopped:
+      OrthancPluginLogWarning(context, "Orthanc has stopped");
+      break;
+
+    default:
+      break;
   }
 
   return OrthancPluginErrorCode_Success;
@@ -357,7 +394,6 @@
 
 ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c)
 {
-  OrthancPluginMemoryBuffer tmp;
   char info[1024], *s;
   int counter, i;
   OrthancPluginDictionaryEntry entry;
@@ -425,19 +461,8 @@
   OrthancPluginSetDescription(context, "This is the description of the sample plugin that can be seen in Orthanc Explorer.");
   OrthancPluginExtendOrthancExplorer(context, "alert('Hello Orthanc! From sample plugin with love.');");
 
-  /* Make REST requests to the built-in Orthanc API */
-  memset(&tmp, 0, sizeof(tmp));
-  OrthancPluginRestApiGet(context, &tmp, "/changes");
-  OrthancPluginFreeMemoryBuffer(context, &tmp);
-  OrthancPluginRestApiGet(context, &tmp, "/changes?limit=1");
-  OrthancPluginFreeMemoryBuffer(context, &tmp);
+  customError = OrthancPluginRegisterErrorCode(context, 4, 402, "Hello world");
   
-  /* Play with PUT by defining a new target modality. */
-  sprintf(info, "[ \"STORESCP\", \"localhost\", 2000 ]");
-  OrthancPluginRestApiPut(context, &tmp, "/modalities/demo", info, strlen(info));
-
-  customError = OrthancPluginRegisterErrorCode(context, 4, 402, "Hello world");
-
   OrthancPluginRegisterDictionaryTag(context, 0x0014, 0x1020, OrthancPluginValueRepresentation_DA,
                                      "ValidationExpiryDate", 1, 1);