Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71ba7e7b1b | ||
|
|
ebe7efa1cc | ||
|
|
22d90c29b4 | ||
|
|
b944f942ee | ||
|
|
550f728165 | ||
|
|
a4b2c61a65 | ||
|
|
5c0135fa5d | ||
|
|
2b5d1eea8d | ||
|
|
d274c0abe5 | ||
|
|
dda2e007a0 | ||
|
|
321a86d9f2 | ||
|
|
ada97046a2 | ||
|
|
6e73a63153 | ||
|
|
cdc223019a | ||
|
|
574f5ce93e | ||
|
|
2996cecee0 | ||
|
|
32bf5c9c09 | ||
|
|
735e5930eb | ||
|
|
748f47b377 | ||
|
|
4cb8ff9f90 | ||
|
|
985cd9f6a2 | ||
|
|
233f0fb1b8 |
4
.github/workflows/abidiff.yaml
vendored
4
.github/workflows/abidiff.yaml
vendored
@ -5,6 +5,10 @@ name: abidiff
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: sh
|
||||
|
||||
6
.github/workflows/cifuzz.yaml
vendored
6
.github/workflows/cifuzz.yaml
vendored
@ -1,5 +1,11 @@
|
||||
name: CIFuzz
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
Fuzzing:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
98
.github/workflows/test.yaml
vendored
98
.github/workflows/test.yaml
vendored
@ -1,11 +1,52 @@
|
||||
name: test
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
gtest_filter:
|
||||
description: 'Google Test filter'
|
||||
test_linux:
|
||||
description: 'Test on Linux'
|
||||
type: boolean
|
||||
default: true
|
||||
test_macos:
|
||||
description: 'Test on MacOS'
|
||||
type: boolean
|
||||
default: true
|
||||
test_windows:
|
||||
description: 'Test on Windows'
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
GTEST_FILTER: ${{ github.event.inputs.gtest_filter || '*' }}
|
||||
|
||||
jobs:
|
||||
ubuntu:
|
||||
style-check:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: run style check
|
||||
run: |
|
||||
clang-format --version
|
||||
cd test && make style_check
|
||||
|
||||
ubuntu:
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
(github.event_name == 'push') ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
@ -18,7 +59,11 @@ jobs:
|
||||
|
||||
macos:
|
||||
runs-on: macos-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
if: >
|
||||
(github.event_name == 'push') ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_macos == 'true')
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
@ -29,7 +74,19 @@ jobs:
|
||||
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
if: >
|
||||
(github.event_name == 'push') ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_windows == 'true')
|
||||
strategy:
|
||||
matrix:
|
||||
config:
|
||||
- with_ssl: false
|
||||
name: without SSL
|
||||
- with_ssl: true
|
||||
name: with SSL
|
||||
name: windows ${{ matrix.config.name }}
|
||||
steps:
|
||||
- name: Prepare Git for Checkout on Windows
|
||||
run: |
|
||||
@ -45,24 +102,25 @@ jobs:
|
||||
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
||||
- name: Setup msbuild on windows
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
- name: Install libraries
|
||||
run: |
|
||||
vcpkg install gtest curl zlib brotli
|
||||
choco install openssl
|
||||
|
||||
- name: Configure CMake with SSL
|
||||
run: cmake -B build -S . -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake -DHTTPLIB_TEST=ON -DHTTPLIB_REQUIRE_OPENSSL=ON -DHTTPLIB_REQUIRE_ZLIB=ON -DHTTPLIB_REQUIRE_BROTLI=ON
|
||||
- name: Build with with SSL
|
||||
run: cmake --build build --config Release
|
||||
- name: Run tests with SSL
|
||||
- name: Install vcpkg dependencies
|
||||
run: vcpkg install gtest curl zlib brotli
|
||||
- name: Install OpenSSL
|
||||
if: ${{ matrix.config.with_ssl }}
|
||||
run: choco install openssl
|
||||
- name: Configure CMake ${{ matrix.config.name }}
|
||||
run: >
|
||||
cmake -B build -S .
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
-DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake
|
||||
-DHTTPLIB_TEST=ON
|
||||
-DHTTPLIB_REQUIRE_ZLIB=ON
|
||||
-DHTTPLIB_REQUIRE_BROTLI=ON
|
||||
-DHTTPLIB_REQUIRE_OPENSSL=${{ matrix.config.with_ssl && 'ON' || 'OFF' }}
|
||||
- name: Build ${{ matrix.config.name }}
|
||||
run: cmake --build build --config Release -- /v:m /clp:ShowCommandLine
|
||||
- name: Run tests ${{ matrix.config.name }}
|
||||
run: ctest --output-on-failure --test-dir build -C Release
|
||||
|
||||
- name: Configure CMake without SSL
|
||||
run: cmake -B build-no-ssl -S . -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake -DHTTPLIB_TEST=ON -DHTTPLIB_REQUIRE_OPENSSL=OFF -DHTTPLIB_REQUIRE_ZLIB=ON -DHTTPLIB_REQUIRE_BROTLI=ON
|
||||
- name: Build without SSL
|
||||
run: cmake --build build-no-ssl --config Release
|
||||
- name: Run tests without SSL
|
||||
run: ctest --output-on-failure --test-dir build-no-ssl -C Release
|
||||
env:
|
||||
VCPKG_ROOT: "C:/vcpkg"
|
||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||
|
||||
@ -2,7 +2,7 @@ FROM yhirose4dockerhub/ubuntu-builder AS builder
|
||||
WORKDIR /build
|
||||
COPY httplib.h .
|
||||
COPY docker/main.cc .
|
||||
RUN g++ -std=c++23 -static -o server -O2 -I. -DCPPHTTPLIB_USE_POLL main.cc && strip server
|
||||
RUN g++ -std=c++23 -static -o server -O2 -I. main.cc && strip server
|
||||
|
||||
FROM scratch
|
||||
COPY --from=builder /build/server /server
|
||||
|
||||
15
README.md
15
README.md
@ -406,11 +406,11 @@ svr.Get("/chunked", [&](const Request& req, Response& res) {
|
||||
|
||||
```cpp
|
||||
svr.Get("/content", [&](const Request &req, Response &res) {
|
||||
res.set_file_content("./path/to/conent.html");
|
||||
res.set_file_content("./path/to/content.html");
|
||||
});
|
||||
|
||||
svr.Get("/content", [&](const Request &req, Response &res) {
|
||||
res.set_file_content("./path/to/conent", "text/html");
|
||||
res.set_file_content("./path/to/content", "text/html");
|
||||
});
|
||||
```
|
||||
|
||||
@ -462,7 +462,7 @@ 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 the **default** task queue, with a default thread count of 8 or `std::thread::hardware_concurrency() - 1`, whichever is greater. You can change it with `CPPHTTPLIB_THREAD_POOL_COUNT`.
|
||||
|
||||
If you want to set the thread count at runtime, there is no convenient way... But here is how.
|
||||
|
||||
@ -843,7 +843,7 @@ Please see https://github.com/google/brotli for more detail.
|
||||
|
||||
### Default `Accept-Encoding` value
|
||||
|
||||
The default `Acdcept-Encoding` value contains all possible compression types. So, the following two examples are same.
|
||||
The default `Accept-Encoding` value contains all possible compression types. So, the following two examples are same.
|
||||
|
||||
```c++
|
||||
res = cli.Get("/resource/foo");
|
||||
@ -872,11 +872,6 @@ res->body; // Compressed data
|
||||
|
||||
```
|
||||
|
||||
Use `poll` instead of `select`
|
||||
------------------------------
|
||||
|
||||
`select` system call is used as default since it's more widely supported. If you want to let cpp-httplib use `poll` instead, you can do so with `CPPHTTPLIB_USE_POLL`.
|
||||
|
||||
Unix Domain Socket Support
|
||||
--------------------------
|
||||
|
||||
@ -884,7 +879,7 @@ Unix Domain Socket support is available on Linux and macOS.
|
||||
|
||||
```c++
|
||||
// Server
|
||||
httplib::Server svr("./my-socket.sock");
|
||||
httplib::Server svr;
|
||||
svr.set_address_family(AF_UNIX).listen("./my-socket.sock", 80);
|
||||
|
||||
// Client
|
||||
|
||||
@ -41,7 +41,7 @@ std::string log(auto &req, auto &res) {
|
||||
auto http_referer = "-"; // TODO:
|
||||
auto http_user_agent = req.get_header_value("User-Agent", "-");
|
||||
|
||||
// NOTE: From NGINX defualt access log format
|
||||
// NOTE: From NGINX default access log format
|
||||
// log_format combined '$remote_addr - $remote_user [$time_local] '
|
||||
// '"$request" $status $body_bytes_sent '
|
||||
// '"$http_referer" "$http_user_agent"';
|
||||
|
||||
@ -12,8 +12,7 @@ using namespace std;
|
||||
|
||||
class EventDispatcher {
|
||||
public:
|
||||
EventDispatcher() {
|
||||
}
|
||||
EventDispatcher() {}
|
||||
|
||||
void wait_event(DataSink *sink) {
|
||||
unique_lock<mutex> lk(m_);
|
||||
|
||||
444
httplib.h
444
httplib.h
@ -193,9 +193,8 @@ using ssize_t = long;
|
||||
#endif
|
||||
|
||||
using socket_t = SOCKET;
|
||||
#ifdef CPPHTTPLIB_USE_POLL
|
||||
using socklen_t = int;
|
||||
#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout)
|
||||
#endif
|
||||
|
||||
#else // not _WIN32
|
||||
|
||||
@ -215,16 +214,11 @@ using socket_t = SOCKET;
|
||||
#ifdef __linux__
|
||||
#include <resolv.h>
|
||||
#endif
|
||||
#include <netinet/tcp.h>
|
||||
#ifdef CPPHTTPLIB_USE_POLL
|
||||
#include <poll.h>
|
||||
#endif
|
||||
#include <csignal>
|
||||
#include <netinet/tcp.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/mman.h>
|
||||
#ifndef __VMS
|
||||
#include <sys/select.h>
|
||||
#endif
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
@ -434,6 +428,15 @@ private:
|
||||
|
||||
} // namespace detail
|
||||
|
||||
enum SSLVerifierResponse {
|
||||
// no decision has been made, use the built-in certificate verifier
|
||||
NoDecisionMade,
|
||||
// connection certificate is verified and accepted
|
||||
CertificateAccepted,
|
||||
// connection certificate was processed but is rejected
|
||||
CertificateRejected
|
||||
};
|
||||
|
||||
enum StatusCode {
|
||||
// Information responses
|
||||
Continue_100 = 100,
|
||||
@ -669,7 +672,7 @@ struct Request {
|
||||
bool is_chunked_content_provider_ = false;
|
||||
size_t authorization_count_ = 0;
|
||||
std::chrono::time_point<std::chrono::steady_clock> start_time_ =
|
||||
std::chrono::steady_clock::time_point::min();
|
||||
(std::chrono::steady_clock::time_point::min)();
|
||||
};
|
||||
|
||||
struct Response {
|
||||
@ -735,7 +738,8 @@ public:
|
||||
virtual ~Stream() = default;
|
||||
|
||||
virtual bool is_readable() const = 0;
|
||||
virtual bool is_writable() const = 0;
|
||||
virtual bool wait_readable() const = 0;
|
||||
virtual bool wait_writable() const = 0;
|
||||
|
||||
virtual ssize_t read(char *ptr, size_t size) = 0;
|
||||
virtual ssize_t write(const char *ptr, size_t size) = 0;
|
||||
@ -848,6 +852,16 @@ using Logger = std::function<void(const Request &, const Response &)>;
|
||||
|
||||
using SocketOptions = std::function<void(socket_t sock)>;
|
||||
|
||||
namespace detail {
|
||||
|
||||
bool set_socket_opt_impl(socket_t sock, int level, int optname,
|
||||
const void *optval, socklen_t optlen);
|
||||
bool set_socket_opt(socket_t sock, int level, int optname, int opt);
|
||||
bool set_socket_opt_time(socket_t sock, int level, int optname, time_t sec,
|
||||
time_t usec);
|
||||
|
||||
} // namespace detail
|
||||
|
||||
void default_socket_options(socket_t sock);
|
||||
|
||||
const char *status_message(int status);
|
||||
@ -868,7 +882,7 @@ public:
|
||||
* Captures parameters in request path and stores them in Request::path_params
|
||||
*
|
||||
* Capture name is a substring of a pattern from : to /.
|
||||
* The rest of the pattern is matched agains the request path directly
|
||||
* The rest of the pattern is matched against the request path directly
|
||||
* Parameters are captured starting from the next character after
|
||||
* the end of the last matched static pattern fragment until the next /.
|
||||
*
|
||||
@ -1098,7 +1112,7 @@ private:
|
||||
virtual bool process_and_close_socket(socket_t sock);
|
||||
|
||||
std::atomic<bool> is_running_{false};
|
||||
std::atomic<bool> is_decommisioned{false};
|
||||
std::atomic<bool> is_decommissioned{false};
|
||||
|
||||
struct MountPointEntry {
|
||||
std::string mount_point;
|
||||
@ -1472,7 +1486,8 @@ public:
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
void enable_server_certificate_verification(bool enabled);
|
||||
void enable_server_hostname_verification(bool enabled);
|
||||
void set_server_certificate_verifier(std::function<bool(SSL *ssl)> verifier);
|
||||
void set_server_certificate_verifier(
|
||||
std::function<SSLVerifierResponse(SSL *ssl)> verifier);
|
||||
#endif
|
||||
|
||||
void set_logger(Logger logger);
|
||||
@ -1589,7 +1604,7 @@ protected:
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
bool server_certificate_verification_ = true;
|
||||
bool server_hostname_verification_ = true;
|
||||
std::function<bool(SSL *ssl)> server_certificate_verifier_;
|
||||
std::function<SSLVerifierResponse(SSL *ssl)> server_certificate_verifier_;
|
||||
#endif
|
||||
|
||||
Logger logger_;
|
||||
@ -1902,7 +1917,8 @@ public:
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
void enable_server_certificate_verification(bool enabled);
|
||||
void enable_server_hostname_verification(bool enabled);
|
||||
void set_server_certificate_verifier(std::function<bool(SSL *ssl)> verifier);
|
||||
void set_server_certificate_verifier(
|
||||
std::function<SSLVerifierResponse(SSL *ssl)> verifier);
|
||||
#endif
|
||||
|
||||
void set_logger(Logger logger);
|
||||
@ -2075,20 +2091,45 @@ inline uint64_t Response::get_header_value_u64(const std::string &key,
|
||||
return detail::get_header_value_u64(headers, key, def, id);
|
||||
}
|
||||
|
||||
inline void default_socket_options(socket_t sock) {
|
||||
int opt = 1;
|
||||
namespace detail {
|
||||
|
||||
inline bool set_socket_opt_impl(socket_t sock, int level, int optname,
|
||||
const void *optval, socklen_t optlen) {
|
||||
return setsockopt(sock, level, optname,
|
||||
#ifdef _WIN32
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
|
||||
reinterpret_cast<const char *>(&opt), sizeof(opt));
|
||||
reinterpret_cast<const char *>(optval),
|
||||
#else
|
||||
optval,
|
||||
#endif
|
||||
optlen) == 0;
|
||||
}
|
||||
|
||||
inline bool set_socket_opt(socket_t sock, int level, int optname, int optval) {
|
||||
return set_socket_opt_impl(sock, level, optname, &optval, sizeof(optval));
|
||||
}
|
||||
|
||||
inline bool set_socket_opt_time(socket_t sock, int level, int optname,
|
||||
time_t sec, time_t usec) {
|
||||
#ifdef _WIN32
|
||||
auto timeout = static_cast<uint32_t>(sec * 1000 + usec / 1000);
|
||||
#else
|
||||
timeval timeout;
|
||||
timeout.tv_sec = static_cast<long>(sec);
|
||||
timeout.tv_usec = static_cast<decltype(timeout.tv_usec)>(usec);
|
||||
#endif
|
||||
return set_socket_opt_impl(sock, level, optname, &timeout, sizeof(timeout));
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
inline void default_socket_options(socket_t sock) {
|
||||
detail::set_socket_opt(sock, SOL_SOCKET,
|
||||
#ifdef SO_REUSEPORT
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
|
||||
reinterpret_cast<const void *>(&opt), sizeof(opt));
|
||||
SO_REUSEPORT,
|
||||
#else
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
|
||||
reinterpret_cast<const void *>(&opt), sizeof(opt));
|
||||
#endif
|
||||
SO_REUSEADDR,
|
||||
#endif
|
||||
1);
|
||||
}
|
||||
|
||||
inline const char *status_message(int status) {
|
||||
@ -2413,7 +2454,8 @@ public:
|
||||
~BufferStream() override = default;
|
||||
|
||||
bool is_readable() const override;
|
||||
bool is_writable() const override;
|
||||
bool wait_readable() const override;
|
||||
bool wait_writable() const override;
|
||||
ssize_t read(char *ptr, size_t size) override;
|
||||
ssize_t write(const char *ptr, size_t size) override;
|
||||
void get_remote_ip_and_port(std::string &ip, int &port) const override;
|
||||
@ -2533,7 +2575,7 @@ private:
|
||||
char *fixed_buffer_;
|
||||
const size_t fixed_buffer_size_;
|
||||
size_t fixed_buffer_used_size_ = 0;
|
||||
std::string glowable_buffer_;
|
||||
std::string growable_buffer_;
|
||||
};
|
||||
|
||||
class mmap {
|
||||
@ -2969,18 +3011,18 @@ inline stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer,
|
||||
fixed_buffer_size_(fixed_buffer_size) {}
|
||||
|
||||
inline const char *stream_line_reader::ptr() const {
|
||||
if (glowable_buffer_.empty()) {
|
||||
if (growable_buffer_.empty()) {
|
||||
return fixed_buffer_;
|
||||
} else {
|
||||
return glowable_buffer_.data();
|
||||
return growable_buffer_.data();
|
||||
}
|
||||
}
|
||||
|
||||
inline size_t stream_line_reader::size() const {
|
||||
if (glowable_buffer_.empty()) {
|
||||
if (growable_buffer_.empty()) {
|
||||
return fixed_buffer_used_size_;
|
||||
} else {
|
||||
return glowable_buffer_.size();
|
||||
return growable_buffer_.size();
|
||||
}
|
||||
}
|
||||
|
||||
@ -2991,7 +3033,7 @@ inline bool stream_line_reader::end_with_crlf() const {
|
||||
|
||||
inline bool stream_line_reader::getline() {
|
||||
fixed_buffer_used_size_ = 0;
|
||||
glowable_buffer_.clear();
|
||||
growable_buffer_.clear();
|
||||
|
||||
#ifndef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
|
||||
char prev_byte = 0;
|
||||
@ -3029,11 +3071,11 @@ inline void stream_line_reader::append(char c) {
|
||||
fixed_buffer_[fixed_buffer_used_size_++] = c;
|
||||
fixed_buffer_[fixed_buffer_used_size_] = '\0';
|
||||
} else {
|
||||
if (glowable_buffer_.empty()) {
|
||||
if (growable_buffer_.empty()) {
|
||||
assert(fixed_buffer_[fixed_buffer_used_size_] == '\0');
|
||||
glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_);
|
||||
growable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_);
|
||||
}
|
||||
glowable_buffer_ += c;
|
||||
growable_buffer_ += c;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3212,7 +3254,6 @@ inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size,
|
||||
|
||||
template <bool Read>
|
||||
inline ssize_t select_impl(socket_t sock, time_t sec, time_t usec) {
|
||||
#ifdef CPPHTTPLIB_USE_POLL
|
||||
struct pollfd pfd;
|
||||
pfd.fd = sock;
|
||||
pfd.events = (Read ? POLLIN : POLLOUT);
|
||||
@ -3220,25 +3261,6 @@ inline ssize_t select_impl(socket_t sock, time_t sec, time_t usec) {
|
||||
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
|
||||
|
||||
return handle_EINTR([&]() { return poll(&pfd, 1, timeout); });
|
||||
#else
|
||||
#ifndef _WIN32
|
||||
if (sock >= FD_SETSIZE) { return -1; }
|
||||
#endif
|
||||
|
||||
fd_set fds, *rfds, *wfds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(sock, &fds);
|
||||
rfds = (Read ? &fds : nullptr);
|
||||
wfds = (Read ? nullptr : &fds);
|
||||
|
||||
timeval tv;
|
||||
tv.tv_sec = static_cast<long>(sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
|
||||
|
||||
return handle_EINTR([&]() {
|
||||
return select(static_cast<int>(sock + 1), rfds, wfds, nullptr, &tv);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
|
||||
@ -3251,7 +3273,6 @@ inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
|
||||
|
||||
inline Error wait_until_socket_is_ready(socket_t sock, time_t sec,
|
||||
time_t usec) {
|
||||
#ifdef CPPHTTPLIB_USE_POLL
|
||||
struct pollfd pfd_read;
|
||||
pfd_read.fd = sock;
|
||||
pfd_read.events = POLLIN | POLLOUT;
|
||||
@ -3272,38 +3293,6 @@ inline Error wait_until_socket_is_ready(socket_t sock, time_t sec,
|
||||
}
|
||||
|
||||
return Error::Connection;
|
||||
#else
|
||||
#ifndef _WIN32
|
||||
if (sock >= FD_SETSIZE) { return Error::Connection; }
|
||||
#endif
|
||||
|
||||
fd_set fdsr;
|
||||
FD_ZERO(&fdsr);
|
||||
FD_SET(sock, &fdsr);
|
||||
|
||||
auto fdsw = fdsr;
|
||||
auto fdse = fdsr;
|
||||
|
||||
timeval tv;
|
||||
tv.tv_sec = static_cast<long>(sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
|
||||
|
||||
auto ret = handle_EINTR([&]() {
|
||||
return select(static_cast<int>(sock + 1), &fdsr, &fdsw, &fdse, &tv);
|
||||
});
|
||||
|
||||
if (ret == 0) { return Error::ConnectionTimeout; }
|
||||
|
||||
if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
|
||||
auto error = 0;
|
||||
socklen_t len = sizeof(error);
|
||||
auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
|
||||
reinterpret_cast<char *>(&error), &len);
|
||||
auto successful = res >= 0 && !error;
|
||||
return successful ? Error::Success : Error::Connection;
|
||||
}
|
||||
return Error::Connection;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool is_socket_alive(socket_t sock) {
|
||||
@ -3323,11 +3312,12 @@ public:
|
||||
time_t write_timeout_sec, time_t write_timeout_usec,
|
||||
time_t max_timeout_msec = 0,
|
||||
std::chrono::time_point<std::chrono::steady_clock> start_time =
|
||||
std::chrono::steady_clock::time_point::min());
|
||||
(std::chrono::steady_clock::time_point::min)());
|
||||
~SocketStream() override;
|
||||
|
||||
bool is_readable() const override;
|
||||
bool is_writable() const override;
|
||||
bool wait_readable() const override;
|
||||
bool wait_writable() const override;
|
||||
ssize_t read(char *ptr, size_t size) override;
|
||||
ssize_t write(const char *ptr, size_t size) override;
|
||||
void get_remote_ip_and_port(std::string &ip, int &port) const override;
|
||||
@ -3359,11 +3349,12 @@ public:
|
||||
time_t read_timeout_usec, time_t write_timeout_sec,
|
||||
time_t write_timeout_usec, time_t max_timeout_msec = 0,
|
||||
std::chrono::time_point<std::chrono::steady_clock> start_time =
|
||||
std::chrono::steady_clock::time_point::min());
|
||||
(std::chrono::steady_clock::time_point::min)());
|
||||
~SSLSocketStream() override;
|
||||
|
||||
bool is_readable() const override;
|
||||
bool is_writable() const override;
|
||||
bool wait_readable() const override;
|
||||
bool wait_writable() const override;
|
||||
ssize_t read(char *ptr, size_t size) override;
|
||||
ssize_t write(const char *ptr, size_t size) override;
|
||||
void get_remote_ip_and_port(std::string &ip, int &port) const override;
|
||||
@ -3605,26 +3596,10 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
|
||||
}
|
||||
#endif
|
||||
|
||||
if (tcp_nodelay) {
|
||||
auto opt = 1;
|
||||
#ifdef _WIN32
|
||||
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
|
||||
reinterpret_cast<const char *>(&opt), sizeof(opt));
|
||||
#else
|
||||
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
|
||||
reinterpret_cast<const void *>(&opt), sizeof(opt));
|
||||
#endif
|
||||
}
|
||||
if (tcp_nodelay) { set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1); }
|
||||
|
||||
if (rp->ai_family == AF_INET6) {
|
||||
auto opt = ipv6_v6only ? 1 : 0;
|
||||
#ifdef _WIN32
|
||||
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
|
||||
reinterpret_cast<const char *>(&opt), sizeof(opt));
|
||||
#else
|
||||
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
|
||||
reinterpret_cast<const void *>(&opt), sizeof(opt));
|
||||
#endif
|
||||
set_socket_opt(sock, IPPROTO_IPV6, IPV6_V6ONLY, ipv6_v6only ? 1 : 0);
|
||||
}
|
||||
|
||||
if (socket_options) { socket_options(sock); }
|
||||
@ -3767,36 +3742,10 @@ inline socket_t create_client_socket(
|
||||
}
|
||||
|
||||
set_nonblocking(sock2, false);
|
||||
|
||||
{
|
||||
#ifdef _WIN32
|
||||
auto timeout = static_cast<uint32_t>(read_timeout_sec * 1000 +
|
||||
read_timeout_usec / 1000);
|
||||
setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO,
|
||||
reinterpret_cast<const char *>(&timeout), sizeof(timeout));
|
||||
#else
|
||||
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,
|
||||
reinterpret_cast<const void *>(&tv), sizeof(tv));
|
||||
#endif
|
||||
}
|
||||
{
|
||||
|
||||
#ifdef _WIN32
|
||||
auto timeout = static_cast<uint32_t>(write_timeout_sec * 1000 +
|
||||
write_timeout_usec / 1000);
|
||||
setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO,
|
||||
reinterpret_cast<const char *>(&timeout), sizeof(timeout));
|
||||
#else
|
||||
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,
|
||||
reinterpret_cast<const void *>(&tv), sizeof(tv));
|
||||
#endif
|
||||
}
|
||||
set_socket_opt_time(sock2, SOL_SOCKET, SO_RCVTIMEO, read_timeout_sec,
|
||||
read_timeout_usec);
|
||||
set_socket_opt_time(sock2, SOL_SOCKET, SO_SNDTIMEO, write_timeout_sec,
|
||||
write_timeout_usec);
|
||||
|
||||
error = Error::Success;
|
||||
return true;
|
||||
@ -4248,10 +4197,6 @@ inline bool parse_header(const char *beg, const char *end, T fn) {
|
||||
if (!key_len) { return false; }
|
||||
|
||||
auto key = std::string(beg, key_end);
|
||||
// auto val = (case_ignore::equal(key, "Location") ||
|
||||
// case_ignore::equal(key, "Referer"))
|
||||
// ? std::string(p, end)
|
||||
// : decode_url(std::string(p, end), false);
|
||||
auto val = std::string(p, end);
|
||||
|
||||
if (!detail::fields::is_field_value(val)) { return false; }
|
||||
@ -4390,7 +4335,7 @@ inline bool read_content_chunked(Stream &strm, T &x,
|
||||
|
||||
assert(chunk_len == 0);
|
||||
|
||||
// NOTE: In RFC 9112, '7.1 Chunked Transfer Coding' mentiones "The chunked
|
||||
// NOTE: In RFC 9112, '7.1 Chunked Transfer Coding' mentions "The chunked
|
||||
// transfer coding is complete when a chunk with a chunk-size of zero is
|
||||
// received, possibly followed by a trailer section, and finally terminated by
|
||||
// an empty line". https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1
|
||||
@ -4400,8 +4345,8 @@ inline bool read_content_chunked(Stream &strm, T &x,
|
||||
// to be ok whether the final CRLF exists or not in the chunked data.
|
||||
// https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1.3
|
||||
//
|
||||
// According to the reference code in RFC 9112, cpp-htpplib now allows
|
||||
// chuncked transfer coding data without the final CRLF.
|
||||
// According to the reference code in RFC 9112, cpp-httplib now allows
|
||||
// chunked transfer coding data without the final CRLF.
|
||||
if (!line_reader.getline()) { return true; }
|
||||
|
||||
while (strcmp(line_reader.ptr(), "\r\n") != 0) {
|
||||
@ -4571,7 +4516,7 @@ inline bool write_content(Stream &strm, const ContentProvider &content_provider,
|
||||
|
||||
data_sink.write = [&](const char *d, size_t l) -> bool {
|
||||
if (ok) {
|
||||
if (strm.is_writable() && write_data(strm, d, l)) {
|
||||
if (write_data(strm, d, l)) {
|
||||
offset += l;
|
||||
} else {
|
||||
ok = false;
|
||||
@ -4580,10 +4525,10 @@ inline bool write_content(Stream &strm, const ContentProvider &content_provider,
|
||||
return ok;
|
||||
};
|
||||
|
||||
data_sink.is_writable = [&]() -> bool { return strm.is_writable(); };
|
||||
data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); };
|
||||
|
||||
while (offset < end_offset && !is_shutting_down()) {
|
||||
if (!strm.is_writable()) {
|
||||
if (!strm.wait_writable()) {
|
||||
error = Error::Write;
|
||||
return false;
|
||||
} else if (!content_provider(offset, end_offset - offset, data_sink)) {
|
||||
@ -4621,17 +4566,17 @@ write_content_without_length(Stream &strm,
|
||||
data_sink.write = [&](const char *d, size_t l) -> bool {
|
||||
if (ok) {
|
||||
offset += l;
|
||||
if (!strm.is_writable() || !write_data(strm, d, l)) { ok = false; }
|
||||
if (!write_data(strm, d, l)) { ok = false; }
|
||||
}
|
||||
return ok;
|
||||
};
|
||||
|
||||
data_sink.is_writable = [&]() -> bool { return strm.is_writable(); };
|
||||
data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); };
|
||||
|
||||
data_sink.done = [&](void) { data_available = false; };
|
||||
|
||||
while (data_available && !is_shutting_down()) {
|
||||
if (!strm.is_writable()) {
|
||||
if (!strm.wait_writable()) {
|
||||
return false;
|
||||
} else if (!content_provider(offset, 0, data_sink)) {
|
||||
return false;
|
||||
@ -4666,10 +4611,7 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
|
||||
// Emit chunked response header and footer for each chunk
|
||||
auto chunk =
|
||||
from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
|
||||
if (!strm.is_writable() ||
|
||||
!write_data(strm, chunk.data(), chunk.size())) {
|
||||
ok = false;
|
||||
}
|
||||
if (!write_data(strm, chunk.data(), chunk.size())) { ok = false; }
|
||||
}
|
||||
} else {
|
||||
ok = false;
|
||||
@ -4678,7 +4620,7 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
|
||||
return ok;
|
||||
};
|
||||
|
||||
data_sink.is_writable = [&]() -> bool { return strm.is_writable(); };
|
||||
data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); };
|
||||
|
||||
auto done_with_trailer = [&](const Headers *trailer) {
|
||||
if (!ok) { return; }
|
||||
@ -4698,8 +4640,7 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
|
||||
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 (!strm.is_writable() ||
|
||||
!write_data(strm, chunk.data(), chunk.size())) {
|
||||
if (!write_data(strm, chunk.data(), chunk.size())) {
|
||||
ok = false;
|
||||
return;
|
||||
}
|
||||
@ -4731,7 +4672,7 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
|
||||
};
|
||||
|
||||
while (data_available && !is_shutting_down()) {
|
||||
if (!strm.is_writable()) {
|
||||
if (!strm.wait_writable()) {
|
||||
error = Error::Write;
|
||||
return false;
|
||||
} else if (!content_provider(offset, 0, data_sink)) {
|
||||
@ -4991,7 +4932,7 @@ public:
|
||||
|
||||
it = params.find("filename*");
|
||||
if (it != params.end()) {
|
||||
// Only allow UTF-8 enconnding...
|
||||
// Only allow UTF-8 encoding...
|
||||
static const std::regex re_rfc5987_encoding(
|
||||
R"~(^UTF-8''(.+?)$)~", std::regex_constants::icase);
|
||||
|
||||
@ -5238,7 +5179,7 @@ serialize_multipart_formdata(const MultipartFormDataItems &items,
|
||||
|
||||
inline bool range_error(Request &req, Response &res) {
|
||||
if (!req.ranges.empty() && 200 <= res.status && res.status < 300) {
|
||||
ssize_t contant_len = static_cast<ssize_t>(
|
||||
ssize_t content_len = static_cast<ssize_t>(
|
||||
res.content_length_ ? res.content_length_ : res.body.size());
|
||||
|
||||
ssize_t prev_first_pos = -1;
|
||||
@ -5258,12 +5199,12 @@ inline bool range_error(Request &req, Response &res) {
|
||||
|
||||
if (first_pos == -1 && last_pos == -1) {
|
||||
first_pos = 0;
|
||||
last_pos = contant_len;
|
||||
last_pos = content_len;
|
||||
}
|
||||
|
||||
if (first_pos == -1) {
|
||||
first_pos = contant_len - last_pos;
|
||||
last_pos = contant_len - 1;
|
||||
first_pos = content_len - last_pos;
|
||||
last_pos = content_len - 1;
|
||||
}
|
||||
|
||||
// NOTE: RFC-9110 '14.1.2. Byte Ranges':
|
||||
@ -5275,13 +5216,13 @@ inline bool range_error(Request &req, Response &res) {
|
||||
// with a value that is one less than the current length of the selected
|
||||
// representation).
|
||||
// https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2-6
|
||||
if (last_pos == -1 || last_pos >= contant_len) {
|
||||
last_pos = contant_len - 1;
|
||||
if (last_pos == -1 || last_pos >= content_len) {
|
||||
last_pos = content_len - 1;
|
||||
}
|
||||
|
||||
// Range must be within content length
|
||||
if (!(0 <= first_pos && first_pos <= last_pos &&
|
||||
last_pos <= contant_len - 1)) {
|
||||
last_pos <= content_len - 1)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -5993,14 +5934,14 @@ inline ssize_t Stream::write(const std::string &s) {
|
||||
|
||||
namespace detail {
|
||||
|
||||
inline void calc_actual_timeout(time_t max_timeout_msec,
|
||||
time_t duration_msec, time_t timeout_sec,
|
||||
time_t timeout_usec, time_t &actual_timeout_sec,
|
||||
inline void calc_actual_timeout(time_t max_timeout_msec, time_t duration_msec,
|
||||
time_t timeout_sec, time_t timeout_usec,
|
||||
time_t &actual_timeout_sec,
|
||||
time_t &actual_timeout_usec) {
|
||||
auto timeout_msec = (timeout_sec * 1000) + (timeout_usec / 1000);
|
||||
|
||||
auto actual_timeout_msec =
|
||||
std::min(max_timeout_msec - duration_msec, timeout_msec);
|
||||
(std::min)(max_timeout_msec - duration_msec, timeout_msec);
|
||||
|
||||
actual_timeout_sec = actual_timeout_msec / 1000;
|
||||
actual_timeout_usec = (actual_timeout_msec % 1000) * 1000;
|
||||
@ -6022,6 +5963,10 @@ inline SocketStream::SocketStream(
|
||||
inline SocketStream::~SocketStream() = default;
|
||||
|
||||
inline bool SocketStream::is_readable() const {
|
||||
return read_buff_off_ < read_buff_content_size_;
|
||||
}
|
||||
|
||||
inline bool SocketStream::wait_readable() const {
|
||||
if (max_timeout_msec_ <= 0) {
|
||||
return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
|
||||
}
|
||||
@ -6034,7 +5979,7 @@ inline bool SocketStream::is_readable() const {
|
||||
return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0;
|
||||
}
|
||||
|
||||
inline bool SocketStream::is_writable() const {
|
||||
inline bool SocketStream::wait_writable() const {
|
||||
return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
|
||||
is_socket_alive(sock_);
|
||||
}
|
||||
@ -6061,7 +6006,7 @@ inline ssize_t SocketStream::read(char *ptr, size_t size) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_readable()) { return -1; }
|
||||
if (!wait_readable()) { return -1; }
|
||||
|
||||
read_buff_off_ = 0;
|
||||
read_buff_content_size_ = 0;
|
||||
@ -6086,7 +6031,7 @@ inline ssize_t SocketStream::read(char *ptr, size_t size) {
|
||||
}
|
||||
|
||||
inline ssize_t SocketStream::write(const char *ptr, size_t size) {
|
||||
if (!is_writable()) { return -1; }
|
||||
if (!wait_writable()) { return -1; }
|
||||
|
||||
#if defined(_WIN32) && !defined(_WIN64)
|
||||
size =
|
||||
@ -6117,7 +6062,9 @@ inline time_t SocketStream::duration() const {
|
||||
// Buffer stream implementation
|
||||
inline bool BufferStream::is_readable() const { return true; }
|
||||
|
||||
inline bool BufferStream::is_writable() const { return true; }
|
||||
inline bool BufferStream::wait_readable() const { return true; }
|
||||
|
||||
inline bool BufferStream::wait_writable() const { return true; }
|
||||
|
||||
inline ssize_t BufferStream::read(char *ptr, size_t size) {
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1910
|
||||
@ -6475,12 +6422,12 @@ inline Server &Server::set_payload_max_length(size_t length) {
|
||||
inline bool Server::bind_to_port(const std::string &host, int port,
|
||||
int socket_flags) {
|
||||
auto ret = bind_internal(host, port, socket_flags);
|
||||
if (ret == -1) { is_decommisioned = true; }
|
||||
if (ret == -1) { is_decommissioned = true; }
|
||||
return ret >= 0;
|
||||
}
|
||||
inline int Server::bind_to_any_port(const std::string &host, int socket_flags) {
|
||||
auto ret = bind_internal(host, 0, socket_flags);
|
||||
if (ret == -1) { is_decommisioned = true; }
|
||||
if (ret == -1) { is_decommissioned = true; }
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -6494,7 +6441,7 @@ inline bool Server::listen(const std::string &host, int port,
|
||||
inline bool Server::is_running() const { return is_running_; }
|
||||
|
||||
inline void Server::wait_until_ready() const {
|
||||
while (!is_running_ && !is_decommisioned) {
|
||||
while (!is_running_ && !is_decommissioned) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{1});
|
||||
}
|
||||
}
|
||||
@ -6506,10 +6453,10 @@ inline void Server::stop() {
|
||||
detail::shutdown_socket(sock);
|
||||
detail::close_socket(sock);
|
||||
}
|
||||
is_decommisioned = false;
|
||||
is_decommissioned = false;
|
||||
}
|
||||
|
||||
inline void Server::decommission() { is_decommisioned = true; }
|
||||
inline void Server::decommission() { is_decommissioned = true; }
|
||||
|
||||
inline bool Server::parse_request_line(const char *s, Request &req) const {
|
||||
auto len = strlen(s);
|
||||
@ -6868,7 +6815,7 @@ Server::create_server_socket(const std::string &host, int port,
|
||||
|
||||
inline int Server::bind_internal(const std::string &host, int port,
|
||||
int socket_flags) {
|
||||
if (is_decommisioned) { return -1; }
|
||||
if (is_decommissioned) { return -1; }
|
||||
|
||||
if (!is_valid()) { return -1; }
|
||||
|
||||
@ -6895,7 +6842,7 @@ inline int Server::bind_internal(const std::string &host, int port,
|
||||
}
|
||||
|
||||
inline bool Server::listen_internal() {
|
||||
if (is_decommisioned) { return false; }
|
||||
if (is_decommissioned) { return false; }
|
||||
|
||||
auto ret = true;
|
||||
is_running_ = true;
|
||||
@ -6919,7 +6866,7 @@ inline bool Server::listen_internal() {
|
||||
#endif
|
||||
|
||||
#if defined _WIN32
|
||||
// sockets conneced via WASAccept inherit flags NO_HANDLE_INHERIT,
|
||||
// sockets connected via WASAccept inherit flags NO_HANDLE_INHERIT,
|
||||
// OVERLAPPED
|
||||
socket_t sock = WSAAccept(svr_sock_, nullptr, nullptr, nullptr, 0);
|
||||
#elif defined SOCK_CLOEXEC
|
||||
@ -6946,35 +6893,10 @@ inline bool Server::listen_internal() {
|
||||
break;
|
||||
}
|
||||
|
||||
{
|
||||
#ifdef _WIN32
|
||||
auto timeout = static_cast<uint32_t>(read_timeout_sec_ * 1000 +
|
||||
read_timeout_usec_ / 1000);
|
||||
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
|
||||
reinterpret_cast<const char *>(&timeout), sizeof(timeout));
|
||||
#else
|
||||
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,
|
||||
reinterpret_cast<const void *>(&tv), sizeof(tv));
|
||||
#endif
|
||||
}
|
||||
{
|
||||
|
||||
#ifdef _WIN32
|
||||
auto timeout = static_cast<uint32_t>(write_timeout_sec_ * 1000 +
|
||||
write_timeout_usec_ / 1000);
|
||||
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO,
|
||||
reinterpret_cast<const char *>(&timeout), sizeof(timeout));
|
||||
#else
|
||||
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,
|
||||
reinterpret_cast<const void *>(&tv), sizeof(tv));
|
||||
#endif
|
||||
}
|
||||
detail::set_socket_opt_time(sock, SOL_SOCKET, SO_RCVTIMEO,
|
||||
read_timeout_sec_, read_timeout_usec_);
|
||||
detail::set_socket_opt_time(sock, SOL_SOCKET, SO_SNDTIMEO,
|
||||
write_timeout_sec_, write_timeout_usec_);
|
||||
|
||||
if (!task_queue->enqueue(
|
||||
[this, sock]() { process_and_close_socket(sock); })) {
|
||||
@ -6986,7 +6908,7 @@ inline bool Server::listen_internal() {
|
||||
task_queue->shutdown();
|
||||
}
|
||||
|
||||
is_decommisioned = !ret;
|
||||
is_decommissioned = !ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -7220,20 +7142,6 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
|
||||
res.version = "HTTP/1.1";
|
||||
res.headers = default_headers_;
|
||||
|
||||
#ifdef _WIN32
|
||||
// TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL).
|
||||
#else
|
||||
#ifndef CPPHTTPLIB_USE_POLL
|
||||
// Socket file descriptor exceeded FD_SETSIZE...
|
||||
if (strm.socket() >= FD_SETSIZE) {
|
||||
Headers dummy;
|
||||
detail::read_headers(strm, dummy);
|
||||
res.status = StatusCode::InternalServerError_500;
|
||||
return write_response(strm, close_connection, req, res);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Request line and headers
|
||||
if (!parse_request_line(line_reader.ptr(), req) ||
|
||||
!detail::read_headers(strm, req.headers)) {
|
||||
@ -7425,6 +7333,16 @@ inline ClientImpl::ClientImpl(const std::string &host, int port,
|
||||
client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
|
||||
|
||||
inline ClientImpl::~ClientImpl() {
|
||||
// Wait until all the requests in flight are handled.
|
||||
size_t retry_count = 10;
|
||||
while (retry_count-- > 0) {
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
if (socket_requests_in_flight_ == 0) { break; }
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{1});
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
shutdown_socket(socket_);
|
||||
close_socket(socket_);
|
||||
@ -7608,7 +7526,7 @@ inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
|
||||
#endif
|
||||
|
||||
if (!is_alive) {
|
||||
// Attempt to avoid sigpipe by shutting down nongracefully if it seems
|
||||
// Attempt to avoid sigpipe by shutting down non-gracefully if it seems
|
||||
// like the other side has already closed the connection Also, there
|
||||
// cannot be any requests in flight from other threads since we locked
|
||||
// request_mutex_, so safe to close everything immediately
|
||||
@ -8244,8 +8162,7 @@ inline bool ClientImpl::process_socket(
|
||||
std::function<bool(Stream &strm)> callback) {
|
||||
return detail::process_client_socket(
|
||||
socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
|
||||
write_timeout_usec_, max_timeout_msec_, start_time,
|
||||
std::move(callback));
|
||||
write_timeout_usec_, max_timeout_msec_, start_time, std::move(callback));
|
||||
}
|
||||
|
||||
inline bool ClientImpl::is_ssl() const { return false; }
|
||||
@ -9040,7 +8957,7 @@ inline void ClientImpl::enable_server_hostname_verification(bool enabled) {
|
||||
}
|
||||
|
||||
inline void ClientImpl::set_server_certificate_verifier(
|
||||
std::function<bool(SSL *ssl)> verifier) {
|
||||
std::function<SSLVerifierResponse(SSL *ssl)> verifier) {
|
||||
server_certificate_verifier_ = verifier;
|
||||
}
|
||||
#endif
|
||||
@ -9093,22 +9010,13 @@ inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, socket_t sock,
|
||||
// Note that it is not always possible to avoid SIGPIPE, this is merely a
|
||||
// best-efforts.
|
||||
if (shutdown_gracefully) {
|
||||
#ifdef _WIN32
|
||||
(void)(sock);
|
||||
SSL_shutdown(ssl);
|
||||
#else
|
||||
timeval tv;
|
||||
tv.tv_sec = 1;
|
||||
tv.tv_usec = 0;
|
||||
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
|
||||
reinterpret_cast<const void *>(&tv), sizeof(tv));
|
||||
|
||||
auto ret = SSL_shutdown(ssl);
|
||||
while (ret == 0) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{100});
|
||||
ret = SSL_shutdown(ssl);
|
||||
// SSL_shutdown() returns 0 on first call (indicating close_notify alert
|
||||
// sent) and 1 on subsequent call (indicating close_notify alert received)
|
||||
if (SSL_shutdown(ssl) == 0) {
|
||||
// Expected to return 1, but even if it doesn't, we free ssl
|
||||
SSL_shutdown(ssl);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> guard(ctx_mutex);
|
||||
@ -9159,8 +9067,8 @@ inline bool process_client_socket_ssl(
|
||||
time_t max_timeout_msec,
|
||||
std::chrono::time_point<std::chrono::steady_clock> start_time, T callback) {
|
||||
SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
|
||||
write_timeout_sec, write_timeout_usec,
|
||||
max_timeout_msec, start_time);
|
||||
write_timeout_sec, write_timeout_usec, max_timeout_msec,
|
||||
start_time);
|
||||
return callback(strm);
|
||||
}
|
||||
|
||||
@ -9189,6 +9097,10 @@ inline SSLSocketStream::SSLSocketStream(
|
||||
inline SSLSocketStream::~SSLSocketStream() = default;
|
||||
|
||||
inline bool SSLSocketStream::is_readable() const {
|
||||
return SSL_pending(ssl_) > 0;
|
||||
}
|
||||
|
||||
inline bool SSLSocketStream::wait_readable() const {
|
||||
if (max_timeout_msec_ <= 0) {
|
||||
return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
|
||||
}
|
||||
@ -9201,7 +9113,7 @@ inline bool SSLSocketStream::is_readable() const {
|
||||
return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0;
|
||||
}
|
||||
|
||||
inline bool SSLSocketStream::is_writable() const {
|
||||
inline bool SSLSocketStream::wait_writable() const {
|
||||
return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
|
||||
is_socket_alive(sock_) && !is_ssl_peer_could_be_closed(ssl_, sock_);
|
||||
}
|
||||
@ -9209,7 +9121,7 @@ inline bool SSLSocketStream::is_writable() const {
|
||||
inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
|
||||
if (SSL_pending(ssl_) > 0) {
|
||||
return SSL_read(ssl_, ptr, static_cast<int>(size));
|
||||
} else if (is_readable()) {
|
||||
} else if (wait_readable()) {
|
||||
auto ret = SSL_read(ssl_, ptr, static_cast<int>(size));
|
||||
if (ret < 0) {
|
||||
auto err = SSL_get_error(ssl_, ret);
|
||||
@ -9223,7 +9135,7 @@ inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
|
||||
#endif
|
||||
if (SSL_pending(ssl_) > 0) {
|
||||
return SSL_read(ssl_, ptr, static_cast<int>(size));
|
||||
} else if (is_readable()) {
|
||||
} else if (wait_readable()) {
|
||||
std::this_thread::sleep_for(std::chrono::microseconds{10});
|
||||
ret = SSL_read(ssl_, ptr, static_cast<int>(size));
|
||||
if (ret >= 0) { return ret; }
|
||||
@ -9240,7 +9152,7 @@ inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
|
||||
}
|
||||
|
||||
inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
|
||||
if (is_writable()) {
|
||||
if (wait_writable()) {
|
||||
auto handle_size = static_cast<int>(
|
||||
std::min<size_t>(size, (std::numeric_limits<int>::max)()));
|
||||
|
||||
@ -9255,7 +9167,7 @@ inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
|
||||
#else
|
||||
while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) {
|
||||
#endif
|
||||
if (is_writable()) {
|
||||
if (wait_writable()) {
|
||||
std::this_thread::sleep_for(std::chrono::microseconds{10});
|
||||
ret = SSL_write(ssl_, ptr, static_cast<int>(handle_size));
|
||||
if (ret >= 0) { return ret; }
|
||||
@ -9658,12 +9570,18 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
|
||||
}
|
||||
|
||||
if (server_certificate_verification_) {
|
||||
auto verification_status = SSLVerifierResponse::NoDecisionMade;
|
||||
|
||||
if (server_certificate_verifier_) {
|
||||
if (!server_certificate_verifier_(ssl2)) {
|
||||
error = Error::SSLServerVerification;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
verification_status = server_certificate_verifier_(ssl2);
|
||||
}
|
||||
|
||||
if (verification_status == SSLVerifierResponse::CertificateRejected) {
|
||||
error = Error::SSLServerVerification;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (verification_status == SSLVerifierResponse::NoDecisionMade) {
|
||||
verify_result_ = SSL_get_verify_result(ssl2);
|
||||
|
||||
if (verify_result_ != X509_V_OK) {
|
||||
@ -9775,8 +9693,8 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
|
||||
|
||||
auto type = GEN_DNS;
|
||||
|
||||
struct in6_addr addr6{};
|
||||
struct in_addr addr{};
|
||||
struct in6_addr addr6 = {};
|
||||
struct in_addr addr = {};
|
||||
size_t addr_len = 0;
|
||||
|
||||
#ifndef __MINGW32__
|
||||
@ -10424,7 +10342,7 @@ inline void Client::enable_server_hostname_verification(bool enabled) {
|
||||
}
|
||||
|
||||
inline void Client::set_server_certificate_verifier(
|
||||
std::function<bool(SSL *ssl)> verifier) {
|
||||
std::function<SSLVerifierResponse(SSL *ssl)> verifier) {
|
||||
cli_->set_server_certificate_verifier(verifier);
|
||||
}
|
||||
#endif
|
||||
@ -10468,7 +10386,7 @@ inline SSL_CTX *Client::ssl_context() const {
|
||||
|
||||
} // namespace httplib
|
||||
|
||||
#if defined(_WIN32) && defined(CPPHTTPLIB_USE_POLL)
|
||||
#ifdef _WIN32
|
||||
#undef poll
|
||||
#endif
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@ endif()
|
||||
|
||||
find_package(CURL REQUIRED)
|
||||
|
||||
add_executable(httplib-test test.cc)
|
||||
add_executable(httplib-test test.cc include_httplib.cc $<$<BOOL:${WIN32}>:include_windows_h.cc>)
|
||||
target_compile_options(httplib-test PRIVATE "$<$<CXX_COMPILER_ID:MSVC>:/utf-8;/bigobj>")
|
||||
target_link_libraries(httplib-test PRIVATE httplib GTest::gtest_main CURL::libcurl)
|
||||
gtest_discover_tests(httplib-test)
|
||||
|
||||
@ -28,6 +28,11 @@ TEST_ARGS = gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUP
|
||||
# OSS-Fuzz will define its own value for LIB_FUZZING_ENGINE.
|
||||
LIB_FUZZING_ENGINE ?= standalone_fuzz_target_runner.o
|
||||
|
||||
CLANG_FORMAT = clang-format
|
||||
REALPATH = $(shell which grealpath 2>/dev/null || which realpath 2>/dev/null)
|
||||
STYLE_CHECK_FILES = $(filter-out httplib.h httplib.cc, \
|
||||
$(wildcard example/*.h example/*.cc fuzzing/*.h fuzzing/*.cc *.h *.cc ../httplib.h))
|
||||
|
||||
all : test test_split
|
||||
./test
|
||||
|
||||
@ -45,6 +50,28 @@ test_split : test.cc ../httplib.h httplib.cc Makefile cert.pem
|
||||
check_abi:
|
||||
@./check-shared-library-abi-compatibility.sh
|
||||
|
||||
.PHONY: style_check
|
||||
style_check: $(STYLE_CHECK_FILES)
|
||||
@for file in $(STYLE_CHECK_FILES); do \
|
||||
$(CLANG_FORMAT) $$file > $$file.formatted; \
|
||||
if ! diff -u $$file $$file.formatted; then \
|
||||
file2=$$($(REALPATH) --relative-to=.. $$file); \
|
||||
printf "\n%*s\n" 80 | tr ' ' '#'; \
|
||||
printf "##%*s##\n" 76; \
|
||||
printf "## %-70s ##\n" "$$file2 not properly formatted. Please run clang-format."; \
|
||||
printf "##%*s##\n" 76; \
|
||||
printf "%*s\n\n" 80 | tr ' ' '#'; \
|
||||
failed=1; \
|
||||
fi; \
|
||||
rm -f $$file.formatted; \
|
||||
done; \
|
||||
if [ -n "$$failed" ]; then \
|
||||
echo "Style check failed for one or more files. See above for details."; \
|
||||
false; \
|
||||
else \
|
||||
echo "All files are properly formatted."; \
|
||||
fi
|
||||
|
||||
test_proxy : test_proxy.cc ../httplib.h Makefile cert.pem
|
||||
$(CXX) -o $@ -I.. $(CXXFLAGS) test_proxy.cc $(TEST_ARGS)
|
||||
|
||||
@ -69,5 +96,5 @@ cert.pem:
|
||||
./gen-certs.sh
|
||||
|
||||
clean:
|
||||
rm -rf test test_split test_proxy server_fuzzer *.pem *.0 *.o *.1 *.srl httplib.h httplib.cc _build*
|
||||
rm -rf test test_split test_proxy server_fuzzer *.pem *.0 *.o *.1 *.srl httplib.h httplib.cc _build* *.dSYM
|
||||
|
||||
|
||||
@ -25,7 +25,9 @@ public:
|
||||
|
||||
bool is_readable() const override { return true; }
|
||||
|
||||
bool is_writable() const override { return true; }
|
||||
bool wait_readable() const override { return true; }
|
||||
|
||||
bool wait_writable() const override { return true; }
|
||||
|
||||
void get_remote_ip_and_port(std::string &ip, int &port) const override {
|
||||
ip = "127.0.0.1";
|
||||
|
||||
6
test/include_windows_h.cc
Normal file
6
test/include_windows_h.cc
Normal file
@ -0,0 +1,6 @@
|
||||
// Test if including windows.h conflicts with httplib.h
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
#include <httplib.h>
|
||||
250
test/test.cc
250
test/test.cc
@ -156,7 +156,7 @@ TEST_F(UnixSocketTest, abstract) {
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(SocketStream, is_writable_UNIX) {
|
||||
TEST(SocketStream, wait_writable_UNIX) {
|
||||
int fds[2];
|
||||
ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds));
|
||||
|
||||
@ -167,17 +167,17 @@ TEST(SocketStream, is_writable_UNIX) {
|
||||
};
|
||||
asSocketStream(fds[0], [&](Stream &s0) {
|
||||
EXPECT_EQ(s0.socket(), fds[0]);
|
||||
EXPECT_TRUE(s0.is_writable());
|
||||
EXPECT_TRUE(s0.wait_writable());
|
||||
|
||||
EXPECT_EQ(0, close(fds[1]));
|
||||
EXPECT_FALSE(s0.is_writable());
|
||||
EXPECT_FALSE(s0.wait_writable());
|
||||
|
||||
return true;
|
||||
});
|
||||
EXPECT_EQ(0, close(fds[0]));
|
||||
}
|
||||
|
||||
TEST(SocketStream, is_writable_INET) {
|
||||
TEST(SocketStream, wait_writable_INET) {
|
||||
sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
@ -212,7 +212,7 @@ TEST(SocketStream, is_writable_INET) {
|
||||
};
|
||||
asSocketStream(disconnected_svr_sock, [&](Stream &ss) {
|
||||
EXPECT_EQ(ss.socket(), disconnected_svr_sock);
|
||||
EXPECT_FALSE(ss.is_writable());
|
||||
EXPECT_FALSE(ss.wait_writable());
|
||||
|
||||
return true;
|
||||
});
|
||||
@ -3553,9 +3553,11 @@ TEST_F(ServerTest, TooLongRequest) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, AlmostTooLongRequest) {
|
||||
// test for #2046 - URI length check shouldn't include other content on req line
|
||||
// URI is max URI length, minus 14 other chars in req line (GET, space, leading /, space, HTTP/1.1)
|
||||
std::string request = "/" + string(CPPHTTPLIB_REQUEST_URI_MAX_LENGTH - 14, 'A');
|
||||
// test for #2046 - URI length check shouldn't include other content on req
|
||||
// line URI is max URI length, minus 14 other chars in req line (GET, space,
|
||||
// leading /, space, HTTP/1.1)
|
||||
std::string request =
|
||||
"/" + string(CPPHTTPLIB_REQUEST_URI_MAX_LENGTH - 14, 'A');
|
||||
|
||||
auto res = cli_.Get(request.c_str());
|
||||
|
||||
@ -8205,9 +8207,8 @@ TEST(Expect100ContinueTest, ServerClosesConnection) {
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(MaxTimeoutTest, ContentStream) {
|
||||
Server svr;
|
||||
|
||||
template <typename S, typename C>
|
||||
inline void max_timeout_test(S &svr, C &cli, time_t timeout, time_t threshold) {
|
||||
svr.Get("/stream", [&](const Request &, Response &res) {
|
||||
auto data = new std::string("01234567890123456789");
|
||||
|
||||
@ -8277,13 +8278,8 @@ TEST(MaxTimeoutTest, ContentStream) {
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
const time_t timeout = 2000;
|
||||
const time_t threshold = 200;
|
||||
|
||||
Client cli("localhost", PORT);
|
||||
cli.set_max_timeout(std::chrono::milliseconds(timeout));
|
||||
|
||||
|
||||
{
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
|
||||
@ -8295,7 +8291,8 @@ TEST(MaxTimeoutTest, ContentStream) {
|
||||
|
||||
ASSERT_FALSE(res);
|
||||
EXPECT_EQ(Error::Read, res.error());
|
||||
EXPECT_TRUE(timeout <= elapsed && elapsed < timeout + threshold);
|
||||
EXPECT_TRUE(timeout <= elapsed && elapsed < timeout + threshold)
|
||||
<< "Timeout exceeded by " << (elapsed - timeout) << "ms";
|
||||
}
|
||||
|
||||
{
|
||||
@ -8309,7 +8306,8 @@ TEST(MaxTimeoutTest, ContentStream) {
|
||||
|
||||
ASSERT_FALSE(res);
|
||||
EXPECT_EQ(Error::Read, res.error());
|
||||
EXPECT_TRUE(timeout <= elapsed && elapsed < timeout + threshold);
|
||||
EXPECT_TRUE(timeout <= elapsed && elapsed < timeout + threshold)
|
||||
<< "Timeout exceeded by " << (elapsed - timeout) << "ms";
|
||||
}
|
||||
|
||||
{
|
||||
@ -8326,133 +8324,113 @@ TEST(MaxTimeoutTest, ContentStream) {
|
||||
|
||||
ASSERT_FALSE(res);
|
||||
EXPECT_EQ(Error::Read, res.error());
|
||||
EXPECT_TRUE(timeout <= elapsed && elapsed < timeout + threshold);
|
||||
EXPECT_TRUE(timeout <= elapsed && elapsed < timeout + threshold)
|
||||
<< "Timeout exceeded by " << (elapsed - timeout) << "ms";
|
||||
}
|
||||
}
|
||||
|
||||
TEST(MaxTimeoutTest, ContentStream) {
|
||||
time_t timeout = 2000;
|
||||
time_t threshold = 200;
|
||||
|
||||
Server svr;
|
||||
Client cli("localhost", PORT);
|
||||
max_timeout_test(svr, cli, timeout, threshold);
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(MaxTimeoutTest, ContentStreamSSL) {
|
||||
time_t timeout = 2000;
|
||||
time_t threshold = 500; // SSL_shutdown is slow on some operating systems.
|
||||
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
|
||||
svr.Get("/stream", [&](const Request &, Response &res) {
|
||||
auto data = new std::string("01234567890123456789");
|
||||
|
||||
res.set_content_provider(
|
||||
data->size(), "text/plain",
|
||||
[&, data](size_t offset, size_t length, DataSink &sink) {
|
||||
const size_t DATA_CHUNK_SIZE = 4;
|
||||
const auto &d = *data;
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
|
||||
return true;
|
||||
},
|
||||
[data](bool success) {
|
||||
EXPECT_FALSE(success);
|
||||
delete data;
|
||||
});
|
||||
});
|
||||
|
||||
svr.Get("/stream_without_length", [&](const Request &, Response &res) {
|
||||
auto i = new size_t(0);
|
||||
|
||||
res.set_content_provider(
|
||||
"text/plain",
|
||||
[i](size_t, DataSink &sink) {
|
||||
if (*i < 5) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
sink.write("abcd", 4);
|
||||
(*i)++;
|
||||
} else {
|
||||
sink.done();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[i](bool success) {
|
||||
EXPECT_FALSE(success);
|
||||
delete i;
|
||||
});
|
||||
});
|
||||
|
||||
svr.Get("/chunked", [&](const Request &, Response &res) {
|
||||
auto i = new size_t(0);
|
||||
|
||||
res.set_chunked_content_provider(
|
||||
"text/plain",
|
||||
[i](size_t, DataSink &sink) {
|
||||
if (*i < 5) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
sink.os << "abcd";
|
||||
(*i)++;
|
||||
} else {
|
||||
sink.done();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[i](bool success) {
|
||||
EXPECT_FALSE(success);
|
||||
delete i;
|
||||
});
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
const time_t timeout = 2000;
|
||||
const time_t threshold = 1000; // SSL_shutdown is slow...
|
||||
|
||||
SSLClient cli("localhost", PORT);
|
||||
cli.enable_server_certificate_verification(false);
|
||||
cli.set_max_timeout(std::chrono::milliseconds(timeout));
|
||||
|
||||
{
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
|
||||
auto res = cli.Get("/stream");
|
||||
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - start)
|
||||
.count();
|
||||
|
||||
ASSERT_FALSE(res);
|
||||
EXPECT_EQ(Error::Read, res.error());
|
||||
EXPECT_TRUE(timeout <= elapsed && elapsed < timeout + threshold);
|
||||
}
|
||||
|
||||
{
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
|
||||
auto res = cli.Get("/stream_without_length");
|
||||
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - start)
|
||||
.count();
|
||||
|
||||
ASSERT_FALSE(res);
|
||||
EXPECT_EQ(Error::Read, res.error());
|
||||
EXPECT_TRUE(timeout <= elapsed && elapsed < timeout + threshold);
|
||||
}
|
||||
|
||||
{
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
|
||||
auto res = cli.Get("/chunked", [&](const char *data, size_t data_length) {
|
||||
EXPECT_EQ("abcd", string(data, data_length));
|
||||
return true;
|
||||
});
|
||||
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - start)
|
||||
.count();
|
||||
|
||||
ASSERT_FALSE(res);
|
||||
EXPECT_EQ(Error::Read, res.error());
|
||||
EXPECT_TRUE(timeout <= elapsed && elapsed < timeout + threshold);
|
||||
}
|
||||
max_timeout_test(svr, cli, timeout, threshold);
|
||||
}
|
||||
#endif
|
||||
|
||||
class EventDispatcher {
|
||||
public:
|
||||
EventDispatcher() {}
|
||||
|
||||
void wait_event(DataSink *sink) {
|
||||
unique_lock<mutex> lk(m_);
|
||||
int id = id_;
|
||||
cv_.wait(lk, [&] { return cid_ == id; });
|
||||
sink->write(message_.data(), message_.size());
|
||||
}
|
||||
|
||||
void send_event(const string &message) {
|
||||
lock_guard<mutex> lk(m_);
|
||||
cid_ = id_++;
|
||||
message_ = message;
|
||||
cv_.notify_all();
|
||||
}
|
||||
|
||||
private:
|
||||
mutex m_;
|
||||
condition_variable cv_;
|
||||
atomic_int id_{0};
|
||||
atomic_int cid_{-1};
|
||||
string message_;
|
||||
};
|
||||
|
||||
TEST(ClientInThreadTest, Issue2068) {
|
||||
EventDispatcher ed;
|
||||
|
||||
Server svr;
|
||||
svr.Get("/event1", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_chunked_content_provider("text/event-stream",
|
||||
[&](size_t /*offset*/, DataSink &sink) {
|
||||
ed.wait_event(&sink);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen(HOST, PORT); });
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
thread event_thread([&] {
|
||||
int id = 0;
|
||||
while (svr.is_running()) {
|
||||
this_thread::sleep_for(chrono::milliseconds(500));
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "data: " << id << "\n\n";
|
||||
ed.send_event(ss.str());
|
||||
id++;
|
||||
}
|
||||
});
|
||||
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
|
||||
listen_thread.join();
|
||||
event_thread.join();
|
||||
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
{
|
||||
auto client = detail::make_unique<Client>(HOST, PORT);
|
||||
client->set_read_timeout(std::chrono::minutes(10));
|
||||
|
||||
std::atomic<bool> stop{false};
|
||||
|
||||
std::thread t([&] {
|
||||
client->Get("/event1",
|
||||
[&](const char *, size_t) -> bool { return !stop; });
|
||||
});
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
stop = true;
|
||||
client->stop();
|
||||
client.reset();
|
||||
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,8 +5,7 @@
|
||||
using namespace std;
|
||||
using namespace httplib;
|
||||
|
||||
template <typename T>
|
||||
void ProxyTest(T& cli, bool basic) {
|
||||
template <typename T> void ProxyTest(T &cli, bool basic) {
|
||||
cli.set_proxy("localhost", basic ? 3128 : 3129);
|
||||
auto res = cli.Get("/httpbin/get");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
@ -38,7 +37,7 @@ TEST(ProxyTest, SSLDigest) {
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
template <typename T>
|
||||
void RedirectProxyText(T& cli, const char *path, bool basic) {
|
||||
void RedirectProxyText(T &cli, const char *path, bool basic) {
|
||||
cli.set_proxy("localhost", basic ? 3128 : 3129);
|
||||
if (basic) {
|
||||
cli.set_proxy_basic_auth("hello", "world");
|
||||
@ -100,8 +99,7 @@ TEST(RedirectTest, YouTubeSSLDigest) {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
template <typename T>
|
||||
void BaseAuthTestFromHTTPWatch(T& cli) {
|
||||
template <typename T> void BaseAuthTestFromHTTPWatch(T &cli) {
|
||||
cli.set_proxy("localhost", 3128);
|
||||
cli.set_proxy_basic_auth("hello", "world");
|
||||
|
||||
@ -112,11 +110,11 @@ void BaseAuthTestFromHTTPWatch(T& cli) {
|
||||
}
|
||||
|
||||
{
|
||||
auto res =
|
||||
cli.Get("/basic-auth/hello/world",
|
||||
{make_basic_authentication_header("hello", "world")});
|
||||
auto res = cli.Get("/basic-auth/hello/world",
|
||||
{make_basic_authentication_header("hello", "world")});
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res->body);
|
||||
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
|
||||
res->body);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
@ -124,7 +122,8 @@ void BaseAuthTestFromHTTPWatch(T& cli) {
|
||||
cli.set_basic_auth("hello", "world");
|
||||
auto res = cli.Get("/basic-auth/hello/world");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res->body);
|
||||
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
|
||||
res->body);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
@ -158,8 +157,7 @@ TEST(BaseAuthTest, SSL) {
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
template <typename T>
|
||||
void DigestAuthTestFromHTTPWatch(T& cli) {
|
||||
template <typename T> void DigestAuthTestFromHTTPWatch(T &cli) {
|
||||
cli.set_proxy("localhost", 3129);
|
||||
cli.set_proxy_digest_auth("hello", "world");
|
||||
|
||||
@ -181,7 +179,8 @@ void DigestAuthTestFromHTTPWatch(T& cli) {
|
||||
for (auto path : paths) {
|
||||
auto res = cli.Get(path.c_str());
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res->body);
|
||||
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
|
||||
res->body);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
@ -216,8 +215,7 @@ TEST(DigestAuthTest, NoSSL) {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
template <typename T>
|
||||
void KeepAliveTest(T& cli, bool basic) {
|
||||
template <typename T> void KeepAliveTest(T &cli, bool basic) {
|
||||
cli.set_proxy("localhost", basic ? 3128 : 3129);
|
||||
if (basic) {
|
||||
cli.set_proxy_basic_auth("hello", "world");
|
||||
@ -249,9 +247,10 @@ void KeepAliveTest(T& cli, bool basic) {
|
||||
"/httpbin/digest-auth/auth-int/hello/world/MD5",
|
||||
};
|
||||
|
||||
for (auto path: paths) {
|
||||
for (auto path : paths) {
|
||||
auto res = cli.Get(path.c_str());
|
||||
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res->body);
|
||||
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
|
||||
res->body);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user