mirror of
https://github.com/BlackMATov/curly.hpp.git
synced 2025-12-15 12:19:47 +07:00
add encoded query parameters #8
This commit is contained in:
@@ -26,6 +26,7 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <initializer_list>
|
||||||
|
|
||||||
namespace curly_hpp
|
namespace curly_hpp
|
||||||
{
|
{
|
||||||
@@ -92,6 +93,17 @@ namespace curly_hpp
|
|||||||
{
|
{
|
||||||
namespace detail
|
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 {
|
struct icase_string_compare final {
|
||||||
using is_transparent = void;
|
using is_transparent = void;
|
||||||
bool operator()(std::string_view l, std::string_view r) const noexcept {
|
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<
|
using headers_t = std::map<
|
||||||
std::string, std::string,
|
std::string, std::string,
|
||||||
detail::icase_string_compare>;
|
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
|
namespace curly_hpp
|
||||||
@@ -249,7 +271,12 @@ namespace curly_hpp
|
|||||||
|
|
||||||
request_builder& url(std::string u) noexcept;
|
request_builder& url(std::string u) noexcept;
|
||||||
request_builder& method(http_method m) 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& verbose(bool v) noexcept;
|
||||||
request_builder& verification(bool v) noexcept;
|
request_builder& verification(bool v) noexcept;
|
||||||
@@ -267,6 +294,7 @@ namespace curly_hpp
|
|||||||
|
|
||||||
const std::string& url() const noexcept;
|
const std::string& url() const noexcept;
|
||||||
http_method method() const noexcept;
|
http_method method() const noexcept;
|
||||||
|
const qparams_t& qparams() const noexcept;
|
||||||
const headers_t& headers() const noexcept;
|
const headers_t& headers() const noexcept;
|
||||||
|
|
||||||
bool verbose() const noexcept;
|
bool verbose() const noexcept;
|
||||||
@@ -293,6 +321,24 @@ namespace curly_hpp
|
|||||||
|
|
||||||
request send();
|
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 >
|
template < typename Callback >
|
||||||
request_builder& callback(Callback&& f) {
|
request_builder& callback(Callback&& f) {
|
||||||
static_assert(
|
static_assert(
|
||||||
@@ -327,6 +373,7 @@ namespace curly_hpp
|
|||||||
private:
|
private:
|
||||||
std::string url_;
|
std::string url_;
|
||||||
http_method method_{http_method::GET};
|
http_method method_{http_method::GET};
|
||||||
|
qparams_t qparams_;
|
||||||
headers_t headers_;
|
headers_t headers_;
|
||||||
bool verbose_{false};
|
bool verbose_{false};
|
||||||
bool verification_{false};
|
bool verification_{false};
|
||||||
|
|||||||
@@ -33,11 +33,11 @@ namespace
|
|||||||
|
|
||||||
using curlh_t = std::unique_ptr<
|
using curlh_t = std::unique_ptr<
|
||||||
CURL,
|
CURL,
|
||||||
void(*)(CURL*)>;
|
decltype(&curl_easy_cleanup)>;
|
||||||
|
|
||||||
using slist_t = std::unique_ptr<
|
using slist_t = std::unique_ptr<
|
||||||
curl_slist,
|
curl_slist,
|
||||||
void(*)(curl_slist*)>;
|
decltype(&curl_slist_free_all)>;
|
||||||
|
|
||||||
class default_uploader final : public upload_handler {
|
class default_uploader final : public upload_handler {
|
||||||
public:
|
public:
|
||||||
@@ -190,6 +190,36 @@ namespace
|
|||||||
}
|
}
|
||||||
return {result, &curl_slist_free_all};
|
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());
|
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 ) {
|
if ( const auto* vi = curl_version_info(CURLVERSION_NOW); vi && vi->version ) {
|
||||||
std::string user_agent("cURL/");
|
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_HEADERDATA, this);
|
||||||
curl_easy_setopt(curlh_.get(), CURLOPT_HEADERFUNCTION, &s_header_callback_);
|
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_HTTPHEADER, hlist_.get());
|
||||||
curl_easy_setopt(curlh_.get(), CURLOPT_VERBOSE, breq_.verbose() ? 1l : 0l);
|
curl_easy_setopt(curlh_.get(), CURLOPT_VERBOSE, breq_.verbose() ? 1l : 0l);
|
||||||
|
|
||||||
@@ -685,6 +716,7 @@ namespace curly_hpp
|
|||||||
request_builder breq_;
|
request_builder breq_;
|
||||||
curlh_t curlh_{nullptr, &curl_easy_cleanup};
|
curlh_t curlh_{nullptr, &curl_easy_cleanup};
|
||||||
slist_t hlist_{nullptr, &curl_slist_free_all};
|
slist_t hlist_{nullptr, &curl_slist_free_all};
|
||||||
|
std::string url_with_qparams_;
|
||||||
time_point_t last_response_{time_point_t::clock::now()};
|
time_point_t last_response_{time_point_t::clock::now()};
|
||||||
time_point_t::duration response_timeout_{0};
|
time_point_t::duration response_timeout_{0};
|
||||||
private:
|
private:
|
||||||
@@ -799,8 +831,27 @@ namespace curly_hpp
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
request_builder& request_builder::header(std::string key, std::string value) {
|
request_builder& request_builder::qparams(qparam_ilist_t ps) {
|
||||||
headers_.insert_or_assign(std::move(key), std::move(value));
|
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;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -872,6 +923,10 @@ namespace curly_hpp
|
|||||||
return method_;
|
return method_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const qparams_t& request_builder::qparams() const noexcept {
|
||||||
|
return qparams_;
|
||||||
|
}
|
||||||
|
|
||||||
const headers_t& request_builder::headers() const noexcept {
|
const headers_t& request_builder::headers() const noexcept {
|
||||||
return headers_;
|
return headers_;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -371,17 +371,58 @@ TEST_CASE("curly") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SECTION("request_inspection") {
|
SECTION("request_inspection") {
|
||||||
auto req = net::request_builder()
|
{
|
||||||
.url("https://httpbin.org/headers")
|
auto resp = net::request_builder()
|
||||||
.header("Custom-Header-1", "custom_header_value_1")
|
.url("https://httpbin.org/headers")
|
||||||
.header("Custom-Header-2", "custom header value 2")
|
.header("Custom-Header-1", "custom_header_value_1")
|
||||||
.header("Custom-Header-3", std::string())
|
.header("Custom-Header-2", "custom header value 2")
|
||||||
.send();
|
.header("Custom-Header-3", std::string())
|
||||||
const auto resp = req.take();
|
.send().take();
|
||||||
const auto content_j = json_parse(resp.content.as_string_view());
|
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-1"] == "custom_header_value_1");
|
||||||
REQUIRE(content_j["headers"]["Custom-Header-2"] == "custom header value 2");
|
REQUIRE(content_j["headers"]["Custom-Header-2"] == "custom header value 2");
|
||||||
REQUIRE(content_j["headers"]["Custom-Header-3"] == "");
|
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") {
|
SECTION("response_inspection") {
|
||||||
@@ -397,14 +438,56 @@ TEST_CASE("curly") {
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto req = net::request_builder()
|
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)
|
.method(net::http_method::POST)
|
||||||
|
.qparam("world", "hello")
|
||||||
.send();
|
.send();
|
||||||
const auto resp = req.take();
|
const auto resp = req.take();
|
||||||
const auto content_j = json_parse(resp.content.as_string_copy());
|
const auto content_j = json_parse(resp.content.as_string_copy());
|
||||||
REQUIRE(content_j["hello"] == "world");
|
REQUIRE(content_j["hello"] == "world");
|
||||||
REQUIRE(content_j["world"] == "hello");
|
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") {
|
SECTION("dynamic_data") {
|
||||||
|
|||||||
Reference in New Issue
Block a user