Skip to content

h1: replace Beast with Boost.HTTP (cppalliance/http) - #46

Open
pgit wants to merge 2 commits into
masterfrom
boost-http
Open

pgit wants to merge 2 commits into
masterfrom
boost-http

Conversation

@pgit

@pgit pgit commented Nov 14, 2026

Copy link
Copy Markdown
Owner

HTTP/1.1 moves from Boost.Beast to Boost.HTTP (cppalliance/http, develop at 1f58e97): its sans-I/O parser and serializer, and boost::http::fields as anyhttp's Fields. After this, no build tree includes a Beast header.

Build

  • cmake/http.cmake FetchContents Boost.HTTP for every tree (ASIO trees too), with capy at the existing pin, and builds only its protocol core as anyhttp_http. Upstream's CMake is skipped because it builds the router/bcrypt/JSON parts and needs Boost.JSON.
  • Two patches in cmake/patches/, applied only once, both to be proposed upstream:
    • http-release-buffered-data.patch: parser::release_buffered_data() is a // TODO stub on develop. The h2c upgrade needs it.
    • http-reserve-bytes-alignment.patch: fields_base::reserve_bytes() left the field table misaligned. Found by UBSAN, and 133 tests fail there without it.
  • The installed package exports anyhttp::http and capy, and ships their headers.

HTTP/1.1 backend

  • h1/io.hpp holds the loops that drive the parser and serializer on the runtime layer. Boost.HTTP's own loops need a capy stream.
  • Each session has one parser and one serializer, and readers and writers use them in turn.
  • Behaviour change: a request released before its body is complete now shuts down the client's send side. Without it, CancellationRange/HTTP11 hangs, because the server waits for the rest of the body.
  • Chunking and Content-Length stay with the library. The body is copied into the serializer's buffer because there is no zero-copy path yet.

Public API (breaking)

  • Fields: iterate with .name / .value, add with append() instead of insert(), look up with value_or(name, "") instead of [].
  • client::Message is now a class derived from boost::http::response with a body() string. result_int() becomes status_int().
  • New: errors::end_of_stream, reason_phrase().

Tested locally

  • build: 612/612, also with ANYHTTP_TEST_SCHEME=https and serially.
  • build-corosio: 576/576.
  • build-gcc, build-h2v2, build-asan, build-tsan: all pass.
  • build-ubsan: clean over cleartext and TLS.
  • build-corosio-gcc: only the three ClientAsync.Recursion cases fail, as they already do on master.
  • scripts/check_package.sh: install, embed-asio and embed-corosio all pass.
  • Include boundaries checked: no Beast anywhere, and no Asio beyond buffer.hpp in the COROSIO library objects.

Benchmark (Release, HTTP/1.1 cleartext, two rounds each, vs master)

Workload Change
Small GETs −3 to −5%
Pipelined (-m 16) about −10%
10 MB download −1%
10 MB upload +5%

🤖 Generated with Claude Code

pgit and others added 2 commits November 14, 2026 20:38
HTTP/1.1 now runs on Boost.HTTP's sans-I/O parser and serializer, and
Fields is boost::http::fields. Nothing in any tree includes Beast any
more.

- cmake/http.cmake FetchContents cppalliance/http at a pinned SHA of
  develop for every tree, with capy at the existing pin, and builds its
  protocol core only, as anyhttp_http. Two patches are applied, both to
  be proposed upstream: parser::release_buffered_data() (a stub on
  develop, needed for the h2c upgrade) and fields_base::reserve_bytes()
  keeping the field table aligned (found by UBSAN).
- h1/io.hpp drives the parser and serializer on the runtime layer; one
  parser and one serializer per session, readers and writers use them
  in turn.
- A request released before its body is complete now shuts the send
  side down, so that neither peer waits forever for the rest of it.
- Public API: Fields iterates as .name/.value and adds with append();
  client::Message derives from boost::http::response with a body();
  new errors::end_of_stream and reason_phrase().
- Buffer is detail::FlatBuffer instead of beast::flat_buffer; the
  installed package carries anyhttp::http and capy along.
- src/issues/beast_issue_3032.cpp is gone with Beast.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ffer in bounds

Two findings of the review of eda7a77:

- A response to HEAD has no payload, but its 'Content-Length' is still
  what it announced: Reader::content_length() reports it, as it did with
  Beast and as HTTP/2 and HTTP/3 do, instead of 0. The HEAD test checks
  the accessor as well as the field now.
- parser::release_buffered_data() (cmake/patches) rebuilt the circular
  buffer with the workspace's whole capacity from a pointer that is not
  its base. Harmless, as only start() and reset() follow, but it claims
  memory past the storage: the buffer now holds the unconsumed body and
  no more.

Changing a patch exposed that the patch step runs again only when its
command changes, and that a changed patch does not apply over its earlier
version: the command now carries each patch's hash, and restores the
pristine files before applying them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant