ominfra 0.0.0.dev123__py3-none-any.whl → 0.0.0.dev125__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- ominfra/deploy/_executor.py +7 -4
- ominfra/deploy/poly/_main.py +4 -10
- ominfra/deploy/poly/base.py +7 -8
- ominfra/pyremote/_runcommands.py +7 -4
- ominfra/scripts/journald2aws.py +7 -4
- ominfra/scripts/supervisor.py +1388 -180
- ominfra/supervisor/LICENSE.txt +28 -0
- ominfra/supervisor/context.py +5 -5
- ominfra/supervisor/dispatchers.py +5 -5
- ominfra/supervisor/events.py +3 -3
- ominfra/supervisor/groups.py +12 -18
- ominfra/supervisor/inject.py +64 -0
- ominfra/supervisor/main.py +38 -75
- ominfra/supervisor/process.py +8 -8
- ominfra/supervisor/supervisor.py +11 -16
- ominfra/supervisor/types.py +51 -6
- {ominfra-0.0.0.dev123.dist-info → ominfra-0.0.0.dev125.dist-info}/METADATA +7 -8
- {ominfra-0.0.0.dev123.dist-info → ominfra-0.0.0.dev125.dist-info}/RECORD +22 -20
- {ominfra-0.0.0.dev123.dist-info → ominfra-0.0.0.dev125.dist-info}/WHEEL +1 -1
- {ominfra-0.0.0.dev123.dist-info → ominfra-0.0.0.dev125.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev123.dist-info → ominfra-0.0.0.dev125.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev123.dist-info → ominfra-0.0.0.dev125.dist-info}/top_level.txt +0 -0
ominfra/scripts/supervisor.py
CHANGED
@@ -4,6 +4,34 @@
|
|
4
4
|
# @omlish-script
|
5
5
|
# @omlish-amalg-output ../supervisor/main.py
|
6
6
|
# ruff: noqa: N802 UP006 UP007 UP012 UP036
|
7
|
+
# Supervisor is licensed under the following license:
|
8
|
+
#
|
9
|
+
# A copyright notice accompanies this license document that identifies the copyright holders.
|
10
|
+
#
|
11
|
+
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
12
|
+
# following conditions are met:
|
13
|
+
#
|
14
|
+
# 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the
|
15
|
+
# following disclaimer.
|
16
|
+
#
|
17
|
+
# 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the
|
18
|
+
# following disclaimer in the documentation and/or other materials provided with the distribution.
|
19
|
+
#
|
20
|
+
# 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without
|
21
|
+
# prior written permission from the copyright holders.
|
22
|
+
#
|
23
|
+
# 4. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed
|
24
|
+
# the files and the date of any change.
|
25
|
+
#
|
26
|
+
# Disclaimer
|
27
|
+
#
|
28
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
|
29
|
+
# NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
30
|
+
# EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
31
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
32
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
33
|
+
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
34
|
+
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
7
35
|
import abc
|
8
36
|
import base64
|
9
37
|
import collections.abc
|
@@ -12,13 +40,18 @@ import ctypes as ct
|
|
12
40
|
import dataclasses as dc
|
13
41
|
import datetime
|
14
42
|
import decimal
|
43
|
+
import email.utils
|
15
44
|
import enum
|
16
45
|
import errno
|
17
46
|
import fcntl
|
18
47
|
import fractions
|
19
48
|
import functools
|
20
49
|
import grp
|
50
|
+
import html
|
51
|
+
import http.client
|
52
|
+
import http.server
|
21
53
|
import inspect
|
54
|
+
import io
|
22
55
|
import itertools
|
23
56
|
import json
|
24
57
|
import logging
|
@@ -30,11 +63,13 @@ import resource
|
|
30
63
|
import select
|
31
64
|
import shlex
|
32
65
|
import signal
|
66
|
+
import socket
|
33
67
|
import stat
|
34
68
|
import string
|
35
69
|
import sys
|
36
70
|
import syslog
|
37
71
|
import tempfile
|
72
|
+
import textwrap
|
38
73
|
import threading
|
39
74
|
import time
|
40
75
|
import traceback
|
@@ -49,8 +84,7 @@ import weakref # noqa
|
|
49
84
|
|
50
85
|
|
51
86
|
if sys.version_info < (3, 8):
|
52
|
-
raise OSError(
|
53
|
-
f'Requires python (3, 8), got {sys.version_info} from {sys.executable}') # noqa
|
87
|
+
raise OSError(f'Requires python (3, 8), got {sys.version_info} from {sys.executable}') # noqa
|
54
88
|
|
55
89
|
|
56
90
|
########################################
|
@@ -64,6 +98,16 @@ TomlPos = int # ta.TypeAlias
|
|
64
98
|
# ../../../omlish/lite/cached.py
|
65
99
|
T = ta.TypeVar('T')
|
66
100
|
|
101
|
+
# ../../../omlish/lite/socket.py
|
102
|
+
SocketAddress = ta.Any
|
103
|
+
SocketHandlerFactory = ta.Callable[[SocketAddress, ta.BinaryIO, ta.BinaryIO], 'SocketHandler']
|
104
|
+
|
105
|
+
# ../events.py
|
106
|
+
EventCallback = ta.Callable[['Event'], None]
|
107
|
+
|
108
|
+
# ../../../omlish/lite/http/parsing.py
|
109
|
+
HttpHeaders = http.client.HTTPMessage # ta.TypeAlias
|
110
|
+
|
67
111
|
# ../../../omlish/lite/inject.py
|
68
112
|
InjectorKeyCls = ta.Union[type, ta.NewType]
|
69
113
|
InjectorProviderFn = ta.Callable[['Injector'], ta.Any]
|
@@ -73,11 +117,11 @@ InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings']
|
|
73
117
|
# ../../configs.py
|
74
118
|
ConfigMapping = ta.Mapping[str, ta.Any]
|
75
119
|
|
76
|
-
#
|
77
|
-
|
120
|
+
# ../../../omlish/lite/http/handlers.py
|
121
|
+
HttpHandler = ta.Callable[['HttpHandlerRequest'], 'HttpHandlerResponse']
|
78
122
|
|
79
|
-
#
|
80
|
-
|
123
|
+
# ../../../omlish/lite/http/coroserver.py
|
124
|
+
CoroHttpServerFactory = ta.Callable[[SocketAddress], 'CoroHttpServer']
|
81
125
|
|
82
126
|
|
83
127
|
########################################
|
@@ -1254,6 +1298,11 @@ def check_not_isinstance(v: T, spec: ta.Union[type, tuple]) -> T:
|
|
1254
1298
|
return v
|
1255
1299
|
|
1256
1300
|
|
1301
|
+
def check_none(v: T) -> None:
|
1302
|
+
if v is not None:
|
1303
|
+
raise ValueError(v)
|
1304
|
+
|
1305
|
+
|
1257
1306
|
def check_not_none(v: ta.Optional[T]) -> T:
|
1258
1307
|
if v is None:
|
1259
1308
|
raise ValueError
|
@@ -1294,6 +1343,25 @@ def check_single(vs: ta.Iterable[T]) -> T:
|
|
1294
1343
|
return v
|
1295
1344
|
|
1296
1345
|
|
1346
|
+
########################################
|
1347
|
+
# ../../../omlish/lite/http/versions.py
|
1348
|
+
|
1349
|
+
|
1350
|
+
class HttpProtocolVersion(ta.NamedTuple):
|
1351
|
+
major: int
|
1352
|
+
minor: int
|
1353
|
+
|
1354
|
+
def __str__(self) -> str:
|
1355
|
+
return f'HTTP/{self.major}.{self.minor}'
|
1356
|
+
|
1357
|
+
|
1358
|
+
class HttpProtocolVersions:
|
1359
|
+
HTTP_0_9 = HttpProtocolVersion(0, 9)
|
1360
|
+
HTTP_1_0 = HttpProtocolVersion(1, 0)
|
1361
|
+
HTTP_1_1 = HttpProtocolVersion(1, 1)
|
1362
|
+
HTTP_2_0 = HttpProtocolVersion(2, 0)
|
1363
|
+
|
1364
|
+
|
1297
1365
|
########################################
|
1298
1366
|
# ../../../omlish/lite/json.py
|
1299
1367
|
|
@@ -1424,6 +1492,88 @@ def deep_subclasses(cls: ta.Type[T]) -> ta.Iterator[ta.Type[T]]:
|
|
1424
1492
|
todo.extend(reversed(cur.__subclasses__()))
|
1425
1493
|
|
1426
1494
|
|
1495
|
+
########################################
|
1496
|
+
# ../../../omlish/lite/socket.py
|
1497
|
+
"""
|
1498
|
+
TODO:
|
1499
|
+
- SocketClientAddress family / tuple pairs
|
1500
|
+
+ codification of https://docs.python.org/3/library/socket.html#socket-families
|
1501
|
+
"""
|
1502
|
+
|
1503
|
+
|
1504
|
+
##
|
1505
|
+
|
1506
|
+
|
1507
|
+
@dc.dataclass(frozen=True)
|
1508
|
+
class SocketAddressInfoArgs:
|
1509
|
+
host: ta.Optional[str]
|
1510
|
+
port: ta.Union[str, int, None]
|
1511
|
+
family: socket.AddressFamily = socket.AddressFamily.AF_UNSPEC
|
1512
|
+
type: int = 0
|
1513
|
+
proto: int = 0
|
1514
|
+
flags: socket.AddressInfo = socket.AddressInfo(0)
|
1515
|
+
|
1516
|
+
|
1517
|
+
@dc.dataclass(frozen=True)
|
1518
|
+
class SocketAddressInfo:
|
1519
|
+
family: socket.AddressFamily
|
1520
|
+
type: int
|
1521
|
+
proto: int
|
1522
|
+
canonname: ta.Optional[str]
|
1523
|
+
sockaddr: SocketAddress
|
1524
|
+
|
1525
|
+
|
1526
|
+
def get_best_socket_family(
|
1527
|
+
host: ta.Optional[str],
|
1528
|
+
port: ta.Union[str, int, None],
|
1529
|
+
family: ta.Union[int, socket.AddressFamily] = socket.AddressFamily.AF_UNSPEC,
|
1530
|
+
) -> ta.Tuple[socket.AddressFamily, SocketAddress]:
|
1531
|
+
"""https://github.com/python/cpython/commit/f289084c83190cc72db4a70c58f007ec62e75247"""
|
1532
|
+
|
1533
|
+
infos = socket.getaddrinfo(
|
1534
|
+
host,
|
1535
|
+
port,
|
1536
|
+
family,
|
1537
|
+
type=socket.SOCK_STREAM,
|
1538
|
+
flags=socket.AI_PASSIVE,
|
1539
|
+
)
|
1540
|
+
ai = SocketAddressInfo(*next(iter(infos)))
|
1541
|
+
return ai.family, ai.sockaddr
|
1542
|
+
|
1543
|
+
|
1544
|
+
##
|
1545
|
+
|
1546
|
+
|
1547
|
+
class SocketHandler(abc.ABC):
|
1548
|
+
def __init__(
|
1549
|
+
self,
|
1550
|
+
client_address: SocketAddress,
|
1551
|
+
rfile: ta.BinaryIO,
|
1552
|
+
wfile: ta.BinaryIO,
|
1553
|
+
) -> None:
|
1554
|
+
super().__init__()
|
1555
|
+
|
1556
|
+
self._client_address = client_address
|
1557
|
+
self._rfile = rfile
|
1558
|
+
self._wfile = wfile
|
1559
|
+
|
1560
|
+
@abc.abstractmethod
|
1561
|
+
def handle(self) -> None:
|
1562
|
+
raise NotImplementedError
|
1563
|
+
|
1564
|
+
|
1565
|
+
########################################
|
1566
|
+
# ../../../omlish/lite/typing.py
|
1567
|
+
|
1568
|
+
|
1569
|
+
@dc.dataclass(frozen=True)
|
1570
|
+
class Func(ta.Generic[T]):
|
1571
|
+
fn: ta.Callable[..., T]
|
1572
|
+
|
1573
|
+
def __call__(self, *args: ta.Any, **kwargs: ta.Any) -> T:
|
1574
|
+
return self.fn(*args, **kwargs)
|
1575
|
+
|
1576
|
+
|
1427
1577
|
########################################
|
1428
1578
|
# ../events.py
|
1429
1579
|
|
@@ -1438,9 +1588,6 @@ class Event(abc.ABC): # noqa
|
|
1438
1588
|
##
|
1439
1589
|
|
1440
1590
|
|
1441
|
-
EventCallback = ta.Callable[['Event'], None]
|
1442
|
-
|
1443
|
-
|
1444
1591
|
class EventCallbacks:
|
1445
1592
|
def __init__(self) -> None:
|
1446
1593
|
super().__init__()
|
@@ -1910,6 +2057,371 @@ def timeslice(period: int, when: float) -> int:
|
|
1910
2057
|
return int(when - (when % period))
|
1911
2058
|
|
1912
2059
|
|
2060
|
+
########################################
|
2061
|
+
# ../../../omlish/lite/http/parsing.py
|
2062
|
+
|
2063
|
+
|
2064
|
+
##
|
2065
|
+
|
2066
|
+
|
2067
|
+
class ParseHttpRequestResult(abc.ABC): # noqa
|
2068
|
+
__slots__ = (
|
2069
|
+
'server_version',
|
2070
|
+
'request_line',
|
2071
|
+
'request_version',
|
2072
|
+
'version',
|
2073
|
+
'headers',
|
2074
|
+
'close_connection',
|
2075
|
+
)
|
2076
|
+
|
2077
|
+
def __init__(
|
2078
|
+
self,
|
2079
|
+
*,
|
2080
|
+
server_version: HttpProtocolVersion,
|
2081
|
+
request_line: str,
|
2082
|
+
request_version: HttpProtocolVersion,
|
2083
|
+
version: HttpProtocolVersion,
|
2084
|
+
headers: ta.Optional[HttpHeaders],
|
2085
|
+
close_connection: bool,
|
2086
|
+
) -> None:
|
2087
|
+
super().__init__()
|
2088
|
+
|
2089
|
+
self.server_version = server_version
|
2090
|
+
self.request_line = request_line
|
2091
|
+
self.request_version = request_version
|
2092
|
+
self.version = version
|
2093
|
+
self.headers = headers
|
2094
|
+
self.close_connection = close_connection
|
2095
|
+
|
2096
|
+
def __repr__(self) -> str:
|
2097
|
+
return f'{self.__class__.__name__}({", ".join(f"{a}={getattr(self, a)!r}" for a in self.__slots__)})'
|
2098
|
+
|
2099
|
+
|
2100
|
+
class EmptyParsedHttpResult(ParseHttpRequestResult):
|
2101
|
+
pass
|
2102
|
+
|
2103
|
+
|
2104
|
+
class ParseHttpRequestError(ParseHttpRequestResult):
|
2105
|
+
__slots__ = (
|
2106
|
+
'code',
|
2107
|
+
'message',
|
2108
|
+
*ParseHttpRequestResult.__slots__,
|
2109
|
+
)
|
2110
|
+
|
2111
|
+
def __init__(
|
2112
|
+
self,
|
2113
|
+
*,
|
2114
|
+
code: http.HTTPStatus,
|
2115
|
+
message: ta.Union[str, ta.Tuple[str, str]],
|
2116
|
+
|
2117
|
+
**kwargs: ta.Any,
|
2118
|
+
) -> None:
|
2119
|
+
super().__init__(**kwargs)
|
2120
|
+
|
2121
|
+
self.code = code
|
2122
|
+
self.message = message
|
2123
|
+
|
2124
|
+
|
2125
|
+
class ParsedHttpRequest(ParseHttpRequestResult):
|
2126
|
+
__slots__ = (
|
2127
|
+
'method',
|
2128
|
+
'path',
|
2129
|
+
'headers',
|
2130
|
+
'expects_continue',
|
2131
|
+
*[a for a in ParseHttpRequestResult.__slots__ if a != 'headers'],
|
2132
|
+
)
|
2133
|
+
|
2134
|
+
def __init__(
|
2135
|
+
self,
|
2136
|
+
*,
|
2137
|
+
method: str,
|
2138
|
+
path: str,
|
2139
|
+
headers: HttpHeaders,
|
2140
|
+
expects_continue: bool,
|
2141
|
+
|
2142
|
+
**kwargs: ta.Any,
|
2143
|
+
) -> None:
|
2144
|
+
super().__init__(
|
2145
|
+
headers=headers,
|
2146
|
+
**kwargs,
|
2147
|
+
)
|
2148
|
+
|
2149
|
+
self.method = method
|
2150
|
+
self.path = path
|
2151
|
+
self.expects_continue = expects_continue
|
2152
|
+
|
2153
|
+
headers: HttpHeaders
|
2154
|
+
|
2155
|
+
|
2156
|
+
#
|
2157
|
+
|
2158
|
+
|
2159
|
+
class HttpRequestParser:
|
2160
|
+
DEFAULT_SERVER_VERSION = HttpProtocolVersions.HTTP_1_0
|
2161
|
+
|
2162
|
+
# The default request version. This only affects responses up until the point where the request line is parsed, so
|
2163
|
+
# it mainly decides what the client gets back when sending a malformed request line.
|
2164
|
+
# Most web servers default to HTTP 0.9, i.e. don't send a status line.
|
2165
|
+
DEFAULT_REQUEST_VERSION = HttpProtocolVersions.HTTP_0_9
|
2166
|
+
|
2167
|
+
#
|
2168
|
+
|
2169
|
+
DEFAULT_MAX_LINE: int = 0x10000
|
2170
|
+
DEFAULT_MAX_HEADERS: int = 100
|
2171
|
+
|
2172
|
+
#
|
2173
|
+
|
2174
|
+
def __init__(
|
2175
|
+
self,
|
2176
|
+
*,
|
2177
|
+
server_version: HttpProtocolVersion = DEFAULT_SERVER_VERSION,
|
2178
|
+
|
2179
|
+
max_line: int = DEFAULT_MAX_LINE,
|
2180
|
+
max_headers: int = DEFAULT_MAX_HEADERS,
|
2181
|
+
) -> None:
|
2182
|
+
super().__init__()
|
2183
|
+
|
2184
|
+
if server_version >= HttpProtocolVersions.HTTP_2_0:
|
2185
|
+
raise ValueError(f'Unsupported protocol version: {server_version}')
|
2186
|
+
self._server_version = server_version
|
2187
|
+
|
2188
|
+
self._max_line = max_line
|
2189
|
+
self._max_headers = max_headers
|
2190
|
+
|
2191
|
+
#
|
2192
|
+
|
2193
|
+
@property
|
2194
|
+
def server_version(self) -> HttpProtocolVersion:
|
2195
|
+
return self._server_version
|
2196
|
+
|
2197
|
+
#
|
2198
|
+
|
2199
|
+
def _run_read_line_coro(
|
2200
|
+
self,
|
2201
|
+
gen: ta.Generator[int, bytes, T],
|
2202
|
+
read_line: ta.Callable[[int], bytes],
|
2203
|
+
) -> T:
|
2204
|
+
sz = next(gen)
|
2205
|
+
while True:
|
2206
|
+
try:
|
2207
|
+
sz = gen.send(read_line(sz))
|
2208
|
+
except StopIteration as e:
|
2209
|
+
return e.value
|
2210
|
+
|
2211
|
+
#
|
2212
|
+
|
2213
|
+
def parse_request_version(self, version_str: str) -> HttpProtocolVersion:
|
2214
|
+
if not version_str.startswith('HTTP/'):
|
2215
|
+
raise ValueError(version_str) # noqa
|
2216
|
+
|
2217
|
+
base_version_number = version_str.split('/', 1)[1]
|
2218
|
+
version_number_parts = base_version_number.split('.')
|
2219
|
+
|
2220
|
+
# RFC 2145 section 3.1 says there can be only one "." and
|
2221
|
+
# - major and minor numbers MUST be treated as separate integers;
|
2222
|
+
# - HTTP/2.4 is a lower version than HTTP/2.13, which in turn is lower than HTTP/12.3;
|
2223
|
+
# - Leading zeros MUST be ignored by recipients.
|
2224
|
+
if len(version_number_parts) != 2:
|
2225
|
+
raise ValueError(version_number_parts) # noqa
|
2226
|
+
if any(not component.isdigit() for component in version_number_parts):
|
2227
|
+
raise ValueError('non digit in http version') # noqa
|
2228
|
+
if any(len(component) > 10 for component in version_number_parts):
|
2229
|
+
raise ValueError('unreasonable length http version') # noqa
|
2230
|
+
|
2231
|
+
return HttpProtocolVersion(
|
2232
|
+
int(version_number_parts[0]),
|
2233
|
+
int(version_number_parts[1]),
|
2234
|
+
)
|
2235
|
+
|
2236
|
+
#
|
2237
|
+
|
2238
|
+
def coro_read_raw_headers(self) -> ta.Generator[int, bytes, ta.List[bytes]]:
|
2239
|
+
raw_headers: ta.List[bytes] = []
|
2240
|
+
while True:
|
2241
|
+
line = yield self._max_line + 1
|
2242
|
+
if len(line) > self._max_line:
|
2243
|
+
raise http.client.LineTooLong('header line')
|
2244
|
+
raw_headers.append(line)
|
2245
|
+
if len(raw_headers) > self._max_headers:
|
2246
|
+
raise http.client.HTTPException(f'got more than {self._max_headers} headers')
|
2247
|
+
if line in (b'\r\n', b'\n', b''):
|
2248
|
+
break
|
2249
|
+
return raw_headers
|
2250
|
+
|
2251
|
+
def read_raw_headers(self, read_line: ta.Callable[[int], bytes]) -> ta.List[bytes]:
|
2252
|
+
return self._run_read_line_coro(self.coro_read_raw_headers(), read_line)
|
2253
|
+
|
2254
|
+
def parse_raw_headers(self, raw_headers: ta.Sequence[bytes]) -> HttpHeaders:
|
2255
|
+
return http.client.parse_headers(io.BytesIO(b''.join(raw_headers)))
|
2256
|
+
|
2257
|
+
#
|
2258
|
+
|
2259
|
+
def coro_parse(self) -> ta.Generator[int, bytes, ParseHttpRequestResult]:
|
2260
|
+
raw_request_line = yield self._max_line + 1
|
2261
|
+
|
2262
|
+
# Common result kwargs
|
2263
|
+
|
2264
|
+
request_line = '-'
|
2265
|
+
request_version = self.DEFAULT_REQUEST_VERSION
|
2266
|
+
|
2267
|
+
# Set to min(server, request) when it gets that far, but if it fails before that the server authoritatively
|
2268
|
+
# responds with its own version.
|
2269
|
+
version = self._server_version
|
2270
|
+
|
2271
|
+
headers: HttpHeaders | None = None
|
2272
|
+
|
2273
|
+
close_connection = True
|
2274
|
+
|
2275
|
+
def result_kwargs():
|
2276
|
+
return dict(
|
2277
|
+
server_version=self._server_version,
|
2278
|
+
request_line=request_line,
|
2279
|
+
request_version=request_version,
|
2280
|
+
version=version,
|
2281
|
+
headers=headers,
|
2282
|
+
close_connection=close_connection,
|
2283
|
+
)
|
2284
|
+
|
2285
|
+
# Decode line
|
2286
|
+
|
2287
|
+
if len(raw_request_line) > self._max_line:
|
2288
|
+
return ParseHttpRequestError(
|
2289
|
+
code=http.HTTPStatus.REQUEST_URI_TOO_LONG,
|
2290
|
+
message='Request line too long',
|
2291
|
+
**result_kwargs(),
|
2292
|
+
)
|
2293
|
+
|
2294
|
+
if not raw_request_line:
|
2295
|
+
return EmptyParsedHttpResult(**result_kwargs())
|
2296
|
+
|
2297
|
+
request_line = raw_request_line.decode('iso-8859-1').rstrip('\r\n')
|
2298
|
+
|
2299
|
+
# Split words
|
2300
|
+
|
2301
|
+
words = request_line.split()
|
2302
|
+
if len(words) == 0:
|
2303
|
+
return EmptyParsedHttpResult(**result_kwargs())
|
2304
|
+
|
2305
|
+
# Parse and set version
|
2306
|
+
|
2307
|
+
if len(words) >= 3: # Enough to determine protocol version
|
2308
|
+
version_str = words[-1]
|
2309
|
+
try:
|
2310
|
+
request_version = self.parse_request_version(version_str)
|
2311
|
+
|
2312
|
+
except (ValueError, IndexError):
|
2313
|
+
return ParseHttpRequestError(
|
2314
|
+
code=http.HTTPStatus.BAD_REQUEST,
|
2315
|
+
message=f'Bad request version ({version_str!r})',
|
2316
|
+
**result_kwargs(),
|
2317
|
+
)
|
2318
|
+
|
2319
|
+
if (
|
2320
|
+
request_version < HttpProtocolVersions.HTTP_0_9 or
|
2321
|
+
request_version >= HttpProtocolVersions.HTTP_2_0
|
2322
|
+
):
|
2323
|
+
return ParseHttpRequestError(
|
2324
|
+
code=http.HTTPStatus.HTTP_VERSION_NOT_SUPPORTED,
|
2325
|
+
message=f'Invalid HTTP version ({version_str})',
|
2326
|
+
**result_kwargs(),
|
2327
|
+
)
|
2328
|
+
|
2329
|
+
version = min([self._server_version, request_version])
|
2330
|
+
|
2331
|
+
if version >= HttpProtocolVersions.HTTP_1_1:
|
2332
|
+
close_connection = False
|
2333
|
+
|
2334
|
+
# Verify word count
|
2335
|
+
|
2336
|
+
if not 2 <= len(words) <= 3:
|
2337
|
+
return ParseHttpRequestError(
|
2338
|
+
code=http.HTTPStatus.BAD_REQUEST,
|
2339
|
+
message=f'Bad request syntax ({request_line!r})',
|
2340
|
+
**result_kwargs(),
|
2341
|
+
)
|
2342
|
+
|
2343
|
+
# Parse method and path
|
2344
|
+
|
2345
|
+
method, path = words[:2]
|
2346
|
+
if len(words) == 2:
|
2347
|
+
close_connection = True
|
2348
|
+
if method != 'GET':
|
2349
|
+
return ParseHttpRequestError(
|
2350
|
+
code=http.HTTPStatus.BAD_REQUEST,
|
2351
|
+
message=f'Bad HTTP/0.9 request type ({method!r})',
|
2352
|
+
**result_kwargs(),
|
2353
|
+
)
|
2354
|
+
|
2355
|
+
# gh-87389: The purpose of replacing '//' with '/' is to protect against open redirect attacks possibly
|
2356
|
+
# triggered if the path starts with '//' because http clients treat //path as an absolute URI without scheme
|
2357
|
+
# (similar to http://path) rather than a path.
|
2358
|
+
if path.startswith('//'):
|
2359
|
+
path = '/' + path.lstrip('/') # Reduce to a single /
|
2360
|
+
|
2361
|
+
# Parse headers
|
2362
|
+
|
2363
|
+
try:
|
2364
|
+
raw_gen = self.coro_read_raw_headers()
|
2365
|
+
raw_sz = next(raw_gen)
|
2366
|
+
while True:
|
2367
|
+
buf = yield raw_sz
|
2368
|
+
try:
|
2369
|
+
raw_sz = raw_gen.send(buf)
|
2370
|
+
except StopIteration as e:
|
2371
|
+
raw_headers = e.value
|
2372
|
+
break
|
2373
|
+
|
2374
|
+
headers = self.parse_raw_headers(raw_headers)
|
2375
|
+
|
2376
|
+
except http.client.LineTooLong as err:
|
2377
|
+
return ParseHttpRequestError(
|
2378
|
+
code=http.HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE,
|
2379
|
+
message=('Line too long', str(err)),
|
2380
|
+
**result_kwargs(),
|
2381
|
+
)
|
2382
|
+
|
2383
|
+
except http.client.HTTPException as err:
|
2384
|
+
return ParseHttpRequestError(
|
2385
|
+
code=http.HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE,
|
2386
|
+
message=('Too many headers', str(err)),
|
2387
|
+
**result_kwargs(),
|
2388
|
+
)
|
2389
|
+
|
2390
|
+
# Check for connection directive
|
2391
|
+
|
2392
|
+
conn_type = headers.get('Connection', '')
|
2393
|
+
if conn_type.lower() == 'close':
|
2394
|
+
close_connection = True
|
2395
|
+
elif (
|
2396
|
+
conn_type.lower() == 'keep-alive' and
|
2397
|
+
version >= HttpProtocolVersions.HTTP_1_1
|
2398
|
+
):
|
2399
|
+
close_connection = False
|
2400
|
+
|
2401
|
+
# Check for expect directive
|
2402
|
+
|
2403
|
+
expect = headers.get('Expect', '')
|
2404
|
+
if (
|
2405
|
+
expect.lower() == '100-continue' and
|
2406
|
+
version >= HttpProtocolVersions.HTTP_1_1
|
2407
|
+
):
|
2408
|
+
expects_continue = True
|
2409
|
+
else:
|
2410
|
+
expects_continue = False
|
2411
|
+
|
2412
|
+
# Return
|
2413
|
+
|
2414
|
+
return ParsedHttpRequest(
|
2415
|
+
method=method,
|
2416
|
+
path=path,
|
2417
|
+
expects_continue=expects_continue,
|
2418
|
+
**result_kwargs(),
|
2419
|
+
)
|
2420
|
+
|
2421
|
+
def parse(self, read_line: ta.Callable[[int], bytes]) -> ParseHttpRequestResult:
|
2422
|
+
return self._run_read_line_coro(self.coro_parse(), read_line)
|
2423
|
+
|
2424
|
+
|
1913
2425
|
########################################
|
1914
2426
|
# ../../../omlish/lite/inject.py
|
1915
2427
|
|
@@ -1919,12 +2431,28 @@ def timeslice(period: int, when: float) -> int:
|
|
1919
2431
|
|
1920
2432
|
|
1921
2433
|
@dc.dataclass(frozen=True)
|
1922
|
-
class InjectorKey:
|
1923
|
-
cls
|
2434
|
+
class InjectorKey(ta.Generic[T]):
|
2435
|
+
# Before PEP-560 typing.Generic was a metaclass with a __new__ that takes a 'cls' arg, so instantiating a dataclass
|
2436
|
+
# with kwargs (such as through dc.replace) causes `TypeError: __new__() got multiple values for argument 'cls'`.
|
2437
|
+
# See:
|
2438
|
+
# - https://github.com/python/cpython/commit/d911e40e788fb679723d78b6ea11cabf46caed5a
|
2439
|
+
# - https://gist.github.com/wrmsr/4468b86efe9f373b6b114bfe85b98fd3
|
2440
|
+
cls_: InjectorKeyCls
|
2441
|
+
|
1924
2442
|
tag: ta.Any = None
|
1925
2443
|
array: bool = False
|
1926
2444
|
|
1927
2445
|
|
2446
|
+
def is_valid_injector_key_cls(cls: ta.Any) -> bool:
|
2447
|
+
return isinstance(cls, type) or is_new_type(cls)
|
2448
|
+
|
2449
|
+
|
2450
|
+
def check_valid_injector_key_cls(cls: T) -> T:
|
2451
|
+
if not is_valid_injector_key_cls(cls):
|
2452
|
+
raise TypeError(cls)
|
2453
|
+
return cls
|
2454
|
+
|
2455
|
+
|
1928
2456
|
##
|
1929
2457
|
|
1930
2458
|
|
@@ -1968,6 +2496,12 @@ class Injector(abc.ABC):
|
|
1968
2496
|
def inject(self, obj: ta.Any) -> ta.Any:
|
1969
2497
|
raise NotImplementedError
|
1970
2498
|
|
2499
|
+
def __getitem__(
|
2500
|
+
self,
|
2501
|
+
target: ta.Union[InjectorKey[T], ta.Type[T]],
|
2502
|
+
) -> T:
|
2503
|
+
return self.provide(target)
|
2504
|
+
|
1971
2505
|
|
1972
2506
|
###
|
1973
2507
|
# exceptions
|
@@ -2000,7 +2534,7 @@ def as_injector_key(o: ta.Any) -> InjectorKey:
|
|
2000
2534
|
raise TypeError(o)
|
2001
2535
|
if isinstance(o, InjectorKey):
|
2002
2536
|
return o
|
2003
|
-
if
|
2537
|
+
if is_valid_injector_key_cls(o):
|
2004
2538
|
return InjectorKey(o)
|
2005
2539
|
raise TypeError(o)
|
2006
2540
|
|
@@ -2025,14 +2559,14 @@ class FnInjectorProvider(InjectorProvider):
|
|
2025
2559
|
|
2026
2560
|
@dc.dataclass(frozen=True)
|
2027
2561
|
class CtorInjectorProvider(InjectorProvider):
|
2028
|
-
|
2562
|
+
cls_: type
|
2029
2563
|
|
2030
2564
|
def __post_init__(self) -> None:
|
2031
|
-
check_isinstance(self.
|
2565
|
+
check_isinstance(self.cls_, type)
|
2032
2566
|
|
2033
2567
|
def provider_fn(self) -> InjectorProviderFn:
|
2034
2568
|
def pfn(i: Injector) -> ta.Any:
|
2035
|
-
return i.inject(self.
|
2569
|
+
return i.inject(self.cls_)
|
2036
2570
|
|
2037
2571
|
return pfn
|
2038
2572
|
|
@@ -2181,19 +2715,42 @@ def build_injector_provider_map(bs: InjectorBindings) -> ta.Mapping[InjectorKey,
|
|
2181
2715
|
# inspection
|
2182
2716
|
|
2183
2717
|
|
2184
|
-
|
2718
|
+
# inspect.signature(eval_str=True) was added in 3.10 and we have to support 3.8, so we have to get_type_hints to eval
|
2719
|
+
# str annotations *in addition to* getting the signature for parameter information.
|
2720
|
+
class _InjectionInspection(ta.NamedTuple):
|
2721
|
+
signature: inspect.Signature
|
2722
|
+
type_hints: ta.Mapping[str, ta.Any]
|
2723
|
+
|
2724
|
+
|
2725
|
+
_INJECTION_INSPECTION_CACHE: ta.MutableMapping[ta.Any, _InjectionInspection] = weakref.WeakKeyDictionary()
|
2726
|
+
|
2185
2727
|
|
2728
|
+
def _do_injection_inspect(obj: ta.Any) -> _InjectionInspection:
|
2729
|
+
uw = obj
|
2730
|
+
while True:
|
2731
|
+
if isinstance(uw, functools.partial):
|
2732
|
+
uw = uw.func
|
2733
|
+
else:
|
2734
|
+
if (uw2 := inspect.unwrap(uw)) is uw:
|
2735
|
+
break
|
2736
|
+
uw = uw2
|
2737
|
+
|
2738
|
+
return _InjectionInspection(
|
2739
|
+
inspect.signature(obj),
|
2740
|
+
ta.get_type_hints(uw),
|
2741
|
+
)
|
2186
2742
|
|
2187
|
-
|
2743
|
+
|
2744
|
+
def _injection_inspect(obj: ta.Any) -> _InjectionInspection:
|
2188
2745
|
try:
|
2189
|
-
return
|
2746
|
+
return _INJECTION_INSPECTION_CACHE[obj]
|
2190
2747
|
except TypeError:
|
2191
|
-
return
|
2748
|
+
return _do_injection_inspect(obj)
|
2192
2749
|
except KeyError:
|
2193
2750
|
pass
|
2194
|
-
|
2195
|
-
|
2196
|
-
return
|
2751
|
+
insp = _do_injection_inspect(obj)
|
2752
|
+
_INJECTION_INSPECTION_CACHE[obj] = insp
|
2753
|
+
return insp
|
2197
2754
|
|
2198
2755
|
|
2199
2756
|
class InjectionKwarg(ta.NamedTuple):
|
@@ -2214,20 +2771,20 @@ def build_injection_kwargs_target(
|
|
2214
2771
|
skip_kwargs: ta.Optional[ta.Iterable[ta.Any]] = None,
|
2215
2772
|
raw_optional: bool = False,
|
2216
2773
|
) -> InjectionKwargsTarget:
|
2217
|
-
|
2774
|
+
insp = _injection_inspect(obj)
|
2218
2775
|
|
2219
2776
|
seen: ta.Set[InjectorKey] = set(map(as_injector_key, skip_kwargs)) if skip_kwargs is not None else set()
|
2220
2777
|
kws: ta.List[InjectionKwarg] = []
|
2221
|
-
for p in list(
|
2778
|
+
for p in list(insp.signature.parameters.values())[skip_args:]:
|
2222
2779
|
if p.annotation is inspect.Signature.empty:
|
2223
2780
|
if p.default is not inspect.Parameter.empty:
|
2224
2781
|
raise KeyError(f'{obj}, {p.name}')
|
2225
2782
|
continue
|
2226
2783
|
|
2227
2784
|
if p.kind not in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY):
|
2228
|
-
raise TypeError(
|
2785
|
+
raise TypeError(insp)
|
2229
2786
|
|
2230
|
-
ann = p.annotation
|
2787
|
+
ann = insp.type_hints.get(p.name, p.annotation)
|
2231
2788
|
if (
|
2232
2789
|
not raw_optional and
|
2233
2790
|
is_optional_alias(ann)
|
@@ -2253,38 +2810,98 @@ def build_injection_kwargs_target(
|
|
2253
2810
|
|
2254
2811
|
|
2255
2812
|
###
|
2256
|
-
#
|
2813
|
+
# injector
|
2257
2814
|
|
2258
2815
|
|
2259
|
-
|
2260
|
-
def __new__(cls, *args, **kwargs): # noqa
|
2261
|
-
raise TypeError
|
2816
|
+
_INJECTOR_INJECTOR_KEY: InjectorKey[Injector] = InjectorKey(Injector)
|
2262
2817
|
|
2263
|
-
_FN_TYPES: ta.Tuple[type, ...] = (
|
2264
|
-
types.FunctionType,
|
2265
|
-
types.MethodType,
|
2266
2818
|
|
2267
|
-
|
2268
|
-
|
2819
|
+
class _Injector(Injector):
|
2820
|
+
def __init__(self, bs: InjectorBindings, p: ta.Optional[Injector] = None) -> None:
|
2821
|
+
super().__init__()
|
2269
2822
|
|
2270
|
-
|
2271
|
-
|
2272
|
-
)
|
2823
|
+
self._bs = check_isinstance(bs, InjectorBindings)
|
2824
|
+
self._p: ta.Optional[Injector] = check_isinstance(p, (Injector, type(None)))
|
2273
2825
|
|
2274
|
-
|
2275
|
-
def _is_fn(cls, obj: ta.Any) -> bool:
|
2276
|
-
return isinstance(obj, cls._FN_TYPES)
|
2826
|
+
self._pfm = {k: v.provider_fn() for k, v in build_injector_provider_map(bs).items()}
|
2277
2827
|
|
2278
|
-
|
2279
|
-
|
2280
|
-
check_isinstance(icls, type)
|
2281
|
-
if icls not in cls._FN_TYPES:
|
2282
|
-
cls._FN_TYPES = (*cls._FN_TYPES, icls)
|
2283
|
-
return icls
|
2828
|
+
if _INJECTOR_INJECTOR_KEY in self._pfm:
|
2829
|
+
raise DuplicateInjectorKeyError(_INJECTOR_INJECTOR_KEY)
|
2284
2830
|
|
2285
|
-
|
2286
|
-
|
2287
|
-
|
2831
|
+
def try_provide(self, key: ta.Any) -> Maybe[ta.Any]:
|
2832
|
+
key = as_injector_key(key)
|
2833
|
+
|
2834
|
+
if key == _INJECTOR_INJECTOR_KEY:
|
2835
|
+
return Maybe.just(self)
|
2836
|
+
|
2837
|
+
fn = self._pfm.get(key)
|
2838
|
+
if fn is not None:
|
2839
|
+
return Maybe.just(fn(self))
|
2840
|
+
|
2841
|
+
if self._p is not None:
|
2842
|
+
pv = self._p.try_provide(key)
|
2843
|
+
if pv is not None:
|
2844
|
+
return Maybe.empty()
|
2845
|
+
|
2846
|
+
return Maybe.empty()
|
2847
|
+
|
2848
|
+
def provide(self, key: ta.Any) -> ta.Any:
|
2849
|
+
v = self.try_provide(key)
|
2850
|
+
if v.present:
|
2851
|
+
return v.must()
|
2852
|
+
raise UnboundInjectorKeyError(key)
|
2853
|
+
|
2854
|
+
def provide_kwargs(self, obj: ta.Any) -> ta.Mapping[str, ta.Any]:
|
2855
|
+
kt = build_injection_kwargs_target(obj)
|
2856
|
+
ret: ta.Dict[str, ta.Any] = {}
|
2857
|
+
for kw in kt.kwargs:
|
2858
|
+
if kw.has_default:
|
2859
|
+
if not (mv := self.try_provide(kw.key)).present:
|
2860
|
+
continue
|
2861
|
+
v = mv.must()
|
2862
|
+
else:
|
2863
|
+
v = self.provide(kw.key)
|
2864
|
+
ret[kw.name] = v
|
2865
|
+
return ret
|
2866
|
+
|
2867
|
+
def inject(self, obj: ta.Any) -> ta.Any:
|
2868
|
+
kws = self.provide_kwargs(obj)
|
2869
|
+
return obj(**kws)
|
2870
|
+
|
2871
|
+
|
2872
|
+
###
|
2873
|
+
# binder
|
2874
|
+
|
2875
|
+
|
2876
|
+
class InjectorBinder:
|
2877
|
+
def __new__(cls, *args, **kwargs): # noqa
|
2878
|
+
raise TypeError
|
2879
|
+
|
2880
|
+
_FN_TYPES: ta.Tuple[type, ...] = (
|
2881
|
+
types.FunctionType,
|
2882
|
+
types.MethodType,
|
2883
|
+
|
2884
|
+
classmethod,
|
2885
|
+
staticmethod,
|
2886
|
+
|
2887
|
+
functools.partial,
|
2888
|
+
functools.partialmethod,
|
2889
|
+
)
|
2890
|
+
|
2891
|
+
@classmethod
|
2892
|
+
def _is_fn(cls, obj: ta.Any) -> bool:
|
2893
|
+
return isinstance(obj, cls._FN_TYPES)
|
2894
|
+
|
2895
|
+
@classmethod
|
2896
|
+
def bind_as_fn(cls, icls: ta.Type[T]) -> ta.Type[T]:
|
2897
|
+
check_isinstance(icls, type)
|
2898
|
+
if icls not in cls._FN_TYPES:
|
2899
|
+
cls._FN_TYPES = (*cls._FN_TYPES, icls)
|
2900
|
+
return icls
|
2901
|
+
|
2902
|
+
_BANNED_BIND_TYPES: ta.Tuple[type, ...] = (
|
2903
|
+
InjectorProvider,
|
2904
|
+
)
|
2288
2905
|
|
2289
2906
|
@classmethod
|
2290
2907
|
def bind(
|
@@ -2301,7 +2918,7 @@ class InjectorBinder:
|
|
2301
2918
|
to_key: ta.Any = None,
|
2302
2919
|
|
2303
2920
|
singleton: bool = False,
|
2304
|
-
) ->
|
2921
|
+
) -> InjectorBindingOrBindings:
|
2305
2922
|
if obj is None or obj is inspect.Parameter.empty:
|
2306
2923
|
raise TypeError(obj)
|
2307
2924
|
if isinstance(obj, cls._BANNED_BIND_TYPES):
|
@@ -2331,9 +2948,9 @@ class InjectorBinder:
|
|
2331
2948
|
elif cls._is_fn(obj) and not has_to:
|
2332
2949
|
to_fn = obj
|
2333
2950
|
if key is None:
|
2334
|
-
|
2335
|
-
|
2336
|
-
key = InjectorKey(
|
2951
|
+
insp = _injection_inspect(obj)
|
2952
|
+
key_cls: ta.Any = check_valid_injector_key_cls(check_not_none(insp.type_hints.get('return')))
|
2953
|
+
key = InjectorKey(key_cls)
|
2337
2954
|
else:
|
2338
2955
|
if to_const is not None:
|
2339
2956
|
raise TypeError('Cannot bind instance with to_const')
|
@@ -2384,67 +3001,21 @@ class InjectorBinder:
|
|
2384
3001
|
|
2385
3002
|
|
2386
3003
|
###
|
2387
|
-
#
|
2388
|
-
|
2389
|
-
|
2390
|
-
_INJECTOR_INJECTOR_KEY = InjectorKey(Injector)
|
2391
|
-
|
2392
|
-
|
2393
|
-
class _Injector(Injector):
|
2394
|
-
def __init__(self, bs: InjectorBindings, p: ta.Optional[Injector] = None) -> None:
|
2395
|
-
super().__init__()
|
2396
|
-
|
2397
|
-
self._bs = check_isinstance(bs, InjectorBindings)
|
2398
|
-
self._p: ta.Optional[Injector] = check_isinstance(p, (Injector, type(None)))
|
2399
|
-
|
2400
|
-
self._pfm = {k: v.provider_fn() for k, v in build_injector_provider_map(bs).items()}
|
2401
|
-
|
2402
|
-
if _INJECTOR_INJECTOR_KEY in self._pfm:
|
2403
|
-
raise DuplicateInjectorKeyError(_INJECTOR_INJECTOR_KEY)
|
2404
|
-
|
2405
|
-
def try_provide(self, key: ta.Any) -> Maybe[ta.Any]:
|
2406
|
-
key = as_injector_key(key)
|
2407
|
-
|
2408
|
-
if key == _INJECTOR_INJECTOR_KEY:
|
2409
|
-
return Maybe.just(self)
|
2410
|
-
|
2411
|
-
fn = self._pfm.get(key)
|
2412
|
-
if fn is not None:
|
2413
|
-
return Maybe.just(fn(self))
|
3004
|
+
# injection helpers
|
2414
3005
|
|
2415
|
-
if self._p is not None:
|
2416
|
-
pv = self._p.try_provide(key)
|
2417
|
-
if pv is not None:
|
2418
|
-
return Maybe.empty()
|
2419
3006
|
|
2420
|
-
|
3007
|
+
def make_injector_factory(
|
3008
|
+
factory_cls: ta.Any,
|
3009
|
+
factory_fn: ta.Callable[..., T],
|
3010
|
+
) -> ta.Callable[..., Func[T]]:
|
3011
|
+
def outer(injector: Injector) -> factory_cls:
|
3012
|
+
def inner(*args, **kwargs):
|
3013
|
+
return injector.inject(functools.partial(factory_fn, *args, **kwargs))
|
3014
|
+
return Func(inner)
|
3015
|
+
return outer
|
2421
3016
|
|
2422
|
-
def provide(self, key: ta.Any) -> ta.Any:
|
2423
|
-
v = self.try_provide(key)
|
2424
|
-
if v.present:
|
2425
|
-
return v.must()
|
2426
|
-
raise UnboundInjectorKeyError(key)
|
2427
3017
|
|
2428
|
-
|
2429
|
-
kt = build_injection_kwargs_target(obj)
|
2430
|
-
ret: ta.Dict[str, ta.Any] = {}
|
2431
|
-
for kw in kt.kwargs:
|
2432
|
-
if kw.has_default:
|
2433
|
-
if not (mv := self.try_provide(kw.key)).present:
|
2434
|
-
continue
|
2435
|
-
v = mv.must()
|
2436
|
-
else:
|
2437
|
-
v = self.provide(kw.key)
|
2438
|
-
ret[kw.name] = v
|
2439
|
-
return ret
|
2440
|
-
|
2441
|
-
def inject(self, obj: ta.Any) -> ta.Any:
|
2442
|
-
kws = self.provide_kwargs(obj)
|
2443
|
-
return obj(**kws)
|
2444
|
-
|
2445
|
-
|
2446
|
-
###
|
2447
|
-
# injection helpers
|
3018
|
+
##
|
2448
3019
|
|
2449
3020
|
|
2450
3021
|
class Injection:
|
@@ -2475,6 +3046,12 @@ class Injection:
|
|
2475
3046
|
def override(cls, p: InjectorBindings, *args: InjectorBindingOrBindings) -> InjectorBindings:
|
2476
3047
|
return injector_override(p, *args)
|
2477
3048
|
|
3049
|
+
# injector
|
3050
|
+
|
3051
|
+
@classmethod
|
3052
|
+
def create_injector(cls, *args: InjectorBindingOrBindings, p: ta.Optional[Injector] = None) -> Injector:
|
3053
|
+
return _Injector(as_injector_bindings(*args), p)
|
3054
|
+
|
2478
3055
|
# binder
|
2479
3056
|
|
2480
3057
|
@classmethod
|
@@ -2492,7 +3069,7 @@ class Injection:
|
|
2492
3069
|
to_key: ta.Any = None,
|
2493
3070
|
|
2494
3071
|
singleton: bool = False,
|
2495
|
-
) ->
|
3072
|
+
) -> InjectorBindingOrBindings:
|
2496
3073
|
return InjectorBinder.bind(
|
2497
3074
|
obj,
|
2498
3075
|
|
@@ -2508,11 +3085,15 @@ class Injection:
|
|
2508
3085
|
singleton=singleton,
|
2509
3086
|
)
|
2510
3087
|
|
2511
|
-
#
|
3088
|
+
# helpers
|
2512
3089
|
|
2513
3090
|
@classmethod
|
2514
|
-
def
|
2515
|
-
|
3091
|
+
def bind_factory(
|
3092
|
+
cls,
|
3093
|
+
factory_cls: ta.Any,
|
3094
|
+
factory_fn: ta.Callable[..., T],
|
3095
|
+
) -> InjectorBindingOrBindings:
|
3096
|
+
return cls.bind(make_injector_factory(factory_cls, factory_fn))
|
2516
3097
|
|
2517
3098
|
|
2518
3099
|
inj = Injection
|
@@ -2542,7 +3123,6 @@ sd_iovec._fields_ = [
|
|
2542
3123
|
def sd_libsystemd() -> ta.Any:
|
2543
3124
|
lib = ct.CDLL('libsystemd.so.0')
|
2544
3125
|
|
2545
|
-
lib.sd_journal_sendv = lib['sd_journal_sendv'] # type: ignore
|
2546
3126
|
lib.sd_journal_sendv.restype = ct.c_int
|
2547
3127
|
lib.sd_journal_sendv.argtypes = [ct.POINTER(sd_iovec), ct.c_int]
|
2548
3128
|
|
@@ -3582,6 +4162,36 @@ def get_poller_impl() -> ta.Type[Poller]:
|
|
3582
4162
|
return SelectPoller
|
3583
4163
|
|
3584
4164
|
|
4165
|
+
########################################
|
4166
|
+
# ../../../omlish/lite/http/handlers.py
|
4167
|
+
|
4168
|
+
|
4169
|
+
@dc.dataclass(frozen=True)
|
4170
|
+
class HttpHandlerRequest:
|
4171
|
+
client_address: SocketAddress
|
4172
|
+
method: str
|
4173
|
+
path: str
|
4174
|
+
headers: HttpHeaders
|
4175
|
+
data: ta.Optional[bytes]
|
4176
|
+
|
4177
|
+
|
4178
|
+
@dc.dataclass(frozen=True)
|
4179
|
+
class HttpHandlerResponse:
|
4180
|
+
status: ta.Union[http.HTTPStatus, int]
|
4181
|
+
|
4182
|
+
headers: ta.Optional[ta.Mapping[str, str]] = None
|
4183
|
+
data: ta.Optional[bytes] = None
|
4184
|
+
close_connection: ta.Optional[bool] = None
|
4185
|
+
|
4186
|
+
|
4187
|
+
class HttpHandlerError(Exception):
|
4188
|
+
pass
|
4189
|
+
|
4190
|
+
|
4191
|
+
class UnsupportedMethodHttpHandlerError(Exception):
|
4192
|
+
pass
|
4193
|
+
|
4194
|
+
|
3585
4195
|
########################################
|
3586
4196
|
# ../configs.py
|
3587
4197
|
|
@@ -3703,11 +4313,571 @@ def prepare_server_config(dct: ta.Mapping[str, ta.Any]) -> ta.Mapping[str, ta.An
|
|
3703
4313
|
return out
|
3704
4314
|
|
3705
4315
|
|
4316
|
+
########################################
|
4317
|
+
# ../../../omlish/lite/http/coroserver.py
|
4318
|
+
# PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
4319
|
+
# --------------------------------------------
|
4320
|
+
#
|
4321
|
+
# 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization
|
4322
|
+
# ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated
|
4323
|
+
# documentation.
|
4324
|
+
#
|
4325
|
+
# 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive,
|
4326
|
+
# royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative
|
4327
|
+
# works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License
|
4328
|
+
# Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
|
4329
|
+
# 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017 Python Software Foundation; All Rights Reserved" are retained in Python
|
4330
|
+
# alone or in any derivative version prepared by Licensee.
|
4331
|
+
#
|
4332
|
+
# 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and
|
4333
|
+
# wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in
|
4334
|
+
# any such work a brief summary of the changes made to Python.
|
4335
|
+
#
|
4336
|
+
# 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES,
|
4337
|
+
# EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY
|
4338
|
+
# OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY
|
4339
|
+
# RIGHTS.
|
4340
|
+
#
|
4341
|
+
# 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL
|
4342
|
+
# DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF
|
4343
|
+
# ADVISED OF THE POSSIBILITY THEREOF.
|
4344
|
+
#
|
4345
|
+
# 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions.
|
4346
|
+
#
|
4347
|
+
# 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint
|
4348
|
+
# venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade
|
4349
|
+
# name in a trademark sense to endorse or promote products or services of Licensee, or any third party.
|
4350
|
+
#
|
4351
|
+
# 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this
|
4352
|
+
# License Agreement.
|
4353
|
+
"""
|
4354
|
+
"Test suite" lol:
|
4355
|
+
|
4356
|
+
curl -v localhost:8000
|
4357
|
+
curl -v localhost:8000 -d 'foo'
|
4358
|
+
curl -v -XFOO localhost:8000 -d 'foo'
|
4359
|
+
curl -v -XPOST -H 'Expect: 100-Continue' localhost:8000 -d 'foo'
|
4360
|
+
|
4361
|
+
curl -v -0 localhost:8000
|
4362
|
+
curl -v -0 localhost:8000 -d 'foo'
|
4363
|
+
curl -v -0 -XFOO localhost:8000 -d 'foo'
|
4364
|
+
|
4365
|
+
curl -v -XPOST localhost:8000 -d 'foo' --next -XPOST localhost:8000 -d 'bar'
|
4366
|
+
curl -v -XPOST localhost:8000 -d 'foo' --next -XFOO localhost:8000 -d 'bar'
|
4367
|
+
curl -v -XFOO localhost:8000 -d 'foo' --next -XPOST localhost:8000 -d 'bar'
|
4368
|
+
curl -v -XFOO localhost:8000 -d 'foo' --next -XFOO localhost:8000 -d 'bar'
|
4369
|
+
"""
|
4370
|
+
|
4371
|
+
|
4372
|
+
##
|
4373
|
+
|
4374
|
+
|
4375
|
+
class CoroHttpServer:
|
4376
|
+
"""
|
4377
|
+
Adapted from stdlib:
|
4378
|
+
- https://github.com/python/cpython/blob/4b4e0dbdf49adc91c35a357ad332ab3abd4c31b1/Lib/http/server.py#L146
|
4379
|
+
"""
|
4380
|
+
|
4381
|
+
#
|
4382
|
+
|
4383
|
+
def __init__(
|
4384
|
+
self,
|
4385
|
+
client_address: SocketAddress,
|
4386
|
+
*,
|
4387
|
+
handler: HttpHandler,
|
4388
|
+
parser: HttpRequestParser = HttpRequestParser(),
|
4389
|
+
|
4390
|
+
default_content_type: ta.Optional[str] = None,
|
4391
|
+
|
4392
|
+
error_message_format: ta.Optional[str] = None,
|
4393
|
+
error_content_type: ta.Optional[str] = None,
|
4394
|
+
) -> None:
|
4395
|
+
super().__init__()
|
4396
|
+
|
4397
|
+
self._client_address = client_address
|
4398
|
+
|
4399
|
+
self._handler = handler
|
4400
|
+
self._parser = parser
|
4401
|
+
|
4402
|
+
self._default_content_type = default_content_type or self.DEFAULT_CONTENT_TYPE
|
4403
|
+
|
4404
|
+
self._error_message_format = error_message_format or self.DEFAULT_ERROR_MESSAGE
|
4405
|
+
self._error_content_type = error_content_type or self.DEFAULT_ERROR_CONTENT_TYPE
|
4406
|
+
|
4407
|
+
#
|
4408
|
+
|
4409
|
+
@property
|
4410
|
+
def client_address(self) -> SocketAddress:
|
4411
|
+
return self._client_address
|
4412
|
+
|
4413
|
+
@property
|
4414
|
+
def handler(self) -> HttpHandler:
|
4415
|
+
return self._handler
|
4416
|
+
|
4417
|
+
@property
|
4418
|
+
def parser(self) -> HttpRequestParser:
|
4419
|
+
return self._parser
|
4420
|
+
|
4421
|
+
#
|
4422
|
+
|
4423
|
+
def _format_timestamp(self, timestamp: ta.Optional[float] = None) -> str:
|
4424
|
+
if timestamp is None:
|
4425
|
+
timestamp = time.time()
|
4426
|
+
return email.utils.formatdate(timestamp, usegmt=True)
|
4427
|
+
|
4428
|
+
#
|
4429
|
+
|
4430
|
+
def _header_encode(self, s: str) -> bytes:
|
4431
|
+
return s.encode('latin-1', 'strict')
|
4432
|
+
|
4433
|
+
class _Header(ta.NamedTuple):
|
4434
|
+
key: str
|
4435
|
+
value: str
|
4436
|
+
|
4437
|
+
def _format_header_line(self, h: _Header) -> str:
|
4438
|
+
return f'{h.key}: {h.value}\r\n'
|
4439
|
+
|
4440
|
+
def _get_header_close_connection_action(self, h: _Header) -> ta.Optional[bool]:
|
4441
|
+
if h.key.lower() != 'connection':
|
4442
|
+
return None
|
4443
|
+
elif h.value.lower() == 'close':
|
4444
|
+
return True
|
4445
|
+
elif h.value.lower() == 'keep-alive':
|
4446
|
+
return False
|
4447
|
+
else:
|
4448
|
+
return None
|
4449
|
+
|
4450
|
+
def _make_default_headers(self) -> ta.List[_Header]:
|
4451
|
+
return [
|
4452
|
+
self._Header('Date', self._format_timestamp()),
|
4453
|
+
]
|
4454
|
+
|
4455
|
+
#
|
4456
|
+
|
4457
|
+
_STATUS_RESPONSES: ta.Mapping[int, ta.Tuple[str, str]] = {
|
4458
|
+
v: (v.phrase, v.description)
|
4459
|
+
for v in http.HTTPStatus.__members__.values()
|
4460
|
+
}
|
4461
|
+
|
4462
|
+
def _format_status_line(
|
4463
|
+
self,
|
4464
|
+
version: HttpProtocolVersion,
|
4465
|
+
code: ta.Union[http.HTTPStatus, int],
|
4466
|
+
message: ta.Optional[str] = None,
|
4467
|
+
) -> str:
|
4468
|
+
if message is None:
|
4469
|
+
if code in self._STATUS_RESPONSES:
|
4470
|
+
message = self._STATUS_RESPONSES[code][0]
|
4471
|
+
else:
|
4472
|
+
message = ''
|
4473
|
+
|
4474
|
+
return f'{version} {int(code)} {message}\r\n'
|
4475
|
+
|
4476
|
+
#
|
4477
|
+
|
4478
|
+
@dc.dataclass(frozen=True)
|
4479
|
+
class _Response:
|
4480
|
+
version: HttpProtocolVersion
|
4481
|
+
code: http.HTTPStatus
|
4482
|
+
|
4483
|
+
message: ta.Optional[str] = None
|
4484
|
+
headers: ta.Optional[ta.Sequence['CoroHttpServer._Header']] = None
|
4485
|
+
data: ta.Optional[bytes] = None
|
4486
|
+
close_connection: ta.Optional[bool] = False
|
4487
|
+
|
4488
|
+
def get_header(self, key: str) -> ta.Optional['CoroHttpServer._Header']:
|
4489
|
+
for h in self.headers or []:
|
4490
|
+
if h.key.lower() == key.lower():
|
4491
|
+
return h
|
4492
|
+
return None
|
4493
|
+
|
4494
|
+
#
|
4495
|
+
|
4496
|
+
def _build_response_bytes(self, a: _Response) -> bytes:
|
4497
|
+
out = io.BytesIO()
|
4498
|
+
|
4499
|
+
if a.version >= HttpProtocolVersions.HTTP_1_0:
|
4500
|
+
out.write(self._header_encode(self._format_status_line(
|
4501
|
+
a.version,
|
4502
|
+
a.code,
|
4503
|
+
a.message,
|
4504
|
+
)))
|
4505
|
+
|
4506
|
+
for h in a.headers or []:
|
4507
|
+
out.write(self._header_encode(self._format_header_line(h)))
|
4508
|
+
|
4509
|
+
out.write(b'\r\n')
|
4510
|
+
|
4511
|
+
if a.data is not None:
|
4512
|
+
out.write(a.data)
|
4513
|
+
|
4514
|
+
return out.getvalue()
|
4515
|
+
|
4516
|
+
#
|
4517
|
+
|
4518
|
+
DEFAULT_CONTENT_TYPE = 'text/plain'
|
4519
|
+
|
4520
|
+
def _preprocess_response(self, resp: _Response) -> _Response:
|
4521
|
+
nh: ta.List[CoroHttpServer._Header] = []
|
4522
|
+
kw: ta.Dict[str, ta.Any] = {}
|
4523
|
+
|
4524
|
+
if resp.get_header('Content-Type') is None:
|
4525
|
+
nh.append(self._Header('Content-Type', self._default_content_type))
|
4526
|
+
if resp.data is not None and resp.get_header('Content-Length') is None:
|
4527
|
+
nh.append(self._Header('Content-Length', str(len(resp.data))))
|
4528
|
+
|
4529
|
+
if nh:
|
4530
|
+
kw.update(headers=[*(resp.headers or []), *nh])
|
4531
|
+
|
4532
|
+
if (clh := resp.get_header('Connection')) is not None:
|
4533
|
+
if self._get_header_close_connection_action(clh):
|
4534
|
+
kw.update(close_connection=True)
|
4535
|
+
|
4536
|
+
if not kw:
|
4537
|
+
return resp
|
4538
|
+
return dc.replace(resp, **kw)
|
4539
|
+
|
4540
|
+
#
|
4541
|
+
|
4542
|
+
@dc.dataclass(frozen=True)
|
4543
|
+
class Error:
|
4544
|
+
version: HttpProtocolVersion
|
4545
|
+
code: http.HTTPStatus
|
4546
|
+
message: str
|
4547
|
+
explain: str
|
4548
|
+
|
4549
|
+
method: ta.Optional[str] = None
|
4550
|
+
|
4551
|
+
def _build_error(
|
4552
|
+
self,
|
4553
|
+
code: ta.Union[http.HTTPStatus, int],
|
4554
|
+
message: ta.Optional[str] = None,
|
4555
|
+
explain: ta.Optional[str] = None,
|
4556
|
+
*,
|
4557
|
+
version: ta.Optional[HttpProtocolVersion] = None,
|
4558
|
+
method: ta.Optional[str] = None,
|
4559
|
+
) -> Error:
|
4560
|
+
code = http.HTTPStatus(code)
|
4561
|
+
|
4562
|
+
try:
|
4563
|
+
short_msg, long_msg = self._STATUS_RESPONSES[code]
|
4564
|
+
except KeyError:
|
4565
|
+
short_msg, long_msg = '???', '???'
|
4566
|
+
if message is None:
|
4567
|
+
message = short_msg
|
4568
|
+
if explain is None:
|
4569
|
+
explain = long_msg
|
4570
|
+
|
4571
|
+
if version is None:
|
4572
|
+
version = self._parser.server_version
|
4573
|
+
|
4574
|
+
return self.Error(
|
4575
|
+
version=version,
|
4576
|
+
code=code,
|
4577
|
+
message=message,
|
4578
|
+
explain=explain,
|
4579
|
+
|
4580
|
+
method=method,
|
4581
|
+
)
|
4582
|
+
|
4583
|
+
#
|
4584
|
+
|
4585
|
+
DEFAULT_ERROR_MESSAGE = textwrap.dedent("""\
|
4586
|
+
<!DOCTYPE HTML>
|
4587
|
+
<html lang="en">
|
4588
|
+
<head>
|
4589
|
+
<meta charset="utf-8">
|
4590
|
+
<title>Error response</title>
|
4591
|
+
</head>
|
4592
|
+
<body>
|
4593
|
+
<h1>Error response</h1>
|
4594
|
+
<p>Error code: %(code)d</p>
|
4595
|
+
<p>Message: %(message)s.</p>
|
4596
|
+
<p>Error code explanation: %(code)s - %(explain)s.</p>
|
4597
|
+
</body>
|
4598
|
+
</html>
|
4599
|
+
""")
|
4600
|
+
|
4601
|
+
DEFAULT_ERROR_CONTENT_TYPE = 'text/html;charset=utf-8'
|
4602
|
+
|
4603
|
+
def _build_error_response(self, err: Error) -> _Response:
|
4604
|
+
headers: ta.List[CoroHttpServer._Header] = [
|
4605
|
+
*self._make_default_headers(),
|
4606
|
+
self._Header('Connection', 'close'),
|
4607
|
+
]
|
4608
|
+
|
4609
|
+
# Message body is omitted for cases described in:
|
4610
|
+
# - RFC7230: 3.3. 1xx, 204(No Content), 304(Not Modified)
|
4611
|
+
# - RFC7231: 6.3.6. 205(Reset Content)
|
4612
|
+
data: ta.Optional[bytes] = None
|
4613
|
+
if (
|
4614
|
+
err.code >= http.HTTPStatus.OK and
|
4615
|
+
err.code not in (
|
4616
|
+
http.HTTPStatus.NO_CONTENT,
|
4617
|
+
http.HTTPStatus.RESET_CONTENT,
|
4618
|
+
http.HTTPStatus.NOT_MODIFIED,
|
4619
|
+
)
|
4620
|
+
):
|
4621
|
+
# HTML encode to prevent Cross Site Scripting attacks (see bug #1100201)
|
4622
|
+
content = self._error_message_format.format(
|
4623
|
+
code=err.code,
|
4624
|
+
message=html.escape(err.message, quote=False),
|
4625
|
+
explain=html.escape(err.explain, quote=False),
|
4626
|
+
)
|
4627
|
+
body = content.encode('UTF-8', 'replace')
|
4628
|
+
|
4629
|
+
headers.extend([
|
4630
|
+
self._Header('Content-Type', self._error_content_type),
|
4631
|
+
self._Header('Content-Length', str(len(body))),
|
4632
|
+
])
|
4633
|
+
|
4634
|
+
if err.method != 'HEAD' and body:
|
4635
|
+
data = body
|
4636
|
+
|
4637
|
+
return self._Response(
|
4638
|
+
version=err.version,
|
4639
|
+
code=err.code,
|
4640
|
+
message=err.message,
|
4641
|
+
headers=headers,
|
4642
|
+
data=data,
|
4643
|
+
close_connection=True,
|
4644
|
+
)
|
4645
|
+
|
4646
|
+
#
|
4647
|
+
|
4648
|
+
class Io(abc.ABC): # noqa
|
4649
|
+
pass
|
4650
|
+
|
4651
|
+
#
|
4652
|
+
|
4653
|
+
class AnyLogIo(Io):
|
4654
|
+
pass
|
4655
|
+
|
4656
|
+
@dc.dataclass(frozen=True)
|
4657
|
+
class ParsedRequestLogIo(AnyLogIo):
|
4658
|
+
request: ParsedHttpRequest
|
4659
|
+
|
4660
|
+
@dc.dataclass(frozen=True)
|
4661
|
+
class ErrorLogIo(AnyLogIo):
|
4662
|
+
error: 'CoroHttpServer.Error'
|
4663
|
+
|
4664
|
+
#
|
4665
|
+
|
4666
|
+
class AnyReadIo(Io): # noqa
|
4667
|
+
pass
|
4668
|
+
|
4669
|
+
@dc.dataclass(frozen=True)
|
4670
|
+
class ReadIo(AnyReadIo):
|
4671
|
+
sz: int
|
4672
|
+
|
4673
|
+
@dc.dataclass(frozen=True)
|
4674
|
+
class ReadLineIo(AnyReadIo):
|
4675
|
+
sz: int
|
4676
|
+
|
4677
|
+
#
|
4678
|
+
|
4679
|
+
@dc.dataclass(frozen=True)
|
4680
|
+
class WriteIo(Io):
|
4681
|
+
data: bytes
|
4682
|
+
|
4683
|
+
#
|
4684
|
+
|
4685
|
+
def coro_handle(self) -> ta.Generator[Io, ta.Optional[bytes], None]:
|
4686
|
+
while True:
|
4687
|
+
gen = self.coro_handle_one()
|
4688
|
+
|
4689
|
+
o = next(gen)
|
4690
|
+
i: ta.Optional[bytes]
|
4691
|
+
while True:
|
4692
|
+
if isinstance(o, self.AnyLogIo):
|
4693
|
+
i = None
|
4694
|
+
yield o
|
4695
|
+
|
4696
|
+
elif isinstance(o, self.AnyReadIo):
|
4697
|
+
i = check_isinstance((yield o), bytes)
|
4698
|
+
|
4699
|
+
elif isinstance(o, self._Response):
|
4700
|
+
i = None
|
4701
|
+
r = self._preprocess_response(o)
|
4702
|
+
b = self._build_response_bytes(r)
|
4703
|
+
check_none((yield self.WriteIo(b)))
|
4704
|
+
|
4705
|
+
else:
|
4706
|
+
raise TypeError(o)
|
4707
|
+
|
4708
|
+
try:
|
4709
|
+
o = gen.send(i)
|
4710
|
+
except EOFError:
|
4711
|
+
return
|
4712
|
+
except StopIteration:
|
4713
|
+
break
|
4714
|
+
|
4715
|
+
def coro_handle_one(self) -> ta.Generator[
|
4716
|
+
ta.Union[AnyLogIo, AnyReadIo, _Response],
|
4717
|
+
ta.Optional[bytes],
|
4718
|
+
None,
|
4719
|
+
]:
|
4720
|
+
# Parse request
|
4721
|
+
|
4722
|
+
gen = self._parser.coro_parse()
|
4723
|
+
sz = next(gen)
|
4724
|
+
while True:
|
4725
|
+
try:
|
4726
|
+
line = check_isinstance((yield self.ReadLineIo(sz)), bytes)
|
4727
|
+
sz = gen.send(line)
|
4728
|
+
except StopIteration as e:
|
4729
|
+
parsed = e.value
|
4730
|
+
break
|
4731
|
+
|
4732
|
+
if isinstance(parsed, EmptyParsedHttpResult):
|
4733
|
+
raise EOFError # noqa
|
4734
|
+
|
4735
|
+
if isinstance(parsed, ParseHttpRequestError):
|
4736
|
+
err = self._build_error(
|
4737
|
+
parsed.code,
|
4738
|
+
*parsed.message,
|
4739
|
+
version=parsed.version,
|
4740
|
+
)
|
4741
|
+
yield self.ErrorLogIo(err)
|
4742
|
+
yield self._build_error_response(err)
|
4743
|
+
return
|
4744
|
+
|
4745
|
+
parsed = check_isinstance(parsed, ParsedHttpRequest)
|
4746
|
+
|
4747
|
+
# Log
|
4748
|
+
|
4749
|
+
check_none((yield self.ParsedRequestLogIo(parsed)))
|
4750
|
+
|
4751
|
+
# Handle CONTINUE
|
4752
|
+
|
4753
|
+
if parsed.expects_continue:
|
4754
|
+
# https://bugs.python.org/issue1491
|
4755
|
+
# https://github.com/python/cpython/commit/0f476d49f8d4aa84210392bf13b59afc67b32b31
|
4756
|
+
yield self._Response(
|
4757
|
+
version=parsed.version,
|
4758
|
+
code=http.HTTPStatus.CONTINUE,
|
4759
|
+
)
|
4760
|
+
|
4761
|
+
# Read data
|
4762
|
+
|
4763
|
+
request_data: ta.Optional[bytes]
|
4764
|
+
if (cl := parsed.headers.get('Content-Length')) is not None:
|
4765
|
+
request_data = check_isinstance((yield self.ReadIo(int(cl))), bytes)
|
4766
|
+
else:
|
4767
|
+
request_data = None
|
4768
|
+
|
4769
|
+
# Build request
|
4770
|
+
|
4771
|
+
handler_request = HttpHandlerRequest(
|
4772
|
+
client_address=self._client_address,
|
4773
|
+
method=check_not_none(parsed.method),
|
4774
|
+
path=parsed.path,
|
4775
|
+
headers=parsed.headers,
|
4776
|
+
data=request_data,
|
4777
|
+
)
|
4778
|
+
|
4779
|
+
# Build handler response
|
4780
|
+
|
4781
|
+
try:
|
4782
|
+
handler_response = self._handler(handler_request)
|
4783
|
+
|
4784
|
+
except UnsupportedMethodHttpHandlerError:
|
4785
|
+
err = self._build_error(
|
4786
|
+
http.HTTPStatus.NOT_IMPLEMENTED,
|
4787
|
+
f'Unsupported method ({parsed.method!r})',
|
4788
|
+
version=parsed.version,
|
4789
|
+
method=parsed.method,
|
4790
|
+
)
|
4791
|
+
yield self.ErrorLogIo(err)
|
4792
|
+
yield self._build_error_response(err)
|
4793
|
+
return
|
4794
|
+
|
4795
|
+
# Build internal response
|
4796
|
+
|
4797
|
+
response_headers = handler_response.headers or {}
|
4798
|
+
response_data = handler_response.data
|
4799
|
+
|
4800
|
+
headers: ta.List[CoroHttpServer._Header] = [
|
4801
|
+
*self._make_default_headers(),
|
4802
|
+
]
|
4803
|
+
|
4804
|
+
for k, v in response_headers.items():
|
4805
|
+
headers.append(self._Header(k, v))
|
4806
|
+
|
4807
|
+
if handler_response.close_connection and 'Connection' not in headers:
|
4808
|
+
headers.append(self._Header('Connection', 'close'))
|
4809
|
+
|
4810
|
+
yield self._Response(
|
4811
|
+
version=parsed.version,
|
4812
|
+
code=http.HTTPStatus(handler_response.status),
|
4813
|
+
headers=headers,
|
4814
|
+
data=response_data,
|
4815
|
+
close_connection=handler_response.close_connection,
|
4816
|
+
)
|
4817
|
+
|
4818
|
+
|
4819
|
+
##
|
4820
|
+
|
4821
|
+
|
4822
|
+
class CoroHttpServerSocketHandler(SocketHandler):
|
4823
|
+
def __init__(
|
4824
|
+
self,
|
4825
|
+
client_address: SocketAddress,
|
4826
|
+
rfile: ta.BinaryIO,
|
4827
|
+
wfile: ta.BinaryIO,
|
4828
|
+
*,
|
4829
|
+
server_factory: CoroHttpServerFactory,
|
4830
|
+
log_handler: ta.Optional[ta.Callable[[CoroHttpServer, CoroHttpServer.AnyLogIo], None]] = None,
|
4831
|
+
) -> None:
|
4832
|
+
super().__init__(
|
4833
|
+
client_address,
|
4834
|
+
rfile,
|
4835
|
+
wfile,
|
4836
|
+
)
|
4837
|
+
|
4838
|
+
self._server_factory = server_factory
|
4839
|
+
self._log_handler = log_handler
|
4840
|
+
|
4841
|
+
def handle(self) -> None:
|
4842
|
+
server = self._server_factory(self._client_address)
|
4843
|
+
|
4844
|
+
gen = server.coro_handle()
|
4845
|
+
|
4846
|
+
o = next(gen)
|
4847
|
+
while True:
|
4848
|
+
if isinstance(o, CoroHttpServer.AnyLogIo):
|
4849
|
+
i = None
|
4850
|
+
if self._log_handler is not None:
|
4851
|
+
self._log_handler(server, o)
|
4852
|
+
|
4853
|
+
elif isinstance(o, CoroHttpServer.ReadIo):
|
4854
|
+
i = self._rfile.read(o.sz)
|
4855
|
+
|
4856
|
+
elif isinstance(o, CoroHttpServer.ReadLineIo):
|
4857
|
+
i = self._rfile.readline(o.sz)
|
4858
|
+
|
4859
|
+
elif isinstance(o, CoroHttpServer.WriteIo):
|
4860
|
+
i = None
|
4861
|
+
self._wfile.write(o.data)
|
4862
|
+
self._wfile.flush()
|
4863
|
+
|
4864
|
+
else:
|
4865
|
+
raise TypeError(o)
|
4866
|
+
|
4867
|
+
try:
|
4868
|
+
if i is not None:
|
4869
|
+
o = gen.send(i)
|
4870
|
+
else:
|
4871
|
+
o = next(gen)
|
4872
|
+
except StopIteration:
|
4873
|
+
break
|
4874
|
+
|
4875
|
+
|
3706
4876
|
########################################
|
3707
4877
|
# ../types.py
|
3708
4878
|
|
3709
4879
|
|
3710
|
-
class
|
4880
|
+
class ServerContext(abc.ABC):
|
3711
4881
|
@property
|
3712
4882
|
@abc.abstractmethod
|
3713
4883
|
def config(self) -> ServerConfig:
|
@@ -3724,12 +4894,24 @@ class AbstractServerContext(abc.ABC):
|
|
3724
4894
|
|
3725
4895
|
@property
|
3726
4896
|
@abc.abstractmethod
|
3727
|
-
def pid_history(self) -> ta.Dict[int, '
|
4897
|
+
def pid_history(self) -> ta.Dict[int, 'Process']:
|
3728
4898
|
raise NotImplementedError
|
3729
4899
|
|
3730
4900
|
|
4901
|
+
# class Dispatcher(abc.ABC):
|
4902
|
+
# pass
|
4903
|
+
#
|
4904
|
+
#
|
4905
|
+
# class OutputDispatcher(Dispatcher, abc.ABC):
|
4906
|
+
# pass
|
4907
|
+
#
|
4908
|
+
#
|
4909
|
+
# class InputDispatcher(Dispatcher, abc.ABC):
|
4910
|
+
# pass
|
4911
|
+
|
4912
|
+
|
3731
4913
|
@functools.total_ordering
|
3732
|
-
class
|
4914
|
+
class Process(abc.ABC):
|
3733
4915
|
@property
|
3734
4916
|
@abc.abstractmethod
|
3735
4917
|
def pid(self) -> int:
|
@@ -3748,7 +4930,7 @@ class AbstractSubprocess(abc.ABC):
|
|
3748
4930
|
|
3749
4931
|
@property
|
3750
4932
|
@abc.abstractmethod
|
3751
|
-
def context(self) ->
|
4933
|
+
def context(self) -> ServerContext:
|
3752
4934
|
raise NotImplementedError
|
3753
4935
|
|
3754
4936
|
@abc.abstractmethod
|
@@ -3784,12 +4966,12 @@ class AbstractSubprocess(abc.ABC):
|
|
3784
4966
|
raise NotImplementedError
|
3785
4967
|
|
3786
4968
|
@abc.abstractmethod
|
3787
|
-
def get_dispatchers(self) -> ta.Mapping[int, ta.Any]: #
|
4969
|
+
def get_dispatchers(self) -> ta.Mapping[int, ta.Any]: # Dispatcher]:
|
3788
4970
|
raise NotImplementedError
|
3789
4971
|
|
3790
4972
|
|
3791
4973
|
@functools.total_ordering
|
3792
|
-
class
|
4974
|
+
class ProcessGroup(abc.ABC):
|
3793
4975
|
@property
|
3794
4976
|
@abc.abstractmethod
|
3795
4977
|
def config(self) -> ProcessGroupConfig:
|
@@ -3801,12 +4983,48 @@ class AbstractProcessGroup(abc.ABC):
|
|
3801
4983
|
def __eq__(self, other):
|
3802
4984
|
return self.config.priority == other.config.priority
|
3803
4985
|
|
4986
|
+
@abc.abstractmethod
|
4987
|
+
def transition(self) -> None:
|
4988
|
+
raise NotImplementedError
|
4989
|
+
|
4990
|
+
@abc.abstractmethod
|
4991
|
+
def stop_all(self) -> None:
|
4992
|
+
raise NotImplementedError
|
4993
|
+
|
4994
|
+
@property
|
4995
|
+
@abc.abstractmethod
|
4996
|
+
def name(self) -> str:
|
4997
|
+
raise NotImplementedError
|
4998
|
+
|
4999
|
+
@abc.abstractmethod
|
5000
|
+
def before_remove(self) -> None:
|
5001
|
+
raise NotImplementedError
|
5002
|
+
|
5003
|
+
@abc.abstractmethod
|
5004
|
+
def get_dispatchers(self) -> ta.Mapping[int, ta.Any]: # Dispatcher]:
|
5005
|
+
raise NotImplementedError
|
5006
|
+
|
5007
|
+
@abc.abstractmethod
|
5008
|
+
def reopen_logs(self) -> None:
|
5009
|
+
raise NotImplementedError
|
5010
|
+
|
5011
|
+
@abc.abstractmethod
|
5012
|
+
def get_unstopped_processes(self) -> ta.List[Process]:
|
5013
|
+
raise NotImplementedError
|
5014
|
+
|
5015
|
+
@abc.abstractmethod
|
5016
|
+
def after_setuid(self) -> None:
|
5017
|
+
raise NotImplementedError
|
5018
|
+
|
3804
5019
|
|
3805
5020
|
########################################
|
3806
5021
|
# ../context.py
|
3807
5022
|
|
3808
5023
|
|
3809
|
-
|
5024
|
+
ServerEpoch = ta.NewType('ServerEpoch', int)
|
5025
|
+
|
5026
|
+
|
5027
|
+
class ServerContextImpl(ServerContext):
|
3810
5028
|
def __init__(
|
3811
5029
|
self,
|
3812
5030
|
config: ServerConfig,
|
@@ -3820,7 +5038,7 @@ class ServerContext(AbstractServerContext):
|
|
3820
5038
|
self._poller = poller
|
3821
5039
|
self._epoch = epoch
|
3822
5040
|
|
3823
|
-
self._pid_history: ta.Dict[int,
|
5041
|
+
self._pid_history: ta.Dict[int, Process] = {}
|
3824
5042
|
self._state: SupervisorState = SupervisorState.RUNNING
|
3825
5043
|
|
3826
5044
|
if config.user is not None:
|
@@ -3853,7 +5071,7 @@ class ServerContext(AbstractServerContext):
|
|
3853
5071
|
self._state = state
|
3854
5072
|
|
3855
5073
|
@property
|
3856
|
-
def pid_history(self) -> ta.Dict[int,
|
5074
|
+
def pid_history(self) -> ta.Dict[int, Process]:
|
3857
5075
|
return self._pid_history
|
3858
5076
|
|
3859
5077
|
@property
|
@@ -4200,7 +5418,7 @@ def check_execv_args(filename, argv, st) -> None:
|
|
4200
5418
|
class Dispatcher(abc.ABC):
|
4201
5419
|
def __init__(
|
4202
5420
|
self,
|
4203
|
-
process:
|
5421
|
+
process: Process,
|
4204
5422
|
channel: str,
|
4205
5423
|
fd: int,
|
4206
5424
|
*,
|
@@ -4219,7 +5437,7 @@ class Dispatcher(abc.ABC):
|
|
4219
5437
|
return f'<{self.__class__.__name__} at {id(self)} for {self._process} ({self._channel})>'
|
4220
5438
|
|
4221
5439
|
@property
|
4222
|
-
def process(self) ->
|
5440
|
+
def process(self) -> Process:
|
4223
5441
|
return self._process
|
4224
5442
|
|
4225
5443
|
@property
|
@@ -4274,7 +5492,7 @@ class OutputDispatcher(Dispatcher):
|
|
4274
5492
|
|
4275
5493
|
def __init__(
|
4276
5494
|
self,
|
4277
|
-
process:
|
5495
|
+
process: Process,
|
4278
5496
|
event_type: ta.Type[ProcessCommunicationEvent],
|
4279
5497
|
fd: int,
|
4280
5498
|
**kwargs: ta.Any,
|
@@ -4483,7 +5701,7 @@ class OutputDispatcher(Dispatcher):
|
|
4483
5701
|
class InputDispatcher(Dispatcher):
|
4484
5702
|
def __init__(
|
4485
5703
|
self,
|
4486
|
-
process:
|
5704
|
+
process: Process,
|
4487
5705
|
channel: str,
|
4488
5706
|
fd: int,
|
4489
5707
|
**kwargs: ta.Any,
|
@@ -4532,31 +5750,26 @@ class InputDispatcher(Dispatcher):
|
|
4532
5750
|
##
|
4533
5751
|
|
4534
5752
|
|
4535
|
-
|
4536
|
-
class SubprocessFactory:
|
4537
|
-
fn: ta.Callable[[ProcessConfig, AbstractProcessGroup], AbstractSubprocess]
|
4538
|
-
|
4539
|
-
def __call__(self, config: ProcessConfig, group: AbstractProcessGroup) -> AbstractSubprocess:
|
4540
|
-
return self.fn(config, group)
|
5753
|
+
ProcessFactory = ta.NewType('ProcessFactory', Func[Process]) # (config: ProcessConfig, group: ProcessGroup)
|
4541
5754
|
|
4542
5755
|
|
4543
|
-
class ProcessGroup
|
5756
|
+
class ProcessGroupImpl(ProcessGroup):
|
4544
5757
|
def __init__(
|
4545
5758
|
self,
|
4546
5759
|
config: ProcessGroupConfig,
|
4547
5760
|
context: ServerContext,
|
4548
5761
|
*,
|
4549
|
-
|
5762
|
+
process_factory: ProcessFactory,
|
4550
5763
|
):
|
4551
5764
|
super().__init__()
|
4552
5765
|
|
4553
5766
|
self._config = config
|
4554
5767
|
self._context = context
|
4555
|
-
self.
|
5768
|
+
self._process_factory = process_factory
|
4556
5769
|
|
4557
5770
|
self._processes = {}
|
4558
5771
|
for pconfig in self._config.processes or []:
|
4559
|
-
process = self.
|
5772
|
+
process = self._process_factory(pconfig, self)
|
4560
5773
|
self._processes[pconfig.name] = process
|
4561
5774
|
|
4562
5775
|
@property
|
@@ -4568,7 +5781,7 @@ class ProcessGroup(AbstractProcessGroup):
|
|
4568
5781
|
return self._config.name
|
4569
5782
|
|
4570
5783
|
@property
|
4571
|
-
def context(self) ->
|
5784
|
+
def context(self) -> ServerContext:
|
4572
5785
|
return self._context
|
4573
5786
|
|
4574
5787
|
def __repr__(self):
|
@@ -4603,7 +5816,7 @@ class ProcessGroup(AbstractProcessGroup):
|
|
4603
5816
|
# BACKOFF -> FATAL
|
4604
5817
|
proc.give_up()
|
4605
5818
|
|
4606
|
-
def get_unstopped_processes(self) -> ta.List[
|
5819
|
+
def get_unstopped_processes(self) -> ta.List[Process]:
|
4607
5820
|
return [x for x in self._processes.values() if not x.get_state().stopped]
|
4608
5821
|
|
4609
5822
|
def get_dispatchers(self) -> ta.Dict[int, Dispatcher]:
|
@@ -4680,18 +5893,21 @@ class ProcessGroups:
|
|
4680
5893
|
# ../process.py
|
4681
5894
|
|
4682
5895
|
|
5896
|
+
InheritedFds = ta.NewType('InheritedFds', ta.FrozenSet[int])
|
5897
|
+
|
5898
|
+
|
4683
5899
|
##
|
4684
5900
|
|
4685
5901
|
|
4686
|
-
class
|
5902
|
+
class ProcessImpl(Process):
|
4687
5903
|
"""A class to manage a subprocess."""
|
4688
5904
|
|
4689
5905
|
def __init__(
|
4690
5906
|
self,
|
4691
5907
|
config: ProcessConfig,
|
4692
|
-
group:
|
5908
|
+
group: ProcessGroup,
|
4693
5909
|
*,
|
4694
|
-
context:
|
5910
|
+
context: ServerContext,
|
4695
5911
|
event_callbacks: EventCallbacks,
|
4696
5912
|
|
4697
5913
|
inherited_fds: ta.Optional[InheritedFds] = None,
|
@@ -4730,7 +5946,7 @@ class Subprocess(AbstractSubprocess):
|
|
4730
5946
|
return self._pid
|
4731
5947
|
|
4732
5948
|
@property
|
4733
|
-
def group(self) ->
|
5949
|
+
def group(self) -> ProcessGroup:
|
4734
5950
|
return self._group
|
4735
5951
|
|
4736
5952
|
@property
|
@@ -4738,7 +5954,7 @@ class Subprocess(AbstractSubprocess):
|
|
4738
5954
|
return self._config
|
4739
5955
|
|
4740
5956
|
@property
|
4741
|
-
def context(self) ->
|
5957
|
+
def context(self) -> ServerContext:
|
4742
5958
|
return self._context
|
4743
5959
|
|
4744
5960
|
@property
|
@@ -5385,7 +6601,7 @@ class SignalHandler:
|
|
5385
6601
|
def __init__(
|
5386
6602
|
self,
|
5387
6603
|
*,
|
5388
|
-
context:
|
6604
|
+
context: ServerContextImpl,
|
5389
6605
|
signal_receiver: SignalReceiver,
|
5390
6606
|
process_groups: ProcessGroups,
|
5391
6607
|
) -> None:
|
@@ -5437,19 +6653,14 @@ class SignalHandler:
|
|
5437
6653
|
##
|
5438
6654
|
|
5439
6655
|
|
5440
|
-
|
5441
|
-
class ProcessGroupFactory:
|
5442
|
-
fn: ta.Callable[[ProcessGroupConfig], ProcessGroup]
|
5443
|
-
|
5444
|
-
def __call__(self, config: ProcessGroupConfig) -> ProcessGroup:
|
5445
|
-
return self.fn(config)
|
6656
|
+
ProcessGroupFactory = ta.NewType('ProcessGroupFactory', Func[ProcessGroup]) # (config: ProcessGroupConfig)
|
5446
6657
|
|
5447
6658
|
|
5448
6659
|
class Supervisor:
|
5449
6660
|
def __init__(
|
5450
6661
|
self,
|
5451
6662
|
*,
|
5452
|
-
context:
|
6663
|
+
context: ServerContextImpl,
|
5453
6664
|
poller: Poller,
|
5454
6665
|
process_groups: ProcessGroups,
|
5455
6666
|
signal_handler: SignalHandler,
|
@@ -5473,7 +6684,7 @@ class Supervisor:
|
|
5473
6684
|
#
|
5474
6685
|
|
5475
6686
|
@property
|
5476
|
-
def context(self) ->
|
6687
|
+
def context(self) -> ServerContextImpl:
|
5477
6688
|
return self._context
|
5478
6689
|
|
5479
6690
|
def get_state(self) -> SupervisorState:
|
@@ -5520,16 +6731,16 @@ class Supervisor:
|
|
5520
6731
|
return True
|
5521
6732
|
|
5522
6733
|
def get_process_map(self) -> ta.Dict[int, Dispatcher]:
|
5523
|
-
process_map = {}
|
6734
|
+
process_map: ta.Dict[int, Dispatcher] = {}
|
5524
6735
|
for group in self._process_groups:
|
5525
6736
|
process_map.update(group.get_dispatchers())
|
5526
6737
|
return process_map
|
5527
6738
|
|
5528
|
-
def shutdown_report(self) -> ta.List[
|
5529
|
-
unstopped: ta.List[
|
6739
|
+
def shutdown_report(self) -> ta.List[Process]:
|
6740
|
+
unstopped: ta.List[Process] = []
|
5530
6741
|
|
5531
6742
|
for group in self._process_groups:
|
5532
|
-
unstopped.extend(group.get_unstopped_processes())
|
6743
|
+
unstopped.extend(group.get_unstopped_processes())
|
5533
6744
|
|
5534
6745
|
if unstopped:
|
5535
6746
|
# throttle 'waiting for x to die' reports
|
@@ -5740,13 +6951,13 @@ class Supervisor:
|
|
5740
6951
|
|
5741
6952
|
|
5742
6953
|
########################################
|
5743
|
-
#
|
6954
|
+
# ../inject.py
|
5744
6955
|
|
5745
6956
|
|
5746
6957
|
##
|
5747
6958
|
|
5748
6959
|
|
5749
|
-
def
|
6960
|
+
def bind_server(
|
5750
6961
|
config: ServerConfig,
|
5751
6962
|
*,
|
5752
6963
|
server_epoch: ta.Optional[ServerEpoch] = None,
|
@@ -5757,8 +6968,8 @@ def build_server_bindings(
|
|
5757
6968
|
|
5758
6969
|
inj.bind(get_poller_impl(), key=Poller, singleton=True),
|
5759
6970
|
|
5760
|
-
inj.bind(
|
5761
|
-
inj.bind(
|
6971
|
+
inj.bind(ServerContextImpl, singleton=True),
|
6972
|
+
inj.bind(ServerContext, to_key=ServerContextImpl),
|
5762
6973
|
|
5763
6974
|
inj.bind(EventCallbacks, singleton=True),
|
5764
6975
|
|
@@ -5767,21 +6978,10 @@ def build_server_bindings(
|
|
5767
6978
|
inj.bind(SignalHandler, singleton=True),
|
5768
6979
|
inj.bind(ProcessGroups, singleton=True),
|
5769
6980
|
inj.bind(Supervisor, singleton=True),
|
5770
|
-
]
|
5771
|
-
|
5772
|
-
#
|
5773
6981
|
|
5774
|
-
|
5775
|
-
|
5776
|
-
|
5777
|
-
return ProcessGroupFactory(inner)
|
5778
|
-
lst.append(inj.bind(make_process_group_factory))
|
5779
|
-
|
5780
|
-
def make_subprocess_factory(injector: Injector) -> SubprocessFactory:
|
5781
|
-
def inner(process_config: ProcessConfig, group: AbstractProcessGroup) -> AbstractSubprocess:
|
5782
|
-
return injector.inject(functools.partial(Subprocess, process_config, group))
|
5783
|
-
return SubprocessFactory(inner)
|
5784
|
-
lst.append(inj.bind(make_subprocess_factory))
|
6982
|
+
inj.bind_factory(ProcessGroupFactory, ProcessGroupImpl),
|
6983
|
+
inj.bind_factory(ProcessFactory, ProcessImpl),
|
6984
|
+
]
|
5785
6985
|
|
5786
6986
|
#
|
5787
6987
|
|
@@ -5795,6 +6995,10 @@ def build_server_bindings(
|
|
5795
6995
|
return inj.as_bindings(*lst)
|
5796
6996
|
|
5797
6997
|
|
6998
|
+
########################################
|
6999
|
+
# main.py
|
7000
|
+
|
7001
|
+
|
5798
7002
|
##
|
5799
7003
|
|
5800
7004
|
|
@@ -5803,6 +7007,10 @@ def main(
|
|
5803
7007
|
*,
|
5804
7008
|
no_logging: bool = False,
|
5805
7009
|
) -> None:
|
7010
|
+
server_cls = CoroHttpServer # noqa
|
7011
|
+
|
7012
|
+
#
|
7013
|
+
|
5806
7014
|
import argparse
|
5807
7015
|
|
5808
7016
|
parser = argparse.ArgumentParser()
|
@@ -5836,14 +7044,14 @@ def main(
|
|
5836
7044
|
prepare=prepare_server_config,
|
5837
7045
|
)
|
5838
7046
|
|
5839
|
-
injector = inj.create_injector(
|
7047
|
+
injector = inj.create_injector(bind_server(
|
5840
7048
|
config,
|
5841
7049
|
server_epoch=ServerEpoch(epoch),
|
5842
7050
|
inherited_fds=inherited_fds,
|
5843
7051
|
))
|
5844
7052
|
|
5845
|
-
context = injector
|
5846
|
-
supervisor = injector
|
7053
|
+
context = injector[ServerContextImpl]
|
7054
|
+
supervisor = injector[Supervisor]
|
5847
7055
|
|
5848
7056
|
try:
|
5849
7057
|
supervisor.main()
|