Tachyon /Users/simon/Documents/Codex/2026-10-12/i-need-a-python-3-15/.venv/lib/python3.15/site-packages/h11/_events.py
12
Self Samples
38
Cumulative
4
Lines Hit
0.13%
% of Total
7
Max Self
33
Max Total
Intensity:
Cold Hot
Self Time
Total Time
Show All
Hot Only
Heat
Specialization
Line
Self
Total
Code
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.
7
8
import re
9
from abc import ABC
10
from dataclasses import dataclass
11
from typing import List, Tuple, Union
12
13
from ._abnf import method, request_target
14
from ._headers import Headers, normalize_and_validate
15
from ._util import bytesify, LocalProtocolError, validate
16
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
]
27
28
method_re = re.compile(method.encode("ascii"))
29
request_target_re = re.compile(request_target.encode("ascii"))
30
31
32
class Event(ABC):
33
"""
34
Base class for h11 events.
35
"""
36
37
__slots__ = ()
38
39
40
@dataclass(init=False, frozen=True)
41
class Request(Event):
42
"""The beginning of an HTTP request.
43
44
Fields:
45
46
.. attribute:: method
47
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.
52
53
.. attribute:: target
54
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.
61
62
.. attribute:: headers
63
64
Request headers, represented as a list of (name, value) pairs. See
65
:ref:`the header normalization rules <headers-format>` for details.
66
67
.. attribute:: http_version
68
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.
72
73
"""
74
75
__slots__ = ("method", "headers", "target", "http_version")
76
77
method: bytes
78
headers: Headers
79
target: bytes
80
http_version: bytes
81
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
3
3
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)
106
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")
120
121
validate(method_re, self.method, "Illegal method characters")
122
validate(request_target_re, self.target, "Illegal target characters")
123
124
# This is an unhashable type.
125
__hash__ = None # type: ignore
126
127
128
@dataclass(init=False, frozen=True)
129
class _ResponseBase(Event):
130
__slots__ = ("headers", "http_version", "reason", "status_code")
131
132
headers: Headers
133
http_version: bytes
134
reason: bytes
135
status_code: int
136
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
1
1
super().__init__()
147
1
1
if isinstance(headers, Headers):
148
object.__setattr__(self, "headers", headers)
149
else:
150
object.__setattr__(
151
7
33
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)
165
166
self.__post_init__()
167
168
def __post_init__(self) -> None:
169
pass
170
171
# This is an unhashable type.
172
__hash__ = None # type: ignore
173
174
175
@dataclass(init=False, frozen=True)
176
class InformationalResponse(_ResponseBase):
177
"""An HTTP informational response.
178
179
Fields:
180
181
.. attribute:: status_code
182
183
The status code of this response, as an integer. For an
184
:class:`InformationalResponse`, this is always in the range [100,
185
200).
186
187
.. attribute:: headers
188
189
Request headers, represented as a list of (name, value) pairs. See
190
:ref:`the header normalization rules <headers-format>` for
191
details.
192
193
.. attribute:: http_version
194
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.
198
199
.. attribute:: reason
200
201
The reason phrase of this response, as a byte string. For example:
202
``b"OK"``, or ``b"Not Found"``.
203
204
"""
205
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
)
212
213
# This is an unhashable type.
214
__hash__ = None # type: ignore
215
216
217
@dataclass(init=False, frozen=True)
218
class Response(_ResponseBase):
219
"""The beginning of an HTTP response.
220
221
Fields:
222
223
.. attribute:: status_code
224
225
The status code of this response, as an integer. For an
226
:class:`Response`, this is always in the range [200,
227
1000).
228
229
.. attribute:: headers
230
231
Request headers, represented as a list of (name, value) pairs. See
232
:ref:`the header normalization rules <headers-format>` for details.
233
234
.. attribute:: http_version
235
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.
239
240
.. attribute:: reason
241
242
The reason phrase of this response, as a byte string. For example:
243
``b"OK"``, or ``b"Not Found"``.
244
245
"""
246
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
)
254
255
# This is an unhashable type.
256
__hash__ = None # type: ignore
257
258
259
@dataclass(init=False, frozen=True)
260
class Data(Event):
261
"""Part of an HTTP message body.
262
263
Fields:
264
265
.. attribute:: data
266
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.
272
273
.. attribute:: chunk_start
274
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.
281
282
.. attribute:: chunk_end
283
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.
290
291
"""
292
293
__slots__ = ("data", "chunk_start", "chunk_end")
294
295
data: bytes
296
chunk_start: bool
297
chunk_end: bool
298
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)
305
306
# This is an unhashable type.
307
__hash__ = None # type: ignore
308
309
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)
316
class EndOfMessage(Event):
317
"""The end of an HTTP message.
318
319
Fields:
320
321
.. attribute:: headers
322
323
Default value: ``[]``
324
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.
328
329
Must be empty unless ``Transfer-Encoding: chunked`` is in use.
330
331
"""
332
333
__slots__ = ("headers",)
334
335
headers: Headers
336
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)
350
351
object.__setattr__(self, "headers", headers)
352
353
# This is an unhashable type.
354
__hash__ = None # type: ignore
355
356
357
@dataclass(frozen=True)
358
class ConnectionClosed(Event):
359
"""This event indicates that the sender has closed their outgoing
360
connection.
361
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.
365
366
No fields.
367
"""
368
369
pass