mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d60dc8839 | |||
| b713a3a651 | |||
| 7e8db1dc68 | |||
| 316add860b | |||
| 79ce6f1745 | |||
| 09fdf4eacd | |||
| 143b2dd15a | |||
| e2c4e9d95c | |||
| d87082f04b | |||
| cc14855ba0 | |||
| 56c418745f | |||
| 4ce9911837 | |||
| 559c407552 | |||
| a2f4e29a7b | |||
| b8cf739d27 | |||
| aec2f9521d | |||
| 7b55ecdc59 | |||
| e9575bcb78 | |||
| 308aeb187b | |||
| 05d18f2bc5 | |||
| 3da4a0ac69 | |||
| 9d12b3f20e |
@@ -20,7 +20,7 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
- name: install brotli library on ubuntu
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: sudo apt-get install -y libbrotli-dev
|
||||
run: sudo apt update && sudo apt-get install -y libbrotli-dev
|
||||
- name: install brotli library on macOS
|
||||
if: matrix.os == 'macOS-latest'
|
||||
run: brew install brotli
|
||||
|
||||
+3
-3
@@ -236,9 +236,9 @@ target_link_libraries(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
|
||||
# Set the definitions to enable optional features
|
||||
target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:"CPPHTTPLIB_BROTLI_SUPPORT">
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_ZLIB}>:"CPPHTTPLIB_ZLIB_SUPPORT">
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:"CPPHTTPLIB_OPENSSL_SUPPORT">
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:CPPHTTPLIB_BROTLI_SUPPORT>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_ZLIB}>:CPPHTTPLIB_ZLIB_SUPPORT>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:CPPHTTPLIB_OPENSSL_SUPPORT>
|
||||
)
|
||||
|
||||
# Cmake's find_package search path is different based on the system
|
||||
|
||||
@@ -297,13 +297,18 @@ Please see [Server example](https://github.com/yhirose/cpp-httplib/blob/master/e
|
||||
|
||||
### Default thread pool support
|
||||
|
||||
`ThreadPool` is used as a **default** task queue, and the default thread count is 8, or `std::thread::hardware_concurrency()`. You can change it with `CPPHTTPLIB_THREAD_POOL_COUNT`.
|
||||
|
||||
`ThreadPool` is used as a default task queue, and the default thread count is set to value from `std::thread::hardware_concurrency()`.
|
||||
If you want to set the thread count at runtime, there is no convenient way... But here is how.
|
||||
|
||||
You can change the thread count by setting `CPPHTTPLIB_THREAD_POOL_COUNT`.
|
||||
```cpp
|
||||
svr.new_task_queue = [] { return new ThreadPool(12); };
|
||||
```
|
||||
|
||||
### Override the default thread pool with yours
|
||||
|
||||
You can supply your own thread pool implementation according to your need.
|
||||
|
||||
```cpp
|
||||
class YourThreadPoolTaskQueue : public TaskQueue {
|
||||
public:
|
||||
|
||||
@@ -16,10 +16,6 @@
|
||||
#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND
|
||||
#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND 0
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT
|
||||
#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5
|
||||
#endif
|
||||
@@ -80,6 +76,10 @@
|
||||
#define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u)
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ
|
||||
#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u)
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_THREAD_POOL_COUNT
|
||||
#define CPPHTTPLIB_THREAD_POOL_COUNT \
|
||||
((std::max)(8u, std::thread::hardware_concurrency() > 0 \
|
||||
@@ -176,6 +176,7 @@ using socket_t = int;
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#include <climits>
|
||||
#include <condition_variable>
|
||||
#include <errno.h>
|
||||
@@ -189,6 +190,7 @@ using socket_t = int;
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
#include <thread>
|
||||
@@ -577,7 +579,8 @@ public:
|
||||
Server &Options(const char *pattern, Handler handler);
|
||||
|
||||
bool set_base_dir(const char *dir, const char *mount_point = nullptr);
|
||||
bool set_mount_point(const char *mount_point, const char *dir);
|
||||
bool set_mount_point(const char *mount_point, const char *dir,
|
||||
Headers headers = Headers());
|
||||
bool remove_mount_point(const char *mount_point);
|
||||
void set_file_extension_and_mimetype_mapping(const char *ext,
|
||||
const char *mime);
|
||||
@@ -591,6 +594,7 @@ public:
|
||||
void set_socket_options(SocketOptions socket_options);
|
||||
|
||||
void set_keep_alive_max_count(size_t count);
|
||||
void set_keep_alive_timeout(time_t sec);
|
||||
void set_read_timeout(time_t sec, time_t usec = 0);
|
||||
void set_write_timeout(time_t sec, time_t usec = 0);
|
||||
void set_idle_interval(time_t sec, time_t usec = 0);
|
||||
@@ -615,6 +619,7 @@ protected:
|
||||
|
||||
std::atomic<socket_t> svr_sock_;
|
||||
size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
|
||||
time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND;
|
||||
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;
|
||||
@@ -659,9 +664,15 @@ private:
|
||||
ContentReceiver multipart_receiver);
|
||||
|
||||
virtual bool process_and_close_socket(socket_t sock);
|
||||
|
||||
struct MountPointEntry {
|
||||
std::string mount_point;
|
||||
std::string base_dir;
|
||||
Headers headers;
|
||||
};
|
||||
std::vector<MountPointEntry> base_dirs_;
|
||||
|
||||
std::atomic<bool> is_running_;
|
||||
std::vector<std::pair<std::string, std::string>> base_dirs_;
|
||||
std::map<std::string, std::string> file_extension_and_mimetype_map_;
|
||||
Handler file_request_handler_;
|
||||
Handlers get_handlers_;
|
||||
@@ -835,6 +846,10 @@ public:
|
||||
void set_proxy_digest_auth(const char *username, const char *password);
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
void enable_server_certificate_verification(bool enabled);
|
||||
#endif
|
||||
|
||||
void set_logger(Logger logger);
|
||||
|
||||
protected:
|
||||
@@ -855,6 +870,8 @@ protected:
|
||||
|
||||
Error get_last_error() const;
|
||||
|
||||
void copy_settings(const ClientImpl &rhs);
|
||||
|
||||
// Error state
|
||||
mutable Error error_ = Error::Success;
|
||||
|
||||
@@ -912,41 +929,11 @@ protected:
|
||||
std::string proxy_digest_auth_password_;
|
||||
#endif
|
||||
|
||||
Logger logger_;
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
bool server_certificate_verification_ = true;
|
||||
#endif
|
||||
|
||||
void copy_settings(const ClientImpl &rhs) {
|
||||
client_cert_path_ = rhs.client_cert_path_;
|
||||
client_key_path_ = rhs.client_key_path_;
|
||||
connection_timeout_sec_ = rhs.connection_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_;
|
||||
basic_auth_username_ = rhs.basic_auth_username_;
|
||||
basic_auth_password_ = rhs.basic_auth_password_;
|
||||
bearer_token_auth_token_ = rhs.bearer_token_auth_token_;
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
digest_auth_username_ = rhs.digest_auth_username_;
|
||||
digest_auth_password_ = rhs.digest_auth_password_;
|
||||
#endif
|
||||
keep_alive_ = rhs.keep_alive_;
|
||||
follow_location_ = rhs.follow_location_;
|
||||
tcp_nodelay_ = rhs.tcp_nodelay_;
|
||||
socket_options_ = rhs.socket_options_;
|
||||
compress_ = rhs.compress_;
|
||||
decompress_ = rhs.decompress_;
|
||||
interface_ = rhs.interface_;
|
||||
proxy_host_ = rhs.proxy_host_;
|
||||
proxy_port_ = rhs.proxy_port_;
|
||||
proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_;
|
||||
proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_;
|
||||
proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_;
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_;
|
||||
proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_;
|
||||
#endif
|
||||
logger_ = rhs.logger_;
|
||||
}
|
||||
Logger logger_;
|
||||
|
||||
private:
|
||||
socket_t create_client_socket() const;
|
||||
@@ -1092,16 +1079,18 @@ public:
|
||||
void set_proxy_digest_auth(const char *username, const char *password);
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
void enable_server_certificate_verification(bool enabled);
|
||||
#endif
|
||||
|
||||
void set_logger(Logger logger);
|
||||
|
||||
// SSL
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
Client &set_ca_cert_path(const char *ca_cert_file_path,
|
||||
const char *ca_cert_dir_path = nullptr);
|
||||
void set_ca_cert_path(const char *ca_cert_file_path,
|
||||
const char *ca_cert_dir_path = nullptr);
|
||||
|
||||
Client &set_ca_cert_store(X509_STORE *ca_cert_store);
|
||||
|
||||
Client &enable_server_certificate_verification(bool enabled);
|
||||
void set_ca_cert_store(X509_STORE *ca_cert_store);
|
||||
|
||||
long get_openssl_verify_result() const;
|
||||
|
||||
@@ -1159,8 +1148,6 @@ public:
|
||||
|
||||
void set_ca_cert_store(X509_STORE *ca_cert_store);
|
||||
|
||||
void enable_server_certificate_verification(bool enabled);
|
||||
|
||||
long get_openssl_verify_result() const;
|
||||
|
||||
SSL_CTX *ssl_context() const;
|
||||
@@ -1191,8 +1178,6 @@ private:
|
||||
|
||||
std::string ca_cert_file_path_;
|
||||
std::string ca_cert_dir_path_;
|
||||
X509_STORE *ca_cert_store_ = nullptr;
|
||||
bool server_certificate_verification_ = true;
|
||||
long verify_result_ = 0;
|
||||
|
||||
friend class ClientImpl;
|
||||
@@ -1248,6 +1233,14 @@ inline std::string from_i_to_hex(size_t n) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline bool start_with(const std::string &a, const std::string &b) {
|
||||
if (a.size() < b.size()) { return false; }
|
||||
for (size_t i = 0; i < b.size(); i++) {
|
||||
if (::tolower(a[i]) != ::tolower(b[i])) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline size_t to_utf8(int code, char *buff) {
|
||||
if (code < 0x0080) {
|
||||
buff[0] = (code & 0x7F);
|
||||
@@ -1441,34 +1434,32 @@ inline std::string file_extension(const std::string &path) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
inline std::pair<int, int> trim(const char *b, const char *e, int left,
|
||||
int right) {
|
||||
while (b + left < e && b[left] == ' ') {
|
||||
inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; }
|
||||
|
||||
inline std::pair<size_t, size_t> trim(const char *b, const char *e, size_t left,
|
||||
size_t right) {
|
||||
while (b + left < e && is_space_or_tab(b[left])) {
|
||||
left++;
|
||||
}
|
||||
while (right - 1 >= 0 && b[right - 1] == ' ') {
|
||||
while (right > 0 && is_space_or_tab(b[right - 1])) {
|
||||
right--;
|
||||
}
|
||||
return std::make_pair(left, right);
|
||||
}
|
||||
|
||||
inline void trim(std::string &s) {
|
||||
auto is_not_space = [](int ch) { return !std::isspace(ch); };
|
||||
s.erase(s.begin(), std::find_if(s.begin(), s.end(), is_not_space));
|
||||
s.erase(std::find_if(s.rbegin(), s.rend(), is_not_space).base(), s.end());
|
||||
inline std::string trim_copy(const std::string &s) {
|
||||
auto r = trim(s.data(), s.data() + s.size(), 0, s.size());
|
||||
return s.substr(r.first, r.second - r.first);
|
||||
}
|
||||
|
||||
|
||||
template <class Fn> void split(const char *b, const char *e, char d, Fn fn) {
|
||||
int i = 0;
|
||||
int beg = 0;
|
||||
size_t i = 0;
|
||||
size_t beg = 0;
|
||||
|
||||
while (e ? (b + i < e) : (b[i] != '\0')) {
|
||||
if (b[i] == d) {
|
||||
auto r = trim(b, e, beg, i);
|
||||
if (r.first < r.second) {
|
||||
fn(&b[r.first], &b[r.second]);
|
||||
}
|
||||
if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
|
||||
beg = i + 1;
|
||||
}
|
||||
i++;
|
||||
@@ -1476,9 +1467,7 @@ template <class Fn> void split(const char *b, const char *e, char d, Fn fn) {
|
||||
|
||||
if (i) {
|
||||
auto r = trim(b, e, beg, i);
|
||||
if (r.first < r.second) {
|
||||
fn(&b[r.first], &b[r.second]);
|
||||
}
|
||||
if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1731,7 +1720,7 @@ private:
|
||||
size_t position = 0;
|
||||
};
|
||||
|
||||
inline bool keep_alive(socket_t sock) {
|
||||
inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) {
|
||||
using namespace std::chrono;
|
||||
auto start = steady_clock::now();
|
||||
while (true) {
|
||||
@@ -1741,8 +1730,7 @@ inline bool keep_alive(socket_t sock) {
|
||||
} else if (val == 0) {
|
||||
auto current = steady_clock::now();
|
||||
auto duration = duration_cast<milliseconds>(current - start);
|
||||
auto timeout = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND * 1000 +
|
||||
CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND / 1000;
|
||||
auto timeout = keep_alive_timeout_sec * 1000;
|
||||
if (duration.count() > timeout) { return false; }
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
} else {
|
||||
@@ -1752,13 +1740,13 @@ inline bool keep_alive(socket_t sock) {
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool process_server_socket_core(socket_t sock,
|
||||
size_t keep_alive_max_count,
|
||||
T callback) {
|
||||
inline bool
|
||||
process_server_socket_core(socket_t sock, size_t keep_alive_max_count,
|
||||
time_t keep_alive_timeout_sec, T callback) {
|
||||
assert(keep_alive_max_count > 0);
|
||||
auto ret = false;
|
||||
auto count = keep_alive_max_count;
|
||||
while (count > 0 && keep_alive(sock)) {
|
||||
while (count > 0 && keep_alive(sock, keep_alive_timeout_sec)) {
|
||||
auto close_connection = count == 1;
|
||||
auto connection_closed = false;
|
||||
ret = callback(close_connection, connection_closed);
|
||||
@@ -1769,14 +1757,14 @@ inline bool process_server_socket_core(socket_t sock,
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool process_server_socket(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) {
|
||||
inline bool
|
||||
process_server_socket(socket_t sock, size_t keep_alive_max_count,
|
||||
time_t keep_alive_timeout_sec, time_t read_timeout_sec,
|
||||
time_t read_timeout_usec, time_t write_timeout_sec,
|
||||
time_t write_timeout_usec, T callback) {
|
||||
return process_server_socket_core(
|
||||
sock, keep_alive_max_count,
|
||||
[&](bool close_connection, bool connection_closed) {
|
||||
sock, keep_alive_max_count, keep_alive_timeout_sec,
|
||||
[&](bool close_connection, bool &connection_closed) {
|
||||
SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
|
||||
write_timeout_sec, write_timeout_usec);
|
||||
return callback(strm, close_connection, connection_closed);
|
||||
@@ -1925,7 +1913,11 @@ inline bool bind_ip_address(socket_t sock, const char *host) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
#if !defined _WIN32 && !defined ANDROID
|
||||
#define USE_IF2IP
|
||||
#endif
|
||||
|
||||
#ifdef USE_IF2IP
|
||||
inline std::string if2ip(const std::string &ifn) {
|
||||
struct ifaddrs *ifap;
|
||||
getifaddrs(&ifap);
|
||||
@@ -1955,7 +1947,7 @@ inline socket_t create_client_socket(const char *host, int port,
|
||||
host, port, 0, tcp_nodelay, socket_options,
|
||||
[&](socket_t sock, struct addrinfo &ai) -> bool {
|
||||
if (!intf.empty()) {
|
||||
#ifndef _WIN32
|
||||
#ifdef USE_IF2IP
|
||||
auto ip = if2ip(intf);
|
||||
if (ip.empty()) { ip = intf; }
|
||||
if (!bind_ip_address(sock, ip.c_str())) {
|
||||
@@ -2222,7 +2214,7 @@ public:
|
||||
|
||||
int ret = Z_OK;
|
||||
|
||||
std::array<char, 16384> buff{};
|
||||
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
|
||||
do {
|
||||
strm_.avail_out = buff.size();
|
||||
strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
|
||||
@@ -2273,7 +2265,7 @@ public:
|
||||
strm_.avail_in = static_cast<decltype(strm_.avail_in)>(data_length);
|
||||
strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
|
||||
|
||||
std::array<char, 16384> buff{};
|
||||
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
|
||||
while (strm_.avail_in > 0) {
|
||||
strm_.avail_out = buff.size();
|
||||
strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
|
||||
@@ -2311,7 +2303,7 @@ public:
|
||||
|
||||
bool compress(const char *data, size_t data_length, bool last,
|
||||
Callback callback) override {
|
||||
std::array<uint8_t, 16384> buff{};
|
||||
std::array<uint8_t, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
|
||||
|
||||
auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS;
|
||||
auto available_in = data_length;
|
||||
@@ -2373,20 +2365,18 @@ public:
|
||||
|
||||
decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;
|
||||
|
||||
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
|
||||
while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
|
||||
char output[1024];
|
||||
char *next_out = output;
|
||||
size_t avail_out = sizeof(output);
|
||||
char *next_out = buff.data();
|
||||
size_t avail_out = buff.size();
|
||||
|
||||
decoder_r = BrotliDecoderDecompressStream(
|
||||
decoder_s, &avail_in, &next_in, &avail_out,
|
||||
reinterpret_cast<unsigned char **>(&next_out), &total_out);
|
||||
reinterpret_cast<uint8_t **>(&next_out), &total_out);
|
||||
|
||||
if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; }
|
||||
|
||||
if (!callback((const char *)output, sizeof(output) - avail_out)) {
|
||||
return false;
|
||||
}
|
||||
if (!callback(buff.data(), buff.size() - avail_out)) { return false; }
|
||||
}
|
||||
|
||||
return decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
|
||||
@@ -2429,26 +2419,34 @@ inline uint64_t get_header_value<uint64_t>(const Headers &headers,
|
||||
return def;
|
||||
}
|
||||
|
||||
inline void parse_header(const char *beg, const char *end, Headers &headers) {
|
||||
template <typename T>
|
||||
inline bool parse_header(const char *beg, const char *end, T fn) {
|
||||
// Skip trailing spaces and tabs.
|
||||
while (beg < end && is_space_or_tab(end[-1])) {
|
||||
end--;
|
||||
}
|
||||
|
||||
auto p = beg;
|
||||
while (p < end && *p != ':') {
|
||||
p++;
|
||||
}
|
||||
if (p < end) {
|
||||
auto key_end = p;
|
||||
p++; // skip ':'
|
||||
while (p < end && (*p == ' ' || *p == '\t')) {
|
||||
p++;
|
||||
}
|
||||
if (p < end) {
|
||||
auto val_begin = p;
|
||||
while (p < end) {
|
||||
p++;
|
||||
}
|
||||
headers.emplace(std::string(beg, key_end),
|
||||
decode_url(std::string(val_begin, end), true));
|
||||
}
|
||||
|
||||
if (p == end) { return false; }
|
||||
|
||||
auto key_end = p;
|
||||
|
||||
if (*p++ != ':') { return false; }
|
||||
|
||||
while (p < end && is_space_or_tab(*p)) {
|
||||
p++;
|
||||
}
|
||||
|
||||
if (p < end) {
|
||||
fn(std::string(beg, key_end), decode_url(std::string(p, end), false));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool read_headers(Stream &strm, Headers &headers) {
|
||||
@@ -2467,13 +2465,13 @@ inline bool read_headers(Stream &strm, Headers &headers) {
|
||||
continue; // Skip invalid line.
|
||||
}
|
||||
|
||||
// Skip trailing spaces and tabs.
|
||||
// Exclude CRLF
|
||||
auto end = line_reader.ptr() + line_reader.size() - 2;
|
||||
while (line_reader.ptr() < end && (end[-1] == ' ' || end[-1] == '\t')) {
|
||||
end--;
|
||||
}
|
||||
|
||||
parse_header(line_reader.ptr(), end, headers);
|
||||
parse_header(line_reader.ptr(), end,
|
||||
[&](std::string &&key, std::string &&val) {
|
||||
headers.emplace(std::move(key), std::move(val));
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -2835,18 +2833,6 @@ inline bool redirect(T &cli, const Request &req, Response &res,
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline bool contains_header(const std::string &header, const std::string &name) {
|
||||
if (header.length() >= name.length()) {
|
||||
for (int i = 0; i < name.length(); ++i) {
|
||||
if (std::tolower(header[i]) != std::tolower(name[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline std::string params_to_query_str(const Params ¶ms) {
|
||||
std::string query;
|
||||
|
||||
@@ -2871,7 +2857,7 @@ inline void parse_query_text(const std::string &s, Params ¶ms) {
|
||||
}
|
||||
});
|
||||
|
||||
if(!key.empty()) {
|
||||
if (!key.empty()) {
|
||||
params.emplace(decode_url(key, true), decode_url(val, true));
|
||||
}
|
||||
});
|
||||
@@ -2975,15 +2961,13 @@ public:
|
||||
break;
|
||||
}
|
||||
|
||||
auto header = buf_.substr(0, pos);
|
||||
{
|
||||
static const std::string header_name = "content-type:";
|
||||
const auto header = buf_.substr(0, pos);
|
||||
if (start_with(header, header_name)) {
|
||||
file_.content_type = trim_copy(header.substr(header_name.size()));
|
||||
} else {
|
||||
std::smatch m;
|
||||
const std::string header_name = "content-type:";
|
||||
if (contains_header(header, header_name)) {
|
||||
header.erase(header.begin(), header.begin() + header_name.size());
|
||||
trim(header);
|
||||
file_.content_type = header;
|
||||
} else if (std::regex_match(header, m, re_content_disposition)) {
|
||||
if (std::regex_match(header, m, re_content_disposition)) {
|
||||
file_.name = m[1];
|
||||
file_.filename = m[2];
|
||||
}
|
||||
@@ -3838,11 +3822,12 @@ inline bool Server::set_base_dir(const char *dir, const char *mount_point) {
|
||||
return set_mount_point(mount_point, dir);
|
||||
}
|
||||
|
||||
inline bool Server::set_mount_point(const char *mount_point, const char *dir) {
|
||||
inline bool Server::set_mount_point(const char *mount_point, const char *dir,
|
||||
Headers headers) {
|
||||
if (detail::is_dir(dir)) {
|
||||
std::string mnt = mount_point ? mount_point : "/";
|
||||
if (!mnt.empty() && mnt[0] == '/') {
|
||||
base_dirs_.emplace_back(mnt, dir);
|
||||
base_dirs_.push_back({mnt, dir, std::move(headers)});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -3851,7 +3836,7 @@ inline bool Server::set_mount_point(const char *mount_point, const char *dir) {
|
||||
|
||||
inline bool Server::remove_mount_point(const char *mount_point) {
|
||||
for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) {
|
||||
if (it->first == mount_point) {
|
||||
if (it->mount_point == mount_point) {
|
||||
base_dirs_.erase(it);
|
||||
return true;
|
||||
}
|
||||
@@ -3889,6 +3874,10 @@ inline void Server::set_keep_alive_max_count(size_t count) {
|
||||
keep_alive_max_count_ = count;
|
||||
}
|
||||
|
||||
inline void Server::set_keep_alive_timeout(time_t sec) {
|
||||
keep_alive_timeout_sec_ = sec;
|
||||
}
|
||||
|
||||
inline void Server::set_read_timeout(time_t sec, time_t usec) {
|
||||
read_timeout_sec_ = sec;
|
||||
read_timeout_usec_ = usec;
|
||||
@@ -3972,10 +3961,11 @@ inline bool Server::write_response(Stream &strm, bool close_connection,
|
||||
// Headers
|
||||
if (close_connection || req.get_header_value("Connection") == "close") {
|
||||
res.set_header("Connection", "close");
|
||||
}
|
||||
|
||||
if (!close_connection && req.get_header_value("Connection") == "Keep-Alive") {
|
||||
res.set_header("Connection", "Keep-Alive");
|
||||
} else {
|
||||
std::stringstream ss;
|
||||
ss << "timeout=" << keep_alive_timeout_sec_
|
||||
<< ", max=" << keep_alive_max_count_;
|
||||
res.set_header("Keep-Alive", ss.str());
|
||||
}
|
||||
|
||||
if (!res.has_header("Content-Type") &&
|
||||
@@ -4268,15 +4258,12 @@ inline bool Server::read_content_core(Stream &strm, Request &req, Response &res,
|
||||
|
||||
inline bool Server::handle_file_request(Request &req, Response &res,
|
||||
bool head) {
|
||||
for (const auto &kv : base_dirs_) {
|
||||
const auto &mount_point = kv.first;
|
||||
const auto &base_dir = kv.second;
|
||||
|
||||
for (const auto &entry : base_dirs_) {
|
||||
// Prefix match
|
||||
if (!req.path.compare(0, mount_point.size(), mount_point)) {
|
||||
std::string sub_path = "/" + req.path.substr(mount_point.size());
|
||||
if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) {
|
||||
std::string sub_path = "/" + req.path.substr(entry.mount_point.size());
|
||||
if (detail::is_valid_path(sub_path)) {
|
||||
auto path = base_dir + sub_path;
|
||||
auto path = entry.base_dir + sub_path;
|
||||
if (path.back() == '/') { path += "index.html"; }
|
||||
|
||||
if (detail::is_file(path)) {
|
||||
@@ -4284,6 +4271,9 @@ inline bool Server::handle_file_request(Request &req, Response &res,
|
||||
auto type =
|
||||
detail::find_content_type(path, file_extension_and_mimetype_map_);
|
||||
if (type) { res.set_header("Content-Type", type); }
|
||||
for (const auto& kv : entry.headers) {
|
||||
res.set_header(kv.first.c_str(), kv.second);
|
||||
}
|
||||
res.status = 200;
|
||||
if (!head && file_request_handler_) {
|
||||
file_request_handler_(req, res);
|
||||
@@ -4576,8 +4566,8 @@ inline bool Server::is_valid() const { return true; }
|
||||
|
||||
inline bool Server::process_and_close_socket(socket_t sock) {
|
||||
auto ret = detail::process_server_socket(
|
||||
sock, keep_alive_max_count_, read_timeout_sec_, read_timeout_usec_,
|
||||
write_timeout_sec_, write_timeout_usec_,
|
||||
sock, keep_alive_max_count_, keep_alive_timeout_sec_, read_timeout_sec_,
|
||||
read_timeout_usec_, write_timeout_sec_, write_timeout_usec_,
|
||||
[this](Stream &strm, bool close_connection, bool &connection_closed) {
|
||||
return process_request(strm, close_connection, connection_closed,
|
||||
nullptr);
|
||||
@@ -4608,6 +4598,43 @@ inline bool ClientImpl::is_valid() const { return true; }
|
||||
|
||||
inline Error ClientImpl::get_last_error() const { return error_; }
|
||||
|
||||
inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
|
||||
client_cert_path_ = rhs.client_cert_path_;
|
||||
client_key_path_ = rhs.client_key_path_;
|
||||
connection_timeout_sec_ = rhs.connection_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_;
|
||||
basic_auth_username_ = rhs.basic_auth_username_;
|
||||
basic_auth_password_ = rhs.basic_auth_password_;
|
||||
bearer_token_auth_token_ = rhs.bearer_token_auth_token_;
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
digest_auth_username_ = rhs.digest_auth_username_;
|
||||
digest_auth_password_ = rhs.digest_auth_password_;
|
||||
#endif
|
||||
keep_alive_ = rhs.keep_alive_;
|
||||
follow_location_ = rhs.follow_location_;
|
||||
tcp_nodelay_ = rhs.tcp_nodelay_;
|
||||
socket_options_ = rhs.socket_options_;
|
||||
compress_ = rhs.compress_;
|
||||
decompress_ = rhs.decompress_;
|
||||
interface_ = rhs.interface_;
|
||||
proxy_host_ = rhs.proxy_host_;
|
||||
proxy_port_ = rhs.proxy_port_;
|
||||
proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_;
|
||||
proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_;
|
||||
proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_;
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_;
|
||||
proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_;
|
||||
#endif
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
server_certificate_verification_ = rhs.server_certificate_verification_;
|
||||
#endif
|
||||
logger_ = rhs.logger_;
|
||||
}
|
||||
|
||||
inline socket_t ClientImpl::create_client_socket() const {
|
||||
if (!proxy_host_.empty() && proxy_port_ != -1) {
|
||||
return detail::create_client_socket(
|
||||
@@ -4645,7 +4672,17 @@ inline bool ClientImpl::read_response_line(Stream &strm, Response &res) {
|
||||
const static std::regex re("(HTTP/1\\.[01]) (\\d+) (.*?)\r\n");
|
||||
|
||||
std::cmatch m;
|
||||
if (std::regex_match(line_reader.ptr(), m, re)) {
|
||||
if (!std::regex_match(line_reader.ptr(), m, re)) { return false; }
|
||||
res.version = std::string(m[1]);
|
||||
res.status = std::stoi(std::string(m[2]));
|
||||
res.reason = std::string(m[3]);
|
||||
|
||||
// Ignore '100 Continue'
|
||||
while (res.status == 100) {
|
||||
if (!line_reader.getline()) { return false; } // CRLF
|
||||
if (!line_reader.getline()) { return false; } // next response line
|
||||
|
||||
if (!std::regex_match(line_reader.ptr(), m, re)) { return false; }
|
||||
res.version = std::string(m[1]);
|
||||
res.status = std::stoi(std::string(m[2]));
|
||||
res.reason = std::string(m[3]);
|
||||
@@ -4762,7 +4799,7 @@ inline bool ClientImpl::redirect(const Request &req, Response &res) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto location = res.get_header_value("location");
|
||||
auto location = detail::decode_url(res.get_header_value("location"), true);
|
||||
if (location.empty()) { return false; }
|
||||
|
||||
const static std::regex re(
|
||||
@@ -4852,7 +4889,10 @@ inline bool ClientImpl::write_request(Stream &strm, const Request &req,
|
||||
auto length = std::to_string(req.content_length);
|
||||
headers.emplace("Content-Length", length);
|
||||
} else {
|
||||
headers.emplace("Content-Length", "0");
|
||||
if (req.method == "POST" || req.method == "PUT" ||
|
||||
req.method == "PATCH") {
|
||||
headers.emplace("Content-Length", "0");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!req.has_header("Content-Type")) {
|
||||
@@ -4865,7 +4905,7 @@ inline bool ClientImpl::write_request(Stream &strm, const Request &req,
|
||||
}
|
||||
}
|
||||
|
||||
if (!basic_auth_username_.empty() && !basic_auth_password_.empty()) {
|
||||
if (!basic_auth_password_.empty()) {
|
||||
headers.insert(make_basic_authentication_header(
|
||||
basic_auth_username_, basic_auth_password_, false));
|
||||
}
|
||||
@@ -5471,6 +5511,12 @@ inline void ClientImpl::set_proxy_digest_auth(const char *username,
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
inline void ClientImpl::enable_server_certificate_verification(bool enabled) {
|
||||
server_certificate_verification_ = enabled;
|
||||
}
|
||||
#endif
|
||||
|
||||
inline void ClientImpl::set_logger(Logger logger) {
|
||||
logger_ = std::move(logger);
|
||||
}
|
||||
@@ -5520,12 +5566,13 @@ inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl,
|
||||
template <typename T>
|
||||
inline bool
|
||||
process_server_socket_ssl(SSL *ssl, socket_t sock, size_t keep_alive_max_count,
|
||||
time_t keep_alive_timeout_sec,
|
||||
time_t read_timeout_sec, time_t read_timeout_usec,
|
||||
time_t write_timeout_sec, time_t write_timeout_usec,
|
||||
T callback) {
|
||||
return process_server_socket_core(
|
||||
sock, keep_alive_max_count,
|
||||
[&](bool close_connection, bool connection_closed) {
|
||||
sock, keep_alive_max_count, keep_alive_timeout_sec,
|
||||
[&](bool close_connection, bool &connection_closed) {
|
||||
SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
|
||||
write_timeout_sec, write_timeout_usec);
|
||||
return callback(strm, close_connection, connection_closed);
|
||||
@@ -5731,8 +5778,9 @@ inline bool SSLServer::process_and_close_socket(socket_t sock) {
|
||||
|
||||
if (ssl) {
|
||||
auto ret = detail::process_server_socket_ssl(
|
||||
ssl, sock, keep_alive_max_count_, read_timeout_sec_, read_timeout_usec_,
|
||||
write_timeout_sec_, write_timeout_usec_,
|
||||
ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
|
||||
read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
|
||||
write_timeout_usec_,
|
||||
[this, ssl](Stream &strm, bool close_connection,
|
||||
bool &connection_closed) {
|
||||
return process_request(strm, close_connection, connection_closed,
|
||||
@@ -5806,11 +5854,16 @@ inline void SSLClient::set_ca_cert_path(const char *ca_cert_file_path,
|
||||
}
|
||||
|
||||
inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) {
|
||||
if (ca_cert_store) { ca_cert_store_ = ca_cert_store; }
|
||||
}
|
||||
|
||||
inline void SSLClient::enable_server_certificate_verification(bool enabled) {
|
||||
server_certificate_verification_ = enabled;
|
||||
if (ca_cert_store) {
|
||||
if (ctx_) {
|
||||
if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) {
|
||||
// Free memory allocated for old cert and use new store `ca_cert_store`
|
||||
SSL_CTX_set_cert_store(ctx_, ca_cert_store);
|
||||
}
|
||||
} else {
|
||||
X509_STORE_free(ca_cert_store);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline long SSLClient::get_openssl_verify_result() const {
|
||||
@@ -5888,10 +5941,6 @@ inline bool SSLClient::load_certs() {
|
||||
ca_cert_dir_path_.c_str())) {
|
||||
ret = false;
|
||||
}
|
||||
} else if (ca_cert_store_ != nullptr) {
|
||||
if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store_) {
|
||||
SSL_CTX_set_cert_store(ctx_, ca_cert_store_);
|
||||
}
|
||||
} else {
|
||||
#ifdef _WIN32
|
||||
detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_));
|
||||
@@ -6393,31 +6442,27 @@ inline void Client::set_proxy_digest_auth(const char *username,
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
inline void Client::enable_server_certificate_verification(bool enabled) {
|
||||
cli_->enable_server_certificate_verification(enabled);
|
||||
}
|
||||
#endif
|
||||
|
||||
inline void Client::set_logger(Logger logger) { cli_->set_logger(logger); }
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
inline Client &Client::set_ca_cert_path(const char *ca_cert_file_path,
|
||||
const char *ca_cert_dir_path) {
|
||||
inline void Client::set_ca_cert_path(const char *ca_cert_file_path,
|
||||
const char *ca_cert_dir_path) {
|
||||
if (is_ssl_) {
|
||||
static_cast<SSLClient &>(*cli_).set_ca_cert_path(ca_cert_file_path,
|
||||
ca_cert_dir_path);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Client &Client::set_ca_cert_store(X509_STORE *ca_cert_store) {
|
||||
inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) {
|
||||
if (is_ssl_) {
|
||||
static_cast<SSLClient &>(*cli_).set_ca_cert_store(ca_cert_store);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Client &Client::enable_server_certificate_verification(bool enabled) {
|
||||
if (is_ssl_) {
|
||||
static_cast<SSLClient &>(*cli_).enable_server_certificate_verification(
|
||||
enabled);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline long Client::get_openssl_verify_result() const {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
|
||||
#CXX = clang++
|
||||
CXXFLAGS = -ggdb -O0 -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I.. -I. -Wall -Wextra -Wtype-limits -Wconversion
|
||||
CXXFLAGS = -ggdb -O0 -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I.. -I. -Wall -Wextra -Wtype-limits -Wconversion #-fsanitize=address
|
||||
|
||||
OPENSSL_DIR = /usr/local/opt/openssl@1.1
|
||||
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
|
||||
|
||||
+126
-43
@@ -43,22 +43,11 @@ TEST(StartupTest, WSAStartup) {
|
||||
ASSERT_EQ(0, ret);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(TrimTests, TrimStringTests) {
|
||||
{
|
||||
std::string s = "abc";
|
||||
detail::trim(s);
|
||||
EXPECT_EQ("abc", s);
|
||||
}
|
||||
{
|
||||
std::string s = " abc ";
|
||||
detail::trim(s);
|
||||
EXPECT_EQ("abc", s);
|
||||
}
|
||||
{
|
||||
std::string s = "";
|
||||
detail::trim(s);
|
||||
EXPECT_TRUE( s.empty() );
|
||||
}
|
||||
EXPECT_EQ("abc", detail::trim_copy("abc"));
|
||||
EXPECT_EQ("abc", detail::trim_copy(" abc "));
|
||||
EXPECT_TRUE(detail::trim_copy("").empty());
|
||||
}
|
||||
|
||||
TEST(SplitTest, ParseQueryString) {
|
||||
@@ -100,7 +89,6 @@ TEST(SplitTest, ParseInvalidQueryTests) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(ParseQueryTest, ParseQueryString) {
|
||||
string s = "key1=val1&key2=val2&key3=val3";
|
||||
Params dic;
|
||||
@@ -1345,8 +1333,11 @@ protected:
|
||||
|
||||
virtual void TearDown() {
|
||||
svr_.stop();
|
||||
for (auto &t : request_threads_) {
|
||||
t.join();
|
||||
if (!request_threads_.empty()) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
for (auto &t : request_threads_) {
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
t_.join();
|
||||
}
|
||||
@@ -1827,8 +1818,7 @@ TEST_F(ServerTest, MultipartFormData) {
|
||||
{"file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain"},
|
||||
{"file2", "{\n \"world\", true\n}\n", "world.json", "application/json"},
|
||||
{"file3", "", "", "application/octet-stream"},
|
||||
{"file4", "", "", " application/json tmp-string "}
|
||||
};
|
||||
{"file4", "", "", " application/json tmp-string "}};
|
||||
|
||||
auto res = cli_.Post("/multipart", items);
|
||||
|
||||
@@ -2064,7 +2054,6 @@ TEST_F(ServerTest, SlowRequest) {
|
||||
std::thread([=]() { auto res = cli_.Get("/slow"); }));
|
||||
request_threads_.push_back(
|
||||
std::thread([=]() { auto res = cli_.Get("/slow"); }));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, SlowPost) {
|
||||
@@ -2081,9 +2070,14 @@ TEST_F(ServerTest, SlowPost) {
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, SlowPostFail) {
|
||||
char buffer[64 * 1024];
|
||||
memset(buffer, 0x42, sizeof(buffer));
|
||||
|
||||
cli_.set_write_timeout(0, 0);
|
||||
res = cli_.Post(
|
||||
auto res = cli_.Post(
|
||||
"/slowpost", 64 * 1024 * 1024,
|
||||
[&](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
sink.write(buffer, sizeof(buffer));
|
||||
@@ -2201,7 +2195,6 @@ TEST_F(ServerTest, GetStreamedChunkedWithGzip2) {
|
||||
EXPECT_EQ(std::string("123456789"), res->body);
|
||||
}
|
||||
|
||||
|
||||
TEST(GzipDecompressor, ChunkedDecompression) {
|
||||
std::string data;
|
||||
for (size_t i = 0; i < 32 * 1024; ++i) {
|
||||
@@ -2212,10 +2205,8 @@ TEST(GzipDecompressor, ChunkedDecompression) {
|
||||
{
|
||||
httplib::detail::gzip_compressor compressor;
|
||||
bool result = compressor.compress(
|
||||
data.data(),
|
||||
data.size(),
|
||||
/*last=*/true,
|
||||
[&] (const char* data, size_t size) {
|
||||
data.data(), data.size(),
|
||||
/*last=*/true, [&](const char *data, size_t size) {
|
||||
compressed_data.insert(compressed_data.size(), data, size);
|
||||
return true;
|
||||
});
|
||||
@@ -2226,15 +2217,16 @@ TEST(GzipDecompressor, ChunkedDecompression) {
|
||||
{
|
||||
httplib::detail::gzip_decompressor decompressor;
|
||||
|
||||
// Chunk size is chosen specificaly to have a decompressed chunk size equal to 16384 bytes
|
||||
// 16384 bytes is the size of decompressor output buffer
|
||||
// Chunk size is chosen specificaly to have a decompressed chunk size equal
|
||||
// to 16384 bytes 16384 bytes is the size of decompressor output buffer
|
||||
size_t chunk_size = 130;
|
||||
for (size_t chunk_begin = 0; chunk_begin < compressed_data.size(); chunk_begin += chunk_size) {
|
||||
size_t current_chunk_size = std::min(compressed_data.size() - chunk_begin, chunk_size);
|
||||
for (size_t chunk_begin = 0; chunk_begin < compressed_data.size();
|
||||
chunk_begin += chunk_size) {
|
||||
size_t current_chunk_size =
|
||||
std::min(compressed_data.size() - chunk_begin, chunk_size);
|
||||
bool result = decompressor.decompress(
|
||||
compressed_data.data() + chunk_begin,
|
||||
current_chunk_size,
|
||||
[&] (const char* data, size_t size) {
|
||||
compressed_data.data() + chunk_begin, current_chunk_size,
|
||||
[&](const char *data, size_t size) {
|
||||
decompressed_data.insert(decompressed_data.size(), data, size);
|
||||
return true;
|
||||
});
|
||||
@@ -2338,6 +2330,41 @@ TEST_F(ServerTest, PostMulitpartFilsContentReceiver) {
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PostMulitpartPlusBoundary) {
|
||||
MultipartFormDataItems items = {
|
||||
{"text1", "text default", "", ""},
|
||||
{"text2", "aωb", "", ""},
|
||||
{"file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain"},
|
||||
{"file2", "{\n \"world\", true\n}\n", "world.json", "application/json"},
|
||||
{"file3", "", "", "application/octet-stream"},
|
||||
};
|
||||
|
||||
auto boundary = std::string("+++++");
|
||||
|
||||
std::string body;
|
||||
|
||||
for (const auto &item : items) {
|
||||
body += "--" + boundary + "\r\n";
|
||||
body += "Content-Disposition: form-data; name=\"" + item.name + "\"";
|
||||
if (!item.filename.empty()) {
|
||||
body += "; filename=\"" + item.filename + "\"";
|
||||
}
|
||||
body += "\r\n";
|
||||
if (!item.content_type.empty()) {
|
||||
body += "Content-Type: " + item.content_type + "\r\n";
|
||||
}
|
||||
body += "\r\n";
|
||||
body += item.content + "\r\n";
|
||||
}
|
||||
body += "--" + boundary + "--\r\n";
|
||||
|
||||
std::string content_type = "multipart/form-data; boundary=" + boundary;
|
||||
auto res = cli_.Post("/content_receiver", body, content_type.c_str());
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PostContentReceiverGzip) {
|
||||
cli_.set_compress(true);
|
||||
auto res = cli_.Post("/content_receiver", "content", "text/plain");
|
||||
@@ -2819,15 +2846,14 @@ TEST(StreamingTest, NoContentLengthStreaming) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/stream", [](const Request & /*req*/, Response &res) {
|
||||
res.set_content_provider(
|
||||
"text/plain", [](size_t offset, DataSink &sink) {
|
||||
if (offset < 6) {
|
||||
sink.os << (offset < 3 ? "a" : "b");
|
||||
} else {
|
||||
sink.done();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
res.set_content_provider("text/plain", [](size_t offset, DataSink &sink) {
|
||||
if (offset < 6) {
|
||||
sink.os << (offset < 3 ? "a" : "b");
|
||||
} else {
|
||||
sink.done();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
@@ -2965,6 +2991,50 @@ TEST(KeepAliveTest, ReadTimeout) {
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(KeepAliveTest, ReadTimeoutSSL) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
ASSERT_TRUE(svr.is_valid());
|
||||
|
||||
svr.Get("/a", [&](const Request & /*req*/, Response &res) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
res.set_content("a", "text/plain");
|
||||
});
|
||||
|
||||
svr.Get("/b", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("b", "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));
|
||||
}
|
||||
|
||||
// Give GET time to get a few messages.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
SSLClient cli("localhost", PORT);
|
||||
cli.enable_server_certificate_verification(false);
|
||||
cli.set_keep_alive(true);
|
||||
cli.set_read_timeout(1);
|
||||
|
||||
auto resa = cli.Get("/a");
|
||||
ASSERT_TRUE(!resa);
|
||||
EXPECT_EQ(Error::Read, resa.error());
|
||||
|
||||
auto resb = cli.Get("/b");
|
||||
ASSERT_TRUE(resb);
|
||||
EXPECT_EQ(200, resb->status);
|
||||
EXPECT_EQ("b", resb->body);
|
||||
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
#endif
|
||||
|
||||
class ServerTestWithAI_PASSIVE : public ::testing::Test {
|
||||
protected:
|
||||
ServerTestWithAI_PASSIVE()
|
||||
@@ -3099,6 +3169,19 @@ TEST_F(PayloadMaxLengthTest, ExceedLimit) {
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(SSLClientTest, UpdateCAStore) {
|
||||
httplib::SSLClient httplib_client("www.google.com");
|
||||
auto ca_store_1 = X509_STORE_new();
|
||||
X509_STORE_load_locations(ca_store_1, "/etc/ssl/certs/ca-certificates.crt",
|
||||
nullptr);
|
||||
httplib_client.set_ca_cert_store(ca_store_1);
|
||||
|
||||
auto ca_store_2 = X509_STORE_new();
|
||||
X509_STORE_load_locations(ca_store_2, "/etc/ssl/certs/ca-certificates.crt",
|
||||
nullptr);
|
||||
httplib_client.set_ca_cert_store(ca_store_2);
|
||||
}
|
||||
|
||||
TEST(SSLClientTest, ServerNameIndication) {
|
||||
SSLClient cli("httpbin.org", 443);
|
||||
auto res = cli.Get("/get");
|
||||
|
||||
Reference in New Issue
Block a user