Compare commits

..

13 Commits

Author SHA1 Message Date
yhirose cc14855ba0 Fix #661 2020-09-26 04:50:09 -04:00
yhirose 56c418745f Fixed conction close problem with HTTP 1.0 client 2020-09-25 20:58:49 -04:00
yhirose 4ce9911837 Add <sstream> 2020-09-25 18:17:32 -04:00
yhirose 559c407552 Adjusted SlowRequest test 2020-09-25 18:13:10 -04:00
yhirose a2f4e29a7b Add set_keep_alive_timeout 2020-09-25 17:57:33 -04:00
mi01 b8cf739d27 Add cctype header (#656) 2020-09-16 16:32:49 -04:00
yhirose aec2f9521d Updated documentation 2020-09-15 10:11:46 -04:00
yhirose 7b55ecdc59 Fixed #650 2020-09-12 16:11:14 -04:00
tmahring e9575bcb78 don't replace plus with space in headers (#649)
* don't replace plus with space in headers

* fixed forward handling with changed header parsing

* add test for boundaries containing plus chars
2020-09-10 20:27:01 -04:00
Jonas Minnberg 308aeb187b Undefined if2ip() also on Android since getifaddrs() does not exist. (#648)
Co-authored-by: Jonas Minnberg <jonas@minnberg.se>
2020-09-10 07:52:01 -04:00
Jodi the Tigger 05d18f2bc5 Fix -D build flags containing escaped quotes (#645)
Fixes #638

Done by removed unneeded quotes from cmake's add_compiler_definitions()
2020-09-08 21:35:50 -04:00
Ivan Fefer 3da4a0ac69 Add compression buffer size customization (#644)
* add compression buffer size customization and small brotli refactor

* allocat brotli buffer once

* add init to brotli decoder buffer
2020-09-08 12:18:14 -04:00
yhirose 9d12b3f20e Fixed warnings and refactoring 2020-09-03 20:33:30 -04:00
4 changed files with 194 additions and 148 deletions
+3 -3
View File
@@ -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
+7 -2
View File
@@ -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:
+121 -101
View File
@@ -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>
@@ -591,6 +593,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 +618,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;
@@ -1248,6 +1252,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 (std::tolower(a[i]) != std::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 +1453,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 +1486,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 +1739,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 +1749,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 +1759,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 +1776,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 +1932,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 +1966,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 +2233,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 +2284,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 +2322,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 +2384,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 +2438,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 +2484,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 +2852,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 &params) {
std::string query;
@@ -2871,7 +2876,7 @@ inline void parse_query_text(const std::string &s, Params &params) {
}
});
if(!key.empty()) {
if (!key.empty()) {
params.emplace(decode_url(key, true), decode_url(val, true));
}
});
@@ -2975,15 +2980,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];
}
@@ -3889,6 +3892,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 +3979,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") &&
@@ -4576,8 +4584,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);
@@ -4645,7 +4653,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 +4780,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(
@@ -4865,7 +4883,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));
}
@@ -5520,12 +5538,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 +5750,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,
+63 -42
View File
@@ -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) {
@@ -2201,7 +2190,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 +2200,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 +2212,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 +2325,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 +2841,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); });