mirror of
https://github.com/BlackMATov/curly.hpp.git
synced 2025-12-14 12:10:53 +07:00
Merge branch 'issue/add-completion-callbacks' into dev
This commit is contained in:
72
README.md
72
README.md
@@ -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
|
||||
|
||||
@@ -74,7 +75,7 @@ auto request = net::request_builder()
|
||||
auto response = request.get();
|
||||
|
||||
// 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 << "Body content: " << response.content.as_string_view() << std::endl;
|
||||
|
||||
@@ -134,9 +135,11 @@ 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.code() << std::endl;
|
||||
std::cout << "Status code: " << response.http_code() << std::endl;
|
||||
} else {
|
||||
// throws net::exception because a response is unavailable
|
||||
// auto response = request.get();
|
||||
@@ -147,6 +150,23 @@ if ( request.wait() == net::request::statuses::done ) {
|
||||
// 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
|
||||
|
||||
#### Downloading
|
||||
@@ -203,6 +223,52 @@ net::request_builder()
|
||||
.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
|
||||
|
||||
> coming soon!
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
#include <functional>
|
||||
|
||||
namespace curly_hpp
|
||||
{
|
||||
@@ -41,13 +42,16 @@ namespace curly_hpp
|
||||
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 time_sec_t = std::chrono::seconds;
|
||||
using time_ms_t = std::chrono::milliseconds;
|
||||
using time_point_t = std::chrono::steady_clock::time_point;
|
||||
|
||||
class request;
|
||||
using callback_t = std::function<void(request)>;
|
||||
|
||||
class upload_handler {
|
||||
public:
|
||||
virtual ~upload_handler() {}
|
||||
@@ -103,15 +107,17 @@ namespace curly_hpp
|
||||
response(const response&) = delete;
|
||||
response& operator=(const response&) = delete;
|
||||
|
||||
explicit response(response_code_t rc) noexcept;
|
||||
response_code_t code() const noexcept;
|
||||
explicit response(http_code_t c) noexcept;
|
||||
|
||||
bool is_http_error() const noexcept;
|
||||
http_code_t http_code() const noexcept;
|
||||
public:
|
||||
content_t content;
|
||||
headers_t headers;
|
||||
uploader_uptr uploader;
|
||||
downloader_uptr downloader;
|
||||
private:
|
||||
response_code_t code_{0u};
|
||||
http_code_t http_code_{0u};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -136,12 +142,20 @@ namespace curly_hpp
|
||||
bool cancel() noexcept;
|
||||
statuses status() const noexcept;
|
||||
|
||||
bool is_done() const noexcept;
|
||||
bool is_pending() const noexcept;
|
||||
|
||||
statuses wait() const noexcept;
|
||||
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_;
|
||||
};
|
||||
@@ -176,6 +190,7 @@ namespace curly_hpp
|
||||
|
||||
request_builder& content(std::string_view b);
|
||||
request_builder& content(content_t b) noexcept;
|
||||
request_builder& callback(callback_t c) noexcept;
|
||||
request_builder& uploader(uploader_uptr u) noexcept;
|
||||
request_builder& downloader(downloader_uptr d) noexcept;
|
||||
|
||||
@@ -193,6 +208,9 @@ namespace curly_hpp
|
||||
content_t& content() noexcept;
|
||||
const content_t& content() const noexcept;
|
||||
|
||||
callback_t& callback() noexcept;
|
||||
const callback_t& callback() const noexcept;
|
||||
|
||||
uploader_uptr& uploader() noexcept;
|
||||
const uploader_uptr& uploader() const noexcept;
|
||||
|
||||
@@ -201,16 +219,22 @@ namespace curly_hpp
|
||||
|
||||
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 >
|
||||
request_builder& uploader(Args&&... args) {
|
||||
return uploader(std::make_unique<Uploader>(
|
||||
std::forward<Args>(args)...));
|
||||
static_assert(std::is_base_of_v<upload_handler, Uploader>);
|
||||
return uploader(std::make_unique<Uploader>(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template < typename Downloader, typename... Args >
|
||||
request_builder& downloader(Args&&... args) {
|
||||
return downloader(std::make_unique<Downloader>(
|
||||
std::forward<Args>(args)...));
|
||||
static_assert(std::is_base_of_v<download_handler, Downloader>);
|
||||
return downloader(std::make_unique<Downloader>(std::forward<Args>(args)...));
|
||||
}
|
||||
private:
|
||||
std::string url_;
|
||||
@@ -224,6 +248,7 @@ namespace curly_hpp
|
||||
time_sec_t connection_timeout_{20u};
|
||||
private:
|
||||
content_t content_;
|
||||
callback_t callback_;
|
||||
uploader_uptr uploader_;
|
||||
downloader_uptr downloader_;
|
||||
};
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <condition_variable>
|
||||
|
||||
#ifndef NOMINMAX
|
||||
@@ -48,9 +47,8 @@ namespace
|
||||
public:
|
||||
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)
|
||||
, mutex_(*m)
|
||||
, size_(src->size()) {}
|
||||
|
||||
std::size_t size() const override {
|
||||
@@ -58,7 +56,6 @@ namespace
|
||||
}
|
||||
|
||||
std::size_t read(char* dst, std::size_t size) override {
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
assert(size <= data_.size() - uploaded_);
|
||||
std::memcpy(dst, data_.data() + uploaded_, size);
|
||||
uploaded_ += size;
|
||||
@@ -66,7 +63,6 @@ namespace
|
||||
}
|
||||
private:
|
||||
const data_t& data_;
|
||||
std::mutex& mutex_;
|
||||
std::size_t uploaded_{0};
|
||||
const std::atomic_size_t size_{0};
|
||||
};
|
||||
@@ -75,18 +71,15 @@ namespace
|
||||
public:
|
||||
using data_t = std::vector<char>;
|
||||
|
||||
default_downloader(data_t* dst, std::mutex* m) noexcept
|
||||
: data_(*dst)
|
||||
, mutex_(*m) {}
|
||||
default_downloader(data_t* dst) noexcept
|
||||
: data_(*dst) {}
|
||||
|
||||
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);
|
||||
return size;
|
||||
}
|
||||
private:
|
||||
data_t& data_;
|
||||
std::mutex& mutex_;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -298,11 +291,15 @@ namespace curly_hpp
|
||||
|
||||
namespace curly_hpp
|
||||
{
|
||||
response::response(response_code_t rc) noexcept
|
||||
: code_(rc) {}
|
||||
response::response(http_code_t c) noexcept
|
||||
: http_code_(c) {}
|
||||
|
||||
response_code_t response::code() const noexcept {
|
||||
return code_;
|
||||
bool response::is_http_error() const noexcept {
|
||||
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))
|
||||
{
|
||||
if ( !breq_.uploader() ) {
|
||||
breq_.uploader<default_uploader>(&breq_.content().data(), &mutex_);
|
||||
breq_.uploader<default_uploader>(&breq_.content().data());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -444,7 +441,7 @@ namespace curly_hpp
|
||||
}
|
||||
|
||||
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_.headers = std::move(response_headers_);
|
||||
response_.uploader = std::move(breq_.uploader());
|
||||
@@ -510,26 +507,39 @@ namespace curly_hpp
|
||||
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_);
|
||||
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_;
|
||||
}
|
||||
@@ -554,6 +564,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_;
|
||||
@@ -580,16 +614,15 @@ namespace curly_hpp
|
||||
return self->header_callback_(buffer, size * nitems);
|
||||
}
|
||||
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 {
|
||||
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);
|
||||
uploaded_.fetch_add(read_bytes);
|
||||
uploaded_ += read_bytes;
|
||||
|
||||
return read_bytes;
|
||||
} catch (...) {
|
||||
return CURL_READFUNC_ABORT;
|
||||
@@ -598,8 +631,12 @@ namespace curly_hpp
|
||||
|
||||
std::size_t download_callback_(const char* src, std::size_t size) noexcept {
|
||||
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);
|
||||
downloaded_.fetch_add(written_bytes);
|
||||
downloaded_ += written_bytes;
|
||||
|
||||
return written_bytes;
|
||||
} catch (...) {
|
||||
return 0u;
|
||||
@@ -609,6 +646,8 @@ namespace curly_hpp
|
||||
std::size_t header_callback_(const char* src, std::size_t size) noexcept {
|
||||
try {
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
last_response_ = time_point_t::clock::now();
|
||||
|
||||
const std::string_view header(src, size);
|
||||
if ( !header.compare(0u, 5u, "HTTP/") ) {
|
||||
response_headers_.clear();
|
||||
@@ -623,6 +662,7 @@ namespace curly_hpp
|
||||
response_headers_.emplace(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
return header.size();
|
||||
} catch (...) {
|
||||
return 0;
|
||||
@@ -639,8 +679,11 @@ namespace curly_hpp
|
||||
headers_t response_headers_;
|
||||
std::vector<char> response_content_;
|
||||
private:
|
||||
std::atomic_size_t uploaded_{0u};
|
||||
std::atomic_size_t downloaded_{0u};
|
||||
std::size_t uploaded_{0u};
|
||||
std::size_t downloaded_{0u};
|
||||
private:
|
||||
bool callbacked_{false};
|
||||
std::exception_ptr callback_exception_{nullptr};
|
||||
private:
|
||||
statuses status_{statuses::pending};
|
||||
std::string error_{"Unknown error"};
|
||||
@@ -665,16 +708,36 @@ namespace curly_hpp
|
||||
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 {
|
||||
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() {
|
||||
@@ -684,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();
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -759,6 +826,11 @@ namespace curly_hpp
|
||||
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 {
|
||||
uploader_ = std::move(u);
|
||||
return *this;
|
||||
@@ -813,6 +885,14 @@ namespace curly_hpp
|
||||
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 {
|
||||
return uploader_;
|
||||
}
|
||||
@@ -887,6 +967,7 @@ namespace curly_hpp
|
||||
} catch (...) {
|
||||
sreq->fail(CURLcode::CURLE_FAILED_INIT);
|
||||
sreq->dequeue(curlm);
|
||||
sreq->call_callback(sreq);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -927,8 +1008,9 @@ namespace curly_hpp
|
||||
|
||||
curl_state::with([](CURLM* curlm){
|
||||
for ( auto iter = active_handles.begin(); iter != active_handles.end(); ) {
|
||||
if ( (*iter)->status() != request::statuses::pending ) {
|
||||
if ( !(*iter)->is_pending() ) {
|
||||
(*iter)->dequeue(curlm);
|
||||
(*iter)->call_callback(*iter);
|
||||
iter = active_handles.erase(iter);
|
||||
} else {
|
||||
++iter;
|
||||
|
||||
@@ -64,3 +64,19 @@ if(NOT tencent_rapidjson_POPULATED)
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PRIVATE ${tencent_rapidjson_SOURCE_DIR}/include)
|
||||
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()
|
||||
|
||||
@@ -16,6 +16,9 @@ namespace json = rapidjson;
|
||||
#include <curly.hpp/curly.hpp>
|
||||
namespace net = curly_hpp;
|
||||
|
||||
#include <promise.hpp/promise.hpp>
|
||||
namespace netex = promise_hpp;
|
||||
|
||||
#include "png_data.h"
|
||||
#include "jpeg_data.h"
|
||||
|
||||
@@ -54,6 +57,26 @@ namespace
|
||||
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") {
|
||||
@@ -67,14 +90,14 @@ TEST_CASE("curly") {
|
||||
REQUIRE(req.wait() == net::request::statuses::done);
|
||||
REQUIRE(req.status() == net::request::statuses::done);
|
||||
auto resp = req.get();
|
||||
REQUIRE(resp.code() == 200u);
|
||||
REQUIRE(resp.http_code() == 200u);
|
||||
REQUIRE(req.status() == net::request::statuses::empty);
|
||||
}
|
||||
{
|
||||
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(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();
|
||||
@@ -82,7 +105,7 @@ TEST_CASE("curly") {
|
||||
== net::request::statuses::pending);
|
||||
REQUIRE(req.wait_until(net::time_point_t::clock::now() + net::time_sec_t(5))
|
||||
== 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") {
|
||||
{
|
||||
auto req = net::request_builder("https://httpbin.org/status/204").send();
|
||||
auto resp = req.get();
|
||||
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();
|
||||
@@ -138,75 +186,75 @@ TEST_CASE("curly") {
|
||||
.url("https://httpbin.org/put")
|
||||
.method(net::methods::put)
|
||||
.send();
|
||||
REQUIRE(req0.get().code() == 200u);
|
||||
REQUIRE(req0.get().http_code() == 200u);
|
||||
|
||||
auto req1 = net::request_builder()
|
||||
.url("https://httpbin.org/put")
|
||||
.method(net::methods::get)
|
||||
.send();
|
||||
REQUIRE(req1.get().code() == 405u);
|
||||
REQUIRE(req1.get().http_code() == 405u);
|
||||
|
||||
auto req2 = net::request_builder()
|
||||
.url("https://httpbin.org/put")
|
||||
.method(net::methods::head)
|
||||
.send();
|
||||
REQUIRE(req2.get().code() == 405u);
|
||||
REQUIRE(req2.get().http_code() == 405u);
|
||||
|
||||
auto req3 = net::request_builder()
|
||||
.url("https://httpbin.org/put")
|
||||
.method(net::methods::post)
|
||||
.send();
|
||||
REQUIRE(req3.get().code() == 405u);
|
||||
REQUIRE(req3.get().http_code() == 405u);
|
||||
}
|
||||
{
|
||||
auto req0 = net::request_builder()
|
||||
.url("https://httpbin.org/get")
|
||||
.method(net::methods::put)
|
||||
.send();
|
||||
REQUIRE(req0.get().code() == 405u);
|
||||
REQUIRE(req0.get().http_code() == 405u);
|
||||
|
||||
auto req1 = net::request_builder()
|
||||
.url("https://httpbin.org/get")
|
||||
.method(net::methods::get)
|
||||
.send();
|
||||
REQUIRE(req1.get().code() == 200u);
|
||||
REQUIRE(req1.get().http_code() == 200u);
|
||||
|
||||
auto req2 = net::request_builder()
|
||||
.url("https://httpbin.org/get")
|
||||
.method(net::methods::head)
|
||||
.send();
|
||||
REQUIRE(req2.get().code() == 200u);
|
||||
REQUIRE(req2.get().http_code() == 200u);
|
||||
|
||||
auto req3 = net::request_builder()
|
||||
.url("https://httpbin.org/get")
|
||||
.method(net::methods::post)
|
||||
.send();
|
||||
REQUIRE(req3.get().code() == 405u);
|
||||
REQUIRE(req3.get().http_code() == 405u);
|
||||
}
|
||||
{
|
||||
auto req0 = net::request_builder()
|
||||
.url("https://httpbin.org/post")
|
||||
.method(net::methods::put)
|
||||
.send();
|
||||
REQUIRE(req0.get().code() == 405u);
|
||||
REQUIRE(req0.get().http_code() == 405u);
|
||||
|
||||
auto req1 = net::request_builder()
|
||||
.url("https://httpbin.org/post")
|
||||
.method(net::methods::get)
|
||||
.send();
|
||||
REQUIRE(req1.get().code() == 405u);
|
||||
REQUIRE(req1.get().http_code() == 405u);
|
||||
|
||||
auto req2 = net::request_builder()
|
||||
.url("https://httpbin.org/post")
|
||||
.method(net::methods::head)
|
||||
.send();
|
||||
REQUIRE(req2.get().code() == 405u);
|
||||
REQUIRE(req2.get().http_code() == 405u);
|
||||
|
||||
auto req3 = net::request_builder()
|
||||
.url("https://httpbin.org/post")
|
||||
.method(net::methods::post)
|
||||
.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")
|
||||
.method(net::methods::put)
|
||||
.send();
|
||||
REQUIRE(req.get().code() == 200u);
|
||||
REQUIRE(req.get().http_code() == 200u);
|
||||
}
|
||||
{
|
||||
auto req = net::request_builder()
|
||||
.url("https://httpbin.org/status/201")
|
||||
.method(net::methods::get)
|
||||
.send();
|
||||
REQUIRE(req.get().code() == 201u);
|
||||
REQUIRE(req.get().http_code() == 201u);
|
||||
}
|
||||
{
|
||||
auto req = net::request_builder()
|
||||
.url("https://httpbin.org/status/202")
|
||||
.method(net::methods::head)
|
||||
.send();
|
||||
REQUIRE(req.get().code() == 202u);
|
||||
REQUIRE(req.get().http_code() == 202u);
|
||||
}
|
||||
{
|
||||
auto req = net::request_builder()
|
||||
.url("https://httpbin.org/status/203")
|
||||
.method(net::methods::post)
|
||||
.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")
|
||||
.method(net::methods::get)
|
||||
.send().get();
|
||||
REQUIRE(resp.code() == 200u);
|
||||
REQUIRE(resp.http_code() == 200u);
|
||||
REQUIRE(resp.headers.count("Content-Type"));
|
||||
REQUIRE(resp.headers.at("Content-Type") == "image/png");
|
||||
REQUIRE(untests::png_data_length == resp.content.size());
|
||||
@@ -338,7 +386,7 @@ TEST_CASE("curly") {
|
||||
.url("https://httpbin.org/image/jpeg")
|
||||
.method(net::methods::get)
|
||||
.send().get();
|
||||
REQUIRE(resp.code() == 200u);
|
||||
REQUIRE(resp.http_code() == 200u);
|
||||
REQUIRE(resp.headers.count("Content-Type"));
|
||||
REQUIRE(resp.headers.at("Content-Type") == "image/jpeg");
|
||||
REQUIRE(untests::jpeg_data_length == resp.content.size());
|
||||
@@ -355,21 +403,21 @@ TEST_CASE("curly") {
|
||||
.url("https://httpbin.org/redirect/2")
|
||||
.method(net::methods::get)
|
||||
.send();
|
||||
REQUIRE(req.get().code() == 200u);
|
||||
REQUIRE(req.get().http_code() == 200u);
|
||||
}
|
||||
{
|
||||
auto req = net::request_builder()
|
||||
.url("https://httpbin.org/absolute-redirect/2")
|
||||
.method(net::methods::get)
|
||||
.send();
|
||||
REQUIRE(req.get().code() == 200u);
|
||||
REQUIRE(req.get().http_code() == 200u);
|
||||
}
|
||||
{
|
||||
auto req = net::request_builder()
|
||||
.url("https://httpbin.org/relative-redirect/2")
|
||||
.method(net::methods::get)
|
||||
.send();
|
||||
REQUIRE(req.get().code() == 200u);
|
||||
REQUIRE(req.get().http_code() == 200u);
|
||||
}
|
||||
}
|
||||
{
|
||||
@@ -379,7 +427,7 @@ TEST_CASE("curly") {
|
||||
.method(net::methods::get)
|
||||
.redirections(0)
|
||||
.send();
|
||||
REQUIRE(req.get().code() == 302u);
|
||||
REQUIRE(req.get().http_code() == 302u);
|
||||
}
|
||||
{
|
||||
auto req = net::request_builder()
|
||||
@@ -403,7 +451,7 @@ TEST_CASE("curly") {
|
||||
.method(net::methods::get)
|
||||
.redirections(3)
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
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") {
|
||||
@@ -529,7 +655,7 @@ TEST_CASE("curly_examples") {
|
||||
auto response = request.get();
|
||||
|
||||
// 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 << "Body content: " << response.content.as_string_view() << std::endl;
|
||||
|
||||
@@ -585,9 +711,11 @@ 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.code() << std::endl;
|
||||
std::cout << "Status code: " << response.http_code() << std::endl;
|
||||
} else {
|
||||
// throws net::exception because a response is unavailable
|
||||
// auto response = request.get();
|
||||
@@ -598,6 +726,21 @@ TEST_CASE("curly_examples") {
|
||||
// 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") {
|
||||
{
|
||||
class file_dowloader : public net::download_handler {
|
||||
@@ -648,4 +791,20 @@ TEST_CASE("curly_examples") {
|
||||
.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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user