mrok 0.3.0__py3-none-any.whl → 0.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. mrok/agent/devtools/__init__.py +0 -0
  2. mrok/agent/devtools/__main__.py +34 -0
  3. mrok/agent/devtools/inspector/__init__.py +0 -0
  4. mrok/agent/devtools/inspector/__main__.py +25 -0
  5. mrok/agent/devtools/inspector/app.py +556 -0
  6. mrok/agent/devtools/inspector/server.py +18 -0
  7. mrok/agent/sidecar/app.py +9 -10
  8. mrok/agent/sidecar/main.py +35 -16
  9. mrok/agent/ziticorn.py +27 -18
  10. mrok/cli/commands/__init__.py +2 -1
  11. mrok/cli/commands/agent/__init__.py +2 -0
  12. mrok/cli/commands/agent/dev/__init__.py +7 -0
  13. mrok/cli/commands/agent/dev/console.py +25 -0
  14. mrok/cli/commands/agent/dev/web.py +37 -0
  15. mrok/cli/commands/agent/run/asgi.py +35 -16
  16. mrok/cli/commands/agent/run/sidecar.py +29 -13
  17. mrok/cli/commands/agent/utils.py +5 -0
  18. mrok/cli/commands/controller/run.py +1 -5
  19. mrok/cli/commands/proxy/__init__.py +6 -0
  20. mrok/cli/commands/proxy/run.py +49 -0
  21. mrok/cli/utils.py +5 -0
  22. mrok/conf.py +6 -0
  23. mrok/controller/auth.py +2 -2
  24. mrok/datastructures.py +159 -0
  25. mrok/http/config.py +3 -6
  26. mrok/http/constants.py +22 -0
  27. mrok/http/forwarder.py +62 -23
  28. mrok/http/lifespan.py +29 -0
  29. mrok/http/middlewares.py +143 -0
  30. mrok/http/types.py +43 -0
  31. mrok/http/utils.py +90 -0
  32. mrok/logging.py +22 -0
  33. mrok/master.py +272 -0
  34. mrok/metrics.py +139 -0
  35. mrok/proxy/__init__.py +3 -0
  36. mrok/proxy/app.py +77 -0
  37. mrok/proxy/dataclasses.py +12 -0
  38. mrok/proxy/main.py +58 -0
  39. mrok/proxy/streams.py +124 -0
  40. mrok/proxy/types.py +12 -0
  41. mrok/proxy/ziti.py +173 -0
  42. {mrok-0.3.0.dist-info → mrok-0.4.1.dist-info}/METADATA +7 -1
  43. {mrok-0.3.0.dist-info → mrok-0.4.1.dist-info}/RECORD +46 -20
  44. mrok/http/master.py +0 -132
  45. {mrok-0.3.0.dist-info → mrok-0.4.1.dist-info}/WHEEL +0 -0
  46. {mrok-0.3.0.dist-info → mrok-0.4.1.dist-info}/entry_points.txt +0 -0
  47. {mrok-0.3.0.dist-info → mrok-0.4.1.dist-info}/licenses/LICENSE.txt +0 -0
@@ -1,27 +1,46 @@
1
- import asyncio
2
- import contextlib
3
- from functools import partial
1
+ import logging
4
2
  from pathlib import Path
5
3
 
6
4
  from mrok.agent.sidecar.app import ForwardApp
7
- from mrok.http.config import MrokBackendConfig
8
- from mrok.http.master import Master
9
- from mrok.http.server import MrokServer
5
+ from mrok.master import MasterBase
10
6
 
7
+ logger = logging.getLogger("mrok.proxy")
11
8
 
12
- def run_sidecar(identity_file: str, target_addr: str | Path | tuple[str, int]):
13
- config = MrokBackendConfig(ForwardApp(target_addr), identity_file)
14
- server = MrokServer(config)
15
- with contextlib.suppress(KeyboardInterrupt, asyncio.CancelledError):
16
- server.run()
9
+
10
+ class SidecarAgent(MasterBase):
11
+ def __init__(
12
+ self,
13
+ identity_file: str,
14
+ target_addr: str | Path | tuple[str, int],
15
+ workers: int = 4,
16
+ publishers_port: int = 50000,
17
+ subscribers_port: int = 50001,
18
+ ):
19
+ super().__init__(
20
+ identity_file,
21
+ workers,
22
+ False,
23
+ publishers_port,
24
+ subscribers_port,
25
+ )
26
+ self.target_address = target_addr
27
+
28
+ def get_asgi_app(self):
29
+ return ForwardApp(self.target_address)
17
30
 
18
31
 
19
32
  def run(
20
33
  identity_file: str,
21
34
  target_addr: str | Path | tuple[str, int],
22
- workers=4,
23
- reload=False,
35
+ workers: int = 4,
36
+ publishers_port: int = 50000,
37
+ subscribers_port: int = 50001,
24
38
  ):
25
- start_fn = partial(run_sidecar, identity_file, target_addr)
26
- master = Master(start_fn, workers=workers, reload=reload)
27
- master.run()
39
+ agent = SidecarAgent(
40
+ identity_file,
41
+ target_addr,
42
+ workers=workers,
43
+ publishers_port=publishers_port,
44
+ subscribers_port=subscribers_port,
45
+ )
46
+ agent.run()
mrok/agent/ziticorn.py CHANGED
@@ -1,29 +1,38 @@
1
- import asyncio
2
- import contextlib
3
- import os
4
- from functools import partial
1
+ from mrok.http.types import ASGIApp
2
+ from mrok.master import MasterBase
5
3
 
6
- from mrok.http.config import ASGIApplication, MrokBackendConfig
7
- from mrok.http.master import Master
8
- from mrok.http.server import MrokServer
9
4
 
5
+ class ZiticornAgent(MasterBase):
6
+ def __init__(
7
+ self,
8
+ app: ASGIApp | str,
9
+ identity_file: str,
10
+ workers: int = 4,
11
+ reload: bool = False,
12
+ publishers_port: int = 50000,
13
+ subscribers_port: int = 50001,
14
+ ):
15
+ super().__init__(identity_file, workers, reload, publishers_port, subscribers_port)
16
+ self.app = app
10
17
 
11
- def run_ziticorn(app: ASGIApplication | str, identity_file: str):
12
- import sys
13
-
14
- sys.path.insert(0, os.getcwd())
15
- config = MrokBackendConfig(app, identity_file)
16
- server = MrokServer(config)
17
- with contextlib.suppress(KeyboardInterrupt, asyncio.CancelledError):
18
- server.run()
18
+ def get_asgi_app(self):
19
+ return self.app
19
20
 
20
21
 
21
22
  def run(
22
- app: ASGIApplication | str,
23
+ app: ASGIApp | str,
23
24
  identity_file: str,
24
25
  workers: int = 4,
25
26
  reload: bool = False,
27
+ publishers_port: int = 50000,
28
+ subscribers_port: int = 50001,
26
29
  ):
27
- start_fn = partial(run_ziticorn, app, identity_file)
28
- master = Master(start_fn, workers=workers, reload=reload)
30
+ master = ZiticornAgent(
31
+ app,
32
+ identity_file,
33
+ workers=workers,
34
+ reload=reload,
35
+ publishers_port=publishers_port,
36
+ subscribers_port=subscribers_port,
37
+ )
29
38
  master.run()
@@ -1,7 +1,8 @@
1
- from mrok.cli.commands import admin, agent, controller
1
+ from mrok.cli.commands import admin, agent, controller, proxy
2
2
 
3
3
  __all__ = [
4
4
  "admin",
5
5
  "agent",
6
6
  "controller",
7
+ "proxy",
7
8
  ]
@@ -1,6 +1,8 @@
1
1
  import typer
2
2
 
3
+ from mrok.cli.commands.agent.dev import app as dev_app
3
4
  from mrok.cli.commands.agent.run import app as run_app
4
5
 
5
6
  app = typer.Typer(help="mrok agent commands.")
6
7
  app.add_typer(run_app)
8
+ app.add_typer(dev_app)
@@ -0,0 +1,7 @@
1
+ import typer
2
+
3
+ from mrok.cli.commands.agent.dev import console, web
4
+
5
+ app = typer.Typer(name="dev", help="Dev tools for mrok agent.")
6
+ console.register(app)
7
+ web.register(app)
@@ -0,0 +1,25 @@
1
+ from typing import Annotated
2
+
3
+ import typer
4
+
5
+ from mrok.agent.devtools.inspector.app import InspectorApp
6
+
7
+
8
+ def register(app: typer.Typer) -> None:
9
+ @app.command("console")
10
+ def run_dev_console(
11
+ subscriber_port: Annotated[
12
+ int,
13
+ typer.Option(
14
+ "--subscriber-port",
15
+ "-s",
16
+ help=(
17
+ "TCP port where the mrok inspector console application "
18
+ "should connect to subscribe to request/response messages."
19
+ ),
20
+ show_default=True,
21
+ ),
22
+ ] = 50001,
23
+ ):
24
+ app = InspectorApp(subscriber_port)
25
+ app.run()
@@ -0,0 +1,37 @@
1
+ from typing import Annotated
2
+
3
+ import typer
4
+
5
+ from mrok.agent.devtools.inspector.server import InspectorServer
6
+
7
+
8
+ def register(app: typer.Typer) -> None:
9
+ @app.command("web")
10
+ def run_web_console(
11
+ port: Annotated[
12
+ int,
13
+ typer.Option(
14
+ "--port",
15
+ "-p",
16
+ help="TCP port where the mrok inspector web application will listen for requests.",
17
+ show_default=True,
18
+ ),
19
+ ] = 7777,
20
+ subscriber_port: Annotated[
21
+ int,
22
+ typer.Option(
23
+ "--subscriber-port",
24
+ "-s",
25
+ help=(
26
+ "TCP port where the mrok inspector web application "
27
+ "should connect to subscribe to request/response messages."
28
+ ),
29
+ show_default=True,
30
+ ),
31
+ ] = 50001,
32
+ ):
33
+ server = InspectorServer(
34
+ port=port,
35
+ subscriber_port=subscriber_port,
36
+ )
37
+ server.serve()
@@ -1,16 +1,10 @@
1
- import multiprocessing
2
1
  from pathlib import Path
3
2
  from typing import Annotated
4
3
 
5
4
  import typer
6
5
 
7
- # from app.logging import get_logging_config
8
6
  from mrok.agent import ziticorn
9
-
10
-
11
- def number_of_workers():
12
- return (multiprocessing.cpu_count() * 2) + 1
13
-
7
+ from mrok.cli.utils import number_of_workers
14
8
 
15
9
  default_workers = number_of_workers()
16
10
 
@@ -18,14 +12,8 @@ default_workers = number_of_workers()
18
12
  def register(app: typer.Typer) -> None:
19
13
  @app.command("asgi")
20
14
  def run_asgi(
21
- app: str = typer.Argument(
22
- ...,
23
- help="ASGI application",
24
- ),
25
- identity_file: Path = typer.Argument(
26
- ...,
27
- help="Identity json file",
28
- ),
15
+ app: Annotated[str, typer.Argument(..., help="ASGI application")],
16
+ identity_file: Annotated[Path, typer.Argument(..., help="Identity json file")],
29
17
  workers: Annotated[
30
18
  int,
31
19
  typer.Option(
@@ -44,6 +32,37 @@ def register(app: typer.Typer) -> None:
44
32
  show_default=True,
45
33
  ),
46
34
  ] = False,
35
+ publishers_port: Annotated[
36
+ int,
37
+ typer.Option(
38
+ "--publishers-port",
39
+ "-p",
40
+ help=(
41
+ "TCP port where the mrok agent "
42
+ "should connect to publish to request/response messages."
43
+ ),
44
+ show_default=True,
45
+ ),
46
+ ] = 50000,
47
+ subscribers_port: Annotated[
48
+ int,
49
+ typer.Option(
50
+ "--subscribers-port",
51
+ "-s",
52
+ help=(
53
+ "TCP port where the mrok agent should listen for incoming subscribers "
54
+ "connections for request/response messages."
55
+ ),
56
+ show_default=True,
57
+ ),
58
+ ] = 50001,
47
59
  ):
48
60
  """Run an ASGI application exposing it through OpenZiti network."""
49
- ziticorn.run(app, str(identity_file), workers=workers, reload=reload)
61
+ ziticorn.run(
62
+ app,
63
+ str(identity_file),
64
+ workers=workers,
65
+ reload=reload,
66
+ publishers_port=publishers_port,
67
+ subscribers_port=subscribers_port,
68
+ )
@@ -1,15 +1,10 @@
1
- import multiprocessing
2
1
  from pathlib import Path
3
2
  from typing import Annotated
4
3
 
5
4
  import typer
6
5
 
7
6
  from mrok.agent import sidecar
8
-
9
-
10
- def number_of_workers():
11
- return (multiprocessing.cpu_count() * 2) + 1
12
-
7
+ from mrok.cli.utils import number_of_workers
13
8
 
14
9
  default_workers = number_of_workers()
15
10
 
@@ -34,15 +29,30 @@ def register(app: typer.Typer) -> None:
34
29
  show_default=True,
35
30
  ),
36
31
  ] = default_workers,
37
- reload: Annotated[
38
- bool,
32
+ publishers_port: Annotated[
33
+ int,
34
+ typer.Option(
35
+ "--publishers-port",
36
+ "-p",
37
+ help=(
38
+ "TCP port where the mrok agent "
39
+ "should connect to publish to request/response messages."
40
+ ),
41
+ show_default=True,
42
+ ),
43
+ ] = 50000,
44
+ subscribers_port: Annotated[
45
+ int,
39
46
  typer.Option(
40
- "--reload",
41
- "-r",
42
- help="Enable auto-reload. Default: False",
47
+ "--subscribers-port",
48
+ "-s",
49
+ help=(
50
+ "TCP port where the mrok agent should listen for incoming subscribers "
51
+ "connections for request/response messages."
52
+ ),
43
53
  show_default=True,
44
54
  ),
45
- ] = False,
55
+ ] = 50001,
46
56
  ):
47
57
  """Run a Sidecar Proxy to expose a web application through OpenZiti."""
48
58
  if ":" in str(target):
@@ -51,4 +61,10 @@ def register(app: typer.Typer) -> None:
51
61
  else:
52
62
  target_addr = str(target) # type: ignore
53
63
 
54
- sidecar.run(str(identity_file), target_addr, workers=workers, reload=reload)
64
+ sidecar.run(
65
+ str(identity_file),
66
+ target_addr,
67
+ workers=workers,
68
+ publishers_port=publishers_port,
69
+ subscribers_port=subscribers_port,
70
+ )
@@ -0,0 +1,5 @@
1
+ import multiprocessing
2
+
3
+
4
+ def number_of_workers() -> int:
5
+ return (multiprocessing.cpu_count() * 2) + 1
@@ -1,18 +1,14 @@
1
- import multiprocessing
2
1
  from collections.abc import Callable
3
2
  from typing import Annotated, Any
4
3
 
5
4
  import typer
6
5
  from gunicorn.app.base import BaseApplication
7
6
 
7
+ from mrok.cli.utils import number_of_workers
8
8
  from mrok.controller.app import app as asgi_app
9
9
  from mrok.logging import get_logging_config
10
10
 
11
11
 
12
- def number_of_workers():
13
- return (multiprocessing.cpu_count() * 2) + 1
14
-
15
-
16
12
  class StandaloneApplication(BaseApplication): # pragma: no cover
17
13
  def __init__(self, application: Callable, options: dict[str, Any] | None = None):
18
14
  self.options = options or {}
@@ -0,0 +1,6 @@
1
+ import typer
2
+
3
+ from mrok.cli.commands.proxy import run
4
+
5
+ app = typer.Typer(help="mrok proxy commands.")
6
+ run.register(app)
@@ -0,0 +1,49 @@
1
+ from pathlib import Path
2
+ from typing import Annotated
3
+
4
+ import typer
5
+
6
+ from mrok import proxy
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_proxy(
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
+ ):
48
+ """Run the mrok proxy with Gunicorn and Uvicorn workers."""
49
+ proxy.run(identity_file, host, port, workers)
mrok/cli/utils.py ADDED
@@ -0,0 +1,5 @@
1
+ import multiprocessing
2
+
3
+
4
+ def number_of_workers() -> int:
5
+ return (multiprocessing.cpu_count() * 2) + 1
mrok/conf.py CHANGED
@@ -15,6 +15,12 @@ DEFAULT_SETTINGS = {
15
15
  "ssl_verify": False,
16
16
  },
17
17
  "PAGINATION": {"limit": 50},
18
+ "SIDECAR": {
19
+ "textual_port": 4040,
20
+ "store_port": 5051,
21
+ "store_size": 1000,
22
+ "textual_command": "python mrok/agent/sidecar/inspector.py",
23
+ },
18
24
  }
19
25
 
20
26
  _settings = None
mrok/controller/auth.py CHANGED
@@ -80,8 +80,8 @@ async def authenticate(
80
80
  )
81
81
  return payload
82
82
  except jwt.InvalidKeyError as e:
83
- logger.error(f"Invalid jwt token: {e}")
83
+ logger.error(f"Invalid jwt token: {e} ({credentials.credentials})")
84
84
  raise UNAUTHORIZED_EXCEPTION
85
85
  except jwt.InvalidTokenError as e:
86
- logger.error(f"Invalid jwt token: {e}")
86
+ logger.error(f"Invalid jwt token: {e} ({credentials.credentials})")
87
87
  raise UNAUTHORIZED_EXCEPTION
mrok/datastructures.py ADDED
@@ -0,0 +1,159 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal
4
+
5
+ from pydantic import BaseModel, Field
6
+ from pydantic_core import core_schema
7
+
8
+
9
+ class FixedSizeByteBuffer:
10
+ def __init__(self, max_size: int):
11
+ self._max_size = max_size
12
+ self._buf = bytearray()
13
+ self.overflow = False
14
+
15
+ def write(self, data: bytes) -> None:
16
+ if not data:
17
+ return
18
+
19
+ remaining = self._max_size - len(self._buf)
20
+ if remaining <= 0:
21
+ self.overflow = True
22
+ return
23
+
24
+ if len(data) > remaining:
25
+ self._buf.extend(data[:remaining])
26
+ self.overflow = True
27
+ else:
28
+ self._buf.extend(data)
29
+
30
+ def getvalue(self) -> bytes:
31
+ return bytes(self._buf)
32
+
33
+ def clear(self) -> None:
34
+ self._buf.clear()
35
+ self.overflow = False
36
+
37
+
38
+ class HTTPHeaders(dict):
39
+ def __init__(self, initial=None):
40
+ super().__init__()
41
+ if initial:
42
+ for k, v in initial.items():
43
+ super().__setitem__(str(k).lower(), str(v))
44
+
45
+ def __getitem__(self, key: str) -> str:
46
+ return super().__getitem__(key.lower())
47
+
48
+ def __setitem__(self, key: str, value: str) -> None:
49
+ super().__setitem__(str(key).lower(), str(value))
50
+
51
+ def __delitem__(self, key: str) -> None:
52
+ super().__delitem__(key.lower())
53
+
54
+ def get(self, k, default=None):
55
+ return super().get(k.lower(), default)
56
+
57
+ @classmethod
58
+ def from_asgi(cls, items: list[tuple[bytes, bytes]]) -> HTTPHeaders:
59
+ d = {k.decode("latin-1"): v.decode("latin-1") for k, v in items}
60
+ return cls(d)
61
+
62
+ @classmethod
63
+ def __get_pydantic_core_schema__(cls, source, handler):
64
+ """Provide a pydantic-core schema so Pydantic treats this as a mapping of str->str.
65
+
66
+ We generate the schema for `dict[str, str]` using the provided handler and wrap
67
+ it with a validator that converts the validated dict into `HTTPHeaders`.
68
+ """
69
+ # handler may be a callable or an object with `generate_schema`; handle both
70
+ try:
71
+ dict_schema = handler.generate_schema(dict[str, str])
72
+ except AttributeError:
73
+ dict_schema = handler(dict[str, str])
74
+
75
+ def _wrap(v, validator):
76
+ # `validator` will validate input according to `dict_schema` and return a dict
77
+ validated = validator(input_value=v)
78
+ if isinstance(validated, HTTPHeaders):
79
+ return validated
80
+ return cls(validated)
81
+
82
+ return core_schema.no_info_wrap_validator_function(
83
+ _wrap,
84
+ dict_schema,
85
+ serialization=core_schema.plain_serializer_function_ser_schema(lambda v: dict(v)),
86
+ )
87
+
88
+
89
+ class HTTPRequest(BaseModel):
90
+ method: str
91
+ url: str
92
+ headers: HTTPHeaders
93
+ query_string: bytes
94
+ start_time: float
95
+ body: bytes | None = None
96
+ body_truncated: bool | None = None
97
+
98
+
99
+ class HTTPResponse(BaseModel):
100
+ type: Literal["response"] = "response"
101
+ request: HTTPRequest
102
+ status: int
103
+ headers: HTTPHeaders
104
+ duration: float
105
+ body: bytes | None = None
106
+ body_truncated: bool | None = None
107
+
108
+
109
+ class ProcessMetrics(BaseModel):
110
+ cpu: float
111
+ mem: float
112
+
113
+
114
+ class DataTransferMetrics(BaseModel):
115
+ bytes_in: int
116
+ bytes_out: int
117
+
118
+
119
+ class RequestsMetrics(BaseModel):
120
+ rps: int
121
+ total: int
122
+ successful: int
123
+ failed: int
124
+
125
+
126
+ class ResponseTimeMetrics(BaseModel):
127
+ avg: float
128
+ min: int
129
+ max: int
130
+ p50: int
131
+ p90: int
132
+ p99: int
133
+
134
+
135
+ class WorkerMetrics(BaseModel):
136
+ worker_id: str
137
+ data_transfer: DataTransferMetrics
138
+ requests: RequestsMetrics
139
+ response_time: ResponseTimeMetrics
140
+ process: ProcessMetrics
141
+
142
+
143
+ class Meta(BaseModel):
144
+ identity: str
145
+ extension: str
146
+ instance: str
147
+ domain: str
148
+ tags: dict[str, str] | None = None
149
+
150
+
151
+ class Status(BaseModel):
152
+ type: Literal["status"] = "status"
153
+ meta: Meta
154
+ metrics: WorkerMetrics
155
+
156
+
157
+ class Event(BaseModel):
158
+ type: Literal["status", "response"]
159
+ data: Status | HTTPResponse = Field(discriminator="type")
mrok/http/config.py CHANGED
@@ -8,21 +8,18 @@ from typing import Any
8
8
  import openziti
9
9
  from uvicorn import config
10
10
 
11
- from mrok.conf import get_settings
12
11
  from mrok.http.protocol import MrokHttpToolsProtocol
13
- from mrok.logging import setup_logging
12
+ from mrok.http.types import ASGIApp
14
13
 
15
14
  logger = logging.getLogger("mrok.proxy")
16
15
 
17
16
  config.LIFESPAN["auto"] = "mrok.http.lifespan:MrokLifespan"
18
17
 
19
- ASGIApplication = config.ASGIApplication
20
-
21
18
 
22
19
  class MrokBackendConfig(config.Config):
23
20
  def __init__(
24
21
  self,
25
- app: ASGIApplication | Callable[..., Any] | str,
22
+ app: ASGIApp | Callable[..., Any] | str,
26
23
  identity_file: str | Path,
27
24
  ziti_load_timeout_ms: int = 5000,
28
25
  backlog: int = 2048,
@@ -62,4 +59,4 @@ class MrokBackendConfig(config.Config):
62
59
  return sock
63
60
 
64
61
  def configure_logging(self) -> None:
65
- setup_logging(get_settings())
62
+ return
mrok/http/constants.py ADDED
@@ -0,0 +1,22 @@
1
+ MAX_REQUEST_BODY_BYTES = 2 * 1024 * 1024
2
+ MAX_RESPONSE_BODY_BYTES = 5 * 1024 * 1024
3
+
4
+ BINARY_CONTENT_TYPES = {
5
+ "application/octet-stream",
6
+ "application/pdf",
7
+ }
8
+
9
+ BINARY_PREFIXES = (
10
+ "image/",
11
+ "video/",
12
+ "audio/",
13
+ )
14
+
15
+ TEXTUAL_CONTENT_TYPES = {
16
+ "application/json",
17
+ "application/xml",
18
+ "application/javascript",
19
+ "application/x-www-form-urlencoded",
20
+ }
21
+
22
+ TEXTUAL_PREFIXES = ("text/",)