Merge pull request #18 from BlackMATov/dev

Dev
This commit is contained in:
2019-07-15 16:21:32 +07:00
committed by GitHub
3 changed files with 212 additions and 27 deletions

View File

@@ -26,6 +26,7 @@
#include <map>
#include <vector>
#include <string>
#include <initializer_list>
namespace curly_hpp
{
@@ -51,20 +52,20 @@ namespace curly_hpp
class upload_handler {
public:
virtual ~upload_handler() {}
virtual ~upload_handler() = default;
virtual std::size_t size() const = 0;
virtual std::size_t read(char* dst, std::size_t size) = 0;
};
class download_handler {
public:
virtual ~download_handler() {}
virtual ~download_handler() = default;
virtual std::size_t write(const char* src, std::size_t size) = 0;
};
class progress_handler {
public:
virtual ~progress_handler() {}
virtual ~progress_handler() = default;
virtual float update(
std::size_t dnow, std::size_t dtotal,
std::size_t unow, std::size_t utotal) = 0;
@@ -92,6 +93,17 @@ namespace curly_hpp
{
namespace detail
{
struct case_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 lc < rc;
});
}
};
struct icase_string_compare final {
using is_transparent = void;
bool operator()(std::string_view l, std::string_view r) const noexcept {
@@ -104,9 +116,19 @@ namespace curly_hpp
};
}
using qparams_t = std::multimap<
std::string, std::string,
detail::case_string_compare>;
using headers_t = std::map<
std::string, std::string,
detail::icase_string_compare>;
using qparam_ilist_t = std::initializer_list<
std::pair<std::string_view, std::string_view>>;
using header_ilist_t = std::initializer_list<
std::pair<std::string_view, std::string_view>>;
}
namespace curly_hpp
@@ -249,7 +271,12 @@ namespace curly_hpp
request_builder& url(std::string u) noexcept;
request_builder& method(http_method m) noexcept;
request_builder& header(std::string key, std::string value);
request_builder& qparams(qparam_ilist_t ps);
request_builder& qparam(std::string k, std::string v);
request_builder& headers(header_ilist_t hs);
request_builder& header(std::string k, std::string v);
request_builder& verbose(bool v) noexcept;
request_builder& verification(bool v) noexcept;
@@ -267,6 +294,7 @@ namespace curly_hpp
const std::string& url() const noexcept;
http_method method() const noexcept;
const qparams_t& qparams() const noexcept;
const headers_t& headers() const noexcept;
bool verbose() const noexcept;
@@ -293,6 +321,24 @@ namespace curly_hpp
request send();
template < typename Iter >
request_builder& qparams(Iter first, Iter last) {
while ( first != last ) {
qparam(first->first, first->second);
++first;
}
return *this;
}
template < typename Iter >
request_builder& headers(Iter first, Iter last) {
while ( first != last ) {
header(first->first, first->second);
++first;
}
return *this;
}
template < typename Callback >
request_builder& callback(Callback&& f) {
static_assert(
@@ -327,6 +373,7 @@ namespace curly_hpp
private:
std::string url_;
http_method method_{http_method::GET};
qparams_t qparams_;
headers_t headers_;
bool verbose_{false};
bool verification_{false};

View File

@@ -33,11 +33,11 @@ namespace
using curlh_t = std::unique_ptr<
CURL,
void(*)(CURL*)>;
decltype(&curl_easy_cleanup)>;
using slist_t = std::unique_ptr<
curl_slist,
void(*)(curl_slist*)>;
decltype(&curl_slist_free_all)>;
class default_uploader final : public upload_handler {
public:
@@ -86,12 +86,12 @@ namespace
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
const double now_d = static_cast<double>(dnow + unow);
const double total_d = static_cast<double>(dtotal + utotal);
const float 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));
return std::min(std::max(progress_d, 0.f), 1.f);
}
};
}
@@ -190,6 +190,36 @@ namespace
}
return {result, &curl_slist_free_all};
}
std::string make_escaped_string(std::string_view s) {
std::unique_ptr<char, decltype(&curl_free)> escaped_string{
curl_easy_escape(nullptr, s.data(), static_cast<int>(s.size())),
&curl_free};
if ( !escaped_string ) {
throw std::bad_alloc();
}
return std::string(escaped_string.get());
}
std::string make_escaped_url(std::string_view u, const qparams_t& ps) {
std::string result{u};
bool has_qparams = result.find('?') != std::string_view::npos;
for ( auto iter = ps.begin(); iter != ps.end(); ++iter ) {
const std::string k = !iter->first.empty() ? iter->first : iter->second;
const std::string v = !iter->first.empty() ? iter->second : std::string();
if ( k.empty() ) {
continue;
}
result.append(has_qparams ? "&" : "?");
result.append(make_escaped_string(k));
if ( !v.empty() ) {
result.append("=");
result.append(make_escaped_string(v));
}
has_qparams = true;
}
return result;
}
}
// -----------------------------------------------------------------------------
@@ -282,6 +312,7 @@ namespace curly_hpp
}
hlist_ = make_header_slist(breq_.headers());
url_with_qparams_ = make_escaped_url(breq_.url(), breq_.qparams());
if ( const auto* vi = curl_version_info(CURLVERSION_NOW); vi && vi->version ) {
std::string user_agent("cURL/");
@@ -308,7 +339,7 @@ namespace curly_hpp
curl_easy_setopt(curlh_.get(), CURLOPT_HEADERDATA, this);
curl_easy_setopt(curlh_.get(), CURLOPT_HEADERFUNCTION, &s_header_callback_);
curl_easy_setopt(curlh_.get(), CURLOPT_URL, breq_.url().c_str());
curl_easy_setopt(curlh_.get(), CURLOPT_URL, url_with_qparams_.c_str());
curl_easy_setopt(curlh_.get(), CURLOPT_HTTPHEADER, hlist_.get());
curl_easy_setopt(curlh_.get(), CURLOPT_VERBOSE, breq_.verbose() ? 1l : 0l);
@@ -685,8 +716,9 @@ namespace curly_hpp
request_builder breq_;
curlh_t curlh_{nullptr, &curl_easy_cleanup};
slist_t hlist_{nullptr, &curl_slist_free_all};
time_point_t last_response_;
time_point_t::duration response_timeout_;
std::string url_with_qparams_;
time_point_t last_response_{time_point_t::clock::now()};
time_point_t::duration response_timeout_{0};
private:
response response_;
headers_t response_headers_;
@@ -799,8 +831,27 @@ namespace curly_hpp
return *this;
}
request_builder& request_builder::header(std::string key, std::string value) {
headers_.insert_or_assign(std::move(key), std::move(value));
request_builder& request_builder::qparams(qparam_ilist_t ps) {
for ( const auto& [k,v] : ps ) {
qparams_.emplace(k, v);
}
return *this;
}
request_builder& request_builder::qparam(std::string k, std::string v) {
qparams_.emplace(std::move(k), std::move(v));
return *this;
}
request_builder& request_builder::headers(header_ilist_t hs) {
for ( const auto& [k,v] : hs ) {
headers_.insert_or_assign(std::string(k), v);
}
return *this;
}
request_builder& request_builder::header(std::string k, std::string v) {
headers_.insert_or_assign(std::move(k), std::move(v));
return *this;
}
@@ -872,6 +923,10 @@ namespace curly_hpp
return method_;
}
const qparams_t& request_builder::qparams() const noexcept {
return qparams_;
}
const headers_t& request_builder::headers() const noexcept {
return headers_;
}

View File

@@ -371,17 +371,58 @@ TEST_CASE("curly") {
}
SECTION("request_inspection") {
auto req = net::request_builder()
.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.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");
REQUIRE(content_j["headers"]["Custom-Header-3"] == "");
{
auto resp = net::request_builder()
.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().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");
REQUIRE(content_j["headers"]["Custom-Header-3"] == "");
}
{
auto resp = net::request_builder()
.url("https://httpbin.org/headers")
.headers({
{"Custom-Header-1", "custom_header_value_1"},
{"Custom-Header-2", "custom header value 2"},
{"Custom-Header-3", ""}})
.send().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");
REQUIRE(content_j["headers"]["Custom-Header-3"] == "");
}
{
auto resp = net::request_builder()
.url("https://httpbin.org/headers")
.headers({
{"Custom-Header-1", "custom_header_value_1"},
{"Custom-Header-2", "custom header value 2"},
{"Custom-Header-3", ""}})
.send().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");
REQUIRE(content_j["headers"]["Custom-Header-3"] == "");
}
{
std::map<std::string, std::string> headers{
{"Custom-Header-1", "custom_header_value_1"},
{"Custom-Header-2", "custom header value 2"},
{"Custom-Header-3", ""}};
auto resp = net::request_builder()
.url("https://httpbin.org/headers")
.headers(headers.begin(), headers.end())
.send().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");
REQUIRE(content_j["headers"]["Custom-Header-3"] == "");
}
}
SECTION("response_inspection") {
@@ -397,14 +438,56 @@ TEST_CASE("curly") {
}
{
auto req = net::request_builder()
.url("https://httpbin.org/response-headers?hello=world&world=hello")
.url("https://httpbin.org/response-headers?hello=world")
.method(net::http_method::POST)
.qparam("world", "hello")
.send();
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");
}
{
auto req = net::request_builder()
.url("https://httpbin.org/response-headers")
.method(net::http_method::GET)
.qparam("hello", "world")
.qparam("world", "hello")
.send();
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");
}
{
auto req = net::request_builder()
.url("https://httpbin.org/response-headers")
.method(net::http_method::GET)
.qparams({
{"", "hello"},
{"world", ""}
})
.send();
const auto resp = req.take();
const auto content_j = json_parse(resp.content.as_string_view());
REQUIRE(content_j["hello"] == "");
REQUIRE(content_j["world"] == "");
}
{
std::map<std::string,std::string> qparams{
{"hello", "world"},
{"world", "hello"}
};
auto req = net::request_builder()
.url("https://httpbin.org/response-headers")
.method(net::http_method::GET)
.qparams(qparams.begin(), qparams.end())
.send();
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");
}
}
SECTION("dynamic_data") {