Voice et bot modif

This commit is contained in:
pi 2026-06-16 17:09:34 +00:00
parent 189d56026b
commit 7333a22bcd
10774 changed files with 634644 additions and 933308 deletions

View file

@ -4,37 +4,27 @@ import sys
import traceback
import warnings
from collections import deque
from collections.abc import Awaitable, Callable, Sequence
from contextlib import suppress
from html import escape as html_escape
from http import HTTPStatus
from logging import Logger
from typing import (
TYPE_CHECKING,
Any,
Awaitable,
Callable,
Deque,
Optional,
Sequence,
Tuple,
Type,
Union,
cast,
)
from typing import TYPE_CHECKING, Any, Optional, cast
import attr
import yarl
from propcache import under_cached_property
from .abc import AbstractAccessLogger, AbstractStreamWriter
from .base_protocol import BaseProtocol
from .helpers import ceil_timeout
from .base_protocol import PAUSE_RESUME_READING_ERRORS, BaseProtocol
from .helpers import DEFAULT_CHUNK_SIZE, ceil_timeout
from .http import (
HttpProcessingError,
HttpRequestParser,
HttpVersion10,
RawRequestMessage,
StreamWriter,
WebSocketReader,
)
from .http_exceptions import BadHttpMethod
from .log import access_logger, server_logger
@ -47,6 +37,11 @@ from .web_response import Response, StreamResponse
__all__ = ("RequestHandler", "RequestPayloadError", "PayloadAccessError")
# Max parsed-but-unhandled pipelined requests buffered per connection before
# reading is paused. Bounds memory a client can pin by keeping one handler busy
# and pipelining behind it; reading resumes as the queue drains.
MAX_MSG_QUEUE_SIZE = 32
if TYPE_CHECKING:
import ssl
@ -98,7 +93,7 @@ class _ErrInfo:
message: str
_MsgType = Tuple[Union[RawRequestMessage, _ErrInfo], StreamReader]
_MsgType = tuple[RawRequestMessage | _ErrInfo, StreamReader]
class RequestHandler(BaseProtocol):
@ -142,6 +137,9 @@ class RequestHandler(BaseProtocol):
"""
__slots__ = (
"max_field_size",
"max_headers",
"max_line_size",
"_request_count",
"_keepalive",
"_manager",
@ -153,14 +151,15 @@ class RequestHandler(BaseProtocol):
"_keepalive_timeout",
"_lingering_time",
"_messages",
"_max_msg_queue_size",
"_msg_queue_resume_size",
"_msg_queue_paused",
"_message_tail",
"_handler_waiter",
"_waiter",
"_task_handler",
"_upgrade",
"_payload_parser",
"_request_parser",
"_reading_paused",
"_data_received_cb",
"logger",
"debug",
"access_log",
@ -183,45 +182,26 @@ class RequestHandler(BaseProtocol):
keepalive_timeout: float = 3630,
tcp_keepalive: bool = True,
logger: Logger = server_logger,
access_log_class: Type[AbstractAccessLogger] = AccessLogger,
access_log_class: type[AbstractAccessLogger] = AccessLogger,
access_log: Logger = access_logger,
access_log_format: str = AccessLogger.LOG_FORMAT,
debug: bool = False,
max_line_size: int = 8190,
max_headers: int = 32768,
max_headers: int = 128,
max_field_size: int = 8190,
lingering_time: float = 10.0,
read_bufsize: int = 2**16,
read_bufsize: int = DEFAULT_CHUNK_SIZE,
auto_decompress: bool = True,
timeout_ceil_threshold: float = 5,
):
super().__init__(loop)
# _request_count is the number of requests processed with the same connection.
self._request_count = 0
self._keepalive = False
self._current_request: Optional[BaseRequest] = None
self._manager: Optional[Server] = manager
self._request_handler: Optional[_RequestHandler] = manager.request_handler
self._request_factory: Optional[_RequestFactory] = manager.request_factory
self._tcp_keepalive = tcp_keepalive
# placeholder to be replaced on keepalive timeout setup
self._next_keepalive_close_time = 0.0
self._keepalive_handle: Optional[asyncio.Handle] = None
self._keepalive_timeout = keepalive_timeout
self._lingering_time = float(lingering_time)
self._messages: Deque[_MsgType] = deque()
self._message_tail = b""
self._waiter: Optional[asyncio.Future[None]] = None
self._handler_waiter: Optional[asyncio.Future[None]] = None
self._task_handler: Optional[asyncio.Task[None]] = None
self._upgrade = False
self._payload_parser: Any = None
self._request_parser: Optional[HttpRequestParser] = HttpRequestParser(
self._max_msg_queue_size = MAX_MSG_QUEUE_SIZE
# Low-water mark: resume reading once the queue drains to half the limit
# so we refill in batches instead of churning pause/resume per request.
self._msg_queue_resume_size = MAX_MSG_QUEUE_SIZE // 2
# Set before super().__init__ so _reading_paused_for_msg_queue() is safe
# if BaseProtocol ever triggers a resume during init.
self._msg_queue_paused = False
parser = HttpRequestParser(
self,
loop,
read_bufsize,
@ -230,7 +210,37 @@ class RequestHandler(BaseProtocol):
max_headers=max_headers,
payload_exception=RequestPayloadError,
auto_decompress=auto_decompress,
max_msg_queue_size=MAX_MSG_QUEUE_SIZE,
)
super().__init__(loop, parser)
# _request_count is the number of requests processed with the same connection.
self._request_count = 0
self._keepalive = False
self._current_request: BaseRequest | None = None
self._manager: Server | None = manager
self._request_handler: _RequestHandler | None = manager.request_handler
self._request_factory: _RequestFactory | None = manager.request_factory
self.max_line_size = max_line_size
self.max_headers = max_headers
self.max_field_size = max_field_size
self._tcp_keepalive = tcp_keepalive
# placeholder to be replaced on keepalive timeout setup
self._next_keepalive_close_time = 0.0
self._keepalive_handle: asyncio.Handle | None = None
self._keepalive_timeout = keepalive_timeout
self._lingering_time = float(lingering_time)
self._messages: deque[_MsgType] = deque()
self._message_tail = b""
self._data_received_cb: Callable[[], None] | None = None
self._waiter: asyncio.Future[None] | None = None
self._handler_waiter: asyncio.Future[None] | None = None
self._task_handler: asyncio.Task[None] | None = None
self._payload_parser: Any = None
self._timeout_ceil_threshold: float = 5
try:
@ -242,7 +252,7 @@ class RequestHandler(BaseProtocol):
self.debug = debug
self.access_log = access_log
if access_log:
self.access_logger: Optional[AbstractAccessLogger] = access_log_class(
self.access_logger: AbstractAccessLogger | None = access_log_class(
access_log, access_log_format
)
self._logging_enabled = self.access_logger.enabled
@ -273,7 +283,7 @@ class RequestHandler(BaseProtocol):
@under_cached_property
def peername(
self,
) -> Optional[Union[str, Tuple[str, int, int, int], Tuple[str, int]]]:
) -> str | tuple[str, int, int, int] | tuple[str, int] | None:
"""Return peername if available."""
return (
None
@ -281,11 +291,22 @@ class RequestHandler(BaseProtocol):
else self.transport.get_extra_info("peername")
)
@under_cached_property
def sockname(
self,
) -> str | tuple[str, int, int, int] | tuple[str, int] | None:
"""Return sockname if available."""
return (
None
if self.transport is None
else self.transport.get_extra_info("sockname")
)
@property
def keepalive_timeout(self) -> float:
return self._keepalive_timeout
async def shutdown(self, timeout: Optional[float] = 15.0) -> None:
async def shutdown(self, timeout: float | None = 15.0) -> None:
"""Do worker process exit preparations.
We need to clean up everything and stop accepting requests.
@ -352,7 +373,7 @@ class RequestHandler(BaseProtocol):
task = loop.create_task(self.start())
self._task_handler = task
def connection_lost(self, exc: Optional[BaseException]) -> None:
def connection_lost(self, exc: BaseException | None) -> None:
if self._manager is None:
return
self._manager.connection_lost(self, exc)
@ -365,7 +386,7 @@ class RequestHandler(BaseProtocol):
self._manager = None
self._request_factory = None
self._request_handler = None
self._request_parser = None
self._parser = None
if self._keepalive_handle is not None:
self._keepalive_handle.cancel()
@ -384,11 +405,15 @@ class RequestHandler(BaseProtocol):
self._payload_parser.feed_eof()
self._payload_parser = None
def set_parser(self, parser: Any) -> None:
# Actual type is WebReader
def set_parser(
self,
parser: WebSocketReader,
data_received_cb: Callable[[], None] | None = None,
) -> None:
assert self._payload_parser is None
self._payload_parser = parser
self._data_received_cb = data_received_cb
if self._message_tail:
self._payload_parser.feed_data(self._message_tail)
@ -402,10 +427,10 @@ class RequestHandler(BaseProtocol):
return
# parse http messages
messages: Sequence[_MsgType]
if self._payload_parser is None and not self._upgrade:
assert self._request_parser is not None
if self._payload_parser is None and not self._upgraded:
assert self._parser is not None
try:
messages, upgraded, tail = self._request_parser.feed_data(data)
messages, upgraded, tail = self._parser.feed_data(data)
except HttpProcessingError as exc:
messages = [
(_ErrInfo(status=400, exc=exc, message=exc.message), EMPTY_PAYLOAD)
@ -413,7 +438,7 @@ class RequestHandler(BaseProtocol):
upgraded = False
tail = b""
for msg, payload in messages or ():
for msg, payload in messages:
self._request_count += 1
self._messages.append((msg, payload))
@ -422,20 +447,60 @@ class RequestHandler(BaseProtocol):
# don't set result twice
waiter.set_result(None)
self._upgrade = upgraded
# Queue full: pause the transport (the parser already stopped
# emitting). start() resumes as it drains the queue.
if (
not self._msg_queue_paused
and len(self._messages) >= self._max_msg_queue_size
):
self._pause_msg_queue_reading()
self._upgraded = upgraded
if upgraded and tail:
self._message_tail = tail
# no parser, just store
elif self._payload_parser is None and self._upgrade and data:
elif self._payload_parser is None and self._upgraded and data:
self._message_tail += data
# feed payload
elif data:
if self._data_received_cb is not None:
self._data_received_cb()
eof, tail = self._payload_parser.feed_data(data)
if eof:
self.close()
def _reading_paused_for_msg_queue(self) -> bool:
return self._msg_queue_paused
def _pause_msg_queue_reading(self) -> None:
self._msg_queue_paused = True
if self.transport is not None:
try:
self.transport.pause_reading()
except PAUSE_RESUME_READING_ERRORS:
# Transport lacks flow control; nothing to pause. Intentionally
# ignored (see PAUSE_RESUME_READING_ERRORS; do not use suppress).
pass
def _resume_msg_queue_reading(self) -> None:
if not self._upgraded:
# Reparse buffered pipelined requests while still marked paused so
# a refill past the limit does not re-pause an already-paused
# transport; only resume below once it stayed under the limit.
self.data_received(b"")
if len(self._messages) >= self._max_msg_queue_size:
return
self._msg_queue_paused = False
if not self._reading_paused and self.transport is not None:
try:
self.transport.resume_reading()
except PAUSE_RESUME_READING_ERRORS:
# Transport lacks flow control; nothing to resume. Intentionally
# ignored (see PAUSE_RESUME_READING_ERRORS; do not use suppress).
pass
def keep_alive(self, val: bool) -> None:
"""Set keep-alive connection mode.
@ -466,7 +531,7 @@ class RequestHandler(BaseProtocol):
self.transport = None
def log_access(
self, request: BaseRequest, response: StreamResponse, time: Optional[float]
self, request: BaseRequest, response: StreamResponse, time: float | None
) -> None:
if self._logging_enabled and self.access_logger is not None:
if TYPE_CHECKING:
@ -500,9 +565,9 @@ class RequestHandler(BaseProtocol):
async def _handle_request(
self,
request: BaseRequest,
start_time: Optional[float],
start_time: float | None,
request_handler: Callable[[BaseRequest], Awaitable[StreamResponse]],
) -> Tuple[StreamResponse, bool]:
) -> tuple[StreamResponse, bool]:
self._request_in_progress = True
try:
try:
@ -568,6 +633,18 @@ class RequestHandler(BaseProtocol):
message, payload = self._messages.popleft()
# Free a parser slot; resume reading once drained to low water so
# pipelining keeps flowing while this request is handled.
# no branch: _parser is only None after connection_lost, whose path
# exits this loop, so the None case is not reachably exercisable.
if self._parser is not None: # pragma: no branch
self._parser.message_consumed()
if (
self._msg_queue_paused
and len(self._messages) <= self._msg_queue_resume_size
):
self._resume_msg_queue_reading()
# time is only fetched if logging is enabled as otherwise
# its thrown away and never used.
start = loop.time() if self._logging_enabled else None
@ -678,8 +755,8 @@ class RequestHandler(BaseProtocol):
self.transport.close()
async def finish_response(
self, request: BaseRequest, resp: StreamResponse, start_time: Optional[float]
) -> Tuple[StreamResponse, bool]:
self, request: BaseRequest, resp: StreamResponse, start_time: float | None
) -> tuple[StreamResponse, bool]:
"""Prepare the response and write_eof, then log access.
This has to
@ -688,12 +765,18 @@ class RequestHandler(BaseProtocol):
prematurely.
"""
request._finish()
if self._request_parser is not None:
self._request_parser.set_upgraded(False)
self._upgrade = False
if self._parser is not None:
self._parser.set_upgraded(False)
self._upgraded = False
if self._message_tail:
self._request_parser.feed_data(self._message_tail)
self._message_tail = b""
messages, _upgraded, tail = self._parser.feed_data(self._message_tail)
self._message_tail = tail
for msg, payload in messages:
self._request_count += 1
self._messages.append((msg, payload))
# This shouldn't be possible. If a future refactor results in this
# failing, then the code may need to be updated to set the waiter.
assert self._waiter is None
try:
prepare_meth = resp.prepare
except AttributeError:
@ -701,8 +784,7 @@ class RequestHandler(BaseProtocol):
self.log_exception("Missing return statement on request handler")
else:
self.log_exception(
"Web-handler should return a response instance, "
"got {!r}".format(resp)
f"Web-handler should return a response instance, got {resp!r}"
)
exc = HTTPInternalServerError()
resp = Response(
@ -723,8 +805,8 @@ class RequestHandler(BaseProtocol):
self,
request: BaseRequest,
status: int = 500,
exc: Optional[BaseException] = None,
message: Optional[str] = None,
exc: BaseException | None = None,
message: str | None = None,
) -> StreamResponse:
"""Handle errors.
@ -753,7 +835,7 @@ class RequestHandler(BaseProtocol):
ct = "text/plain"
if status == HTTPStatus.INTERNAL_SERVER_ERROR:
title = "{0.value} {0.phrase}".format(HTTPStatus.INTERNAL_SERVER_ERROR)
title = f"{HTTPStatus.INTERNAL_SERVER_ERROR.value} {HTTPStatus.INTERNAL_SERVER_ERROR.phrase}"
msg = HTTPStatus.INTERNAL_SERVER_ERROR.description
tb = None
if self.debug:
@ -766,10 +848,10 @@ class RequestHandler(BaseProtocol):
msg = f"<h2>Traceback:</h2>\n<pre>{tb}</pre>"
message = (
"<html><head>"
"<title>{title}</title>"
"</head><body>\n<h1>{title}</h1>"
"\n{msg}\n</body></html>\n"
).format(title=title, msg=msg)
f"<title>{title}</title>"
f"</head><body>\n<h1>{title}</h1>"
f"\n{msg}\n</body></html>\n"
)
ct = "text/html"
else:
if tb: