# HG changeset patch # User Sebastien Jodogne # Date 1559849705 -7200 # Node ID 4981405e6c5c134ed6c4671ec106ccfa4164368c # Parent 0ce9b4f5fdf5e499461c2161e3c8d680a59da745 new sdk: OrthancPluginRegisterMultipartRestCallback() diff -r 0ce9b4f5fdf5 -r 4981405e6c5c NEWS --- a/NEWS Thu Jun 06 18:54:27 2019 +0200 +++ b/NEWS Thu Jun 06 21:35:05 2019 +0200 @@ -13,7 +13,8 @@ ------- * New functions in the SDK: - - OrthancPluginHttpClientChunkedBody(): POST/PUT query with a chunked body + - OrthancPluginHttpClientChunkedBody(): HTTP client for POST/PUT with a chunked body + - OrthancPluginRegisterMultipartRestCallback(): HTTP server for POST/PUT with multipart body Maintenance ----------- diff -r 0ce9b4f5fdf5 -r 4981405e6c5c Plugins/Engine/OrthancPlugins.cpp --- a/Plugins/Engine/OrthancPlugins.cpp Thu Jun 06 18:54:27 2019 +0200 +++ b/Plugins/Engine/OrthancPlugins.cpp Thu Jun 06 21:35:05 2019 +0200 @@ -459,6 +459,33 @@ return *errorDetails_; } } + + void Close(OrthancPluginErrorCode error, + PluginsErrorDictionary& dictionary) + { + if (error == OrthancPluginErrorCode_Success) + { + if (GetOutput().IsWritingMultipart()) + { + GetOutput().CloseMultipart(); + } + } + else + { + dictionary.LogError(error, false); + + if (HasErrorDetails()) + { + throw OrthancException(static_cast(error), + GetErrorDetails(), + IsLogDetails()); + } + else + { + throw OrthancException(static_cast(error)); + } + } + } }; @@ -511,6 +538,31 @@ }; + class MultipartRestCallback : public boost::noncopyable + { + private: + _OrthancPluginMultipartRestCallback parameters_; + boost::regex regex_; + + public: + MultipartRestCallback(_OrthancPluginMultipartRestCallback parameters) : + parameters_(parameters), + regex_(parameters.pathRegularExpression) + { + } + + const boost::regex& GetRegularExpression() const + { + return regex_; + } + + const _OrthancPluginMultipartRestCallback& GetParameters() const + { + return parameters_; + } + }; + + class ServerContextLock { private: @@ -545,6 +597,7 @@ typedef std::pair Property; typedef std::list RestCallbacks; + typedef std::list MultipartRestCallbacks; typedef std::list OnStoredCallbacks; typedef std::list OnChangeCallbacks; typedef std::list IncomingHttpRequestFilters; @@ -557,6 +610,7 @@ PluginsManager manager_; RestCallbacks restCallbacks_; + MultipartRestCallbacks multipartRestCallbacks_; OnStoredCallbacks onStoredCallbacks_; OnChangeCallbacks onChangeCallbacks_; OrthancPluginFindCallback findCallback_; @@ -1124,6 +1178,12 @@ { delete *it; } + + for (PImpl::MultipartRestCallbacks::iterator it = pimpl_->multipartRestCallbacks_.begin(); + it != pimpl_->multipartRestCallbacks_.end(); ++it) + { + delete *it; + } } @@ -1160,6 +1220,68 @@ } + namespace + { + class RestCallbackMatcher : public boost::noncopyable + { + private: + std::string flatUri_; + std::vector groups_; + std::vector cgroups_; + + public: + RestCallbackMatcher(const UriComponents& uri) : + flatUri_(Toolbox::FlattenUri(uri)) + { + } + + bool IsMatch(const boost::regex& re) + { + // Check whether the regular expression associated to this + // callback matches the URI + boost::cmatch what; + + if (boost::regex_match(flatUri_.c_str(), what, re)) + { + // Extract the value of the free parameters of the regular expression + if (what.size() > 1) + { + groups_.resize(what.size() - 1); + cgroups_.resize(what.size() - 1); + for (size_t i = 1; i < what.size(); i++) + { + groups_[i - 1] = what[i]; + cgroups_[i - 1] = groups_[i - 1].c_str(); + } + } + + return true; + } + else + { + // Not a match + return false; + } + } + + uint32_t GetGroupsCount() const + { + return cgroups_.size(); + } + + const char* const* GetGroups() const + { + return cgroups_.empty() ? NULL : &cgroups_[0]; + } + + const std::string& GetFlatUri() const + { + return flatUri_; + } + }; + } + + bool OrthancPlugins::Handle(HttpOutput& output, RequestOrigin /*origin*/, const char* /*remoteIp*/, @@ -1171,35 +1293,18 @@ const char* bodyData, size_t bodySize) { - std::string flatUri = Toolbox::FlattenUri(uri); + RestCallbackMatcher matcher(uri); + PImpl::RestCallback* callback = NULL; - std::vector groups; - std::vector cgroups; - // Loop over the callbacks registered by the plugins - bool found = false; for (PImpl::RestCallbacks::const_iterator it = pimpl_->restCallbacks_.begin(); - it != pimpl_->restCallbacks_.end() && !found; ++it) + it != pimpl_->restCallbacks_.end(); ++it) { - // Check whether the regular expression associated to this - // callback matches the URI - boost::cmatch what; - if (boost::regex_match(flatUri.c_str(), what, (*it)->GetRegularExpression())) + if (matcher.IsMatch((*it)->GetRegularExpression())) { callback = *it; - - // Extract the value of the free parameters of the regular expression - if (what.size() > 1) - { - groups.resize(what.size() - 1); - cgroups.resize(what.size() - 1); - for (size_t i = 1; i < what.size(); i++) - { - groups[i - 1] = what[i]; - cgroups[i - 1] = groups[i - 1].c_str(); - } - } + break; } } @@ -1209,7 +1314,7 @@ return false; } - LOG(INFO) << "Delegating HTTP request to plugin for URI: " << flatUri; + LOG(INFO) << "Delegating HTTP request to plugin for URI: " << matcher.GetFlatUri(); std::vector getKeys, getValues, headersKeys, headersValues; @@ -1241,9 +1346,8 @@ throw OrthancException(ErrorCode_InternalError); } - - request.groups = (cgroups.size() ? &cgroups[0] : NULL); - request.groupsCount = cgroups.size(); + request.groups = matcher.GetGroups(); + request.groupsCount = matcher.GetGroupsCount(); request.getCount = getArguments.size(); request.body = bodyData; request.bodySize = bodySize; @@ -1266,33 +1370,10 @@ PImpl::PluginHttpOutput pluginOutput(output); OrthancPluginErrorCode error = callback->Invoke - (pimpl_->restCallbackMutex_, pluginOutput, flatUri, request); - - if (error == OrthancPluginErrorCode_Success && - output.IsWritingMultipart()) - { - output.CloseMultipart(); - } - - if (error == OrthancPluginErrorCode_Success) - { - return true; - } - else - { - GetErrorDictionary().LogError(error, false); - - if (pluginOutput.HasErrorDetails()) - { - throw OrthancException(static_cast(error), - pluginOutput.GetErrorDetails(), - pluginOutput.IsLogDetails()); - } - else - { - throw OrthancException(static_cast(error)); - } - } + (pimpl_->restCallbackMutex_, pluginOutput, matcher.GetFlatUri(), request); + + pluginOutput.Close(error, GetErrorDictionary()); + return true; } @@ -1366,6 +1447,17 @@ } + void OrthancPlugins::RegisterMultipartRestCallback(const void* parameters) + { + const _OrthancPluginMultipartRestCallback& p = + *reinterpret_cast(parameters); + + LOG(INFO) << "Plugin has registered a REST callback for multipart streams on: " + << p.pathRegularExpression; + + pimpl_->multipartRestCallbacks_.push_back(new PImpl::MultipartRestCallback(p)); + } + void OrthancPlugins::RegisterOnStoredInstanceCallback(const void* parameters) { @@ -3451,6 +3543,10 @@ RegisterRestCallback(parameters, false); return true; + case _OrthancPluginService_RegisterMultipartRestCallback: + RegisterMultipartRestCallback(parameters); + return true; + case _OrthancPluginService_RegisterOnStoredInstanceCallback: RegisterOnStoredInstanceCallback(parameters); return true; @@ -4011,6 +4107,57 @@ } + class OrthancPlugins::MultipartStream : public IHttpHandler::IStream + { + private: + OrthancPluginMultipartRestHandler* handler_; + _OrthancPluginMultipartRestCallback parameters_; + PluginsErrorDictionary& errorDictionary_; + + public: + MultipartStream(OrthancPluginMultipartRestHandler* handler, + const _OrthancPluginMultipartRestCallback& parameters, + PluginsErrorDictionary& errorDictionary) : + handler_(handler), + parameters_(parameters), + errorDictionary_(errorDictionary) + { + if (handler_ == NULL) + { + throw OrthancException(ErrorCode_Plugin, "The plugin has not created a multipart stream handler"); + } + } + + virtual ~MultipartStream() + { + if (handler_ != NULL) + { + parameters_.finalize(handler_); + } + } + + virtual void AddBodyChunk(const void* data, + size_t size) + { + assert(handler_ != NULL); + + // TODO => multipart parsing + } + + virtual void Execute(HttpOutput& output) + { + assert(handler_ != NULL); + + PImpl::PluginHttpOutput pluginOutput(output); + + OrthancPluginErrorCode error = parameters_.execute( + handler_, reinterpret_cast(&pluginOutput)); + + pluginOutput.Close(error, errorDictionary_); + } + }; + + IHttpHandler::IStream* OrthancPlugins::CreateStreamHandler(RequestOrigin origin, const char* remoteIp, const char* username, @@ -4018,7 +4165,45 @@ const UriComponents& uri, const Arguments& headers) { - // TODO - Plugins to install a handler for multipart body. + RestCallbackMatcher matcher(uri); + + // Loop over the callbacks registered by the plugins + for (PImpl::MultipartRestCallbacks::const_iterator it = pimpl_->multipartRestCallbacks_.begin(); + it != pimpl_->multipartRestCallbacks_.end(); ++it) + { + if (matcher.IsMatch((*it)->GetRegularExpression())) + { + LOG(INFO) << "Delegating HTTP multipart request to plugin for URI: " << matcher.GetFlatUri(); + + std::vector headersKeys, headersValues; + ArgumentsToPlugin(headersKeys, headersValues, headers); + + OrthancPluginHttpMethod convertedMethod; + switch (method) + { + case HttpMethod_Post: + convertedMethod = OrthancPluginHttpMethod_Post; + break; + + case HttpMethod_Put: + convertedMethod = OrthancPluginHttpMethod_Put; + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + std::string contentType = "TODO"; // TODO + + OrthancPluginMultipartRestHandler* handler = (*it)->GetParameters().factory( + convertedMethod, matcher.GetFlatUri().c_str(), contentType.c_str(), + matcher.GetGroupsCount(), matcher.GetGroups(), headers.size(), + headers.empty() ? NULL : &headersKeys[0], + headers.empty() ? NULL : &headersValues[0]); + + return new MultipartStream(handler, (*it)->GetParameters(), GetErrorDictionary()); + } + } return NULL; } diff -r 0ce9b4f5fdf5 -r 4981405e6c5c Plugins/Engine/OrthancPlugins.h --- a/Plugins/Engine/OrthancPlugins.h Thu Jun 06 18:54:27 2019 +0200 +++ b/Plugins/Engine/OrthancPlugins.h Thu Jun 06 21:35:05 2019 +0200 @@ -91,10 +91,13 @@ class MoveHandler; class StreamingHttpRequest; class StreamingHttpAnswer; + class MultipartStream; void RegisterRestCallback(const void* parameters, bool lock); + void RegisterMultipartRestCallback(const void* parameters); + void RegisterOnStoredInstanceCallback(const void* parameters); void RegisterOnChangeCallback(const void* parameters); diff -r 0ce9b4f5fdf5 -r 4981405e6c5c Plugins/Include/orthanc/OrthancCPlugin.h --- a/Plugins/Include/orthanc/OrthancCPlugin.h Thu Jun 06 18:54:27 2019 +0200 +++ b/Plugins/Include/orthanc/OrthancCPlugin.h Thu Jun 06 21:35:05 2019 +0200 @@ -333,7 +333,7 @@ /** * @brief The HTTP method. **/ - OrthancPluginHttpMethod method; + OrthancPluginHttpMethod method; /** * @brief The number of groups of the regular expression. @@ -444,6 +444,7 @@ _OrthancPluginService_RegisterMoveCallback = 1009, _OrthancPluginService_RegisterIncomingHttpRequestFilter2 = 1010, _OrthancPluginService_RegisterRefreshMetricsCallback = 1011, + _OrthancPluginService_RegisterMultipartRestCallback = 1012, /* New in Orthanc 1.5.7 */ /* Sending answers to REST calls */ _OrthancPluginService_AnswerBuffer = 2000, @@ -6907,7 +6908,60 @@ } + + typedef struct _OrthancPluginMultipartRestHandler_t OrthancPluginMultipartRestHandler; + + typedef OrthancPluginMultipartRestHandler* (*OrthancPluginMultipartRestFactory) ( + OrthancPluginHttpMethod method, + const char* url, + const char* contentType, + uint32_t groupsCount, + const char* const* groups, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues); + + typedef OrthancPluginErrorCode (*OrthancPluginMultipartHandlerAddPart) ( + OrthancPluginMultipartRestHandler* handler, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues, + const char* contentType); + + typedef OrthancPluginErrorCode (*OrthancPluginMultipartHandlerExecute) ( + OrthancPluginMultipartRestHandler* handler, + OrthancPluginRestOutput* output); + typedef void (*OrthancPluginMultipartHandlerFinalize) ( + OrthancPluginMultipartRestHandler* handler); + + typedef struct + { + const char* pathRegularExpression; + OrthancPluginMultipartRestFactory factory; + OrthancPluginMultipartHandlerAddPart addPart; + OrthancPluginMultipartHandlerExecute execute; + OrthancPluginMultipartHandlerFinalize finalize; + } _OrthancPluginMultipartRestCallback; + + ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterMultipartRestCallback( + OrthancPluginContext* context, + const char* pathRegularExpression, + OrthancPluginMultipartRestFactory factory, + OrthancPluginMultipartHandlerAddPart addPart, + OrthancPluginMultipartHandlerExecute execute, + OrthancPluginMultipartHandlerFinalize finalize) + { + _OrthancPluginMultipartRestCallback params; + params.pathRegularExpression = pathRegularExpression; + params.factory = factory; + params.addPart = addPart; + params.execute = execute; + params.finalize = finalize; + + context->InvokeService(context, _OrthancPluginService_RegisterMultipartRestCallback, ¶ms); + } + #ifdef __cplusplus } #endif