Skip to content

Add [server] type = cheroot (optional, pure-Python HTTP/1.1 server) - #2270

Merged
pbiering merged 3 commits into
Kozea:masterfrom
deronnax:feature/cheroot-server
Nov 2, 2026
Merged

pbiering merged 3 commits into
Kozea:masterfrom
deronnax:feature/cheroot-server

Conversation

@deronnax

@deronnax deronnax commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Rationale

Following the review discussion, this PR no longer replaces the built-in server. It adds cheroot as a third, optional [server] type next to builtin (still the default) and hypercorn.

cheroot is a pure-Python, actively maintained, cross-platform WSGI server (the one CherryPy uses): thread pool, HTTP/1.1 keep-alive, and native TLS through the stdlib ssl module.

What it does

  • [server] type = cheroot, installed with pip install radicale[cheroot] (cheroot>=8.5.0) or the OS package python3-cheroot. It is only imported when asked for, like hypercorn; if missing, the error names the extra.
  • All [server] options are honoured: hosts (multiple, IPv4/IPv6), max_connections (thread pool size), timeout, ssl, certificate, key, certificate_authority (mandatory client certs), protocol, ciphersuite.
  • IPV6_V6ONLY is forced so IPv4 0.0.0.0 and IPv6 :: can be listened on independently.
  • Graceful shutdown through the existing shutdown_socket mechanism.
  • PATH_INFO/QUERY_STRING are decoded as UTF-8 by a small WSGI middleware (cheroot's native Gateway_u0 is unusable until 🐞 Fix Gateway.gateway_map() to find indirect Gateway subclasses cherrypy/cheroot#854 is released).
  • Server errors go through radicale.log.logger.

Compatibility / tests

  • TestCherootServerRequests re-runs the whole TestBaseServerRequests suite with type = cheroot (SSL, IPv6, CLI, bind failures, external WSGI server), plus a missing-dependency and a UTF-8 path test. Skipped when cheroot is not installed.
  • Verified locally against cheroot 8.5.0 and 10.0.1 (the EL9 version) as well as the latest release. The only incompatibility found was prepare_socket() not accepting reuse_port in old versions, handled.
  • Not tested on a real EL9/Alma/Rocky host yet.

Side note: warning for the built-in server

With the new security context coming (AIs hacking everything, finding vulnerabilities where we did not think there were some), I think Radicale should adopt Django rationale: the internal server is only for local testing, not for production, and it displays a warning at launch saying it is not meant for a production environment. If you are OK with this, I can do the PR.

@deronnax

Copy link
Copy Markdown
Contributor Author

The PyPy 3.10/3.11 CI jobs occasionally fail on test_ssl with BufferError: Existing exports of data: object cannot be re-sized. This is a PyPy-specific bug in cheroot: its StreamReader relies on _pyio.RawIOBase.read(), which resizes a buffer in place (del b[n:]) right after ssl.SSLSocket.recv_into() has taken a memoryview over it. On CPython that view is released deterministically, but on PyPy it can stay alive until the next GC run, making the in-place resize fail intermittently.

I've opened cherrypy/cheroot#855 with a fix (avoid resizing by returning a sliced copy instead) and a regression test. Once merged and released, bumping the cheroot dependency here will resolve this flakiness; in the meantime, it's a known, non-blocking, intermittent CI failure specific to PyPy+SSL.

@pbiering pbiering added the DRAFT PR in DRAFT status label Oct 11, 2026
@pbiering

Copy link
Copy Markdown
Collaborator

EL9 is shipping: python3-cheroot 10.0.1-5.el9 means everything has to work with that version on a hard replacement and there are no plans to start module bundling on EL9 (for EL8 it's a hard requirement).

If this cannot be satisfied, imho use of "cheroot" can only be implemented option by using a new toggle named e.g. type and can have builtin (default) or cheroot (optional) - and all server related test cases must test both variants.

@deronnax

Copy link
Copy Markdown
Contributor Author

python3-cheroot 10.0.1-5.el9

How can I test that? I don't have access to a RHEL (what normal dev does, btw?) The -5 means it's even patched by red hat, so I can't just target this version, I have to have access to the red hat variant. And how could I know that compatibility to this RHEL version is needed?

@pbiering

Copy link
Copy Markdown
Collaborator

python3-cheroot 10.0.1-5.el9

How can I test that? I don't have access to a RHEL (what normal dev does, btw?) The -5 means it's even patched by red hat, so I can't just target this version, I have to have access to the red hat variant.

And how could I know that compatibility to this RHEL version is needed?

https://github.com/Kozea/Radicale/wiki

Developer can use "Alma" or "Rocky" as free versions of EL. "-5" is only the revision, normally not including any significant patch.

See also:

https://src.fedoraproject.org/rpms/python-cheroot
https://pkgs.org/search/?q=python3-cheroot

Note that Ubuntu 22 LTS also contains only version 8.5

@deronnax

Copy link
Copy Markdown
Contributor Author

OK, I will test with those variants. WSGI is a very old and mature spec, I would be very surprized we find incompatibilities with Radicale, be it with very old cheerot version.

@deronnax
deronnax force-pushed the feature/cheroot-server branch from 1ddfc5f to 0a253d1 Compare October 24, 2026 22:37
@deronnax deronnax changed the title Replace the built-in server with a cheroot-based WSGI server Add [server] type = cheroot (optional, pure-Python HTTP/1.1 server) Oct 24, 2026
@deronnax

Copy link
Copy Markdown
Contributor Author

Thanks @pbiering, I reworked the PR along the lines you suggested: it no longer replaces the built-in server. cheroot is now an optional [server] type (builtin stays the default, next to hypercorn), with its own cheroot extra, and it is only imported when selected.

The server test suite is run for both variants (TestCherootServerRequests reuses all of TestBaseServerRequests). I tested cheroot 8.5.0 (Ubuntu 22) and 10.0.1 (the EL9 version): the only incompatibility was prepare_socket() not accepting reuse_port on old versions, now handled. I have not yet run it on an actual Alma/Rocky host with the EL9 package, so I'd appreciate it if you could confirm on your side.

I dropped the PyPy flakiness note from the description since it only concerns the optional type now.

@pbiering

Copy link
Copy Markdown
Collaborator

Somehow it's not passing Test Ubuntu:26 Python:Matrix (ubuntu-26.04, pypy-3.11)

Please investigate, thank you!

@pbiering pbiering added this to the 3.9.1 milestone Oct 25, 2026
@pbiering pbiering added the need:rebase pull request cannot be applied before rebased label Oct 27, 2026
@deronnax

Copy link
Copy Markdown
Contributor Author

OK, I looked into it. It's only test_ssl of the cheroot type, and it's a PyPy bug, not a Radicale or cheroot one: cheroot's StreamReader resizes a buffer in place (del b[n:]) right after ssl.recv_into() took a memoryview on it. CPython drops that view immediately, PyPy doesn't, so the resize fails sometimes with a BufferError.

I reported it at pypy/pypy#5634. The PyPy folks agreed and fixed _io, but the fix isn't in the 3.11.16 that CI runs on yet.

I had a workaround for cheroot, but it meant subclassing a method and copying most of its code. That's worse than the problem, so I didn't submit it. Instead I skipped that single test on PyPy (cheroot type only, builtin and hypercorn still run there), and added a line in the doc. We can drop the skip as soon as CI gets a PyPy with the fix.

But I don't know what it means for all your RHEL stuff, those people maintaining old stuff are very, very likely not going to use both a "new" python implementation (pypy) and a new server (cheeroot) for a ~8 years old system.

@deronnax
deronnax force-pushed the feature/cheroot-server branch from 409f557 to 2221744 Compare October 27, 2026 20:47
@pbiering pbiering removed the need:rebase pull request cannot be applied before rebased label Oct 28, 2026
Comment thread .claude/worktrees/check-pr-2269 Outdated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please exclude directory .claude from PR

Comment thread CHANGELOG.md

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

meanwhile 3.9.1 was released, please rebase

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not overtake old copyrights, only place your's for 2026

@deronnax
deronnax force-pushed the feature/cheroot-server branch from 2221744 to c175167 Compare November 1, 2026 16:31
@pbiering

pbiering commented Nov 1, 2026

Copy link
Copy Markdown
Collaborator

@deronnax - please rebase after merge of #2329, the broken test is solved by #2328

deronnax and others added 3 commits November 1, 2026 21:44
* New optional server type based on the pure-Python cheroot HTTP server
  (`pip install radicale[cheroot]`, cheroot>=8.5.0), imported only when
  asked for, like hypercorn. `builtin` stays the default.
* TLS through cheroot's BuiltinSSLAdapter (stdlib ssl): protocol,
  ciphersuite and mandatory client certificate verification are honoured.
* PATH_INFO/QUERY_STRING decoded as UTF-8 by a small WSGI middleware.
* IPV6_V6ONLY forced so IPv4/IPv6 "any" can be listened on independently.
* Tested against cheroot 8.5.0 and 10.0.1 (EL9).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
cheroot's StreamReader resizes a buffer in place right after
ssl.recv_into took a memoryview on it; PyPy does not release the view
deterministically, so the resize fails intermittently with BufferError.
PyPy fixed `_io` (pypy/pypy#5634); skip until CI has a release with it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@deronnax
deronnax force-pushed the feature/cheroot-server branch from c175167 to d128cb4 Compare November 1, 2026 21:16

@pbiering pbiering left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you!

@pbiering
pbiering marked this pull request as ready for review November 2, 2026 05:44
@pbiering pbiering added feature and removed DRAFT PR in DRAFT status labels Nov 2, 2026
@pbiering
pbiering merged commit b15f520 into Kozea:master Nov 2, 2026
44 checks passed
pbiering added a commit that referenced this pull request Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants