boring-http 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- boring_http/__init__.py +58 -0
- boring_http/client.py +692 -0
- boring_http/codings.py +39 -0
- boring_http/cookies.py +127 -0
- boring_http/errors.py +49 -0
- boring_http/hpack.py +247 -0
- boring_http/hpack_tables.py +326 -0
- boring_http/http1.py +259 -0
- boring_http/http2.py +685 -0
- boring_http/http3.py +551 -0
- boring_http/message.py +598 -0
- boring_http/pool.py +96 -0
- boring_http/py.typed +0 -0
- boring_http/qpack.py +245 -0
- boring_http/qpack_tables.py +104 -0
- boring_http/quic/__init__.py +0 -0
- boring_http/quic/connection.py +1261 -0
- boring_http/quic/packet.py +655 -0
- boring_http/quic/protocol.py +474 -0
- boring_http/quic/recovery.py +303 -0
- boring_http/quic/streams.py +154 -0
- boring_http/timing.py +63 -0
- boring_http/websocket.py +216 -0
- boring_http-1.0.0.dist-info/METADATA +303 -0
- boring_http-1.0.0.dist-info/RECORD +26 -0
- boring_http-1.0.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# generated from the text of RFC 7541 appendices A and B, so regenerate rather than edit
|
|
2
|
+
from typing import Final
|
|
3
|
+
|
|
4
|
+
STATIC_TABLE: Final[tuple[tuple[bytes, bytes], ...]] = (
|
|
5
|
+
(b":authority", b""),
|
|
6
|
+
(b":method", b"GET"),
|
|
7
|
+
(b":method", b"POST"),
|
|
8
|
+
(b":path", b"/"),
|
|
9
|
+
(b":path", b"/index.html"),
|
|
10
|
+
(b":scheme", b"http"),
|
|
11
|
+
(b":scheme", b"https"),
|
|
12
|
+
(b":status", b"200"),
|
|
13
|
+
(b":status", b"204"),
|
|
14
|
+
(b":status", b"206"),
|
|
15
|
+
(b":status", b"304"),
|
|
16
|
+
(b":status", b"400"),
|
|
17
|
+
(b":status", b"404"),
|
|
18
|
+
(b":status", b"500"),
|
|
19
|
+
(b"accept-charset", b""),
|
|
20
|
+
(b"accept-encoding", b"gzip, deflate"),
|
|
21
|
+
(b"accept-language", b""),
|
|
22
|
+
(b"accept-ranges", b""),
|
|
23
|
+
(b"accept", b""),
|
|
24
|
+
(b"access-control-allow-origin", b""),
|
|
25
|
+
(b"age", b""),
|
|
26
|
+
(b"allow", b""),
|
|
27
|
+
(b"authorization", b""),
|
|
28
|
+
(b"cache-control", b""),
|
|
29
|
+
(b"content-disposition", b""),
|
|
30
|
+
(b"content-encoding", b""),
|
|
31
|
+
(b"content-language", b""),
|
|
32
|
+
(b"content-length", b""),
|
|
33
|
+
(b"content-location", b""),
|
|
34
|
+
(b"content-range", b""),
|
|
35
|
+
(b"content-type", b""),
|
|
36
|
+
(b"cookie", b""),
|
|
37
|
+
(b"date", b""),
|
|
38
|
+
(b"etag", b""),
|
|
39
|
+
(b"expect", b""),
|
|
40
|
+
(b"expires", b""),
|
|
41
|
+
(b"from", b""),
|
|
42
|
+
(b"host", b""),
|
|
43
|
+
(b"if-match", b""),
|
|
44
|
+
(b"if-modified-since", b""),
|
|
45
|
+
(b"if-none-match", b""),
|
|
46
|
+
(b"if-range", b""),
|
|
47
|
+
(b"if-unmodified-since", b""),
|
|
48
|
+
(b"last-modified", b""),
|
|
49
|
+
(b"link", b""),
|
|
50
|
+
(b"location", b""),
|
|
51
|
+
(b"max-forwards", b""),
|
|
52
|
+
(b"proxy-authenticate", b""),
|
|
53
|
+
(b"proxy-authorization", b""),
|
|
54
|
+
(b"range", b""),
|
|
55
|
+
(b"referer", b""),
|
|
56
|
+
(b"refresh", b""),
|
|
57
|
+
(b"retry-after", b""),
|
|
58
|
+
(b"server", b""),
|
|
59
|
+
(b"set-cookie", b""),
|
|
60
|
+
(b"strict-transport-security", b""),
|
|
61
|
+
(b"transfer-encoding", b""),
|
|
62
|
+
(b"user-agent", b""),
|
|
63
|
+
(b"vary", b""),
|
|
64
|
+
(b"via", b""),
|
|
65
|
+
(b"www-authenticate", b""),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
HUFFMAN_CODES: Final[tuple[tuple[int, int], ...]] = (
|
|
69
|
+
(0x1FF8, 13),
|
|
70
|
+
(0x7FFFD8, 23),
|
|
71
|
+
(0xFFFFFE2, 28),
|
|
72
|
+
(0xFFFFFE3, 28),
|
|
73
|
+
(0xFFFFFE4, 28),
|
|
74
|
+
(0xFFFFFE5, 28),
|
|
75
|
+
(0xFFFFFE6, 28),
|
|
76
|
+
(0xFFFFFE7, 28),
|
|
77
|
+
(0xFFFFFE8, 28),
|
|
78
|
+
(0xFFFFEA, 24),
|
|
79
|
+
(0x3FFFFFFC, 30),
|
|
80
|
+
(0xFFFFFE9, 28),
|
|
81
|
+
(0xFFFFFEA, 28),
|
|
82
|
+
(0x3FFFFFFD, 30),
|
|
83
|
+
(0xFFFFFEB, 28),
|
|
84
|
+
(0xFFFFFEC, 28),
|
|
85
|
+
(0xFFFFFED, 28),
|
|
86
|
+
(0xFFFFFEE, 28),
|
|
87
|
+
(0xFFFFFEF, 28),
|
|
88
|
+
(0xFFFFFF0, 28),
|
|
89
|
+
(0xFFFFFF1, 28),
|
|
90
|
+
(0xFFFFFF2, 28),
|
|
91
|
+
(0x3FFFFFFE, 30),
|
|
92
|
+
(0xFFFFFF3, 28),
|
|
93
|
+
(0xFFFFFF4, 28),
|
|
94
|
+
(0xFFFFFF5, 28),
|
|
95
|
+
(0xFFFFFF6, 28),
|
|
96
|
+
(0xFFFFFF7, 28),
|
|
97
|
+
(0xFFFFFF8, 28),
|
|
98
|
+
(0xFFFFFF9, 28),
|
|
99
|
+
(0xFFFFFFA, 28),
|
|
100
|
+
(0xFFFFFFB, 28),
|
|
101
|
+
(0x14, 6),
|
|
102
|
+
(0x3F8, 10),
|
|
103
|
+
(0x3F9, 10),
|
|
104
|
+
(0xFFA, 12),
|
|
105
|
+
(0x1FF9, 13),
|
|
106
|
+
(0x15, 6),
|
|
107
|
+
(0xF8, 8),
|
|
108
|
+
(0x7FA, 11),
|
|
109
|
+
(0x3FA, 10),
|
|
110
|
+
(0x3FB, 10),
|
|
111
|
+
(0xF9, 8),
|
|
112
|
+
(0x7FB, 11),
|
|
113
|
+
(0xFA, 8),
|
|
114
|
+
(0x16, 6),
|
|
115
|
+
(0x17, 6),
|
|
116
|
+
(0x18, 6),
|
|
117
|
+
(0x0, 5),
|
|
118
|
+
(0x1, 5),
|
|
119
|
+
(0x2, 5),
|
|
120
|
+
(0x19, 6),
|
|
121
|
+
(0x1A, 6),
|
|
122
|
+
(0x1B, 6),
|
|
123
|
+
(0x1C, 6),
|
|
124
|
+
(0x1D, 6),
|
|
125
|
+
(0x1E, 6),
|
|
126
|
+
(0x1F, 6),
|
|
127
|
+
(0x5C, 7),
|
|
128
|
+
(0xFB, 8),
|
|
129
|
+
(0x7FFC, 15),
|
|
130
|
+
(0x20, 6),
|
|
131
|
+
(0xFFB, 12),
|
|
132
|
+
(0x3FC, 10),
|
|
133
|
+
(0x1FFA, 13),
|
|
134
|
+
(0x21, 6),
|
|
135
|
+
(0x5D, 7),
|
|
136
|
+
(0x5E, 7),
|
|
137
|
+
(0x5F, 7),
|
|
138
|
+
(0x60, 7),
|
|
139
|
+
(0x61, 7),
|
|
140
|
+
(0x62, 7),
|
|
141
|
+
(0x63, 7),
|
|
142
|
+
(0x64, 7),
|
|
143
|
+
(0x65, 7),
|
|
144
|
+
(0x66, 7),
|
|
145
|
+
(0x67, 7),
|
|
146
|
+
(0x68, 7),
|
|
147
|
+
(0x69, 7),
|
|
148
|
+
(0x6A, 7),
|
|
149
|
+
(0x6B, 7),
|
|
150
|
+
(0x6C, 7),
|
|
151
|
+
(0x6D, 7),
|
|
152
|
+
(0x6E, 7),
|
|
153
|
+
(0x6F, 7),
|
|
154
|
+
(0x70, 7),
|
|
155
|
+
(0x71, 7),
|
|
156
|
+
(0x72, 7),
|
|
157
|
+
(0xFC, 8),
|
|
158
|
+
(0x73, 7),
|
|
159
|
+
(0xFD, 8),
|
|
160
|
+
(0x1FFB, 13),
|
|
161
|
+
(0x7FFF0, 19),
|
|
162
|
+
(0x1FFC, 13),
|
|
163
|
+
(0x3FFC, 14),
|
|
164
|
+
(0x22, 6),
|
|
165
|
+
(0x7FFD, 15),
|
|
166
|
+
(0x3, 5),
|
|
167
|
+
(0x23, 6),
|
|
168
|
+
(0x4, 5),
|
|
169
|
+
(0x24, 6),
|
|
170
|
+
(0x5, 5),
|
|
171
|
+
(0x25, 6),
|
|
172
|
+
(0x26, 6),
|
|
173
|
+
(0x27, 6),
|
|
174
|
+
(0x6, 5),
|
|
175
|
+
(0x74, 7),
|
|
176
|
+
(0x75, 7),
|
|
177
|
+
(0x28, 6),
|
|
178
|
+
(0x29, 6),
|
|
179
|
+
(0x2A, 6),
|
|
180
|
+
(0x7, 5),
|
|
181
|
+
(0x2B, 6),
|
|
182
|
+
(0x76, 7),
|
|
183
|
+
(0x2C, 6),
|
|
184
|
+
(0x8, 5),
|
|
185
|
+
(0x9, 5),
|
|
186
|
+
(0x2D, 6),
|
|
187
|
+
(0x77, 7),
|
|
188
|
+
(0x78, 7),
|
|
189
|
+
(0x79, 7),
|
|
190
|
+
(0x7A, 7),
|
|
191
|
+
(0x7B, 7),
|
|
192
|
+
(0x7FFE, 15),
|
|
193
|
+
(0x7FC, 11),
|
|
194
|
+
(0x3FFD, 14),
|
|
195
|
+
(0x1FFD, 13),
|
|
196
|
+
(0xFFFFFFC, 28),
|
|
197
|
+
(0xFFFE6, 20),
|
|
198
|
+
(0x3FFFD2, 22),
|
|
199
|
+
(0xFFFE7, 20),
|
|
200
|
+
(0xFFFE8, 20),
|
|
201
|
+
(0x3FFFD3, 22),
|
|
202
|
+
(0x3FFFD4, 22),
|
|
203
|
+
(0x3FFFD5, 22),
|
|
204
|
+
(0x7FFFD9, 23),
|
|
205
|
+
(0x3FFFD6, 22),
|
|
206
|
+
(0x7FFFDA, 23),
|
|
207
|
+
(0x7FFFDB, 23),
|
|
208
|
+
(0x7FFFDC, 23),
|
|
209
|
+
(0x7FFFDD, 23),
|
|
210
|
+
(0x7FFFDE, 23),
|
|
211
|
+
(0xFFFFEB, 24),
|
|
212
|
+
(0x7FFFDF, 23),
|
|
213
|
+
(0xFFFFEC, 24),
|
|
214
|
+
(0xFFFFED, 24),
|
|
215
|
+
(0x3FFFD7, 22),
|
|
216
|
+
(0x7FFFE0, 23),
|
|
217
|
+
(0xFFFFEE, 24),
|
|
218
|
+
(0x7FFFE1, 23),
|
|
219
|
+
(0x7FFFE2, 23),
|
|
220
|
+
(0x7FFFE3, 23),
|
|
221
|
+
(0x7FFFE4, 23),
|
|
222
|
+
(0x1FFFDC, 21),
|
|
223
|
+
(0x3FFFD8, 22),
|
|
224
|
+
(0x7FFFE5, 23),
|
|
225
|
+
(0x3FFFD9, 22),
|
|
226
|
+
(0x7FFFE6, 23),
|
|
227
|
+
(0x7FFFE7, 23),
|
|
228
|
+
(0xFFFFEF, 24),
|
|
229
|
+
(0x3FFFDA, 22),
|
|
230
|
+
(0x1FFFDD, 21),
|
|
231
|
+
(0xFFFE9, 20),
|
|
232
|
+
(0x3FFFDB, 22),
|
|
233
|
+
(0x3FFFDC, 22),
|
|
234
|
+
(0x7FFFE8, 23),
|
|
235
|
+
(0x7FFFE9, 23),
|
|
236
|
+
(0x1FFFDE, 21),
|
|
237
|
+
(0x7FFFEA, 23),
|
|
238
|
+
(0x3FFFDD, 22),
|
|
239
|
+
(0x3FFFDE, 22),
|
|
240
|
+
(0xFFFFF0, 24),
|
|
241
|
+
(0x1FFFDF, 21),
|
|
242
|
+
(0x3FFFDF, 22),
|
|
243
|
+
(0x7FFFEB, 23),
|
|
244
|
+
(0x7FFFEC, 23),
|
|
245
|
+
(0x1FFFE0, 21),
|
|
246
|
+
(0x1FFFE1, 21),
|
|
247
|
+
(0x3FFFE0, 22),
|
|
248
|
+
(0x1FFFE2, 21),
|
|
249
|
+
(0x7FFFED, 23),
|
|
250
|
+
(0x3FFFE1, 22),
|
|
251
|
+
(0x7FFFEE, 23),
|
|
252
|
+
(0x7FFFEF, 23),
|
|
253
|
+
(0xFFFEA, 20),
|
|
254
|
+
(0x3FFFE2, 22),
|
|
255
|
+
(0x3FFFE3, 22),
|
|
256
|
+
(0x3FFFE4, 22),
|
|
257
|
+
(0x7FFFF0, 23),
|
|
258
|
+
(0x3FFFE5, 22),
|
|
259
|
+
(0x3FFFE6, 22),
|
|
260
|
+
(0x7FFFF1, 23),
|
|
261
|
+
(0x3FFFFE0, 26),
|
|
262
|
+
(0x3FFFFE1, 26),
|
|
263
|
+
(0xFFFEB, 20),
|
|
264
|
+
(0x7FFF1, 19),
|
|
265
|
+
(0x3FFFE7, 22),
|
|
266
|
+
(0x7FFFF2, 23),
|
|
267
|
+
(0x3FFFE8, 22),
|
|
268
|
+
(0x1FFFFEC, 25),
|
|
269
|
+
(0x3FFFFE2, 26),
|
|
270
|
+
(0x3FFFFE3, 26),
|
|
271
|
+
(0x3FFFFE4, 26),
|
|
272
|
+
(0x7FFFFDE, 27),
|
|
273
|
+
(0x7FFFFDF, 27),
|
|
274
|
+
(0x3FFFFE5, 26),
|
|
275
|
+
(0xFFFFF1, 24),
|
|
276
|
+
(0x1FFFFED, 25),
|
|
277
|
+
(0x7FFF2, 19),
|
|
278
|
+
(0x1FFFE3, 21),
|
|
279
|
+
(0x3FFFFE6, 26),
|
|
280
|
+
(0x7FFFFE0, 27),
|
|
281
|
+
(0x7FFFFE1, 27),
|
|
282
|
+
(0x3FFFFE7, 26),
|
|
283
|
+
(0x7FFFFE2, 27),
|
|
284
|
+
(0xFFFFF2, 24),
|
|
285
|
+
(0x1FFFE4, 21),
|
|
286
|
+
(0x1FFFE5, 21),
|
|
287
|
+
(0x3FFFFE8, 26),
|
|
288
|
+
(0x3FFFFE9, 26),
|
|
289
|
+
(0xFFFFFFD, 28),
|
|
290
|
+
(0x7FFFFE3, 27),
|
|
291
|
+
(0x7FFFFE4, 27),
|
|
292
|
+
(0x7FFFFE5, 27),
|
|
293
|
+
(0xFFFEC, 20),
|
|
294
|
+
(0xFFFFF3, 24),
|
|
295
|
+
(0xFFFED, 20),
|
|
296
|
+
(0x1FFFE6, 21),
|
|
297
|
+
(0x3FFFE9, 22),
|
|
298
|
+
(0x1FFFE7, 21),
|
|
299
|
+
(0x1FFFE8, 21),
|
|
300
|
+
(0x7FFFF3, 23),
|
|
301
|
+
(0x3FFFEA, 22),
|
|
302
|
+
(0x3FFFEB, 22),
|
|
303
|
+
(0x1FFFFEE, 25),
|
|
304
|
+
(0x1FFFFEF, 25),
|
|
305
|
+
(0xFFFFF4, 24),
|
|
306
|
+
(0xFFFFF5, 24),
|
|
307
|
+
(0x3FFFFEA, 26),
|
|
308
|
+
(0x7FFFF4, 23),
|
|
309
|
+
(0x3FFFFEB, 26),
|
|
310
|
+
(0x7FFFFE6, 27),
|
|
311
|
+
(0x3FFFFEC, 26),
|
|
312
|
+
(0x3FFFFED, 26),
|
|
313
|
+
(0x7FFFFE7, 27),
|
|
314
|
+
(0x7FFFFE8, 27),
|
|
315
|
+
(0x7FFFFE9, 27),
|
|
316
|
+
(0x7FFFFEA, 27),
|
|
317
|
+
(0x7FFFFEB, 27),
|
|
318
|
+
(0xFFFFFFE, 28),
|
|
319
|
+
(0x7FFFFEC, 27),
|
|
320
|
+
(0x7FFFFED, 27),
|
|
321
|
+
(0x7FFFFEE, 27),
|
|
322
|
+
(0x7FFFFEF, 27),
|
|
323
|
+
(0x7FFFFF0, 27),
|
|
324
|
+
(0x3FFFFEE, 26),
|
|
325
|
+
(0x3FFFFFFF, 30),
|
|
326
|
+
)
|
boring_http/http1.py
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
from collections.abc import AsyncGenerator, Callable
|
|
2
|
+
from typing import Final, final
|
|
3
|
+
|
|
4
|
+
from ssl2 import AsyncTransport
|
|
5
|
+
|
|
6
|
+
from boring_http.errors import ProtocolError, RemoteDisconnected
|
|
7
|
+
from boring_http.message import Headers, HTTPVersion, Request, Response
|
|
8
|
+
from boring_http.timing import Deadline
|
|
9
|
+
|
|
10
|
+
READ_SIZE: Final = 65536
|
|
11
|
+
HEAD_LIMIT: Final = 65536
|
|
12
|
+
LINE_LIMIT: Final = 8192
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@final
|
|
16
|
+
class Http1Connection:
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
transport: AsyncTransport,
|
|
20
|
+
*,
|
|
21
|
+
leftover: bytes = b"",
|
|
22
|
+
absolute_form: bool = False,
|
|
23
|
+
proxy_authorization: str | None = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
self._transport = transport
|
|
26
|
+
self._buffer = bytearray(leftover)
|
|
27
|
+
self._absolute_form = absolute_form
|
|
28
|
+
self._proxy_authorization = proxy_authorization
|
|
29
|
+
self._keep_alive = True
|
|
30
|
+
self.idle = True
|
|
31
|
+
self.closed = False
|
|
32
|
+
self.reused = False
|
|
33
|
+
self.released: Callable[[], None] = self._nothing
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def available(self) -> bool:
|
|
37
|
+
return self.idle and not self.closed
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def shared(self) -> bool:
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
def take(self) -> None:
|
|
44
|
+
self.idle = False
|
|
45
|
+
|
|
46
|
+
async def send(
|
|
47
|
+
self, request: Request, deadline: Deadline, *, decode: bool
|
|
48
|
+
) -> Response:
|
|
49
|
+
self.idle = False
|
|
50
|
+
headers = request.wire_headers(HTTPVersion.HTTP_1)
|
|
51
|
+
if (
|
|
52
|
+
self._proxy_authorization is not None
|
|
53
|
+
and "proxy-authorization" not in headers
|
|
54
|
+
):
|
|
55
|
+
headers.add("Proxy-Authorization", self._proxy_authorization)
|
|
56
|
+
target = (
|
|
57
|
+
request.url.origin + request.url.target
|
|
58
|
+
if self._absolute_form
|
|
59
|
+
else request.url.target
|
|
60
|
+
)
|
|
61
|
+
lines = [
|
|
62
|
+
f"{request.method} {target} HTTP/1.1",
|
|
63
|
+
*(f"{name}: {value}" for name, value in headers),
|
|
64
|
+
]
|
|
65
|
+
try:
|
|
66
|
+
async with deadline.phase("write"):
|
|
67
|
+
await self._transport.write("\r\n".join(lines).encode() + b"\r\n\r\n")
|
|
68
|
+
if request.body is not None:
|
|
69
|
+
await self._write_body(
|
|
70
|
+
request,
|
|
71
|
+
headers.get("transfer-encoding", "").lower() == "chunked",
|
|
72
|
+
)
|
|
73
|
+
status, reason, response_headers, old = await self._read_head(deadline)
|
|
74
|
+
except BaseException:
|
|
75
|
+
await self.close()
|
|
76
|
+
raise
|
|
77
|
+
tokens = {
|
|
78
|
+
token.strip().lower()
|
|
79
|
+
for token in (
|
|
80
|
+
headers.get("connection", "")
|
|
81
|
+
+ ","
|
|
82
|
+
+ response_headers.get("connection", "")
|
|
83
|
+
).split(",")
|
|
84
|
+
}
|
|
85
|
+
self._keep_alive = "close" not in tokens and (not old or "keep-alive" in tokens)
|
|
86
|
+
chunked, length = self._framing(request.method, status, response_headers)
|
|
87
|
+
trailers = Headers()
|
|
88
|
+
body = self._body(chunked, length, trailers, deadline)
|
|
89
|
+
return Response(
|
|
90
|
+
status=status,
|
|
91
|
+
reason=reason,
|
|
92
|
+
version=HTTPVersion.HTTP_1,
|
|
93
|
+
headers=response_headers,
|
|
94
|
+
request=request,
|
|
95
|
+
raw=body,
|
|
96
|
+
trailers=trailers,
|
|
97
|
+
decode=decode,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def detach(self) -> tuple[AsyncTransport, bytes]:
|
|
101
|
+
self.closed = True
|
|
102
|
+
self.released()
|
|
103
|
+
return self._transport, bytes(self._buffer)
|
|
104
|
+
|
|
105
|
+
async def close(self) -> None:
|
|
106
|
+
if not self.closed:
|
|
107
|
+
self.closed = True
|
|
108
|
+
await self._transport.close()
|
|
109
|
+
self.released()
|
|
110
|
+
|
|
111
|
+
async def _write_body(self, request: Request, chunked: bool) -> None:
|
|
112
|
+
if request.body is None:
|
|
113
|
+
return
|
|
114
|
+
async for chunk in request.body.chunks():
|
|
115
|
+
if chunked:
|
|
116
|
+
await self._transport.write(
|
|
117
|
+
f"{len(chunk):x}\r\n".encode() + chunk + b"\r\n"
|
|
118
|
+
)
|
|
119
|
+
else:
|
|
120
|
+
await self._transport.write(chunk)
|
|
121
|
+
if chunked:
|
|
122
|
+
await self._transport.write(b"0\r\n\r\n")
|
|
123
|
+
|
|
124
|
+
async def _read_head(self, deadline: Deadline) -> tuple[int, str, Headers, bool]:
|
|
125
|
+
while True:
|
|
126
|
+
head = (await self._read_until(b"\r\n\r\n", HEAD_LIMIT, deadline)).decode(
|
|
127
|
+
"latin-1"
|
|
128
|
+
)
|
|
129
|
+
status_line, *lines = head.split("\r\n")
|
|
130
|
+
version, _, rest = status_line.partition(" ")
|
|
131
|
+
code, _, reason = rest.partition(" ")
|
|
132
|
+
if (
|
|
133
|
+
version not in ("HTTP/1.1", "HTTP/1.0")
|
|
134
|
+
or len(code) != 3
|
|
135
|
+
or not (code.isascii() and code.isdigit())
|
|
136
|
+
):
|
|
137
|
+
raise ProtocolError(f"malformed status line {status_line!r}")
|
|
138
|
+
headers = Headers()
|
|
139
|
+
for line in lines:
|
|
140
|
+
name, separator, value = line.partition(":")
|
|
141
|
+
if not separator or line[:1] in (" ", "\t"):
|
|
142
|
+
raise ProtocolError(f"malformed header line {line!r}")
|
|
143
|
+
try:
|
|
144
|
+
headers.add(name.strip(), value)
|
|
145
|
+
except ValueError as error:
|
|
146
|
+
raise ProtocolError(str(error)) from None
|
|
147
|
+
status = int(code)
|
|
148
|
+
if 100 <= status < 200 and status != 101:
|
|
149
|
+
continue
|
|
150
|
+
return status, reason, headers, version == "HTTP/1.0"
|
|
151
|
+
|
|
152
|
+
@staticmethod
|
|
153
|
+
def _framing(method: str, status: int, headers: Headers) -> tuple[bool, int | None]:
|
|
154
|
+
if method == "HEAD" or status in (101, 204, 304):
|
|
155
|
+
return False, 0
|
|
156
|
+
encoding = headers.get("transfer-encoding")
|
|
157
|
+
if encoding is not None:
|
|
158
|
+
return encoding.split(",")[-1].strip().lower() == "chunked", None
|
|
159
|
+
length = headers.get("content-length")
|
|
160
|
+
if length is None:
|
|
161
|
+
return False, None
|
|
162
|
+
# str.isdigit accepts superscripts and other Unicode digits that int rejects
|
|
163
|
+
if not (length.isascii() and length.isdigit()):
|
|
164
|
+
raise ProtocolError(f"malformed Content-Length {length!r}")
|
|
165
|
+
return False, int(length)
|
|
166
|
+
|
|
167
|
+
async def _body(
|
|
168
|
+
self, chunked: bool, length: int | None, trailers: Headers, deadline: Deadline
|
|
169
|
+
) -> AsyncGenerator[bytes]:
|
|
170
|
+
try:
|
|
171
|
+
if chunked:
|
|
172
|
+
async for chunk in self._chunked(trailers, deadline):
|
|
173
|
+
yield chunk
|
|
174
|
+
elif length is None:
|
|
175
|
+
self._keep_alive = False
|
|
176
|
+
while piece := await self._read_some(READ_SIZE, deadline):
|
|
177
|
+
yield piece
|
|
178
|
+
else:
|
|
179
|
+
async for piece in self._exactly(length, deadline):
|
|
180
|
+
yield piece
|
|
181
|
+
except BaseException:
|
|
182
|
+
await self.close()
|
|
183
|
+
raise
|
|
184
|
+
if self._keep_alive:
|
|
185
|
+
self.idle = True
|
|
186
|
+
self.released()
|
|
187
|
+
else:
|
|
188
|
+
await self.close()
|
|
189
|
+
|
|
190
|
+
async def _chunked(
|
|
191
|
+
self, trailers: Headers, deadline: Deadline
|
|
192
|
+
) -> AsyncGenerator[bytes]:
|
|
193
|
+
while True:
|
|
194
|
+
line = await self._read_line(deadline)
|
|
195
|
+
try:
|
|
196
|
+
size = int(line.split(b";", 1)[0].strip(), 16)
|
|
197
|
+
except ValueError:
|
|
198
|
+
raise ProtocolError(f"malformed chunk size {line!r}") from None
|
|
199
|
+
if size == 0:
|
|
200
|
+
break
|
|
201
|
+
async for piece in self._exactly(size, deadline):
|
|
202
|
+
yield piece
|
|
203
|
+
if await self._read_line(deadline):
|
|
204
|
+
raise ProtocolError("chunk data not followed by CRLF")
|
|
205
|
+
while line := await self._read_line(deadline):
|
|
206
|
+
name, separator, value = line.decode("latin-1").partition(":")
|
|
207
|
+
if not separator:
|
|
208
|
+
raise ProtocolError(f"malformed trailer line {line!r}")
|
|
209
|
+
try:
|
|
210
|
+
trailers.add(name.strip(), value)
|
|
211
|
+
except ValueError as error:
|
|
212
|
+
raise ProtocolError(str(error)) from None
|
|
213
|
+
|
|
214
|
+
async def _exactly(self, length: int, deadline: Deadline) -> AsyncGenerator[bytes]:
|
|
215
|
+
remaining = length
|
|
216
|
+
while remaining:
|
|
217
|
+
piece = await self._read_some(remaining, deadline)
|
|
218
|
+
if not piece:
|
|
219
|
+
raise ProtocolError(
|
|
220
|
+
f"the connection closed with {remaining} body bytes missing"
|
|
221
|
+
)
|
|
222
|
+
remaining -= len(piece)
|
|
223
|
+
yield piece
|
|
224
|
+
|
|
225
|
+
async def _read_some(self, limit: int, deadline: Deadline) -> bytes:
|
|
226
|
+
if not self._buffer:
|
|
227
|
+
async with deadline.phase("read"):
|
|
228
|
+
return await self._transport.read(min(limit, READ_SIZE))
|
|
229
|
+
piece = bytes(self._buffer[:limit])
|
|
230
|
+
del self._buffer[:limit]
|
|
231
|
+
return piece
|
|
232
|
+
|
|
233
|
+
async def _read_until(
|
|
234
|
+
self, terminator: bytes, limit: int, deadline: Deadline
|
|
235
|
+
) -> bytes:
|
|
236
|
+
while (end := self._buffer.find(terminator)) < 0:
|
|
237
|
+
if len(self._buffer) > limit:
|
|
238
|
+
raise ProtocolError(f"the response head exceeds {limit} bytes")
|
|
239
|
+
async with deadline.phase("read"):
|
|
240
|
+
piece = await self._transport.read(READ_SIZE)
|
|
241
|
+
if not piece:
|
|
242
|
+
if self._buffer:
|
|
243
|
+
raise ProtocolError(
|
|
244
|
+
"the connection closed in the middle of the response head"
|
|
245
|
+
)
|
|
246
|
+
raise RemoteDisconnected(
|
|
247
|
+
"the server closed the connection before responding"
|
|
248
|
+
)
|
|
249
|
+
self._buffer += piece
|
|
250
|
+
head = bytes(self._buffer[:end])
|
|
251
|
+
del self._buffer[: end + len(terminator)]
|
|
252
|
+
return head
|
|
253
|
+
|
|
254
|
+
async def _read_line(self, deadline: Deadline) -> bytes:
|
|
255
|
+
return await self._read_until(b"\r\n", LINE_LIMIT, deadline)
|
|
256
|
+
|
|
257
|
+
@staticmethod
|
|
258
|
+
def _nothing() -> None:
|
|
259
|
+
pass
|