From bcccd4c05e788601a63195a6cc5458fef1d4cd3c Mon Sep 17 00:00:00 2001 From: Felipe Cardozo Date: Sat, 16 Dec 2023 15:54:44 -0300 Subject: [PATCH] add retry node --- README.md | 1 + main.go | 1 + middlewares/retry.go | 25 +++++++++++++++++ utils/balancer.go | 21 ++++++++++---- utils/config.go | 66 +++++++++++++++++++++++++------------------- 5 files changed, 80 insertions(+), 34 deletions(-) create mode 100644 middlewares/retry.go diff --git a/README.md b/README.md index e99b781..819de49 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,7 @@ You can also configure or overwrite TzProxy with environment variables, using th - `TZPROXY_HOST` is the host of the proxy. - `TZPROXY_TEZOS_HOST` are the hosts of the tezos nodes. +- `TZPROXY_TEZOS_HOST_RETRY` is the host used when finding a 404 or 410. It's recommended use full or archive nodes. - `TZPROXY_REDIS_HOST` is the host of the redis. - `TZPROXY_REDIS_ENABLE` is a flag to enable redis. - `TZPROXY_LOAD_BALANCER_TTL` is the time to live to keep using the same node by user IP. diff --git a/main.go b/main.go index 914d4dc..bb4792b 100644 --- a/main.go +++ b/main.go @@ -33,6 +33,7 @@ func main() { e.Use(middlewares.DenyRoutes(config)) e.Use(middlewares.Cache(config)) e.Use(middlewares.Gzip(config)) + e.Use(middlewares.Retry(config)) e.Use(middleware.ProxyWithConfig(*config.ProxyConfig)) // Start metrics server diff --git a/middlewares/retry.go b/middlewares/retry.go new file mode 100644 index 0000000..ade6336 --- /dev/null +++ b/middlewares/retry.go @@ -0,0 +1,25 @@ +package middlewares + +import ( + "github.com/labstack/echo/v4" + "github.com/marigold-dev/tzproxy/utils" +) + +func Retry(config *utils.Config) echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) (err error) { + if config.ConfigFile.TezosHostRetry == "" { + return next(c) + } + err = next(c) + + status := c.Response().Status + if status == 404 || status == 410 { + c.Set("retry", true) + return next(c) + } + + return err + } + } +} diff --git a/utils/balancer.go b/utils/balancer.go index b28b208..3a5fea6 100644 --- a/utils/balancer.go +++ b/utils/balancer.go @@ -8,19 +8,22 @@ import ( echocache "github.com/fraidev/go-echo-cache" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" + "github.com/rs/zerolog/log" ) type sameNodeBalancer struct { - targets []*middleware.ProxyTarget - mutex sync.Mutex - random *rand.Rand - store echocache.Cache - TTL int + targets []*middleware.ProxyTarget + retryTarget *middleware.ProxyTarget + mutex sync.Mutex + random *rand.Rand + store echocache.Cache + TTL int } -func NewSameNodeBalancer(targets []*middleware.ProxyTarget, ttl int, store echocache.Cache) middleware.ProxyBalancer { +func NewSameNodeBalancer(targets []*middleware.ProxyTarget, retryTarget *middleware.ProxyTarget, ttl int, store echocache.Cache) middleware.ProxyBalancer { b := sameNodeBalancer{} b.targets = targets + b.retryTarget = retryTarget b.random = rand.New(rand.NewSource(int64(time.Now().Nanosecond()))) b.store = store b.TTL = ttl @@ -54,6 +57,12 @@ func (b *sameNodeBalancer) RemoveTarget(name string) bool { func (b *sameNodeBalancer) Next(c echo.Context) *middleware.ProxyTarget { b.mutex.Lock() defer b.mutex.Unlock() + + if b.retryTarget != nil && c.Get("retry") != nil { + log.Info().Msg("Retrying request") + return b.retryTarget + } + if len(b.targets) == 0 { return nil } else if len(b.targets) == 1 { diff --git a/utils/config.go b/utils/config.go index 675251b..2e64e7f 100644 --- a/utils/config.go +++ b/utils/config.go @@ -93,24 +93,26 @@ type LoadBalancer struct { } type ConfigFile struct { - LoadBalancer LoadBalancer `mapstructure:"load_balancer"` - Redis Redis `mapstructure:"redis"` - Logger Logger `mapstructure:"logger"` - RateLimit RateLimit `mapstructure:"rate_limit"` - Cache Cache `mapstructure:"cache"` - DenyList DenyList `mapstructure:"deny_list"` - DenyRoutes DenyRoutes `mapstructure:"deny_routes"` - Metrics Metrics `mapstructure:"metrics"` - GC GC `mapstructure:"gc"` - CORS CORS `mapstructure:"cors"` - GZIP GZIP `mapstructure:"gzip"` - Host string `mapstructure:"host"` - TezosHost []string `mapstructure:"tezos_host"` + LoadBalancer LoadBalancer `mapstructure:"load_balancer"` + Redis Redis `mapstructure:"redis"` + Logger Logger `mapstructure:"logger"` + RateLimit RateLimit `mapstructure:"rate_limit"` + Cache Cache `mapstructure:"cache"` + DenyList DenyList `mapstructure:"deny_list"` + DenyRoutes DenyRoutes `mapstructure:"deny_routes"` + Metrics Metrics `mapstructure:"metrics"` + GC GC `mapstructure:"gc"` + CORS CORS `mapstructure:"cors"` + GZIP GZIP `mapstructure:"gzip"` + Host string `mapstructure:"host"` + TezosHost []string `mapstructure:"tezos_host"` + TezosHostRetry string `mapstructure:"tezos_host_retry"` } var defaultConfig = &ConfigFile{ - Host: "0.0.0.0:8080", - TezosHost: []string{"127.0.0.1:8732"}, + Host: "0.0.0.0:8080", + TezosHost: []string{"127.0.0.1:8732"}, + TezosHostRetry: "", Redis: Redis{ Host: "", Enabled: false, @@ -187,6 +189,7 @@ func NewConfig() *Config { // Set default values for configuration viper.SetDefault("host", defaultConfig.Host) viper.SetDefault("tezos_host", defaultConfig.TezosHost) + viper.SetDefault("tezos_host_retry", defaultConfig.TezosHostRetry) viper.SetDefault("redis.host", defaultConfig.Redis.Host) viper.SetDefault("redis.enabled", defaultConfig.Redis.Enabled) viper.SetDefault("load_balancer.ttl", defaultConfig.LoadBalancer.TTL) @@ -227,19 +230,12 @@ func NewConfig() *Config { } var targets = []*middleware.ProxyTarget{} - + var retryTarget *middleware.ProxyTarget = nil + if configFile.TezosHostRetry != "" { + retryTarget = hostToTarget(configFile.TezosHostRetry) + } for _, host := range configFile.TezosHost { - tezosHost := host - - if !strings.Contains(host, "http") { - tezosHost = "http://" + host - } - - url, err := url.ParseRequestURI(tezosHost) - if err != nil { - log.Fatal(err) - } - targets = append(targets, &middleware.ProxyTarget{URL: url}) + targets = append(targets, hostToTarget(host)) } var redisClient *redis.Client @@ -259,7 +255,7 @@ func NewConfig() *Config { store = &memoryStore } - balancer := NewSameNodeBalancer(targets, configFile.LoadBalancer.TTL, store) + balancer := NewSameNodeBalancer(targets, retryTarget, configFile.LoadBalancer.TTL, store) transport := &http.Transport{ Proxy: http.ProxyFromEnvironment, @@ -277,6 +273,7 @@ func NewConfig() *Config { proxyConfig := middleware.ProxyConfig{ Skipper: middleware.DefaultSkipper, ContextKey: "target", + RetryCount: 2, Balancer: balancer, Transport: transport, } @@ -365,3 +362,16 @@ func NewConfig() *Config { return config } + +func hostToTarget(host string) *middleware.ProxyTarget { + hostWithScheme := host + if !strings.Contains(host, "http") { + hostWithScheme = "http://" + host + } + targetURL, err := url.ParseRequestURI(hostWithScheme) + if err != nil { + log.Fatal(err) + } + + return &middleware.ProxyTarget{URL: targetURL} +}