Mercurial > hg > orthanc
diff Core/HttpClient.cpp @ 3393:2cd0369a156f
support of chunked answers in HttpClient and in SDK
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 06 Jun 2019 16:12:55 +0200 |
parents | ad434967a68c |
children | 6add197274b1 |
line wrap: on
line diff
--- 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<HttpClient::CurlRequestChunkedBody*>(userdata); + assert(userdata != NULL); + return reinterpret_cast<HttpClient::CurlRequestBody*>(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<CurlAnswer*>(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<const char*>(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<CurlAnswer*>(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<ChunkedBuffer*>(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<CurlHeaderParameters*>(payload)); - assert(parameters.headers_ != NULL); - - size_t length = size * nmemb; - if (length == 0) - { - return 0; - } - else - { - std::string s(reinterpret_cast<const char*>(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<HttpStatus>(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))