Merge pull request #4 from BlackMATov/dev

Dev
This commit is contained in:
2019-06-28 08:43:00 +07:00
committed by GitHub
5 changed files with 167 additions and 62 deletions

View File

@@ -29,9 +29,9 @@
- Custom headers
- Asynchronous requests
- Different types of timeouts
- PUT, GET, HEAD, POST methods
- Custom uploading and downloading streams
- Connection and last server response timeouts
## Installation

View File

@@ -21,13 +21,13 @@
namespace curly_hpp
{
class exception : public std::runtime_error {
class exception final : public std::runtime_error {
public:
explicit exception(const char* what);
explicit exception(const std::string& what);
};
struct icase_string_compare {
struct icase_string_compare final {
using is_transparent = void;
bool operator()(
std::string_view l,
@@ -67,7 +67,7 @@ namespace curly_hpp
namespace curly_hpp
{
class content_t {
class content_t final {
public:
content_t() = default;
@@ -93,7 +93,7 @@ namespace curly_hpp
namespace curly_hpp
{
class response {
class response final {
public:
response() = default;
@@ -117,7 +117,7 @@ namespace curly_hpp
namespace curly_hpp
{
class request {
class request final {
public:
enum class statuses {
done,
@@ -146,7 +146,7 @@ namespace curly_hpp
namespace curly_hpp
{
class request_builder {
class request_builder final {
public:
request_builder() = default;
@@ -167,6 +167,7 @@ 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;
@@ -182,6 +183,7 @@ 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;
@@ -198,12 +200,14 @@ namespace curly_hpp
template < typename Uploader, typename... Args >
request_builder& uploader(Args&&... args) {
return uploader(std::make_unique<Uploader>(std::forward<Args>(args)...));
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)...));
return downloader(std::make_unique<Downloader>(
std::forward<Args>(args)...));
}
private:
std::string url_;
@@ -212,6 +216,7 @@ 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};
private:
@@ -223,7 +228,7 @@ namespace curly_hpp
namespace curly_hpp
{
class auto_performer {
class auto_performer final {
public:
auto_performer();
~auto_performer() noexcept;

View File

@@ -75,7 +75,7 @@ namespace
return {result, &curl_slist_free_all};
}
class default_uploader : public upload_handler {
class default_uploader final : public upload_handler {
public:
using data_t = std::vector<char>;
@@ -97,11 +97,11 @@ namespace
}
private:
const data_t& data_;
std::size_t uploaded_{0};
std::mutex& mutex_;
std::size_t uploaded_{0};
};
class default_downloader : public download_handler {
class default_downloader final : public download_handler {
public:
using data_t = std::vector<char>;
@@ -130,7 +130,7 @@ namespace
{
using namespace curly_hpp;
class curl_state {
class curl_state final {
public:
template < typename F >
static std::invoke_result_t<F, CURLM*> with(F&& f)
@@ -156,11 +156,8 @@ namespace
~curl_state() noexcept {
std::lock_guard<std::mutex> guard(mutex_);
if ( curlm_ ) {
curl_multi_cleanup(curlm_);
curl_global_cleanup();
curlm_ = nullptr;
}
curl_multi_cleanup(curlm_);
curl_global_cleanup();
}
private:
CURLM* curlm_{nullptr};
@@ -344,6 +341,9 @@ 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), rb.request_timeout()).count()));
curl_easy_setopt(curlh_.get(), CURLOPT_CONNECTTIMEOUT,
static_cast<long>(std::max(time_sec_t(1), rb.connection_timeout()).count()));
@@ -393,9 +393,19 @@ namespace curly_hpp
return false;
}
status_ = (err == CURLE_OPERATION_TIMEDOUT)
? statuses::timeout
: statuses::failed;
switch ( err ) {
case CURLE_OPERATION_TIMEDOUT:
status_ = statuses::timeout;
break;
case CURLE_READ_ERROR:
case CURLE_WRITE_ERROR:
case CURLE_ABORTED_BY_CALLBACK:
status_ = statuses::canceled;
break;
default:
status_ = statuses::failed;
break;
}
try {
error_ = curl_easy_strerror(err);
@@ -632,6 +642,11 @@ namespace curly_hpp
return *this;
}
request_builder& request_builder::request_timeout(time_sec_t t) noexcept {
request_timeout_ = t;
return *this;
}
request_builder& request_builder::response_timeout(time_sec_t t) noexcept {
response_timeout_ = t;
return *this;
@@ -686,6 +701,10 @@ namespace curly_hpp
return redirections_;
}
time_sec_t request_builder::request_timeout() const noexcept {
return request_timeout_;
}
time_sec_t request_builder::response_timeout() const noexcept {
return response_timeout_;
}
@@ -792,8 +811,11 @@ namespace curly_hpp
void perform() {
curl_state::with([](CURLM* curlm){
int running_handles = 0;
curl_multi_perform(curlm, &running_handles);
if ( !running_handles || static_cast<std::size_t>(running_handles) != handles.size() ) {
if ( CURLM_OK != curl_multi_perform(curlm, &running_handles) ) {
throw exception("curly_hpp: failed to curl_multi_perform");
}
if ( static_cast<std::size_t>(running_handles) != handles.size() ) {
while ( true ) {
int msgs_in_queue = 0;
CURLMsg* msg = curl_multi_info_read(curlm, &msgs_in_queue);
@@ -833,7 +855,10 @@ namespace curly_hpp
void wait_activity(time_ms_t ms) {
curl_state::with([ms](CURLM* curlm){
curl_multi_wait(curlm, nullptr, 0, static_cast<int>(ms.count()), nullptr);
const int timeout_ms = static_cast<int>(ms.count());
if ( CURLM_OK != curl_multi_wait(curlm, nullptr, 0, timeout_ms, nullptr) ) {
throw exception("curly_hpp: failed to curl_multi_wait");
}
});
}
}

View File

@@ -5,5 +5,4 @@
******************************************************************************/
#define CATCH_CONFIG_MAIN
#define CATCH_CONFIG_FAST_COMPILE
#include <catch2/catch.hpp>

View File

@@ -4,10 +4,10 @@
* Copyright (C) 2019, by Matvey Cherevko (blackmatov@gmail.com)
******************************************************************************/
#define CATCH_CONFIG_FAST_COMPILE
#include <catch2/catch.hpp>
#include <fstream>
#include <utility>
#include <iostream>
#include <rapidjson/document.h>
@@ -29,35 +29,36 @@ namespace
return d;
}
class verbose_uploader : public net::upload_handler {
class canceled_uploader : public net::upload_handler {
public:
verbose_uploader() = default;
canceled_uploader() = default;
std::size_t size() const override {
return 0;
return 10;
}
std::size_t read(char* dst, std::size_t size) override {
(void)dst;
std::cout << "---------- ** UPLOAD (" << size << ") ** ---------- " << std::endl;
return size;
(void)size;
throw std::exception();
}
};
class verbose_downloader : public net::download_handler {
class canceled_downloader : public net::download_handler {
public:
verbose_downloader() = default;
canceled_downloader() = default;
std::size_t write(const char* src, std::size_t size) override {
(void)src;
std::cout << "---------- ** DOWNLOAD (" << size << ") ** ---------- " << std::endl;
return size;
(void)size;
throw std::exception();
}
};
}
TEST_CASE("curly") {
net::auto_performer performer;
performer.wait_activity(net::time_ms_t(10));
SECTION("wait") {
auto req = net::request_builder("https://httpbin.org/delay/1").send();
@@ -229,11 +230,13 @@ TEST_CASE("curly") {
.url("https://httpbin.org/headers")
.header("Custom-Header-1", "custom_header_value_1")
.header("Custom-Header-2", "custom header value 2")
.header("Custom-Header-3", std::string())
.send();
const auto resp = req.get();
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");
REQUIRE(content_j["headers"]["Custom-Header-3"] == "");
}
SECTION("response_inspection") {
@@ -253,7 +256,7 @@ TEST_CASE("curly") {
.method(net::methods::post)
.send();
const auto resp = req.get();
const auto content_j = json_parse(resp.content.as_string_view());
const auto content_j = json_parse(resp.content.as_string_copy());
REQUIRE(content_j["hello"] == "world");
REQUIRE(content_j["world"] == "hello");
}
@@ -269,20 +272,34 @@ TEST_CASE("curly") {
REQUIRE(req.error().empty());
}
{
auto req = net::request_builder()
auto req0 = net::request_builder()
.url("https://httpbin.org/delay/10")
.request_timeout(net::time_sec_t(0))
.send();
REQUIRE(req0.wait() == net::request::statuses::timeout);
REQUIRE_FALSE(req0.error().empty());
auto req1 = net::request_builder()
.url("https://httpbin.org/delay/10")
.response_timeout(net::time_sec_t(0))
.send();
REQUIRE(req.wait() == net::request::statuses::timeout);
REQUIRE_FALSE(req.error().empty());
REQUIRE(req1.wait() == net::request::statuses::timeout);
REQUIRE_FALSE(req1.error().empty());
}
{
auto req = net::request_builder()
auto req0 = net::request_builder()
.url("https://httpbin.org/delay/10")
.request_timeout(net::time_sec_t(1))
.send();
REQUIRE(req0.wait() == net::request::statuses::timeout);
REQUIRE_FALSE(req0.error().empty());
auto req1 = net::request_builder()
.url("https://httpbin.org/delay/10")
.response_timeout(net::time_sec_t(1))
.send();
REQUIRE(req.wait() == net::request::statuses::timeout);
REQUIRE_FALSE(req.error().empty());
REQUIRE(req1.wait() == net::request::statuses::timeout);
REQUIRE_FALSE(req1.error().empty());
}
}
@@ -296,7 +313,9 @@ TEST_CASE("curly") {
REQUIRE(resp.headers.count("Content-Type"));
REQUIRE(resp.headers.at("Content-Type") == "image/png");
REQUIRE(untests::png_data_length == resp.content.size());
REQUIRE(!std::memcmp(resp.content.data().data(), untests::png_data, untests::png_data_length));
REQUIRE(!std::memcmp(
std::move(resp.content).data().data(),
untests::png_data, untests::png_data_length));
}
{
auto resp = net::request_builder()
@@ -307,31 +326,69 @@ TEST_CASE("curly") {
REQUIRE(resp.headers.count("Content-Type"));
REQUIRE(resp.headers.at("Content-Type") == "image/jpeg");
REQUIRE(untests::jpeg_data_length == resp.content.size());
REQUIRE(!std::memcmp(resp.content.data().data(), untests::jpeg_data, untests::jpeg_data_length));
REQUIRE(!std::memcmp(
std::as_const(resp.content).data().data(),
untests::jpeg_data, untests::jpeg_data_length));
}
}
SECTION("redirects") {
{
auto req = net::request_builder()
.url("https://httpbin.org/redirect/2")
.method(net::methods::get)
.send();
REQUIRE(req.get().code() == 200u);
{
auto req = net::request_builder()
.url("https://httpbin.org/redirect/2")
.method(net::methods::get)
.send();
REQUIRE(req.get().code() == 200u);
}
{
auto req = net::request_builder()
.url("https://httpbin.org/absolute-redirect/2")
.method(net::methods::get)
.send();
REQUIRE(req.get().code() == 200u);
}
{
auto req = net::request_builder()
.url("https://httpbin.org/relative-redirect/2")
.method(net::methods::get)
.send();
REQUIRE(req.get().code() == 200u);
}
}
{
auto req = net::request_builder()
.url("https://httpbin.org/absolute-redirect/2")
.method(net::methods::get)
.send();
REQUIRE(req.get().code() == 200u);
}
{
auto req = net::request_builder()
.url("https://httpbin.org/relative-redirect/2")
.method(net::methods::get)
.send();
REQUIRE(req.get().code() == 200u);
{
auto req = net::request_builder()
.url("https://httpbin.org/redirect/3")
.method(net::methods::get)
.redirections(0)
.send();
REQUIRE(req.get().code() == 302u);
}
{
auto req = net::request_builder()
.url("https://httpbin.org/redirect/3")
.method(net::methods::get)
.redirections(1)
.send();
REQUIRE(req.wait() == net::request::statuses::failed);
}
{
auto req = net::request_builder()
.url("https://httpbin.org/redirect/3")
.method(net::methods::get)
.redirections(2)
.send();
REQUIRE(req.wait() == net::request::statuses::failed);
}
{
auto req = net::request_builder()
.url("https://httpbin.org/redirect/3")
.method(net::methods::get)
.redirections(3)
.send();
REQUIRE(req.get().code() == 200u);
}
}
}
@@ -421,6 +478,25 @@ TEST_CASE("curly") {
REQUIRE(req3.wait() == net::request::statuses::done);
}
}
SECTION("canceled_handlers") {
{
auto req = net::request_builder("https://httpbin.org/anything")
.verbose(true)
.method(net::methods::post)
.uploader<canceled_uploader>()
.send();
REQUIRE(req.wait() == net::request::statuses::canceled);
}
{
auto req = net::request_builder("https://httpbin.org/anything")
.verbose(true)
.method(net::methods::get)
.downloader<canceled_downloader>()
.send();
REQUIRE(req.wait() == net::request::statuses::canceled);
}
}
}
TEST_CASE("curly_examples") {