# HG changeset patch # User Sebastien Jodogne # Date 1559830375 -7200 # Node ID 2cd0369a156fbafc8e97131dfdca9559fa4f45c1 # Parent ad434967a68c4bd596260d12e9ddfb7ebb7c7cd7 support of chunked answers in HttpClient and in SDK diff -r ad434967a68c -r 2cd0369a156f Core/HttpClient.cpp --- a/Core/HttpClient.cpp Thu Jun 06 10:35:16 2019 +0200 +++ b/Core/HttpClient.cpp Thu Jun 06 16:12:55 2019 +0200 @@ -201,11 +201,11 @@ }; - class HttpClient::CurlRequestChunkedBody : public boost::noncopyable + class HttpClient::CurlRequestBody : public boost::noncopyable { private: - HttpClient::IRequestChunkedBody* body_; - std::string buffer_; + HttpClient::IRequestBody* body_; + std::string buffer_; size_t CallbackInternal(char* curlBuffer, size_t curlBufferSize) @@ -243,12 +243,12 @@ } public: - CurlRequestChunkedBody() : + CurlRequestBody() : body_(NULL) { } - void SetBody(HttpClient::IRequestChunkedBody& body) + void SetBody(HttpClient::IRequestBody& body) { body_ = &body; buffer_.clear(); @@ -265,22 +265,79 @@ return body_ != NULL; } - static size_t Callback(char *buffer, - size_t size, - size_t nitems, - void *userdata) + static size_t Callback(char *buffer, size_t size, size_t nitems, void *userdata) { try { - HttpClient::CurlRequestChunkedBody* body = reinterpret_cast(userdata); + assert(userdata != NULL); + return reinterpret_cast(userdata)-> + CallbackInternal(buffer, size * nitems); + } + catch (OrthancException& e) + { + LOG(ERROR) << "Exception while streaming HTTP body: " << e.What(); + return CURL_READFUNC_ABORT; + } + catch (...) + { + LOG(ERROR) << "Native exception while streaming HTTP body"; + return CURL_READFUNC_ABORT; + } + } + }; + - if (body == NULL) + class HttpClient::CurlAnswer : public boost::noncopyable + { + private: + HttpClient::IAnswer& answer_; + bool headersLowerCase_; + + public: + CurlAnswer(HttpClient::IAnswer& answer, + bool headersLowerCase) : + answer_(answer), + headersLowerCase_(headersLowerCase) + { + } + + static size_t HeaderCallback(void *buffer, size_t size, size_t nmemb, void *userdata) + { + try + { + assert(userdata != NULL); + CurlAnswer& that = *(static_cast(userdata)); + + size_t length = size * nmemb; + if (length == 0) { - throw OrthancException(ErrorCode_NullPointer); + return 0; } else { - return body->CallbackInternal(buffer, size * nitems); + std::string s(reinterpret_cast(buffer), length); + std::size_t colon = s.find(':'); + std::size_t eol = s.find("\r\n"); + if (colon != std::string::npos && + eol != std::string::npos) + { + std::string tmp(s.substr(0, colon)); + + if (that.headersLowerCase_) + { + Toolbox::ToLowerCase(tmp); + } + + std::string key = Toolbox::StripSpaces(tmp); + + if (!key.empty()) + { + std::string value = Toolbox::StripSpaces(s.substr(colon + 1, eol)); + that.answer_.AddHeader(key, value); + } + } + + return length; } } catch (OrthancException& e) @@ -294,6 +351,75 @@ return CURL_READFUNC_ABORT; } } + + static size_t BodyCallback(void *buffer, size_t size, size_t nmemb, void *userdata) + { + try + { + assert(userdata != NULL); + CurlAnswer& that = *(static_cast(userdata)); + + size_t length = size * nmemb; + if (length == 0) + { + return 0; + } + else + { + that.answer_.AddChunk(buffer, length); + return length; + } + } + catch (OrthancException& e) + { + LOG(ERROR) << "Exception while streaming HTTP body: " << e.What(); + return CURL_READFUNC_ABORT; + } + catch (...) + { + LOG(ERROR) << "Native exception while streaming HTTP body"; + return CURL_READFUNC_ABORT; + } + } + }; + + + class HttpClient::DefaultAnswer : public HttpClient::IAnswer + { + private: + ChunkedBuffer answer_; + HttpHeaders* headers_; + + public: + DefaultAnswer() : headers_(NULL) + { + } + + void SetHeaders(HttpHeaders& headers) + { + headers_ = &headers; + headers_->clear(); + } + + void FlattenBody(std::string& target) + { + answer_.Flatten(target); + } + + virtual void AddHeader(const std::string& key, + const std::string& value) + { + if (headers_ != NULL) + { + (*headers_) [key] = value; + } + } + + virtual void AddChunk(const void* data, + size_t size) + { + answer_.AddChunk(data, size); + } }; @@ -404,7 +530,7 @@ CurlHeaders defaultPostHeaders_; CurlHeaders defaultChunkedHeaders_; CurlHeaders userHeaders_; - CurlRequestChunkedBody chunkedBody_; + CurlRequestBody requestBody_; }; @@ -428,23 +554,6 @@ } - static size_t CurlBodyCallback(void *buffer, size_t size, size_t nmemb, void *payload) - { - ChunkedBuffer& target = *(static_cast(payload)); - - size_t length = size * nmemb; - if (length == 0) - { - return 0; - } - else - { - target.AddChunk(buffer, length); - return length; - } - } - - /*static int CurlDebugCallback(CURL *handle, curl_infotype type, char *data, @@ -475,52 +584,6 @@ }*/ - struct CurlHeaderParameters - { - bool lowerCase_; - HttpClient::HttpHeaders* headers_; - }; - - - static size_t CurlHeaderCallback(void *buffer, size_t size, size_t nmemb, void *payload) - { - CurlHeaderParameters& parameters = *(static_cast(payload)); - assert(parameters.headers_ != NULL); - - size_t length = size * nmemb; - if (length == 0) - { - return 0; - } - else - { - std::string s(reinterpret_cast(buffer), length); - std::size_t colon = s.find(':'); - std::size_t eol = s.find("\r\n"); - if (colon != std::string::npos && - eol != std::string::npos) - { - std::string tmp(s.substr(0, colon)); - - if (parameters.lowerCase_) - { - Toolbox::ToLowerCase(tmp); - } - - std::string key = Toolbox::StripSpaces(tmp); - - if (!key.empty()) - { - std::string value = Toolbox::StripSpaces(s.substr(colon + 1, eol)); - (*parameters.headers_) [key] = value; - } - } - - return length; - } - } - - void HttpClient::Setup() { pimpl_->defaultPostHeaders_.AddHeader("Expect", ""); @@ -529,7 +592,8 @@ pimpl_->curl_ = curl_easy_init(); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEFUNCTION, &CurlBodyCallback)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERFUNCTION, &CurlAnswer::HeaderCallback)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEFUNCTION, &CurlAnswer::BodyCallback)); CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADER, 0)); CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 1)); @@ -604,21 +668,21 @@ void HttpClient::SetBody(const std::string& data) { body_ = data; - pimpl_->chunkedBody_.Clear(); + pimpl_->requestBody_.Clear(); } - void HttpClient::SetBody(IRequestChunkedBody& body) + void HttpClient::SetBody(IRequestBody& body) { body_.clear(); - pimpl_->chunkedBody_.SetBody(body); + pimpl_->requestBody_.SetBody(body); } void HttpClient::ClearBody() { body_.clear(); - pimpl_->chunkedBody_.Clear(); + pimpl_->requestBody_.Clear(); } @@ -658,26 +722,10 @@ } - bool HttpClient::ApplyInternal(std::string& answerBody, - HttpHeaders* answerHeaders) + bool HttpClient::ApplyInternal(CurlAnswer& answer) { - answerBody.clear(); CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_URL, url_.c_str())); - - CurlHeaderParameters headerParameters; - - if (answerHeaders == NULL) - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERFUNCTION, NULL)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERDATA, NULL)); - } - else - { - headerParameters.lowerCase_ = headersToLowerCase_; - headerParameters.headers_ = answerHeaders; - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERFUNCTION, &CurlHeaderCallback)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERDATA, &headerParameters)); - } + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERDATA, &answer)); #if ORTHANC_ENABLE_SSL == 1 // Setup HTTPS-related options @@ -827,10 +875,10 @@ LOG(INFO) << "For performance, the HTTP header \"Expect\" should be set to empty string in POST/PUT requests"; } - if (pimpl_->chunkedBody_.IsValid()) + if (pimpl_->requestBody_.IsValid()) { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_READFUNCTION, CurlRequestChunkedBody::Callback)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_READDATA, &pimpl_->chunkedBody_)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_READFUNCTION, CurlRequestBody::Callback)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_READDATA, &pimpl_->requestBody_)); CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 1L)); CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, -1L)); @@ -840,7 +888,8 @@ } else if (!pimpl_->userHeaders_.IsChunkedTransfer()) { - LOG(WARNING) << "The HTTP header \"Transfer-Encoding\" must be set to \"chunked\" in streamed POST/PUT requests"; + LOG(WARNING) << "The HTTP header \"Transfer-Encoding\" must be set to \"chunked\" " + << "if streaming a chunked body in POST/PUT requests"; } } else @@ -849,6 +898,12 @@ CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_READFUNCTION, NULL)); CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_UPLOAD, 0)); + if (pimpl_->userHeaders_.IsChunkedTransfer()) + { + LOG(WARNING) << "The HTTP header \"Transfer-Encoding\" must only be set " + << "if streaming a chunked body in POST/PUT requests"; + } + if (pimpl_->userHeaders_.IsEmpty()) { pimpl_->defaultPostHeaders_.Assign(pimpl_->curl_); @@ -872,8 +927,7 @@ CURLcode code; long status = 0; - ChunkedBuffer buffer; - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEDATA, &buffer)); + CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEDATA, &answer)); if (boost::starts_with(url_, "https://")) { @@ -904,20 +958,42 @@ lastStatus_ = static_cast(status); } - bool success = (status >= 200 && status < 300); - - if (success) + if (status >= 200 && status < 300) { - buffer.Flatten(answerBody); + return true; // Success } else { - answerBody.clear(); LOG(ERROR) << "Error in HTTP request, received HTTP status " << status << " (" << EnumerationToString(lastStatus_) << ")"; + return false; + } + } + + + bool HttpClient::ApplyInternal(std::string& answerBody, + HttpHeaders* answerHeaders) + { + answerBody.clear(); + + DefaultAnswer answer; + + if (answerHeaders != NULL) + { + answer.SetHeaders(*answerHeaders); } - return success; + CurlAnswer wrapper(answer, headersToLowerCase_); + + if (ApplyInternal(wrapper)) + { + answer.FlattenBody(answerBody); + return true; + } + else + { + return false; + } } @@ -1008,6 +1084,24 @@ } + bool HttpClient::Apply(IAnswer& answer) + { + CurlAnswer wrapper(answer, headersToLowerCase_); + return ApplyInternal(wrapper); + } + + + void HttpClient::ApplyAndThrowException(IAnswer& answer) + { + CurlAnswer wrapper(answer, headersToLowerCase_); + + if (!ApplyInternal(wrapper)) + { + ThrowException(GetLastStatus()); + } + } + + void HttpClient::ApplyAndThrowException(std::string& answerBody) { if (!Apply(answerBody)) diff -r ad434967a68c -r 2cd0369a156f Core/HttpClient.h --- a/Core/HttpClient.h Thu Jun 06 10:35:16 2019 +0200 +++ b/Core/HttpClient.h Thu Jun 06 16:12:55 2019 +0200 @@ -57,19 +57,35 @@ public: typedef std::map HttpHeaders; - class IRequestChunkedBody : public boost::noncopyable + class IRequestBody : public boost::noncopyable { public: - virtual ~IRequestChunkedBody() + virtual ~IRequestBody() { } virtual bool ReadNextChunk(std::string& chunk) = 0; }; + class IAnswer : public boost::noncopyable + { + public: + virtual ~IAnswer() + { + } + + virtual void AddHeader(const std::string& key, + const std::string& value) = 0; + + virtual void AddChunk(const void* data, + size_t size) = 0; + }; + private: class CurlHeaders; - class CurlRequestChunkedBody; + class CurlRequestBody; + class CurlAnswer; + class DefaultAnswer; class GlobalParameters; struct PImpl; @@ -97,6 +113,8 @@ void operator= (const HttpClient&); // Assignment forbidden HttpClient(const HttpClient& base); // Copy forbidden + bool ApplyInternal(CurlAnswer& answer); + bool ApplyInternal(std::string& answerBody, HttpHeaders* answerHeaders); @@ -158,7 +176,7 @@ return body_; } - void SetBody(IRequestChunkedBody& body); + void SetBody(IRequestBody& body); void ClearBody(); @@ -174,6 +192,8 @@ void ClearHeaders(); + bool Apply(IAnswer& answer); + bool Apply(std::string& answerBody) { return ApplyInternal(answerBody, NULL); @@ -295,6 +315,8 @@ static void SetDefaultTimeout(long timeout); + void ApplyAndThrowException(IAnswer& answer); + void ApplyAndThrowException(std::string& answerBody); void ApplyAndThrowException(Json::Value& answerBody); diff -r ad434967a68c -r 2cd0369a156f Plugins/Engine/OrthancPlugins.cpp --- a/Plugins/Engine/OrthancPlugins.cpp Thu Jun 06 10:35:16 2019 +0200 +++ b/Plugins/Engine/OrthancPlugins.cpp Thu Jun 06 16:12:55 2019 +0200 @@ -43,7 +43,6 @@ #endif -#include "../../Core/ChunkedBuffer.h" #include "../../Core/Compression/GzipCompressor.h" #include "../../Core/Compression/ZlibCompressor.h" #include "../../Core/DicomFormat/DicomArray.h" @@ -356,7 +355,7 @@ public: DicomWebBinaryFormatter(const _OrthancPluginEncodeDicomWeb& parameters) : - callback_(parameters.callback) + callback_(parameters.callback) { } @@ -422,8 +421,8 @@ public: PluginHttpOutput(HttpOutput& output) : - output_(output), - logDetails_(false) + output_(output), + logDetails_(false) { } @@ -520,8 +519,8 @@ public: ServerContextLock(PImpl& that) : - lock_(that.contextMutex_), - context_(that.context_) + lock_(that.contextMutex_), + context_(that.context_) { if (context_ == NULL) { @@ -976,15 +975,15 @@ - class OrthancPlugins::HttpRequestBody : public HttpClient::IRequestChunkedBody + class OrthancPlugins::StreamingHttpRequest : public HttpClient::IRequestBody { private: - const _OrthancPluginHttpClientChunkedBody& params_; - PluginsErrorDictionary& errorDictionary_; + const _OrthancPluginStreamingHttpClient& params_; + PluginsErrorDictionary& errorDictionary_; public: - HttpRequestBody(const _OrthancPluginHttpClientChunkedBody& params, - PluginsErrorDictionary& errorDictionary) : + StreamingHttpRequest(const _OrthancPluginStreamingHttpClient& params, + PluginsErrorDictionary& errorDictionary) : params_(params), errorDictionary_(errorDictionary) { @@ -992,23 +991,23 @@ virtual bool ReadNextChunk(std::string& chunk) { - if (params_.requestBodyIsDone(params_.requestBody)) + if (params_.requestIsDone(params_.request)) { return false; } else { - size_t size = params_.requestBodyChunkSize(params_.requestBody); + size_t size = params_.requestChunkSize(params_.request); chunk.resize(size); if (size != 0) { - const void* data = params_.requestBodyChunkData(params_.requestBody); + const void* data = params_.requestChunkData(params_.request); memcpy(&chunk[0], data, size); } - OrthancPluginErrorCode error = params_.requestBodyNext(params_.requestBody); + OrthancPluginErrorCode error = params_.requestNext(params_.request); if (error != OrthancPluginErrorCode_Success) { @@ -1024,6 +1023,46 @@ }; + class OrthancPlugins::StreamingHttpAnswer : public HttpClient::IAnswer + { + private: + const _OrthancPluginStreamingHttpClient& params_; + PluginsErrorDictionary& errorDictionary_; + + public: + StreamingHttpAnswer(const _OrthancPluginStreamingHttpClient& params, + PluginsErrorDictionary& errorDictionary) : + params_(params), + errorDictionary_(errorDictionary) + { + } + + virtual void AddHeader(const std::string& key, + const std::string& value) + { + OrthancPluginErrorCode error = params_.answerAddHeader(params_.answer, key.c_str(), value.c_str()); + + if (error != OrthancPluginErrorCode_Success) + { + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast(error)); + } + } + + virtual void AddChunk(const void* data, + size_t size) + { + OrthancPluginErrorCode error = params_.answerAddChunk(params_.answer, data, size); + + if (error != OrthancPluginErrorCode_Success) + { + errorDictionary_.LogError(error, true); + throw OrthancException(static_cast(error)); + } + } + }; + + OrthancPlugins::OrthancPlugins() { /* Sanity check of the compiler */ @@ -2085,8 +2124,8 @@ } - static void RunHttpClient(HttpClient& client, - const _OrthancPluginCallHttpClient2& parameters) + static void SetupHttpClient(HttpClient& client, + const _OrthancPluginCallHttpClient2& parameters) { client.SetUrl(parameters.url); client.SetConvertHeadersToLowerCase(false); @@ -2154,6 +2193,18 @@ default: throw OrthancException(ErrorCode_ParameterOutOfRange); } + } + + + static void ExecuteHttpClientWithoutStream(uint16_t& httpStatus, + OrthancPluginMemoryBuffer* answerBody, + OrthancPluginMemoryBuffer* answerHeaders, + HttpClient& client) + { + if (answerBody == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } std::string body; HttpClient::HttpHeaders headers; @@ -2161,7 +2212,7 @@ bool success = client.Apply(body, headers); // The HTTP request has succeeded - *parameters.httpStatus = static_cast(client.GetLastStatus()); + httpStatus = static_cast(client.GetLastStatus()); if (!success) { @@ -2169,7 +2220,7 @@ } // Copy the HTTP headers of the answer, if the plugin requested them - if (parameters.answerHeaders != NULL) + if (answerHeaders != NULL) { Json::Value json = Json::objectValue; @@ -2180,13 +2231,13 @@ } std::string s = json.toStyledString(); - CopyToMemoryBuffer(*parameters.answerHeaders, s); + CopyToMemoryBuffer(*answerHeaders, s); } // Copy the body of the answer if it makes sense - if (parameters.method != OrthancPluginHttpMethod_Delete) + if (client.GetMethod() != HttpMethod_Delete) { - CopyToMemoryBuffer(*parameters.answerBody, body); + CopyToMemoryBuffer(*answerBody, body); } } @@ -2194,32 +2245,36 @@ void OrthancPlugins::CallHttpClient(const void* parameters) { const _OrthancPluginCallHttpClient& p = *reinterpret_cast(parameters); - - _OrthancPluginCallHttpClient2 converted; - memset(&converted, 0, sizeof(converted)); - - uint16_t httpStatus; - - converted.answerBody = p.target; - converted.answerHeaders = NULL; - converted.httpStatus = &httpStatus; - converted.method = p.method; - converted.url = p.url; - converted.headersCount = 0; - converted.headersKeys = NULL; - converted.headersValues = NULL; - converted.body = p.body; - converted.bodySize = p.bodySize; - converted.username = p.username; - converted.password = p.password; - converted.timeout = 0; // Use default timeout - converted.certificateFile = NULL; - converted.certificateKeyFile = NULL; - converted.certificateKeyPassword = NULL; - converted.pkcs11 = false; HttpClient client; - RunHttpClient(client, converted); + + { + _OrthancPluginCallHttpClient2 converted; + memset(&converted, 0, sizeof(converted)); + + converted.answerBody = NULL; + converted.answerHeaders = NULL; + converted.httpStatus = NULL; + converted.method = p.method; + converted.url = p.url; + converted.headersCount = 0; + converted.headersKeys = NULL; + converted.headersValues = NULL; + converted.body = p.body; + converted.bodySize = p.bodySize; + converted.username = p.username; + converted.password = p.password; + converted.timeout = 0; // Use default timeout + converted.certificateFile = NULL; + converted.certificateKeyFile = NULL; + converted.certificateKeyPassword = NULL; + converted.pkcs11 = false; + + SetupHttpClient(client, converted); + } + + uint16_t status; + ExecuteHttpClientWithoutStream(status, p.target, NULL, client); } @@ -2227,57 +2282,74 @@ { const _OrthancPluginCallHttpClient2& p = *reinterpret_cast(parameters); + if (p.httpStatus == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + HttpClient client; if (p.method == OrthancPluginHttpMethod_Post || p.method == OrthancPluginHttpMethod_Put) { - client.GetBody().assign(p.body, p.bodySize); + client.GetBody().assign(p.body, p.bodySize); } - RunHttpClient(client, p); + SetupHttpClient(client, p); + ExecuteHttpClientWithoutStream(*p.httpStatus, p.answerBody, p.answerHeaders, client); } - void OrthancPlugins::HttpClientChunkedBody(const void* parameters) + void OrthancPlugins::StreamingHttpClient(const void* parameters) { - const _OrthancPluginHttpClientChunkedBody& p = - *reinterpret_cast(parameters); - - if (p.method != OrthancPluginHttpMethod_Post && - p.method != OrthancPluginHttpMethod_Put) + const _OrthancPluginStreamingHttpClient& p = + *reinterpret_cast(parameters); + + if (p.httpStatus == NULL) { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "This plugin service is only allowed for PUT and POST HTTP requests"); + throw OrthancException(ErrorCode_NullPointer); } - HttpRequestBody body(p, pimpl_->dictionary_); - HttpClient client; - client.SetBody(body); + + { + _OrthancPluginCallHttpClient2 converted; + memset(&converted, 0, sizeof(converted)); + + converted.answerBody = NULL; + converted.answerHeaders = NULL; + converted.httpStatus = NULL; + converted.method = p.method; + converted.url = p.url; + converted.headersCount = p.headersCount; + converted.headersKeys = p.headersKeys; + converted.headersValues = p.headersValues; + converted.body = NULL; + converted.bodySize = 0; + converted.username = p.username; + converted.password = p.password; + converted.timeout = p.timeout; + converted.certificateFile = p.certificateFile; + converted.certificateKeyFile = p.certificateKeyFile; + converted.certificateKeyPassword = p.certificateKeyPassword; + converted.pkcs11 = p.pkcs11; + + SetupHttpClient(client, converted); + } - _OrthancPluginCallHttpClient2 converted; - memset(&converted, 0, sizeof(converted)); - - converted.answerBody = p.answerBody; - converted.answerHeaders = p.answerHeaders; - converted.httpStatus = p.httpStatus; - converted.method = p.method; - converted.url = p.url; - converted.headersCount = p.headersCount; - converted.headersKeys = p.headersKeys; - converted.headersValues = p.headersValues; - converted.body = NULL; - converted.bodySize = 0; - converted.username = p.username; - converted.password = p.password; - converted.timeout = p.timeout; - converted.certificateFile = p.certificateFile; - converted.certificateKeyFile = p.certificateKeyFile; - converted.certificateKeyPassword = p.certificateKeyPassword; - converted.pkcs11 = p.pkcs11; - - RunHttpClient(client, converted); + StreamingHttpRequest body(p, pimpl_->dictionary_); + client.SetBody(body); + + StreamingHttpAnswer answer(p, pimpl_->dictionary_); + + bool success = client.Apply(answer); + + *p.httpStatus = static_cast(client.GetLastStatus()); + + if (!success) + { + HttpClient::ThrowException(client.GetLastStatus()); + } } @@ -2959,8 +3031,8 @@ CallHttpClient2(parameters); return true; - case _OrthancPluginService_HttpClientChunkedBody: - HttpClientChunkedBody(parameters); + case _OrthancPluginService_StreamingHttpClient: + StreamingHttpClient(parameters); return true; case _OrthancPluginService_ConvertPixelFormat: diff -r ad434967a68c -r 2cd0369a156f Plugins/Engine/OrthancPlugins.h --- a/Plugins/Engine/OrthancPlugins.h Thu Jun 06 10:35:16 2019 +0200 +++ b/Plugins/Engine/OrthancPlugins.h Thu Jun 06 16:12:55 2019 +0200 @@ -89,7 +89,8 @@ class WorklistHandler; class FindHandler; class MoveHandler; - class HttpRequestBody; + class StreamingHttpRequest; + class StreamingHttpAnswer; void RegisterRestCallback(const void* parameters, bool lock); @@ -165,7 +166,7 @@ void CallHttpClient2(const void* parameters); - void HttpClientChunkedBody(const void* parameters); + void StreamingHttpClient(const void* parameters); void CallPeerApi(const void* parameters); diff -r ad434967a68c -r 2cd0369a156f Plugins/Include/orthanc/OrthancCPlugin.h --- a/Plugins/Include/orthanc/OrthancCPlugin.h Thu Jun 06 10:35:16 2019 +0200 +++ b/Plugins/Include/orthanc/OrthancCPlugin.h Thu Jun 06 16:12:55 2019 +0200 @@ -429,7 +429,7 @@ _OrthancPluginService_SetMetricsValue = 31, _OrthancPluginService_EncodeDicomWebJson = 32, _OrthancPluginService_EncodeDicomWebXml = 33, - _OrthancPluginService_HttpClientChunkedBody = 34, /* New in Orthanc 1.5.7 */ + _OrthancPluginService_StreamingHttpClient = 34, /* New in Orthanc 1.5.7 */ /* Registration of callbacks */ _OrthancPluginService_RegisterRestCallback = 1000, @@ -6808,71 +6808,77 @@ - - - typedef uint8_t (*OrthancPluginHttpRequestBodyIsDone) (void* body); - - typedef OrthancPluginErrorCode (*OrthancPluginHttpRequestBodyNext) (void* body); - - typedef const void* (*OrthancPluginHttpRequestBodyGetChunkData) (void* body); - - typedef uint32_t (*OrthancPluginHttpRequestBodyGetChunkSize) (void* body); + typedef OrthancPluginErrorCode (*OrthancPluginHttpAnswerStreamAddHeader) (void* answer, + const char* key, + const char* value); + + typedef OrthancPluginErrorCode (*OrthancPluginHttpAnswerStreamAddChunk) (void* answer, + const void* data, + uint32_t size); + + typedef uint8_t (*OrthancPluginHttpRequestStreamIsDone) (void* request); + + typedef OrthancPluginErrorCode (*OrthancPluginHttpRequestStreamNext) (void* request); + + typedef const void* (*OrthancPluginHttpRequestStreamGetChunkData) (void* request); + + typedef uint32_t (*OrthancPluginHttpRequestStreamGetChunkSize) (void* request); typedef struct { - OrthancPluginMemoryBuffer* answerBody; - OrthancPluginMemoryBuffer* answerHeaders; - uint16_t* httpStatus; - OrthancPluginHttpMethod method; - const char* url; - uint32_t headersCount; - const char* const* headersKeys; - const char* const* headersValues; - const char* username; - const char* password; - uint32_t timeout; - const char* certificateFile; - const char* certificateKeyFile; - const char* certificateKeyPassword; - uint8_t pkcs11; - void* requestBody; - OrthancPluginHttpRequestBodyIsDone requestBodyIsDone; - OrthancPluginHttpRequestBodyGetChunkData requestBodyChunkData; - OrthancPluginHttpRequestBodyGetChunkSize requestBodyChunkSize; - OrthancPluginHttpRequestBodyNext requestBodyNext; - } _OrthancPluginHttpClientChunkedBody; - - ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginHttpClientChunkedBody( - OrthancPluginContext* context, - OrthancPluginMemoryBuffer* answerBody, - OrthancPluginMemoryBuffer* answerHeaders, - uint16_t* httpStatus, - OrthancPluginHttpMethod method, - const char* url, - uint32_t headersCount, - const char* const* headersKeys, - const char* const* headersValues, - const char* username, - const char* password, - uint32_t timeout, - const char* certificateFile, - const char* certificateKeyFile, - const char* certificateKeyPassword, - uint8_t pkcs11, - void* requestBody, - OrthancPluginHttpRequestBodyIsDone requestBodyIsDone, - OrthancPluginHttpRequestBodyGetChunkData requestBodyChunkData, - OrthancPluginHttpRequestBodyGetChunkSize requestBodyChunkSize, - OrthancPluginHttpRequestBodyNext requestBodyNext) - { - _OrthancPluginHttpClientChunkedBody params; + void* answer; + OrthancPluginHttpAnswerStreamAddChunk answerAddChunk; + OrthancPluginHttpAnswerStreamAddHeader answerAddHeader; + uint16_t* httpStatus; + OrthancPluginHttpMethod method; + const char* url; + uint32_t headersCount; + const char* const* headersKeys; + const char* const* headersValues; + void* request; + OrthancPluginHttpRequestStreamIsDone requestIsDone; + OrthancPluginHttpRequestStreamGetChunkData requestChunkData; + OrthancPluginHttpRequestStreamGetChunkSize requestChunkSize; + OrthancPluginHttpRequestStreamNext requestNext; + const char* username; + const char* password; + uint32_t timeout; + const char* certificateFile; + const char* certificateKeyFile; + const char* certificateKeyPassword; + uint8_t pkcs11; + } _OrthancPluginStreamingHttpClient; + + ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStreamingHttpClient( + OrthancPluginContext* context, + void* answer, + OrthancPluginHttpAnswerStreamAddChunk answerAddChunk, + OrthancPluginHttpAnswerStreamAddHeader answerAddHeader, + uint16_t* httpStatus, + OrthancPluginHttpMethod method, + const char* url, + uint32_t headersCount, + const char* const* headersKeys, + const char* const* headersValues, + void* request, + OrthancPluginHttpRequestStreamIsDone requestIsDone, + OrthancPluginHttpRequestStreamGetChunkData requestChunkData, + OrthancPluginHttpRequestStreamGetChunkSize requestChunkSize, + OrthancPluginHttpRequestStreamNext requestNext, + const char* username, + const char* password, + uint32_t timeout, + const char* certificateFile, + const char* certificateKeyFile, + const char* certificateKeyPassword, + uint8_t pkcs11) + { + _OrthancPluginStreamingHttpClient params; memset(¶ms, 0, sizeof(params)); /* In common with OrthancPluginHttpClient() */ - params.answerBody = answerBody; - params.answerHeaders = answerHeaders; params.httpStatus = httpStatus; params.method = method; params.url = url; @@ -6887,14 +6893,17 @@ params.certificateKeyPassword = certificateKeyPassword; params.pkcs11 = pkcs11; - /* For body stream */ - params.requestBody = requestBody; - params.requestBodyIsDone = requestBodyIsDone; - params.requestBodyChunkData = requestBodyChunkData; - params.requestBodyChunkSize = requestBodyChunkSize; - params.requestBodyNext = requestBodyNext; - - return context->InvokeService(context, _OrthancPluginService_HttpClientChunkedBody, ¶ms); + /* For streaming */ + params.answer = answer; + params.answerAddChunk = answerAddChunk; + params.answerAddHeader = answerAddHeader; + params.request = request; + params.requestIsDone = requestIsDone; + params.requestChunkData = requestChunkData; + params.requestChunkSize = requestChunkSize; + params.requestNext = requestNext; + + return context->InvokeService(context, _OrthancPluginService_StreamingHttpClient, ¶ms); } diff -r ad434967a68c -r 2cd0369a156f Plugins/Samples/Common/OrthancPluginCppWrapper.cpp --- a/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Thu Jun 06 10:35:16 2019 +0200 +++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Thu Jun 06 16:12:55 2019 +0200 @@ -2059,22 +2059,21 @@ #if HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 -#if HAS_ORTHANC_PLUGIN_HTTP_CHUNKED_BODY == 1 - class HttpClient::RequestChunkedBody : public boost::noncopyable + class HttpClient::RequestBodyWrapper : public boost::noncopyable { private: - static RequestChunkedBody& GetObject(void* body) + static RequestBodyWrapper& GetObject(void* body) { assert(body != NULL); - return *reinterpret_cast(body); + return *reinterpret_cast(body); } - IRequestChunkedBody& body_; + IRequestBody& body_; bool done_; std::string chunk_; public: - RequestChunkedBody(IRequestChunkedBody& body) : + RequestBodyWrapper(IRequestBody& body) : body_(body), done_(false) { @@ -2097,7 +2096,7 @@ static OrthancPluginErrorCode Next(void* body) { - RequestChunkedBody& that = GetObject(body); + RequestBodyWrapper& that = GetObject(body); if (that.done_) { @@ -2121,14 +2120,62 @@ } } }; + + +#if HAS_ORTHANC_PLUGIN_STREAMING_HTTP_CLIENT == 1 + static OrthancPluginErrorCode AnswerAddHeaderCallback(void* answer, + const char* key, + const char* value) + { + assert(answer != NULL && key != NULL && value != NULL); + + try + { + reinterpret_cast(answer)->AddHeader(key, value); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } #endif + +#if HAS_ORTHANC_PLUGIN_STREAMING_HTTP_CLIENT == 1 + static OrthancPluginErrorCode AnswerAddChunkCallback(void* answer, + const void* data, + uint32_t size) + { + assert(answer != NULL); + + try + { + reinterpret_cast(answer)->AddChunk(data, size); + return OrthancPluginErrorCode_Success; + } + catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e) + { + return static_cast(e.GetErrorCode()); + } + catch (...) + { + return OrthancPluginErrorCode_Plugin; + } + } +#endif + + HttpClient::HttpClient() : httpStatus_(0), method_(OrthancPluginHttpMethod_Get), timeout_(0), pkcs11_(false), - chunkedBody_(NULL) + streamingBody_(NULL) { } @@ -2169,108 +2216,356 @@ void HttpClient::ClearBody() { body_.clear(); - chunkedBody_ = NULL; + streamingBody_ = NULL; } void HttpClient::SwapBody(std::string& body) { body_.swap(body); - chunkedBody_ = NULL; + streamingBody_ = NULL; } void HttpClient::SetBody(const std::string& body) { body_ = body; - chunkedBody_ = NULL; + streamingBody_ = NULL; } -#if HAS_ORTHANC_PLUGIN_HTTP_CHUNKED_BODY == 1 - void HttpClient::SetBody(IRequestChunkedBody& body) + void HttpClient::SetBody(IRequestBody& body) { body_.clear(); - chunkedBody_ = &body; + streamingBody_ = &body; } -#endif - - - void HttpClient::Execute() + + + namespace { - std::vector headersKeys; - std::vector headersValues; - - headersKeys.reserve(headers_.size()); - headersValues.reserve(headers_.size()); - - for (HttpHeaders::const_iterator it = headers_.begin(); - it != headers_.end(); ++it) + class HeadersWrapper : public boost::noncopyable + { + private: + std::vector headersKeys_; + std::vector headersValues_; + + public: + HeadersWrapper(const HttpClient::HttpHeaders& headers) + { + headersKeys_.reserve(headers.size()); + headersValues_.reserve(headers.size()); + + for (HttpClient::HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it) + { + headersKeys_.push_back(it->first.c_str()); + headersValues_.push_back(it->second.c_str()); + } + } + + uint32_t GetCount() const + { + return headersKeys_.size(); + } + + const char* const* GetKeys() const + { + return headersKeys_.empty() ? NULL : &headersKeys_[0]; + } + + const char* const* GetValues() const + { + return headersValues_.empty() ? NULL : &headersValues_[0]; + } + }; + + + class MemoryRequestBody : public HttpClient::IRequestBody + { + private: + std::string body_; + + public: + MemoryRequestBody(const std::string& body) : + body_(body) + { + } + + virtual bool ReadNextChunk(std::string& chunk) + { + chunk.swap(body_); + return true; + } + }; + + + // This class mimics Orthanc::ChunkedBuffer + class ChunkedBuffer : public boost::noncopyable { - headersKeys.push_back(it->first.c_str()); - headersValues.push_back(it->second.c_str()); - } - - OrthancPluginErrorCode error; - - if (chunkedBody_ == NULL) + private: + typedef std::list Content; + + Content content_; + size_t size_; + + public: + ChunkedBuffer() : + size_(0) + { + } + + ~ChunkedBuffer() + { + Clear(); + } + + void Clear() + { + for (Content::iterator it = content_.begin(); it != content_.end(); ++it) + { + assert(*it != NULL); + delete *it; + } + + content_.clear(); + } + + void Flatten(std::string& target) const + { + target.resize(size_); + + size_t pos = 0; + + for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it) + { + assert(*it != NULL); + size_t s = (*it)->size(); + + if (s != 0) + { + memcpy(&target[pos], (*it)->c_str(), s); + pos += s; + } + } + + assert(size_ == 0 || + pos == target.size()); + } + + void AddChunk(const void* data, + size_t size) + { + content_.push_back(new std::string(reinterpret_cast(data), size)); + size_ += size; + } + + void AddChunk(const std::string& chunk) + { + content_.push_back(new std::string(chunk)); + size_ += chunk.size(); + } + }; + + +#if HAS_ORTHANC_PLUGIN_STREAMING_HTTP_CLIENT == 1 + class MemoryAnswer : public HttpClient::IAnswer { - error = OrthancPluginHttpClient( - GetGlobalContext(), - *answerBody_, - *answerHeaders_, - &httpStatus_, - method_, - url_.c_str(), - headersKeys.size(), - headersKeys.empty() ? NULL : &headersKeys[0], - headersValues.empty() ? NULL : &headersValues[0], - body_.empty() ? NULL : body_.c_str(), - body_.size(), - username_.empty() ? NULL : username_.c_str(), - password_.empty() ? NULL : password_.c_str(), - timeout_, - certificateFile_.empty() ? NULL : certificateFile_.c_str(), - certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(), - certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(), - pkcs11_ ? 1 : 0); + private: + HttpClient::HttpHeaders headers_; + ChunkedBuffer body_; + + public: + const HttpClient::HttpHeaders& GetHeaders() const + { + return headers_; + } + + const ChunkedBuffer& GetBody() const + { + return body_; + } + + virtual void AddHeader(const std::string& key, + const std::string& value) + { + headers_[key] = value; + } + + virtual void AddChunk(const void* data, + size_t size) + { + body_.AddChunk(data, size); + } + }; +#endif + } + + +#if HAS_ORTHANC_PLUGIN_STREAMING_HTTP_CLIENT == 1 + void HttpClient::ExecuteWithStream(uint16_t& httpStatus, + IAnswer& answer, + IRequestBody& body) const + { + std::auto_ptr h; + + // Automatically set the "Transfer-Encoding" header if absent + if (headers_.find("Transfer-Encoding") == headers_.end()) + { + HttpHeaders tmp = headers_; + tmp["Transfer-Encoding"] = "chunked"; + h.reset(new HeadersWrapper(tmp)); } else { -#if HAS_ORTHANC_PLUGIN_HTTP_CHUNKED_BODY != 1 - error = OrthancPluginErrorCode_InternalError; -#else - RequestChunkedBody wrapper(*chunkedBody_); + h.reset(new HeadersWrapper(headers_)); + } + + RequestBodyWrapper request(body); - error = OrthancPluginHttpClientChunkedBody( - GetGlobalContext(), - *answerBody_, - *answerHeaders_, - &httpStatus_, - method_, - url_.c_str(), - headersKeys.size(), - headersKeys.empty() ? NULL : &headersKeys[0], - headersValues.empty() ? NULL : &headersValues[0], - username_.empty() ? NULL : username_.c_str(), - password_.empty() ? NULL : password_.c_str(), - timeout_, - certificateFile_.empty() ? NULL : certificateFile_.c_str(), - certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(), - certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(), - pkcs11_ ? 1 : 0, - &wrapper, - RequestChunkedBody::IsDone, - RequestChunkedBody::GetChunkData, - RequestChunkedBody::GetChunkSize, - RequestChunkedBody::Next); -#endif - } + OrthancPluginErrorCode error = OrthancPluginStreamingHttpClient( + GetGlobalContext(), + &answer, + AnswerAddChunkCallback, + AnswerAddHeaderCallback, + &httpStatus, + method_, + url_.c_str(), + h->GetCount(), + h->GetKeys(), + h->GetValues(), + &request, + RequestBodyWrapper::IsDone, + RequestBodyWrapper::GetChunkData, + RequestBodyWrapper::GetChunkSize, + RequestBodyWrapper::Next, + username_.empty() ? NULL : username_.c_str(), + password_.empty() ? NULL : password_.c_str(), + timeout_, + certificateFile_.empty() ? NULL : certificateFile_.c_str(), + certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(), + certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(), + pkcs11_ ? 1 : 0); if (error != OrthancPluginErrorCode_Success) { ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); } } +#endif + + + void HttpClient::ExecuteWithoutStream(uint16_t& httpStatus, + HttpHeaders& answerHeaders, + std::string& answerBody, + const std::string& body) const + { + HeadersWrapper headers(headers_); + + MemoryBuffer answerBodyBuffer, answerHeadersBuffer; + + OrthancPluginErrorCode error = OrthancPluginHttpClient( + GetGlobalContext(), + *answerBodyBuffer, + *answerHeadersBuffer, + &httpStatus, + method_, + url_.c_str(), + headers.GetCount(), + headers.GetKeys(), + headers.GetValues(), + body.empty() ? NULL : body.c_str(), + body.size(), + username_.empty() ? NULL : username_.c_str(), + password_.empty() ? NULL : password_.c_str(), + timeout_, + certificateFile_.empty() ? NULL : certificateFile_.c_str(), + certificateFile_.empty() ? NULL : certificateKeyFile_.c_str(), + certificateFile_.empty() ? NULL : certificateKeyPassword_.c_str(), + pkcs11_ ? 1 : 0); + + if (error != OrthancPluginErrorCode_Success) + { + ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error); + } + + Json::Value v; + answerHeadersBuffer.ToJson(v); + + if (v.type() != Json::objectValue) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + + Json::Value::Members members = v.getMemberNames(); + answerHeaders.clear(); + + for (size_t i = 0; i < members.size(); i++) + { + const Json::Value& h = v[members[i]]; + if (h.type() != Json::stringValue) + { + ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError); + } + else + { + answerHeaders[members[i]] = h.asString(); + } + } + + answerBodyBuffer.ToString(answerBody); + } + + +#if HAS_ORTHANC_PLUGIN_STREAMING_HTTP_CLIENT == 1 + void HttpClient::Execute(IAnswer& answer) + { + if (streamingBody_ != NULL) + { + ExecuteWithStream(httpStatus_, answer, *streamingBody_); + } + else + { + MemoryRequestBody wrapper(body_); + ExecuteWithStream(httpStatus_, answer, wrapper); + } + } +#endif + + + void HttpClient::Execute(HttpHeaders& answerHeaders /* out */, + std::string& answerBody /* out */) + { +#if HAS_ORTHANC_PLUGIN_STREAMING_HTTP_CLIENT == 1 + MemoryAnswer answer; + Execute(answer); + answerHeaders = answer.GetHeaders(); + answer.GetBody().Flatten(answerBody); + +#else + // Compatibility mode for Orthanc SDK <= 1.5.6. This results in + // higher memory usage (all chunks from the body request are sent + // at once) + + if (streamingBody_ != NULL) + { + ChunkedBuffer buffer; + + std::string chunk; + while (streamingBody_->ReadNextChunk(chunk)) + { + buffer.AddChunk(chunk); + } + + std::string body; + buffer.Flatten(body); + + ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, body); + } + else + { + ExecuteWithoutStream(httpStatus_, answerHeaders, answerBody, body_); + } +#endif + } + #endif /* HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1 */ } diff -r ad434967a68c -r 2cd0369a156f Plugins/Samples/Common/OrthancPluginCppWrapper.h --- a/Plugins/Samples/Common/OrthancPluginCppWrapper.h Thu Jun 06 10:35:16 2019 +0200 +++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.h Thu Jun 06 16:12:55 2019 +0200 @@ -92,9 +92,9 @@ #endif #if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 5, 7) -# define HAS_ORTHANC_PLUGIN_HTTP_CHUNKED_BODY 1 +# define HAS_ORTHANC_PLUGIN_STREAMING_HTTP_CLIENT 1 #else -# define HAS_ORTHANC_PLUGIN_HTTP_CHUNKED_BODY 0 +# define HAS_ORTHANC_PLUGIN_STREAMING_HTTP_CLIENT 0 #endif @@ -789,24 +789,38 @@ class HttpClient : public boost::noncopyable { public: -#if HAS_ORTHANC_PLUGIN_HTTP_CHUNKED_BODY == 1 - class IRequestChunkedBody : public boost::noncopyable + typedef std::map HttpHeaders; + + class IRequestBody : public boost::noncopyable { public: - virtual ~IRequestChunkedBody() + virtual ~IRequestBody() { } virtual bool ReadNextChunk(std::string& chunk) = 0; }; + +#if HAS_ORTHANC_PLUGIN_STREAMING_HTTP_CLIENT == 1 + class IAnswer : public boost::noncopyable + { + public: + virtual ~IAnswer() + { + } + + virtual void AddHeader(const std::string& key, + const std::string& value) = 0; + + virtual void AddChunk(const void* data, + size_t size) = 0; + }; #endif - + private: - typedef std::map HttpHeaders; - - MemoryBuffer answerBody_; - MemoryBuffer answerHeaders_; + class RequestBodyWrapper; + uint16_t httpStatus_; OrthancPluginHttpMethod method_; std::string url_; @@ -819,28 +833,22 @@ std::string certificateKeyPassword_; bool pkcs11_; std::string body_; + IRequestBody* streamingBody_; -#if HAS_ORTHANC_PLUGIN_HTTP_CHUNKED_BODY == 1 - class RequestChunkedBody; - IRequestChunkedBody* chunkedBody_; -#else - // Dummy variable for backward compatibility - void* chunkedBody_; +#if HAS_ORTHANC_PLUGIN_STREAMING_HTTP_CLIENT == 1 + void ExecuteWithStream(uint16_t& httpStatus, // out + IAnswer& answer, // out + IRequestBody& body) const; #endif + void ExecuteWithoutStream(uint16_t& httpStatus, // out + HttpHeaders& answerHeaders, // out + std::string& answerBody, // out + const std::string& body) const; + public: HttpClient(); - const MemoryBuffer& GetAnswerBody() const - { - return answerBody_; - } - - const MemoryBuffer& GetAnswerHeaders() const - { - return answerHeaders_; - } - uint16_t GetHttpStatus() const { return httpStatus_; @@ -894,11 +902,14 @@ void SetBody(const std::string& body); -#if HAS_ORTHANC_PLUGIN_HTTP_CHUNKED_BODY == 1 - void SetBody(IRequestChunkedBody& body); + void SetBody(IRequestBody& body); + +#if HAS_ORTHANC_PLUGIN_STREAMING_HTTP_CLIENT == 1 + void Execute(IAnswer& answer); #endif - void Execute(); + void Execute(HttpHeaders& answerHeaders /* out */, + std::string& answerBody /* out */); }; #endif }