mrok 0.4.6__py3-none-any.whl → 0.6.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.
Files changed (50) hide show
  1. mrok/agent/devtools/inspector/app.py +2 -2
  2. mrok/agent/sidecar/app.py +61 -35
  3. mrok/agent/sidecar/main.py +35 -9
  4. mrok/agent/ziticorn.py +9 -3
  5. mrok/cli/commands/__init__.py +2 -2
  6. mrok/cli/commands/admin/bootstrap.py +3 -2
  7. mrok/cli/commands/admin/utils.py +2 -2
  8. mrok/cli/commands/agent/run/sidecar.py +59 -1
  9. mrok/cli/commands/{proxy → frontend}/__init__.py +1 -1
  10. mrok/cli/commands/frontend/run.py +91 -0
  11. mrok/constants.py +0 -2
  12. mrok/controller/openapi/examples.py +13 -0
  13. mrok/controller/schemas.py +2 -2
  14. mrok/frontend/__init__.py +3 -0
  15. mrok/frontend/app.py +75 -0
  16. mrok/{proxy → frontend}/main.py +12 -10
  17. mrok/proxy/__init__.py +0 -3
  18. mrok/proxy/app.py +158 -83
  19. mrok/proxy/asgi.py +96 -0
  20. mrok/proxy/backend.py +45 -0
  21. mrok/proxy/event_publisher.py +66 -0
  22. mrok/proxy/exceptions.py +22 -0
  23. mrok/{master.py → proxy/master.py} +36 -81
  24. mrok/{metrics.py → proxy/metrics.py} +38 -50
  25. mrok/{http/middlewares.py → proxy/middleware.py} +17 -26
  26. mrok/{datastructures.py → proxy/models.py} +43 -10
  27. mrok/proxy/stream.py +68 -0
  28. mrok/{http → proxy}/utils.py +1 -1
  29. mrok/proxy/worker.py +64 -0
  30. mrok/{http/config.py → proxy/ziticorn.py} +29 -6
  31. mrok/types/proxy.py +20 -0
  32. mrok/types/ziti.py +1 -0
  33. mrok/ziti/api.py +15 -18
  34. mrok/ziti/bootstrap.py +3 -2
  35. mrok/ziti/identities.py +5 -4
  36. mrok/ziti/services.py +3 -2
  37. {mrok-0.4.6.dist-info → mrok-0.6.0.dist-info}/METADATA +2 -5
  38. {mrok-0.4.6.dist-info → mrok-0.6.0.dist-info}/RECORD +43 -39
  39. mrok/cli/commands/proxy/run.py +0 -49
  40. mrok/http/forwarder.py +0 -354
  41. mrok/http/lifespan.py +0 -39
  42. mrok/http/pool.py +0 -239
  43. mrok/http/protocol.py +0 -11
  44. mrok/http/server.py +0 -14
  45. mrok/http/types.py +0 -18
  46. /mrok/{http → proxy}/constants.py +0 -0
  47. /mrok/{http → types}/__init__.py +0 -0
  48. {mrok-0.4.6.dist-info → mrok-0.6.0.dist-info}/WHEEL +0 -0
  49. {mrok-0.4.6.dist-info → mrok-0.6.0.dist-info}/entry_points.txt +0 -0
  50. {mrok-0.4.6.dist-info → mrok-0.6.0.dist-info}/licenses/LICENSE.txt +0 -0
@@ -27,7 +27,7 @@ from textual.widgets.data_table import ColumnKey
27
27
  from textual.worker import get_current_worker
28
28
 
29
29
  from mrok import __version__
30
- from mrok.datastructures import Event, HTTPHeaders, HTTPResponse, Meta, WorkerMetrics
30
+ from mrok.proxy.models import Event, HTTPHeaders, HTTPResponse, ServiceMetadata, WorkerMetrics
31
31
 
32
32
 
33
33
  def build_tree(node, data):
@@ -185,7 +185,7 @@ class InfoPanel(Static):
185
185
  # mem=int(mean([m.process.mem for m in self.workers_metrics.values()])),
186
186
  # )
187
187
 
188
- def update_meta(self, meta: Meta) -> None:
188
+ def update_meta(self, meta: ServiceMetadata) -> None:
189
189
  table = self.query_one(DataTable)
190
190
  if len(table.rows) == 0:
191
191
  table.add_row("URL", f"https://{meta.extension}.{meta.domain}")
mrok/agent/sidecar/app.py CHANGED
@@ -1,51 +1,77 @@
1
- import asyncio
2
1
  import logging
3
- from collections.abc import AsyncGenerator
4
- from contextlib import asynccontextmanager
5
2
  from pathlib import Path
3
+ from typing import Literal
6
4
 
7
- from mrok.http.forwarder import ForwardAppBase
8
- from mrok.http.pool import ConnectionPool
9
- from mrok.http.types import Scope, StreamPair
5
+ from httpcore import AsyncConnectionPool
6
+
7
+ from mrok.proxy.app import ProxyAppBase
8
+ from mrok.types.proxy import Scope
10
9
 
11
10
  logger = logging.getLogger("mrok.agent")
12
11
 
13
12
 
14
- class ForwardApp(ForwardAppBase):
13
+ TargetType = Literal["tcp", "unix"]
14
+
15
+
16
+ class SidecarProxyApp(ProxyAppBase):
15
17
  def __init__(
16
18
  self,
17
- target_address: str | Path | tuple[str, int],
18
- read_chunk_size: int = 65536,
19
- ) -> None:
19
+ target: str | Path | tuple[str, int],
20
+ *,
21
+ max_connections: int | None = 10,
22
+ max_keepalive_connections: int | None = None,
23
+ keepalive_expiry: float | None = None,
24
+ retries: int = 0,
25
+ ):
26
+ self._target = target
27
+ self._target_type, self._target_address = self._parse_target()
20
28
  super().__init__(
21
- read_chunk_size=read_chunk_size,
29
+ max_connections=max_connections,
30
+ max_keepalive_connections=max_keepalive_connections,
31
+ keepalive_expiry=keepalive_expiry,
32
+ retries=retries,
22
33
  )
23
- self._target_address = target_address
24
- self._pool = ConnectionPool(
25
- pool_name=str(self._target_address),
26
- factory=self.connect,
27
- initial_connections=5,
28
- max_size=100,
29
- idle_timeout=20.0,
30
- reaper_interval=5.0,
34
+
35
+ def setup_connection_pool(
36
+ self,
37
+ max_connections: int | None,
38
+ max_keepalive_connections: int | None,
39
+ keepalive_expiry: float | None,
40
+ retries: int,
41
+ ) -> AsyncConnectionPool:
42
+ if self._target_type == "unix":
43
+ return AsyncConnectionPool(
44
+ max_connections=max_connections,
45
+ max_keepalive_connections=max_keepalive_connections,
46
+ keepalive_expiry=keepalive_expiry,
47
+ retries=retries,
48
+ uds=self._target_address,
49
+ )
50
+ return AsyncConnectionPool(
51
+ max_connections=max_connections,
52
+ max_keepalive_connections=max_keepalive_connections,
53
+ keepalive_expiry=keepalive_expiry,
54
+ retries=retries,
31
55
  )
32
56
 
33
- async def connect(self) -> StreamPair:
34
- if isinstance(self._target_address, tuple):
35
- return await asyncio.open_connection(*self._target_address)
36
- return await asyncio.open_unix_connection(str(self._target_address))
57
+ def get_upstream_base_url(self, scope: Scope) -> str:
58
+ if self._target_type == "unix":
59
+ return "http://localhost"
60
+ return f"http://{self._target_address}"
37
61
 
38
- async def startup(self):
39
- await self._pool.start()
62
+ def _parse_target(self) -> tuple[TargetType, str]:
63
+ if isinstance(self._target, Path) or (
64
+ isinstance(self._target, str) and ":" not in self._target
65
+ ):
66
+ return "unix", str(self._target)
40
67
 
41
- async def shutdown(self):
42
- await self._pool.stop()
68
+ if isinstance(self._target, str) and ":" in self._target:
69
+ host, port = str(self._target).split(":", 1)
70
+ host = host or "127.0.0.1"
71
+ elif isinstance(self._target, tuple) and len(self._target) == 2:
72
+ host = self._target[0]
73
+ port = str(self._target[1])
74
+ else:
75
+ raise Exception(f"Invalid target address: {self._target}")
43
76
 
44
- @asynccontextmanager
45
- async def select_backend(
46
- self,
47
- scope: Scope,
48
- headers: dict[str, str],
49
- ) -> AsyncGenerator[StreamPair, None]:
50
- async with self._pool.acquire() as (reader, writer):
51
- yield reader, writer
77
+ return "tcp", f"{host}:{port}"
@@ -1,8 +1,8 @@
1
1
  import logging
2
2
  from pathlib import Path
3
3
 
4
- from mrok.agent.sidecar.app import ForwardApp
5
- from mrok.master import MasterBase
4
+ from mrok.agent.sidecar.app import SidecarProxyApp
5
+ from mrok.proxy.master import MasterBase
6
6
 
7
7
  logger = logging.getLogger("mrok.proxy")
8
8
 
@@ -11,28 +11,49 @@ class SidecarAgent(MasterBase):
11
11
  def __init__(
12
12
  self,
13
13
  identity_file: str,
14
- target_addr: str | Path | tuple[str, int],
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
- publishers_port,
24
- subscribers_port,
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
- self.target_address = target_addr
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 ForwardApp(self.target_address)
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
- from mrok.http.types import ASGIApp
2
- from mrok.master import MasterBase
1
+ from mrok.proxy.master import MasterBase
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__(identity_file, workers, reload, publishers_port, subscribers_port)
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):
@@ -1,8 +1,8 @@
1
- from mrok.cli.commands import admin, agent, controller, proxy
1
+ from mrok.cli.commands import admin, agent, controller, frontend
2
2
 
3
3
  __all__ = [
4
4
  "admin",
5
5
  "agent",
6
6
  "controller",
7
- "proxy",
7
+ "frontend",
8
8
  ]
@@ -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.api import TagsType, ZitiClientAPI, ZitiManagementAPI
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: TagsType | None
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(
@@ -2,10 +2,10 @@ from datetime import datetime
2
2
 
3
3
  import typer
4
4
 
5
- from mrok.ziti.api import TagsType
5
+ from mrok.types.ziti import Tags
6
6
 
7
7
 
8
- def parse_tags(pairs: list[str] | None) -> TagsType | 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=f"Number of workers. Default: {default_workers}",
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
  )
@@ -1,6 +1,6 @@
1
1
  import typer
2
2
 
3
- from mrok.cli.commands.proxy import run
3
+ from mrok.cli.commands.frontend import run
4
4
 
5
5
  app = typer.Typer(help="mrok proxy commands.")
6
6
  run.register(app)
@@ -0,0 +1,91 @@
1
+ from pathlib import Path
2
+ from typing import Annotated
3
+
4
+ import typer
5
+
6
+ from mrok import frontend
7
+ from mrok.cli.utils import number_of_workers
8
+
9
+ default_workers = number_of_workers()
10
+
11
+
12
+ def register(app: typer.Typer) -> None:
13
+ @app.command("run")
14
+ def run_frontend(
15
+ ctx: typer.Context,
16
+ identity_file: Path = typer.Argument(
17
+ ...,
18
+ help="Identity json file",
19
+ ),
20
+ host: Annotated[
21
+ str,
22
+ typer.Option(
23
+ "--host",
24
+ "-h",
25
+ help="Host to bind to. Default: 127.0.0.1",
26
+ show_default=True,
27
+ ),
28
+ ] = "127.0.0.1",
29
+ port: Annotated[
30
+ int,
31
+ typer.Option(
32
+ "--port",
33
+ "-P",
34
+ help="Port to bind to. Default: 8000",
35
+ show_default=True,
36
+ ),
37
+ ] = 8000,
38
+ workers: Annotated[
39
+ int,
40
+ typer.Option(
41
+ "--workers",
42
+ "-w",
43
+ help=f"Number of workers. Default: {default_workers}",
44
+ show_default=True,
45
+ ),
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,
81
+ ):
82
+ """Run the mrok frontend with Gunicorn and Uvicorn workers."""
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/constants.py CHANGED
@@ -2,5 +2,3 @@ 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
- RE_SUBDOMAIN = re.compile(r"(?i)^(?:EXT-\d{4}-\d{4}|INS-\d{4}-\d{4}-\d{4})$")
@@ -13,6 +13,7 @@ INSTANCE_RESPONSE = {
13
13
  "name": "ins-1234-5678-0001.ext-1234-5678",
14
14
  "extension": {"id": "EXT-1234-5678"},
15
15
  "instance": {"id": "INS-1234-5678-0001"},
16
+ "status": "offline",
16
17
  "tags": {
17
18
  "account": "ACC-5555-3333",
18
19
  MROK_VERSION_TAG_NAME: "1.0",
@@ -25,6 +26,7 @@ INSTANCE_CREATE_RESPONSE = {
25
26
  "name": "ins-1234-5678-0001.ext-1234-5678",
26
27
  "extension": {"id": "EXT-1234-5678"},
27
28
  "instance": {"id": "INS-1234-5678-0001"},
29
+ "status": "online",
28
30
  "identity": {
29
31
  "ztAPI": "https://ziti.exts.platform.softwareone.com/edge/client/v1",
30
32
  "ztAPIs": None,
@@ -35,6 +37,17 @@ INSTANCE_CREATE_RESPONSE = {
35
37
  "ca": "pem:-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n",
36
38
  },
37
39
  "enableHa": None,
40
+ "mrok": {
41
+ "identity": "ins-0000-0000-0000.ext-0000-0000",
42
+ "extension": "ext-0000-0000",
43
+ "instance": "ins-0000-0000-0000",
44
+ "domain": "ext.s1.today",
45
+ "tags": {
46
+ "mrok-service": "ext-0000-0000",
47
+ "mrok-identity-type": "instance",
48
+ "mrok": "0.4.0",
49
+ },
50
+ },
38
51
  },
39
52
  "tags": {
40
53
  "account": "ACC-5555-3333",
@@ -9,12 +9,12 @@ from pydantic import (
9
9
  computed_field,
10
10
  )
11
11
 
12
- from mrok.ziti.api import TagsType
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: TagsType | None = None
17
+ tags: Tags | None = None
18
18
 
19
19
 
20
20
  class IdSchema(BaseModel):
@@ -0,0 +1,3 @@
1
+ from mrok.frontend.main import run
2
+
3
+ __all__ = ["run"]
mrok/frontend/app.py ADDED
@@ -0,0 +1,75 @@
1
+ import re
2
+
3
+ from httpcore import AsyncConnectionPool
4
+
5
+ from mrok.conf import get_settings
6
+ from mrok.proxy.app import ProxyAppBase
7
+ from mrok.proxy.backend import AIOZitiNetworkBackend
8
+ from mrok.proxy.exceptions import InvalidTargetError
9
+ from mrok.types.proxy import Scope
10
+
11
+ RE_SUBDOMAIN = re.compile(r"(?i)^(?:EXT-\d{4}-\d{4}|INS-\d{4}-\d{4}-\d{4})$")
12
+
13
+
14
+ class FrontendProxyApp(ProxyAppBase):
15
+ def __init__(
16
+ self,
17
+ identity_file: str,
18
+ *,
19
+ max_connections: int | None = 10,
20
+ max_keepalive_connections: int | None = None,
21
+ keepalive_expiry: float | None = None,
22
+ retries=0,
23
+ ):
24
+ self._identity_file = identity_file
25
+ self._proxy_domain = self._get_proxy_domain()
26
+ super().__init__(
27
+ max_connections=max_connections,
28
+ max_keepalive_connections=max_keepalive_connections,
29
+ keepalive_expiry=keepalive_expiry,
30
+ retries=retries,
31
+ )
32
+
33
+ def setup_connection_pool(
34
+ self,
35
+ max_connections: int | None,
36
+ max_keepalive_connections: int | None,
37
+ keepalive_expiry: float | None,
38
+ retries: int,
39
+ ) -> AsyncConnectionPool:
40
+ return AsyncConnectionPool(
41
+ max_connections=max_connections,
42
+ max_keepalive_connections=max_keepalive_connections,
43
+ keepalive_expiry=keepalive_expiry,
44
+ retries=retries,
45
+ network_backend=AIOZitiNetworkBackend(self._identity_file),
46
+ )
47
+
48
+ def get_upstream_base_url(self, scope: Scope) -> str:
49
+ target = self._get_target_name(
50
+ {k.decode("latin1"): v.decode("latin1") for k, v in scope.get("headers", {})}
51
+ )
52
+ return f"http://{target.lower()}"
53
+
54
+ def _get_proxy_domain(self):
55
+ settings = get_settings()
56
+ return (
57
+ settings.proxy.domain
58
+ if settings.proxy.domain[0] == "."
59
+ else f".{settings.proxy.domain}"
60
+ )
61
+
62
+ def _get_target_from_header(self, headers: dict[str, str], name: str) -> str | None:
63
+ header_value = headers.get(name, "")
64
+ if self._proxy_domain in header_value:
65
+ if ":" in header_value:
66
+ header_value, _ = header_value.split(":", 1)
67
+ return header_value[: -len(self._proxy_domain)]
68
+
69
+ def _get_target_name(self, headers: dict[str, str]) -> str:
70
+ target = self._get_target_from_header(headers, "x-forwarded-host")
71
+ if not target:
72
+ target = self._get_target_from_header(headers, "host")
73
+ if not target or not RE_SUBDOMAIN.fullmatch(target):
74
+ raise InvalidTargetError()
75
+ return target
@@ -6,9 +6,8 @@ from gunicorn.app.base import BaseApplication
6
6
  from uvicorn_worker import UvicornWorker
7
7
 
8
8
  from mrok.conf import get_settings
9
- from mrok.http.lifespan import LifespanWrapper
9
+ from mrok.frontend.app import FrontendProxyApp
10
10
  from mrok.logging import get_logging_config
11
- from mrok.proxy.app import ProxyApp
12
11
 
13
12
 
14
13
  class MrokUvicornWorker(UvicornWorker):
@@ -39,20 +38,23 @@ def run(
39
38
  host: str,
40
39
  port: int,
41
40
  workers: int,
41
+ max_connections: int | None,
42
+ max_keepalive_connections: int | None,
43
+ keepalive_expiry: float | None,
42
44
  ):
43
- proxy_app = ProxyApp(identity_file)
44
-
45
- asgi_app = LifespanWrapper(
46
- proxy_app,
47
- proxy_app.startup,
48
- proxy_app.shutdown,
45
+ app = FrontendProxyApp(
46
+ str(identity_file),
47
+ max_connections=max_connections,
48
+ max_keepalive_connections=max_keepalive_connections,
49
+ keepalive_expiry=keepalive_expiry,
49
50
  )
51
+
50
52
  options = {
51
53
  "bind": f"{host}:{port}",
52
54
  "workers": workers,
53
- "worker_class": "mrok.proxy.main.MrokUvicornWorker",
55
+ "worker_class": "mrok.frontend.main.MrokUvicornWorker",
54
56
  "logconfig_dict": get_logging_config(get_settings()),
55
57
  "reload": False,
56
58
  }
57
59
 
58
- StandaloneApplication(asgi_app, options).run()
60
+ StandaloneApplication(app, options).run()
mrok/proxy/__init__.py CHANGED
@@ -1,3 +0,0 @@
1
- from mrok.proxy.main import run
2
-
3
- __all__ = ["run"]