diff --git a/cmd/litefs/config.go b/cmd/litefs/config.go index 7385d2d..6847af9 100644 --- a/cmd/litefs/config.go +++ b/cmd/litefs/config.go @@ -120,6 +120,7 @@ type ProxyConfig struct { MaxLag time.Duration `yaml:"max-lag"` Debug bool `yaml:"debug"` Passthrough []string `yaml:"passthrough"` + AlwaysForward []string `yaml:"always-forward"` PrimaryRedirectTimeout time.Duration `yaml:"primary-redirect-timeout"` } diff --git a/cmd/litefs/mount_linux.go b/cmd/litefs/mount_linux.go index 811e061..c3c4d3c 100644 --- a/cmd/litefs/mount_linux.go +++ b/cmd/litefs/mount_linux.go @@ -522,6 +522,16 @@ func (c *MountCommand) runProxyServer(ctx context.Context) error { passthroughs = append(passthroughs, re) } + // Parse always-forward expressions. + var alwaysForward []*regexp.Regexp + for _, s := range c.Config.Proxy.AlwaysForward { + re, err := http.CompileMatch(s) + if err != nil { + return fmt.Errorf("cannot parse proxy always-forward expression: %q", s) + } + alwaysForward = append(alwaysForward, re) + } + server := http.NewProxyServer(c.Store) server.Target = c.Config.Proxy.Target server.DBName = c.Config.Proxy.DB @@ -529,6 +539,7 @@ func (c *MountCommand) runProxyServer(ctx context.Context) error { server.MaxLag = c.Config.Proxy.MaxLag server.Debug = c.Config.Proxy.Debug server.Passthroughs = passthroughs + server.AlwaysForward = alwaysForward server.PrimaryRedirectTimeout = c.Config.Proxy.PrimaryRedirectTimeout if err := server.Listen(); err != nil { return err diff --git a/http/proxy_server.go b/http/proxy_server.go index 204e76d..c62addc 100644 --- a/http/proxy_server.go +++ b/http/proxy_server.go @@ -58,6 +58,9 @@ type ProxyServer struct { // List of path expressions that will be passed through if matched. Passthroughs []*regexp.Regexp + // List of path expressions that will always be redirected to the primary. + AlwaysForward []*regexp.Regexp + // If true, add verbose debug logging. Debug bool @@ -184,14 +187,18 @@ func (s *ProxyServer) serveHTTP(w http.ResponseWriter, r *http.Request) { return } - switch r.Method { - case http.MethodGet: - s.serveRead(w, r) - case http.MethodHead: + isReadOnly := r.Method == http.MethodGet || r.Method == http.MethodHead + + // Override if path is configured to always forward. + if isReadOnly && s.isAlwaysForwarded(r) { + isReadOnly = false + } + + if isReadOnly { s.serveRead(w, r) - default: - s.serveNonRead(w, r) + return } + s.serveNonRead(w, r) } func (s *ProxyServer) serveGetHealth(w http.ResponseWriter, r *http.Request) { @@ -337,6 +344,16 @@ func (s *ProxyServer) isPassthrough(r *http.Request) bool { return false } +// isAlwaysForwarded returns true if request matches any of the redirection expressions. +func (s *ProxyServer) isAlwaysForwarded(r *http.Request) bool { + for _, re := range s.AlwaysForward { + if re.MatchString(r.URL.Path) { + return true + } + } + return false +} + // logf logs if debug logging is enabled. func (s *ProxyServer) logf(format string, v ...any) { if s.Debug {