fixed route not being updated on restart, added experimental middleware compose support

This commit is contained in:
yusing 2024-09-30 19:00:27 +08:00
parent 478311fe9e
commit e951194bee
5 changed files with 184 additions and 34 deletions

View file

@ -0,0 +1,104 @@
package middleware
import (
"net/http"
"os"
E "github.com/yusing/go-proxy/internal/error"
"gopkg.in/yaml.v3"
)
func BuildMiddlewaresFromYAML(filePath string) (middlewares map[string]*Middleware, outErr E.NestedError) {
b := E.NewBuilder("middlewares compile errors")
defer b.To(&outErr)
var data map[string][]map[string]any
fileContent, err := os.ReadFile(filePath)
if err != nil {
b.Add(E.FailWith("read file", err))
return
}
err = yaml.Unmarshal(fileContent, &data)
if err != nil {
b.Add(E.FailWith("toml unmarshal", err))
return
}
middlewares = make(map[string]*Middleware)
for name, defs := range data {
chainErr := E.NewBuilder("errors in middleware chain %s", name)
chain := make([]*Middleware, 0, len(defs))
for i, def := range defs {
if def["use"] == nil || def["use"].(string) == "" {
chainErr.Add(E.Missing("use").Subjectf("%s.%d", name, i))
continue
}
baseName := def["use"].(string)
base, ok := Get(baseName)
if !ok {
chainErr.Add(E.NotExist("middleware", baseName).Subjectf("%s.%d", name, i))
continue
}
delete(def, "use")
m, err := base.withOptions(def)
if err != nil {
chainErr.Add(err.Subjectf("%s.%d", name, i))
continue
}
chain = append(chain, m)
}
if chainErr.HasError() {
b.Add(chainErr.Build())
} else {
name = name + "@file"
middlewares[name] = BuildMiddlewareFromChain(name, chain)
}
}
return
}
// TODO: check conflict or duplicates
func BuildMiddlewareFromChain(name string, chain []*Middleware) *Middleware {
var (
befores []BeforeFunc
rewrites []RewriteFunc
modResps []ModifyResponseFunc
)
for _, m := range chain {
if m.before != nil {
befores = append(befores, m.before)
}
if m.rewrite != nil {
rewrites = append(rewrites, m.rewrite)
}
if m.modifyResponse != nil {
modResps = append(modResps, m.modifyResponse)
}
}
m := &Middleware{name: name}
if len(befores) > 0 {
m.before = func(next http.Handler, w ResponseWriter, r *Request) {
for _, before := range befores {
before(next, w, r)
}
}
}
if len(rewrites) > 0 {
m.rewrite = func(r *Request) {
for _, rewrite := range rewrites {
rewrite(r)
}
}
}
if len(modResps) > 0 {
m.modifyResponse = func(res *Response) error {
b := E.NewBuilder("errors in middleware %s", name)
for _, mr := range modResps {
b.AddE(mr(res))
}
return b.Build().Error()
}
}
return m
}

View file

@ -0,0 +1,9 @@
package middleware
import (
"testing"
)
func TestBuild(t *testing.T) {
}

View file

@ -2,10 +2,14 @@ package middleware
import ( import (
"fmt" "fmt"
"path"
"strings" "strings"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/yusing/go-proxy/internal/common"
D "github.com/yusing/go-proxy/internal/docker" D "github.com/yusing/go-proxy/internal/docker"
E "github.com/yusing/go-proxy/internal/error"
U "github.com/yusing/go-proxy/internal/utils"
) )
var middlewares map[string]*Middleware var middlewares map[string]*Middleware
@ -46,27 +50,27 @@ func init() {
} }
} }
// TODO: seperate from init() // TODO: seperate from init()
// b := E.NewBuilder("failed to load middlewares") b := E.NewBuilder("failed to load middlewares")
// middlewareDefs, err := U.ListFiles(common.MiddlewareDefsBasePath, 0) middlewareDefs, err := U.ListFiles(common.MiddlewareDefsBasePath, 0)
// if err != nil { if err != nil {
// logrus.Errorf("failed to list middleware definitions: %s", err) logrus.Errorf("failed to list middleware definitions: %s", err)
// return return
// } }
// for _, defFile := range middlewareDefs { for _, defFile := range middlewareDefs {
// mws, err := BuildMiddlewaresFromYAML(defFile) mws, err := BuildMiddlewaresFromYAML(defFile)
// for name, m := range mws { for name, m := range mws {
// if _, ok := middlewares[name]; ok { if _, ok := middlewares[name]; ok {
// b.Add(E.Duplicated("middleware", name)) b.Add(E.Duplicated("middleware", name))
// continue continue
// } }
// middlewares[name] = m middlewares[name] = m
// logger.Infof("middleware %s loaded from %s", name, path.Base(defFile)) logger.Infof("middleware %s loaded from %s", name, path.Base(defFile))
// } }
// b.Add(err.Subject(defFile)) b.Add(err.Subject(defFile))
// } }
// if b.HasError() { if b.HasError() {
// logger.Error(b.Build()) logger.Error(b.Build())
// } }
} }
var logger = logrus.WithField("module", "middlewares") var logger = logrus.WithField("module", "middlewares")

View file

@ -12,6 +12,7 @@ import (
M "github.com/yusing/go-proxy/internal/models" M "github.com/yusing/go-proxy/internal/models"
R "github.com/yusing/go-proxy/internal/route" R "github.com/yusing/go-proxy/internal/route"
W "github.com/yusing/go-proxy/internal/watcher" W "github.com/yusing/go-proxy/internal/watcher"
"github.com/yusing/go-proxy/internal/watcher/events"
) )
type DockerProvider struct { type DockerProvider struct {
@ -80,10 +81,17 @@ func (p *DockerProvider) LoadRoutesImpl() (routes R.Routes, err E.NestedError) {
func (p *DockerProvider) shouldIgnore(container D.Container) bool { func (p *DockerProvider) shouldIgnore(container D.Container) bool {
return container.IsExcluded || return container.IsExcluded ||
!container.IsExplicit && p.ExplicitOnly !container.IsExplicit && p.ExplicitOnly ||
strings.HasSuffix(container.ContainerName, "-old")
} }
func (p *DockerProvider) OnEvent(event W.Event, routes R.Routes) (res EventResult) { func (p *DockerProvider) OnEvent(event W.Event, routes R.Routes) (res EventResult) {
switch event.Action {
case events.ActionContainerStart, events.ActionContainerDie:
break
default:
return
}
b := E.NewBuilder("event %s error", event) b := E.NewBuilder("event %s error", event)
defer b.To(&res.err) defer b.To(&res.err)

View file

@ -110,24 +110,42 @@ func Deserialize(src SerializedObject, target any) E.NestedError {
if src == nil || target == nil { if src == nil || target == nil {
return nil return nil
} }
tValue := reflect.ValueOf(target)
mapping := make(map[string]string)
if tValue.Kind() == reflect.Ptr {
tValue = tValue.Elem()
}
// convert data fields to lower no-snake // convert data fields to lower no-snake
// convert target fields to lower no-snake // convert target fields to lower no-snake
// then check if the field of data is in the target // then check if the field of data is in the target
mapping := make(map[string]string)
if tValue.Kind() == reflect.Struct {
t := reflect.TypeOf(target).Elem() t := reflect.TypeOf(target).Elem()
for i := 0; i < t.NumField(); i++ { for i := 0; i < t.NumField(); i++ {
field := t.Field(i) field := t.Field(i)
snakeCaseField := ToLowerNoSnake(field.Name) snakeCaseField := ToLowerNoSnake(field.Name)
mapping[snakeCaseField] = field.Name mapping[snakeCaseField] = field.Name
} }
tValue := reflect.ValueOf(target) } else if tValue.Kind() == reflect.Map && tValue.Type().Key().Kind() == reflect.String {
if tValue.IsZero() { if tValue.IsNil() {
return E.Invalid("value", "nil") tValue.Set(reflect.MakeMap(tValue.Type()))
} }
for k := range src {
// TODO: type check
tValue.SetMapIndex(reflect.ValueOf(ToLowerNoSnake(k)), reflect.ValueOf(src[k]))
}
return nil
} else {
return E.Unsupported("target type", fmt.Sprintf("%T", target))
}
for k, v := range src { for k, v := range src {
kCleaned := ToLowerNoSnake(k) kCleaned := ToLowerNoSnake(k)
if fieldName, ok := mapping[kCleaned]; ok { if fieldName, ok := mapping[kCleaned]; ok {
prop := reflect.ValueOf(target).Elem().FieldByName(fieldName) prop := tValue.FieldByName(fieldName)
propType := prop.Type() propType := prop.Type()
isPtr := prop.Kind() == reflect.Ptr isPtr := prop.Kind() == reflect.Ptr
if prop.CanSet() { if prop.CanSet() {
@ -157,8 +175,15 @@ func Deserialize(src SerializedObject, target any) E.NestedError {
} }
prop.Set(propNew) prop.Set(propNew)
default: default:
obj, ok := val.Interface().(SerializedObject)
if !ok {
return E.Invalid("conversion", k).Extraf("from %s to %s", vType, propType) return E.Invalid("conversion", k).Extraf("from %s to %s", vType, propType)
} }
err := Deserialize(obj, prop.Addr().Interface())
if err.HasError() {
return E.Failure("set field").With(err).Subject(k)
}
}
} else { } else {
return E.Unsupported("field", k).Extraf("type %s is not settable", propType) return E.Unsupported("field", k).Extraf("type %s is not settable", propType)
} }