Compare commits

..

No commits in common. "master" and "v0.18.4" have entirely different histories.

18 changed files with 526 additions and 1133 deletions

View File

@ -1,69 +0,0 @@
# SPDX-FileCopyrightText: 2025 Andrea Pappacoda <andrea@pappacoda.it>
# SPDX-License-Identifier: MIT
name: abidiff
on: [push, pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
cancel-in-progress: true
defaults:
run:
shell: sh
jobs:
abi:
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
container:
image: debian:testing
steps:
- name: Install dependencies
run: apt -y --update install --no-install-recommends
abigail-tools
ca-certificates
g++
git
libbrotli-dev
libssl-dev
meson
pkg-config
python3
zlib1g-dev
- uses: actions/checkout@v4
with:
path: current
- uses: actions/checkout@v4
with:
path: previous
fetch-depth: 0
- name: Checkout previous
working-directory: previous
run: |
git switch master
git describe --tags --abbrev=0 master | xargs git checkout
- name: Build current
working-directory: current
run: |
meson setup --buildtype=debug -Dcpp-httplib_compile=true build
ninja -C build
- name: Build previous
working-directory: previous
run: |
meson setup --buildtype=debug -Dcpp-httplib_compile=true build
ninja -C build
- name: Run abidiff
run: abidiff
--headers-dir1 previous/build
--headers-dir2 current/build
previous/build/libcpp-httplib.so
current/build/libcpp-httplib.so

View File

@ -1,11 +1,5 @@
name: CIFuzz name: CIFuzz
on: [pull_request] on: [pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
cancel-in-progress: true
jobs: jobs:
Fuzzing: Fuzzing:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@ -1,52 +1,10 @@
name: test name: test
on: on: [push, pull_request]
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: jobs:
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: ubuntu:
runs-on: ubuntu-latest 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: steps:
- name: checkout - name: checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -59,11 +17,6 @@ jobs:
macos: macos:
runs-on: macos-latest runs-on: macos-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_macos == 'true')
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -74,19 +27,6 @@ jobs:
windows: windows:
runs-on: windows-latest runs-on: windows-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_windows == 'true')
strategy:
matrix:
config:
- with_ssl: false
name: without SSL
- with_ssl: true
name: with SSL
name: windows ${{ matrix.config.name }}
steps: steps:
- name: Prepare Git for Checkout on Windows - name: Prepare Git for Checkout on Windows
run: | run: |
@ -102,25 +42,24 @@ jobs:
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
- name: Setup msbuild on windows - name: Setup msbuild on windows
uses: microsoft/setup-msbuild@v2 uses: microsoft/setup-msbuild@v2
- name: Install vcpkg dependencies - name: Install libraries
run: vcpkg install gtest curl zlib brotli run: |
- name: Install OpenSSL vcpkg install gtest curl zlib brotli
if: ${{ matrix.config.with_ssl }} choco install openssl
run: choco install openssl
- name: Configure CMake ${{ matrix.config.name }} - name: Configure CMake with SSL
run: > 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
cmake -B build -S . - name: Build with with SSL
-DCMAKE_BUILD_TYPE=Release run: cmake --build build --config Release
-DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake - name: Run tests with SSL
-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 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=ON -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: env:
VCPKG_ROOT: "C:/vcpkg" VCPKG_ROOT: "C:/vcpkg"
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"

1
.gitignore vendored
View File

@ -23,7 +23,6 @@ test/test.xcodeproj/*/xcuser*
test/*.o test/*.o
test/*.pem test/*.pem
test/*.srl test/*.srl
test/_build_*
work/ work/
benchmark/server* benchmark/server*

View File

@ -2,7 +2,7 @@ FROM yhirose4dockerhub/ubuntu-builder AS builder
WORKDIR /build WORKDIR /build
COPY httplib.h . COPY httplib.h .
COPY docker/main.cc . COPY docker/main.cc .
RUN g++ -std=c++23 -static -o server -O2 -I. main.cc && strip server RUN g++ -std=c++23 -static -o server -O2 -I. -DCPPHTTPLIB_USE_POLL main.cc && strip server
FROM scratch FROM scratch
COPY --from=builder /build/server /server COPY --from=builder /build/server /server

View File

@ -39,10 +39,10 @@ svr.listen("0.0.0.0", 8080);
#include "path/to/httplib.h" #include "path/to/httplib.h"
// HTTP // HTTP
httplib::Client cli("http://yhirose.github.io"); httplib::Client cli("http://cpp-httplib-server.yhirose.repl.co");
// HTTPS // HTTPS
httplib::Client cli("https://yhirose.github.io"); httplib::Client cli("https://cpp-httplib-server.yhirose.repl.co");
auto res = cli.Get("/hi"); auto res = cli.Get("/hi");
res->status; res->status;
@ -125,21 +125,6 @@ int main(void)
res.set_content(req.body, "text/plain"); res.set_content(req.body, "text/plain");
}); });
// If the handler takes time to finish, you can also poll the connection state
svr.Get("/task", [&](const Request& req, Response& res) {
const char * result = nullptr;
process.run(); // for example, starting an external process
while (result == nullptr) {
sleep(1);
if (req.is_connection_closed()) {
process.kill(); // kill the process
return;
}
result = process.stdout(); // != nullptr if the process finishes
}
res.set_content(result, "text/plain");
});
svr.Get("/stop", [&](const Request& req, Response& res) { svr.Get("/stop", [&](const Request& req, Response& res) {
svr.stop(); svr.stop();
}); });
@ -406,11 +391,11 @@ svr.Get("/chunked", [&](const Request& req, Response& res) {
```cpp ```cpp
svr.Get("/content", [&](const Request &req, Response &res) { svr.Get("/content", [&](const Request &req, Response &res) {
res.set_file_content("./path/to/content.html"); res.set_file_content("./path/to/conent.html");
}); });
svr.Get("/content", [&](const Request &req, Response &res) { svr.Get("/content", [&](const Request &req, Response &res) {
res.set_file_content("./path/to/content", "text/html"); res.set_file_content("./path/to/conent", "text/html");
}); });
``` ```
@ -462,7 +447,7 @@ Please see [Server example](https://github.com/yhirose/cpp-httplib/blob/master/e
### Default thread pool support ### Default thread pool support
`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`. `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`.
If you want to set the thread count at runtime, there is no convenient way... But here is how. If you want to set the thread count at runtime, there is no convenient way... But here is how.
@ -654,9 +639,6 @@ res = cli.Options("/resource/foo");
cli.set_connection_timeout(0, 300000); // 300 milliseconds cli.set_connection_timeout(0, 300000); // 300 milliseconds
cli.set_read_timeout(5, 0); // 5 seconds cli.set_read_timeout(5, 0); // 5 seconds
cli.set_write_timeout(5, 0); // 5 seconds cli.set_write_timeout(5, 0); // 5 seconds
// This method works the same as curl's `--max-timeout` option
svr.set_max_timeout(5000); // 5 seconds
``` ```
### Receive content with a content receiver ### Receive content with a content receiver
@ -843,7 +825,7 @@ Please see https://github.com/google/brotli for more detail.
### Default `Accept-Encoding` value ### Default `Accept-Encoding` value
The default `Accept-Encoding` value contains all possible compression types. So, the following two examples are same. The default `Acdcept-Encoding` value contains all possible compression types. So, the following two examples are same.
```c++ ```c++
res = cli.Get("/resource/foo"); res = cli.Get("/resource/foo");
@ -872,6 +854,11 @@ 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 Unix Domain Socket Support
-------------------------- --------------------------
@ -879,7 +866,7 @@ Unix Domain Socket support is available on Linux and macOS.
```c++ ```c++
// Server // Server
httplib::Server svr; httplib::Server svr("./my-socket.sock");
svr.set_address_family(AF_UNIX).listen("./my-socket.sock", 80); svr.set_address_family(AF_UNIX).listen("./my-socket.sock", 80);
// Client // Client
@ -929,6 +916,9 @@ From Docker Hub
```bash ```bash
> docker run --rm -it -p 8080:80 -v ./docker/html:/html yhirose4dockerhub/cpp-httplib-server > docker run --rm -it -p 8080:80 -v ./docker/html:/html yhirose4dockerhub/cpp-httplib-server
...
> docker run --init --rm -it -p 8080:80 -v ./docker/html:/html cpp-httplib-server
Serving HTTP on 0.0.0.0 port 80 ... Serving HTTP on 0.0.0.0 port 80 ...
192.168.65.1 - - [31/Aug/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1" 192.168.65.1 - - [31/Aug/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1"
192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..." 192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..."
@ -961,12 +951,12 @@ Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32
> cpp-httplib officially supports only the latest Visual Studio. It might work with former versions of Visual Studio, but I can no longer verify it. Pull requests are always welcome for the older versions of Visual Studio unless they break the C++11 conformance. > cpp-httplib officially supports only the latest Visual Studio. It might work with former versions of Visual Studio, but I can no longer verify it. Pull requests are always welcome for the older versions of Visual Studio unless they break the C++11 conformance.
> [!NOTE] > [!NOTE]
> Windows 8 or lower, Visual Studio 2015 or lower, and Cygwin and MSYS2 including MinGW are neither supported nor tested. > Windows 8 or lower, Visual Studio 2013 or lower, and Cygwin and MSYS2 including MinGW are neither supported nor tested.
License License
------- -------
MIT license (© 2025 Yuji Hirose) MIT license (© 2024 Yuji Hirose)
Special Thanks To Special Thanks To
----------------- -----------------

View File

@ -1,7 +1,7 @@
// //
// httplib.h // httplib.h
// //
// Copyright (c) 2025 Yuji Hirose. All rights reserved. // Copyright (c) 2024 Yuji Hirose. All rights reserved.
// MIT License // MIT License
// //

View File

@ -1,7 +1,7 @@
// //
// main.cc // main.cc
// //
// Copyright (c) 2025 Yuji Hirose. All rights reserved. // Copyright (c) 2024 Yuji Hirose. All rights reserved.
// MIT License // MIT License
// //
@ -41,7 +41,7 @@ std::string log(auto &req, auto &res) {
auto http_referer = "-"; // TODO: auto http_referer = "-"; // TODO:
auto http_user_agent = req.get_header_value("User-Agent", "-"); auto http_user_agent = req.get_header_value("User-Agent", "-");
// NOTE: From NGINX default access log format // NOTE: From NGINX defualt access log format
// log_format combined '$remote_addr - $remote_user [$time_local] ' // log_format combined '$remote_addr - $remote_user [$time_local] '
// '"$request" $status $body_bytes_sent ' // '"$request" $status $body_bytes_sent '
// '"$http_referer" "$http_user_agent"'; // '"$http_referer" "$http_user_agent"';

View File

@ -1,7 +1,7 @@
// //
// server_and_client.cc // server_and_client.cc
// //
// Copyright (c) 2025 Yuji Hirose. All rights reserved. // Copyright (c) 2024 Yuji Hirose. All rights reserved.
// MIT License // MIT License
// //

View File

@ -12,7 +12,8 @@ using namespace std;
class EventDispatcher { class EventDispatcher {
public: public:
EventDispatcher() {} EventDispatcher() {
}
void wait_event(DataSink *sink) { void wait_event(DataSink *sink) {
unique_lock<mutex> lk(m_); unique_lock<mutex> lk(m_);

930
httplib.h

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,7 @@ endif()
find_package(CURL REQUIRED) find_package(CURL REQUIRED)
add_executable(httplib-test test.cc include_httplib.cc $<$<BOOL:${WIN32}>:include_windows_h.cc>) add_executable(httplib-test test.cc)
target_compile_options(httplib-test PRIVATE "$<$<CXX_COMPILER_ID:MSVC>:/utf-8;/bigobj>") 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) target_link_libraries(httplib-test PRIVATE httplib GTest::gtest_main CURL::libcurl)
gtest_discover_tests(httplib-test) gtest_discover_tests(httplib-test)

View File

@ -28,11 +28,6 @@ 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. # OSS-Fuzz will define its own value for LIB_FUZZING_ENGINE.
LIB_FUZZING_ENGINE ?= standalone_fuzz_target_runner.o 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 all : test test_split
./test ./test
@ -47,31 +42,6 @@ test : test.cc include_httplib.cc ../httplib.h Makefile cert.pem
test_split : test.cc ../httplib.h httplib.cc Makefile cert.pem test_split : test.cc ../httplib.h httplib.cc Makefile cert.pem
$(CXX) -o $@ $(CXXFLAGS) test.cc httplib.cc $(TEST_ARGS) $(CXX) -o $@ $(CXXFLAGS) test.cc httplib.cc $(TEST_ARGS)
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 test_proxy : test_proxy.cc ../httplib.h Makefile cert.pem
$(CXX) -o $@ -I.. $(CXXFLAGS) test_proxy.cc $(TEST_ARGS) $(CXX) -o $@ -I.. $(CXXFLAGS) test_proxy.cc $(TEST_ARGS)
@ -96,5 +66,5 @@ cert.pem:
./gen-certs.sh ./gen-certs.sh
clean: clean:
rm -rf test test_split test_proxy server_fuzzer *.pem *.0 *.o *.1 *.srl httplib.h httplib.cc _build* *.dSYM rm -f test test_split test_proxy server_fuzzer *.pem *.0 *.o *.1 *.srl httplib.h httplib.cc

View File

@ -25,9 +25,7 @@ public:
bool is_readable() const override { return true; } bool is_readable() const override { return true; }
bool wait_readable() const override { return true; } bool is_writable() const override { return true; }
bool wait_writable() const override { return true; }
void get_remote_ip_and_port(std::string &ip, int &port) const override { void get_remote_ip_and_port(std::string &ip, int &port) const override {
ip = "127.0.0.1"; ip = "127.0.0.1";
@ -41,8 +39,6 @@ public:
socket_t socket() const override { return 0; } socket_t socket() const override { return 0; }
time_t duration() const override { return 0; };
private: private:
const uint8_t *data_; const uint8_t *data_;
size_t size_; size_t size_;

View File

@ -1,6 +0,0 @@
// Test if including windows.h conflicts with httplib.h
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <httplib.h>

View File

@ -1,28 +0,0 @@
#!/usr/bin/env bash
if [ "$#" -ne 1 ]; then
echo "Usage: $0 build_dir"
exit 1
fi
BUILD_DIR=$1
# Make the build directory
rm -rf $BUILD_DIR
mkdir -p $BUILD_DIR/out
cd $BUILD_DIR
# Build the version
git checkout $BUILD_DIR -q
cmake \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_FLAGS="-g -Og" \
-DBUILD_SHARED_LIBS=ON \
-DHTTPLIB_COMPILE=ON \
-DCMAKE_INSTALL_PREFIX=./out \
../..
cmake --build . --target install
cmake --build . --target clean

View File

@ -156,28 +156,27 @@ TEST_F(UnixSocketTest, abstract) {
} }
#endif #endif
TEST(SocketStream, wait_writable_UNIX) { TEST(SocketStream, is_writable_UNIX) {
int fds[2]; int fds[2];
ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds)); ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds));
const auto asSocketStream = [&](socket_t fd, const auto asSocketStream = [&](socket_t fd,
std::function<bool(Stream &)> func) { std::function<bool(Stream &)> func) {
return detail::process_client_socket( return detail::process_client_socket(fd, 0, 0, 0, 0, func);
fd, 0, 0, 0, 0, 0, std::chrono::steady_clock::time_point::min(), func);
}; };
asSocketStream(fds[0], [&](Stream &s0) { asSocketStream(fds[0], [&](Stream &s0) {
EXPECT_EQ(s0.socket(), fds[0]); EXPECT_EQ(s0.socket(), fds[0]);
EXPECT_TRUE(s0.wait_writable()); EXPECT_TRUE(s0.is_writable());
EXPECT_EQ(0, close(fds[1])); EXPECT_EQ(0, close(fds[1]));
EXPECT_FALSE(s0.wait_writable()); EXPECT_FALSE(s0.is_writable());
return true; return true;
}); });
EXPECT_EQ(0, close(fds[0])); EXPECT_EQ(0, close(fds[0]));
} }
TEST(SocketStream, wait_writable_INET) { TEST(SocketStream, is_writable_INET) {
sockaddr_in addr; sockaddr_in addr;
memset(&addr, 0, sizeof(addr)); memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; addr.sin_family = AF_INET;
@ -207,12 +206,11 @@ TEST(SocketStream, wait_writable_INET) {
const auto asSocketStream = [&](socket_t fd, const auto asSocketStream = [&](socket_t fd,
std::function<bool(Stream &)> func) { std::function<bool(Stream &)> func) {
return detail::process_client_socket( return detail::process_client_socket(fd, 0, 0, 0, 0, func);
fd, 0, 0, 0, 0, 0, std::chrono::steady_clock::time_point::min(), func);
}; };
asSocketStream(disconnected_svr_sock, [&](Stream &ss) { asSocketStream(disconnected_svr_sock, [&](Stream &ss) {
EXPECT_EQ(ss.socket(), disconnected_svr_sock); EXPECT_EQ(ss.socket(), disconnected_svr_sock);
EXPECT_FALSE(ss.wait_writable()); EXPECT_FALSE(ss.is_writable());
return true; return true;
}); });
@ -513,15 +511,6 @@ TEST(GetHeaderValueTest, RegularValueInt) {
EXPECT_EQ(100ull, val); EXPECT_EQ(100ull, val);
} }
TEST(GetHeaderValueTest, RegularInvalidValueInt) {
Headers headers = {{"Content-Length", "x"}};
auto is_invalid_value = false;
auto val = detail::get_header_value_u64(headers, "Content-Length", 0, 0,
is_invalid_value);
EXPECT_EQ(0ull, val);
EXPECT_TRUE(is_invalid_value);
}
TEST(GetHeaderValueTest, Range) { TEST(GetHeaderValueTest, Range) {
{ {
Headers headers = {make_range_header({{1, -1}})}; Headers headers = {make_range_header({{1, -1}})};
@ -1865,32 +1854,6 @@ TEST(PathUrlEncodeTest, PathUrlEncode) {
} }
} }
TEST(PathUrlEncodeTest, IncludePercentEncodingLF) {
Server svr;
svr.Get("/", [](const Request &req, Response &) {
EXPECT_EQ("\x0A", req.get_param_value("something"));
});
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
});
svr.wait_until_ready();
{
Client cli(HOST, PORT);
cli.set_url_encode(false);
auto res = cli.Get("/?something=%0A");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
}
}
TEST(BindServerTest, DISABLED_BindDualStack) { TEST(BindServerTest, DISABLED_BindDualStack) {
Server svr; Server svr;
@ -2979,11 +2942,6 @@ protected:
res.status = 401; res.status = 401;
res.set_header("WWW-Authenticate", "Basic realm=123456"); res.set_header("WWW-Authenticate", "Basic realm=123456");
}) })
.Delete("/issue609",
[](const httplib::Request &, httplib::Response &res,
const httplib::ContentReader &) {
res.set_content("ok", "text/plain");
})
#if defined(CPPHTTPLIB_ZLIB_SUPPORT) || defined(CPPHTTPLIB_BROTLI_SUPPORT) #if defined(CPPHTTPLIB_ZLIB_SUPPORT) || defined(CPPHTTPLIB_BROTLI_SUPPORT)
.Get("/compress", .Get("/compress",
[&](const Request & /*req*/, Response &res) { [&](const Request & /*req*/, Response &res) {
@ -3541,7 +3499,7 @@ TEST_F(ServerTest, LongRequest) {
TEST_F(ServerTest, TooLongRequest) { TEST_F(ServerTest, TooLongRequest) {
std::string request; std::string request;
for (size_t i = 0; i < 546; i++) { for (size_t i = 0; i < 545; i++) {
request += "/TooLongRequest"; request += "/TooLongRequest";
} }
request += "_NG"; request += "_NG";
@ -3552,19 +3510,6 @@ TEST_F(ServerTest, TooLongRequest) {
EXPECT_EQ(StatusCode::UriTooLong_414, res->status); EXPECT_EQ(StatusCode::UriTooLong_414, res->status);
} }
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');
auto res = cli_.Get(request.c_str());
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::NotFound_404, res->status);
}
TEST_F(ServerTest, LongHeader) { TEST_F(ServerTest, LongHeader) {
Request req; Request req;
req.method = "GET"; req.method = "GET";
@ -4068,13 +4013,6 @@ TEST_F(ServerTest, Issue1772) {
EXPECT_EQ(StatusCode::Unauthorized_401, res->status); EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
} }
TEST_F(ServerTest, Issue609) {
auto res = cli_.Delete("/issue609");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
EXPECT_EQ(std::string("ok"), res->body);
}
TEST_F(ServerTest, GetStreamedChunked) { TEST_F(ServerTest, GetStreamedChunked) {
auto res = cli_.Get("/streamed-chunked"); auto res = cli_.Get("/streamed-chunked");
ASSERT_TRUE(res); ASSERT_TRUE(res);
@ -4919,8 +4857,7 @@ static bool send_request(time_t read_timeout_sec, const std::string &req,
if (client_sock == INVALID_SOCKET) { return false; } if (client_sock == INVALID_SOCKET) { return false; }
auto ret = detail::process_client_socket( auto ret = detail::process_client_socket(
client_sock, read_timeout_sec, 0, 0, 0, 0, client_sock, read_timeout_sec, 0, 0, 0, [&](Stream &strm) {
std::chrono::steady_clock::time_point::min(), [&](Stream &strm) {
if (req.size() != if (req.size() !=
static_cast<size_t>(strm.write(req.data(), req.size()))) { static_cast<size_t>(strm.write(req.data(), req.size()))) {
return false; return false;
@ -4964,10 +4901,8 @@ TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
"Connection: close\r\n" "Connection: close\r\n"
"\r\n"; "\r\n";
std::string res; ASSERT_TRUE(send_request(5, req));
ASSERT_TRUE(send_request(5, req, &res)); EXPECT_EQ(header_value, "\v bar \x1B");
EXPECT_EQ(header_value, "");
EXPECT_EQ("HTTP/1.1 400 Bad Request", res.substr(0, 24));
} }
// Sends a raw request and verifies that there isn't a crash or exception. // Sends a raw request and verifies that there isn't a crash or exception.
@ -5125,14 +5060,6 @@ TEST(ServerRequestParsingTest, InvalidFieldValueContains_CR_LF_NUL) {
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24)); EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
} }
TEST(ServerRequestParsingTest, InvalidFieldValueContains_LF) {
std::string out;
std::string request(
"GET /header_field_value_check HTTP/1.1\r\nTest: [\n\n\n]\r\n\r\n", 55);
test_raw_request(request, &out);
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
}
TEST(ServerRequestParsingTest, EmptyFieldValue) { TEST(ServerRequestParsingTest, EmptyFieldValue) {
std::string out; std::string out;
@ -6217,7 +6144,7 @@ TEST(SSLClientTest, WildcardHostNameMatch_Online) {
ASSERT_EQ(StatusCode::OK_200, res->status); ASSERT_EQ(StatusCode::OK_200, res->status);
} }
TEST(SSLClientTest, Issue2004_Online) { TEST(SSLClientTest, Issue2004) {
Client client("https://google.com"); Client client("https://google.com");
client.set_follow_location(true); client.set_follow_location(true);
@ -7569,9 +7496,9 @@ TEST(MultipartFormDataTest, CloseDelimiterWithoutCRLF) {
"text2" "text2"
"\r\n------------"; "\r\n------------";
std::string response; std::string resonse;
ASSERT_TRUE(send_request(1, req, &response)); ASSERT_TRUE(send_request(1, req, &resonse));
ASSERT_EQ("200", response.substr(9, 3)); ASSERT_EQ("200", resonse.substr(9, 3));
} }
TEST(MultipartFormDataTest, ContentLength) { TEST(MultipartFormDataTest, ContentLength) {
@ -7616,10 +7543,11 @@ TEST(MultipartFormDataTest, ContentLength) {
"text2" "text2"
"\r\n------------\r\n"; "\r\n------------\r\n";
std::string response; std::string resonse;
ASSERT_TRUE(send_request(1, req, &response)); ASSERT_TRUE(send_request(1, req, &resonse));
ASSERT_EQ("200", response.substr(9, 3)); ASSERT_EQ("200", resonse.substr(9, 3));
} }
#endif #endif
TEST(TaskQueueTest, IncreaseAtomicInteger) { TEST(TaskQueueTest, IncreaseAtomicInteger) {
@ -8022,7 +7950,7 @@ TEST(InvalidHeaderCharsTest, is_field_value) {
EXPECT_FALSE(detail::fields::is_field_value(" example_token")); EXPECT_FALSE(detail::fields::is_field_value(" example_token"));
EXPECT_FALSE(detail::fields::is_field_value("example_token ")); EXPECT_FALSE(detail::fields::is_field_value("example_token "));
EXPECT_TRUE(detail::fields::is_field_value("token@123")); EXPECT_TRUE(detail::fields::is_field_value("token@123"));
EXPECT_TRUE(detail::fields::is_field_value("")); EXPECT_FALSE(detail::fields::is_field_value(""));
EXPECT_FALSE(detail::fields::is_field_value("example\rtoken")); EXPECT_FALSE(detail::fields::is_field_value("example\rtoken"));
EXPECT_FALSE(detail::fields::is_field_value("example\ntoken")); EXPECT_FALSE(detail::fields::is_field_value("example\ntoken"));
EXPECT_FALSE(detail::fields::is_field_value(std::string("\0", 1))); EXPECT_FALSE(detail::fields::is_field_value(std::string("\0", 1)));
@ -8079,32 +8007,6 @@ TEST(InvalidHeaderCharsTest, OnServer) {
} }
} }
TEST(InvalidHeaderValueTest, InvalidContentLength) {
auto handled = false;
Server svr;
svr.Post("/test", [&](const Request &, Response &) { handled = true; });
thread t = thread([&] { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
ASSERT_FALSE(svr.is_running());
ASSERT_FALSE(handled);
});
svr.wait_until_ready();
auto req = "POST /test HTTP/1.1\r\n"
"Content-Length: x\r\n"
"\r\n";
std::string response;
ASSERT_TRUE(send_request(1, req, &response));
ASSERT_EQ("HTTP/1.1 400 Bad Request",
response.substr(0, response.find("\r\n")));
}
#ifndef _WIN32 #ifndef _WIN32
TEST(Expect100ContinueTest, ServerClosesConnection) { TEST(Expect100ContinueTest, ServerClosesConnection) {
static constexpr char reject[] = "Unauthorized"; static constexpr char reject[] = "Unauthorized";
@ -8206,231 +8108,3 @@ TEST(Expect100ContinueTest, ServerClosesConnection) {
} }
} }
#endif #endif
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");
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();
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)
<< "Timeout exceeded by " << (elapsed - timeout) << "ms";
}
{
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)
<< "Timeout exceeded by " << (elapsed - timeout) << "ms";
}
{
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)
<< "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);
SSLClient cli("localhost", PORT);
cli.enable_server_certificate_verification(false);
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,7 +5,8 @@
using namespace std; using namespace std;
using namespace httplib; 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); cli.set_proxy("localhost", basic ? 3128 : 3129);
auto res = cli.Get("/httpbin/get"); auto res = cli.Get("/httpbin/get");
ASSERT_TRUE(res != nullptr); ASSERT_TRUE(res != nullptr);
@ -37,7 +38,7 @@ TEST(ProxyTest, SSLDigest) {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
template <typename T> 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); cli.set_proxy("localhost", basic ? 3128 : 3129);
if (basic) { if (basic) {
cli.set_proxy_basic_auth("hello", "world"); cli.set_proxy_basic_auth("hello", "world");
@ -99,7 +100,8 @@ 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("localhost", 3128);
cli.set_proxy_basic_auth("hello", "world"); cli.set_proxy_basic_auth("hello", "world");
@ -110,11 +112,11 @@ template <typename T> void BaseAuthTestFromHTTPWatch(T &cli) {
} }
{ {
auto res = cli.Get("/basic-auth/hello/world", auto res =
cli.Get("/basic-auth/hello/world",
{make_basic_authentication_header("hello", "world")}); {make_basic_authentication_header("hello", "world")});
ASSERT_TRUE(res != nullptr); ASSERT_TRUE(res != nullptr);
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res->body);
res->body);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
} }
@ -122,8 +124,7 @@ template <typename T> void BaseAuthTestFromHTTPWatch(T &cli) {
cli.set_basic_auth("hello", "world"); cli.set_basic_auth("hello", "world");
auto res = cli.Get("/basic-auth/hello/world"); auto res = cli.Get("/basic-auth/hello/world");
ASSERT_TRUE(res != nullptr); ASSERT_TRUE(res != nullptr);
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res->body);
res->body);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
} }
@ -157,7 +158,8 @@ TEST(BaseAuthTest, SSL) {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT #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("localhost", 3129);
cli.set_proxy_digest_auth("hello", "world"); cli.set_proxy_digest_auth("hello", "world");
@ -179,8 +181,7 @@ template <typename T> void DigestAuthTestFromHTTPWatch(T &cli) {
for (auto path : paths) { for (auto path : paths) {
auto res = cli.Get(path.c_str()); auto res = cli.Get(path.c_str());
ASSERT_TRUE(res != nullptr); ASSERT_TRUE(res != nullptr);
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res->body);
res->body);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
} }
@ -215,7 +216,8 @@ 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); cli.set_proxy("localhost", basic ? 3128 : 3129);
if (basic) { if (basic) {
cli.set_proxy_basic_auth("hello", "world"); cli.set_proxy_basic_auth("hello", "world");
@ -247,10 +249,9 @@ template <typename T> void KeepAliveTest(T &cli, bool basic) {
"/httpbin/digest-auth/auth-int/hello/world/MD5", "/httpbin/digest-auth/auth-int/hello/world/MD5",
}; };
for (auto path : paths) { for (auto path: paths) {
auto res = cli.Get(path.c_str()); auto res = cli.Get(path.c_str());
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", res->body);
res->body);
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
} }
} }