Skip to main content

Informations de source

Dépôt
zalando/skipper
Dernière activité de la source
31 août 2026 à 13:54
Langue détectée de SKILL.md
anglais
Étoiles
3 332
Forks
408

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
go-coding
description
Create or modify Go skipper code
license
MIT
compatibility
opencode, claude
metadata
{"audience":"maintainers, contributors"}
## What I do - focus on Go code and architecture ## When to use me Use this when you are reading or writing Go code ## Quick start Use `gopls` to navigate the code. ### Package and type organization Group related types together using a single `type (...)` block. Use iota for enumerated constants. Keep unexported types lowercase; only export what callers need. ```go type ( matchBehavior int matchMode int ) const ( matchBehaviorAll matchBehavior = iota matchBehaviorAny matchModeExact matchMode = iota matchModeRegexp ) type ( spec struct { name string matchBehavior matchBehavior matchMode matchMode reg *registry } predicate struct { kv map[string][]valueMatcher matchBehavior matchBehavior cache sync.Map } ) ``` ### Precompute in Create/New, not in the hot path `CreateFilter` and `Create` run at route-build time. Precompute everything costly there (compile regexps, build maps, normalize config). The hot path (`Request`, `Response`, `Match`) must be allocation-free on the common case. ```go func (s *spec) Create(args []any) (routing.Predicate, error) { if len(args) == 0 || len(args)%2 != 0 { return nil, predicates.ErrInvalidPredicateParameters } kv := make(map[string][]valueMatcher) for i := 0; i < len(args); i += 2 { key, keyOk := args[i].(string) value, valueOk := args[i+1].(string) if !keyOk || !valueOk { return nil, predicates.ErrInvalidPredicateParameters } re, err := regexp.Compile(value) if err != nil { return nil, predicates.ErrInvalidPredicateParameters } kv[key] = append(kv[key], regexMatcher{regexp: re}) } return &predicate{kv: kv}, nil } ``` ### Cache expensive Create results to survive frequent route table rebuilds Route tables rebuild every few seconds. Cache `CreateFilter`/`Create` results keyed by config args to avoid recreating identical instances and leaking memory. Use `sync.Mutex` around the map; check-then-insert is fine for idempotent objects. ```go type spec struct { mu sync.Mutex filterMap map[string]*myFilter } func (s *spec) CreateFilter(args []interface{}) (filters.Filter, error) { key, err := keyFromArgs(args) if err != nil { return nil, err } s.mu.Lock() if f, ok := s.filterMap[key]; ok { s.mu.Unlock() return f, nil } s.mu.Unlock() // ... build f ... s.mu.Lock() s.filterMap[key] = f s.mu.Unlock() return f, nil } ``` ### Cache hot-path lookups with sync.Map For repeated lookups in `Match`/`Request`/`Response` (e.g. parsed tokens, validated values), use `sync.Map` — it avoids a single global mutex bottleneck under heavy read concurrency. Periodically clear it rather than tracking per-entry TTLs; infrequent full clears are cheap. ```go type predicate struct { kv map[string][]valueMatcher cache sync.Map // wire-string → bool } // in a background goroutine started at spec construction time: func (r *registry) clean() { tick := time.NewTicker(time.Hour) defer tick.Stop() for { select { case <-r.quit: return case <-tick.C: r.mu.Lock() for _, p := range r.predicateMap { p.cache.Clear() } r.mu.Unlock() } } } ``` ### Avoid allocations in string parsing Prefer `strings.Index` / `strings.Cut` over `strings.Split` to avoid allocating a slice. Use iterator-style helpers (or `strings.SplitSeq` in go1.24+) to range over parts without materializing them. ```go // zero-allocation split iteration (backport of strings.SplitSeq) func splitSeq(s, sep string) func(yield func(string) bool) { return func(yield func(string) bool) { for { i := strings.Index(s, sep) if i < 0 { break } if !yield(s[:i]) { return } s = s[i+len(sep):] } yield(s) } } // usage for part := range splitSeq(header, ",") { token, value, found := strings.Cut(strings.TrimSpace(part), "=") _ = token; _ = value; _ = found } ``` ### Skip work when input hasn't changed For dataclients and watchers, record the last successful input (e.g. file bytes, ETag) and return early without parsing when nothing changed. ```go func (c *WatchClient) loadUpdates() watchResponse { content, err := os.ReadFile(c.fileName) if err != nil { ... } if bytes.Equal(content, c.lastContent) { return watchResponse{} // no change } r, err := eskip.Parse(string(content)) if err != nil { c.lastContent = nil return watchResponse{err: err} } c.lastContent = content ... } ``` ### Error handling Wrap errors with context using `fmt.Errorf("...: %w", err)`. Return sentinel errors (`predicates.ErrInvalidPredicateParameters`) from `Create`/`CreateFilter` for invalid config so callers can distinguish config errors from runtime errors. Never swallow errors in the hot path. ```go req, endpointMetrics, err := p.mapRequest(ctx, requestContext) if err != nil { return nil, &proxyError{err: fmt.Errorf("could not map backend request: %w", err)} } ```
Voir sur GitHub