mirror of
https://github.com/frappe/gunicorn.git
synced 2026-07-04 03:31:29 +08:00
- WSGI fast parser now applies the same per-header policy as the Python parser (Expect, secure_scheme_headers, forwarded_allow_ips trust gate, forwarder_headers / header_map). Shared helpers extracted on Message. - ASGI keepalive no longer resets the parser when the previous request body was not fully framed; the connection closes instead, preventing request smuggling on pipelined connections. - BodyReceiver._wait_for_data timeout flips _closed and yields http.disconnect rather than synthesizing more_body=False. Timeout honors cfg.timeout. - ASGI chunked encoding now skips HEAD, 204, and 304 (matches Response.is_chunked in the WSGI path) via a small helper. - _setup_callback_parser passes proxy_protocol to PythonProtocol; auto falls back to the Python parser when proxy_protocol != off (the C parser does not implement PROXY framing). _effective_peername swaps the transport peer with the PROXY-supplied client address. - Parser.finish_body accepts a deadline and a 64KiB byte cap; gthread passes a deadline and abandons keepalive on incomplete drain so a stalled client cannot tie up a worker thread.
115 lines
3.7 KiB
Python
115 lines
3.7 KiB
Python
#
|
|
# This file is part of gunicorn released under the MIT license.
|
|
# See the NOTICE for more information.
|
|
|
|
import socket
|
|
import ssl
|
|
import time
|
|
|
|
from gunicorn.http.message import Request
|
|
from gunicorn.http.unreader import SocketUnreader, IterUnreader
|
|
|
|
|
|
# Cap on bytes drained from an unconsumed request body before a keepalive
|
|
# reset. Defends against a slow-but-steady client that stays under a per-read
|
|
# deadline yet streams indefinitely.
|
|
_DRAIN_MAX_BYTES = 64 * 1024
|
|
|
|
|
|
class Parser:
|
|
|
|
mesg_class = None
|
|
|
|
def __init__(self, cfg, source, source_addr):
|
|
self.cfg = cfg
|
|
if hasattr(source, "recv"):
|
|
self.unreader = SocketUnreader(source)
|
|
else:
|
|
self.unreader = IterUnreader(source)
|
|
self.mesg = None
|
|
self.source_addr = source_addr
|
|
|
|
# request counter (for keepalive connetions)
|
|
self.req_count = 0
|
|
|
|
def __iter__(self):
|
|
return self
|
|
|
|
def finish_body(self, deadline=None, max_bytes=_DRAIN_MAX_BYTES):
|
|
"""Discard any unread body of the current message.
|
|
|
|
Called before returning a keepalive connection to the poller so the
|
|
socket does not appear readable due to leftover body bytes.
|
|
|
|
``deadline`` is an absolute ``time.monotonic()`` value; when set the
|
|
socket read timeout is bounded by the remaining time before each read.
|
|
``max_bytes`` caps the total drained bytes to defend against a slow
|
|
client that keeps trickling under the deadline.
|
|
|
|
Returns ``True`` when the body was fully drained, ``False`` when the
|
|
drain was abandoned (deadline, byte cap, or socket timeout). Callers
|
|
that observe ``False`` MUST close the connection rather than serve
|
|
another request on it.
|
|
"""
|
|
if not self.mesg:
|
|
return True
|
|
|
|
sock = getattr(self.unreader, "sock", None)
|
|
# gettimeout/settimeout only matter when bounding a real socket; a
|
|
# mock or non-socket source skips the timeout plumbing.
|
|
if sock is not None and hasattr(sock, "gettimeout") and hasattr(sock, "settimeout"):
|
|
timeoutable_sock = sock
|
|
prior_timeout = sock.gettimeout()
|
|
else:
|
|
timeoutable_sock = None
|
|
prior_timeout = None
|
|
|
|
drained = 0
|
|
try:
|
|
while True:
|
|
if deadline is not None and timeoutable_sock is not None:
|
|
remaining = deadline - time.monotonic()
|
|
if remaining <= 0:
|
|
return False
|
|
timeoutable_sock.settimeout(remaining)
|
|
try:
|
|
data = self.mesg.body.read(1024)
|
|
except (socket.timeout, TimeoutError):
|
|
return False
|
|
except ssl.SSLWantReadError:
|
|
# SSL socket has no more application data available
|
|
return True
|
|
if not data:
|
|
return True
|
|
drained += len(data)
|
|
if drained >= max_bytes:
|
|
return False
|
|
finally:
|
|
if timeoutable_sock is not None:
|
|
try:
|
|
timeoutable_sock.settimeout(prior_timeout)
|
|
except OSError:
|
|
pass
|
|
|
|
def __next__(self):
|
|
# Stop if HTTP dictates a stop.
|
|
if self.mesg and self.mesg.should_close():
|
|
raise StopIteration()
|
|
|
|
# Discard any unread body of the previous message
|
|
self.finish_body()
|
|
|
|
# Parse the next request
|
|
self.req_count += 1
|
|
self.mesg = self.mesg_class(self.cfg, self.unreader, self.source_addr, self.req_count)
|
|
if not self.mesg:
|
|
raise StopIteration()
|
|
return self.mesg
|
|
|
|
next = __next__
|
|
|
|
|
|
class RequestParser(Parser):
|
|
|
|
mesg_class = Request
|