deepctl-cmd-debug-probe 0.0.2__tar.gz → 0.0.3__tar.gz
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.
- {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/PKG-INFO +3 -3
- {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/README.md +1 -1
- {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/pyproject.toml +2 -2
- {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/src/deepctl_cmd_debug_probe/proxy.py +77 -2
- {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/src/deepctl_cmd_debug_probe.egg-info/PKG-INFO +3 -3
- {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/src/deepctl_cmd_debug_probe.egg-info/requires.txt +1 -1
- {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/setup.cfg +0 -0
- {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/src/deepctl_cmd_debug_probe/__init__.py +0 -0
- {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/src/deepctl_cmd_debug_probe/command.py +0 -0
- {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/src/deepctl_cmd_debug_probe/models.py +0 -0
- {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/src/deepctl_cmd_debug_probe.egg-info/SOURCES.txt +0 -0
- {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/src/deepctl_cmd_debug_probe.egg-info/dependency_links.txt +0 -0
- {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/src/deepctl_cmd_debug_probe.egg-info/entry_points.txt +0 -0
- {deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/src/deepctl_cmd_debug_probe.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deepctl-cmd-debug-probe
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: Debug probe subcommand for deepctl — live ffprobe analysis during streaming
|
|
5
5
|
Author-email: Deepgram <devrel@deepgram.com>
|
|
6
6
|
Maintainer-email: Deepgram <devrel@deepgram.com>
|
|
@@ -19,7 +19,7 @@ Requires-Dist: deepctl-shared-utils>=0.1.10
|
|
|
19
19
|
Requires-Dist: click>=8.0.0
|
|
20
20
|
Requires-Dist: rich>=13.0.0
|
|
21
21
|
Requires-Dist: pydantic>=2.0.0
|
|
22
|
-
Requires-Dist: aiohttp>=3.
|
|
22
|
+
Requires-Dist: aiohttp>=3.11.0
|
|
23
23
|
|
|
24
24
|
# deepctl-cmd-debug-probe
|
|
25
25
|
|
|
@@ -61,7 +61,7 @@ pipx run deepctl --help
|
|
|
61
61
|
- `click>=8.0.0`
|
|
62
62
|
- `rich>=13.0.0`
|
|
63
63
|
- `pydantic>=2.0.0`
|
|
64
|
-
- `aiohttp>=3.
|
|
64
|
+
- `aiohttp>=3.11.0`
|
|
65
65
|
|
|
66
66
|
## License
|
|
67
67
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "deepctl-cmd-debug-probe"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.3" # x-release-please-version
|
|
8
8
|
description = "Debug probe subcommand for deepctl — live ffprobe analysis during streaming"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -26,7 +26,7 @@ dependencies = [
|
|
|
26
26
|
"click>=8.0.0",
|
|
27
27
|
"rich>=13.0.0",
|
|
28
28
|
"pydantic>=2.0.0",
|
|
29
|
-
"aiohttp>=3.
|
|
29
|
+
"aiohttp>=3.11.0",
|
|
30
30
|
]
|
|
31
31
|
|
|
32
32
|
[project.scripts]
|
{deepctl_cmd_debug_probe-0.0.2 → deepctl_cmd_debug_probe-0.0.3}/src/deepctl_cmd_debug_probe/proxy.py
RENAMED
|
@@ -4,14 +4,17 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import json
|
|
7
|
+
import re
|
|
7
8
|
import time
|
|
8
9
|
import uuid
|
|
9
10
|
from typing import TYPE_CHECKING
|
|
10
11
|
|
|
11
12
|
import aiohttp
|
|
12
13
|
from aiohttp import web
|
|
14
|
+
from deepctl_core.websocket import RedirectRefused, redirect_refused
|
|
13
15
|
from deepctl_shared_utils import probe_buffer
|
|
14
16
|
from rich.console import Console
|
|
17
|
+
from rich.markup import escape
|
|
15
18
|
|
|
16
19
|
from .models import ProbeConnectionStats, ProbeSnapshot
|
|
17
20
|
|
|
@@ -20,6 +23,37 @@ if TYPE_CHECKING:
|
|
|
20
23
|
|
|
21
24
|
console = Console()
|
|
22
25
|
|
|
26
|
+
# Browsers send Origin on every WebSocket handshake; native clients usually
|
|
27
|
+
# send none. Only pages served from this machine may use the proxy.
|
|
28
|
+
_LOOPBACK_ORIGIN = re.compile(
|
|
29
|
+
r"https?://(?:localhost|127\.0\.0\.1|\[::1\])(?::[0-9]{1,5})?"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _refuse_redirects(upstream_url: str) -> aiohttp.TraceConfig:
|
|
34
|
+
"""A trace hook that stops aiohttp before it follows a redirect.
|
|
35
|
+
|
|
36
|
+
aiohttp's ws_connect has no allow_redirects switch; it follows a 3xx
|
|
37
|
+
on the upgrade and would then stream the audio to the new host. The
|
|
38
|
+
hook runs after the 3xx arrives and before any request to the new
|
|
39
|
+
location, so nothing reaches it.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
async def on_redirect(
|
|
43
|
+
_session: aiohttp.ClientSession,
|
|
44
|
+
_ctx: object,
|
|
45
|
+
params: aiohttp.TraceRequestRedirectParams,
|
|
46
|
+
) -> None:
|
|
47
|
+
location = params.response.headers.get("Location")
|
|
48
|
+
params.response.close()
|
|
49
|
+
raise redirect_refused(
|
|
50
|
+
upstream_url, location, command="dg debug probe", payload="audio"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
trace = aiohttp.TraceConfig()
|
|
54
|
+
trace.on_request_redirect.append(on_redirect)
|
|
55
|
+
return trace
|
|
56
|
+
|
|
23
57
|
|
|
24
58
|
class ProbeProxy:
|
|
25
59
|
"""WebSocket proxy that probes audio at configurable intervals."""
|
|
@@ -55,8 +89,42 @@ class ProbeProxy:
|
|
|
55
89
|
return "agent"
|
|
56
90
|
return "unknown"
|
|
57
91
|
|
|
58
|
-
|
|
92
|
+
def _refusal_reason(self, request: web.Request) -> str | None:
|
|
93
|
+
"""Return why a connection is refused, or None to accept it.
|
|
94
|
+
|
|
95
|
+
Host must name this listener as 127.0.0.1:PORT or localhost:PORT.
|
|
96
|
+
Origin may be absent (native clients) or a loopback page origin;
|
|
97
|
+
anything else, including "null", is refused.
|
|
98
|
+
"""
|
|
99
|
+
sockname = (
|
|
100
|
+
request.transport.get_extra_info("sockname")
|
|
101
|
+
if request.transport is not None
|
|
102
|
+
else None
|
|
103
|
+
)
|
|
104
|
+
if not sockname:
|
|
105
|
+
return "host"
|
|
106
|
+
port = sockname[1]
|
|
107
|
+
allowed_hosts = {f"127.0.0.1:{port}", f"localhost:{port}"}
|
|
108
|
+
if port == 80:
|
|
109
|
+
allowed_hosts |= {"127.0.0.1", "localhost"}
|
|
110
|
+
hosts = request.headers.getall("Host", [])
|
|
111
|
+
if len(hosts) != 1 or hosts[0].lower() not in allowed_hosts:
|
|
112
|
+
return "host"
|
|
113
|
+
origins = request.headers.getall("Origin", [])
|
|
114
|
+
if not origins:
|
|
115
|
+
return None
|
|
116
|
+
if len(origins) != 1 or not _LOOPBACK_ORIGIN.fullmatch(origins[0].lower()):
|
|
117
|
+
return "origin"
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
async def handle_connection(self, request: web.Request) -> web.StreamResponse:
|
|
59
121
|
"""Handle an incoming WebSocket connection."""
|
|
122
|
+
# Checked before the handshake and before anything is sent upstream.
|
|
123
|
+
reason = self._refusal_reason(request)
|
|
124
|
+
if reason is not None:
|
|
125
|
+
console.print(escape(f"[conn] refused: {reason} not allowed"))
|
|
126
|
+
return web.Response(status=403, text="Forbidden\n")
|
|
127
|
+
|
|
60
128
|
ws_client = web.WebSocketResponse()
|
|
61
129
|
await ws_client.prepare(request)
|
|
62
130
|
|
|
@@ -91,7 +159,9 @@ class ProbeProxy:
|
|
|
91
159
|
|
|
92
160
|
try:
|
|
93
161
|
async with (
|
|
94
|
-
aiohttp.ClientSession(
|
|
162
|
+
aiohttp.ClientSession(
|
|
163
|
+
trace_configs=[_refuse_redirects(upstream_url)]
|
|
164
|
+
) as session,
|
|
95
165
|
session.ws_connect(
|
|
96
166
|
upstream_url,
|
|
97
167
|
headers=upstream_headers,
|
|
@@ -179,6 +249,11 @@ class ProbeProxy:
|
|
|
179
249
|
# Suppress CancelledError from the cancelled task
|
|
180
250
|
await asyncio.gather(task_c2u, task_u2c, return_exceptions=True)
|
|
181
251
|
|
|
252
|
+
except RedirectRefused as e:
|
|
253
|
+
console.print(
|
|
254
|
+
f"[red]Upstream connection refused[/red] [{conn_id}]: "
|
|
255
|
+
f"{escape(e.message)}"
|
|
256
|
+
)
|
|
182
257
|
except aiohttp.ClientError as e:
|
|
183
258
|
console.print(f"[red]Upstream connection failed[/red] [{conn_id}]: {e}")
|
|
184
259
|
except Exception as e:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deepctl-cmd-debug-probe
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: Debug probe subcommand for deepctl — live ffprobe analysis during streaming
|
|
5
5
|
Author-email: Deepgram <devrel@deepgram.com>
|
|
6
6
|
Maintainer-email: Deepgram <devrel@deepgram.com>
|
|
@@ -19,7 +19,7 @@ Requires-Dist: deepctl-shared-utils>=0.1.10
|
|
|
19
19
|
Requires-Dist: click>=8.0.0
|
|
20
20
|
Requires-Dist: rich>=13.0.0
|
|
21
21
|
Requires-Dist: pydantic>=2.0.0
|
|
22
|
-
Requires-Dist: aiohttp>=3.
|
|
22
|
+
Requires-Dist: aiohttp>=3.11.0
|
|
23
23
|
|
|
24
24
|
# deepctl-cmd-debug-probe
|
|
25
25
|
|
|
@@ -61,7 +61,7 @@ pipx run deepctl --help
|
|
|
61
61
|
- `click>=8.0.0`
|
|
62
62
|
- `rich>=13.0.0`
|
|
63
63
|
- `pydantic>=2.0.0`
|
|
64
|
-
- `aiohttp>=3.
|
|
64
|
+
- `aiohttp>=3.11.0`
|
|
65
65
|
|
|
66
66
|
## License
|
|
67
67
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|