settings.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. # -*- coding: utf-8 -*-
  2. """
  3. h2/settings
  4. ~~~~~~~~~~~
  5. This module contains a HTTP/2 settings object. This object provides a simple
  6. API for manipulating HTTP/2 settings, keeping track of both the current active
  7. state of the settings and the unacknowledged future values of the settings.
  8. """
  9. import collections
  10. import enum
  11. from hyperframe.frame import SettingsFrame
  12. from h2.errors import ErrorCodes
  13. from h2.exceptions import InvalidSettingsValueError
  14. class SettingCodes(enum.IntEnum):
  15. """
  16. All known HTTP/2 setting codes.
  17. .. versionadded:: 2.6.0
  18. """
  19. #: Allows the sender to inform the remote endpoint of the maximum size of
  20. #: the header compression table used to decode header blocks, in octets.
  21. HEADER_TABLE_SIZE = SettingsFrame.HEADER_TABLE_SIZE
  22. #: This setting can be used to disable server push. To disable server push
  23. #: on a client, set this to 0.
  24. ENABLE_PUSH = SettingsFrame.ENABLE_PUSH
  25. #: Indicates the maximum number of concurrent streams that the sender will
  26. #: allow.
  27. MAX_CONCURRENT_STREAMS = SettingsFrame.MAX_CONCURRENT_STREAMS
  28. #: Indicates the sender's initial window size (in octets) for stream-level
  29. #: flow control.
  30. INITIAL_WINDOW_SIZE = SettingsFrame.INITIAL_WINDOW_SIZE
  31. try: # Platform-specific: Hyperframe < 4.0.0
  32. _max_frame_size = SettingsFrame.SETTINGS_MAX_FRAME_SIZE
  33. except AttributeError: # Platform-specific: Hyperframe >= 4.0.0
  34. _max_frame_size = SettingsFrame.MAX_FRAME_SIZE
  35. #: Indicates the size of the largest frame payload that the sender is
  36. #: willing to receive, in octets.
  37. MAX_FRAME_SIZE = _max_frame_size
  38. try: # Platform-specific: Hyperframe < 4.0.0
  39. _max_header_list_size = SettingsFrame.SETTINGS_MAX_HEADER_LIST_SIZE
  40. except AttributeError: # Platform-specific: Hyperframe >= 4.0.0
  41. _max_header_list_size = SettingsFrame.MAX_HEADER_LIST_SIZE
  42. #: This advisory setting informs a peer of the maximum size of header list
  43. #: that the sender is prepared to accept, in octets. The value is based on
  44. #: the uncompressed size of header fields, including the length of the name
  45. #: and value in octets plus an overhead of 32 octets for each header field.
  46. MAX_HEADER_LIST_SIZE = _max_header_list_size
  47. def _setting_code_from_int(code):
  48. """
  49. Given an integer setting code, returns either one of :class:`SettingCodes
  50. <h2.settings.SettingCodes>` or, if not present in the known set of codes,
  51. returns the integer directly.
  52. """
  53. try:
  54. return SettingCodes(code)
  55. except ValueError:
  56. return code
  57. # Aliases for all the settings values.
  58. #: Allows the sender to inform the remote endpoint of the maximum size of the
  59. #: header compression table used to decode header blocks, in octets.
  60. #:
  61. #: .. deprecated:: 2.6.0
  62. #: Deprecated in favour of :data:`SettingCodes.HEADER_TABLE_SIZE
  63. #: <h2.settings.SettingCodes.HEADER_TABLE_SIZE>`.
  64. HEADER_TABLE_SIZE = SettingCodes.HEADER_TABLE_SIZE
  65. #: This setting can be used to disable server push. To disable server push on
  66. #: a client, set this to 0.
  67. #:
  68. #: .. deprecated:: 2.6.0
  69. #: Deprecated in favour of :data:`SettingCodes.ENABLE_PUSH
  70. #: <h2.settings.SettingCodes.ENABLE_PUSH>`.
  71. ENABLE_PUSH = SettingCodes.ENABLE_PUSH
  72. #: Indicates the maximum number of concurrent streams that the sender will
  73. #: allow.
  74. #:
  75. #: .. deprecated:: 2.6.0
  76. #: Deprecated in favour of :data:`SettingCodes.MAX_CONCURRENT_STREAMS
  77. #: <h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS>`.
  78. MAX_CONCURRENT_STREAMS = SettingCodes.MAX_CONCURRENT_STREAMS
  79. #: Indicates the sender's initial window size (in octets) for stream-level flow
  80. #: control.
  81. #:
  82. #: .. deprecated:: 2.6.0
  83. #: Deprecated in favour of :data:`SettingCodes.INITIAL_WINDOW_SIZE
  84. #: <h2.settings.SettingCodes.INITIAL_WINDOW_SIZE>`.
  85. INITIAL_WINDOW_SIZE = SettingCodes.INITIAL_WINDOW_SIZE
  86. #: Indicates the size of the largest frame payload that the sender is willing
  87. #: to receive, in octets.
  88. #:
  89. #: .. deprecated:: 2.6.0
  90. #: Deprecated in favour of :data:`SettingCodes.MAX_FRAME_SIZE
  91. #: <h2.settings.SettingCodes.MAX_FRAME_SIZE>`.
  92. MAX_FRAME_SIZE = SettingCodes.MAX_FRAME_SIZE
  93. #: This advisory setting informs a peer of the maximum size of header list that
  94. #: the sender is prepared to accept, in octets. The value is based on the
  95. #: uncompressed size of header fields, including the length of the name and
  96. #: value in octets plus an overhead of 32 octets for each header field.
  97. #:
  98. #: .. deprecated:: 2.6.0
  99. #: Deprecated in favour of :data:`SettingCodes.MAX_HEADER_LIST_SIZE
  100. #: <h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE>`.
  101. MAX_HEADER_LIST_SIZE = SettingCodes.MAX_HEADER_LIST_SIZE
  102. class ChangedSetting:
  103. def __init__(self, setting, original_value, new_value):
  104. #: The setting code given. Either one of :class:`SettingCodes
  105. #: <h2.settings.SettingCodes>` or ``int``
  106. #:
  107. #: .. versionchanged:: 2.6.0
  108. self.setting = setting
  109. #: The original value before being changed.
  110. self.original_value = original_value
  111. #: The new value after being changed.
  112. self.new_value = new_value
  113. def __repr__(self):
  114. return (
  115. "ChangedSetting(setting=%s, original_value=%s, "
  116. "new_value=%s)"
  117. ) % (
  118. self.setting,
  119. self.original_value,
  120. self.new_value
  121. )
  122. class Settings(collections.MutableMapping):
  123. """
  124. An object that encapsulates HTTP/2 settings state.
  125. HTTP/2 Settings are a complex beast. Each party, remote and local, has its
  126. own settings and a view of the other party's settings. When a settings
  127. frame is emitted by a peer it cannot assume that the new settings values
  128. are in place until the remote peer acknowledges the setting. In principle,
  129. multiple settings changes can be "in flight" at the same time, all with
  130. different values.
  131. This object encapsulates this mess. It provides a dict-like interface to
  132. settings, which return the *current* values of the settings in question.
  133. Additionally, it keeps track of the stack of proposed values: each time an
  134. acknowledgement is sent/received, it updates the current values with the
  135. stack of proposed values. On top of all that, it validates the values to
  136. make sure they're allowed, and raises :class:`InvalidSettingsValueError
  137. <h2.exceptions.InvalidSettingsValueError>` if they are not.
  138. Finally, this object understands what the default values of the HTTP/2
  139. settings are, and sets those defaults appropriately.
  140. .. versionchanged:: 2.2.0
  141. Added the ``initial_values`` parameter.
  142. .. versionchanged:: 2.5.0
  143. Added the ``max_header_list_size`` property.
  144. :param client: (optional) Whether these settings should be defaulted for a
  145. client implementation or a server implementation. Defaults to ``True``.
  146. :type client: ``bool``
  147. :param initial_values: (optional) Any initial values the user would like
  148. set, rather than RFC 7540's defaults.
  149. :type initial_vales: ``MutableMapping``
  150. """
  151. def __init__(self, client=True, initial_values=None):
  152. # Backing object for the settings. This is a dictionary of
  153. # (setting: [list of values]), where the first value in the list is the
  154. # current value of the setting. Strictly this doesn't use lists but
  155. # instead uses collections.deque to avoid repeated memory allocations.
  156. #
  157. # This contains the default values for HTTP/2.
  158. self._settings = {
  159. SettingCodes.HEADER_TABLE_SIZE: collections.deque([4096]),
  160. SettingCodes.ENABLE_PUSH: collections.deque([int(client)]),
  161. SettingCodes.INITIAL_WINDOW_SIZE: collections.deque([65535]),
  162. SettingCodes.MAX_FRAME_SIZE: collections.deque([16384]),
  163. }
  164. if initial_values is not None:
  165. for key, value in initial_values.items():
  166. invalid = _validate_setting(key, value)
  167. if invalid:
  168. raise InvalidSettingsValueError(
  169. "Setting %d has invalid value %d" % (key, value),
  170. error_code=invalid
  171. )
  172. self._settings[key] = collections.deque([value])
  173. def acknowledge(self):
  174. """
  175. The settings have been acknowledged, either by the user (remote
  176. settings) or by the remote peer (local settings).
  177. :returns: A dict of {setting: ChangedSetting} that were applied.
  178. """
  179. changed_settings = {}
  180. # If there is more than one setting in the list, we have a setting
  181. # value outstanding. Update them.
  182. for k, v in self._settings.items():
  183. if len(v) > 1:
  184. old_setting = v.popleft()
  185. new_setting = v[0]
  186. changed_settings[k] = ChangedSetting(
  187. k, old_setting, new_setting
  188. )
  189. return changed_settings
  190. # Provide easy-access to well known settings.
  191. @property
  192. def header_table_size(self):
  193. """
  194. The current value of the :data:`HEADER_TABLE_SIZE
  195. <h2.settings.SettingCodes.HEADER_TABLE_SIZE>` setting.
  196. """
  197. return self[SettingCodes.HEADER_TABLE_SIZE]
  198. @header_table_size.setter
  199. def header_table_size(self, value):
  200. self[SettingCodes.HEADER_TABLE_SIZE] = value
  201. @property
  202. def enable_push(self):
  203. """
  204. The current value of the :data:`ENABLE_PUSH
  205. <h2.settings.SettingCodes.ENABLE_PUSH>` setting.
  206. """
  207. return self[SettingCodes.ENABLE_PUSH]
  208. @enable_push.setter
  209. def enable_push(self, value):
  210. self[SettingCodes.ENABLE_PUSH] = value
  211. @property
  212. def initial_window_size(self):
  213. """
  214. The current value of the :data:`INITIAL_WINDOW_SIZE
  215. <h2.settings.SettingCodes.INITIAL_WINDOW_SIZE>` setting.
  216. """
  217. return self[SettingCodes.INITIAL_WINDOW_SIZE]
  218. @initial_window_size.setter
  219. def initial_window_size(self, value):
  220. self[SettingCodes.INITIAL_WINDOW_SIZE] = value
  221. @property
  222. def max_frame_size(self):
  223. """
  224. The current value of the :data:`MAX_FRAME_SIZE
  225. <h2.settings.SettingCodes.MAX_FRAME_SIZE>` setting.
  226. """
  227. return self[SettingCodes.MAX_FRAME_SIZE]
  228. @max_frame_size.setter
  229. def max_frame_size(self, value):
  230. self[SettingCodes.MAX_FRAME_SIZE] = value
  231. @property
  232. def max_concurrent_streams(self):
  233. """
  234. The current value of the :data:`MAX_CONCURRENT_STREAMS
  235. <h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS>` setting.
  236. """
  237. return self.get(SettingCodes.MAX_CONCURRENT_STREAMS, 2**32+1)
  238. @max_concurrent_streams.setter
  239. def max_concurrent_streams(self, value):
  240. self[SettingCodes.MAX_CONCURRENT_STREAMS] = value
  241. @property
  242. def max_header_list_size(self):
  243. """
  244. The current value of the :data:`MAX_HEADER_LIST_SIZE
  245. <h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE>` setting. If not set,
  246. returns ``None``, which means unlimited.
  247. .. versionadded:: 2.5.0
  248. """
  249. return self.get(SettingCodes.MAX_HEADER_LIST_SIZE, None)
  250. @max_header_list_size.setter
  251. def max_header_list_size(self, value):
  252. self[SettingCodes.MAX_HEADER_LIST_SIZE] = value
  253. # Implement the MutableMapping API.
  254. def __getitem__(self, key):
  255. val = self._settings[key][0]
  256. # Things that were created when a setting was received should stay
  257. # KeyError'd.
  258. if val is None:
  259. raise KeyError
  260. return val
  261. def __setitem__(self, key, value):
  262. invalid = _validate_setting(key, value)
  263. if invalid:
  264. raise InvalidSettingsValueError(
  265. "Setting %d has invalid value %d" % (key, value),
  266. error_code=invalid
  267. )
  268. try:
  269. items = self._settings[key]
  270. except KeyError:
  271. items = collections.deque([None])
  272. self._settings[key] = items
  273. items.append(value)
  274. def __delitem__(self, key):
  275. del self._settings[key]
  276. def __iter__(self):
  277. return self._settings.__iter__()
  278. def __len__(self):
  279. return len(self._settings)
  280. def __eq__(self, other):
  281. if isinstance(other, Settings):
  282. return self._settings == other._settings
  283. else:
  284. return NotImplemented
  285. def __ne__(self, other):
  286. if isinstance(other, Settings):
  287. return not self == other
  288. else:
  289. return NotImplemented
  290. def _validate_setting(setting, value):
  291. """
  292. Confirms that a specific setting has a well-formed value. If the setting is
  293. invalid, returns an error code. Otherwise, returns 0 (NO_ERROR).
  294. """
  295. if setting == SettingCodes.ENABLE_PUSH:
  296. if value not in (0, 1):
  297. return ErrorCodes.PROTOCOL_ERROR
  298. elif setting == SettingCodes.INITIAL_WINDOW_SIZE:
  299. if not 0 <= value <= 2147483647: # 2^31 - 1
  300. return ErrorCodes.FLOW_CONTROL_ERROR
  301. elif setting == SettingCodes.MAX_FRAME_SIZE:
  302. if not 16384 <= value <= 16777215: # 2^14 and 2^24 - 1
  303. return ErrorCodes.PROTOCOL_ERROR
  304. elif setting == SettingCodes.MAX_HEADER_LIST_SIZE:
  305. if value < 0:
  306. return ErrorCodes.PROTOCOL_ERROR
  307. return 0