mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cc14855ba0 | |||
| 56c418745f | |||
| 4ce9911837 | |||
| 559c407552 | |||
| a2f4e29a7b | |||
| b8cf739d27 | |||
| aec2f9521d | |||
| 7b55ecdc59 | |||
| e9575bcb78 | |||
| 308aeb187b | |||
| 05d18f2bc5 |
+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
|
||||
@@ -180,6 +176,7 @@ using socket_t = int;
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#include <climits>
|
||||
#include <condition_variable>
|
||||
#include <errno.h>
|
||||
@@ -193,6 +190,7 @@ using socket_t = int;
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
#include <thread>
|
||||
@@ -595,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);
|
||||
@@ -619,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;
|
||||
@@ -1739,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) {
|
||||
@@ -1749,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 {
|
||||
@@ -1760,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);
|
||||
@@ -1777,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);
|
||||
@@ -1933,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);
|
||||
@@ -1963,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())) {
|
||||
@@ -2392,9 +2395,7 @@ public:
|
||||
|
||||
if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; }
|
||||
|
||||
if (!callback(buff.data(), buff.size() - avail_out)) {
|
||||
return false;
|
||||
}
|
||||
if (!callback(buff.data(), buff.size() - avail_out)) { return false; }
|
||||
}
|
||||
|
||||
return decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
|
||||
@@ -2460,7 +2461,7 @@ inline bool parse_header(const char *beg, const char *end, T fn) {
|
||||
}
|
||||
|
||||
if (p < end) {
|
||||
fn(std::string(beg, key_end), decode_url(std::string(p, end), true));
|
||||
fn(std::string(beg, key_end), decode_url(std::string(p, end), false));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3891,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;
|
||||
@@ -3974,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") &&
|
||||
@@ -4578,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);
|
||||
@@ -4647,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]);
|
||||
@@ -4764,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(
|
||||
@@ -4867,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));
|
||||
}
|
||||
@@ -5522,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);
|
||||
@@ -5733,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,
|
||||
|
||||
+40
-3
@@ -1333,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();
|
||||
}
|
||||
@@ -2051,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) {
|
||||
@@ -2323,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");
|
||||
|
||||
Reference in New Issue
Block a user