# HG changeset patch # User Sebastien Jodogne # Date 1534949707 -7200 # Node ID 6e3a60b85da6e5fdfc98b09d270dcc3b33c77d9c # Parent 5c83e5cf9d7988344ce0399d1206beeaccdf60f0 New primitives to access Orthanc peers from plugins diff -r 5c83e5cf9d79 -r 6e3a60b85da6 NEWS --- a/NEWS Tue Aug 21 10:42:16 2018 +0200 +++ b/NEWS Wed Aug 22 16:55:07 2018 +0200 @@ -1,6 +1,16 @@ Pending changes in the mainline =============================== + +Plugins +------- + +* New primitives to access Orthanc peers from plugins + + +Maintenance +----------- + * New configuration option: "HttpVerbose" to debug outgoing HTTP connections * Fix incoming DICOM C-Store filtering for JPEG-LS transfer syntaxes * Fix OrthancPluginHttpClient() to return the HTTP status on errors diff -r 5c83e5cf9d79 -r 6e3a60b85da6 OrthancServer/LuaScripting.cpp --- a/OrthancServer/LuaScripting.cpp Tue Aug 21 10:42:16 2018 +0200 +++ b/OrthancServer/LuaScripting.cpp Wed Aug 22 16:55:07 2018 +0200 @@ -456,9 +456,15 @@ std::string name = parameters["Peer"].asString(); WebServiceParameters peer; - Configuration::GetOrthancPeer(peer, name); - - return lock.AddStorePeerOperation(peer); + if (Configuration::GetOrthancPeer(peer, name)) + { + return lock.AddStorePeerOperation(peer); + } + else + { + LOG(ERROR) << "No peer with symbolic name: " << name; + throw OrthancException(ErrorCode_UnknownResource); + } } if (operation == "modify") diff -r 5c83e5cf9d79 -r 6e3a60b85da6 OrthancServer/OrthancInitialization.cpp --- a/OrthancServer/OrthancInitialization.cpp Tue Aug 21 10:42:16 2018 +0200 +++ b/OrthancServer/OrthancInitialization.cpp Wed Aug 22 16:55:07 2018 +0200 @@ -608,16 +608,14 @@ } - - void Configuration::GetOrthancPeer(WebServiceParameters& peer, + bool Configuration::GetOrthancPeer(WebServiceParameters& peer, const std::string& name) { boost::recursive_mutex::scoped_lock lock(globalMutex_); if (!configuration_.isMember("OrthancPeers")) { - LOG(ERROR) << "No peer with symbolic name: " << name; - throw OrthancException(ErrorCode_InexistentItem); + return false; } try @@ -626,11 +624,13 @@ if (modalities.type() != Json::objectValue || !modalities.isMember(name)) { - LOG(ERROR) << "No peer with symbolic name: " << name; - throw OrthancException(ErrorCode_InexistentItem); + return false; } - - peer.FromJson(modalities[name]); + else + { + peer.FromJson(modalities[name]); + return true; + } } catch (OrthancException&) { diff -r 5c83e5cf9d79 -r 6e3a60b85da6 OrthancServer/OrthancInitialization.h --- a/OrthancServer/OrthancInitialization.h Tue Aug 21 10:42:16 2018 +0200 +++ b/OrthancServer/OrthancInitialization.h Wed Aug 22 16:55:07 2018 +0200 @@ -78,7 +78,7 @@ static bool LookupDicomModalityUsingAETitle(RemoteModalityParameters& modality, const std::string& aet); - static void GetOrthancPeer(WebServiceParameters& peer, + static bool GetOrthancPeer(WebServiceParameters& peer, const std::string& name); static void GetListOfDicomModalities(std::set& target); diff -r 5c83e5cf9d79 -r 6e3a60b85da6 OrthancServer/OrthancRestApi/OrthancRestModalities.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Tue Aug 21 10:42:16 2018 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Wed Aug 22 16:55:07 2018 +0200 @@ -836,16 +836,18 @@ it = peers.begin(); it != peers.end(); ++it) { WebServiceParameters peer; - Configuration::GetOrthancPeer(peer, *it); - - Json::Value jsonPeer = Json::objectValue; - // only return the minimum information to identify the destination, do not include "security" information like passwords - jsonPeer["Url"] = peer.GetUrl(); - if (!peer.GetUsername().empty()) + + if (Configuration::GetOrthancPeer(peer, *it)) { - jsonPeer["Username"] = peer.GetUsername(); + Json::Value jsonPeer = Json::objectValue; + // only return the minimum information to identify the destination, do not include "security" information like passwords + jsonPeer["Url"] = peer.GetUrl(); + if (!peer.GetUsername().empty()) + { + jsonPeer["Username"] = peer.GetUsername(); + } + result[*it] = jsonPeer; } - result[*it] = jsonPeer; } call.GetOutput().AnswerJson(result); } @@ -886,12 +888,17 @@ if (GetInstancesToExport(request, *job, remote, call)) { WebServiceParameters peer; - Configuration::GetOrthancPeer(peer, remote); - - job->SetDescription("REST API"); - job->SetPeer(peer); - - SubmitJob(call, request, job.release()); + if (Configuration::GetOrthancPeer(peer, remote)) + { + job->SetDescription("REST API"); + job->SetPeer(peer); + SubmitJob(call, request, job.release()); + } + else + { + LOG(ERROR) << "No peer with symbolic name: " << remote; + throw OrthancException(ErrorCode_UnknownResource); + } } } diff -r 5c83e5cf9d79 -r 6e3a60b85da6 Plugins/Engine/OrthancPlugins.cpp --- a/Plugins/Engine/OrthancPlugins.cpp Tue Aug 21 10:42:16 2018 +0200 +++ b/Plugins/Engine/OrthancPlugins.cpp Wed Aug 22 16:55:07 2018 +0200 @@ -248,6 +248,61 @@ return new PluginStorageArea(callbacks_, errorDictionary_); } }; + + + class OrthancPeers : public boost::noncopyable + { + private: + std::vector names_; + std::vector parameters_; + + void CheckIndex(size_t i) const + { + assert(names_.size() == parameters_.size()); + if (i >= names_.size()) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + + public: + OrthancPeers() + { + std::set peers; + Configuration::GetListOfOrthancPeers(peers); + + names_.reserve(peers.size()); + parameters_.reserve(peers.size()); + + for (std::set::const_iterator + it = peers.begin(); it != peers.end(); ++it) + { + WebServiceParameters peer; + if (Configuration::GetOrthancPeer(peer, *it)) + { + names_.push_back(*it); + parameters_.push_back(peer); + } + } + } + + size_t GetPeersCount() const + { + return names_.size(); + } + + const std::string& GetPeerName(size_t i) const + { + CheckIndex(i); + return names_[i]; + } + + const WebServiceParameters& GetPeerParameters(size_t i) const + { + CheckIndex(i); + return parameters_[i]; + } + }; } @@ -1832,7 +1887,7 @@ if (p.headersKeys[i] == NULL || p.headersValues[i] == NULL) { - throw OrthancException(ErrorCode_ParameterOutOfRange); + throw OrthancException(ErrorCode_NullPointer); } client.AddHeader(p.headersKeys[i], p.headersValues[i]); @@ -1898,6 +1953,90 @@ } + void OrthancPlugins::CallPeerApi(const void* parameters) + { + const _OrthancPluginCallPeerApi& p = *reinterpret_cast(parameters); + const OrthancPeers& peers = *reinterpret_cast(p.peers); + + HttpClient client(peers.GetPeerParameters(p.peerIndex), p.uri); + client.SetConvertHeadersToLowerCase(false); + + if (p.timeout != 0) + { + client.SetTimeout(p.timeout); + } + + for (uint32_t i = 0; i < p.additionalHeadersCount; i++) + { + if (p.additionalHeadersKeys[i] == NULL || + p.additionalHeadersValues[i] == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + + client.AddHeader(p.additionalHeadersKeys[i], p.additionalHeadersValues[i]); + } + + switch (p.method) + { + case OrthancPluginHttpMethod_Get: + client.SetMethod(HttpMethod_Get); + break; + + case OrthancPluginHttpMethod_Post: + client.SetMethod(HttpMethod_Post); + client.GetBody().assign(p.body, p.bodySize); + break; + + case OrthancPluginHttpMethod_Put: + client.SetMethod(HttpMethod_Put); + client.GetBody().assign(p.body, p.bodySize); + break; + + case OrthancPluginHttpMethod_Delete: + client.SetMethod(HttpMethod_Delete); + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + std::string body; + HttpClient::HttpHeaders headers; + + bool success = client.Apply(body, headers); + + // The HTTP request has succeeded + *p.httpStatus = static_cast(client.GetLastStatus()); + + if (!success) + { + HttpClient::ThrowException(client.GetLastStatus()); + } + + // Copy the HTTP headers of the answer, if the plugin requested them + if (p.answerHeaders != NULL) + { + Json::Value json = Json::objectValue; + + for (HttpClient::HttpHeaders::const_iterator + it = headers.begin(); it != headers.end(); ++it) + { + json[it->first] = it->second; + } + + std::string s = json.toStyledString(); + CopyToMemoryBuffer(*p.answerHeaders, s); + } + + // Copy the body of the answer if it makes sense + if (p.method != OrthancPluginHttpMethod_Delete) + { + CopyToMemoryBuffer(*p.answerBody, body); + } + } + + void OrthancPlugins::ConvertPixelFormat(const void* parameters) { const _OrthancPluginConvertPixelFormat& p = *reinterpret_cast(parameters); @@ -1959,7 +2098,7 @@ { if (p.instanceId == NULL) { - throw OrthancException(ErrorCode_ParameterOutOfRange); + throw OrthancException(ErrorCode_NullPointer); } std::string content; @@ -2127,7 +2266,6 @@ } - namespace { class DictionaryReadLocker @@ -2179,7 +2317,6 @@ } - bool OrthancPlugins::InvokeSafeService(SharedLibrary& plugin, _OrthancPluginService service, const void* parameters) @@ -2444,15 +2581,13 @@ case _OrthancPluginService_FreeImage: { const _OrthancPluginFreeImage& p = *reinterpret_cast(parameters); - if (p.image == NULL) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else + + if (p.image != NULL) { delete reinterpret_cast(p.image); - return true; } + + return true; } case _OrthancPluginService_UncompressImage: @@ -2624,15 +2759,12 @@ const _OrthancPluginFreeFindMatcher& p = *reinterpret_cast(parameters); - if (p.matcher == NULL) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else + if (p.matcher != NULL) { delete reinterpret_cast(p.matcher); - return true; } + + return true; } case _OrthancPluginService_FindMatcherIsMatch: @@ -2642,7 +2774,7 @@ if (p.matcher == NULL) { - throw OrthancException(ErrorCode_ParameterOutOfRange); + throw OrthancException(ErrorCode_NullPointer); } else { @@ -2652,6 +2784,79 @@ } } + case _OrthancPluginService_GetPeers: + { + const _OrthancPluginGetPeers& p = + *reinterpret_cast(parameters); + *(p.peers) = reinterpret_cast(new OrthancPeers); + return true; + } + + case _OrthancPluginService_FreePeers: + { + const _OrthancPluginFreePeers& p = + *reinterpret_cast(parameters); + + if (p.peers != NULL) + { + delete reinterpret_cast(p.peers); + } + + return true; + } + + case _OrthancPluginService_GetPeersCount: + { + const _OrthancPluginGetPeersCount& p = + *reinterpret_cast(parameters); + + if (p.peers == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + else + { + *(p.target) = reinterpret_cast(p.peers)->GetPeersCount(); + return true; + } + } + + case _OrthancPluginService_GetPeerName: + { + const _OrthancPluginGetPeerProperty& p = + *reinterpret_cast(parameters); + + if (p.peers == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + else + { + *(p.target) = reinterpret_cast(p.peers)->GetPeerName(p.peerIndex).c_str(); + return true; + } + } + + case _OrthancPluginService_GetPeerUrl: + { + const _OrthancPluginGetPeerProperty& p = + *reinterpret_cast(parameters); + + if (p.peers == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + else + { + *(p.target) = reinterpret_cast(p.peers)->GetPeerParameters(p.peerIndex).GetUrl().c_str(); + return true; + } + } + + case _OrthancPluginService_CallPeerApi: + CallPeerApi(parameters); + return true; + default: return false; } diff -r 5c83e5cf9d79 -r 6e3a60b85da6 Plugins/Engine/OrthancPlugins.h --- a/Plugins/Engine/OrthancPlugins.h Tue Aug 21 10:42:16 2018 +0200 +++ b/Plugins/Engine/OrthancPlugins.h Wed Aug 22 16:55:07 2018 +0200 @@ -157,6 +157,8 @@ void CallHttpClient2(const void* parameters); + void CallPeerApi(const void* parameters); + void GetFontInfo(const void* parameters); void DrawText(const void* parameters); diff -r 5c83e5cf9d79 -r 6e3a60b85da6 Plugins/Include/orthanc/OrthancCPlugin.h --- a/Plugins/Include/orthanc/OrthancCPlugin.h Tue Aug 21 10:42:16 2018 +0200 +++ b/Plugins/Include/orthanc/OrthancCPlugin.h Wed Aug 22 16:55:07 2018 +0200 @@ -118,7 +118,7 @@ #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER 1 #define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER 4 -#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 0 +#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER 2 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE) @@ -519,6 +519,14 @@ _OrthancPluginService_FreeFindMatcher = 7011, _OrthancPluginService_FindMatcherIsMatch = 7012, + /* Primitives for accessing Orthanc Peers (new in 1.4.2) */ + _OrthancPluginService_GetPeers = 8000, + _OrthancPluginService_FreePeers = 8001, + _OrthancPluginService_GetPeersCount = 8003, + _OrthancPluginService_GetPeerName = 8004, + _OrthancPluginService_GetPeerUrl = 8005, + _OrthancPluginService_CallPeerApi = 8006, + _OrthancPluginService_INTERNAL = 0x7fffffff } _OrthancPluginService; @@ -921,6 +929,14 @@ typedef struct _OrthancPluginFindAnswers_t OrthancPluginFindMatcher; + + /** + * @brief Opaque structure to the set of remote Orthanc Peers that are known to the local Orthanc server. + * @ingroup Toolbox + **/ + typedef struct _OrthancPluginPeers_t OrthancPluginPeers; + + /** * @brief Signature of a callback function that answers to a REST request. @@ -1284,6 +1300,9 @@ * Orthanc. * * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param expectedMajor Expected major version. + * @param expectedMinor Expected minor version. + * @param expectedRevision Expected revision. * @return 1 if and only if the versions are compatible. If the * result is 0, the initialization of the plugin should fail. * @see OrthancPluginCheckVersion @@ -5177,7 +5196,7 @@ * @param headersValues Array containing the values of the HTTP headers (can be NULL if no header). * @param username The username (can be NULL if no password protection). * @param password The password (can be NULL if no password protection). - * @param body The body of the POST request. + * @param body The HTTP body for a POST or PUT request. * @param bodySize The size of the body. * @param timeout Timeout in seconds (0 for default timeout). * @param certificateFile Path to the client certificate for HTTPS, in PEM format @@ -5188,6 +5207,7 @@ * (can be NULL if no client certificate or if not using HTTPS). * @param pkcs11 Enable PKCS#11 client authentication for hardware security modules and smart cards. * @return 0 if success, or the error code if failure. + * @see OrthancPluginCallPeerApi() **/ ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpClient( OrthancPluginContext* context, @@ -5691,6 +5711,278 @@ return context->InvokeService(context, _OrthancPluginService_RegisterIncomingHttpRequestFilter2, ¶ms); } + + + typedef struct + { + OrthancPluginPeers** peers; + } _OrthancPluginGetPeers; + + /** + * @brief Return the list of available Orthanc peers. + * + * This function returns the parameters of the Orthanc peers that are known to + * the Orthanc server hosting the plugin. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @return NULL if error, or a newly allocated opaque data structure containing the peers. + * This structure must be freed with OrthancPluginFreePeers(). + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginPeers* OrthancPluginGetPeers( + OrthancPluginContext* context) + { + OrthancPluginPeers* peers = NULL; + + _OrthancPluginGetPeers params; + memset(¶ms, 0, sizeof(params)); + params.peers = &peers; + + if (context->InvokeService(context, _OrthancPluginService_GetPeers, ¶ms) != OrthancPluginErrorCode_Success) + { + return NULL; + } + else + { + return peers; + } + } + + + typedef struct + { + OrthancPluginPeers* peers; + } _OrthancPluginFreePeers; + + /** + * @brief Free the list of available Orthanc peers. + * + * This function frees the data structure returned by OrthancPluginGetPeers(). + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param peers The data structure describing the Orthanc peers. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE void OrthancPluginFreePeers( + OrthancPluginContext* context, + OrthancPluginPeers* peers) + { + _OrthancPluginFreePeers params; + params.peers = peers; + + context->InvokeService(context, _OrthancPluginService_FreePeers, ¶ms); + } + + + typedef struct + { + uint32_t* target; + const OrthancPluginPeers* peers; + } _OrthancPluginGetPeersCount; + + /** + * @brief Get the number of Orthanc peers. + * + * This function returns the number of Orthanc peers. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param peers The data structure describing the Orthanc peers. + * @result The number of peers. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetPeersCount( + OrthancPluginContext* context, + const OrthancPluginPeers* peers) + { + uint32_t target = 0; + + _OrthancPluginGetPeersCount params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.peers = peers; + + if (context->InvokeService(context, _OrthancPluginService_GetPeersCount, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return 0; + } + else + { + return target; + } + } + + + typedef struct + { + const char** target; + const OrthancPluginPeers* peers; + uint32_t peerIndex; + } _OrthancPluginGetPeerProperty; + + /** + * @brief Get the symbolic name of an Orthanc peer. + * + * This function returns the symbolic name of the Orthanc peer, + * which corresponds to the key of the "OrthancPeers" configuration + * option of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param peers The data structure describing the Orthanc peers. + * @param peerIndex The index of the peer of interest. + * This value must be lower than OrthancPluginGetPeersCount(). + * @result The symbolic name, or NULL in the case of an error. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetPeerName( + OrthancPluginContext* context, + const OrthancPluginPeers* peers, + uint32_t peerIndex) + { + const char* target = NULL; + + _OrthancPluginGetPeerProperty params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.peers = peers; + params.peerIndex = peerIndex; + + if (context->InvokeService(context, _OrthancPluginService_GetPeerName, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + /** + * @brief Get the base URL of an Orthanc peer. + * + * This function returns the base URL to the REST API of some Orthanc peer. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param peers The data structure describing the Orthanc peers. + * @param peerIndex The index of the peer of interest. + * This value must be lower than OrthancPluginGetPeersCount(). + * @result The URL, or NULL in the case of an error. + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetPeerUrl( + OrthancPluginContext* context, + const OrthancPluginPeers* peers, + uint32_t peerIndex) + { + const char* target = NULL; + + _OrthancPluginGetPeerProperty params; + memset(¶ms, 0, sizeof(params)); + params.target = ⌖ + params.peers = peers; + params.peerIndex = peerIndex; + + if (context->InvokeService(context, _OrthancPluginService_GetPeerUrl, ¶ms) != OrthancPluginErrorCode_Success) + { + /* Error */ + return NULL; + } + else + { + return target; + } + } + + + + typedef struct + { + OrthancPluginMemoryBuffer* answerBody; + OrthancPluginMemoryBuffer* answerHeaders; + uint16_t* httpStatus; + const OrthancPluginPeers* peers; + uint32_t peerIndex; + OrthancPluginHttpMethod method; + const char* uri; + uint32_t additionalHeadersCount; + const char* const* additionalHeadersKeys; + const char* const* additionalHeadersValues; + const char* body; + uint32_t bodySize; + uint32_t timeout; + } _OrthancPluginCallPeerApi; + + /** + * @brief Call the REST API of an Orthanc peer. + * + * Make a REST call to the given URI in the REST API of a remote + * Orthanc peer. The result to the query is stored into a newly + * allocated memory buffer. The HTTP request will be done according + * to the "OrthancPeers" configuration option of Orthanc. + * + * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). + * @param answerBody The target memory buffer (out argument). + * It must be freed with OrthancPluginFreeMemoryBuffer(). + * @param answerHeaders The target memory buffer for the HTTP headers in the answers (out argument). + * The answer headers are formatted as a JSON object (associative array). + * The buffer must be freed with OrthancPluginFreeMemoryBuffer(). + * This argument can be set to NULL if the plugin has no interest in the HTTP headers. + * @param httpStatus The HTTP status after the execution of the request (out argument). + * @param peers The data structure describing the Orthanc peers. + * @param peerIndex The index of the peer of interest. + * This value must be lower than OrthancPluginGetPeersCount(). + * @param method HTTP method to be used. + * @param uri The URI of interest in the REST API. + * @param additionalHeadersCount The number of HTTP headers to be added to the + * HTTP headers provided in the global configuration of Orthanc. + * @param additionalHeadersKeys Array containing the keys of the HTTP headers (can be NULL if no header). + * @param additionalHeadersValues Array containing the values of the HTTP headers (can be NULL if no header). + * @param body The HTTP body for a POST or PUT request. + * @param bodySize The size of the body. + * @param timeout Timeout in seconds (0 for default timeout). + * @return 0 if success, or the error code if failure. + * @see OrthancPluginHttpClient() + * @ingroup Toolbox + **/ + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCallPeerApi( + OrthancPluginContext* context, + OrthancPluginMemoryBuffer* answerBody, + OrthancPluginMemoryBuffer* answerHeaders, + uint16_t* httpStatus, + const OrthancPluginPeers* peers, + uint32_t peerIndex, + OrthancPluginHttpMethod method, + const char* uri, + uint32_t additionalHeadersCount, + const char* const* additionalHeadersKeys, + const char* const* additionalHeadersValues, + const char* body, + uint32_t bodySize, + uint32_t timeout) + { + _OrthancPluginCallPeerApi params; + memset(¶ms, 0, sizeof(params)); + + params.answerBody = answerBody; + params.answerHeaders = answerHeaders; + params.httpStatus = httpStatus; + params.peers = peers; + params.peerIndex = peerIndex; + params.method = method; + params.uri = uri; + params.additionalHeadersCount = additionalHeadersCount; + params.additionalHeadersKeys = additionalHeadersKeys; + params.additionalHeadersValues = additionalHeadersValues; + params.body = body; + params.bodySize = bodySize; + params.timeout = timeout; + + return context->InvokeService(context, _OrthancPluginService_CallPeerApi, ¶ms); + } + + #ifdef __cplusplus } #endif