Files
syn-chat-bot/.venv/lib/python3.9/site-packages/urllib3_future/contrib/webextensions/protocol.py
Hyungi Ahn c2257d3a86 fix: 포트 충돌 회피 — note_bridge 8098, intent_service 8099
Jellyfin(8096), OrbStack(8097) 포트 충돌으로 변경.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 13:53:55 +09:00

190 lines
6.9 KiB
Python

from __future__ import annotations
import typing
from abc import ABCMeta
from contextlib import contextmanager
from socket import timeout as SocketTimeout
if typing.TYPE_CHECKING:
from ...backend import HttpVersion
from ...backend._base import DirectStreamAccess
from ...response import HTTPResponse
from ...util.traffic_police import TrafficPolice
from ...exceptions import (
BaseSSLError,
ProtocolError,
ReadTimeoutError,
SSLError,
MustRedialError,
)
class ExtensionFromHTTP(metaclass=ABCMeta):
"""Represent an extension that can be negotiated just after a "101 Switching Protocol" HTTP response.
This will considerably ease downstream integration."""
def __init__(self) -> None:
self._dsa: DirectStreamAccess | None = None
self._response: HTTPResponse | None = None
self._police_officer: TrafficPolice | None = None # type: ignore[type-arg]
@contextmanager
def _read_error_catcher(self) -> typing.Generator[None, None, None]:
"""
Catch low-level python exceptions, instead re-raising urllib3
variants, so that low-level exceptions are not leaked in the
high-level api.
On unrecoverable issues, release the connection back to the pool.
"""
clean_exit = False
try:
try:
yield
except SocketTimeout as e:
clean_exit = True
pool = (
self._response._pool
if self._response and hasattr(self._response, "_pool")
else None
)
raise ReadTimeoutError(pool, None, "Read timed out.") from e # type: ignore[arg-type]
except BaseSSLError as e:
# FIXME: Is there a better way to differentiate between SSLErrors?
if "read operation timed out" not in str(e):
# SSL errors related to framing/MAC get wrapped and reraised here
raise SSLError(e) from e
clean_exit = True # ws algorithms based on timeouts can expect this without being harmful!
pool = (
self._response._pool
if self._response and hasattr(self._response, "_pool")
else None
)
raise ReadTimeoutError(pool, None, "Read timed out.") from e # type: ignore[arg-type]
except (OSError, MustRedialError) as e:
# This includes IncompleteRead.
raise ProtocolError(f"Connection broken: {e!r}", e) from e
# If no exception is thrown, we should avoid cleaning up
# unnecessarily.
clean_exit = True
finally:
# If we didn't terminate cleanly, we need to throw away our
# connection.
if not clean_exit:
# The response may not be closed but we're not going to use it
# anymore so close it now to ensure that the connection is
# released back to the pool.
if self._response:
self.close()
@contextmanager
def _write_error_catcher(self) -> typing.Generator[None, None, None]:
"""
Catch low-level python exceptions, instead re-raising urllib3
variants, so that low-level exceptions are not leaked in the
high-level api.
On unrecoverable issues, release the connection back to the pool.
"""
clean_exit = False
try:
try:
yield
except SocketTimeout as e:
pool = (
self._response._pool
if self._response and hasattr(self._response, "_pool")
else None
)
raise ReadTimeoutError(pool, None, "Read timed out.") from e # type: ignore[arg-type]
except BaseSSLError as e:
raise SSLError(e) from e
except OSError as e:
# This includes IncompleteRead.
raise ProtocolError(f"Connection broken: {e!r}", e) from e
# If no exception is thrown, we should avoid cleaning up
# unnecessarily.
clean_exit = True
finally:
# If we didn't terminate cleanly, we need to throw away our
# connection.
if not clean_exit:
# The response may not be closed but we're not going to use it
# anymore so close it now to ensure that the connection is
# released back to the pool.
if self._response:
self.close()
@property
def urlopen_kwargs(self) -> dict[str, typing.Any]:
"""Return prerequisites. Must be passed as additional parameters to urlopen."""
return {}
def start(self, response: HTTPResponse) -> None:
"""The HTTP server gave us the go-to start negotiating another protocol."""
if response._fp is None or not hasattr(response._fp, "_dsa"):
raise RuntimeError(
"Attempt to start an HTTP extension without direct I/O access to the stream"
)
self._dsa = response._fp._dsa
self._police_officer = response._police_officer
self._response = response
@property
def closed(self) -> bool:
return self._dsa is None
@staticmethod
def supported_svn() -> set[HttpVersion]:
"""Hint about supported parent SVN for this extension."""
raise NotImplementedError
@staticmethod
def implementation() -> str:
raise NotImplementedError
@staticmethod
def supported_schemes() -> set[str]:
"""Recognized schemes for the extension."""
raise NotImplementedError
@staticmethod
def scheme_to_http_scheme(scheme: str) -> str:
"""Convert the extension scheme to a known http scheme (either http or https)"""
raise NotImplementedError
def headers(self, http_version: HttpVersion) -> dict[str, str]:
"""Specific HTTP headers required (request) before the 101 status response."""
raise NotImplementedError
def close(self) -> None:
"""End/Notify close for sub protocol."""
raise NotImplementedError
def next_payload(self) -> str | bytes | None:
"""Unpack the next received message/payload from remote. This call does read from the socket.
If the method return None, it means that the remote closed the (extension) pipeline.
"""
raise NotImplementedError
def send_payload(self, buf: str | bytes) -> None:
"""Dispatch a buffer to remote."""
raise NotImplementedError
def on_payload(self, callback: typing.Callable[[str | bytes | None], None]) -> None:
"""Set up a callback that will be invoked automatically once a payload is received.
Meaning that you stop calling manually next_payload()."""
raise NotImplementedError