uvicorn 0.27.1__py3-none-any.whl → 0.28.1__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.
- uvicorn/__init__.py +1 -1
- uvicorn/_subprocess.py +3 -3
- uvicorn/_types.py +5 -11
- uvicorn/config.py +17 -57
- uvicorn/importer.py +2 -6
- uvicorn/lifespan/off.py +4 -2
- uvicorn/lifespan/on.py +8 -8
- uvicorn/logging.py +3 -5
- uvicorn/main.py +12 -24
- uvicorn/middleware/asgi2.py +1 -3
- uvicorn/middleware/proxy_headers.py +12 -27
- uvicorn/middleware/wsgi.py +17 -24
- uvicorn/protocols/http/flow_control.py +1 -3
- uvicorn/protocols/http/h11_impl.py +12 -19
- uvicorn/protocols/http/httptools_impl.py +17 -36
- uvicorn/protocols/utils.py +1 -3
- uvicorn/protocols/websockets/auto.py +3 -1
- uvicorn/protocols/websockets/websockets_impl.py +16 -45
- uvicorn/protocols/websockets/wsproto_impl.py +10 -30
- uvicorn/server.py +5 -17
- uvicorn/supervisors/__init__.py +4 -2
- uvicorn/supervisors/basereload.py +4 -10
- uvicorn/supervisors/multiprocess.py +5 -11
- uvicorn/supervisors/statreload.py +1 -4
- uvicorn/supervisors/watchfilesreload.py +2 -10
- uvicorn/supervisors/watchgodreload.py +7 -18
- uvicorn/workers.py +6 -4
- {uvicorn-0.27.1.dist-info → uvicorn-0.28.1.dist-info}/METADATA +2 -2
- uvicorn-0.28.1.dist-info/RECORD +45 -0
- {uvicorn-0.27.1.dist-info → uvicorn-0.28.1.dist-info}/WHEEL +1 -1
- uvicorn-0.27.1.dist-info/RECORD +0 -45
- {uvicorn-0.27.1.dist-info → uvicorn-0.28.1.dist-info}/entry_points.txt +0 -0
- {uvicorn-0.27.1.dist-info → uvicorn-0.28.1.dist-info}/licenses/LICENSE.md +0 -0
@@ -43,9 +43,7 @@ def _get_status_phrase(status_code: int) -> bytes:
|
|
43
43
|
return b""
|
44
44
|
|
45
45
|
|
46
|
-
STATUS_PHRASES = {
|
47
|
-
status_code: _get_status_phrase(status_code) for status_code in range(100, 600)
|
48
|
-
}
|
46
|
+
STATUS_PHRASES = {status_code: _get_status_phrase(status_code) for status_code in range(100, 600)}
|
49
47
|
|
50
48
|
|
51
49
|
class H11Protocol(asyncio.Protocol):
|
@@ -205,7 +203,7 @@ class H11Protocol(asyncio.Protocol):
|
|
205
203
|
"type": "http",
|
206
204
|
"asgi": {
|
207
205
|
"version": self.config.asgi_version,
|
208
|
-
"spec_version": "2.
|
206
|
+
"spec_version": "2.4",
|
209
207
|
},
|
210
208
|
"http_version": event.http_version.decode("ascii"),
|
211
209
|
"server": self.server,
|
@@ -227,8 +225,7 @@ class H11Protocol(asyncio.Protocol):
|
|
227
225
|
|
228
226
|
# Handle 503 responses when 'limit_concurrency' is exceeded.
|
229
227
|
if self.limit_concurrency is not None and (
|
230
|
-
len(self.connections) >= self.limit_concurrency
|
231
|
-
or len(self.tasks) >= self.limit_concurrency
|
228
|
+
len(self.connections) >= self.limit_concurrency or len(self.tasks) >= self.limit_concurrency
|
232
229
|
):
|
233
230
|
app = service_unavailable
|
234
231
|
message = "Exceeded concurrency limit."
|
@@ -322,9 +319,7 @@ class H11Protocol(asyncio.Protocol):
|
|
322
319
|
# Set a short Keep-Alive timeout.
|
323
320
|
self._unset_keepalive_if_required()
|
324
321
|
|
325
|
-
self.timeout_keep_alive_task = self.loop.call_later(
|
326
|
-
self.timeout_keep_alive, self.timeout_keep_alive_handler
|
327
|
-
)
|
322
|
+
self.timeout_keep_alive_task = self.loop.call_later(self.timeout_keep_alive, self.timeout_keep_alive_handler)
|
328
323
|
|
329
324
|
# Unpause data reads if needed.
|
330
325
|
self.flow.resume_reading()
|
@@ -371,7 +366,7 @@ class H11Protocol(asyncio.Protocol):
|
|
371
366
|
class RequestResponseCycle:
|
372
367
|
def __init__(
|
373
368
|
self,
|
374
|
-
scope:
|
369
|
+
scope: HTTPScope,
|
375
370
|
conn: h11.Connection,
|
376
371
|
transport: asyncio.Transport,
|
377
372
|
flow: FlowControl,
|
@@ -407,7 +402,7 @@ class RequestResponseCycle:
|
|
407
402
|
self.response_complete = False
|
408
403
|
|
409
404
|
# ASGI exception wrapper
|
410
|
-
async def run_asgi(self, app:
|
405
|
+
async def run_asgi(self, app: ASGI3Application) -> None:
|
411
406
|
try:
|
412
407
|
result = await app( # type: ignore[func-returns-value]
|
413
408
|
self.scope, self.receive, self.send
|
@@ -436,7 +431,7 @@ class RequestResponseCycle:
|
|
436
431
|
self.on_response = lambda: None
|
437
432
|
|
438
433
|
async def send_500_response(self) -> None:
|
439
|
-
response_start_event:
|
434
|
+
response_start_event: HTTPResponseStartEvent = {
|
440
435
|
"type": "http.response.start",
|
441
436
|
"status": 500,
|
442
437
|
"headers": [
|
@@ -445,7 +440,7 @@ class RequestResponseCycle:
|
|
445
440
|
],
|
446
441
|
}
|
447
442
|
await self.send(response_start_event)
|
448
|
-
response_body_event:
|
443
|
+
response_body_event: HTTPResponseBodyEvent = {
|
449
444
|
"type": "http.response.body",
|
450
445
|
"body": b"Internal Server Error",
|
451
446
|
"more_body": False,
|
@@ -453,7 +448,7 @@ class RequestResponseCycle:
|
|
453
448
|
await self.send(response_body_event)
|
454
449
|
|
455
450
|
# ASGI interface
|
456
|
-
async def send(self, message:
|
451
|
+
async def send(self, message: ASGISendEvent) -> None:
|
457
452
|
message_type = message["type"]
|
458
453
|
|
459
454
|
if self.flow.write_paused and not self.disconnected:
|
@@ -527,12 +522,10 @@ class RequestResponseCycle:
|
|
527
522
|
self.transport.close()
|
528
523
|
self.on_response()
|
529
524
|
|
530
|
-
async def receive(self) ->
|
525
|
+
async def receive(self) -> ASGIReceiveEvent:
|
531
526
|
if self.waiting_for_100_continue and not self.transport.is_closing():
|
532
527
|
headers: list[tuple[str, str]] = []
|
533
|
-
event = h11.InformationalResponse(
|
534
|
-
status_code=100, headers=headers, reason="Continue"
|
535
|
-
)
|
528
|
+
event = h11.InformationalResponse(status_code=100, headers=headers, reason="Continue")
|
536
529
|
output = self.conn.send(event=event)
|
537
530
|
self.transport.write(output)
|
538
531
|
self.waiting_for_100_continue = False
|
@@ -545,7 +538,7 @@ class RequestResponseCycle:
|
|
545
538
|
if self.disconnected or self.response_complete:
|
546
539
|
return {"type": "http.disconnect"}
|
547
540
|
|
548
|
-
message:
|
541
|
+
message: HTTPRequestEvent = {
|
549
542
|
"type": "http.request",
|
550
543
|
"body": self.body,
|
551
544
|
"more_body": self.more_body,
|
@@ -7,7 +7,7 @@ import re
|
|
7
7
|
import urllib
|
8
8
|
from asyncio.events import TimerHandle
|
9
9
|
from collections import deque
|
10
|
-
from typing import Any, Callable,
|
10
|
+
from typing import Any, Callable, Literal, cast
|
11
11
|
|
12
12
|
import httptools
|
13
13
|
|
@@ -15,7 +15,6 @@ from uvicorn._types import (
|
|
15
15
|
ASGI3Application,
|
16
16
|
ASGIReceiveEvent,
|
17
17
|
ASGISendEvent,
|
18
|
-
HTTPDisconnectEvent,
|
19
18
|
HTTPRequestEvent,
|
20
19
|
HTTPResponseBodyEvent,
|
21
20
|
HTTPResponseStartEvent,
|
@@ -50,9 +49,7 @@ def _get_status_line(status_code: int) -> bytes:
|
|
50
49
|
return b"".join([b"HTTP/1.1 ", str(status_code).encode(), b" ", phrase, b"\r\n"])
|
51
50
|
|
52
51
|
|
53
|
-
STATUS_LINE = {
|
54
|
-
status_code: _get_status_line(status_code) for status_code in range(100, 600)
|
55
|
-
}
|
52
|
+
STATUS_LINE = {status_code: _get_status_line(status_code) for status_code in range(100, 600)}
|
56
53
|
|
57
54
|
|
58
55
|
class HttpToolsProtocol(asyncio.Protocol):
|
@@ -93,7 +90,7 @@ class HttpToolsProtocol(asyncio.Protocol):
|
|
93
90
|
self.server: tuple[str, int] | None = None
|
94
91
|
self.client: tuple[str, int] | None = None
|
95
92
|
self.scheme: Literal["http", "https"] | None = None
|
96
|
-
self.pipeline:
|
93
|
+
self.pipeline: deque[tuple[RequestResponseCycle, ASGI3Application]] = deque()
|
97
94
|
|
98
95
|
# Per-request state
|
99
96
|
self.scope: HTTPScope = None # type: ignore[assignment]
|
@@ -227,7 +224,7 @@ class HttpToolsProtocol(asyncio.Protocol):
|
|
227
224
|
self.headers = []
|
228
225
|
self.scope = { # type: ignore[typeddict-item]
|
229
226
|
"type": "http",
|
230
|
-
"asgi": {"version": self.config.asgi_version, "spec_version": "2.
|
227
|
+
"asgi": {"version": self.config.asgi_version, "spec_version": "2.4"},
|
231
228
|
"http_version": "1.1",
|
232
229
|
"server": self.server,
|
233
230
|
"client": self.client,
|
@@ -268,8 +265,7 @@ class HttpToolsProtocol(asyncio.Protocol):
|
|
268
265
|
|
269
266
|
# Handle 503 responses when 'limit_concurrency' is exceeded.
|
270
267
|
if self.limit_concurrency is not None and (
|
271
|
-
len(self.connections) >= self.limit_concurrency
|
272
|
-
or len(self.tasks) >= self.limit_concurrency
|
268
|
+
len(self.connections) >= self.limit_concurrency or len(self.tasks) >= self.limit_concurrency
|
273
269
|
):
|
274
270
|
app = service_unavailable
|
275
271
|
message = "Exceeded concurrency limit."
|
@@ -302,9 +298,7 @@ class HttpToolsProtocol(asyncio.Protocol):
|
|
302
298
|
self.pipeline.appendleft((self.cycle, app))
|
303
299
|
|
304
300
|
def on_body(self, body: bytes) -> None:
|
305
|
-
if (
|
306
|
-
self.parser.should_upgrade() and self._should_upgrade()
|
307
|
-
) or self.cycle.response_complete:
|
301
|
+
if (self.parser.should_upgrade() and self._should_upgrade()) or self.cycle.response_complete:
|
308
302
|
return
|
309
303
|
self.cycle.body += body
|
310
304
|
if len(self.cycle.body) > HIGH_WATER_LIMIT:
|
@@ -312,9 +306,7 @@ class HttpToolsProtocol(asyncio.Protocol):
|
|
312
306
|
self.cycle.message_event.set()
|
313
307
|
|
314
308
|
def on_message_complete(self) -> None:
|
315
|
-
if (
|
316
|
-
self.parser.should_upgrade() and self._should_upgrade()
|
317
|
-
) or self.cycle.response_complete:
|
309
|
+
if (self.parser.should_upgrade() and self._should_upgrade()) or self.cycle.response_complete:
|
318
310
|
return
|
319
311
|
self.cycle.more_body = False
|
320
312
|
self.cycle.message_event.set()
|
@@ -376,7 +368,7 @@ class HttpToolsProtocol(asyncio.Protocol):
|
|
376
368
|
class RequestResponseCycle:
|
377
369
|
def __init__(
|
378
370
|
self,
|
379
|
-
scope:
|
371
|
+
scope: HTTPScope,
|
380
372
|
transport: asyncio.Transport,
|
381
373
|
flow: FlowControl,
|
382
374
|
logger: logging.Logger,
|
@@ -414,7 +406,7 @@ class RequestResponseCycle:
|
|
414
406
|
self.expected_content_length = 0
|
415
407
|
|
416
408
|
# ASGI exception wrapper
|
417
|
-
async def run_asgi(self, app:
|
409
|
+
async def run_asgi(self, app: ASGI3Application) -> None:
|
418
410
|
try:
|
419
411
|
result = await app( # type: ignore[func-returns-value]
|
420
412
|
self.scope, self.receive, self.send
|
@@ -443,7 +435,7 @@ class RequestResponseCycle:
|
|
443
435
|
self.on_response = lambda: None
|
444
436
|
|
445
437
|
async def send_500_response(self) -> None:
|
446
|
-
response_start_event:
|
438
|
+
response_start_event: HTTPResponseStartEvent = {
|
447
439
|
"type": "http.response.start",
|
448
440
|
"status": 500,
|
449
441
|
"headers": [
|
@@ -452,7 +444,7 @@ class RequestResponseCycle:
|
|
452
444
|
],
|
453
445
|
}
|
454
446
|
await self.send(response_start_event)
|
455
|
-
response_body_event:
|
447
|
+
response_body_event: HTTPResponseBodyEvent = {
|
456
448
|
"type": "http.response.body",
|
457
449
|
"body": b"Internal Server Error",
|
458
450
|
"more_body": False,
|
@@ -460,7 +452,7 @@ class RequestResponseCycle:
|
|
460
452
|
await self.send(response_body_event)
|
461
453
|
|
462
454
|
# ASGI interface
|
463
|
-
async def send(self, message:
|
455
|
+
async def send(self, message: ASGISendEvent) -> None:
|
464
456
|
message_type = message["type"]
|
465
457
|
|
466
458
|
if self.flow.write_paused and not self.disconnected:
|
@@ -515,11 +507,7 @@ class RequestResponseCycle:
|
|
515
507
|
self.keep_alive = False
|
516
508
|
content.extend([name, b": ", value, b"\r\n"])
|
517
509
|
|
518
|
-
if (
|
519
|
-
self.chunked_encoding is None
|
520
|
-
and self.scope["method"] != "HEAD"
|
521
|
-
and status_code not in (204, 304)
|
522
|
-
):
|
510
|
+
if self.chunked_encoding is None and self.scope["method"] != "HEAD" and status_code not in (204, 304):
|
523
511
|
# Neither content-length nor transfer-encoding specified
|
524
512
|
self.chunked_encoding = True
|
525
513
|
content.append(b"transfer-encoding: chunked\r\n")
|
@@ -570,7 +558,7 @@ class RequestResponseCycle:
|
|
570
558
|
msg = "Unexpected ASGI message '%s' sent, after response already completed."
|
571
559
|
raise RuntimeError(msg % message_type)
|
572
560
|
|
573
|
-
async def receive(self) ->
|
561
|
+
async def receive(self) -> ASGIReceiveEvent:
|
574
562
|
if self.waiting_for_100_continue and not self.transport.is_closing():
|
575
563
|
self.transport.write(b"HTTP/1.1 100 Continue\r\n\r\n")
|
576
564
|
self.waiting_for_100_continue = False
|
@@ -580,15 +568,8 @@ class RequestResponseCycle:
|
|
580
568
|
await self.message_event.wait()
|
581
569
|
self.message_event.clear()
|
582
570
|
|
583
|
-
message: HTTPDisconnectEvent | HTTPRequestEvent
|
584
571
|
if self.disconnected or self.response_complete:
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
"type": "http.request",
|
589
|
-
"body": self.body,
|
590
|
-
"more_body": self.more_body,
|
591
|
-
}
|
592
|
-
self.body = b""
|
593
|
-
|
572
|
+
return {"type": "http.disconnect"}
|
573
|
+
message: HTTPRequestEvent = {"type": "http.request", "body": self.body, "more_body": self.more_body}
|
574
|
+
self.body = b""
|
594
575
|
return message
|
uvicorn/protocols/utils.py
CHANGED
@@ -53,7 +53,5 @@ def get_client_addr(scope: WWWScope) -> str:
|
|
53
53
|
def get_path_with_query_string(scope: WWWScope) -> str:
|
54
54
|
path_with_query_string = urllib.parse.quote(scope["path"])
|
55
55
|
if scope["query_string"]:
|
56
|
-
path_with_query_string = "{}?{}".format(
|
57
|
-
path_with_query_string, scope["query_string"].decode("ascii")
|
58
|
-
)
|
56
|
+
path_with_query_string = "{}?{}".format(path_with_query_string, scope["query_string"].decode("ascii"))
|
59
57
|
return path_with_query_string
|
@@ -1,7 +1,9 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import asyncio
|
2
4
|
import typing
|
3
5
|
|
4
|
-
AutoWebSocketsProtocol: typing.
|
6
|
+
AutoWebSocketsProtocol: typing.Callable[..., asyncio.Protocol] | None
|
5
7
|
try:
|
6
8
|
import websockets # noqa
|
7
9
|
except ImportError: # pragma: no cover
|
@@ -3,16 +3,7 @@ from __future__ import annotations
|
|
3
3
|
import asyncio
|
4
4
|
import http
|
5
5
|
import logging
|
6
|
-
from typing import
|
7
|
-
Any,
|
8
|
-
List,
|
9
|
-
Literal,
|
10
|
-
Optional,
|
11
|
-
Sequence,
|
12
|
-
Tuple,
|
13
|
-
Union,
|
14
|
-
cast,
|
15
|
-
)
|
6
|
+
from typing import Any, Literal, Optional, Sequence, cast
|
16
7
|
from urllib.parse import unquote
|
17
8
|
|
18
9
|
import websockets
|
@@ -61,7 +52,7 @@ class Server:
|
|
61
52
|
|
62
53
|
|
63
54
|
class WebSocketProtocol(WebSocketServerProtocol):
|
64
|
-
extra_headers:
|
55
|
+
extra_headers: list[tuple[str, str]]
|
65
56
|
|
66
57
|
def __init__(
|
67
58
|
self,
|
@@ -117,8 +108,7 @@ class WebSocketProtocol(WebSocketServerProtocol):
|
|
117
108
|
)
|
118
109
|
self.server_header = None
|
119
110
|
self.extra_headers = [
|
120
|
-
(name.decode("latin-1"), value.decode("latin-1"))
|
121
|
-
for name, value in server_state.default_headers
|
111
|
+
(name.decode("latin-1"), value.decode("latin-1")) for name, value in server_state.default_headers
|
122
112
|
]
|
123
113
|
|
124
114
|
def connection_made( # type: ignore[override]
|
@@ -136,16 +126,14 @@ class WebSocketProtocol(WebSocketServerProtocol):
|
|
136
126
|
|
137
127
|
super().connection_made(transport)
|
138
128
|
|
139
|
-
def connection_lost(self, exc:
|
129
|
+
def connection_lost(self, exc: Exception | None) -> None:
|
140
130
|
self.connections.remove(self)
|
141
131
|
|
142
132
|
if self.logger.isEnabledFor(TRACE_LOG_LEVEL):
|
143
133
|
prefix = "%s:%d - " % self.client if self.client else ""
|
144
134
|
self.logger.log(TRACE_LOG_LEVEL, "%sWebSocket connection lost", prefix)
|
145
135
|
|
146
|
-
self.lost_connection_before_handshake = (
|
147
|
-
not self.handshake_completed_event.is_set()
|
148
|
-
)
|
136
|
+
self.lost_connection_before_handshake = not self.handshake_completed_event.is_set()
|
149
137
|
self.handshake_completed_event.set()
|
150
138
|
super().connection_lost(exc)
|
151
139
|
if exc is None:
|
@@ -162,9 +150,7 @@ class WebSocketProtocol(WebSocketServerProtocol):
|
|
162
150
|
def on_task_complete(self, task: asyncio.Task) -> None:
|
163
151
|
self.tasks.discard(task)
|
164
152
|
|
165
|
-
async def process_request(
|
166
|
-
self, path: str, headers: Headers
|
167
|
-
) -> Optional[HTTPResponse]:
|
153
|
+
async def process_request(self, path: str, headers: Headers) -> HTTPResponse | None:
|
168
154
|
"""
|
169
155
|
This hook is called to determine if the websocket should return
|
170
156
|
an HTTP response and close.
|
@@ -212,8 +198,8 @@ class WebSocketProtocol(WebSocketServerProtocol):
|
|
212
198
|
return self.initial_response
|
213
199
|
|
214
200
|
def process_subprotocol(
|
215
|
-
self, headers: Headers, available_subprotocols:
|
216
|
-
) ->
|
201
|
+
self, headers: Headers, available_subprotocols: Sequence[Subprotocol] | None
|
202
|
+
) -> Subprotocol | None:
|
217
203
|
"""
|
218
204
|
We override the standard 'process_subprotocol' behavior here so that
|
219
205
|
we return whatever subprotocol is sent in the 'accept' message.
|
@@ -223,8 +209,7 @@ class WebSocketProtocol(WebSocketServerProtocol):
|
|
223
209
|
def send_500_response(self) -> None:
|
224
210
|
msg = b"Internal Server Error"
|
225
211
|
content = [
|
226
|
-
b"HTTP/1.1 500 Internal Server Error\r\n"
|
227
|
-
b"content-type: text/plain; charset=utf-8\r\n",
|
212
|
+
b"HTTP/1.1 500 Internal Server Error\r\n" b"content-type: text/plain; charset=utf-8\r\n",
|
228
213
|
b"content-length: " + str(len(msg)).encode("ascii") + b"\r\n",
|
229
214
|
b"connection: close\r\n",
|
230
215
|
b"\r\n",
|
@@ -278,7 +263,7 @@ class WebSocketProtocol(WebSocketServerProtocol):
|
|
278
263
|
await self.handshake_completed_event.wait()
|
279
264
|
self.transport.close()
|
280
265
|
|
281
|
-
async def asgi_send(self, message:
|
266
|
+
async def asgi_send(self, message: ASGISendEvent) -> None:
|
282
267
|
message_type = message["type"]
|
283
268
|
|
284
269
|
if not self.handshake_started_event.is_set():
|
@@ -290,9 +275,7 @@ class WebSocketProtocol(WebSocketServerProtocol):
|
|
290
275
|
get_path_with_query_string(self.scope),
|
291
276
|
)
|
292
277
|
self.initial_response = None
|
293
|
-
self.accepted_subprotocol = cast(
|
294
|
-
Optional[Subprotocol], message.get("subprotocol")
|
295
|
-
)
|
278
|
+
self.accepted_subprotocol = cast(Optional[Subprotocol], message.get("subprotocol"))
|
296
279
|
if "headers" in message:
|
297
280
|
self.extra_headers.extend(
|
298
281
|
# ASGI spec requires bytes
|
@@ -324,8 +307,7 @@ class WebSocketProtocol(WebSocketServerProtocol):
|
|
324
307
|
# websockets requires the status to be an enum. look it up.
|
325
308
|
status = http.HTTPStatus(message["status"])
|
326
309
|
headers = [
|
327
|
-
(name.decode("latin-1"), value.decode("latin-1"))
|
328
|
-
for name, value in message.get("headers", [])
|
310
|
+
(name.decode("latin-1"), value.decode("latin-1")) for name, value in message.get("headers", [])
|
329
311
|
]
|
330
312
|
self.initial_response = (status, headers, b"")
|
331
313
|
self.handshake_started_event.set()
|
@@ -356,10 +338,7 @@ class WebSocketProtocol(WebSocketServerProtocol):
|
|
356
338
|
self.closed_event.set()
|
357
339
|
|
358
340
|
else:
|
359
|
-
msg =
|
360
|
-
"Expected ASGI message 'websocket.send' or 'websocket.close',"
|
361
|
-
" but got '%s'."
|
362
|
-
)
|
341
|
+
msg = "Expected ASGI message 'websocket.send' or 'websocket.close'," " but got '%s'."
|
363
342
|
raise RuntimeError(msg % message_type)
|
364
343
|
except ConnectionClosed as exc:
|
365
344
|
raise ClientDisconnected from exc
|
@@ -372,24 +351,16 @@ class WebSocketProtocol(WebSocketServerProtocol):
|
|
372
351
|
if not message.get("more_body", False):
|
373
352
|
self.closed_event.set()
|
374
353
|
else:
|
375
|
-
msg =
|
376
|
-
"Expected ASGI message 'websocket.http.response.body' "
|
377
|
-
"but got '%s'."
|
378
|
-
)
|
354
|
+
msg = "Expected ASGI message 'websocket.http.response.body' " "but got '%s'."
|
379
355
|
raise RuntimeError(msg % message_type)
|
380
356
|
|
381
357
|
else:
|
382
|
-
msg =
|
383
|
-
"Unexpected ASGI message '%s', after sending 'websocket.close' "
|
384
|
-
"or response already completed."
|
385
|
-
)
|
358
|
+
msg = "Unexpected ASGI message '%s', after sending 'websocket.close' " "or response already completed."
|
386
359
|
raise RuntimeError(msg % message_type)
|
387
360
|
|
388
361
|
async def asgi_receive(
|
389
362
|
self,
|
390
|
-
) ->
|
391
|
-
"WebSocketDisconnectEvent", "WebSocketConnectEvent", "WebSocketReceiveEvent"
|
392
|
-
]:
|
363
|
+
) -> WebSocketDisconnectEvent | WebSocketConnectEvent | WebSocketReceiveEvent:
|
393
364
|
if not self.connect_sent:
|
394
365
|
self.connect_sent = True
|
395
366
|
return {"type": "websocket.connect"}
|
@@ -168,7 +168,7 @@ class WSProtocol(asyncio.Protocol):
|
|
168
168
|
path = unquote(raw_path)
|
169
169
|
full_path = self.root_path + path
|
170
170
|
full_raw_path = self.root_path.encode("ascii") + raw_path.encode("ascii")
|
171
|
-
self.scope:
|
171
|
+
self.scope: WebSocketScope = {
|
172
172
|
"type": "websocket",
|
173
173
|
"asgi": {"version": self.config.asgi_version, "spec_version": "2.4"},
|
174
174
|
"http_version": "1.1",
|
@@ -224,14 +224,8 @@ class WSProtocol(asyncio.Protocol):
|
|
224
224
|
(b"content-type", b"text/plain; charset=utf-8"),
|
225
225
|
(b"connection", b"close"),
|
226
226
|
]
|
227
|
-
output = self.conn.send(
|
228
|
-
|
229
|
-
status_code=500, headers=headers, has_body=True
|
230
|
-
)
|
231
|
-
)
|
232
|
-
output += self.conn.send(
|
233
|
-
wsproto.events.RejectData(data=b"Internal Server Error")
|
234
|
-
)
|
227
|
+
output = self.conn.send(wsproto.events.RejectConnection(status_code=500, headers=headers, has_body=True))
|
228
|
+
output += self.conn.send(wsproto.events.RejectData(data=b"Internal Server Error"))
|
235
229
|
self.transport.write(output)
|
236
230
|
|
237
231
|
async def run_asgi(self) -> None:
|
@@ -269,7 +263,7 @@ class WSProtocol(asyncio.Protocol):
|
|
269
263
|
)
|
270
264
|
subprotocol = message.get("subprotocol")
|
271
265
|
extra_headers = self.default_headers + list(message.get("headers", []))
|
272
|
-
extensions:
|
266
|
+
extensions: list[Extension] = []
|
273
267
|
if self.config.ws_per_message_deflate:
|
274
268
|
extensions.append(PerMessageDeflate())
|
275
269
|
if not self.transport.is_closing():
|
@@ -343,21 +337,14 @@ class WSProtocol(asyncio.Protocol):
|
|
343
337
|
self.close_sent = True
|
344
338
|
code = message.get("code", 1000)
|
345
339
|
reason = message.get("reason", "") or ""
|
346
|
-
self.queue.put_nowait(
|
347
|
-
|
348
|
-
)
|
349
|
-
output = self.conn.send(
|
350
|
-
wsproto.events.CloseConnection(code=code, reason=reason)
|
351
|
-
)
|
340
|
+
self.queue.put_nowait({"type": "websocket.disconnect", "code": code})
|
341
|
+
output = self.conn.send(wsproto.events.CloseConnection(code=code, reason=reason))
|
352
342
|
if not self.transport.is_closing():
|
353
343
|
self.transport.write(output)
|
354
344
|
self.transport.close()
|
355
345
|
|
356
346
|
else:
|
357
|
-
msg =
|
358
|
-
"Expected ASGI message 'websocket.send' or 'websocket.close',"
|
359
|
-
" but got '%s'."
|
360
|
-
)
|
347
|
+
msg = "Expected ASGI message 'websocket.send' or 'websocket.close'," " but got '%s'."
|
361
348
|
raise RuntimeError(msg % message_type)
|
362
349
|
except LocalProtocolError as exc:
|
363
350
|
raise ClientDisconnected from exc
|
@@ -365,24 +352,17 @@ class WSProtocol(asyncio.Protocol):
|
|
365
352
|
if message_type == "websocket.http.response.body":
|
366
353
|
message = typing.cast("WebSocketResponseBodyEvent", message)
|
367
354
|
body_finished = not message.get("more_body", False)
|
368
|
-
reject_data = events.RejectData(
|
369
|
-
data=message["body"], body_finished=body_finished
|
370
|
-
)
|
355
|
+
reject_data = events.RejectData(data=message["body"], body_finished=body_finished)
|
371
356
|
output = self.conn.send(reject_data)
|
372
357
|
self.transport.write(output)
|
373
358
|
|
374
359
|
if body_finished:
|
375
|
-
self.queue.put_nowait(
|
376
|
-
{"type": "websocket.disconnect", "code": 1006}
|
377
|
-
)
|
360
|
+
self.queue.put_nowait({"type": "websocket.disconnect", "code": 1006})
|
378
361
|
self.close_sent = True
|
379
362
|
self.transport.close()
|
380
363
|
|
381
364
|
else:
|
382
|
-
msg =
|
383
|
-
"Expected ASGI message 'websocket.http.response.body' "
|
384
|
-
"but got '%s'."
|
385
|
-
)
|
365
|
+
msg = "Expected ASGI message 'websocket.http.response.body' " "but got '%s'."
|
386
366
|
raise RuntimeError(msg % message_type)
|
387
367
|
|
388
368
|
else:
|
uvicorn/server.py
CHANGED
@@ -126,18 +126,14 @@ class Server:
|
|
126
126
|
is_windows = platform.system() == "Windows"
|
127
127
|
if config.workers > 1 and is_windows: # pragma: py-not-win32
|
128
128
|
sock = _share_socket(sock) # type: ignore[assignment]
|
129
|
-
server = await loop.create_server(
|
130
|
-
create_protocol, sock=sock, ssl=config.ssl, backlog=config.backlog
|
131
|
-
)
|
129
|
+
server = await loop.create_server(create_protocol, sock=sock, ssl=config.ssl, backlog=config.backlog)
|
132
130
|
self.servers.append(server)
|
133
131
|
listeners = sockets
|
134
132
|
|
135
133
|
elif config.fd is not None: # pragma: py-win32
|
136
134
|
# Use an existing socket, from a file descriptor.
|
137
135
|
sock = socket.fromfd(config.fd, socket.AF_UNIX, socket.SOCK_STREAM)
|
138
|
-
server = await loop.create_server(
|
139
|
-
create_protocol, sock=sock, ssl=config.ssl, backlog=config.backlog
|
140
|
-
)
|
136
|
+
server = await loop.create_server(create_protocol, sock=sock, ssl=config.ssl, backlog=config.backlog)
|
141
137
|
assert server.sockets is not None # mypy
|
142
138
|
listeners = server.sockets
|
143
139
|
self.servers = [server]
|
@@ -194,9 +190,7 @@ class Server:
|
|
194
190
|
)
|
195
191
|
|
196
192
|
elif config.uds is not None: # pragma: py-win32
|
197
|
-
logger.info(
|
198
|
-
"Uvicorn running on unix socket %s (Press CTRL+C to quit)", config.uds
|
199
|
-
)
|
193
|
+
logger.info("Uvicorn running on unix socket %s (Press CTRL+C to quit)", config.uds)
|
200
194
|
|
201
195
|
else:
|
202
196
|
addr_format = "%s://%s:%d"
|
@@ -211,11 +205,7 @@ class Server:
|
|
211
205
|
|
212
206
|
protocol_name = "https" if config.ssl else "http"
|
213
207
|
message = f"Uvicorn running on {addr_format} (Press CTRL+C to quit)"
|
214
|
-
color_message = (
|
215
|
-
"Uvicorn running on "
|
216
|
-
+ click.style(addr_format, bold=True)
|
217
|
-
+ " (Press CTRL+C to quit)"
|
218
|
-
)
|
208
|
+
color_message = "Uvicorn running on " + click.style(addr_format, bold=True) + " (Press CTRL+C to quit)"
|
219
209
|
logger.info(
|
220
210
|
message,
|
221
211
|
protocol_name,
|
@@ -244,9 +234,7 @@ class Server:
|
|
244
234
|
else:
|
245
235
|
date_header = []
|
246
236
|
|
247
|
-
self.server_state.default_headers =
|
248
|
-
date_header + self.config.encoded_headers
|
249
|
-
)
|
237
|
+
self.server_state.default_headers = date_header + self.config.encoded_headers
|
250
238
|
|
251
239
|
# Callback to `callback_notify` once every `timeout_notify` seconds.
|
252
240
|
if self.config.callback_notify is not None:
|
uvicorn/supervisors/__init__.py
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
-
from
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import TYPE_CHECKING
|
2
4
|
|
3
5
|
from uvicorn.supervisors.basereload import BaseReload
|
4
6
|
from uvicorn.supervisors.multiprocess import Multiprocess
|
5
7
|
|
6
8
|
if TYPE_CHECKING:
|
7
|
-
ChangeReload:
|
9
|
+
ChangeReload: type[BaseReload]
|
8
10
|
else:
|
9
11
|
try:
|
10
12
|
from uvicorn.supervisors.watchfilesreload import (
|
@@ -81,9 +81,7 @@ class BaseReload:
|
|
81
81
|
for sig in HANDLED_SIGNALS:
|
82
82
|
signal.signal(sig, self.signal_handler)
|
83
83
|
|
84
|
-
self.process = get_subprocess(
|
85
|
-
config=self.config, target=self.target, sockets=self.sockets
|
86
|
-
)
|
84
|
+
self.process = get_subprocess(config=self.config, target=self.target, sockets=self.sockets)
|
87
85
|
self.process.start()
|
88
86
|
|
89
87
|
def restart(self) -> None:
|
@@ -95,9 +93,7 @@ class BaseReload:
|
|
95
93
|
self.process.terminate()
|
96
94
|
self.process.join()
|
97
95
|
|
98
|
-
self.process = get_subprocess(
|
99
|
-
config=self.config, target=self.target, sockets=self.sockets
|
100
|
-
)
|
96
|
+
self.process = get_subprocess(config=self.config, target=self.target, sockets=self.sockets)
|
101
97
|
self.process.start()
|
102
98
|
|
103
99
|
def shutdown(self) -> None:
|
@@ -110,10 +106,8 @@ class BaseReload:
|
|
110
106
|
for sock in self.sockets:
|
111
107
|
sock.close()
|
112
108
|
|
113
|
-
message = "Stopping reloader process [{
|
114
|
-
color_message = "Stopping reloader process [{}]".format(
|
115
|
-
click.style(str(self.pid), fg="cyan", bold=True)
|
116
|
-
)
|
109
|
+
message = f"Stopping reloader process [{str(self.pid)}]"
|
110
|
+
color_message = "Stopping reloader process [{}]".format(click.style(str(self.pid), fg="cyan", bold=True))
|
117
111
|
logger.info(message, extra={"color_message": color_message})
|
118
112
|
|
119
113
|
def should_restart(self) -> list[Path] | None:
|
@@ -48,19 +48,15 @@ class Multiprocess:
|
|
48
48
|
self.shutdown()
|
49
49
|
|
50
50
|
def startup(self) -> None:
|
51
|
-
message = "Started parent process [{
|
52
|
-
color_message = "Started parent process [{}]".format(
|
53
|
-
click.style(str(self.pid), fg="cyan", bold=True)
|
54
|
-
)
|
51
|
+
message = f"Started parent process [{str(self.pid)}]"
|
52
|
+
color_message = "Started parent process [{}]".format(click.style(str(self.pid), fg="cyan", bold=True))
|
55
53
|
logger.info(message, extra={"color_message": color_message})
|
56
54
|
|
57
55
|
for sig in HANDLED_SIGNALS:
|
58
56
|
signal.signal(sig, self.signal_handler)
|
59
57
|
|
60
58
|
for _idx in range(self.config.workers):
|
61
|
-
process = get_subprocess(
|
62
|
-
config=self.config, target=self.target, sockets=self.sockets
|
63
|
-
)
|
59
|
+
process = get_subprocess(config=self.config, target=self.target, sockets=self.sockets)
|
64
60
|
process.start()
|
65
61
|
self.processes.append(process)
|
66
62
|
|
@@ -69,8 +65,6 @@ class Multiprocess:
|
|
69
65
|
process.terminate()
|
70
66
|
process.join()
|
71
67
|
|
72
|
-
message = "Stopping parent process [{
|
73
|
-
color_message = "Stopping parent process [{}]".format(
|
74
|
-
click.style(str(self.pid), fg="cyan", bold=True)
|
75
|
-
)
|
68
|
+
message = f"Stopping parent process [{str(self.pid)}]"
|
69
|
+
color_message = "Stopping parent process [{}]".format(click.style(str(self.pid), fg="cyan", bold=True))
|
76
70
|
logger.info(message, extra={"color_message": color_message})
|