mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 089b9daa1c | |||
| ba34ea4ee8 | |||
| 2917b8a005 | |||
| dcf24d45a2 | |||
| 75fdb06696 | |||
| e00ad37580 | |||
| 5cfb70c2b4 | |||
| 2a70c45697 | |||
| c58b00580e | |||
| 7c60e69c33 | |||
| 33e94891ee | |||
| 73e0729f63 | |||
| 21c529229c | |||
| 63643e6386 | |||
| 6cc2edce99 | |||
| d122ff3ca8 | |||
| 14c6d526b4 | |||
| 28e07bca16 | |||
| faa5f1d802 | |||
| 9d3365df54 | |||
| 6ff84d34d1 | |||
| b845425cd0 | |||
| 89519c88e2 | |||
| ff813bf99d |
+2
-4
@@ -20,8 +20,6 @@ using namespace std;
|
||||
class EventDispatcher {
|
||||
public:
|
||||
EventDispatcher() {
|
||||
id_ = 0;
|
||||
cid_ = -1;
|
||||
}
|
||||
|
||||
void wait_event(DataSink *sink) {
|
||||
@@ -41,8 +39,8 @@ public:
|
||||
private:
|
||||
mutex m_;
|
||||
condition_variable cv_;
|
||||
atomic_int id_;
|
||||
atomic_int cid_;
|
||||
atomic_int id_{0};
|
||||
atomic_int cid_{-1};
|
||||
string message_;
|
||||
};
|
||||
|
||||
|
||||
@@ -200,6 +200,7 @@ using socket_t = int;
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
#include <regex>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
@@ -256,7 +257,7 @@ namespace detail {
|
||||
|
||||
template <class T, class... Args>
|
||||
typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type
|
||||
make_unique(Args &&... args) {
|
||||
make_unique(Args &&...args) {
|
||||
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
@@ -307,7 +308,7 @@ public:
|
||||
DataSink(DataSink &&) = delete;
|
||||
DataSink &operator=(DataSink &&) = delete;
|
||||
|
||||
std::function<void(const char *data, size_t data_len)> write;
|
||||
std::function<bool(const char *data, size_t data_len)> write;
|
||||
std::function<void()> done;
|
||||
std::function<bool()> is_writable;
|
||||
std::ostream os;
|
||||
@@ -394,7 +395,7 @@ struct Request {
|
||||
ContentReceiverWithProgress content_receiver;
|
||||
Progress progress;
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
const SSL *ssl;
|
||||
const SSL *ssl = nullptr;
|
||||
#endif
|
||||
|
||||
bool has_header(const char *key) const;
|
||||
@@ -486,7 +487,7 @@ public:
|
||||
virtual socket_t socket() const = 0;
|
||||
|
||||
template <typename... Args>
|
||||
ssize_t write_format(const char *fmt, const Args &... args);
|
||||
ssize_t write_format(const char *fmt, const Args &...args);
|
||||
ssize_t write(const char *ptr);
|
||||
ssize_t write(const std::string &s);
|
||||
};
|
||||
@@ -662,14 +663,24 @@ public:
|
||||
Server &set_expect_100_continue_handler(Expect100ContinueHandler handler);
|
||||
Server &set_logger(Logger logger);
|
||||
|
||||
Server &set_address_family(int family);
|
||||
Server &set_tcp_nodelay(bool on);
|
||||
Server &set_socket_options(SocketOptions socket_options);
|
||||
|
||||
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);
|
||||
|
||||
@@ -772,11 +783,12 @@ private:
|
||||
Logger logger_;
|
||||
Expect100ContinueHandler expect_100_continue_handler_;
|
||||
|
||||
int address_family_ = AF_UNSPEC;
|
||||
bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
|
||||
SocketOptions socket_options_ = default_socket_options;
|
||||
};
|
||||
|
||||
enum Error {
|
||||
enum class Error {
|
||||
Success = 0,
|
||||
Unknown,
|
||||
Connection,
|
||||
@@ -792,6 +804,11 @@ enum Error {
|
||||
Compression,
|
||||
};
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &os, const Error &obj) {
|
||||
os << static_cast<std::underlying_type<Error>::type>(obj);
|
||||
return os;
|
||||
}
|
||||
|
||||
class Result {
|
||||
public:
|
||||
Result(std::unique_ptr<Response> &&res, Error err,
|
||||
@@ -961,12 +978,22 @@ public:
|
||||
|
||||
void set_default_headers(Headers headers);
|
||||
|
||||
void set_address_family(int family);
|
||||
void set_tcp_nodelay(bool on);
|
||||
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);
|
||||
@@ -977,6 +1004,8 @@ public:
|
||||
void set_keep_alive(bool on);
|
||||
void set_follow_location(bool on);
|
||||
|
||||
void set_url_encode(bool on);
|
||||
|
||||
void set_compress(bool on);
|
||||
|
||||
void set_decompress(bool on);
|
||||
@@ -1021,10 +1050,6 @@ protected:
|
||||
void shutdown_socket(Socket &socket);
|
||||
void close_socket(Socket &socket);
|
||||
|
||||
// Similar to shutdown_ssl and close_socket, this should NOT be called
|
||||
// concurrently with a DIFFERENT thread sending requests from the socket
|
||||
void lock_socket_and_shutdown_and_close();
|
||||
|
||||
bool process_request(Stream &strm, Request &req, Response &res,
|
||||
bool close_connection, Error &error);
|
||||
|
||||
@@ -1073,6 +1098,9 @@ protected:
|
||||
bool keep_alive_ = false;
|
||||
bool follow_location_ = false;
|
||||
|
||||
bool url_encode_ = true;
|
||||
|
||||
int address_family_ = AF_UNSPEC;
|
||||
bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
|
||||
SocketOptions socket_options_ = nullptr;
|
||||
|
||||
@@ -1263,12 +1291,22 @@ public:
|
||||
|
||||
void set_default_headers(Headers headers);
|
||||
|
||||
void set_address_family(int family);
|
||||
void set_tcp_nodelay(bool on);
|
||||
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);
|
||||
@@ -1279,6 +1317,8 @@ public:
|
||||
void set_keep_alive(bool on);
|
||||
void set_follow_location(bool on);
|
||||
|
||||
void set_url_encode(bool on);
|
||||
|
||||
void set_compress(bool on);
|
||||
|
||||
void set_decompress(bool on);
|
||||
@@ -1368,6 +1408,7 @@ public:
|
||||
private:
|
||||
bool create_and_connect_socket(Socket &socket, Error &error) override;
|
||||
void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override;
|
||||
void shutdown_ssl_impl(Socket &socket, bool shutdown_socket);
|
||||
|
||||
bool process_socket(const Socket &socket,
|
||||
std::function<bool(Stream &strm)> callback) override;
|
||||
@@ -1579,6 +1620,7 @@ inline std::string encode_query_param(const std::string &value) {
|
||||
|
||||
inline std::string encode_url(const std::string &s) {
|
||||
std::string result;
|
||||
result.reserve(s.size());
|
||||
|
||||
for (size_t i = 0; s[i]; i++) {
|
||||
switch (s[i]) {
|
||||
@@ -2032,15 +2074,16 @@ inline int shutdown_socket(socket_t sock) {
|
||||
}
|
||||
|
||||
template <typename BindOrConnect>
|
||||
socket_t create_socket(const char *host, int port, int socket_flags,
|
||||
bool tcp_nodelay, SocketOptions socket_options,
|
||||
socket_t create_socket(const char *host, int port, int address_family,
|
||||
int socket_flags, bool tcp_nodelay,
|
||||
SocketOptions socket_options,
|
||||
BindOrConnect bind_or_connect) {
|
||||
// Get address info
|
||||
struct addrinfo hints;
|
||||
struct addrinfo *result;
|
||||
|
||||
memset(&hints, 0, sizeof(struct addrinfo));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_family = address_family;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_flags = socket_flags;
|
||||
hints.ai_protocol = 0;
|
||||
@@ -2057,8 +2100,9 @@ socket_t create_socket(const char *host, int port, int socket_flags,
|
||||
for (auto rp = result; rp; rp = rp->ai_next) {
|
||||
// Create a socket
|
||||
#ifdef _WIN32
|
||||
auto sock = WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol,
|
||||
nullptr, 0, WSA_FLAG_NO_HANDLE_INHERIT);
|
||||
auto sock =
|
||||
WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0,
|
||||
WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED);
|
||||
/**
|
||||
* Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1
|
||||
* and above the socket creation fails on older Windows Systems.
|
||||
@@ -2180,40 +2224,55 @@ inline std::string if2ip(const std::string &ifn) {
|
||||
}
|
||||
#endif
|
||||
|
||||
inline socket_t create_client_socket(const char *host, int port,
|
||||
bool tcp_nodelay,
|
||||
SocketOptions socket_options,
|
||||
time_t timeout_sec, time_t timeout_usec,
|
||||
const std::string &intf, Error &error) {
|
||||
inline socket_t create_client_socket(
|
||||
const char *host, int port, int address_family, bool tcp_nodelay,
|
||||
SocketOptions socket_options, time_t connection_timeout_sec,
|
||||
time_t connection_timeout_usec, time_t read_timeout_sec,
|
||||
time_t read_timeout_usec, time_t write_timeout_sec,
|
||||
time_t write_timeout_usec, const std::string &intf, Error &error) {
|
||||
auto sock = create_socket(
|
||||
host, port, 0, tcp_nodelay, std::move(socket_options),
|
||||
[&](socket_t sock, struct addrinfo &ai) -> bool {
|
||||
host, port, address_family, 0, tcp_nodelay, std::move(socket_options),
|
||||
[&](socket_t sock2, struct addrinfo &ai) -> bool {
|
||||
if (!intf.empty()) {
|
||||
#ifdef USE_IF2IP
|
||||
auto ip = if2ip(intf);
|
||||
if (ip.empty()) { ip = intf; }
|
||||
if (!bind_ip_address(sock, ip.c_str())) {
|
||||
if (!bind_ip_address(sock2, ip.c_str())) {
|
||||
error = Error::BindIPAddress;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
set_nonblocking(sock, true);
|
||||
set_nonblocking(sock2, true);
|
||||
|
||||
auto ret =
|
||||
::connect(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen));
|
||||
::connect(sock2, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen));
|
||||
|
||||
if (ret < 0) {
|
||||
if (is_connection_error() ||
|
||||
!wait_until_socket_is_ready(sock, timeout_sec, timeout_usec)) {
|
||||
close_socket(sock);
|
||||
!wait_until_socket_is_ready(sock2, connection_timeout_sec,
|
||||
connection_timeout_usec)) {
|
||||
error = Error::Connection;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
set_nonblocking(sock, false);
|
||||
set_nonblocking(sock2, false);
|
||||
|
||||
{
|
||||
timeval tv;
|
||||
tv.tv_sec = static_cast<long>(read_timeout_sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(read_timeout_usec);
|
||||
setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
|
||||
}
|
||||
{
|
||||
timeval tv;
|
||||
tv.tv_sec = static_cast<long>(write_timeout_sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(write_timeout_usec);
|
||||
setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv));
|
||||
}
|
||||
|
||||
error = Error::Success;
|
||||
return true;
|
||||
});
|
||||
@@ -2501,7 +2560,7 @@ public:
|
||||
|
||||
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
|
||||
do {
|
||||
strm_.avail_out = buff.size();
|
||||
strm_.avail_out = static_cast<uInt>(buff.size());
|
||||
strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
|
||||
|
||||
ret = deflate(&strm_, flush);
|
||||
@@ -2552,7 +2611,7 @@ public:
|
||||
|
||||
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
|
||||
while (strm_.avail_in > 0) {
|
||||
strm_.avail_out = buff.size();
|
||||
strm_.avail_out = static_cast<uInt>(buff.size());
|
||||
strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
|
||||
|
||||
ret = inflate(&strm_, Z_NO_FLUSH);
|
||||
@@ -2889,8 +2948,8 @@ bool prepare_content_receiver(T &x, int &status,
|
||||
ContentReceiverWithProgress out = [&](const char *buf, size_t n,
|
||||
uint64_t off, uint64_t len) {
|
||||
return decompressor->decompress(buf, n,
|
||||
[&](const char *buf, size_t n) {
|
||||
return receiver(buf, n, off, len);
|
||||
[&](const char *buf2, size_t n2) {
|
||||
return receiver(buf2, n2, off, len);
|
||||
});
|
||||
};
|
||||
return callback(std::move(out));
|
||||
@@ -2970,7 +3029,7 @@ inline bool write_content(Stream &strm, const ContentProvider &content_provider,
|
||||
auto ok = true;
|
||||
DataSink data_sink;
|
||||
|
||||
data_sink.write = [&](const char *d, size_t l) {
|
||||
data_sink.write = [&](const char *d, size_t l) -> bool {
|
||||
if (ok) {
|
||||
if (write_data(strm, d, l)) {
|
||||
offset += l;
|
||||
@@ -2978,6 +3037,7 @@ inline bool write_content(Stream &strm, const ContentProvider &content_provider,
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
};
|
||||
|
||||
data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
|
||||
@@ -3016,11 +3076,12 @@ write_content_without_length(Stream &strm,
|
||||
auto ok = true;
|
||||
DataSink data_sink;
|
||||
|
||||
data_sink.write = [&](const char *d, size_t l) {
|
||||
data_sink.write = [&](const char *d, size_t l) -> bool {
|
||||
if (ok) {
|
||||
offset += l;
|
||||
if (!write_data(strm, d, l)) { ok = false; }
|
||||
}
|
||||
return ok;
|
||||
};
|
||||
|
||||
data_sink.done = [&](void) { data_available = false; };
|
||||
@@ -3043,30 +3104,28 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
|
||||
auto ok = true;
|
||||
DataSink data_sink;
|
||||
|
||||
data_sink.write = [&](const char *d, size_t l) {
|
||||
if (!ok) { return; }
|
||||
data_sink.write = [&](const char *d, size_t l) -> bool {
|
||||
if (ok) {
|
||||
data_available = l > 0;
|
||||
offset += l;
|
||||
|
||||
data_available = l > 0;
|
||||
offset += l;
|
||||
|
||||
std::string payload;
|
||||
if (!compressor.compress(d, l, false,
|
||||
[&](const char *data, size_t data_len) {
|
||||
payload.append(data, data_len);
|
||||
return true;
|
||||
})) {
|
||||
ok = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!payload.empty()) {
|
||||
// Emit chunked response header and footer for each chunk
|
||||
auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
|
||||
if (!write_data(strm, chunk.data(), chunk.size())) {
|
||||
std::string payload;
|
||||
if (compressor.compress(d, l, false,
|
||||
[&](const char *data, size_t data_len) {
|
||||
payload.append(data, data_len);
|
||||
return true;
|
||||
})) {
|
||||
if (!payload.empty()) {
|
||||
// Emit chunked response header and footer for each chunk
|
||||
auto chunk =
|
||||
from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
|
||||
if (!write_data(strm, chunk.data(), chunk.size())) { ok = false; }
|
||||
}
|
||||
} else {
|
||||
ok = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
};
|
||||
|
||||
data_sink.done = [&](void) {
|
||||
@@ -3171,7 +3230,12 @@ 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) {
|
||||
@@ -3711,9 +3775,9 @@ inline std::pair<std::string, std::string> make_digest_authentication_header(
|
||||
|
||||
string response;
|
||||
{
|
||||
auto H = algo == "SHA-256"
|
||||
? detail::SHA_256
|
||||
: algo == "SHA-512" ? detail::SHA_512 : detail::MD5;
|
||||
auto H = algo == "SHA-256" ? detail::SHA_256
|
||||
: algo == "SHA-512" ? detail::SHA_512
|
||||
: detail::MD5;
|
||||
|
||||
auto A1 = username + ":" + auth.at("realm") + ":" + password;
|
||||
|
||||
@@ -3775,7 +3839,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 +3860,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
|
||||
@@ -4016,7 +4089,7 @@ inline ssize_t Stream::write(const std::string &s) {
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline ssize_t Stream::write_format(const char *fmt, const Args &... args) {
|
||||
inline ssize_t Stream::write_format(const char *fmt, const Args &...args) {
|
||||
const auto bufsiz = 2048;
|
||||
std::array<char, bufsiz> buf;
|
||||
|
||||
@@ -4291,13 +4364,11 @@ inline Server &
|
||||
Server::set_file_extension_and_mimetype_mapping(const char *ext,
|
||||
const char *mime) {
|
||||
file_extension_and_mimetype_map_[ext] = mime;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::set_file_request_handler(Handler handler) {
|
||||
file_request_handler_ = std::move(handler);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -4331,7 +4402,6 @@ inline Server &Server::set_post_routing_handler(Handler handler) {
|
||||
|
||||
inline Server &Server::set_logger(Logger logger) {
|
||||
logger_ = std::move(logger);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -4342,54 +4412,75 @@ Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::set_address_family(int family) {
|
||||
address_family_ = family;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::set_tcp_nodelay(bool on) {
|
||||
tcp_nodelay_ = on;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::set_socket_options(SocketOptions socket_options) {
|
||||
socket_options_ = std::move(socket_options);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::set_keep_alive_max_count(size_t count) {
|
||||
keep_alive_max_count_ = count;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::set_keep_alive_timeout(time_t sec) {
|
||||
keep_alive_timeout_sec_ = sec;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::set_read_timeout(time_t sec, time_t usec) {
|
||||
read_timeout_sec_ = sec;
|
||||
read_timeout_usec_ = 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;
|
||||
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;
|
||||
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;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -4705,7 +4796,8 @@ inline socket_t
|
||||
Server::create_server_socket(const char *host, int port, int socket_flags,
|
||||
SocketOptions socket_options) const {
|
||||
return detail::create_socket(
|
||||
host, port, socket_flags, tcp_nodelay_, std::move(socket_options),
|
||||
host, port, address_family_, socket_flags, tcp_nodelay_,
|
||||
std::move(socket_options),
|
||||
[](socket_t sock, struct addrinfo &ai) -> bool {
|
||||
if (::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
|
||||
return false;
|
||||
@@ -4780,6 +4872,19 @@ inline bool Server::listen_internal() {
|
||||
break;
|
||||
}
|
||||
|
||||
{
|
||||
timeval tv;
|
||||
tv.tv_sec = static_cast<long>(read_timeout_sec_);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(read_timeout_usec_);
|
||||
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
|
||||
}
|
||||
{
|
||||
timeval tv;
|
||||
tv.tv_sec = static_cast<long>(write_timeout_sec_);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(write_timeout_usec_);
|
||||
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv));
|
||||
}
|
||||
|
||||
#if __cplusplus > 201703L
|
||||
task_queue->enqueue([=, this]() { process_and_close_socket(sock); });
|
||||
#else
|
||||
@@ -5150,7 +5255,11 @@ inline ClientImpl::ClientImpl(const std::string &host, int port,
|
||||
host_and_port_(host_ + ":" + std::to_string(port_)),
|
||||
client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
|
||||
|
||||
inline ClientImpl::~ClientImpl() { lock_socket_and_shutdown_and_close(); }
|
||||
inline ClientImpl::~ClientImpl() {
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
shutdown_socket(socket_);
|
||||
close_socket(socket_);
|
||||
}
|
||||
|
||||
inline bool ClientImpl::is_valid() const { return true; }
|
||||
|
||||
@@ -5171,6 +5280,8 @@ inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
|
||||
#endif
|
||||
keep_alive_ = rhs.keep_alive_;
|
||||
follow_location_ = rhs.follow_location_;
|
||||
url_encode_ = rhs.url_encode_;
|
||||
address_family_ = rhs.address_family_;
|
||||
tcp_nodelay_ = rhs.tcp_nodelay_;
|
||||
socket_options_ = rhs.socket_options_;
|
||||
compress_ = rhs.compress_;
|
||||
@@ -5194,12 +5305,16 @@ inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
|
||||
inline socket_t ClientImpl::create_client_socket(Error &error) const {
|
||||
if (!proxy_host_.empty() && proxy_port_ != -1) {
|
||||
return detail::create_client_socket(
|
||||
proxy_host_.c_str(), proxy_port_, tcp_nodelay_, socket_options_,
|
||||
connection_timeout_sec_, connection_timeout_usec_, interface_, error);
|
||||
proxy_host_.c_str(), proxy_port_, address_family_, tcp_nodelay_,
|
||||
socket_options_, connection_timeout_sec_, connection_timeout_usec_,
|
||||
read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
|
||||
write_timeout_usec_, interface_, error);
|
||||
}
|
||||
return detail::create_client_socket(
|
||||
host_.c_str(), port_, tcp_nodelay_, socket_options_,
|
||||
connection_timeout_sec_, connection_timeout_usec_, interface_, error);
|
||||
host_.c_str(), port_, address_family_, tcp_nodelay_, socket_options_,
|
||||
connection_timeout_sec_, connection_timeout_usec_, read_timeout_sec_,
|
||||
read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, interface_,
|
||||
error);
|
||||
}
|
||||
|
||||
inline bool ClientImpl::create_and_connect_socket(Socket &socket,
|
||||
@@ -5242,13 +5357,6 @@ inline void ClientImpl::close_socket(Socket &socket) {
|
||||
socket.sock = INVALID_SOCKET;
|
||||
}
|
||||
|
||||
inline void ClientImpl::lock_socket_and_shutdown_and_close() {
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
shutdown_ssl(socket_, true);
|
||||
shutdown_socket(socket_);
|
||||
close_socket(socket_);
|
||||
}
|
||||
|
||||
inline bool ClientImpl::read_response_line(Stream &strm, const Request &req,
|
||||
Response &res) {
|
||||
std::array<char, 2048> buf;
|
||||
@@ -5594,7 +5702,7 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
|
||||
{
|
||||
detail::BufferStream bstrm;
|
||||
|
||||
const auto &path = detail::encode_url(req.path);
|
||||
const auto &path = url_encode_ ? detail::encode_url(req.path) : req.path;
|
||||
bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str());
|
||||
|
||||
detail::write_headers(bstrm, req.headers);
|
||||
@@ -5645,7 +5753,7 @@ inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
|
||||
size_t offset = 0;
|
||||
DataSink data_sink;
|
||||
|
||||
data_sink.write = [&](const char *data, size_t data_len) {
|
||||
data_sink.write = [&](const char *data, size_t data_len) -> bool {
|
||||
if (ok) {
|
||||
auto last = offset + data_len == content_length;
|
||||
|
||||
@@ -5661,6 +5769,7 @@ inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
};
|
||||
|
||||
data_sink.is_writable = [&](void) { return ok && true; };
|
||||
@@ -5738,19 +5847,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 +5878,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;
|
||||
@@ -5793,7 +5905,10 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
||||
// mutex during the process. It would be a bug to call it from a different
|
||||
// thread since it's a thread-safety issue to do these things to the socket
|
||||
// if another thread is using the socket.
|
||||
lock_socket_and_shutdown_and_close();
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
shutdown_ssl(socket_, true);
|
||||
shutdown_socket(socket_);
|
||||
close_socket(socket_);
|
||||
}
|
||||
|
||||
// Log
|
||||
@@ -5925,7 +6040,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 +6380,38 @@ 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;
|
||||
@@ -6297,10 +6434,16 @@ inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; }
|
||||
|
||||
inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; }
|
||||
|
||||
inline void ClientImpl::set_url_encode(bool on) { url_encode_ = on; }
|
||||
|
||||
inline void ClientImpl::set_default_headers(Headers headers) {
|
||||
default_headers_ = std::move(headers);
|
||||
}
|
||||
|
||||
inline void ClientImpl::set_address_family(int family) {
|
||||
address_family_ = family;
|
||||
}
|
||||
|
||||
inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
|
||||
|
||||
inline void ClientImpl::set_socket_options(SocketOptions socket_options) {
|
||||
@@ -6525,7 +6668,12 @@ inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
|
||||
auto ret = SSL_read(ssl_, ptr, static_cast<int>(size));
|
||||
if (ret < 0) {
|
||||
auto err = SSL_get_error(ssl_, ret);
|
||||
#ifdef _WIN32
|
||||
while (err == SSL_ERROR_WANT_READ ||
|
||||
err == SSL_ERROR_SYSCALL && WSAGetLastError() == WSAETIMEDOUT) {
|
||||
#else
|
||||
while (err == SSL_ERROR_WANT_READ) {
|
||||
#endif
|
||||
if (SSL_pending(ssl_) > 0) {
|
||||
return SSL_read(ssl_, ptr, static_cast<int>(size));
|
||||
} else if (is_readable()) {
|
||||
@@ -6562,7 +6710,7 @@ static SSLInit sslinit_;
|
||||
inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
|
||||
const char *client_ca_cert_file_path,
|
||||
const char *client_ca_cert_dir_path) {
|
||||
ctx_ = SSL_CTX_new(SSLv23_server_method());
|
||||
ctx_ = SSL_CTX_new(TLS_method());
|
||||
|
||||
if (ctx_) {
|
||||
SSL_CTX_set_options(ctx_,
|
||||
@@ -6713,7 +6861,7 @@ inline SSLClient::~SSLClient() {
|
||||
// Make sure to shut down SSL since shutdown_ssl will resolve to the
|
||||
// base function rather than the derived function once we get to the
|
||||
// base class destructor, and won't free the SSL (causing a leak).
|
||||
SSLClient::shutdown_ssl(socket_, true);
|
||||
shutdown_ssl_impl(socket_, true);
|
||||
}
|
||||
|
||||
inline bool SSLClient::is_valid() const { return ctx_; }
|
||||
@@ -6892,6 +7040,10 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
|
||||
}
|
||||
|
||||
inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) {
|
||||
shutdown_ssl_impl(socket, shutdown_gracefully);
|
||||
}
|
||||
|
||||
inline void SSLClient::shutdown_ssl_impl(Socket &socket, bool shutdown_gracefully) {
|
||||
if (socket.sock == INVALID_SOCKET) {
|
||||
assert(socket.ssl == nullptr);
|
||||
return;
|
||||
@@ -7363,7 +7515,12 @@ inline void Client::set_default_headers(Headers headers) {
|
||||
cli_->set_default_headers(std::move(headers));
|
||||
}
|
||||
|
||||
inline void Client::set_address_family(int family) {
|
||||
cli_->set_address_family(family);
|
||||
}
|
||||
|
||||
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 +7528,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);
|
||||
}
|
||||
@@ -7396,6 +7573,8 @@ inline void Client::set_follow_location(bool on) {
|
||||
cli_->set_follow_location(on);
|
||||
}
|
||||
|
||||
inline void Client::set_url_encode(bool on) { cli_->set_url_encode(on); }
|
||||
|
||||
inline void Client::set_compress(bool on) { cli_->set_compress(on); }
|
||||
|
||||
inline void Client::set_decompress(bool on) { cli_->set_decompress(on); }
|
||||
|
||||
+269
-79
@@ -7,6 +7,7 @@
|
||||
#include <future>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <sstream>
|
||||
|
||||
#define SERVER_CERT_FILE "./cert.pem"
|
||||
#define SERVER_CERT2_FILE "./cert2.pem"
|
||||
@@ -417,16 +418,16 @@ TEST(ChunkedEncodingTest, WithResponseHandlerAndContentReceiver) {
|
||||
cli.set_connection_timeout(2);
|
||||
|
||||
std::string body;
|
||||
auto res =
|
||||
cli.Get("/httpgallery/chunked/chunkedimage.aspx?0.4153841143030137",
|
||||
[&](const Response &response) {
|
||||
EXPECT_EQ(200, response.status);
|
||||
return true;
|
||||
},
|
||||
[&](const char *data, size_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
auto res = cli.Get(
|
||||
"/httpgallery/chunked/chunkedimage.aspx?0.4153841143030137",
|
||||
[&](const Response &response) {
|
||||
EXPECT_EQ(200, response.status);
|
||||
return true;
|
||||
},
|
||||
[&](const char *data, size_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
ASSERT_TRUE(res);
|
||||
|
||||
std::string out;
|
||||
@@ -525,7 +526,7 @@ TEST(ConnectionErrorTest, InvalidHost) {
|
||||
auto port = 80;
|
||||
Client cli(host, port);
|
||||
#endif
|
||||
cli.set_connection_timeout(2);
|
||||
cli.set_connection_timeout(std::chrono::seconds(2));
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(!res);
|
||||
@@ -540,13 +541,30 @@ 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, 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;
|
||||
@@ -556,7 +574,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 +591,7 @@ TEST(ConnectionErrorTest, Timeout) {
|
||||
auto port = 8080;
|
||||
Client cli(host, port);
|
||||
#endif
|
||||
cli.set_connection_timeout(2);
|
||||
cli.set_connection_timeout(std::chrono::seconds(2));
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(!res);
|
||||
@@ -590,7 +608,7 @@ TEST(CancelTest, NoCancel) {
|
||||
auto port = 80;
|
||||
Client cli(host, port);
|
||||
#endif
|
||||
cli.set_connection_timeout(5);
|
||||
cli.set_connection_timeout(std::chrono::seconds(5));
|
||||
|
||||
auto res = cli.Get("/range/32", [](uint64_t, uint64_t) { return true; });
|
||||
ASSERT_TRUE(res);
|
||||
@@ -610,7 +628,7 @@ TEST(CancelTest, WithCancelSmallPayload) {
|
||||
#endif
|
||||
|
||||
auto res = cli.Get("/range/32", [](uint64_t, uint64_t) { return false; });
|
||||
cli.set_connection_timeout(5);
|
||||
cli.set_connection_timeout(std::chrono::seconds(5));
|
||||
ASSERT_TRUE(!res);
|
||||
EXPECT_EQ(Error::Canceled, res.error());
|
||||
}
|
||||
@@ -625,7 +643,7 @@ TEST(CancelTest, WithCancelLargePayload) {
|
||||
auto port = 80;
|
||||
Client cli(host, port);
|
||||
#endif
|
||||
cli.set_connection_timeout(5);
|
||||
cli.set_connection_timeout(std::chrono::seconds(5));
|
||||
|
||||
uint32_t count = 0;
|
||||
auto res = cli.Get("/range/65536",
|
||||
@@ -889,8 +907,100 @@ TEST(UrlWithSpace, Redirect) {
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(18527, res->get_header_value<uint64_t>("Content-Length"));
|
||||
}
|
||||
|
||||
TEST(RedirectFromPageWithContent, Redirect) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/1", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("___", "text/plain");
|
||||
res.set_redirect("/2");
|
||||
});
|
||||
|
||||
svr.Get("/2", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
auto th = std::thread([&]() { svr.listen("localhost", PORT); });
|
||||
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
// Give GET time to get a few messages.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
{
|
||||
Client cli("localhost", PORT);
|
||||
cli.set_follow_location(true);
|
||||
|
||||
std::string body;
|
||||
auto res = cli.Get("/1", [&](const char *data, size_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("Hello World!", body);
|
||||
}
|
||||
|
||||
{
|
||||
Client cli("localhost", PORT);
|
||||
|
||||
std::string body;
|
||||
auto res = cli.Get("/1", [&](const char *data, size_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(302, res->status);
|
||||
EXPECT_EQ("___", body);
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
th.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST(PathUrlEncodeTest, PathUrlEncode) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/foo", [](const Request & req, Response &res) {
|
||||
auto a = req.params.find("a");
|
||||
if (a != req.params.end()) {
|
||||
res.set_content((*a).second, "text/plain");
|
||||
res.status = 200;
|
||||
} else {
|
||||
res.status = 400;
|
||||
}
|
||||
});
|
||||
|
||||
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||
|
||||
// Give GET time to get a few messages.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
{
|
||||
Client cli(HOST, PORT);
|
||||
cli.set_url_encode(false);
|
||||
|
||||
auto res = cli.Get("/foo?a=explicitly+encoded");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
// This expects it back with a space, as the `+` won't have been
|
||||
// url-encoded, and server-side the params get decoded turning `+`
|
||||
// into spaces.
|
||||
EXPECT_EQ("explicitly encoded", res->body);
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(BindServerTest, BindDualStack) {
|
||||
Server svr;
|
||||
|
||||
@@ -1310,7 +1420,8 @@ protected:
|
||||
const auto &d = *data;
|
||||
auto out_len =
|
||||
std::min(static_cast<size_t>(length), DATA_CHUNK_SIZE);
|
||||
sink.write(&d[static_cast<size_t>(offset)], out_len);
|
||||
auto ret = sink.write(&d[static_cast<size_t>(offset)], out_len);
|
||||
EXPECT_TRUE(ret);
|
||||
return true;
|
||||
},
|
||||
[data] { delete data; });
|
||||
@@ -2462,13 +2573,14 @@ 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");
|
||||
auto res = cli_.Post(
|
||||
"/slowpost", 64 * 1024 * 1024,
|
||||
[&](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
auto ret = sink.write(buffer, sizeof(buffer));
|
||||
EXPECT_TRUE(ret);
|
||||
return true;
|
||||
},
|
||||
"text/plain");
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -2478,14 +2590,14 @@ TEST_F(ServerTest, SlowPostFail) {
|
||||
char buffer[64 * 1024];
|
||||
memset(buffer, 0x42, sizeof(buffer));
|
||||
|
||||
cli_.set_write_timeout(0, 0);
|
||||
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");
|
||||
cli_.set_write_timeout(std::chrono::seconds(0));
|
||||
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);
|
||||
EXPECT_EQ(Error::Write, res.error());
|
||||
@@ -2499,13 +2611,14 @@ TEST_F(ServerTest, Put) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PutWithContentProvider) {
|
||||
auto res = cli_.Put("/put", 3,
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.os << "PUT";
|
||||
return true;
|
||||
},
|
||||
"text/plain");
|
||||
auto res = cli_.Put(
|
||||
"/put", 3,
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.os << "PUT";
|
||||
return true;
|
||||
},
|
||||
"text/plain");
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -2513,24 +2626,27 @@ TEST_F(ServerTest, PutWithContentProvider) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PostWithContentProviderAbort) {
|
||||
auto res = cli_.Post("/post", 42,
|
||||
[](size_t /*offset*/, size_t /*length*/,
|
||||
DataSink & /*sink*/) { return false; },
|
||||
"text/plain");
|
||||
auto res = cli_.Post(
|
||||
"/post", 42,
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink & /*sink*/) {
|
||||
return false;
|
||||
},
|
||||
"text/plain");
|
||||
|
||||
ASSERT_TRUE(!res);
|
||||
EXPECT_EQ(Error::Canceled, res.error());
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PutWithContentProviderWithoutLength) {
|
||||
auto res = cli_.Put("/put",
|
||||
[](size_t /*offset*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.os << "PUT";
|
||||
sink.done();
|
||||
return true;
|
||||
},
|
||||
"text/plain");
|
||||
auto res = cli_.Put(
|
||||
"/put",
|
||||
[](size_t /*offset*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.os << "PUT";
|
||||
sink.done();
|
||||
return true;
|
||||
},
|
||||
"text/plain");
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -2549,13 +2665,14 @@ TEST_F(ServerTest, PostWithContentProviderWithoutLengthAbort) {
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
TEST_F(ServerTest, PutWithContentProviderWithGzip) {
|
||||
cli_.set_compress(true);
|
||||
auto res = cli_.Put("/put", 3,
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.os << "PUT";
|
||||
return true;
|
||||
},
|
||||
"text/plain");
|
||||
auto res = cli_.Put(
|
||||
"/put", 3,
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.os << "PUT";
|
||||
return true;
|
||||
},
|
||||
"text/plain");
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -2564,10 +2681,12 @@ TEST_F(ServerTest, PutWithContentProviderWithGzip) {
|
||||
|
||||
TEST_F(ServerTest, PostWithContentProviderWithGzipAbort) {
|
||||
cli_.set_compress(true);
|
||||
auto res = cli_.Post("/post", 42,
|
||||
[](size_t /*offset*/, size_t /*length*/,
|
||||
DataSink & /*sink*/) { return false; },
|
||||
"text/plain");
|
||||
auto res = cli_.Post(
|
||||
"/post", 42,
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink & /*sink*/) {
|
||||
return false;
|
||||
},
|
||||
"text/plain");
|
||||
|
||||
ASSERT_TRUE(!res);
|
||||
EXPECT_EQ(Error::Canceled, res.error());
|
||||
@@ -2575,14 +2694,15 @@ TEST_F(ServerTest, PostWithContentProviderWithGzipAbort) {
|
||||
|
||||
TEST_F(ServerTest, PutWithContentProviderWithoutLengthWithGzip) {
|
||||
cli_.set_compress(true);
|
||||
auto res = cli_.Put("/put",
|
||||
[](size_t /*offset*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.os << "PUT";
|
||||
sink.done();
|
||||
return true;
|
||||
},
|
||||
"text/plain");
|
||||
auto res = cli_.Put(
|
||||
"/put",
|
||||
[](size_t /*offset*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.os << "PUT";
|
||||
sink.done();
|
||||
return true;
|
||||
},
|
||||
"text/plain");
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
@@ -2902,8 +3022,9 @@ TEST_F(ServerTest, KeepAlive) {
|
||||
EXPECT_EQ("empty", res->body);
|
||||
EXPECT_EQ("close", res->get_header_value("Connection"));
|
||||
|
||||
res = cli_.Post("/empty", 0, [&](size_t, size_t, DataSink &) { return true; },
|
||||
"text/plain");
|
||||
res = cli_.Post(
|
||||
"/empty", 0, [&](size_t, size_t, DataSink &) { return true; },
|
||||
"text/plain");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||
@@ -3079,8 +3200,11 @@ static bool send_request(time_t read_timeout_sec, const std::string &req,
|
||||
auto error = Error::Success;
|
||||
|
||||
auto client_sock =
|
||||
detail::create_client_socket(HOST, PORT, false, nullptr,
|
||||
/*timeout_sec=*/5, 0, std::string(), error);
|
||||
detail::create_client_socket(HOST, PORT, AF_UNSPEC, false, nullptr,
|
||||
/*connection_timeout_sec=*/5, 0,
|
||||
/*read_timeout_sec=*/5, 0,
|
||||
/*write_timeout_sec=*/5, 0,
|
||||
std::string(), error);
|
||||
|
||||
if (client_sock == INVALID_SOCKET) { return false; }
|
||||
|
||||
@@ -3146,7 +3270,7 @@ static void test_raw_request(const std::string &req,
|
||||
// bug to reproduce, probably to force the server to process a request
|
||||
// without a trailing blank line.
|
||||
const time_t client_read_timeout_sec = 1;
|
||||
svr.set_read_timeout(client_read_timeout_sec + 1, 0);
|
||||
svr.set_read_timeout(std::chrono::seconds(client_read_timeout_sec + 1));
|
||||
bool listen_thread_ok = false;
|
||||
thread t = thread([&] { listen_thread_ok = svr.listen(HOST, PORT); });
|
||||
while (!svr.is_running()) {
|
||||
@@ -3281,7 +3405,8 @@ TEST(ServerStopTest, StopServerWithChunkedTransmission) {
|
||||
DataSink &sink) {
|
||||
char buffer[27];
|
||||
auto size = static_cast<size_t>(sprintf(buffer, "data:%ld\n\n", offset));
|
||||
sink.write(buffer, size);
|
||||
auto ret = sink.write(buffer, size);
|
||||
EXPECT_TRUE(ret);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
return true;
|
||||
});
|
||||
@@ -3446,7 +3571,7 @@ TEST(KeepAliveTest, ReadTimeout) {
|
||||
|
||||
Client cli("localhost", PORT);
|
||||
cli.set_keep_alive(true);
|
||||
cli.set_read_timeout(1);
|
||||
cli.set_read_timeout(std::chrono::seconds(1));
|
||||
|
||||
auto resa = cli.Get("/a");
|
||||
ASSERT_TRUE(!resa);
|
||||
@@ -3495,6 +3620,70 @@ TEST(ErrorHandlerWithContentProviderTest, ErrorHandler) {
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(GetWithParametersTest, GetWithParameters) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/", [&](const Request &req, Response &res) {
|
||||
auto text = req.get_param_value("hello");
|
||||
res.set_content(text, "text/plain");
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
Client cli("localhost", PORT);
|
||||
|
||||
Params params;
|
||||
params.emplace("hello", "world");
|
||||
auto res = cli.Get("/", params, Headers{});
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("world", res->body);
|
||||
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(GetWithParametersTest, GetWithParameters2) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/", [&](const Request &req, Response &res) {
|
||||
auto text = req.get_param_value("hello");
|
||||
res.set_content(text, "text/plain");
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
Client cli("localhost", PORT);
|
||||
|
||||
Params params;
|
||||
params.emplace("hello", "world");
|
||||
|
||||
std::string body;
|
||||
auto res = cli.Get("/", params, Headers{},
|
||||
[&](const char *data, size_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("world", body);
|
||||
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(KeepAliveTest, ReadTimeoutSSL) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
@@ -3520,7 +3709,7 @@ TEST(KeepAliveTest, ReadTimeoutSSL) {
|
||||
SSLClient cli("localhost", PORT);
|
||||
cli.enable_server_certificate_verification(false);
|
||||
cli.set_keep_alive(true);
|
||||
cli.set_read_timeout(1);
|
||||
cli.set_read_timeout(std::chrono::seconds(1));
|
||||
|
||||
auto resa = cli.Get("/a");
|
||||
ASSERT_TRUE(!resa);
|
||||
@@ -3745,6 +3934,7 @@ TEST(SSLClientTest, WildcardHostNameMatch) {
|
||||
|
||||
cli.set_ca_cert_path(CA_CERT_FILE);
|
||||
cli.enable_server_certificate_verification(true);
|
||||
cli.set_follow_location(true);
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(res);
|
||||
|
||||
Reference in New Issue
Block a user