Tachyon /Users/simon/Documents/Codex/2026-10-12/i-need-a-python-3-15/.venv/lib/python3.15/site-packages/uvicorn/protocols/http/h11_impl.py
32
Self Samples
452
Cumulative
16
Lines Hit
1.55%
% of Total
9
Max Self
296
Max Total
Intensity:
Cold Hot
Self Time
Total Time
Show All
Hot Only
Heat
Specialization
Line
Self
Total
Code
1
from __future__ import annotations
2
3
import asyncio
4
import contextvars
5
import http
6
import logging
7
import sys
8
from collections.abc import Callable
9
from typing import Any, Literal
10
from urllib.parse import unquote
11
12
import h11
13
from h11._connection import DEFAULT_MAX_INCOMPLETE_EVENT_SIZE
14
15
from uvicorn._types import (
16
ASGI3Application,
17
ASGIReceiveEvent,
18
ASGISendEvent,
19
HTTPRequestEvent,
20
HTTPResponseBodyEvent,
21
HTTPResponseStartEvent,
22
HTTPScope,
23
)
24
from uvicorn.config import Config
25
from uvicorn.logging import TRACE_LOG_LEVEL
26
from uvicorn.protocols.http.flow_control import CLOSE_HEADER, HIGH_WATER_LIMIT, FlowControl, service_unavailable
27
from uvicorn.protocols.utils import get_client_addr, get_local_addr, get_path_with_query_string, get_remote_addr, is_ssl
28
from uvicorn.server import ServerState
29
30
31
def _get_status_phrase(status_code: int) -> bytes:
32
try:
33
return http.HTTPStatus(status_code).phrase.encode()
34
except ValueError:
35
return b""
36
37
38
STATUS_PHRASES = {status_code: _get_status_phrase(status_code) for status_code in range(100, 600)}
39
40
41
class H11Protocol(asyncio.Protocol):
42
def __init__(
43
self,
44
config: Config,
45
server_state: ServerState,
46
app_state: dict[str, Any],
47
_loop: asyncio.AbstractEventLoop | None = None,
48
) -> None:
49
if not config.loaded:
50
config.load()
51
52
self.config = config
53
self.app = config.loaded_app
54
self.loop = _loop or asyncio.get_event_loop()
55
self.logger = logging.getLogger("uvicorn.error")
56
self.access_logger = logging.getLogger("uvicorn.access")
57
self.access_log = self.access_logger.hasHandlers()
58
self.conn = h11.Connection(
59
h11.SERVER,
60
config.h11_max_incomplete_event_size
61
if config.h11_max_incomplete_event_size is not None
62
else DEFAULT_MAX_INCOMPLETE_EVENT_SIZE,
63
)
64
self.ws_protocol_class = config.ws_protocol_class
65
self.root_path = config.root_path
66
self.asgi_version = config.asgi_version
67
self.limit_concurrency = config.limit_concurrency
68
self.app_state = app_state
69
70
# Timeouts
71
self.timeout_keep_alive_task: asyncio.TimerHandle | None = None
72
self.timeout_keep_alive = config.timeout_keep_alive
73
74
# Shared server state
75
self.server_state = server_state
76
self.connections = server_state.connections
77
self.tasks = server_state.tasks
78
79
# Per-connection state
80
self.transport: asyncio.Transport = None # type: ignore[assignment]
81
self.flow: FlowControl = None # type: ignore[assignment]
82
self.server: tuple[str, int | None] | None = None
83
self.client: tuple[str, int] | None = None
84
self.scheme: Literal["http", "https"] | None = None
85
86
# Per-request state
87
self.scope: HTTPScope = None # type: ignore[assignment]
88
self.headers: list[tuple[bytes, bytes]] = None # type: ignore[assignment]
89
self.cycle: RequestResponseCycle = None # type: ignore[assignment]
90
91
# Protocol interface
92
def connection_made( # type: ignore[override]
93
self, transport: asyncio.Transport
94
) -> None:
95
self.connections.add(self)
96
97
self.transport = transport
98
self.flow = FlowControl(transport)
99
self.server = get_local_addr(transport)
100
self.client = get_remote_addr(transport)
101
self.scheme = "https" if is_ssl(transport) else "http"
102
103
if self.logger.level <= TRACE_LOG_LEVEL:
104
prefix = "%s:%d - " % self.client if self.client else ""
105
self.logger.log(TRACE_LOG_LEVEL, "%sHTTP connection made", prefix)
106
107
def connection_lost(self, exc: Exception | None) -> None:
108
self.connections.discard(self)
109
110
if self.logger.level <= TRACE_LOG_LEVEL:
111
prefix = "%s:%d - " % self.client if self.client else ""
112
self.logger.log(TRACE_LOG_LEVEL, "%sHTTP connection lost", prefix)
113
114
if self.cycle and not self.cycle.response_complete:
115
self.cycle.disconnected = True
116
if self.conn.our_state != h11.ERROR:
117
event = h11.ConnectionClosed()
118
try:
119
self.conn.send(event)
120
except h11.LocalProtocolError:
121
# Premature client disconnect
122
pass
123
124
if self.cycle is not None:
125
self.cycle.message_event.set()
126
if self.flow is not None:
127
self.flow.resume_writing()
128
if exc is None:
129
self.transport.close()
130
self._unset_keepalive_if_required()
131
132
def eof_received(self) -> None:
133
pass
134
135
def _unset_keepalive_if_required(self) -> None:
136
if self.timeout_keep_alive_task is not None:
137
self.timeout_keep_alive_task.cancel()
138
self.timeout_keep_alive_task = None
139
140
def _get_upgrade(self) -> bytes | None:
141
connection = []
142
upgrade = None
143
for name, value in self.headers:
144
if name == b"connection":
145
connection = [token.lower().strip() for token in value.split(b",")]
146
if name == b"upgrade":
147
upgrade = value.lower()
148
if b"upgrade" in connection:
149
return upgrade
150
return None
151
152
def _should_upgrade_to_ws(self) -> bool:
153
if self.ws_protocol_class is None:
154
return False
155
return True
156
157
def _unsupported_upgrade_warning(self) -> None:
158
msg = "Unsupported upgrade request."
159
self.logger.warning(msg)
160
if not self._should_upgrade_to_ws():
161
msg = "No supported WebSocket library detected. Please use \"pip install 'uvicorn[standard]'\", or install 'websockets' or 'wsproto' manually." # noqa: E501
162
self.logger.warning(msg)
163
164
def _should_upgrade(self) -> bool:
165
upgrade = self._get_upgrade()
166
if upgrade == b"websocket" and self._should_upgrade_to_ws():
167
return True
168
if upgrade is not None:
169
self._unsupported_upgrade_warning()
170
return False
171
172
def data_received(self, data: bytes) -> None:
173
self._unset_keepalive_if_required()
174
175
self.conn.receive_data(data)
176
2
34
self.handle_events()
177
178
def handle_events(self) -> None:
179
while True:
180
try:
181
9
21
event = self.conn.next_event()
182
except h11.RemoteProtocolError:
183
msg = "Invalid HTTP request received."
184
self.logger.warning(msg)
185
self.send_400_response(msg)
186
return
187
188
if event is h11.NEED_DATA:
189
break
190
191
elif event is h11.PAUSED:
192
# This case can occur in HTTP pipelining, so we need to
193
# stop reading any more data, and ensure that at the end
194
# of the active request/response cycle we handle any
195
# events that have been buffered up.
196
self.flow.pause_reading()
197
break
198
199
elif isinstance(event, h11.Request):
200
# Pipelined HTTP requests and WebSocket upgrades may be processed after the keep-alive timer is armed.
201
self._unset_keepalive_if_required()
202
203
self.headers = [(key.lower(), value) for key, value in event.headers]
204
raw_path, _, query_string = event.target.partition(b"?")
205
path = unquote(raw_path.decode("ascii"))
206
full_path = self.root_path + path
207
full_raw_path = self.root_path.encode("ascii") + raw_path
208
self.scope = {
209
"type": "http",
210
"asgi": {"version": self.asgi_version, "spec_version": "2.3"},
211
"http_version": event.http_version.decode("ascii"),
212
"server": self.server,
213
"client": self.client,
214
"scheme": self.scheme, # type: ignore[typeddict-item]
215
"method": event.method.decode("ascii"),
216
"root_path": self.root_path,
217
"path": full_path,
218
"raw_path": full_raw_path,
219
"query_string": query_string,
220
"headers": self.headers,
221
"state": self.app_state.copy(),
222
}
223
2
2
if self._should_upgrade():
224
self.handle_websocket_upgrade(event)
225
return
226
227
# Handle 503 responses when 'limit_concurrency' is exceeded.
228
if self.limit_concurrency is not None and (
229
len(self.connections) >= self.limit_concurrency or len(self.tasks) >= self.limit_concurrency
230
):
231
app = service_unavailable
232
message = "Exceeded concurrency limit."
233
self.logger.warning(message)
234
else:
235
app = self.app
236
237
self.cycle = RequestResponseCycle(
238
scope=self.scope,
239
conn=self.conn,
240
transport=self.transport,
241
flow=self.flow,
242
logger=self.logger,
243
access_logger=self.access_logger,
244
access_log=self.access_log,
245
default_headers=self.server_state.default_headers,
246
message_event=asyncio.Event(),
247
on_response=self.on_response_complete,
248
)
249
if self.config.reset_contextvars:
250
# Opt-in workaround for https://github.com/python/cpython/issues/140947:
251
# asyncio can leak context vars between tasks. Hides context set in the
252
# lifespan or by external instrumentation.
253
if sys.version_info >= (3, 11): # pragma: py-lt-311
254
task = self.loop.create_task(self.cycle.run_asgi(app), context=contextvars.Context())
255
else: # pragma: py-gte-311
256
task = contextvars.Context().run(self.loop.create_task, self.cycle.run_asgi(app))
257
else:
258
2
3
task = self.loop.create_task(self.cycle.run_asgi(app))
259
task.add_done_callback(self.tasks.discard)
260
1
1
self.tasks.add(task)
261
262
elif isinstance(event, h11.Data):
263
if self.conn.our_state is h11.DONE:
264
continue
265
3
3
self.cycle.body += event.data
266
if len(self.cycle.body) > HIGH_WATER_LIMIT:
267
2
2
self.flow.pause_reading()
268
self.cycle.message_event.set()
269
270
elif isinstance(event, h11.EndOfMessage):
271
if self.conn.our_state is h11.DONE:
272
self.transport.resume_reading()
273
self.conn.start_next_cycle()
274
continue
275
self.cycle.more_body = False
276
self.cycle.message_event.set()
277
if self.conn.their_state == h11.MUST_CLOSE:
278
break
279
280
def handle_websocket_upgrade(self, event: h11.Request) -> None:
281
if self.logger.level <= TRACE_LOG_LEVEL: # pragma: full coverage
282
prefix = "%s:%d - " % self.client if self.client else ""
283
self.logger.log(TRACE_LOG_LEVEL, "%sUpgrading to WebSocket", prefix)
284
285
self.connections.discard(self)
286
output = [event.method, b" ", event.target, b" HTTP/1.1\r\n"]
287
for name, value in self.headers:
288
output += [name, b": ", value, b"\r\n"]
289
output.append(b"\r\n")
290
protocol = self.ws_protocol_class( # type: ignore[call-arg, misc]
291
config=self.config,
292
server_state=self.server_state,
293
app_state=self.app_state,
294
)
295
protocol.connection_made(self.transport)
296
protocol.data_received(b"".join(output))
297
self.transport.set_protocol(protocol)
298
299
def send_400_response(self, msg: str) -> None:
300
reason = STATUS_PHRASES[400]
301
headers: list[tuple[bytes, bytes]] = [
302
(b"content-type", b"text/plain; charset=utf-8"),
303
(b"connection", b"close"),
304
]
305
event = h11.Response(status_code=400, headers=headers, reason=reason)
306
output = self.conn.send(event)
307
self.transport.write(output)
308
309
output = self.conn.send(event=h11.Data(data=msg.encode("ascii")))
310
self.transport.write(output)
311
312
output = self.conn.send(event=h11.EndOfMessage())
313
self.transport.write(output)
314
315
self.transport.close()
316
317
def on_response_complete(self) -> None:
318
self.server_state.total_requests += 1
319
320
if self.transport.is_closing():
321
return
322
323
# Set a short Keep-Alive timeout.
324
self._unset_keepalive_if_required()
325
326
self.timeout_keep_alive_task = self.loop.call_later(self.timeout_keep_alive, self.timeout_keep_alive_handler)
327
328
# Unpause data reads if needed.
329
self.flow.resume_reading()
330
331
# Unblock any pipelined events.
332
if self.conn.our_state is h11.DONE and self.conn.their_state is h11.DONE:
333
self.conn.start_next_cycle()
334
self.handle_events()
335
336
def shutdown(self) -> None:
337
"""
338
Called by the server to commence a graceful shutdown.
339
"""
340
if self.cycle is None or self.cycle.response_complete:
341
event = h11.ConnectionClosed()
342
self.conn.send(event)
343
self.transport.close()
344
else:
345
self.cycle.keep_alive = False
346
347
def pause_writing(self) -> None:
348
"""
349
Called by the transport when the write buffer exceeds the high water mark.
350
"""
351
self.flow.pause_writing() # pragma: full coverage
352
353
def resume_writing(self) -> None:
354
"""
355
Called by the transport when the write buffer drops below the low water mark.
356
"""
357
self.flow.resume_writing() # pragma: full coverage
358
359
def timeout_keep_alive_handler(self) -> None:
360
"""
361
Called on a keep-alive connection if no new data is received after a short
362
delay.
363
"""
364
if not self.transport.is_closing():
365
event = h11.ConnectionClosed()
366
self.conn.send(event)
367
self.transport.close()
368
369
370
class RequestResponseCycle:
371
def __init__(
372
self,
373
scope: HTTPScope,
374
conn: h11.Connection,
375
transport: asyncio.Transport,
376
flow: FlowControl,
377
logger: logging.Logger,
378
access_logger: logging.Logger,
379
access_log: bool,
380
default_headers: list[tuple[bytes, bytes]],
381
message_event: asyncio.Event,
382
on_response: Callable[..., None],
383
) -> None:
384
self.scope = scope
385
self.conn = conn
386
self.transport = transport
387
self.flow = flow
388
self.logger = logger
389
self.access_logger = access_logger
390
self.access_log = access_log
391
self.default_headers = default_headers
392
self.message_event = message_event
393
self.on_response = on_response
394
395
# Connection state
396
self.disconnected = False
397
self.keep_alive = True
398
self.waiting_for_100_continue = conn.they_are_waiting_for_100_continue
399
400
# Request state
401
self.body = bytearray()
402
self.more_body = True
403
404
# Response state
405
self.response_started = False
406
self.response_complete = False
407
408
# ASGI exception wrapper
409
async def run_asgi(self, app: ASGI3Application) -> None:
410
try:
411
1
296
result = await app( # type: ignore[func-returns-value]
412
self.scope, self.receive, self.send
413
)
414
except BaseException as exc:
415
msg = "Exception in ASGI application\n"
416
self.logger.error(msg, exc_info=exc)
417
if not self.response_started:
418
await self.send_500_response()
419
else:
420
self.transport.close()
421
else:
422
if result is not None:
423
msg = "ASGI callable should return None, but returned '%s'."
424
self.logger.error(msg, result)
425
self.transport.close()
426
elif not self.response_started and not self.disconnected:
427
msg = "ASGI callable returned without starting response."
428
self.logger.error(msg)
429
await self.send_500_response()
430
elif not self.response_complete and not self.disconnected:
431
msg = "ASGI callable returned without completing response."
432
self.logger.error(msg)
433
self.transport.close()
434
finally:
435
self.on_response = lambda: None
436
437
async def send_500_response(self) -> None:
438
response_start_event: HTTPResponseStartEvent = {
439
"type": "http.response.start",
440
"status": 500,
441
"headers": [
442
(b"content-type", b"text/plain; charset=utf-8"),
443
(b"connection", b"close"),
444
],
445
}
446
await self.send(response_start_event)
447
response_body_event: HTTPResponseBodyEvent = {
448
"type": "http.response.body",
449
"body": b"Internal Server Error",
450
"more_body": False,
451
}
452
await self.send(response_body_event)
453
454
# ASGI interface
455
3
3
async def send(self, message: ASGISendEvent) -> None:
456
if self.flow.write_paused and not self.disconnected:
457
await self.flow.drain() # pragma: full coverage
458
459
if self.disconnected:
460
return # pragma: full coverage
461
462
if not self.response_started:
463
# Sending response status line and headers
464
if message["type"] != "http.response.start":
465
raise RuntimeError(f"Expected ASGI message 'http.response.start', but got '{message['type']}'.")
466
467
self.response_started = True
468
self.waiting_for_100_continue = False
469
470
status = message["status"]
471
headers = self.default_headers + list(message.get("headers", []))
472
473
if CLOSE_HEADER in self.scope["headers"] and CLOSE_HEADER not in headers:
474
headers = headers + [CLOSE_HEADER]
475
476
if self.access_log:
477
self.access_logger.info(
478
'%s - "%s %s HTTP/%s" %d',
479
get_client_addr(self.scope),
480
self.scope["method"],
481
get_path_with_query_string(self.scope),
482
self.scope["http_version"],
483
status,
484
)
485
486
# Write response status line and headers
487
reason = STATUS_PHRASES[status]
488
4
13
response = h11.Response(status_code=status, headers=headers, reason=reason)
489
39
output = self.conn.send(event=response)
490
1
3
self.transport.write(output)
491
492
elif not self.response_complete:
493
# Sending response body
494
1
if message["type"] != "http.response.body":
495
raise RuntimeError(f"Expected ASGI message 'http.response.body', but got '{message['type']}'.")
496
497
body = message.get("body", b"")
498
more_body = message.get("more_body", False)
499
500
# Write response body
501
data = b"" if self.scope["method"] == "HEAD" else body
502
1
output = self.conn.send(event=h11.Data(data=data))
503
1
29
self.transport.write(output)
504
505
# Handle response completion
506
if not more_body:
507
self.response_complete = True
508
self.message_event.set()
509
output = self.conn.send(event=h11.EndOfMessage())
510
self.transport.write(output)
511
512
else:
513
# Response already sent
514
raise RuntimeError(f"Unexpected ASGI message '{message['type']}' sent, after response already completed.")
515
516
if self.response_complete:
517
if self.conn.our_state is h11.MUST_CLOSE or not self.keep_alive:
518
self.conn.send(event=h11.ConnectionClosed())
519
self.transport.close()
520
self.on_response()
521
522
async def receive(self) -> ASGIReceiveEvent:
523
if self.waiting_for_100_continue and not self.transport.is_closing():
524
headers: list[tuple[str, str]] = []
525
event = h11.InformationalResponse(status_code=100, headers=headers, reason="Continue")
526
output = self.conn.send(event=event)
527
self.transport.write(output)
528
self.waiting_for_100_continue = False
529
530
if not self.disconnected and not self.response_complete:
531
self.flow.resume_reading()
532
await self.message_event.wait()
533
self.message_event.clear()
534
535
if self.disconnected or self.response_complete:
536
return {"type": "http.disconnect"}
537
538
1
1
message: HTTPRequestEvent = {"type": "http.request", "body": bytes(self.body), "more_body": self.more_body}
539
self.body = bytearray()
540
return message