Merge pull request #14 from BlackMATov/dev

Dev
This commit is contained in:
2019-07-07 15:39:37 +07:00
committed by GitHub
5 changed files with 621 additions and 358 deletions

View File

@@ -51,9 +51,22 @@ endif()
# library
#
file(GLOB_RECURSE CURLY_HPP_HEADERS
headers/curly.hpp/*.hpp
headers/curly.hpp/*.inl)
file(GLOB_RECURSE CURLY_HPP_SOURCES
sources/curly.hpp/*.cpp
sources/curly.hpp/*.hpp
sources/curly.hpp/*.inl)
add_library(${PROJECT_NAME} STATIC
headers/curly.hpp/curly.hpp
sources/curly.cpp)
${CURLY_HPP_HEADERS}
${CURLY_HPP_SOURCES})
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES
${CURLY_HPP_HEADERS}
${CURLY_HPP_SOURCES})
target_include_directories(${PROJECT_NAME}
PUBLIC headers)

View File

@@ -56,7 +56,7 @@ target_link_libraries(your_project_target curly.hpp)
namespace net = curly_hpp;
// creates and hold a separate thread for automatically update async requests
net::auto_performer performer;
net::performer performer;
// also, you can update requests manually from your favorite thread
net::perform();
@@ -67,12 +67,12 @@ net::perform();
```cpp
// makes a GET request and async send it
auto request = net::request_builder()
.method(net::methods::get)
.method(net::http_method::GET)
.url("http://www.httpbin.org/get")
.send();
// synchronous waits and get a response
auto response = request.get();
// synchronous waits and take a response
auto response = request.take();
// prints results
std::cout << "Status code: " << response.http_code() << std::endl;
@@ -97,13 +97,13 @@ std::cout << "Body content: " << response.content.as_string_view() << std::endl;
```cpp
auto request = net::request_builder()
.method(net::methods::post)
.method(net::http_method::POST)
.url("http://www.httpbin.org/post")
.header("Content-Type", "application/json")
.content(R"({"hello" : "world"})")
.send();
auto response = request.get();
auto response = request.take();
std::cout << "Body content: " << response.content.as_string_view() << std::endl;
std::cout << "Content Length: " << response.headers["content-length"] << std::endl;
@@ -138,11 +138,11 @@ auto request = net::request_builder()
request.wait();
if ( request.is_done() ) {
auto response = request.get();
auto response = request.take();
std::cout << "Status code: " << response.http_code() << std::endl;
} else {
// throws net::exception because a response is unavailable
// auto response = request.get();
// auto response = request.take();
std::cout << "Error message: " << request.get_error() << std::endl;
}
@@ -156,7 +156,7 @@ if ( request.is_done() ) {
auto request = net::request_builder("http://www.httpbin.org/get")
.callback([](net::request request){
if ( request.is_done() ) {
auto response = request.get();
auto response = request.take();
std::cout << "Status code: " << response.http_code() << std::endl;
} else {
std::cout << "Error message: " << request.get_error() << std::endl;
@@ -217,7 +217,7 @@ private:
};
net::request_builder()
.method(net::methods::post)
.method(net::http_method::POST)
.url("https://httpbin.org/anything")
.uploader<file_uploader>("image.jpeg")
.send().wait();

View File

@@ -6,51 +6,48 @@
#pragma once
#include <cctype>
#include <cassert>
#include <cstring>
#include <cstdint>
#include <cstddef>
#include <atomic>
#include <chrono>
#include <thread>
#include <map>
#include <chrono>
#include <memory>
#include <string>
#include <vector>
#include <utility>
#include <algorithm>
#include <stdexcept>
#include <functional>
#include <map>
#include <vector>
#include <string>
namespace curly_hpp
{
class exception final : public std::runtime_error {
public:
explicit exception(const char* what);
explicit exception(const std::string& what);
};
struct icase_string_compare final {
using is_transparent = void;
bool operator()(
std::string_view l,
std::string_view r) const noexcept;
};
enum class methods {
put,
get,
head,
post
};
class request;
class response;
class request_builder;
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)>;
enum class http_method {
DEL,
PUT,
GET,
HEAD,
POST,
PATCH,
OPTIONS
};
class upload_handler {
public:
@@ -65,8 +62,51 @@ namespace curly_hpp
virtual std::size_t write(const char* src, std::size_t size) = 0;
};
class progress_handler {
public:
virtual ~progress_handler() {}
virtual float update(
std::size_t dnow, std::size_t dtotal,
std::size_t unow, std::size_t utotal) = 0;
};
using callback_t = std::function<void(request)>;
using uploader_uptr = std::unique_ptr<upload_handler>;
using downloader_uptr = std::unique_ptr<download_handler>;
using progressor_uptr = std::unique_ptr<progress_handler>;
}
namespace curly_hpp
{
class exception final : public std::runtime_error {
public:
explicit exception(const char* what)
: std::runtime_error(what) {}
explicit exception(const std::string& what)
: std::runtime_error(what) {}
};
}
namespace curly_hpp
{
namespace detail
{
struct icase_string_compare final {
using is_transparent = void;
bool operator()(std::string_view l, std::string_view r) const noexcept {
return std::lexicographical_compare(
l.begin(), l.end(), r.begin(), r.end(),
[](const char lc, const char rc) noexcept {
return std::tolower(lc) < std::tolower(rc);
});
}
};
}
using headers_t = std::map<
std::string, std::string,
detail::icase_string_compare>;
}
namespace curly_hpp
@@ -81,15 +121,31 @@ namespace curly_hpp
content_t(const content_t&) = default;
content_t& operator=(const content_t&) = default;
content_t(std::string_view data);
content_t(std::vector<char> data) noexcept;
content_t(std::string_view data)
: data_(data.cbegin(), data.cend() ) {}
std::size_t size() const noexcept;
std::vector<char>& data() noexcept;
const std::vector<char>& data() const noexcept;
content_t(std::vector<char> data) noexcept
: data_(std::move(data)) {}
std::string as_string_copy() const;
std::string_view as_string_view() const noexcept;
std::size_t size() const noexcept {
return data_.size();
}
std::vector<char>& data() noexcept {
return data_;
}
const std::vector<char>& data() const noexcept {
return data_;
}
std::string as_string_copy() const {
return {data_.data(), data_.size()};
}
std::string_view as_string_view() const noexcept {
return {data_.data(), data_.size()};
}
private:
std::vector<char> data_;
};
@@ -107,15 +163,22 @@ namespace curly_hpp
response(const response&) = delete;
response& operator=(const response&) = delete;
explicit response(http_code_t c) noexcept;
explicit response(http_code_t c) noexcept
: http_code_(c) {}
bool is_http_error() const noexcept;
http_code_t http_code() const noexcept;
bool is_http_error() const noexcept {
return http_code_ >= 400u;
}
http_code_t http_code() const noexcept {
return http_code_;
}
public:
content_t content;
headers_t headers;
uploader_uptr uploader;
downloader_uptr downloader;
progressor_uptr progressor;
private:
http_code_t http_code_{0u};
};
@@ -123,16 +186,16 @@ namespace curly_hpp
namespace curly_hpp
{
enum class req_status {
done,
empty,
failed,
timeout,
pending,
canceled
};
class request final {
public:
enum class statuses {
done,
empty,
failed,
timeout,
pending,
canceled
};
public:
class internal_state;
using internal_state_ptr = std::shared_ptr<internal_state>;
@@ -140,20 +203,21 @@ namespace curly_hpp
request(internal_state_ptr);
bool cancel() noexcept;
statuses status() const noexcept;
float progress() const noexcept;
req_status 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;
req_status wait() const noexcept;
req_status wait_for(time_ms_t ms) const noexcept;
req_status 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;
req_status wait_callback() const noexcept;
req_status wait_callback_for(time_ms_t ms) const noexcept;
req_status wait_callback_until(time_point_t tp) const noexcept;
response get();
response take();
const std::string& get_error() const noexcept;
std::exception_ptr get_callback_exception() const noexcept;
private:
@@ -173,12 +237,12 @@ namespace curly_hpp
request_builder(const request_builder&) = delete;
request_builder& operator=(const request_builder&) = delete;
explicit request_builder(methods m) noexcept;
explicit request_builder(http_method m) noexcept;
explicit request_builder(std::string u) noexcept;
explicit request_builder(methods m, std::string u) noexcept;
explicit request_builder(http_method m, std::string u) noexcept;
request_builder& url(std::string u) noexcept;
request_builder& method(methods m) noexcept;
request_builder& method(http_method m) noexcept;
request_builder& header(std::string key, std::string value);
request_builder& verbose(bool v) noexcept;
@@ -193,9 +257,10 @@ namespace curly_hpp
request_builder& callback(callback_t c) noexcept;
request_builder& uploader(uploader_uptr u) noexcept;
request_builder& downloader(downloader_uptr d) noexcept;
request_builder& progressor(progressor_uptr p) noexcept;
const std::string& url() const noexcept;
methods method() const noexcept;
http_method method() const noexcept;
const headers_t& headers() const noexcept;
bool verbose() const noexcept;
@@ -217,6 +282,9 @@ namespace curly_hpp
downloader_uptr& downloader() noexcept;
const downloader_uptr& downloader() const noexcept;
progressor_uptr& progressor() noexcept;
const progressor_uptr& progressor() const noexcept;
request send();
template < typename Callback >
@@ -236,9 +304,15 @@ namespace curly_hpp
static_assert(std::is_base_of_v<download_handler, Downloader>);
return downloader(std::make_unique<Downloader>(std::forward<Args>(args)...));
}
template < typename Progressor, typename... Args >
request_builder& progressor(Args&&... args) {
static_assert(std::is_base_of_v<progress_handler, Progressor>);
return progressor(std::make_unique<Progressor>(std::forward<Args>(args)...));
}
private:
std::string url_;
methods method_{methods::get};
http_method method_{http_method::GET};
headers_t headers_;
bool verbose_{false};
bool verification_{false};
@@ -251,15 +325,16 @@ namespace curly_hpp
callback_t callback_;
uploader_uptr uploader_;
downloader_uptr downloader_;
progressor_uptr progressor_;
};
}
namespace curly_hpp
{
class auto_performer final {
class performer final {
public:
auto_performer();
~auto_performer() noexcept;
performer();
~performer() noexcept;
time_ms_t wait_activity() const noexcept;
void wait_activity(time_ms_t ms) noexcept;

View File

@@ -6,13 +6,9 @@
#include <curly.hpp/curly.hpp>
#include <cctype>
#include <cassert>
#include <cstring>
#include <mutex>
#include <queue>
#include <algorithm>
#include <type_traits>
#include <condition_variable>
#ifndef NOMINMAX
@@ -81,6 +77,23 @@ namespace
private:
data_t& data_;
};
class default_progressor final : public progress_handler {
public:
default_progressor() = default;
float update(
std::size_t dnow, std::size_t dtotal,
std::size_t unow, std::size_t utotal) override
{
double now_d = static_cast<double>(dnow + unow);
double total_d = static_cast<double>(dtotal + utotal);
double progress_d = total_d > 0.0
? static_cast<float>(now_d / total_d)
: 0.f;
return static_cast<float>(std::min(std::max(progress_d, 0.0), 1.0));
}
};
}
// -----------------------------------------------------------------------------
@@ -96,18 +109,26 @@ namespace
template < typename T >
class mt_queue final {
public:
void enqueue(T v) {
void enqueue(T&& v) {
std::lock_guard<std::mutex> guard(mutex_);
queue_.push(std::move(v));
cvar_.notify_all();
}
bool try_dequeue(T& v) {
void enqueue(const T& v) {
std::lock_guard<std::mutex> guard(mutex_);
queue_.push(v);
cvar_.notify_all();
}
bool try_dequeue(T& v) noexcept(
std::is_nothrow_move_assignable_v<T>)
{
std::lock_guard<std::mutex> guard(mutex_);
if ( queue_.empty() ) {
return false;
}
v = queue_.front();
v = std::move(queue_.front());
queue_.pop();
return true;
}
@@ -117,9 +138,25 @@ namespace
return queue_.empty();
}
bool wait_content_for(time_ms_t ms) const noexcept {
void wait() const noexcept {
std::unique_lock<std::mutex> lock(mutex_);
return cvar_.wait_for(lock, ms, [this](){
cvar_.wait(lock, [this](){
return !queue_.empty();
});
}
template < typename Rep, typename Period >
bool wait_for(const std::chrono::duration<Rep, Period> duration) const noexcept {
std::unique_lock<std::mutex> lock(mutex_);
return cvar_.wait_for(lock, duration, [this](){
return !queue_.empty();
});
}
template < typename Clock, typename Duration >
bool wait_until(const std::chrono::time_point<Clock, Duration>& time) const {
std::unique_lock<std::mutex> lock(mutex_);
return cvar_.wait_until(lock, time, [this](){
return !queue_.empty();
});
}
@@ -213,96 +250,6 @@ namespace
std::unique_ptr<curl_state> curl_state::self_;
}
// -----------------------------------------------------------------------------
//
// exception
//
// -----------------------------------------------------------------------------
namespace curly_hpp
{
exception::exception(const char* what)
: std::runtime_error(what) {}
exception::exception(const std::string& what)
: std::runtime_error(what) {}
}
// -----------------------------------------------------------------------------
//
// icase_string_compare
//
// -----------------------------------------------------------------------------
namespace curly_hpp
{
bool icase_string_compare::operator()(
std::string_view l,
std::string_view r) const noexcept
{
return std::lexicographical_compare(
l.begin(), l.end(), r.begin(), r.end(),
[](const auto lc, const auto rc) noexcept {
return std::tolower(lc) < std::tolower(rc);
});
}
}
// -----------------------------------------------------------------------------
//
// content_t
//
// -----------------------------------------------------------------------------
namespace curly_hpp
{
content_t::content_t(std::string_view data)
: data_(data.cbegin(), data.cend() ) {}
content_t::content_t(std::vector<char> data) noexcept
: data_(std::move(data)) {}
std::size_t content_t::size() const noexcept {
return data_.size();
}
std::vector<char>& content_t::data() noexcept {
return data_;
}
const std::vector<char>& content_t::data() const noexcept {
return data_;
}
std::string content_t::as_string_copy() const {
return {data_.data(), data_.size()};
}
std::string_view content_t::as_string_view() const noexcept {
return {data_.data(), data_.size()};
}
}
// -----------------------------------------------------------------------------
//
// response
//
// -----------------------------------------------------------------------------
namespace curly_hpp
{
response::response(http_code_t c) noexcept
: http_code_(c) {}
bool response::is_http_error() const noexcept {
return http_code_ >= 400u;
}
http_code_t response::http_code() const noexcept {
return http_code_;
}
}
// -----------------------------------------------------------------------------
//
// request
@@ -323,6 +270,10 @@ namespace curly_hpp
if ( !breq_.downloader() ) {
breq_.downloader<default_downloader>(&response_content_);
}
if ( !breq_.progressor() ) {
breq_.progressor<default_progressor>();
}
}
void enqueue(CURLM* curlm) {
@@ -357,6 +308,10 @@ namespace curly_hpp
curl_easy_setopt(curlh_.get(), CURLOPT_WRITEDATA, this);
curl_easy_setopt(curlh_.get(), CURLOPT_WRITEFUNCTION, &s_download_callback_);
curl_easy_setopt(curlh_.get(), CURLOPT_NOPROGRESS, 0l);
curl_easy_setopt(curlh_.get(), CURLOPT_XFERINFODATA, this);
curl_easy_setopt(curlh_.get(), CURLOPT_XFERINFOFUNCTION, &s_progress_callback_);
curl_easy_setopt(curlh_.get(), CURLOPT_HEADERDATA, this);
curl_easy_setopt(curlh_.get(), CURLOPT_HEADERFUNCTION, &s_header_callback_);
@@ -365,22 +320,38 @@ namespace curly_hpp
curl_easy_setopt(curlh_.get(), CURLOPT_VERBOSE, breq_.verbose() ? 1l : 0l);
switch ( breq_.method() ) {
case methods::put:
case http_method::DEL:
curl_easy_setopt(curlh_.get(), CURLOPT_CUSTOMREQUEST, "DELETE");
curl_easy_setopt(curlh_.get(), CURLOPT_POST, 1l);
curl_easy_setopt(curlh_.get(), CURLOPT_POSTFIELDSIZE_LARGE,
static_cast<curl_off_t>(breq_.uploader()->size()));
break;
case http_method::PUT:
curl_easy_setopt(curlh_.get(), CURLOPT_UPLOAD, 1l);
curl_easy_setopt(curlh_.get(), CURLOPT_INFILESIZE_LARGE,
static_cast<curl_off_t>(breq_.uploader()->size()));
break;
case methods::get:
case http_method::GET:
curl_easy_setopt(curlh_.get(), CURLOPT_HTTPGET, 1l);
break;
case methods::head:
case http_method::HEAD:
curl_easy_setopt(curlh_.get(), CURLOPT_NOBODY, 1l);
break;
case methods::post:
case http_method::POST:
curl_easy_setopt(curlh_.get(), CURLOPT_POST, 1l);
curl_easy_setopt(curlh_.get(), CURLOPT_POSTFIELDSIZE_LARGE,
static_cast<curl_off_t>(breq_.uploader()->size()));
break;
case http_method::PATCH:
curl_easy_setopt(curlh_.get(), CURLOPT_CUSTOMREQUEST, "PATCH");
curl_easy_setopt(curlh_.get(), CURLOPT_UPLOAD, 1l);
curl_easy_setopt(curlh_.get(), CURLOPT_INFILESIZE_LARGE,
static_cast<curl_off_t>(breq_.uploader()->size()));
break;
case http_method::OPTIONS:
curl_easy_setopt(curlh_.get(), CURLOPT_CUSTOMREQUEST, "OPTIONS");
curl_easy_setopt(curlh_.get(), CURLOPT_NOBODY, 1l);
break;
default:
throw exception("curly_hpp: unexpected request method");
}
@@ -418,6 +389,21 @@ namespace curly_hpp
void dequeue(CURLM* curlm) noexcept {
std::lock_guard<std::mutex> guard(mutex_);
if ( curlh_ ) {
curl_easy_setopt(curlh_.get(), CURLOPT_PRIVATE, nullptr);
curl_easy_setopt(curlh_.get(), CURLOPT_READDATA, nullptr);
curl_easy_setopt(curlh_.get(), CURLOPT_READFUNCTION, nullptr);
curl_easy_setopt(curlh_.get(), CURLOPT_WRITEDATA, nullptr);
curl_easy_setopt(curlh_.get(), CURLOPT_WRITEFUNCTION, nullptr);
curl_easy_setopt(curlh_.get(), CURLOPT_NOPROGRESS, 1l);
curl_easy_setopt(curlh_.get(), CURLOPT_XFERINFODATA, nullptr);
curl_easy_setopt(curlh_.get(), CURLOPT_XFERINFOFUNCTION, nullptr);
curl_easy_setopt(curlh_.get(), CURLOPT_HEADERDATA, nullptr);
curl_easy_setopt(curlh_.get(), CURLOPT_HEADERFUNCTION, nullptr);
curl_multi_remove_handle(curlm, curlh_.get());
curlh_.reset();
}
@@ -425,7 +411,7 @@ namespace curly_hpp
bool done() noexcept {
std::lock_guard<std::mutex> guard(mutex_);
if ( status_ != statuses::pending ) {
if ( status_ != req_status::pending ) {
return false;
}
@@ -435,7 +421,7 @@ namespace curly_hpp
CURLINFO_RESPONSE_CODE,
&http_code) || !http_code )
{
status_ = statuses::failed;
status_ = req_status::failed;
cvar_.notify_all();
return false;
}
@@ -446,13 +432,15 @@ namespace curly_hpp
response_.headers = std::move(response_headers_);
response_.uploader = std::move(breq_.uploader());
response_.downloader = std::move(breq_.downloader());
response_.progressor = std::move(breq_.progressor());
} catch (...) {
status_ = statuses::failed;
status_ = req_status::failed;
cvar_.notify_all();
return false;
}
status_ = statuses::done;
progress_ = 1.f;
status_ = req_status::done;
error_.clear();
cvar_.notify_all();
@@ -461,21 +449,21 @@ namespace curly_hpp
bool fail(CURLcode err) noexcept {
std::lock_guard<std::mutex> guard(mutex_);
if ( status_ != statuses::pending ) {
if ( status_ != req_status::pending ) {
return false;
}
switch ( err ) {
case CURLE_OPERATION_TIMEDOUT:
status_ = statuses::timeout;
status_ = req_status::timeout;
break;
case CURLE_READ_ERROR:
case CURLE_WRITE_ERROR:
case CURLE_ABORTED_BY_CALLBACK:
status_ = statuses::canceled;
status_ = req_status::canceled;
break;
default:
status_ = statuses::failed;
status_ = req_status::failed;
break;
}
@@ -491,75 +479,80 @@ namespace curly_hpp
bool cancel() noexcept {
std::lock_guard<std::mutex> guard(mutex_);
if ( status_ != statuses::pending ) {
if ( status_ != req_status::pending ) {
return false;
}
status_ = statuses::canceled;
status_ = req_status::canceled;
error_.clear();
cvar_.notify_all();
return true;
}
statuses status() const noexcept {
float progress() const noexcept {
std::lock_guard<std::mutex> guard(mutex_);
return progress_;
}
req_status status() const noexcept {
std::lock_guard<std::mutex> guard(mutex_);
return status_;
}
bool is_done() const noexcept {
std::lock_guard<std::mutex> guard(mutex_);
return status_ == statuses::done;
return status_ == req_status::done;
}
bool is_pending() const noexcept {
std::lock_guard<std::mutex> guard(mutex_);
return status_ == statuses::pending;
return status_ == req_status::pending;
}
statuses wait(bool wait_callback) const noexcept {
req_status wait(bool wait_callback) const noexcept {
std::unique_lock<std::mutex> lock(mutex_);
cvar_.wait(lock, [this, wait_callback](){
return (status_ != statuses::pending)
return (status_ != req_status::pending)
&& (!wait_callback || callbacked_);
});
return status_;
}
statuses wait_for(time_ms_t ms, bool wait_callback) const noexcept {
req_status wait_for(time_ms_t ms, bool wait_callback) const noexcept {
std::unique_lock<std::mutex> lock(mutex_);
cvar_.wait_for(lock, ms, [this, wait_callback](){
return (status_ != statuses::pending)
return (status_ != req_status::pending)
&& (!wait_callback || callbacked_);
});
return status_;
}
statuses wait_until(time_point_t tp, bool wait_callback) const noexcept {
req_status wait_until(time_point_t tp, bool wait_callback) const noexcept {
std::unique_lock<std::mutex> lock(mutex_);
cvar_.wait_until(lock, tp, [this, wait_callback](){
return (status_ != statuses::pending)
return (status_ != req_status::pending)
&& (!wait_callback || callbacked_);
});
return status_;
}
response get() {
response take() {
std::unique_lock<std::mutex> lock(mutex_);
cvar_.wait(lock, [this](){
return status_ != statuses::pending;
return status_ != req_status::pending;
});
if ( status_ != statuses::done ) {
if ( status_ != req_status::done ) {
throw exception("curly_hpp: response is unavailable");
}
status_ = statuses::empty;
status_ = req_status::empty;
return std::move(response_);
}
const std::string& get_error() const noexcept {
std::unique_lock<std::mutex> lock(mutex_);
cvar_.wait(lock, [this](){
return status_ != statuses::pending;
return status_ != req_status::pending;
});
return error_;
}
@@ -583,7 +576,7 @@ namespace curly_hpp
callback_exception_ = std::current_exception();
}
std::lock_guard<std::mutex> guard(mutex_);
assert(!callbacked_ && status_ != statuses::pending);
assert(!callbacked_ && status_ != req_status::pending);
callbacked_ = true;
cvar_.notify_all();
}
@@ -607,6 +600,13 @@ namespace curly_hpp
return self->download_callback_(ptr, size * nmemb);
}
static int s_progress_callback_(
void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) noexcept
{
auto* self = static_cast<internal_state*>(clientp);
return self->progress_callback(dlnow, dltotal, ulnow, ultotal);
}
static std::size_t s_header_callback_(
char* buffer, std::size_t size, std::size_t nitems, void* userdata) noexcept
{
@@ -643,6 +643,26 @@ namespace curly_hpp
}
}
int progress_callback(
curl_off_t dlnow, curl_off_t dltotal,
curl_off_t ulnow, curl_off_t ultotal) noexcept
{
try {
std::lock_guard<std::mutex> guard(mutex_);
std::size_t dnow_sz = dlnow > 0 ? static_cast<std::size_t>(dlnow) : 0u;
std::size_t dtotal_sz = dltotal > 0 ? static_cast<std::size_t>(dltotal) : 0u;
std::size_t unow_sz = ulnow > 0 ? static_cast<std::size_t>(ulnow) : 0u;
std::size_t utotal_sz = ultotal > 0 ? static_cast<std::size_t>(ultotal) : 0u;
progress_ = breq_.progressor()->update(dnow_sz, dtotal_sz, unow_sz, utotal_sz);
return 0;
} catch (...) {
return 1;
}
}
std::size_t header_callback_(const char* src, std::size_t size) noexcept {
try {
std::lock_guard<std::mutex> guard(mutex_);
@@ -685,7 +705,8 @@ namespace curly_hpp
bool callbacked_{false};
std::exception_ptr callback_exception_{nullptr};
private:
statuses status_{statuses::pending};
float progress_{0.f};
req_status status_{req_status::pending};
std::string error_{"Unknown error"};
private:
mutable std::mutex mutex_;
@@ -704,7 +725,11 @@ namespace curly_hpp
return state_->cancel();
}
request::statuses request::status() const noexcept {
float request::progress() const noexcept {
return state_->progress();
}
req_status request::status() const noexcept {
return state_->status();
}
@@ -716,32 +741,32 @@ namespace curly_hpp
return state_->is_pending();
}
request::statuses request::wait() const noexcept {
req_status request::wait() const noexcept {
return state_->wait(false);
}
request::statuses request::wait_for(time_ms_t ms) const noexcept {
req_status request::wait_for(time_ms_t ms) const noexcept {
return state_->wait_for(ms, false);
}
request::statuses request::wait_until(time_point_t tp) const noexcept {
req_status request::wait_until(time_point_t tp) const noexcept {
return state_->wait_until(tp, false);
}
request::statuses request::wait_callback() const noexcept {
req_status request::wait_callback() const noexcept {
return state_->wait(true);
}
request::statuses request::wait_callback_for(time_ms_t ms) const noexcept {
req_status 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 {
req_status request::wait_callback_until(time_point_t tp) const noexcept {
return state_->wait_until(tp, true);
}
response request::get() {
return state_->get();
response request::take() {
return state_->take();
}
const std::string& request::get_error() const noexcept {
@@ -761,13 +786,13 @@ namespace curly_hpp
namespace curly_hpp
{
request_builder::request_builder(methods m) noexcept
request_builder::request_builder(http_method m) noexcept
: method_(m) {}
request_builder::request_builder(std::string u) noexcept
: url_(std::move(u)) {}
request_builder::request_builder(methods m, std::string u) noexcept
request_builder::request_builder(http_method m, std::string u) noexcept
: url_(std::move(u))
, method_(m) {}
@@ -776,7 +801,7 @@ namespace curly_hpp
return *this;
}
request_builder& request_builder::method(methods m) noexcept {
request_builder& request_builder::method(http_method m) noexcept {
method_ = m;
return *this;
}
@@ -841,11 +866,16 @@ namespace curly_hpp
return *this;
}
request_builder& request_builder::progressor(progressor_uptr p) noexcept {
progressor_ = std::move(p);
return *this;
}
const std::string& request_builder::url() const noexcept {
return url_;
}
methods request_builder::method() const noexcept {
http_method request_builder::method() const noexcept {
return method_;
}
@@ -909,6 +939,14 @@ namespace curly_hpp
return downloader_;
}
progressor_uptr& request_builder::progressor() noexcept {
return progressor_;
}
const progressor_uptr& request_builder::progressor() const noexcept {
return progressor_;
}
request request_builder::send() {
auto sreq = std::make_shared<request::internal_state>(std::move(*this));
new_handles.enqueue(sreq);
@@ -918,13 +956,13 @@ namespace curly_hpp
// -----------------------------------------------------------------------------
//
// auto_performer
// performer
//
// -----------------------------------------------------------------------------
namespace curly_hpp
{
auto_performer::auto_performer() {
performer::performer() {
thread_ = std::thread([this](){
while ( !done_ ) {
curly_hpp::perform();
@@ -933,18 +971,18 @@ namespace curly_hpp
});
}
auto_performer::~auto_performer() noexcept {
performer::~performer() noexcept {
done_.store(true);
if ( thread_.joinable() ) {
thread_.join();
}
}
time_ms_t auto_performer::wait_activity() const noexcept {
time_ms_t performer::wait_activity() const noexcept {
return wait_activity_;
}
void auto_performer::wait_activity(time_ms_t ms) noexcept {
void performer::wait_activity(time_ms_t ms) noexcept {
wait_activity_ = ms;
}
}
@@ -1022,7 +1060,7 @@ namespace curly_hpp
void wait_activity(time_ms_t ms) {
curl_state::with([ms](CURLM* curlm){
if ( active_handles.empty() ) {
new_handles.wait_content_for(ms);
new_handles.wait_for(ms);
} else if ( new_handles.empty() ) {
const int timeout_ms = static_cast<int>(ms.count());
if ( CURLM_OK != curl_multi_wait(curlm, nullptr, 0, timeout_ms, nullptr) ) {

View File

@@ -58,6 +58,22 @@ namespace
}
};
class canceled_progressor : public net::progress_handler {
public:
canceled_progressor() = default;
float update(
std::size_t dnow, std::size_t dtotal,
std::size_t unow, std::size_t utotal) override
{
(void)dnow;
(void)dtotal;
(void)unow;
(void)utotal;
throw std::exception();
}
};
netex::promise<net::content_t> download(std::string url) {
return netex::make_promise<net::content_t>([
url = std::move(url)
@@ -68,7 +84,7 @@ namespace
reject(net::exception("network error"));
return;
}
net::response response = request.get();
net::response response = request.take();
if ( response.is_http_error() ) {
reject(net::exception("server error"));
return;
@@ -80,39 +96,39 @@ namespace
}
TEST_CASE("curly") {
net::auto_performer performer;
net::performer performer;
performer.wait_activity(net::time_ms_t(10));
SECTION("wait") {
{
auto req = net::request_builder("https://httpbin.org/delay/1").send();
REQUIRE(req.status() == net::request::statuses::pending);
REQUIRE(req.wait() == net::request::statuses::done);
REQUIRE(req.status() == net::request::statuses::done);
auto resp = req.get();
REQUIRE(req.status() == net::req_status::pending);
REQUIRE(req.wait() == net::req_status::done);
REQUIRE(req.status() == net::req_status::done);
auto resp = req.take();
REQUIRE(resp.http_code() == 200u);
REQUIRE(req.status() == net::request::statuses::empty);
REQUIRE(req.status() == net::req_status::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().http_code() == 200u);
REQUIRE(req.wait_for(net::time_sec_t(1)) == net::req_status::pending);
REQUIRE(req.wait_for(net::time_sec_t(5)) == net::req_status::done);
REQUIRE(req.take().http_code() == 200u);
}
{
auto req = net::request_builder("https://httpbin.org/delay/2").send();
REQUIRE(req.wait_until(net::time_point_t::clock::now() + net::time_sec_t(1))
== net::request::statuses::pending);
== net::req_status::pending);
REQUIRE(req.wait_until(net::time_point_t::clock::now() + net::time_sec_t(5))
== net::request::statuses::done);
REQUIRE(req.get().http_code() == 200u);
== net::req_status::done);
REQUIRE(req.take().http_code() == 200u);
}
}
SECTION("error") {
auto req = net::request_builder("|||").send();
REQUIRE(req.wait() == net::request::statuses::failed);
REQUIRE(req.status() == net::request::statuses::failed);
REQUIRE(req.wait() == net::req_status::failed);
REQUIRE(req.status() == net::req_status::failed);
REQUIRE_FALSE(req.get_error().empty());
}
@@ -120,21 +136,21 @@ TEST_CASE("curly") {
{
auto req = net::request_builder("https://httpbin.org/delay/1").send();
REQUIRE(req.cancel());
REQUIRE(req.status() == net::request::statuses::canceled);
REQUIRE(req.status() == net::req_status::canceled);
REQUIRE(req.get_error().empty());
}
{
auto req = net::request_builder("https://httpbin.org/status/200").send();
REQUIRE(req.wait() == net::request::statuses::done);
REQUIRE(req.wait() == net::req_status::done);
REQUIRE_FALSE(req.cancel());
REQUIRE(req.status() == net::request::statuses::done);
REQUIRE(req.status() == net::req_status::done);
REQUIRE(req.get_error().empty());
}
}
SECTION("is_done/is_pending") {
{
auto req = net::request_builder(net::methods::get)
auto req = net::request_builder(net::http_method::GET)
.url("https://httpbin.org/delay/1")
.send();
REQUIRE_FALSE(req.is_done());
@@ -144,7 +160,7 @@ TEST_CASE("curly") {
REQUIRE_FALSE(req.is_pending());
}
{
auto req = net::request_builder(net::methods::post, "http://www.httpbin.org/post")
auto req = net::request_builder(net::http_method::POST, "http://www.httpbin.org/post")
.url("https://httpbin.org/delay/2")
.request_timeout(net::time_sec_t(1))
.send();
@@ -160,23 +176,23 @@ TEST_CASE("curly") {
SECTION("get") {
{
auto req = net::request_builder("https://httpbin.org/status/204").send();
auto resp = req.get();
REQUIRE(req.status() == net::request::statuses::empty);
auto resp = req.take();
REQUIRE(req.status() == net::req_status::empty);
REQUIRE(resp.http_code() == 204u);
}
{
auto req = net::request_builder("https://httpbin.org/delay/2").send();
REQUIRE(req.cancel());
REQUIRE_THROWS_AS(req.get(), net::exception);
REQUIRE(req.status() == net::request::statuses::canceled);
REQUIRE_THROWS_AS(req.take(), net::exception);
REQUIRE(req.status() == net::req_status::canceled);
}
{
auto req = net::request_builder("https://httpbin.org/delay/2")
.response_timeout(net::time_sec_t(0))
.send();
REQUIRE(req.wait() == net::request::statuses::timeout);
REQUIRE_THROWS_AS(req.get(), net::exception);
REQUIRE(req.status() == net::request::statuses::timeout);
REQUIRE(req.wait() == net::req_status::timeout);
REQUIRE_THROWS_AS(req.take(), net::exception);
REQUIRE(req.status() == net::req_status::timeout);
}
}
@@ -184,77 +200,128 @@ TEST_CASE("curly") {
{
auto req0 = net::request_builder()
.url("https://httpbin.org/put")
.method(net::methods::put)
.method(net::http_method::PUT)
.send();
REQUIRE(req0.get().http_code() == 200u);
REQUIRE(req0.take().http_code() == 200u);
auto req1 = net::request_builder()
.url("https://httpbin.org/put")
.method(net::methods::get)
.method(net::http_method::GET)
.send();
REQUIRE(req1.get().http_code() == 405u);
REQUIRE(req1.take().http_code() == 405u);
auto req2 = net::request_builder()
.url("https://httpbin.org/put")
.method(net::methods::head)
.method(net::http_method::HEAD)
.send();
REQUIRE(req2.get().http_code() == 405u);
REQUIRE(req2.take().http_code() == 405u);
auto req3 = net::request_builder()
.url("https://httpbin.org/put")
.method(net::methods::post)
.method(net::http_method::POST)
.send();
REQUIRE(req3.get().http_code() == 405u);
REQUIRE(req3.take().http_code() == 405u);
auto req4 = net::request_builder()
.url("https://httpbin.org/put")
.method(net::http_method::PATCH)
.send();
REQUIRE(req4.take().http_code() == 405u);
auto req5 = net::request_builder()
.url("https://httpbin.org/put")
.method(net::http_method::DEL)
.send();
REQUIRE(req5.take().http_code() == 405u);
}
{
auto req0 = net::request_builder()
.url("https://httpbin.org/get")
.method(net::methods::put)
.method(net::http_method::PUT)
.send();
REQUIRE(req0.get().http_code() == 405u);
REQUIRE(req0.take().http_code() == 405u);
auto req1 = net::request_builder()
.url("https://httpbin.org/get")
.method(net::methods::get)
.method(net::http_method::GET)
.send();
REQUIRE(req1.get().http_code() == 200u);
REQUIRE(req1.take().http_code() == 200u);
auto req2 = net::request_builder()
.url("https://httpbin.org/get")
.method(net::methods::head)
.method(net::http_method::HEAD)
.send();
REQUIRE(req2.get().http_code() == 200u);
REQUIRE(req2.take().http_code() == 200u);
auto req3 = net::request_builder()
.url("https://httpbin.org/get")
.method(net::methods::post)
.method(net::http_method::POST)
.send();
REQUIRE(req3.get().http_code() == 405u);
REQUIRE(req3.take().http_code() == 405u);
auto req4 = net::request_builder()
.url("https://httpbin.org/get")
.method(net::http_method::PATCH)
.send();
REQUIRE(req4.take().http_code() == 405u);
auto req5 = net::request_builder()
.url("https://httpbin.org/get")
.method(net::http_method::DEL)
.send();
REQUIRE(req5.take().http_code() == 405u);
}
{
auto req0 = net::request_builder()
.url("https://httpbin.org/post")
.method(net::methods::put)
.method(net::http_method::PUT)
.send();
REQUIRE(req0.get().http_code() == 405u);
REQUIRE(req0.take().http_code() == 405u);
auto req1 = net::request_builder()
.url("https://httpbin.org/post")
.method(net::methods::get)
.method(net::http_method::GET)
.send();
REQUIRE(req1.get().http_code() == 405u);
REQUIRE(req1.take().http_code() == 405u);
auto req2 = net::request_builder()
.url("https://httpbin.org/post")
.method(net::methods::head)
.method(net::http_method::HEAD)
.send();
REQUIRE(req2.get().http_code() == 405u);
REQUIRE(req2.take().http_code() == 405u);
auto req3 = net::request_builder()
.url("https://httpbin.org/post")
.method(net::methods::post)
.method(net::http_method::POST)
.send();
REQUIRE(req3.get().http_code() == 200u);
REQUIRE(req3.take().http_code() == 200u);
auto req4 = net::request_builder()
.url("https://httpbin.org/post")
.method(net::http_method::PATCH)
.send();
REQUIRE(req4.take().http_code() == 405u);
auto req5 = net::request_builder()
.url("https://httpbin.org/post")
.method(net::http_method::DEL)
.send();
REQUIRE(req5.take().http_code() == 405u);
}
{
auto req1 = net::request_builder()
.url("https://httpbin.org/put")
.method(net::http_method::OPTIONS)
.send();
const auto allow1 = req1.take().headers.at("Allow");
REQUIRE((allow1 == "PUT, OPTIONS" || allow1 == "OPTIONS, PUT"));
auto req2 = net::request_builder()
.url("https://httpbin.org/post")
.method(net::http_method::OPTIONS)
.send();
const auto allow2 = req2.take().headers.at("Allow");
REQUIRE((allow2 == "POST, OPTIONS" || allow2 == "OPTIONS, POST"));
}
}
@@ -262,30 +329,44 @@ TEST_CASE("curly") {
{
auto req = net::request_builder()
.url("https://httpbin.org/status/200")
.method(net::methods::put)
.method(net::http_method::PUT)
.send();
REQUIRE(req.get().http_code() == 200u);
REQUIRE(req.take().http_code() == 200u);
}
{
auto req = net::request_builder()
.url("https://httpbin.org/status/201")
.method(net::methods::get)
.method(net::http_method::GET)
.send();
REQUIRE(req.get().http_code() == 201u);
REQUIRE(req.take().http_code() == 201u);
}
{
auto req = net::request_builder()
.url("https://httpbin.org/status/202")
.method(net::methods::head)
.method(net::http_method::HEAD)
.send();
REQUIRE(req.get().http_code() == 202u);
REQUIRE(req.take().http_code() == 202u);
}
{
auto req = net::request_builder()
.url("https://httpbin.org/status/203")
.method(net::methods::post)
.method(net::http_method::POST)
.send();
REQUIRE(req.get().http_code() == 203u);
REQUIRE(req.take().http_code() == 203u);
}
{
auto req = net::request_builder()
.url("https://httpbin.org/status/203")
.method(net::http_method::PATCH)
.send();
REQUIRE(req.take().http_code() == 203u);
}
{
auto req = net::request_builder()
.url("https://httpbin.org/status/203")
.method(net::http_method::DEL)
.send();
REQUIRE(req.take().http_code() == 203u);
}
}
@@ -296,7 +377,7 @@ TEST_CASE("curly") {
.header("Custom-Header-2", "custom header value 2")
.header("Custom-Header-3", std::string())
.send();
const auto resp = req.get();
const auto resp = req.take();
const auto content_j = json_parse(resp.content.as_string_view());
REQUIRE(content_j["headers"]["Custom-Header-1"] == "custom_header_value_1");
REQUIRE(content_j["headers"]["Custom-Header-2"] == "custom header value 2");
@@ -307,9 +388,9 @@ TEST_CASE("curly") {
{
auto req = net::request_builder()
.url("https://httpbin.org/response-headers?hello=world&world=hello")
.method(net::methods::get)
.method(net::http_method::GET)
.send();
const auto resp = req.get();
const auto resp = req.take();
const auto content_j = json_parse(resp.content.as_string_view());
REQUIRE(content_j["hello"] == "world");
REQUIRE(content_j["world"] == "hello");
@@ -317,9 +398,9 @@ TEST_CASE("curly") {
{
auto req = net::request_builder()
.url("https://httpbin.org/response-headers?hello=world&world=hello")
.method(net::methods::post)
.method(net::http_method::POST)
.send();
const auto resp = req.get();
const auto resp = req.take();
const auto content_j = json_parse(resp.content.as_string_copy());
REQUIRE(content_j["hello"] == "world");
REQUIRE(content_j["world"] == "hello");
@@ -331,7 +412,7 @@ TEST_CASE("curly") {
auto req = net::request_builder()
.url("https://httpbin.org/base64/SFRUUEJJTiBpcyBhd2Vzb21l")
.send();
const auto resp = req.get();
const auto resp = req.take();
REQUIRE(resp.content.as_string_view() == "HTTPBIN is awesome");
REQUIRE(req.get_error().empty());
}
@@ -340,14 +421,14 @@ TEST_CASE("curly") {
.url("https://httpbin.org/delay/10")
.request_timeout(net::time_sec_t(0))
.send();
REQUIRE(req0.wait() == net::request::statuses::timeout);
REQUIRE(req0.wait() == net::req_status::timeout);
REQUIRE_FALSE(req0.get_error().empty());
auto req1 = net::request_builder()
.url("https://httpbin.org/delay/10")
.response_timeout(net::time_sec_t(0))
.send();
REQUIRE(req1.wait() == net::request::statuses::timeout);
REQUIRE(req1.wait() == net::req_status::timeout);
REQUIRE_FALSE(req1.get_error().empty());
}
{
@@ -355,14 +436,14 @@ TEST_CASE("curly") {
.url("https://httpbin.org/delay/10")
.request_timeout(net::time_sec_t(1))
.send();
REQUIRE(req0.wait() == net::request::statuses::timeout);
REQUIRE(req0.wait() == net::req_status::timeout);
REQUIRE_FALSE(req0.get_error().empty());
auto req1 = net::request_builder()
.url("https://httpbin.org/delay/10")
.response_timeout(net::time_sec_t(1))
.send();
REQUIRE(req1.wait() == net::request::statuses::timeout);
REQUIRE(req1.wait() == net::req_status::timeout);
REQUIRE_FALSE(req1.get_error().empty());
}
}
@@ -371,11 +452,25 @@ TEST_CASE("curly") {
{
auto resp = net::request_builder()
.url("https://httpbin.org/image/png")
.method(net::methods::get)
.send().get();
.method(net::http_method::HEAD)
.send().take();
REQUIRE(resp.http_code() == 200u);
REQUIRE(resp.headers.count("Content-Type"));
REQUIRE(resp.headers.count("Content-Length"));
REQUIRE(resp.headers.at("Content-Type") == "image/png");
REQUIRE(resp.headers.at("Content-Length") == std::to_string(untests::png_data_length));
REQUIRE_FALSE(resp.content.size());
}
{
auto resp = net::request_builder()
.url("https://httpbin.org/image/png")
.method(net::http_method::GET)
.send().take();
REQUIRE(resp.http_code() == 200u);
REQUIRE(resp.headers.count("Content-Type"));
REQUIRE(resp.headers.count("Content-Length"));
REQUIRE(resp.headers.at("Content-Type") == "image/png");
REQUIRE(resp.headers.at("Content-Length") == std::to_string(untests::png_data_length));
REQUIRE(untests::png_data_length == resp.content.size());
REQUIRE(!std::memcmp(
std::move(resp.content).data().data(),
@@ -384,11 +479,25 @@ TEST_CASE("curly") {
{
auto resp = net::request_builder()
.url("https://httpbin.org/image/jpeg")
.method(net::methods::get)
.send().get();
.method(net::http_method::HEAD)
.send().take();
REQUIRE(resp.http_code() == 200u);
REQUIRE(resp.headers.count("Content-Type"));
REQUIRE(resp.headers.count("Content-Length"));
REQUIRE(resp.headers.at("Content-Type") == "image/jpeg");
REQUIRE(resp.headers.at("Content-Length") == std::to_string(untests::jpeg_data_length));
REQUIRE_FALSE(resp.content.size());
}
{
auto resp = net::request_builder()
.url("https://httpbin.org/image/jpeg")
.method(net::http_method::GET)
.send().take();
REQUIRE(resp.http_code() == 200u);
REQUIRE(resp.headers.count("Content-Type"));
REQUIRE(resp.headers.count("Content-Length"));
REQUIRE(resp.headers.at("Content-Type") == "image/jpeg");
REQUIRE(resp.headers.at("Content-Length") == std::to_string(untests::jpeg_data_length));
REQUIRE(untests::jpeg_data_length == resp.content.size());
REQUIRE(!std::memcmp(
std::as_const(resp.content).data().data(),
@@ -401,57 +510,57 @@ TEST_CASE("curly") {
{
auto req = net::request_builder()
.url("https://httpbin.org/redirect/2")
.method(net::methods::get)
.method(net::http_method::GET)
.send();
REQUIRE(req.get().http_code() == 200u);
REQUIRE(req.take().http_code() == 200u);
}
{
auto req = net::request_builder()
.url("https://httpbin.org/absolute-redirect/2")
.method(net::methods::get)
.method(net::http_method::GET)
.send();
REQUIRE(req.get().http_code() == 200u);
REQUIRE(req.take().http_code() == 200u);
}
{
auto req = net::request_builder()
.url("https://httpbin.org/relative-redirect/2")
.method(net::methods::get)
.method(net::http_method::GET)
.send();
REQUIRE(req.get().http_code() == 200u);
REQUIRE(req.take().http_code() == 200u);
}
}
{
{
auto req = net::request_builder()
.url("https://httpbin.org/redirect/3")
.method(net::methods::get)
.method(net::http_method::GET)
.redirections(0)
.send();
REQUIRE(req.get().http_code() == 302u);
REQUIRE(req.take().http_code() == 302u);
}
{
auto req = net::request_builder()
.url("https://httpbin.org/redirect/3")
.method(net::methods::get)
.method(net::http_method::GET)
.redirections(1)
.send();
REQUIRE(req.wait() == net::request::statuses::failed);
REQUIRE(req.wait() == net::req_status::failed);
}
{
auto req = net::request_builder()
.url("https://httpbin.org/redirect/3")
.method(net::methods::get)
.method(net::http_method::GET)
.redirections(2)
.send();
REQUIRE(req.wait() == net::request::statuses::failed);
REQUIRE(req.wait() == net::req_status::failed);
}
{
auto req = net::request_builder()
.url("https://httpbin.org/redirect/3")
.method(net::methods::get)
.method(net::http_method::GET)
.redirections(3)
.send();
REQUIRE(req.get().http_code() == 200u);
REQUIRE(req.take().http_code() == 200u);
}
}
}
@@ -460,30 +569,50 @@ TEST_CASE("curly") {
{
auto resp = net::request_builder()
.url("https://httpbin.org/anything")
.method(net::methods::put)
.method(net::http_method::PUT)
.header("Content-Type", "application/json")
.content(R"({"hello":"world"})")
.send().get();
.send().take();
const auto content_j = json_parse(resp.content.as_string_view());
REQUIRE(content_j["data"] == R"({"hello":"world"})");
}
{
auto resp = net::request_builder()
.url("https://httpbin.org/anything")
.method(net::methods::post)
.method(net::http_method::PATCH)
.header("Content-Type", "application/json")
.content(R"({"hello":"world"})")
.send().get();
.send().take();
const auto content_j = json_parse(resp.content.as_string_view());
REQUIRE(content_j["data"] == R"({"hello":"world"})");
}
{
auto resp = net::request_builder()
.url("https://httpbin.org/anything")
.method(net::methods::post)
.method(net::http_method::DEL)
.header("Content-Type", "application/json")
.content(R"({"hello":"world"})")
.send().take();
const auto content_j = json_parse(resp.content.as_string_view());
REQUIRE(content_j["data"] == R"({"hello":"world"})");
}
{
auto resp = net::request_builder()
.url("https://httpbin.org/anything")
.method(net::http_method::POST)
.header("Content-Type", "application/json")
.content(R"({"hello":"world"})")
.send().take();
const auto content_j = json_parse(resp.content.as_string_view());
REQUIRE(content_j["data"] == R"({"hello":"world"})");
}
{
auto resp = net::request_builder()
.url("https://httpbin.org/anything")
.method(net::http_method::POST)
.header("Content-Type", "application/x-www-form-urlencoded")
.content("hello=world&world=hello")
.send().get();
.send().take();
const auto content_j = json_parse(resp.content.as_string_view());
REQUIRE(content_j["form"]["hello"] == "world");
REQUIRE(content_j["form"]["world"] == "hello");
@@ -493,53 +622,53 @@ TEST_CASE("curly") {
SECTION("ssl_verification") {
{
auto req0 = net::request_builder("https://expired.badssl.com")
.method(net::methods::head)
.method(net::http_method::HEAD)
.verification(true)
.send();
REQUIRE(req0.wait() == net::request::statuses::failed);
REQUIRE(req0.wait() == net::req_status::failed);
auto req1 = net::request_builder("https://wrong.host.badssl.com")
.method(net::methods::head)
.method(net::http_method::HEAD)
.verification(true)
.send();
REQUIRE(req1.wait() == net::request::statuses::failed);
REQUIRE(req1.wait() == net::req_status::failed);
auto req2 = net::request_builder("https://self-signed.badssl.com")
.method(net::methods::head)
.method(net::http_method::HEAD)
.verification(true)
.send();
REQUIRE(req2.wait() == net::request::statuses::failed);
REQUIRE(req2.wait() == net::req_status::failed);
auto req3 = net::request_builder("https://untrusted-root.badssl.com")
.method(net::methods::head)
.method(net::http_method::HEAD)
.verification(true)
.send();
REQUIRE(req3.wait() == net::request::statuses::failed);
REQUIRE(req3.wait() == net::req_status::failed);
}
{
auto req0 = net::request_builder("https://expired.badssl.com")
.method(net::methods::head)
.method(net::http_method::HEAD)
.verification(false)
.send();
REQUIRE(req0.wait() == net::request::statuses::done);
REQUIRE(req0.wait() == net::req_status::done);
auto req1 = net::request_builder("https://wrong.host.badssl.com")
.method(net::methods::head)
.method(net::http_method::HEAD)
.verification(false)
.send();
REQUIRE(req1.wait() == net::request::statuses::done);
REQUIRE(req1.wait() == net::req_status::done);
auto req2 = net::request_builder("https://self-signed.badssl.com")
.method(net::methods::head)
.method(net::http_method::HEAD)
.verification(false)
.send();
REQUIRE(req2.wait() == net::request::statuses::done);
REQUIRE(req2.wait() == net::req_status::done);
auto req3 = net::request_builder("https://untrusted-root.badssl.com")
.method(net::methods::head)
.method(net::http_method::HEAD)
.verification(false)
.send();
REQUIRE(req3.wait() == net::request::statuses::done);
REQUIRE(req3.wait() == net::req_status::done);
}
}
@@ -547,18 +676,26 @@ TEST_CASE("curly") {
{
auto req = net::request_builder("https://httpbin.org/anything")
.verbose(true)
.method(net::methods::post)
.method(net::http_method::POST)
.uploader<canceled_uploader>()
.send();
REQUIRE(req.wait() == net::request::statuses::canceled);
REQUIRE(req.wait() == net::req_status::canceled);
}
{
auto req = net::request_builder("https://httpbin.org/anything")
.verbose(true)
.method(net::methods::get)
.method(net::http_method::GET)
.downloader<canceled_downloader>()
.send();
REQUIRE(req.wait() == net::request::statuses::canceled);
REQUIRE(req.wait() == net::req_status::canceled);
}
{
auto req = net::request_builder("https://httpbin.org/anything")
.verbose(true)
.method(net::http_method::GET)
.progressor<canceled_progressor>()
.send();
REQUIRE(req.wait() == net::req_status::canceled);
}
}
@@ -570,10 +707,10 @@ TEST_CASE("curly") {
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);
REQUIRE(request.status() == net::req_status::done);
REQUIRE(request.take().http_code() == 200u);
}).send();
REQUIRE(req.wait_callback() == net::request::statuses::empty);
REQUIRE(req.wait_callback() == net::req_status::empty);
REQUIRE_FALSE(req.get_callback_exception());
REQUIRE(call_once.load() == 1u);
}
@@ -584,10 +721,10 @@ TEST_CASE("curly") {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
++call_once;
REQUIRE_FALSE(request.is_done());
REQUIRE(request.status() == net::request::statuses::failed);
REQUIRE(request.status() == net::req_status::failed);
REQUIRE_FALSE(request.get_error().empty());
}).send();
REQUIRE(req.wait_callback() == net::request::statuses::failed);
REQUIRE(req.wait_callback() == net::req_status::failed);
REQUIRE_FALSE(req.get_callback_exception());
REQUIRE(call_once.load() == 1u);
}
@@ -599,10 +736,10 @@ TEST_CASE("curly") {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
++call_once;
REQUIRE_FALSE(request.is_done());
REQUIRE(request.status() == net::request::statuses::timeout);
REQUIRE(request.status() == net::req_status::timeout);
REQUIRE_FALSE(request.get_error().empty());
}).send();
REQUIRE(req.wait_callback() == net::request::statuses::timeout);
REQUIRE(req.wait_callback() == net::req_status::timeout);
REQUIRE_FALSE(req.get_callback_exception());
REQUIRE(call_once.load() == 1u);
}
@@ -613,11 +750,11 @@ TEST_CASE("curly") {
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.status() == net::req_status::canceled);
REQUIRE(request.get_error().empty());
}).send();
REQUIRE(req.cancel());
REQUIRE(req.wait_callback() == net::request::statuses::canceled);
REQUIRE(req.wait_callback() == net::req_status::canceled);
REQUIRE_FALSE(req.get_callback_exception());
REQUIRE(call_once.load() == 1u);
}
@@ -627,11 +764,11 @@ TEST_CASE("curly") {
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() ) {
if ( request.take().is_http_error() ) {
throw std::logic_error("my_logic_error");
}
}).send();
REQUIRE(req.wait_callback() == net::request::statuses::empty);
REQUIRE(req.wait_callback() == net::req_status::empty);
REQUIRE(req.get_callback_exception());
try {
std::rethrow_exception(req.get_callback_exception());
@@ -642,17 +779,17 @@ TEST_CASE("curly") {
}
TEST_CASE("curly_examples") {
net::auto_performer performer;
net::performer performer;
SECTION("Get Requests") {
// makes a GET request and async send it
auto request = net::request_builder()
.method(net::methods::get)
.method(net::http_method::GET)
.url("http://www.httpbin.org/get")
.send();
// synchronous waits and get a response
auto response = request.get();
// synchronous waits and take a response
auto response = request.take();
// prints results
std::cout << "Status code: " << response.http_code() << std::endl;
@@ -675,13 +812,13 @@ TEST_CASE("curly_examples") {
SECTION("Post Requests") {
auto request = net::request_builder()
.method(net::methods::post)
.method(net::http_method::POST)
.url("http://www.httpbin.org/post")
.header("Content-Type", "application/json")
.content(R"({"hello" : "world"})")
.send();
auto response = request.get();
auto response = request.take();
std::cout << "Body content: " << response.content.as_string_view() << std::endl;
std::cout << "Content Length: " << response.headers["content-length"] << std::endl;
@@ -714,11 +851,11 @@ TEST_CASE("curly_examples") {
request.wait();
if ( request.is_done() ) {
auto response = request.get();
auto response = request.take();
std::cout << "Status code: " << response.http_code() << std::endl;
} else {
// throws net::exception because a response is unavailable
// auto response = request.get();
// auto response = request.take();
std::cout << "Error message: " << request.get_error() << std::endl;
}
@@ -730,7 +867,7 @@ TEST_CASE("curly_examples") {
auto request = net::request_builder("http://www.httpbin.org/get")
.callback([](net::request request){
if ( request.is_done() ) {
auto response = request.get();
auto response = request.take();
std::cout << "Status code: " << response.http_code() << std::endl;
} else {
std::cout << "Error message: " << request.get_error() << std::endl;
@@ -759,7 +896,7 @@ TEST_CASE("curly_examples") {
net::request_builder()
.url("https://httpbin.org/image/jpeg")
.downloader<file_dowloader>("image.jpeg")
.send().get();
.send().take();
}
{
class file_uploader : public net::upload_handler {
@@ -785,10 +922,10 @@ TEST_CASE("curly_examples") {
};
net::request_builder()
.method(net::methods::post)
.method(net::http_method::POST)
.url("https://httpbin.org/anything")
.uploader<file_uploader>("image.jpeg")
.send().get();
.send().take();
}
}