Mercurial > hg > orthanc
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 { |