1980 Commits

Author SHA1 Message Date
Benoit Chesneau
47bd20a7cb Fix HTTP 100 Continue adding Transfer-Encoding: chunked
Skip adding Transfer-Encoding: chunked for 1xx informational
responses per RFC 9110 Section 15.2.
2026-04-03 13:57:49 +02:00
Benoit Chesneau
4e9db71aeb
Merge pull request #3568 from benleembruggen/fix/h2-stream-ended-body-complete
fix: HTTP/2 ASGI body duplication in async_connection.py
2026-04-03 01:51:02 +02:00
Benoit Chesneau
7953c2585b Fix ASGI disconnect handling for Django-style apps
BodyReceiver.receive() now blocks after body is finished until actual
disconnect, instead of returning http.disconnect immediately. This fixes
Django's listen_for_disconnect task thinking client disconnected early.

Adds regression tests for the fix.

Fixes #3484
2026-04-02 23:55:27 +02:00
Ben Leembruggen
bcb13b1e74 Fix _handle_stream_ended to set _body_complete in async HTTP/2 handler
_handle_stream_ended() in async_connection.py (used by the ASGI worker)
did not set stream._body_complete = True or signal stream._body_event.
This caused the receive() closure in protocol.py to never see the body
as complete via the streaming path, so on the next call the fast path
re-read the entire body from BytesIO, doubling it.

The sync handler in connection.py already had a partial fix from #3559
but was also missing _body_event signalling, which is needed to unblock
any pending read_body_chunk() await.

Fixes https://github.com/benoitc/gunicorn/discussions/3567
2026-04-01 11:53:06 +11:00
Benoit Chesneau
3e2167c346
Add InvalidChunkExtension mapping and fast parser support for ASGI tests (#3565)
* Add InvalidChunkExtension to treq_asgi.py and fast parser support

- Add InvalidChunkExtension import and exception mapping for proper test
  coverage of bare CR rejection in chunk extensions per RFC 9112 7.1.1
- Add fast parser (H1CProtocol) support to treq_asgi.py and the ASGI
  invalid request tests
- Fast parser now receives limit configuration (limit_request_line,
  limit_request_fields, limit_request_field_size)
- Handle gunicorn_h1c's multiple ParseError classes from different modules
- Skip tests where fast parser has different validation than Python parser

* Handle gunicorn_h1c limit exceptions in ASGI protocol

Add handling for gunicorn_h1c.LimitRequestLine and
gunicorn_h1c.LimitRequestHeaders exceptions, matching the behavior
of the Python parser exceptions with appropriate HTTP status codes:
- LimitRequestLine: 414 URI Too Long
- LimitRequestHeaders: 431 Request Header Fields Too Large

* Refactor data_received to fix too-many-return-statements lint
2026-03-31 03:07:56 +02:00
Benoit Chesneau
2a15fdb93a Fix pylint isinstance-second-argument-not-valid-type warning 2026-03-26 23:45:38 +01:00
Benoit Chesneau
8d08aaa2cb Fix --limit-request-line 0 to mean unlimited
Per documentation, limit_request_line=0 means unlimited. The code was
incorrectly treating 0 as "use default max" by checking <= 0 instead
of < 0.

For the fast C parser (gunicorn_h1c), which doesn't support 0 as
unlimited, pass a large value (1MB) instead. This applies to both
WSGI workers (http/message.py) and ASGI workers (asgi/protocol.py).

Fixes #3563
2026-03-26 23:42:14 +01:00
Benoit Chesneau
da8bd4850a Remove unused AsyncRequest class
AsyncRequest was the legacy pull-based async HTTP parser, now replaced
by the push-based CallbackRequest/PythonProtocol. Remove the unused
code and associated tests.
2026-03-26 16:08:35 +01:00
Benoit Chesneau
b00f125755 Integrate gunicorn_h1c 0.6.3 with InvalidChunkExtension support
Update to gunicorn_h1c >= 0.6.3 which adds InvalidChunkExtension
validation for rejecting chunk extensions with bare CR bytes per
RFC 9112.

Changes:
- Update pyproject.toml to require gunicorn_h1c >= 0.6.3
- Add InvalidChunkExtension exception to gunicorn/asgi/parser.py
- Handle InvalidChunkExtension from both Python and C parsers in protocol.py
- Add chunk extension validation tests
- Update treq.py badrequest class to support hex escapes
2026-03-26 15:46:51 +01:00
Benoit Chesneau
bdb2ebd5a4 Reject chunk extensions with bare CR bytes (RFC 9112)
Both WSGI and ASGI parsers now validate that chunk extensions
do not contain bare CR characters, which are not allowed per
RFC 9112.

Fixes #3556
2026-03-26 15:45:48 +01:00
Benoit Chesneau
d43acb8fe0 Update to gunicorn_h1c >= 0.6.2 for asgi_headers support
- Use asgi_headers property (lowercase names) from fast parser
- Bump version to 25.3.0
- Update changelog with all changes for this release
2026-03-26 15:45:11 +01:00
Benoit Chesneau
cbd27e82a2
Merge pull request #3559 from benleembruggen/fix/http2-asgi-body-duplication
fix: prevent HTTP/2 ASGI body duplication in receive()
2026-03-26 14:38:53 +01:00
Benoit Chesneau
997eec4f45 Fix pylint superfluous-parens warning 2026-03-26 14:22:44 +01:00
Benoit Chesneau
1f8e60c199 Add finish() method to ASGI callback parser for EOF handling
Handle chunked encoding edge case where connection closes before
final CRLF after zero-chunk. Skip WSGI-specific tests (casefold,
underscore headers) that don't apply to ASGI.
2026-03-26 12:13:50 +01:00
Benoit Chesneau
ffcebce4a7 Fix ASGI callback parser header validation
Add security checks to PythonProtocol per RFC 9110/9112:
- Reject duplicate Content-Length headers
- Reject CL + TE combinations
- Reject chunked in HTTP/1.0
- Reject stacked chunked encoding
- Validate Transfer-Encoding values
- Strict chunk size validation

Add PROXY protocol v1/v2 support to callback parser.

Add treq-based test infrastructure for ASGI parser.
2026-03-26 06:32:15 +01:00
Ben Leembruggen
8fba44cf02 fix: prevent HTTP/2 ASGI request body duplication
receive_data() stores every DATA frame in both _body_chunks (list)
and request_body (BytesIO). The receive() closure in
_handle_http2_request() has two read paths: a streaming path that
pops from _body_chunks, and a fast path that reads from BytesIO.

After the streaming path consumed the body, the fast path could
re-read the same data from BytesIO because body_received was never
set in the streaming return path. This caused the application to
receive a doubled request body (e.g. 18 bytes sent, 36 bytes
received), breaking JSON parsing with "Extra data" errors.

Fix: set body_received = True in the streaming path when
_body_complete is True, preventing the fast path from re-reading
already-consumed data.

Fixes #3558
2026-03-26 14:15:14 +11:00
Benoit Chesneau
6f601a0de9 Bump version to 25.2.0 2026-03-24 23:42:01 +01:00
Benoit Chesneau
385a9211e2
Fix uwsgi incomplete header error with async workers (#3554)
The _read_exact method was calling unreader.read() without a size
parameter, which only reads one chunk at a time. With gevent/gthread
workers, this could return incomplete data before the full header
arrived.

Use unreader.read(size) which has proper retry logic built-in to
read the exact number of bytes requested.

Fixes #3552
2026-03-24 23:17:51 +01:00
r266-tech
f8fca7a72f
fix: add __iter__ and __next__ to FileWrapper for PEP 3333 compliance (#3550)
* fix: add __iter__ and __next__ to FileWrapper for PEP 3333 compliance

The WSGI spec (PEP 3333) requires that wsgi.file_wrapper return an
iterable object. Gunicorn's FileWrapper only implemented __getitem__,
which technically makes it iterable via old-style iteration but breaks
code that explicitly relies on the iterator protocol (e.g., calling
iter() or using next()).

This adds __iter__ (returning self) and __next__ to make FileWrapper
a proper iterator, maintaining backward compatibility with existing
__getitem__-based usage.

Fixes #3396

* Fix lint: move imports to top of file

---------

Co-authored-by: contributor <noreply@users.noreply.github.com>
Co-authored-by: Benoit Chesneau <bchesneau@gmail.com>
2026-03-24 22:38:16 +01:00
Benoit Chesneau
0ad47db800
Use user-writable default path for control socket (#3551)
The previous default /run/gunicorn.ctl requires root permissions.
Now uses $XDG_RUNTIME_DIR/gunicorn.ctl if available, otherwise
$HOME/.gunicorn/gunicorn.ctl. This works on Linux, FreeBSD, OpenBSD,
and macOS without requiring elevated privileges.

- Add _get_default_control_socket() helper in config.py
- Create parent directory automatically with 0o700 permissions
- Update gunicornc CLI to use the same default path
- Add unit tests for path selection and directory creation
2026-03-23 20:08:03 +01:00
Benoit Chesneau
f9ca296d21 Fix WebSocket and body receiver issues in ASGI protocol
- Fix body receiver timeout handling to prevent infinite loops
- Add WebSocket data forwarding via callbacks instead of StreamReader
- Fix HTTP/2 stream race condition where DATA frames arrive before first read
- Update WebSocketProtocol constructor (removed reader parameter)
2026-03-23 13:38:47 +01:00
Benoit Chesneau
241c479701 Fix WebSocket race condition in callback-based _read_exact()
Add double-check after clearing _data_event to prevent deadlock when
data arrives between clear() and wait(). The race condition occurred
when:
1. Task A checks buffer, needs more data
2. Task A clears _data_event
3. Task B (feed_data) sets event
4. Task A awaits on cleared event - deadlock

The fix re-checks the buffer after clear() to catch data that arrived
in the race window.

Also adds tests for edge cases: race condition simulation, EOF during
wait, fragmented message reassembly, and control frames during
fragmentation.
2026-03-23 13:08:57 +01:00
Benoit Chesneau
ba1aaa5e33 Fix non-ASCII URL handling in ASGI worker
Percent-decode path to UTF-8 and preserve raw_path as original bytes
per ASGI spec. Fixes #3543
2026-03-22 16:35:28 +01:00
Benoit Chesneau
4ce6aa1f3e Fix limit handling and add default max limit tests
- LimitRequestLine now accepts optional max_size parameter
- Use default max limits when limit_request_line or limit_request_field_size is 0
- Add tests validating default max enforcement (8190 bytes)
- Handle alternate exceptions from fast parser in test_invalid_requests
2026-03-22 16:17:55 +01:00
Benoit Chesneau
03cc85ef48 Integrate gunicorn_h1c 0.4.1 exception types and limit parameters
Require gunicorn_h1c >= 0.4.1 for fast parser mode. Add new exception
types and limit parameters to PythonProtocol for parity with C parser.
Update tests to parametrize across both parser implementations.
2026-03-22 13:43:18 +01:00
Benoit Chesneau
86c0baf933 Fix lint issues in ASGI parser and protocol 2026-03-22 09:00:48 +01:00
Benoit Chesneau
e8150e27b2 Simplify ASGI parser to always use callback mode
Remove pull-based HttpParser path and always use callback-based parsing:

- Remove HttpParser, ParseResult, FastAsyncRequest classes from parser.py
- Remove BufferReader, _handle_connection_fast(), _parse_request_fast()
- Update _setup_callback_parser() to handle auto/fast/python modes
- Fix race condition when data arrives before _handle_connection starts
- Simplify http_parser config to auto/fast/python (remove callback modes)

Parser selection for ASGI:
- auto: H1CProtocol if available, else PythonProtocol
- fast: H1CProtocol required (error if unavailable)
- python: PythonProtocol only

Reduces code by ~1150 lines while maintaining performance.
2026-03-22 02:02:03 +01:00
Benoit Chesneau
87bfb7d190 Add RFC 7230 validation for chunked transfer-encoding
Validate after fast parser returns:
- Reject chunked with HTTP/1.0
- Reject chunked + Content-Length conflict
2026-03-22 00:18:25 +01:00
Benoit Chesneau
0ca0d0cb02 Fix body polling and HTTP/2 request streaming
- Replace 100ms polling with event-based waiting in BodyReceiver
- Stream HTTP/2 request bodies instead of buffering entire uploads
- Add timeout handling for disconnect detection
2026-03-22 00:01:17 +01:00
Benoit Chesneau
ea37eaaa6d Add streaming body support to HTTP2Stream
- Add _body_chunks, _body_event, _body_complete fields for streaming
- Modify receive_data() to populate chunks queue alongside BytesIO
- Add async read_body_chunk() method for streaming body reads

This enables HTTP/2 request body streaming instead of buffering
entire uploads, reducing memory usage for large file uploads.
2026-03-22 00:00:37 +01:00
Benoit Chesneau
464cbbfad5 Add write flow control and HTTP/2 streaming
- Add FlowControl class for transport-level write backpressure
- Integrate flow control into HTTP/1.1 protocol to prevent memory
  issues with large streaming responses
- Set write buffer high water mark to 64KB
- Add pause_writing/resume_writing protocol callbacks
- Stream HTTP/2 responses immediately instead of buffering
- Add _convert_h2_headers helper for cleaner header conversion
2026-03-21 23:50:06 +01:00
Benoit Chesneau
22bdca22e1 Integrate callback parsers into ASGI protocol
Add callback parser support to ASGIProtocol:
- Add _handle_connection_callback() for callback-based parsing
- Add parser callbacks: _on_headers_complete, _on_body, _on_message_complete
- Update data_received() to feed callback parser
- Add _setup_callback_parser() with H1CProtocol/PythonProtocol selection

Add http_parser config options:
- callback: Use callback parser (H1CProtocol if available, else PythonProtocol)
- fast-callback: Require H1CProtocol callback parser

Callback parsing moves HTTP parsing to data_received(), reducing async
overhead in the request handling loop.
2026-03-21 23:24:49 +01:00
Benoit Chesneau
ae7653057f Add callback-based HTTP parser for ASGI protocol
Add PythonProtocol class that mirrors H1CProtocol callback interface:
- Callbacks: on_message_begin, on_url, on_header, on_headers_complete,
  on_body, on_message_complete
- Properties: method, path, http_version, headers, content_length,
  is_chunked, should_keep_alive
- Methods: feed(data), reset()
- Supports Content-Length and chunked transfer encoding

Add CallbackRequest adapter for building requests from parser state.
Works with both H1CProtocol (C extension) and PythonProtocol.

Add unit tests for PythonProtocol and CallbackRequest.
2026-03-21 23:24:23 +01:00
Benoit Chesneau
7818401182 Optimize ASGI protocol for 16x performance improvement
- Replace datetime.now() with time.monotonic() for request timing
- Add access_log_enabled property to skip log work when disabled
- Rewrite BodyReceiver with Future-based waiting (no create_task)
- Remove StreamReader for HTTP/1.1, use direct bytearray buffering
- Add BufferReader wrapper for FastAsyncRequest compatibility
- Use pre-cached chunk prefixes in _send_body()
- Convert async methods to sync where no await needed
- Batch response writes (headers + body in single write)

Performance: 4,200 -> 69,500 req/s
2026-03-21 22:20:05 +01:00
Benoit Chesneau
fa967743c0 Optimize ASGI performance with fast parser integration
Wire HttpParser to ASGI hot path, replacing AsyncRequest.parse() with
direct buffer-based parsing. Add FastAsyncRequest wrapper for body
reading. Replace per-request Queue/Task with BodyReceiver for on-demand
body reading. Keep headers as bytes end-to-end to avoid conversion
overhead. Add backpressure control and keepalive timer. Cache response
status lines and Date header.

Benchmark shows 3x improvement: ~875K req/s for simple GET (was ~340K).
2026-03-21 11:36:46 +01:00
Benoit Chesneau
7f175fb171 Add fast HTTP parser support for WSGI workers
- Integrate gunicorn_h1c fast parser into WSGI Request class
- Add _check_fast_parser() and _parse_fast() methods
- Tests use Python parser for consistent validation behavior
- Update config description to reflect all worker types
2026-03-21 09:29:01 +01:00
Benoit Chesneau
b833a9b6df Add optional fast HTTP parser for ASGI workers
- Add http_parser config setting (auto/fast/python)
- Add gunicorn_h1c as optional dependency [fast]
- Add unified HttpParser class with fallback to pure Python
- Parser tries gunicorn_h1c in 'auto' mode, falls back gracefully
- 'fast' mode requires gunicorn_h1c, 'python' forces pure Python

Install with: pip install gunicorn[fast]
2026-03-21 09:19:41 +01:00
Benoit Chesneau
2cc38503b7
Merge pull request #3514 from lukqw/enrich-error-logging
chore(logging): enrich request handling log line with request method
2026-03-13 10:25:38 +01:00
Benoit Chesneau
8caf79ec64
Merge pull request #3494 from Juneezee/docs/forwarded_allow_ips
docs: update forwarded_allow_ips to Markdown
2026-03-09 13:35:57 +01:00
Benoit Chesneau
24e7ced609
Merge pull request #3520 from benoitc/fix/control-socket-issues
fix(ctl): prevent fork deadlock and file watcher triggers
2026-03-06 23:58:57 +01:00
Benoit Chesneau
474f3ffa1e fix(lint): resolve pylint warnings in ctl/server and gtornado
- Replace global statement with module-level dict in ctl/server.py
- Fix argument rename warning in gtornado.py nested class method
2026-02-27 02:23:08 +01:00
Benoit Chesneau
089ad45818 fix(ctl): prevent fork deadlock in control socket server
- Use os.register_at_fork() to properly handle fork() with asyncio
- Start control server after initial workers spawn, not before
- Change default socket path to /run/gunicorn.ctl (like BIRD)
- Add integration tests for sync, gthread, and gevent workers

Fixes #3509
2026-02-26 20:54:35 +01:00
Benoit Chesneau
d606336de4
Merge pull request #3523 from bysiber/fix-pidfile-fd-leak
fix: ensure pidfile fd is closed on rename failure
2026-02-20 13:43:14 +01:00
Benoit Chesneau
32e46a58ce
Merge pull request #3522 from bysiber/fix-sd-notify-unbound-error
fix: prevent UnboundLocalError in sd_notify when socket creation fails
2026-02-20 13:42:51 +01:00
Benoit Chesneau
cef6b337d8
Merge pull request #3519 from benoitc/fix/gthread-slow-client-resilience
fix(gthread): prevent thread pool exhaustion from slow clients
2026-02-20 10:18:08 +01:00
Kadir Can Ozden
6ada1ce03a fix: ensure pidfile fd is closed on rename failure 2026-02-20 06:17:29 +03:00
Kadir Can Ozden
41c6bf8e3e fix: prevent UnboundLocalError in sd_notify when socket creation fails 2026-02-20 06:16:54 +03:00
Benoit Chesneau
d3f80e8cfd fix(ctl): prevent fork deadlock and file watcher triggers
Fix two issues with the control socket feature introduced in 25.1.0:

1. Fork deadlock (#3509): The control server runs asyncio in a background
   thread. When fork() is called to spawn workers, locks held by asyncio
   can remain locked in the child process, causing deadlocks. Fix by
   stopping the control server before fork and restarting after fork in
   the parent.

2. File watcher triggers (#3508): The default socket path `gunicorn.ctl`
   was created in the app directory, triggering file watchers in dev
   servers (e.g., GAE dev_appserver). Fix by changing the default path
   to `/tmp/gunicorn.ctl`.

Fixes #3508
Fixes #3509
2026-02-19 15:57:41 +01:00
Benoit Chesneau
b5f127e99b fix(gthread): prevent thread pool exhaustion from slow clients
Add a timeout when waiting for initial request data in worker threads.
If no data arrives within 5 seconds, the connection is deferred back
to the main poller instead of blocking the thread indefinitely.

This fixes a regression from v24 where connections were submitted
directly to the thread pool after accept(). In v23, connections were
registered with the poller first and only submitted when data arrived.

The new hybrid approach maintains the performance benefits for fast
clients (immediate processing) while protecting against slow-client
scenarios that can exhaust the thread pool and cause health check
timeouts.

Changes:
- Add _DEFER sentinel and DEFAULT_WORKER_DATA_TIMEOUT constant
- Add TConn.wait_for_data() method using selectors
- Add TConn.data_ready flag to track data availability
- Add pending_conns deque for deferred connections
- Add on_pending_socket_readable() callback
- Add murder_pending() to clean up timed-out pending connections
- Modify handle() to wait for data with timeout before processing
- Modify finish_request() to handle _DEFER and register with poller

Fixes #3518
2026-02-19 15:21:01 +01:00
Benoit Chesneau
3f0c1f9ce5 fix(lint): remove unused io import 2026-02-19 01:02:14 +01:00