Merge pull request #12 from BlackMATov/dev

Dev
This commit is contained in:
2019-07-02 06:06:41 +07:00
committed by GitHub
5 changed files with 451 additions and 103 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
@@ -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!

View File

@@ -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_;
};
@@ -170,12 +184,13 @@ namespace curly_hpp
request_builder& verbose(bool v) noexcept;
request_builder& verification(bool v) noexcept;
request_builder& redirections(std::uint32_t r) noexcept;
request_builder& request_timeout(time_sec_t t) noexcept;
request_builder& response_timeout(time_sec_t t) noexcept;
request_builder& connection_timeout(time_sec_t t) noexcept;
request_builder& request_timeout(time_ms_t t) noexcept;
request_builder& response_timeout(time_ms_t t) noexcept;
request_builder& connection_timeout(time_ms_t t) noexcept;
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;
@@ -186,13 +201,16 @@ namespace curly_hpp
bool verbose() const noexcept;
bool verification() const noexcept;
std::uint32_t redirections() const noexcept;
time_sec_t request_timeout() const noexcept;
time_sec_t response_timeout() const noexcept;
time_sec_t connection_timeout() const noexcept;
time_ms_t request_timeout() const noexcept;
time_ms_t response_timeout() const noexcept;
time_ms_t connection_timeout() const noexcept;
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_;
@@ -219,11 +243,12 @@ namespace curly_hpp
bool verbose_{false};
bool verification_{false};
std::uint32_t redirections_{10u};
time_sec_t request_timeout_{~0u};
time_sec_t response_timeout_{60u};
time_sec_t connection_timeout_{20u};
time_ms_t request_timeout_{time_sec_t{~0u}};
time_ms_t response_timeout_{time_sec_t{60u}};
time_ms_t connection_timeout_{time_sec_t{20u}};
private:
content_t content_;
callback_t callback_;
uploader_uptr uploader_;
downloader_uptr downloader_;
};

View File

@@ -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_);
}
}
@@ -404,14 +401,14 @@ namespace curly_hpp
curl_easy_setopt(curlh_.get(), CURLOPT_FOLLOWLOCATION, 0l);
}
curl_easy_setopt(curlh_.get(), CURLOPT_TIMEOUT,
static_cast<long>(std::max(time_sec_t(1), breq_.request_timeout()).count()));
curl_easy_setopt(curlh_.get(), CURLOPT_TIMEOUT_MS,
static_cast<long>(std::max(time_ms_t(1), breq_.request_timeout()).count()));
curl_easy_setopt(curlh_.get(), CURLOPT_CONNECTTIMEOUT,
static_cast<long>(std::max(time_sec_t(1), breq_.connection_timeout()).count()));
curl_easy_setopt(curlh_.get(), CURLOPT_CONNECTTIMEOUT_MS,
static_cast<long>(std::max(time_ms_t(1), breq_.connection_timeout()).count()));
last_response_ = time_point_t::clock::now();
response_timeout_ = std::max(time_sec_t(1), breq_.response_timeout());
response_timeout_ = std::max(time_ms_t(1), breq_.response_timeout());
if ( CURLM_OK != curl_multi_add_handle(curlm, curlh_.get()) ) {
throw exception("curly_hpp: failed to curl_multi_add_handle");
@@ -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();
}
}
// -----------------------------------------------------------------------------
@@ -734,17 +801,17 @@ namespace curly_hpp
return *this;
}
request_builder& request_builder::request_timeout(time_sec_t t) noexcept {
request_builder& request_builder::request_timeout(time_ms_t t) noexcept {
request_timeout_ = t;
return *this;
}
request_builder& request_builder::response_timeout(time_sec_t t) noexcept {
request_builder& request_builder::response_timeout(time_ms_t t) noexcept {
response_timeout_ = t;
return *this;
}
request_builder& request_builder::connection_timeout(time_sec_t t) noexcept {
request_builder& request_builder::connection_timeout(time_ms_t t) noexcept {
connection_timeout_ = t;
return *this;
}
@@ -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;
@@ -793,15 +865,15 @@ namespace curly_hpp
return redirections_;
}
time_sec_t request_builder::request_timeout() const noexcept {
time_ms_t request_builder::request_timeout() const noexcept {
return request_timeout_;
}
time_sec_t request_builder::response_timeout() const noexcept {
time_ms_t request_builder::response_timeout() const noexcept {
return response_timeout_;
}
time_sec_t request_builder::connection_timeout() const noexcept {
time_ms_t request_builder::connection_timeout() const noexcept {
return connection_timeout_;
}
@@ -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;

View File

@@ -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()

View File

@@ -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
}
}