fxTunnel

· Часть 4 серии «Пишу свой ngrok на Go: дневник разработки»

Пишу свой ngrok на Go: auto-refresh, 404 и первая боевая фича

И начинают всплывать реальные проблемы — те, которые на localhost никогда не увидишь.

Контекст

27 января. fxTunnel крутится на боевом сервере, пользуюсь каждый день. И начинают всплывать реальные проблемы — те, которые на localhost никогда не увидишь. Главная боль: клиент периодически теряет соединение (Wi-Fi, VPN, смена сети) и переподключается. Переподключение работает — но авторизация падает. Почему? Потому что JWT-токен протух.

Проблема: токен, который умирает не вовремя

JWT (JSON Web Token) — это как пропуск в офис с ограниченным сроком. Мой access-токен живёт 15 минут. Если клиент был подключён 20 минут, отвалился и пытается переподключиться — токен уже мёртв. Сценарий: 1. Клиент подключается с валидным токеном — всё ок 2. Работает 20 минут 3. Wi-Fi моргнул — соединение потеряно 4. Клиент переподключается с тем же токеном 5. Сервер: «токен протух, до свидания» 6. Туннель не поднимается Для пользователя это выглядит как «всё сломалось». Ужасно.

Решение: автоматический refresh

В вебе эта проблема решена давно: пара токенов. Access-токен — короткий (15 мин), для повседневных запросов. Refresh-токен — длинный (7 дней), для обновления access-токена. Добавил в клиент callback TokenRefresher. Когда сервер отвечает «TOKEN_EXPIRED», клиент не сдаётся — вызывает refresher, получает новый access-токен и повторяет авторизацию. Для пользователя это прозрачно: реконнект произошёл, туннель снова работает. В GUI refresh-токен хранится в системном keyring, и при каждом реконнекте автоматически обновляет access-токен. Клиент может работать неделями без ручной переавторизации.

Кастомная 404

Когда кто-то заходит на несуществующий поддомен (random.mydomain.com), раньше он видел стандартный nginx 404. Скучно и непрофессионально. Сделал свою страницу ошибки в стиле проекта — cyber-industrial тема, анимированный фон, брендинг fxTunnel. 243 строки HTML/CSS прямо в Go-коде через go:embed. go:embed — способ упаковать любые файлы (HTML, CSS, картинки) прямо внутрь Go-бинарника. Не нужен отдельный веб-сервер для статики. Один бинарник содержит всё. Мелочь? Да. Но именно из таких мелочей складывается впечатление. Когда пользователь видит стилизованную страницу ошибки вместо стандартной — это показывает, что о нём подумали.

Грабли

Порядок проверки токена. Сначала я проверял токен ДО попытки refresh. Протух — сразу отказ. Правильно: сервер возвращает специальный код TOKEN_EXPIRED, клиент ловит и делает refresh. Два round-trip вместо одного, зато надёжно. go:embed и inline HTML. 243 строки HTML внутри Go-файла — это не красиво. Позже вынес шаблоны в отдельные файлы. Но на первом этапе — и так сойдёт. Мобильная верстка 404. Первая версия выглядела отлично на десктопе и ужасно на телефоне. Layout shift, overflow — классика. Быстрый фикс в следующем коммите.

Итог

27 января — две фичи, которые делают продукт заметно зрелее: - Auto-refresh JWT — клиент работает неделями без ручного вмешательства - Кастомная 404 — выглядит как часть продукта, а не как ошибка сервера

1feff99 feat(client): add automatic JWT token refresh on reconnect
2b5d08a feat(server): add custom styled 404 error page

В следующей части: полный редизайн GUI-клиента. Почему первый дизайн не устроил и почему переделывать — это нормально.

Все части дневника разработки