ominfra 0.0.0.dev123__py3-none-any.whl → 0.0.0.dev125__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.
- 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()
|