package http import ( "log/slog" "net/http" "net/netip" "git.vakhrushev.me/av/transcriber/internal/contract" ) // AppChain одевает адреса приложения в их слои. // // Порядок один и он несущий: ограничитель частоты → узнавание → требование // учётной записи → обработчик. // // - ограничитель стоит первым, потому что узнавание читает базу, а на новом // имени ещё и пишет в неё: поставленное раньше, оно работало бы на запросах, // которые ограничитель уже отверг, и поток отвергнутых обращений заводил бы // учётные записи, которые потом не убираются ничем; // - требование учётной записи стоит перед чтением тела: запись, за которую не // заплатит узнанный отправитель, не должна попасть даже в память, а позже // пришлось бы убирать уже уложенный файл — чего сервис не умеет вовсе. // // Область действия всех трёх — корень приложения, и берётся она из перечня // адресного пространства: цепочка вешается на корень целиком, вторым списком // адресов её не описывают. func AppChain( routes http.Handler, users contract.UserRepository, trusted []netip.Prefix, logger *slog.Logger, ) http.Handler { handler := RequireUser()(routes) handler = TrustedHeaderIdentity(users, trusted, logger)(handler) handler = RateLimit(trusted)(handler) return handler } // BuildHandler собирает поверхность сервиса целиком. // // Раздача приложения вешается последней: она занимает корень, и всё, что не // совпало ни с одним адресом сервиса, доходит до неё. // // Снаружи всех — журнал и восстановление после паники: первый обязан видеть код // ответа, каким бы слоем тот ни родился, второй — не дать одному паникующему // запросу уронить процесс. func BuildHandler(mounts []Mount, webapp *WebappHandler, logger *slog.Logger) http.Handler { mux := http.NewServeMux() RegisterServiceRoutes(mux, mounts) webapp.Register(mux) return Journal(mounts, logger)(Recover(logger)(mux)) }