Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/h11/_events.py: 72%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# High level events that make up HTTP/1.1 conversations. Loosely inspired by
2# the corresponding events in hyper-h2:
3#
4# http://python-hyper.org/h2/en/stable/api.html#events
5#
6# Don't subclass these. Stuff will break.
8import re
9from abc import ABC
10from dataclasses import dataclass
11from typing import List, Tuple, Union
13from ._abnf import method, request_target
14from ._headers import Headers, normalize_and_validate
15from ._util import bytesify, LocalProtocolError, validate
17# Everything in __all__ gets re-exported as part of the h11 public API.
18__all__ = [
19 "Event",
20 "Request",
21 "InformationalResponse",
22 "Response",
23 "Data",
24 "EndOfMessage",
25 "ConnectionClosed",
26]
28method_re = re.compile(method.encode("ascii"))
29request_target_re = re.compile(request_target.encode("ascii"))
32class Event(ABC):
33 """
34 Base class for h11 events.
35 """
37 __slots__ = ()
40@dataclass(init=False, frozen=True)
41class Request(Event):
42 """The beginning of an HTTP request.
44 Fields:
46 .. attribute:: method
48 An HTTP method, e.g. ``b"GET"`` or ``b"POST"``. Always a byte
49 string. :term:`Bytes-like objects <bytes-like object>` and native
50 strings containing only ascii characters will be automatically
51 converted to byte strings.
53 .. attribute:: target
55 The target of an HTTP request, e.g. ``b"/index.html"``, or one of the
56 more exotic formats described in `RFC 7320, section 5.3
57 <https://tools.ietf.org/html/rfc7230#section-5.3>`_. Always a byte
58 string. :term:`Bytes-like objects <bytes-like object>` and native
59 strings containing only ascii characters will be automatically
60 converted to byte strings.
62 .. attribute:: headers
64 Request headers, represented as a list of (name, value) pairs. See
65 :ref:`the header normalization rules <headers-format>` for details.
67 .. attribute:: http_version
69 The HTTP protocol version, represented as a byte string like
70 ``b"1.1"``. See :ref:`the HTTP version normalization rules
71 <http_version-format>` for details.
73 """
75 __slots__ = ("method", "headers", "target", "http_version")
77 method: bytes
78 headers: Headers
79 target: bytes
80 http_version: bytes
82 def __init__(
83 self,
84 *,
85 method: Union[bytes, str],
86 headers: Union[Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]]],
87 target: Union[bytes, str],
88 http_version: Union[bytes, str] = b"1.1",
89 _parsed: bool = False,
90 ) -> None:
91 super().__init__()
92 if isinstance(headers, Headers):
93 object.__setattr__(self, "headers", headers)
94 else:
95 object.__setattr__(
96 self, "headers", normalize_and_validate(headers, _parsed=_parsed)
97 )
98 if not _parsed:
99 object.__setattr__(self, "method", bytesify(method))
100 object.__setattr__(self, "target", bytesify(target))
101 object.__setattr__(self, "http_version", bytesify(http_version))
102 else:
103 object.__setattr__(self, "method", method)
104 object.__setattr__(self, "target", target)
105 object.__setattr__(self, "http_version", http_version)
107 # "A server MUST respond with a 400 (Bad Request) status code to any
108 # HTTP/1.1 request message that lacks a Host header field and to any
109 # request message that contains more than one Host header field or a
110 # Host header field with an invalid field-value."
111 # -- https://tools.ietf.org/html/rfc7230#section-5.4
112 host_count = 0
113 for name, value in self.headers:
114 if name == b"host":
115 host_count += 1
116 if self.http_version == b"1.1" and host_count == 0:
117 raise LocalProtocolError("Missing mandatory Host: header")
118 if host_count > 1:
119 raise LocalProtocolError("Found multiple Host: headers")
121 validate(method_re, self.method, "Illegal method characters")
122 validate(request_target_re, self.target, "Illegal target characters")
124 # This is an unhashable type.
125 __hash__ = None # type: ignore
128@dataclass(init=False, frozen=True)
129class _ResponseBase(Event):
130 __slots__ = ("headers", "http_version", "reason", "status_code")
132 headers: Headers
133 http_version: bytes
134 reason: bytes
135 status_code: int
137 def __init__(
138 self,
139 *,
140 headers: Union[Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]]],
141 status_code: int,
142 http_version: Union[bytes, str] = b"1.1",
143 reason: Union[bytes, str] = b"",
144 _parsed: bool = False,
145 ) -> None:
146 super().__init__()
147 if isinstance(headers, Headers):
148 object.__setattr__(self, "headers", headers)
149 else:
150 object.__setattr__(
151 self, "headers", normalize_and_validate(headers, _parsed=_parsed)
152 )
153 if not _parsed:
154 object.__setattr__(self, "reason", bytesify(reason))
155 object.__setattr__(self, "http_version", bytesify(http_version))
156 if not isinstance(status_code, int):
157 raise LocalProtocolError("status code must be integer")
158 # Because IntEnum objects are instances of int, but aren't
159 # duck-compatible (sigh), see gh-72.
160 object.__setattr__(self, "status_code", int(status_code))
161 else:
162 object.__setattr__(self, "reason", reason)
163 object.__setattr__(self, "http_version", http_version)
164 object.__setattr__(self, "status_code", status_code)
166 self.__post_init__()
168 def __post_init__(self) -> None:
169 pass
171 # This is an unhashable type.
172 __hash__ = None # type: ignore
175@dataclass(init=False, frozen=True)
176class InformationalResponse(_ResponseBase):
177 """An HTTP informational response.
179 Fields:
181 .. attribute:: status_code
183 The status code of this response, as an integer. For an
184 :class:`InformationalResponse`, this is always in the range [100,
185 200).
187 .. attribute:: headers
189 Request headers, represented as a list of (name, value) pairs. See
190 :ref:`the header normalization rules <headers-format>` for
191 details.
193 .. attribute:: http_version
195 The HTTP protocol version, represented as a byte string like
196 ``b"1.1"``. See :ref:`the HTTP version normalization rules
197 <http_version-format>` for details.
199 .. attribute:: reason
201 The reason phrase of this response, as a byte string. For example:
202 ``b"OK"``, or ``b"Not Found"``.
204 """
206 def __post_init__(self) -> None:
207 if not (100 <= self.status_code < 200):
208 raise LocalProtocolError(
209 "InformationalResponse status_code should be in range "
210 "[100, 200), not {}".format(self.status_code)
211 )
213 # This is an unhashable type.
214 __hash__ = None # type: ignore
217@dataclass(init=False, frozen=True)
218class Response(_ResponseBase):
219 """The beginning of an HTTP response.
221 Fields:
223 .. attribute:: status_code
225 The status code of this response, as an integer. For an
226 :class:`Response`, this is always in the range [200,
227 1000).
229 .. attribute:: headers
231 Request headers, represented as a list of (name, value) pairs. See
232 :ref:`the header normalization rules <headers-format>` for details.
234 .. attribute:: http_version
236 The HTTP protocol version, represented as a byte string like
237 ``b"1.1"``. See :ref:`the HTTP version normalization rules
238 <http_version-format>` for details.
240 .. attribute:: reason
242 The reason phrase of this response, as a byte string. For example:
243 ``b"OK"``, or ``b"Not Found"``.
245 """
247 def __post_init__(self) -> None:
248 if not (200 <= self.status_code < 1000):
249 raise LocalProtocolError(
250 "Response status_code should be in range [200, 1000), not {}".format(
251 self.status_code
252 )
253 )
255 # This is an unhashable type.
256 __hash__ = None # type: ignore
259@dataclass(init=False, frozen=True)
260class Data(Event):
261 """Part of an HTTP message body.
263 Fields:
265 .. attribute:: data
267 A :term:`bytes-like object` containing part of a message body. Or, if
268 using the ``combine=False`` argument to :meth:`Connection.send`, then
269 any object that your socket writing code knows what to do with, and for
270 which calling :func:`len` returns the number of bytes that will be
271 written -- see :ref:`sendfile` for details.
273 .. attribute:: chunk_start
275 A marker that indicates whether this data object is from the start of a
276 chunked transfer encoding chunk. This field is ignored when when a Data
277 event is provided to :meth:`Connection.send`: it is only valid on
278 events emitted from :meth:`Connection.next_event`. You probably
279 shouldn't use this attribute at all; see
280 :ref:`chunk-delimiters-are-bad` for details.
282 .. attribute:: chunk_end
284 A marker that indicates whether this data object is the last for a
285 given chunked transfer encoding chunk. This field is ignored when when
286 a Data event is provided to :meth:`Connection.send`: it is only valid
287 on events emitted from :meth:`Connection.next_event`. You probably
288 shouldn't use this attribute at all; see
289 :ref:`chunk-delimiters-are-bad` for details.
291 """
293 __slots__ = ("data", "chunk_start", "chunk_end")
295 data: bytes
296 chunk_start: bool
297 chunk_end: bool
299 def __init__(
300 self, data: bytes, chunk_start: bool = False, chunk_end: bool = False
301 ) -> None:
302 object.__setattr__(self, "data", data)
303 object.__setattr__(self, "chunk_start", chunk_start)
304 object.__setattr__(self, "chunk_end", chunk_end)
306 # This is an unhashable type.
307 __hash__ = None # type: ignore
310# XX FIXME: "A recipient MUST ignore (or consider as an error) any fields that
311# are forbidden to be sent in a trailer, since processing them as if they were
312# present in the header section might bypass external security filters."
313# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#chunked.trailer.part
314# Unfortunately, the list of forbidden fields is long and vague :-/
315@dataclass(init=False, frozen=True)
316class EndOfMessage(Event):
317 """The end of an HTTP message.
319 Fields:
321 .. attribute:: headers
323 Default value: ``[]``
325 Any trailing headers attached to this message, represented as a list of
326 (name, value) pairs. See :ref:`the header normalization rules
327 <headers-format>` for details.
329 Must be empty unless ``Transfer-Encoding: chunked`` is in use.
331 """
333 __slots__ = ("headers",)
335 headers: Headers
337 def __init__(
338 self,
339 *,
340 headers: Union[
341 Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]], None
342 ] = None,
343 _parsed: bool = False,
344 ) -> None:
345 super().__init__()
346 if headers is None:
347 headers = Headers([])
348 elif not isinstance(headers, Headers):
349 headers = normalize_and_validate(headers, _parsed=_parsed)
351 object.__setattr__(self, "headers", headers)
353 # This is an unhashable type.
354 __hash__ = None # type: ignore
357@dataclass(frozen=True)
358class ConnectionClosed(Event):
359 """This event indicates that the sender has closed their outgoing
360 connection.
362 Note that this does not necessarily mean that they can't *receive* further
363 data, because TCP connections are composed to two one-way channels which
364 can be closed independently. See :ref:`closing` for details.
366 No fields.
367 """
369 pass