feat: rewriter layer
This commit is contained in:
45
internal/rule/engine.go
Normal file
45
internal/rule/engine.go
Normal file
@ -0,0 +1,45 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"github.com/expr-lang/expr"
|
||||
"github.com/expr-lang/expr/vm"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Engine[E any] struct {
|
||||
rules []*vm.Program
|
||||
}
|
||||
|
||||
func (e *Engine[E]) Apply(env E) ([]any, error) {
|
||||
results := make([]any, 0, len(e.rules))
|
||||
for i, r := range e.rules {
|
||||
result, err := expr.Run(r, env)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not run rule #%d", i)
|
||||
}
|
||||
|
||||
results = append(results, result)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func NewEngine[E any](funcs ...OptionFunc) (*Engine[E], error) {
|
||||
opts := NewOptions(funcs...)
|
||||
|
||||
engine := &Engine[E]{
|
||||
rules: make([]*vm.Program, 0, len(opts.Rules)),
|
||||
}
|
||||
|
||||
for i, r := range opts.Rules {
|
||||
|
||||
program, err := expr.Compile(r, opts.Expr...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not compile rule #%d", i)
|
||||
}
|
||||
|
||||
engine.rules = append(engine.rules, program)
|
||||
}
|
||||
|
||||
return engine, nil
|
||||
}
|
42
internal/rule/http/option.go
Normal file
42
internal/rule/http/option.go
Normal file
@ -0,0 +1,42 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/rule"
|
||||
"github.com/expr-lang/expr"
|
||||
)
|
||||
|
||||
func WithRequestFuncs(r *http.Request) rule.OptionFunc {
|
||||
return func(opts *rule.Options) {
|
||||
funcs := []expr.Option{
|
||||
setRequestURL(r),
|
||||
setRequestHeaderFunc(r),
|
||||
addRequestHeaderFunc(r),
|
||||
delRequestHeadersFunc(r),
|
||||
setRequestHostFunc(r),
|
||||
}
|
||||
|
||||
if len(opts.Expr) == 0 {
|
||||
opts.Expr = make([]expr.Option, 0)
|
||||
}
|
||||
|
||||
opts.Expr = append(opts.Expr, funcs...)
|
||||
}
|
||||
}
|
||||
|
||||
func WithResponseFuncs(r *http.Response) rule.OptionFunc {
|
||||
return func(opts *rule.Options) {
|
||||
funcs := []expr.Option{
|
||||
setResponseHeaderFunc(r),
|
||||
addResponseHeaderFunc(r),
|
||||
delResponseHeadersFunc(r),
|
||||
}
|
||||
|
||||
if len(opts.Expr) == 0 {
|
||||
opts.Expr = make([]expr.Option, 0)
|
||||
}
|
||||
|
||||
opts.Expr = append(opts.Expr, funcs...)
|
||||
}
|
||||
}
|
122
internal/rule/http/request.go
Normal file
122
internal/rule/http/request.go
Normal file
@ -0,0 +1,122 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/Cadoles/go-proxy/wildcard"
|
||||
"github.com/expr-lang/expr"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func setRequestHostFunc(r *http.Request) expr.Option {
|
||||
return expr.Function(
|
||||
"set_host",
|
||||
func(params ...any) (any, error) {
|
||||
host := params[0].(string)
|
||||
r.Host = host
|
||||
|
||||
return true, nil
|
||||
},
|
||||
new(func(string) bool),
|
||||
)
|
||||
}
|
||||
|
||||
func setRequestURL(r *http.Request) expr.Option {
|
||||
return expr.Function(
|
||||
"set_url",
|
||||
func(params ...any) (any, error) {
|
||||
rawURL := params[0].(string)
|
||||
|
||||
url, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
r.URL = url
|
||||
|
||||
return true, nil
|
||||
},
|
||||
new(func(string) bool),
|
||||
)
|
||||
}
|
||||
|
||||
func addRequestHeaderFunc(r *http.Request) expr.Option {
|
||||
return expr.Function(
|
||||
"add_header",
|
||||
func(params ...any) (any, error) {
|
||||
name := params[0].(string)
|
||||
rawValue := params[1]
|
||||
|
||||
var value string
|
||||
switch v := rawValue.(type) {
|
||||
case []string:
|
||||
value = strings.Join(v, ",")
|
||||
case time.Time:
|
||||
value = strconv.FormatInt(v.UTC().Unix(), 10)
|
||||
case time.Duration:
|
||||
value = strconv.FormatInt(int64(v.Seconds()), 10)
|
||||
default:
|
||||
value = fmt.Sprintf("%v", rawValue)
|
||||
}
|
||||
|
||||
r.Header.Add(name, value)
|
||||
|
||||
return true, nil
|
||||
},
|
||||
new(func(string, string) bool),
|
||||
)
|
||||
}
|
||||
|
||||
func setRequestHeaderFunc(r *http.Request) expr.Option {
|
||||
return expr.Function(
|
||||
"set_header",
|
||||
func(params ...any) (any, error) {
|
||||
name := params[0].(string)
|
||||
rawValue := params[1]
|
||||
|
||||
var value string
|
||||
switch v := rawValue.(type) {
|
||||
case []string:
|
||||
value = strings.Join(v, ",")
|
||||
case time.Time:
|
||||
value = strconv.FormatInt(v.UTC().Unix(), 10)
|
||||
case time.Duration:
|
||||
value = strconv.FormatInt(int64(v.Seconds()), 10)
|
||||
default:
|
||||
value = fmt.Sprintf("%v", rawValue)
|
||||
}
|
||||
|
||||
r.Header.Set(name, value)
|
||||
|
||||
return true, nil
|
||||
},
|
||||
new(func(string, string) bool),
|
||||
)
|
||||
}
|
||||
|
||||
func delRequestHeadersFunc(r *http.Request) expr.Option {
|
||||
return expr.Function(
|
||||
"del_headers",
|
||||
func(params ...any) (any, error) {
|
||||
pattern := params[0].(string)
|
||||
deleted := false
|
||||
|
||||
for key := range r.Header {
|
||||
if !wildcard.Match(key, pattern) {
|
||||
continue
|
||||
}
|
||||
|
||||
r.Header.Del(key)
|
||||
deleted = true
|
||||
}
|
||||
|
||||
return deleted, nil
|
||||
},
|
||||
new(func(string) bool),
|
||||
)
|
||||
}
|
88
internal/rule/http/response.go
Normal file
88
internal/rule/http/response.go
Normal file
@ -0,0 +1,88 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/Cadoles/go-proxy/wildcard"
|
||||
"github.com/expr-lang/expr"
|
||||
)
|
||||
|
||||
func addResponseHeaderFunc(r *http.Response) expr.Option {
|
||||
return expr.Function(
|
||||
"add_header",
|
||||
func(params ...any) (any, error) {
|
||||
name := params[0].(string)
|
||||
rawValue := params[1]
|
||||
|
||||
var value string
|
||||
switch v := rawValue.(type) {
|
||||
case []string:
|
||||
value = strings.Join(v, ",")
|
||||
case time.Time:
|
||||
value = strconv.FormatInt(v.UTC().Unix(), 10)
|
||||
case time.Duration:
|
||||
value = strconv.FormatInt(int64(v.Seconds()), 10)
|
||||
default:
|
||||
value = fmt.Sprintf("%v", rawValue)
|
||||
}
|
||||
|
||||
r.Header.Add(name, value)
|
||||
|
||||
return true, nil
|
||||
},
|
||||
new(func(string, string) bool),
|
||||
)
|
||||
}
|
||||
|
||||
func setResponseHeaderFunc(r *http.Response) expr.Option {
|
||||
return expr.Function(
|
||||
"set_header",
|
||||
func(params ...any) (any, error) {
|
||||
name := params[0].(string)
|
||||
rawValue := params[1]
|
||||
|
||||
var value string
|
||||
switch v := rawValue.(type) {
|
||||
case []string:
|
||||
value = strings.Join(v, ",")
|
||||
case time.Time:
|
||||
value = strconv.FormatInt(v.UTC().Unix(), 10)
|
||||
case time.Duration:
|
||||
value = strconv.FormatInt(int64(v.Seconds()), 10)
|
||||
default:
|
||||
value = fmt.Sprintf("%v", rawValue)
|
||||
}
|
||||
|
||||
r.Header.Set(name, value)
|
||||
|
||||
return true, nil
|
||||
},
|
||||
new(func(string, string) bool),
|
||||
)
|
||||
}
|
||||
|
||||
func delResponseHeadersFunc(r *http.Response) expr.Option {
|
||||
return expr.Function(
|
||||
"del_headers",
|
||||
func(params ...any) (any, error) {
|
||||
pattern := params[0].(string)
|
||||
deleted := false
|
||||
|
||||
for key := range r.Header {
|
||||
if !wildcard.Match(key, pattern) {
|
||||
continue
|
||||
}
|
||||
|
||||
r.Header.Del(key)
|
||||
deleted = true
|
||||
}
|
||||
|
||||
return deleted, nil
|
||||
},
|
||||
new(func(string) bool),
|
||||
)
|
||||
}
|
35
internal/rule/options.go
Normal file
35
internal/rule/options.go
Normal file
@ -0,0 +1,35 @@
|
||||
package rule
|
||||
|
||||
import "github.com/expr-lang/expr"
|
||||
|
||||
type Options struct {
|
||||
Rules []string
|
||||
Expr []expr.Option
|
||||
}
|
||||
|
||||
type OptionFunc func(opts *Options)
|
||||
|
||||
func NewOptions(funcs ...OptionFunc) *Options {
|
||||
opts := &Options{
|
||||
Expr: make([]expr.Option, 0),
|
||||
Rules: make([]string, 0),
|
||||
}
|
||||
|
||||
for _, fn := range funcs {
|
||||
fn(opts)
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
func WithRules(rules ...string) OptionFunc {
|
||||
return func(opts *Options) {
|
||||
opts.Rules = rules
|
||||
}
|
||||
}
|
||||
|
||||
func WithExpr(options ...expr.Option) OptionFunc {
|
||||
return func(opts *Options) {
|
||||
opts.Expr = options
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user