mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 29fd136afd | |||
| f5598237b2 | |||
| 01058659ab | |||
| 66f698fab6 | |||
| b9a9df4d73 | |||
| 25aa3ca982 | |||
| 2d67211183 |
@@ -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
@@ -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([&] {
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user