config.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. """
  2. h2/config
  3. ~~~~~~~~~
  4. Objects for controlling the configuration of the HTTP/2 stack.
  5. """
  6. from __future__ import annotations
  7. import sys
  8. from typing import Any
  9. class _BooleanConfigOption:
  10. """
  11. Descriptor for handling a boolean config option. This will block
  12. attempts to set boolean config options to non-bools.
  13. """
  14. def __init__(self, name: str) -> None:
  15. self.name = name
  16. self.attr_name = f"_{self.name}"
  17. def __get__(self, instance: Any, owner: Any) -> bool:
  18. return getattr(instance, self.attr_name) # type: ignore
  19. def __set__(self, instance: Any, value: bool) -> None:
  20. if not isinstance(value, bool):
  21. msg = f"{self.name} must be a bool"
  22. raise ValueError(msg) # noqa: TRY004
  23. setattr(instance, self.attr_name, value)
  24. class DummyLogger:
  25. """
  26. A Logger object that does not actual logging, hence a DummyLogger.
  27. For the class the log operation is merely a no-op. The intent is to avoid
  28. conditionals being sprinkled throughout the h2 code for calls to
  29. logging functions when no logger is passed into the corresponding object.
  30. """
  31. def __init__(self, *vargs) -> None: # type: ignore
  32. pass
  33. def debug(self, *vargs, **kwargs) -> None: # type: ignore
  34. """
  35. No-op logging. Only level needed for now.
  36. """
  37. def trace(self, *vargs, **kwargs) -> None: # type: ignore
  38. """
  39. No-op logging. Only level needed for now.
  40. """
  41. class OutputLogger:
  42. """
  43. A Logger object that prints to stderr or any other file-like object.
  44. This class is provided for convenience and not part of the stable API.
  45. :param file: A file-like object passed to the print function.
  46. Defaults to ``sys.stderr``.
  47. :param trace: Enables trace-level output. Defaults to ``False``.
  48. """
  49. def __init__(self, file=None, trace_level=False) -> None: # type: ignore
  50. super().__init__()
  51. self.file = file or sys.stderr
  52. self.trace_level = trace_level
  53. def debug(self, fmtstr, *args) -> None: # type: ignore
  54. print(f"h2 (debug): {fmtstr % args}", file=self.file)
  55. def trace(self, fmtstr, *args) -> None: # type: ignore
  56. if self.trace_level:
  57. print(f"h2 (trace): {fmtstr % args}", file=self.file)
  58. class H2Configuration:
  59. """
  60. An object that controls the way a single HTTP/2 connection behaves.
  61. This object allows the users to customize behaviour. In particular, it
  62. allows users to enable or disable optional features, or to otherwise handle
  63. various unusual behaviours.
  64. This object has very little behaviour of its own: it mostly just ensures
  65. that configuration is self-consistent.
  66. :param client_side: Whether this object is to be used on the client side of
  67. a connection, or on the server side. Affects the logic used by the
  68. state machine, the default settings values, the allowable stream IDs,
  69. and several other properties. Defaults to ``True``.
  70. :type client_side: ``bool``
  71. :param header_encoding: Controls whether the headers emitted by this object
  72. in events are transparently decoded to ``unicode`` strings, and what
  73. encoding is used to do that decoding. This defaults to ``None``,
  74. meaning that headers will be returned as bytes. To automatically
  75. decode headers (that is, to return them as unicode strings), this can
  76. be set to the string name of any encoding, e.g. ``'utf-8'``.
  77. .. versionchanged:: 3.0.0
  78. Changed default value from ``'utf-8'`` to ``None``
  79. :type header_encoding: ``str``, ``False``, or ``None``
  80. :param validate_outbound_headers: Controls whether the headers emitted
  81. by this object are validated against the rules in RFC 7540.
  82. Disabling this setting will cause outbound header validation to
  83. be skipped, and allow the object to emit headers that may be illegal
  84. according to RFC 7540. Defaults to ``True``.
  85. :type validate_outbound_headers: ``bool``
  86. :param normalize_outbound_headers: Controls whether the headers emitted
  87. by this object are normalized before sending. Disabling this setting
  88. will cause outbound header normalization to be skipped, and allow
  89. the object to emit headers that may be illegal according to
  90. RFC 7540. Defaults to ``True``.
  91. :type normalize_outbound_headers: ``bool``
  92. :param split_outbound_cookies: Controls whether the outbound cookie
  93. headers are split before sending or not. According to RFC 7540
  94. - 8.1.2.5 the outbound header cookie headers may be split to improve
  95. headers compression. Default is ``False``.
  96. :type split_outbound_cookies: ``bool``
  97. :param validate_inbound_headers: Controls whether the headers received
  98. by this object are validated against the rules in RFC 7540.
  99. Disabling this setting will cause inbound header validation to
  100. be skipped, and allow the object to receive headers that may be illegal
  101. according to RFC 7540. Defaults to ``True``.
  102. :type validate_inbound_headers: ``bool``
  103. :param normalize_inbound_headers: Controls whether the headers received by
  104. this object are normalized according to the rules of RFC 7540.
  105. Disabling this setting may lead to h2 emitting header blocks that
  106. some RFCs forbid, e.g. with multiple cookie fields.
  107. .. versionadded:: 3.0.0
  108. :type normalize_inbound_headers: ``bool``
  109. :param logger: A logger that conforms to the requirements for this module,
  110. those being no I/O and no context switches, which is needed in order
  111. to run in asynchronous operation.
  112. .. versionadded:: 2.6.0
  113. :type logger: ``logging.Logger``
  114. """
  115. client_side = _BooleanConfigOption("client_side")
  116. validate_outbound_headers = _BooleanConfigOption(
  117. "validate_outbound_headers",
  118. )
  119. normalize_outbound_headers = _BooleanConfigOption(
  120. "normalize_outbound_headers",
  121. )
  122. split_outbound_cookies = _BooleanConfigOption(
  123. "split_outbound_cookies",
  124. )
  125. validate_inbound_headers = _BooleanConfigOption(
  126. "validate_inbound_headers",
  127. )
  128. normalize_inbound_headers = _BooleanConfigOption(
  129. "normalize_inbound_headers",
  130. )
  131. def __init__(self,
  132. client_side: bool = True,
  133. header_encoding: bool | str | None = None,
  134. validate_outbound_headers: bool = True,
  135. normalize_outbound_headers: bool = True,
  136. split_outbound_cookies: bool = False,
  137. validate_inbound_headers: bool = True,
  138. normalize_inbound_headers: bool = True,
  139. logger: DummyLogger | OutputLogger | None = None) -> None:
  140. self.client_side = client_side
  141. self.header_encoding = header_encoding
  142. self.validate_outbound_headers = validate_outbound_headers
  143. self.normalize_outbound_headers = normalize_outbound_headers
  144. self.split_outbound_cookies = split_outbound_cookies
  145. self.validate_inbound_headers = validate_inbound_headers
  146. self.normalize_inbound_headers = normalize_inbound_headers
  147. self.logger = logger or DummyLogger(__name__)
  148. @property
  149. def header_encoding(self) -> bool | str | None:
  150. """
  151. Controls whether the headers emitted by this object in events are
  152. transparently decoded to ``unicode`` strings, and what encoding is used
  153. to do that decoding. This defaults to ``None``, meaning that headers
  154. will be returned as bytes. To automatically decode headers (that is, to
  155. return them as unicode strings), this can be set to the string name of
  156. any encoding, e.g. ``'utf-8'``.
  157. """
  158. return self._header_encoding
  159. @header_encoding.setter
  160. def header_encoding(self, value: bool | str | None) -> None:
  161. """
  162. Enforces constraints on the value of header encoding.
  163. """
  164. if not isinstance(value, (bool, str, type(None))):
  165. msg = "header_encoding must be bool, string, or None"
  166. raise ValueError(msg) # noqa: TRY004
  167. if value is True:
  168. msg = "header_encoding cannot be True"
  169. raise ValueError(msg)
  170. self._header_encoding = value