comparison 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
comparison
equal deleted inserted replaced
3392:ad434967a68c 3393:2cd0369a156f
199 return isChunkedTransfer_; 199 return isChunkedTransfer_;
200 } 200 }
201 }; 201 };
202 202
203 203
204 class HttpClient::CurlRequestChunkedBody : public boost::noncopyable 204 class HttpClient::CurlRequestBody : public boost::noncopyable
205 { 205 {
206 private: 206 private:
207 HttpClient::IRequestChunkedBody* body_; 207 HttpClient::IRequestBody* body_;
208 std::string buffer_; 208 std::string buffer_;
209 209
210 size_t CallbackInternal(char* curlBuffer, 210 size_t CallbackInternal(char* curlBuffer,
211 size_t curlBufferSize) 211 size_t curlBufferSize)
212 { 212 {
213 if (body_ == NULL) 213 if (body_ == NULL)
241 241
242 return s; 242 return s;
243 } 243 }
244 244
245 public: 245 public:
246 CurlRequestChunkedBody() : 246 CurlRequestBody() :
247 body_(NULL) 247 body_(NULL)
248 { 248 {
249 } 249 }
250 250
251 void SetBody(HttpClient::IRequestChunkedBody& body) 251 void SetBody(HttpClient::IRequestBody& body)
252 { 252 {
253 body_ = &body; 253 body_ = &body;
254 buffer_.clear(); 254 buffer_.clear();
255 } 255 }
256 256
263 bool IsValid() const 263 bool IsValid() const
264 { 264 {
265 return body_ != NULL; 265 return body_ != NULL;
266 } 266 }
267 267
268 static size_t Callback(char *buffer, 268 static size_t Callback(char *buffer, size_t size, size_t nitems, void *userdata)
269 size_t size,
270 size_t nitems,
271 void *userdata)
272 { 269 {
273 try 270 try
274 { 271 {
275 HttpClient::CurlRequestChunkedBody* body = reinterpret_cast<HttpClient::CurlRequestChunkedBody*>(userdata); 272 assert(userdata != NULL);
276 273 return reinterpret_cast<HttpClient::CurlRequestBody*>(userdata)->
277 if (body == NULL) 274 CallbackInternal(buffer, size * nitems);
275 }
276 catch (OrthancException& e)
277 {
278 LOG(ERROR) << "Exception while streaming HTTP body: " << e.What();
279 return CURL_READFUNC_ABORT;
280 }
281 catch (...)
282 {
283 LOG(ERROR) << "Native exception while streaming HTTP body";
284 return CURL_READFUNC_ABORT;
285 }
286 }
287 };
288
289
290 class HttpClient::CurlAnswer : public boost::noncopyable
291 {
292 private:
293 HttpClient::IAnswer& answer_;
294 bool headersLowerCase_;
295
296 public:
297 CurlAnswer(HttpClient::IAnswer& answer,
298 bool headersLowerCase) :
299 answer_(answer),
300 headersLowerCase_(headersLowerCase)
301 {
302 }
303
304 static size_t HeaderCallback(void *buffer, size_t size, size_t nmemb, void *userdata)
305 {
306 try
307 {
308 assert(userdata != NULL);
309 CurlAnswer& that = *(static_cast<CurlAnswer*>(userdata));
310
311 size_t length = size * nmemb;
312 if (length == 0)
278 { 313 {
279 throw OrthancException(ErrorCode_NullPointer); 314 return 0;
280 } 315 }
281 else 316 else
282 { 317 {
283 return body->CallbackInternal(buffer, size * nitems); 318 std::string s(reinterpret_cast<const char*>(buffer), length);
319 std::size_t colon = s.find(':');
320 std::size_t eol = s.find("\r\n");
321 if (colon != std::string::npos &&
322 eol != std::string::npos)
323 {
324 std::string tmp(s.substr(0, colon));
325
326 if (that.headersLowerCase_)
327 {
328 Toolbox::ToLowerCase(tmp);
329 }
330
331 std::string key = Toolbox::StripSpaces(tmp);
332
333 if (!key.empty())
334 {
335 std::string value = Toolbox::StripSpaces(s.substr(colon + 1, eol));
336 that.answer_.AddHeader(key, value);
337 }
338 }
339
340 return length;
284 } 341 }
285 } 342 }
286 catch (OrthancException& e) 343 catch (OrthancException& e)
287 { 344 {
288 LOG(ERROR) << "Exception while streaming HTTP body: " << e.What(); 345 LOG(ERROR) << "Exception while streaming HTTP body: " << e.What();
291 catch (...) 348 catch (...)
292 { 349 {
293 LOG(ERROR) << "Native exception while streaming HTTP body"; 350 LOG(ERROR) << "Native exception while streaming HTTP body";
294 return CURL_READFUNC_ABORT; 351 return CURL_READFUNC_ABORT;
295 } 352 }
353 }
354
355 static size_t BodyCallback(void *buffer, size_t size, size_t nmemb, void *userdata)
356 {
357 try
358 {
359 assert(userdata != NULL);
360 CurlAnswer& that = *(static_cast<CurlAnswer*>(userdata));
361
362 size_t length = size * nmemb;
363 if (length == 0)
364 {
365 return 0;
366 }
367 else
368 {
369 that.answer_.AddChunk(buffer, length);
370 return length;
371 }
372 }
373 catch (OrthancException& e)
374 {
375 LOG(ERROR) << "Exception while streaming HTTP body: " << e.What();
376 return CURL_READFUNC_ABORT;
377 }
378 catch (...)
379 {
380 LOG(ERROR) << "Native exception while streaming HTTP body";
381 return CURL_READFUNC_ABORT;
382 }
383 }
384 };
385
386
387 class HttpClient::DefaultAnswer : public HttpClient::IAnswer
388 {
389 private:
390 ChunkedBuffer answer_;
391 HttpHeaders* headers_;
392
393 public:
394 DefaultAnswer() : headers_(NULL)
395 {
396 }
397
398 void SetHeaders(HttpHeaders& headers)
399 {
400 headers_ = &headers;
401 headers_->clear();
402 }
403
404 void FlattenBody(std::string& target)
405 {
406 answer_.Flatten(target);
407 }
408
409 virtual void AddHeader(const std::string& key,
410 const std::string& value)
411 {
412 if (headers_ != NULL)
413 {
414 (*headers_) [key] = value;
415 }
416 }
417
418 virtual void AddChunk(const void* data,
419 size_t size)
420 {
421 answer_.AddChunk(data, size);
296 } 422 }
297 }; 423 };
298 424
299 425
300 class HttpClient::GlobalParameters 426 class HttpClient::GlobalParameters
402 { 528 {
403 CURL* curl_; 529 CURL* curl_;
404 CurlHeaders defaultPostHeaders_; 530 CurlHeaders defaultPostHeaders_;
405 CurlHeaders defaultChunkedHeaders_; 531 CurlHeaders defaultChunkedHeaders_;
406 CurlHeaders userHeaders_; 532 CurlHeaders userHeaders_;
407 CurlRequestChunkedBody chunkedBody_; 533 CurlRequestBody requestBody_;
408 }; 534 };
409 535
410 536
411 void HttpClient::ThrowException(HttpStatus status) 537 void HttpClient::ThrowException(HttpStatus status)
412 { 538 {
422 case HttpStatus_404_NotFound: 548 case HttpStatus_404_NotFound:
423 throw OrthancException(ErrorCode_UnknownResource); 549 throw OrthancException(ErrorCode_UnknownResource);
424 550
425 default: 551 default:
426 throw OrthancException(ErrorCode_NetworkProtocol); 552 throw OrthancException(ErrorCode_NetworkProtocol);
427 }
428 }
429
430
431 static size_t CurlBodyCallback(void *buffer, size_t size, size_t nmemb, void *payload)
432 {
433 ChunkedBuffer& target = *(static_cast<ChunkedBuffer*>(payload));
434
435 size_t length = size * nmemb;
436 if (length == 0)
437 {
438 return 0;
439 }
440 else
441 {
442 target.AddChunk(buffer, length);
443 return length;
444 } 553 }
445 } 554 }
446 555
447 556
448 /*static int CurlDebugCallback(CURL *handle, 557 /*static int CurlDebugCallback(CURL *handle,
473 582
474 return 0; 583 return 0;
475 }*/ 584 }*/
476 585
477 586
478 struct CurlHeaderParameters
479 {
480 bool lowerCase_;
481 HttpClient::HttpHeaders* headers_;
482 };
483
484
485 static size_t CurlHeaderCallback(void *buffer, size_t size, size_t nmemb, void *payload)
486 {
487 CurlHeaderParameters& parameters = *(static_cast<CurlHeaderParameters*>(payload));
488 assert(parameters.headers_ != NULL);
489
490 size_t length = size * nmemb;
491 if (length == 0)
492 {
493 return 0;
494 }
495 else
496 {
497 std::string s(reinterpret_cast<const char*>(buffer), length);
498 std::size_t colon = s.find(':');
499 std::size_t eol = s.find("\r\n");
500 if (colon != std::string::npos &&
501 eol != std::string::npos)
502 {
503 std::string tmp(s.substr(0, colon));
504
505 if (parameters.lowerCase_)
506 {
507 Toolbox::ToLowerCase(tmp);
508 }
509
510 std::string key = Toolbox::StripSpaces(tmp);
511
512 if (!key.empty())
513 {
514 std::string value = Toolbox::StripSpaces(s.substr(colon + 1, eol));
515 (*parameters.headers_) [key] = value;
516 }
517 }
518
519 return length;
520 }
521 }
522
523
524 void HttpClient::Setup() 587 void HttpClient::Setup()
525 { 588 {
526 pimpl_->defaultPostHeaders_.AddHeader("Expect", ""); 589 pimpl_->defaultPostHeaders_.AddHeader("Expect", "");
527 pimpl_->defaultChunkedHeaders_.AddHeader("Expect", ""); 590 pimpl_->defaultChunkedHeaders_.AddHeader("Expect", "");
528 pimpl_->defaultChunkedHeaders_.AddHeader("Transfer-Encoding", "chunked"); 591 pimpl_->defaultChunkedHeaders_.AddHeader("Transfer-Encoding", "chunked");
529 592
530 pimpl_->curl_ = curl_easy_init(); 593 pimpl_->curl_ = curl_easy_init();
531 594
532 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEFUNCTION, &CurlBodyCallback)); 595 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERFUNCTION, &CurlAnswer::HeaderCallback));
596 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEFUNCTION, &CurlAnswer::BodyCallback));
533 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADER, 0)); 597 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADER, 0));
534 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 1)); 598 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 1));
535 599
536 // This fixes the "longjmp causes uninitialized stack frame" crash 600 // This fixes the "longjmp causes uninitialized stack frame" crash
537 // that happens on modern Linux versions. 601 // that happens on modern Linux versions.
602 666
603 667
604 void HttpClient::SetBody(const std::string& data) 668 void HttpClient::SetBody(const std::string& data)
605 { 669 {
606 body_ = data; 670 body_ = data;
607 pimpl_->chunkedBody_.Clear(); 671 pimpl_->requestBody_.Clear();
608 } 672 }
609 673
610 674
611 void HttpClient::SetBody(IRequestChunkedBody& body) 675 void HttpClient::SetBody(IRequestBody& body)
612 { 676 {
613 body_.clear(); 677 body_.clear();
614 pimpl_->chunkedBody_.SetBody(body); 678 pimpl_->requestBody_.SetBody(body);
615 } 679 }
616 680
617 681
618 void HttpClient::ClearBody() 682 void HttpClient::ClearBody()
619 { 683 {
620 body_.clear(); 684 body_.clear();
621 pimpl_->chunkedBody_.Clear(); 685 pimpl_->requestBody_.Clear();
622 } 686 }
623 687
624 688
625 void HttpClient::SetVerbose(bool isVerbose) 689 void HttpClient::SetVerbose(bool isVerbose)
626 { 690 {
656 { 720 {
657 pimpl_->userHeaders_.Clear(); 721 pimpl_->userHeaders_.Clear();
658 } 722 }
659 723
660 724
661 bool HttpClient::ApplyInternal(std::string& answerBody, 725 bool HttpClient::ApplyInternal(CurlAnswer& answer)
662 HttpHeaders* answerHeaders) 726 {
663 {
664 answerBody.clear();
665 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_URL, url_.c_str())); 727 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_URL, url_.c_str()));
666 728 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERDATA, &answer));
667 CurlHeaderParameters headerParameters;
668
669 if (answerHeaders == NULL)
670 {
671 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERFUNCTION, NULL));
672 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERDATA, NULL));
673 }
674 else
675 {
676 headerParameters.lowerCase_ = headersToLowerCase_;
677 headerParameters.headers_ = answerHeaders;
678 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERFUNCTION, &CurlHeaderCallback));
679 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERDATA, &headerParameters));
680 }
681 729
682 #if ORTHANC_ENABLE_SSL == 1 730 #if ORTHANC_ENABLE_SSL == 1
683 // Setup HTTPS-related options 731 // Setup HTTPS-related options
684 732
685 if (verifyPeers_) 733 if (verifyPeers_)
825 !pimpl_->userHeaders_.HasExpect()) 873 !pimpl_->userHeaders_.HasExpect())
826 { 874 {
827 LOG(INFO) << "For performance, the HTTP header \"Expect\" should be set to empty string in POST/PUT requests"; 875 LOG(INFO) << "For performance, the HTTP header \"Expect\" should be set to empty string in POST/PUT requests";
828 } 876 }
829 877
830 if (pimpl_->chunkedBody_.IsValid()) 878 if (pimpl_->requestBody_.IsValid())
831 { 879 {
832 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_READFUNCTION, CurlRequestChunkedBody::Callback)); 880 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_READFUNCTION, CurlRequestBody::Callback));
833 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_READDATA, &pimpl_->chunkedBody_)); 881 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_READDATA, &pimpl_->requestBody_));
834 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 1L)); 882 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 1L));
835 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, -1L)); 883 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, -1L));
836 884
837 if (pimpl_->userHeaders_.IsEmpty()) 885 if (pimpl_->userHeaders_.IsEmpty())
838 { 886 {
839 pimpl_->defaultChunkedHeaders_.Assign(pimpl_->curl_); 887 pimpl_->defaultChunkedHeaders_.Assign(pimpl_->curl_);
840 } 888 }
841 else if (!pimpl_->userHeaders_.IsChunkedTransfer()) 889 else if (!pimpl_->userHeaders_.IsChunkedTransfer())
842 { 890 {
843 LOG(WARNING) << "The HTTP header \"Transfer-Encoding\" must be set to \"chunked\" in streamed POST/PUT requests"; 891 LOG(WARNING) << "The HTTP header \"Transfer-Encoding\" must be set to \"chunked\" "
892 << "if streaming a chunked body in POST/PUT requests";
844 } 893 }
845 } 894 }
846 else 895 else
847 { 896 {
848 // Disable possible previous stream transfers 897 // Disable possible previous stream transfers
849 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_READFUNCTION, NULL)); 898 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_READFUNCTION, NULL));
850 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_UPLOAD, 0)); 899 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_UPLOAD, 0));
900
901 if (pimpl_->userHeaders_.IsChunkedTransfer())
902 {
903 LOG(WARNING) << "The HTTP header \"Transfer-Encoding\" must only be set "
904 << "if streaming a chunked body in POST/PUT requests";
905 }
851 906
852 if (pimpl_->userHeaders_.IsEmpty()) 907 if (pimpl_->userHeaders_.IsEmpty())
853 { 908 {
854 pimpl_->defaultPostHeaders_.Assign(pimpl_->curl_); 909 pimpl_->defaultPostHeaders_.Assign(pimpl_->curl_);
855 } 910 }
870 925
871 // Do the actual request 926 // Do the actual request
872 CURLcode code; 927 CURLcode code;
873 long status = 0; 928 long status = 0;
874 929
875 ChunkedBuffer buffer; 930 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEDATA, &answer));
876 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEDATA, &buffer));
877 931
878 if (boost::starts_with(url_, "https://")) 932 if (boost::starts_with(url_, "https://"))
879 { 933 {
880 code = OrthancHttpClientPerformSSL(pimpl_->curl_, &status); 934 code = OrthancHttpClientPerformSSL(pimpl_->curl_, &status);
881 } 935 }
902 else 956 else
903 { 957 {
904 lastStatus_ = static_cast<HttpStatus>(status); 958 lastStatus_ = static_cast<HttpStatus>(status);
905 } 959 }
906 960
907 bool success = (status >= 200 && status < 300); 961 if (status >= 200 && status < 300)
908 962 {
909 if (success) 963 return true; // Success
910 {
911 buffer.Flatten(answerBody);
912 } 964 }
913 else 965 else
914 { 966 {
915 answerBody.clear();
916 LOG(ERROR) << "Error in HTTP request, received HTTP status " << status 967 LOG(ERROR) << "Error in HTTP request, received HTTP status " << status
917 << " (" << EnumerationToString(lastStatus_) << ")"; 968 << " (" << EnumerationToString(lastStatus_) << ")";
918 } 969 return false;
919 970 }
920 return success; 971 }
972
973
974 bool HttpClient::ApplyInternal(std::string& answerBody,
975 HttpHeaders* answerHeaders)
976 {
977 answerBody.clear();
978
979 DefaultAnswer answer;
980
981 if (answerHeaders != NULL)
982 {
983 answer.SetHeaders(*answerHeaders);
984 }
985
986 CurlAnswer wrapper(answer, headersToLowerCase_);
987
988 if (ApplyInternal(wrapper))
989 {
990 answer.FlattenBody(answerBody);
991 return true;
992 }
993 else
994 {
995 return false;
996 }
921 } 997 }
922 998
923 999
924 bool HttpClient::ApplyInternal(Json::Value& answerBody, 1000 bool HttpClient::ApplyInternal(Json::Value& answerBody,
925 HttpClient::HttpHeaders* answerHeaders) 1001 HttpClient::HttpHeaders* answerHeaders)
1003 1079
1004 1080
1005 void HttpClient::SetDefaultTimeout(long timeout) 1081 void HttpClient::SetDefaultTimeout(long timeout)
1006 { 1082 {
1007 GlobalParameters::GetInstance().SetDefaultTimeout(timeout); 1083 GlobalParameters::GetInstance().SetDefaultTimeout(timeout);
1084 }
1085
1086
1087 bool HttpClient::Apply(IAnswer& answer)
1088 {
1089 CurlAnswer wrapper(answer, headersToLowerCase_);
1090 return ApplyInternal(wrapper);
1091 }
1092
1093
1094 void HttpClient::ApplyAndThrowException(IAnswer& answer)
1095 {
1096 CurlAnswer wrapper(answer, headersToLowerCase_);
1097
1098 if (!ApplyInternal(wrapper))
1099 {
1100 ThrowException(GetLastStatus());
1101 }
1008 } 1102 }
1009 1103
1010 1104
1011 void HttpClient::ApplyAndThrowException(std::string& answerBody) 1105 void HttpClient::ApplyAndThrowException(std::string& answerBody)
1012 { 1106 {