Загрузить файлы в «venv/Lib/site-packages/websockets»
This commit is contained in:
236
venv/Lib/site-packages/websockets/__init__.py
Normal file
236
venv/Lib/site-packages/websockets/__init__.py
Normal file
@@ -0,0 +1,236 @@
|
||||
from __future__ import annotations
|
||||
|
||||
# Importing the typing module would conflict with websockets.typing.
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .imports import lazy_import
|
||||
from .version import version as __version__ # noqa: F401
|
||||
|
||||
|
||||
__all__ = [
|
||||
# .asyncio.client
|
||||
"connect",
|
||||
"unix_connect",
|
||||
"ClientConnection",
|
||||
# .asyncio.router
|
||||
"route",
|
||||
"unix_route",
|
||||
"Router",
|
||||
# .asyncio.server
|
||||
"basic_auth",
|
||||
"broadcast",
|
||||
"serve",
|
||||
"unix_serve",
|
||||
"ServerConnection",
|
||||
"Server",
|
||||
# .client
|
||||
"ClientProtocol",
|
||||
# .datastructures
|
||||
"Headers",
|
||||
"HeadersLike",
|
||||
"MultipleValuesError",
|
||||
# .exceptions
|
||||
"ConcurrencyError",
|
||||
"ConnectionClosed",
|
||||
"ConnectionClosedError",
|
||||
"ConnectionClosedOK",
|
||||
"DuplicateParameter",
|
||||
"InvalidHandshake",
|
||||
"InvalidHeader",
|
||||
"InvalidHeaderFormat",
|
||||
"InvalidHeaderValue",
|
||||
"InvalidMessage",
|
||||
"InvalidOrigin",
|
||||
"InvalidParameterName",
|
||||
"InvalidParameterValue",
|
||||
"InvalidProxy",
|
||||
"InvalidProxyMessage",
|
||||
"InvalidProxyStatus",
|
||||
"InvalidState",
|
||||
"InvalidStatus",
|
||||
"InvalidUpgrade",
|
||||
"InvalidURI",
|
||||
"NegotiationError",
|
||||
"PayloadTooBig",
|
||||
"ProtocolError",
|
||||
"ProxyError",
|
||||
"SecurityError",
|
||||
"WebSocketException",
|
||||
# .frames
|
||||
"Close",
|
||||
"CloseCode",
|
||||
"Frame",
|
||||
"Opcode",
|
||||
# .http11
|
||||
"Request",
|
||||
"Response",
|
||||
# .protocol
|
||||
"Protocol",
|
||||
"Side",
|
||||
"State",
|
||||
# .server
|
||||
"ServerProtocol",
|
||||
# .typing
|
||||
"Data",
|
||||
"ExtensionName",
|
||||
"ExtensionParameter",
|
||||
"LoggerLike",
|
||||
"StatusLike",
|
||||
"Origin",
|
||||
"Subprotocol",
|
||||
]
|
||||
|
||||
# When type checking, import non-deprecated aliases eagerly. Else, import on demand.
|
||||
if TYPE_CHECKING:
|
||||
from .asyncio.client import ClientConnection, connect, unix_connect
|
||||
from .asyncio.router import Router, route, unix_route
|
||||
from .asyncio.server import (
|
||||
Server,
|
||||
ServerConnection,
|
||||
basic_auth,
|
||||
broadcast,
|
||||
serve,
|
||||
unix_serve,
|
||||
)
|
||||
from .client import ClientProtocol
|
||||
from .datastructures import Headers, HeadersLike, MultipleValuesError
|
||||
from .exceptions import (
|
||||
ConcurrencyError,
|
||||
ConnectionClosed,
|
||||
ConnectionClosedError,
|
||||
ConnectionClosedOK,
|
||||
DuplicateParameter,
|
||||
InvalidHandshake,
|
||||
InvalidHeader,
|
||||
InvalidHeaderFormat,
|
||||
InvalidHeaderValue,
|
||||
InvalidMessage,
|
||||
InvalidOrigin,
|
||||
InvalidParameterName,
|
||||
InvalidParameterValue,
|
||||
InvalidProxy,
|
||||
InvalidProxyMessage,
|
||||
InvalidProxyStatus,
|
||||
InvalidState,
|
||||
InvalidStatus,
|
||||
InvalidUpgrade,
|
||||
InvalidURI,
|
||||
NegotiationError,
|
||||
PayloadTooBig,
|
||||
ProtocolError,
|
||||
ProxyError,
|
||||
SecurityError,
|
||||
WebSocketException,
|
||||
)
|
||||
from .frames import Close, CloseCode, Frame, Opcode
|
||||
from .http11 import Request, Response
|
||||
from .protocol import Protocol, Side, State
|
||||
from .server import ServerProtocol
|
||||
from .typing import (
|
||||
Data,
|
||||
ExtensionName,
|
||||
ExtensionParameter,
|
||||
LoggerLike,
|
||||
Origin,
|
||||
StatusLike,
|
||||
Subprotocol,
|
||||
)
|
||||
else:
|
||||
lazy_import(
|
||||
globals(),
|
||||
aliases={
|
||||
# .asyncio.client
|
||||
"connect": ".asyncio.client",
|
||||
"unix_connect": ".asyncio.client",
|
||||
"ClientConnection": ".asyncio.client",
|
||||
# .asyncio.router
|
||||
"route": ".asyncio.router",
|
||||
"unix_route": ".asyncio.router",
|
||||
"Router": ".asyncio.router",
|
||||
# .asyncio.server
|
||||
"basic_auth": ".asyncio.server",
|
||||
"broadcast": ".asyncio.server",
|
||||
"serve": ".asyncio.server",
|
||||
"unix_serve": ".asyncio.server",
|
||||
"ServerConnection": ".asyncio.server",
|
||||
"Server": ".asyncio.server",
|
||||
# .client
|
||||
"ClientProtocol": ".client",
|
||||
# .datastructures
|
||||
"Headers": ".datastructures",
|
||||
"HeadersLike": ".datastructures",
|
||||
"MultipleValuesError": ".datastructures",
|
||||
# .exceptions
|
||||
"ConcurrencyError": ".exceptions",
|
||||
"ConnectionClosed": ".exceptions",
|
||||
"ConnectionClosedError": ".exceptions",
|
||||
"ConnectionClosedOK": ".exceptions",
|
||||
"DuplicateParameter": ".exceptions",
|
||||
"InvalidHandshake": ".exceptions",
|
||||
"InvalidHeader": ".exceptions",
|
||||
"InvalidHeaderFormat": ".exceptions",
|
||||
"InvalidHeaderValue": ".exceptions",
|
||||
"InvalidMessage": ".exceptions",
|
||||
"InvalidOrigin": ".exceptions",
|
||||
"InvalidParameterName": ".exceptions",
|
||||
"InvalidParameterValue": ".exceptions",
|
||||
"InvalidProxy": ".exceptions",
|
||||
"InvalidProxyMessage": ".exceptions",
|
||||
"InvalidProxyStatus": ".exceptions",
|
||||
"InvalidState": ".exceptions",
|
||||
"InvalidStatus": ".exceptions",
|
||||
"InvalidUpgrade": ".exceptions",
|
||||
"InvalidURI": ".exceptions",
|
||||
"NegotiationError": ".exceptions",
|
||||
"PayloadTooBig": ".exceptions",
|
||||
"ProtocolError": ".exceptions",
|
||||
"ProxyError": ".exceptions",
|
||||
"SecurityError": ".exceptions",
|
||||
"WebSocketException": ".exceptions",
|
||||
# .frames
|
||||
"Close": ".frames",
|
||||
"CloseCode": ".frames",
|
||||
"Frame": ".frames",
|
||||
"Opcode": ".frames",
|
||||
# .http11
|
||||
"Request": ".http11",
|
||||
"Response": ".http11",
|
||||
# .protocol
|
||||
"Protocol": ".protocol",
|
||||
"Side": ".protocol",
|
||||
"State": ".protocol",
|
||||
# .server
|
||||
"ServerProtocol": ".server",
|
||||
# .typing
|
||||
"Data": ".typing",
|
||||
"ExtensionName": ".typing",
|
||||
"ExtensionParameter": ".typing",
|
||||
"LoggerLike": ".typing",
|
||||
"Origin": ".typing",
|
||||
"StatusLike": ".typing",
|
||||
"Subprotocol": ".typing",
|
||||
},
|
||||
deprecated_aliases={
|
||||
# deprecated in 9.0 - 2021-09-01
|
||||
"framing": ".legacy",
|
||||
"handshake": ".legacy",
|
||||
"parse_uri": ".uri",
|
||||
"WebSocketURI": ".uri",
|
||||
# deprecated in 14.0 - 2024-11-09
|
||||
# .legacy.auth
|
||||
"BasicAuthWebSocketServerProtocol": ".legacy.auth",
|
||||
"basic_auth_protocol_factory": ".legacy.auth",
|
||||
# .legacy.client
|
||||
"WebSocketClientProtocol": ".legacy.client",
|
||||
# .legacy.exceptions
|
||||
"AbortHandshake": ".legacy.exceptions",
|
||||
"InvalidStatusCode": ".legacy.exceptions",
|
||||
"RedirectHandshake": ".legacy.exceptions",
|
||||
"WebSocketProtocolError": ".legacy.exceptions",
|
||||
# .legacy.protocol
|
||||
"WebSocketCommonProtocol": ".legacy.protocol",
|
||||
# .legacy.server
|
||||
"WebSocketServer": ".legacy.server",
|
||||
"WebSocketServerProtocol": ".legacy.server",
|
||||
},
|
||||
)
|
||||
5
venv/Lib/site-packages/websockets/__main__.py
Normal file
5
venv/Lib/site-packages/websockets/__main__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .cli import main
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
18
venv/Lib/site-packages/websockets/auth.py
Normal file
18
venv/Lib/site-packages/websockets/auth.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import warnings
|
||||
|
||||
|
||||
with warnings.catch_warnings():
|
||||
# Suppress redundant DeprecationWarning raised by websockets.legacy.
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
||||
from .legacy.auth import *
|
||||
from .legacy.auth import __all__ # noqa: F401
|
||||
|
||||
|
||||
warnings.warn( # deprecated in 14.0 - 2024-11-09
|
||||
"websockets.auth, an alias for websockets.legacy.auth, is deprecated; "
|
||||
"see https://websockets.readthedocs.io/en/stable/howto/upgrade.html "
|
||||
"for upgrade instructions",
|
||||
DeprecationWarning,
|
||||
)
|
||||
178
venv/Lib/site-packages/websockets/cli.py
Normal file
178
venv/Lib/site-packages/websockets/cli.py
Normal file
@@ -0,0 +1,178 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
from typing import Generator
|
||||
|
||||
from .asyncio.client import ClientConnection, connect
|
||||
from .asyncio.messages import SimpleQueue
|
||||
from .exceptions import ConnectionClosed
|
||||
from .frames import Close
|
||||
from .streams import StreamReader
|
||||
from .version import version as websockets_version
|
||||
|
||||
|
||||
__all__ = ["main"]
|
||||
|
||||
|
||||
def print_during_input(string: str) -> None:
|
||||
sys.stdout.write(
|
||||
# Save cursor position
|
||||
"\N{ESC}7"
|
||||
# Add a new line
|
||||
"\N{LINE FEED}"
|
||||
# Move cursor up
|
||||
"\N{ESC}[A"
|
||||
# Insert blank line, scroll last line down
|
||||
"\N{ESC}[L"
|
||||
# Print string in the inserted blank line
|
||||
f"{string}\N{LINE FEED}"
|
||||
# Restore cursor position
|
||||
"\N{ESC}8"
|
||||
# Move cursor down
|
||||
"\N{ESC}[B"
|
||||
)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def print_over_input(string: str) -> None:
|
||||
sys.stdout.write(
|
||||
# Move cursor to beginning of line
|
||||
"\N{CARRIAGE RETURN}"
|
||||
# Delete current line
|
||||
"\N{ESC}[K"
|
||||
# Print string
|
||||
f"{string}\N{LINE FEED}"
|
||||
)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
class ReadLines(asyncio.Protocol):
|
||||
def __init__(self) -> None:
|
||||
self.reader = StreamReader()
|
||||
self.messages: SimpleQueue[str] = SimpleQueue()
|
||||
|
||||
def parse(self) -> Generator[None, None, None]:
|
||||
while True:
|
||||
sys.stdout.write("> ")
|
||||
sys.stdout.flush()
|
||||
line = yield from self.reader.read_line(sys.maxsize)
|
||||
self.messages.put(line.decode().rstrip("\r\n"))
|
||||
|
||||
def connection_made(self, transport: asyncio.BaseTransport) -> None:
|
||||
self.parser = self.parse()
|
||||
next(self.parser)
|
||||
|
||||
def data_received(self, data: bytes) -> None:
|
||||
self.reader.feed_data(data)
|
||||
next(self.parser)
|
||||
|
||||
def eof_received(self) -> None:
|
||||
self.reader.feed_eof()
|
||||
# next(self.parser) isn't useful and would raise EOFError.
|
||||
|
||||
def connection_lost(self, exc: Exception | None) -> None:
|
||||
self.reader.discard()
|
||||
self.messages.abort()
|
||||
|
||||
|
||||
async def print_incoming_messages(websocket: ClientConnection) -> None:
|
||||
async for message in websocket:
|
||||
if isinstance(message, str):
|
||||
print_during_input("< " + message)
|
||||
else:
|
||||
print_during_input("< (binary) " + message.hex())
|
||||
|
||||
|
||||
async def send_outgoing_messages(
|
||||
websocket: ClientConnection,
|
||||
messages: SimpleQueue[str],
|
||||
) -> None:
|
||||
while True:
|
||||
try:
|
||||
message = await messages.get()
|
||||
except EOFError:
|
||||
break
|
||||
try:
|
||||
await websocket.send(message)
|
||||
except ConnectionClosed: # pragma: no cover
|
||||
break
|
||||
|
||||
|
||||
async def interactive_client(uri: str) -> None:
|
||||
try:
|
||||
websocket = await connect(uri)
|
||||
except Exception as exc:
|
||||
print(f"Failed to connect to {uri}: {exc}.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(f"Connected to {uri}.")
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
transport, protocol = await loop.connect_read_pipe(ReadLines, sys.stdin)
|
||||
incoming = asyncio.create_task(
|
||||
print_incoming_messages(websocket),
|
||||
)
|
||||
outgoing = asyncio.create_task(
|
||||
send_outgoing_messages(websocket, protocol.messages),
|
||||
)
|
||||
try:
|
||||
await asyncio.wait(
|
||||
[incoming, outgoing],
|
||||
# Clean up and exit when the server closes the connection
|
||||
# or the user enters EOT (^D), whichever happens first.
|
||||
return_when=asyncio.FIRST_COMPLETED,
|
||||
)
|
||||
# asyncio.run() cancels the main task when the user triggers SIGINT (^C).
|
||||
# https://docs.python.org/3/library/asyncio-runner.html#handling-keyboard-interruption
|
||||
# Clean up and exit without re-raising CancelledError to prevent Python
|
||||
# from raising KeyboardInterrupt and displaying a stack track.
|
||||
except asyncio.CancelledError: # pragma: no cover
|
||||
pass
|
||||
finally:
|
||||
incoming.cancel()
|
||||
outgoing.cancel()
|
||||
transport.close()
|
||||
|
||||
await websocket.close()
|
||||
assert websocket.close_code is not None and websocket.close_reason is not None
|
||||
close_status = Close(websocket.close_code, websocket.close_reason)
|
||||
print_over_input(f"Connection closed: {close_status}.")
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="websockets",
|
||||
description="Interactive WebSocket client.",
|
||||
add_help=False,
|
||||
)
|
||||
group = parser.add_mutually_exclusive_group()
|
||||
group.add_argument("--version", action="store_true")
|
||||
group.add_argument("uri", metavar="<uri>", nargs="?")
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
if args.version:
|
||||
print(f"websockets {websockets_version}")
|
||||
return
|
||||
|
||||
if args.uri is None:
|
||||
parser.print_usage()
|
||||
sys.exit(2)
|
||||
|
||||
# Enable VT100 to support ANSI escape codes in Command Prompt on Windows.
|
||||
# See https://github.com/python/cpython/issues/74261 for why this works.
|
||||
if sys.platform == "win32":
|
||||
os.system("")
|
||||
|
||||
try:
|
||||
import readline # noqa: F401
|
||||
except ImportError: # readline isn't available on all platforms
|
||||
pass
|
||||
|
||||
# Remove the try/except block when dropping Python < 3.11.
|
||||
try:
|
||||
asyncio.run(interactive_client(args.uri))
|
||||
except KeyboardInterrupt: # pragma: no cover
|
||||
pass
|
||||
391
venv/Lib/site-packages/websockets/client.py
Normal file
391
venv/Lib/site-packages/websockets/client.py
Normal file
@@ -0,0 +1,391 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import random
|
||||
import warnings
|
||||
from collections.abc import Generator, Sequence
|
||||
from typing import Any
|
||||
|
||||
from .datastructures import Headers, MultipleValuesError
|
||||
from .exceptions import (
|
||||
InvalidHandshake,
|
||||
InvalidHeader,
|
||||
InvalidHeaderValue,
|
||||
InvalidMessage,
|
||||
InvalidStatus,
|
||||
InvalidUpgrade,
|
||||
NegotiationError,
|
||||
)
|
||||
from .extensions import ClientExtensionFactory, Extension
|
||||
from .headers import (
|
||||
build_authorization_basic,
|
||||
build_extension,
|
||||
build_host,
|
||||
build_subprotocol,
|
||||
parse_connection,
|
||||
parse_extension,
|
||||
parse_subprotocol,
|
||||
parse_upgrade,
|
||||
)
|
||||
from .http11 import Request, Response
|
||||
from .imports import lazy_import
|
||||
from .protocol import CLIENT, CONNECTING, OPEN, Protocol, State
|
||||
from .typing import (
|
||||
ConnectionOption,
|
||||
ExtensionHeader,
|
||||
LoggerLike,
|
||||
Origin,
|
||||
Subprotocol,
|
||||
UpgradeProtocol,
|
||||
)
|
||||
from .uri import WebSocketURI
|
||||
from .utils import accept_key, generate_key
|
||||
|
||||
|
||||
__all__ = ["ClientProtocol"]
|
||||
|
||||
|
||||
class ClientProtocol(Protocol):
|
||||
"""
|
||||
Sans-I/O implementation of a WebSocket client connection.
|
||||
|
||||
Args:
|
||||
uri: URI of the WebSocket server, parsed
|
||||
with :func:`~websockets.uri.parse_uri`.
|
||||
origin: Value of the ``Origin`` header. This is useful when connecting
|
||||
to a server that validates the ``Origin`` header to defend against
|
||||
Cross-Site WebSocket Hijacking attacks.
|
||||
extensions: List of supported extensions, in order in which they
|
||||
should be tried.
|
||||
subprotocols: List of supported subprotocols, in order of decreasing
|
||||
preference.
|
||||
state: Initial state of the WebSocket connection.
|
||||
max_size: Maximum size of incoming messages in bytes.
|
||||
:obj:`None` disables the limit. You may pass a ``(max_message_size,
|
||||
max_fragment_size)`` tuple to set different limits for messages and
|
||||
fragments when you expect long messages sent in short fragments.
|
||||
logger: Logger for this connection;
|
||||
defaults to ``logging.getLogger("websockets.client")``;
|
||||
see the :doc:`logging guide <../../topics/logging>` for details.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
uri: WebSocketURI,
|
||||
*,
|
||||
origin: Origin | None = None,
|
||||
extensions: Sequence[ClientExtensionFactory] | None = None,
|
||||
subprotocols: Sequence[Subprotocol] | None = None,
|
||||
state: State = CONNECTING,
|
||||
max_size: int | None | tuple[int | None, int | None] = 2**20,
|
||||
logger: LoggerLike | None = None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
side=CLIENT,
|
||||
state=state,
|
||||
max_size=max_size,
|
||||
logger=logger,
|
||||
)
|
||||
self.uri = uri
|
||||
self.origin = origin
|
||||
self.available_extensions = extensions
|
||||
self.available_subprotocols = subprotocols
|
||||
self.key = generate_key()
|
||||
|
||||
def connect(self) -> Request:
|
||||
"""
|
||||
Create a handshake request to open a connection.
|
||||
|
||||
You must send the handshake request with :meth:`send_request`.
|
||||
|
||||
You can modify it before sending it, for example to add HTTP headers.
|
||||
|
||||
Returns:
|
||||
WebSocket handshake request event to send to the server.
|
||||
|
||||
"""
|
||||
headers = Headers()
|
||||
headers["Host"] = build_host(self.uri.host, self.uri.port, self.uri.secure)
|
||||
if self.uri.user_info:
|
||||
headers["Authorization"] = build_authorization_basic(*self.uri.user_info)
|
||||
if self.origin is not None:
|
||||
headers["Origin"] = self.origin
|
||||
headers["Upgrade"] = "websocket"
|
||||
headers["Connection"] = "Upgrade"
|
||||
headers["Sec-WebSocket-Key"] = self.key
|
||||
headers["Sec-WebSocket-Version"] = "13"
|
||||
if self.available_extensions is not None:
|
||||
headers["Sec-WebSocket-Extensions"] = build_extension(
|
||||
[
|
||||
(extension_factory.name, extension_factory.get_request_params())
|
||||
for extension_factory in self.available_extensions
|
||||
]
|
||||
)
|
||||
if self.available_subprotocols is not None:
|
||||
headers["Sec-WebSocket-Protocol"] = build_subprotocol(
|
||||
self.available_subprotocols
|
||||
)
|
||||
return Request(self.uri.resource_name, headers)
|
||||
|
||||
def process_response(self, response: Response) -> None:
|
||||
"""
|
||||
Check a handshake response.
|
||||
|
||||
Args:
|
||||
request: WebSocket handshake response received from the server.
|
||||
|
||||
Raises:
|
||||
InvalidHandshake: If the handshake response is invalid.
|
||||
|
||||
"""
|
||||
|
||||
if response.status_code != 101:
|
||||
raise InvalidStatus(response)
|
||||
|
||||
headers = response.headers
|
||||
|
||||
connection: list[ConnectionOption] = sum(
|
||||
[parse_connection(value) for value in headers.get_all("Connection")], []
|
||||
)
|
||||
if not any(value.lower() == "upgrade" for value in connection):
|
||||
raise InvalidUpgrade(
|
||||
"Connection", ", ".join(connection) if connection else None
|
||||
)
|
||||
|
||||
upgrade: list[UpgradeProtocol] = sum(
|
||||
[parse_upgrade(value) for value in headers.get_all("Upgrade")], []
|
||||
)
|
||||
# For compatibility with non-strict implementations, ignore case when
|
||||
# checking the Upgrade header. It's supposed to be 'WebSocket'.
|
||||
if not (len(upgrade) == 1 and upgrade[0].lower() == "websocket"):
|
||||
raise InvalidUpgrade("Upgrade", ", ".join(upgrade) if upgrade else None)
|
||||
|
||||
try:
|
||||
s_w_accept = headers["Sec-WebSocket-Accept"]
|
||||
except KeyError:
|
||||
raise InvalidHeader("Sec-WebSocket-Accept") from None
|
||||
except MultipleValuesError:
|
||||
raise InvalidHeader("Sec-WebSocket-Accept", "multiple values") from None
|
||||
if s_w_accept != accept_key(self.key):
|
||||
raise InvalidHeaderValue("Sec-WebSocket-Accept", s_w_accept)
|
||||
|
||||
self.extensions = self.process_extensions(headers)
|
||||
self.subprotocol = self.process_subprotocol(headers)
|
||||
|
||||
def process_extensions(self, headers: Headers) -> list[Extension]:
|
||||
"""
|
||||
Handle the Sec-WebSocket-Extensions HTTP response header.
|
||||
|
||||
Check that each extension is supported, as well as its parameters.
|
||||
|
||||
:rfc:`6455` leaves the rules up to the specification of each
|
||||
extension.
|
||||
|
||||
To provide this level of flexibility, for each extension accepted by
|
||||
the server, we check for a match with each extension available in the
|
||||
client configuration. If no match is found, an exception is raised.
|
||||
|
||||
If several variants of the same extension are accepted by the server,
|
||||
it may be configured several times, which won't make sense in general.
|
||||
Extensions must implement their own requirements. For this purpose,
|
||||
the list of previously accepted extensions is provided.
|
||||
|
||||
Other requirements, for example related to mandatory extensions or the
|
||||
order of extensions, may be implemented by overriding this method.
|
||||
|
||||
Args:
|
||||
headers: WebSocket handshake response headers.
|
||||
|
||||
Returns:
|
||||
List of accepted extensions.
|
||||
|
||||
Raises:
|
||||
InvalidHandshake: To abort the handshake.
|
||||
|
||||
"""
|
||||
accepted_extensions: list[Extension] = []
|
||||
|
||||
extensions = headers.get_all("Sec-WebSocket-Extensions")
|
||||
|
||||
if extensions:
|
||||
if self.available_extensions is None:
|
||||
raise NegotiationError("no extensions supported")
|
||||
|
||||
parsed_extensions: list[ExtensionHeader] = sum(
|
||||
[parse_extension(header_value) for header_value in extensions], []
|
||||
)
|
||||
|
||||
for name, response_params in parsed_extensions:
|
||||
for extension_factory in self.available_extensions:
|
||||
# Skip non-matching extensions based on their name.
|
||||
if extension_factory.name != name:
|
||||
continue
|
||||
|
||||
# Skip non-matching extensions based on their params.
|
||||
try:
|
||||
extension = extension_factory.process_response_params(
|
||||
response_params, accepted_extensions
|
||||
)
|
||||
except NegotiationError:
|
||||
continue
|
||||
|
||||
# Add matching extension to the final list.
|
||||
accepted_extensions.append(extension)
|
||||
|
||||
# Break out of the loop once we have a match.
|
||||
break
|
||||
|
||||
# If we didn't break from the loop, no extension in our list
|
||||
# matched what the server sent. Fail the connection.
|
||||
else:
|
||||
raise NegotiationError(
|
||||
f"Unsupported extension: "
|
||||
f"name = {name}, params = {response_params}"
|
||||
)
|
||||
|
||||
return accepted_extensions
|
||||
|
||||
def process_subprotocol(self, headers: Headers) -> Subprotocol | None:
|
||||
"""
|
||||
Handle the Sec-WebSocket-Protocol HTTP response header.
|
||||
|
||||
If provided, check that it contains exactly one supported subprotocol.
|
||||
|
||||
Args:
|
||||
headers: WebSocket handshake response headers.
|
||||
|
||||
Returns:
|
||||
Subprotocol, if one was selected.
|
||||
|
||||
"""
|
||||
subprotocol: Subprotocol | None = None
|
||||
|
||||
subprotocols = headers.get_all("Sec-WebSocket-Protocol")
|
||||
|
||||
if subprotocols:
|
||||
if self.available_subprotocols is None:
|
||||
raise NegotiationError("no subprotocols supported")
|
||||
|
||||
parsed_subprotocols: Sequence[Subprotocol] = sum(
|
||||
[parse_subprotocol(header_value) for header_value in subprotocols], []
|
||||
)
|
||||
if len(parsed_subprotocols) > 1:
|
||||
raise InvalidHeader(
|
||||
"Sec-WebSocket-Protocol",
|
||||
f"multiple values: {', '.join(parsed_subprotocols)}",
|
||||
)
|
||||
|
||||
subprotocol = parsed_subprotocols[0]
|
||||
if subprotocol not in self.available_subprotocols:
|
||||
raise NegotiationError(f"unsupported subprotocol: {subprotocol}")
|
||||
|
||||
return subprotocol
|
||||
|
||||
def send_request(self, request: Request) -> None:
|
||||
"""
|
||||
Send a handshake request to the server.
|
||||
|
||||
Args:
|
||||
request: WebSocket handshake request event.
|
||||
|
||||
"""
|
||||
if self.debug:
|
||||
self.logger.debug("> GET %s HTTP/1.1", request.path)
|
||||
for key, value in request.headers.raw_items():
|
||||
self.logger.debug("> %s: %s", key, value)
|
||||
|
||||
self.writes.append(request.serialize())
|
||||
|
||||
def parse(self) -> Generator[None]:
|
||||
if self.state is CONNECTING:
|
||||
try:
|
||||
response = yield from Response.parse(
|
||||
self.reader.read_line,
|
||||
self.reader.read_exact,
|
||||
self.reader.read_to_eof,
|
||||
)
|
||||
except Exception as exc:
|
||||
self.handshake_exc = InvalidMessage(
|
||||
"did not receive a valid HTTP response"
|
||||
)
|
||||
self.handshake_exc.__cause__ = exc
|
||||
self.send_eof()
|
||||
self.parser = self.discard()
|
||||
next(self.parser) # start coroutine
|
||||
yield
|
||||
|
||||
if self.debug:
|
||||
code, phrase = response.status_code, response.reason_phrase
|
||||
self.logger.debug("< HTTP/1.1 %d %s", code, phrase)
|
||||
for key, value in response.headers.raw_items():
|
||||
self.logger.debug("< %s: %s", key, value)
|
||||
if response.body:
|
||||
self.logger.debug("< [body] (%d bytes)", len(response.body))
|
||||
|
||||
try:
|
||||
self.process_response(response)
|
||||
except InvalidHandshake as exc:
|
||||
response._exception = exc
|
||||
self.events.append(response)
|
||||
self.handshake_exc = exc
|
||||
self.send_eof()
|
||||
self.parser = self.discard()
|
||||
next(self.parser) # start coroutine
|
||||
yield
|
||||
|
||||
assert self.state is CONNECTING
|
||||
self.state = OPEN
|
||||
self.events.append(response)
|
||||
|
||||
yield from super().parse()
|
||||
|
||||
|
||||
class ClientConnection(ClientProtocol):
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
warnings.warn( # deprecated in 11.0 - 2023-04-02
|
||||
"ClientConnection was renamed to ClientProtocol",
|
||||
DeprecationWarning,
|
||||
)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
BACKOFF_INITIAL_DELAY = float(os.environ.get("WEBSOCKETS_BACKOFF_INITIAL_DELAY", "5"))
|
||||
BACKOFF_MIN_DELAY = float(os.environ.get("WEBSOCKETS_BACKOFF_MIN_DELAY", "3.1"))
|
||||
BACKOFF_MAX_DELAY = float(os.environ.get("WEBSOCKETS_BACKOFF_MAX_DELAY", "90.0"))
|
||||
BACKOFF_FACTOR = float(os.environ.get("WEBSOCKETS_BACKOFF_FACTOR", "1.618"))
|
||||
|
||||
|
||||
def backoff(
|
||||
initial_delay: float = BACKOFF_INITIAL_DELAY,
|
||||
min_delay: float = BACKOFF_MIN_DELAY,
|
||||
max_delay: float = BACKOFF_MAX_DELAY,
|
||||
factor: float = BACKOFF_FACTOR,
|
||||
) -> Generator[float]:
|
||||
"""
|
||||
Generate a series of backoff delays between reconnection attempts.
|
||||
|
||||
Yields:
|
||||
How many seconds to wait before retrying to connect.
|
||||
|
||||
"""
|
||||
# Add a random initial delay between 0 and 5 seconds.
|
||||
# See 7.2.3. Recovering from Abnormal Closure in RFC 6455.
|
||||
yield random.random() * initial_delay
|
||||
delay = min_delay
|
||||
while delay < max_delay:
|
||||
yield delay
|
||||
delay *= factor
|
||||
while True:
|
||||
yield max_delay
|
||||
|
||||
|
||||
lazy_import(
|
||||
globals(),
|
||||
deprecated_aliases={
|
||||
# deprecated in 14.0 - 2024-11-09
|
||||
"WebSocketClientProtocol": ".legacy.client",
|
||||
"connect": ".legacy.client",
|
||||
"unix_connect": ".legacy.client",
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user