mirror of
https://github.com/frappe/gunicorn.git
synced 2026-07-02 18:51:31 +08:00
- Bump version_info to (26, 0, 0) - Update SECURITY.md supported releases (26.0.0, 25.3.0) - Add 26.0.0 entry to news.md and 2026-news.md covering eventlet removal, ASGI framework compatibility suite, RFC 9110/9112 request-target and header hardening, smuggling fixes, HEAD/204/304 body framing, WebSocket close handshake compliance, HTTP/2 ASGI stream completion, early-hints validation, framework fixes (Django/Litestar/Quart/BlackSheep), and gunicorn_h1c >= 0.6.5
476 lines
19 KiB
Markdown
476 lines
19 KiB
Markdown
<span id="news-2026"></span>
|
||
# Changelog - 2026
|
||
|
||
## 26.0.0 - 2026-05-05
|
||
|
||
### Breaking Changes
|
||
|
||
- **Eventlet worker removed**: The `eventlet` worker class has been dropped.
|
||
Migrate to `gevent`, `gthread`, or `tornado`.
|
||
|
||
### New Features
|
||
|
||
- **ASGI Framework Compatibility Suite**: New end-to-end compatibility test
|
||
harness covering Starlette, FastAPI, Litestar, Quart, Sanic, and BlackSheep.
|
||
Current grid passes 438/444 tests (98%).
|
||
|
||
- **ASGI Test Suite Expansion**: 134 additional ASGI unit tests covering
|
||
protocol semantics, lifespan, websockets, and chunked framing.
|
||
|
||
### Security
|
||
|
||
- **HTTP/1.1 Request-Target Validation** (RFC 9112 sections 3.2.3, 3.2.4):
|
||
- Reject `authority-form` request-target outside `CONNECT`
|
||
- Reject `asterisk-form` request-target outside `OPTIONS`
|
||
- Reject `relative-reference` request-targets
|
||
|
||
- **Header Field Hardening** (RFC 9110):
|
||
- Reject control characters in header field-value (section 5.5)
|
||
- Reject forbidden trailer field-names (section 6.5.1)
|
||
- Reject `Content-Length` list form (RFC 9112 section 6.3)
|
||
|
||
- **Request Smuggling Hardening**:
|
||
- Tighten keepalive gate and scope `finish_body` byte cap
|
||
- Keep `_body_receiver` alive across the keepalive smuggling gate so
|
||
pipelined requests cannot re-enter a closed body
|
||
- Address parser/protocol findings from a six-point WSGI/ASGI audit
|
||
|
||
- **PROXY Protocol (ASGI)**: Enforce `proxy_allow_ips` and tighten v1/v2
|
||
parsing in the ASGI callback parser.
|
||
|
||
- **Connection Draining**: Drain the connection on close per RFC 9112
|
||
section 9.6 to prevent reset-on-close truncation.
|
||
|
||
### Bug Fixes
|
||
|
||
- **Body Framing on HEAD/204/304**:
|
||
- Keep `Content-Length` on HEAD and 304 responses
|
||
([#3621](https://github.com/benoitc/gunicorn/pull/3621))
|
||
- Drop body framing on HEAD/204/304 even when the framework set it
|
||
- Warn once when an ASGI app emits a body for a no-body response
|
||
|
||
- **HTTP/2 ASGI**:
|
||
- Fix `_handle_stream_ended` to set `_body_complete` in the async HTTP/2
|
||
handler so request bodies finalize correctly on stream end
|
||
- Add `InvalidChunkExtension` mapping and fast-parser support in ASGI
|
||
tests ([#3565](https://github.com/benoitc/gunicorn/pull/3565))
|
||
|
||
- **HTTP/1.1 100-Continue**: Stop adding `Transfer-Encoding: chunked` to
|
||
100-Continue interim responses.
|
||
|
||
- **WebSocket Close Handshake** (RFC 6455):
|
||
- Comply with the close handshake state machine
|
||
- Close the transport after the close handshake completes
|
||
- Fix binary send when the `text` key is `None`
|
||
|
||
- **Early Hints**: Validate headers in the `early_hints` callback to match
|
||
`process_headers`; pass only the header name to `InvalidHeader`
|
||
([#3588](https://github.com/benoitc/gunicorn/pull/3588)).
|
||
|
||
- **ASGI Framework Fixes**:
|
||
- Fix ASGI disconnect handling for Django-style apps
|
||
- Fix Litestar request handling (use raw ASGI receive for body/headers)
|
||
- Fix Litestar HTTP endpoints for compatibility tests
|
||
- Fix Quart headers endpoint to normalize keys to lowercase
|
||
- Fix Quart WebSocket close test app (missing `accept()`)
|
||
- Fix duplicate `Transfer-Encoding` header for BlackSheep streaming
|
||
|
||
### Refactoring
|
||
|
||
- Split `BodyReceiver._closed` into separate transport and body-wait flags
|
||
for clearer keepalive/EOF semantics.
|
||
|
||
### Changes
|
||
|
||
- **Fast HTTP Parser**: Require `gunicorn_h1c >= 0.6.5`. Drop the last
|
||
`python_only` test markers; the C extension is now used wherever
|
||
available (CPython only; PyPy continues to use the Python parser).
|
||
|
||
- **Test Dependencies**: Add `h2` and `uvloop` to the `testing` extra;
|
||
remove `eventlet`.
|
||
|
||
- **Docker Build**: Bump GitHub Actions `docker/setup-qemu-action`,
|
||
`docker/setup-buildx-action`, `docker/login-action`,
|
||
`docker/build-push-action`, and `docker/metadata-action` to current
|
||
major versions.
|
||
|
||
---
|
||
|
||
## 25.3.0 - 2026-03-26
|
||
|
||
### Bug Fixes
|
||
|
||
- **HTTP/2 ASGI Body Duplication**: Fix request body being received twice in HTTP/2
|
||
ASGI requests, causing JSON parsing errors with "Extra data" messages
|
||
([#3558](https://github.com/benoitc/gunicorn/issues/3558))
|
||
|
||
- **ASGI Chunked EOF Handling**: Add `finish()` method to callback parser to handle
|
||
chunked encoding edge case where connection closes before final CRLF after zero-chunk
|
||
|
||
- **HTTP/2 Documentation**: Fix `http_protocols` examples to use comma-separated string
|
||
instead of list syntax ([#3561](https://github.com/benoitc/gunicorn/issues/3561))
|
||
|
||
- **Chunked Encoding**: Reject chunk extensions containing bare CR bytes per RFC 9112
|
||
([#3556](https://github.com/benoitc/gunicorn/discussions/3556))
|
||
|
||
### Security
|
||
|
||
- **ASGI Parser Header Validation**: Add security checks per RFC 9110/9112:
|
||
- Reject duplicate Content-Length headers
|
||
- Reject requests with both Content-Length and Transfer-Encoding
|
||
- Reject chunked transfer encoding in HTTP/1.0
|
||
- Reject stacked chunked encoding
|
||
- Validate Transfer-Encoding values
|
||
- Strict chunk size validation
|
||
|
||
### Changes
|
||
|
||
- **Fast HTTP Parser**: Update to gunicorn_h1c >= 0.6.2 for `asgi_headers` property
|
||
which provides headers with lowercase names directly from the C parser
|
||
|
||
- **ASGI PROXY Protocol**: Add PROXY protocol v1/v2 support to callback parser
|
||
|
||
---
|
||
|
||
## 25.2.0 - 2026-03-24
|
||
|
||
### New Features
|
||
|
||
- **Fast HTTP Parser (gunicorn_h1c 0.6.0)**: Integrate new exception types and limit
|
||
parameters from gunicorn_h1c 0.6.0 for both WSGI and ASGI workers
|
||
- Requires gunicorn_h1c >= 0.6.0 for `http_parser='fast'`
|
||
- Falls back to Python parser in `auto` mode if version not met
|
||
- Proper HTTP status codes for limit errors (414, 431)
|
||
|
||
### Bug Fixes
|
||
|
||
- **uWSGI Async Workers**: Fix `InvalidUWSGIHeader: incomplete header` error
|
||
when using gevent or gthread workers with uwsgi protocol behind nginx.
|
||
([#3552](https://github.com/benoitc/gunicorn/issues/3552),
|
||
[PR #3554](https://github.com/benoitc/gunicorn/pull/3554))
|
||
|
||
- **FileWrapper Iterator Protocol**: Add `__iter__` and `__next__` methods to
|
||
`FileWrapper` for full PEP 3333 compliance. Previously only supported old-style
|
||
`__getitem__` iteration which broke code explicitly using `iter()` or `next()`.
|
||
([#3396](https://github.com/benoitc/gunicorn/issues/3396),
|
||
[PR #3550](https://github.com/benoitc/gunicorn/pull/3550))
|
||
|
||
### Performance
|
||
|
||
- **ASGI HTTP Parser Optimizations**: Improve ASGI worker HTTP parsing performance
|
||
- Read chunks in 64-byte blocks instead of 1 byte at a time for chunk size lines and trailers
|
||
- Reuse BytesIO buffers with truncate/seek instead of creating new objects (reduces GC pressure)
|
||
- Use `bytearray.find()` directly instead of converting to bytes first
|
||
- Use index-based iteration for header parsing instead of `list.pop(0)` (O(1) vs O(n))
|
||
|
||
---
|
||
|
||
## 25.1.0 - 2026-02-13
|
||
|
||
### New Features
|
||
|
||
- **Control Interface (gunicornc)**: Add interactive control interface for managing
|
||
running Gunicorn instances, similar to birdc for BIRD routing daemon
|
||
([PR #3505](https://github.com/benoitc/gunicorn/pull/3505))
|
||
- Unix socket-based communication with JSON protocol
|
||
- Interactive mode with readline support and command history
|
||
- Commands: `show all/workers/dirty/config/stats/listeners`
|
||
- Worker management: `worker add/remove/kill`, `dirty add/remove`
|
||
- Server control: `reload`, `reopen`, `shutdown`
|
||
- New settings: `--control-socket`, `--control-socket-mode`, `--no-control-socket`
|
||
- New CLI tool: `gunicornc` for connecting to control socket
|
||
- See [Control Interface Guide](guides/gunicornc.md) for details
|
||
|
||
- **Dirty Stash**: Add global shared state between workers via `dirty.stash`
|
||
([PR #3503](https://github.com/benoitc/gunicorn/pull/3503))
|
||
- In-memory key-value store accessible by all workers
|
||
- Supports get, set, delete, clear, keys, and has operations
|
||
- Useful for sharing state like feature flags, rate limits, or cached data
|
||
|
||
- **Dirty Binary Protocol**: Implement efficient binary protocol for dirty arbiter IPC
|
||
using TLV (Type-Length-Value) encoding
|
||
([PR #3500](https://github.com/benoitc/gunicorn/pull/3500))
|
||
- More efficient than JSON for binary data
|
||
- Supports all Python types: str, bytes, int, float, bool, None, list, dict
|
||
- Better performance for large payloads
|
||
|
||
- **Dirty TTIN/TTOU Signals**: Add dynamic worker scaling for dirty arbiters
|
||
([PR #3504](https://github.com/benoitc/gunicorn/pull/3504))
|
||
- Send SIGTTIN to increase dirty workers
|
||
- Send SIGTTOU to decrease dirty workers
|
||
- Respects minimum worker constraints from app configurations
|
||
|
||
### Changes
|
||
|
||
- **ASGI Worker**: Promoted from beta to stable
|
||
- **Dirty Arbiters**: Now marked as beta feature
|
||
|
||
### Documentation
|
||
|
||
- Fix Markdown formatting in /configure documentation
|
||
|
||
---
|
||
|
||
## 25.0.3 - 2026-02-07
|
||
|
||
### Bug Fixes
|
||
|
||
- Fix RuntimeError when StopIteration is raised inside ASGI response body
|
||
coroutine (PEP 479 compliance)
|
||
|
||
- Fix deprecation warning for passing maxsplit as positional argument in
|
||
`re.split()` (Python 3.13+)
|
||
|
||
---
|
||
|
||
## 25.0.2 - 2026-02-06
|
||
|
||
### Bug Fixes
|
||
|
||
- Fix ASGI concurrent request failures through nginx proxy by normalizing
|
||
sockaddr tuples to handle both 2-tuple (IPv4) and 4-tuple (IPv6) formats
|
||
([PR #3485](https://github.com/benoitc/gunicorn/pull/3485))
|
||
|
||
- Fix graceful disconnect handling for ASGI worker to properly handle
|
||
client disconnects without raising exceptions
|
||
([PR #3485](https://github.com/benoitc/gunicorn/pull/3485))
|
||
|
||
- Fix lazy import of dirty module for gevent compatibility - prevents
|
||
import errors when concurrent.futures is imported before gevent monkey-patching
|
||
([PR #3483](https://github.com/benoitc/gunicorn/pull/3483))
|
||
|
||
### Changes
|
||
|
||
- Refactor: Extract `_normalize_sockaddr` utility function for consistent
|
||
socket address handling across workers
|
||
|
||
- Add license headers to all Python source files
|
||
|
||
- Update copyright year to 2026 in LICENSE and NOTICE files
|
||
|
||
---
|
||
|
||
## 25.0.1 - 2026-02-02
|
||
|
||
### Bug Fixes
|
||
|
||
- Fix ASGI streaming responses (SSE) hanging: add chunked transfer encoding for
|
||
HTTP/1.1 responses without Content-Length header. Without chunked encoding,
|
||
clients wait for connection close to determine end-of-response.
|
||
|
||
### Changes
|
||
|
||
- Update celery_alternative example to use FastAPI with native ASGI worker and
|
||
uvloop for async task execution
|
||
|
||
### Testing
|
||
|
||
- Add ASGI compliance test suite with Docker-based integration tests covering HTTP,
|
||
WebSocket, streaming, lifespan, framework integration (Starlette, FastAPI),
|
||
HTTP/2, and concurrency scenarios
|
||
|
||
---
|
||
|
||
## 25.0.0 - 2026-02-01
|
||
|
||
### New Features
|
||
|
||
- **Dirty Arbiters**: Separate process pool for executing long-running, blocking
|
||
operations (AI model loading, heavy computation) without blocking HTTP workers
|
||
([PR #3460](https://github.com/benoitc/gunicorn/pull/3460))
|
||
- Inspired by Erlang's dirty schedulers
|
||
- Asyncio-based with Unix socket IPC
|
||
- Stateful workers that persist loaded resources
|
||
- New settings: `--dirty-app`, `--dirty-workers`, `--dirty-timeout`,
|
||
`--dirty-threads`, `--dirty-graceful-timeout`
|
||
- Lifecycle hooks: `on_dirty_starting`, `dirty_post_fork`,
|
||
`dirty_worker_init`, `dirty_worker_exit`
|
||
|
||
- **Per-App Worker Allocation for Dirty Arbiters**: Control how many dirty workers
|
||
load each app for memory optimization with heavy models
|
||
([PR #3473](https://github.com/benoitc/gunicorn/pull/3473))
|
||
- Set `workers` class attribute on DirtyApp (e.g., `workers = 2`)
|
||
- Or use config format `module:class:N` (e.g., `myapp:HeavyModel:2`)
|
||
- Requests automatically routed to workers with the target app
|
||
- New exception `DirtyNoWorkersAvailableError` for graceful error handling
|
||
- Example: 8 workers × 10GB model = 80GB → with `workers=2`: 20GB (75% savings)
|
||
|
||
- **HTTP/2 Support (Beta)**: Native HTTP/2 (RFC 7540) support for improved performance
|
||
with modern clients ([PR #3468](https://github.com/benoitc/gunicorn/pull/3468))
|
||
- Multiplexed streams over a single connection
|
||
- Header compression (HPACK)
|
||
- Flow control and stream prioritization
|
||
- Works with gthread, gevent, and ASGI workers
|
||
- New settings: `--http-protocols`, `--http2-max-concurrent-streams`,
|
||
`--http2-initial-window-size`, `--http2-max-frame-size`, `--http2-max-header-list-size`
|
||
- Requires SSL/TLS and h2 library: `pip install gunicorn[http2]`
|
||
- See [HTTP/2 Guide](guides/http2.md) for details
|
||
- New example: `examples/http2_gevent/` with Docker and tests
|
||
|
||
- **HTTP 103 Early Hints**: Support for RFC 8297 Early Hints to enable browsers to
|
||
preload resources before the final response
|
||
([PR #3468](https://github.com/benoitc/gunicorn/pull/3468))
|
||
- WSGI: `environ['wsgi.early_hints'](headers)` callback
|
||
- ASGI: `http.response.informational` message type
|
||
- Works with both HTTP/1.1 and HTTP/2
|
||
|
||
- **uWSGI Protocol for ASGI Worker**: The ASGI worker now supports receiving requests
|
||
via the uWSGI binary protocol from nginx
|
||
([PR #3467](https://github.com/benoitc/gunicorn/pull/3467))
|
||
|
||
### Bug Fixes
|
||
|
||
- Fix HTTP/2 ALPN negotiation for gevent and eventlet workers when
|
||
`do_handshake_on_connect` is False (the default). The TLS handshake is now
|
||
explicitly performed before checking `selected_alpn_protocol()`.
|
||
|
||
- Fix setproctitle initialization with systemd socket activation
|
||
([#3465](https://github.com/benoitc/gunicorn/issues/3465))
|
||
|
||
- Fix `Expect: 100-continue` handling: ignore the header for HTTP/1.0 requests
|
||
since 100-continue is only valid for HTTP/1.1+
|
||
([PR #3463](https://github.com/benoitc/gunicorn/pull/3463))
|
||
|
||
- Fix missing `_expected_100_continue` attribute in UWSGIRequest
|
||
|
||
- Disable setproctitle on macOS to prevent segfaults during process title updates
|
||
|
||
- Publish full exception traceback when the application fails to load
|
||
([#3462](https://github.com/benoitc/gunicorn/issues/3462))
|
||
|
||
### Deprecations
|
||
|
||
- **Eventlet Worker**: The `eventlet` worker is deprecated and will be removed in
|
||
Gunicorn 26.0. Eventlet itself is [no longer actively maintained](https://eventlet.readthedocs.io/en/latest/asyncio/migration.html).
|
||
Please migrate to `gevent`, `gthread`, or another supported worker type.
|
||
|
||
### Changes
|
||
|
||
- Remove obsolete Makefile targets
|
||
([PR #3471](https://github.com/benoitc/gunicorn/pull/3471))
|
||
|
||
---
|
||
|
||
## 24.1.1 - 2026-01-24
|
||
|
||
### Bug Fixes
|
||
|
||
- Fix `forwarded_allow_ips` and `proxy_allow_ips` to remain as strings for backward
|
||
compatibility with external tools like uvicorn. Network validation now uses strict
|
||
mode to detect invalid CIDR notation (e.g., `192.168.1.1/24` where host bits are set)
|
||
([#3458](https://github.com/benoitc/gunicorn/issues/3458),
|
||
[PR #3459](https://github.com/benoitc/gunicorn/pull/3459))
|
||
|
||
---
|
||
|
||
## 24.1.0 - 2026-01-23
|
||
|
||
### New Features
|
||
|
||
- **Official Docker Image**: Gunicorn now publishes official Docker images to GitHub
|
||
Container Registry at `ghcr.io/benoitc/gunicorn`
|
||
- Based on Python 3.12 slim image
|
||
- Uses recommended worker formula (2 × CPU + 1)
|
||
- Configurable via environment variables
|
||
|
||
- **PROXY Protocol v2 Support**: Extended PROXY protocol implementation to support
|
||
the binary v2 format in addition to the existing text-based v1 format
|
||
([PR #3451](https://github.com/benoitc/gunicorn/pull/3451))
|
||
- New `--proxy-protocol` modes: `off`, `v1`, `v2`, `auto`
|
||
- `auto` mode (default when enabled) detects v1 or v2 automatically
|
||
- v2 binary format is more efficient and supports additional metadata
|
||
- Works with HAProxy, AWS NLB/ALB, and other PROXY protocol v2 sources
|
||
|
||
- **CIDR Network Support**: `--forwarded-allow-ips` and `--proxy-allow-from` now
|
||
accept CIDR notation (e.g., `192.168.0.0/16`) for specifying trusted networks
|
||
([PR #3449](https://github.com/benoitc/gunicorn/pull/3449))
|
||
|
||
- **Socket Backlog Metric**: New `gunicorn.socket.backlog` gauge metric reports
|
||
the current socket backlog size on Linux systems
|
||
([PR #3450](https://github.com/benoitc/gunicorn/pull/3450))
|
||
|
||
- **InotifyReloader Enhancement**: The inotify-based reloader now watches newly
|
||
imported modules, not just those loaded at startup
|
||
([PR #3447](https://github.com/benoitc/gunicorn/pull/3447))
|
||
|
||
### Bug Fixes
|
||
|
||
- Fix signal handling regression where SIGCLD alias caused "Unhandled signal: cld"
|
||
errors on Linux when workers fail during boot
|
||
([#3453](https://github.com/benoitc/gunicorn/discussions/3453))
|
||
|
||
- Fix socket blocking mode on keepalive connections preventing SSL handshake
|
||
failures with async workers
|
||
([PR #3452](https://github.com/benoitc/gunicorn/pull/3452))
|
||
|
||
- Use smaller buffer size in `finish_body()` for faster timeout detection on
|
||
slow or abandoned connections
|
||
([PR #3453](https://github.com/benoitc/gunicorn/pull/3453))
|
||
|
||
- Handle `SSLWantReadError` in `finish_body()` to prevent worker hangs during
|
||
SSL renegotiation
|
||
([PR #3448](https://github.com/benoitc/gunicorn/pull/3448))
|
||
|
||
- Log SIGTERM as info level instead of warning to reduce noise in orchestrated
|
||
environments
|
||
([PR #3446](https://github.com/benoitc/gunicorn/pull/3446))
|
||
|
||
- Print exception details to stderr when worker fails to boot
|
||
([PR #3443](https://github.com/benoitc/gunicorn/pull/3443))
|
||
|
||
- Fix `unreader.unread()` to prepend data to buffer instead of appending
|
||
([PR #3442](https://github.com/benoitc/gunicorn/pull/3442))
|
||
|
||
- Prevent `RecursionError` when pickling Config objects
|
||
([PR #3441](https://github.com/benoitc/gunicorn/pull/3441))
|
||
|
||
- Use proper exception chaining with `raise from` in glogging.py
|
||
([PR #3440](https://github.com/benoitc/gunicorn/pull/3440))
|
||
|
||
---
|
||
|
||
## 24.0.0 - 2026-01-23
|
||
|
||
### New Features
|
||
|
||
- **ASGI Worker (Beta)**: Native asyncio-based ASGI support for running async Python
|
||
frameworks like FastAPI, Starlette, and Quart without external dependencies
|
||
([PR #3444](https://github.com/benoitc/gunicorn/pull/3444))
|
||
- HTTP/1.1 with keepalive connections
|
||
- WebSocket support
|
||
- Lifespan protocol for startup/shutdown hooks
|
||
- Optional uvloop for improved performance
|
||
- New settings: `--asgi-loop`, `--asgi-lifespan`, `--root-path`
|
||
|
||
- **uWSGI Binary Protocol**: Support for receiving requests from nginx via
|
||
`uwsgi_pass` directive, enabling efficient binary protocol communication
|
||
([PR #3444](https://github.com/benoitc/gunicorn/pull/3444))
|
||
- New settings: `--protocol uwsgi`, `--uwsgi-allow-from`
|
||
|
||
- **Documentation Migration**: Migrated documentation from Sphinx to MkDocs
|
||
with Material theme for improved navigation and mobile experience
|
||
([PR #3426](https://github.com/benoitc/gunicorn/pull/3426))
|
||
|
||
### Security
|
||
|
||
- **eventlet**: Require eventlet >= 0.40.3 to address CVE-2021-21419 (websocket
|
||
memory exhaustion) and CVE-2025-58068 (HTTP request smuggling)
|
||
([PR #3445](https://github.com/benoitc/gunicorn/pull/3445))
|
||
|
||
- **gevent**: Require gevent >= 24.10.1 to address CVE-2023-41419 (HTTP request
|
||
smuggling) and CVE-2024-3219 (socket.socketpair vulnerability)
|
||
([PR #3445](https://github.com/benoitc/gunicorn/pull/3445))
|
||
|
||
- **tornado**: Require tornado >= 6.5.0 to address CVE-2025-47287 (HTTP request
|
||
smuggling) and other security fixes
|
||
([PR #3445](https://github.com/benoitc/gunicorn/pull/3445))
|
||
|
||
### Changes
|
||
|
||
- Documentation now hosted at https://gunicorn.org
|
||
- Updated license configuration to PEP 639 format for uv compatibility
|
||
|
||
!!! warning "ASGI Worker Beta"
|
||
The ASGI worker is a beta feature. While tested, the API and behavior
|
||
may change in future releases. Please report any issues on GitHub.
|