- storage-without-pocketbase закрыта как реализованная: приёмка сошлась по всем
пяти критериям записи и по двенадцати приёмочным свойствам рубрики ревью
дизайна, работа лежит коммитом c9b7765
- урожай триажа ревью развёрнут в одиннадцать записей с тегом партии
review-2026-08-23 и расставлен по зависимости, а не в конец списка
- находка про признак живости воркера слита в stalled-pipeline-metric: у неё та
же причина — вставший конвейер неотличим от простоя
49 lines
3.6 KiB
Markdown
49 lines
3.6 KiB
Markdown
# 🧹 Отдавать файл записи через http.ServeContent
|
||
|
||
- **Тип:** chore
|
||
- **Категория:** Очередь — Стандартная семантика диапазонов нужна проигрыванию записи на экране: браузер тянет звук именно диапазонами
|
||
- **Зачем:** Отдача файла считает диапазоны байт своей рукой — около 110 строк семантики HTTP, которую стандартная библиотека делает сама, и на этих строках стоит проигрывание записи на экране.
|
||
- **Теги:** review-2026-08-23
|
||
|
||
Отдача файла записи разбирает заголовок `Range`, считает границы, ставит
|
||
`Content-Range` и код `206` своей рукой — около 110 строк семантики HTTP.
|
||
`http.ServeContent` делает то же самое: диапазоны, `If-Range`, `Last-Modified`,
|
||
`ETag`, неверный диапазон с кодом `416`.
|
||
|
||
Своя реализация стоит не тем, что длиннее, а тем, что расходится: браузер
|
||
проигрывает звук именно диапазонами, и задача `play-recording-in-app` встанет
|
||
поверх этих строк.
|
||
|
||
Нашёл проход `review-architecture` (вторая половина R2) ревью change
|
||
`2026-08-23-storage-without-pocketbase`.
|
||
|
||
## Затрагивает
|
||
|
||
- `internal/controller/http/file.go` — разбор `Range`, ответ `206` и заголовки
|
||
диапазона;
|
||
- тип содержимого ответа: он выводится из закрытого перечня форматов и остаётся
|
||
за нами, `ServeContent` его не выбирает;
|
||
- открытие копии в `internal/adapter/repo/sqlite/store.go` — `ServeContent`
|
||
требует `io.ReadSeeker` и время последнего изменения;
|
||
- публичный контракт HTTP: коды `200`, `206`, `416` и заголовки ответа на
|
||
запрос файла.
|
||
|
||
## Критерии приёмки
|
||
|
||
- Диапазоны считает стандартная библиотека. **Оракул:** `grep` по
|
||
`internal/controller/http` — ни одного разбора заголовка `Range` руками,
|
||
вызов `http.ServeContent` один.
|
||
- Ответы на диапазон не изменились. **Оракул:** тест контроллера — запрос без
|
||
`Range` отдаёт `200` и всю длину, запрос `bytes=0-99` отдаёт `206` с
|
||
`Content-Range`, запрос за пределом длины отдаёт `416`.
|
||
- Тип содержимого по-прежнему выбирает сервис. **Оракул:** тест на записи с
|
||
расширением `.html` — ответ несёт `application/octet-stream` и
|
||
`attachment`, а не тип из имени.
|
||
- Чужой файл остаётся недостижимым. **Оракул:** прежний тест владения —
|
||
чужая и несуществующая запись отвечают байт в байт одинаково.
|
||
|
||
## Рамки
|
||
|
||
Раскладку каталога данных и имена копий не трогаем: она объявлена необратимой.
|
||
Ответ на запрос чужого файла не меняется ни кодом, ни телом.
|