add progress information to request #13

This commit is contained in:
2019-07-07 14:49:54 +07:00
parent f9507e73a2
commit 8f1dad571f
3 changed files with 138 additions and 0 deletions

View File

@@ -62,9 +62,18 @@ namespace curly_hpp
virtual std::size_t write(const char* src, std::size_t size) = 0; virtual std::size_t write(const char* src, std::size_t size) = 0;
}; };
class progress_handler {
public:
virtual ~progress_handler() {}
virtual float update(
std::size_t dnow, std::size_t dtotal,
std::size_t unow, std::size_t utotal) = 0;
};
using callback_t = std::function<void(request)>; using callback_t = std::function<void(request)>;
using uploader_uptr = std::unique_ptr<upload_handler>; using uploader_uptr = std::unique_ptr<upload_handler>;
using downloader_uptr = std::unique_ptr<download_handler>; using downloader_uptr = std::unique_ptr<download_handler>;
using progressor_uptr = std::unique_ptr<progress_handler>;
} }
namespace curly_hpp namespace curly_hpp
@@ -169,6 +178,7 @@ namespace curly_hpp
headers_t headers; headers_t headers;
uploader_uptr uploader; uploader_uptr uploader;
downloader_uptr downloader; downloader_uptr downloader;
progressor_uptr progressor;
private: private:
http_code_t http_code_{0u}; http_code_t http_code_{0u};
}; };
@@ -193,6 +203,7 @@ namespace curly_hpp
request(internal_state_ptr); request(internal_state_ptr);
bool cancel() noexcept; bool cancel() noexcept;
float progress() const noexcept;
req_status status() const noexcept; req_status status() const noexcept;
bool is_done() const noexcept; bool is_done() const noexcept;
@@ -246,6 +257,7 @@ namespace curly_hpp
request_builder& callback(callback_t c) noexcept; request_builder& callback(callback_t c) noexcept;
request_builder& uploader(uploader_uptr u) noexcept; request_builder& uploader(uploader_uptr u) noexcept;
request_builder& downloader(downloader_uptr d) noexcept; request_builder& downloader(downloader_uptr d) noexcept;
request_builder& progressor(progressor_uptr p) noexcept;
const std::string& url() const noexcept; const std::string& url() const noexcept;
http_method method() const noexcept; http_method method() const noexcept;
@@ -270,6 +282,9 @@ namespace curly_hpp
downloader_uptr& downloader() noexcept; downloader_uptr& downloader() noexcept;
const downloader_uptr& downloader() const noexcept; const downloader_uptr& downloader() const noexcept;
progressor_uptr& progressor() noexcept;
const progressor_uptr& progressor() const noexcept;
request send(); request send();
template < typename Callback > template < typename Callback >
@@ -289,6 +304,12 @@ namespace curly_hpp
static_assert(std::is_base_of_v<download_handler, Downloader>); static_assert(std::is_base_of_v<download_handler, Downloader>);
return downloader(std::make_unique<Downloader>(std::forward<Args>(args)...)); return downloader(std::make_unique<Downloader>(std::forward<Args>(args)...));
} }
template < typename Progressor, typename... Args >
request_builder& progressor(Args&&... args) {
static_assert(std::is_base_of_v<progress_handler, Progressor>);
return progressor(std::make_unique<Progressor>(std::forward<Args>(args)...));
}
private: private:
std::string url_; std::string url_;
http_method method_{http_method::GET}; http_method method_{http_method::GET};
@@ -304,6 +325,7 @@ namespace curly_hpp
callback_t callback_; callback_t callback_;
uploader_uptr uploader_; uploader_uptr uploader_;
downloader_uptr downloader_; downloader_uptr downloader_;
progressor_uptr progressor_;
}; };
} }

View File

@@ -77,6 +77,23 @@ namespace
private: private:
data_t& data_; data_t& data_;
}; };
class default_progressor final : public progress_handler {
public:
default_progressor() = default;
float update(
std::size_t dnow, std::size_t dtotal,
std::size_t unow, std::size_t utotal) override
{
double now_d = static_cast<double>(dnow + unow);
double total_d = static_cast<double>(dtotal + utotal);
double progress_d = total_d > 0.0
? static_cast<float>(now_d / total_d)
: 0.f;
return static_cast<float>(std::min(std::max(progress_d, 0.0), 1.0));
}
};
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -253,6 +270,10 @@ namespace curly_hpp
if ( !breq_.downloader() ) { if ( !breq_.downloader() ) {
breq_.downloader<default_downloader>(&response_content_); breq_.downloader<default_downloader>(&response_content_);
} }
if ( !breq_.progressor() ) {
breq_.progressor<default_progressor>();
}
} }
void enqueue(CURLM* curlm) { void enqueue(CURLM* curlm) {
@@ -287,6 +308,10 @@ namespace curly_hpp
curl_easy_setopt(curlh_.get(), CURLOPT_WRITEDATA, this); curl_easy_setopt(curlh_.get(), CURLOPT_WRITEDATA, this);
curl_easy_setopt(curlh_.get(), CURLOPT_WRITEFUNCTION, &s_download_callback_); curl_easy_setopt(curlh_.get(), CURLOPT_WRITEFUNCTION, &s_download_callback_);
curl_easy_setopt(curlh_.get(), CURLOPT_NOPROGRESS, 0l);
curl_easy_setopt(curlh_.get(), CURLOPT_XFERINFODATA, this);
curl_easy_setopt(curlh_.get(), CURLOPT_XFERINFOFUNCTION, &s_progress_callback_);
curl_easy_setopt(curlh_.get(), CURLOPT_HEADERDATA, this); curl_easy_setopt(curlh_.get(), CURLOPT_HEADERDATA, this);
curl_easy_setopt(curlh_.get(), CURLOPT_HEADERFUNCTION, &s_header_callback_); curl_easy_setopt(curlh_.get(), CURLOPT_HEADERFUNCTION, &s_header_callback_);
@@ -364,6 +389,21 @@ namespace curly_hpp
void dequeue(CURLM* curlm) noexcept { void dequeue(CURLM* curlm) noexcept {
std::lock_guard<std::mutex> guard(mutex_); std::lock_guard<std::mutex> guard(mutex_);
if ( curlh_ ) { if ( curlh_ ) {
curl_easy_setopt(curlh_.get(), CURLOPT_PRIVATE, nullptr);
curl_easy_setopt(curlh_.get(), CURLOPT_READDATA, nullptr);
curl_easy_setopt(curlh_.get(), CURLOPT_READFUNCTION, nullptr);
curl_easy_setopt(curlh_.get(), CURLOPT_WRITEDATA, nullptr);
curl_easy_setopt(curlh_.get(), CURLOPT_WRITEFUNCTION, nullptr);
curl_easy_setopt(curlh_.get(), CURLOPT_NOPROGRESS, 1l);
curl_easy_setopt(curlh_.get(), CURLOPT_XFERINFODATA, nullptr);
curl_easy_setopt(curlh_.get(), CURLOPT_XFERINFOFUNCTION, nullptr);
curl_easy_setopt(curlh_.get(), CURLOPT_HEADERDATA, nullptr);
curl_easy_setopt(curlh_.get(), CURLOPT_HEADERFUNCTION, nullptr);
curl_multi_remove_handle(curlm, curlh_.get()); curl_multi_remove_handle(curlm, curlh_.get());
curlh_.reset(); curlh_.reset();
} }
@@ -392,12 +432,14 @@ namespace curly_hpp
response_.headers = std::move(response_headers_); response_.headers = std::move(response_headers_);
response_.uploader = std::move(breq_.uploader()); response_.uploader = std::move(breq_.uploader());
response_.downloader = std::move(breq_.downloader()); response_.downloader = std::move(breq_.downloader());
response_.progressor = std::move(breq_.progressor());
} catch (...) { } catch (...) {
status_ = req_status::failed; status_ = req_status::failed;
cvar_.notify_all(); cvar_.notify_all();
return false; return false;
} }
progress_ = 1.f;
status_ = req_status::done; status_ = req_status::done;
error_.clear(); error_.clear();
@@ -448,6 +490,11 @@ namespace curly_hpp
return true; return true;
} }
float progress() const noexcept {
std::lock_guard<std::mutex> guard(mutex_);
return progress_;
}
req_status status() const noexcept { req_status status() const noexcept {
std::lock_guard<std::mutex> guard(mutex_); std::lock_guard<std::mutex> guard(mutex_);
return status_; return status_;
@@ -553,6 +600,13 @@ namespace curly_hpp
return self->download_callback_(ptr, size * nmemb); return self->download_callback_(ptr, size * nmemb);
} }
static int s_progress_callback_(
void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) noexcept
{
auto* self = static_cast<internal_state*>(clientp);
return self->progress_callback(dlnow, dltotal, ulnow, ultotal);
}
static std::size_t s_header_callback_( static std::size_t s_header_callback_(
char* buffer, std::size_t size, std::size_t nitems, void* userdata) noexcept char* buffer, std::size_t size, std::size_t nitems, void* userdata) noexcept
{ {
@@ -589,6 +643,26 @@ namespace curly_hpp
} }
} }
int progress_callback(
curl_off_t dlnow, curl_off_t dltotal,
curl_off_t ulnow, curl_off_t ultotal) noexcept
{
try {
std::lock_guard<std::mutex> guard(mutex_);
std::size_t dnow_sz = dlnow > 0 ? static_cast<std::size_t>(dlnow) : 0u;
std::size_t dtotal_sz = dltotal > 0 ? static_cast<std::size_t>(dltotal) : 0u;
std::size_t unow_sz = ulnow > 0 ? static_cast<std::size_t>(ulnow) : 0u;
std::size_t utotal_sz = ultotal > 0 ? static_cast<std::size_t>(ultotal) : 0u;
progress_ = breq_.progressor()->update(dnow_sz, dtotal_sz, unow_sz, utotal_sz);
return 0;
} catch (...) {
return 1;
}
}
std::size_t header_callback_(const char* src, std::size_t size) noexcept { std::size_t header_callback_(const char* src, std::size_t size) noexcept {
try { try {
std::lock_guard<std::mutex> guard(mutex_); std::lock_guard<std::mutex> guard(mutex_);
@@ -631,6 +705,7 @@ namespace curly_hpp
bool callbacked_{false}; bool callbacked_{false};
std::exception_ptr callback_exception_{nullptr}; std::exception_ptr callback_exception_{nullptr};
private: private:
float progress_{0.f};
req_status status_{req_status::pending}; req_status status_{req_status::pending};
std::string error_{"Unknown error"}; std::string error_{"Unknown error"};
private: private:
@@ -650,6 +725,10 @@ namespace curly_hpp
return state_->cancel(); return state_->cancel();
} }
float request::progress() const noexcept {
return state_->progress();
}
req_status request::status() const noexcept { req_status request::status() const noexcept {
return state_->status(); return state_->status();
} }
@@ -787,6 +866,11 @@ namespace curly_hpp
return *this; return *this;
} }
request_builder& request_builder::progressor(progressor_uptr p) noexcept {
progressor_ = std::move(p);
return *this;
}
const std::string& request_builder::url() const noexcept { const std::string& request_builder::url() const noexcept {
return url_; return url_;
} }
@@ -855,6 +939,14 @@ namespace curly_hpp
return downloader_; return downloader_;
} }
progressor_uptr& request_builder::progressor() noexcept {
return progressor_;
}
const progressor_uptr& request_builder::progressor() const noexcept {
return progressor_;
}
request request_builder::send() { request request_builder::send() {
auto sreq = std::make_shared<request::internal_state>(std::move(*this)); auto sreq = std::make_shared<request::internal_state>(std::move(*this));
new_handles.enqueue(sreq); new_handles.enqueue(sreq);

View File

@@ -58,6 +58,22 @@ namespace
} }
}; };
class canceled_progressor : public net::progress_handler {
public:
canceled_progressor() = default;
float update(
std::size_t dnow, std::size_t dtotal,
std::size_t unow, std::size_t utotal) override
{
(void)dnow;
(void)dtotal;
(void)unow;
(void)utotal;
throw std::exception();
}
};
netex::promise<net::content_t> download(std::string url) { netex::promise<net::content_t> download(std::string url) {
return netex::make_promise<net::content_t>([ return netex::make_promise<net::content_t>([
url = std::move(url) url = std::move(url)
@@ -673,6 +689,14 @@ TEST_CASE("curly") {
.send(); .send();
REQUIRE(req.wait() == net::req_status::canceled); REQUIRE(req.wait() == net::req_status::canceled);
} }
{
auto req = net::request_builder("https://httpbin.org/anything")
.verbose(true)
.method(net::http_method::GET)
.progressor<canceled_progressor>()
.send();
REQUIRE(req.wait() == net::req_status::canceled);
}
} }
SECTION("callback") { SECTION("callback") {