92 lines
3.1 KiB
Go
92 lines
3.1 KiB
Go
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})
|
|
}
|