Files
transcriber/docs/research/webapp-serving.md
T
av 663021f712 приложение собрано каркасом и вшито в бинарник
- заведён каталог web/ — Vue 3, роутер пятой версии, сборка Vite; собранное
  вшивается через go:embed и раздаётся корневым маршрутом: разметка на
  неизвестном пути вне корней сервиса, отказ контракта внутри корня
- перечень корней сервиса стал единой точкой и порождает регистрацию маршрутов,
  а не описывает её; журнал раздачи пишет исход и длину пути, но не сам путь
- шаг front зовёт Node контейнером docker — Biome, юнит-тесты Vue и сборка
  входят в гейт, а в Dockerfile появилась ступень приложения
2026-08-15 18:51:05 +03:00

6.5 KiB
Raw Blame History

Раздача приложения: что делают за нас библиотека и сборщик

Отвечает на вопросы, возникшие по ходу задачи spa-skeleton, — какие свойства раздачи приходят не из нашего кода, а из стандартной библиотеки, из PocketBase и из инструментов приложения. Наблюдения понадобились потому, что ревью нашло три места, где записанное намерение расходилось с тем, что на деле делает чужой код.

Как снималось

Прогонами на живом бинарнике (свой конфиг с выдуманными ключами, свой каталог данных вне репозитория) и чтением исходников зависимостей, зафиксированных в go.mod: github.com/pocketbase/pocketbase версии v0.39.10 и стандартной библиотеки Go. Отдельно — прогоны установщика и сборщика приложения в контейнере. Числа ниже сняты 2026-08-15 на этом прогоне, а не взяты из чужих записок.

Что выяснилось

  • Маршрутизатор стандартной библиотеки сравнивает сегменты пути после раскодирования. Поэтому /%5f/ попадает туда же, куда /_/, а /%68ealth — туда же, куда /health: ответы совпадают байт в байт. Исходная форма остаётся в URL.RawPath, и решение, принимаемое вне сервиса по сырому пути — правилом обратного прокси, — такой формы не видит. Цена записана в security.md, «Периметр»: барьер перед панелью владельца обходится подменой одного знака.

  • PocketBase пишет каждый запрос в свою таблицу журнала, а не только в вывод контейнера: слой activityLogger подключён ко всем маршрутам и кладёт путь целиком (до 3000 знаков), адрес отправителя, источник перехода и клиент. Умолчания — хранить пять суток, адрес записывать. Готовая раздача статики (apis.Static) первой же строкой ставит признак «успех не записывать»; своя раздача этого признака не наследует, и его надо ставить руками. Отсюда правило в review.md: журналов два, и говорить надо про оба.

  • Вшитая файловая система не несёт времени правки. embed.FS отдаёт нулевое время у любого файла, поэтому отдача файла стандартной библиотекой никогда не отвечает подтверждением «не менялось» — всякая проверка приходит полным телом. Заголовок, обещающий дешёвую проверку, без метки ответа обещает то, чего код не делает.

  • Сборщик приложения чистит выходной каталог перед каждой сборкой. Метка, положенная рядом с собранным ради того, чтобы каталог существовал в git, уезжает первым же прогоном. Живёт она только этажом выше выходного каталога.

  • Проверка типов однофайловых компонентов не работает с седьмой линией TypeScript. vue-tsc версии 3.3.10 зовёт у компилятора точку входа, которой новый компилятор не отдаёт, и сборка падает на этапе проверки типов. Рабочая пара — пятая линия TypeScript.

  • Установщик пакетов без сети не отказывает, а виснет. Он уходит в повторы с нарастающей паузой на каждом пакете, и набор проверок вместо кода отказа просто стоит. Пределы у отдельных обращений положения не спасают: их сумма и даёт зависание. Помогает короткое обращение-проба перед установкой.

  • Вес приложения в бинарнике равен весу собранного. Замер: две сборки, с собранным приложением и с пустым каталогом, разница — 86 072 байта, то есть ровно index.html плюс единственный ресурс. Собранное приложение на четыре экрана в разведке spa-framework весило того же порядка.

Чего эта записка не узнала

  • Во что ступень сборки обходится образу по времени. Прогон до конца не доходит: из контейнеров этой машины нет исходящей сети при рабочем разрешении имён. По весу вопрос закрыт иначе — ступень в рабочий слой не копируется, и финальный образ от неё не растёт вовсе.
  • Как поведёт себя раздача под настоящим потоком. Ограничителя частоты на корневом маршруте нет, а профиля нагрузки у проекта нет тоже.
  • Что делает настоящий браузер с этими заголовками: проверено кодами ответов и заголовками, а не браузером.