diff --git a/.codacy.yaml b/.codacy.yaml new file mode 100644 index 00000000..9d602065 --- /dev/null +++ b/.codacy.yaml @@ -0,0 +1,13 @@ +--- +# Codacy project configuration. +# +# specs/ holds internal groundwork artifacts (product specs, architecture +# notes, task records, review notes). They are not user-facing docs and are +# not subject to the same markdown style as README/ChangeLog/CONTRIBUTING. +# +# test/*.md are internal test-suite documents (e.g. the v2.0 routing +# regression gate at test/REGRESSION.md). Same rationale as specs/. +exclude_paths: + - 'specs/**' + - 'test/*.md' + - 'test/**/*.md' diff --git a/.github/workflows/verify-build.yml b/.github/workflows/verify-build.yml index db762069..f512c151 100644 --- a/.github/workflows/verify-build.yml +++ b/.github/workflows/verify-build.yml @@ -108,26 +108,7 @@ jobs: debug: debug coverage: nocoverage shell: bash - - test-group: extra - os: ubuntu-latest - os-type: ubuntu - build-type: none - compiler-family: gcc - c-compiler: gcc-9 - cc-compiler: g++-9 - debug: nodebug - coverage: nocoverage - shell: bash - - test-group: extra - os: ubuntu-latest - os-type: ubuntu - build-type: none - compiler-family: gcc - c-compiler: gcc-10 - cc-compiler: g++-10 - debug: nodebug - coverage: nocoverage - shell: bash + # gcc-9 and gcc-10 dropped: lack full C++20 support (no concepts library, no std::span, no features). - test-group: extra os: ubuntu-latest os-type: ubuntu @@ -168,26 +149,8 @@ jobs: debug: nodebug coverage: nocoverage shell: bash - - test-group: extra - os: ubuntu-22.04 - os-type: ubuntu - build-type: none - compiler-family: clang - c-compiler: clang-11 - cc-compiler: clang++-11 - debug: nodebug - coverage: nocoverage - shell: bash - - test-group: extra - os: ubuntu-22.04 - os-type: ubuntu - build-type: none - compiler-family: clang - c-compiler: clang-12 - cc-compiler: clang++-12 - debug: nodebug - coverage: nocoverage - shell: bash + # clang-11, clang-12, clang-14, and clang-15 dropped: incomplete C++20 support (concepts// gaps). + # clang-13 retained: passes the autoconf C++20 feature check on ubuntu-22.04. - test-group: extra os: ubuntu-22.04 os-type: ubuntu @@ -198,26 +161,6 @@ jobs: debug: nodebug coverage: nocoverage shell: bash - - test-group: extra - os: ubuntu-latest - os-type: ubuntu - build-type: none - compiler-family: clang - c-compiler: clang-14 - cc-compiler: clang++-14 - debug: nodebug - coverage: nocoverage - shell: bash - - test-group: extra - os: ubuntu-latest - os-type: ubuntu - build-type: none - compiler-family: clang - c-compiler: clang-15 - cc-compiler: clang++-15 - debug: nodebug - coverage: nocoverage - shell: bash - test-group: extra os: ubuntu-latest os-type: ubuntu @@ -275,8 +218,8 @@ jobs: os-type: ubuntu build-type: select compiler-family: gcc - c-compiler: gcc-10 - cc-compiler: g++-10 + c-compiler: gcc-14 + cc-compiler: g++-14 debug: nodebug coverage: nocoverage shell: bash @@ -285,8 +228,8 @@ jobs: os-type: ubuntu build-type: nodelay compiler-family: gcc - c-compiler: gcc-10 - cc-compiler: g++-10 + c-compiler: gcc-14 + cc-compiler: g++-14 debug: nodebug coverage: nocoverage shell: bash @@ -295,8 +238,8 @@ jobs: os-type: ubuntu build-type: threads compiler-family: gcc - c-compiler: gcc-10 - cc-compiler: g++-10 + c-compiler: gcc-14 + cc-compiler: g++-14 debug: nodebug coverage: nocoverage shell: bash @@ -305,11 +248,29 @@ jobs: os-type: ubuntu build-type: lint compiler-family: gcc - c-compiler: gcc-10 - cc-compiler: g++-10 + c-compiler: gcc-14 + cc-compiler: g++-14 debug: debug coverage: nocoverage shell: bash + # TASK-007: dedicated header-hygiene gate. Runs `make check-hygiene` + # (preprocesses against the staged install and greps + # for forbidden backend headers). Surfaces this gate as its own named + # GitHub Actions check so reviewers see header-hygiene status + # independently of the broader `make check` log. Until M5 lands the + # check is informational (HEADER_HYGIENE_STRICT defaults to "no"); + # TASK-020 flips it to strict. + - test-group: extra + os: ubuntu-latest + os-type: ubuntu + build-type: header-hygiene + compiler-family: gcc + c-compiler: gcc-14 + cc-compiler: g++-14 + debug: nodebug + coverage: nocoverage + linking: dynamic + shell: bash - test-group: basic os: windows-latest os-type: windows @@ -393,8 +354,12 @@ jobs: pacman --noconfirm -S --needed msys2-devel gcc make libcurl-devel libgnutls-devel - name: Install Ubuntu test sources + # ppa:ubuntu-toolchain-r/test was historically used to backport newer + # gcc onto older Ubuntu LTS. With the C++20 floor (TASK-001), our matrix + # only retains compilers that ship in stock ubuntu-22.04 / 24.04 repos + # (gcc-11..14, clang-13/16/17/18), so the PPA is no longer needed -- and + # add-apt-repository talks to launchpad, which is a flaky dependency. run: | - sudo add-apt-repository ppa:ubuntu-toolchain-r/test ; sudo apt-get update ; if: ${{ matrix.os-type == 'ubuntu' }} @@ -662,7 +627,7 @@ jobs: # IWYU always return an error code. If it returns "2" it indicates a success so we manage this within the function below. function safe_make_iwyu() { { - make -k CXX='/usr/local/bin/include-what-you-use -Xiwyu --mapping_file=${top_builddir}/../custom_iwyu.imp' CXXFLAGS="-std=c++11 -DHTTPSERVER_COMPILATION -D_REENTRANT $CXXFLAGS" ; + make -k CXX='/usr/local/bin/include-what-you-use -Xiwyu --mapping_file=${top_builddir}/../custom_iwyu.imp' CXXFLAGS="-std=c++20 -DHTTPSERVER_COMPILATION -D_REENTRANT $CXXFLAGS" ; } || { if [ $? -ne 2 ]; then return 1; @@ -685,7 +650,18 @@ jobs: run: | cd build ; make check; - if: ${{ matrix.build-type != 'iwyu' && matrix.compiler-family != 'arm-cross' }} + if: ${{ matrix.build-type != 'iwyu' && matrix.compiler-family != 'arm-cross' && matrix.build-type != 'header-hygiene' }} + + - name: Run header-hygiene check + # TASK-007: dedicated public-header hygiene gate. Runs the + # preprocessor-grep target (Layer 2) against a staged install and + # reports any forbidden backend headers reaching . + # Currently informational (HEADER_HYGIENE_STRICT=no) -- TASK-020 + # flips this to strict when M5 closes the umbrella. + run: | + cd build + make check-hygiene + if: ${{ matrix.build-type == 'header-hygiene' }} - name: Print tests results shell: bash diff --git a/.gitignore b/.gitignore index addf8862..40430a4b 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,5 @@ libtool .worktrees .claude CLAUDE.md +.groundwork-plans/ +.DS_Store diff --git a/ChangeLog b/ChangeLog index ea6c2045..531da1a7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,10 @@ Version 0.20.0 + Raised minimum C++ standard to C++20. Build now requires gcc >= 10 + or clang >= 13 (Apple Clang from Xcode 15+). Updated + AX_CXX_COMPILE_STDCXX macro (m4/ax_cxx_compile_stdcxx.m4) to + serial 25 to support C++20 detection. Pruned CI matrix rows + (gcc-9, clang-11, clang-12) that lack full C++20 support. Raised minimum libmicrohttpd requirement to 1.0.0. Migrated Basic Auth to v3 API (MHD_basic_auth_get_username_password3, MHD_queue_basic_auth_required_response3) with UTF-8 support. diff --git a/Makefile.am b/Makefile.am index 02121fde..f74ad8b0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -38,11 +38,272 @@ endif endif -EXTRA_DIST = libhttpserver.pc.in $(DX_CONFIG) scripts/extract-release-notes.sh scripts/validate-version.sh +EXTRA_DIST = libhttpserver.pc.in $(DX_CONFIG) scripts/extract-release-notes.sh scripts/validate-version.sh \ + test/headers/consumer_direct.cpp test/headers/consumer_detail.cpp test/headers/consumer_umbrella.cpp \ + test/headers/consumer_post_umbrella.cpp \ + test/headers/consumer_umbrella_no_backend.cpp + +# --------------------------------------------------------------------------- +# Header-hygiene checks (TASK-002) +# +# check-headers verifies that the public/private header gates are wired up +# correctly: +# A.1 a consumer including a public header WITHOUT the umbrella must hit the +# inclusion-gate #error. +# A.2 a consumer including a detail header WITHOUT HTTPSERVER_COMPILATION +# must hit the gate. +# A.3 a consumer including only the umbrella, WITHOUT HTTPSERVER_COMPILATION, +# must compile cleanly. +# +# The CXX invocations below override CXXFLAGS to '' so that +# -DHTTPSERVER_COMPILATION (injected by configure.ac into CXXFLAGS for the +# library and test build) does NOT leak into the consumer-style compile. We +# still pass -std=c++20 explicitly because libhttpserver requires C++20. +# --------------------------------------------------------------------------- + +# Compose CXX with: explicit -std, the source/build include search paths used by +# the library, and $(CPPFLAGS) (e.g., -I/opt/homebrew/include from configure). +# Deliberately omit $(CXXFLAGS), $(AM_CPPFLAGS), and any per-target CPPFLAGS so +# that -DHTTPSERVER_COMPILATION (set in src/ and test/ AM_CPPFLAGS) cannot +# leak into the consumer-style compile. A true consumer never has that macro. +CHECK_HEADERS_CXX = $(CXX) -std=c++20 -I$(top_builddir) -I$(top_srcdir)/src -I$(top_srcdir)/src/httpserver $(CPPFLAGS) +CHECK_HEADERS_GATE_MSG = Only or can be included directly + +check-headers: + @echo "=== check-headers A.1: direct public-header include must fail ===" + @if $(CHECK_HEADERS_CXX) -c $(top_srcdir)/test/headers/consumer_direct.cpp -o /dev/null 2>check-headers-A1.log; then \ + echo "FAIL: consumer_direct.cpp compiled but should have errored"; \ + cat check-headers-A1.log; \ + rm -f check-headers-A1.log; \ + exit 1; \ + fi + @if ! grep -q "$(CHECK_HEADERS_GATE_MSG)" check-headers-A1.log; then \ + echo "FAIL: consumer_direct.cpp failed but not for the gate reason"; \ + cat check-headers-A1.log; \ + rm -f check-headers-A1.log; \ + exit 1; \ + fi + @rm -f check-headers-A1.log + @echo " PASS: A.1 gate fired as expected" + @echo "=== check-headers A.2: direct detail-header include must fail ===" + @if $(CHECK_HEADERS_CXX) -c $(top_srcdir)/test/headers/consumer_detail.cpp -o /dev/null 2>check-headers-A2.log; then \ + echo "FAIL: consumer_detail.cpp compiled but should have errored"; \ + cat check-headers-A2.log; \ + rm -f check-headers-A2.log; \ + exit 1; \ + fi + @if ! grep -q "$(CHECK_HEADERS_GATE_MSG)" check-headers-A2.log; then \ + echo "FAIL: consumer_detail.cpp failed but not for the gate reason"; \ + cat check-headers-A2.log; \ + rm -f check-headers-A2.log; \ + exit 1; \ + fi + @rm -f check-headers-A2.log + @echo " PASS: A.2 gate fired as expected" + @echo "=== check-headers A.3: umbrella include must succeed ===" + @if ! $(CHECK_HEADERS_CXX) -c $(top_srcdir)/test/headers/consumer_umbrella.cpp -o consumer_umbrella.check.o 2>check-headers-A3.log; then \ + echo "FAIL: consumer_umbrella.cpp did not compile"; \ + cat check-headers-A3.log; \ + rm -f check-headers-A3.log consumer_umbrella.check.o; \ + exit 1; \ + fi + @rm -f check-headers-A3.log consumer_umbrella.check.o + @echo " PASS: A.3 umbrella compiled cleanly" + @echo "=== check-headers A.4: post-umbrella direct include must fail ===" + @if $(CHECK_HEADERS_CXX) -c $(top_srcdir)/test/headers/consumer_post_umbrella.cpp -o /dev/null 2>check-headers-A4.log; then \ + echo "FAIL: consumer_post_umbrella.cpp compiled but should have errored"; \ + cat check-headers-A4.log; \ + rm -f check-headers-A4.log; \ + exit 1; \ + fi + @if ! grep -q "$(CHECK_HEADERS_GATE_MSG)" check-headers-A4.log; then \ + echo "FAIL: consumer_post_umbrella.cpp failed but not for the gate reason"; \ + cat check-headers-A4.log; \ + rm -f check-headers-A4.log; \ + exit 1; \ + fi + @rm -f check-headers-A4.log + @echo " PASS: A.4 umbrella does not leak _HTTPSERVER_HPP_INSIDE_" + +# check-install-layout asserts that `make install DESTDIR=$(STAGE)` produces +# a public include tree with NO `detail/` directory and NO `*_impl.hpp` files. +# This protects the public/private split as described in TASK-002 / DR-002. +CHECK_INSTALL_STAGE = $(abs_top_builddir)/.install-stage + +check-install-layout: + @echo "=== check-install-layout: staged install must hide detail/ and *_impl.hpp ===" + @if test "$(CHECK_INSTALL_SHARED)" != "yes"; then \ + rm -rf $(CHECK_INSTALL_STAGE); \ + $(MAKE) $(AM_MAKEFLAGS) install DESTDIR=$(CHECK_INSTALL_STAGE) >check-install.log 2>&1 || { \ + echo "FAIL: staged install failed"; \ + cat check-install.log; \ + rm -f check-install.log; \ + rm -rf $(CHECK_INSTALL_STAGE); \ + exit 1; \ + }; \ + rm -f check-install.log; \ + fi + @leaked_detail=`find $(CHECK_INSTALL_STAGE) -type d -name detail 2>/dev/null`; \ + if test -n "$$leaked_detail"; then \ + echo "FAIL: detail/ directory leaked into install:"; \ + echo "$$leaked_detail"; \ + if test "$(CHECK_INSTALL_SHARED)" != "yes"; then rm -rf $(CHECK_INSTALL_STAGE); fi; \ + exit 1; \ + fi + @leaked_impl=`find $(CHECK_INSTALL_STAGE) -name '*_impl.hpp' 2>/dev/null`; \ + if test -n "$$leaked_impl"; then \ + echo "FAIL: *_impl.hpp file leaked into install:"; \ + echo "$$leaked_impl"; \ + if test "$(CHECK_INSTALL_SHARED)" != "yes"; then rm -rf $(CHECK_INSTALL_STAGE); fi; \ + exit 1; \ + fi + @umbrella_count=`find $(CHECK_INSTALL_STAGE) -name 'httpserver.hpp' | wc -l | tr -d ' '`; \ + if test "$$umbrella_count" != "1"; then \ + echo "FAIL: expected exactly 1 installed httpserver.hpp, got $$umbrella_count"; \ + if test "$(CHECK_INSTALL_SHARED)" != "yes"; then rm -rf $(CHECK_INSTALL_STAGE); fi; \ + exit 1; \ + fi + @if test "$(CHECK_INSTALL_SHARED)" != "yes"; then rm -rf $(CHECK_INSTALL_STAGE); fi + @echo " PASS: staged install layout is clean" + +# --------------------------------------------------------------------------- +# Header-hygiene preprocessor gate (TASK-007). +# +# This is the preprocessor-grep half of the TASK-007 enforcement (the +# compile-time half lives as `header_hygiene` in test/Makefile.am). +# +# Procedure: +# 1. Stage `make install DESTDIR=$(CHECK_HYGIENE_STAGE)` to get a +# pristine public include tree -- exactly what packagers and +# downstream consumers see. +# 2. Preprocess test/headers/consumer_umbrella_no_backend.cpp using +# ONLY -I$(CHECK_HYGIENE_STAGE)$(includedir) plus $(CPPFLAGS) (so +# e.g. /opt/homebrew/include is on the search path -- the grep +# below NEEDS to resolve if the umbrella pulls it +# in, otherwise we couldn't detect the leak). +# 3. Grep the cpp output for `# ""` line markers that +# name any forbidden backend header. The line-marker filter +# avoids false positives from substrings in code or comments. +# +# HEADER_HYGIENE_STRICT controls whether a leak is fatal: +# - "yes" (default since TASK-020): leaks fail the build. The umbrella +# is now clean and any regression should break CI loudly. +# - "no" (legacy): leaks were reported as EXPECTED-FAIL and exit 0 +# while M2-M5 were in flight. Override from the command line +# (`make check-hygiene HEADER_HYGIENE_STRICT=no`) only if you +# are deliberately running against an in-flight umbrella. +# +# Cross-reference: keep HEADER_HYGIENE_FORBIDDEN in sync with the +# #ifdef ladder in test/unit/header_hygiene_test.cpp. +# +# TASK-020 caveat (libc++ AND libstdc++ in thread mode): +# is intentionally absent from the forbidden list below. Both +# mainstream STLs unconditionally pull in from any STL +# container header (, , , etc.) when threading +# is enabled: +# - libc++ (Apple's default STL on macOS) routes through +# <__thread/support/pthread.h>. +# - libstdc++ in thread-enabled mode (which is the default whenever +# -D_REENTRANT is set, as configure.ac does) routes through +# , which #include directly. +# The resulting `# N "...pthread.h"` line markers therefore appear in +# the preprocessed output even though libhttpserver itself does not +# include . The runtime sentinel +# test/unit/header_hygiene_test.cpp keeps the pthread guards but skips +# them on both libc++ (_LIBCPP_VERSION) and libstdc++ in thread mode +# (_GLIBCXX_HAS_GTHREADS), so the guards still fire on STLs that don't +# route std::thread through pthread (e.g. MSVC's Microsoft STL). +# --------------------------------------------------------------------------- + +HEADER_HYGIENE_FORBIDDEN = microhttpd\.h|gnutls/gnutls\.h|sys/socket\.h|sys/uio\.h +CHECK_HYGIENE_STAGE = $(abs_top_builddir)/.hygiene-stage +CHECK_HYGIENE_CXX = $(CXX) -std=c++20 -E -I$(CHECK_HYGIENE_STAGE)$(includedir) $(CPPFLAGS) +HEADER_HYGIENE_STRICT ?= yes + +# Sentinel file: only re-run the staged install when headers have changed. +# This is an mtime gate used exclusively for standalone `make check-hygiene` +# invocations — it avoids paying a full `make install` cost on every +# repeated standalone run. When check-local drives check-hygiene it sets +# CHECK_HYGIENE_SHARED=yes and passes CHECK_HYGIENE_STAGE pointing at its +# own pre-built shared stage, so this stamp target is bypassed entirely. +HYGIENE_STAMP = $(CHECK_HYGIENE_STAGE)/.hygiene-stamp + +$(HYGIENE_STAMP): $(wildcard $(top_srcdir)/src/httpserver/*.hpp) + @rm -rf $(CHECK_HYGIENE_STAGE) + @$(MAKE) $(AM_MAKEFLAGS) install DESTDIR=$(CHECK_HYGIENE_STAGE) >check-hygiene-install.log 2>&1 || { \ + echo "FAIL: staged install failed"; cat check-hygiene-install.log; \ + rm -f check-hygiene-install.log; rm -rf $(CHECK_HYGIENE_STAGE); exit 1; } + @rm -f check-hygiene-install.log + @touch $(HYGIENE_STAMP) + +check-hygiene: + @echo "=== check-hygiene: must not transitively include backend headers ===" + @if test "$(CHECK_HYGIENE_SHARED)" != "yes"; then \ + $(MAKE) $(AM_MAKEFLAGS) $(HYGIENE_STAMP); \ + else \ + if ! test -d "$(CHECK_HYGIENE_STAGE)"; then \ + echo "FAIL: CHECK_HYGIENE_SHARED=yes but stage dir '$(CHECK_HYGIENE_STAGE)' does not exist."; \ + echo " Always pair CHECK_HYGIENE_SHARED=yes with CHECK_HYGIENE_STAGE=."; \ + exit 1; \ + fi; \ + fi + @status=0; \ + if ! $(CHECK_HYGIENE_CXX) $(top_srcdir)/test/headers/consumer_umbrella_no_backend.cpp >check-hygiene.i 2>check-hygiene.err; then \ + if test "$(HEADER_HYGIENE_STRICT)" = "yes"; then \ + echo "FAIL: preprocessor failed"; cat check-hygiene.err; \ + status=1; \ + else \ + echo "EXPECTED-FAIL (informational until M5): preprocessor failed against staged install."; \ + echo " This is expected while M2-M5 are in flight (e.g. webserver.hpp still"; \ + echo " references private detail headers that aren't shipped)."; \ + echo " Tail of preprocessor diagnostics:"; \ + sed 's/^/ /' check-hygiene.err | tail -10; \ + fi; \ + else \ + leaks=`grep -hE '^# [0-9]+ "[^"]*/($(HEADER_HYGIENE_FORBIDDEN))"' check-hygiene.i | awk '{print $$3}' | sort -u`; \ + if test -n "$$leaks"; then \ + if test "$(HEADER_HYGIENE_STRICT)" = "yes"; then \ + echo "FAIL: forbidden headers leaked through :"; \ + echo "$$leaks"; \ + status=1; \ + else \ + echo "EXPECTED-FAIL (informational until M5): forbidden headers currently leak through :"; \ + echo "$$leaks"; \ + fi; \ + else \ + echo " PASS: no forbidden headers reached the consumer TU"; \ + fi; \ + fi; \ + rm -f check-hygiene.i check-hygiene.err; \ + exit $$status + +# check-local runs check-install-layout and check-hygiene against a single +# shared staged install to avoid paying two full `make install` costs on +# every `make check`. Both sub-checks can still be invoked standalone (they +# will do their own install when CHECK_*_SHARED is not set). +check-local: check-headers + @echo "=== Shared staged install for check-install-layout and check-hygiene ===" + @rm -rf $(abs_top_builddir)/.shared-check-stage + @$(MAKE) $(AM_MAKEFLAGS) install DESTDIR=$(abs_top_builddir)/.shared-check-stage >check-shared-install.log 2>&1 || { \ + echo "FAIL: shared staged install failed"; cat check-shared-install.log; \ + rm -f check-shared-install.log; rm -rf $(abs_top_builddir)/.shared-check-stage; exit 1; } + @rm -f check-shared-install.log + @$(MAKE) $(AM_MAKEFLAGS) check-install-layout \ + CHECK_INSTALL_STAGE=$(abs_top_builddir)/.shared-check-stage \ + CHECK_INSTALL_SHARED=yes + @$(MAKE) $(AM_MAKEFLAGS) check-hygiene \ + CHECK_HYGIENE_STAGE=$(abs_top_builddir)/.shared-check-stage \ + CHECK_HYGIENE_SHARED=yes + @rm -rf $(abs_top_builddir)/.shared-check-stage + +.PHONY: check-headers check-install-layout check-hygiene MOSTLYCLEANFILES = $(DX_CLEANFILES) *.gcda *.gcno *.gcov DISTCLEANFILES = DIST_REVISION +clean-local: + rm -rf $(CHECK_HYGIENE_STAGE) $(abs_top_builddir)/.shared-check-stage $(CHECK_INSTALL_STAGE) + pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libhttpserver.pc diff --git a/README.CentOS-7 b/README.CentOS-7 index 1dfaaa70..4cbaf071 100644 --- a/README.CentOS-7 +++ b/README.CentOS-7 @@ -1,7 +1,8 @@ ## Cent OS 7 / RHEL 7 -CentOS 7 has a lower version of gcc (4.8.7) that is barely C++11 capable and this library -needs a better compiler. We recommend at least gcc 5+ +CentOS 7's stock gcc (4.8.7) is far too old: this library requires a C++20 compiler +(gcc >= 10 or clang >= 13). -We recommend installing devtoolset-8 -https://www.softwarecollections.org/en/scls/rhscl/devtoolset-8/ +Install gcc-toolset-14 (or newer) from the RHEL/CentOS Software Collections and +`source /opt/rh/gcc-toolset-14/enable` before configuring. The same workaround applies +to RHEL 9 systems whose stock gcc-11 lacks some C++20 library features. diff --git a/README.md b/README.md index 7933a235..6a26d1ff 100644 --- a/README.md +++ b/README.md @@ -87,12 +87,14 @@ Additionally, clients can specify resource limits on the overall number of conne libhttpserver can be used without any dependencies aside from libmicrohttpd. The minimum versions required are: -* g++ >= 5.5.0 or clang-3.6 -* C++17 or newer +* g++ >= 10 or clang >= 13 (Apple Clang from Xcode 15+) +* C++20 or newer * libmicrohttpd >= 1.0.0 * [Optionally]: for TLS (HTTPS) support, you'll need [libgnutls](http://www.gnutls.org/). * [Optionally]: to compile the code-reference, you'll need [doxygen](http://www.doxygen.nl/). +On RHEL 9 (and derivatives), the stock GCC 11 is too old for some C++20 library features the build relies on; install the `gcc-toolset-14` package and `source /opt/rh/gcc-toolset-14/enable` before configuring. + Additionally, for MinGW on windows you will need: * libwinpthread (For MinGW-w64, if you use thread model posix then you have this) @@ -215,7 +217,7 @@ The most basic example of creating a server and handling a requests for the path }; int main(int argc, char** argv) { - webserver ws = create_webserver(8080); + webserver ws{create_webserver(8080)}; hello_world_resource hwr; ws.register_resource("/hello", &hwr); @@ -254,7 +256,7 @@ You can also check this example on [github](https://github.com/etr/libhttpserver ## Create and work with a webserver As you can see from the example above, creating a webserver with standard configuration is quite simple: ```cpp - webserver ws = create_webserver(8080); + webserver ws{create_webserver(8080)}; ``` The `create_webserver` class is a supporting _builder_ class that eases the building of a webserver through chained syntax. @@ -273,31 +275,31 @@ For example, if your connection limit is “1”, a browser may open a first con * _.bind_address(**const std::string&** ip):_ Bind the server to a specific network interface by IP address string (e.g., `"127.0.0.1"` for localhost only, or `"192.168.1.100"` for a specific interface). Supports both IPv4 and IPv6 addresses. When an IPv6 address is provided, IPv6 mode is automatically enabled. Example: `create_webserver(8080).bind_address("127.0.0.1")`. * _.bind_socket(**int** socket_fd):_ Listen socket to use. Pass a listen socket for the daemon to use (systemd-style). If this option is used, the daemon will not open its own listen socket(s). The argument passed must be of type "int" and refer to an existing socket that has been bound to a port and is listening. * _.max_thread_stack_size(**int** stack_size):_ Maximum stack size for threads created by the library. Not specifying this option or using a value of zero means using the system default (which is likely to differ based on your platform). Default is `0 (system default)`. -* _.use_ipv6() and .no_ipv6():_ Enable or disable the IPv6 protocol support (by default, libhttpserver will just support IPv4). If you specify this and the local platform does not support it, starting up the server will throw an exception. `off` by default. -* _.use_dual_stack() and .no_dual_stack():_ Enable or disable the support for both IPv6 and IPv4 protocols at the same time (by default, libhttpserver will just support IPv4). If you specify this and the local platform does not support it, starting up the server will throw an exception. Note that this will mean that IPv4 addresses are returned in the IPv6-mapped format (the ’structsockaddrin6’ format will be used for IPv4 and IPv6). `off` by default. -* _.pedantic() and .no_pedantic():_ Enables pedantic checks about the protocol (as opposed to as tolerant as possible). Specifically, at the moment, this flag causes the library to reject HTTP 1.1 connections without a `Host` header. This is required by the standard, but of course in violation of the “be as liberal as possible in what you accept” norm. It is recommended to turn this **off** if you are testing clients against the library, and **on** in production. `off` by default. -* _.debug() and .no_debug():_ Enables debug messages from the library. `off` by default. -* _.regex_checking() and .no_regex_checking():_ Enables pattern matching for endpoints. Read more [here](#registering-resources). `on` by default. -* _.post_process() and .no_post_process():_ Enables/Disables the library to automatically parse the body of the http request as arguments if in querystring format. Read more [here](#parsing-requests). `on` by default. -* _.put_processed_data_to_content() and .no_put_processed_data_to_content():_ Enables/Disables the library to copy parsed body data to the content or to only store it in the arguments map. `on` by default. +* _.use_ipv6(**bool** enable = true):_ Enable or disable IPv6 protocol support (by default, libhttpserver supports only IPv4). If you enable this and the local platform does not support it, starting up the server will throw an exception. `off` by default. Pass `false` to disable explicitly. +* _.use_dual_stack(**bool** enable = true):_ Enable or disable support for both IPv6 and IPv4 protocols at the same time (by default, libhttpserver supports only IPv4). If you enable this and the local platform does not support it, starting up the server will throw an exception. Note that this will mean that IPv4 addresses are returned in the IPv6-mapped format (the ’structsockaddrin6’ format will be used for IPv4 and IPv6). `off` by default. +* _.pedantic(**bool** enable = true):_ Enables pedantic checks about the protocol (as opposed to as tolerant as possible). Specifically, at the moment, this flag causes the library to reject HTTP 1.1 connections without a `Host` header. This is required by the standard, but of course in violation of the "be as liberal as possible in what you accept" norm. It is recommended to turn this **off** if you are testing clients against the library, and **on** in production. `off` by default. +* _.debug(**bool** enable = true):_ Enables debug messages from the library. `off` by default. +* _.regex_checking(**bool** enable = true):_ Enables pattern matching for endpoints. Read more [here](#registering-resources). `on` by default. +* _.post_process(**bool** enable = true):_ Enables/Disables the library to automatically parse the body of the http request as arguments if in querystring format. Read more [here](#parsing-requests). `on` by default. +* _.put_processed_data_to_content(**bool** enable = true):_ Enables/Disables the library to copy parsed body data to the content or to only store it in the arguments map. `on` by default. * _.file_upload_target(**file_upload_target_T** file_upload_target):_ Controls, how the library stores uploaded files. Default value is `FILE_UPLOAD_MEMORY_ONLY`. * `FILE_UPLOAD_MEMORY_ONLY`: The content of the file is only stored in memory. Depending on `put_processed_data_to_content` only as part of the arguments map or additionally in the content. * `FILE_UPLOAD_DISK_ONLY`: The content of the file is stored only in the file system. The path is created from `file_upload_dir` and either a random name (if `generate_random_filename_on_upload` is true) or the actually uploaded file name. * `FILE_UPLOAD_MEMORY_AND_DISK`: The content of the file is stored in memory and on the file system. * _.file_upload_dir(**const std::string&** file_upload_dir):_ Specifies the directory to store all uploaded files. Default value is `/tmp`. -* _.generate_random_filename_on_upload() and .no_generate_random_filename_on_upload():_ Enables/Disables the library to generate a unique and unused filename to store the uploaded file to. Otherwise the actually uploaded file name is used. `off` by default. +* _.generate_random_filename_on_upload(**bool** enable = true):_ Enables/Disables the library to generate a unique and unused filename to store the uploaded file to. Otherwise the actually uploaded file name is used. `off` by default. * _.file_cleanup_callback(**file_cleanup_callback_ptr** callback):_ Sets a callback function to control what happens to uploaded files when the request completes. By default (when no callback is set), all uploaded files are automatically deleted. The callback signature is `bool(const std::string& key, const std::string& filename, const http::file_info& info)` where `key` is the form field name, `filename` is the original uploaded filename, and `info` contains file metadata including the filesystem path. Return `true` to delete the file (default behavior) or `false` to keep it (e.g., after moving it to permanent storage). If the callback throws an exception, the file will be deleted as a safety measure. -* _.deferred()_ and _.no_deferred():_ Enables/Disables the ability for the server to suspend and resume connections. Simply put, it enables/disables the ability to use `deferred_response`. Read more [here](#building-responses-to-requests). `on` by default. -* _.single_resource() and .no_single_resource:_ Sets or unsets the server in single resource mode. This limits all endpoints to be served from a single resource. The resultant is that the webserver will process the request matching to the endpoint skipping any complex semantic. Because of this, the option is incompatible with `regex_checking` and requires the resource to be registered against an empty endpoint or the root endpoint (`"/"`). The resource will also have to be registered as family. (For more information on resource registration, read more [here](#registering-resources)). `off` by default. -* _.no_listen_socket():_ Run the daemon without a listening socket. The server will not bind to any port on its own; instead, you must provide connections externally via `add_connection()`. Useful for integrating with an external accept loop or passing sockets from systemd or another process. `off` by default. -* _.no_thread_safety():_ Disable internal thread-safety mechanisms. This can improve performance when you guarantee that only a single thread will access the daemon at a time. **Only use this if you are sure you do not need concurrent access.** `off` by default. -* _.turbo():_ Enable turbo mode. This is a performance optimization that allows the daemon to skip certain internal operations. Requires the application to meet specific threading and response constraints — consult the libmicrohttpd documentation for details. `off` by default. -* _.suppress_date_header():_ Suppress the automatic addition of a `Date:` header in responses. Useful for reproducible tests or when the application manages its own date headers. `off` by default. +* _.deferred(**bool** enable = true):_ Enables/Disables the ability for the server to suspend and resume connections. Simply put, it enables/disables the ability to use `deferred_response`. Read more [here](#building-responses-to-requests). `on` by default. +* _.single_resource(**bool** enable = true):_ Sets or unsets the server in single resource mode. This limits all endpoints to be served from a single resource. The resultant is that the webserver will process the request matching to the endpoint skipping any complex semantic. Because of this, the option is incompatible with `regex_checking` and requires the resource to be registered against an empty endpoint or the root endpoint (`"/"`). The resource will also have to be registered as family. (For more information on resource registration, read more [here](#registering-resources)). `off` by default. +* _.listen_socket(**bool** enable = true):_ Run the daemon with (`true`) or without (`false`) a listening socket. When disabled, the server will not bind to any port on its own; instead, you must provide connections externally via `add_connection()`. Useful for integrating with an external accept loop or passing sockets from systemd or another process. `on` by default. +* _.thread_safety(**bool** enable = true):_ Enable or disable internal thread-safety mechanisms. Disabling can improve performance when you guarantee that only a single thread will access the daemon at a time. **Only disable this if you are sure you do not need concurrent access.** `on` by default. +* _.turbo(**bool** enable = true):_ Enable turbo mode. This is a performance optimization that allows the daemon to skip certain internal operations. Requires the application to meet specific threading and response constraints — consult the libmicrohttpd documentation for details. `off` by default. +* _.suppress_date_header(**bool** enable = true):_ Suppress the automatic addition of a `Date:` header in responses. Useful for reproducible tests or when the application manages its own date headers. `off` by default. * _.listen_backlog(**int** backlog):_ Set the TCP listen backlog size. Higher values allow more pending connections in the kernel queue. Default is `0` (system default). * _.address_reuse(**int** reuse):_ Control address reuse (`SO_REUSEADDR`/`SO_REUSEPORT`). Pass `1` to enable, `-1` to disable. Default is `0` (system default). * _.connection_memory_increment(**size_t** increment):_ Increment size for per-connection memory allocation when the initial pool is exhausted. Default is `0` (system default, typically 1024 bytes). * _.tcp_fastopen_queue_size(**int** queue_size):_ Set the size of the TCP Fast Open queue. When set, enables TCP Fast Open with the specified queue depth. Default is `0` (disabled). -* _.sigpipe_handled_by_app():_ Inform the daemon that the application is handling `SIGPIPE` on its own, so libmicrohttpd should not install a handler. `off` by default. +* _.sigpipe_handled_by_app(**bool** enable = true):_ Inform the daemon that the application is handling `SIGPIPE` on its own, so libmicrohttpd should not install a handler. `off` by default. * _.client_discipline_level(**int** level):_ Controls how strictly the server enforces HTTP protocol compliance. Higher values make the server stricter with misbehaving clients. Default is `-1` (use libmicrohttpd default). ### Threading Models @@ -309,9 +311,9 @@ For example, if your connection limit is “1”, a browser may open a first con ### Custom defaulted error messages libhttpserver allows to override internal error retrieving functions to provide custom messages to the HTTP client. There are only 3 cases in which implementing logic (an http_resource) cannot be invoked: (1) a not found resource, where the library is not being able to match the URL requested by the client to any implementing http_resource object; (2) a not allowed method, when the HTTP client is requesting a method explicitly marked as not allowed (more info [here](#allowing-and-disallowing-methods-on-a-resource)) by the implementation; (3) an exception being thrown. In all these 3 cases libhttpserver would provide a standard HTTP response to the client with the correct error code; respectively a `404`, a `405` and a `500`. The library allows its user to specify custom callbacks that will be called to replace the default behavior. -* _.not_found_resource(**const shared_ptr(*render_ptr)(const http_request&)** resource):_ Specifies a function to handle a request when no matching registered endpoint exist for the URL requested by the client. -* _.method_not_allowed_resource(**const shared_ptr(*render_ptr)(const http_request&)** resource):_ Specifies a function to handle a request that is asking for a method marked as not allowed on the matching http_resource. -* _.internal_error_resource(**const shared_ptr(*render_ptr)(const http_request&)** resource):_ Specifies a function to handle a request that is causing an uncaught exception during its execution. **REMEMBER:** is this callback is causing an exception itself, the standard default response from libhttpserver will be reported to the HTTP client. +* _.not_found_handler(**std::function** handler):_ Specifies a function to handle a request when no matching registered endpoint exist for the URL requested by the client. +* _.method_not_allowed_handler(**std::function** handler):_ Specifies a function to handle a request that is asking for a method marked as not allowed on the matching http_resource. +* _.internal_error_handler(**std::function** handler):_ Specifies a function to handle a request that is causing an uncaught exception during its execution. **REMEMBER:** is this callback is causing an exception itself, the standard default response from libhttpserver will be reported to the HTTP client. #### Example of custom errors: ```cpp @@ -319,12 +321,12 @@ In all these 3 cases libhttpserver would provide a standard HTTP response to the using namespace httpserver; - std::shared_ptr not_found_custom(const http_request& req) { - return std::shared_ptr(new string_response("Not found custom", 404, "text/plain")); + http_response not_found_custom(const http_request& req) { + return http_response::string("Not found custom").with_status(404); } - std::shared_ptr not_allowed_custom(const http_request& req) { - return std::shared_ptr(new string_response("Not allowed custom", 405, "text/plain")); + http_response not_allowed_custom(const http_request& req) { + return http_response::string("Not allowed custom").with_status(405); } class hello_world_resource : public http_resource { @@ -335,9 +337,9 @@ In all these 3 cases libhttpserver would provide a standard HTTP response to the }; int main(int argc, char** argv) { - webserver ws = create_webserver(8080) - .not_found_resource(not_found_custom) - .method_not_allowed_resource(not_allowed_custom); + webserver ws{create_webserver(8080) + .not_found_handler(not_found_custom) + .method_not_allowed_handler(not_allowed_custom)}; hello_world_resource hwr; hwr.disallow_all(); @@ -381,8 +383,8 @@ You can also check this example on [github](https://github.com/etr/libhttpserver }; int main(int argc, char** argv) { - webserver ws = create_webserver(8080) - .log_access(custom_access_log); + webserver ws{create_webserver(8080) + .log_access(custom_access_log)}; hello_world_resource hwr; ws.register_resource("/hello", &hwr); @@ -400,7 +402,7 @@ You'll notice how, on the terminal runing your server, the logs will now be prin You can also check this example on [github](https://github.com/etr/libhttpserver/blob/master/examples/custom_access_log.cpp). ### TLS/HTTPS -* _.use_ssl() and .no_ssl():_ Determines whether to run in HTTPS-mode or not. If you set this as on and libhttpserver was compiled without SSL support, the library will throw an exception at start of the server. `off` by default. +* _.use_ssl(**bool** enable = true):_ Determines whether to run in HTTPS-mode or not. If you set this as on and libhttpserver was compiled without SSL support, the library will throw an exception at start of the server. `off` by default. * _.cred_type(**const http::http_utils::cred_type_T&** cred_type):_ Daemon credentials type. Either certificate or anonymous. Acceptable values are: * `NONE`: No credentials. * `CERTIFICATE`: Certificate credential. @@ -417,7 +419,7 @@ You can also check this example on [github](https://github.com/etr/libhttpserver * _.https_mem_dhparams(**const std::string&** dhparams):_ String containing the Diffie-Hellman (DH) parameters in PEM format. This is used for DHE key exchange in TLS. If not specified, default DH parameters may be used. * _.https_key_password(**const std::string&** password):_ Password for the private key specified by `https_mem_key`, if the key file is encrypted. * _.https_priorities_append(**const std::string&** priorities):_ Additional GnuTLS priorities to append to the base priority string. Unlike `https_priorities()` which replaces the entire string, this appends to the default, making it easier to adjust specific cipher suites or algorithms. -* _.no_alpn():_ Disable Application-Layer Protocol Negotiation (ALPN) for TLS connections. `off` by default. +* _.alpn(**bool** enable = true):_ Enable or disable Application-Layer Protocol Negotiation (ALPN) for TLS connections. `on` by default. #### Minimal example using HTTPS ```cpp @@ -433,10 +435,10 @@ You can also check this example on [github](https://github.com/etr/libhttpserver }; int main(int argc, char** argv) { - webserver ws = create_webserver(8080) + webserver ws{create_webserver(8080) .use_ssl() .https_mem_key("key.pem") - .https_mem_cert("cert.pem"); + .https_mem_cert("cert.pem")}; hello_world_resource hwr; ws.register_resource("/hello", &hwr); @@ -483,11 +485,11 @@ You can also check this example on [github](https://github.com/etr/libhttpserver }; int main(int argc, char** argv) { - webserver ws = create_webserver(8080) + webserver ws{create_webserver(8080) .use_ssl() .cred_type(http::http_utils::PSK) .psk_cred_handler(psk_handler) - .https_priorities("NORMAL:-VERS-TLS-ALL:+VERS-TLS1.2:+PSK:+DHE-PSK"); + .https_priorities("NORMAL:-VERS-TLS-ALL:+VERS-TLS1.2:+PSK:+DHE-PSK")}; hello_world_resource hwr; ws.register_resource("/hello", &hwr); @@ -506,49 +508,49 @@ You can also check this example on [github](https://github.com/etr/libhttpserver ### IP Blacklisting/Whitelisting libhttpserver supports IP blacklisting and whitelisting as an internal feature. This section explains the startup options related with IP blacklisting/whitelisting. See the [specific section](#ip-blacklisting-and-whitelisting) to read more about the topic. -* _.ban_system() and .no_ban_system:_ Can be used to enable/disable the ban system. `on` by default. +* _.ban_system(**bool** enable = true):_ Can be used to enable/disable the ban system. `on` by default. * _.default_policy(**const http::http_utils::policy_T&** default_policy):_ Specifies what should be the default behavior when receiving a request. Possible values are `ACCEPT` and `REJECT`. Default is `ACCEPT`. ### Authentication Parameters -* _.basic_auth() and .no_basic_auth:_ Can be used to enable/disable parsing of the basic authorization header sent by the client. `on` by default. -* _.digest_auth() and .no_digest_auth:_ Can be used to enable/disable parsing of the digested authentication data sent by the client. `on` by default. +* _.basic_auth(**bool** enable = true):_ Can be used to enable/disable parsing of the basic authorization header sent by the client. `on` by default. +* _.digest_auth(**bool** enable = true):_ Can be used to enable/disable parsing of the digested authentication data sent by the client. `on` by default. * _.nonce_nc_size(**int** nonce_size):_ Size of an array of nonce and nonce counter map. This option represents the size (number of elements) of a map of a nonce and a nonce-counter. If this option is not specified, a default value of 4 will be used (which might be too small for servers handling many requests). You should calculate the value of NC_SIZE based on the number of connections per second multiplied by your expected session duration plus a factor of about two for hash table collisions. For example, if you expect 100 digest-authenticated connections per second and the average user to stay on your site for 5 minutes, then you likely need a value of about 60000. On the other hand, if you can only expect only 10 digest-authenticated connections per second, tolerate browsers getting a fresh nonce for each request and expect a HTTP request latency of 250 ms, then a value of about 5 should be fine. * _.digest_auth_random(**const std::string&** nonce_seed):_ Digest Authentication nonce’s seed. For security, you SHOULD provide a fresh random nonce when actually using Digest Authentication with libhttpserver in production. ### Examples of chaining syntax to create a webserver ```cpp - webserver ws = create_webserver(8080) - .no_ssl() - .no_ipv6() - .no_debug() - .no_pedantic() - .no_basic_auth() - .no_digest_auth() - .no_comet() - .no_regex_checking() - .no_ban_system() - .no_post_process(); + webserver ws{create_webserver(8080) + .use_ssl(false) + .use_ipv6(false) + .debug(false) + .pedantic(false) + .basic_auth(false) + .digest_auth(false) + .deferred(false) + .regex_checking(false) + .ban_system(false) + .post_process(false)}; ``` ## ```cpp - webserver ws = create_webserver(8080) + webserver ws{create_webserver(8080) .use_ssl() .https_mem_key("key.pem") - .https_mem_cert("cert.pem"); + .https_mem_cert("cert.pem")}; ``` ### Starting and stopping a webserver Once a webserver is created, you can manage its execution through the following methods on the `webserver` class: * _**void** webserver::start(**bool** blocking):_ Allows to start a server. If the `blocking` flag is passed as `true`, it will block the execution of the current thread until a call to stop on the same webserver object is performed. * _**void** webserver::stop():_ Allows to stop a server. It immediately stops it. * _**bool** webserver::is_running():_ Checks if a server is running -* _**void** webserver::sweet_kill():_ Allows to stop a server. It doesn't guarantee an immediate halt to allow for thread termination and connection closure. +* _**void** webserver::stop_and_wait():_ Stop the webserver and wait for in-flight handlers to complete before returning. Use `stop()` when no such guarantee is required. * _**int** webserver::quiesce():_ Quiesce the daemon: stop accepting new connections while letting in-flight requests complete. Returns the listen socket file descriptor (the caller can close it), or `-1` on error. * _**bool** webserver::run():_ Run the webserver's event loop once (non-blocking). For use with external event loops when the server is started without internal threading. Returns `true` on success. * _**bool** webserver::run_wait(**int32_t** millisec):_ Run the webserver's event loop, blocking until there is activity or the timeout expires. Pass `-1` for indefinite wait. Returns `true` on success. * _**bool** webserver::get_fdset(**fd_set*** read_fd_set, **fd_set*** write_fd_set, **fd_set*** except_fd_set, **int*** max_fd):_ Get the file descriptor sets for `select()`-based external event loop integration. Returns `true` on success. * _**bool** webserver::get_timeout(**uint64_t*** timeout):_ Get the timeout (in milliseconds) until the next daemon action is needed. Returns `true` if a timeout was set, `false` if no timeout is needed. -* _**bool** webserver::add_connection(**int** client_socket, **const struct sockaddr*** addr, **socklen_t** addrlen):_ Add an externally-accepted socket connection to the daemon. Useful with `no_listen_socket()`. Returns `true` on success. +* _**bool** webserver::add_connection(**int** client_socket, **const struct sockaddr*** addr, **socklen_t** addrlen):_ Add an externally-accepted socket connection to the daemon. Useful with `listen_socket(false)`. Returns `true` on success. * _**int** webserver::get_listen_fd():_ Get the listen socket file descriptor, or `-1` if not available. * _**unsigned int** webserver::get_active_connections():_ Get the number of currently active connections. * _**uint16_t** webserver::get_bound_port():_ Get the actual port the daemon is bound to. Particularly useful when port `0` was specified to let the OS choose an ephemeral port. @@ -559,14 +561,14 @@ Once a webserver is created, you can manage its execution through the following The `http_resource` class represents a logical collection of HTTP methods that will be associated to a URL when registered on the webserver. The class is **designed for extension** and it is where most of your code should ideally live. When the webserver matches a request against a resource (see: [resource registration](#registering-resources)), the method correspondent to the one in the request (GET, POST, etc..) (see below) is called on the resource. Given this, the `http_resource` class contains the following extensible methods (also called `handlers` or `render methods`): -* _**std::shared_ptr** http_resource::render_GET(**const http_request&** req):_ Invoked on an HTTP GET request. -* _**std::shared_ptr** http_resource::render_POST(**const http_request&** req):_ Invoked on an HTTP POST request. -* _**std::shared_ptr** http_resource::render_PUT(**const http_request&** req):_ Invoked on an HTTP PUT request. -* _**std::shared_ptr** http_resource::render_HEAD(**const http_request&** req):_ Invoked on an HTTP HEAD request. -* _**std::shared_ptr** http_resource::render_DELETE(**const http_request&** req):_ Invoked on an HTTP DELETE request. -* _**std::shared_ptr** http_resource::render_TRACE(**const http_request&** req):_ Invoked on an HTTP TRACE request. -* _**std::shared_ptr** http_resource::render_OPTIONS(**const http_request&** req):_ Invoked on an HTTP OPTIONS request. -* _**std::shared_ptr** http_resource::render_CONNECT(**const http_request&** req):_ Invoked on an HTTP CONNECT request. +* _**std::shared_ptr** http_resource::render_get(**const http_request&** req):_ Invoked on an HTTP GET request. +* _**std::shared_ptr** http_resource::render_post(**const http_request&** req):_ Invoked on an HTTP POST request. +* _**std::shared_ptr** http_resource::render_put(**const http_request&** req):_ Invoked on an HTTP PUT request. +* _**std::shared_ptr** http_resource::render_head(**const http_request&** req):_ Invoked on an HTTP HEAD request. +* _**std::shared_ptr** http_resource::render_delete(**const http_request&** req):_ Invoked on an HTTP DELETE request. +* _**std::shared_ptr** http_resource::render_trace(**const http_request&** req):_ Invoked on an HTTP TRACE request. +* _**std::shared_ptr** http_resource::render_options(**const http_request&** req):_ Invoked on an HTTP OPTIONS request. +* _**std::shared_ptr** http_resource::render_connect(**const http_request&** req):_ Invoked on an HTTP CONNECT request. * _**std::shared_ptr** http_resource::render(**const http_request&** req):_ Invoked as a backup method if the matching method is not implemented. It can be used whenever you want all the invocations on a URL to activate the same behavior regardless of the HTTP method requested. The default implementation of the `render` method returns an empty response with a `404`. #### Example of implementation of render methods @@ -577,7 +579,7 @@ Given this, the `http_resource` class contains the following extensible methods class hello_world_resource : public http_resource { public: - std::shared_ptr render_GET(const http_request&) { + std::shared_ptr render_get(const http_request&) { return std::shared_ptr(new string_response("GET: Hello, World!")); } @@ -587,7 +589,7 @@ Given this, the `http_resource` class contains the following extensible methods }; int main(int argc, char** argv) { - webserver ws = create_webserver(8080); + webserver ws{create_webserver(8080)}; hello_world_resource hwr; ws.register_resource("/hello", &hwr); @@ -623,7 +625,7 @@ The base `http_resource` class has a set of methods that can be used to allow an }; int main(int argc, char** argv) { - webserver ws = create_webserver(8080); + webserver ws{create_webserver(8080)}; hello_world_resource hwr; hwr.disallow_all(); @@ -686,7 +688,7 @@ There are essentially four ways to specify an endpoint string: }; int main(int argc, char** argv) { - webserver ws = create_webserver(8080); + webserver ws{create_webserver(8080)}; hello_world_resource hwr; ws.register_resource("/hello", &hwr); @@ -778,7 +780,7 @@ By default, uploaded files are automatically deleted when the request completes. using namespace httpserver; int main() { - webserver ws = create_webserver(8080) + webserver ws{create_webserver(8080) .file_upload_target(FILE_UPLOAD_DISK_ONLY) .file_upload_dir("/tmp/uploads") .file_cleanup_callback([](const std::string& key, @@ -788,7 +790,7 @@ int main() { std::string dest = "/var/uploads/" + filename; std::rename(info.get_file_system_file_name().c_str(), dest.c_str()); return false; // Don't delete - we moved it - }); + })}; // ... register resources and start server } @@ -821,7 +823,7 @@ Details on the `http_arg_value` structure. }; int main(int argc, char** argv) { - webserver ws = create_webserver(8080); + webserver ws{create_webserver(8080)}; hello_world_resource hwr; ws.register_resource("/hello", &hwr); @@ -884,7 +886,7 @@ The `http_response` class offers an additional set of methods to "decorate" your }; int main(int argc, char** argv) { - webserver ws = create_webserver(8080); + webserver ws{create_webserver(8080)}; hello_world_resource hwr; ws.register_resource("/hello", &hwr); @@ -911,7 +913,7 @@ You can also check this example on [github](https://github.com/etr/libhttpserver class image_resource : public http_resource { public: - std::shared_ptr render_GET(const http_request&) { + std::shared_ptr render_get(const http_request&) { // binary_data could come from a camera capture, image library, etc. std::string binary_data = get_image_bytes_from_camera(); @@ -921,7 +923,7 @@ You can also check this example on [github](https://github.com/etr/libhttpserver }; int main() { - webserver ws = create_webserver(8080); + webserver ws{create_webserver(8080)}; image_resource ir; ws.register_resource("/image", &ir); @@ -939,15 +941,13 @@ You can also check the complete example on [github](https://github.com/etr/libht [Back to TOC](#table-of-contents) ## IP Blacklisting and Whitelisting -libhttpserver provides natively a system to blacklist and whitelist IP addresses. To enable/disable the system, it is possible to use the `ban_system` and `no_ban_system` methods on the `create_webserver` class. In the same way, you can specify what you want to be your "default behavior" (allow by default or disallow by default) by using the `default_policy` method (see [here](#create-and-work-with-a-webserver)). +libhttpserver provides natively a system to blacklist and whitelist IP addresses. To enable/disable the system, it is possible to use the `ban_system(bool enable = true)` method on the `create_webserver` class (`ban_system(false)` to disable). In the same way, you can specify what you want to be your "default behavior" (allow by default or disallow by default) by using the `default_policy` method (see [here](#create-and-work-with-a-webserver)). The system supports both IPV4 and IPV6 and manages them transparently. The only requirement is for ipv6 to be enabled on your server - you'll have to enable this by using the `use_ipv6` method on `create_webserver`. You can explicitly ban or allow an IP address using the following methods on the `webserver` class: -* _**void** ban_ip(**const std::string&** ip):_ Adds one IP (or a range of IPs) to the list of the banned ones. Takes in input a `string` that contains the IP (or range of IPs) to ban. To use when the `default_policy` is `ACCEPT`. -* _**void** allow_ip(**const std::string&** ip):_ Adds one IP (or a range of IPs) to the list of the allowed ones. Takes in input a `string` that contains the IP (or range of IPs) to allow. To use when the `default_policy` is `REJECT`. -* _**void** unban_ip(**const std::string&** ip):_ Removes one IP (or a range of IPs) from the list of the banned ones. Takes in input a `string` that contains the IP (or range of IPs) to remove from the list. To use when the `default_policy` is `ACCEPT`. -* _**void** disallow_ip(**const std::string&** ip):_ Removes one IP (or a range of IPs) from the list of the allowed ones. Takes in input a `string` that contains the IP (or range of IPs) to remove from the list. To use when the `default_policy` is `REJECT`. +* _**void** block_ip(**std::string_view** ip):_ Add one IP (or a range, e.g. `"127.0.0.*"`) to the block list. Connections from a matching address are refused at the policy callback. Intended for use under the default `ACCEPT` policy. +* _**void** unblock_ip(**std::string_view** ip):_ Remove one IP (or a range) from the block list. Idempotent: removing an entry that is not currently blocked is a no-op. ### IP String Format The IP string format can represent both IPV4 and IPV6. Addresses will be normalized by the webserver to operate in the same sapce. Any valid IPV4 or IPV6 textual representation works. @@ -975,10 +975,11 @@ Examples of valid IPs include: }; int main(int argc, char** argv) { - webserver ws = create_webserver(8080) - .default_policy(http::http_utils::REJECT); + webserver ws{create_webserver(8080)}; - ws.allow_ip("127.0.0.1"); + // Refuse connections from this address; everything else is accepted + // by default. Use a range like "127.0.0.*" to block a wildcard. + ws.block_ip("10.0.0.1"); hello_world_resource hwr; ws.register_resource("/hello", &hwr); @@ -998,11 +999,11 @@ You can also check this example on [github](https://github.com/etr/libhttpserver ## Authentication libhttpserver support three types of client authentication. -Basic authentication uses a simple authentication method based on BASE64 algorithm. Username and password are exchanged in clear between the client and the server, so this method must only be used for non-sensitive content or when the session is protected with https. When using basic authentication libhttpserver will have access to the clear password, possibly allowing to create a chained authentication toward an external authentication server. You can enable/disable support for Basic authentication through the `basic_auth` and `no_basic_auth` methods of the `create_webserver` class. +Basic authentication uses a simple authentication method based on BASE64 algorithm. Username and password are exchanged in clear between the client and the server, so this method must only be used for non-sensitive content or when the session is protected with https. When using basic authentication libhttpserver will have access to the clear password, possibly allowing to create a chained authentication toward an external authentication server. You can enable/disable support for Basic authentication through the `basic_auth(bool enable = true)` method of the `create_webserver` class (`basic_auth(false)` to disable). -Digest authentication uses a one-way authentication method based on hash algorithms (MD5, SHA-256, or SHA-512/256). Only the hash will transit over the network, hence protecting the user password. The nonce will prevent replay attacks. This method is appropriate for general use, especially when https is not used to encrypt the session. SHA-256 is the default algorithm; SHA-512/256 is also available for stronger security. You can enable/disable support for Digest authentication through the `digest_auth` and `no_digest_auth` methods of the `create_webserver` class. +Digest authentication uses a one-way authentication method based on hash algorithms (MD5, SHA-256, or SHA-512/256). Only the hash will transit over the network, hence protecting the user password. The nonce will prevent replay attacks. This method is appropriate for general use, especially when https is not used to encrypt the session. SHA-256 is the default algorithm; SHA-512/256 is also available for stronger security. You can enable/disable support for Digest authentication through the `digest_auth(bool enable = true)` method of the `create_webserver` class (`digest_auth(false)` to disable). -Client certificate authentication uses a X.509 certificate from the client. This is the strongest authentication mechanism but it requires the use of HTTPS. Client certificate authentication can be used simultaneously with Basic or Digest Authentication in order to provide a two levels authentication (like for instance separate machine and user authentication). You can enable/disable support for Certificate authentication through the `use_ssl` and `no_ssl` methods of the `create_webserver` class. +Client certificate authentication uses a X.509 certificate from the client. This is the strongest authentication mechanism but it requires the use of HTTPS. Client certificate authentication can be used simultaneously with Basic or Digest Authentication in order to provide a two levels authentication (like for instance separate machine and user authentication). You can enable/disable support for Certificate authentication through the `use_ssl(bool enable = true)` method of the `create_webserver` class (`use_ssl(false)` to disable). ### Using Basic Authentication ```cpp @@ -1012,7 +1013,7 @@ Client certificate authentication uses a X.509 certificate from the client. This class user_pass_resource : public httpserver::http_resource { public: - std::shared_ptr render_GET(const http_request& req) { + std::shared_ptr render_get(const http_request& req) { if (req.get_user() != "myuser" || req.get_pass() != "mypass") { return std::shared_ptr(new basic_auth_fail_response("FAIL", "test@example.com")); } @@ -1021,7 +1022,7 @@ Client certificate authentication uses a X.509 certificate from the client. This }; int main(int argc, char** argv) { - webserver ws = create_webserver(8080); + webserver ws{create_webserver(8080)}; user_pass_resource hwr; ws.register_resource("/hello", &hwr); @@ -1058,7 +1059,7 @@ You can also use `check_digest_auth_digest` to verify against a pre-computed HA1 class digest_resource : public httpserver::http_resource { public: - std::shared_ptr render_GET(const http_request& req) { + std::shared_ptr render_get(const http_request& req) { if (req.get_digested_user() == "") { return std::make_shared("FAIL", "test@example.com", MY_OPAQUE, true, http_utils::http_ok, http_utils::text_plain, http_utils::digest_algorithm::MD5); @@ -1077,7 +1078,7 @@ You can also use `check_digest_auth_digest` to verify against a pre-computed HA1 }; int main(int argc, char** argv) { - webserver ws = create_webserver(8080); + webserver ws{create_webserver(8080)}; digest_resource hwr; ws.register_resource("/hello", &hwr); @@ -1110,14 +1111,14 @@ libhttpserver provides a centralized authentication mechanism that runs a single // Resources no longer need authentication logic class hello_resource : public http_resource { public: - std::shared_ptr render_GET(const http_request&) { + std::shared_ptr render_get(const http_request&) { return std::make_shared("Hello, authenticated user!", 200, "text/plain"); } }; class health_resource : public http_resource { public: - std::shared_ptr render_GET(const http_request&) { + std::shared_ptr render_get(const http_request&) { return std::make_shared("OK", 200, "text/plain"); } }; @@ -1132,9 +1133,9 @@ libhttpserver provides a centralized authentication mechanism that runs a single } int main() { - webserver ws = create_webserver(8080) + webserver ws{create_webserver(8080) .auth_handler(my_auth_handler) - .auth_skip_paths({"/health", "/public/*"}); + .auth_skip_paths({"/health", "/public/*"})}; hello_resource hello; health_resource health; @@ -1183,7 +1184,7 @@ To enable client certificate authentication, configure your webserver with: class secure_resource : public http_resource { public: - std::shared_ptr render_GET(const http_request& req) { + std::shared_ptr render_get(const http_request& req) { // Check if client provided a certificate if (!req.has_client_certificate()) { return std::make_shared( @@ -1210,11 +1211,11 @@ To enable client certificate authentication, configure your webserver with: }; int main() { - webserver ws = create_webserver(8443) + webserver ws{create_webserver(8443) .use_ssl() .https_mem_key("server_key.pem") .https_mem_cert("server_cert.pem") - .https_mem_trust("ca_cert.pem"); // CA for client certs + .https_mem_trust("ca_cert.pem")}; // CA for client certs secure_resource sr; ws.register_resource("/secure", &sr); @@ -1272,11 +1273,11 @@ To use SNI with libhttpserver, configure an SNI callback that returns the certif certs["www.example.com"] = {load_file("www_cert.pem"), load_file("www_key.pem")}; certs["api.example.com"] = {load_file("api_cert.pem"), load_file("api_key.pem")}; - webserver ws = create_webserver(443) + webserver ws{create_webserver(443) .use_ssl() .https_mem_key("default_key.pem") // Default certificate .https_mem_cert("default_cert.pem") - .sni_callback(sni_callback); // SNI callback + .sni_callback(sni_callback)}; // SNI callback // ... register resources and start ws.start(true); @@ -1324,7 +1325,7 @@ Register a WebSocket handler using `register_ws_resource`: }; int main() { - webserver ws = create_webserver(8080); + webserver ws{create_webserver(8080)}; echo_handler handler; ws.register_ws_resource("/ws", &handler); @@ -1348,7 +1349,7 @@ libhttpserver exposes several methods for integrating with external event loops * _**unsigned int** webserver::get_active_connections():_ Returns the number of currently active connections. ### External event loop integration -When using the server without internal threading (e.g., with `no_listen_socket()` or a single-threaded design), you can drive the event loop yourself: +When using the server without internal threading (e.g., with `listen_socket(false)` or a single-threaded design), you can drive the event loop yourself: * _**bool** webserver::run():_ Process pending events once and return immediately. * _**bool** webserver::run_wait(**int32_t** millisec):_ Block until events are available or the timeout expires. * _**bool** webserver::get_fdset(...):_ Retrieve file descriptor sets for use with `select()`. @@ -1371,7 +1372,7 @@ When using the server without internal threading (e.g., with `no_listen_socket() }; int main() { - webserver ws = create_webserver(0); // Let the OS choose a port + webserver ws{create_webserver(0)}; // Let the OS choose a port hello_resource hr; ws.register_resource("/hello", &hr); @@ -1407,13 +1408,13 @@ Additionally, the following utility methods are available: class file_response_resource : public http_resource { public: - std::shared_ptr render_GET(const http_request& req) { + std::shared_ptr render_get(const http_request& req) { return std::shared_ptr(new file_response("test_content", 200, "text/plain")); } }; int main(int argc, char** argv) { - webserver ws = create_webserver(8080); + webserver ws{create_webserver(8080)}; file_response_resource hwr; ws.register_resource("/hello", &hwr); @@ -1450,13 +1451,13 @@ You can also check this example on [github](https://github.com/etr/libhttpserver class deferred_resource : public http_resource { public: - std::shared_ptr render_GET(const http_request& req) { + std::shared_ptr render_get(const http_request& req) { return std::shared_ptr >(new deferred_response(test_callback, nullptr, "cycle callback response")); } }; int main(int argc, char** argv) { - webserver ws = create_webserver(8080); + webserver ws{create_webserver(8080)}; deferred_resource hwr; ws.register_resource("/hello", &hwr); @@ -1507,14 +1508,14 @@ You can also check this example on [github](https://github.com/etr/libhttpserver class deferred_resource : public http_resource { public: - std::shared_ptr render_GET(const http_request& req) { + std::shared_ptr render_get(const http_request& req) { std::shared_ptr > closure_data(new std::atomic(counter++)); return std::shared_ptr > >(new deferred_response >(test_callback, closure_data, "cycle callback response")); } }; int main(int argc, char** argv) { - webserver ws = create_webserver(8080); + webserver ws{create_webserver(8080)}; deferred_resource hwr; ws.register_resource("/hello", &hwr); @@ -1537,13 +1538,13 @@ You can also check this example on [github](https://github.com/etr/libhttpserver class no_content_resource : public http_resource { public: - std::shared_ptr render_DELETE(const http_request&) { + std::shared_ptr render_delete(const http_request&) { // Return a 204 No Content response with no body return std::make_shared( http::http_utils::http_no_content); } - std::shared_ptr render_HEAD(const http_request&) { + std::shared_ptr render_head(const http_request&) { // Return a HEAD-only response with headers but no body auto response = std::make_shared( http::http_utils::http_ok, @@ -1554,7 +1555,7 @@ You can also check this example on [github](https://github.com/etr/libhttpserver }; int main() { - webserver ws = create_webserver(8080); + webserver ws{create_webserver(8080)}; no_content_resource ncr; ws.register_resource("/items", &ncr); @@ -1578,7 +1579,7 @@ You can also check this example on [github](https://github.com/etr/libhttpserver class iovec_resource : public http_resource { public: - std::shared_ptr render_GET(const http_request&) { + std::shared_ptr render_get(const http_request&) { // Build a response from multiple separate buffers without copying std::vector parts; parts.push_back("{\"header\": \"value\", "); @@ -1591,7 +1592,7 @@ You can also check this example on [github](https://github.com/etr/libhttpserver }; int main() { - webserver ws = create_webserver(8080); + webserver ws{create_webserver(8080)}; iovec_resource ir; ws.register_resource("/data", &ir); @@ -1617,7 +1618,7 @@ You can also check this example on [github](https://github.com/etr/libhttpserver class pipe_resource : public http_resource { public: - std::shared_ptr render_GET(const http_request&) { + std::shared_ptr render_get(const http_request&) { int pipefd[2]; if (pipe(pipefd) == -1) { return std::make_shared("pipe failed", 500); @@ -1640,7 +1641,7 @@ You can also check this example on [github](https://github.com/etr/libhttpserver }; int main() { - webserver ws = create_webserver(8080); + webserver ws{create_webserver(8080)}; pipe_resource pr; ws.register_resource("/stream", &pr); @@ -1680,7 +1681,7 @@ You can also check this example on [github](https://github.com/etr/libhttpserver }; int main() { - webserver ws = create_webserver(8080); + webserver ws{create_webserver(8080)}; echo_handler handler; ws.register_ws_resource("/ws", &handler); @@ -1702,14 +1703,14 @@ You can also check this example on [github](https://github.com/etr/libhttpserver class hello_resource : public http_resource { public: - std::shared_ptr render_GET(const http_request&) { + std::shared_ptr render_get(const http_request&) { return std::make_shared("Hello, World!"); } }; int main() { // Use port 0 to let the OS assign an ephemeral port - webserver ws = create_webserver(0); + webserver ws{create_webserver(0)}; hello_resource hr; ws.register_resource("/hello", &hr); @@ -1726,7 +1727,7 @@ You can also check this example on [github](https://github.com/etr/libhttpserver std::cout << "HTTP 404 reason: " << http::http_utils::reason_phrase(404) << std::endl; - ws.sweet_kill(); + ws.stop_and_wait(); return 0; } ``` @@ -1746,7 +1747,7 @@ You can also check this example on [github](https://github.com/etr/libhttpserver class hello_resource : public http_resource { public: - std::shared_ptr render_GET(const http_request&) { + std::shared_ptr render_get(const http_request&) { return std::make_shared("Hello from external event loop!"); } }; @@ -1754,7 +1755,7 @@ You can also check this example on [github](https://github.com/etr/libhttpserver int main() { signal(SIGINT, signal_handler); - webserver ws = create_webserver(8080); + webserver ws{create_webserver(8080)}; hello_resource hr; ws.register_resource("/hello", &hr); @@ -1791,7 +1792,7 @@ You can also check this example on [github](https://github.com/etr/libhttpserver class hello_resource : public http_resource { public: - std::shared_ptr render_GET(const http_request&) { + std::shared_ptr render_get(const http_request&) { return std::make_shared("Hello, turbo world!"); } }; @@ -1799,13 +1800,13 @@ You can also check this example on [github](https://github.com/etr/libhttpserver int main() { // Create a high-performance server with turbo mode, // suppressed date headers, and a thread pool. - webserver ws = create_webserver(8080) + webserver ws{create_webserver(8080) .start_method(http::http_utils::INTERNAL_SELECT) .max_threads(4) .turbo() .suppress_date_header() .tcp_fastopen_queue_size(16) - .listen_backlog(128); + .listen_backlog(128)}; hello_resource hr; ws.register_resource("/hello", &hr); diff --git a/configure.ac b/configure.ac index 4069589d..5fad0371 100644 --- a/configure.ac +++ b/configure.ac @@ -44,7 +44,7 @@ AC_LANG([C++]) AC_SYS_LARGEFILE # Minimal feature-set required -AX_CXX_COMPILE_STDCXX([17]) +AX_CXX_COMPILE_STDCXX([20], [noext], [mandatory]) native_srcdir=$srcdir @@ -80,10 +80,19 @@ For native Windows binaries, use the MinGW64 shell instead. ADDITIONAL_LIBS="-lpthread -no-undefined" NETWORK_LIBS="-lws2_32" native_srcdir=$(cd $srcdir; pwd -W) + # libmicrohttpd's asserts _SYS_TYPES_FD_SET on Cygwin/MSYS. + # newlib defines that macro via , included from + # only when __BSD_VISIBLE -- i.e. when _DEFAULT_SOURCE is set. Strict ANSI + # C++ (-std=c++NN, AX_CXX_COMPILE_STDCXX noext) suppresses newlib's + # auto-define, so expose it explicitly here. + CPPFLAGS="-D_DEFAULT_SOURCE $CPPFLAGS" ;; *-cygwin*) NETWORK_HEADER="arpa/inet.h" ADDITIONAL_LIBS="-lpthread -no-undefined" + # See *-msys* note: libmicrohttpd's fd_set check needs _DEFAULT_SOURCE + # under -std=c++NN strict mode. + CPPFLAGS="-D_DEFAULT_SOURCE $CPPFLAGS" ;; *) NETWORK_HEADER="arpa/inet.h" @@ -127,7 +136,11 @@ if test x"$host" = x"$build"; then [AC_MSG_ERROR(["microhttpd.h not found"])] ) - CXXFLAGS="-DHTTPSERVER_COMPILATION -D_REENTRANT $LIBMICROHTTPD_CFLAGS $CXXFLAGS" + # -DHTTPSERVER_COMPILATION is intentionally NOT injected globally into + # CXXFLAGS. It is added per-target via AM_CPPFLAGS in src/Makefile.am and + # test/Makefile.am so that examples (and any other consumer-style TUs) + # build through the umbrella header without seeing the internal macro. + CXXFLAGS="-D_REENTRANT $LIBMICROHTTPD_CFLAGS $CXXFLAGS" LDFLAGS="$LIBMICROHTTPD_LIBS $NETWORK_LIBS $ADDITIONAL_LIBS $LDFLAGS" cond_cross_compile="no" @@ -140,7 +153,9 @@ else [AC_MSG_ERROR(["microhttpd.h not found"])] ) - CXXFLAGS="-DHTTPSERVER_COMPILATION -D_REENTRANT $CXXFLAGS" + # See note above: HTTPSERVER_COMPILATION is scoped to lib + tests via + # per-directory AM_CPPFLAGS, not injected globally into CXXFLAGS. + CXXFLAGS="-D_REENTRANT $CXXFLAGS" LDFLAGS="$NETWORK_LIBS $ADDITIONAL_LIBS $LDFLAGS" cond_cross_compile="yes" @@ -221,7 +236,7 @@ AM_LDFLAGS="-lstdc++" if test x"$debugit" = x"yes"; then AC_DEFINE([DEBUG],[],[Debug Mode]) - AM_CXXFLAGS="$AM_CXXFLAGS -DDEBUG -g -Wall -Wextra -Werror -pedantic -std=c++17 -Wno-unused-command-line-argument -O0" + AM_CXXFLAGS="$AM_CXXFLAGS -DDEBUG -g -Wall -Wextra -Werror -pedantic -Wno-unused-command-line-argument -O0" AM_CFLAGS="$AM_CXXFLAGS -DDEBUG -g -Wall -Wextra -Werror -pedantic -Wno-unused-command-line-argument -O0" else AC_DEFINE([NDEBUG],[],[No-debug Mode]) diff --git a/examples/allowing_disallowing_methods.cpp b/examples/allowing_disallowing_methods.cpp index 50efa4fd..3a3357ed 100644 --- a/examples/allowing_disallowing_methods.cpp +++ b/examples/allowing_disallowing_methods.cpp @@ -25,17 +25,17 @@ class hello_world_resource : public httpserver::http_resource { public: std::shared_ptr render(const httpserver::http_request&) { - return std::shared_ptr(new httpserver::string_response("Hello, World!")); + return std::shared_ptr(new httpserver::http_response(httpserver::http_response::string("Hello, World!"))); } }; int main() { - httpserver::webserver ws = httpserver::create_webserver(8080); + httpserver::webserver ws{httpserver::create_webserver(8080)}; - hello_world_resource hwr; - hwr.disallow_all(); - hwr.set_allowing("GET", true); - ws.register_resource("/hello", &hwr); + auto hwr = std::make_shared(); + hwr->disallow_all(); + hwr->set_allowing(httpserver::http_method::get, true); + ws.register_path("/hello", hwr); ws.start(true); return 0; diff --git a/examples/args_processing.cpp b/examples/args_processing.cpp index ddf41c4e..8904be04 100644 --- a/examples/args_processing.cpp +++ b/examples/args_processing.cpp @@ -40,9 +40,11 @@ class args_resource : public httpserver::http_resource { response_body << "=== Using get_args() (supports multiple values per key) ===\n\n"; - // get_args() returns a map where each key maps to an http_arg_value. - // http_arg_value contains a vector of values for parameters like "?id=1&id=2&id=3" - auto args = req.get_args(); + // get_args() returns a const reference to a map where each key + // maps to an http_arg_value. http_arg_value contains a vector of + // values for parameters like "?id=1&id=2&id=3". The reference + // remains valid for the duration of this handler call. + const auto& args = req.get_args(); for (const auto& [key, arg_value] : args) { response_body << "Key: " << key << "\n"; // Use get_all_values() to get all values for this key @@ -80,15 +82,15 @@ class args_resource : public httpserver::http_resource { response_body << "name (via get_arg_flat): " << name_flat << "\n"; } - return std::make_shared(response_body.str(), 200, "text/plain"); + return std::make_shared(httpserver::http_response::string(response_body.str())); } }; int main() { - httpserver::webserver ws = httpserver::create_webserver(8080); + httpserver::webserver ws{httpserver::create_webserver(8080)}; - args_resource ar; - ws.register_resource("/args", &ar); + auto ar = std::make_shared(); + ws.register_path("/args", ar); std::cout << "Server running on http://localhost:8080/args\n"; std::cout << "Try: http://localhost:8080/args?name=john&age=30\n"; diff --git a/examples/basic_authentication.cpp b/examples/basic_authentication.cpp index 661bbb3c..1afac9a1 100644 --- a/examples/basic_authentication.cpp +++ b/examples/basic_authentication.cpp @@ -25,20 +25,21 @@ class user_pass_resource : public httpserver::http_resource { public: - std::shared_ptr render_GET(const httpserver::http_request& req) { + std::shared_ptr render_get(const httpserver::http_request& req) { if (req.get_user() != "myuser" || req.get_pass() != "mypass") { - return std::shared_ptr(new httpserver::basic_auth_fail_response("FAIL", "test@example.com")); + return std::make_shared( + httpserver::http_response::unauthorized("Basic", "test@example.com", "FAIL")); } - return std::shared_ptr(new httpserver::string_response(std::string(req.get_user()) + " " + std::string(req.get_pass()), 200, "text/plain")); + return std::shared_ptr(new httpserver::http_response(httpserver::http_response::string(std::string(req.get_user()) + " " + std::string(req.get_pass())))); } }; int main() { - httpserver::webserver ws = httpserver::create_webserver(8080); + httpserver::webserver ws{httpserver::create_webserver(8080)}; - user_pass_resource hwr; - ws.register_resource("/hello", &hwr); + auto hwr = std::make_shared(); + ws.register_path("/hello", hwr); ws.start(true); return 0; diff --git a/examples/benchmark_nodelay.cpp b/examples/benchmark_nodelay.cpp index 96c2f570..24a33fd4 100755 --- a/examples/benchmark_nodelay.cpp +++ b/examples/benchmark_nodelay.cpp @@ -43,16 +43,16 @@ class hello_world_resource : public httpserver::http_resource { int main(int argc, char** argv) { std::ignore = argc; - httpserver::webserver ws = httpserver::create_webserver(atoi(argv[1])) + httpserver::webserver ws{httpserver::create_webserver(atoi(argv[1])) .start_method(httpserver::http::http_utils::INTERNAL_SELECT) .tcp_nodelay() - .max_threads(atoi(argv[2])); + .max_threads(atoi(argv[2]))}; - std::shared_ptr hello = std::shared_ptr(new httpserver::string_response(BODY, 200)); + std::shared_ptr hello = std::shared_ptr(new httpserver::http_response(httpserver::http_response::string(BODY))); hello->with_header("Server", "libhttpserver"); - hello_world_resource hwr(hello); - ws.register_resource(PATH, &hwr, false); + auto hwr = std::make_shared(hello); + ws.register_path(PATH, hwr); ws.start(true); diff --git a/examples/benchmark_select.cpp b/examples/benchmark_select.cpp index ef5cd089..409370f0 100755 --- a/examples/benchmark_select.cpp +++ b/examples/benchmark_select.cpp @@ -43,15 +43,15 @@ class hello_world_resource : public httpserver::http_resource { int main(int argc, char** argv) { std::ignore = argc; - httpserver::webserver ws = httpserver::create_webserver(atoi(argv[1])) + httpserver::webserver ws{httpserver::create_webserver(atoi(argv[1])) .start_method(httpserver::http::http_utils::INTERNAL_SELECT) - .max_threads(atoi(argv[2])); + .max_threads(atoi(argv[2]))}; - std::shared_ptr hello = std::shared_ptr(new httpserver::string_response(BODY, 200)); + std::shared_ptr hello = std::shared_ptr(new httpserver::http_response(httpserver::http_response::string(BODY))); hello->with_header("Server", "libhttpserver"); - hello_world_resource hwr(hello); - ws.register_resource(PATH, &hwr, false); + auto hwr = std::make_shared(hello); + ws.register_path(PATH, hwr); ws.start(true); diff --git a/examples/benchmark_threads.cpp b/examples/benchmark_threads.cpp index db376168..da7508b2 100755 --- a/examples/benchmark_threads.cpp +++ b/examples/benchmark_threads.cpp @@ -43,14 +43,14 @@ class hello_world_resource : public httpserver::http_resource { int main(int argc, char** argv) { std::ignore = argc; - httpserver::webserver ws = httpserver::create_webserver(atoi(argv[1])) - .start_method(httpserver::http::http_utils::THREAD_PER_CONNECTION); + httpserver::webserver ws{httpserver::create_webserver(atoi(argv[1])) + .start_method(httpserver::http::http_utils::THREAD_PER_CONNECTION)}; - std::shared_ptr hello = std::shared_ptr(new httpserver::string_response(BODY, 200)); + std::shared_ptr hello = std::shared_ptr(new httpserver::http_response(httpserver::http_response::string(BODY))); hello->with_header("Server", "libhttpserver"); - hello_world_resource hwr(hello); - ws.register_resource(PATH, &hwr, false); + auto hwr = std::make_shared(hello); + ws.register_path(PATH, hwr); ws.start(true); diff --git a/examples/binary_buffer_response.cpp b/examples/binary_buffer_response.cpp index 19559cfc..d1735702 100644 --- a/examples/binary_buffer_response.cpp +++ b/examples/binary_buffer_response.cpp @@ -58,7 +58,7 @@ static std::string generate_png_data() { class image_resource : public httpserver::http_resource { public: - std::shared_ptr render_GET(const httpserver::http_request&) { + std::shared_ptr render_get(const httpserver::http_request&) { // Build binary content as a std::string. The string can contain any // bytes — it is not limited to printable characters or null-terminated // C strings. The size is tracked internally by std::string::size(). @@ -66,16 +66,15 @@ class image_resource : public httpserver::http_resource { // Use string_response with the appropriate content type. The response // will send the exact bytes contained in the string. - return std::make_shared( - std::move(image_data), 200, "image/png"); + return std::make_shared(httpserver::http_response::string(std::move(image_data), "image/png")); } }; int main() { - httpserver::webserver ws = httpserver::create_webserver(8080); + httpserver::webserver ws{httpserver::create_webserver(8080)}; - image_resource ir; - ws.register_resource("/image", &ir); + auto ir = std::make_shared(); + ws.register_path("/image", ir); ws.start(true); return 0; diff --git a/examples/centralized_authentication.cpp b/examples/centralized_authentication.cpp index 0f965af6..4044aa0e 100644 --- a/examples/centralized_authentication.cpp +++ b/examples/centralized_authentication.cpp @@ -28,21 +28,18 @@ using httpserver::http_response; using httpserver::http_resource; using httpserver::webserver; using httpserver::create_webserver; -using httpserver::string_response; -using httpserver::basic_auth_fail_response; - // Simple resource that doesn't need to handle auth itself class hello_resource : public http_resource { public: - std::shared_ptr render_GET(const http_request&) { - return std::make_shared("Hello, authenticated user!", 200, "text/plain"); + std::shared_ptr render_get(const http_request&) { + return std::make_shared(http_response::string("Hello, authenticated user!")); } }; class health_resource : public http_resource { public: - std::shared_ptr render_GET(const http_request&) { - return std::make_shared("OK", 200, "text/plain"); + std::shared_ptr render_get(const http_request&) { + return std::make_shared(http_response::string("OK")); } }; @@ -50,7 +47,7 @@ class health_resource : public http_resource { // Returns nullptr to allow the request, or an http_response to reject it std::shared_ptr auth_handler(const http_request& req) { if (req.get_user() != "admin" || req.get_pass() != "secret") { - return std::make_shared("Unauthorized", "MyRealm"); + return std::make_shared(http_response::unauthorized("Basic", "MyRealm", "Unauthorized")); } return nullptr; // Allow request } @@ -59,15 +56,15 @@ int main() { // Create webserver with centralized authentication // - auth_handler: called before every resource's render method // - auth_skip_paths: paths that bypass authentication - webserver ws = create_webserver(8080) + webserver ws{create_webserver(8080) .auth_handler(auth_handler) - .auth_skip_paths({"/health", "/public/*"}); + .auth_skip_paths({"/health", "/public/*"})}; - hello_resource hello; - health_resource health; + auto hello = std::make_shared(); + auto health = std::make_shared(); - ws.register_resource("/api", &hello); - ws.register_resource("/health", &health); + ws.register_path("/api", hello); + ws.register_path("/health", health); ws.start(true); diff --git a/examples/client_cert_auth.cpp b/examples/client_cert_auth.cpp index 90a3ba84..bfd283bc 100644 --- a/examples/client_cert_auth.cpp +++ b/examples/client_cert_auth.cpp @@ -52,6 +52,7 @@ * curl -k https://localhost:8443/secure */ +#include #include #include #include @@ -66,51 +67,45 @@ std::set allowed_fingerprints; // Resource that requires client certificate authentication class secure_resource : public httpserver::http_resource { public: - std::shared_ptr render_GET(const httpserver::http_request& req) { + std::shared_ptr render_get(const httpserver::http_request& req) { // Check if client provided a certificate if (!req.has_client_certificate()) { - return std::make_shared( - "Client certificate required", - httpserver::http::http_utils::http_unauthorized, "text/plain"); + return std::make_shared(httpserver::http_response::string("Client certificate required").with_status(httpserver::http::http_utils::http_unauthorized)); } - // Get certificate information - std::string cn = req.get_client_cert_cn(); - std::string dn = req.get_client_cert_dn(); - std::string issuer = req.get_client_cert_issuer_dn(); - std::string fingerprint = req.get_client_cert_fingerprint_sha256(); + // Get certificate information. TASK-019: the four string-typed + // accessors return string_view aliasing per-request storage; we + // copy into std::string here so the locals survive the rest of + // this method (and so the `+` chains below compile). + std::string cn(req.get_client_cert_cn()); + std::string dn(req.get_client_cert_dn()); + std::string issuer(req.get_client_cert_issuer_dn()); + std::string fingerprint(req.get_client_cert_fingerprint_sha256()); bool verified = req.is_client_cert_verified(); // Check if certificate is verified by our CA if (!verified) { - return std::make_shared( - "Certificate not verified by trusted CA", - httpserver::http::http_utils::http_forbidden, "text/plain"); + return std::make_shared(httpserver::http_response::string("Certificate not verified by trusted CA").with_status(httpserver::http::http_utils::http_forbidden)); } // Optional: Check fingerprint against allowlist if (!allowed_fingerprints.empty() && allowed_fingerprints.find(fingerprint) == allowed_fingerprints.end()) { - return std::make_shared( - "Certificate not in allowlist", - httpserver::http::http_utils::http_forbidden, "text/plain"); + return std::make_shared(httpserver::http_response::string("Certificate not in allowlist").with_status(httpserver::http::http_utils::http_forbidden)); } - // Check certificate validity times + // Check certificate validity times. TASK-019 narrows the + // accessor return type to std::int64_t. time_t now = time(nullptr); - time_t not_before = req.get_client_cert_not_before(); - time_t not_after = req.get_client_cert_not_after(); + std::int64_t not_before = req.get_client_cert_not_before(); + std::int64_t not_after = req.get_client_cert_not_after(); if (now < not_before) { - return std::make_shared( - "Certificate not yet valid", - httpserver::http::http_utils::http_forbidden, "text/plain"); + return std::make_shared(httpserver::http_response::string("Certificate not yet valid").with_status(httpserver::http::http_utils::http_forbidden)); } if (now > not_after) { - return std::make_shared( - "Certificate has expired", - httpserver::http::http_utils::http_forbidden, "text/plain"); + return std::make_shared(httpserver::http_response::string("Certificate has expired").with_status(httpserver::http::http_utils::http_forbidden)); } // Build response with certificate info @@ -121,26 +116,28 @@ class secure_resource : public httpserver::http_resource { response += " Fingerprint (SHA-256): " + fingerprint + "\n"; response += " Verified: " + std::string(verified ? "Yes" : "No") + "\n"; - return std::make_shared(response, 200, "text/plain"); + return std::make_shared(httpserver::http_response::string(response)); } }; // Public resource that shows certificate info but doesn't require it class info_resource : public httpserver::http_resource { public: - std::shared_ptr render_GET(const httpserver::http_request& req) { + std::shared_ptr render_get(const httpserver::http_request& req) { std::string response; if (req.has_client_certificate()) { response = "Client certificate detected:\n"; - response += " Common Name: " + req.get_client_cert_cn() + "\n"; + // TASK-019: get_client_cert_cn() returns string_view; copy + // into std::string for the `+` chain. + response += " Common Name: " + std::string(req.get_client_cert_cn()) + "\n"; response += " Verified: " + std::string(req.is_client_cert_verified() ? "Yes" : "No") + "\n"; } else { response = "No client certificate provided.\n"; response += "Use --cert and --key with curl to provide one.\n"; } - return std::make_shared(response, 200, "text/plain"); + return std::make_shared(httpserver::http_response::string(response)); } }; @@ -151,17 +148,17 @@ int main() { std::cout << " /secure - Requires valid client certificate\n\n"; // Create webserver with SSL and client certificate trust store - httpserver::webserver ws = httpserver::create_webserver(8443) + httpserver::webserver ws{httpserver::create_webserver(8443) .use_ssl() .https_mem_key("server_key.pem") // Server private key .https_mem_cert("server_cert.pem") // Server certificate - .https_mem_trust("ca_cert.pem"); // CA certificate for verifying client certs + .https_mem_trust("ca_cert.pem")}; // CA certificate for verifying client certs - secure_resource secure; - info_resource info; + auto secure = std::make_shared(); + auto info = std::make_shared(); - ws.register_resource("/secure", &secure); - ws.register_resource("/info", &info); + ws.register_path("/secure", secure); + ws.register_path("/info", info); std::cout << "Server started. Press Ctrl+C to stop.\n\n"; std::cout << "Test commands:\n"; diff --git a/examples/custom_access_log.cpp b/examples/custom_access_log.cpp index 8f596c90..5bb0ac77 100644 --- a/examples/custom_access_log.cpp +++ b/examples/custom_access_log.cpp @@ -31,16 +31,16 @@ void custom_access_log(const std::string& url) { class hello_world_resource : public httpserver::http_resource { public: std::shared_ptr render(const httpserver::http_request&) { - return std::shared_ptr(new httpserver::string_response("Hello, World!")); + return std::shared_ptr(new httpserver::http_response(httpserver::http_response::string("Hello, World!"))); } }; int main() { - httpserver::webserver ws = httpserver::create_webserver(8080) - .log_access(custom_access_log); + httpserver::webserver ws{httpserver::create_webserver(8080) + .log_access(custom_access_log)}; - hello_world_resource hwr; - ws.register_resource("/hello", &hwr); + auto hwr = std::make_shared(); + ws.register_path("/hello", hwr); ws.start(true); return 0; diff --git a/examples/custom_error.cpp b/examples/custom_error.cpp index c38fb169..ca227163 100644 --- a/examples/custom_error.cpp +++ b/examples/custom_error.cpp @@ -22,30 +22,30 @@ #include -std::shared_ptr not_found_custom(const httpserver::http_request&) { - return std::shared_ptr(new httpserver::string_response("Not found custom", 404, "text/plain")); +httpserver::http_response not_found_custom(const httpserver::http_request&) { + return httpserver::http_response::string("Not found custom").with_status(404); } -std::shared_ptr not_allowed_custom(const httpserver::http_request&) { - return std::shared_ptr(new httpserver::string_response("Not allowed custom", 405, "text/plain")); +httpserver::http_response not_allowed_custom(const httpserver::http_request&) { + return httpserver::http_response::string("Not allowed custom").with_status(405); } class hello_world_resource : public httpserver::http_resource { public: std::shared_ptr render(const httpserver::http_request&) { - return std::shared_ptr(new httpserver::string_response("Hello, World!")); + return std::shared_ptr(new httpserver::http_response(httpserver::http_response::string("Hello, World!"))); } }; int main() { - httpserver::webserver ws = httpserver::create_webserver(8080) - .not_found_resource(not_found_custom) - .method_not_allowed_resource(not_allowed_custom); - - hello_world_resource hwr; - hwr.disallow_all(); - hwr.set_allowing("GET", true); - ws.register_resource("/hello", &hwr); + httpserver::webserver ws{httpserver::create_webserver(8080) + .not_found_handler(not_found_custom) + .method_not_allowed_handler(not_allowed_custom)}; + + auto hwr = std::make_shared(); + hwr->disallow_all(); + hwr->set_allowing(httpserver::http_method::get, true); + ws.register_path("/hello", hwr); ws.start(true); return 0; diff --git a/examples/daemon_info.cpp b/examples/daemon_info.cpp index c854bbac..868ddebc 100644 --- a/examples/daemon_info.cpp +++ b/examples/daemon_info.cpp @@ -25,17 +25,17 @@ class hello_resource : public httpserver::http_resource { public: - std::shared_ptr render_GET(const httpserver::http_request&) { - return std::make_shared("Hello, World!"); + std::shared_ptr render_get(const httpserver::http_request&) { + return std::make_shared(httpserver::http_response::string("Hello, World!")); } }; int main() { // Use port 0 to let the OS assign an ephemeral port - httpserver::webserver ws = httpserver::create_webserver(0); + httpserver::webserver ws{httpserver::create_webserver(0)}; - hello_resource hr; - ws.register_resource("/hello", &hr); + auto hr = std::make_shared(); + ws.register_path("/hello", hr); ws.start(false); // Query daemon information @@ -53,7 +53,7 @@ int main() { << ". Press Ctrl+C to stop." << std::endl; // Block until interrupted - ws.sweet_kill(); + ws.stop_and_wait(); return 0; } diff --git a/examples/deferred_with_accumulator.cpp b/examples/deferred_with_accumulator.cpp index a4367773..348dfe7f 100644 --- a/examples/deferred_with_accumulator.cpp +++ b/examples/deferred_with_accumulator.cpp @@ -20,6 +20,7 @@ #include #include +#include #include // cpplint errors on chrono and thread because they are replaced (in Chromium) by other google libraries. // This is not an issue here. @@ -60,17 +61,30 @@ ssize_t test_callback(std::shared_ptr > closure_data, char* buf class deferred_resource : public httpserver::http_resource { public: - std::shared_ptr render_GET(const httpserver::http_request&) { + std::shared_ptr render_get(const httpserver::http_request&) { std::shared_ptr > closure_data(new std::atomic(counter++)); - return std::shared_ptr > >(new httpserver::deferred_response >(test_callback, closure_data, "cycle callback response")); + std::string initial = "cycle callback response"; + return std::make_shared( + httpserver::http_response::deferred( + [closure_data, initial, + served = false](std::uint64_t, char* buf, + std::size_t max) mutable -> ssize_t { + if (!served) { + served = true; + std::size_t n = std::min(initial.size(), max); + memcpy(buf, initial.data(), n); + return n; + } + return test_callback(closure_data, buf, max); + })); } }; int main() { - httpserver::webserver ws = httpserver::create_webserver(8080); + httpserver::webserver ws{httpserver::create_webserver(8080)}; - deferred_resource hwr; - ws.register_resource("/hello", &hwr); + auto hwr = std::make_shared(); + ws.register_path("/hello", hwr); ws.start(true); return 0; diff --git a/examples/digest_authentication.cpp b/examples/digest_authentication.cpp index ddf0be77..3f9395d7 100644 --- a/examples/digest_authentication.cpp +++ b/examples/digest_authentication.cpp @@ -26,30 +26,27 @@ class digest_resource : public httpserver::http_resource { public: - std::shared_ptr render_GET(const httpserver::http_request& req) { + std::shared_ptr render_get(const httpserver::http_request& req) { using httpserver::http::http_utils; if (req.get_digested_user() == "") { - return std::make_shared("FAIL", "test@example.com", MY_OPAQUE, true, - http_utils::http_ok, http_utils::text_plain, http_utils::digest_algorithm::MD5); + return std::make_shared(httpserver::http_response::unauthorized("Digest", "test@example.com", "FAIL")); } else { auto result = req.check_digest_auth("test@example.com", "mypass", 300, 0, http_utils::digest_algorithm::MD5); if (result == http_utils::digest_auth_result::NONCE_STALE) { - return std::make_shared("FAIL", "test@example.com", MY_OPAQUE, true, - http_utils::http_ok, http_utils::text_plain, http_utils::digest_algorithm::MD5); + return std::make_shared(httpserver::http_response::unauthorized("Digest", "test@example.com", "FAIL")); } else if (result != http_utils::digest_auth_result::OK) { - return std::make_shared("FAIL", "test@example.com", MY_OPAQUE, false, - http_utils::http_ok, http_utils::text_plain, http_utils::digest_algorithm::MD5); + return std::make_shared(httpserver::http_response::unauthorized("Digest", "test@example.com", "FAIL")); } } - return std::make_shared("SUCCESS", 200, "text/plain"); + return std::make_shared(httpserver::http_response::string("SUCCESS")); } }; int main() { - httpserver::webserver ws = httpserver::create_webserver(8080); + httpserver::webserver ws{httpserver::create_webserver(8080)}; - digest_resource hwr; - ws.register_resource("/hello", &hwr); + auto hwr = std::make_shared(); + ws.register_path("/hello", hwr); ws.start(true); return 0; diff --git a/examples/empty_response_example.cpp b/examples/empty_response_example.cpp index 17a4a443..b57e4291 100644 --- a/examples/empty_response_example.cpp +++ b/examples/empty_response_example.cpp @@ -18,33 +18,35 @@ USA */ +#include + #include #include class no_content_resource : public httpserver::http_resource { public: - std::shared_ptr render_DELETE(const httpserver::http_request&) { + std::shared_ptr render_delete(const httpserver::http_request&) { // Return a 204 No Content response with no body - return std::make_shared( - httpserver::http::http_utils::http_no_content); + return std::make_shared( + httpserver::http_response::empty()); } - std::shared_ptr render_HEAD(const httpserver::http_request&) { + std::shared_ptr render_head(const httpserver::http_request&) { // Return a HEAD-only response with headers but no body - auto response = std::make_shared( - httpserver::http::http_utils::http_ok, - httpserver::empty_response::HEAD_ONLY); + auto response = std::make_shared( + httpserver::http_response::empty(MHD_RF_HEAD_ONLY_RESPONSE) + .with_status(httpserver::http::http_utils::http_ok)); response->with_header("X-Total-Count", "42"); return response; } }; int main() { - httpserver::webserver ws = httpserver::create_webserver(8080); + httpserver::webserver ws{httpserver::create_webserver(8080)}; - no_content_resource ncr; - ws.register_resource("/items", &ncr); + auto ncr = std::make_shared(); + ws.register_path("/items", ncr); ws.start(true); return 0; diff --git a/examples/external_event_loop.cpp b/examples/external_event_loop.cpp index df6d9749..09aa9d25 100644 --- a/examples/external_event_loop.cpp +++ b/examples/external_event_loop.cpp @@ -33,8 +33,8 @@ void signal_handler(int) { class hello_resource : public httpserver::http_resource { public: - std::shared_ptr render_GET(const httpserver::http_request&) { - return std::make_shared("Hello from external event loop!"); + std::shared_ptr render_get(const httpserver::http_request&) { + return std::make_shared(httpserver::http_response::string("Hello from external event loop!")); } }; @@ -42,15 +42,15 @@ int main() { signal(SIGINT, signal_handler); // EXTERNAL_SELECT runs MHD without an internal polling thread; the - // application drives it via run_wait() below. no_thread_safety() can be - // added for a small perf gain when the daemon is only ever touched from - // a single thread, but it is omitted here for portability (some MHD - // builds, notably Windows/MSYS2, reject that combination at start). - httpserver::webserver ws = httpserver::create_webserver(8080) - .start_method(httpserver::http::http_utils::EXTERNAL_SELECT); - - hello_resource hr; - ws.register_resource("/hello", &hr); + // application drives it via run_wait() below. thread_safety(false) can + // be added for a small perf gain when the daemon is only ever touched + // from a single thread, but it is omitted here for portability (some + // MHD builds, notably Windows/MSYS2, reject that combination at start). + httpserver::webserver ws{httpserver::create_webserver(8080) + .start_method(httpserver::http::http_utils::EXTERNAL_SELECT)}; + + auto hr = std::make_shared(); + ws.register_path("/hello", hr); ws.start(false); std::cout << "Server running on port " << ws.get_bound_port() << std::endl; diff --git a/examples/file_upload.cpp b/examples/file_upload.cpp index 0916a4fc..1bb3a795 100644 --- a/examples/file_upload.cpp +++ b/examples/file_upload.cpp @@ -26,7 +26,7 @@ class file_upload_resource : public httpserver::http_resource { public: - std::shared_ptr render_GET(const httpserver::http_request&) { + std::shared_ptr render_get(const httpserver::http_request&) { std::string get_response = "\n"; get_response += " \n"; get_response += "
\n"; @@ -40,10 +40,10 @@ class file_upload_resource : public httpserver::http_resource { get_response += " \n"; get_response += "\n"; - return std::shared_ptr(new httpserver::string_response(get_response, 200, "text/html")); + return std::shared_ptr(new httpserver::http_response(httpserver::http_response::string(get_response, "text/html"))); } - std::shared_ptr render_POST(const httpserver::http_request& req) { + std::shared_ptr render_post(const httpserver::http_request& req) { std::string post_response = "\n"; post_response += "\n"; post_response += "