mirror of
https://github.com/hanzoai/gateway.git
synced 2026-11-02 23:52:23 +00:00
Every import in this module now resolves to github.com/hanzoai/gateway/v2.
The 34 github.com/krakend/* + github.com/luraproject/lura modules that
supplied the engine and its components are vendored under internal/ and
their 91 packages rewritten onto our own path.
Why fold rather than mirror into 34 hanzoai/* repos:
- It removes the exposure outright. 34 rows leave 34 rows of go.sum to
reason about; zero modules leave nothing. `go list -m all` and go.sum
now contain no krakend/lura entry at all.
- Cross-references cannot go wrong. 28 of the 34 import lura (97 files
inside lura self-reference it). Mirroring means rewriting every one of
those to our lura, and a single miss silently links a SECOND copy of
lura into the binary. With one module that is structurally impossible.
- It creates no duplicates. These are lura-coupled gateway glue with no
consumer outside this binary; publishing 34 repos would be 34 new
things to maintain for zero second caller. internal/ makes that
guarantee mechanical — Go forbids importing it from outside this
module, so none of it can become a competing public package. Verified
no repo outside gateway imports any hanzoai/gateway/v2 subpackage.
Layout mirrors provenance so an upstream re-sync stays a diff:
luraproject/lura/v2 -> internal/lura (the engine, 20 pkgs)
krakend/krakend-<x> -> internal/plugin/<x> (28 components)
krakend/<x> -> internal/pkg/<x> (6 standalone libs)
This commit is PURELY MECHANICAL: 196 of the 197 vendored files are
byte-identical to their upstream original once the import rewrite is
applied. The single exception is bloomfilter's subpackage, which upstream
names `krakend` — the one package name that could not survive as-is; it
becomes internal/pkg/bloomfilter/register. Hand edits land separately in
the next commit so they are readable on their own.
Per `go mod vendor` semantics, dependencies' _test.go files are not
vendored. They test upstream code at a pinned version that we do not
modify, several cannot run at all from a module distribution (they need
TLS certs and compiled .so plugins upstream generates but never ships),
and one is a 100k-request stress test that exhausts the ephemeral port
range and fails every package running beside it. Fold integrity is proven
by the byte-diff above instead, which is the stronger check.
Preserved deliberately — each of these is behaviour, not branding:
- the `github.com/devopsfaith/krakend*` extra_config namespace strings
(35 of them). These are config-surface keys, not import paths;
renaming one silently stops that component finding its config. The
jose validator namespace backs `auth/validator` on 212 endpoints.
- core.KrakendVersion / KrakendUserAgent / the X-KRAKEND consts, whose
VALUES rebrand.go overwrites — that seam is unchanged.
- the KRAKEND_ koanf env prefix (a hardcoded upstream const).
- every vendored module's LICENSE (34 files, Apache-2.0/MIT).
The Makefile/CI -X paths move with the engine. An -X naming a symbol that
does not exist is silently ignored by the linker, so a stale path here
would have shipped an "undefined" version in the backend User-Agent and
X-Api-Version with a perfectly green build. Verified live:
`gateway version` reports 2.13.0, not undefined.
Verification (GOWORK=off CGO_ENABLED=0, both tag modes):
go build ./... OK go build -tags legacy ./... OK
go vet ./... clean go vet -tags legacy ./... clean
go test ./... 6/6 ok go test -tags legacy 5/5 ok
tests/ integration harness 47 fixtures pass
gateway check -c configs/{hanzo,lux}/gateway.json -> Syntax OK!
go list -m all | grep -iE 'krakend|lura' -> empty
go mod verify -> all modules verified
Co-authored-by: Hanzo Dev <[email protected]>
225 lines
4.5 KiB
Go
225 lines
4.5 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
/*
|
|
Package dnssrv defines some implementations for a dns based service discovery
|
|
*/
|
|
package dnssrv
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/hanzoai/gateway/v2/internal/lura/config"
|
|
"github.com/hanzoai/gateway/v2/internal/lura/sd"
|
|
)
|
|
|
|
// Namespace is the key for the dns sd module
|
|
const Namespace = "dns"
|
|
const DefaultTTL = 30 * time.Second
|
|
const MinTTL = time.Second
|
|
|
|
// Register registers the dns sd subscriber factory under the name defined by Namespace
|
|
func Register() error {
|
|
return sd.GetRegister().Register(Namespace, SubscriberFactory)
|
|
}
|
|
|
|
// TTL is the duration of the cached data
|
|
var TTL = DefaultTTL
|
|
|
|
func SetTTL(d time.Duration) {
|
|
if d < MinTTL {
|
|
// in case the TTL is less than the minimum, we leave what is
|
|
// already set.
|
|
return
|
|
}
|
|
TTL = d
|
|
}
|
|
|
|
// DefaultLookup is the function used for the DNS resolution
|
|
var DefaultLookup = net.LookupSRV
|
|
|
|
// SubscriberFactory builds a DNS_SRV Subscriber with the received config
|
|
func SubscriberFactory(cfg *config.Backend) sd.Subscriber {
|
|
return NewDetailedWithScheme(cfg.Host[0], DefaultLookup, TTL, cfg.SDScheme)
|
|
}
|
|
|
|
// New creates a DNS subscriber with the default values
|
|
func New(name string) sd.Subscriber {
|
|
return NewDetailed(name, DefaultLookup, TTL)
|
|
}
|
|
|
|
// NewDetailed creates a DNS subscriber with the received values
|
|
func NewDetailed(name string, lookup lookup, ttl time.Duration) sd.Subscriber {
|
|
return NewDetailedWithScheme(name, lookup, ttl, "http")
|
|
}
|
|
|
|
// NewDetailedWithScheme creates a DNS subscriber with the received values and the scheme to use
|
|
// for the fetched server entries.
|
|
func NewDetailedWithScheme(name string, lookup lookup, ttl time.Duration, scheme string) sd.Subscriber {
|
|
if scheme == "" {
|
|
scheme = "http"
|
|
}
|
|
s := subscriber{
|
|
name: name,
|
|
cache: &sd.FixedSubscriber{},
|
|
mutex: &sync.RWMutex{},
|
|
ttl: ttl,
|
|
lookup: lookup,
|
|
scheme: scheme,
|
|
}
|
|
|
|
s.update()
|
|
|
|
go func() {
|
|
for {
|
|
<-time.After(s.ttl)
|
|
s.update()
|
|
}
|
|
}()
|
|
|
|
return s
|
|
}
|
|
|
|
type lookup func(service, proto, name string) (cname string, addrs []*net.SRV, err error)
|
|
|
|
type subscriber struct {
|
|
name string
|
|
cache *sd.FixedSubscriber
|
|
mutex *sync.RWMutex
|
|
ttl time.Duration
|
|
lookup lookup
|
|
scheme string
|
|
}
|
|
|
|
// Hosts returns a copy of the cached set of hosts. It is safe to call it concurrently
|
|
func (s subscriber) Hosts() ([]string, error) {
|
|
s.mutex.RLock()
|
|
defer s.mutex.RUnlock()
|
|
|
|
hs, err := s.cache.Hosts()
|
|
if err != nil {
|
|
return []string{}, err
|
|
}
|
|
|
|
res := make([]string, len(hs))
|
|
copy(res, hs)
|
|
return res, nil
|
|
}
|
|
|
|
func (s subscriber) update() {
|
|
instances, err := s.resolve()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
|
|
if len(instances) > 100 {
|
|
*(s.cache) = sd.NewRandomFixedSubscriber(instances)
|
|
} else {
|
|
*(s.cache) = sd.FixedSubscriber(instances)
|
|
}
|
|
}
|
|
|
|
func (s subscriber) resolve() ([]string, error) {
|
|
_, srvs, err := s.lookup("", "", s.name)
|
|
if err != nil {
|
|
return []string{}, err
|
|
}
|
|
|
|
sort.Slice(
|
|
srvs,
|
|
func(i, j int) bool {
|
|
if srvs[i].Priority == srvs[j].Priority {
|
|
if srvs[i].Weight == srvs[j].Weight {
|
|
if srvs[i].Target == srvs[j].Target {
|
|
return srvs[i].Port < srvs[j].Port
|
|
}
|
|
return srvs[i].Target < srvs[j].Target
|
|
}
|
|
return srvs[i].Weight > srvs[j].Weight
|
|
}
|
|
return srvs[i].Priority < srvs[j].Priority
|
|
},
|
|
)
|
|
|
|
ws := make([]uint16, 0, len(srvs))
|
|
host := make([]string, 0, len(srvs))
|
|
|
|
for _, a := range srvs {
|
|
if a.Priority > srvs[0].Priority {
|
|
break
|
|
}
|
|
ws = append(ws, a.Weight)
|
|
host = append(host, s.scheme+"://"+net.JoinHostPort(a.Target, fmt.Sprint(a.Port)))
|
|
}
|
|
|
|
instances := make([]string, 0, len(ws))
|
|
for i, times := range compact(ws) {
|
|
for j := uint16(0); j < times; j++ {
|
|
instances = append(instances, host[i])
|
|
}
|
|
}
|
|
return instances, nil
|
|
}
|
|
|
|
func compact(ws []uint16) []uint16 {
|
|
tmp := normalize(ws)
|
|
div := gcd(tmp)
|
|
if div < 2 {
|
|
return tmp
|
|
}
|
|
|
|
res := make([]uint16, len(tmp))
|
|
for i, w := range tmp {
|
|
res[i] = w / div
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
func normalize(ws []uint16) []uint16 {
|
|
scale := 100
|
|
if l := len(ws); l > scale {
|
|
scale = l
|
|
}
|
|
|
|
var sum int64
|
|
for _, w := range ws {
|
|
sum += int64(w)
|
|
}
|
|
if sum <= int64(scale) {
|
|
return ws
|
|
}
|
|
|
|
res := make([]uint16, len(ws))
|
|
for i, w := range ws {
|
|
res[i] = uint16(int64(w) * int64(scale) / sum)
|
|
}
|
|
return res
|
|
}
|
|
|
|
func gcd(ws []uint16) uint16 {
|
|
if len(ws) == 0 {
|
|
return 0
|
|
}
|
|
|
|
localGCD := func(a uint16, b uint16) uint16 {
|
|
for b > 0 {
|
|
a, b = b, a%b
|
|
}
|
|
return a
|
|
}
|
|
|
|
result := ws[0]
|
|
for _, i := range ws[1:] {
|
|
result = localGCD(result, i)
|
|
}
|
|
|
|
return result
|
|
}
|