Files
artifactapi/internal/proxy/classifier.go
T
unkinben b698d1bdc0
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful
perf: memoise regex compilation in the classifier
Classify runs on every proxied request and recompiled each remote's
pattern lists every time. Cache compiled regexes keyed by pattern text so
each distinct pattern compiles once and is reused thereafter.

Refs #73
2026-07-02 00:33:46 +10:00

101 lines
2.2 KiB
Go

package proxy
import (
"regexp"
"sync"
"git.unkin.net/unkin/artifactapi/internal/provider"
"git.unkin.net/unkin/artifactapi/pkg/models"
)
type Classification int
const (
ClassImmutable Classification = iota
ClassMutable
ClassDenied
)
func (c Classification) String() string {
switch c {
case ClassImmutable:
return "immutable"
case ClassMutable:
return "mutable"
case ClassDenied:
return "denied"
default:
return "unknown"
}
}
type Classifier struct {
provider provider.Provider
}
func NewClassifier(p provider.Provider) *Classifier {
return &Classifier{provider: p}
}
func (c *Classifier) Classify(remote models.Remote, path string) Classification {
if matchesAny(path, compilePatterns(remote.Blocklist)) {
return ClassDenied
}
if len(remote.Patterns) > 0 && !matchesAny(path, compilePatterns(remote.Patterns)) {
return ClassDenied
}
if matchesAny(path, compilePatterns(remote.ImmutablePatterns)) {
return ClassImmutable
}
if matchesAny(path, compilePatterns(remote.MutablePatterns)) {
return ClassMutable
}
if c.provider.Classify(path) == provider.Mutable {
return ClassMutable
}
return ClassImmutable
}
// patternCache memoises regex compilation. Classify runs on every proxied
// request and previously recompiled each remote's pattern lists every time;
// keying by the pattern string lets each distinct pattern compile once and
// then be reused, with no invalidation needed (the pattern text is the key).
// A pattern that fails to compile is cached as a typed nil so we don't retry.
var patternCache sync.Map // map[string]*regexp.Regexp
func compileCached(pattern string) *regexp.Regexp {
if v, ok := patternCache.Load(pattern); ok {
return v.(*regexp.Regexp)
}
re, err := regexp.Compile(pattern)
if err != nil {
re = nil
}
patternCache.Store(pattern, re)
return re
}
func compilePatterns(patterns []string) []*regexp.Regexp {
compiled := make([]*regexp.Regexp, 0, len(patterns))
for _, p := range patterns {
if re := compileCached(p); re != nil {
compiled = append(compiled, re)
}
}
return compiled
}
func matchesAny(path string, patterns []*regexp.Regexp) bool {
for _, re := range patterns {
if re.MatchString(path) {
return true
}
}
return false
}