Compare commits

...

7 Commits

Author SHA1 Message Date
yhirose 29fd136afd Code cleanup and format 2020-05-16 17:35:04 -04:00
yhirose f5598237b2 Fixed many redirects problem on Proxy 2020-05-16 17:34:03 -04:00
Daniel Ottiger 01058659ab make write timeout configurable (like the read timeout already is) (#477)
In case we want to send a lot of data,
and the receiver is slower than the sender.

This will first fill up the receivers queues and after this
eventually also the senders queues,
until the socket is temporarily unable to accept more data to send.

select_write is done with an timeout of zero,
which makes the select call used always return immediately:
(see http://man7.org/linux/man-pages/man2/select.2.html)

This means that every marginal unavailability will make it return false
for is_writable and therefore httplib will immediately abort the transfer.

Therefore make this values configurable in the same way
as the read timeout already is.

Set the default write timeout to 5 seconds,
the same default value used for the read timeout.
2020-05-16 17:31:46 -04:00
yhirose 66f698fab6 Fixed build errors with some examples 2020-05-16 00:50:52 -04:00
yhirose b9a9df4d73 Fixed problem with writing large data 2020-05-15 22:21:58 -04:00
yhirose 25aa3ca982 Added std::ostream os in DataSink. 2020-05-15 21:26:13 -04:00
yhirose 2d67211183 Added more unit tests for the simple interface 2020-05-14 18:25:18 -04:00
5 changed files with 285 additions and 134 deletions
+5 -6
View File
@@ -14,13 +14,12 @@ using namespace std;
int main(void) {
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
httplib::url::Options options;
options.ca_cert_file_path = CA_CERT_FILE;
// options.server_certificate_verification = true;
auto res = httplib::url::Get("https://localhost:8080/hi", options);
auto res = httplib::Client2("https://localhost:8080")
.set_ca_cert_path(CA_CERT_FILE)
// .enable_server_certificate_verification(true)
.Get("/hi");
#else
auto res = httplib::url::Get("http://localhost:8080/hi");
auto res = httplib::Client2("http://localhost:8080").Get("/hi");
#endif
if (res) {
+8 -4
View File
@@ -80,15 +80,19 @@ int main(void) {
svr.Get("/event1", [&](const Request & /*req*/, Response &res) {
cout << "connected to event1..." << endl;
res.set_header("Content-Type", "text/event-stream");
res.set_chunked_content_provider(
[&](uint64_t /*offset*/, DataSink &sink) { ed.wait_event(&sink); });
res.set_chunked_content_provider([&](size_t /*offset*/, DataSink &sink) {
ed.wait_event(&sink);
return true;
});
});
svr.Get("/event2", [&](const Request & /*req*/, Response &res) {
cout << "connected to event2..." << endl;
res.set_header("Content-Type", "text/event-stream");
res.set_chunked_content_provider(
[&](uint64_t /*offset*/, DataSink &sink) { ed.wait_event(&sink); });
res.set_chunked_content_provider([&](size_t /*offset*/, DataSink &sink) {
ed.wait_event(&sink);
return true;
});
});
thread t([&] {
+155 -58
View File
@@ -32,6 +32,14 @@
#define CPPHTTPLIB_READ_TIMEOUT_USECOND 0
#endif
#ifndef CPPHTTPLIB_WRITE_TIMEOUT_SECOND
#define CPPHTTPLIB_WRITE_TIMEOUT_SECOND 5
#endif
#ifndef CPPHTTPLIB_WRITE_TIMEOUT_USECOND
#define CPPHTTPLIB_WRITE_TIMEOUT_USECOND 0
#endif
#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH
#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192
#endif
@@ -215,7 +223,8 @@ using MultipartFormDataMap = std::multimap<std::string, MultipartFormData>;
class DataSink {
public:
DataSink() = default;
DataSink() : os(&sb_), sb_(*this) {}
DataSink(const DataSink &) = delete;
DataSink &operator=(const DataSink &) = delete;
DataSink(DataSink &&) = delete;
@@ -224,6 +233,24 @@ public:
std::function<void(const char *data, size_t data_len)> write;
std::function<void()> done;
std::function<bool()> is_writable;
std::ostream os;
private:
class data_sink_streambuf : public std::streambuf {
public:
data_sink_streambuf(DataSink &sink) : sink_(sink) {}
protected:
std::streamsize xsputn(const char *s, std::streamsize n) {
sink_.write(s, static_cast<size_t>(n));
return n;
}
private:
DataSink &sink_;
};
data_sink_streambuf sb_;
};
using ContentProvider =
@@ -306,7 +333,7 @@ struct Request {
MultipartFormData get_file_value(const char *key) const;
// private members...
size_t authorization_count_ = 1;
size_t authorization_count_ = 0;
};
struct Response {
@@ -490,6 +517,7 @@ public:
void set_keep_alive_max_count(size_t count);
void set_read_timeout(time_t sec, time_t usec);
void set_write_timeout(time_t sec, time_t usec);
void set_payload_max_length(size_t length);
bool bind_to_port(const char *host, int port, int socket_flags = 0);
@@ -511,6 +539,8 @@ protected:
size_t keep_alive_max_count_;
time_t read_timeout_sec_;
time_t read_timeout_usec_;
time_t write_timeout_sec_;
time_t write_timeout_usec_;
size_t payload_max_length_;
private:
@@ -712,6 +742,8 @@ public:
void set_read_timeout(time_t sec, time_t usec);
void set_write_timeout(time_t sec, time_t usec);
void set_keep_alive_max_count(size_t count);
void set_basic_auth(const char *username, const char *password);
@@ -753,6 +785,8 @@ protected:
time_t timeout_sec_ = 300;
time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND;
time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND;
time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND;
time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND;
size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
@@ -787,6 +821,8 @@ protected:
timeout_sec_ = rhs.timeout_sec_;
read_timeout_sec_ = rhs.read_timeout_sec_;
read_timeout_usec_ = rhs.read_timeout_usec_;
write_timeout_sec_ = rhs.write_timeout_sec_;
write_timeout_usec_ = rhs.write_timeout_usec_;
keep_alive_max_count_ = rhs.keep_alive_max_count_;
basic_auth_username_ = rhs.basic_auth_username_;
basic_auth_password_ = rhs.basic_auth_password_;
@@ -1328,8 +1364,8 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
class SocketStream : public Stream {
public:
SocketStream(socket_t sock, time_t read_timeout_sec,
time_t read_timeout_usec);
SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
time_t write_timeout_sec, time_t write_timeout_usec);
~SocketStream() override;
bool is_readable() const override;
@@ -1342,13 +1378,16 @@ private:
socket_t sock_;
time_t read_timeout_sec_;
time_t read_timeout_usec_;
time_t write_timeout_sec_;
time_t write_timeout_usec_;
};
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
class SSLSocketStream : public Stream {
public:
SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec,
time_t read_timeout_usec);
time_t read_timeout_usec, time_t write_timeout_sec,
time_t write_timeout_usec);
~SSLSocketStream() override;
bool is_readable() const override;
@@ -1362,6 +1401,8 @@ private:
SSL *ssl_;
time_t read_timeout_sec_;
time_t read_timeout_usec_;
time_t write_timeout_sec_;
time_t write_timeout_usec_;
};
#endif
@@ -1386,7 +1427,8 @@ private:
template <typename T>
inline bool process_socket(bool is_client_request, socket_t sock,
size_t keep_alive_max_count, time_t read_timeout_sec,
time_t read_timeout_usec, T callback) {
time_t read_timeout_usec, time_t write_timeout_sec,
time_t write_timeout_usec, T callback) {
assert(keep_alive_max_count > 0);
auto ret = false;
@@ -1397,7 +1439,8 @@ inline bool process_socket(bool is_client_request, socket_t sock,
(is_client_request ||
select_read(sock, CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND,
CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0)) {
SocketStream strm(sock, read_timeout_sec, read_timeout_usec);
SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
write_timeout_sec, write_timeout_usec);
auto last_connection = count == 1;
auto connection_close = false;
@@ -1407,7 +1450,8 @@ inline bool process_socket(bool is_client_request, socket_t sock,
count--;
}
} else { // keep_alive_max_count is 0 or 1
SocketStream strm(sock, read_timeout_sec, read_timeout_usec);
SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
write_timeout_sec, write_timeout_usec);
auto dummy_connection_close = false;
ret = callback(strm, true, dummy_connection_close);
}
@@ -1416,12 +1460,14 @@ inline bool process_socket(bool is_client_request, socket_t sock,
}
template <typename T>
inline bool process_and_close_socket(bool is_client_request, socket_t sock,
size_t keep_alive_max_count,
time_t read_timeout_sec,
time_t read_timeout_usec, T callback) {
inline bool
process_and_close_socket(bool is_client_request, socket_t sock,
size_t keep_alive_max_count, time_t read_timeout_sec,
time_t read_timeout_usec, time_t write_timeout_sec,
time_t write_timeout_usec, T callback) {
auto ret = process_socket(is_client_request, sock, keep_alive_max_count,
read_timeout_sec, read_timeout_usec, callback);
read_timeout_sec, read_timeout_usec,
write_timeout_sec, write_timeout_usec, callback);
close_socket(sock);
return ret;
}
@@ -2079,27 +2125,37 @@ inline ssize_t write_headers(Stream &strm, const T &info,
return write_len;
}
inline bool write_data(Stream &strm, const char *d, size_t l) {
size_t offset = 0;
while (offset < l) {
auto length = strm.write(d + offset, l - offset);
if (length < 0) { return false; }
offset += static_cast<size_t>(length);
}
return true;
}
inline ssize_t write_content(Stream &strm, ContentProvider content_provider,
size_t offset, size_t length) {
size_t begin_offset = offset;
size_t end_offset = offset + length;
ssize_t written_length = 0;
auto ok = true;
DataSink data_sink;
data_sink.write = [&](const char *d, size_t l) {
offset += l;
written_length = strm.write(d, l);
};
data_sink.is_writable = [&](void) {
return strm.is_writable() && written_length >= 0;
if (ok) {
offset += l;
if (!write_data(strm, d, l)) { ok = false; }
}
};
data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
while (offset < end_offset) {
while (ok && offset < end_offset) {
if (!content_provider(offset, end_offset - offset, data_sink)) {
return -1;
}
if (written_length < 0) { return written_length; }
if (!ok) { return -1; }
}
return static_cast<ssize_t>(offset - begin_offset);
@@ -2113,29 +2169,39 @@ inline ssize_t write_content_chunked(Stream &strm,
auto data_available = true;
ssize_t total_written_length = 0;
ssize_t written_length = 0;
auto ok = true;
DataSink data_sink;
data_sink.write = [&](const char *d, size_t l) {
data_available = l > 0;
offset += l;
if (ok) {
data_available = l > 0;
offset += l;
// Emit chunked response header and footer for each chunk
auto chunk = from_i_to_hex(l) + "\r\n" + std::string(d, l) + "\r\n";
written_length = strm.write(chunk);
// Emit chunked response header and footer for each chunk
auto chunk = from_i_to_hex(l) + "\r\n" + std::string(d, l) + "\r\n";
if (write_data(strm, chunk.data(), chunk.size())) {
total_written_length += chunk.size();
} else {
ok = false;
}
}
};
data_sink.done = [&](void) {
data_available = false;
written_length = strm.write("0\r\n\r\n");
};
data_sink.is_writable = [&](void) {
return strm.is_writable() && written_length >= 0;
if (ok) {
static const std::string done_marker("0\r\n\r\n");
if (write_data(strm, done_marker.data(), done_marker.size())) {
total_written_length += done_marker.size();
} else {
ok = false;
}
}
};
data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
while (data_available && !is_shutting_down()) {
if (!content_provider(offset, 0, data_sink)) { return -1; }
if (written_length < 0) { return written_length; }
total_written_length += written_length;
if (!ok) { return -1; }
}
return total_written_length;
@@ -2983,9 +3049,13 @@ namespace detail {
// Socket stream implementation
inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec,
time_t read_timeout_usec)
time_t read_timeout_usec,
time_t write_timeout_sec,
time_t write_timeout_usec)
: sock_(sock), read_timeout_sec_(read_timeout_sec),
read_timeout_usec_(read_timeout_usec) {}
read_timeout_usec_(read_timeout_usec),
write_timeout_sec_(write_timeout_sec),
write_timeout_usec_(write_timeout_usec) {}
inline SocketStream::~SocketStream() {}
@@ -2994,7 +3064,7 @@ inline bool SocketStream::is_readable() const {
}
inline bool SocketStream::is_writable() const {
return select_write(sock_, 0, 0) > 0;
return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0;
}
inline ssize_t SocketStream::read(char *ptr, size_t size) {
@@ -3060,6 +3130,8 @@ inline Server::Server()
: keep_alive_max_count_(CPPHTTPLIB_KEEPALIVE_MAX_COUNT),
read_timeout_sec_(CPPHTTPLIB_READ_TIMEOUT_SECOND),
read_timeout_usec_(CPPHTTPLIB_READ_TIMEOUT_USECOND),
write_timeout_sec_(CPPHTTPLIB_WRITE_TIMEOUT_SECOND),
write_timeout_usec_(CPPHTTPLIB_WRITE_TIMEOUT_USECOND),
payload_max_length_(CPPHTTPLIB_PAYLOAD_MAX_LENGTH), is_running_(false),
svr_sock_(INVALID_SOCKET) {
#ifndef _WIN32
@@ -3182,6 +3254,11 @@ inline void Server::set_read_timeout(time_t sec, time_t usec) {
read_timeout_usec_ = usec;
}
inline void Server::set_write_timeout(time_t sec, time_t usec) {
write_timeout_sec_ = sec;
write_timeout_usec_ = usec;
}
inline void Server::set_payload_max_length(size_t length) {
payload_max_length_ = length;
}
@@ -3787,6 +3864,7 @@ inline bool Server::is_valid() const { return true; }
inline bool Server::process_and_close_socket(socket_t sock) {
return detail::process_and_close_socket(
false, sock, keep_alive_max_count_, read_timeout_sec_, read_timeout_usec_,
write_timeout_sec_, write_timeout_usec_,
[this](Stream &strm, bool last_connection, bool &connection_close) {
return process_request(strm, last_connection, connection_close,
nullptr);
@@ -3915,7 +3993,7 @@ inline bool Client::handle_request(Stream &strm, const Request &req,
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
if ((res.status == 401 || res.status == 407) &&
req.authorization_count_ == 1) {
req.authorization_count_ < 5) {
auto is_proxy = res.status == 407;
const auto &username =
is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
@@ -3952,6 +4030,7 @@ inline bool Client::connect(socket_t sock, Response &res, bool &error) {
if (!detail::process_socket(
true, sock, 1, read_timeout_sec_, read_timeout_usec_,
write_timeout_sec_, write_timeout_usec_,
[&](Stream &strm, bool /*last_connection*/, bool &connection_close) {
Request req2;
req2.method = "CONNECT";
@@ -3971,6 +4050,7 @@ inline bool Client::connect(socket_t sock, Response &res, bool &error) {
Response res3;
if (!detail::process_socket(
true, sock, 1, read_timeout_sec_, read_timeout_usec_,
write_timeout_sec_, write_timeout_usec_,
[&](Stream &strm, bool /*last_connection*/,
bool &connection_close) {
Request req3;
@@ -4115,7 +4195,7 @@ inline bool Client::write_request(Stream &strm, const Request &req,
// Flush buffer
auto &data = bstrm.get_buffer();
strm.write(data.data(), data.size());
if (!detail::write_data(strm, data.data(), data.size())) { return false; }
// Body
if (req.body.empty()) {
@@ -4123,26 +4203,29 @@ inline bool Client::write_request(Stream &strm, const Request &req,
size_t offset = 0;
size_t end_offset = req.content_length;
ssize_t written_length = 0;
bool ok = true;
DataSink data_sink;
data_sink.write = [&](const char *d, size_t l) {
written_length = strm.write(d, l);
offset += static_cast<size_t>(written_length);
};
data_sink.is_writable = [&](void) {
return strm.is_writable() && written_length >= 0;
if (ok) {
if (detail::write_data(strm, d, l)) {
offset += l;
} else {
ok = false;
}
}
};
data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
while (offset < end_offset) {
if (!req.content_provider(offset, end_offset - offset, data_sink)) {
return false;
}
if (written_length < 0) { return false; }
if (!ok) { return false; }
}
}
} else {
strm.write(req.body);
return detail::write_data(strm, req.body.data(), req.body.size());
}
return true;
@@ -4251,9 +4334,9 @@ inline bool Client::process_and_close_socket(
bool &connection_close)>
callback) {
request_count = (std::min)(request_count, keep_alive_max_count_);
return detail::process_and_close_socket(true, sock, request_count,
read_timeout_sec_, read_timeout_usec_,
callback);
return detail::process_and_close_socket(
true, sock, request_count, read_timeout_sec_, read_timeout_usec_,
write_timeout_sec_, write_timeout_usec_, callback);
}
inline bool Client::is_ssl() const { return false; }
@@ -4565,6 +4648,11 @@ inline void Client::set_read_timeout(time_t sec, time_t usec) {
read_timeout_usec_ = usec;
}
inline void Client::set_write_timeout(time_t sec, time_t usec) {
write_timeout_sec_ = sec;
write_timeout_usec_ = usec;
}
inline void Client::set_keep_alive_max_count(size_t count) {
keep_alive_max_count_ = count;
}
@@ -4618,8 +4706,9 @@ namespace detail {
template <typename U, typename V, typename T>
inline bool process_and_close_socket_ssl(
bool is_client_request, socket_t sock, size_t keep_alive_max_count,
time_t read_timeout_sec, time_t read_timeout_usec, SSL_CTX *ctx,
std::mutex &ctx_mutex, U SSL_connect_or_accept, V setup, T callback) {
time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
time_t write_timeout_usec, SSL_CTX *ctx, std::mutex &ctx_mutex,
U SSL_connect_or_accept, V setup, T callback) {
assert(keep_alive_max_count > 0);
SSL *ssl = nullptr;
@@ -4656,7 +4745,8 @@ inline bool process_and_close_socket_ssl(
(is_client_request ||
select_read(sock, CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND,
CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0)) {
SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec);
SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
write_timeout_sec, write_timeout_usec);
auto last_connection = count == 1;
auto connection_close = false;
@@ -4666,7 +4756,8 @@ inline bool process_and_close_socket_ssl(
count--;
}
} else {
SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec);
SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
write_timeout_sec, write_timeout_usec);
auto dummy_connection_close = false;
ret = callback(ssl, strm, true, dummy_connection_close);
}
@@ -4739,9 +4830,13 @@ private:
// SSL socket stream implementation
inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl,
time_t read_timeout_sec,
time_t read_timeout_usec)
time_t read_timeout_usec,
time_t write_timeout_sec,
time_t write_timeout_usec)
: sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec),
read_timeout_usec_(read_timeout_usec) {}
read_timeout_usec_(read_timeout_usec),
write_timeout_sec_(write_timeout_sec),
write_timeout_usec_(write_timeout_usec) {}
inline SSLSocketStream::~SSLSocketStream() {}
@@ -4750,7 +4845,8 @@ inline bool SSLSocketStream::is_readable() const {
}
inline bool SSLSocketStream::is_writable() const {
return detail::select_write(sock_, 0, 0) > 0;
return detail::select_write(sock_, write_timeout_sec_, write_timeout_usec_) >
0;
}
inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
@@ -4850,7 +4946,8 @@ inline bool SSLServer::is_valid() const { return ctx_; }
inline bool SSLServer::process_and_close_socket(socket_t sock) {
return detail::process_and_close_socket_ssl(
false, sock, keep_alive_max_count_, read_timeout_sec_, read_timeout_usec_,
ctx_, ctx_mutex_, SSL_accept, [](SSL * /*ssl*/) { return true; },
write_timeout_sec_, write_timeout_usec_, ctx_, ctx_mutex_, SSL_accept,
[](SSL * /*ssl*/) { return true; },
[this](SSL *ssl, Stream &strm, bool last_connection,
bool &connection_close) {
return process_request(strm, last_connection, connection_close,
@@ -4941,7 +5038,7 @@ inline bool SSLClient::process_and_close_socket(
return is_valid() &&
detail::process_and_close_socket_ssl(
true, sock, request_count, read_timeout_sec_, read_timeout_usec_,
ctx_, ctx_mutex_,
write_timeout_sec_, write_timeout_usec_, ctx_, ctx_mutex_,
[&](SSL *ssl) {
if (ca_cert_file_path_.empty() && ca_cert_store_ == nullptr) {
SSL_CTX_set_verify(ctx_, SSL_VERIFY_NONE, nullptr);
+107 -58
View File
@@ -334,7 +334,7 @@ TEST(RangeTest, FromHTTPBin) {
httplib::Headers headers;
auto res = cli.Get("/range/32", headers);
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(res->body, "abcdefghijklmnopqrstuvwxyzabcdef");
EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body);
EXPECT_EQ(200, res->status);
}
@@ -342,7 +342,7 @@ TEST(RangeTest, FromHTTPBin) {
httplib::Headers headers = {httplib::make_range_header({{1, -1}})};
auto res = cli.Get("/range/32", headers);
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(res->body, "bcdefghijklmnopqrstuvwxyzabcdef");
EXPECT_EQ("bcdefghijklmnopqrstuvwxyzabcdef", res->body);
EXPECT_EQ(206, res->status);
}
@@ -350,7 +350,7 @@ TEST(RangeTest, FromHTTPBin) {
httplib::Headers headers = {httplib::make_range_header({{1, 10}})};
auto res = cli.Get("/range/32", headers);
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(res->body, "bcdefghijk");
EXPECT_EQ("bcdefghijk", res->body);
EXPECT_EQ(206, res->status);
}
@@ -358,7 +358,7 @@ TEST(RangeTest, FromHTTPBin) {
httplib::Headers headers = {httplib::make_range_header({{0, 31}})};
auto res = cli.Get("/range/32", headers);
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(res->body, "abcdefghijklmnopqrstuvwxyzabcdef");
EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body);
EXPECT_EQ(200, res->status);
}
@@ -366,7 +366,7 @@ TEST(RangeTest, FromHTTPBin) {
httplib::Headers headers = {httplib::make_range_header({{0, -1}})};
auto res = cli.Get("/range/32", headers);
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(res->body, "abcdefghijklmnopqrstuvwxyzabcdef");
EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body);
EXPECT_EQ(200, res->status);
}
@@ -440,7 +440,7 @@ TEST(CancelTest, NoCancel) {
auto res = cli.Get("/range/32", [](uint64_t, uint64_t) { return true; });
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(res->body, "abcdefghijklmnopqrstuvwxyzabcdef");
EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body);
EXPECT_EQ(200, res->status);
}
@@ -501,8 +501,8 @@ TEST(BaseAuthTest, FromHTTPWatch) {
cli.Get("/basic-auth/hello/world",
{httplib::make_basic_authentication_header("hello", "world")});
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(res->body,
"{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n");
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
res->body);
EXPECT_EQ(200, res->status);
}
@@ -510,8 +510,8 @@ TEST(BaseAuthTest, FromHTTPWatch) {
cli.set_basic_auth("hello", "world");
auto res = cli.Get("/basic-auth/hello/world");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(res->body,
"{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n");
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
res->body);
EXPECT_EQ(200, res->status);
}
@@ -554,8 +554,8 @@ TEST(DigestAuthTest, FromHTTPWatch) {
for (auto path : paths) {
auto res = cli.Get(path.c_str());
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(res->body,
"{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n");
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
res->body);
EXPECT_EQ(200, res->status);
}
@@ -651,19 +651,6 @@ TEST(YahooRedirectTest, Redirect) {
EXPECT_EQ(200, res->status);
}
TEST(YahooRedirectTest2, Redirect) {
httplib::Client2 cli("http://yahoo.com");
auto res = cli.Get("/");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(301, res->status);
cli.set_follow_location(true);
res = cli.Get("/");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
}
TEST(HttpsToHttpRedirectTest, Redirect) {
httplib::SSLClient cli("httpbin.org");
cli.set_follow_location(true);
@@ -673,16 +660,6 @@ TEST(HttpsToHttpRedirectTest, Redirect) {
EXPECT_EQ(200, res->status);
}
TEST(HttpsToHttpRedirectTest2, Redirect) {
auto res =
httplib::Client2("https://httpbin.org")
.set_follow_location(true)
.Get("/redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
}
TEST(RedirectToDifferentPort, Redirect) {
Server svr8080;
Server svr8081;
@@ -712,7 +689,7 @@ TEST(RedirectToDifferentPort, Redirect) {
auto res = cli.Get("/1");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
EXPECT_EQ(res->body, "Hello World!");
EXPECT_EQ("Hello World!", res->body);
svr8080.stop();
svr8081.stop();
@@ -741,7 +718,7 @@ TEST(Server, BindDualStack) {
auto res = cli.Get("/1");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
EXPECT_EQ(res->body, "Hello World!");
EXPECT_EQ("Hello World!", res->body);
}
{
Client cli("::1", PORT);
@@ -749,7 +726,7 @@ TEST(Server, BindDualStack) {
auto res = cli.Get("/1");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
EXPECT_EQ(res->body, "Hello World!");
EXPECT_EQ("Hello World!", res->body);
}
svr.stop();
thread.join();
@@ -830,6 +807,11 @@ protected:
std::this_thread::sleep_for(std::chrono::seconds(2));
res.set_content("slow", "text/plain");
})
.Post("/slowpost",
[&](const Request & /*req*/, Response &res) {
std::this_thread::sleep_for(std::chrono::seconds(2));
res.set_content("slow", "text/plain");
})
.Get("/remote_addr",
[&](const Request &req, Response &res) {
auto remote_addr = req.headers.find("REMOTE_ADDR")->second;
@@ -898,9 +880,9 @@ protected:
res.set_chunked_content_provider(
[](size_t /*offset*/, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
sink.write("123", 3);
sink.write("456", 3);
sink.write("789", 3);
sink.os << "123";
sink.os << "456";
sink.os << "789";
sink.done();
return true;
});
@@ -912,9 +894,9 @@ protected:
[i](size_t /*offset*/, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
switch (*i) {
case 0: sink.write("123", 3); break;
case 1: sink.write("456", 3); break;
case 2: sink.write("789", 3); break;
case 0: sink.os << "123"; break;
case 1: sink.os << "456"; break;
case 2: sink.os << "789"; break;
case 3: sink.done(); break;
}
(*i)++;
@@ -926,7 +908,7 @@ protected:
[&](const Request & /*req*/, Response &res) {
res.set_content_provider(
6, [](size_t offset, size_t /*length*/, DataSink &sink) {
sink.write(offset < 3 ? "a" : "b", 1);
sink.os << (offset < 3 ? "a" : "b");
return true;
});
})
@@ -952,8 +934,7 @@ protected:
size_t(-1),
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
std::string data = "data_chunk";
sink.write(data.data(), data.size());
sink.os << "data_chunk";
return true;
});
})
@@ -1909,6 +1890,33 @@ TEST_F(ServerTest, SlowRequest) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
TEST_F(ServerTest, SlowPost) {
char buffer[64 * 1024];
memset(buffer, 0x42, sizeof(buffer));
auto res = cli_.Post(
"/slowpost", 64 * 1024 * 1024,
[&](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
sink.write(buffer, sizeof(buffer));
return true;
},
"text/plain");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
cli_.set_write_timeout(0, 0);
res = cli_.Post(
"/slowpost", 64 * 1024 * 1024,
[&](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
sink.write(buffer, sizeof(buffer));
return true;
},
"text/plain");
ASSERT_FALSE(res != nullptr);
}
TEST_F(ServerTest, Put) {
auto res = cli_.Put("/put", "PUT", "text/plain");
ASSERT_TRUE(res != nullptr);
@@ -1921,7 +1929,7 @@ TEST_F(ServerTest, PutWithContentProvider) {
"/put", 3,
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
sink.write("PUT", 3);
sink.os << "PUT";
return true;
},
"text/plain");
@@ -1949,7 +1957,7 @@ TEST_F(ServerTest, PutWithContentProviderWithGzip) {
"/put", 3,
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
sink.write("PUT", 3);
sink.os << "PUT";
return true;
},
"text/plain");
@@ -2277,7 +2285,7 @@ static bool send_request(time_t read_timeout_sec, const std::string &req,
if (client_sock == INVALID_SOCKET) { return false; }
return detail::process_and_close_socket(
true, client_sock, 1, read_timeout_sec, 0,
true, client_sock, 1, read_timeout_sec, 0, 0, 0,
[&](Stream &strm, bool /*last_connection*/, bool &
/*connection_close*/) -> bool {
if (req.size() !=
@@ -2462,7 +2470,7 @@ TEST(ServerStopTest, StopServerWithChunkedTransmission) {
svr.Get("/events", [](const Request & /*req*/, Response &res) {
res.set_header("Content-Type", "text/event-stream");
res.set_header("Cache-Control", "no-cache");
res.set_chunked_content_provider([](size_t offset, const DataSink &sink) {
res.set_chunked_content_provider([](size_t offset, DataSink &sink) {
char buffer[27];
auto size = static_cast<size_t>(sprintf(buffer, "data:%ld\n\n", offset));
sink.write(buffer, size);
@@ -2887,13 +2895,6 @@ TEST(SSLClientServerTest, TrustDirOptional) {
t.join();
}
/* Cannot test this case as there is no external access to SSL object to check
SSL_get_peer_certificate() == NULL TEST(SSLClientServerTest,
ClientCAPathRequired) { SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE,
nullptr, CLIENT_CA_CERT_DIR);
}
*/
#endif
#ifdef _WIN32
@@ -2902,3 +2903,51 @@ TEST(CleanupTest, WSACleanup) {
ASSERT_EQ(0, ret);
}
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(InvalidScheme, SimpleInterface) {
httplib::Client2 cli("scheme://yahoo.com");
ASSERT_FALSE(cli.is_valid());
}
TEST(NoScheme, SimpleInterface) {
httplib::Client2 cli("yahoo.com");
ASSERT_FALSE(cli.is_valid());
}
TEST(YahooRedirectTest2, SimpleInterface) {
httplib::Client2 cli("http://yahoo.com");
auto res = cli.Get("/");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(301, res->status);
cli.set_follow_location(true);
res = cli.Get("/");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
}
TEST(YahooRedirectTest3, SimpleInterface) {
httplib::Client2 cli("https://yahoo.com");
auto res = cli.Get("/");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(301, res->status);
cli.set_follow_location(true);
res = cli.Get("/");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
}
TEST(HttpsToHttpRedirectTest2, SimpleInterface) {
auto res =
httplib::Client2("https://httpbin.org")
.set_follow_location(true)
.Get("/redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
}
#endif
+10 -8
View File
@@ -185,15 +185,17 @@ void DigestAuthTestFromHTTPWatch(Client& cli) {
for (auto path : paths) {
auto res = cli.Get(path.c_str());
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(400, res->status);
EXPECT_EQ(401, res->status);
}
cli.set_digest_auth("bad", "world");
for (auto path : paths) {
auto res = cli.Get(path.c_str());
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(400, res->status);
}
// NOTE: Until httpbin.org fixes issue #46, the following test is commented
// out. Plese see https://httpbin.org/digest-auth/auth/hello/world
// cli.set_digest_auth("bad", "world");
// for (auto path : paths) {
// auto res = cli.Get(path.c_str());
// ASSERT_TRUE(res != nullptr);
// EXPECT_EQ(401, res->status);
// }
}
}
@@ -266,7 +268,7 @@ void KeepAliveTest(Client& cli, bool basic) {
{
int count = paths.size();
int count = static_cast<int>(paths.size());
while (count--) {
auto &res = responses[i++];
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res.body);