Compare commits

..

51 Commits

Author SHA1 Message Date
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
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
Nikolas 0542fdb8e4 Add exception handler (#845)
* Add exception handler

* revert content reader changes

* Add test for and fix exception handler

* Fix warning in test

* Readd exception test, improve readme note, don't rethrow errors, remove exception handler response
2021-01-28 17:19:11 -05:00
yhirose 78c474c744 Update README 2021-01-27 11:59:42 -05:00
yhirose 88411a1f52 Fix #846 2021-01-27 14:35:32 +00:00
yhirose ae6cf70bc4 Updated README 2021-01-26 08:38:28 -05:00
yhirose 68d1281759 Resolve #839 2021-01-23 13:33:27 -05:00
yhirose 0308d60cb2 Resolve #831 (#835) 2021-01-23 12:23:06 -05:00
yhirose 59f5fdbb33 Resolve #840 2021-01-23 11:43:01 -05:00
Anonymous 13184f5f80 Return Server& from handler setters (#836)
* Update httplib.h

* Update httplib.h

* Update httplib.h

* Update httplib.h
2021-01-22 09:20:21 -05:00
5 changed files with 1393 additions and 427 deletions
+45 -31
View File
@@ -7,7 +7,7 @@ A C++11 single-file header-only cross platform HTTP/HTTPS library.
It's extremely easy to setup. Just include the **httplib.h** file in your code!
NOTE: This is a 'blocking' HTTP library. If you are looking for a 'non-blocking' library, this is not the one that you want.
NOTE: This is a multi-threaded 'blocking' HTTP library. If you are looking for a 'non-blocking' library, this is not the one that you want.
Simple examples
---------------
@@ -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
------
@@ -177,15 +204,28 @@ svr.set_error_handler([](const auto& req, auto& res) {
});
```
### Exception handler
The exception handler gets called if a user routing handler throws an error.
```cpp
svr.set_exception_handler([](const auto& req, auto& res, std::exception &e) {
res.status = 500;
auto fmt = "<h1>Error 500</h1><p>%s</p>";
char buf[BUFSIZ];
snprintf(buf, sizeof(buf), fmt, e.what());
res.set_content(buf, "text/html");
});
```
### Pre routing handler
```cpp
svr.set_pre_routing_handler([](const auto& req, auto& res) -> bool {
if (req.path == "/hello") {
res.set_content("world", "text/html");
return true; // This request is handled
return Server::HandlerResponse::Handled;
}
return false; // Let the router handle this request
return Server::HandlerResponse::Unhandled;
});
```
@@ -253,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; });
});
```
@@ -706,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
-------------------------------
@@ -768,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 -11
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>
@@ -20,8 +13,6 @@ using namespace std;
class EventDispatcher {
public:
EventDispatcher() {
id_ = 0;
cid_ = -1;
}
void wait_event(DataSink *sink) {
@@ -41,8 +32,8 @@ public:
private:
mutex m_;
condition_variable cv_;
atomic_int id_;
atomic_int cid_;
atomic_int id_{0};
atomic_int cid_{-1};
string message_;
};
+817 -326
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
+522 -59
View File
@@ -5,6 +5,8 @@
#include <atomic>
#include <chrono>
#include <future>
#include <sstream>
#include <stdexcept>
#include <thread>
#define SERVER_CERT_FILE "./cert.pem"
@@ -435,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";
@@ -524,7 +506,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);
@@ -539,24 +521,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);
@@ -573,7 +571,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);
@@ -590,7 +588,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);
@@ -610,7 +608,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());
}
@@ -625,7 +623,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",
@@ -816,6 +814,43 @@ TEST(HttpsToHttpRedirectTest, Redirect) {
EXPECT_EQ(200, res->status);
}
TEST(HttpsToHttpRedirectTest2, Redirect) {
SSLClient cli("nghttp2.org");
cli.set_follow_location(true);
Params params;
params.emplace("url", "http://www.google.com");
params.emplace("status_code", "302");
auto res = cli.Get("/httpbin/redirect-to", params, Headers{});
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
}
TEST(HttpsToHttpRedirectTest3, Redirect) {
SSLClient cli("nghttp2.org");
cli.set_follow_location(true);
Params params;
params.emplace("url", "http://www.google.com");
auto res = cli.Get("/httpbin/redirect-to?status_code=302", params, Headers{});
ASSERT_TRUE(res);
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(18527, res->get_header_value<uint64_t>("Content-Length"));
}
#endif
TEST(RedirectToDifferentPort, Redirect) {
Server svr8080;
Server svr8081;
@@ -855,16 +890,152 @@ TEST(RedirectToDifferentPort, Redirect) {
ASSERT_FALSE(svr8081.is_running());
}
TEST(UrlWithSpace, Redirect) {
SSLClient cli("edge.forgecdn.net");
cli.set_follow_location(true);
TEST(RedirectFromPageWithContent, Redirect) {
Server svr;
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"));
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());
}
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) {
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());
}
#endif
TEST(BindServerTest, BindDualStack) {
Server svr;
@@ -953,12 +1124,47 @@ TEST(ErrorHandlerTest, ContentLength) {
ASSERT_FALSE(svr.is_running());
}
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.Get("/hi", [](const Request & /*req*/, Response &res) {
res.set_content("Hello World!\n", "text/plain");
throw std::runtime_error("abc");
});
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);
auto res = cli.Get("/hi");
ASSERT_TRUE(res);
EXPECT_EQ(500, res->status);
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
EXPECT_EQ("26", res->get_header_value("Content-Length"));
EXPECT_EQ("abcdefghijklmnopqrstuvwxyz", res->body);
}
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
}
TEST(NoContentTest, ContentLength) {
Server svr;
svr.Get("/hi", [](const Request & /*req*/, Response &res) {
res.status = 204;
});
svr.Get("/hi",
[](const Request & /*req*/, Response &res) { res.status = 204; });
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
// Give GET time to get a few messages.
@@ -985,9 +1191,9 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
if (req.path == "/routing_handler") {
res.set_header("PRE_ROUTING", "on");
res.set_content("Routing Handler", "text/plain");
return true;
return httplib::Server::HandlerResponse::Handled;
}
return false;
return httplib::Server::HandlerResponse::Unhandled;
});
svr.set_error_handler([](const Request & /*req*/, Response &res) {
@@ -1228,7 +1434,10 @@ protected:
(*i)++;
return true;
},
[i] { delete i; });
[i](bool success) {
EXPECT_TRUE(success);
delete i;
});
})
.Get("/streamed",
[&](const Request & /*req*/, Response &res) {
@@ -1250,10 +1459,15 @@ 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; });
[data](bool success) {
EXPECT_TRUE(success);
delete data;
});
})
.Get("/streamed-cancel",
[&](const Request & /*req*/, Response &res) {
@@ -1584,6 +1798,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);
@@ -2255,7 +2470,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);
}));
}
@@ -2403,7 +2619,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");
@@ -2416,7 +2633,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) {
@@ -2554,6 +2771,26 @@ TEST_F(ServerTest, PutLargeFileWithGzip) {
EXPECT_EQ(LARGE_DATA, res->body);
}
TEST_F(ServerTest, PutLargeFileWithGzip2) {
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
std::string s = std::string("https://") + HOST + ":" + std::to_string(PORT);
Client cli(s.c_str());
cli.enable_server_certificate_verification(false);
#else
std::string s = std::string("http://") + HOST + ":" + std::to_string(PORT);
Client cli(s.c_str());
#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;
@@ -3007,9 +3244,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, false, nullptr,
/*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; }
@@ -3075,7 +3314,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()) {
@@ -3210,7 +3449,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;
});
@@ -3345,7 +3585,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();
@@ -3374,7 +3615,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);
@@ -3390,6 +3631,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;
@@ -3423,6 +3702,152 @@ TEST(ErrorHandlerWithContentProviderTest, ErrorHandler) {
ASSERT_FALSE(svr.is_running());
}
TEST(GetWithParametersTest, GetWithParameters) {
Server svr;
svr.Get("/", [&](const Request &req, Response &res) {
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("/params", [&](const Request &req, Response &res) {
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& res) {
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(HOST, PORT);
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);
}
{
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();
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());
}
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);
@@ -3448,7 +3873,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);
@@ -3673,6 +4098,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);
@@ -3903,7 +4329,6 @@ TEST(NoSSLSupport, SimpleInterface) {
}
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(InvalidScheme, SimpleInterface) {
ASSERT_ANY_THROW(Client cli("scheme://yahoo.com"));
}
@@ -3913,6 +4338,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");
@@ -3979,7 +4417,7 @@ TEST(DecodeWithChunkedEncoding, BrotliEncoding) {
}
#endif
TEST(HttpsToHttpRedirectTest2, SimpleInterface) {
TEST(HttpsToHttpRedirectTest, SimpleInterface) {
Client cli("https://nghttp2.org");
cli.set_follow_location(true);
auto res =
@@ -3989,4 +4427,29 @@ TEST(HttpsToHttpRedirectTest2, SimpleInterface) {
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
}
TEST(HttpsToHttpRedirectTest2, SimpleInterface) {
Client cli("https://nghttp2.org");
cli.set_follow_location(true);
Params params;
params.emplace("url", "http://www.google.com");
params.emplace("status_code", "302");
auto res = cli.Get("/httpbin/redirect-to", params, Headers{});
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
}
TEST(HttpsToHttpRedirectTest3, SimpleInterface) {
Client cli("https://nghttp2.org");
cli.set_follow_location(true);
Params params;
params.emplace("url", "http://www.google.com");
auto res = cli.Get("/httpbin/redirect-to?status_code=302", params, Headers{});
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
}
#endif