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")) }