From 7e5db48bdfca5cddf353d7a4a8b316fd77b9538c Mon Sep 17 00:00:00 2001 From: yhirose Date: Tue, 12 Dec 2017 22:20:40 -0500 Subject: [PATCH] Fixed #33 --- README.md | 19 +++++++-- httplib.h | 114 +++++++++++++++++++++++++++++++++++++++------------ test/test.cc | 107 +++++++++++++++++++++++++++++++++++++---------- 3 files changed, 189 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 2ed1de9..2708834 100644 --- a/README.md +++ b/README.md @@ -126,15 +126,28 @@ auto res = cli.post("/post", params); httplib::Client client(url, port); // prints: 0 / 000 bytes => 50% complete -std::shared_ptr res = - cli.get("/", [](int64_t len, int64_t total) { - printf("%lld / %lld bytes => %d%% complete\n", +std::shared_ptr res = + cli.get("/", [](uint64_t len, uint64_t total) { + printf("%lld / %lld bytes => %d%% complete\n", len, total, (int)((len/total)*100)); } ); ``` +### Range (HTTP/1.1) + +```cpp +httplib::Client cli("httpbin.org", 80, httplib::HttpVersion::v1_1); + +// 'Range: bytes=1-10' +httplib::Headers headers = { httplib::make_range_header(1, 10) }; + +auto res = cli.get("/range/32", headers); +// res->status should be 206. +// res->body should be "bcdefghijk". +``` + ![progress](https://user-images.githubusercontent.com/236374/33138910-495c4ecc-cf86-11e7-8693-2fc6d09615c4.gif) This feature was contributed by [underscorediscovery](https://github.com/yhirose/cpp-httplib/pull/23). diff --git a/httplib.h b/httplib.h index eaf701c..d159b9f 100644 --- a/httplib.h +++ b/httplib.h @@ -91,9 +91,13 @@ struct ci { enum class HttpVersion { v1_0 = 0, v1_1 }; typedef std::multimap Headers; + +template +std::pair make_range_header(uint64_t value, Args... args); + typedef std::multimap Params; typedef std::smatch Match; -typedef std::function Progress; +typedef std::function Progress; struct MultipartFile { std::string filename; @@ -111,11 +115,11 @@ struct Request { Params params; MultipartFiles files; Match matches; + Progress progress; bool has_header(const char* key) const; std::string get_header_value(const char* key) const; - void set_header(const char* key, const char* val); bool has_param(const char* key) const; std::string get_param_value(const char* key) const; @@ -211,10 +215,17 @@ public: Client(const char* host, int port, HttpVersion http_version = HttpVersion::v1_0); virtual ~Client(); - std::shared_ptr get(const char* path, Progress callback = [](int64_t,int64_t){}); + std::shared_ptr get(const char* path, Progress progress = nullptr); + std::shared_ptr get(const char* path, const Headers& headers, Progress progress = nullptr); + std::shared_ptr head(const char* path); + std::shared_ptr head(const char* path, const Headers& headers); + std::shared_ptr post(const char* path, const std::string& body, const char* content_type); + std::shared_ptr post(const char* path, const Headers& headers, const std::string& body, const char* content_type); + std::shared_ptr post(const char* path, const Params& params); + std::shared_ptr post(const char* path, const Headers& headers, const Params& params); bool send(const Request& req, Response& res); @@ -633,7 +644,9 @@ bool read_content_with_length(Stream& strm, T& x, size_t len, Progress progress) if (r_incr <= 0) { return false; } + r += r_incr; + if (progress) { progress(r, len); } @@ -702,7 +715,7 @@ bool read_content_chunked(Stream& strm, T& x) } template -bool read_content(Stream& strm, T& x, Progress progress = [](int64_t,int64_t){}) +bool read_content(Stream& strm, T& x, Progress progress = Progress()) { auto len = get_header_value_int(x.headers, "Content-Length", 0); @@ -980,6 +993,38 @@ inline bool parse_multipart_formdata( return true; } +inline std::string to_lower(const char* beg, const char* end) +{ + std::string out; + auto it = beg; + while (it != end) { + out += ::tolower(*it); + it++; + } + return out; +} + +inline void make_range_header_core(std::string&) {} + +template +inline void make_range_header_core(std::string& field, uint64_t value) +{ + if (!field.empty()) { + field += ", "; + } + field += std::to_string(value) + "-"; +} + +template +inline void make_range_header_core(std::string& field, uint64_t value1, uint64_t value2, Args... args) +{ + if (!field.empty()) { + field += ", "; + } + field += std::to_string(value1) + "-" + std::to_string(value2); + make_range_header_core(field, args...); +} + #ifdef _WIN32 class WSInit { public: @@ -996,19 +1041,18 @@ public: static WSInit wsinit_; #endif -inline std::string to_lower(const char* beg, const char* end) -{ - std::string out; - auto it = beg; - while (it != end) { - out += ::tolower(*it); - it++; - } - return out; -} - } // namespace detail +// Header utilities +template +inline std::pair make_range_header(uint64_t value, Args... args) +{ + std::string field; + detail::make_range_header_core(field, value, args...); + field.insert(0, "bytes="); + return std::make_pair("Range", field); +} + // Request implementation inline bool Request::has_header(const char* key) const { @@ -1020,11 +1064,6 @@ inline std::string Request::get_header_value(const char* key) const return detail::get_header_value(headers, key, ""); } -inline void Request::set_header(const char* key, const char* val) -{ - headers.emplace(key, val); -} - inline bool Request::has_param(const char* key) const { return params.find(key) != params.end(); @@ -1485,12 +1524,18 @@ inline bool Client::read_and_close_socket(socket_t sock, const Request& req, Res }); } -inline std::shared_ptr Client::get(const char* path, Progress callback) +inline std::shared_ptr Client::get(const char* path, Progress progress) +{ + return get(path, Headers(), progress); +} + +inline std::shared_ptr Client::get(const char* path, const Headers& headers, Progress progress) { Request req; req.method = "GET"; req.path = path; - req.progress = callback; + req.headers = headers; + req.progress = progress; auto res = std::make_shared(); @@ -1498,9 +1543,15 @@ inline std::shared_ptr Client::get(const char* path, Progress callback } inline std::shared_ptr Client::head(const char* path) +{ + return head(path, Headers()); +} + +inline std::shared_ptr Client::head(const char* path, const Headers& headers) { Request req; req.method = "HEAD"; + req.headers = headers; req.path = path; auto res = std::make_shared(); @@ -1510,12 +1561,19 @@ inline std::shared_ptr Client::head(const char* path) inline std::shared_ptr Client::post( const char* path, const std::string& body, const char* content_type) +{ + return post(path, Headers(), body, content_type); +} + +inline std::shared_ptr Client::post( + const char* path, const Headers& headers, const std::string& body, const char* content_type) { Request req; req.method = "POST"; + req.headers = headers; req.path = path; - req.set_header("Content-Type", content_type); + req.headers.emplace("Content-Type", content_type); req.body = body; auto res = std::make_shared(); @@ -1523,8 +1581,12 @@ inline std::shared_ptr Client::post( return send(req, *res) ? res : nullptr; } -inline std::shared_ptr Client::post( - const char* path, const Params& params) +inline std::shared_ptr Client::post(const char* path, const Params& params) +{ + return post(path, Headers(), params); +} + +inline std::shared_ptr Client::post(const char* path, const Headers& headers, const Params& params) { std::string query; for (auto it = params.begin(); it != params.end(); ++it) { @@ -1536,7 +1598,7 @@ inline std::shared_ptr Client::post( query += it->second; } - return post(path, query, "application/x-www-form-urlencoded"); + return post(path, headers, query, "application/x-www-form-urlencoded"); } /* diff --git a/test/test.cc b/test/test.cc index abda551..d1996da 100644 --- a/test/test.cc +++ b/test/test.cc @@ -83,32 +83,59 @@ TEST(SocketTest, OpenCloseWithAI_PASSIVE) TEST(GetHeaderValueTest, DefaultValue) { - Headers map = {{"Dummy","Dummy"}}; - auto val = detail::get_header_value(map, "Content-Type", "text/plain"); - ASSERT_STREQ("text/plain", val); + Headers headers = {{"Dummy","Dummy"}}; + auto val = detail::get_header_value(headers, "Content-Type", "text/plain"); + EXPECT_STREQ("text/plain", val); } TEST(GetHeaderValueTest, DefaultValueInt) { - Headers map = {{"Dummy","Dummy"}}; - auto val = detail::get_header_value_int(map, "Content-Length", 100); + Headers headers = {{"Dummy","Dummy"}}; + auto val = detail::get_header_value_int(headers, "Content-Length", 100); EXPECT_EQ(100, val); } TEST(GetHeaderValueTest, RegularValue) { - Headers map = {{"Content-Type", "text/html"}, {"Dummy", "Dummy"}}; - auto val = detail::get_header_value(map, "Content-Type", "text/plain"); - ASSERT_STREQ("text/html", val); + Headers headers = {{"Content-Type", "text/html"}, {"Dummy", "Dummy"}}; + auto val = detail::get_header_value(headers, "Content-Type", "text/plain"); + EXPECT_STREQ("text/html", val); } TEST(GetHeaderValueTest, RegularValueInt) { - Headers map = {{"Content-Length", "100"}, {"Dummy", "Dummy"}}; - auto val = detail::get_header_value_int(map, "Content-Length", 0); + Headers headers = {{"Content-Length", "100"}, {"Dummy", "Dummy"}}; + auto val = detail::get_header_value_int(headers, "Content-Length", 0); EXPECT_EQ(100, val); } +TEST(GetHeaderValueTest, Range) +{ + { + Headers headers = { make_range_header(1) }; + auto val = detail::get_header_value(headers, "Range", 0); + EXPECT_STREQ("bytes=1-", val); + } + + { + Headers headers = { make_range_header(1, 10) }; + auto val = detail::get_header_value(headers, "Range", 0); + EXPECT_STREQ("bytes=1-10", val); + } + + { + Headers headers = { make_range_header(1, 10, 100) }; + auto val = detail::get_header_value(headers, "Range", 0); + EXPECT_STREQ("bytes=1-10, 100-", val); + } + + { + Headers headers = { make_range_header(1, 10, 100, 200) }; + auto val = detail::get_header_value(headers, "Range", 0); + EXPECT_STREQ("bytes=1-10, 100-200", val); + } +} + void testChunkedEncoding(httplib::HttpVersion ver) { auto host = "www.httpwatch.com"; @@ -136,6 +163,42 @@ TEST(ChunkedEncodingTest, FromHTTPWatch) testChunkedEncoding(httplib::HttpVersion::v1_1); } +TEST(RangeTest, FromHTTPBin) +{ + auto host = "httpbin.org"; + auto port = 80; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + httplib::SSLClient cli(host, port, httplib::HttpVersion::v1_1); +#else + httplib::Client cli(host, port, httplib::HttpVersion::v1_1); +#endif + + { + httplib::Headers headers; + auto res = cli.get("/range/32", headers); + ASSERT_TRUE(res != nullptr); + EXPECT_EQ(res->body, "abcdefghijklmnopqrstuvwxyzabcdef"); + EXPECT_EQ(200, res->status); + } + + { + httplib::Headers headers = { httplib::make_range_header(1) }; + auto res = cli.get("/range/32", headers); + ASSERT_TRUE(res != nullptr); + EXPECT_EQ(res->body, "bcdefghijklmnopqrstuvwxyzabcdef"); + EXPECT_EQ(206, res->status); + } + + { + httplib::Headers headers = { httplib::make_range_header(1, 10) }; + auto res = cli.get("/range/32", headers); + ASSERT_TRUE(res != nullptr); + EXPECT_EQ(res->body, "bcdefghijk"); + EXPECT_EQ(206, res->status); + } +} + class ServerTest : public ::testing::Test { protected: ServerTest() @@ -422,11 +485,11 @@ TEST_F(ServerTest, LongHeader) host_and_port += ":"; host_and_port += std::to_string(PORT); - req.set_header("Host", host_and_port.c_str()); - req.set_header("Accept", "*/*"); - req.set_header("User-Agent", "cpp-httplib/0.1"); + req.headers.emplace("Host", host_and_port.c_str()); + req.headers.emplace("Accept", "*/*"); + req.headers.emplace("User-Agent", "cpp-httplib/0.1"); - req.set_header("Header-Name", "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); + req.headers.emplace("Header-Name", "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); auto res = std::make_shared(); auto ret = cli_.send(req, *res); @@ -446,11 +509,11 @@ TEST_F(ServerTest, TooLongHeader) host_and_port += ":"; host_and_port += std::to_string(PORT); - req.set_header("Host", host_and_port.c_str()); - req.set_header("Accept", "*/*"); - req.set_header("User-Agent", "cpp-httplib/0.1"); + req.headers.emplace("Host", host_and_port.c_str()); + req.headers.emplace("Accept", "*/*"); + req.headers.emplace("User-Agent", "cpp-httplib/0.1"); - req.set_header("Header-Name", "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); + req.headers.emplace("Header-Name", "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); auto res = std::make_shared(); auto ret = cli_.send(req, *res); @@ -498,10 +561,10 @@ TEST_F(ServerTest, MultipartFormData) host_and_port += ":"; host_and_port += std::to_string(PORT); - req.set_header("Host", host_and_port.c_str()); - req.set_header("Accept", "*/*"); - req.set_header("User-Agent", "cpp-httplib/0.1"); - req.set_header("Content-Type", "multipart/form-data; boundary=----WebKitFormBoundarysBREP3G013oUrLB4"); + req.headers.emplace("Host", host_and_port.c_str()); + req.headers.emplace("Accept", "*/*"); + req.headers.emplace("User-Agent", "cpp-httplib/0.1"); + req.headers.emplace("Content-Type", "multipart/form-data; boundary=----WebKitFormBoundarysBREP3G013oUrLB4"); req.body = "------WebKitFormBoundarysBREP3G013oUrLB4\r\nContent-Disposition: form-data; name=\"text1\"\r\n\r\ntext default\r\n------WebKitFormBoundarysBREP3G013oUrLB4\r\nContent-Disposition: form-data; name=\"text2\"\r\n\r\naωb\r\n------WebKitFormBoundarysBREP3G013oUrLB4\r\nContent-Disposition: form-data; name=\"file1\"; filename=\"hello.txt\"\r\nContent-Type: text/plain\r\n\r\nh\ne\n\nl\nl\no\n\r\n------WebKitFormBoundarysBREP3G013oUrLB4\r\nContent-Disposition: form-data; name=\"file2\"; filename=\"world.json\"\r\nContent-Type: application/json\r\n\r\n{\n \"world\", true\n}\n\r\n------WebKitFormBoundarysBREP3G013oUrLB4\r\nContent-Disposition: form-data; name=\"file3\"; filename=\"\"\r\nContent-Type: application/octet-stream\r\n\r\n\r\n------WebKitFormBoundarysBREP3G013oUrLB4--\r\n";