Merge branch 'issue/add-completion-callbacks' into dev

This commit is contained in:
2019-07-02 05:00:37 +07:00
5 changed files with 431 additions and 83 deletions

View File

@@ -30,6 +30,7 @@
- Custom headers - Custom headers
- Asynchronous requests - Asynchronous requests
- Different types of timeouts - Different types of timeouts
- Custom completion callbacks
- PUT, GET, HEAD, POST methods - PUT, GET, HEAD, POST methods
- Custom uploading and downloading streams - Custom uploading and downloading streams
@@ -74,7 +75,7 @@ auto request = net::request_builder()
auto response = request.get(); auto response = request.get();
// prints results // prints results
std::cout << "Status code: " << response.code() << std::endl; std::cout << "Status code: " << response.http_code() << std::endl;
std::cout << "Content type: " << response.headers["content-type"] << std::endl; std::cout << "Content type: " << response.headers["content-type"] << std::endl;
std::cout << "Body content: " << response.content.as_string_view() << std::endl; std::cout << "Body content: " << response.content.as_string_view() << std::endl;
@@ -134,9 +135,11 @@ auto request = net::request_builder()
.url("http://unavailable.site.com") .url("http://unavailable.site.com")
.send(); .send();
if ( request.wait() == net::request::statuses::done ) { request.wait();
if ( request.is_done() ) {
auto response = request.get(); auto response = request.get();
std::cout << "Status code: " << response.code() << std::endl; std::cout << "Status code: " << response.http_code() << std::endl;
} else { } else {
// throws net::exception because a response is unavailable // throws net::exception because a response is unavailable
// auto response = request.get(); // auto response = request.get();
@@ -147,6 +150,23 @@ if ( request.wait() == net::request::statuses::done ) {
// Error message: Couldn't resolve host name // Error message: Couldn't resolve host name
``` ```
### Request Callbacks
```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 ### Streamed Requests
#### Downloading #### Downloading
@@ -203,6 +223,52 @@ net::request_builder()
.send().wait(); .send().wait();
``` ```
## Promised Requests
Also, you can easily integrate promises like a [promise.hpp](https://github.com/BlackMATov/promise.hpp).
```cpp
#include <curly.hpp/curly.hpp>
namespace net = curly_hpp;
#include <promise.hpp/promise.hpp>
namespace netex = promise_hpp;
netex::promise<net::content_t> download(std::string url) {
return netex::make_promise<net::content_t>([
url = std::move(url)
](auto resolve, auto reject){
net::request_builder(std::move(url))
.callback([resolve,reject](net::request request) mutable {
if ( !request.is_done() ) {
reject(net::exception("network error"));
return;
}
net::response response = request.get();
if ( response.is_http_error() ) {
reject(net::exception("server error"));
return;
}
resolve(std::move(response.content));
}).send();
});
}
auto promise = download("https://httpbin.org/image/png")
.then([](const net::content_t& content){
std::cout << content.size() << " bytes downloaded" << std::endl;
}).except([](std::exception_ptr e){
try {
std::rethrow_exception(e);
} catch (const std::exception& ee) {
std::cerr << "Failed to download: " << ee.what() << std::endl;
}
});
promise.wait();
// 8090 bytes downloaded
```
## API ## API
> coming soon! > coming soon!

View File

@@ -18,6 +18,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <stdexcept> #include <stdexcept>
#include <functional>
namespace curly_hpp namespace curly_hpp
{ {
@@ -41,13 +42,16 @@ namespace curly_hpp
post post
}; };
using response_code_t = std::uint16_t; using http_code_t = std::uint16_t;
using headers_t = std::map<std::string, std::string, icase_string_compare>; using headers_t = std::map<std::string, std::string, icase_string_compare>;
using time_sec_t = std::chrono::seconds; using time_sec_t = std::chrono::seconds;
using time_ms_t = std::chrono::milliseconds; using time_ms_t = std::chrono::milliseconds;
using time_point_t = std::chrono::steady_clock::time_point; using time_point_t = std::chrono::steady_clock::time_point;
class request;
using callback_t = std::function<void(request)>;
class upload_handler { class upload_handler {
public: public:
virtual ~upload_handler() {} virtual ~upload_handler() {}
@@ -103,15 +107,17 @@ namespace curly_hpp
response(const response&) = delete; response(const response&) = delete;
response& operator=(const response&) = delete; response& operator=(const response&) = delete;
explicit response(response_code_t rc) noexcept; explicit response(http_code_t c) noexcept;
response_code_t code() const noexcept;
bool is_http_error() const noexcept;
http_code_t http_code() const noexcept;
public: public:
content_t content; content_t content;
headers_t headers; headers_t headers;
uploader_uptr uploader; uploader_uptr uploader;
downloader_uptr downloader; downloader_uptr downloader;
private: private:
response_code_t code_{0u}; http_code_t http_code_{0u};
}; };
} }
@@ -136,12 +142,20 @@ namespace curly_hpp
bool cancel() noexcept; bool cancel() noexcept;
statuses status() const noexcept; statuses status() const noexcept;
bool is_done() const noexcept;
bool is_pending() const noexcept;
statuses wait() const noexcept; statuses wait() const noexcept;
statuses wait_for(time_ms_t ms) const noexcept; statuses wait_for(time_ms_t ms) const noexcept;
statuses wait_until(time_point_t tp) 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(); response get();
const std::string& get_error() const noexcept; const std::string& get_error() const noexcept;
std::exception_ptr get_callback_exception() const noexcept;
private: private:
internal_state_ptr state_; internal_state_ptr state_;
}; };
@@ -176,6 +190,7 @@ namespace curly_hpp
request_builder& content(std::string_view b); request_builder& content(std::string_view b);
request_builder& content(content_t b) noexcept; request_builder& content(content_t b) 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;
@@ -193,6 +208,9 @@ namespace curly_hpp
content_t& content() noexcept; content_t& content() noexcept;
const content_t& content() const noexcept; const content_t& content() const noexcept;
callback_t& callback() noexcept;
const callback_t& callback() const noexcept;
uploader_uptr& uploader() noexcept; uploader_uptr& uploader() noexcept;
const uploader_uptr& uploader() const noexcept; const uploader_uptr& uploader() const noexcept;
@@ -201,16 +219,22 @@ namespace curly_hpp
request send(); request send();
template < typename Callback >
request_builder& callback(Callback&& f) {
static_assert(std::is_convertible_v<Callback, callback_t>);
return callback(callback_t(std::forward<Callback>(f)));
}
template < typename Uploader, typename... Args > template < typename Uploader, typename... Args >
request_builder& uploader(Args&&... args) { request_builder& uploader(Args&&... args) {
return uploader(std::make_unique<Uploader>( static_assert(std::is_base_of_v<upload_handler, Uploader>);
std::forward<Args>(args)...)); return uploader(std::make_unique<Uploader>(std::forward<Args>(args)...));
} }
template < typename Downloader, typename... Args > template < typename Downloader, typename... Args >
request_builder& downloader(Args&&... args) { request_builder& downloader(Args&&... args) {
return downloader(std::make_unique<Downloader>( static_assert(std::is_base_of_v<download_handler, Downloader>);
std::forward<Args>(args)...)); return downloader(std::make_unique<Downloader>(std::forward<Args>(args)...));
} }
private: private:
std::string url_; std::string url_;
@@ -224,6 +248,7 @@ namespace curly_hpp
time_sec_t connection_timeout_{20u}; time_sec_t connection_timeout_{20u};
private: private:
content_t content_; content_t content_;
callback_t callback_;
uploader_uptr uploader_; uploader_uptr uploader_;
downloader_uptr downloader_; downloader_uptr downloader_;
}; };

View File

@@ -13,7 +13,6 @@
#include <mutex> #include <mutex>
#include <queue> #include <queue>
#include <algorithm> #include <algorithm>
#include <functional>
#include <condition_variable> #include <condition_variable>
#ifndef NOMINMAX #ifndef NOMINMAX
@@ -48,9 +47,8 @@ namespace
public: public:
using data_t = std::vector<char>; using data_t = std::vector<char>;
default_uploader(const data_t* src, std::mutex* m) noexcept default_uploader(const data_t* src) noexcept
: data_(*src) : data_(*src)
, mutex_(*m)
, size_(src->size()) {} , size_(src->size()) {}
std::size_t size() const override { std::size_t size() const override {
@@ -58,7 +56,6 @@ namespace
} }
std::size_t read(char* dst, std::size_t size) override { std::size_t read(char* dst, std::size_t size) override {
std::lock_guard<std::mutex> guard(mutex_);
assert(size <= data_.size() - uploaded_); assert(size <= data_.size() - uploaded_);
std::memcpy(dst, data_.data() + uploaded_, size); std::memcpy(dst, data_.data() + uploaded_, size);
uploaded_ += size; uploaded_ += size;
@@ -66,7 +63,6 @@ namespace
} }
private: private:
const data_t& data_; const data_t& data_;
std::mutex& mutex_;
std::size_t uploaded_{0}; std::size_t uploaded_{0};
const std::atomic_size_t size_{0}; const std::atomic_size_t size_{0};
}; };
@@ -75,18 +71,15 @@ namespace
public: public:
using data_t = std::vector<char>; using data_t = std::vector<char>;
default_downloader(data_t* dst, std::mutex* m) noexcept default_downloader(data_t* dst) noexcept
: data_(*dst) : data_(*dst) {}
, mutex_(*m) {}
std::size_t write(const char* src, std::size_t size) override { std::size_t write(const char* src, std::size_t size) override {
std::lock_guard<std::mutex> guard(mutex_);
data_.insert(data_.end(), src, src + size); data_.insert(data_.end(), src, src + size);
return size; return size;
} }
private: private:
data_t& data_; data_t& data_;
std::mutex& mutex_;
}; };
} }
@@ -298,11 +291,15 @@ namespace curly_hpp
namespace curly_hpp namespace curly_hpp
{ {
response::response(response_code_t rc) noexcept response::response(http_code_t c) noexcept
: code_(rc) {} : http_code_(c) {}
response_code_t response::code() const noexcept { bool response::is_http_error() const noexcept {
return code_; return http_code_ >= 400u;
}
http_code_t response::http_code() const noexcept {
return http_code_;
} }
} }
@@ -320,11 +317,11 @@ namespace curly_hpp
: breq_(std::move(rb)) : breq_(std::move(rb))
{ {
if ( !breq_.uploader() ) { if ( !breq_.uploader() ) {
breq_.uploader<default_uploader>(&breq_.content().data(), &mutex_); breq_.uploader<default_uploader>(&breq_.content().data());
} }
if ( !breq_.downloader() ) { if ( !breq_.downloader() ) {
breq_.downloader<default_downloader>(&response_content_, &mutex_); breq_.downloader<default_downloader>(&response_content_);
} }
} }
@@ -432,11 +429,11 @@ namespace curly_hpp
return false; return false;
} }
long code = 0; long http_code = 0;
if ( CURLE_OK != curl_easy_getinfo( if ( CURLE_OK != curl_easy_getinfo(
curlh_.get(), curlh_.get(),
CURLINFO_RESPONSE_CODE, CURLINFO_RESPONSE_CODE,
&code) || !code ) &http_code) || !http_code )
{ {
status_ = statuses::failed; status_ = statuses::failed;
cvar_.notify_all(); cvar_.notify_all();
@@ -444,7 +441,7 @@ namespace curly_hpp
} }
try { try {
response_ = response(static_cast<response_code_t>(code)); response_ = response(static_cast<http_code_t>(http_code));
response_.content = std::move(response_content_); response_.content = std::move(response_content_);
response_.headers = std::move(response_headers_); response_.headers = std::move(response_headers_);
response_.uploader = std::move(breq_.uploader()); response_.uploader = std::move(breq_.uploader());
@@ -510,26 +507,39 @@ namespace curly_hpp
return status_; return status_;
} }
statuses wait() const noexcept { bool is_done() const noexcept {
std::lock_guard<std::mutex> guard(mutex_);
return status_ == statuses::done;
}
bool is_pending() const noexcept {
std::lock_guard<std::mutex> guard(mutex_);
return status_ == statuses::pending;
}
statuses wait(bool wait_callback) const noexcept {
std::unique_lock<std::mutex> lock(mutex_); std::unique_lock<std::mutex> lock(mutex_);
cvar_.wait(lock, [this](){ cvar_.wait(lock, [this, wait_callback](){
return status_ != statuses::pending; return (status_ != statuses::pending)
&& (!wait_callback || callbacked_);
}); });
return status_; 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_); std::unique_lock<std::mutex> lock(mutex_);
cvar_.wait_for(lock, ms, [this](){ cvar_.wait_for(lock, ms, [this, wait_callback](){
return status_ != statuses::pending; return (status_ != statuses::pending)
&& (!wait_callback || callbacked_);
}); });
return status_; 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_); std::unique_lock<std::mutex> lock(mutex_);
cvar_.wait_until(lock, tp, [this](){ cvar_.wait_until(lock, tp, [this, wait_callback](){
return status_ != statuses::pending; return (status_ != statuses::pending)
&& (!wait_callback || callbacked_);
}); });
return status_; return status_;
} }
@@ -554,6 +564,30 @@ namespace curly_hpp
return error_; 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 { bool check_response_timeout(time_point_t now) const noexcept {
std::lock_guard<std::mutex> guard(mutex_); std::lock_guard<std::mutex> guard(mutex_);
return now - last_response_ >= response_timeout_; return now - last_response_ >= response_timeout_;
@@ -580,16 +614,15 @@ namespace curly_hpp
return self->header_callback_(buffer, size * nitems); return self->header_callback_(buffer, size * nitems);
} }
private: private:
void response_callback_() noexcept {
std::lock_guard<std::mutex> guard(mutex_);
last_response_ = time_point_t::clock::now();
}
std::size_t upload_callback_(char* dst, std::size_t size) noexcept { std::size_t upload_callback_(char* dst, std::size_t size) noexcept {
try { try {
size = std::min(size, breq_.uploader()->size() - uploaded_.load()); std::lock_guard<std::mutex> guard(mutex_);
last_response_ = time_point_t::clock::now();
size = std::min(size, breq_.uploader()->size() - uploaded_);
const std::size_t read_bytes = breq_.uploader()->read(dst, size); const std::size_t read_bytes = breq_.uploader()->read(dst, size);
uploaded_.fetch_add(read_bytes); uploaded_ += read_bytes;
return read_bytes; return read_bytes;
} catch (...) { } catch (...) {
return CURL_READFUNC_ABORT; return CURL_READFUNC_ABORT;
@@ -598,8 +631,12 @@ namespace curly_hpp
std::size_t download_callback_(const char* src, std::size_t size) noexcept { std::size_t download_callback_(const char* src, std::size_t size) noexcept {
try { try {
std::lock_guard<std::mutex> guard(mutex_);
last_response_ = time_point_t::clock::now();
const std::size_t written_bytes = breq_.downloader()->write(src, size); const std::size_t written_bytes = breq_.downloader()->write(src, size);
downloaded_.fetch_add(written_bytes); downloaded_ += written_bytes;
return written_bytes; return written_bytes;
} catch (...) { } catch (...) {
return 0u; return 0u;
@@ -609,6 +646,8 @@ namespace curly_hpp
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_);
last_response_ = time_point_t::clock::now();
const std::string_view header(src, size); const std::string_view header(src, size);
if ( !header.compare(0u, 5u, "HTTP/") ) { if ( !header.compare(0u, 5u, "HTTP/") ) {
response_headers_.clear(); response_headers_.clear();
@@ -623,6 +662,7 @@ namespace curly_hpp
response_headers_.emplace(key, val); response_headers_.emplace(key, val);
} }
} }
return header.size(); return header.size();
} catch (...) { } catch (...) {
return 0; return 0;
@@ -639,8 +679,11 @@ namespace curly_hpp
headers_t response_headers_; headers_t response_headers_;
std::vector<char> response_content_; std::vector<char> response_content_;
private: private:
std::atomic_size_t uploaded_{0u}; std::size_t uploaded_{0u};
std::atomic_size_t downloaded_{0u}; std::size_t downloaded_{0u};
private:
bool callbacked_{false};
std::exception_ptr callback_exception_{nullptr};
private: private:
statuses status_{statuses::pending}; statuses status_{statuses::pending};
std::string error_{"Unknown error"}; std::string error_{"Unknown error"};
@@ -665,16 +708,36 @@ namespace curly_hpp
return state_->status(); return state_->status();
} }
bool request::is_done() const noexcept {
return state_->is_done();
}
bool request::is_pending() const noexcept {
return state_->is_pending();
}
request::statuses request::wait() const noexcept { request::statuses request::wait() const noexcept {
return state_->wait(); return state_->wait(false);
} }
request::statuses request::wait_for(time_ms_t ms) const noexcept { 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 { 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() { response request::get() {
@@ -684,6 +747,10 @@ namespace curly_hpp
const std::string& request::get_error() const noexcept { const std::string& request::get_error() const noexcept {
return state_->get_error(); return state_->get_error();
} }
std::exception_ptr request::get_callback_exception() const noexcept {
return state_->get_callback_exception();
}
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -759,6 +826,11 @@ namespace curly_hpp
return *this; return *this;
} }
request_builder& request_builder::callback(callback_t c) noexcept {
callback_ = std::move(c);
return *this;
}
request_builder& request_builder::uploader(uploader_uptr u) noexcept { request_builder& request_builder::uploader(uploader_uptr u) noexcept {
uploader_ = std::move(u); uploader_ = std::move(u);
return *this; return *this;
@@ -813,6 +885,14 @@ namespace curly_hpp
return content_; return content_;
} }
callback_t& request_builder::callback() noexcept {
return callback_;
}
const callback_t& request_builder::callback() const noexcept {
return callback_;
}
uploader_uptr& request_builder::uploader() noexcept { uploader_uptr& request_builder::uploader() noexcept {
return uploader_; return uploader_;
} }
@@ -887,6 +967,7 @@ namespace curly_hpp
} catch (...) { } catch (...) {
sreq->fail(CURLcode::CURLE_FAILED_INIT); sreq->fail(CURLcode::CURLE_FAILED_INIT);
sreq->dequeue(curlm); sreq->dequeue(curlm);
sreq->call_callback(sreq);
} }
} }
}); });
@@ -927,8 +1008,9 @@ namespace curly_hpp
curl_state::with([](CURLM* curlm){ curl_state::with([](CURLM* curlm){
for ( auto iter = active_handles.begin(); iter != active_handles.end(); ) { for ( auto iter = active_handles.begin(); iter != active_handles.end(); ) {
if ( (*iter)->status() != request::statuses::pending ) { if ( !(*iter)->is_pending() ) {
(*iter)->dequeue(curlm); (*iter)->dequeue(curlm);
(*iter)->call_callback(*iter);
iter = active_handles.erase(iter); iter = active_handles.erase(iter);
} else { } else {
++iter; ++iter;

View File

@@ -64,3 +64,19 @@ if(NOT tencent_rapidjson_POPULATED)
target_include_directories(${PROJECT_NAME} target_include_directories(${PROJECT_NAME}
PRIVATE ${tencent_rapidjson_SOURCE_DIR}/include) PRIVATE ${tencent_rapidjson_SOURCE_DIR}/include)
endif() endif()
#
# blackmatov/promise.hpp
#
include(FetchContent)
FetchContent_Declare(
blackmatov_promise_hpp
GIT_REPOSITORY https://github.com/blackmatov/promise.hpp)
FetchContent_GetProperties(blackmatov_promise_hpp)
if(NOT blackmatov_promise_hpp_POPULATED)
FetchContent_Populate(blackmatov_promise_hpp)
target_include_directories(${PROJECT_NAME}
PRIVATE ${blackmatov_promise_hpp_SOURCE_DIR}/headers)
endif()

View File

@@ -16,6 +16,9 @@ namespace json = rapidjson;
#include <curly.hpp/curly.hpp> #include <curly.hpp/curly.hpp>
namespace net = curly_hpp; namespace net = curly_hpp;
#include <promise.hpp/promise.hpp>
namespace netex = promise_hpp;
#include "png_data.h" #include "png_data.h"
#include "jpeg_data.h" #include "jpeg_data.h"
@@ -54,6 +57,26 @@ namespace
throw std::exception(); throw std::exception();
} }
}; };
netex::promise<net::content_t> download(std::string url) {
return netex::make_promise<net::content_t>([
url = std::move(url)
](auto resolve, auto reject){
net::request_builder(std::move(url))
.callback([resolve,reject](net::request request) mutable {
if ( !request.is_done() ) {
reject(net::exception("network error"));
return;
}
net::response response = request.get();
if ( response.is_http_error() ) {
reject(net::exception("server error"));
return;
}
resolve(std::move(response.content));
}).send();
});
}
} }
TEST_CASE("curly") { TEST_CASE("curly") {
@@ -67,14 +90,14 @@ TEST_CASE("curly") {
REQUIRE(req.wait() == net::request::statuses::done); REQUIRE(req.wait() == net::request::statuses::done);
REQUIRE(req.status() == net::request::statuses::done); REQUIRE(req.status() == net::request::statuses::done);
auto resp = req.get(); auto resp = req.get();
REQUIRE(resp.code() == 200u); REQUIRE(resp.http_code() == 200u);
REQUIRE(req.status() == net::request::statuses::empty); REQUIRE(req.status() == net::request::statuses::empty);
} }
{ {
auto req = net::request_builder("https://httpbin.org/delay/2").send(); auto req = net::request_builder("https://httpbin.org/delay/2").send();
REQUIRE(req.wait_for(net::time_sec_t(1)) == net::request::statuses::pending); REQUIRE(req.wait_for(net::time_sec_t(1)) == net::request::statuses::pending);
REQUIRE(req.wait_for(net::time_sec_t(5)) == net::request::statuses::done); REQUIRE(req.wait_for(net::time_sec_t(5)) == net::request::statuses::done);
REQUIRE(req.get().code() == 200u); REQUIRE(req.get().http_code() == 200u);
} }
{ {
auto req = net::request_builder("https://httpbin.org/delay/2").send(); auto req = net::request_builder("https://httpbin.org/delay/2").send();
@@ -82,7 +105,7 @@ TEST_CASE("curly") {
== net::request::statuses::pending); == net::request::statuses::pending);
REQUIRE(req.wait_until(net::time_point_t::clock::now() + net::time_sec_t(5)) REQUIRE(req.wait_until(net::time_point_t::clock::now() + net::time_sec_t(5))
== net::request::statuses::done); == net::request::statuses::done);
REQUIRE(req.get().code() == 200u); REQUIRE(req.get().http_code() == 200u);
} }
} }
@@ -109,12 +132,37 @@ TEST_CASE("curly") {
} }
} }
SECTION("is_done/is_pending") {
{
auto req = net::request_builder(net::methods::get)
.url("https://httpbin.org/delay/1")
.send();
REQUIRE_FALSE(req.is_done());
REQUIRE(req.is_pending());
req.wait();
REQUIRE(req.is_done());
REQUIRE_FALSE(req.is_pending());
}
{
auto req = net::request_builder(net::methods::post, "http://www.httpbin.org/post")
.url("https://httpbin.org/delay/2")
.request_timeout(net::time_sec_t(1))
.send();
REQUIRE_FALSE(req.is_done());
REQUIRE(req.is_pending());
req.wait();
REQUIRE_FALSE(req.is_done());
REQUIRE_FALSE(req.is_pending());
REQUIRE(!req.get_error().empty());
}
}
SECTION("get") { SECTION("get") {
{ {
auto req = net::request_builder("https://httpbin.org/status/204").send(); auto req = net::request_builder("https://httpbin.org/status/204").send();
auto resp = req.get(); auto resp = req.get();
REQUIRE(req.status() == net::request::statuses::empty); REQUIRE(req.status() == net::request::statuses::empty);
REQUIRE(resp.code() == 204u); REQUIRE(resp.http_code() == 204u);
} }
{ {
auto req = net::request_builder("https://httpbin.org/delay/2").send(); auto req = net::request_builder("https://httpbin.org/delay/2").send();
@@ -138,75 +186,75 @@ TEST_CASE("curly") {
.url("https://httpbin.org/put") .url("https://httpbin.org/put")
.method(net::methods::put) .method(net::methods::put)
.send(); .send();
REQUIRE(req0.get().code() == 200u); REQUIRE(req0.get().http_code() == 200u);
auto req1 = net::request_builder() auto req1 = net::request_builder()
.url("https://httpbin.org/put") .url("https://httpbin.org/put")
.method(net::methods::get) .method(net::methods::get)
.send(); .send();
REQUIRE(req1.get().code() == 405u); REQUIRE(req1.get().http_code() == 405u);
auto req2 = net::request_builder() auto req2 = net::request_builder()
.url("https://httpbin.org/put") .url("https://httpbin.org/put")
.method(net::methods::head) .method(net::methods::head)
.send(); .send();
REQUIRE(req2.get().code() == 405u); REQUIRE(req2.get().http_code() == 405u);
auto req3 = net::request_builder() auto req3 = net::request_builder()
.url("https://httpbin.org/put") .url("https://httpbin.org/put")
.method(net::methods::post) .method(net::methods::post)
.send(); .send();
REQUIRE(req3.get().code() == 405u); REQUIRE(req3.get().http_code() == 405u);
} }
{ {
auto req0 = net::request_builder() auto req0 = net::request_builder()
.url("https://httpbin.org/get") .url("https://httpbin.org/get")
.method(net::methods::put) .method(net::methods::put)
.send(); .send();
REQUIRE(req0.get().code() == 405u); REQUIRE(req0.get().http_code() == 405u);
auto req1 = net::request_builder() auto req1 = net::request_builder()
.url("https://httpbin.org/get") .url("https://httpbin.org/get")
.method(net::methods::get) .method(net::methods::get)
.send(); .send();
REQUIRE(req1.get().code() == 200u); REQUIRE(req1.get().http_code() == 200u);
auto req2 = net::request_builder() auto req2 = net::request_builder()
.url("https://httpbin.org/get") .url("https://httpbin.org/get")
.method(net::methods::head) .method(net::methods::head)
.send(); .send();
REQUIRE(req2.get().code() == 200u); REQUIRE(req2.get().http_code() == 200u);
auto req3 = net::request_builder() auto req3 = net::request_builder()
.url("https://httpbin.org/get") .url("https://httpbin.org/get")
.method(net::methods::post) .method(net::methods::post)
.send(); .send();
REQUIRE(req3.get().code() == 405u); REQUIRE(req3.get().http_code() == 405u);
} }
{ {
auto req0 = net::request_builder() auto req0 = net::request_builder()
.url("https://httpbin.org/post") .url("https://httpbin.org/post")
.method(net::methods::put) .method(net::methods::put)
.send(); .send();
REQUIRE(req0.get().code() == 405u); REQUIRE(req0.get().http_code() == 405u);
auto req1 = net::request_builder() auto req1 = net::request_builder()
.url("https://httpbin.org/post") .url("https://httpbin.org/post")
.method(net::methods::get) .method(net::methods::get)
.send(); .send();
REQUIRE(req1.get().code() == 405u); REQUIRE(req1.get().http_code() == 405u);
auto req2 = net::request_builder() auto req2 = net::request_builder()
.url("https://httpbin.org/post") .url("https://httpbin.org/post")
.method(net::methods::head) .method(net::methods::head)
.send(); .send();
REQUIRE(req2.get().code() == 405u); REQUIRE(req2.get().http_code() == 405u);
auto req3 = net::request_builder() auto req3 = net::request_builder()
.url("https://httpbin.org/post") .url("https://httpbin.org/post")
.method(net::methods::post) .method(net::methods::post)
.send(); .send();
REQUIRE(req3.get().code() == 200u); REQUIRE(req3.get().http_code() == 200u);
} }
} }
@@ -216,28 +264,28 @@ TEST_CASE("curly") {
.url("https://httpbin.org/status/200") .url("https://httpbin.org/status/200")
.method(net::methods::put) .method(net::methods::put)
.send(); .send();
REQUIRE(req.get().code() == 200u); REQUIRE(req.get().http_code() == 200u);
} }
{ {
auto req = net::request_builder() auto req = net::request_builder()
.url("https://httpbin.org/status/201") .url("https://httpbin.org/status/201")
.method(net::methods::get) .method(net::methods::get)
.send(); .send();
REQUIRE(req.get().code() == 201u); REQUIRE(req.get().http_code() == 201u);
} }
{ {
auto req = net::request_builder() auto req = net::request_builder()
.url("https://httpbin.org/status/202") .url("https://httpbin.org/status/202")
.method(net::methods::head) .method(net::methods::head)
.send(); .send();
REQUIRE(req.get().code() == 202u); REQUIRE(req.get().http_code() == 202u);
} }
{ {
auto req = net::request_builder() auto req = net::request_builder()
.url("https://httpbin.org/status/203") .url("https://httpbin.org/status/203")
.method(net::methods::post) .method(net::methods::post)
.send(); .send();
REQUIRE(req.get().code() == 203u); REQUIRE(req.get().http_code() == 203u);
} }
} }
@@ -325,7 +373,7 @@ TEST_CASE("curly") {
.url("https://httpbin.org/image/png") .url("https://httpbin.org/image/png")
.method(net::methods::get) .method(net::methods::get)
.send().get(); .send().get();
REQUIRE(resp.code() == 200u); REQUIRE(resp.http_code() == 200u);
REQUIRE(resp.headers.count("Content-Type")); REQUIRE(resp.headers.count("Content-Type"));
REQUIRE(resp.headers.at("Content-Type") == "image/png"); REQUIRE(resp.headers.at("Content-Type") == "image/png");
REQUIRE(untests::png_data_length == resp.content.size()); REQUIRE(untests::png_data_length == resp.content.size());
@@ -338,7 +386,7 @@ TEST_CASE("curly") {
.url("https://httpbin.org/image/jpeg") .url("https://httpbin.org/image/jpeg")
.method(net::methods::get) .method(net::methods::get)
.send().get(); .send().get();
REQUIRE(resp.code() == 200u); REQUIRE(resp.http_code() == 200u);
REQUIRE(resp.headers.count("Content-Type")); REQUIRE(resp.headers.count("Content-Type"));
REQUIRE(resp.headers.at("Content-Type") == "image/jpeg"); REQUIRE(resp.headers.at("Content-Type") == "image/jpeg");
REQUIRE(untests::jpeg_data_length == resp.content.size()); REQUIRE(untests::jpeg_data_length == resp.content.size());
@@ -355,21 +403,21 @@ TEST_CASE("curly") {
.url("https://httpbin.org/redirect/2") .url("https://httpbin.org/redirect/2")
.method(net::methods::get) .method(net::methods::get)
.send(); .send();
REQUIRE(req.get().code() == 200u); REQUIRE(req.get().http_code() == 200u);
} }
{ {
auto req = net::request_builder() auto req = net::request_builder()
.url("https://httpbin.org/absolute-redirect/2") .url("https://httpbin.org/absolute-redirect/2")
.method(net::methods::get) .method(net::methods::get)
.send(); .send();
REQUIRE(req.get().code() == 200u); REQUIRE(req.get().http_code() == 200u);
} }
{ {
auto req = net::request_builder() auto req = net::request_builder()
.url("https://httpbin.org/relative-redirect/2") .url("https://httpbin.org/relative-redirect/2")
.method(net::methods::get) .method(net::methods::get)
.send(); .send();
REQUIRE(req.get().code() == 200u); REQUIRE(req.get().http_code() == 200u);
} }
} }
{ {
@@ -379,7 +427,7 @@ TEST_CASE("curly") {
.method(net::methods::get) .method(net::methods::get)
.redirections(0) .redirections(0)
.send(); .send();
REQUIRE(req.get().code() == 302u); REQUIRE(req.get().http_code() == 302u);
} }
{ {
auto req = net::request_builder() auto req = net::request_builder()
@@ -403,7 +451,7 @@ TEST_CASE("curly") {
.method(net::methods::get) .method(net::methods::get)
.redirections(3) .redirections(3)
.send(); .send();
REQUIRE(req.get().code() == 200u); REQUIRE(req.get().http_code() == 200u);
} }
} }
} }
@@ -513,6 +561,84 @@ TEST_CASE("curly") {
REQUIRE(req.wait() == net::request::statuses::canceled); 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") { TEST_CASE("curly_examples") {
@@ -529,7 +655,7 @@ TEST_CASE("curly_examples") {
auto response = request.get(); auto response = request.get();
// prints results // prints results
std::cout << "Status code: " << response.code() << std::endl; std::cout << "Status code: " << response.http_code() << std::endl;
std::cout << "Content type: " << response.headers["content-type"] << std::endl; std::cout << "Content type: " << response.headers["content-type"] << std::endl;
std::cout << "Body content: " << response.content.as_string_view() << std::endl; std::cout << "Body content: " << response.content.as_string_view() << std::endl;
@@ -585,9 +711,11 @@ TEST_CASE("curly_examples") {
.url("http://unavailable.site.com") .url("http://unavailable.site.com")
.send(); .send();
if ( request.wait() == net::request::statuses::done ) { request.wait();
if ( request.is_done() ) {
auto response = request.get(); auto response = request.get();
std::cout << "Status code: " << response.code() << std::endl; std::cout << "Status code: " << response.http_code() << std::endl;
} else { } else {
// throws net::exception because a response is unavailable // throws net::exception because a response is unavailable
// auto response = request.get(); // auto response = request.get();
@@ -598,6 +726,21 @@ TEST_CASE("curly_examples") {
// Error message: Couldn't resolve host name // Error message: Couldn't resolve host name
} }
SECTION("Request Callbacks") {
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") { SECTION("Streamed Requests") {
{ {
class file_dowloader : public net::download_handler { class file_dowloader : public net::download_handler {
@@ -648,4 +791,20 @@ TEST_CASE("curly_examples") {
.send().get(); .send().get();
} }
} }
SECTION("Promised Requests") {
auto promise = download("https://httpbin.org/image/png")
.then([](const net::content_t& content){
std::cout << content.size() << " bytes downloaded" << std::endl;
}).except([](std::exception_ptr e){
try {
std::rethrow_exception(e);
} catch (const std::exception& ee) {
std::cerr << "Failed to download: " << ee.what() << std::endl;
}
});
promise.wait();
// 8090 bytes downloaded
}
} }