Compare commits

...

27 Commits

Author SHA1 Message Date
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
Ken Schalk 7c60e69c33 Remove redunant call to close_socket (#911) 2021-04-23 17:07:19 -04:00
yhirose 33e94891ee Updated test.cc 2021-04-22 08:04:46 -04:00
yhirose 73e0729f63 Change sink.write() to return boolean 2021-04-22 07:14:08 -04:00
yhirose 21c529229c Fixed timeout issues 2021-04-22 07:14:08 -04:00
yhirose 63643e6386 Code format 2021-04-13 20:52:49 -04:00
yhirose 6cc2edce99 Added set_address_family 2021-04-13 20:49:52 -04:00
yhirose d122ff3ca8 Code formatting 2021-04-13 12:38:45 -04:00
James Young 14c6d526b4 Use newer version-flexible TLS/SSL method (#904) 2021-04-13 09:11:38 -04:00
Philipp Hasper 28e07bca16 Fixed minor code smells (#901) 2021-04-09 14:55:21 -04:00
yhirose faa5f1d802 Additional changes for #889 2021-04-05 16:13:41 -04:00
yhirose 9d3365df54 Fix #889 2021-04-05 11:40:53 -04:00
yhirose 6ff84d34d1 Another simpler implementation of #890 (#891) 2021-04-02 18:25:04 -04:00
yhirose b845425cd0 Fix #878 2021-03-16 19:42:44 -04:00
yhirose 89519c88e2 Fix #874 2021-03-10 15:57:56 -05:00
yhirose ff813bf99d Fix #863 2021-02-17 15:36:56 -05:00
yhirose cf475bcb50 Fix #860 2021-02-12 12:21:43 -05:00
yhirose bc80d7c789 Fixed ClientStop test problem 2021-02-06 20:12:30 -05:00
yhirose b7566f6961 Resolve #852 2021-02-02 22:09:35 -05:00
3 changed files with 653 additions and 216 deletions
+2 -4
View File
@@ -20,8 +20,6 @@ using namespace std;
class EventDispatcher {
public:
EventDispatcher() {
id_ = 0;
cid_ = -1;
}
void wait_event(DataSink *sink) {
@@ -41,8 +39,8 @@ public:
private:
mutex m_;
condition_variable cv_;
atomic_int id_;
atomic_int cid_;
atomic_int id_{0};
atomic_int cid_{-1};
string message_;
};
+409 -184
View File
File diff suppressed because it is too large Load Diff
+242 -28
View File
@@ -5,8 +5,9 @@
#include <atomic>
#include <chrono>
#include <future>
#include <thread>
#include <stdexcept>
#include <thread>
#include <sstream>
#define SERVER_CERT_FILE "./cert.pem"
#define SERVER_CERT2_FILE "./cert2.pem"
@@ -525,7 +526,7 @@ TEST(ConnectionErrorTest, InvalidHost) {
auto port = 80;
Client cli(host, port);
#endif
cli.set_connection_timeout(2);
cli.set_connection_timeout(std::chrono::seconds(2));
auto res = cli.Get("/");
ASSERT_TRUE(!res);
@@ -540,24 +541,40 @@ TEST(ConnectionErrorTest, InvalidHost2) {
#else
Client cli(host);
#endif
cli.set_connection_timeout(2);
cli.set_connection_timeout(std::chrono::seconds(2));
auto res = cli.Get("/");
ASSERT_TRUE(!res);
EXPECT_EQ(Error::Connection, res.error());
}
TEST(ConnectionErrorTest, InvalidPort) {
auto host = "localhost";
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: 2", s.str());
}
TEST(ConnectionErrorTest, InvalidPort) {
auto host = "localhost";
auto port = 44380;
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
SSLClient cli(host, port);
#else
auto port = 8080;
Client cli(host, port);
#endif
cli.set_connection_timeout(2);
cli.set_connection_timeout(std::chrono::seconds(2));
auto res = cli.Get("/");
ASSERT_TRUE(!res);
@@ -574,7 +591,7 @@ TEST(ConnectionErrorTest, Timeout) {
auto port = 8080;
Client cli(host, port);
#endif
cli.set_connection_timeout(2);
cli.set_connection_timeout(std::chrono::seconds(2));
auto res = cli.Get("/");
ASSERT_TRUE(!res);
@@ -591,7 +608,7 @@ TEST(CancelTest, NoCancel) {
auto port = 80;
Client cli(host, port);
#endif
cli.set_connection_timeout(5);
cli.set_connection_timeout(std::chrono::seconds(5));
auto res = cli.Get("/range/32", [](uint64_t, uint64_t) { return true; });
ASSERT_TRUE(res);
@@ -611,7 +628,7 @@ TEST(CancelTest, WithCancelSmallPayload) {
#endif
auto res = cli.Get("/range/32", [](uint64_t, uint64_t) { return false; });
cli.set_connection_timeout(5);
cli.set_connection_timeout(std::chrono::seconds(5));
ASSERT_TRUE(!res);
EXPECT_EQ(Error::Canceled, res.error());
}
@@ -626,7 +643,7 @@ TEST(CancelTest, WithCancelLargePayload) {
auto port = 80;
Client cli(host, port);
#endif
cli.set_connection_timeout(5);
cli.set_connection_timeout(std::chrono::seconds(5));
uint32_t count = 0;
auto res = cli.Get("/range/65536",
@@ -890,8 +907,100 @@ TEST(UrlWithSpace, Redirect) {
EXPECT_EQ(200, res->status);
EXPECT_EQ(18527, res->get_header_value<uint64_t>("Content-Length"));
}
TEST(RedirectFromPageWithContent, Redirect) {
Server svr;
svr.Get("/1", [&](const Request & /*req*/, Response &res) {
res.set_content("___", "text/plain");
res.set_redirect("/2");
});
svr.Get("/2", [&](const Request & /*req*/, Response &res) {
res.set_content("Hello World!", "text/plain");
});
auto th = std::thread([&]() { 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);
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("localhost", PORT);
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());
}
#endif
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;
@@ -982,11 +1091,12 @@ TEST(ErrorHandlerTest, ContentLength) {
TEST(ExceptionHandlerTest, ContentLength) {
Server svr;
svr.set_exception_handler([](const Request & /*req*/, Response &res, std::exception & /*e*/) {
res.status = 500;
res.set_content("abcdefghijklmnopqrstuvwxyz",
"text/html"); // <= Content-Length still 13
});
svr.set_exception_handler(
[](const Request & /*req*/, Response &res, std::exception & /*e*/) {
res.status = 500;
res.set_content("abcdefghijklmnopqrstuvwxyz",
"text/html"); // <= Content-Length still 13
});
svr.Get("/hi", [](const Request & /*req*/, Response &res) {
res.set_content("Hello World!\n", "text/plain");
@@ -1310,7 +1420,8 @@ protected:
const auto &d = *data;
auto out_len =
std::min(static_cast<size_t>(length), DATA_CHUNK_SIZE);
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; });
@@ -1644,6 +1755,7 @@ TEST_F(ServerTest, GetMethod200) {
ASSERT_TRUE(res);
EXPECT_EQ("HTTP/1.1", res->version);
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("Hello World!", res->body);
@@ -2315,7 +2427,8 @@ TEST_F(ServerTest, ClientStop) {
auto res = cli_.Get("/streamed-cancel",
[&](const char *, uint64_t) { return true; });
ASSERT_TRUE(!res);
EXPECT_TRUE(res.error() == Error::Canceled || res.error() == Error::Read);
EXPECT_TRUE(res.error() == Error::Canceled ||
res.error() == Error::Read || res.error() == Error::Write);
}));
}
@@ -2463,7 +2576,8 @@ TEST_F(ServerTest, SlowPost) {
auto res = cli_.Post(
"/slowpost", 64 * 1024 * 1024,
[&](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
sink.write(buffer, sizeof(buffer));
auto ret = sink.write(buffer, sizeof(buffer));
EXPECT_TRUE(ret);
return true;
},
"text/plain");
@@ -2476,7 +2590,7 @@ TEST_F(ServerTest, SlowPostFail) {
char buffer[64 * 1024];
memset(buffer, 0x42, sizeof(buffer));
cli_.set_write_timeout(0, 0);
cli_.set_write_timeout(std::chrono::seconds(0));
auto res = cli_.Post(
"/slowpost", 64 * 1024 * 1024,
[&](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
@@ -2614,6 +2728,24 @@ TEST_F(ServerTest, PutLargeFileWithGzip) {
EXPECT_EQ(LARGE_DATA, res->body);
}
TEST_F(ServerTest, PutLargeFileWithGzip2) {
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
Client cli("https://localhost:1234");
cli.enable_server_certificate_verification(false);
#else
Client cli("http://localhost:1234");
#endif
cli.set_compress(true);
auto res = cli.Put("/put-large", LARGE_DATA, "text/plain");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ(LARGE_DATA, res->body);
EXPECT_EQ(101942u, res.get_request_header_value<uint64_t>("Content-Length"));
EXPECT_EQ("gzip", res.get_request_header_value("Content-Encoding"));
}
TEST_F(ServerTest, PutContentWithDeflate) {
cli_.set_compress(false);
Headers headers;
@@ -3068,8 +3200,11 @@ static bool send_request(time_t read_timeout_sec, const std::string &req,
auto error = Error::Success;
auto client_sock =
detail::create_client_socket(HOST, PORT, false, nullptr,
/*timeout_sec=*/5, 0, std::string(), error);
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; }
@@ -3135,7 +3270,7 @@ static void test_raw_request(const std::string &req,
// bug to reproduce, probably to force the server to process a request
// without a trailing blank line.
const time_t client_read_timeout_sec = 1;
svr.set_read_timeout(client_read_timeout_sec + 1, 0);
svr.set_read_timeout(std::chrono::seconds(client_read_timeout_sec + 1));
bool listen_thread_ok = false;
thread t = thread([&] { listen_thread_ok = svr.listen(HOST, PORT); });
while (!svr.is_running()) {
@@ -3270,7 +3405,8 @@ TEST(ServerStopTest, StopServerWithChunkedTransmission) {
DataSink &sink) {
char buffer[27];
auto size = static_cast<size_t>(sprintf(buffer, "data:%ld\n\n", offset));
sink.write(buffer, size);
auto ret = sink.write(buffer, size);
EXPECT_TRUE(ret);
std::this_thread::sleep_for(std::chrono::seconds(1));
return true;
});
@@ -3405,7 +3541,8 @@ TEST(ExceptionTest, ThrowExceptionInHandler) {
auto res = cli.Get("/hi");
ASSERT_TRUE(res);
EXPECT_EQ(500, res->status);
ASSERT_FALSE(res->has_header("EXCEPTION_WHAT"));
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
EXPECT_EQ("exception...", res->get_header_value("EXCEPTION_WHAT"));
svr.stop();
listen_thread.join();
@@ -3434,7 +3571,7 @@ TEST(KeepAliveTest, ReadTimeout) {
Client cli("localhost", PORT);
cli.set_keep_alive(true);
cli.set_read_timeout(1);
cli.set_read_timeout(std::chrono::seconds(1));
auto resa = cli.Get("/a");
ASSERT_TRUE(!resa);
@@ -3483,6 +3620,70 @@ TEST(ErrorHandlerWithContentProviderTest, ErrorHandler) {
ASSERT_FALSE(svr.is_running());
}
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");
});
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);
Params params;
params.emplace("hello", "world");
auto res = cli.Get("/", params, Headers{});
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("world", res->body);
svr.stop();
listen_thread.join();
ASSERT_FALSE(svr.is_running());
}
TEST(GetWithParametersTest, GetWithParameters2) {
Server svr;
svr.Get("/", [&](const Request &req, Response &res) {
auto text = req.get_param_value("hello");
res.set_content(text, "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);
Params params;
params.emplace("hello", "world");
std::string body;
auto res = cli.Get("/", params, Headers{},
[&](const char *data, size_t data_length) {
body.append(data, data_length);
return true;
});
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("world", body);
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);
@@ -3508,7 +3709,7 @@ TEST(KeepAliveTest, ReadTimeoutSSL) {
SSLClient cli("localhost", PORT);
cli.enable_server_certificate_verification(false);
cli.set_keep_alive(true);
cli.set_read_timeout(1);
cli.set_read_timeout(std::chrono::seconds(1));
auto resa = cli.Get("/a");
ASSERT_TRUE(!resa);
@@ -3733,6 +3934,7 @@ TEST(SSLClientTest, WildcardHostNameMatch) {
cli.set_ca_cert_path(CA_CERT_FILE);
cli.enable_server_certificate_verification(true);
cli.set_follow_location(true);
auto res = cli.Get("/");
ASSERT_TRUE(res);
@@ -3963,7 +4165,6 @@ TEST(NoSSLSupport, SimpleInterface) {
}
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(InvalidScheme, SimpleInterface) {
ASSERT_ANY_THROW(Client cli("scheme://yahoo.com"));
}
@@ -3973,6 +4174,19 @@ TEST(NoScheme, SimpleInterface) {
ASSERT_TRUE(cli.is_valid());
}
TEST(SendAPI, SimpleInterface) {
Client cli("http://yahoo.com");
Request req;
req.method = "GET";
req.path = "/";
auto res = cli.send(req);
ASSERT_TRUE(res);
EXPECT_EQ(301, res->status);
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(YahooRedirectTest2, SimpleInterface) {
Client cli("http://yahoo.com");