package app import ( "fmt" "net/http" "strings" "git.yuntuo.hk/k2safe/go-omni/config" "git.yuntuo.hk/k2safe/go-omni/httpx" "git.yuntuo.hk/k2safe/go-omni/internal/httplimits" "git.yuntuo.hk/k2safe/go-omni/response" ) type routeBodyLimit struct { method, path string maxBytes int64 } func (a *App) hardBodyLimit() int64 { if a.config.HTTP.MaxBodyBytes > 0 { return a.config.HTTP.MaxBodyBytes } return httplimits.MaxBodyBytes } func (a *App) defaultBodyLimit() int64 { limit := a.config.HTTP.DefaultMaxBodyBytes if limit <= 0 { limit = httplimits.DefaultBodyBytes } if limit > a.hardBodyLimit() { limit = a.hardBodyLimit() } // Invalid configuration is rejected at startup or the first external request. if limit > httplimits.MaxBodyBytes { limit = httplimits.MaxBodyBytes } return limit } // ValidateHTTP checks HTTP settings and registered route body limits without // opening a listener. Call after registration when hosting App/Engine externally. // Built-in serving entry points and RunWith perform this check automatically. func (a *App) ValidateHTTP() error { if err := a.config.HTTP.Validate(); err != nil { return err } for _, route := range a.bodyLimits { if route.maxBytes > a.hardBodyLimit() { return &config.ConfigError{Field: "http.maxBodyBytes", Message: fmt.Sprintf("route %s %s maxBytes=%d exceeds service hard limit=%d; raise http.maxBodyBytes or lower the route limit", route.method, route.path, route.maxBytes, a.hardBodyLimit())} } } return nil } func joinRoutePath(prefix, path string) string { if path == "" { if prefix == "" { return "/" } return prefix } return strings.TrimSuffix(prefix, "/") + "/" + strings.TrimPrefix(path, "/") } func (a *App) bodyHandler(maxBytes int64, encoder response.Encoder, handler httpx.Handler, middleware ...httpx.Middleware) http.Handler { checked := httpx.LimitBody(maxBytes)(handler) if encoder == nil { encoder = a.responseEncoder } next := a.adaptResponse(encoder, checked, middleware...) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Body != nil { r = r.WithContext(r.Context()) r.Body = http.MaxBytesReader(w, r.Body, maxBytes) } next.ServeHTTP(w, r) }) } // HandleWithBodyLimit caps the reader before application middleware and preserves // the response policy, including auth failures and parameter errors. func (a *App) HandleWithBodyLimit(method, path string, maxBytes int64, encoder response.Encoder, handler httpx.Handler, middleware ...httpx.Middleware) { a.router.Handle(method, path, a.bodyHandler(maxBytes, encoder, handler, middleware...)) a.bodyLimits = append(a.bodyLimits, routeBodyLimit{method, path, maxBytes}) } func (g *RouterGroup) HandleWithBodyLimit(method, path string, maxBytes int64, encoder response.Encoder, handler httpx.Handler, middleware ...httpx.Middleware) { combined := append(append([]httpx.Middleware{}, g.middlewares...), middleware...) g.group.Handle(method, path, g.app.bodyHandler(maxBytes, encoder, handler, combined...)) g.app.bodyLimits = append(g.app.bodyLimits, routeBodyLimit{method, joinRoutePath(g.prefix, path), maxBytes}) }