хранилище переехало с PocketBase на SQLite со своим каталогом файлов
- база своя: два пула, захват одним UPDATE ... RETURNING, шаги схемы на goose под файловым замком, одна миграция начальной схемы вместо семи прежних - транспорт переписан на net/http: свои слои, свой ограничитель частоты, отдача файла с проверкой владельца; панель /_/ и пространство /api/ исчезли - по находкам ревью: журнал не пишет путь под корнем приложения, ключ бюджета читается справа налево, узнавание известного идёт читающим пулом
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
хранения ответов, поведение при несобранном приложении и то, что уходит в журнал.
|
||||
Отпечаток в именах ресурсов — свойство сборки, и его дом — конвенция приложения.
|
||||
## Requirements
|
||||
|
||||
### Requirement: Приложение отдаётся самим бинарником
|
||||
|
||||
Сервис SHALL отдавать разметку приложения и её ресурсы из самого бинарника.
|
||||
@@ -68,21 +69,36 @@
|
||||
### Requirement: Неизвестный путь вне корней открывает приложение
|
||||
|
||||
Сервис SHALL отдавать разметку приложения на всяком пути, который не принадлежит
|
||||
ни одному корню сервиса и не совпадает с отдельным адресом наблюдения. Корни
|
||||
перечислены поимённо — `/api` у хранилища, `/app` у приложения, `/_` у панели, —
|
||||
отдельными адресами стоят `/health` и `/metrics`.
|
||||
ни одному корню сервиса и не совпадает с отдельным адресом наблюдения. Корень у
|
||||
сервиса остался **один** — `/app` у приложения; отдельными адресами стоят
|
||||
`/health` и `/metrics`.
|
||||
|
||||
Корня `/auth` в перечне больше нет: собственного входа у сервиса не осталось, и
|
||||
адресов под этим корнем не существует. Прежние адреса входа поэтому отвечают тем
|
||||
же, чем отвечает всякий путь вне корней, — разметкой приложения. Резервировать имя
|
||||
за отказом сервис не берётся: имя, за которым ничего не стоит, ничем не отличается
|
||||
от любого другого свободного имени, а второй перечень «когда-то занятых корней»
|
||||
разошёлся бы с первым молча.
|
||||
Корней `/api` и `/_` в перечне больше нет: встроенного хранилища с его
|
||||
собственным пространством и панели администратора у сервиса не осталось, и
|
||||
адресов под этими именами не существует. Прежние пути хранилища и панели поэтому
|
||||
отвечают тем же, чем отвечает всякий путь вне корней, — разметкой приложения.
|
||||
Резервировать имя за отказом сервис не берётся: имя, за которым ничего не стоит,
|
||||
ничем не отличается от любого другого свободного имени, а второй перечень
|
||||
«когда-то занятых корней» разошёлся бы с первым молча.
|
||||
|
||||
Этим же снимается дефект подменённого знака: путь панели, записанный кодом знака,
|
||||
раскодируется в тот же путь и попадает в то же правило — правило одно, и особого
|
||||
случая у него нет.
|
||||
|
||||
Корня `/auth` в перечне нет тоже: собственного входа у сервиса не осталось.
|
||||
|
||||
Путь принадлежит корню, когда **совпадает с ним точно либо начинается им вместе с
|
||||
косой чертой**. Оба условия обязательны: по одному лишь префиксу корню `/app`
|
||||
достался бы посторонний `/apple`, а по одному лишь префиксу с косой чертой голый
|
||||
`/api` не достался бы никому и уехал бы разметкой.
|
||||
`/app` не достался бы никому и уехал бы разметкой.
|
||||
|
||||
Чем отвечает голый `/app`, названо прямо: он принадлежит корню приложения,
|
||||
адресом приложения при этом не является и потому MUST отвечать как **неизвестный
|
||||
путь под корнем приложения** — узнанному `404` телом отказа приложения,
|
||||
неузнанному `401` тем же телом, каким отвечают прочие адреса под этим корнем.
|
||||
Разметки в ответе нет ни в одном из двух случаев. Без этой строки «не разметка
|
||||
приложения» читается как «что-нибудь ещё», и код ответа выбрала бы за нас первая
|
||||
же сборка.
|
||||
|
||||
Путь, принадлежащий корню, MUST не проваливаться в приложение никогда: отказ
|
||||
контракта остаётся отказом контракта и уходит той формой, которой этот корень
|
||||
@@ -108,9 +124,18 @@
|
||||
|
||||
#### Scenario: Голый корень разметкой не подменяется
|
||||
|
||||
- **WHEN** запрос приходит на путь, совпадающий с корнем сервиса точно и без
|
||||
- **GIVEN** запрос идёт с заголовком, поставленным прокси
|
||||
- **WHEN** запрос приходит на путь, совпадающий с корнем приложения точно и без
|
||||
косой черты
|
||||
- **THEN** тело ответа — не разметка приложения
|
||||
- **THEN** ответ имеет код `404`
|
||||
- **AND** тело ответа — отказ приложения с машиночитаемым кодом, а не разметка
|
||||
|
||||
#### Scenario: Голый корень неузнанному отвечает как прочие адреса под корнем
|
||||
|
||||
- **WHEN** запрос приходит без заголовка на путь, совпадающий с корнем приложения
|
||||
точно и без косой черты
|
||||
- **THEN** ответ имеет код `401`
|
||||
- **AND** тело ответа — не разметка приложения
|
||||
|
||||
#### Scenario: Посторонний путь, начинающийся именем корня, открывает приложение
|
||||
|
||||
@@ -125,6 +150,19 @@
|
||||
- **THEN** ответ имеет код `200`
|
||||
- **AND** тело ответа — разметка приложения
|
||||
|
||||
#### Scenario: Прежний путь хранилища открывает приложение
|
||||
|
||||
- **WHEN** запрос приходит на путь под прежним корнем хранилища
|
||||
- **THEN** ответ имеет код `200`
|
||||
- **AND** тело ответа — разметка приложения
|
||||
|
||||
#### Scenario: Прежний адрес панели открывает приложение
|
||||
|
||||
- **WHEN** запрос приходит на прежний адрес панели — и записанный знаком, и
|
||||
записанный кодом этого знака
|
||||
- **THEN** оба ответа имеют код `200`
|
||||
- **AND** тело каждого — разметка приложения
|
||||
|
||||
#### Scenario: Неизвестный путь под корнем приложения отвечает отказом
|
||||
|
||||
- **GIVEN** запрос идёт с заголовком, поставленным прокси
|
||||
@@ -139,11 +177,6 @@
|
||||
- **THEN** ответ имеет код `401`
|
||||
- **AND** тело ответа — не разметка приложения
|
||||
|
||||
#### Scenario: Неизвестный путь под корнем хранилища отвечает отказом
|
||||
|
||||
- **WHEN** запрос приходит на неизвестный путь под корнем хранилища
|
||||
- **THEN** тело ответа — не разметка приложения
|
||||
|
||||
#### Scenario: Несуществующий ресурс отвечает отсутствием, а не разметкой
|
||||
|
||||
- **WHEN** браузер спрашивает под каталогом ресурсов файл, которого в сборке нет
|
||||
@@ -186,9 +219,19 @@
|
||||
|
||||
### Requirement: Путь, отданный приложению, в журнал не идёт
|
||||
|
||||
Сервис SHALL записывать о таком запросе **исход из закрытого перечня** —
|
||||
разметка, ресурс, отказ — и длину пути, а самого пути MUST не записывать ни в
|
||||
один свой журнал. То же относится к меткам метрик.
|
||||
Сервис SHALL записывать о запросе **маршрут из закрытого перечня** и длину пути,
|
||||
а самого запрошенного пути MUST не записывать ни в один свой журнал. То же
|
||||
относится к меткам метрик. Перечень маршрутов закрыт и назван: точные адреса
|
||||
наблюдения и объявленные образцы адресов приложения; всё, что ни одному из них
|
||||
не отвечает, MUST обозначаться одним общим значением. Для запроса, отданного
|
||||
приложению, к строке добавляется **исход из закрытого перечня** — разметка,
|
||||
ресурс, отказ.
|
||||
|
||||
Правило MUST накрывать обе половины адресного пространства — и путь вне корней
|
||||
сервиса, и путь под корнем приложения. Принадлежность пути сервису тут ничего не
|
||||
меняет: `/app/<произвольный текст>` принадлежит сервису и отвечает отказом, но
|
||||
множеством значений под корнем распоряжается спрашивающий ровно так же, как и
|
||||
вне его.
|
||||
|
||||
Причина в том, кто этот путь выбирает. До появления раздачи путь вне корней
|
||||
ловил отказ маршрутизатора; теперь он успешный ответ, и множеством его значений
|
||||
@@ -197,8 +240,12 @@
|
||||
есть: причина отказа, пришедшая от провайдера строкой запроса, приводится к
|
||||
перечню известных.
|
||||
|
||||
Журналов при этом **два**: свой и журнал хранилища, куда библиотека кладёт путь
|
||||
целиком вместе с адресом отправителя. Требование относится к обоим.
|
||||
Идентификатор записи от этого не пропадает: его пишет обработчик своим полем, и
|
||||
пишет он прочитанный идентификатор, а не тот, что стоял в запросе.
|
||||
|
||||
Журнал у сервиса теперь **один**: второй, куда чужая библиотека клала путь целиком
|
||||
вместе с адресом отправителя, ушёл вместе с ней. Правило от этого не ослабло, а
|
||||
перестало зависеть от настройки чужого журнала, которую мы не писали.
|
||||
|
||||
#### Scenario: Путь не доезжает до журнала
|
||||
|
||||
@@ -211,6 +258,25 @@
|
||||
- **WHEN** приходит запрос на путь длиной в тысячу знаков
|
||||
- **THEN** записи о нём не растут вместе с длиной пути
|
||||
|
||||
#### Scenario: Путь под корнем приложения журнал не пишет
|
||||
|
||||
- **GIVEN** пришедший не узнан
|
||||
- **WHEN** он спрашивает под корнем приложения путь, не отвечающий ни одному
|
||||
объявленному образцу адреса
|
||||
- **THEN** записи о нём не несут этого пути
|
||||
- **AND** не растут вместе с его длиной
|
||||
|
||||
#### Scenario: Объявленный образец адреса приложения в журнале различим
|
||||
|
||||
- **WHEN** приходит запрос на объявленный адрес приложения
|
||||
- **THEN** запись о нём несёт образец этого адреса, а не запрошенный путь
|
||||
|
||||
#### Scenario: Второго журнала у сервиса нет
|
||||
|
||||
- **GIVEN** сервис поднялся
|
||||
- **WHEN** приходит запрос на путь вне корней сервиса
|
||||
- **THEN** запись о нём появляется только в журнале сервиса
|
||||
|
||||
### Requirement: Сервис объявляет, какая сборка приложения в нём вшита
|
||||
|
||||
Сервис SHALL писать при подъёме отпечаток вшитой сборки. Он же MUST уходить
|
||||
@@ -272,4 +338,3 @@
|
||||
- **WHEN** человек открывает приложение
|
||||
- **THEN** приложение показывает строку о неудаче
|
||||
- **AND** эта строка не та, которой оно сообщает о неузнавании
|
||||
|
||||
|
||||
Reference in New Issue
Block a user