- база своя: два пула, захват одним UPDATE ... RETURNING, шаги схемы на goose под файловым замком, одна миграция начальной схемы вместо семи прежних - транспорт переписан на net/http: свои слои, свой ограничитель частоты, отдача файла с проверкой владельца; панель /_/ и пространство /api/ исчезли - по находкам ревью: журнал не пишет путь под корнем приложения, ключ бюджета читается справа налево, узнавание известного идёт читающим пулом
125 lines
5.8 KiB
Go
125 lines
5.8 KiB
Go
package http
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// Отдельные адреса наблюдения. Корней у сервиса остался один — корень
|
|
// приложения; `/api` и `/_` ушли вместе со встроенным хранилищем и его панелью,
|
|
// и адресов под этими именами не существует.
|
|
//
|
|
// Резервировать имя за отказом сервис не берётся: имя, за которым ничего не
|
|
// стоит, ничем не отличается от любого другого свободного, а второй перечень
|
|
// «когда-то занятых корней» разошёлся бы с первым молча.
|
|
const (
|
|
HealthPath = "/health"
|
|
MetricsPath = "/metrics"
|
|
)
|
|
|
|
// Mount — часть адресного пространства, принадлежащая сервису.
|
|
//
|
|
// Перечень этих частей — **единственное** описание того, что сервису
|
|
// принадлежит, и он не описывает регистрацию, а порождает её: корень,
|
|
// заведённый мимо перечня, не получит обработчика вовсе. Из него же выводятся
|
|
// правило неизвестного пути, уровень журнала и область действия узнавания.
|
|
type Mount struct {
|
|
// Path — корень либо точный адрес.
|
|
Path string
|
|
|
|
// Exact — путь является точным адресом, а не корнем: `/health` накрывает
|
|
// только сам себя, а `/app` — всё, что под ним.
|
|
Exact bool
|
|
|
|
// Bind вешает обработчики этой части.
|
|
Bind func(mux *http.ServeMux)
|
|
}
|
|
|
|
// Covers говорит, принадлежит ли путь этой части адресного пространства.
|
|
//
|
|
// Условий два, и оба обязательны: точное совпадение либо префикс **вместе с
|
|
// косой чертой**. По одному префиксу корню `/app` достался бы посторонний
|
|
// `/apple`; по одному префиксу с косой чертой голый `/app` не достался бы никому
|
|
// и уехал бы разметкой приложения.
|
|
func (m Mount) Covers(requestPath string) bool {
|
|
if m.Exact {
|
|
return requestPath == m.Path
|
|
}
|
|
|
|
return requestPath == m.Path || strings.HasPrefix(requestPath, m.Path+"/")
|
|
}
|
|
|
|
// ServiceMounts перечисляет адресное пространство сервиса целиком.
|
|
//
|
|
// Обработчик приложения приходит уже одетым в свои слои — ограничитель частоты,
|
|
// узнавание, требование учётной записи: область их действия и есть корень
|
|
// приложения, и берётся она отсюда, а не перечисляется вторым списком.
|
|
func ServiceMounts(app http.Handler, metricsHandler http.Handler) []Mount {
|
|
return []Mount{
|
|
{Path: AppRoot, Bind: bindApp(app)},
|
|
{Path: HealthPath, Exact: true, Bind: bindHealth},
|
|
{Path: MetricsPath, Exact: true, Bind: bindMetrics(metricsHandler)},
|
|
}
|
|
}
|
|
|
|
// RegisterServiceRoutes вешает всё, что сервис вешает сам.
|
|
func RegisterServiceRoutes(mux *http.ServeMux, mounts []Mount) {
|
|
for _, mount := range mounts {
|
|
if mount.Bind != nil {
|
|
mount.Bind(mux)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ExactAddressOf называет точный адрес сервиса, которому отвечает путь. Второе
|
|
// значение ложно у всего прочего — у пути под корнем и у пути вне корней.
|
|
//
|
|
// Точные адреса — закрытый перечень, и только они пишутся в журнал дословно:
|
|
// значением такого пути распоряжается не спрашивающий, а сам перечень.
|
|
func ExactAddressOf(mounts []Mount, requestPath string) (string, bool) {
|
|
for _, mount := range mounts {
|
|
if mount.Exact && mount.Covers(requestPath) {
|
|
return mount.Path, true
|
|
}
|
|
}
|
|
|
|
return "", false
|
|
}
|
|
|
|
// IsObservationAddress говорит, что путь — адрес наблюдения.
|
|
//
|
|
// Опрос здоровья и метрик идёт постоянно и полезного не несёт, поэтому уровень
|
|
// журнала у него свой. Перечень при этом тот же самый: второе перечисление этих
|
|
// адресов разошлось бы с первым молча.
|
|
func IsObservationAddress(mounts []Mount, requestPath string) bool {
|
|
_, ok := ExactAddressOf(mounts, requestPath)
|
|
return ok
|
|
}
|
|
|
|
// bindApp вешает корень приложения.
|
|
//
|
|
// Образцов два, и оба обязательны: без точного `/app` маршрутизатор увёл бы
|
|
// голый корень перенаправлением на `/app/`, а норма требует от него отказа
|
|
// приложения, а не переезда.
|
|
func bindApp(app http.Handler) func(mux *http.ServeMux) {
|
|
return func(mux *http.ServeMux) {
|
|
mux.Handle(AppRoot+"/", app)
|
|
mux.Handle(AppRoot, app)
|
|
}
|
|
}
|
|
|
|
func bindHealth(mux *http.ServeMux) {
|
|
mux.HandleFunc(HealthPath, func(w http.ResponseWriter, _ *http.Request) {
|
|
writeJSON(w, http.StatusOK, map[string]string{
|
|
"status": "ok",
|
|
"message": "Transcriber service is running",
|
|
})
|
|
})
|
|
}
|
|
|
|
func bindMetrics(handler http.Handler) func(mux *http.ServeMux) {
|
|
return func(mux *http.ServeMux) {
|
|
mux.Handle(MetricsPath, handler)
|
|
}
|
|
}
|