httpp 0.7.0__tar.gz → 0.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- httpp-0.8.0/.github/workflows/autobahn.yml +52 -0
- {httpp-0.7.0 → httpp-0.8.0}/.github/workflows/cmake.yml +2 -2
- {httpp-0.7.0 → httpp-0.8.0}/.github/workflows/orchestrator.yml +7 -0
- {httpp-0.7.0 → httpp-0.8.0}/.gitignore +1 -1
- httpp-0.8.0/CHANGELOG.md +442 -0
- {httpp-0.7.0 → httpp-0.8.0}/CMakeLists.txt +69 -14
- httpp-0.8.0/HANDOFF.md +144 -0
- {httpp-0.7.0 → httpp-0.8.0}/PKG-INFO +216 -17
- {httpp-0.7.0 → httpp-0.8.0}/README.md +214 -16
- httpp-0.8.0/benchmarks/.gitignore +1 -0
- httpp-0.8.0/benchmarks/CMakeLists.txt +162 -0
- httpp-0.8.0/benchmarks/README.md +42 -0
- {httpp-0.7.0 → httpp-0.8.0}/benchmarks/bench.py +4 -4
- httpp-0.8.0/benchmarks/bench_setup.py +113 -0
- httpp-0.8.0/benchmarks/cpp/cpp_bench.cpp +49 -0
- httpp-0.8.0/benchmarks/cpp/drogon_bench.cpp +53 -0
- httpp-0.8.0/benchmarks/cpp/ws_load.cpp +279 -0
- httpp-0.8.0/benchmarks/cpu_app.py +16 -0
- httpp-0.8.0/benchmarks/cpu_bench.py +24 -0
- httpp-0.8.0/benchmarks/diag_io.sh +100 -0
- httpp-0.8.0/benchmarks/fastapi_app.py +27 -0
- httpp-0.8.0/benchmarks/io_app.py +27 -0
- httpp-0.8.0/benchmarks/io_bench.py +61 -0
- httpp-0.8.0/benchmarks/stress.py +151 -0
- httpp-0.8.0/benchmarks/suite.py +528 -0
- httpp-0.8.0/benchmarks/suite_app.py +61 -0
- httpp-0.8.0/benchmarks/suite_asgi.py +28 -0
- httpp-0.8.0/benchmarks/ws_websockets.py +25 -0
- httpp-0.8.0/checklist.md +98 -0
- {httpp-0.7.0 → httpp-0.8.0}/cmake/EmbedFile.cmake +51 -6
- httpp-0.8.0/docs/Architecture.md +410 -0
- httpp-0.8.0/docs/adr/0001-own-both-layers.md +33 -0
- httpp-0.8.0/docs/adr/0002-standard-asyncio-loop.md +33 -0
- httpp-0.8.0/docs/adr/0003-engine-and-third-party.md +38 -0
- httpp-0.8.0/docs/adr/0004-zero-system-dependencies.md +22 -0
- httpp-0.8.0/docs/adr/0005-multiprocess-workers.md +31 -0
- httpp-0.8.0/docs/adr/0006-own-model-layer.md +32 -0
- httpp-0.8.0/docs/adr/0007-openapi-from-one-plan.md +28 -0
- httpp-0.8.0/docs/adr/0008-swagger-ui-compiled-in.md +35 -0
- httpp-0.8.0/docs/adr/0009-hot-path.md +29 -0
- httpp-0.8.0/docs/adr/0010-mbedtls-threading.md +30 -0
- httpp-0.8.0/docs/adr/0011-logging.md +37 -0
- httpp-0.8.0/docs/adr/0012-run-wins-over-listen.md +35 -0
- httpp-0.8.0/docs/adr/0013-packaging.md +25 -0
- httpp-0.8.0/docs/adr/0014-coding-conventions.md +22 -0
- httpp-0.8.0/docs/adr/0015-testing-and-ci.md +52 -0
- httpp-0.8.0/docs/adr/0016-hybrid-api-parity.md +49 -0
- httpp-0.8.0/docs/adr/0017-python-binding-policy.md +44 -0
- httpp-0.8.0/docs/adr/0018-python-logging-bridge.md +50 -0
- httpp-0.8.0/docs/adr/0019-vendored-deflate-inflate.md +46 -0
- httpp-0.8.0/docs/adr/0020-http-exception-and-response-classes.md +33 -0
- httpp-0.8.0/docs/adr/0021-pool-thread-state-and-two-core-numbers.md +153 -0
- httpp-0.8.0/docs/adr/0022-dual-api-rule-and-delegated-decisions.md +109 -0
- httpp-0.8.0/docs/adr/0023-inline-async-handlers.md +79 -0
- httpp-0.8.0/docs/adr/0024-io-threads-and-lanes.md +50 -0
- httpp-0.8.0/docs/adr/0025-measured-defaults-on-16-threads.md +155 -0
- httpp-0.8.0/docs/adr/0026-websocket.md +111 -0
- httpp-0.8.0/docs/adr/0027-websocket-python-and-async-handshake.md +79 -0
- httpp-0.8.0/docs/adr/0028-autobahn-called-by-the-orchestrator.md +27 -0
- httpp-0.8.0/docs/adr/0029-incremental-utf8-validation.md +30 -0
- httpp-0.8.0/docs/adr/0030-benchmarks-built-by-cmake.md +36 -0
- httpp-0.8.0/docs/adr/0031-compressed-embedded-files.md +50 -0
- httpp-0.8.0/docs/adr/0032-read-only-file-browser.md +54 -0
- httpp-0.8.0/docs/adr/0033-engine-per-request-cost.md +109 -0
- httpp-0.8.0/docs/adr/0034-balanced-connections-between-io-threads.md +49 -0
- httpp-0.8.0/docs/adr/0035-hook-path-needs-no-server-pointer.md +38 -0
- httpp-0.8.0/docs/adr/0036-connection-read-buffer-not-zero-filled.md +28 -0
- httpp-0.8.0/docs/adr/0037-middleware-through-use.md +34 -0
- httpp-0.8.0/docs/adr/0038-graceful-shutdown.md +38 -0
- httpp-0.8.0/docs/adr/0039-reload-supervisor.md +34 -0
- httpp-0.8.0/docs/adr/0040-server-tls.md +45 -0
- httpp-0.8.0/docs/adr/0041-native-cli-and-py.md +59 -0
- httpp-0.8.0/docs/adr/0042-test-client.md +46 -0
- httpp-0.8.0/docs/adr/README.md +61 -0
- httpp-0.8.0/docs/agent.md +385 -0
- httpp-0.8.0/docs/index.rst +13 -0
- {httpp-0.7.0 → httpp-0.8.0}/examples/find_httpp_via_python/CMakeLists.txt +1 -1
- {httpp-0.7.0 → httpp-0.8.0}/examples/python/01_hello_server.py +2 -1
- {httpp-0.7.0 → httpp-0.8.0}/examples/python/02_path_params.py +2 -1
- {httpp-0.7.0 → httpp-0.8.0}/examples/python/03_request_object.py +2 -1
- {httpp-0.7.0 → httpp-0.8.0}/examples/python/04_responses.py +2 -1
- {httpp-0.7.0 → httpp-0.8.0}/examples/python/05_http_methods.py +2 -1
- {httpp-0.7.0 → httpp-0.8.0}/examples/python/06_async_handlers.py +2 -1
- {httpp-0.7.0 → httpp-0.8.0}/examples/python/14_async_client.py +2 -1
- {httpp-0.7.0 → httpp-0.8.0}/include/httpp/client.hpp +10 -4
- {httpp-0.7.0 → httpp-0.8.0}/include/httpp/curl.hpp +2 -2
- {httpp-0.7.0 → httpp-0.8.0}/include/httpp/curl_compat.h +4 -4
- {httpp-0.7.0 → httpp-0.8.0}/include/httpp/download.hpp +2 -2
- {httpp-0.7.0 → httpp-0.8.0}/include/httpp/export.hpp +1 -1
- httpp-0.8.0/include/httpp/file_browser.hpp +34 -0
- httpp-0.8.0/include/httpp/middleware.hpp +54 -0
- {httpp-0.7.0 → httpp-0.8.0}/include/httpp/progress.hpp +3 -3
- httpp-0.8.0/include/httpp/router.hpp +65 -0
- {httpp-0.7.0 → httpp-0.8.0}/include/httpp/server.hpp +94 -3
- {httpp-0.7.0 → httpp-0.8.0}/include/httpp/swagger_ui.hpp +9 -1
- {httpp-0.7.0 → httpp-0.8.0}/include/httpp/url.hpp +1 -1
- httpp-0.8.0/include/httpp/websocket.hpp +107 -0
- {httpp-0.7.0 → httpp-0.8.0}/include/httpp.h +3 -2
- {httpp-0.7.0 → httpp-0.8.0}/pyproject.toml +7 -4
- httpp-0.8.0/src/assets/file_browser.html +185 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/bindings/python/CMakeLists.txt +24 -17
- httpp-0.8.0/src/bindings/python/httpp/__init__.py +78 -0
- httpp-0.8.0/src/bindings/python/httpp/__main__.py +131 -0
- httpp-0.8.0/src/bindings/python/httpp/_background.py +56 -0
- httpp-0.8.0/src/bindings/python/httpp/_forms.py +58 -0
- httpp-0.8.0/src/bindings/python/httpp/_log.py +71 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/bindings/python/httpp/_params.py +95 -8
- httpp-0.8.0/src/bindings/python/httpp/_reload.py +118 -0
- httpp-0.8.0/src/bindings/python/httpp/_router.py +106 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/bindings/python/httpp/_run.py +80 -15
- {httpp-0.7.0 → httpp-0.8.0}/src/bindings/python/httpp/_schema.py +23 -3
- httpp-0.8.0/src/bindings/python/httpp/_websocket.py +187 -0
- httpp-0.8.0/src/bindings/python/httpp/middleware.py +97 -0
- httpp-0.8.0/src/bindings/python/httpp/responses.py +107 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/bindings/python/httpp/server.py +606 -106
- httpp-0.8.0/src/bindings/python/httpp/status.py +9 -0
- httpp-0.8.0/src/bindings/python/httpp/testclient.py +361 -0
- httpp-0.8.0/src/bindings/python/httpp/utils.py +13 -0
- httpp-0.8.0/src/bindings/python/httpp_cli.py +79 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/bindings/python/httpp_cy.pyx +961 -154
- httpp-0.8.0/src/cli/httpp_cli.cpp +414 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/core/async_http.cpp +2 -0
- httpp-0.8.0/src/core/async_server.cpp +1247 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/core/client.cpp +11 -3
- httpp-0.8.0/src/core/content_decode.cpp +78 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/core/curl.cpp +2 -2
- {httpp-0.7.0 → httpp-0.8.0}/src/core/curl_compat.cpp +2 -2
- httpp-0.8.0/src/core/deflate.cpp +96 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/core/download.cpp +9 -7
- httpp-0.8.0/src/core/file_browser.cpp +340 -0
- httpp-0.8.0/src/core/form.cpp +205 -0
- httpp-0.8.0/src/core/inflate.cpp +118 -0
- httpp-0.8.0/src/core/internal/async_server.hpp +153 -0
- httpp-0.8.0/src/core/internal/checksums.hpp +45 -0
- httpp-0.8.0/src/core/internal/content_decode.hpp +23 -0
- httpp-0.8.0/src/core/internal/deflate.hpp +19 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/core/internal/httplib_common.hpp +1 -1
- httpp-0.8.0/src/core/internal/inflate.hpp +37 -0
- httpp-0.8.0/src/core/internal/response_compression.hpp +31 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/core/internal/tls_session.hpp +10 -2
- httpp-0.8.0/src/core/internal/urlencoded.hpp +17 -0
- httpp-0.8.0/src/core/internal/websocket.hpp +134 -0
- httpp-0.8.0/src/core/internal/worker_pool.hpp +179 -0
- httpp-0.8.0/src/core/middleware.cpp +247 -0
- httpp-0.8.0/src/core/response_compression.cpp +106 -0
- httpp-0.8.0/src/core/router.cpp +89 -0
- httpp-0.8.0/src/core/server.cpp +1305 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/core/swagger_ui.cpp +56 -7
- {httpp-0.7.0 → httpp-0.8.0}/src/core/tls_session.cpp +74 -2
- {httpp-0.7.0 → httpp-0.8.0}/src/core/url.cpp +2 -2
- httpp-0.8.0/src/core/websocket.cpp +432 -0
- httpp-0.8.0/src/core/websocket_api.cpp +45 -0
- httpp-0.8.0/src/third_party/CLI11/CLI11.hpp +11527 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/third_party/README.md +37 -3
- httpp-0.8.0/src/third_party/stb_deflate/LICENSE +39 -0
- httpp-0.8.0/src/third_party/stb_deflate/VENDORED.md +9 -0
- httpp-0.8.0/src/third_party/stb_deflate/stb_deflate.h +534 -0
- httpp-0.8.0/src/third_party/stb_inflate/LICENSE +38 -0
- httpp-0.8.0/src/third_party/stb_inflate/VENDORED.md +8 -0
- httpp-0.8.0/src/third_party/stb_inflate/stb_inflate.h +622 -0
- httpp-0.8.0/tests/CMakeLists.txt +22 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cmake/consumer_project/CMakeLists.txt +1 -1
- {httpp-0.7.0 → httpp-0.8.0}/tests/cmake/consumer_project/main.cpp +1 -1
- httpp-0.8.0/tests/cmake/embed_compress/CMakeLists.txt +26 -0
- httpp-0.8.0/tests/cmake/embed_compress/main.cpp +61 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cpp/CMakeLists.txt +8 -3
- httpp-0.8.0/tests/cpp/inflate_fixtures.hpp +14 -0
- httpp-0.8.0/tests/cpp/swagger_original.hpp +16 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cpp/test_async_server.cpp +87 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cpp/test_client.cpp +1 -1
- httpp-0.8.0/tests/cpp/test_client_encoding.cpp +160 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cpp/test_client_request.cpp +4 -4
- {httpp-0.7.0 → httpp-0.8.0}/tests/cpp/test_curl.cpp +4 -4
- httpp-0.8.0/tests/cpp/test_deflate.cpp +73 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cpp/test_download.cpp +18 -0
- httpp-0.8.0/tests/cpp/test_file_browser.cpp +409 -0
- httpp-0.8.0/tests/cpp/test_form.cpp +127 -0
- httpp-0.8.0/tests/cpp/test_inflate.cpp +91 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cpp/test_log.cpp +46 -0
- httpp-0.8.0/tests/cpp/test_router.cpp +96 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cpp/test_server.cpp +66 -0
- httpp-0.8.0/tests/cpp/test_server_after_send.cpp +96 -0
- httpp-0.8.0/tests/cpp/test_server_compression.cpp +93 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cpp/test_server_features.cpp +168 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cpp/test_server_io_routes.cpp +89 -1
- httpp-0.8.0/tests/cpp/test_server_io_threads.cpp +172 -0
- httpp-0.8.0/tests/cpp/test_server_middleware.cpp +283 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cpp/test_server_routing.cpp +4 -3
- httpp-0.8.0/tests/cpp/test_server_shutdown.cpp +172 -0
- httpp-0.8.0/tests/cpp/test_server_tls.cpp +270 -0
- httpp-0.8.0/tests/cpp/test_server_websocket.cpp +712 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cpp/test_swagger_ui.cpp +22 -3
- httpp-0.8.0/tests/cpp/test_swagger_ui_route.cpp +123 -0
- httpp-0.8.0/tests/cpp/test_websocket_session.cpp +638 -0
- httpp-0.8.0/tests/cpp/test_worker_pool.cpp +153 -0
- httpp-0.8.0/tests/cpp/ws_test_client.hpp +171 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/python/CMakeLists.txt +4 -1
- httpp-0.8.0/tests/python/conftest.py +91 -0
- httpp-0.8.0/tests/python/test_abi3_safe.py +17 -0
- httpp-0.8.0/tests/python/test_cli.py +442 -0
- httpp-0.8.0/tests/python/test_cli_path.py +29 -0
- httpp-0.8.0/tests/python/test_client_encoding.py +105 -0
- httpp-0.8.0/tests/python/test_client_keep_alive.py +45 -0
- httpp-0.8.0/tests/python/test_cpp_python_parity.py +211 -0
- httpp-0.8.0/tests/python/test_download_sha256.py +150 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/python/test_embed_file.py +3 -0
- httpp-0.8.0/tests/python/test_file_browser.py +124 -0
- httpp-0.8.0/tests/python/test_forms.py +143 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/python/test_httpp_routes.py +59 -1
- httpp-0.8.0/tests/python/test_inline_async.py +232 -0
- httpp-0.8.0/tests/python/test_io_threads.py +165 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/python/test_log.py +11 -0
- httpp-0.8.0/tests/python/test_log_bridge.py +133 -0
- httpp-0.8.0/tests/python/test_middleware.py +165 -0
- httpp-0.8.0/tests/python/test_path_templates.py +35 -0
- httpp-0.8.0/tests/python/test_pool_thread_state.py +37 -0
- httpp-0.8.0/tests/python/test_query_encoding.py +48 -0
- httpp-0.8.0/tests/python/test_reload.py +149 -0
- httpp-0.8.0/tests/python/test_response_injection.py +173 -0
- httpp-0.8.0/tests/python/test_responses.py +225 -0
- httpp-0.8.0/tests/python/test_router.py +128 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/python/test_run.py +47 -3
- {httpp-0.7.0 → httpp-0.8.0}/tests/python/test_server_features.py +205 -2
- {httpp-0.7.0 → httpp-0.8.0}/tests/python/test_server_raw_request.py +14 -0
- httpp-0.8.0/tests/python/test_shutdown.py +167 -0
- httpp-0.8.0/tests/python/test_testclient.py +290 -0
- httpp-0.8.0/tests/python/test_tls.py +183 -0
- httpp-0.8.0/tests/python/test_websocket.py +540 -0
- httpp-0.8.0/tools/autobahn/README.md +5 -0
- httpp-0.8.0/tools/autobahn/check_report.py +50 -0
- httpp-0.8.0/tools/autobahn/echo_server.py +22 -0
- httpp-0.8.0/tools/autobahn/fuzzingclient.json +7 -0
- httpp-0.7.0/CHANGELOG.md +0 -198
- httpp-0.7.0/docs/Architecture.md +0 -404
- httpp-0.7.0/docs/agent.md +0 -381
- httpp-0.7.0/docs/checklist.md +0 -290
- httpp-0.7.0/docs/index.rst +0 -19
- httpp-0.7.0/handoff.md +0 -479
- httpp-0.7.0/src/bindings/python/httpp/__init__.py +0 -61
- httpp-0.7.0/src/bindings/python/httpp/__main__.py +0 -257
- httpp-0.7.0/src/core/async_server.cpp +0 -654
- httpp-0.7.0/src/core/internal/async_server.hpp +0 -95
- httpp-0.7.0/src/core/internal/worker_pool.hpp +0 -107
- httpp-0.7.0/src/core/server.cpp +0 -853
- httpp-0.7.0/tests/CMakeLists.txt +0 -13
- httpp-0.7.0/tests/cpp/test_swagger_ui_route.cpp +0 -62
- {httpp-0.7.0 → httpp-0.8.0}/.github/workflows/release_template.md +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/.github/workflows/wheels.yml +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/.gitmodules +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/LICENSE.txt +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/benchmarks/asgi_app.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/benchmarks/bench_validate.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/benchmarks/httpp_app.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/cmake/DynamicVersion.cmake +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/cmake/FindCython.cmake +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/cmake/Optimize.cmake +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/cmake/Startup.cmake +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/cmake/UseCython.cmake +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/cmake/httppConfig.cmake.in +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/cmake/httpp_mbedtls_config.h +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/cmake/utils/embedded_files.in.hpp +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/examples/find_httpp_via_python/main.cpp +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/examples/python/07_serve_directory.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/examples/python/08_error_handling.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/examples/python/09_client.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/examples/python/10_request_builder.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/examples/python/11_download.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/examples/python/12_hooks_and_settings.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/examples/python/13_streaming_and_files.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/examples/python/15_progress_and_url.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/include/httpp/log.hpp +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/include/httpp/sha256.hpp +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/requirements-dev.txt +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/bindings/python/_validate_cy.pyx +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/bindings/python/httpp/_http.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/bindings/python/httpp/_validate.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/bindings/python/httpp/errors.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/bindings/python/httpp/models.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/bindings/python/httpp/progress.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/core/internal/async_http.hpp +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/core/internal/blocking_pool.hpp +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/core/internal/log.hpp +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/core/log.cpp +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/core/mbedtls_alt/threading_alt.h +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/core/mbedtls_threading.cpp +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/core/progress.cpp +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/core/sha256.cpp +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/third_party/aixlog/LICENSE +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/third_party/aixlog/aixlog.hpp +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/third_party/cpp-httplib/httplib.h +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/third_party/indicators/indicators.hpp +0 -0
- {httpp-0.7.0/src → httpp-0.8.0/src/third_party}/llhttp/LICENSE +0 -0
- {httpp-0.7.0/src → httpp-0.8.0/src/third_party}/llhttp/VENDORED.md +0 -0
- {httpp-0.7.0/src → httpp-0.8.0/src/third_party}/llhttp/include/llhttp.h +0 -0
- {httpp-0.7.0/src → httpp-0.8.0/src/third_party}/llhttp/src/api.c +0 -0
- {httpp-0.7.0/src → httpp-0.8.0/src/third_party}/llhttp/src/http.c +0 -0
- {httpp-0.7.0/src → httpp-0.8.0/src/third_party}/llhttp/src/llhttp.c +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/third_party/swagger-ui/LICENSE +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/third_party/swagger-ui/NOTICE +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/third_party/swagger-ui/VENDORED.md +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/third_party/swagger-ui/swagger-api-icon.svg +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/third_party/swagger-ui/swagger-ui-bundle.js +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/third_party/swagger-ui/swagger-ui-bundle.js.LICENSE.txt +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/src/third_party/swagger-ui/swagger-ui.css +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cmake/CMakeLists.txt +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cmake/run_consumer_test.cmake +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cpp/local_server.hpp +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cpp/test_async_http.cpp +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cpp/test_async_https.cpp +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cpp/test_async_pool_proxy.cpp +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cpp/test_certs.hpp +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cpp/test_client_async_engine.cpp +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cpp/test_client_session.cpp +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cpp/test_curl_compat.cpp +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cpp/test_progress.cpp +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cpp/test_sha256.cpp +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cpp/test_url.cpp +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/cpp/tls_test_server.hpp +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/python/test_async_engine.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/python/test_client.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/python/test_download_progress_url.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/python/test_examples.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/python/test_httpp_basic.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/python/test_models_vs_pydantic.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/python/test_openapi.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/python/test_params_depends.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/python/test_validate_vs_pydantic.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/utest/utest.h +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tests/utest/utest_main.cxx +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tools/fastapi_probes/probe_fastapi_deps_responses.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tools/fastapi_probes/probe_fastapi_params.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/tools/fastapi_probes/probe_pydantic_types.py +0 -0
- {httpp-0.7.0 → httpp-0.8.0}/version.py +0 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: autobahn
|
|
2
|
+
|
|
3
|
+
# Called by the orchestrator after its `init` job (which clears the __beta__ assets), so the summary is uploaded after the clean-up
|
|
4
|
+
# and never deleted by it. It can also be run by hand. ADR-0028.
|
|
5
|
+
# WebSocket conformance (RFC 6455) with the Autobahn test suite against the Python echo server. Cases 12.* and 13.* (permessage-deflate) are excluded.
|
|
6
|
+
on:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
workflow_call:
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: write
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
autobahn:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v6
|
|
18
|
+
with:
|
|
19
|
+
submodules: recursive
|
|
20
|
+
- uses: actions/setup-python@v6
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.12"
|
|
23
|
+
- name: Build and install httpp
|
|
24
|
+
run: pip install .
|
|
25
|
+
- name: Start the echo server
|
|
26
|
+
run: |
|
|
27
|
+
python tools/autobahn/echo_server.py 9001 &
|
|
28
|
+
sleep 2
|
|
29
|
+
- name: Run the Autobahn fuzzing client
|
|
30
|
+
working-directory: tools/autobahn
|
|
31
|
+
run: |
|
|
32
|
+
mkdir -p reports
|
|
33
|
+
docker run --rm --net=host -v "$PWD:/config" -v "$PWD/reports:/reports" crossbario/autobahn-testsuite wstest -m fuzzingclient -s /config/fuzzingclient.json
|
|
34
|
+
- name: Summarise the report
|
|
35
|
+
id: summary
|
|
36
|
+
run: python tools/autobahn/check_report.py tools/autobahn/reports/index.json autobahn-report.md
|
|
37
|
+
continue-on-error: true
|
|
38
|
+
- name: Publish the summary to the __beta__ release
|
|
39
|
+
if: always()
|
|
40
|
+
env:
|
|
41
|
+
GH_TOKEN: ${{ github.token }}
|
|
42
|
+
run: |
|
|
43
|
+
gh release view __beta__ >/dev/null 2>&1 || gh release create __beta__ --prerelease --title __beta__ --notes "pre-release builds"
|
|
44
|
+
gh release upload __beta__ autobahn-report.md --clobber
|
|
45
|
+
- uses: actions/upload-artifact@v4
|
|
46
|
+
if: always()
|
|
47
|
+
with:
|
|
48
|
+
name: autobahn-report
|
|
49
|
+
path: tools/autobahn/reports
|
|
50
|
+
- name: Fail if a case FAILED
|
|
51
|
+
if: steps.summary.outcome == 'failure'
|
|
52
|
+
run: exit 1
|
|
@@ -44,7 +44,7 @@ jobs:
|
|
|
44
44
|
|
|
45
45
|
- name: Run tests (Linux/macOS)
|
|
46
46
|
if: runner.os != 'Windows'
|
|
47
|
-
run: ctest --test-dir build --output-on-failure --extra-verbose
|
|
47
|
+
run: ctest --test-dir build --output-on-failure --extra-verbose --timeout 900
|
|
48
48
|
|
|
49
49
|
build-coverage:
|
|
50
50
|
runs-on: ubuntu-latest
|
|
@@ -85,7 +85,7 @@ jobs:
|
|
|
85
85
|
- name: Build and run C++ tests
|
|
86
86
|
run: |
|
|
87
87
|
cmake --build build --target test_httpp_cpp -j4
|
|
88
|
-
ctest --test-dir build -R test_httpp_cpp --output-on-failure
|
|
88
|
+
ctest --test-dir build -R test_httpp_cpp --output-on-failure --timeout 900
|
|
89
89
|
|
|
90
90
|
- name: Capture coverage
|
|
91
91
|
run: |
|
|
@@ -56,6 +56,13 @@ jobs:
|
|
|
56
56
|
uses: ./.github/workflows/wheels.yml
|
|
57
57
|
needs: [init]
|
|
58
58
|
|
|
59
|
+
# Informational: release-wheels does not wait for it, a FAILED case must not block publishing the wheels (ADR-0028).
|
|
60
|
+
autobahn:
|
|
61
|
+
uses: ./.github/workflows/autobahn.yml
|
|
62
|
+
needs: [init]
|
|
63
|
+
permissions:
|
|
64
|
+
contents: write
|
|
65
|
+
|
|
59
66
|
release-wheels:
|
|
60
67
|
needs: [wheel, init]
|
|
61
68
|
runs-on: ubuntu-latest
|
httpp-0.8.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to httpp are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project follows
|
|
5
|
+
[Semantic Versioning](https://semver.org/) (while the major version is 0, a minor release may still change an API;
|
|
6
|
+
such changes are listed under **Changed**).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`httpp.testclient.TestClient`** (ADR-0042): a Starlette-shaped client for testing your app. It serves the app on a free loopback port of its own and goes through httpp's client, so tests meet the real routing, middleware and error paths. Cookies, redirects, `base_url`/`Host`, `raise_server_exceptions`, startup and shutdown hooks (`with TestClient(app)`). Not yet: `websocket_connect`, multipart `files=`, an in-memory mode.
|
|
13
|
+
|
|
14
|
+
### Changed (breaking)
|
|
15
|
+
|
|
16
|
+
- The command line is a native executable now (ADR-0041): `httpp server`, `httpp browse`, `httpp download`, `httpp curl` and `httpp --version` run without Python and without the GIL (new `--shutdown-timeout` on
|
|
17
|
+
`server` and `browse`). The Python commands moved behind `py`: **`httpp run ...` is now `httpp py run ...`, and `httpp path ...` is now `httpp py path ...`** (`python -m httpp ...` is the same program as `httpp ...`, so it is `python -m httpp py run ...` too). The old spelling
|
|
18
|
+
fails with exit 2 and says what to type. Everything after `py` goes to Python untouched.
|
|
19
|
+
- **The wheel keeps its C++ parts in `httpp/sdk/`** (`sdk/bin/httpp`, `sdk/include/`, `sdk/lib/`, CMake config in `sdk/lib/cmake/httpp/`; on Windows the DLL is in `sdk/bin`, next to `httpp.exe`): the shape of an SDK install prefix. `httpp.get_include_dir()`, `get_lib_dir()` and `get_cmake_dir()` return the new places, so code that uses them is unaffected; anything that hard-coded `httpp/lib` or `httpp/include` must change. `find_package(httpp)` also works with `CMAKE_PREFIX_PATH` set to that `sdk` folder.
|
|
20
|
+
- The native executable is inside the package (`httpp/sdk/bin/httpp`, `httpp.exe` on Windows); the `httpp` console script is `httpp_cli.py` (a module next to the package, so a native command does not import it: 25 ms to start, not 80), which finds it (restoring its execute bit if an installer dropped it) and runs it. `python -m httpp ARGS` is the same program as `httpp ARGS`
|
|
21
|
+
(it calls the launcher). C++: CMake target `httpp_cli` (`-DHTTPP_BUILD_CLI=OFF` to skip), installed to `bin/` with the SDK. `HTTPP_PYTHON` names the interpreter the executable starts for `py` when it is run on its own.
|
|
22
|
+
|
|
23
|
+
### Removed
|
|
24
|
+
|
|
25
|
+
- `httpp install` and `httpp uninstall` are gone (they were never released); `httpp path` (`--include-dir`, `--lib-dir`, `--cmake-dir`) is the only SDK helper.
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- C++: `server::use(before, after)` for middleware next to the application's own hooks, `httpp::add_cors` and `httpp::add_trusted_host` (`httpp/middleware.hpp`) (ADR-0037).
|
|
30
|
+
- Python: `Server.add_middleware(CORSMiddleware | TrustedHostMiddleware | GZipMiddleware, **options)`, shaped like Starlette's; the work runs in C++ without the GIL.
|
|
31
|
+
- C++: `httpp::mount_file_browser(server&, base, root, file_browser_options)`, a read-only file browser (list, navigate, preview, download; theme, accent, filter, hidden files, entry limit) with its own
|
|
32
|
+
embedded page and no dependencies (ADR-0032).
|
|
33
|
+
- Python: `Server.mount_file_browser(base, root, **options)` (same options as C++) and the `httpp browse [DIRECTORY]` command.
|
|
34
|
+
- C++: `server::directory_options` and `serve_directory(mount, dir, options)`: `index`, `hidden` and `download_active` (nosniff, attachment for active types). The defaults keep the old behaviour.
|
|
35
|
+
|
|
36
|
+
- TLS for the server (ADR-0040): C++ `server::set_tls(cert, key, password)`, `request::secure` / `scheme()`, `add_https_redirect`; Python `Server.set_tls`, `request.secure` / `request.scheme`, `HTTPSRedirectMiddleware`,
|
|
37
|
+
`run(ssl_certfile=, ssl_keyfile=, ssl_keyfile_password=)` and `httpp py run --ssl-certfile --ssl-keyfile --ssl-keyfile-password` (also with `--workers` and `--reload`). https and wss; TLS 1.2/1.3; no client certificates.
|
|
38
|
+
- Python: `httpp py run --reload` (`--reload-dir`, `--reload-pattern`, `run(reload=True, ...)`): restarts the app when a source file changes, development only (ADR-0039).
|
|
39
|
+
- C++: `server::shutdown(grace)`; Python: `Server.shutdown(grace)`, `Server.shutdown_timeout`, `run(shutdown_timeout=...)` and `httpp py run --shutdown-timeout` (ADR-0038).
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- Server: a NUL byte in a path under a mounted directory is refused.
|
|
44
|
+
- Server: a late answer through the `after` hook, error handler or compression no longer reads the server object after the server is destroyed, and a refused job runs on the current thread
|
|
45
|
+
instead of using a moved-from response (ADR-0035). No API change.
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- Ctrl+C and SIGTERM in `httpp py run` / `Server.listen` now drain: requests in progress finish (answered with `Connection: close`) within `shutdown_timeout` (default 10 s) instead of being cut; a second Ctrl+C cuts at once (ADR-0038).
|
|
50
|
+
- Server: the method and target are compared with string literals through `std::string_view` (no `strlen`; about 0.8 percent fewer instructions per request, ADR-0033 addendum). No API change.
|
|
51
|
+
- Server: the 16 KiB read buffer of a connection is no longer zero-filled when the connection is made (ADR-0036). No API change.
|
|
52
|
+
- Server: with several I/O threads (`set_io_threads`), new connections go to the I/O thread with the fewest connections instead of wherever the kernel's `SO_REUSEPORT` hash put them (ADR-0034). No API change.
|
|
53
|
+
- Server: about 23 percent fewer instructions per `route_io` request (ADR-0033, items 9 to 12): no shared reference count per answer when no hook or compression is set, the response head
|
|
54
|
+
is written without `strlen`, responses are moved once instead of once per layer, a request answered on the spot is not entered in the 504 list. One visible detail: the 504 deadline of
|
|
55
|
+
an unanswered asynchronous request starts when its handler returns, not at arrival. No API change.
|
|
56
|
+
|
|
57
|
+
## [0.8.0] - 2026-11-01
|
|
58
|
+
|
|
59
|
+
WebSocket in the C++ core and in Python, checked with the Autobahn conformance suite, and a benchmark suite built by CMake that
|
|
60
|
+
now sets httpp next to Drogon. One public C++ type changed: `swagger_ui_asset` (see the first entry).
|
|
61
|
+
|
|
62
|
+
- **Autobahn result** (second run, after [ADR-0029](docs/adr/0029-incremental-utf8-validation.md)): no FAILED case, 589 OK, 7 NON-STRICT and 6 INFORMATIONAL.
|
|
63
|
+
The `permessage-deflate` cases (12 and 13) are left out on purpose. The NON-STRICT cases (3.2, 3.3, 4.1.3, 4.1.4, 4.2.3, 4.2.4, 5.15) are behaviour the RFC leaves open.
|
|
64
|
+
- **Build**: `embed_files(... COMPRESS)` embeds gzip-compressed bytes (same bytes on every build), with `<name>_raw_size` and `<name>_is_gzip`; `-DHTTPP_EMBED_COMPRESS=OFF` turns it off. Swagger UI uses it: `libhttpp_core.so` is about 1.3 MB smaller, and `/docs` files are sent as stored (`Content-Encoding: gzip`, `Vary: Accept-Encoding`) to clients that accept gzip and inflated for the others. **Changed:** `httpp::swagger_ui_asset::data` holds the stored bytes (gzip unless built with `HTTPP_EMBED_COMPRESS=OFF`); new fields `raw_size` and `gzip`. See [ADR-0031](docs/adr/0031-compressed-embedded-files.md).
|
|
65
|
+
- **WebSocket (RFC 6455) in the C++ core**: `server::route_ws(path, ws_handler)` with a `websocket` handle (`send_text`, `send_binary`, `ping`, `close`,
|
|
66
|
+
`pause_reading`, `buffered_amount`). Callbacks run on the I/O thread; sends are thread-safe and the send queue is bounded (`max_buffered`, `status::full`,
|
|
67
|
+
`on_drain`). Text, binary, fragmentation, ping/pong, close handshake, UTF-8 and size limits, optional keep-alive pings; no extensions (no `permessage-deflate`).
|
|
68
|
+
The engine got a generic upgrade "tunnel". See [ADR-0026](docs/adr/0026-websocket.md).
|
|
69
|
+
- **WebSocket in Python**: `@app.websocket("/ws/{room}")` with an `async def` handler taking `websocket` (`accept`, `receive*`, `async for`, `send_*`, `ping`, `close`,
|
|
70
|
+
`WebSocketDisconnect`). One task per connection on the lane's loop; the I/O thread fills a per-connection mailbox without the GIL. Receiving is bounded (`max_queued`, the socket
|
|
71
|
+
is not read past it) and `await send_*` waits when the send queue is full. Browsers' `Origin` must match the host unless `origins=` says otherwise. The C++ handshake is now
|
|
72
|
+
asynchronous (`ws_acceptor`, `websocket::user()`, `on_close` exactly once per accepted upgrade). See [ADR-0027](docs/adr/0027-websocket-python-and-async-handshake.md).
|
|
73
|
+
- WebSocket text messages are checked for UTF-8 as the bytes arrive: a bad byte closes with 1007 at once (also in the middle of a frame or a fragment), instead of at the end of the message. First Autobahn run: no FAILED case; this was the only real finding ([ADR-0029](docs/adr/0029-incremental-utf8-validation.md)).
|
|
74
|
+
- **Benchmarks are a CMake project** (`cmake -S benchmarks -B build-bench`, targets `bench`, `bench_quick`, `bench_check`; the Makefile is gone). New: a WebSocket echo benchmark with its own load generator (`ws_load`), a memory table for many idle WebSocket connections, and Drogon (fetched and built by CMake) next to the httpp C++ API ([ADR-0030](docs/adr/0030-benchmarks-built-by-cmake.md)).
|
|
75
|
+
- Autobahn runs as a job of the orchestrator, after its clean-up of the `__beta__` release ([ADR-0028](docs/adr/0028-autobahn-called-by-the-orchestrator.md)).
|
|
76
|
+
- Fixed a data race with several I/O threads (`set_io_threads`, Python `io_threads`): the cached `Date` header text was shared by all of them, so two threads could
|
|
77
|
+
write and read it together. ThreadSanitizer reported it, and in the benchmarks two runs out of about 150 lost the server (a round with zero answers). The cache is now per thread.
|
|
78
|
+
- Answers from other threads are batched into one Asio post per burst, which cuts the cost of the thread hop.
|
|
79
|
+
- An `async def` handler that cannot suspend (no `await`, `async with` or `async for`, no `Depends`; Python 3.12+) runs on the I/O thread when the
|
|
80
|
+
loop thread is asleep and no `def` handler is running, so it skips the hop to the asyncio loop: 54.7k to 71.5k req/s on the `hello` benchmark
|
|
81
|
+
(2 cores, one box, interleaved). Handlers that await are unchanged. See [ADR-0023](docs/adr/0023-inline-async-handlers.md).
|
|
82
|
+
- `async def` and `route_async` / `route_io` requests allocate about 8 fewer objects (30 to 22 per request): `responder` keeps its state in plain members, and that state is released on the I/O thread with the answer. About 4 percent more req/s and 8 percent less CPU for an awaiting `async def`.
|
|
83
|
+
|
|
84
|
+
### Fixed
|
|
85
|
+
- **A failing timing assertion could hang CI for ever.** The `worker_pool` gate tests returned early when an assertion failed, the gate stayed closed and the
|
|
86
|
+
pool's destructor joined blocked threads without end (a 14 minute macOS step). The tests now open the gate from an RAII guard, wait in bounded loops, and the
|
|
87
|
+
CMake workflow runs `ctest --timeout 900`. The growth test no longer depends on a tight wall-clock bound (`help_after` 10 ms, limit 250 ms).
|
|
88
|
+
- WebSocket tests poll with a timeout instead of sleeping for a fixed time.
|
|
89
|
+
|
|
90
|
+
### Changed
|
|
91
|
+
- **Less work per request in the engine ([ADR-0033](docs/adr/0033-engine-per-request-cost.md))**: the Asio reactor runs without locking (sockets and timers were already
|
|
92
|
+
used on the loop thread only); an answer made on the loop thread (`route_io`, an inline handler) is sent at once instead of going through the poster; connection timers
|
|
93
|
+
are armed lazily (one deadline per connection, no timer work per keep-alive request); the response head is built without temporaries. About 27 percent fewer
|
|
94
|
+
instructions per `route_io` request (counted with callgrind: 25.5k to 18.3k; wall-clock gain not measured on a many-core machine). ThreadSanitizer is clean on the whole C++ suite.
|
|
95
|
+
- The 504 backstop for async requests keeps one list per I/O thread instead of one shared list under one mutex, so I/O threads no longer contend on it (`route_io` and Python `async def` with several I/O threads).
|
|
96
|
+
- **`keep_alive_max` (C++ `set_keep_alive_max`, `Server(keep_alive_max=...)`) now defaults to 1000 and `0` means no limit.** It defaulted to 100, and `0` meant 1.
|
|
97
|
+
- **Defaults from the first 16-thread measurement ([ADR-0025](docs/adr/0025-measured-defaults-on-16-threads.md))**: `Server(io_threads=None)` now
|
|
98
|
+
means `min(4, CPUs // 4)` with a GIL (1 on a free-threaded Python and on Windows); the inline `async def` path of ADR-0023 is on by default only
|
|
99
|
+
with 2 CPUs or fewer (`Server(inline_async=True/False)` forces it): on a machine with cores to spare it was 20 percent slower at saturation.
|
|
100
|
+
- **`set_thread_pool(base, max, grow_after)`** (Python: `grow_after_ms`): how long a job waits for a free worker before the pool grows. A step adds one thread per 500 us of that wait
|
|
101
|
+
(so one thread with the default wait, as before).
|
|
102
|
+
- With a GIL the worker pool starts with one thread and grows after a 10 ms wait (the maximum is unchanged): on 16 threads one worker gave 16.7k req/s on
|
|
103
|
+
a CPU-bound `def` handler, sixteen 10.6k (ADR-0025).
|
|
104
|
+
|
|
105
|
+
### Added
|
|
106
|
+
- Benchmark suite: JSON and FastAPI rows, `io_threads` 1, 2, 4, 8 for Python too, library versions and compiler in the header, standard deviation, a fixed-rate
|
|
107
|
+
latency phase (p99.9 at half of each maximum), efficiency cores for the load generator.
|
|
108
|
+
- **`server::set_io_threads(n)`, `Server(io_threads=N)` and `httpp run --io-threads N`**: several I/O threads share the port (one engine and
|
|
109
|
+
one acceptor each, `SO_REUSEPORT`; not on Windows, which keeps one). On a free-threaded Python every I/O thread also gets its own asyncio
|
|
110
|
+
loop thread, so `async def` handlers use several cores; with a GIL there is still one loop. Default 1. See
|
|
111
|
+
[ADR-0024](docs/adr/0024-io-threads-and-lanes.md). Not yet measured on a many-core machine: `benchmarks/Makefile`.
|
|
112
|
+
- **`benchmarks/Makefile`** (`make bench`): builds venvs for 3.13, 3.14 and 3.14t, the C++ benchmark server and `oha`, then compares
|
|
113
|
+
uvicorn, httpp `def` / `async def` / `await` and the C++ API at several I/O thread counts into one text file.
|
|
114
|
+
- **`server::set_body_timeout(seconds)` and `Server(body_timeout=...)`**: the whole request body must arrive within this time after
|
|
115
|
+
its head (default 120 s, `0` = no limit). Before, a body dripped one byte at a time was held only by the per-read `read_timeout`.
|
|
116
|
+
- **`server::set_compression(min_size, level = 1)` and `Server(compression=..., compression_level=...)`**: gzip for text answers
|
|
117
|
+
(`text/*`, JSON, XML, JavaScript, SVG) when the client sends `Accept-Encoding: gzip`. Off by default. It runs after the hooks, adds
|
|
118
|
+
`Vary: Accept-Encoding`, and leaves files, streams, partial answers, already encoded bodies and bodies it cannot shrink alone. On a
|
|
119
|
+
`route_io` answer the work moves to a worker, never the I/O thread. Uses the vendored deflate: no new dependency.
|
|
120
|
+
- **Routers**: `httpp::router` and `server::include(router, prefix)` in C++, `APIRouter` and `Server.include` (alias `include_router`) in
|
|
121
|
+
Python. Same rules in both: outer prefix, the router's own prefix, then the path; `/` under a prefix is the prefix. Nesting works.
|
|
122
|
+
No request-time cost: prefixes are joined when the router is included.
|
|
123
|
+
- **`response::after_send`** (C++) and, in Python, the injected **`response`** argument and **`BackgroundTasks`**: a job that runs on a
|
|
124
|
+
worker thread after the answer was written, for work the client should not wait for. The injected `response` adds a status,
|
|
125
|
+
headers and cookies to a handler's answer. A throwing job is logged and ignored.
|
|
126
|
+
- **Forms and uploads**: `request::form()` in C++ (`form_data`, `upload_file`) parses urlencoded and `multipart/form-data` bodies, only when a
|
|
127
|
+
handler asks. Python: `request.form()` (a malformed body answers 400), and FastAPI-style `Form()`, `File()` and `UploadFile` parameters
|
|
128
|
+
with 422 errors and OpenAPI request bodies. `percent_decode` and `parse_query` moved to one internal header.
|
|
129
|
+
- `ServerRequest.header()`, `.query_param()`, `.path_param()` and `.cookie()`: the lookups the C++ `request` already had, same names,
|
|
130
|
+
`""` when absent (a second argument sets another default).
|
|
131
|
+
- ADR-0022: the dual C++/Python rule and the decisions the owner delegated.
|
|
132
|
+
|
|
133
|
+
### Changed
|
|
134
|
+
- A failing `done()` of an `async def` route (for example a bad header) now answers a 500 at once instead of leaving the client to the 504.
|
|
135
|
+
- **The worker pool wakes threads last-in first-out, and does not wake a second thread until a job has waited 500 us** (a scout
|
|
136
|
+
thread watches the queue), so light `def` handlers run on one hot thread instead of passing the Python lock around. On two cores,
|
|
137
|
+
`def` `hello` went from 16k to 36k req/s (3.13) and from 19k to 33k (3.14t); `item` from 12k to 25k and from 17k to 30k. A
|
|
138
|
+
blocked handler delays queued requests by about 500 us. New `tests/cpp/test_worker_pool.cpp` (also run under ThreadSanitizer).
|
|
139
|
+
|
|
140
|
+
### Changed (async)
|
|
141
|
+
- **`async def` routes are about 30 percent faster:** one shared timer for `handler_timeout` instead of a timer per request, and a prebuilt JSON
|
|
142
|
+
encoder (same output as `json.dumps`; CPython only, a circular structure now ends in a RecursionError, still a 500).
|
|
143
|
+
- **`async def` handlers start eagerly on Python 3.12+:** a handler that never waits finishes inside `dispatch` without a trip through the
|
|
144
|
+
loop's ready queue (2 to 8 percent on `hello` / `item`).
|
|
145
|
+
- **The `async def` inbox is drained in batches:** one lock per batch of queued requests instead of one per request (about 12 percent on
|
|
146
|
+
`hello`, 17 percent on `item`, 2 cores).
|
|
147
|
+
- **Faster answers for `str`, `dict` and `list` results,** and no `handler_timeout` bookkeeping for a handler that finishes at once
|
|
148
|
+
(together with the eager start about 20 percent on `hello` / `item`, 2 cores).
|
|
149
|
+
|
|
150
|
+
### Fixed
|
|
151
|
+
- **`Server.listen()` could hang for ever after Ctrl+C on free-threaded Python (seen on the macOS 3.15t wheel test):** it waited on a
|
|
152
|
+
`threading.Event`, and an interrupt that lands inside `Event.wait()` can leave that Event's internal lock taken (or released twice), after which
|
|
153
|
+
`listen()` and the serving thread both block on it. It now polls a plain flag every 50 ms (`time.sleep` holds no lock). Two tests: Ctrl+C at
|
|
154
|
+
many different moments, and a deterministic one that `listen()` never creates an `Event` or `Condition`.
|
|
155
|
+
- **The macOS wheel build works again:** `serve_directory` checked symlinks with `std::filesystem`, which needs macOS 10.15 while the
|
|
156
|
+
wheels target 10.13. It now uses `realpath` on Linux and macOS (`std::filesystem` stays on Windows only). The tests also cover a sibling
|
|
157
|
+
directory whose name starts like the mounted one, and a mounted directory given with a trailing slash.
|
|
158
|
+
- **`def` handlers were slow: a Python thread state was created and destroyed for every request.** Each pool thread now keeps
|
|
159
|
+
its state (`httpp_py_pin_thread`). Measured on 2 cores, `oha`, 32 connections, plain `hello` route: CPython 3.13 9.4k -> 17.9k
|
|
160
|
+
req/s, free-threaded 3.14.6t 1.9k -> 24.6k req/s. A CPU-bound `def` handler on 3.14t now scales with the cores
|
|
161
|
+
(conc 1: 382, conc 8: 734 req/s) while 3.13 stays at about 330. A client that hangs up now closes a sync streaming generator
|
|
162
|
+
at once (its `finally` runs), because on free-threaded CPython an object released from another thread is freed only when
|
|
163
|
+
the thread that created it next runs Python.
|
|
164
|
+
- **A dependency without parameters failed on a route with path parameters** (`TypeError: dep() got an unexpected keyword argument 'n'`):
|
|
165
|
+
every dependency was handed all the path parameters of the route, not only the ones it declares. Found by a stress test; it
|
|
166
|
+
happened on every Python version.
|
|
167
|
+
- **405 instead of 404 for a wrong method.** A path that has a route for another method answers 405 with an `Allow` header
|
|
168
|
+
(a GET route also allows HEAD). A path with no route at all is still 404. (Changed: code that checked for 404 on a wrong method.)
|
|
169
|
+
- **Static files no longer follow a symlink out of the mounted directory.** Such a link answers 404; a link that stays inside is served.
|
|
170
|
+
- **A path parameter that does not parse** (`n: int`, `x: float`) answers 422 with the FastAPI body
|
|
171
|
+
`{"detail": [{"type": "int_parsing", "loc": ["path", "n"], ...}]}` instead of the old hand-made `{"error": ...}`.
|
|
172
|
+
- **abi3 wheel build.** `sha256_hex` took a typed memoryview, which needs `Py_buffer`; the stable ABI has it only from Python 3.11,
|
|
173
|
+
and the cp39-abi3 wheel failed to compile (CI only). It now takes any bytes-like object without one (`bytes` in place, others copied
|
|
174
|
+
once). `tests/python/test_abi3_safe.py` guards against typed memoryviews coming back.
|
|
175
|
+
|
|
176
|
+
### Added
|
|
177
|
+
- **Slowloris guard and a connection limit.** The whole request head (request line and headers) must now arrive within
|
|
178
|
+
`header_timeout` seconds (default 10, `0` = no limit); before, a client sending one byte every few seconds kept the connection
|
|
179
|
+
forever because only each single read had a timeout. `set_max_connections(n)` (C++) and `Server(max_connections=n)` refuse
|
|
180
|
+
the connection over the limit with a 503 and close it (default: no limit). A slowly dripped request **body** is still only
|
|
181
|
+
covered by `read_timeout` per read.
|
|
182
|
+
- **`HTTPException`, response classes, `httpp.status`.** `raise httpp.HTTPException(404)` (or `(status, detail, headers)`) in a
|
|
183
|
+
handler or a dependency answers `{"detail": ...}` with that status; override it with `@app.exception_handler(HTTPException)`.
|
|
184
|
+
`JSONResponse`, `PlainTextResponse`, `HTMLResponse`, `RedirectResponse` (307 by default), `StreamingResponse` and `FileResponse`
|
|
185
|
+
can be returned from a handler and skip `response_model`. `httpp.status.HTTP_404_NOT_FOUND` and the rest. See ADR-0020.
|
|
186
|
+
- **gzip / deflate in the client, no system zlib.** Both the blocking and the async client send `Accept-Encoding: gzip, deflate`
|
|
187
|
+
(unless you set the header yourself) and undo `Content-Encoding: gzip`, `x-gzip`, `deflate` (zlib, or raw as some servers send it)
|
|
188
|
+
and chains of them, so `response.body` is always the real body. Headers are left as received, like requests and httpx. A
|
|
189
|
+
coding we do not advertise (`br`, `zstd`) leaves the body untouched. `max_response_size` limits the **decoded** size
|
|
190
|
+
(a zip bomb ends as `too_large`, status 413); corrupt data ends as `error_kind::other`, never as garbage. Set
|
|
191
|
+
`Accept-Encoding: identity` to ask the server not to compress. `download()` is unchanged in what it sends but now saves a compressed response exactly as received, instead of failing with 415. Built on two vendored decoders/encoders, extracted unmodified:
|
|
192
|
+
`src/third_party/stb_inflate` (the decoder of stb_image.h) and `src/third_party/stb_deflate` (the compressor of libstb), wrapped
|
|
193
|
+
by the internal `httpp::detail::inflate` / `deflate` (zlib, gzip and raw; levels 0 to 9; Adler-32 and CRC-32 checked), and tested
|
|
194
|
+
against Python's zlib and gzip in both directions. The compressor is ready for the server side (a GZip middleware) and for
|
|
195
|
+
compressing request bodies; neither is wired yet.
|
|
196
|
+
- **Access log.** At the `info` log level the server writes one line per answered request (`GET /items/1 200`),
|
|
197
|
+
including the answers the server makes itself (404, 413, ...). It goes through the library's own logging
|
|
198
|
+
(AixLog), so `set_log_level`, `set_log_file` and `set_log_callback` apply to it. Nothing is built or written
|
|
199
|
+
while the level is higher.
|
|
200
|
+
- **"Listening" line.** After a successful bind the server logs `listening on http://host:port` at `info`
|
|
201
|
+
(also for `bind_to_any_port`, with the real port). A port that is already taken only produces the existing
|
|
202
|
+
`cannot listen` error.
|
|
203
|
+
|
|
204
|
+
- **Python parity for downloads and hashing.** `Download.sha256()`, `Download.sha256_url()`, `Download.set_timeout()`,
|
|
205
|
+
`Download.set_max_wait(seconds)` and `DownloadResult.sha256`; `httpp.utils.sha256_hex()`, `httpp.utils.sha256_file()` and
|
|
206
|
+
`httpp.utils.parse_sha256_checksum()` (they raise `RuntimeError` where the C++ functions throw). `Client(keep_alive=False)`
|
|
207
|
+
mirrors `httpp::client::set_keep_alive`.
|
|
208
|
+
- **Parity test.** `tests/python/test_cpp_python_parity.py` fails when a public C++ name has no Python counterpart and no documented reason.
|
|
209
|
+
|
|
210
|
+
### Changed
|
|
211
|
+
- **The C++ log goes through Python's `logging` automatically.** Lines arrive in `logging.getLogger("httpp")`, and that logger's level
|
|
212
|
+
decides what C++ produces: `logging.getLogger("httpp").setLevel(logging.INFO)` (or `logging.config.dictConfig`) now also turns on the access
|
|
213
|
+
log and the `listening on ...` line. `httpp.set_log_level(...)` sets the same logger level. `httpp run` still prints to stderr when the
|
|
214
|
+
application configured no logging. An explicit `set_log_callback`/`set_log_file` replaces the bridge. See ADR-0018.
|
|
215
|
+
- `Client`/`AsyncClient` encode query strings and forms with the C++ `url::build_query` instead of `urllib` (identical output, about 4x
|
|
216
|
+
faster for that step). One path-template parser in Python instead of two; one shared option block for `Client` and `AsyncClient`.
|
|
217
|
+
- **`httpp.run`, `app.run()` and `httpp run` log at `info` by default** (startup line and one line per request on
|
|
218
|
+
stderr, like uvicorn). Use `log_level="warning"` or `--log-level warning` to keep only problems, `off` for
|
|
219
|
+
nothing. `app.listen()` and the C++ API keep the default `warning`, so an embedded server stays quiet until you
|
|
220
|
+
call `set_log_level`. Benchmarks should run at `warning`.
|
|
221
|
+
- **llhttp moved to `src/third_party/llhttp`** (it was `src/llhttp`). The files are unmodified: they match the checksums in
|
|
222
|
+
`VENDORED.md` and the upstream `release/v9.4.3` tag byte for byte. Only the path changed.
|
|
223
|
+
- The `starting on ...` line printed by `run` before binding is gone; the `listening on ...` line replaces it and
|
|
224
|
+
only appears once the port is really bound.
|
|
225
|
+
- **`httpp run` wins over a `listen` in the file.** While `httpp run module:app` imports the module, `app.listen()`
|
|
226
|
+
and `app.run()` calls at import time (without an `if __name__ == "__main__":` guard) are skipped with a warning
|
|
227
|
+
(`httpp: ignoring app.listen('127.0.0.1', 8001): ...`), so `--host`, `--port` and `--workers` apply. Before, the
|
|
228
|
+
file's `listen` blocked the import, the CLI options were silently ignored, and `--workers` failed with a bind
|
|
229
|
+
error. Running the file with `python file.py` is unchanged. Only a string target (`"module:app"`) is affected.
|
|
230
|
+
|
|
231
|
+
### Measured
|
|
232
|
+
First WebSocket and Drogon numbers, from the full `bench` target on a 16 thread machine (medians of 3 rounds of 5 s, 64 connections, `powersave` governor, one box,
|
|
233
|
+
so compare rows of this run only):
|
|
234
|
+
- WebSocket echo, 64 byte messages: Python 3.14 httpp 287k msg/s (1 I/O thread) against 122k for uvicorn and 111k for the `websockets` library; 3.14t with
|
|
235
|
+
4 I/O threads 715k msg/s. C++: httpp 339k, 648k, 1.05M and 973k msg/s with 1, 2, 4 and 8 I/O threads against 373k, 755k, 1.06M and 974k for Drogon.
|
|
236
|
+
- `hello` on Python 3.14: `def` 241k req/s with 1 I/O thread and 521k with 4, against 78k for uvicorn and 22k for FastAPI with uvicorn (run after the engine changes).
|
|
237
|
+
- 0 errors in every row (3 rounds each), which is the first full run without a lost server since the `Date` cache fix.
|
|
238
|
+
|
|
239
|
+
### Known limitations
|
|
240
|
+
- WebSocket: no `permessage-deflate`, and no TLS of its own (`wss` needs a TLS proxy in front; not tried). On WebSocket routes there are no dependencies,
|
|
241
|
+
no injected `response`, no background tasks, no OpenAPI entry and no `APIRouter.websocket` yet. The engine closes with a lingering close (up to 2 s), so the final
|
|
242
|
+
close code can arrive after the close frames.
|
|
243
|
+
- Memory per idle WebSocket connection is higher than in Drogon and in the `websockets` library: about 20 KiB (C++) and 24 KiB (Python) against 8.5 KiB and
|
|
244
|
+
12 KiB (10,000 connections, one run).
|
|
245
|
+
- Plain HTTP in C++ is still slower than Drogon at the same number of I/O threads, by 7 to 17 percent after the changes above (`hello`, `route_io`: 256k, 460k and 630k
|
|
246
|
+
req/s with 1, 2 and 4 threads against 281k, 496k and 760k; a later run than the one in "Measured", same machine). WebSocket echo is about 9 to 14 percent behind with 1 or 2 threads and level with 4 or more.
|
|
247
|
+
- A free-threaded Python still defaults to one I/O thread (`io_threads`), which is about 2.4 times slower than 4 on the `hello` benchmark; set it by hand.
|
|
248
|
+
- The WebSocket code was run on Linux; Windows and macOS were covered by CI only. ThreadSanitizer is clean on the whole C++ suite (384 test cases); the Python layer was not run under it.
|
|
249
|
+
|
|
250
|
+
## [0.7.0] - 2026-10-17
|
|
251
|
+
|
|
252
|
+
The biggest release so far: a native async client and server, a FastAPI-style Python layer with OpenAPI and Swagger
|
|
253
|
+
UI, and a download that verifies, locks and times out.
|
|
254
|
+
|
|
255
|
+
### Added
|
|
256
|
+
|
|
257
|
+
#### Client
|
|
258
|
+
- **Async client** (`Client.run_async()`, `AsyncClient`) running on the library's own non-blocking network thread
|
|
259
|
+
(Asio) with the vendored `llhttp` parser, instead of a shared thread pool. Keep-alive connection pooling and reuse
|
|
260
|
+
across requests, redirect handling, TLS and proxy support.
|
|
261
|
+
- **Streaming responses** and **structured errors** (`httpp.errors`, with an error kind such as `timeout`) for both
|
|
262
|
+
the blocking and the async client; extra URL helpers in `httpp/url.hpp`.
|
|
263
|
+
|
|
264
|
+
#### Server (C++ and Python)
|
|
265
|
+
- **Async HTTP server** (`async_server`, with a worker pool). It speaks plain HTTP: the server has no TLS yet. TLS (Mbed TLS)
|
|
266
|
+
is in the async client, with session caching: TLS contexts for identical (verify, CA file) settings are built once and reused.
|
|
267
|
+
- **Python handlers:** route handlers with path parameters, `async def` handlers, and `listen()` that blocks and
|
|
268
|
+
returns a status (0.6.1); new in this release: `ResponseHead`, `file()`, `redirect()`, `set_cookie()`,
|
|
269
|
+
`ServerRequest.cookies`, streaming responses from generators and async generators, `@app.before_request`,
|
|
270
|
+
`@app.after_request`, error and exception handlers, startup and shutdown hooks, and `handler_timeout` (60 s by
|
|
271
|
+
default) that cancels a hanging async route and answers 504. Header values are checked against injection.
|
|
272
|
+
- **OpenAPI 3.1 and Swagger UI for Python.** Schema generation with Pydantic v2 models, per-route options (`summary`,
|
|
273
|
+
`tags`, `response_model`, ...), and `Query`, `Path`, `Header`, `Cookie`, `Body` and `Depends` (dependencies with their
|
|
274
|
+
own scopes and caching) that drive both the request handling and the generated document, so the docs cannot drift
|
|
275
|
+
from the behaviour.
|
|
276
|
+
- **`httpp::mount_swagger_ui(server, docs_path = "/docs", openapi_url = "/openapi.json", title = "API")`** in C++:
|
|
277
|
+
serves the page and the Swagger UI files (`swagger-ui-bundle.js`, `swagger-ui.css`, `swagger-api-icon.svg`) from
|
|
278
|
+
bytes compiled into the library, with `Cache-Control: public, max-age=86400`. Title and URL are escaped; an invalid
|
|
279
|
+
path throws `std::invalid_argument`. The Python server's `/docs` is now served from C++, without the GIL, and there
|
|
280
|
+
is no CDN or data directory involved.
|
|
281
|
+
- **`cmake/EmbedFile.cmake`** (`embed_files()`): compiles data files into a header at configure time (`constexpr`
|
|
282
|
+
data in `.rodata`, binary-safe, 32 bytes per source line).
|
|
283
|
+
|
|
284
|
+
#### Downloads
|
|
285
|
+
- **`download::sha256(hex)` and `download::sha256_url(url = "")`**: verify the finished file against a SHA-256 digest
|
|
286
|
+
(any letter case) or against a checksum file in `sha256sum` format (default `<url>.sha256`). The checksum file is
|
|
287
|
+
fetched first, so a missing or malformed one (an HTML error page, a name for another file, several lines) fails the
|
|
288
|
+
call before any download. A mismatch deletes the file, returns `ok = false` and says what the digest actually was.
|
|
289
|
+
With `resume()`, a complete-looking but corrupt file is downloaded again once from byte 0. `download_result` has a
|
|
290
|
+
new `sha256` field.
|
|
291
|
+
- **`httpp/sha256.hpp`**: `sha256_hex()`, `sha256_file()` (chunked) and `parse_sha256_checksum()`, on top of the
|
|
292
|
+
Mbed TLS already linked for HTTPS. About 165 MB/s on the machine it was measured on (130 MB file in 0.8 s).
|
|
293
|
+
- **`download::set_timeout(seconds)`**: connect, read and write timeouts of the transfer (like
|
|
294
|
+
`Client::set_timeout`), and also how long to wait for another process that holds the same file's lock.
|
|
295
|
+
- **`download::set_max_wait(milliseconds)`**: how long a lock that is *not being updated* is waited for before its
|
|
296
|
+
owner is taken for dead (default 2 s; the wait is `min(max_wait, timeout)`).
|
|
297
|
+
- **Cross-process lock, always on.** A download takes the directory `<dest>.lock` and its owner rewrites
|
|
298
|
+
`<dest>.lock/alive` every 200 ms at most; a lock that keeps being updated is waited for as long as it lives, one that
|
|
299
|
+
stops is taken over after `min(max_wait, timeout)`. A waiter that finds the file finished and matching `sha256()` /
|
|
300
|
+
`sha256_url()` uses it without a request (`skipped = true`). If the lock directory cannot be created the download
|
|
301
|
+
goes ahead unlocked. Written with `mkdir`/`rmdir` and plain files, no `<filesystem>`.
|
|
302
|
+
|
|
303
|
+
#### Library and tooling
|
|
304
|
+
- **Logging** through the vendored AixLog, with a `set_log_*` API in C, C++ and Python.
|
|
305
|
+
- **Python wheels:** one `cp39-abi3` wheel per platform for every GIL-enabled Python from 3.9 up, plus version-specific
|
|
306
|
+
free-threaded wheels (`cp313t`, `cp314t`, `cp315t`), because free-threaded builds cannot load abi3 wheels.
|
|
307
|
+
- **Documentation:** `docs/Architecture.md` (design, goals and test strategy), `docs/agent.md`, Python micro-benchmark
|
|
308
|
+
for the validators, and README sections for streaming, files, hooks, error handling and the client's new
|
|
309
|
+
capabilities.
|
|
310
|
+
|
|
311
|
+
### Changed
|
|
312
|
+
- **Mbed TLS 4.2.0** (release tag), with threading support switched on (`MBEDTLS_THREADING_C`, via
|
|
313
|
+
`cmake/httpp_mbedtls_config.h`): pthread on POSIX, a small `SRWLOCK`/`CONDITION_VARIABLE` layer on Windows, or on
|
|
314
|
+
every platform with `-DHTTPP_MBEDTLS_THREADING_ALT=ON`. `MBEDTLS_SELF_TEST` is off.
|
|
315
|
+
- **`HTTPP_EMBED_AS_ARRAY` is `ON` on every platform** (it was MSVC only). One code path everywhere and no
|
|
316
|
+
`-Woverlength-strings` warning for the 1.5 MB Swagger UI bundle. Cost: compiling that translation unit takes about
|
|
317
|
+
4.6 s instead of 0.9 s with GCC, once per build. `-DHTTPP_EMBED_AS_ARRAY=OFF` gives string literals again.
|
|
318
|
+
- The Swagger UI tab icon is a real SVG file served at `/docs/swagger-api-icon.svg`; the PNG favicon is gone.
|
|
319
|
+
- `<filesystem>` is not used any more (macOS 10.13 compatibility); `download.cpp` and the lock use `std::ifstream`,
|
|
320
|
+
`std::remove` and platform calls.
|
|
321
|
+
- Asio now drives both server loops and the async client (documentation and build comments updated).
|
|
322
|
+
- Build requirement: `scikit-build-core[pyproject]>=1.1`; the version is read through the standard
|
|
323
|
+
`[[tool.dynamic-metadata]]` table instead of the deprecated `tool.scikit-build.metadata`.
|
|
324
|
+
- A download creates (and removes again) `<dest>.lock` next to the destination file; the destination's directory must
|
|
325
|
+
be writable for the lock to work (otherwise the download is not locked).
|
|
326
|
+
|
|
327
|
+
### Fixed
|
|
328
|
+
- Concurrent TLS handshakes shared Mbed TLS's global random generator without a lock; two handshakes could draw the
|
|
329
|
+
same random bytes. Found with ThreadSanitizer, fixed by the threading support above.
|
|
330
|
+
- Async `after`/`error` hooks, or a slow client, could block the event loop and deadlock concurrent requests; the
|
|
331
|
+
Python adapter now dispatches synchronous work directly when no async hook is registered.
|
|
332
|
+
- **Windows:** a finished download did not replace an existing file (`std::rename` fails when the target exists); it is
|
|
333
|
+
now an atomic `MoveFileExA(MOVEFILE_REPLACE_EXISTING)`. `ws2_32` is linked for `send()`/`WSAGetLastError()`, and the
|
|
334
|
+
`EmbedFile` generator no longer emits a zero-size array for an empty file (MSVC `C2466`).
|
|
335
|
+
- **macOS:** link the Keychain frameworks for the C++ tests; a URL-encoding test relied on `std::isalnum` being false
|
|
336
|
+
for non-ASCII bytes.
|
|
337
|
+
- Coverage builds use atomic profile counters (the server is multi-threaded, lcov saw negative counts); free-threaded
|
|
338
|
+
wheel builds prefer binary wheels so `rpds-py` is not built from source.
|
|
339
|
+
- Tests for concurrent async downloads measure event-loop ticks and overlapping streams instead of wall-clock time.
|
|
340
|
+
- **Release pipeline:** wheels are uploaded as one flat artifact, so `dist/` holds only `.whl` and `.tar.gz` files and
|
|
341
|
+
the PyPI upload no longer fails on a `wheelhouse` sub-directory. Windows wheels are not run through `delvewheel`
|
|
342
|
+
(`httpp_core.dll` is loaded from `httpp/lib/` at import time), and the PyPI step uses `skip-existing`.
|
|
343
|
+
|
|
344
|
+
### Known limitations
|
|
345
|
+
- The async client has no timeout yet, and async hooks hold a thread while they run.
|
|
346
|
+
- `download::set_timeout`, `set_max_wait`, `sha256` and `sha256_url` are not exposed in the Python binding yet.
|
|
347
|
+
- A lock whose owner stops updating it for more than `max_wait` (heavy load, a suspended process) is taken over, and two
|
|
348
|
+
downloads can then run at once. Two waiters taking over the same dead lock in the same instant, or writing the same
|
|
349
|
+
destination, can write it together; use `sha256()` so a damaged result is caught and deleted. Keep `max_wait`
|
|
350
|
+
at 1 s or more.
|
|
351
|
+
- The Windows and macOS changes of this release were checked by CI only; the lock was not run under ThreadSanitizer.
|
|
352
|
+
- `/docs` has been looked at in Chromium only.
|
|
353
|
+
|
|
354
|
+
## [0.6.1] - 2026-10-03
|
|
355
|
+
|
|
356
|
+
### Added
|
|
357
|
+
- Python route handlers with path parameters and `async def` handlers; native async support for Python and C++
|
|
358
|
+
handlers.
|
|
359
|
+
- `listen()` blocks and returns a status in the C++ and Python bindings.
|
|
360
|
+
- Runnable standalone Python examples: server basics, routing, request and response handling, async handlers, static
|
|
361
|
+
files, error handling.
|
|
362
|
+
|
|
363
|
+
### Changed
|
|
364
|
+
- Asio 1.34.2 is a git submodule (no system dependency, better scalability).
|
|
365
|
+
- README rewritten with a clearer description and structure; the Asio documentation was consolidated.
|
|
366
|
+
|
|
367
|
+
### Fixed
|
|
368
|
+
- Windows: winsock type redefinition and missing socket headers in the tests; a Ctrl+C listener test that cannot run
|
|
369
|
+
there (Popen) is skipped and the SIGINT test was replaced with an `interrupt_main` variant.
|
|
370
|
+
|
|
371
|
+
## [0.6.0] - 2026-09-16
|
|
372
|
+
|
|
373
|
+
### Added
|
|
374
|
+
- Resumable downloads with skip-if-complete.
|
|
375
|
+
|
|
376
|
+
### Changed
|
|
377
|
+
- **Breaking:** `download_file` was renamed to `download` and folded into one fluent builder.
|
|
378
|
+
|
|
379
|
+
## [0.5.1] - 2026-09-16
|
|
380
|
+
|
|
381
|
+
### Added
|
|
382
|
+
- `max_response_size` for the client and `follow_redirects` for downloads.
|
|
383
|
+
- `abi3` wheels in CI.
|
|
384
|
+
|
|
385
|
+
### Changed
|
|
386
|
+
- `std::filesystem::remove` replaced by `std::remove` in downloads.
|
|
387
|
+
|
|
388
|
+
## [0.5.0] - 2026-09-15
|
|
389
|
+
|
|
390
|
+
### Added
|
|
391
|
+
- HTTPS support through Mbed TLS (documented in the API docs).
|
|
392
|
+
|
|
393
|
+
### Changed
|
|
394
|
+
- Mbed TLS sources are generated before CMake configures; `jinja2` is a build dependency; submodules are no longer
|
|
395
|
+
fetched recursively by default.
|
|
396
|
+
- macOS: the Keychain frameworks are linked into `httpp_core`.
|
|
397
|
+
- Vendored-code warnings are suppressed and class-level exports avoided.
|
|
398
|
+
- Linux wheels are repaired with `auditwheel`.
|
|
399
|
+
|
|
400
|
+
## [0.4.0] - 2026-09-14
|
|
401
|
+
|
|
402
|
+
### Fixed
|
|
403
|
+
- Linux wheels are repaired with `auditwheel` in cibuildwheel builds.
|
|
404
|
+
|
|
405
|
+
## [0.3.0] - 2026-09-14
|
|
406
|
+
|
|
407
|
+
Release only; no functional change recorded in the history.
|
|
408
|
+
|
|
409
|
+
## [0.2.0] - 2026-09-13
|
|
410
|
+
|
|
411
|
+
### Added
|
|
412
|
+
- curl-compatible API and CLI commands.
|
|
413
|
+
|
|
414
|
+
## [0.1.1] - 2026-09-13
|
|
415
|
+
|
|
416
|
+
### Fixed
|
|
417
|
+
- Linux wheels are repaired with `auditwheel` instead of copying libraries.
|
|
418
|
+
|
|
419
|
+
## [0.1.0] - 2026-09-12
|
|
420
|
+
|
|
421
|
+
First release.
|
|
422
|
+
|
|
423
|
+
### Added
|
|
424
|
+
- C++17 core: client, server and URL parsing (`liburlparser`, IPv4, IPv6 and Public Suffix List aware).
|
|
425
|
+
- Downloads with a progress bar (vendored `indicators`).
|
|
426
|
+
- Python package with `httpp_core` as a shared library bundled in the wheel; macOS rpath handling.
|
|
427
|
+
- CMake package: consumers find httpp through `httpp.get_cmake_dir()`; `HTTPP_BUILDING_DLL` for the shared core.
|
|
428
|
+
- Single umbrella header `include/httpp.h` that carries the version, and `version.py` for tagging and releases.
|
|
429
|
+
- CMake and wheels workflows in CI.
|
|
430
|
+
|
|
431
|
+
[Unreleased]: https://github.com/MohammadRaziei/httpp/compare/v0.8.0...HEAD
|
|
432
|
+
[0.8.0]: https://github.com/MohammadRaziei/httpp/compare/v0.7.0...v0.8.0
|
|
433
|
+
[0.7.0]: https://github.com/MohammadRaziei/httpp/compare/v0.6.1...v0.7.0
|
|
434
|
+
[0.6.1]: https://github.com/MohammadRaziei/httpp/compare/v0.6.0...v0.6.1
|
|
435
|
+
[0.6.0]: https://github.com/MohammadRaziei/httpp/compare/v0.5.1...v0.6.0
|
|
436
|
+
[0.5.1]: https://github.com/MohammadRaziei/httpp/compare/v0.5.0...v0.5.1
|
|
437
|
+
[0.5.0]: https://github.com/MohammadRaziei/httpp/compare/v0.4.0...v0.5.0
|
|
438
|
+
[0.4.0]: https://github.com/MohammadRaziei/httpp/compare/v0.3.0...v0.4.0
|
|
439
|
+
[0.3.0]: https://github.com/MohammadRaziei/httpp/compare/v0.2.0...v0.3.0
|
|
440
|
+
[0.2.0]: https://github.com/MohammadRaziei/httpp/compare/v0.1.1...v0.2.0
|
|
441
|
+
[0.1.1]: https://github.com/MohammadRaziei/httpp/compare/v0.1.0...v0.1.1
|
|
442
|
+
[0.1.0]: https://github.com/MohammadRaziei/httpp/releases/tag/v0.1.0
|