Compare commits

...

22 Commits

Author SHA1 Message Date
yhirose
71ba7e7b1b
Fix #2068 (#2080)
* Fix #2068

* Add unit test
2025-02-20 23:45:21 -05:00
Florian Albrechtskirchinger
ebe7efa1cc
Parallelize testing with/without SSL on Windows & set concurrency group (#2079)
* Parallelize testing with/without SSL on Windows

* Set concurrency group in workflows
2025-02-20 20:57:18 -05:00
Florian Albrechtskirchinger
22d90c29b4
Remove select() and use poll() (#2078)
* Revert "Fix typo in meson.build (#2070)"

This reverts commit 5c0135fa5d.

* Revert "build(meson): automatically use poll or select as needed (#2067)"

This reverts commit 2b5d1eea8d.

* Revert "Make poll() the default (#2065)"

This reverts commit 6e73a63153.

* Remove select() and use poll()
2025-02-20 18:51:35 -05:00
Florian Albrechtskirchinger
b944f942ee
Correct default thread pool size in README.md (#2077) 2025-02-20 12:59:38 -05:00
Florian Albrechtskirchinger
550f728165
Refactor streams: rename is_* to wait_* for clarity (#2069)
- Replace is_readable() with wait_readable() and is_writable() with
  wait_writable() in the Stream interface.
- Implement a new is_readable() function with semantics that more
  closely reflect its name. It returns immediately whether data is
  available for reading, without waiting.
- Update call sites of is_writable(), removing redundant checks.
2025-02-20 12:56:39 -05:00
yhirose
a4b2c61a65
Max timeout test refactoring (#2071)
* Simplify code

* Adjust threshold
2025-02-19 22:19:02 -05:00
Florian Albrechtskirchinger
5c0135fa5d
Fix typo in meson.build (#2070) 2025-02-19 16:20:44 -05:00
Andrea Pappacoda
2b5d1eea8d
build(meson): automatically use poll or select as needed (#2067)
Follow-up to 6e73a63153
2025-02-19 12:47:56 -05:00
yhirose
d274c0abe5 Fix typo 2025-02-18 21:33:32 -05:00
yhirose
dda2e007a0 Fixed documentation about Unix Domain Sockt (#2066) 2025-02-18 11:40:50 -05:00
yhirose
321a86d9f2 Add *.dSYM to Makefile clean 2025-02-18 05:56:22 -05:00
yhirose
ada97046a2 Fix misspelled words 2025-02-18 05:54:22 -05:00
Florian Albrechtskirchinger
6e73a63153
Make poll() the default (#2065)
* Make poll() the default

select() can still be enabled by defining CPPHTTPLIB_USE_SELECT.

* Run tests with select() and poll()
2025-02-18 05:23:23 -05:00
Uros Gaber
cdc223019a
server_certificate_verifier extended to reuse built-in verifier (#2064)
* server_certificate_verifier extended to reuse built-in verifier

* code cleanup and SSLVerifierResponse enum clarification as per @falbrechtskirchinger comment

* cleanup

* clang-format

* change local var verification_status_ declaration to auto

* change local var verification_status_ to verification_status

* clang-format

* clang-format

---------

Co-authored-by: UrosG <uros@ub330.net>
2025-02-17 17:24:41 -05:00
Florian Albrechtskirchinger
574f5ce93e
Add style check to workflow (#2062)
* Add style check to workflow

* Add example files to style check
2025-02-17 12:14:53 -05:00
Florian Albrechtskirchinger
2996cecee0
Fix code inconsistently formatted and re-format (#2063)
* Fix code inconsistently formatted by clang-format

* Run clang-format
2025-02-17 12:14:02 -05:00
Florian Albrechtskirchinger
32bf5c9c09
Simplify SSL shutdown (#2059) 2025-02-16 17:38:41 -05:00
Florian Albrechtskirchinger
735e5930eb
Detect additional CMake build failures (#2058)
Add include_httplib.cc to the main test executable (already done in
Makefile), and add include_windows_h.cc to the main test executable on
Windows to test if including windows.h conflicts with httplib.h.
2025-02-16 15:45:28 -05:00
Florian Albrechtskirchinger
748f47b377
Add workflow_dispatch with Google Test filter and OS selection (#2056)
* Add workflow_dispatch with Google Test filter

Add the workflow_dispatch trigger to the test.yaml workflow. Includes an
input for an optional Google Test filter pattern.

* Add OS selection to workflow_dispatch

* Fix wording
2025-02-16 12:34:28 -05:00
Florian Albrechtskirchinger
4cb8ff9f90
Print timeout exceedance in MaxTimeoutTest (#2060) 2025-02-16 08:43:54 -05:00
Florian Albrechtskirchinger
985cd9f6a2
Fix compilation failures with include <windows.h> (#2057) 2025-02-16 08:39:29 -05:00
Florian Albrechtskirchinger
233f0fb1b8
Refactor setting socket options (#2053)
Add detail::set_socket_opt() and detail::set_socket_opt_time() to avoid
repetition of platform-specific code.
2025-02-14 22:40:24 -05:00
14 changed files with 445 additions and 453 deletions

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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"';

View File

@ -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
View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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";

View 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>

View File

@ -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();
}
}

View File

@ -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);
}
}