mrok 0.5.0__py3-none-any.whl → 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mrok/agent/devtools/inspector/__main__.py +2 -24
- mrok/agent/devtools/inspector/app.py +408 -113
- mrok/agent/devtools/inspector/utils.py +149 -0
- mrok/agent/sidecar/app.py +9 -9
- mrok/agent/sidecar/main.py +31 -5
- mrok/agent/ziticorn.py +8 -2
- mrok/cli/commands/admin/bootstrap.py +3 -2
- mrok/cli/commands/admin/utils.py +2 -2
- mrok/cli/commands/agent/run/sidecar.py +59 -1
- mrok/cli/commands/frontend/run.py +43 -1
- mrok/cli/main.py +17 -1
- mrok/constants.py +21 -0
- mrok/controller/schemas.py +2 -2
- mrok/frontend/app.py +8 -8
- mrok/frontend/main.py +9 -1
- mrok/logging.py +0 -22
- mrok/proxy/app.py +10 -9
- mrok/proxy/asgi.py +96 -0
- mrok/proxy/backend.py +5 -3
- mrok/proxy/event_publisher.py +66 -0
- mrok/proxy/master.py +18 -60
- mrok/proxy/metrics.py +2 -2
- mrok/proxy/{middlewares.py → middleware.py} +11 -42
- mrok/proxy/{datastructures.py → models.py} +43 -17
- mrok/proxy/{streams.py → stream.py} +24 -1
- mrok/proxy/worker.py +64 -0
- mrok/proxy/ziticorn.py +76 -0
- mrok/types/__init__.py +0 -0
- mrok/{proxy/types.py → types/proxy.py} +7 -2
- mrok/types/ziti.py +1 -0
- mrok/ziti/api.py +16 -19
- mrok/ziti/bootstrap.py +3 -7
- mrok/ziti/identities.py +15 -13
- mrok/ziti/services.py +3 -2
- {mrok-0.5.0.dist-info → mrok-0.7.0.dist-info}/METADATA +8 -2
- {mrok-0.5.0.dist-info → mrok-0.7.0.dist-info}/RECORD +39 -40
- mrok/agent/devtools/__main__.py +0 -34
- mrok/cli/commands/agent/utils.py +0 -5
- mrok/proxy/config.py +0 -62
- mrok/proxy/constants.py +0 -22
- mrok/proxy/lifespan.py +0 -10
- mrok/proxy/protocol.py +0 -11
- mrok/proxy/server.py +0 -14
- mrok/proxy/utils.py +0 -90
- {mrok-0.5.0.dist-info → mrok-0.7.0.dist-info}/WHEEL +0 -0
- {mrok-0.5.0.dist-info → mrok-0.7.0.dist-info}/entry_points.txt +0 -0
- {mrok-0.5.0.dist-info → mrok-0.7.0.dist-info}/licenses/LICENSE.txt +0 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from collections.abc import Generator
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from io import BytesIO
|
|
4
|
+
|
|
5
|
+
from multipart import MultipartParser
|
|
6
|
+
|
|
7
|
+
TEXTUAL_CONTENT_TYPES = {
|
|
8
|
+
"application/json",
|
|
9
|
+
"application/xml",
|
|
10
|
+
"application/javascript",
|
|
11
|
+
"application/x-www-form-urlencoded",
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
TEXTUAL_PREFIXES = ("text/",)
|
|
15
|
+
|
|
16
|
+
CONTENT_TYPE_TO_LANGUAGE = {
|
|
17
|
+
"application/json": "json",
|
|
18
|
+
"application/ld+json": "json",
|
|
19
|
+
"application/problem+json": "json",
|
|
20
|
+
"application/schema+json": "json",
|
|
21
|
+
"application/xml": "xml",
|
|
22
|
+
"text/xml": "xml",
|
|
23
|
+
"application/xhtml+xml": "html",
|
|
24
|
+
"text/html": "html",
|
|
25
|
+
"text/css": "css",
|
|
26
|
+
"application/javascript": "javascript",
|
|
27
|
+
"application/x-javascript": "javascript",
|
|
28
|
+
"text/javascript": "javascript",
|
|
29
|
+
"application/ecmascript": "javascript",
|
|
30
|
+
"text/markdown": "markdown",
|
|
31
|
+
"text/x-markdown": "markdown",
|
|
32
|
+
"application/yaml": "yaml",
|
|
33
|
+
"application/x-yaml": "yaml",
|
|
34
|
+
"text/yaml": "yaml",
|
|
35
|
+
"application/toml": "toml",
|
|
36
|
+
"application/x-toml": "toml",
|
|
37
|
+
"application/sql": "sql",
|
|
38
|
+
"text/x-sql": "sql",
|
|
39
|
+
"application/java": "java",
|
|
40
|
+
"text/x-java-source": "java",
|
|
41
|
+
"application/python": "python",
|
|
42
|
+
"text/x-python": "python",
|
|
43
|
+
"application/x-python-code": "python",
|
|
44
|
+
"application/rust": "rust",
|
|
45
|
+
"text/x-rust": "rust",
|
|
46
|
+
"application/go": "go",
|
|
47
|
+
"text/x-go": "go",
|
|
48
|
+
"application/bash": "bash",
|
|
49
|
+
"application/x-sh": "bash",
|
|
50
|
+
"text/x-shellscript": "bash",
|
|
51
|
+
"application/regex": "regex",
|
|
52
|
+
"text/x-regex": "regex",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class ContentTypeInfo:
|
|
58
|
+
content_type: str
|
|
59
|
+
binary: bool
|
|
60
|
+
charset: str | None = None
|
|
61
|
+
boundary: str | None = None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def parse_content_type(content_type_header: str) -> ContentTypeInfo:
|
|
65
|
+
parts = content_type_header.split(";")
|
|
66
|
+
content_type = parts[0].strip().lower()
|
|
67
|
+
|
|
68
|
+
charset = None
|
|
69
|
+
boundary = None
|
|
70
|
+
|
|
71
|
+
for part in parts[1:]:
|
|
72
|
+
part = part.strip()
|
|
73
|
+
if "=" in part:
|
|
74
|
+
key, value = part.split("=", 1)
|
|
75
|
+
key = key.strip().lower()
|
|
76
|
+
value = value.strip().strip('"')
|
|
77
|
+
if key == "charset":
|
|
78
|
+
charset = value
|
|
79
|
+
elif key == "boundary":
|
|
80
|
+
boundary = value
|
|
81
|
+
|
|
82
|
+
binary = not is_textual(content_type)
|
|
83
|
+
|
|
84
|
+
if charset is None and not binary:
|
|
85
|
+
charset = "utf-8"
|
|
86
|
+
|
|
87
|
+
return ContentTypeInfo(
|
|
88
|
+
content_type=content_type, binary=binary, charset=charset, boundary=boundary
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def parse_form_data(data: bytes, boundary: str) -> Generator[tuple[str, str]]:
|
|
93
|
+
parser = MultipartParser(BytesIO(data), boundary)
|
|
94
|
+
for part in parser:
|
|
95
|
+
if is_textual(part.content_type):
|
|
96
|
+
yield part.name, part.value
|
|
97
|
+
continue
|
|
98
|
+
yield part.name, "<binary>"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def is_textual(content_type: str) -> bool:
|
|
102
|
+
ct = content_type.lower()
|
|
103
|
+
if ct in TEXTUAL_CONTENT_TYPES:
|
|
104
|
+
return True
|
|
105
|
+
if any(ct.startswith(p) for p in TEXTUAL_PREFIXES):
|
|
106
|
+
return True
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def build_tree(node, data):
|
|
111
|
+
if isinstance(data, dict):
|
|
112
|
+
for key, value in data.items():
|
|
113
|
+
child = node.add(str(key))
|
|
114
|
+
build_tree(child, value)
|
|
115
|
+
elif isinstance(data, list):
|
|
116
|
+
for index, value in enumerate(data):
|
|
117
|
+
child = node.add(f"[{index}]")
|
|
118
|
+
build_tree(child, value)
|
|
119
|
+
else:
|
|
120
|
+
node.add(repr(data))
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def hexdump(data, width=16):
|
|
124
|
+
lines = []
|
|
125
|
+
for i in range(0, len(data), width):
|
|
126
|
+
chunk = data[i : i + width]
|
|
127
|
+
hex_part = " ".join(f"{b:02x}" for b in chunk)
|
|
128
|
+
ascii_part = "".join(chr(b) if 32 <= b <= 126 else "." for b in chunk)
|
|
129
|
+
lines.append(f"{hex_part:<{width * 3}} {ascii_part}")
|
|
130
|
+
return "\n".join(lines)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def humanize_bytes(num_bytes: int) -> tuple[float, str]: # type: ignore[return-value]
|
|
134
|
+
if num_bytes < 0:
|
|
135
|
+
raise ValueError("num_bytes must be non-negative")
|
|
136
|
+
|
|
137
|
+
units = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"]
|
|
138
|
+
value = float(num_bytes)
|
|
139
|
+
|
|
140
|
+
for unit in units:
|
|
141
|
+
if value < 1024 or unit == units[-1]:
|
|
142
|
+
return round(value, 2), unit
|
|
143
|
+
value /= 1024
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def get_highlighter_language_by_content_type(content_type: str) -> str | None:
|
|
147
|
+
if content_type in CONTENT_TYPE_TO_LANGUAGE:
|
|
148
|
+
return CONTENT_TYPE_TO_LANGUAGE[content_type]
|
|
149
|
+
return None
|
mrok/agent/sidecar/app.py
CHANGED
|
@@ -5,7 +5,7 @@ from typing import Literal
|
|
|
5
5
|
from httpcore import AsyncConnectionPool
|
|
6
6
|
|
|
7
7
|
from mrok.proxy.app import ProxyAppBase
|
|
8
|
-
from mrok.proxy
|
|
8
|
+
from mrok.types.proxy import Scope
|
|
9
9
|
|
|
10
10
|
logger = logging.getLogger("mrok.agent")
|
|
11
11
|
|
|
@@ -18,10 +18,10 @@ class SidecarProxyApp(ProxyAppBase):
|
|
|
18
18
|
self,
|
|
19
19
|
target: str | Path | tuple[str, int],
|
|
20
20
|
*,
|
|
21
|
-
max_connections=
|
|
22
|
-
max_keepalive_connections=
|
|
23
|
-
keepalive_expiry=
|
|
24
|
-
retries=0,
|
|
21
|
+
max_connections: int | None = 10,
|
|
22
|
+
max_keepalive_connections: int | None = None,
|
|
23
|
+
keepalive_expiry: float | None = None,
|
|
24
|
+
retries: int = 0,
|
|
25
25
|
):
|
|
26
26
|
self._target = target
|
|
27
27
|
self._target_type, self._target_address = self._parse_target()
|
|
@@ -34,10 +34,10 @@ class SidecarProxyApp(ProxyAppBase):
|
|
|
34
34
|
|
|
35
35
|
def setup_connection_pool(
|
|
36
36
|
self,
|
|
37
|
-
max_connections: int | None
|
|
38
|
-
max_keepalive_connections: int | None
|
|
39
|
-
keepalive_expiry: float | None
|
|
40
|
-
retries: int
|
|
37
|
+
max_connections: int | None,
|
|
38
|
+
max_keepalive_connections: int | None,
|
|
39
|
+
keepalive_expiry: float | None,
|
|
40
|
+
retries: int,
|
|
41
41
|
) -> AsyncConnectionPool:
|
|
42
42
|
if self._target_type == "unix":
|
|
43
43
|
return AsyncConnectionPool(
|
mrok/agent/sidecar/main.py
CHANGED
|
@@ -13,26 +13,47 @@ class SidecarAgent(MasterBase):
|
|
|
13
13
|
identity_file: str,
|
|
14
14
|
target: str | Path | tuple[str, int],
|
|
15
15
|
workers: int = 4,
|
|
16
|
+
events_enabled: bool = True,
|
|
17
|
+
max_connections: int | None = 10,
|
|
18
|
+
max_keepalive_connections: int | None = None,
|
|
19
|
+
keepalive_expiry: float | None = None,
|
|
20
|
+
retries: int = 0,
|
|
16
21
|
publishers_port: int = 50000,
|
|
17
22
|
subscribers_port: int = 50001,
|
|
18
23
|
):
|
|
19
24
|
super().__init__(
|
|
20
25
|
identity_file,
|
|
21
|
-
workers,
|
|
22
|
-
False,
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
workers=workers,
|
|
27
|
+
reload=False,
|
|
28
|
+
events_enabled=events_enabled,
|
|
29
|
+
events_pub_port=publishers_port,
|
|
30
|
+
events_sub_port=subscribers_port,
|
|
25
31
|
)
|
|
26
32
|
self._target = target
|
|
33
|
+
self._max_connections = max_connections
|
|
34
|
+
self._max_keepalive_connections = max_keepalive_connections
|
|
35
|
+
self._keepalive_expiry = keepalive_expiry
|
|
36
|
+
self._retries = retries
|
|
27
37
|
|
|
28
38
|
def get_asgi_app(self):
|
|
29
|
-
return SidecarProxyApp(
|
|
39
|
+
return SidecarProxyApp(
|
|
40
|
+
self._target,
|
|
41
|
+
max_connections=self._max_connections,
|
|
42
|
+
max_keepalive_connections=self._max_keepalive_connections,
|
|
43
|
+
keepalive_expiry=self._keepalive_expiry,
|
|
44
|
+
retries=self._retries,
|
|
45
|
+
)
|
|
30
46
|
|
|
31
47
|
|
|
32
48
|
def run(
|
|
33
49
|
identity_file: str,
|
|
34
50
|
target_addr: str | Path | tuple[str, int],
|
|
35
51
|
workers: int = 4,
|
|
52
|
+
events_enabled: bool = True,
|
|
53
|
+
max_connections: int | None = 10,
|
|
54
|
+
max_keepalive_connections: int | None = None,
|
|
55
|
+
keepalive_expiry: float | None = None,
|
|
56
|
+
retries: int = 0,
|
|
36
57
|
publishers_port: int = 50000,
|
|
37
58
|
subscribers_port: int = 50001,
|
|
38
59
|
):
|
|
@@ -40,6 +61,11 @@ def run(
|
|
|
40
61
|
identity_file,
|
|
41
62
|
target_addr,
|
|
42
63
|
workers=workers,
|
|
64
|
+
events_enabled=events_enabled,
|
|
65
|
+
max_connections=max_connections,
|
|
66
|
+
max_keepalive_connections=max_keepalive_connections,
|
|
67
|
+
keepalive_expiry=keepalive_expiry,
|
|
68
|
+
retries=retries,
|
|
43
69
|
publishers_port=publishers_port,
|
|
44
70
|
subscribers_port=subscribers_port,
|
|
45
71
|
)
|
mrok/agent/ziticorn.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from mrok.proxy.master import MasterBase
|
|
2
|
-
from mrok.proxy
|
|
2
|
+
from mrok.types.proxy import ASGIApp
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class ZiticornAgent(MasterBase):
|
|
@@ -12,7 +12,13 @@ class ZiticornAgent(MasterBase):
|
|
|
12
12
|
publishers_port: int = 50000,
|
|
13
13
|
subscribers_port: int = 50001,
|
|
14
14
|
):
|
|
15
|
-
super().__init__(
|
|
15
|
+
super().__init__(
|
|
16
|
+
identity_file,
|
|
17
|
+
workers=workers,
|
|
18
|
+
reload=reload,
|
|
19
|
+
events_pub_port=publishers_port,
|
|
20
|
+
events_sub_port=subscribers_port,
|
|
21
|
+
)
|
|
16
22
|
self.app = app
|
|
17
23
|
|
|
18
24
|
def get_asgi_app(self):
|
|
@@ -8,14 +8,15 @@ import typer
|
|
|
8
8
|
|
|
9
9
|
from mrok.cli.commands.admin.utils import parse_tags
|
|
10
10
|
from mrok.conf import Settings
|
|
11
|
-
from mrok.ziti
|
|
11
|
+
from mrok.types.ziti import Tags
|
|
12
|
+
from mrok.ziti.api import ZitiClientAPI, ZitiManagementAPI
|
|
12
13
|
from mrok.ziti.bootstrap import bootstrap_identity
|
|
13
14
|
|
|
14
15
|
logger = logging.getLogger(__name__)
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
async def bootstrap(
|
|
18
|
-
settings: Settings, forced: bool, tags:
|
|
19
|
+
settings: Settings, forced: bool, tags: Tags | None
|
|
19
20
|
) -> tuple[str, dict[str, Any] | None]:
|
|
20
21
|
async with ZitiManagementAPI(settings) as mgmt_api, ZitiClientAPI(settings) as client_api:
|
|
21
22
|
return await bootstrap_identity(
|
mrok/cli/commands/admin/utils.py
CHANGED
|
@@ -2,10 +2,10 @@ from datetime import datetime
|
|
|
2
2
|
|
|
3
3
|
import typer
|
|
4
4
|
|
|
5
|
-
from mrok.ziti
|
|
5
|
+
from mrok.types.ziti import Tags
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
def parse_tags(pairs: list[str] | None) ->
|
|
8
|
+
def parse_tags(pairs: list[str] | None) -> Tags | None:
|
|
9
9
|
if not pairs:
|
|
10
10
|
return None
|
|
11
11
|
|
|
@@ -25,10 +25,55 @@ def register(app: typer.Typer) -> None:
|
|
|
25
25
|
typer.Option(
|
|
26
26
|
"--workers",
|
|
27
27
|
"-w",
|
|
28
|
-
help=
|
|
28
|
+
help="Number of workers.",
|
|
29
29
|
show_default=True,
|
|
30
30
|
),
|
|
31
31
|
] = default_workers,
|
|
32
|
+
max_connections: Annotated[
|
|
33
|
+
int,
|
|
34
|
+
typer.Option(
|
|
35
|
+
"--max-pool-connections",
|
|
36
|
+
help=(
|
|
37
|
+
"The maximum number of concurrent HTTP connections that "
|
|
38
|
+
"the pool should allow. Any attempt to send a request on a pool that "
|
|
39
|
+
"would exceed this amount will block until a connection is available."
|
|
40
|
+
),
|
|
41
|
+
show_default=True,
|
|
42
|
+
),
|
|
43
|
+
] = 10,
|
|
44
|
+
max_keepalive_connections: Annotated[
|
|
45
|
+
int | None,
|
|
46
|
+
typer.Option(
|
|
47
|
+
"--max-pool-keepalive-connections",
|
|
48
|
+
help=(
|
|
49
|
+
"The maximum number of idle HTTP connections "
|
|
50
|
+
"that will be maintained in the pool."
|
|
51
|
+
),
|
|
52
|
+
show_default=True,
|
|
53
|
+
),
|
|
54
|
+
] = None,
|
|
55
|
+
keepalive_expiry: Annotated[
|
|
56
|
+
float | None,
|
|
57
|
+
typer.Option(
|
|
58
|
+
"--max-pool-keepalive-expiry",
|
|
59
|
+
help=(
|
|
60
|
+
"The duration in seconds that an idle HTTP connection "
|
|
61
|
+
"may be maintained for before being expired from the pool."
|
|
62
|
+
),
|
|
63
|
+
show_default=True,
|
|
64
|
+
),
|
|
65
|
+
] = None,
|
|
66
|
+
retries: Annotated[
|
|
67
|
+
int,
|
|
68
|
+
typer.Option(
|
|
69
|
+
"--max-pool-connect-retries",
|
|
70
|
+
help=(
|
|
71
|
+
"The duration in seconds that an idle HTTP connection "
|
|
72
|
+
"may be maintained for before being expired from the pool."
|
|
73
|
+
),
|
|
74
|
+
show_default=True,
|
|
75
|
+
),
|
|
76
|
+
] = 0,
|
|
32
77
|
publishers_port: Annotated[
|
|
33
78
|
int,
|
|
34
79
|
typer.Option(
|
|
@@ -53,6 +98,14 @@ def register(app: typer.Typer) -> None:
|
|
|
53
98
|
show_default=True,
|
|
54
99
|
),
|
|
55
100
|
] = 50001,
|
|
101
|
+
no_events: Annotated[
|
|
102
|
+
bool,
|
|
103
|
+
typer.Option(
|
|
104
|
+
"--no-events",
|
|
105
|
+
help="Disable events. Default: False",
|
|
106
|
+
show_default=True,
|
|
107
|
+
),
|
|
108
|
+
] = False,
|
|
56
109
|
):
|
|
57
110
|
"""Run a Sidecar Proxy to expose a web application through OpenZiti."""
|
|
58
111
|
if ":" in str(target):
|
|
@@ -65,6 +118,11 @@ def register(app: typer.Typer) -> None:
|
|
|
65
118
|
str(identity_file),
|
|
66
119
|
target_addr,
|
|
67
120
|
workers=workers,
|
|
121
|
+
events_enabled=not no_events,
|
|
122
|
+
max_connections=max_connections,
|
|
123
|
+
max_keepalive_connections=max_keepalive_connections,
|
|
124
|
+
keepalive_expiry=keepalive_expiry,
|
|
125
|
+
retries=retries,
|
|
68
126
|
publishers_port=publishers_port,
|
|
69
127
|
subscribers_port=subscribers_port,
|
|
70
128
|
)
|
|
@@ -44,6 +44,48 @@ def register(app: typer.Typer) -> None:
|
|
|
44
44
|
show_default=True,
|
|
45
45
|
),
|
|
46
46
|
] = default_workers,
|
|
47
|
+
max_connections: Annotated[
|
|
48
|
+
int,
|
|
49
|
+
typer.Option(
|
|
50
|
+
"--max-pool-connections",
|
|
51
|
+
help=(
|
|
52
|
+
"The maximum number of concurrent HTTP connections that "
|
|
53
|
+
"the pool should allow. Any attempt to send a request on a pool that "
|
|
54
|
+
"would exceed this amount will block until a connection is available."
|
|
55
|
+
),
|
|
56
|
+
show_default=True,
|
|
57
|
+
),
|
|
58
|
+
] = 1000,
|
|
59
|
+
max_keepalive_connections: Annotated[
|
|
60
|
+
int | None,
|
|
61
|
+
typer.Option(
|
|
62
|
+
"--max-pool-keepalive-connections",
|
|
63
|
+
help=(
|
|
64
|
+
"The maximum number of idle HTTP connections "
|
|
65
|
+
"that will be maintained in the pool."
|
|
66
|
+
),
|
|
67
|
+
show_default=True,
|
|
68
|
+
),
|
|
69
|
+
] = 100,
|
|
70
|
+
keepalive_expiry: Annotated[
|
|
71
|
+
float | None,
|
|
72
|
+
typer.Option(
|
|
73
|
+
"--max-pool-keepalive-expiry",
|
|
74
|
+
help=(
|
|
75
|
+
"The duration in seconds that an idle HTTP connection "
|
|
76
|
+
"may be maintained for before being expired from the pool."
|
|
77
|
+
),
|
|
78
|
+
show_default=True,
|
|
79
|
+
),
|
|
80
|
+
] = 300,
|
|
47
81
|
):
|
|
48
82
|
"""Run the mrok frontend with Gunicorn and Uvicorn workers."""
|
|
49
|
-
frontend.run(
|
|
83
|
+
frontend.run(
|
|
84
|
+
identity_file,
|
|
85
|
+
host,
|
|
86
|
+
port,
|
|
87
|
+
workers,
|
|
88
|
+
max_connections=max_connections,
|
|
89
|
+
max_keepalive_connections=max_keepalive_connections,
|
|
90
|
+
keepalive_expiry=keepalive_expiry,
|
|
91
|
+
)
|
mrok/cli/main.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
import sys
|
|
3
|
+
from typing import Annotated
|
|
3
4
|
|
|
4
5
|
import typer
|
|
5
6
|
from pyfiglet import Figlet
|
|
@@ -79,11 +80,23 @@ for name, module in inspect.getmembers(commands):
|
|
|
79
80
|
elif hasattr(module, "app"): # pragma: no branch
|
|
80
81
|
app.add_typer(module.app, name=name.replace("_", "-"))
|
|
81
82
|
|
|
83
|
+
_debug_mode = False
|
|
84
|
+
|
|
82
85
|
|
|
83
86
|
@app.callback()
|
|
84
87
|
def main(
|
|
85
88
|
ctx: typer.Context,
|
|
89
|
+
debug: Annotated[
|
|
90
|
+
bool,
|
|
91
|
+
typer.Option(
|
|
92
|
+
"--debug",
|
|
93
|
+
help="Run the CLI in debug mode",
|
|
94
|
+
show_default=True,
|
|
95
|
+
),
|
|
96
|
+
] = False,
|
|
86
97
|
):
|
|
98
|
+
global _debug_mode
|
|
99
|
+
_debug_mode = debug
|
|
87
100
|
settings = get_settings()
|
|
88
101
|
setup_logging(settings, cli_mode=True)
|
|
89
102
|
ctx.obj = settings
|
|
@@ -93,5 +106,8 @@ def run():
|
|
|
93
106
|
try:
|
|
94
107
|
app()
|
|
95
108
|
except Exception as e:
|
|
96
|
-
|
|
109
|
+
if _debug_mode:
|
|
110
|
+
raise
|
|
111
|
+
message = str(e) or "Unexpected error. Debug it with --debug"
|
|
112
|
+
err_console.print(f"[bold red]Error:[/bold red] {message}")
|
|
97
113
|
sys.exit(-1)
|
mrok/constants.py
CHANGED
|
@@ -2,3 +2,24 @@ import re
|
|
|
2
2
|
|
|
3
3
|
RE_EXTENSION_ID = re.compile(r"(?i)EXT-\d{4}-\d{4}")
|
|
4
4
|
RE_INSTANCE_ID = re.compile(r"(?i)INS-\d{4}-\d{4}-\d{4}")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
BINARY_CONTENT_TYPES = {
|
|
8
|
+
"application/octet-stream",
|
|
9
|
+
"application/pdf",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
BINARY_PREFIXES = (
|
|
13
|
+
"image/",
|
|
14
|
+
"video/",
|
|
15
|
+
"audio/",
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
TEXTUAL_CONTENT_TYPES = {
|
|
19
|
+
"application/json",
|
|
20
|
+
"application/xml",
|
|
21
|
+
"application/javascript",
|
|
22
|
+
"application/x-www-form-urlencoded",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
TEXTUAL_PREFIXES = ("text/",)
|
mrok/controller/schemas.py
CHANGED
|
@@ -9,12 +9,12 @@ from pydantic import (
|
|
|
9
9
|
computed_field,
|
|
10
10
|
)
|
|
11
11
|
|
|
12
|
-
from mrok.ziti
|
|
12
|
+
from mrok.types.ziti import Tags
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class BaseSchema(BaseModel):
|
|
16
16
|
model_config = ConfigDict(from_attributes=True, extra="ignore")
|
|
17
|
-
tags:
|
|
17
|
+
tags: Tags | None = None
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class IdSchema(BaseModel):
|
mrok/frontend/app.py
CHANGED
|
@@ -6,7 +6,7 @@ from mrok.conf import get_settings
|
|
|
6
6
|
from mrok.proxy.app import ProxyAppBase
|
|
7
7
|
from mrok.proxy.backend import AIOZitiNetworkBackend
|
|
8
8
|
from mrok.proxy.exceptions import InvalidTargetError
|
|
9
|
-
from mrok.proxy
|
|
9
|
+
from mrok.types.proxy import Scope
|
|
10
10
|
|
|
11
11
|
RE_SUBDOMAIN = re.compile(r"(?i)^(?:EXT-\d{4}-\d{4}|INS-\d{4}-\d{4}-\d{4})$")
|
|
12
12
|
|
|
@@ -16,9 +16,9 @@ class FrontendProxyApp(ProxyAppBase):
|
|
|
16
16
|
self,
|
|
17
17
|
identity_file: str,
|
|
18
18
|
*,
|
|
19
|
-
max_connections: int =
|
|
20
|
-
max_keepalive_connections: int =
|
|
21
|
-
keepalive_expiry: float =
|
|
19
|
+
max_connections: int | None = 10,
|
|
20
|
+
max_keepalive_connections: int | None = None,
|
|
21
|
+
keepalive_expiry: float | None = None,
|
|
22
22
|
retries=0,
|
|
23
23
|
):
|
|
24
24
|
self._identity_file = identity_file
|
|
@@ -32,10 +32,10 @@ class FrontendProxyApp(ProxyAppBase):
|
|
|
32
32
|
|
|
33
33
|
def setup_connection_pool(
|
|
34
34
|
self,
|
|
35
|
-
max_connections: int | None
|
|
36
|
-
max_keepalive_connections: int | None
|
|
37
|
-
keepalive_expiry: float | None
|
|
38
|
-
retries: int
|
|
35
|
+
max_connections: int | None,
|
|
36
|
+
max_keepalive_connections: int | None,
|
|
37
|
+
keepalive_expiry: float | None,
|
|
38
|
+
retries: int,
|
|
39
39
|
) -> AsyncConnectionPool:
|
|
40
40
|
return AsyncConnectionPool(
|
|
41
41
|
max_connections=max_connections,
|
mrok/frontend/main.py
CHANGED
|
@@ -38,8 +38,16 @@ def run(
|
|
|
38
38
|
host: str,
|
|
39
39
|
port: int,
|
|
40
40
|
workers: int,
|
|
41
|
+
max_connections: int | None,
|
|
42
|
+
max_keepalive_connections: int | None,
|
|
43
|
+
keepalive_expiry: float | None,
|
|
41
44
|
):
|
|
42
|
-
app = FrontendProxyApp(
|
|
45
|
+
app = FrontendProxyApp(
|
|
46
|
+
str(identity_file),
|
|
47
|
+
max_connections=max_connections,
|
|
48
|
+
max_keepalive_connections=max_keepalive_connections,
|
|
49
|
+
keepalive_expiry=keepalive_expiry,
|
|
50
|
+
)
|
|
43
51
|
|
|
44
52
|
options = {
|
|
45
53
|
"bind": f"{host}:{port}",
|
mrok/logging.py
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import logging.config
|
|
2
2
|
|
|
3
|
-
from rich.console import Console
|
|
4
|
-
from rich.logging import RichHandler
|
|
5
|
-
from textual_serve.server import LogHighlighter
|
|
6
|
-
|
|
7
3
|
from mrok.conf import Settings
|
|
8
4
|
|
|
9
5
|
|
|
@@ -78,21 +74,3 @@ def get_logging_config(settings: Settings, cli_mode: bool = False) -> dict:
|
|
|
78
74
|
def setup_logging(settings: Settings, cli_mode: bool = False) -> None:
|
|
79
75
|
logging_config = get_logging_config(settings, cli_mode)
|
|
80
76
|
logging.config.dictConfig(logging_config)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def setup_inspector_logging(console: Console) -> None:
|
|
84
|
-
logging.basicConfig(
|
|
85
|
-
level="WARNING",
|
|
86
|
-
format="%(message)s",
|
|
87
|
-
datefmt="%Y-%m-%d %H:%M:%S",
|
|
88
|
-
handlers=[
|
|
89
|
-
RichHandler(
|
|
90
|
-
show_path=False,
|
|
91
|
-
show_time=True,
|
|
92
|
-
rich_tracebacks=True,
|
|
93
|
-
tracebacks_show_locals=True,
|
|
94
|
-
highlighter=LogHighlighter(),
|
|
95
|
-
console=console,
|
|
96
|
-
)
|
|
97
|
-
],
|
|
98
|
-
)
|
mrok/proxy/app.py
CHANGED
|
@@ -4,8 +4,8 @@ import logging
|
|
|
4
4
|
from httpcore import AsyncConnectionPool, Request
|
|
5
5
|
|
|
6
6
|
from mrok.proxy.exceptions import ProxyError
|
|
7
|
-
from mrok.proxy.
|
|
8
|
-
from mrok.proxy
|
|
7
|
+
from mrok.proxy.stream import ASGIRequestBodyStream
|
|
8
|
+
from mrok.types.proxy import ASGIReceive, ASGISend, Scope
|
|
9
9
|
|
|
10
10
|
logger = logging.getLogger("mrok.proxy")
|
|
11
11
|
|
|
@@ -26,9 +26,9 @@ class ProxyAppBase(abc.ABC):
|
|
|
26
26
|
def __init__(
|
|
27
27
|
self,
|
|
28
28
|
*,
|
|
29
|
-
max_connections: int | None =
|
|
30
|
-
max_keepalive_connections: int | None =
|
|
31
|
-
keepalive_expiry: float | None =
|
|
29
|
+
max_connections: int | None = 10,
|
|
30
|
+
max_keepalive_connections: int | None = None,
|
|
31
|
+
keepalive_expiry: float | None = None,
|
|
32
32
|
retries: int = 0,
|
|
33
33
|
) -> None:
|
|
34
34
|
self._pool = self.setup_connection_pool(
|
|
@@ -41,10 +41,10 @@ class ProxyAppBase(abc.ABC):
|
|
|
41
41
|
@abc.abstractmethod
|
|
42
42
|
def setup_connection_pool(
|
|
43
43
|
self,
|
|
44
|
-
max_connections: int | None
|
|
45
|
-
max_keepalive_connections: int | None
|
|
46
|
-
keepalive_expiry: float | None
|
|
47
|
-
retries: int
|
|
44
|
+
max_connections: int | None,
|
|
45
|
+
max_keepalive_connections: int | None,
|
|
46
|
+
keepalive_expiry: float | None,
|
|
47
|
+
retries: int,
|
|
48
48
|
) -> AsyncConnectionPool:
|
|
49
49
|
raise NotImplementedError()
|
|
50
50
|
|
|
@@ -78,6 +78,7 @@ class ProxyAppBase(abc.ABC):
|
|
|
78
78
|
content=body_stream,
|
|
79
79
|
)
|
|
80
80
|
response = await self._pool.handle_async_request(request)
|
|
81
|
+
logger.debug(f"connection pool status: {self._pool}")
|
|
81
82
|
response_headers = []
|
|
82
83
|
for k, v in response.headers:
|
|
83
84
|
if k.lower() not in HOP_BY_HOP_HEADERS:
|