소스 정보
- 저장소
- zalando/skipper
- 최근 소스 활동
- 2026년 8월 31일 13:54
- 감지된 SKILL.md 언어
- 영어
- 스타
- 3,332
- 포크
- 409
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
SKILL.md 표시 중
SKILL.md
소스 지침 · 읽기 전용 미리보기- 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)}
}
```
GitHub에서 보기