package httpapi import ( "net/http" "sort" "strings" ) type routeMethodPattern struct { path string methods map[string]struct{} allow []string } // WithRouteMethodCompatibility applies the legacy-compatible method matrix // for the checked-in public HTTP contract. It sits outside authentication so // OPTIONS never depends on runtime configuration and HEAD executes the // corresponding GET semantics while suppressing the response body. func WithRouteMethodCompatibility(next http.Handler) http.Handler { patterns := routeMethodPatterns(GoRouteSurface()) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { pattern := matchingRouteMethodPattern(patterns, r.URL.Path) if pattern == nil { next.ServeHTTP(w, r) return } if r.Method == http.MethodOptions { w.Header().Set("Allow", strings.Join(pattern.allow, ", ")) w.WriteHeader(http.StatusNoContent) return } if r.Method == http.MethodHead { if _, ok := pattern.methods[http.MethodHead]; !ok { w.WriteHeader(http.StatusMethodNotAllowed) return } clone := r.Clone(r.Context()) clone.Method = http.MethodGet next.ServeHTTP(&methodHeadResponseWriter{ResponseWriter: w}, clone) return } if _, ok := pattern.methods[r.Method]; !ok { w.WriteHeader(http.StatusMethodNotAllowed) return } next.ServeHTTP(w, r) }) } func routeMethodPatterns(surface []RouteSurface) []routeMethodPattern { byPath := make(map[string]int) patterns := make([]routeMethodPattern, 0) for _, route := range surface { index, exists := byPath[route.Path] if !exists { index = len(patterns) byPath[route.Path] = index patterns = append(patterns, routeMethodPattern{path: route.Path, methods: make(map[string]struct{})}) } patterns[index].methods[route.Method] = struct{}{} } for index := range patterns { if _, hasGet := patterns[index].methods[http.MethodGet]; hasGet { patterns[index].methods[http.MethodHead] = struct{}{} } patterns[index].methods[http.MethodOptions] = struct{}{} patterns[index].allow = make([]string, 0, len(patterns[index].methods)) for method := range patterns[index].methods { patterns[index].allow = append(patterns[index].allow, method) } sort.Strings(patterns[index].allow) } return patterns } func matchingRouteMethodPattern(patterns []routeMethodPattern, path string) *routeMethodPattern { for index := range patterns { if MatchSurfacePath(patterns[index].path, path) { return &patterns[index] } } return nil } type methodHeadResponseWriter struct { http.ResponseWriter wroteHeader bool } func (writer *methodHeadResponseWriter) WriteHeader(status int) { if writer.wroteHeader { return } writer.wroteHeader = true writer.ResponseWriter.WriteHeader(status) } func (writer *methodHeadResponseWriter) Write(body []byte) (int, error) { if !writer.wroteHeader { writer.WriteHeader(http.StatusOK) } return len(body), nil }