Skip to content

Commit b4bcd98

Browse files
committed
wip
1 parent 6f948a0 commit b4bcd98

File tree

11 files changed

+142
-60
lines changed

11 files changed

+142
-60
lines changed

.flake8

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[flake8]
22
max-line-length = 79
3-
extend-ignore = E203, W503
3+
extend-ignore = E203, W503, E501
44
exclude =
55
.git,
66
__pycache__,

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ dev = [
3939
"pytest-asyncio>=0.21.0",
4040
"pytest-cov>=4.0.0",
4141
"black>=23.0.0",
42+
"bandit>=1.8.3",
4243
"isort>=5.12.0",
4344
"flake8>=6.0.0",
4445
"mypy>=1.0.0",

retunnel/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
Tunnel,
1111
TunnelConfig,
1212
)
13-
14-
ReTunnelClient = HighPerformanceClient # Alias for backward compatibility
1513
from .core.exceptions import (
1614
AuthenticationError,
1715
ConfigurationError,
@@ -20,6 +18,8 @@
2018
TunnelError,
2119
)
2220

21+
ReTunnelClient = HighPerformanceClient # Alias for backward compatibility
22+
2323
__all__ = [
2424
"ReTunnelClient",
2525
"Tunnel",

retunnel/client/api_client.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
API client for ReTunnel server interactions
33
"""
44

5-
import json
65
from typing import Any, Optional
76
from urllib.parse import urljoin
87

@@ -87,7 +86,7 @@ async def _request(
8786
)
8887
raise APIError(response.status, error_msg)
8988

90-
return data
89+
return data # type: ignore[no-any-return]
9190

9291
async def register_user(
9392
self, email: Optional[str] = None

retunnel/client/cli.py

Lines changed: 49 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,18 @@
44

55
import asyncio
66
import logging
7-
import time
87
from pathlib import Path
98
from typing import Optional
109

1110
import typer
1211
import yaml
1312
from rich.console import Console
13+
from rich.live import Live
1414
from rich.logging import RichHandler
1515
from rich.panel import Panel
16-
from rich.table import Table
1716
from rich.progress import Progress, SpinnerColumn, TextColumn
1817
from rich.rule import Rule
19-
from rich.live import Live
18+
from rich.table import Table
2019

2120
from .. import __version__
2221
from ..core.config import AuthConfig, ClientConfig
@@ -34,13 +33,14 @@
3433

3534
def _format_bytes(num: int) -> str:
3635
"""Format bytes to human readable format"""
37-
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
38-
if abs(num) < 1024.0:
39-
if unit == 'B':
40-
return f"{num:.0f}{unit}"
41-
return f"{num:.1f}{unit}"
42-
num /= 1024.0
43-
return f"{num:.1f}PB"
36+
size = float(num)
37+
for unit in ["B", "KB", "MB", "GB", "TB"]:
38+
if abs(size) < 1024.0:
39+
if unit == "B":
40+
return f"{size:.0f}{unit}"
41+
return f"{size:.1f}{unit}"
42+
size /= 1024.0
43+
return f"{size:.1f}PB"
4444

4545

4646
def setup_logging(level: str = "INFO") -> None:
@@ -49,7 +49,14 @@ def setup_logging(level: str = "INFO") -> None:
4949
level=level,
5050
format="%(message)s",
5151
datefmt="[%X]",
52-
handlers=[RichHandler(console=console, rich_tracebacks=True, show_time=False, show_path=False)],
52+
handlers=[
53+
RichHandler(
54+
console=console,
55+
rich_tracebacks=True,
56+
show_time=False,
57+
show_path=False,
58+
)
59+
],
5360
)
5461

5562

@@ -213,14 +220,14 @@ def version() -> None:
213220
def credits() -> None:
214221
"""Show open source credits."""
215222
console.print("\n[bold cyan]Open Source Credits[/bold cyan]\n")
216-
223+
217224
table = Table(
218225
show_header=True,
219226
header_style="bold cyan",
220227
border_style="dim",
221228
show_edge=False,
222229
pad_edge=False,
223-
box=None
230+
box=None,
224231
)
225232
table.add_column("Package", style="cyan")
226233
table.add_column("License", style="green")
@@ -250,26 +257,30 @@ async def _run_tunnel(
250257
"""Run a single tunnel."""
251258
# Suppress client logging to preserve Rich output
252259
import logging as _logging
260+
253261
_logging.getLogger("client").setLevel(_logging.ERROR)
254262
_logging.getLogger("retunnel").setLevel(_logging.ERROR)
255263
_logging.getLogger("aiohttp").setLevel(_logging.ERROR)
256264
_logging.getLogger("asyncio").setLevel(_logging.ERROR)
257-
265+
258266
# Disable the default KeyboardInterrupt traceback
259267
import sys
268+
260269
sys.tracebacklimit = 0
261-
270+
262271
# Create client (server defaults to RETUNNEL_SERVER_ENDPOINT or localhost:6400)
263272
client = HighPerformanceClient(server, auth_token=token)
264273

265274
try:
266275
# Clean header
267276
console.clear()
268277
console.print()
269-
console.print(f"[bold cyan]ReTunnel[/bold cyan] [dim]v{__version__}[/dim]")
278+
console.print(
279+
f"[bold cyan]ReTunnel[/bold cyan] [dim]v{__version__}[/dim]"
280+
)
270281
console.print("[dim]Secure tunnel service[/dim]")
271282
console.print()
272-
283+
273284
# Connection progress
274285
with Progress(
275286
SpinnerColumn(spinner_name="dots", style="cyan"),
@@ -279,23 +290,31 @@ async def _run_tunnel(
279290
) as progress:
280291
task = progress.add_task("Connecting", total=None)
281292
await client.connect()
282-
progress.update(task, description="[green]✓[/green] Connected successfully")
293+
progress.update(
294+
task, description="[green]✓[/green] Connected successfully"
295+
)
283296
await asyncio.sleep(0.3)
284-
285-
console.print(f"[green]✓[/green] Connected to tunnel service")
297+
298+
console.print("[green]✓[/green] Connected to tunnel service")
286299
console.print(f"[dim]Client ID: {client.client_id}[/dim]")
287300
console.print()
288301

289302
# Request tunnel
290303
with Progress(
291304
SpinnerColumn(spinner_name="dots", style="cyan"),
292-
TextColumn(f"[cyan]Creating {config.protocol.upper()} tunnel...[/cyan]"),
305+
TextColumn(
306+
"[cyan]Creating {protocol} tunnel...[/cyan]".format(
307+
protocol=config.protocol.upper()
308+
)
309+
),
293310
console=console,
294311
transient=True,
295312
) as progress:
296313
task = progress.add_task("Creating", total=None)
297314
tunnel = await client.request_tunnel(config)
298-
progress.update(task, description="[green]✓[/green] Tunnel created")
315+
progress.update(
316+
task, description="[green]✓[/green] Tunnel created"
317+
)
299318
await asyncio.sleep(0.3)
300319

301320
# Display tunnel info cleanly
@@ -306,29 +325,29 @@ async def _run_tunnel(
306325
console.print()
307326
console.print(f" [cyan]Protocol[/cyan] {tunnel.protocol.upper()}")
308327
console.print(f" [cyan]Local port[/cyan] {tunnel.config.local_port}")
309-
console.print(f" [cyan]Status[/cyan] [green]● Active[/green]")
328+
console.print(" [cyan]Status[/cyan] [green]● Active[/green]")
310329
console.print()
311330
console.print(Rule(style="dim"))
312331
console.print()
313332
console.print("[dim]Press Ctrl+C to stop[/dim]")
314333
console.print()
315-
334+
316335
# Keep running and show stats using Live
317336
try:
318337
with Live(
319338
"[dim]↑ 0B ↓ 0B[/dim]",
320339
console=console,
321340
refresh_per_second=0.5,
322-
transient=False
341+
transient=False,
323342
) as live:
324343
while True:
325344
stats = tunnel.get_stats()
326-
in_bytes = _format_bytes(stats['bytes_in'])
327-
out_bytes = _format_bytes(stats['bytes_out'])
328-
345+
in_bytes = _format_bytes(stats["bytes_in"])
346+
out_bytes = _format_bytes(stats["bytes_out"])
347+
329348
# Update the live display
330349
live.update(f"[dim]↑ {in_bytes}{out_bytes}[/dim]")
331-
350+
332351
await asyncio.sleep(2)
333352
except KeyboardInterrupt:
334353
pass
@@ -345,7 +364,7 @@ async def _run_tunnel(
345364
raise typer.Exit(1)
346365
finally:
347366
# Ensure client is closed
348-
if 'client' in locals():
367+
if "client" in locals():
349368
await client.close()
350369
# Give asyncio time to clean up
351370
await asyncio.sleep(0.1)

retunnel/client/client.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import logging
77
import os
88
import platform
9-
import sys
109
from typing import Any, Dict, Optional, Set
1110

1211
from pydantic import BaseModel

retunnel/client/high_performance_model.py

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from typing import Any, Dict, Optional, Set
1212

1313
import aiohttp
14-
import msgpack
14+
import msgpack # type: ignore[import-untyped]
1515

1616
from ..utils.id import generate_client_id, generate_request_id
1717
from .api_client import APIError, ReTunnelAPIClient
@@ -55,7 +55,7 @@ class Tunnel:
5555
def public_url(self) -> str:
5656
"""Get the public URL for this tunnel"""
5757
return self.url
58-
58+
5959
def get_stats(self) -> Dict[str, Any]:
6060
"""Get tunnel statistics"""
6161
return {
@@ -205,7 +205,9 @@ async def connect(self) -> None:
205205
# Create session with proper timeout and connector settings
206206
timeout = aiohttp.ClientTimeout(total=60)
207207
connector = aiohttp.TCPConnector(limit=100, limit_per_host=10)
208-
self.session = aiohttp.ClientSession(timeout=timeout, connector=connector)
208+
self.session = aiohttp.ClientSession(
209+
timeout=timeout, connector=connector
210+
)
209211

210212
# Build WebSocket URL
211213
if self.server_addr.startswith(("ws://", "wss://")):
@@ -288,8 +290,9 @@ async def request_tunnel(self, config: TunnelConfig) -> Tunnel:
288290
if url:
289291
# Replace http://localhost:6400 or http://anyhost:6400 with https://retunnel.net
290292
import re
291-
url = re.sub(r'http://[^/]+/', 'https://retunnel.net/', url)
292-
293+
294+
url = re.sub(r"http://[^/]+/", "https://retunnel.net/", url)
295+
293296
tunnel = Tunnel(
294297
id=req_id,
295298
url=url,
@@ -336,7 +339,7 @@ async def _receive_message(
336339
if length == len(data) - 8:
337340
# It's length-prefixed, extract actual data
338341
data = data[8:]
339-
except:
342+
except Exception:
340343
# Not length-prefixed
341344
pass
342345

@@ -384,22 +387,26 @@ async def _handle_req_proxy(self) -> None:
384387
try:
385388
# Create new proxy WebSocket connection
386389
# Build WebSocket URL from server address
387-
if self.server_addr.startswith(("ws://", "wss://")):
390+
if self.server_addr and self.server_addr.startswith(
391+
("ws://", "wss://")
392+
):
388393
# Extract the base URL and construct proxy endpoint
389394
base_url = self.server_addr.replace("/api/v1/ws/tunnel", "")
390395
ws_url = f"{base_url}/api/v1/ws/proxy"
391396
else:
392397
ws_url = f"ws://{self.server_addr}/api/v1/ws/proxy"
393-
398+
394399
# Include auth headers
395400
headers = {}
396401
if self.auth_token:
397402
headers["Authorization"] = f"Bearer {self.auth_token}"
398-
403+
399404
self.logger.debug(f"Creating proxy connection to: {ws_url}")
400-
405+
401406
if self.session:
402-
proxy_ws = await self.session.ws_connect(ws_url, headers=headers, heartbeat=30)
407+
proxy_ws = await self.session.ws_connect(
408+
ws_url, headers=headers, heartbeat=30
409+
)
403410
else:
404411
raise RuntimeError("Session not initialized")
405412

@@ -413,7 +420,9 @@ async def _handle_req_proxy(self) -> None:
413420
task.add_done_callback(self.proxy_tasks.discard)
414421

415422
except Exception as e:
416-
self.logger.warning(f"Error creating proxy connection: {e} (URL: {ws_url if 'ws_url' in locals() else 'unknown'})")
423+
self.logger.warning(
424+
f"Error creating proxy connection: {e} (URL: {ws_url if 'ws_url' in locals() else 'unknown'})"
425+
)
417426

418427
async def _handle_proxy_connection(
419428
self, proxy_ws: aiohttp.ClientWebSocketResponse
@@ -491,15 +500,15 @@ async def _handle_proxy_connection(
491500
local_writer.write(body)
492501
request_bytes += body
493502
await local_writer.drain()
494-
503+
495504
# Count incoming bytes
496505
if tunnel:
497506
tunnel.bytes_in += len(request_bytes)
498507

499508
# Read response
500509
response_data = b""
501510
status_code = 500 # Default status code
502-
response_headers = {}
511+
response_headers: Dict[str, str] = {}
503512
response_body = b""
504513

505514
while True:
@@ -573,10 +582,12 @@ async def _handle_proxy_connection(
573582
}
574583

575584
await self._send_message(proxy_ws, response_msg)
576-
585+
577586
# Count outgoing bytes
578587
if tunnel:
579-
tunnel.bytes_out += len(response_body) + len(str(response_headers))
588+
tunnel.bytes_out += len(response_body) + len(
589+
str(response_headers)
590+
)
580591

581592
except Exception as e:
582593
self.logger.debug(
@@ -683,7 +694,7 @@ async def close(self) -> None:
683694
await self.session.close()
684695
# Wait for the underlying connections to close
685696
await asyncio.sleep(0.1)
686-
697+
687698
# Small delay to allow aiohttp to clean up
688699
await asyncio.sleep(0.1)
689700

retunnel/core/connection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import asyncio
66
import struct
7-
from typing import Any, Optional, Union
7+
from typing import Any, Optional
88

99
import websockets
1010

0 commit comments

Comments
 (0)