Mercurial > hg > orthanc
comparison Core/HttpClient.cpp @ 3386:af9432e46c07
HttpClient::IBodyStream
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 05 Jun 2019 14:40:14 +0200 |
parents | 974e12006b7d |
children | a48d652f1500 |
comparison
equal
deleted
inserted
replaced
3385:f5467ab24aa4 | 3386:af9432e46c07 |
---|---|
88 | 88 |
89 | 89 |
90 | 90 |
91 namespace Orthanc | 91 namespace Orthanc |
92 { | 92 { |
93 static CURLcode CheckCode(CURLcode code) | |
94 { | |
95 if (code == CURLE_NOT_BUILT_IN) | |
96 { | |
97 throw OrthancException(ErrorCode_InternalError, | |
98 "Your libcurl does not contain a required feature, " | |
99 "please recompile Orthanc with -DUSE_SYSTEM_CURL=OFF"); | |
100 } | |
101 | |
102 if (code != CURLE_OK) | |
103 { | |
104 throw OrthancException(ErrorCode_NetworkProtocol, | |
105 "libCURL error: " + std::string(curl_easy_strerror(code))); | |
106 } | |
107 | |
108 return code; | |
109 } | |
110 | |
111 | |
112 // RAII pattern around a "curl_slist" | |
113 class HttpClient::CurlHeaders : public boost::noncopyable | |
114 { | |
115 private: | |
116 struct curl_slist *content_; | |
117 bool isChunkedTransfer_; | |
118 bool hasExpect_; | |
119 | |
120 public: | |
121 CurlHeaders() : | |
122 content_(NULL), | |
123 isChunkedTransfer_(false), | |
124 hasExpect_(false) | |
125 { | |
126 } | |
127 | |
128 CurlHeaders(const HttpClient::HttpHeaders& headers) | |
129 { | |
130 for (HttpClient::HttpHeaders::const_iterator | |
131 it = headers.begin(); it != headers.end(); ++it) | |
132 { | |
133 AddHeader(it->first, it->second); | |
134 } | |
135 } | |
136 | |
137 ~CurlHeaders() | |
138 { | |
139 Clear(); | |
140 } | |
141 | |
142 bool IsEmpty() const | |
143 { | |
144 return content_ == NULL; | |
145 } | |
146 | |
147 void Clear() | |
148 { | |
149 if (content_ != NULL) | |
150 { | |
151 curl_slist_free_all(content_); | |
152 content_ = NULL; | |
153 } | |
154 | |
155 isChunkedTransfer_ = false; | |
156 hasExpect_ = false; | |
157 } | |
158 | |
159 void AddHeader(const std::string& key, | |
160 const std::string& value) | |
161 { | |
162 if (boost::iequals(key, "Expect")) | |
163 { | |
164 hasExpect_ = true; | |
165 } | |
166 | |
167 if (boost::iequals(key, "Transfer-Encoding") && | |
168 value == "chunked") | |
169 { | |
170 isChunkedTransfer_ = true; | |
171 } | |
172 | |
173 std::string item = key + ": " + value; | |
174 | |
175 struct curl_slist *tmp = curl_slist_append(content_, item.c_str()); | |
176 | |
177 if (tmp == NULL) | |
178 { | |
179 throw OrthancException(ErrorCode_NotEnoughMemory); | |
180 } | |
181 else | |
182 { | |
183 content_ = tmp; | |
184 } | |
185 } | |
186 | |
187 void Assign(CURL* curl) const | |
188 { | |
189 CheckCode(curl_easy_setopt(curl, CURLOPT_HTTPHEADER, content_)); | |
190 } | |
191 | |
192 bool HasExpect() const | |
193 { | |
194 return hasExpect_; | |
195 } | |
196 | |
197 bool IsChunkedTransfer() const | |
198 { | |
199 return isChunkedTransfer_; | |
200 } | |
201 }; | |
202 | |
203 | |
204 class HttpClient::CurlBodyStream : public boost::noncopyable | |
205 { | |
206 private: | |
207 HttpClient::IBodyStream* stream_; | |
208 std::string buffer_; | |
209 | |
210 size_t CallbackInternal(char* curlBuffer, | |
211 size_t curlBufferSize) | |
212 { | |
213 if (stream_ == NULL) | |
214 { | |
215 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
216 } | |
217 | |
218 if (curlBufferSize == 0) | |
219 { | |
220 throw OrthancException(ErrorCode_InternalError); | |
221 } | |
222 | |
223 // Read chunks from the stream so as to fill the target buffer | |
224 std::string chunk; | |
225 | |
226 while (buffer_.size() < curlBufferSize && | |
227 stream_->ReadNextChunk(chunk)) | |
228 { | |
229 buffer_ += chunk; | |
230 } | |
231 | |
232 size_t s = std::min(buffer_.size(), curlBufferSize); | |
233 | |
234 if (s != 0) | |
235 { | |
236 memcpy(curlBuffer, buffer_.c_str(), s); | |
237 | |
238 // Remove the bytes that were actually sent from the buffer | |
239 buffer_.erase(0, s); | |
240 } | |
241 | |
242 return s; | |
243 } | |
244 | |
245 public: | |
246 CurlBodyStream() : | |
247 stream_(NULL) | |
248 { | |
249 } | |
250 | |
251 void SetStream(HttpClient::IBodyStream& stream) | |
252 { | |
253 stream_ = &stream; | |
254 buffer_.clear(); | |
255 } | |
256 | |
257 void Clear() | |
258 { | |
259 stream_ = NULL; | |
260 buffer_.clear(); | |
261 } | |
262 | |
263 bool IsValid() const | |
264 { | |
265 return stream_ != NULL; | |
266 } | |
267 | |
268 static size_t Callback(char *buffer, | |
269 size_t size, | |
270 size_t nitems, | |
271 void *userdata) | |
272 { | |
273 try | |
274 { | |
275 HttpClient::CurlBodyStream* stream = reinterpret_cast<HttpClient::CurlBodyStream*>(userdata); | |
276 | |
277 if (stream == NULL) | |
278 { | |
279 throw OrthancException(ErrorCode_NullPointer); | |
280 } | |
281 else | |
282 { | |
283 return stream->CallbackInternal(buffer, size * nitems); | |
284 } | |
285 } | |
286 catch (OrthancException& e) | |
287 { | |
288 LOG(ERROR) << "Exception while streaming HTTP body: " << e.What(); | |
289 return CURL_READFUNC_ABORT; | |
290 } | |
291 catch (...) | |
292 { | |
293 LOG(ERROR) << "Native exception while streaming HTTP body"; | |
294 return CURL_READFUNC_ABORT; | |
295 } | |
296 } | |
297 }; | |
298 | |
299 | |
93 class HttpClient::GlobalParameters | 300 class HttpClient::GlobalParameters |
94 { | 301 { |
95 private: | 302 private: |
96 boost::mutex mutex_; | 303 boost::mutex mutex_; |
97 bool httpsVerifyPeers_; | 304 bool httpsVerifyPeers_; |
192 | 399 |
193 | 400 |
194 struct HttpClient::PImpl | 401 struct HttpClient::PImpl |
195 { | 402 { |
196 CURL* curl_; | 403 CURL* curl_; |
197 struct curl_slist *defaultPostHeaders_; | 404 CurlHeaders defaultPostHeaders_; |
198 struct curl_slist *userHeaders_; | 405 CurlHeaders defaultChunkedHeaders_; |
406 CurlHeaders userHeaders_; | |
407 CurlBodyStream bodyStream_; | |
199 }; | 408 }; |
200 | 409 |
201 | 410 |
202 void HttpClient::ThrowException(HttpStatus status) | 411 void HttpClient::ThrowException(HttpStatus status) |
203 { | 412 { |
214 throw OrthancException(ErrorCode_UnknownResource); | 423 throw OrthancException(ErrorCode_UnknownResource); |
215 | 424 |
216 default: | 425 default: |
217 throw OrthancException(ErrorCode_NetworkProtocol); | 426 throw OrthancException(ErrorCode_NetworkProtocol); |
218 } | 427 } |
219 } | |
220 | |
221 | |
222 static CURLcode CheckCode(CURLcode code) | |
223 { | |
224 if (code == CURLE_NOT_BUILT_IN) | |
225 { | |
226 throw OrthancException(ErrorCode_InternalError, | |
227 "Your libcurl does not contain a required feature, " | |
228 "please recompile Orthanc with -DUSE_SYSTEM_CURL=OFF"); | |
229 } | |
230 | |
231 if (code != CURLE_OK) | |
232 { | |
233 throw OrthancException(ErrorCode_NetworkProtocol, | |
234 "libCURL error: " + std::string(curl_easy_strerror(code))); | |
235 } | |
236 | |
237 return code; | |
238 } | 428 } |
239 | 429 |
240 | 430 |
241 static size_t CurlBodyCallback(void *buffer, size_t size, size_t nmemb, void *payload) | 431 static size_t CurlBodyCallback(void *buffer, size_t size, size_t nmemb, void *payload) |
242 { | 432 { |
331 } | 521 } |
332 | 522 |
333 | 523 |
334 void HttpClient::Setup() | 524 void HttpClient::Setup() |
335 { | 525 { |
336 pimpl_->userHeaders_ = NULL; | 526 pimpl_->defaultPostHeaders_.AddHeader("Expect", ""); |
337 pimpl_->defaultPostHeaders_ = NULL; | 527 pimpl_->defaultChunkedHeaders_.AddHeader("Expect", ""); |
338 if ((pimpl_->defaultPostHeaders_ = curl_slist_append(pimpl_->defaultPostHeaders_, "Expect:")) == NULL) | 528 pimpl_->defaultChunkedHeaders_.AddHeader("Transfer-Encoding", "chunked"); |
339 { | |
340 throw OrthancException(ErrorCode_NotEnoughMemory); | |
341 } | |
342 | 529 |
343 pimpl_->curl_ = curl_easy_init(); | 530 pimpl_->curl_ = curl_easy_init(); |
344 if (!pimpl_->curl_) | |
345 { | |
346 curl_slist_free_all(pimpl_->defaultPostHeaders_); | |
347 throw OrthancException(ErrorCode_NotEnoughMemory); | |
348 } | |
349 | 531 |
350 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEFUNCTION, &CurlBodyCallback)); | 532 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEFUNCTION, &CurlBodyCallback)); |
351 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADER, 0)); | 533 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADER, 0)); |
352 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 1)); | 534 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 1)); |
353 | 535 |
365 GlobalParameters::GetInstance().GetSslConfiguration(verifyPeers_, caCertificates_); | 547 GlobalParameters::GetInstance().GetSslConfiguration(verifyPeers_, caCertificates_); |
366 } | 548 } |
367 | 549 |
368 | 550 |
369 HttpClient::HttpClient() : | 551 HttpClient::HttpClient() : |
370 pimpl_(new PImpl), | 552 pimpl_(new PImpl), |
371 verifyPeers_(true), | 553 verifyPeers_(true), |
372 pkcs11Enabled_(false), | 554 pkcs11Enabled_(false), |
373 headersToLowerCase_(true), | 555 headersToLowerCase_(true), |
374 redirectionFollowed_(true) | 556 redirectionFollowed_(true) |
375 { | 557 { |
377 } | 559 } |
378 | 560 |
379 | 561 |
380 HttpClient::HttpClient(const WebServiceParameters& service, | 562 HttpClient::HttpClient(const WebServiceParameters& service, |
381 const std::string& uri) : | 563 const std::string& uri) : |
382 pimpl_(new PImpl), | 564 pimpl_(new PImpl), |
383 verifyPeers_(true), | 565 verifyPeers_(true), |
384 headersToLowerCase_(true), | 566 headersToLowerCase_(true), |
385 redirectionFollowed_(true) | 567 redirectionFollowed_(true) |
386 { | 568 { |
387 Setup(); | 569 Setup(); |
414 | 596 |
415 | 597 |
416 HttpClient::~HttpClient() | 598 HttpClient::~HttpClient() |
417 { | 599 { |
418 curl_easy_cleanup(pimpl_->curl_); | 600 curl_easy_cleanup(pimpl_->curl_); |
419 curl_slist_free_all(pimpl_->defaultPostHeaders_); | 601 } |
420 ClearHeaders(); | 602 |
603 | |
604 void HttpClient::SetBody(const std::string& data) | |
605 { | |
606 body_ = data; | |
607 pimpl_->bodyStream_.Clear(); | |
608 } | |
609 | |
610 | |
611 void HttpClient::SetBodyStream(IBodyStream& stream) | |
612 { | |
613 pimpl_->bodyStream_.SetStream(stream); | |
614 } | |
615 | |
616 | |
617 void HttpClient::ClearBodyStream() | |
618 { | |
619 pimpl_->bodyStream_.Clear(); | |
421 } | 620 } |
422 | 621 |
423 | 622 |
424 void HttpClient::SetVerbose(bool isVerbose) | 623 void HttpClient::SetVerbose(bool isVerbose) |
425 { | 624 { |
442 { | 641 { |
443 if (key.empty()) | 642 if (key.empty()) |
444 { | 643 { |
445 throw OrthancException(ErrorCode_ParameterOutOfRange); | 644 throw OrthancException(ErrorCode_ParameterOutOfRange); |
446 } | 645 } |
447 | 646 else |
448 std::string s = key + ": " + value; | 647 { |
449 | 648 pimpl_->userHeaders_.AddHeader(key, value); |
450 if ((pimpl_->userHeaders_ = curl_slist_append(pimpl_->userHeaders_, s.c_str())) == NULL) | |
451 { | |
452 throw OrthancException(ErrorCode_NotEnoughMemory); | |
453 } | 649 } |
454 } | 650 } |
455 | 651 |
456 | 652 |
457 void HttpClient::ClearHeaders() | 653 void HttpClient::ClearHeaders() |
458 { | 654 { |
459 if (pimpl_->userHeaders_ != NULL) | 655 pimpl_->userHeaders_.Clear(); |
460 { | |
461 curl_slist_free_all(pimpl_->userHeaders_); | |
462 pimpl_->userHeaders_ = NULL; | |
463 } | |
464 } | 656 } |
465 | 657 |
466 | 658 |
467 bool HttpClient::ApplyInternal(std::string& answerBody, | 659 bool HttpClient::ApplyInternal(std::string& answerBody, |
468 HttpHeaders* answerHeaders) | 660 HttpHeaders* answerHeaders) |
553 "cannot use HTTPS client authentication"); | 745 "cannot use HTTPS client authentication"); |
554 #endif | 746 #endif |
555 } | 747 } |
556 | 748 |
557 // Reset the parameters from previous calls to Apply() | 749 // Reset the parameters from previous calls to Apply() |
558 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->userHeaders_)); | 750 pimpl_->userHeaders_.Assign(pimpl_->curl_); |
559 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 0L)); | 751 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 0L)); |
560 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 0L)); | 752 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 0L)); |
561 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOBODY, 0L)); | 753 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOBODY, 0L)); |
562 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, NULL)); | 754 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, NULL)); |
563 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, NULL)); | 755 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, NULL)); |
602 break; | 794 break; |
603 | 795 |
604 case HttpMethod_Post: | 796 case HttpMethod_Post: |
605 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 1L)); | 797 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 1L)); |
606 | 798 |
607 if (pimpl_->userHeaders_ == NULL) | |
608 { | |
609 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->defaultPostHeaders_)); | |
610 } | |
611 | |
612 break; | 799 break; |
613 | 800 |
614 case HttpMethod_Delete: | 801 case HttpMethod_Delete: |
615 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOBODY, 1L)); | 802 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOBODY, 1L)); |
616 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, "DELETE")); | 803 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, "DELETE")); |
621 // CURLOPT_PUT if there is a body | 808 // CURLOPT_PUT if there is a body |
622 | 809 |
623 // CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PUT, 1L)); | 810 // CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PUT, 1L)); |
624 | 811 |
625 curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, "PUT"); /* !!! */ | 812 curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, "PUT"); /* !!! */ |
626 | |
627 if (pimpl_->userHeaders_ == NULL) | |
628 { | |
629 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->defaultPostHeaders_)); | |
630 } | |
631 | |
632 break; | 813 break; |
633 | 814 |
634 default: | 815 default: |
635 throw OrthancException(ErrorCode_InternalError); | 816 throw OrthancException(ErrorCode_InternalError); |
636 } | 817 } |
637 | 818 |
638 | |
639 if (method_ == HttpMethod_Post || | 819 if (method_ == HttpMethod_Post || |
640 method_ == HttpMethod_Put) | 820 method_ == HttpMethod_Put) |
641 { | 821 { |
642 if (body_.size() > 0) | 822 if (!pimpl_->userHeaders_.IsEmpty() && |
643 { | 823 !pimpl_->userHeaders_.HasExpect()) |
644 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, body_.c_str())); | 824 { |
645 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, body_.size())); | 825 LOG(INFO) << "For performance, the HTTP header \"Expect\" should be set to empty string in POST/PUT requests"; |
826 } | |
827 | |
828 if (pimpl_->bodyStream_.IsValid()) | |
829 { | |
830 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_READFUNCTION, CurlBodyStream::Callback)); | |
831 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_READDATA, &pimpl_->bodyStream_)); | |
832 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 1L)); | |
833 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, -1L)); | |
834 | |
835 if (pimpl_->userHeaders_.IsEmpty()) | |
836 { | |
837 pimpl_->defaultChunkedHeaders_.Assign(pimpl_->curl_); | |
838 } | |
839 else if (!pimpl_->userHeaders_.IsChunkedTransfer()) | |
840 { | |
841 LOG(WARNING) << "The HTTP header \"Transfer-Encoding\" must be set to \"chunked\" in streamed POST/PUT requests"; | |
842 } | |
646 } | 843 } |
647 else | 844 else |
648 { | 845 { |
649 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, NULL)); | 846 // Disable possible previous stream transfers |
650 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, 0)); | 847 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_READFUNCTION, NULL)); |
848 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_UPLOAD, 0)); | |
849 | |
850 if (pimpl_->userHeaders_.IsEmpty()) | |
851 { | |
852 pimpl_->defaultPostHeaders_.Assign(pimpl_->curl_); | |
853 } | |
854 | |
855 if (body_.size() > 0) | |
856 { | |
857 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, body_.c_str())); | |
858 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, body_.size())); | |
859 } | |
860 else | |
861 { | |
862 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, NULL)); | |
863 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, 0)); | |
864 } | |
651 } | 865 } |
652 } | 866 } |
653 | 867 |
654 | 868 |
655 // Do the actual request | 869 // Do the actual request |