Files
glchat/internal/server/fallback.go
T
grendervill 69aa98e18c feat(server): скелет Go-сервера с healthz, readyz и api/v1/meta
- internal/config: конфигурация из env с валидацией и дефолтами (§5.1, §9.1)
- internal/httpx: middleware (request id, slog-логи, recover, security-заголовки),
  единый формат ошибок §8.5 и лимит тела запроса
- internal/database: SQLite WAL + busy_timeout + foreign_keys, один writer
  и пул чтения, goose-миграции embedded, PRAGMA optimize по расписанию
- internal/meta: полезная нагрузка /api/v1/meta (версия, фичи, лимиты)
- internal/server: маршруты, SPA-раздача собранного клиента, OpenAPI 3.1
- cmd/glchat: точка входа с graceful shutdown и JSON-логами
- тесты: конфиг, миграции и идемпотентность, HTTP-эндпоинты, статика, 404/405
2026-09-19 18:20:56 +03:00

63 lines
1.7 KiB
Go

package server
import (
"net/http"
"strings"
"glchat/internal/httpx"
)
// methodOfPattern splits a Go 1.22 routing pattern such as "GET /api/v1/meta"
// into its method and path parts. Patterns without a method apply to all.
func methodOfPattern(pattern string) (method, path string) {
if i := strings.IndexByte(pattern, ' '); i > 0 {
return pattern[:i], pattern[i+1:]
}
return "", pattern
}
func (s *Server) routeExists(method, path string) bool {
for _, pattern := range s.patterns {
patternMethod, patternPath := methodOfPattern(pattern)
if patternMethod == "" || patternMethod == method {
continue
}
if patternPath == path {
return true
}
if strings.HasSuffix(patternPath, "/") && strings.HasPrefix(path, patternPath) {
return true
}
}
return false
}
// reservedPrefixes are handled by the API, the gateway or the file CDN; an
// unknown path under them is a real 404 and must not receive the SPA shell.
var reservedPrefixes = []string{"/api/", "/gateway", "/files/", "/rtc"}
func isReservedPath(path string) bool {
for _, prefix := range reservedPrefixes {
if path == strings.TrimSuffix(prefix, "/") || strings.HasPrefix(path, prefix) {
return true
}
}
return false
}
func (s *Server) handleFallback(w http.ResponseWriter, r *http.Request) {
if s.routeExists(r.Method, r.URL.Path) {
httpx.WriteErrorStatus(w, http.StatusMethodNotAllowed, httpx.CodeBadRequest, "method not allowed")
return
}
if isReservedPath(r.URL.Path) {
httpx.WriteError(w, httpx.NewError(httpx.CodeNotFound, "resource not found"))
return
}
if r.Method == http.MethodGet || r.Method == http.MethodHead {
s.static.serve(w, r)
return
}
httpx.WriteError(w, httpx.NewError(httpx.CodeNotFound, "resource not found"))
}