Compare commits

..

33 Commits

Author SHA1 Message Date
yhirose ccbddd8842 Allow LargeRandomData test only on Windows 2021-07-22 22:17:31 -04:00
yosh-matsuda 879dd261c2 Fix gzip compression/decompression over 4 GiB data size (#1002)
* Fix gzip compression/decompression over 4 GiB data size

* Add gzip test for large random data
2021-07-22 22:07:40 -04:00
xxrl 52f5eb5980 [Fix] ca_cert_path/ce_cert_store lose (#1004)
When redirect from http to https, user setting for ca_cert will lose

issue: #1003
2021-07-22 21:41:41 -04:00
Gregor Jasny ea2f69a0d7 Add httplib::Error to std::string function (#999)
Fixes: #978
2021-07-19 21:17:44 -04:00
Gregor Jasny 9f2064a8ed Fix remaining test warnings (#1001)
* Use portable way to encode ESC

'\e' is a GNU extension

* Use length specifier for size_t
2021-07-19 21:17:18 -04:00
yhirose e3750d9ddf Simplefied server APIs 2021-07-17 18:09:30 -04:00
yhirose c1eee3012e Fix #998 2021-07-17 17:18:56 -04:00
Gregor Jasny 6b08babbd2 Use googletest 1.11.0 (#1000)
* Update googletest to version 1.11.0

* Fix test warnings
2021-07-17 13:21:03 -04:00
yhirose 215b81342e Added a test case for #996 2021-07-15 08:24:06 -04:00
yhirose 06bfa7e08b Fix #979 2021-07-14 22:49:49 -04:00
yhirose 3d83cbb872 Improve string compare performance 2021-07-12 23:51:56 -04:00
yhirose 8a803b30f6 Fix #990 2021-07-12 23:46:25 -04:00
yhirose 80be649de7 Fix #961 2021-06-26 18:26:33 -04:00
yhirose 9648f950f5 Updated README 2021-06-18 08:45:50 -04:00
Gregor Jasny 6b9ffc8bec Remove dead code (#965) 2021-06-18 07:20:34 -04:00
Simon Edlund d903053faf Update httplib.h (#964)
operator""_ replaced by operator""_t
2021-06-17 10:57:25 -04:00
yhirose 676f1b5a26 Updated the user agent string 2021-06-14 08:43:12 -04:00
Baruch Nissenbaum b8dec12f15 Limit SSL_ERROR_WANT_READ retries to 1 sec (#957)
retry with 1ms delays to prevent CPU hoggin
2021-06-14 08:41:20 -04:00
yhirose fc9b223acc Updated copyright year 2021-06-11 14:45:35 -04:00
CncGpp ba824089d7 Fix code err code 401 when the password is empty in base_auth. (#958) 2021-06-11 14:39:33 -04:00
Andrea Pappacoda 1a2faf09e0 Add header-only Meson support (#955)
* Add header-only Meson support
This allows users to call `dependency('httplib')` and have the include
directory automatically configured

* Rename `httplib` to `cpp-httplib`
2021-06-05 16:45:00 -04:00
yhirose 5a43bb8149 Implemented #946 in a different way 2021-06-02 13:45:47 -04:00
yhirose 0104614656 Code refactoring 2021-06-02 08:11:31 -04:00
yhirose 77a77f6d2d Added set_default_headers on Server 2021-05-23 19:06:28 -04:00
Mathias Laurin 089b9daa1c Fix virtual call in ClientImpl::~ClientImpl() (#942)
* Fix virtual call in ClientImpl::~ClientImpl()

This fixes a warning in clang tidy:

> Call to virtual method 'ClientImpl::shutdown_ssl' during
> destruction bypasses virtual dispatch

ClientImpl::~ClientImpl() calls lock_socket_and_shutdown_and_close()
that itself calls shutdown_ssl().  However, shutdown_ssl() is virtual
and C++ does not perform virtual dispatch in destructors, which results
in the wrong overload being called.

This change adds a non-virtual shutdown_ssl_impl() function that is
called from ~SSLClient().  We also inline sock_socket_and_shutdown_and_close()
and removes the virtual call in ~ClientImpl().

* Inline and remove lock_socket_and_shutdown_and_close()

The function only has one caller.
2021-05-22 20:15:20 -04:00
yhirose ba34ea4ee8 Fix #944 2021-05-22 19:24:50 -04:00
Baruch Nissenbaum 2917b8a005 Explicit cast from size_t to uInt (#941)
* Explicit cast from size_t to uInt

* static_cast<uInt> instead of C style cast
2021-05-19 18:03:59 -04:00
Joseph Huang dcf24d45a2 fix ssesvr use of deleted function (#938) 2021-05-18 23:19:15 -04:00
yhirose 75fdb06696 Added a missing member in copy_settings. 2021-05-15 09:14:44 -04:00
Alex Hornung e00ad37580 Add option to bypass URL encode of path (#934) 2021-05-15 08:48:25 -04:00
Vincent Stumpf 5cfb70c2b4 Fix some shadowed variable warnings (#935) 2021-05-15 08:46:16 -04:00
Alessio Pollero 2a70c45697 Fix client.cc code, since res.error() without operator overloading… (#921)
* Fix client.cc code, since res.error() without operator overloading causing error in Xcode

* Add unit test to check new error to string with operator overloading

* Add inline as requested in code review comment
2021-05-01 13:29:23 -04:00
Aswin Raj Kharel c58b00580e reserving before encoding (#912) 2021-04-24 16:19:14 -04:00
9 changed files with 13485 additions and 16882 deletions
+29 -28
View File
@@ -53,6 +53,33 @@ res->body;
1. Run server at https://repl.it/@yhirose/cpp-httplib-server
2. Run client at https://repl.it/@yhirose/cpp-httplib-client
SSL Support
-----------
SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked.
NOTE: cpp-httplib currently supports only version 1.1.1.
```c++
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "path/to/httplib.h"
// Server
httplib::SSLServer svr("./cert.pem", "./key.pem");
// Client
httplib::Client cli("https://localhost:1234"); // scheme + host
httplib::SSLClient cli("localhost:1234"); // host
// Use your CA bundle
cli.set_ca_cert_path("./ca-bundle.crt");
// Disable cert verification
cli.enable_server_certificate_verification(false);
```
Note: When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might be to set up a signal handler for SIGPIPE to handle or ignore it yourself.
Server
------
@@ -266,7 +293,7 @@ svr.Get("/stream", [&](const Request &req, Response &res) {
sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
return true; // return 'false' if you want to cancel the process.
},
[data] { delete data; });
[data](bool success) { delete data; });
});
```
@@ -719,32 +746,6 @@ res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}});
res->body; // Compressed data
```
SSL Support
-----------
SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked.
NOTE: cpp-httplib currently supports only version 1.1.1.
```c++
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "path/to/httplib.h"
// Server
httplib::SSLServer svr("./cert.pem", "./key.pem");
// Client
httplib::Client cli("https://localhost:1234");
// Use your CA bundle
cli.set_ca_cert_path("./ca-bundle.crt");
// Disable cert verification
cli.enable_server_certificate_verification(false);
```
Note: When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might be to set up a signal handler for SIGPIPE to handle or ignore it yourself.
Split httplib.h into .h and .cc
-------------------------------
@@ -781,7 +782,7 @@ Note: Windows 8 or lower and Cygwin on Windows are not supported.
License
-------
MIT license (© 2020 Yuji Hirose)
MIT license (© 2021 Yuji Hirose)
Special Thanks To
-----------------
+2 -9
View File
@@ -1,10 +1,3 @@
//
// sse.cc
//
// Copyright (c) 2020 Yuji Hirose. All rights reserved.
// MIT License
//
#include <atomic>
#include <chrono>
#include <condition_variable>
@@ -39,8 +32,8 @@ public:
private:
mutex m_;
condition_variable cv_;
atomic_int id_ = 0;
atomic_int cid_ = -1;
atomic_int id_{0};
atomic_int cid_{-1};
string message_;
};
+396 -288
View File
File diff suppressed because it is too large Load Diff
+7
View File
@@ -0,0 +1,7 @@
project('cpp-httplib', 'cpp', license: 'MIT')
cpp_httplib_dep = declare_dependency(include_directories: include_directories('.'))
if meson.version().version_compare('>=0.54.0')
meson.override_dependency('cpp-httplib', cpp_httplib_dep)
endif
+1 -1
View File
@@ -1,5 +1,5 @@
#CXX = clang++
CXXFLAGS = -g -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I.. -I. -Wall -Wextra -Wtype-limits -Wconversion #-fsanitize=address
CXXFLAGS = -g -std=c++11 -I.. -I. -Wall -Wextra -Wtype-limits -Wconversion #-fsanitize=address
OPENSSL_DIR = /usr/local/opt/openssl@1.1
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
+5790 -2421
View File
File diff suppressed because it is too large Load Diff
+6845 -14039
View File
File diff suppressed because it is too large Load Diff
+19 -4
View File
@@ -27,13 +27,28 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <iostream>
#include <cstdio>
#include "gtest/gtest.h"
GTEST_API_ int main(int argc, char **argv) {
std::cout << "Running main() from gtest_main.cc\n";
#if GTEST_OS_ESP8266 || GTEST_OS_ESP32
#if GTEST_OS_ESP8266
extern "C" {
#endif
void setup() {
testing::InitGoogleTest();
}
void loop() { RUN_ALL_TESTS(); }
#if GTEST_OS_ESP8266
}
#endif
#else
GTEST_API_ int main(int argc, char **argv) {
printf("Running main() from %s\n", __FILE__);
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
#endif
+396 -92
View File
@@ -5,6 +5,7 @@
#include <atomic>
#include <chrono>
#include <future>
#include <sstream>
#include <stdexcept>
#include <thread>
@@ -171,7 +172,7 @@ TEST(GetHeaderValueTest, SetContent) {
EXPECT_EQ("text/html", res.get_header_value("Content-Type"));
res.set_content("text", "text/plain");
EXPECT_EQ(1, res.get_header_value_count("Content-Type"));
EXPECT_EQ(1U, res.get_header_value_count("Content-Type"));
EXPECT_EQ("text/plain", res.get_header_value("Content-Type"));
}
@@ -436,26 +437,6 @@ TEST(ChunkedEncodingTest, WithResponseHandlerAndContentReceiver) {
EXPECT_EQ(out, body);
}
TEST(DefaultHeadersTest, FromHTTPBin) {
Client cli("httpbin.org");
cli.set_default_headers({make_range_header({{1, 10}})});
cli.set_connection_timeout(5);
{
auto res = cli.Get("/range/32");
ASSERT_TRUE(res);
EXPECT_EQ("bcdefghijk", res->body);
EXPECT_EQ(206, res->status);
}
{
auto res = cli.Get("/range/32");
ASSERT_TRUE(res);
EXPECT_EQ("bcdefghijk", res->body);
EXPECT_EQ(206, res->status);
}
}
TEST(RangeTest, FromHTTPBin) {
auto host = "httpbin.org";
@@ -547,6 +528,23 @@ TEST(ConnectionErrorTest, InvalidHost2) {
EXPECT_EQ(Error::Connection, res.error());
}
TEST(ConnectionErrorTest, InvalidHostCheckResultErrorToString) {
auto host = "httpbin.org/";
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
SSLClient cli(host);
#else
Client cli(host);
#endif
cli.set_connection_timeout(std::chrono::seconds(2));
auto res = cli.Get("/");
ASSERT_TRUE(!res);
stringstream s;
s << "error code: " << res.error();
EXPECT_EQ("error code: Connection (2)", s.str());
}
TEST(ConnectionErrorTest, InvalidPort) {
auto host = "localhost";
auto port = 44380;
@@ -841,6 +839,18 @@ TEST(HttpsToHttpRedirectTest3, Redirect) {
EXPECT_EQ(200, res->status);
}
TEST(UrlWithSpace, Redirect) {
SSLClient cli("edge.forgecdn.net");
cli.set_follow_location(true);
auto res = cli.Get("/files/2595/310/Neat 1.4-17.jar");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ(18527U, res->get_header_value<uint64_t>("Content-Length"));
}
#endif
TEST(RedirectToDifferentPort, Redirect) {
Server svr8080;
Server svr8081;
@@ -880,16 +890,6 @@ TEST(RedirectToDifferentPort, Redirect) {
ASSERT_FALSE(svr8081.is_running());
}
TEST(UrlWithSpace, Redirect) {
SSLClient cli("edge.forgecdn.net");
cli.set_follow_location(true);
auto res = cli.Get("/files/2595/310/Neat 1.4-17.jar");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ(18527, res->get_header_value<uint64_t>("Content-Length"));
}
TEST(RedirectFromPageWithContent, Redirect) {
Server svr;
@@ -945,7 +945,101 @@ TEST(RedirectFromPageWithContent, Redirect) {
ASSERT_FALSE(svr.is_running());
}
#endif
TEST(RedirectFromPageWithContentIP6, Redirect) {
Server svr;
svr.Get("/1", [&](const Request & /*req*/, Response &res) {
res.set_content("___", "text/plain");
// res.set_redirect("/2");
res.set_redirect("http://[::1]:1234/2");
});
svr.Get("/2", [&](const Request &req, Response &res) {
auto host_header = req.headers.find("Host");
ASSERT_TRUE(host_header != req.headers.end());
EXPECT_EQ("[::1]:1234", host_header->second);
res.set_content("Hello World!", "text/plain");
});
auto th = std::thread([&]() { svr.listen("::1", 1234); });
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
{
Client cli("http://[::1]:1234");
cli.set_follow_location(true);
std::string body;
auto res = cli.Get("/1", [&](const char *data, size_t data_length) {
body.append(data, data_length);
return true;
});
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("Hello World!", body);
}
{
Client cli("http://[::1]:1234");
std::string body;
auto res = cli.Get("/1", [&](const char *data, size_t data_length) {
body.append(data, data_length);
return true;
});
ASSERT_TRUE(res);
EXPECT_EQ(302, res->status);
EXPECT_EQ("___", body);
}
svr.stop();
th.join();
ASSERT_FALSE(svr.is_running());
}
TEST(PathUrlEncodeTest, PathUrlEncode) {
Server svr;
svr.Get("/foo", [](const Request &req, Response &res) {
auto a = req.params.find("a");
if (a != req.params.end()) {
res.set_content((*a).second, "text/plain");
res.status = 200;
} else {
res.status = 400;
}
});
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
{
Client cli(HOST, PORT);
cli.set_url_encode(false);
auto res = cli.Get("/foo?a=explicitly+encoded");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
// This expects it back with a space, as the `+` won't have been
// url-encoded, and server-side the params get decoded turning `+`
// into spaces.
EXPECT_EQ("explicitly encoded", res->body);
}
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
}
TEST(BindServerTest, BindDualStack) {
Server svr;
@@ -1132,9 +1226,9 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("Routing Handler", res->body);
EXPECT_EQ(1, res->get_header_value_count("PRE_ROUTING"));
EXPECT_EQ(1U, res->get_header_value_count("PRE_ROUTING"));
EXPECT_EQ("on", res->get_header_value("PRE_ROUTING"));
EXPECT_EQ(1, res->get_header_value_count("POST_ROUTING"));
EXPECT_EQ(1U, res->get_header_value_count("POST_ROUTING"));
EXPECT_EQ("on", res->get_header_value("POST_ROUTING"));
}
@@ -1145,8 +1239,8 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("Hello World!\n", res->body);
EXPECT_EQ(0, res->get_header_value_count("PRE_ROUTING"));
EXPECT_EQ(0, res->get_header_value_count("POST_ROUTING"));
EXPECT_EQ(0U, res->get_header_value_count("PRE_ROUTING"));
EXPECT_EQ(0U, res->get_header_value_count("POST_ROUTING"));
}
{
@@ -1156,8 +1250,8 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
ASSERT_TRUE(res);
EXPECT_EQ(404, res->status);
EXPECT_EQ("Error", res->body);
EXPECT_EQ(0, res->get_header_value_count("PRE_ROUTING"));
EXPECT_EQ(0, res->get_header_value_count("POST_ROUTING"));
EXPECT_EQ(0U, res->get_header_value_count("PRE_ROUTING"));
EXPECT_EQ(0U, res->get_header_value_count("POST_ROUTING"));
}
svr.stop();
@@ -1216,31 +1310,31 @@ protected:
.Get("/http_response_splitting",
[&](const Request & /*req*/, Response &res) {
res.set_header("a", "1\r\nSet-Cookie: a=1");
EXPECT_EQ(0, res.headers.size());
EXPECT_EQ(0U, res.headers.size());
EXPECT_FALSE(res.has_header("a"));
res.set_header("a", "1\nSet-Cookie: a=1");
EXPECT_EQ(0, res.headers.size());
EXPECT_EQ(0U, res.headers.size());
EXPECT_FALSE(res.has_header("a"));
res.set_header("a", "1\rSet-Cookie: a=1");
EXPECT_EQ(0, res.headers.size());
EXPECT_EQ(0U, res.headers.size());
EXPECT_FALSE(res.has_header("a"));
res.set_header("a\r\nb", "0");
EXPECT_EQ(0, res.headers.size());
EXPECT_EQ(0U, res.headers.size());
EXPECT_FALSE(res.has_header("a"));
res.set_header("a\rb", "0");
EXPECT_EQ(0, res.headers.size());
EXPECT_EQ(0U, res.headers.size());
EXPECT_FALSE(res.has_header("a"));
res.set_header("a\nb", "0");
EXPECT_EQ(0, res.headers.size());
EXPECT_EQ(0U, res.headers.size());
EXPECT_FALSE(res.has_header("a"));
res.set_redirect("1\r\nSet-Cookie: a=1");
EXPECT_EQ(0, res.headers.size());
EXPECT_EQ(0U, res.headers.size());
EXPECT_FALSE(res.has_header("Location"));
})
.Get("/slow",
@@ -1344,7 +1438,10 @@ protected:
(*i)++;
return true;
},
[i] { delete i; });
[i](bool success) {
EXPECT_TRUE(success);
delete i;
});
})
.Get("/streamed",
[&](const Request & /*req*/, Response &res) {
@@ -1366,11 +1463,15 @@ protected:
const auto &d = *data;
auto out_len =
std::min(static_cast<size_t>(length), DATA_CHUNK_SIZE);
auto ret = sink.write(&d[static_cast<size_t>(offset)], out_len);
auto ret =
sink.write(&d[static_cast<size_t>(offset)], out_len);
EXPECT_TRUE(ret);
return true;
},
[data] { delete data; });
[data](bool success) {
EXPECT_TRUE(success);
delete data;
});
})
.Get("/streamed-cancel",
[&](const Request & /*req*/, Response &res) {
@@ -1551,7 +1652,7 @@ protected:
} else {
std::string body;
content_reader([&](const char *data, size_t data_length) {
EXPECT_EQ(data_length, 7);
EXPECT_EQ(7U, data_length);
body.append(data, data_length);
return true;
});
@@ -1599,7 +1700,7 @@ protected:
})
.Post("/binary",
[&](const Request &req, Response &res) {
EXPECT_EQ(4, req.body.size());
EXPECT_EQ(4U, req.body.size());
EXPECT_EQ("application/octet-stream",
req.get_header_value("Content-Type"));
EXPECT_EQ("4", req.get_header_value("Content-Length"));
@@ -1607,7 +1708,7 @@ protected:
})
.Put("/binary",
[&](const Request &req, Response &res) {
EXPECT_EQ(4, req.body.size());
EXPECT_EQ(4U, req.body.size());
EXPECT_EQ("application/octet-stream",
req.get_header_value("Content-Type"));
EXPECT_EQ("4", req.get_header_value("Content-Length"));
@@ -1615,7 +1716,7 @@ protected:
})
.Patch("/binary",
[&](const Request &req, Response &res) {
EXPECT_EQ(4, req.body.size());
EXPECT_EQ(4U, req.body.size());
EXPECT_EQ("application/octet-stream",
req.get_header_value("Content-Type"));
EXPECT_EQ("4", req.get_header_value("Content-Length"));
@@ -1623,7 +1724,7 @@ protected:
})
.Delete("/binary",
[&](const Request &req, Response &res) {
EXPECT_EQ(4, req.body.size());
EXPECT_EQ(4U, req.body.size());
EXPECT_EQ("application/octet-stream",
req.get_header_value("Content-Type"));
EXPECT_EQ("4", req.get_header_value("Content-Length"));
@@ -1703,7 +1804,7 @@ TEST_F(ServerTest, GetMethod200) {
EXPECT_EQ(200, res->status);
EXPECT_EQ("OK", res->reason);
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_EQ(1, res->get_header_value_count("Content-Type"));
EXPECT_EQ(1U, res->get_header_value_count("Content-Type"));
EXPECT_EQ("Hello World!", res->body);
}
@@ -1713,7 +1814,7 @@ TEST_F(ServerTest, GetMethod200withPercentEncoding) {
EXPECT_EQ("HTTP/1.1", res->version);
EXPECT_EQ(200, res->status);
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_EQ(1, res->get_header_value_count("Content-Type"));
EXPECT_EQ(1U, res->get_header_value_count("Content-Type"));
EXPECT_EQ("Hello World!", res->body);
}
@@ -1991,25 +2092,25 @@ TEST_F(ServerTest, Binary) {
"application/octet-stream");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ(4, res->body.size());
ASSERT_EQ(4U, res->body.size());
res = cli_.Put("/binary", binary.data(), binary.size(),
"application/octet-stream");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ(4, res->body.size());
ASSERT_EQ(4U, res->body.size());
res = cli_.Patch("/binary", binary.data(), binary.size(),
"application/octet-stream");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ(4, res->body.size());
ASSERT_EQ(4U, res->body.size());
res = cli_.Delete("/binary", binary.data(), binary.size(),
"application/octet-stream");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ(4, res->body.size());
ASSERT_EQ(4U, res->body.size());
}
TEST_F(ServerTest, BinaryString) {
@@ -2018,22 +2119,22 @@ TEST_F(ServerTest, BinaryString) {
auto res = cli_.Post("/binary", binary, "application/octet-stream");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ(4, res->body.size());
ASSERT_EQ(4U, res->body.size());
res = cli_.Put("/binary", binary, "application/octet-stream");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ(4, res->body.size());
ASSERT_EQ(4U, res->body.size());
res = cli_.Patch("/binary", binary, "application/octet-stream");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ(4, res->body.size());
ASSERT_EQ(4U, res->body.size());
res = cli_.Delete("/binary", binary, "application/octet-stream");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
ASSERT_EQ(4, res->body.size());
ASSERT_EQ(4U, res->body.size());
}
TEST_F(ServerTest, EmptyRequest) {
@@ -2349,7 +2450,7 @@ TEST_F(ServerTest, GetStreamedWithRangeMultipart) {
EXPECT_EQ(206, res->status);
EXPECT_EQ("269", res->get_header_value("Content-Length"));
EXPECT_EQ(false, res->has_header("Content-Range"));
EXPECT_EQ(269, res->body.size());
EXPECT_EQ(269U, res->body.size());
}
TEST_F(ServerTest, GetStreamedEndless) {
@@ -2436,7 +2537,7 @@ TEST_F(ServerTest, GetWithRangeMultipart) {
EXPECT_EQ(206, res->status);
EXPECT_EQ("269", res->get_header_value("Content-Length"));
EXPECT_EQ(false, res->has_header("Content-Range"));
EXPECT_EQ(269, res->body.size());
EXPECT_EQ(269U, res->body.size());
}
TEST_F(ServerTest, GetWithRangeMultipartOffsetGreaterThanContent) {
@@ -2676,10 +2777,12 @@ TEST_F(ServerTest, PutLargeFileWithGzip) {
TEST_F(ServerTest, PutLargeFileWithGzip2) {
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
Client cli("https://localhost:1234");
std::string s = std::string("https://") + HOST + ":" + std::to_string(PORT);
Client cli(s.c_str());
cli.enable_server_certificate_verification(false);
#else
Client cli("http://localhost:1234");
std::string s = std::string("http://") + HOST + ":" + std::to_string(PORT);
Client cli(s.c_str());
#endif
cli.set_compress(true);
@@ -2765,6 +2868,54 @@ TEST(GzipDecompressor, ChunkedDecompression) {
}
ASSERT_EQ(data, decompressed_data);
}
#ifdef _WIN32
TEST(GzipDecompressor, LargeRandomData) {
// prepare large random data that is difficult to be compressed and is
// expected to have large size even when compressed
std::random_device seed_gen;
std::mt19937 random(seed_gen());
constexpr auto large_size_byte = 4294967296UL; // 4GiB
constexpr auto data_size = large_size_byte + 134217728UL; // + 128MiB
std::vector<std::uint32_t> data(data_size / sizeof(std::uint32_t));
std::generate(data.begin(), data.end(), [&]() { return random(); });
// compress data over 4GiB
std::string compressed_data;
compressed_data.reserve(large_size_byte + 536870912UL); // + 512MiB reserved
httplib::detail::gzip_compressor compressor;
auto result = compressor.compress(reinterpret_cast<const char *>(data.data()),
data.size() * sizeof(std::uint32_t), true,
[&](const char *data, size_t size) {
compressed_data.insert(
compressed_data.size(), data, size);
return true;
});
ASSERT_TRUE(result);
// FIXME: compressed data size is expected to be greater than 4GiB,
// but there is no guarantee
// ASSERT_TRUE(compressed_data.size() >= large_size_byte);
// decompress data over 4GiB
std::string decompressed_data;
decompressed_data.reserve(data_size);
httplib::detail::gzip_decompressor decompressor;
result = decompressor.decompress(
compressed_data.data(), compressed_data.size(),
[&](const char *data, size_t size) {
decompressed_data.insert(decompressed_data.size(), data, size);
return true;
});
ASSERT_TRUE(result);
// compare
ASSERT_EQ(data_size, decompressed_data.size());
ASSERT_TRUE(std::memcmp(data.data(), decompressed_data.data(), data_size) ==
0);
}
#endif
#endif
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
@@ -3025,7 +3176,7 @@ TEST_F(ServerTest, GzipWithContentReceiver) {
std::string body;
auto res = cli_.Get("/compress", headers,
[&](const char *data, uint64_t data_length) {
EXPECT_EQ(data_length, 100);
EXPECT_EQ(100U, data_length);
body.append(data, data_length);
return true;
});
@@ -3051,14 +3202,14 @@ TEST_F(ServerTest, GzipWithoutDecompressing) {
EXPECT_EQ("gzip", res->get_header_value("Content-Encoding"));
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_EQ("33", res->get_header_value("Content-Length"));
EXPECT_EQ(33, res->body.size());
EXPECT_EQ(33U, res->body.size());
EXPECT_EQ(200, res->status);
}
TEST_F(ServerTest, GzipWithContentReceiverWithoutAcceptEncoding) {
std::string body;
auto res = cli_.Get("/compress", [&](const char *data, uint64_t data_length) {
EXPECT_EQ(data_length, 100);
EXPECT_EQ(100U, data_length);
body.append(data, data_length);
return true;
});
@@ -3094,7 +3245,7 @@ TEST_F(ServerTest, NoGzipWithContentReceiver) {
std::string body;
auto res = cli_.Get("/nocompress", headers,
[&](const char *data, uint64_t data_length) {
EXPECT_EQ(data_length, 100);
EXPECT_EQ(100U, data_length);
body.append(data, data_length);
return true;
});
@@ -3145,12 +3296,11 @@ static bool send_request(time_t read_timeout_sec, const std::string &req,
std::string *resp = nullptr) {
auto error = Error::Success;
auto client_sock =
detail::create_client_socket(HOST, PORT, AF_UNSPEC, false, nullptr,
/*connection_timeout_sec=*/5, 0,
/*read_timeout_sec=*/5, 0,
/*write_timeout_sec=*/5, 0,
std::string(), error);
auto client_sock = detail::create_client_socket(
HOST, PORT, AF_UNSPEC, false, nullptr,
/*connection_timeout_sec=*/5, 0,
/*read_timeout_sec=*/5, 0,
/*write_timeout_sec=*/5, 0, std::string(), error);
if (client_sock == INVALID_SOCKET) { return false; }
@@ -3191,14 +3341,14 @@ TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
// Only space and horizontal tab are whitespace. Make sure other whitespace-
// like characters are not treated the same - use vertical tab and escape.
const std::string req = "GET /validate-ws-in-headers HTTP/1.1\r\n"
"foo: \t \v bar \e\t \r\n"
"foo: \t \v bar \x1B\t \r\n"
"Connection: close\r\n"
"\r\n";
ASSERT_TRUE(send_request(5, req));
svr.stop();
t.join();
EXPECT_EQ(header_value, "\v bar \e");
EXPECT_EQ(header_value, "\v bar \x1B");
}
// Sends a raw request and verifies that there isn't a crash or exception.
@@ -3350,7 +3500,7 @@ TEST(ServerStopTest, StopServerWithChunkedTransmission) {
res.set_chunked_content_provider("text/event-stream", [](size_t offset,
DataSink &sink) {
char buffer[27];
auto size = static_cast<size_t>(sprintf(buffer, "data:%ld\n\n", offset));
auto size = static_cast<size_t>(sprintf(buffer, "data:%zd\n\n", offset));
auto ret = sink.write(buffer, size);
EXPECT_TRUE(ret);
std::this_thread::sleep_for(std::chrono::seconds(1));
@@ -3533,6 +3683,44 @@ TEST(KeepAliveTest, ReadTimeout) {
ASSERT_FALSE(svr.is_running());
}
TEST(ClientProblemDetectionTest, ContentProvider) {
Server svr;
size_t content_length = 1024 * 1024;
svr.Get("/hi", [&](const Request & /*req*/, Response &res) {
res.set_content_provider(
content_length, "text/plain",
[&](size_t offset, size_t length, DataSink &sink) {
auto out_len = std::min(length, static_cast<size_t>(1024));
std::string out(out_len, '@');
sink.write(out.data(), out_len);
return offset < 4096;
},
[](bool success) { ASSERT_FALSE(success); });
});
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
Client cli("localhost", PORT);
auto res = cli.Get("/hi", [&](const char * /*data*/, size_t /*data_length*/) {
return false;
});
ASSERT_FALSE(res);
svr.stop();
listen_thread.join();
ASSERT_FALSE(svr.is_running());
}
TEST(ErrorHandlerWithContentProviderTest, ErrorHandler) {
Server svr;
@@ -3569,26 +3757,60 @@ TEST(ErrorHandlerWithContentProviderTest, ErrorHandler) {
TEST(GetWithParametersTest, GetWithParameters) {
Server svr;
svr.Get("/", [&](const Request &req, Response &res) {
auto text = req.get_param_value("hello");
res.set_content(text, "text/plain");
svr.Get("/", [&](const Request &req, Response &) {
EXPECT_EQ("world", req.get_param_value("hello"));
EXPECT_EQ("world2", req.get_param_value("hello2"));
EXPECT_EQ("world3", req.get_param_value("hello3"));
});
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
svr.Get("/params", [&](const Request &req, Response &) {
EXPECT_EQ("world", req.get_param_value("hello"));
EXPECT_EQ("world2", req.get_param_value("hello2"));
EXPECT_EQ("world3", req.get_param_value("hello3"));
});
svr.Get(R"(/resources/([a-z0-9\\-]+))", [&](const Request& req, Response&) {
EXPECT_EQ("resource-id", req.matches[1]);
EXPECT_EQ("foo", req.get_param_value("param1"));
EXPECT_EQ("bar", req.get_param_value("param2"));
});
auto listen_thread = std::thread([&svr]() { svr.listen(HOST, PORT); });
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
std::this_thread::sleep_for(std::chrono::seconds(1));
Client cli("localhost", PORT);
{
Client cli(HOST, PORT);
Params params;
params.emplace("hello", "world");
auto res = cli.Get("/", params, Headers{});
Params params;
params.emplace("hello", "world");
params.emplace("hello2", "world2");
params.emplace("hello3", "world3");
auto res = cli.Get("/", params, Headers{});
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("world", res->body);
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
}
{
Client cli(HOST, PORT);
auto res = cli.Get("/params?hello=world&hello2=world2&hello3=world3");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
}
{
Client cli(HOST, PORT);
auto res = cli.Get("/resources/resource-id?param1=foo&param2=bar");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
}
svr.stop();
listen_thread.join();
@@ -3630,6 +3852,54 @@ TEST(GetWithParametersTest, GetWithParameters2) {
ASSERT_FALSE(svr.is_running());
}
TEST(ClientDefaultHeadersTest, DefaultHeaders) {
Client cli("httpbin.org");
cli.set_default_headers({make_range_header({{1, 10}})});
cli.set_connection_timeout(5);
{
auto res = cli.Get("/range/32");
ASSERT_TRUE(res);
EXPECT_EQ("bcdefghijk", res->body);
EXPECT_EQ(206, res->status);
}
{
auto res = cli.Get("/range/32");
ASSERT_TRUE(res);
EXPECT_EQ("bcdefghijk", res->body);
EXPECT_EQ(206, res->status);
}
}
TEST(ServerDefaultHeadersTest, DefaultHeaders) {
Server svr;
svr.set_default_headers({{"Hello", "World"}});
svr.Get("/", [&](const Request & /*req*/, Response &res) {
res.set_content("ok", "text/plain");
});
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
std::this_thread::sleep_for(std::chrono::seconds(1));
Client cli("localhost", PORT);
auto res = cli.Get("/");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("ok", res->body);
EXPECT_EQ("World", res->get_header_value("Hello"));
svr.stop();
listen_thread.join();
ASSERT_FALSE(svr.is_running());
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(KeepAliveTest, ReadTimeoutSSL) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
@@ -4193,7 +4463,7 @@ TEST(DecodeWithChunkedEncoding, BrotliEncoding) {
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ(287630, res->body.size());
EXPECT_EQ(287630U, res->body.size());
EXPECT_EQ("application/javascript; charset=utf-8",
res->get_header_value("Content-Type"));
}
@@ -4234,4 +4504,38 @@ TEST(HttpsToHttpRedirectTest3, SimpleInterface) {
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
}
TEST(HttpToHttpsRedirectTest, CertFile) {
Server svr;
ASSERT_TRUE(svr.is_valid());
svr.Get("/index", [&](const Request &, Response &res) {
res.set_redirect("https://127.0.0.1:1235/index");
svr.stop();
});
SSLServer ssl_svr(SERVER_CERT2_FILE, SERVER_PRIVATE_KEY_FILE);
ASSERT_TRUE(ssl_svr.is_valid());
ssl_svr.Get("/index", [&](const Request &, Response &res) {
res.set_content("test", "text/plain");
ssl_svr.stop();
});
thread t = thread([&]() { ASSERT_TRUE(svr.listen("127.0.0.1", PORT)); });
thread t2 = thread([&]() { ASSERT_TRUE(ssl_svr.listen("127.0.0.1", 1235)); });
std::this_thread::sleep_for(std::chrono::milliseconds(1));
Client cli("127.0.0.1", PORT);
cli.set_ca_cert_path(SERVER_CERT2_FILE);
cli.enable_server_certificate_verification(true);
cli.set_follow_location(true);
cli.set_connection_timeout(30);
auto res = cli.Get("/index");
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
t.join();
t2.join();
}
#endif