Voice et bot modif
This commit is contained in:
parent
189d56026b
commit
7333a22bcd
10774 changed files with 634644 additions and 933308 deletions
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue