From e6b7c6ca75d5733d6be03370c24d098b56121311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 21 Aug 2026 13:03:47 +0800 Subject: [PATCH] Invalidate remote rule-set cache when URL changes --- adapter/experimental.go | 26 +++++++++++++++++++++++++- route/rule/rule_set_remote.go | 25 ++++++++++++++++++------- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/adapter/experimental.go b/adapter/experimental.go index 58502634..023e01bf 100644 --- a/adapter/experimental.go +++ b/adapter/experimental.go @@ -61,11 +61,12 @@ type SavedBinary struct { Content []byte LastUpdated time.Time LastEtag string + URLHash []byte } func (s *SavedBinary) MarshalBinary() ([]byte, error) { var buffer bytes.Buffer - err := binary.Write(&buffer, binary.BigEndian, uint8(1)) + err := binary.Write(&buffer, binary.BigEndian, uint8(2)) if err != nil { return nil, err } @@ -89,6 +90,14 @@ func (s *SavedBinary) MarshalBinary() ([]byte, error) { if err != nil { return nil, err } + _, err = varbin.WriteUvarint(&buffer, uint64(len(s.URLHash))) + if err != nil { + return nil, err + } + _, err = buffer.Write(s.URLHash) + if err != nil { + return nil, err + } return buffer.Bytes(), nil } @@ -130,6 +139,21 @@ func (s *SavedBinary) UnmarshalBinary(data []byte) error { return err } s.LastEtag = string(etagBytes) + if version < 2 { + return nil + } + urlHashLength, err := binary.ReadUvarint(reader) + if err != nil { + return err + } + if urlHashLength > uint64(reader.Len()) { + return E.New("invalid url hash length: ", urlHashLength) + } + s.URLHash = make([]byte, urlHashLength) + _, err = io.ReadFull(reader, s.URLHash) + if err != nil { + return err + } return nil } diff --git a/route/rule/rule_set_remote.go b/route/rule/rule_set_remote.go index 64f6c8ee..10b1f13c 100644 --- a/route/rule/rule_set_remote.go +++ b/route/rule/rule_set_remote.go @@ -3,6 +3,7 @@ package rule import ( "bytes" "context" + "crypto/sha256" "io" "net/http" "path/filepath" @@ -38,6 +39,7 @@ type RemoteRuleSet struct { outbound adapter.OutboundManager tag string url string + urlHash [32]byte initialPath string options option.RuleSet updateInterval time.Duration @@ -66,13 +68,15 @@ func NewRemoteRuleSet(ctx context.Context, logger logger.ContextLogger, tag stri initialPath = filemanager.BasePath(ctx, strings.ReplaceAll(options.RemoteOptions.InitialPath, C.RuleSetTagPlaceholder, tag)) initialPath, _ = filepath.Abs(initialPath) } + url := strings.ReplaceAll(options.RemoteOptions.URL, C.RuleSetTagPlaceholder, tag) return &RemoteRuleSet{ ctx: ctx, cancel: cancel, outbound: service.FromContext[adapter.OutboundManager](ctx), logger: logger, tag: tag, - url: strings.ReplaceAll(options.RemoteOptions.URL, C.RuleSetTagPlaceholder, tag), + url: url, + urlHash: sha256.Sum256([]byte(url)), initialPath: initialPath, options: options, updateInterval: updateInterval, @@ -97,13 +101,18 @@ func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext *adapter. startContext.Register(transport) s.httpClient = &http.Client{Transport: transport} if s.cacheFile != nil { - if savedSet := s.cacheFile.LoadRuleSet(s.tag); savedSet != nil { - err = s.loadBytes(savedSet.Content) - if err != nil { - s.logger.Warn(E.Cause(err, "restore cached rule-set, will refetch")) + savedSet := s.cacheFile.LoadRuleSet(s.tag) + if savedSet != nil { + if len(savedSet.URLHash) > 0 && !bytes.Equal(savedSet.URLHash, s.urlHash[:]) { + s.logger.Info("cached rule-set was downloaded from another URL, will refetch") } else { - s.lastUpdated = savedSet.LastUpdated - s.lastEtag = savedSet.LastEtag + err = s.loadBytes(savedSet.Content) + if err != nil { + s.logger.Warn(E.Cause(err, "restore cached rule-set, will refetch")) + } else { + s.lastUpdated = savedSet.LastUpdated + s.lastEtag = savedSet.LastEtag + } } } } @@ -249,6 +258,7 @@ func (s *RemoteRuleSet) fetch(ctx context.Context, isStart bool) error { savedRuleSet := s.cacheFile.LoadRuleSet(s.tag) if savedRuleSet != nil { savedRuleSet.LastUpdated = s.lastUpdated + savedRuleSet.URLHash = s.urlHash[:] err = s.cacheFile.SaveRuleSet(s.tag, savedRuleSet) if err != nil { s.logger.Error("save rule-set updated time: ", err) @@ -279,6 +289,7 @@ func (s *RemoteRuleSet) fetch(ctx context.Context, isStart bool) error { LastUpdated: s.lastUpdated, Content: content, LastEtag: s.lastEtag, + URLHash: s.urlHash[:], }) if err != nil { s.logger.Error("save rule-set cache: ", err)