first callback impl with tests

This commit is contained in:
2019-07-01 01:53:18 +07:00
parent ee00b426b2
commit e0f5be0fb3
4 changed files with 191 additions and 17 deletions

View File

@@ -30,6 +30,7 @@
- Custom headers
- Asynchronous requests
- Different types of timeouts
- Custom completion callbacks
- PUT, GET, HEAD, POST methods
- Custom uploading and downloading streams
@@ -134,7 +135,9 @@ auto request = net::request_builder()
.url("http://unavailable.site.com")
.send();
if ( request.wait() == net::request::statuses::done ) {
request.wait();
if ( request.is_done() ) {
auto response = request.get();
std::cout << "Status code: " << response.http_code() << std::endl;
} else {
@@ -147,6 +150,23 @@ if ( request.wait() == net::request::statuses::done ) {
// Error message: Couldn't resolve host name
```
### Request Callback
```cpp
auto request = net::request_builder("http://www.httpbin.org/get")
.callback([](net::request request){
if ( request.is_done() ) {
auto response = request.get();
std::cout << "Status code: " << response.http_code() << std::endl;
} else {
std::cout << "Error message: " << request.get_error() << std::endl;
}
}).send();
request.wait_callback();
// Status code: 200
```
### Streamed Requests
#### Downloading

View File

@@ -108,6 +108,8 @@ namespace curly_hpp
response& operator=(const response&) = delete;
explicit response(http_code_t c) noexcept;
bool is_http_error() const noexcept;
http_code_t http_code() const noexcept;
public:
content_t content;
@@ -147,8 +149,13 @@ namespace curly_hpp
statuses wait_for(time_ms_t ms) const noexcept;
statuses wait_until(time_point_t tp) const noexcept;
statuses wait_callback() const noexcept;
statuses wait_callback_for(time_ms_t ms) const noexcept;
statuses wait_callback_until(time_point_t tp) const noexcept;
response get();
const std::string& get_error() const noexcept;
std::exception_ptr get_callback_exception() const noexcept;
private:
internal_state_ptr state_;
};

View File

@@ -300,6 +300,10 @@ namespace curly_hpp
response::response(http_code_t c) noexcept
: http_code_(c) {}
bool response::is_http_error() const noexcept {
return http_code_ >= 400u;
}
http_code_t response::http_code() const noexcept {
return http_code_;
}
@@ -431,11 +435,11 @@ namespace curly_hpp
return false;
}
long code = 0;
long http_code = 0;
if ( CURLE_OK != curl_easy_getinfo(
curlh_.get(),
CURLINFO_RESPONSE_CODE,
&code) || !code )
&http_code) || !http_code )
{
status_ = statuses::failed;
cvar_.notify_all();
@@ -443,7 +447,7 @@ namespace curly_hpp
}
try {
response_ = response(static_cast<http_code_t>(code));
response_ = response(static_cast<http_code_t>(http_code));
response_.content = std::move(response_content_);
response_.headers = std::move(response_headers_);
response_.uploader = std::move(breq_.uploader());
@@ -519,26 +523,29 @@ namespace curly_hpp
return status_ == statuses::pending;
}
statuses wait() const noexcept {
statuses wait(bool wait_callback) const noexcept {
std::unique_lock<std::mutex> lock(mutex_);
cvar_.wait(lock, [this](){
return status_ != statuses::pending;
cvar_.wait(lock, [this, wait_callback](){
return (status_ != statuses::pending)
&& (!wait_callback || callbacked_);
});
return status_;
}
statuses wait_for(time_ms_t ms) const noexcept {
statuses wait_for(time_ms_t ms, bool wait_callback) const noexcept {
std::unique_lock<std::mutex> lock(mutex_);
cvar_.wait_for(lock, ms, [this](){
return status_ != statuses::pending;
cvar_.wait_for(lock, ms, [this, wait_callback](){
return (status_ != statuses::pending)
&& (!wait_callback || callbacked_);
});
return status_;
}
statuses wait_until(time_point_t tp) const noexcept {
statuses wait_until(time_point_t tp, bool wait_callback) const noexcept {
std::unique_lock<std::mutex> lock(mutex_);
cvar_.wait_until(lock, tp, [this](){
return status_ != statuses::pending;
cvar_.wait_until(lock, tp, [this, wait_callback](){
return (status_ != statuses::pending)
&& (!wait_callback || callbacked_);
});
return status_;
}
@@ -563,6 +570,30 @@ namespace curly_hpp
return error_;
}
std::exception_ptr get_callback_exception() const noexcept {
std::unique_lock<std::mutex> lock(mutex_);
cvar_.wait(lock, [this](){
return callbacked_;
});
return callback_exception_;
}
template < typename... Args >
void call_callback(Args&&... args) noexcept {
try {
if ( breq_.callback() ) {
breq_.callback()(std::forward<Args>(args)...);
}
} catch (...) {
std::lock_guard<std::mutex> guard(mutex_);
callback_exception_ = std::current_exception();
}
std::lock_guard<std::mutex> guard(mutex_);
assert(!callbacked_ && status_ != statuses::pending);
callbacked_ = true;
cvar_.notify_all();
}
bool check_response_timeout(time_point_t now) const noexcept {
std::lock_guard<std::mutex> guard(mutex_);
return now - last_response_ >= response_timeout_;
@@ -650,6 +681,9 @@ namespace curly_hpp
private:
std::atomic_size_t uploaded_{0u};
std::atomic_size_t downloaded_{0u};
private:
bool callbacked_{false};
std::exception_ptr callback_exception_{nullptr};
private:
statuses status_{statuses::pending};
std::string error_{"Unknown error"};
@@ -683,15 +717,27 @@ namespace curly_hpp
}
request::statuses request::wait() const noexcept {
return state_->wait();
return state_->wait(false);
}
request::statuses request::wait_for(time_ms_t ms) const noexcept {
return state_->wait_for(ms);
return state_->wait_for(ms, false);
}
request::statuses request::wait_until(time_point_t tp) const noexcept {
return state_->wait_until(tp);
return state_->wait_until(tp, false);
}
request::statuses request::wait_callback() const noexcept {
return state_->wait(true);
}
request::statuses request::wait_callback_for(time_ms_t ms) const noexcept {
return state_->wait_for(ms, true);
}
request::statuses request::wait_callback_until(time_point_t tp) const noexcept {
return state_->wait_until(tp, true);
}
response request::get() {
@@ -701,6 +747,10 @@ namespace curly_hpp
const std::string& request::get_error() const noexcept {
return state_->get_error();
}
std::exception_ptr request::get_callback_exception() const noexcept {
return state_->get_callback_exception();
}
}
// -----------------------------------------------------------------------------
@@ -917,6 +967,7 @@ namespace curly_hpp
} catch (...) {
sreq->fail(CURLcode::CURLE_FAILED_INIT);
sreq->dequeue(curlm);
sreq->call_callback(sreq);
}
}
});
@@ -959,6 +1010,7 @@ namespace curly_hpp
for ( auto iter = active_handles.begin(); iter != active_handles.end(); ) {
if ( !(*iter)->is_pending() ) {
(*iter)->dequeue(curlm);
(*iter)->call_callback(*iter);
iter = active_handles.erase(iter);
} else {
++iter;

View File

@@ -538,6 +538,84 @@ TEST_CASE("curly") {
REQUIRE(req.wait() == net::request::statuses::canceled);
}
}
SECTION("callback") {
{
std::atomic_size_t call_once{0u};
auto req = net::request_builder("http://www.httpbin.org/get")
.callback([&call_once](net::request request){
std::this_thread::sleep_for(std::chrono::milliseconds(10));
++call_once;
REQUIRE(request.is_done());
REQUIRE(request.status() == net::request::statuses::done);
REQUIRE(request.get().http_code() == 200u);
}).send();
REQUIRE(req.wait_callback() == net::request::statuses::empty);
REQUIRE_FALSE(req.get_callback_exception());
REQUIRE(call_once.load() == 1u);
}
{
std::atomic_size_t call_once{0u};
auto req = net::request_builder("|||")
.callback([&call_once](net::request request){
std::this_thread::sleep_for(std::chrono::milliseconds(10));
++call_once;
REQUIRE_FALSE(request.is_done());
REQUIRE(request.status() == net::request::statuses::failed);
REQUIRE_FALSE(request.get_error().empty());
}).send();
REQUIRE(req.wait_callback() == net::request::statuses::failed);
REQUIRE_FALSE(req.get_callback_exception());
REQUIRE(call_once.load() == 1u);
}
{
std::atomic_size_t call_once{0u};
auto req = net::request_builder("http://www.httpbin.org/delay/2")
.response_timeout(net::time_sec_t(0))
.callback([&call_once](net::request request){
std::this_thread::sleep_for(std::chrono::milliseconds(10));
++call_once;
REQUIRE_FALSE(request.is_done());
REQUIRE(request.status() == net::request::statuses::timeout);
REQUIRE_FALSE(request.get_error().empty());
}).send();
REQUIRE(req.wait_callback() == net::request::statuses::timeout);
REQUIRE_FALSE(req.get_callback_exception());
REQUIRE(call_once.load() == 1u);
}
{
std::atomic_size_t call_once{0u};
auto req = net::request_builder("http://www.httpbin.org/delay/2")
.callback([&call_once](net::request request){
std::this_thread::sleep_for(std::chrono::milliseconds(10));
++call_once;
REQUIRE_FALSE(request.is_done());
REQUIRE(request.status() == net::request::statuses::canceled);
REQUIRE(request.get_error().empty());
}).send();
REQUIRE(req.cancel());
REQUIRE(req.wait_callback() == net::request::statuses::canceled);
REQUIRE_FALSE(req.get_callback_exception());
REQUIRE(call_once.load() == 1u);
}
}
SECTION("callback_exception") {
auto req = net::request_builder("http://www.httpbin.org/post")
.callback([](net::request request){
std::this_thread::sleep_for(std::chrono::milliseconds(10));
if ( request.get().is_http_error() ) {
throw std::logic_error("my_logic_error");
}
}).send();
REQUIRE(req.wait_callback() == net::request::statuses::empty);
REQUIRE(req.get_callback_exception());
try {
std::rethrow_exception(req.get_callback_exception());
} catch (const std::logic_error& e) {
REQUIRE(std::string_view("my_logic_error") == e.what());
}
}
}
TEST_CASE("curly_examples") {
@@ -610,7 +688,9 @@ TEST_CASE("curly_examples") {
.url("http://unavailable.site.com")
.send();
if ( request.wait() == net::request::statuses::done ) {
request.wait();
if ( request.is_done() ) {
auto response = request.get();
std::cout << "Status code: " << response.http_code() << std::endl;
} else {
@@ -623,6 +703,21 @@ TEST_CASE("curly_examples") {
// Error message: Couldn't resolve host name
}
SECTION("Request Callback") {
auto request = net::request_builder("http://www.httpbin.org/get")
.callback([](net::request request){
if ( request.is_done() ) {
auto response = request.get();
std::cout << "Status code: " << response.http_code() << std::endl;
} else {
std::cout << "Error message: " << request.get_error() << std::endl;
}
}).send();
request.wait_callback();
// Status code: 200
}
SECTION("Streamed Requests") {
{
class file_dowloader : public net::download_handler {