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