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, substitution map[string]string, logger *slog.Logger, ) http.Handler { handler := RequireUser()(routes) handler = TrustedHeaderIdentity(users, trusted, logger)(handler) handler = SubstituteIdentityHeaders(substitution, 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)) }