Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
settings.py332 linesDownload Raw Back to h2
1"""2h2/settings3~~~~~~~~~~~4 5This module contains a HTTP/2 settings object. This object provides a simple6API for manipulating HTTP/2 settings, keeping track of both the current active7state of the settings and the unacknowledged future values of the settings.8"""9from __future__ import annotations10 11import collections12import enum13from collections.abc import Iterator, MutableMapping14from typing import Union15 16from hyperframe.frame import SettingsFrame17 18from .errors import ErrorCodes19from .exceptions import InvalidSettingsValueError20 21 22class SettingCodes(enum.IntEnum):23    """24    All known HTTP/2 setting codes.25 26    .. versionadded:: 2.6.027    """28 29    #: Allows the sender to inform the remote endpoint of the maximum size of30    #: the header compression table used to decode header blocks, in octets.31    HEADER_TABLE_SIZE = SettingsFrame.HEADER_TABLE_SIZE32 33    #: This setting can be used to disable server push. To disable server push34    #: on a client, set this to 0.35    ENABLE_PUSH = SettingsFrame.ENABLE_PUSH36 37    #: Indicates the maximum number of concurrent streams that the sender will38    #: allow.39    MAX_CONCURRENT_STREAMS = SettingsFrame.MAX_CONCURRENT_STREAMS40 41    #: Indicates the sender's initial window size (in octets) for stream-level42    #: flow control.43    INITIAL_WINDOW_SIZE = SettingsFrame.INITIAL_WINDOW_SIZE44 45    #: Indicates the size of the largest frame payload that the sender is46    #: willing to receive, in octets.47    MAX_FRAME_SIZE = SettingsFrame.MAX_FRAME_SIZE48 49    #: This advisory setting informs a peer of the maximum size of header list50    #: that the sender is prepared to accept, in octets.  The value is based on51    #: the uncompressed size of header fields, including the length of the name52    #: and value in octets plus an overhead of 32 octets for each header field.53    MAX_HEADER_LIST_SIZE = SettingsFrame.MAX_HEADER_LIST_SIZE54 55    #: This setting can be used to enable the connect protocol. To enable on a56    #: client set this to 1.57    ENABLE_CONNECT_PROTOCOL = SettingsFrame.ENABLE_CONNECT_PROTOCOL58 59 60def _setting_code_from_int(code: int) -> SettingCodes | int:61    """62    Given an integer setting code, returns either one of :class:`SettingCodes63    <h2.settings.SettingCodes>` or, if not present in the known set of codes,64    returns the integer directly.65    """66    try:67        return SettingCodes(code)68    except ValueError:69        return code70 71 72class ChangedSetting:73 74    def __init__(self, setting: SettingCodes | int, original_value: int | None, new_value: int) -> None:75        #: The setting code given. Either one of :class:`SettingCodes76        #: <h2.settings.SettingCodes>` or ``int``77        #:78        #: .. versionchanged:: 2.6.079        self.setting = setting80 81        #: The original value before being changed.82        self.original_value = original_value83 84        #: The new value after being changed.85        self.new_value = new_value86 87    def __repr__(self) -> str:88        return (89            f"ChangedSetting(setting={self.setting!s}, original_value={self.original_value}, new_value={self.new_value})"90        )91 92 93class Settings(MutableMapping[Union[SettingCodes, int], int]):94    """95    An object that encapsulates HTTP/2 settings state.96 97    HTTP/2 Settings are a complex beast. Each party, remote and local, has its98    own settings and a view of the other party's settings. When a settings99    frame is emitted by a peer it cannot assume that the new settings values100    are in place until the remote peer acknowledges the setting. In principle,101    multiple settings changes can be "in flight" at the same time, all with102    different values.103 104    This object encapsulates this mess. It provides a dict-like interface to105    settings, which return the *current* values of the settings in question.106    Additionally, it keeps track of the stack of proposed values: each time an107    acknowledgement is sent/received, it updates the current values with the108    stack of proposed values. On top of all that, it validates the values to109    make sure they're allowed, and raises :class:`InvalidSettingsValueError110    <h2.exceptions.InvalidSettingsValueError>` if they are not.111 112    Finally, this object understands what the default values of the HTTP/2113    settings are, and sets those defaults appropriately.114 115    .. versionchanged:: 2.2.0116       Added the ``initial_values`` parameter.117 118    .. versionchanged:: 2.5.0119       Added the ``max_header_list_size`` property.120 121    :param client: (optional) Whether these settings should be defaulted for a122        client implementation or a server implementation. Defaults to ``True``.123    :type client: ``bool``124    :param initial_values: (optional) Any initial values the user would like125        set, rather than RFC 7540's defaults.126    :type initial_vales: ``MutableMapping``127    """128 129    def __init__(self, client: bool = True, initial_values: dict[SettingCodes, int] | None = None) -> None:130        # Backing object for the settings. This is a dictionary of131        # (setting: [list of values]), where the first value in the list is the132        # current value of the setting. Strictly this doesn't use lists but133        # instead uses collections.deque to avoid repeated memory allocations.134        #135        # This contains the default values for HTTP/2.136        self._settings: dict[SettingCodes | int, collections.deque[int]] = {137            SettingCodes.HEADER_TABLE_SIZE: collections.deque([4096]),138            SettingCodes.ENABLE_PUSH: collections.deque([int(client)]),139            SettingCodes.INITIAL_WINDOW_SIZE: collections.deque([65535]),140            SettingCodes.MAX_FRAME_SIZE: collections.deque([16384]),141            SettingCodes.ENABLE_CONNECT_PROTOCOL: collections.deque([0]),142        }143        if initial_values is not None:144            for key, value in initial_values.items():145                invalid = _validate_setting(key, value)146                if invalid:147                    msg = f"Setting {key} has invalid value {value}"148                    raise InvalidSettingsValueError(149                        msg,150                        error_code=invalid,151                    )152                self._settings[key] = collections.deque([value])153 154    def acknowledge(self) -> dict[SettingCodes | int, ChangedSetting]:155        """156        The settings have been acknowledged, either by the user (remote157        settings) or by the remote peer (local settings).158 159        :returns: A dict of {setting: ChangedSetting} that were applied.160        """161        changed_settings: dict[SettingCodes | int, ChangedSetting] = {}162 163        # If there is more than one setting in the list, we have a setting164        # value outstanding. Update them.165        for k, v in self._settings.items():166            if len(v) > 1:167                old_setting = v.popleft()168                new_setting = v[0]169                changed_settings[k] = ChangedSetting(170                    k, old_setting, new_setting,171                )172 173        return changed_settings174 175    # Provide easy-access to well known settings.176    @property177    def header_table_size(self) -> int:178        """179        The current value of the :data:`HEADER_TABLE_SIZE180        <h2.settings.SettingCodes.HEADER_TABLE_SIZE>` setting.181        """182        return self[SettingCodes.HEADER_TABLE_SIZE]183 184    @header_table_size.setter185    def header_table_size(self, value: int) -> None:186        self[SettingCodes.HEADER_TABLE_SIZE] = value187 188    @property189    def enable_push(self) -> int:190        """191        The current value of the :data:`ENABLE_PUSH192        <h2.settings.SettingCodes.ENABLE_PUSH>` setting.193        """194        return self[SettingCodes.ENABLE_PUSH]195 196    @enable_push.setter197    def enable_push(self, value: int) -> None:198        self[SettingCodes.ENABLE_PUSH] = value199 200    @property201    def initial_window_size(self) -> int:202        """203        The current value of the :data:`INITIAL_WINDOW_SIZE204        <h2.settings.SettingCodes.INITIAL_WINDOW_SIZE>` setting.205        """206        return self[SettingCodes.INITIAL_WINDOW_SIZE]207 208    @initial_window_size.setter209    def initial_window_size(self, value: int) -> None:210        self[SettingCodes.INITIAL_WINDOW_SIZE] = value211 212    @property213    def max_frame_size(self) -> int:214        """215        The current value of the :data:`MAX_FRAME_SIZE216        <h2.settings.SettingCodes.MAX_FRAME_SIZE>` setting.217        """218        return self[SettingCodes.MAX_FRAME_SIZE]219 220    @max_frame_size.setter221    def max_frame_size(self, value: int) -> None:222        self[SettingCodes.MAX_FRAME_SIZE] = value223 224    @property225    def max_concurrent_streams(self) -> int:226        """227        The current value of the :data:`MAX_CONCURRENT_STREAMS228        <h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS>` setting.229        """230        return self.get(SettingCodes.MAX_CONCURRENT_STREAMS, 2**32+1)231 232    @max_concurrent_streams.setter233    def max_concurrent_streams(self, value: int) -> None:234        self[SettingCodes.MAX_CONCURRENT_STREAMS] = value235 236    @property237    def max_header_list_size(self) -> int | None:238        """239        The current value of the :data:`MAX_HEADER_LIST_SIZE240        <h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE>` setting. If not set,241        returns ``None``, which means unlimited.242 243        .. versionadded:: 2.5.0244        """245        return self.get(SettingCodes.MAX_HEADER_LIST_SIZE, None)246 247    @max_header_list_size.setter248    def max_header_list_size(self, value: int) -> None:249        self[SettingCodes.MAX_HEADER_LIST_SIZE] = value250 251    @property252    def enable_connect_protocol(self) -> int:253        """254        The current value of the :data:`ENABLE_CONNECT_PROTOCOL255        <h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL>` setting.256        """257        return self[SettingCodes.ENABLE_CONNECT_PROTOCOL]258 259    @enable_connect_protocol.setter260    def enable_connect_protocol(self, value: int) -> None:261        self[SettingCodes.ENABLE_CONNECT_PROTOCOL] = value262 263    # Implement the MutableMapping API.264    def __getitem__(self, key: SettingCodes | int) -> int:265        val = self._settings[key][0]266 267        # Things that were created when a setting was received should stay268        # KeyError'd.269        if val is None:270            raise KeyError271 272        return val273 274    def __setitem__(self, key: SettingCodes | int, value: int) -> None:275        invalid = _validate_setting(key, value)276        if invalid:277            msg = f"Setting {key} has invalid value {value}"278            raise InvalidSettingsValueError(279                msg,280                error_code=invalid,281            )282 283        try:284            items = self._settings[key]285        except KeyError:286            items = collections.deque([None])  # type: ignore287            self._settings[key] = items288 289        items.append(value)290 291    def __delitem__(self, key: SettingCodes | int) -> None:292        del self._settings[key]293 294    def __iter__(self) -> Iterator[SettingCodes | int]:295        return self._settings.__iter__()296 297    def __len__(self) -> int:298        return len(self._settings)299 300    def __eq__(self, other: object) -> bool:301        if isinstance(other, Settings):302            return self._settings == other._settings303        return NotImplemented304 305    def __ne__(self, other: object) -> bool:306        if isinstance(other, Settings):307            return not self == other308        return NotImplemented309 310 311def _validate_setting(setting: SettingCodes | int, value: int) -> ErrorCodes:312    """313    Confirms that a specific setting has a well-formed value. If the setting is314    invalid, returns an error code. Otherwise, returns 0 (NO_ERROR).315    """316    if setting == SettingCodes.ENABLE_PUSH:317        if value not in (0, 1):318            return ErrorCodes.PROTOCOL_ERROR319    elif setting == SettingCodes.INITIAL_WINDOW_SIZE:320        if not 0 <= value <= 2147483647:  # 2^31 - 1321            return ErrorCodes.FLOW_CONTROL_ERROR322    elif setting == SettingCodes.MAX_FRAME_SIZE:323        if not 16384 <= value <= 16777215:  # 2^14 and 2^24 - 1324            return ErrorCodes.PROTOCOL_ERROR325    elif setting == SettingCodes.MAX_HEADER_LIST_SIZE:326        if value < 0:327            return ErrorCodes.PROTOCOL_ERROR328    elif setting == SettingCodes.ENABLE_CONNECT_PROTOCOL and value not in (0, 1):329        return ErrorCodes.PROTOCOL_ERROR330 331    return ErrorCodes.NO_ERROR332 
codekingpro/portable-devtools · Team Ai