69aa98e18c
- 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
63 lines
1.7 KiB
Go
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"))
|
|
}
|