feat(06-03): add path-scoped CORS and per-route body limits
- CORS matches Laravel path globs (api/* includes nested segments); unlisted paths get no headers - Non-raw routes wrap http.MaxBytesReader from http.body_limits.default_bytes; body.limit:N overrides innermost - Raw routes stay uncapped at this layer
This commit is contained in:
@@ -41,16 +41,19 @@ type route struct {
|
||||
|
||||
// Router compiles group declarations onto net/http ServeMux.
|
||||
type Router struct {
|
||||
pluginID string
|
||||
prefix string
|
||||
middleware []string
|
||||
named map[string]namedMiddleware
|
||||
factories map[string]namedMiddlewareFactory
|
||||
routes []route
|
||||
seen map[string]string
|
||||
origins []string
|
||||
compileErr error
|
||||
limiter *FixedWindowLimiter
|
||||
pluginID string
|
||||
prefix string
|
||||
middleware []string
|
||||
named map[string]namedMiddleware
|
||||
factories map[string]namedMiddlewareFactory
|
||||
routes []route
|
||||
seen map[string]string
|
||||
origins []string
|
||||
compileErr error
|
||||
limiter *FixedWindowLimiter
|
||||
corsCfg CORSConfig
|
||||
defaultBytes int64
|
||||
uploadBytes int64
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -342,11 +345,18 @@ func (r *Router) compile() (http.Handler, error) {
|
||||
}
|
||||
mux.Handle(rt.method+" "+rt.path, h)
|
||||
}
|
||||
return cors(r.origins, mux), nil
|
||||
return pathScopedCORS(r.corsCfg, mux), nil
|
||||
}
|
||||
|
||||
func (r *Router) wrap(rt route) (http.Handler, error) {
|
||||
h := constrain(rt.handler, rt.constraints)
|
||||
limit, err := routeBodyLimit(rt, r.defaultBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("surf: plugin %q: %w", rt.pluginID, err)
|
||||
}
|
||||
if limit > 0 {
|
||||
h = bodyLimit(limit)(h)
|
||||
}
|
||||
h = orgSlot(h)
|
||||
for i := len(rt.middleware) - 1; i >= 0; i-- {
|
||||
name := rt.middleware[i]
|
||||
@@ -368,6 +378,11 @@ func (r *Router) wrap(rt route) (http.Handler, error) {
|
||||
return nil, fmt.Errorf("surf: plugin %q: %w", rt.pluginID, err)
|
||||
}
|
||||
}
|
||||
if base == "body.limit" {
|
||||
if _, err := parseBodyLimit(param); err != nil {
|
||||
return nil, fmt.Errorf("surf: plugin %q: %w", rt.pluginID, err)
|
||||
}
|
||||
}
|
||||
h = factory.fn(param)(h)
|
||||
continue
|
||||
}
|
||||
@@ -408,6 +423,23 @@ func BuildRouter(app *backpack.App, plugins []party.Plugin) (*Router, error) {
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := r.RegisterMiddlewareFactory("surf", "body.limit", func(param string) pact.Middleware {
|
||||
// Limit is applied innermost in wrap(); the factory only occupies the name.
|
||||
return func(next http.Handler) http.Handler { return next }
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if app != nil {
|
||||
corsCfg, err := LoadCORSConfig(app.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.corsCfg = corsCfg
|
||||
if app.Config != nil {
|
||||
r.defaultBytes = int64(app.Config.Int("http.body_limits.default_bytes"))
|
||||
r.uploadBytes = int64(app.Config.Int("http.body_limits.upload_bytes"))
|
||||
}
|
||||
}
|
||||
for _, p := range plugins {
|
||||
if hm, ok := p.(pact.HasMiddleware); ok {
|
||||
for name, fn := range hm.Middlewares() {
|
||||
@@ -509,27 +541,6 @@ func recoverBare(next http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
func cors(origins []string, next http.Handler) http.Handler {
|
||||
allowed := make(map[string]struct{}, len(origins))
|
||||
for _, o := range origins {
|
||||
allowed[o] = struct{}{}
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
origin := r.Header.Get("Origin")
|
||||
if _, ok := allowed[origin]; ok && origin != "" {
|
||||
w.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
w.Header().Set("Vary", "Origin")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
|
||||
}
|
||||
if r.Method == http.MethodOptions {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func locale(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
next.ServeHTTP(w, r.WithContext(towel.WithLocale(r.Context(), r.Header.Get("Accept-Language"))))
|
||||
|
||||
Reference in New Issue
Block a user