Upload 12 files
Browse files- colab_tunnel/_diagnostics.py +34 -12
- colab_tunnel/_tunnels.py +16 -3
- pyproject.toml +1 -1
colab_tunnel/_diagnostics.py
CHANGED
|
@@ -81,23 +81,39 @@ def _stop_test_server(server_bin: Path) -> None:
|
|
| 81 |
logger.debug('Тестовый сервер остановлен.')
|
| 82 |
|
| 83 |
|
| 84 |
-
def _check_url_latency(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
"""
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
|
| 88 |
Returns:
|
| 89 |
-
|
| 90 |
"""
|
| 91 |
-
# Убираем строку с IPv4-паролем
|
| 92 |
clean_url = url.split('\n')[0].strip()
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
return None
|
| 102 |
|
| 103 |
|
|
@@ -145,6 +161,12 @@ def _print_report(results: list[TunnelBenchmarkResult]) -> None:
|
|
| 145 |
if with_lat:
|
| 146 |
lowest = min(with_lat, key=lambda r: r.latency) # type: ignore[arg-type]
|
| 147 |
print(f'Минимальная задержка: {lowest.name} ({lowest.latency * 1000:.0f}мс)') # type: ignore[operator]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
print()
|
| 149 |
|
| 150 |
|
|
|
|
| 81 |
logger.debug('Тестовый сервер остановлен.')
|
| 82 |
|
| 83 |
|
| 84 |
+
def _check_url_latency(
|
| 85 |
+
url: str,
|
| 86 |
+
timeout: float = 10.0,
|
| 87 |
+
warmup_retries: int = 2,
|
| 88 |
+
warmup_delay: float = 5.0,
|
| 89 |
+
) -> Optional[float]:
|
| 90 |
"""
|
| 91 |
+
Проверяет доступность публичного URL и возвращает RTT.
|
| 92 |
+
|
| 93 |
+
Делает повторные попытки с паузой — некоторые провайдеры (cloudflared,
|
| 94 |
+
tunnelite) анонсируют URL раньше, чем маршрут фактически распространяется
|
| 95 |
+
по их сети. Cloudflared обычно требует 5–10 секунд после старта.
|
| 96 |
|
| 97 |
Returns:
|
| 98 |
+
RTT в секундах, или None если URL недоступен после всех попыток.
|
| 99 |
"""
|
| 100 |
+
# Убираем строку с IPv4-паролем, если провайдер вернул её второй строкой
|
| 101 |
clean_url = url.split('\n')[0].strip()
|
| 102 |
+
|
| 103 |
+
for attempt in range(warmup_retries):
|
| 104 |
+
if attempt > 0:
|
| 105 |
+
logger.debug(f'Повтор проверки доступности (попытка {attempt + 1}): {clean_url}')
|
| 106 |
+
time.sleep(warmup_delay)
|
| 107 |
+
try:
|
| 108 |
+
t = time.time()
|
| 109 |
+
resp = get_url(clean_url, timeout=timeout, allow_redirects=True)
|
| 110 |
+
elapsed = round(time.time() - t, 3)
|
| 111 |
+
if 200 <= resp.status_code < 400:
|
| 112 |
+
return elapsed
|
| 113 |
+
logger.debug(f'HTTP {resp.status_code} от {clean_url}')
|
| 114 |
+
except Exception as e:
|
| 115 |
+
logger.debug(f'Попытка {attempt + 1}/{warmup_retries} не удалась: {e}')
|
| 116 |
+
|
| 117 |
return None
|
| 118 |
|
| 119 |
|
|
|
|
| 161 |
if with_lat:
|
| 162 |
lowest = min(with_lat, key=lambda r: r.latency) # type: ignore[arg-type]
|
| 163 |
print(f'Минимальная задержка: {lowest.name} ({lowest.latency * 1000:.0f}мс)') # type: ignore[operator]
|
| 164 |
+
|
| 165 |
+
# Пояснение для native — его URL требует сессионные куки Google Colab
|
| 166 |
+
native_result = next((r for r in results if r.name == 'native'), None)
|
| 167 |
+
if native_result and native_result.success and not native_result.latency:
|
| 168 |
+
print('\n ℹ native: URL работает, но требует Google-авторизации —')
|
| 169 |
+
print(' внешняя проверка без куки браузера всегда будет недоступна.')
|
| 170 |
print()
|
| 171 |
|
| 172 |
|
colab_tunnel/_tunnels.py
CHANGED
|
@@ -36,6 +36,7 @@ def get_revproxy_url(
|
|
| 36 |
timeout: float = 20.0,
|
| 37 |
write_link: bool = False,
|
| 38 |
stdin_input: str | None = None,
|
|
|
|
| 39 |
) -> str:
|
| 40 |
"""
|
| 41 |
Универсальный запуск бинарного туннельного провайдера.
|
|
@@ -53,6 +54,8 @@ def get_revproxy_url(
|
|
| 53 |
timeout: Максимальное время ожидания URL (сек).
|
| 54 |
write_link: Сохранять ли ссылку в links.txt.
|
| 55 |
stdin_input: Строка для отправки в stdin процесса.
|
|
|
|
|
|
|
| 56 |
|
| 57 |
Returns:
|
| 58 |
Публичный URL туннеля.
|
|
@@ -90,11 +93,18 @@ def get_revproxy_url(
|
|
| 90 |
|
| 91 |
# ── Завершение предыдущего экземпляра ───────────────────────────────
|
| 92 |
kill_process_by_name(bin_path.name)
|
| 93 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
# ── Запуск процесса ──────────────────────────────────────────────────
|
| 95 |
stdin_flag = PIPE if stdin_input is not None else None
|
| 96 |
-
process = Popen(
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
| 98 |
if stdin_input is not None and process.stdin:
|
| 99 |
try:
|
| 100 |
process.stdin.write(stdin_input.encode())
|
|
@@ -325,6 +335,9 @@ def get_bore_url(port: int) -> str:
|
|
| 325 |
read_from_stderr=True, # Rust логи идут в stderr
|
| 326 |
url_pattern=r'bore\.pub:\d+',
|
| 327 |
timeout=15.0,
|
|
|
|
|
|
|
|
|
|
| 328 |
)
|
| 329 |
return f'http://{raw}/'
|
| 330 |
|
|
|
|
| 36 |
timeout: float = 20.0,
|
| 37 |
write_link: bool = False,
|
| 38 |
stdin_input: str | None = None,
|
| 39 |
+
extra_env: dict[str, str] | None = None,
|
| 40 |
) -> str:
|
| 41 |
"""
|
| 42 |
Универсальный запуск бинарного туннельного провайдера.
|
|
|
|
| 54 |
timeout: Максимальное время ожидания URL (сек).
|
| 55 |
write_link: Сохранять ли ссылку в links.txt.
|
| 56 |
stdin_input: Строка для отправки в stdin процесса.
|
| 57 |
+
extra_env: Дополнительные переменные окружения для процесса.
|
| 58 |
+
Накладываются поверх os.environ (не заменяют его).
|
| 59 |
|
| 60 |
Returns:
|
| 61 |
Публичный URL туннеля.
|
|
|
|
| 93 |
|
| 94 |
# ── Завершение предыдущего экземпляра ───────────────────────────────
|
| 95 |
kill_process_by_name(bin_path.name)
|
| 96 |
+
# ── Подготовка окружения ─────────────────────────────────────────────
|
| 97 |
+
import os as _os
|
| 98 |
+
proc_env = _os.environ.copy()
|
| 99 |
+
if extra_env:
|
| 100 |
+
proc_env.update(extra_env)
|
| 101 |
# ── Запуск процесса ──────────────────────────────────────────────────
|
| 102 |
stdin_flag = PIPE if stdin_input is not None else None
|
| 103 |
+
process = Popen(
|
| 104 |
+
start_commands,
|
| 105 |
+
stdout=PIPE, stderr=PIPE, stdin=stdin_flag,
|
| 106 |
+
env=proc_env, # ← передаём окружение
|
| 107 |
+
)
|
| 108 |
if stdin_input is not None and process.stdin:
|
| 109 |
try:
|
| 110 |
process.stdin.write(stdin_input.encode())
|
|
|
|
| 335 |
read_from_stderr=True, # Rust логи идут в stderr
|
| 336 |
url_pattern=r'bore\.pub:\d+',
|
| 337 |
timeout=15.0,
|
| 338 |
+
# Bore использует Rust-крейт `tracing`. Без RUST_LOG=info строка
|
| 339 |
+
# "listening at bore.pub:XXXXX" (INFO-уровень) не выводится совсем.
|
| 340 |
+
extra_env={'RUST_LOG': 'info'},
|
| 341 |
)
|
| 342 |
return f'http://{raw}/'
|
| 343 |
|
pyproject.toml
CHANGED
|
@@ -10,7 +10,7 @@ readme = "README.md"
|
|
| 10 |
requires-python = ">=3.10"
|
| 11 |
license = { text = "MIT" }
|
| 12 |
dependencies = [
|
| 13 |
-
"requests",
|
| 14 |
]
|
| 15 |
|
| 16 |
[project.optional-dependencies]
|
|
|
|
| 10 |
requires-python = ">=3.10"
|
| 11 |
license = { text = "MIT" }
|
| 12 |
dependencies = [
|
| 13 |
+
"requests>=2.32.4",
|
| 14 |
]
|
| 15 |
|
| 16 |
[project.optional-dependencies]
|