Skip to content

Commit

Permalink
add retry node
Browse files Browse the repository at this point in the history
  • Loading branch information
fraidev committed Dec 16, 2023
1 parent 6260734 commit bcccd4c
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 34 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions middlewares/retry.go
Original file line number Diff line number Diff line change
@@ -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
}
}
}
21 changes: 15 additions & 6 deletions utils/balancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
66 changes: 38 additions & 28 deletions utils/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -277,6 +273,7 @@ func NewConfig() *Config {
proxyConfig := middleware.ProxyConfig{
Skipper: middleware.DefaultSkipper,
ContextKey: "target",
RetryCount: 2,
Balancer: balancer,
Transport: transport,
}
Expand Down Expand Up @@ -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}
}

0 comments on commit bcccd4c

Please sign in to comment.