# HG changeset patch # User Sebastien Jodogne # Date 1543853695 -3600 # Node ID ccf61f6e22ef648d13788a6b25a27a3d26b3f52c # Parent bfee0b9f32090c6900ae5753304e3ae8ddfeda96 New function in the SDK: "OrthancPluginSetHttpErrorDetails()" diff -r bfee0b9f3209 -r ccf61f6e22ef NEWS --- 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 ----------- diff -r bfee0b9f3209 -r ccf61f6e22ef Plugins/Engine/OrthancPlugins.cpp --- 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 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(error)); + + if (pluginOutput.HasErrorDetails()) + { + throw OrthancException(static_cast(error), pluginOutput.GetErrorDetails()); + } + else + { + throw OrthancException(static_cast(error)); + } } } @@ -1247,9 +1299,9 @@ const _OrthancPluginAnswerBuffer& p = *reinterpret_cast(parameters); - HttpOutput* translatedOutput = reinterpret_cast(p.output); - translatedOutput->SetContentType(p.mimeType); - translatedOutput->Answer(p.answer, p.answerSize); + HttpOutput& translatedOutput = reinterpret_cast(p.output)->GetOutput(); + translatedOutput.SetContentType(p.mimeType); + translatedOutput.Answer(p.answer, p.answerSize); } @@ -1258,8 +1310,8 @@ const _OrthancPluginOutputPlusArgument& p = *reinterpret_cast(parameters); - HttpOutput* translatedOutput = reinterpret_cast(p.output); - translatedOutput->Redirect(p.argument); + HttpOutput& translatedOutput = reinterpret_cast(p.output)->GetOutput(); + translatedOutput.Redirect(p.argument); } @@ -1268,8 +1320,8 @@ const _OrthancPluginSendHttpStatusCode& p = *reinterpret_cast(parameters); - HttpOutput* translatedOutput = reinterpret_cast(p.output); - translatedOutput->SendStatus(static_cast(p.status)); + HttpOutput& translatedOutput = reinterpret_cast(p.output)->GetOutput(); + translatedOutput.SendStatus(static_cast(p.status)); } @@ -1278,16 +1330,16 @@ const _OrthancPluginSendHttpStatus& p = *reinterpret_cast(parameters); - HttpOutput* translatedOutput = reinterpret_cast(p.output); + HttpOutput& translatedOutput = reinterpret_cast(p.output)->GetOutput(); HttpStatus status = static_cast(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(parameters); - HttpOutput* translatedOutput = reinterpret_cast(p.output); - translatedOutput->SendUnauthorized(p.argument); + HttpOutput& translatedOutput = reinterpret_cast(p.output)->GetOutput(); + translatedOutput.SendUnauthorized(p.argument); } @@ -1307,8 +1359,8 @@ const _OrthancPluginOutputPlusArgument& p = *reinterpret_cast(parameters); - HttpOutput* translatedOutput = reinterpret_cast(p.output); - translatedOutput->SendMethodNotAllowed(p.argument); + HttpOutput& translatedOutput = reinterpret_cast(p.output)->GetOutput(); + translatedOutput.SendMethodNotAllowed(p.argument); } @@ -1317,8 +1369,8 @@ const _OrthancPluginSetHttpHeader& p = *reinterpret_cast(parameters); - HttpOutput* translatedOutput = reinterpret_cast(p.output); - translatedOutput->SetCookie(p.key, p.value); + HttpOutput& translatedOutput = reinterpret_cast(p.output)->GetOutput(); + translatedOutput.SetCookie(p.key, p.value); } @@ -1327,8 +1379,18 @@ const _OrthancPluginSetHttpHeader& p = *reinterpret_cast(parameters); - HttpOutput* translatedOutput = reinterpret_cast(p.output); - translatedOutput->AddHeader(p.key, p.value); + HttpOutput& translatedOutput = reinterpret_cast(p.output)->GetOutput(); + translatedOutput.AddHeader(p.key, p.value); + } + + + void OrthancPlugins::SetHttpErrorDetails(const void* parameters) + { + const _OrthancPluginSetHttpErrorDetails& p = + *reinterpret_cast(parameters); + + PImpl::PluginHttpOutput* output = reinterpret_cast(p.output); + output->SetErrorDetails(p.details); } @@ -1357,7 +1419,7 @@ const _OrthancPluginCompressAndAnswerImage& p = *reinterpret_cast(parameters); - HttpOutput* translatedOutput = reinterpret_cast(p.output); + HttpOutput& translatedOutput = reinterpret_cast(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(parameters); - HttpOutput* output = reinterpret_cast(p.output); + HttpOutput& output = reinterpret_cast(p.output)->GetOutput(); std::map 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(parameters); - HttpOutput* output = reinterpret_cast(p.output); + HttpOutput& output = reinterpret_cast(p.output)->GetOutput(); std::map 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(parameters); - HttpOutput* output = reinterpret_cast(p.output); - output->StartMultipart(p.subType, p.contentType); + HttpOutput& output = reinterpret_cast(p.output)->GetOutput(); + output.StartMultipart(p.subType, p.contentType); return true; } diff -r bfee0b9f3209 -r ccf61f6e22ef Plugins/Engine/OrthancPlugins.h --- 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); diff -r bfee0b9f3209 -r ccf61f6e22ef Plugins/Include/orthanc/OrthancCPlugin.h --- 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, ¶ms); + } + + + #ifdef __cplusplus } #endif diff -r bfee0b9f3209 -r ccf61f6e22ef Plugins/Samples/Basic/Plugin.c --- 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 ? "" : 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);