bouncer/internal/proxy/director/layer/rewriter/layer.go
William Petit d4c28b80d7
Some checks are pending
Cadoles/bouncer/pipeline/pr-develop Build started...
feat: global error handler with template rendering
2024-09-27 15:02:49 +02:00

119 lines
3.1 KiB
Go

package rewriter
import (
"net/http"
proxy "forge.cadoles.com/Cadoles/go-proxy"
"forge.cadoles.com/Cadoles/go-proxy/wildcard"
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director/layer/util"
"forge.cadoles.com/cadoles/bouncer/internal/rule"
ruleHTTP "forge.cadoles.com/cadoles/bouncer/internal/rule/http"
"forge.cadoles.com/cadoles/bouncer/internal/store"
"github.com/pkg/errors"
)
const LayerType store.LayerType = "rewriter"
type Layer struct {
requestRuleEngine *util.RevisionedRuleEngine[*RequestVars, *LayerOptions]
responseRuleEngine *util.RevisionedRuleEngine[*ResponseVars, *LayerOptions]
}
func (l *Layer) LayerType() store.LayerType {
return LayerType
}
func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware {
return func(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
options, err := fromStoreOptions(layer.Options)
if err != nil {
director.HandleError(ctx, w, r, http.StatusInternalServerError, errors.New("could not parse layer options"))
return
}
matches := wildcard.MatchAny(r.URL.String(), options.MatchURLs...)
if !matches {
next.ServeHTTP(w, r)
return
}
if err := l.applyRequestRules(ctx, r, layer.Revision, options); err != nil {
var redirect *errRedirect
if errors.As(err, &redirect) {
http.Redirect(w, r, redirect.URL(), redirect.StatusCode())
return
}
director.HandleError(ctx, w, r, http.StatusInternalServerError, errors.Wrap(err, "could not apply request rules"))
return
}
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
}
// ResponseTransformer implements director.ResponseTransformerLayer.
func (l *Layer) ResponseTransformer(layer *store.Layer) proxy.ResponseTransformer {
return func(r *http.Response) error {
options, err := fromStoreOptions(layer.Options)
if err != nil {
return errors.WithStack(err)
}
matches := wildcard.MatchAny(r.Request.URL.String(), options.MatchURLs...)
if !matches {
return nil
}
ctx := r.Request.Context()
if err := l.applyResponseRules(ctx, r, layer.Revision, options); err != nil {
return errors.WithStack(err)
}
return nil
}
}
func New(funcs ...OptionFunc) *Layer {
return &Layer{
requestRuleEngine: util.NewRevisionedRuleEngine(func(options *LayerOptions) (*rule.Engine[*RequestVars], error) {
engine, err := rule.NewEngine[*RequestVars](
rule.WithRules(options.Rules.Request...),
ruleHTTP.WithRequestFuncs(),
WithRewriterFuncs(),
)
if err != nil {
return nil, errors.WithStack(err)
}
return engine, nil
}),
responseRuleEngine: util.NewRevisionedRuleEngine(func(options *LayerOptions) (*rule.Engine[*ResponseVars], error) {
engine, err := rule.NewEngine[*ResponseVars](
rule.WithRules(options.Rules.Response...),
ruleHTTP.WithResponseFuncs(),
)
if err != nil {
return nil, errors.WithStack(err)
}
return engine, nil
}),
}
}
var (
_ director.MiddlewareLayer = &Layer{}
_ director.ResponseTransformerLayer = &Layer{}
)