mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| faa5f1d802 | |||
| 9d3365df54 | |||
| 6ff84d34d1 | |||
| b845425cd0 | |||
| 89519c88e2 | |||
| ff813bf99d |
@@ -204,6 +204,7 @@ using socket_t = int;
|
||||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
#include <thread>
|
||||
#include <set>
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#include <openssl/err.h>
|
||||
@@ -667,9 +668,18 @@ public:
|
||||
|
||||
Server &set_keep_alive_max_count(size_t count);
|
||||
Server &set_keep_alive_timeout(time_t sec);
|
||||
|
||||
Server &set_read_timeout(time_t sec, time_t usec = 0);
|
||||
template <class Rep, class Period>
|
||||
Server &set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
|
||||
|
||||
Server &set_write_timeout(time_t sec, time_t usec = 0);
|
||||
template <class Rep, class Period>
|
||||
Server &set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
|
||||
|
||||
Server &set_idle_interval(time_t sec, time_t usec = 0);
|
||||
template <class Rep, class Period>
|
||||
Server &set_idle_interval(const std::chrono::duration<Rep, Period> &duration);
|
||||
|
||||
Server &set_payload_max_length(size_t length);
|
||||
|
||||
@@ -965,8 +975,16 @@ public:
|
||||
void set_socket_options(SocketOptions socket_options);
|
||||
|
||||
void set_connection_timeout(time_t sec, time_t usec = 0);
|
||||
template <class Rep, class Period>
|
||||
void set_connection_timeout(const std::chrono::duration<Rep, Period> &duration);
|
||||
|
||||
void set_read_timeout(time_t sec, time_t usec = 0);
|
||||
template <class Rep, class Period>
|
||||
void set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
|
||||
|
||||
void set_write_timeout(time_t sec, time_t usec = 0);
|
||||
template <class Rep, class Period>
|
||||
void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
|
||||
|
||||
void set_basic_auth(const char *username, const char *password);
|
||||
void set_bearer_token_auth(const char *token);
|
||||
@@ -1267,8 +1285,16 @@ public:
|
||||
void set_socket_options(SocketOptions socket_options);
|
||||
|
||||
void set_connection_timeout(time_t sec, time_t usec = 0);
|
||||
template <class Rep, class Period>
|
||||
void set_connection_timeout(const std::chrono::duration<Rep, Period> &duration);
|
||||
|
||||
void set_read_timeout(time_t sec, time_t usec = 0);
|
||||
template <class Rep, class Period>
|
||||
void set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
|
||||
|
||||
void set_write_timeout(time_t sec, time_t usec = 0);
|
||||
template <class Rep, class Period>
|
||||
void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
|
||||
|
||||
void set_basic_auth(const char *username, const char *password);
|
||||
void set_bearer_token_auth(const char *token);
|
||||
@@ -3171,7 +3197,14 @@ inline std::string append_query_params(const char *path, const Params ¶ms) {
|
||||
}
|
||||
|
||||
inline void parse_query_text(const std::string &s, Params ¶ms) {
|
||||
std::set<std::string> cache;
|
||||
split(s.data(), s.data() + s.size(), '&', [&](const char *b, const char *e) {
|
||||
std::string kv(b, e);
|
||||
if (cache.find(kv) != cache.end()) {
|
||||
return;
|
||||
}
|
||||
cache.insert(kv);
|
||||
|
||||
std::string key;
|
||||
std::string val;
|
||||
split(b, e, '=', [&](const char *b2, const char *e2) {
|
||||
@@ -3775,7 +3808,7 @@ inline std::string random_string(size_t length) {
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz";
|
||||
const size_t max_index = (sizeof(charset) - 1);
|
||||
return charset[static_cast<size_t>(rand()) % max_index];
|
||||
return charset[static_cast<size_t>(std::rand()) % max_index];
|
||||
};
|
||||
std::string str(length, 0);
|
||||
std::generate_n(str.begin(), length, randchar);
|
||||
@@ -3796,6 +3829,15 @@ private:
|
||||
ContentProviderWithoutLength content_provider_;
|
||||
};
|
||||
|
||||
template <typename T, typename U>
|
||||
inline void duration_to_sec_and_usec(const T &duration, U callback) {
|
||||
auto sec = std::chrono::duration_cast<std::chrono::seconds>(duration).count();
|
||||
auto usec = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
duration - std::chrono::seconds(sec))
|
||||
.count();
|
||||
callback(sec, usec);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// Header utilities
|
||||
@@ -4373,6 +4415,15 @@ inline Server &Server::set_read_timeout(time_t sec, time_t usec) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class Rep, class Period>
|
||||
inline Server &Server::set_read_timeout(
|
||||
const std::chrono::duration<Rep, Period> &duration) {
|
||||
detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) {
|
||||
set_read_timeout(sec, usec);
|
||||
});
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::set_write_timeout(time_t sec, time_t usec) {
|
||||
write_timeout_sec_ = sec;
|
||||
write_timeout_usec_ = usec;
|
||||
@@ -4380,6 +4431,15 @@ inline Server &Server::set_write_timeout(time_t sec, time_t usec) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class Rep, class Period>
|
||||
inline Server &Server::set_write_timeout(
|
||||
const std::chrono::duration<Rep, Period> &duration) {
|
||||
detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) {
|
||||
set_write_timeout(sec, usec);
|
||||
});
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::set_idle_interval(time_t sec, time_t usec) {
|
||||
idle_interval_sec_ = sec;
|
||||
idle_interval_usec_ = usec;
|
||||
@@ -4387,6 +4447,15 @@ inline Server &Server::set_idle_interval(time_t sec, time_t usec) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class Rep, class Period>
|
||||
inline Server &Server::set_idle_interval(
|
||||
const std::chrono::duration<Rep, Period> &duration) {
|
||||
detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) {
|
||||
set_idle_interval(sec, usec);
|
||||
});
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::set_payload_max_length(size_t length) {
|
||||
payload_max_length_ = length;
|
||||
|
||||
@@ -5738,19 +5807,22 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (req.response_handler) {
|
||||
if (!req.response_handler(res)) {
|
||||
error = Error::Canceled;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Body
|
||||
if ((res.status != 204) && req.method != "HEAD" && req.method != "CONNECT") {
|
||||
auto redirect = 300 < res.status && res.status < 400 && follow_location_;
|
||||
|
||||
if (req.response_handler && !redirect) {
|
||||
if (!req.response_handler(res)) {
|
||||
error = Error::Canceled;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto out =
|
||||
req.content_receiver
|
||||
? static_cast<ContentReceiverWithProgress>(
|
||||
[&](const char *buf, size_t n, uint64_t off, uint64_t len) {
|
||||
if (redirect) { return true; }
|
||||
auto ret = req.content_receiver(buf, n, off, len);
|
||||
if (!ret) { error = Error::Canceled; }
|
||||
return ret;
|
||||
@@ -5766,7 +5838,7 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
||||
});
|
||||
|
||||
auto progress = [&](uint64_t current, uint64_t total) {
|
||||
if (!req.progress) { return true; }
|
||||
if (!req.progress || redirect) { return true; }
|
||||
auto ret = req.progress(current, total);
|
||||
if (!ret) { error = Error::Canceled; }
|
||||
return ret;
|
||||
@@ -5925,7 +5997,7 @@ inline Result ClientImpl::Get(const char *path, const Params ¶ms,
|
||||
}
|
||||
|
||||
std::string path_with_query = detail::append_query_params(path, params);
|
||||
return Get(path_with_query.c_str(), params, headers, response_handler,
|
||||
return Get(path_with_query.c_str(), headers, response_handler,
|
||||
content_receiver, progress);
|
||||
}
|
||||
|
||||
@@ -6265,16 +6337,40 @@ inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) {
|
||||
connection_timeout_usec_ = usec;
|
||||
}
|
||||
|
||||
template <class Rep, class Period>
|
||||
inline void ClientImpl::set_connection_timeout(
|
||||
const std::chrono::duration<Rep, Period> &duration) {
|
||||
detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) {
|
||||
set_connection_timeout(sec, usec);
|
||||
});
|
||||
}
|
||||
|
||||
inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) {
|
||||
read_timeout_sec_ = sec;
|
||||
read_timeout_usec_ = usec;
|
||||
}
|
||||
|
||||
template <class Rep, class Period>
|
||||
inline void ClientImpl::set_read_timeout(
|
||||
const std::chrono::duration<Rep, Period> &duration) {
|
||||
detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) {
|
||||
set_read_timeout(sec, usec);
|
||||
});
|
||||
}
|
||||
|
||||
inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) {
|
||||
write_timeout_sec_ = sec;
|
||||
write_timeout_usec_ = usec;
|
||||
}
|
||||
|
||||
template <class Rep, class Period>
|
||||
inline void ClientImpl::set_write_timeout(
|
||||
const std::chrono::duration<Rep, Period> &duration) {
|
||||
detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) {
|
||||
set_write_timeout(sec, usec);
|
||||
});
|
||||
}
|
||||
|
||||
inline void ClientImpl::set_basic_auth(const char *username,
|
||||
const char *password) {
|
||||
basic_auth_username_ = username;
|
||||
@@ -7364,6 +7460,7 @@ inline void Client::set_default_headers(Headers headers) {
|
||||
}
|
||||
|
||||
inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); }
|
||||
|
||||
inline void Client::set_socket_options(SocketOptions socket_options) {
|
||||
cli_->set_socket_options(std::move(socket_options));
|
||||
}
|
||||
@@ -7371,13 +7468,33 @@ inline void Client::set_socket_options(SocketOptions socket_options) {
|
||||
inline void Client::set_connection_timeout(time_t sec, time_t usec) {
|
||||
cli_->set_connection_timeout(sec, usec);
|
||||
}
|
||||
|
||||
template <class Rep, class Period>
|
||||
inline void Client::set_connection_timeout(
|
||||
const std::chrono::duration<Rep, Period> &duration) {
|
||||
cli_->set_connection_timeout(duration);
|
||||
}
|
||||
|
||||
inline void Client::set_read_timeout(time_t sec, time_t usec) {
|
||||
cli_->set_read_timeout(sec, usec);
|
||||
}
|
||||
|
||||
template <class Rep, class Period>
|
||||
inline void Client::set_read_timeout(
|
||||
const std::chrono::duration<Rep, Period> &duration) {
|
||||
cli_->set_read_timeout(duration);
|
||||
}
|
||||
|
||||
inline void Client::set_write_timeout(time_t sec, time_t usec) {
|
||||
cli_->set_write_timeout(sec, usec);
|
||||
}
|
||||
|
||||
template <class Rep, class Period>
|
||||
inline void Client::set_write_timeout(
|
||||
const std::chrono::duration<Rep, Period> &duration) {
|
||||
cli_->set_write_timeout(duration);
|
||||
}
|
||||
|
||||
inline void Client::set_basic_auth(const char *username, const char *password) {
|
||||
cli_->set_basic_auth(username, password);
|
||||
}
|
||||
|
||||
+132
-11
@@ -525,7 +525,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,7 +540,7 @@ 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);
|
||||
@@ -556,7 +556,7 @@ TEST(ConnectionErrorTest, InvalidPort) {
|
||||
#else
|
||||
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 +573,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 +590,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 +610,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 +625,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",
|
||||
@@ -889,6 +889,64 @@ 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(BindServerTest, BindDualStack) {
|
||||
@@ -2478,7 +2536,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) {
|
||||
@@ -3146,7 +3204,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()) {
|
||||
@@ -3446,7 +3504,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);
|
||||
@@ -3495,6 +3553,69 @@ 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);
|
||||
@@ -3520,7 +3641,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);
|
||||
|
||||
Reference in New Issue
Block a user