Skip to content

Commit

Permalink
Longest-Match Router (#730)
Browse files Browse the repository at this point in the history
* remove gorilla/mux embed
* add/use new longest-match router
  • Loading branch information
jranson authored Aug 15, 2024
1 parent 65513a4 commit 86df65a
Show file tree
Hide file tree
Showing 33 changed files with 810 additions and 3,710 deletions.
8 changes: 4 additions & 4 deletions pkg/backends/backends_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ import (

ho "github.com/trickstercache/trickster/v2/pkg/backends/healthcheck/options"
bo "github.com/trickstercache/trickster/v2/pkg/backends/options"
"github.com/trickstercache/trickster/v2/pkg/router"
"github.com/trickstercache/trickster/v2/pkg/router/lm"
)

func TestBackends(t *testing.T) {

cl, _ := New("test1", bo.New(), nil, router.NewRouter(), nil)
cl, _ := New("test1", bo.New(), nil, lm.NewRouter(), nil)
o := Backends{"test1": cl}

c := o.Get("test1")
Expand Down Expand Up @@ -77,11 +77,11 @@ func TestStartHealthChecks(t *testing.T) {
// 1: rule / Virtual provider
o1 := bo.New()
o1.Provider = "rule"
c1, _ := New("test1", o1, nil, router.NewRouter(), nil)
c1, _ := New("test1", o1, nil, lm.NewRouter(), nil)

// 2: non-virtual provider with no health check options
o2 := bo.New()
c2, _ := New("test2", o2, nil, router.NewRouter(), nil)
c2, _ := New("test2", o2, nil, lm.NewRouter(), nil)

b := Backends{"test1": c1}
_, err := b.StartHealthChecks(nil)
Expand Down
4 changes: 2 additions & 2 deletions pkg/backends/irondb/url.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (c *Client) FastForwardRequest(r *http.Request) (*http.Request, error) {

rsc := request.GetResources(r)
if rsc == nil || rsc.PathConfig == nil {
return nil, tkerr.ErrMissingPathconfig
return nil, tkerr.ErrMissingPathConfig
}

switch rsc.PathConfig.HandlerName {
Expand All @@ -68,7 +68,7 @@ func (c *Client) ParseTimeRangeQuery(

rsc := request.GetResources(r)
if rsc == nil || rsc.PathConfig == nil {
return nil, nil, false, tkerr.ErrMissingPathconfig
return nil, nil, false, tkerr.ErrMissingPathConfig
}

var trq *timeseries.TimeRangeQuery
Expand Down
2 changes: 1 addition & 1 deletion pkg/backends/reverseproxy/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (c *Client) DefaultPathConfigs(o *bo.Options) map[string]*po.Options {
"/-" + strings.Join(am, "-"): {
Path: "/",
HandlerName: "proxy",
Methods: methods.AllHTTPMethods(),
Methods: am,
MatchType: matching.PathMatchTypePrefix,
MatchTypeName: "prefix",
},
Expand Down
4 changes: 2 additions & 2 deletions pkg/backends/timeseries_backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ import (
"testing"

bo "github.com/trickstercache/trickster/v2/pkg/backends/options"
"github.com/trickstercache/trickster/v2/pkg/router"
"github.com/trickstercache/trickster/v2/pkg/router/lm"
)

func TestNewTimeseriesBackend(t *testing.T) {
tb, _ := NewTimeseriesBackend("test1", bo.New(), nil, router.NewRouter(), nil, nil)
tb, _ := NewTimeseriesBackend("test1", bo.New(), nil, lm.NewRouter(), nil, nil)
if tb.Name() != "test1" {
t.Error("expected test1 got", tb.Name())
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/config/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const (
// DefaultHealthHandlerPath defines the default path for the Health Handler
DefaultHealthHandlerPath = "/trickster/health"
// DefaultPurgeKeyHandlerPath defines the default path for the Cache Purge (by Key) Handler
DefaultPurgeKeyHandlerPath = "/trickster/purge/key/{backend}/{key}"
DefaultPurgeKeyHandlerPath = "/trickster/purge/key/"
// DefaultPurgePathHandlerPath defines the default path for the Cache Purge (by Path) Handler
// Requires ?backend={backend}&path={path}
DefaultPurgePathHandlerPath = "/trickster/purge/path"
Expand Down
5 changes: 0 additions & 5 deletions pkg/config/errors.go

This file was deleted.

3 changes: 2 additions & 1 deletion pkg/config/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (

bo "github.com/trickstercache/trickster/v2/pkg/backends/options"
"github.com/trickstercache/trickster/v2/pkg/cache/negative"
"github.com/trickstercache/trickster/v2/pkg/errors"
)

// Load returns the Application Configuration, starting with a default config,
Expand Down Expand Up @@ -71,7 +72,7 @@ func Load(applicationName string, applicationVersion string, arguments []string)
}

if len(c.Backends) == 0 {
return nil, flags, ErrNoValidBackends
return nil, flags, errors.ErrNoValidBackends
}

ncl, err := negative.ConfigLookup(c.NegativeCacheConfigs).Validate()
Expand Down
5 changes: 3 additions & 2 deletions pkg/config/loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"time"

"github.com/trickstercache/trickster/v2/pkg/cache/evictionmethods"
"github.com/trickstercache/trickster/v2/pkg/errors"
tlstest "github.com/trickstercache/trickster/v2/pkg/testutil/tls"
)

Expand Down Expand Up @@ -620,7 +621,7 @@ func TestLoadConfigurationWarning2(t *testing.T) {
func TestLoadEmptyArgs(t *testing.T) {
a := []string{}
_, _, err := Load("trickster-test", "0", a)
if err != ErrNoValidBackends {
t.Error("expected error:", ErrNoValidBackends)
if err != errors.ErrNoValidBackends {
t.Error("expected error:", errors.ErrNoValidBackends)
}
}
11 changes: 10 additions & 1 deletion pkg/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,13 @@ var ErrNilWriter = errors.New("nil writer")
var ErrInvalidOptions = errors.New("invalid options")

// ErrMissingPathconfig is an error for when a configuration is missing a path value
var ErrMissingPathconfig = errors.New("missing path config")
var ErrMissingPathConfig = errors.New("missing path config")

// ErrInvalidPath is an error for when a configuration's path is invalid
var ErrInvalidPath = errors.New("invalid path value in config")

// ErrInvalidMethod is an error for when a configuration's method is invalid
var ErrInvalidMethod = errors.New("invalid method value in config")

// ErrNoValidBackends is an error for when not valid backends have been configured
var ErrNoValidBackends = errors.New("no valid backends configured")
24 changes: 17 additions & 7 deletions pkg/httpserver/httpserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import (
"github.com/trickstercache/trickster/v2/pkg/observability/metrics"
tr "github.com/trickstercache/trickster/v2/pkg/observability/tracing/registration"
"github.com/trickstercache/trickster/v2/pkg/proxy/handlers"
"github.com/trickstercache/trickster/v2/pkg/router"
"github.com/trickstercache/trickster/v2/pkg/router/lm"
"github.com/trickstercache/trickster/v2/pkg/routing"
)

Expand Down Expand Up @@ -130,10 +130,14 @@ func applyConfig(conf, oldConf *config.Config, wg *sync.WaitGroup, logger *tl.Lo
}

// every config (re)load is a new router
r := router.NewRouter()
mr := http.NewServeMux()
r := lm.NewRouter()
mr := lm.NewRouter()
mr.SetMatchingScheme(0) // metrics router is exact-match only

r.RegisterRoute(conf.Main.PingHandlerPath, nil,
[]string{http.MethodGet, http.MethodHead}, false,
http.HandlerFunc((handlers.PingHandleFunc(conf))))

r.HandleFunc(conf.Main.PingHandlerPath, handlers.PingHandleFunc(conf)).Methods(http.MethodGet)
var caches = applyCachingConfig(conf, oldConf, logger, oldCaches)
rh := handlers.ReloadHandleFunc(Serve, conf, wg, logger, caches, args)

Expand All @@ -144,7 +148,12 @@ func applyConfig(conf, oldConf *config.Config, wg *sync.WaitGroup, logger *tl.Lo
return err
}

r.HandleFunc(conf.Main.PurgeKeyHandlerPath, handlers.PurgeKeyHandleFunc(conf, o)).Methods(http.MethodDelete)
if !strings.HasSuffix(conf.Main.PurgeKeyHandlerPath, "/") {
conf.Main.PurgeKeyHandlerPath += "/"
}
r.RegisterRoute(conf.Main.PurgeKeyHandlerPath, nil,
[]string{http.MethodDelete}, true,
http.HandlerFunc(handlers.PurgeKeyHandleFunc(conf, o)))

if hc != nil {
hc.Shutdown()
Expand Down Expand Up @@ -320,8 +329,9 @@ func validateConfig(conf *config.Config) error {
caches[k] = nil
}

r := router.NewRouter()
mr := http.NewServeMux()
r := lm.NewRouter()
mr := lm.NewRouter()
mr.SetMatchingScheme(0) // metrics router is exact-match only
logger := tl.ConsoleLogger(conf.Logging.LogLevel)

tracers, err := tr.RegisterAll(conf, logger, true)
Expand Down
37 changes: 25 additions & 12 deletions pkg/httpserver/listeners.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ import (
"github.com/trickstercache/trickster/v2/pkg/proxy/handlers"
"github.com/trickstercache/trickster/v2/pkg/proxy/listener"
ttls "github.com/trickstercache/trickster/v2/pkg/proxy/tls"
"github.com/trickstercache/trickster/v2/pkg/router"
"github.com/trickstercache/trickster/v2/pkg/router/lm"
"github.com/trickstercache/trickster/v2/pkg/routing"
)

var lg = listener.NewListenerGroup()

func applyListenerConfigs(conf, oldConf *config.Config,
router, reloadHandler http.Handler, metricsRouter *http.ServeMux,
router, reloadHandler http.Handler, metricsRouter router.Router,
log *tl.Logger, tracers tracing.Tracers, o backends.Backends,
wg *sync.WaitGroup, errorFunc func()) {

Expand Down Expand Up @@ -137,8 +139,10 @@ func applyListenerConfigs(conf, oldConf *config.Config,
(!hasOldMC || (conf.Metrics.ListenAddress != oldConf.Metrics.ListenAddress ||
conf.Metrics.ListenPort != oldConf.Metrics.ListenPort)) {
lg.DrainAndClose("metricsListener", 0)
metricsRouter.Handle("/metrics", metrics.Handler())
metricsRouter.HandleFunc(conf.Main.ConfigHandlerPath, handlers.ConfigHandleFunc(conf))
metricsRouter.RegisterRoute("/metrics", nil, nil,
false, metrics.Handler())
metricsRouter.RegisterRoute(conf.Main.ConfigHandlerPath, nil, nil,
false, http.HandlerFunc(handlers.ConfigHandleFunc(conf)))
if conf.Main.PprofServer == "both" || conf.Main.PprofServer == "metrics" {
routing.RegisterPprofRoutes("metrics", metricsRouter, log)
}
Expand All @@ -147,32 +151,41 @@ func applyListenerConfigs(conf, oldConf *config.Config,
conf.Metrics.ListenAddress, conf.Metrics.ListenPort,
conf.Frontend.ConnectionsLimit, nil, metricsRouter, wg, nil, errorFunc, 0, log)
} else {
metricsRouter.Handle("/metrics", metrics.Handler())
metricsRouter.HandleFunc(conf.Main.ConfigHandlerPath, handlers.ConfigHandleFunc(conf))
metricsRouter.RegisterRoute("/metrics", nil, nil,
false, metrics.Handler())
metricsRouter.RegisterRoute(conf.Main.ConfigHandlerPath, nil, nil,
false, http.HandlerFunc(handlers.ConfigHandleFunc(conf)))
lg.UpdateRouter("metricsListener", metricsRouter)
}

rr := http.NewServeMux() // serveMux router for the Reload port
rr := lm.NewRouter() // router for the Reload port
rr.SetMatchingScheme(0) // reload router is exact-match only

// if the Reload HTTP port is configured, then set up the http listener instance
if conf.ReloadConfig != nil && conf.ReloadConfig.ListenPort > 0 &&
(!hasOldRC || (conf.ReloadConfig.ListenAddress != oldConf.ReloadConfig.ListenAddress ||
conf.ReloadConfig.ListenPort != oldConf.ReloadConfig.ListenPort)) {
wg.Add(1)
lg.DrainAndClose("reloadListener", time.Millisecond*500)
rr.HandleFunc(conf.Main.ConfigHandlerPath, handlers.ConfigHandleFunc(conf))
rr.Handle(conf.ReloadConfig.HandlerPath, reloadHandler)
rr.HandleFunc(conf.Main.PurgePathHandlerPath, handlers.PurgePathHandlerFunc(conf, &o))
rr.RegisterRoute(conf.Main.ConfigHandlerPath, nil, nil,
false, http.HandlerFunc(handlers.ConfigHandleFunc(conf)))
rr.RegisterRoute(conf.ReloadConfig.HandlerPath, nil, nil,
false, reloadHandler)
rr.RegisterRoute(conf.Main.PurgePathHandlerPath, nil, nil,
false, http.HandlerFunc(handlers.PurgePathHandlerFunc(conf, &o)))
if conf.Main.PprofServer == "both" || conf.Main.PprofServer == "reload" {
routing.RegisterPprofRoutes("reload", rr, log)
}
go lg.StartListener("reloadListener",
conf.ReloadConfig.ListenAddress, conf.ReloadConfig.ListenPort,
conf.Frontend.ConnectionsLimit, nil, rr, wg, nil, errorFunc, 0, log)
} else {
rr.HandleFunc(conf.Main.ConfigHandlerPath, handlers.ConfigHandleFunc(conf))
rr.Handle(conf.ReloadConfig.HandlerPath, reloadHandler)
rr.HandleFunc(conf.Main.PurgePathHandlerPath, handlers.PurgePathHandlerFunc(conf, &o))
rr.RegisterRoute(conf.Main.ConfigHandlerPath, nil, nil,
false, http.HandlerFunc(handlers.ConfigHandleFunc(conf)))
rr.RegisterRoute(conf.ReloadConfig.HandlerPath, nil, nil,
false, reloadHandler)
rr.RegisterRoute(conf.Main.PurgePathHandlerPath, nil, nil,
false, http.HandlerFunc(handlers.PurgePathHandlerFunc(conf, &o)))
lg.UpdateRouter("reloadListener", rr)
}
}
6 changes: 3 additions & 3 deletions pkg/proxy/handlers/clickhouse/clickhouse.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
bo "github.com/trickstercache/trickster/v2/pkg/backends/options"
co "github.com/trickstercache/trickster/v2/pkg/cache/options"
"github.com/trickstercache/trickster/v2/pkg/cache/registration"
"github.com/trickstercache/trickster/v2/pkg/router"
"github.com/trickstercache/trickster/v2/pkg/router/lm"
"github.com/trickstercache/trickster/v2/pkg/routing"
)

Expand Down Expand Up @@ -57,8 +57,8 @@ func NewAcceleratorWithOptions(baseURL string, o *bo.Options, c *co.Options) (ht
o.Scheme = u.Scheme
o.Host = u.Host
o.PathPrefix = u.Path
r := router.NewRouter()
cl, err := clickhouse.NewClient("default", o, router.NewRouter(), cache, nil, nil)
r := lm.NewRouter()
cl, err := clickhouse.NewClient("default", o, lm.NewRouter(), cache, nil, nil)
if err != nil {
return nil, err
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/proxy/handlers/influxdb/influxdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
bo "github.com/trickstercache/trickster/v2/pkg/backends/options"
co "github.com/trickstercache/trickster/v2/pkg/cache/options"
"github.com/trickstercache/trickster/v2/pkg/cache/registration"
"github.com/trickstercache/trickster/v2/pkg/router"
"github.com/trickstercache/trickster/v2/pkg/router/lm"
"github.com/trickstercache/trickster/v2/pkg/routing"
)

Expand Down Expand Up @@ -57,8 +57,8 @@ func NewAcceleratorWithOptions(baseURL string, o *bo.Options, c *co.Options) (ht
o.Scheme = u.Scheme
o.Host = u.Host
o.PathPrefix = u.Path
r := router.NewRouter()
cl, err := influxdb.NewClient("default", o, router.NewRouter(), cache, nil, nil)
r := lm.NewRouter()
cl, err := influxdb.NewClient("default", o, lm.NewRouter(), cache, nil, nil)
if err != nil {
return nil, err
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/proxy/handlers/prometheus/prometheus.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
"github.com/trickstercache/trickster/v2/pkg/backends/prometheus"
co "github.com/trickstercache/trickster/v2/pkg/cache/options"
"github.com/trickstercache/trickster/v2/pkg/cache/registration"
"github.com/trickstercache/trickster/v2/pkg/router"
"github.com/trickstercache/trickster/v2/pkg/router/lm"
"github.com/trickstercache/trickster/v2/pkg/routing"
)

Expand Down Expand Up @@ -57,8 +57,8 @@ func NewAcceleratorWithOptions(baseURL string, o *bo.Options, c *co.Options) (ht
o.Scheme = u.Scheme
o.Host = u.Host
o.PathPrefix = u.Path
r := router.NewRouter()
cl, err := prometheus.NewClient("default", o, router.NewRouter(), cache, nil, nil)
r := lm.NewRouter()
cl, err := prometheus.NewClient("default", o, lm.NewRouter(), cache, nil, nil)
if err != nil {
return nil, err
}
Expand Down
12 changes: 9 additions & 3 deletions pkg/proxy/handlers/purge.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,27 @@ package handlers

import (
"net/http"
"strings"

"github.com/trickstercache/trickster/v2/pkg/backends"
"github.com/trickstercache/trickster/v2/pkg/checksum/md5"
"github.com/trickstercache/trickster/v2/pkg/config"
"github.com/trickstercache/trickster/v2/pkg/observability/logging"
"github.com/trickstercache/trickster/v2/pkg/proxy/headers"
"github.com/trickstercache/trickster/v2/pkg/proxy/request"
"github.com/trickstercache/trickster/v2/pkg/router"
)

// PurgeHandleFunc purges an object from a cache based on key.
func PurgeKeyHandleFunc(conf *config.Config, from backends.Backends) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, req *http.Request) {
params := router.Vars(req)
purgeFrom, purgeKey := params["backend"], params["key"]
vals := strings.Replace(req.URL.Path, conf.Main.PurgeKeyHandlerPath, "", 1)
parts := strings.Split(vals, "/")
if len(parts) != 2 {
http.NotFound(w, req)
return
}
purgeFrom := parts[0]
purgeKey := parts[1]
fromBackend := from.Get(purgeFrom)
if fromBackend == nil {
w.Header().Set(headers.NameContentType, headers.ValueTextPlain)
Expand Down
4 changes: 2 additions & 2 deletions pkg/proxy/handlers/rpc/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
rpc "github.com/trickstercache/trickster/v2/pkg/backends/reverseproxycache"
co "github.com/trickstercache/trickster/v2/pkg/cache/options"
"github.com/trickstercache/trickster/v2/pkg/cache/registration"
"github.com/trickstercache/trickster/v2/pkg/router"
"github.com/trickstercache/trickster/v2/pkg/router/lm"
"github.com/trickstercache/trickster/v2/pkg/routing"
)

Expand Down Expand Up @@ -57,7 +57,7 @@ func NewWithOptions(baseURL string, o *bo.Options, c *co.Options) (http.Handler,
o.Scheme = u.Scheme
o.Host = u.Host
o.PathPrefix = u.Path
r := router.NewRouter()
r := lm.NewRouter()
cl, err := rpc.NewClient("default", o, r, cache, nil, nil)
if err != nil {
return nil, err
Expand Down
2 changes: 2 additions & 0 deletions pkg/proxy/headers/headers.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ const (
ValueApplicationFlux = "application/vnd.flux"
// ValueChunked represents the HTTP Header Value of "chunked"
ValueChunked = "chunked"
// ValueClose represents the HTTP Header Value of "close"
ValueClose = "close"
// ValueMaxAge represents the HTTP Header Value of "max-age"
ValueMaxAge = "max-age"
// ValueMultipartFormData represents the HTTP Header Value of "multipart/form-data"
Expand Down
Loading

0 comments on commit 86df65a

Please sign in to comment.