457 using ptr_t = std::unique_ptr<Request>;
460 : eh_{std::make_unique<EasyHandle>()}
464 Request(EasyHandle::ptr_t&& eh)
471 curl_slist_free_all(headers_);
478 completion_ = std::move(completion);
481 void OpenSourceFile(
const std::string& path) {
483 auto fp = fopen(path.c_str(),
"rb");
485 const auto e = errno;
489 fp_= std::shared_ptr<FILE>(fp, std::fclose);
492 FILE *GetSourceFp() {
499 const auto result = curl_easy_perform(*eh_);
500 CallCompletion(result);
503 void Complete(CURLcode cc,
const CURLMSG& ) {
507 EasyHandle& GetEasyHandle()
noexcept { assert(eh_);
return *eh_; }
508 RequestType GetRequestType()
noexcept {
return request_type_; }
510 void SetDefaultInHandler(std::unique_ptr<DataHandlerBase> ptr) {
511 default_in_handler_ = std::move(ptr);
514 void SetDefaultOutHandler(std::unique_ptr<DataHandlerBase> ptr) {
515 default_out_handler_ = std::move(ptr);
518 using headers_t = curl_slist *;
519 headers_t& GetHeaders() {
523 std::string& getDefaultInBuffer() {
524 return default_data_buffer_;
529 mime_ = curl_mime_init(*eh_);
533 void AddFileAsMimeData(
const std::string& path,
534 const std::string& name,
535 const std::string& remoteName,
536 const std::string& mimeType) {
540 auto * part = curl_mime_addpart(mime_);
541 curl_mime_filedata(part, path.c_str());
542 curl_mime_name(part, name.empty() ?
"file" :name.c_str());
544 if (!remoteName.empty()) {
545 curl_mime_filename(part, remoteName.c_str());
548 if (!mimeType.empty()) {
549 curl_mime_type(part, mimeType.c_str());
554 void CallCompletion(CURLcode cc) {
557 curl_easy_getinfo (*eh_, CURLINFO_RESPONSE_CODE,
561 if (!default_data_buffer_.empty()) {
562 result.
body = std::move(default_data_buffer_);
568 void SetRequestType() {
569 switch(request_type_) {
570 case RequestType::GET:
571 curl_easy_setopt(*eh_, CURLOPT_HTTPGET, 1L);
573 case RequestType::PUT:
574 headers_ = curl_slist_append(headers_,
"Transfer-Encoding: chunked");
575 curl_easy_setopt(*eh_, CURLOPT_UPLOAD, 1L);
577 case RequestType::POST:
578 headers_ = curl_slist_append(headers_,
"Transfer-Encoding: chunked");
579 curl_easy_setopt(*eh_, CURLOPT_UPLOAD, 0L);
580 curl_easy_setopt(*eh_, CURLOPT_POST, 1L);
582 case RequestType::HEAD:
583 curl_easy_setopt(*eh_, CURLOPT_NOBODY, 1L);
585 case RequestType::OPTIONS:
586 curl_easy_setopt(*eh_, CURLOPT_CUSTOMREQUEST,
"OPTIONS");
588 case RequestType::PATCH:
589 headers_ = curl_slist_append(headers_,
"Transfer-Encoding: chunked");
590 curl_easy_setopt(*eh_, CURLOPT_CUSTOMREQUEST,
"PATCH");
592 case RequestType::DELETE:
593 curl_easy_setopt(*eh_, CURLOPT_CUSTOMREQUEST,
"DELETE");
595 case RequestType::POST_MIME:
597 curl_easy_setopt(*eh_, CURLOPT_MIMEPOST, mime_);
600 throw Exception(
"Unsupported request type" + std::to_string(
static_cast<int>(request_type_)));
604 EasyHandle::ptr_t eh_;
605 RequestType request_type_ = RequestType::INVALID;
607 std::unique_ptr<DataHandlerBase> default_out_handler_;
608 std::unique_ptr<DataHandlerBase> default_in_handler_;
609 headers_t headers_ =
nullptr;
610 std::string default_data_buffer_;
611 std::shared_ptr<FILE> fp_;
612 curl_mime *mime_ = {};
747 WorkerThread(std::function<
void ()> && fn)
748 : thread_{std::move(fn)} {}
751 if (thread_.get_id() == std::this_thread::get_id()) {
758 std::call_once(joined_, [
this] {
759 const_cast<WorkerThread *
>(
this)->thread_.join();
763 bool Joinable()
const {
764 return thread_.joinable();
767 operator std::thread& () {
return thread_; }
771 mutable std::once_flag joined_;
778 if (thread_ && thread_->Joinable()) {
784 void PrepareThread() {
786 assert(!mutex_.try_lock());
787 if (abort_ || done_) {
791 thread_ = std::make_shared<WorkerThread>([&] {
797 }
catch (
const std::exception& ex) {
810 static std::unique_ptr<Worker> Create() {
811 return std::make_unique<Worker>();
814 void Enqueue(Request::ptr_t req) {
818 queue_.push_back(std::move(req));
823 decltype(thread_) thd;
827 if (thread_ && thread_->Joinable()) {
838 void CloseWhenFinished() {
841 close_pending_ =
true;
856 bool IsDone()
const {
861 bool HaveThread() const noexcept {
869 size_t GetNumActiveRequests()
const {
871 return ongoing_.size();
880 decltype(queue_) tmp;
885 tmp = std::move(queue_);
886 pending_entries_in_queue_ =
false;
890 auto it = queue_.begin();
894 assert(it != queue_.end());
895 tmp.push_back(std::move(*it));
898 queue_.erase(queue_.begin(), it);
904 pending_entries_in_queue_ =
true;
908 for(
auto& req: tmp) {
910 const auto& eh = req->GetEasyHandle();
912 ongoing_[eh] = std::move(req);
913 const auto mc = curl_multi_add_handle(handle_, eh);
914 if (mc != CURLM_OK) {
915 throw CurlException(
"curl_multi_add_handle", mc);
921 if ((handle_ = curl_multi_init()) ==
nullptr) {
922 throw std::runtime_error(
"curl_multi_init() failed");
931 curl_multi_cleanup(handle_);
936 bool EvaluateState(
const bool transfersRunning,
const bool doDequeue)
const noexcept {
940 <<
", do_dequeue=" << doDequeue
941 <<
", close_pending_=" << close_pending_);
943 return !abort_ && (transfersRunning || !close_pending_);
946 auto GetNextTimeout() const noexcept {
947 return std::chrono::steady_clock::now()
952 int transfers_running = -1;
956 bool do_dequeue =
true;
957 auto timeout = GetNextTimeout();
959 while (EvaluateState(transfers_running, do_dequeue)) {
967 const bool initial_ideling = transfers_running == -1;
968 curl_multi_perform(handle_, &transfers_running);
969 if ((transfers_running == 0) && initial_ideling) {
970 transfers_running = -1;
974 if (transfers_running <= 0) {
975 if (timeout < std::chrono::steady_clock::now()) {
976 RESTINCURL_LOG(
"Idle timeout. Will shut down the worker-thread.");
980 timeout = GetNextTimeout();
984 while (
auto m = curl_multi_info_read(handle_, &numLeft)) {
986 auto it = ongoing_.find(m->easy_handle);
987 if (it != ongoing_.end()) {
989 << (EasyHandle::handle_t)it->second->GetEasyHandle()
990 <<
"; with result: " << m->data.result <<
" expl: '" << curl_easy_strerror(m->data.result)
991 <<
"'; with msg: " << m->msg);
994 it->second->Complete(m->data.result, m->msg);
995 }
catch(
const std::exception& ex) {
998 if (m->msg == CURLMSG_DONE) {
999 curl_multi_remove_handle(handle_, m->easy_handle);
1001 it->second->GetEasyHandle().Close();
1010 lock_t lock(mutex_);
1012 if (abort_ || (!transfers_running && close_pending_)) {
1017 auto next_timeout = std::chrono::duration_cast<std::chrono::milliseconds>(
1018 timeout - std::chrono::steady_clock::now());
1019 long sleep_duration = std::max<long>(1, next_timeout.count());
1028 if (transfers_running > 0) {
1029 curl_multi_timeout(handle_, &sleep_duration);
1030 if (sleep_duration < 0) {
1031 sleep_duration = 1000;
1035 const auto mc = curl_multi_fdset(handle_, &fdread, &fdwrite, &fdexcep, &maxfd);
1037 if (mc != CURLM_OK) {
1038 throw CurlException(
"curl_multi_fdset", mc);
1043 sleep_duration = 50;
1047 struct timeval tv = {};
1048 tv.tv_sec = sleep_duration / 1000;
1049 tv.tv_usec = (sleep_duration % 1000) * 1000;
1051 const auto signalfd = signal_.GetReadFd();
1055 <<
" ms. Next timeout in " << next_timeout.count() <<
" ms. "
1056 << transfers_running <<
" active transfers.");
1058 FD_SET(signalfd, &fdread);
1059 maxfd = std::max(signalfd, maxfd) + 1;
1061 const auto rval = select(maxfd, &fdread, &fdwrite, &fdexcep, &tv);
1065 if (FD_ISSET(signalfd, &fdread)) {
1067 do_dequeue = signal_.WasSignalled();
1071 if (pending_entries_in_queue_) {
1077 lock_t lock(mutex_);
1078 if (close_pending_ || abort_) {
1083 bool close_pending_ {
false};
1084 bool abort_ {
false};
1086 bool pending_entries_in_queue_ =
false;
1087 decltype(curl_multi_init()) handle_ = {};
1088 mutable std::mutex mutex_;
1089 std::shared_ptr<WorkerThread> thread_;
1090 std::deque<Request::ptr_t> queue_;
1091 std::map<EasyHandle::handle_t, Request::ptr_t> ongoing_;
1103 class RequestBuilder {
1105 static size_t write_callback(
char *ptr,
size_t size,
size_t nitems,
void *userdata) {
1106 const auto bytes = size * nitems;
1110 static int debug_callback(CURL *handle, curl_infotype type,
1111 char *data,
size_t size,
1119 case CURLINFO_HEADER_OUT:
1120 msg =
"=> Send header: ";
1122 case CURLINFO_DATA_OUT:
1123 msg =
"=> Send data: ";
1125 case CURLINFO_SSL_DATA_OUT:
1126 msg =
"=> Send SSL data: ";
1128 case CURLINFO_HEADER_IN:
1129 msg =
"<= Recv header: ";
1131 case CURLINFO_DATA_IN:
1132 msg =
"<= Recv data: ";
1134 case CURLINFO_SSL_DATA_IN:
1135 msg =
"<= Recv SSL data: ";
1142 std::copy(data, data + size, std::back_inserter(msg));
1148 using ptr_t = std::unique_ptr<RequestBuilder>;
1154 : request_{std::make_unique<Request>()}
1155 , options_{std::make_unique<class Options>(request_->GetEasyHandle())}
1156#if RESTINCURL_ENABLE_ASYNC
1165 RequestBuilder& Prepare(RequestType rt,
const std::string& url) {
1166 assert(request_type_ == RequestType::INVALID);
1174 bool CanSendFile()
const noexcept {
1175 return request_type_ == RequestType::POST
1176 || request_type_ == RequestType::PUT;
1193 RequestBuilder&
Get(
const std::string& url) {
1194 return Prepare(RequestType::GET, url);
1198 RequestBuilder&
Head(
const std::string& url) {
1199 return Prepare(RequestType::HEAD, url);
1203 RequestBuilder&
Post(
const std::string& url) {
1204 return Prepare(RequestType::POST, url);
1209 return Prepare(RequestType::POST_MIME, url);
1213 RequestBuilder&
Put(
const std::string& url) {
1214 return Prepare(RequestType::PUT, url);
1218 RequestBuilder&
Patch(
const std::string& url) {
1219 return Prepare(RequestType::PATCH, url);
1223 RequestBuilder&
Delete(
const std::string& url) {
1224 return Prepare(RequestType::DELETE, url);
1229 return Prepare(RequestType::OPTIONS, url);
1240 request_->GetHeaders() = curl_slist_append(request_->GetHeaders(), value);
1251 RequestBuilder&
Header(
const std::string& name,
1252 const std::string& value) {
1253 const auto v = name +
": " + value;
1254 return Header(v.c_str());
1259 return Header(
"Content-type: Application/json; charset=utf-8");
1273 return Header(
"Accept: Application/json");
1287 template <
typename T>
1288 RequestBuilder&
Option(
const CURLoption& opt,
const T& value) {
1290 options_->Set(opt, value);
1301 RequestBuilder&
Trace(
bool enable =
true) {
1303 Option(CURLOPT_DEBUGFUNCTION, debug_callback);
1304 Option(CURLOPT_VERBOSE, 1L);
1306 Option(CURLOPT_VERBOSE, 0L);
1317 request_timeout_ = timeout;
1326 connect_timeout_ = timeout;
1339 assert(CanSendFile());
1340 if (!CanSendFile()) {
1341 throw Exception{
"Invalid curl operation for a file upload"};
1345 request_->OpenSourceFile(path);
1346 struct stat st = {};
1347 if(fstat(fileno(request_->GetSourceFp()), &st) != 0) {
1348 const auto e = errno;
1353 options_->Set(CURLOPT_READDATA, request_->GetSourceFp());
1354 options_->Set(CURLOPT_INFILESIZE_LARGE,
static_cast<curl_off_t
>(st.st_size));
1355 have_data_out_ =
true;
1371 const std::string& name = {},
1372 const std::string& remoteName = {},
1373 const std::string& mimeType = {}) {
1375 assert(request_type_ == RequestType::POST_MIME);
1376 if (request_type_ != RequestType::POST_MIME) {
1377 throw Exception{
"Must use PostMime operation to add mime attachments"};
1379 request_->AddFileAsMimeData(path, name, remoteName, mimeType);
1392 assert(CanSendFile());
1393 if (!CanSendFile()) {
1394 throw Exception{
"Invalid curl operation for a file upload"};
1398 request_->OpenSourceFile(path);
1399 struct stat st = {};
1400 if(fstat(fileno(request_->GetSourceFp()), &st) != 0) {
1401 const auto e = errno;
1406 options_->Set(CURLOPT_READDATA, request_->GetSourceFp());
1407 options_->Set(CURLOPT_INFILESIZE_LARGE,
static_cast<curl_off_t
>(st.st_size));
1408 have_data_out_ =
true;
1422 template <
typename T>
1425 options_->Set(CURLOPT_READFUNCTION, dh.read_callback);
1426 options_->Set(CURLOPT_READDATA, &dh);
1427 have_data_out_ =
true;
1438 template <
typename T>
1441 auto handler = std::make_unique<OutDataHandler<T>>(std::move(data));
1442 auto& handler_ref = *handler;
1443 request_->SetDefaultOutHandler(std::move(handler));
1457 template <
typename T>
1460 options_->Set(CURLOPT_WRITEFUNCTION, dh.write_callback);
1461 options_->Set(CURLOPT_WRITEDATA, &dh);
1462 have_data_in_ =
true;
1474 template <
typename T>
1477 auto handler = std::make_unique<InDataHandler<T>>(data);
1478 auto& handler_ref = *handler;
1479 request_->SetDefaultInHandler(std::move(handler));
1490 options_->Set(CURLOPT_WRITEFUNCTION, write_callback);
1491 have_data_in_ =
true;
1507 completion_ = std::move(fn);
1524 const std::string& passwd) {
1527 if (!name.empty() && !passwd.empty()) {
1528 options_->Set(CURLOPT_USERNAME, name.c_str());
1529 options_->Set(CURLOPT_PASSWORD, passwd.c_str());
1541 RequestBuilder&
SetReadHandler(
size_t (*handler)(
char *,
size_t ,
size_t ,
void *),
void *userdata) {
1542 options_->Set(CURLOPT_READFUNCTION, handler);
1543 options_->Set(CURLOPT_READDATA, userdata);
1544 have_data_out_ =
true;
1554 RequestBuilder&
SetWriteHandler(
size_t (*handler)(
char *,
size_t ,
size_t ,
void *),
void *userdata) {
1555 options_->Set(CURLOPT_WRITEFUNCTION,handler);
1556 options_->Set(CURLOPT_WRITEDATA, userdata);
1557 have_data_in_ =
true;
1564 if (!have_data_in_) {
1566 this->
StoreData(request_->getDefaultInBuffer());
1569 if (have_data_out_) {
1570 options_->Set(CURLOPT_UPLOAD, 1L);
1573 if (request_timeout_ >= 0) {
1574 options_->Set(CURLOPT_TIMEOUT_MS, request_timeout_);
1577 if (connect_timeout_ >= 0) {
1578 options_->Set(CURLOPT_CONNECTTIMEOUT_MS, connect_timeout_);
1582 if (request_->GetHeaders()) {
1583 options_->Set(CURLOPT_HTTPHEADER, request_->GetHeaders());
1587 options_->Set(CURLOPT_URL, url_);
1591 request_->Prepare(request_type_, std::move(completion_));
1607 request_->Execute();
1610#if RESTINCURL_ENABLE_ASYNC
1625 worker_.Enqueue(std::move(request_));
1630 std::unique_ptr<Request> request_;
1631 std::unique_ptr<class Options> options_;
1633 RequestType request_type_ = RequestType::INVALID;
1634 bool have_data_in_ =
false;
1635 bool have_data_out_ =
false;
1636 bool is_built_ =
false;
1638 long request_timeout_ = 10000L;
1639 long connect_timeout_ = 3000L;
1640#if RESTINCURL_ENABLE_ASYNC