mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-06-10 16:47:14 +00:00
* Make ThreadPool ctor exception-safe on partial thread creation If std::thread construction throws partway through the ThreadPool constructor (e.g., pthread_create returns EAGAIN under thread-resource pressure), the partially-built threads_ vector would destruct joinable std::thread objects, calling std::terminate(). Wrap the spawn loop and, on failure, signal shutdown to the workers already created, join them, and rethrow. Adds a reproducer test in test_thread_pool.cc that interposes pthread_create at link time to deterministically fail the second call, gated to POSIX + exceptions-enabled builds. Fix #2444 * Strip ASAN from test_thread_pool to coexist with pthread_create override Linux libasan installs its own pthread_create interceptor; our in-binary symbol override sits on top of it and corrupts ASAN's thread bookkeeping, which surfaces as "Joining already joined thread" on the very first test. Disable ASAN for this small unit-test binary -- ThreadPool memory behavior is still exercised under ASAN by the main `test` binary.
319 lines
14 KiB
Makefile
319 lines
14 KiB
Makefile
CXX = clang++
|
|
CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion -Wshadow $(EXTRA_CXXFLAGS) -DCPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO -fsanitize=address # -fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS
|
|
|
|
ifneq ($(OS), Windows_NT)
|
|
UNAME_S := $(shell uname -s)
|
|
ifeq ($(UNAME_S), Darwin)
|
|
PREFIX ?= $(shell brew --prefix)
|
|
OPENSSL_DIR = $(PREFIX)/opt/openssl@3
|
|
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
|
|
OPENSSL_SUPPORT += -framework CoreFoundation -framework Security
|
|
MBEDTLS_DIR ?= $(shell brew --prefix mbedtls@3)
|
|
MBEDTLS_SUPPORT = -DCPPHTTPLIB_MBEDTLS_SUPPORT -I$(MBEDTLS_DIR)/include -L$(MBEDTLS_DIR)/lib -lmbedtls -lmbedx509 -lmbedcrypto
|
|
MBEDTLS_SUPPORT += -framework CoreFoundation -framework Security
|
|
WOLFSSL_DIR ?= $(shell brew --prefix wolfssl)
|
|
WOLFSSL_SUPPORT = -DCPPHTTPLIB_WOLFSSL_SUPPORT -I$(WOLFSSL_DIR)/include -I$(WOLFSSL_DIR)/include/wolfssl -L$(WOLFSSL_DIR)/lib -lwolfssl
|
|
WOLFSSL_SUPPORT += -framework CoreFoundation -framework Security
|
|
else
|
|
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -lssl -lcrypto
|
|
MBEDTLS_SUPPORT = -DCPPHTTPLIB_MBEDTLS_SUPPORT -lmbedtls -lmbedx509 -lmbedcrypto
|
|
WOLFSSL_SUPPORT = -DCPPHTTPLIB_WOLFSSL_SUPPORT -lwolfssl
|
|
ifeq ($(UNAME_S), Linux)
|
|
# Disable ASLR for ASAN compatibility on WSL2 (high-entropy ASLR conflicts with ASAN shadow memory)
|
|
SETARCH = setarch $(shell uname -m) -R
|
|
endif
|
|
endif
|
|
endif
|
|
|
|
ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
|
|
|
|
ifneq ($(OS), Windows_NT)
|
|
UNAME_S := $(shell uname -s)
|
|
ifeq ($(UNAME_S), Darwin)
|
|
# macOS: use Homebrew paths for brotli and zstd
|
|
BROTLI_DIR = $(PREFIX)/opt/brotli
|
|
BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec
|
|
ZSTD_DIR = $(PREFIX)/opt/zstd
|
|
ZSTD_SUPPORT = -DCPPHTTPLIB_ZSTD_SUPPORT -I$(ZSTD_DIR)/include -L$(ZSTD_DIR)/lib -lzstd
|
|
LIBS = -lpthread -lcurl -framework CoreFoundation -framework CFNetwork
|
|
else
|
|
# Linux: use system paths
|
|
BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -lbrotlicommon -lbrotlienc -lbrotlidec
|
|
ZSTD_SUPPORT = -DCPPHTTPLIB_ZSTD_SUPPORT -lzstd
|
|
LIBS = -lpthread -lcurl -lanl
|
|
endif
|
|
endif
|
|
|
|
TEST_ARGS = gtest/src/gtest-all.cc gtest/src/gtest_main.cc -Igtest -Igtest/include $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(ZSTD_SUPPORT) $(LIBS)
|
|
TEST_ARGS_MBEDTLS = gtest/src/gtest-all.cc gtest/src/gtest_main.cc -Igtest -Igtest/include $(MBEDTLS_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(ZSTD_SUPPORT) $(LIBS)
|
|
TEST_ARGS_WOLFSSL = gtest/src/gtest-all.cc gtest/src/gtest_main.cc -Igtest -Igtest/include $(WOLFSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(ZSTD_SUPPORT) $(LIBS)
|
|
TEST_ARGS_NO_TLS = gtest/src/gtest-all.cc gtest/src/gtest_main.cc -Igtest -Igtest/include $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(ZSTD_SUPPORT) $(LIBS)
|
|
|
|
# By default, use standalone_fuzz_target_runner.
|
|
# This runner does no fuzzing, but simply executes the inputs
|
|
# provided via parameters.
|
|
# Run e.g. "make all LIB_FUZZING_ENGINE=/path/to/libFuzzer.a"
|
|
# to link the fuzzer(s) against a real fuzzing engine.
|
|
# 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
|
|
LSAN_OPTIONS=suppressions=lsan_suppressions.txt $(SETARCH) ./test
|
|
|
|
SHARDS ?= 4
|
|
|
|
define run_parallel
|
|
@echo "Running $(1) with $(SHARDS) shards in parallel..."
|
|
@fail=0; pids=""; \
|
|
for i in $$(seq 0 $$(($(SHARDS) - 1))); do \
|
|
GTEST_TOTAL_SHARDS=$(SHARDS) GTEST_SHARD_INDEX=$$i \
|
|
LSAN_OPTIONS=suppressions=lsan_suppressions.txt \
|
|
$(SETARCH) ./$(1) --gtest_color=yes > $(1)_shard_$$i.log 2>&1 & \
|
|
pids="$$pids $$!"; \
|
|
done; \
|
|
exits=""; \
|
|
for pid in $$pids; do \
|
|
wait $$pid; exits="$$exits $$?"; \
|
|
done; \
|
|
i=0; \
|
|
for ec in $$exits; do \
|
|
log=$(1)_shard_$$i.log; \
|
|
if grep -q "\[ PASSED \]" $$log && ! grep -q "\[ FAILED \]" $$log && [ $$ec -eq 0 ]; then \
|
|
passed=$$(grep "\[ PASSED \]" $$log); \
|
|
echo "Shard $$i: $$passed"; \
|
|
else \
|
|
echo "=== Shard $$i FAILED (exit=$$ec) ==="; \
|
|
cat $$log; \
|
|
fail=1; \
|
|
fi; \
|
|
i=$$((i+1)); \
|
|
done; \
|
|
if [ $$fail -ne 0 ]; then exit 1; fi; \
|
|
echo "All shards passed."
|
|
endef
|
|
|
|
.PHONY: test_openssl_parallel test_mbedtls_parallel test_wolfssl_parallel test_no_tls_parallel
|
|
|
|
test_openssl_parallel : test
|
|
$(call run_parallel,test)
|
|
|
|
test_mbedtls_parallel : test_mbedtls
|
|
$(call run_parallel,test_mbedtls)
|
|
|
|
test_wolfssl_parallel : test_wolfssl
|
|
$(call run_parallel,test_wolfssl)
|
|
|
|
test_no_tls_parallel : test_no_tls
|
|
$(call run_parallel,test_no_tls)
|
|
|
|
proxy : test_proxy
|
|
@echo "Starting proxy server..."
|
|
cd proxy && \
|
|
docker compose up -d
|
|
@echo "Waiting for proxy to be ready..."
|
|
@until nc -z localhost 3128 && nc -z localhost 3129; do sleep 1; done
|
|
@echo "Proxy servers are ready, waiting additional 5 seconds for full startup..."
|
|
@sleep 5
|
|
@echo "Checking proxy server status..."
|
|
@cd proxy && docker compose ps
|
|
@echo "Checking proxy server logs..."
|
|
@cd proxy && docker compose logs --tail=20
|
|
@echo "Running proxy tests..."
|
|
./test_proxy; \
|
|
exit_code=$$?; \
|
|
echo "Stopping proxy server..."; \
|
|
cd proxy && docker compose down; \
|
|
exit $$exit_code
|
|
|
|
proxy_mbedtls : test_proxy_mbedtls
|
|
@echo "Starting proxy server..."
|
|
cd proxy && \
|
|
docker compose up -d
|
|
@echo "Waiting for proxy to be ready..."
|
|
@until nc -z localhost 3128 && nc -z localhost 3129; do sleep 1; done
|
|
@echo "Proxy servers are ready, waiting additional 5 seconds for full startup..."
|
|
@sleep 5
|
|
@echo "Checking proxy server status..."
|
|
@cd proxy && docker compose ps
|
|
@echo "Checking proxy server logs..."
|
|
@cd proxy && docker compose logs --tail=20
|
|
@echo "Running proxy tests (Mbed TLS)..."
|
|
./test_proxy_mbedtls; \
|
|
exit_code=$$?; \
|
|
echo "Stopping proxy server..."; \
|
|
cd proxy && docker compose down; \
|
|
exit $$exit_code
|
|
|
|
proxy_wolfssl : test_proxy_wolfssl
|
|
@echo "Starting proxy server..."
|
|
cd proxy && \
|
|
docker compose up -d
|
|
@echo "Waiting for proxy to be ready..."
|
|
@until nc -z localhost 3128 && nc -z localhost 3129; do sleep 1; done
|
|
@echo "Proxy servers are ready, waiting additional 5 seconds for full startup..."
|
|
@sleep 5
|
|
@echo "Checking proxy server status..."
|
|
@cd proxy && docker compose ps
|
|
@echo "Checking proxy server logs..."
|
|
@cd proxy && docker compose logs --tail=20
|
|
@echo "Running proxy tests (wolfSSL)..."
|
|
./test_proxy_wolfssl; \
|
|
exit_code=$$?; \
|
|
echo "Stopping proxy server..."; \
|
|
cd proxy && docker compose down; \
|
|
exit $$exit_code
|
|
|
|
test : test.cc include_httplib.cc ../httplib.h Makefile cert.pem
|
|
$(CXX) -o $@ -I.. $(CXXFLAGS) test.cc include_httplib.cc $(TEST_ARGS)
|
|
@file $@
|
|
|
|
# Note: The intention of test_split is to verify that it works to compile and
|
|
# link the split httplib.h, so there is normally no need to execute it.
|
|
test_split : test.cc ../httplib.h httplib.cc Makefile cert.pem
|
|
$(CXX) -o $@ $(CXXFLAGS) test.cc httplib.cc $(TEST_ARGS)
|
|
|
|
# Mbed TLS backend targets
|
|
test_mbedtls : test.cc include_httplib.cc ../httplib.h Makefile cert.pem
|
|
$(CXX) -o $@ -I.. $(CXXFLAGS) test.cc include_httplib.cc $(TEST_ARGS_MBEDTLS)
|
|
@file $@
|
|
|
|
test_split_mbedtls : test.cc ../httplib.h httplib.cc Makefile cert.pem
|
|
$(CXX) -o $@ $(CXXFLAGS) test.cc httplib.cc $(TEST_ARGS_MBEDTLS)
|
|
|
|
# wolfSSL backend targets
|
|
test_wolfssl : test.cc include_httplib.cc ../httplib.h Makefile cert.pem
|
|
$(CXX) -o $@ -I.. $(CXXFLAGS) test.cc include_httplib.cc $(TEST_ARGS_WOLFSSL)
|
|
@file $@
|
|
|
|
test_split_wolfssl : test.cc ../httplib.h httplib.cc Makefile cert.pem
|
|
$(CXX) -o $@ $(CXXFLAGS) test.cc httplib.cc $(TEST_ARGS_WOLFSSL)
|
|
|
|
# No TLS
|
|
test_no_tls : test.cc include_httplib.cc ../httplib.h Makefile
|
|
$(CXX) -o $@ -I.. $(CXXFLAGS) test.cc include_httplib.cc $(TEST_ARGS_NO_TLS)
|
|
@file $@
|
|
|
|
test_split_no_tls : test.cc ../httplib.h httplib.cc Makefile
|
|
$(CXX) -o $@ $(CXXFLAGS) test.cc httplib.cc $(TEST_ARGS_NO_TLS)
|
|
|
|
# ThreadPool unit tests (no TLS, no compression needed)
|
|
#
|
|
# The constructor-exception-safety reproducer test interposes pthread_create
|
|
# at link time. The link flags below enable that interposition. ASAN is also
|
|
# stripped from this target because libasan installs its own pthread_create
|
|
# interceptor; layering our override on top corrupts ASAN's thread bookkeeping
|
|
# and trips "Joining already joined thread" on Linux. ThreadPool memory
|
|
# behavior is still covered by the ASAN-instrumented `test` binary.
|
|
ifneq ($(OS), Windows_NT)
|
|
ifeq ($(shell uname -s), Darwin)
|
|
THREAD_POOL_INTERPOSE_LDFLAGS := -Wl,-flat_namespace
|
|
else
|
|
THREAD_POOL_INTERPOSE_LDFLAGS := -Wl,--export-dynamic
|
|
endif
|
|
endif
|
|
|
|
THREAD_POOL_CXXFLAGS := $(filter-out -fsanitize=address,$(CXXFLAGS))
|
|
|
|
test_thread_pool : test_thread_pool.cc ../httplib.h Makefile
|
|
$(CXX) -o $@ -I.. $(THREAD_POOL_CXXFLAGS) test_thread_pool.cc gtest/src/gtest-all.cc gtest/src/gtest_main.cc -Igtest -Igtest/include -lpthread $(THREAD_POOL_INTERPOSE_LDFLAGS)
|
|
|
|
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
|
|
|
|
BENCHMARK_LIBS = -lpthread
|
|
ifneq ($(OS), Windows_NT)
|
|
ifeq ($(shell uname -s), Darwin)
|
|
BENCHMARK_LIBS += -framework CoreFoundation -framework CFNetwork
|
|
endif
|
|
endif
|
|
|
|
test_benchmark : test_benchmark.cc ../httplib.h Makefile
|
|
$(CXX) -o $@ -I.. $(CXXFLAGS) test_benchmark.cc gtest/src/gtest-all.cc gtest/src/gtest_main.cc -Igtest -Igtest/include $(BENCHMARK_LIBS)
|
|
|
|
test_websocket_heartbeat : test_websocket_heartbeat.cc ../httplib.h Makefile
|
|
$(CXX) -o $@ -I.. $(CXXFLAGS) test_websocket_heartbeat.cc $(TEST_ARGS)
|
|
@file $@
|
|
|
|
test_proxy : test_proxy.cc ../httplib.h Makefile cert.pem
|
|
$(CXX) -o $@ -I.. $(CXXFLAGS) test_proxy.cc $(TEST_ARGS)
|
|
|
|
test_proxy_mbedtls : test_proxy.cc ../httplib.h Makefile cert.pem
|
|
$(CXX) -o $@ -I.. $(CXXFLAGS) test_proxy.cc $(TEST_ARGS_MBEDTLS)
|
|
|
|
test_proxy_wolfssl : test_proxy.cc ../httplib.h Makefile cert.pem
|
|
$(CXX) -o $@ -I.. $(CXXFLAGS) test_proxy.cc $(TEST_ARGS_WOLFSSL)
|
|
|
|
# Runs all fuzz harnesses based on the value of $(LIB_FUZZING_ENGINE).
|
|
# By default LIB_FUZZING_ENGINE is standalone_fuzz_target_runner.o, so each
|
|
# fuzzer is replayed over its regression corpus.
|
|
# Override for actual fuzzing:
|
|
# make fuzz_test LIB_FUZZING_ENGINE=/path/to/libFuzzer
|
|
fuzz_test: server_fuzzer client_fuzzer header_parser_fuzzer url_parser_fuzzer
|
|
@m=""; for f in fuzzing/corpus/[0-9]* fuzzing/corpus/issue1264 fuzzing/corpus/clusterfuzz-testcase-minimized-server_fuzzer-*; do if [ -f "$$f" ]; then m="$$m $$f"; fi; done; \
|
|
if [ -n "$$m" ]; then echo "./server_fuzzer$$m"; ./server_fuzzer $$m; else echo "(no server_fuzzer corpus)"; fi
|
|
@m=""; for f in fuzzing/corpus/clusterfuzz-testcase-minimized-client_fuzzer-*; do if [ -f "$$f" ]; then m="$$m $$f"; fi; done; \
|
|
if [ -n "$$m" ]; then echo "./client_fuzzer$$m"; ./client_fuzzer $$m; else echo "(no client_fuzzer corpus)"; fi
|
|
@m=""; for f in fuzzing/corpus/clusterfuzz-testcase-minimized-header_parser_fuzzer-*; do if [ -f "$$f" ]; then m="$$m $$f"; fi; done; \
|
|
if [ -n "$$m" ]; then echo "./header_parser_fuzzer$$m"; ./header_parser_fuzzer $$m; else echo "(no header_parser_fuzzer corpus)"; fi
|
|
@m=""; for f in fuzzing/corpus/clusterfuzz-testcase-minimized-url_parser_fuzzer-*; do if [ -f "$$f" ]; then m="$$m $$f"; fi; done; \
|
|
if [ -n "$$m" ]; then echo "./url_parser_fuzzer$$m"; ./url_parser_fuzzer $$m; else echo "(no url_parser_fuzzer corpus)"; fi
|
|
|
|
# Fuzz target, so that you can choose which $(LIB_FUZZING_ENGINE) to use.
|
|
server_fuzzer : fuzzing/server_fuzzer.cc ../httplib.h standalone_fuzz_target_runner.o
|
|
$(CXX) -o $@ -I.. $(CXXFLAGS) $< $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(LIB_FUZZING_ENGINE) $(ZSTD_SUPPORT) $(LIBS)
|
|
@file $@
|
|
|
|
client_fuzzer : fuzzing/client_fuzzer.cc ../httplib.h standalone_fuzz_target_runner.o
|
|
$(CXX) -o $@ -I.. $(CXXFLAGS) $< $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(LIB_FUZZING_ENGINE) $(ZSTD_SUPPORT) $(LIBS)
|
|
@file $@
|
|
|
|
header_parser_fuzzer : fuzzing/header_parser_fuzzer.cc ../httplib.h standalone_fuzz_target_runner.o
|
|
$(CXX) -o $@ -I.. $(CXXFLAGS) $< $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(LIB_FUZZING_ENGINE) $(ZSTD_SUPPORT) $(LIBS)
|
|
@file $@
|
|
|
|
url_parser_fuzzer : fuzzing/url_parser_fuzzer.cc ../httplib.h standalone_fuzz_target_runner.o
|
|
$(CXX) -o $@ -I.. $(CXXFLAGS) $< $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(LIB_FUZZING_ENGINE) $(ZSTD_SUPPORT) $(LIBS)
|
|
@file $@
|
|
|
|
# Standalone fuzz runner, which just reads inputs from fuzzing/corpus/ dir and
|
|
# feeds it to server_fuzzer.
|
|
standalone_fuzz_target_runner.o : fuzzing/standalone_fuzz_target_runner.cpp
|
|
$(CXX) -o $@ -I.. $(CXXFLAGS) -c $<
|
|
|
|
httplib.cc : ../httplib.h
|
|
python3 ../split.py -o .
|
|
|
|
cert.pem:
|
|
./gen-certs.sh
|
|
|
|
clean:
|
|
rm -rf test test_split test_mbedtls test_split_mbedtls test_wolfssl test_split_wolfssl test_no_tls, test_split_no_tls test_proxy test_proxy_mbedtls test_proxy_wolfssl test_benchmark server_fuzzer client_fuzzer header_parser_fuzzer url_parser_fuzzer *.pem *.0 *.o *.1 *.srl httplib.h httplib.cc _build* *.dSYM *_shard_*.log cpp-httplib
|
|
|