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;
};
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 uploader_uptr = std::unique_ptr<upload_handler>;
using downloader_uptr = std::unique_ptr<download_handler>;
using progressor_uptr = std::unique_ptr<progress_handler>;
}
namespace curly_hpp
@@ -169,6 +178,7 @@ namespace curly_hpp
headers_t headers;
uploader_uptr uploader;
downloader_uptr downloader;
progressor_uptr progressor;
private:
http_code_t http_code_{0u};
};
@@ -193,6 +203,7 @@ namespace curly_hpp
request(internal_state_ptr);
bool cancel() noexcept;
float progress() const noexcept;
req_status status() const noexcept;
bool is_done() const noexcept;
@@ -246,6 +257,7 @@ namespace curly_hpp
request_builder& callback(callback_t c) noexcept;
request_builder& uploader(uploader_uptr u) noexcept;
request_builder& downloader(downloader_uptr d) noexcept;
request_builder& progressor(progressor_uptr p) noexcept;
const std::string& url() const noexcept;
http_method method() const noexcept;
@@ -270,6 +282,9 @@ namespace curly_hpp
downloader_uptr& downloader() noexcept;
const downloader_uptr& downloader() const noexcept;
progressor_uptr& progressor() noexcept;
const progressor_uptr& progressor() const noexcept;
request send();
template < typename Callback >
@@ -289,6 +304,12 @@ namespace curly_hpp
static_assert(std::is_base_of_v<download_handler, Downloader>);
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:
std::string url_;
http_method method_{http_method::GET};
@@ -304,6 +325,7 @@ namespace curly_hpp
callback_t callback_;
uploader_uptr uploader_;
downloader_uptr downloader_;
progressor_uptr progressor_;
};
}

View File

@@ -77,6 +77,23 @@ namespace
private:
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() ) {
breq_.downloader<default_downloader>(&response_content_);
}
if ( !breq_.progressor() ) {
breq_.progressor<default_progressor>();
}
}
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_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_HEADERFUNCTION, &s_header_callback_);
@@ -364,6 +389,21 @@ namespace curly_hpp
void dequeue(CURLM* curlm) noexcept {
std::lock_guard<std::mutex> guard(mutex_);
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());
curlh_.reset();
}
@@ -392,12 +432,14 @@ namespace curly_hpp
response_.headers = std::move(response_headers_);
response_.uploader = std::move(breq_.uploader());
response_.downloader = std::move(breq_.downloader());
response_.progressor = std::move(breq_.progressor());
} catch (...) {
status_ = req_status::failed;
cvar_.notify_all();
return false;
}
progress_ = 1.f;
status_ = req_status::done;
error_.clear();
@@ -448,6 +490,11 @@ namespace curly_hpp
return true;
}
float progress() const noexcept {
std::lock_guard<std::mutex> guard(mutex_);
return progress_;
}
req_status status() const noexcept {
std::lock_guard<std::mutex> guard(mutex_);
return status_;
@@ -553,6 +600,13 @@ namespace curly_hpp
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_(
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 {
try {
std::lock_guard<std::mutex> guard(mutex_);
@@ -631,6 +705,7 @@ namespace curly_hpp
bool callbacked_{false};
std::exception_ptr callback_exception_{nullptr};
private:
float progress_{0.f};
req_status status_{req_status::pending};
std::string error_{"Unknown error"};
private:
@@ -650,6 +725,10 @@ namespace curly_hpp
return state_->cancel();
}
float request::progress() const noexcept {
return state_->progress();
}
req_status request::status() const noexcept {
return state_->status();
}
@@ -787,6 +866,11 @@ namespace curly_hpp
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 {
return url_;
}
@@ -855,6 +939,14 @@ namespace curly_hpp
return downloader_;
}
progressor_uptr& request_builder::progressor() noexcept {
return progressor_;
}
const progressor_uptr& request_builder::progressor() const noexcept {
return progressor_;
}
request request_builder::send() {
auto sreq = std::make_shared<request::internal_state>(std::move(*this));
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) {
return netex::make_promise<net::content_t>([
url = std::move(url)
@@ -673,6 +689,14 @@ TEST_CASE("curly") {
.send();
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") {