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..7429e821 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. @@ -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); @@ -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); @@ -518,7 +520,7 @@ You should calculate the value of NC_SIZE based on the number of connections per ### Examples of chaining syntax to create a webserver ```cpp - webserver ws = create_webserver(8080) + webserver ws{create_webserver(8080) .no_ssl() .no_ipv6() .no_debug() @@ -528,21 +530,21 @@ You should calculate the value of NC_SIZE based on the number of connections per .no_comet() .no_regex_checking() .no_ban_system() - .no_post_process(); + .no_post_process()}; ``` ## ```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. @@ -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); @@ -944,10 +946,8 @@ libhttpserver provides natively a system to blacklist and whitelist IP addresses 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); @@ -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); @@ -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..eefcffb2 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!")); } }; @@ -46,11 +46,11 @@ int main() { // 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); + httpserver::webserver ws{httpserver::create_webserver(8080) + .start_method(httpserver::http::http_utils::EXTERNAL_SELECT)}; - hello_resource hr; - ws.register_resource("/hello", &hr); + 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..c7d02e9d 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 += "