diff --git a/.github/workflows/lint-test.yml b/.github/workflows/lint-test.yml index 0d2cbd1e0..f3cb3a75c 100644 --- a/.github/workflows/lint-test.yml +++ b/.github/workflows/lint-test.yml @@ -12,15 +12,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - + - uses: "actions/checkout@v4" - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v5 with: - go-version: 1.22 + go-version-file: 'go.mod' - name: golangci-lint - uses: golangci/golangci-lint-action@v2 + uses: golangci/golangci-lint-action@v8 with: skip-go-installation: true args: --timeout 3m --verbose @@ -30,12 +29,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 - - - name: Set up Go - uses: actions/setup-go@v2 + uses: actions/checkout@v4 + - uses: actions/setup-go@v5 with: - go-version: 1.22 + go-version-file: 'go.mod' - name: Collect dependencies run: | @@ -46,7 +43,7 @@ jobs: run: gotestsum --junitfile unit-tests.xml -- -gcflags=-l ./... - name: Test report - uses: dorny/test-reporter@v1 + uses: dorny/test-reporter@v2 if: success() || failure() with: name: Test report diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 986be8ffb..000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Release -on: - push: - tags: ["v*.*.*"] - -jobs: - release: - name: Release on GitHub - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Set up Go - uses: actions/setup-go@v5 - with: - cache-dependency-path: "**/*.sum" - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v5 - with: - version: latest - args: release --clean - env: - GITHUB_TOKEN: ${{ secrets.TOKEN }} diff --git a/.gitignore b/.gitignore index 82d9ddbdd..a2111e00c 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,4 @@ pkg/example/services/logs logs bin /example/bin -.proto \ No newline at end of file +.proto diff --git a/.golangci.yaml b/.golangci.yaml index 133d78670..91afde0d5 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,30 +1,44 @@ +version: "2" +issues: + fix: true linters: - fast: true - disable-all: true + default: none + enable: + - govet + exclusions: + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - internal/examples + - vendor + - third_party$ + - builtin$ + - examples$ +formatters: enable: -# - bodyclose # checks whether HTTP response body is closed successfully - - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases - - gosimple # Linter for Go source code that specializes in simplifying a code - - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string -# - ineffassign # Detects when assignments to existing variables are not used -# - dogsled # Checks assignments with too many blank identifiers (e.g. x, , , _, := f()) -# - goconst # Finds repeated strings that could be replaced by a constant - - goimports # Goimports does everything that gofmt does. Additionally it checks unused imports -# - goprintffuncname # Checks that printf-like functions are named with f at the end -# - nolintlint # Reports ill-formed or insufficient nolint directives -# - rowserrcheck # checks whether Err of rows is checked successfully - - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks -# - unconvert # Remove unnecessary type conversions -# - unparam # Reports unused function parameters -# - unused # Checks Go code for unused constants, variables, functions and types + - goimports + - gofmt + - gofumpt + settings: + gofumpt: + extra-rules: true + goimports: + local-prefixes: + - github.com/pubgo/lava/v2 + gofmt: + simplify: false + rewrite-rules: + - pattern: 'interface{}' + replacement: 'any' + - pattern: 'a[b:len(a)]' + replacement: 'a[b:]' -issues: - exclude-dirs: - - internal/example - - cmds - - vendor - - pkg/proto - exclude-generated: strict - exclude-dirs-use-default: true - exclude-case-sensitive: false - exclude-use-default: true + exclusions: + paths: + - internal/examples + - vendor + - examples$ + - proto diff --git a/.goreleaser.yaml b/.goreleaser.yaml deleted file mode 100644 index 27a6a68d0..000000000 --- a/.goreleaser.yaml +++ /dev/null @@ -1,26 +0,0 @@ -builds: - - main: ./cmds/protoc-gen-lava/main.go - id: protoc-gen-lava - binary: protoc-gen-lava - goos: - - linux - - darwin - - windows - - main: ./main.go - id: lava - binary: lava - env: - - CGO_ENABLED=0 - goos: - - linux - - darwin - - windows - ldflags: - - -X 'github.com/pubgo/lava/version.BuildTime={{ .Date }}' - - -X 'github.com/pubgo/lava/version.CommitID={{ .Commit }}' - - -X 'github.com/pubgo/lava/version.Version={{ .Version }}' -archives: - - name_template: "{{ .Binary }}-{{ .Tag }}-{{ .Os }}-{{ .Arch }}" - format: binary - replacements: - amd64: x86_64 diff --git a/.version b/.version new file mode 100644 index 000000000..6eaf89433 --- /dev/null +++ b/.version @@ -0,0 +1 @@ +v2.0.0 \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index 14a6e8783..000000000 --- a/Makefile +++ /dev/null @@ -1,50 +0,0 @@ -WORKDIR=`pwd` -Project=lava -Base=github.com/pubgo/funk -Version=$(shell git tag --sort=committerdate | tail -n 1) -BuildTime=$(shell date "+%F %T") -CommitID=$(shell git rev-parse --short=8 HEAD) -GOPATH=$(shell go env GOPATH ) - -LDFLAGS=-ldflags " \ --X '${Base}/version.buildTime=${BuildTime}' \ --X '${Base}/version.commitID=${CommitID}' \ --X '${Base}/version.version=${Version:-"v0.0.1-dev"}' \ --X '${Base}/version.project=${Project}' \ -" - -.PHONY: test -test: - @go test -short -race -v ./... -cover - -.PHONY: cover -cover: - gocov test -tags "kcp quic" ./... | gocov-html > cover.html - open cover.html - -.PHONY: vet -vet: - @go vet ./... - -.PHONY: generate -generate: - @go generate ./... - -.PHONY: deps -deps: - # https://github.com/protocolbuffers/protobuf - @go install -v github.com/tinylib/msgp - @go install -v github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 - -.PHONY: protobuf -protobuf: - protobuild vendor - protobuild gen - -.PHONY: lint -lint: - golangci-lint --version - golangci-lint run --timeout 3m --verbose ./... - -install-protoc: - go install -v ./cmds/protoc-gen-lava diff --git a/README.md b/README.md index 4d899216e..068d275da 100644 --- a/README.md +++ b/README.md @@ -25,3 +25,12 @@ 11. 统一protobuf定义grpc和http服务, 便于生成swagger和sdk, 方便第三方调用 12. 专注于业务开发,把额外的组件抽象成一个独立的服务 + + +## install + +```shell +brew install go-task/tap/go-task +brew install go-task +eval "$(task --completion zsh)" +``` diff --git a/buildtasks/bench.go b/buildtasks/bench.go deleted file mode 100644 index ec3993e02..000000000 --- a/buildtasks/bench.go +++ /dev/null @@ -1,27 +0,0 @@ -package buildtasks - -import "fmt" - -type BenchMode int - -const ( - BenchModeWazero BenchMode = iota - BenchModeCGO - BenchModeDefault -) - -func BenchArgs(pkg string, count int, mode BenchMode, libName string) []string { - args := []string{"test", "-bench=.", "-run=^$", "-v", "-timeout=60m"} - if count > 0 { - args = append(args, fmt.Sprintf("-count=%d", count)) - } - switch mode { - case BenchModeCGO: - args = append(args, fmt.Sprintf("-tags=%s_cgo", libName)) - case BenchModeDefault: - args = append(args, fmt.Sprintf("-tags=%s_bench_default", libName)) - } - args = append(args, pkg) - - return args -} diff --git a/buildtasks/lint.go b/buildtasks/lint.go deleted file mode 100644 index f08864404..000000000 --- a/buildtasks/lint.go +++ /dev/null @@ -1,17 +0,0 @@ -package buildtasks - -import ( - "github.com/goyek/goyek/v2" - "github.com/goyek/x/cmd" -) - -var GoLint = goyek.Define(goyek.Task{ - Name: "go-lint", - Usage: "golangci-lint run --fix", - Action: func(a *goyek.A) { - if !cmd.Exec(a, "go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest") { - return - } - cmd.Exec(a, "golangci-lint run --fix") - }, -}) diff --git a/buildtasks/util.go b/buildtasks/util.go deleted file mode 100644 index e519e3d46..000000000 --- a/buildtasks/util.go +++ /dev/null @@ -1,23 +0,0 @@ -package buildtasks - -import ( - "os/exec" - - "github.com/goyek/goyek/v2" - "github.com/goyek/x/cmd" -) - -// check if docker is installed and running -func checkDocker(a *goyek.A) bool { - if !hasBinary("docker") { - return true - } - - return cmd.Exec(a, "docker ps") -} - -// check if a binary exists -func hasBinary(binaryName string) bool { - _, err := exec.LookPath(binaryName) - return err == nil -} diff --git a/clients/grpcc/aaa.go b/clients/grpcc/aaa.go index 9be9e315f..f353996e1 100644 --- a/clients/grpcc/aaa.go +++ b/clients/grpcc/aaa.go @@ -3,13 +3,14 @@ package grpcc import ( "context" - "github.com/pubgo/lava/clients/grpcc/grpcc_config" "google.golang.org/grpc" + + "github.com/pubgo/lava/v2/clients/grpcc/grpccconfig" ) const Name = "grpcc" -type Config = grpcc_config.Cfg +type Config = grpccconfig.Cfg // Client grpc client interface type Client interface { diff --git a/clients/grpcc/client.go b/clients/grpcc/client.go index 5e548aef5..cb078b41d 100644 --- a/clients/grpcc/client.go +++ b/clients/grpcc/client.go @@ -5,18 +5,19 @@ import ( "fmt" "sync" - "github.com/pubgo/funk/config" - "github.com/pubgo/funk/errors" - "github.com/pubgo/funk/log" - "github.com/pubgo/funk/recovery" - "github.com/pubgo/funk/result" - "github.com/pubgo/funk/vars" - "github.com/pubgo/lava/clients/grpcc/grpcc_config" - "github.com/pubgo/lava/core/metrics" - "github.com/pubgo/lava/lava" + "github.com/pubgo/funk/v2/config" + "github.com/pubgo/funk/v2/errors" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/recovery" + "github.com/pubgo/funk/v2/result" + "github.com/pubgo/funk/v2/vars" "google.golang.org/grpc" "google.golang.org/grpc/health/grpc_health_v1" "google.golang.org/grpc/resolver" + + "github.com/pubgo/lava/v2/clients/grpcc/grpccconfig" + "github.com/pubgo/lava/v2/core/metrics" + "github.com/pubgo/lava/v2/lava" ) type Params struct { @@ -25,8 +26,8 @@ type Params struct { Resolvers []resolver.Builder } -func New(cfg *grpcc_config.Cfg, p Params, middlewares ...lava.Middleware) Client { - cfg = config.MergeR(grpcc_config.DefaultCfg(), cfg).Unwrap() +func New(cfg *grpccconfig.Cfg, p Params, middlewares ...lava.Middleware) Client { + cfg = config.MergeR(grpccconfig.DefaultCfg(), cfg).Unwrap() cfg.Resolvers = p.Resolvers c := &clientImpl{ @@ -35,57 +36,64 @@ func New(cfg *grpcc_config.Cfg, p Params, middlewares ...lava.Middleware) Client middlewares: middlewares, } - vars.RegisterValue(fmt.Sprintf("%s-grpc-client-config", cfg.Service.Name), cfg) + vars.Register(fmt.Sprintf("%s-grpc-client-config", cfg.Service.Name), func() any { return cfg }) return c } type clientImpl struct { log log.Logger - cfg *grpcc_config.Cfg + cfg *grpccconfig.Cfg mu sync.Mutex conn grpc.ClientConnInterface middlewares []lava.Middleware } -func (t *clientImpl) Invoke(ctx context.Context, method string, args, reply interface{}, opts ...grpc.CallOption) (err error) { +func (t *clientImpl) Invoke(ctx context.Context, method string, args, reply any, opts ...grpc.CallOption) (err error) { defer recovery.Err(&err, func(err error) error { - return errors.WrapTag(err, errors.T("method", method), errors.T("args", args)) + return errors.WrapTags(err, errors.Tags{"method": method, "args": args}) }) - conn := t.Get() - if conn.IsErr() { - return errors.Wrapf(conn.Err(), "failed to get grpc client, service=%s, method=%s", t.cfg.Service, method) - } + conn := t.Get(). + MapErr(func(err error) error { + return errors.Wrapf(err, "failed to get grpc client, service=%s, method=%s", t.cfg.Service, method) + }). + CallIfOK(func(val grpc.ClientConnInterface) error { + return val.Invoke(ctx, method, args, reply, opts...) + }) - return conn.Unwrap().Invoke(ctx, method, args, reply, opts...) + return conn.Err() } func (t *clientImpl) Healthy(ctx context.Context) error { - conn := t.Get() - if conn.IsErr() { - return errors.Wrapf(conn.Err(), "failed to get grpc client, service=%s, method=healthy", t.cfg.Service) - } - - _, err := grpc_health_v1.NewHealthClient(conn.Unwrap()).Check(ctx, &grpc_health_v1.HealthCheckRequest{}) - return errors.Wrapf(err, "service %s heath check failed", t.cfg.Service) + return t.Get(). + MapErr(func(err error) error { + return errors.Wrapf(err, "failed to get grpc client, service=%s, method=healthy", t.cfg.Service) + }). + CallIfOK(func(val grpc.ClientConnInterface) error { + _, err := grpc_health_v1.NewHealthClient(val).Check(ctx, &grpc_health_v1.HealthCheckRequest{}) + return errors.Wrapf(err, "service %s heath check failed", t.cfg.Service) + }). + Err() } func (t *clientImpl) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) { - conn := t.Get() - if conn.IsErr() { - return nil, errors.Wrapf(conn.Err(), "failed to get grpc client, service=%s, method=%s", t.cfg.Service, method) - } - - c, err1 := conn.Unwrap().NewStream(ctx, desc, method, opts...) - return c, errors.Wrap(err1, method) + conn := t.Get().MapErr(func(err error) error { + return errors.Wrapf(err, "failed to get grpc client, service=%s, method=%s", t.cfg.Service, method) + }) + return result.FlatMapTo(conn, func(val grpc.ClientConnInterface) (r result.Result[grpc.ClientStream]) { + return result.Wrap(val.NewStream(ctx, desc, method, opts...)). + MapErr(func(err error) error { + return errors.Wrapf(err, "service %s:%s new stream failed", t.cfg.Service, method) + }) + }).UnwrapErr() } // Get new grpc client func (t *clientImpl) Get() (r result.Result[grpc.ClientConnInterface]) { - defer recovery.Result(&r) + defer result.Recovery(&r) if t.conn != nil { - return r.WithVal(t.conn) + return r.WithValue(t.conn) } t.mu.Lock() @@ -93,14 +101,14 @@ func (t *clientImpl) Get() (r result.Result[grpc.ClientConnInterface]) { // 双检, 避免多次创建 if t.conn != nil { - return r.WithVal(t.conn) + return r.WithValue(t.conn) } - conn, err := createConn(t.cfg, t.log, t.middlewares) - if err != nil { - return r.WithErr(err) + conn := createConn(t.cfg, t.log, t.middlewares).UnwrapOrThrow(&r) + if r.IsErr() { + return r } t.conn = conn - return r.WithVal(t.conn) + return r.WithValue(t.conn) } diff --git a/clients/grpcc/grpcc_config/config.go b/clients/grpcc/grpccconfig/config.go similarity index 94% rename from clients/grpcc/grpcc_config/config.go rename to clients/grpcc/grpccconfig/config.go index f2e32a263..56e89f8a9 100644 --- a/clients/grpcc/grpcc_config/config.go +++ b/clients/grpcc/grpccconfig/config.go @@ -1,11 +1,12 @@ -package grpcc_config +package grpccconfig import ( "time" - "github.com/pubgo/lava/clients/grpcc/grpcc_resolver" "google.golang.org/grpc" "google.golang.org/grpc/resolver" + + "github.com/pubgo/lava/v2/clients/grpcc/grpccresolver" ) const ( @@ -45,7 +46,7 @@ type ServiceCfg struct { func DefaultCfg() *Cfg { cfg := &Cfg{ Service: &ServiceCfg{ - Scheme: grpcc_resolver.DirectScheme, + Scheme: grpccresolver.DirectScheme, }, Client: &GrpcClientCfg{ Insecure: true, diff --git a/clients/grpcc/grpcc_config/grpc.go b/clients/grpcc/grpccconfig/grpc.go similarity index 99% rename from clients/grpcc/grpcc_config/grpc.go rename to clients/grpcc/grpccconfig/grpc.go index fb8869c3b..3fe2145ff 100644 --- a/clients/grpcc/grpcc_config/grpc.go +++ b/clients/grpcc/grpccconfig/grpc.go @@ -1,4 +1,4 @@ -package grpcc_config +package grpccconfig import ( "time" diff --git a/clients/grpcc/grpcc_lb/p2c/lc.go b/clients/grpcc/grpcclb/p2c/lc.go similarity index 86% rename from clients/grpcc/grpcc_lb/p2c/lc.go rename to clients/grpcc/grpcclb/p2c/lc.go index 87ff75c91..f57d546b0 100644 --- a/clients/grpcc/grpcc_lb/p2c/lc.go +++ b/clients/grpcc/grpcclb/p2c/lc.go @@ -10,7 +10,7 @@ import ( ) type node struct { - item interface{} + item any loadCount int64 } @@ -28,11 +28,11 @@ func newP2cAgl() *loadAggregate { } } -func (la *loadAggregate) Add(n interface{}) { +func (la *loadAggregate) Add(n any) { la.items = append(la.items, &node{item: n}) } -func (la *loadAggregate) Next(info balancer.PickInfo) (interface{}, func(info balancer.DoneInfo)) { +func (la *loadAggregate) Next(info balancer.PickInfo) (any, func(info balancer.DoneInfo)) { var election, alternative *node switch len(la.items) { case 0: diff --git a/clients/grpcc/grpcc_lb/p2c/p2c.go b/clients/grpcc/grpcclb/p2c/p2c.go similarity index 92% rename from clients/grpcc/grpcc_lb/p2c/p2c.go rename to clients/grpcc/grpcclb/p2c/p2c.go index 46906894b..a503d74ca 100644 --- a/clients/grpcc/grpcc_lb/p2c/p2c.go +++ b/clients/grpcc/grpcclb/p2c/p2c.go @@ -1,7 +1,7 @@ package p2c import ( - "github.com/pubgo/funk/errors" + "github.com/pubgo/funk/v2/errors" "google.golang.org/grpc/balancer" "google.golang.org/grpc/balancer/base" ) @@ -11,7 +11,7 @@ var _ base.PickerBuilder = (*p2cBalancer)(nil) type p2cBalancer struct{} func (p2c *p2cBalancer) Build(info base.PickerBuildInfo) balancer.Picker { - if info.ReadySCs == nil || len(info.ReadySCs) == 0 { + if len(info.ReadySCs) == 0 { return base.NewErrPicker(balancer.ErrNoSubConnAvailable) } diff --git a/clients/grpcc/grpcc_lb/p2c/register.go b/clients/grpcc/grpcclb/p2c/register.go similarity index 100% rename from clients/grpcc/grpcc_lb/p2c/register.go rename to clients/grpcc/grpcclb/p2c/register.go diff --git a/clients/grpcc/grpcc_resolver/directbuilder.go b/clients/grpcc/grpccresolver/directbuilder.go similarity index 91% rename from clients/grpcc/grpcc_resolver/directbuilder.go rename to clients/grpcc/grpccresolver/directbuilder.go index b89b30712..29d8cdbf3 100644 --- a/clients/grpcc/grpcc_resolver/directbuilder.go +++ b/clients/grpcc/grpccresolver/directbuilder.go @@ -1,12 +1,12 @@ -package grpcc_resolver +package grpccresolver import ( "fmt" "strings" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/log" - "github.com/pubgo/funk/recovery" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/recovery" "google.golang.org/grpc/resolver" ) diff --git a/clients/grpcc/grpcc_resolver/discoverybuilder.go b/clients/grpcc/grpccresolver/discoverybuilder.go similarity index 74% rename from clients/grpcc/grpcc_resolver/discoverybuilder.go rename to clients/grpcc/grpccresolver/discoverybuilder.go index af59b1e2c..71517d704 100644 --- a/clients/grpcc/grpcc_resolver/discoverybuilder.go +++ b/clients/grpcc/grpccresolver/discoverybuilder.go @@ -1,23 +1,23 @@ -package grpcc_resolver +package grpccresolver import ( "context" "strings" "sync" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/async" - "github.com/pubgo/funk/errors" - "github.com/pubgo/funk/log" - "github.com/pubgo/funk/pretty" - "github.com/pubgo/funk/recovery" - "github.com/pubgo/funk/try" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/async" + "github.com/pubgo/funk/v2/errors" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/pretty" + "github.com/pubgo/funk/v2/recovery" + "github.com/pubgo/funk/v2/try" "google.golang.org/grpc/resolver" - "github.com/pubgo/lava/core/discovery" - "github.com/pubgo/lava/core/service" - "github.com/pubgo/lava/internal/logutil" - "github.com/pubgo/lava/pkg/proto/lavapbv1" + "github.com/pubgo/lava/v2/core/discovery" + "github.com/pubgo/lava/v2/core/service" + "github.com/pubgo/lava/v2/internal/logutil" + "github.com/pubgo/lava/v2/pkg/proto/lavapbv1" ) func NewDiscoveryBuilder(disco discovery.Discovery) resolver.Builder { @@ -71,7 +71,7 @@ func (d *discoveryBuilder) updateService(services ...*service.Service) { // 获取服务地址 func (d *discoveryBuilder) getAddrList(name string) []resolver.Address { var addrList []resolver.Address - d.services.Range(func(_, value interface{}) bool { + d.services.Range(func(_, value any) bool { addr := *value.(*resolver.Address) if addr.ServerName == name { addrList = append(addrList, *value.(*resolver.Address)) @@ -96,9 +96,14 @@ func (d *discoveryBuilder) Build(target resolver.Target, cc resolver.ClientConn, // target.Endpoint是服务的名字, 是项目启动的时候注册中心中注册的项目名字 // GetService根据服务名字获取注册中心该项目所有服务 - services := d.disco.GetService(context.Background(), srv).Unwrap(func(err error) error { - return errors.Wrapf(err, "failed to GetService, srv=%s", srv) - }) + services, gErr := d.disco.GetService(context.Background(), srv). + MapErr(func(err error) error { + return errors.Wrapf(err, "failed to GetService, srv=%s", srv) + }). + UnwrapErr() + if gErr != nil { + return nil, gErr + } // 启动后,更新服务地址 d.updateService(services...) @@ -109,9 +114,13 @@ func (d *discoveryBuilder) Build(target resolver.Target, cc resolver.ClientConn, logs.Info().Msgf("discovery builder UpdateState, address=%v", address) assert.MustF(cc.UpdateState(newState(address)), "update resolver address: %v", address) - w := d.disco.Watch(context.Background(), srv).Unwrap(func(err error) error { - return errors.Wrapf(err, "target.Endpoint: %s", srv) - }) + w, gErr := d.disco.Watch(context.Background(), srv). + MapErr(func(err error) error { + return errors.Wrapf(err, "target.Endpoint: %s", srv) + }).UnwrapErr() + if gErr != nil { + return nil, gErr + } return &baseResolver{ serviceName: srv, @@ -122,17 +131,17 @@ func (d *discoveryBuilder) Build(target resolver.Target, cc resolver.ClientConn, for { select { case <-ctx.Done(): - return + return gErr default: res := w.Next() if res.IsErr() { - if errors.Is(res.Err(), discovery.ErrWatcherStopped) { - return + if errors.Is(res.GetErr(), discovery.ErrWatcherStopped) { + return gErr } - d.log.Err(res.Err(), ctx).Msg("failed to get service watcher event") + d.log.Err(res.GetErr(), ctx).Msg("failed to get service watcher event") - if errors.Is(res.Err(), discovery.ErrTimeout) { + if errors.Is(res.GetErr(), discovery.ErrTimeout) { continue } diff --git a/clients/grpcc/grpcc_resolver/endpoint.go b/clients/grpcc/grpccresolver/endpoint.go similarity index 99% rename from clients/grpcc/grpcc_resolver/endpoint.go rename to clients/grpcc/grpccresolver/endpoint.go index b4ff8013d..6c0ad583f 100644 --- a/clients/grpcc/grpcc_resolver/endpoint.go +++ b/clients/grpcc/grpccresolver/endpoint.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package grpcc_resolver +package grpccresolver import ( "fmt" diff --git a/clients/grpcc/grpcc_resolver/register.go b/clients/grpcc/grpccresolver/register.go similarity index 90% rename from clients/grpcc/grpcc_resolver/register.go rename to clients/grpcc/grpccresolver/register.go index 446f39fb1..f392973b2 100644 --- a/clients/grpcc/grpcc_resolver/register.go +++ b/clients/grpcc/grpccresolver/register.go @@ -1,4 +1,4 @@ -package grpcc_resolver +package grpccresolver import "google.golang.org/grpc/resolver" diff --git a/clients/grpcc/grpcc_resolver/resolver.go b/clients/grpcc/grpccresolver/resolver.go similarity index 92% rename from clients/grpcc/grpcc_resolver/resolver.go rename to clients/grpcc/grpccresolver/resolver.go index 502f2189d..4b454dd61 100644 --- a/clients/grpcc/grpcc_resolver/resolver.go +++ b/clients/grpcc/grpccresolver/resolver.go @@ -1,9 +1,9 @@ -package grpcc_resolver +package grpccresolver import ( "context" - "github.com/pubgo/funk/log" + "github.com/pubgo/funk/v2/log" "google.golang.org/grpc/resolver" ) diff --git a/clients/grpcc/grpcc_resolver/util.go b/clients/grpcc/grpccresolver/util.go similarity index 98% rename from clients/grpcc/grpcc_resolver/util.go rename to clients/grpcc/grpccresolver/util.go index ba90e66ec..a2d640277 100644 --- a/clients/grpcc/grpcc_resolver/util.go +++ b/clients/grpcc/grpccresolver/util.go @@ -1,4 +1,4 @@ -package grpcc_resolver +package grpccresolver import ( "fmt" diff --git a/clients/grpcc/middleware.go b/clients/grpcc/middleware.go index aa3140552..a2520b37e 100644 --- a/clients/grpcc/middleware.go +++ b/clients/grpcc/middleware.go @@ -5,19 +5,20 @@ import ( "strings" "time" - "github.com/pubgo/funk/convert" - "github.com/pubgo/funk/errors" - "github.com/pubgo/funk/strutil" - "github.com/pubgo/lava/clients/grpcc/grpcc_config" - "github.com/pubgo/lava/core/lavacontexts" - "github.com/pubgo/lava/lava" - "github.com/pubgo/lava/pkg/grpcutil" - "github.com/pubgo/lava/pkg/httputil" + "github.com/pubgo/funk/v2/convert" + "github.com/pubgo/funk/v2/errors" + "github.com/pubgo/funk/v2/strutil" "github.com/rs/xid" "github.com/valyala/fasthttp" "google.golang.org/grpc" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" + + "github.com/pubgo/lava/v2/clients/grpcc/grpccconfig" + "github.com/pubgo/lava/v2/core/lavacontexts" + "github.com/pubgo/lava/v2/lava" + "github.com/pubgo/lava/v2/pkg/grpcutil" + "github.com/pubgo/lava/v2/pkg/httputil" ) func md2Head(md metadata.MD, header interface{ Add(key, value string) }) { @@ -29,9 +30,9 @@ func md2Head(md metadata.MD, header interface{ Add(key, value string) }) { } func head2md(header *lava.RequestHeader, md metadata.MD) { - header.VisitAll(func(key, value []byte) { + for key, value := range header.All() { md.Append(convert.BtoS(key), convert.BtoS(value)) - }) + } } func unaryInterceptor(middlewares []lava.Middleware) grpc.UnaryClientInterceptor { @@ -54,7 +55,7 @@ func unaryInterceptor(middlewares []lava.Middleware) grpc.UnaryClientInterceptor } unaryWrapper = lava.Chain(middlewares...).Middleware(unaryWrapper) - return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) (err error) { + return func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) (err error) { md, ok := metadata.FromOutgoingContext(ctx) if !ok { md = make(metadata.MD) @@ -66,7 +67,7 @@ func unaryInterceptor(middlewares []lava.Middleware) grpc.UnaryClientInterceptor }, func() string { return grpcutil.HeaderGet(md, "x-content-type") }, func() string { - return grpcc_config.DefaultContentType + return grpccconfig.DefaultContentType }) delete(md, "x-content-type") @@ -145,7 +146,7 @@ func streamInterceptor(middlewares []lava.Middleware) grpc.StreamClientIntercept }, func() string { return grpcutil.HeaderGet(md, "x-content-type") }, func() string { - return grpcc_config.DefaultContentType + return grpccconfig.DefaultContentType }) delete(md, "x-content-type") diff --git a/clients/grpcc/request.go b/clients/grpcc/request.go index 8b73d31da..632091f38 100644 --- a/clients/grpcc/request.go +++ b/clients/grpcc/request.go @@ -3,19 +3,19 @@ package grpcc import ( "google.golang.org/grpc" - "github.com/pubgo/lava/lava" + "github.com/pubgo/lava/v2/lava" ) var _ lava.Request = (*request)(nil) type request struct { resp grpc.ClientStream - reply interface{} + reply any ct string opts []grpc.CallOption method string service string - req interface{} + req any cc *grpc.ClientConn invoker grpc.UnaryInvoker streamer grpc.Streamer @@ -30,5 +30,5 @@ func (r *request) Service() string { return r.service } func (r *request) Endpoint() string { return r.method } func (r *request) ContentType() string { return r.ct } func (r *request) Header() *lava.RequestHeader { return r.header } -func (r *request) Payload() interface{} { return r.req } +func (r *request) Payload() any { return r.req } func (r *request) Stream() bool { return r.desc != nil } diff --git a/clients/grpcc/response.go b/clients/grpcc/response.go index fe30c2d3b..755b2ff90 100644 --- a/clients/grpcc/response.go +++ b/clients/grpcc/response.go @@ -3,7 +3,7 @@ package grpcc import ( "google.golang.org/grpc" - "github.com/pubgo/lava/lava" + "github.com/pubgo/lava/v2/lava" ) var _ lava.Response = (*response)(nil) @@ -11,9 +11,9 @@ var _ lava.Response = (*response)(nil) type response struct { header *lava.ResponseHeader stream grpc.ClientStream - resp interface{} + resp any } func (r *response) Stream() bool { return r.stream != nil } func (r *response) Header() *lava.ResponseHeader { return r.header } -func (r *response) Payload() interface{} { return r.resp } +func (r *response) Payload() any { return r.resp } diff --git a/clients/grpcc/util.go b/clients/grpcc/util.go index 1db160b3c..e93ec4b18 100644 --- a/clients/grpcc/util.go +++ b/clients/grpcc/util.go @@ -3,64 +3,65 @@ package grpcc import ( "fmt" - "github.com/pubgo/funk/errors" - "github.com/pubgo/funk/log" - "github.com/pubgo/lava/clients/grpcc/grpcc_config" - "github.com/pubgo/lava/clients/grpcc/grpcc_resolver" - "github.com/pubgo/lava/core/logging/logkey" - "github.com/pubgo/lava/lava" + "github.com/pubgo/funk/v2/errors" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/result" "github.com/rs/zerolog" "google.golang.org/grpc" + + "github.com/pubgo/lava/v2/clients/grpcc/grpccconfig" + "github.com/pubgo/lava/v2/clients/grpcc/grpccresolver" + "github.com/pubgo/lava/v2/core/logging/logkey" + "github.com/pubgo/lava/v2/lava" ) -func buildTarget(cfg *grpcc_config.ServiceCfg) string { +func buildTarget(cfg *grpccconfig.ServiceCfg) string { addr := cfg.Addr - scheme := grpcc_resolver.DirectScheme + scheme := grpccresolver.DirectScheme if cfg.Scheme != "" { scheme = cfg.Scheme } switch scheme { - case grpcc_resolver.DiscoveryScheme: - return grpcc_resolver.BuildDiscoveryTarget(addr) - case grpcc_resolver.DirectScheme: - return grpcc_resolver.BuildDirectTarget(cfg.Name, addr) - case grpcc_resolver.K8sScheme, grpcc_resolver.DnsScheme: + case grpccresolver.DiscoveryScheme: + return grpccresolver.BuildDiscoveryTarget(addr) + case grpccresolver.DirectScheme: + return grpccresolver.BuildDirectTarget(cfg.Name, addr) + case grpccresolver.K8sScheme, grpccresolver.DnsScheme: return fmt.Sprintf("dns:///%s", addr) default: return addr } } -func createConn(cfg *grpcc_config.Cfg, log log.Logger, mm []lava.Middleware) (_ grpc.ClientConnInterface, gErr error) { +func createConn(cfg *grpccconfig.Cfg, log log.Logger, mm []lava.Middleware) (r result.Result[grpc.ClientConnInterface]) { addr := buildTarget(cfg.Service) - var logMsg = func(e *zerolog.Event) { + logMsg := func(e *zerolog.Event) { e.Any(logkey.Service, cfg.Service) e.Any("config", cfg.Client) e.Str("addr", addr) } defer func() { - if gErr == nil { + if r.IsOK() { log.Info(). Func(logMsg).Msg("succeed to create grpc client") } else { - log.Err(gErr). + log.Err(r.GetErr()). Func(logMsg).Msg("failed to create grpc client") } }() - opts := append( - cfg.Client.ToOpts(), - grpc.WithResolvers(cfg.Resolvers...), - grpc.WithChainUnaryInterceptor(unaryInterceptor(mm)), - grpc.WithChainStreamInterceptor(streamInterceptor(mm)), - ) + opts := cfg.Client.ToOpts() + opts = append(opts, grpc.WithResolvers(cfg.Resolvers...)) + opts = append(opts, grpc.WithChainUnaryInterceptor(unaryInterceptor(mm))) + opts = append(opts, grpc.WithChainStreamInterceptor(streamInterceptor(mm))) + conn, err := grpc.NewClient(addr, opts...) if err != nil { - return nil, errors.Wrapf(err, "grpc dial failed, target=>%s", addr) + return r.WithErr(errors.Wrapf(err, "failed to dial grpc server, target=%s", addr)) } - return conn, nil + return r.WithValue(conn) } diff --git a/clients/resty/aaa.go b/clients/resty/aaa.go index 1256367f6..0cc70cf58 100644 --- a/clients/resty/aaa.go +++ b/clients/resty/aaa.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/pubgo/funk/result" + "github.com/pubgo/funk/v2/result" "github.com/valyala/fasthttp" ) diff --git a/clients/resty/client.go b/clients/resty/client.go index c9d5d01f9..6d0eb03c7 100644 --- a/clients/resty/client.go +++ b/clients/resty/client.go @@ -5,20 +5,19 @@ import ( "net/url" "sync" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/config" - "github.com/pubgo/funk/log" - "github.com/pubgo/funk/recovery" - "github.com/pubgo/funk/result" - "github.com/pubgo/funk/retry" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/config" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/result" + "github.com/pubgo/funk/v2/retry" "github.com/valyala/fasthttp" - "github.com/pubgo/lava/core/metrics" - "github.com/pubgo/lava/internal/middlewares/middleware_accesslog" - "github.com/pubgo/lava/internal/middlewares/middleware_metric" - "github.com/pubgo/lava/internal/middlewares/middleware_recovery" - "github.com/pubgo/lava/internal/middlewares/middleware_service_info" - "github.com/pubgo/lava/lava" + "github.com/pubgo/lava/v2/core/metrics" + "github.com/pubgo/lava/v2/internal/middlewares/middleware_accesslog" + "github.com/pubgo/lava/v2/internal/middlewares/middleware_metric" + "github.com/pubgo/lava/v2/internal/middlewares/middleware_recovery" + "github.com/pubgo/lava/v2/internal/middlewares/middleware_serviceinfo" + "github.com/pubgo/lava/v2/lava" ) type Params struct { @@ -29,9 +28,9 @@ type Params struct { func New(cfg *Config, p Params, mm ...lava.Middleware) *Client { cfg = config.MergeR(DefaultCfg(), cfg).Unwrap() middlewares := lava.Middlewares{ - middleware_service_info.New(), + middleware_serviceinfo.New(), middleware_metric.New(p.Metric), - middleware_accesslog.New(p.Log.WithFields(log.Map{"service": cfg.ServiceName})), + middleware_accesslog.New(p.Log.WithFields(log.Fields{"service": cfg.ServiceName})), middleware_recovery.New(), } middlewares = append(middlewares, mm...) @@ -45,8 +44,10 @@ func New(cfg *Config, p Params, mm ...lava.Middleware) *Client { backoff = retry.WithMaxRetries(cfg.DefaultRetryCount, backoff) } + handler := do(cfg) + handler = lava.Chain(middlewares...).Middleware(handler) return &Client{ - do: lava.Chain(middlewares...).Middleware(do(cfg)), + do: handler, log: p.Log, cfg: cfg, baseUrl: assert.Must1(url.Parse(cfg.BaseUrl)), @@ -67,20 +68,17 @@ type Client struct { } func (c *Client) Do(ctx context.Context, req *Request) (r result.Result[*fasthttp.Response]) { - defer recovery.Result(&r) + defer result.Recovery(&r) - reqErr := doRequest(c, req) - if reqErr.IsErr() { - return r.WithErr(reqErr.Err()) + if doRequest(c, req).ValueTo(&req.req).Throw(&r) { + return r } - req.req = reqErr.Unwrap() - request := &requestImpl{service: c.cfg.ServiceName, req: req} resp, err := c.do(ctx, request) if err != nil { return r.WithErr(err) } - return r.WithVal(resp.(*responseImpl).resp) + return r.WithValue(resp.(*responseImpl).resp) } diff --git a/clients/resty/config.go b/clients/resty/config.go index 2b818cef7..4a9a7e63f 100644 --- a/clients/resty/config.go +++ b/clients/resty/config.go @@ -5,7 +5,7 @@ import ( "net" "time" - "github.com/pubgo/funk/version" + "github.com/pubgo/funk/v2/buildinfo/version" "github.com/valyala/fasthttp" "github.com/valyala/fasthttp/fasthttpproxy" "golang.org/x/net/http/httpproxy" diff --git a/clients/resty/jar.go b/clients/resty/jar.go index c3d6c421a..abd62c9bb 100644 --- a/clients/resty/jar.go +++ b/clients/resty/jar.go @@ -8,10 +8,10 @@ import ( "time" "github.com/goccy/go-json" - "github.com/pubgo/funk/log" + "github.com/pubgo/funk/v2/log" "github.com/valyala/fasthttp" - "github.com/pubgo/lava/lava" + "github.com/pubgo/lava/v2/lava" ) func NewJar(log log.Logger) *Jar { @@ -41,14 +41,14 @@ func (j *Jar) Middleware(next lava.HandlerFunc) lava.HandlerFunc { return nil, err } - rsp.Header().VisitAllCookie(func(key, value []byte) { + for _, value := range rsp.Header().All() { acquireCookie := fasthttp.AcquireCookie() if err := acquireCookie.ParseBytes(value); err != nil { j.log.Err(err, ctx).Msg("failed to parse cookie") } else { j.cookies[string(acquireCookie.Key())] = acquireCookie } - }) + } return rsp, err } diff --git a/clients/resty/middleware.go b/clients/resty/middleware.go index 69fefa30d..52ed25ed9 100644 --- a/clients/resty/middleware.go +++ b/clients/resty/middleware.go @@ -4,10 +4,10 @@ import ( "fmt" "github.com/gofiber/utils" - "github.com/pubgo/funk/convert" + "github.com/pubgo/funk/v2/convert" "github.com/valyala/fasthttp" - "github.com/pubgo/lava/lava" + "github.com/pubgo/lava/v2/lava" ) var _ lava.Request = (*requestImpl)(nil) @@ -27,7 +27,7 @@ func (r *requestImpl) Service() string { return r.service } func (r *requestImpl) Endpoint() string { return utils.UnsafeString(r.req.req.URI().Path()) } func (r *requestImpl) ContentType() string { return convert.B2S(r.req.req.Header.ContentType()) } func (r *requestImpl) Header() *lava.RequestHeader { return &r.req.req.Header } -func (r *requestImpl) Payload() interface{} { return r.req.req.Body() } +func (r *requestImpl) Payload() any { return r.req.req.Body() } func (r *requestImpl) Stream() bool { return false } var _ lava.Response = (*responseImpl)(nil) @@ -37,5 +37,5 @@ type responseImpl struct { } func (r *responseImpl) Header() *lava.ResponseHeader { return &r.resp.Header } -func (r *responseImpl) Payload() interface{} { return r.resp.Body() } +func (r *responseImpl) Payload() any { return r.resp.Body() } func (r *responseImpl) Stream() bool { return false } diff --git a/clients/resty/request.go b/clients/resty/request.go index 07ab27b6f..d0dd32412 100644 --- a/clients/resty/request.go +++ b/clients/resty/request.go @@ -5,13 +5,13 @@ import ( "net/url" "regexp" - "github.com/pubgo/funk/retry" + "github.com/pubgo/funk/v2/retry" "github.com/valyala/fasthttp" ) var regParam = regexp.MustCompile(`{.+}`) -type RequestConfig struct { +type RequestSpec struct { Header map[string]string Path string Method string @@ -20,14 +20,21 @@ type RequestConfig struct { EnableAuth bool } -func NewRequest(cfg *RequestConfig) *Request { - r := &Request{cfg: cfg} +func (r RequestSpec) CreateRequest() *Request { return NewRequest(&r) } + +func NewRequest(cfg *RequestSpec) *Request { + r := &Request{ + cfg: cfg, + header: make(http.Header), + query: make(url.Values), + params: make(map[string]any), + } return r } type Request struct { req *fasthttp.Request - cfg *RequestConfig + cfg *RequestSpec header http.Header query url.Values params map[string]any diff --git a/clients/resty/util.go b/clients/resty/util.go index 5c098666e..1d1eb2ae2 100644 --- a/clients/resty/util.go +++ b/clients/resty/util.go @@ -12,16 +12,15 @@ import ( "strings" "github.com/goccy/go-json" - "github.com/pubgo/funk/convert" - "github.com/pubgo/funk/result" - "github.com/pubgo/funk/retry" - "github.com/valyala/bytebufferpool" + "github.com/pubgo/funk/v2/convert" + "github.com/pubgo/funk/v2/result" + "github.com/pubgo/funk/v2/retry" "github.com/valyala/fasthttp" "github.com/valyala/fasttemplate" "golang.org/x/net/http/httpguts" - "github.com/pubgo/lava/lava" - "github.com/pubgo/lava/pkg/httputil" + "github.com/pubgo/lava/v2/lava" + "github.com/pubgo/lava/v2/pkg/httputil" ) func do(cfg *Config) lava.HandlerFunc { @@ -58,16 +57,16 @@ func do(cfg *Config) lava.HandlerFunc { } } -func getBodyReader(rawBody interface{}) ([]byte, error) { +func getBodyReader(rawBody any) (r result.Result[[]byte]) { switch body := rawBody.(type) { case nil: - return nil, nil + return r case *bytes.Buffer: - return body.Bytes(), nil + return r.WithValue(body.Bytes()) case []byte: - return body, nil + return r.WithValue(body) case string: - return convert.StoB(body), nil + return r.WithValue(convert.StoB(body)) // We prioritize *bytes.Reader here because we don't really want to // deal with it seeking so want it to match here instead of the @@ -75,46 +74,39 @@ func getBodyReader(rawBody interface{}) ([]byte, error) { case *bytes.Reader: buf, err := io.ReadAll(body) if err != nil { - return nil, err + return r.WithErr(err) } - return buf, nil + return r.WithValue(buf) // Compat case case io.ReadSeeker: _, err := body.Seek(0, 0) if err != nil { - return nil, err + return r.WithErr(err) } buf, err := io.ReadAll(body) if err != nil { - return nil, err + return r.WithErr(err) } - return buf, nil + return r.WithValue(buf) case url.Values: - return convert.StoB(body.Encode()), nil + return r.WithValue(convert.StoB(body.Encode())) // Read all in so we can reset case io.Reader: buf, err := io.ReadAll(body) if err != nil { - return nil, err + return r.WithErr(err) } - return buf, nil + return r.WithValue(buf) case json.Marshaler: - return body.MarshalJSON() + return result.Wrap(body.MarshalJSON()) default: - bb := bytebufferpool.Get() - defer bytebufferpool.Put(bb) - - if err := json.NewEncoder(bb).Encode(rawBody); err != nil { - return nil, err - } - - return bb.Bytes(), nil + return result.Wrap(json.Marshal(rawBody)) } } @@ -134,36 +126,30 @@ func handleHeader(c *Client, req *Request) { } } -func handlePath(c *Client, req *Request) (path string, err error) { +func handlePath(c *Client, req *Request) (r result.Result[string]) { reqConf := req.cfg reqUrl := c.baseUrl.JoinPath(reqConf.Path) req.operation = reqUrl.Path - path = reqUrl.Path - if v, ok := c.pathTemplates.Load(reqUrl.Path); ok { - if v != nil { - path, err = pathTemplateRun(v.(*fasttemplate.Template), req.params) - if err != nil { - return - } - } + if v, ok := c.pathTemplates.Load(reqUrl.Path); ok && v != nil { + return result.Wrap(pathTemplateRun(v.(*fasttemplate.Template), req.params)) } else { if regParam.MatchString(reqUrl.Path) { pathTemplate, err := fasttemplate.NewTemplate(reqUrl.Path, "{", "}") if err != nil { - return "", err + return r.WithErr(err) } c.pathTemplates.Store(reqUrl.Path, pathTemplate) } else { - c.pathTemplates.Store(reqUrl.Path, nil) + return r.WithValue(reqUrl.Path) } } - return + return r } -func handleContentType(c *Client, req *Request) (string, error) { +func handleContentType(c *Client, req *Request) (r result.Result[string]) { defaultConf := c.cfg reqConf := req.cfg @@ -181,27 +167,23 @@ func handleContentType(c *Client, req *Request) (string, error) { } if contentType == "" { - return "", errors.New("context-type header is empty") + return r.WithErr(errors.New("content-type header is empty")) } - return contentType, nil + return r.WithValue(contentType) } // doRequest data:[bytes|string|map|struct] func doRequest(c *Client, req *Request) (rsp result.Result[*fasthttp.Request]) { r := fasthttp.AcquireRequest() - ct, err := handleContentType(c, req) - if err != nil { - return rsp.WithErr(err) + if handleContentType(c, req). + IfOK(func(val string) { + r.Header.Set(httputil.HeaderContentType, val) + }). + Throw(&rsp) { + return rsp } - r.Header.Set(httputil.HeaderContentType, ct) - - path, err := handlePath(c, req) - if err != nil { - return rsp.WithErr(err) - } - r.SetRequestURI(path) mth := req.cfg.Method if mth == "" { @@ -210,11 +192,13 @@ func doRequest(c *Client, req *Request) (rsp result.Result[*fasthttp.Request]) { r.Header.SetMethod(mth) - bodyRaw, err := getBodyReader(req.body) - if err != nil { - return rsp.WithErr(err) + if getBodyReader(req.body). + IfOK(func(val []byte) { + r.SetBodyRaw(val) + }). + Throw(&rsp) { + return rsp } - r.SetBodyRaw(bodyRaw) handleHeader(c, req) @@ -227,11 +211,11 @@ func doRequest(c *Client, req *Request) (rsp result.Result[*fasthttp.Request]) { // enable auth if c.cfg.EnableAuth || req.cfg.EnableAuth { if c.cfg.BasicToken != "" { - r.Header.Set("Authentication", "Basic "+c.cfg.BasicToken) + r.Header.Set(httputil.HeaderAuthorization, "Basic "+c.cfg.BasicToken) } if c.cfg.JwtToken != "" { - r.Header.Set("Authentication", "Bearer "+c.cfg.JwtToken) + r.Header.Set(httputil.HeaderAuthorization, "Bearer "+c.cfg.JwtToken) } } @@ -239,7 +223,14 @@ func doRequest(c *Client, req *Request) (rsp result.Result[*fasthttp.Request]) { defer fasthttp.ReleaseURI(uri) uri.SetScheme(c.baseUrl.Scheme) uri.SetHost(c.baseUrl.Host) - uri.SetPath(path) + if handlePath(c, req). + IfOK(func(val string) { + uri.SetPath(val) + }). + Throw(&rsp) { + return rsp + } + if req.query != nil { uri.SetQueryString(req.query.Encode()) } @@ -255,7 +246,7 @@ func doRequest(c *Client, req *Request) (rsp result.Result[*fasthttp.Request]) { } } - return rsp.WithVal(r) + return rsp.WithValue(r) } func filterFlags(content string) string { diff --git a/cmds/app/cmd.go b/cmds/app/cmd.go deleted file mode 100644 index 6ff12195b..000000000 --- a/cmds/app/cmd.go +++ /dev/null @@ -1,100 +0,0 @@ -package app - -import ( - "os" - "sort" - - "github.com/pubgo/dix" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/recovery" - "github.com/pubgo/funk/running" - "github.com/pubgo/funk/version" - cli "github.com/urfave/cli/v3" - - "github.com/pubgo/lava/clients/grpcc/grpcc_resolver" - "github.com/pubgo/lava/cmds/depcmd" - "github.com/pubgo/lava/cmds/grpcservercmd" - "github.com/pubgo/lava/cmds/healthcmd" - "github.com/pubgo/lava/cmds/httpservercmd" - "github.com/pubgo/lava/cmds/versioncmd" - "github.com/pubgo/lava/core/discovery" - "github.com/pubgo/lava/core/flags" - "github.com/pubgo/lava/core/lifecycle" - "github.com/pubgo/lava/core/logging" - "github.com/pubgo/lava/core/metrics" - "github.com/pubgo/lava/core/scheduler" - "github.com/pubgo/lava/core/signal" - "github.com/pubgo/lava/internal/middlewares/middleware_accesslog" - "github.com/pubgo/lava/internal/middlewares/middleware_metric" - "github.com/pubgo/lava/pkg/cmdutil" - - _ "github.com/pubgo/lava/core/debug/debug" - // debug - _ "github.com/pubgo/lava/core/debug/pprof" - _ "github.com/pubgo/lava/core/debug/process" - _ "github.com/pubgo/lava/core/debug/statsviz" - _ "github.com/pubgo/lava/core/debug/trace" - _ "github.com/pubgo/lava/core/debug/vars" - _ "github.com/pubgo/lava/core/debug/version" - - // metric - _ "github.com/pubgo/lava/core/metrics/drivers/prometheus" - - // encoding - _ "github.com/pubgo/lava/core/encoding/protobuf" - _ "github.com/pubgo/lava/core/encoding/protojson" - - // logging - _ "github.com/pubgo/lava/core/logging/logext/grpclog" - _ "github.com/pubgo/lava/core/logging/logext/stdlog" - - _ "go.uber.org/automaxprocs" -) - -var defaultProviders = []any{ - grpcc_resolver.NewDirectBuilder, - grpcc_resolver.NewDiscoveryBuilder, - discovery.NewNoopDiscovery, - - middleware_accesslog.New, - middleware_metric.New, - logging.New, - metrics.New, - - lifecycle.New, - scheduler.New, -} - -func NewBuilder(opts ...dix.Option) *dix.Dix { - di := dix.New(append(opts, dix.WithValuesNull())...) - for _, p := range defaultProviders { - di.Provide(p) - } - return di -} - -func Run(di *dix.Dix) { - defer recovery.Exit() - - di.Provide(versioncmd.New) - di.Provide(healthcmd.New) - di.Provide(depcmd.New) - di.Provide(grpcservercmd.New) - di.Provide(httpservercmd.New) - - di.Inject(func(cmd []*cli.Command) { - app := &cli.Command{ - Name: version.Project(), - Suggest: true, - UseShortOptionHandling: true, - Usage: cmdutil.UsageDesc("%s service", version.Project()), - Version: version.Version(), - Flags: flags.GetFlags(), - Commands: cmd, - ExtraInfo: running.GetSysInfo, - } - - sort.Sort(cli.FlagsByName(app.Flags)) - assert.Must(app.Run(signal.Context(), os.Args)) - }) -} diff --git a/cmds/depcmd/cmd.go b/cmds/depcmd/cmd.go index bdc7a1557..ee88a40ba 100644 --- a/cmds/depcmd/cmd.go +++ b/cmds/depcmd/cmd.go @@ -7,21 +7,22 @@ import ( "runtime/debug" "github.com/olekukonko/tablewriter" - "github.com/pubgo/dix" - "github.com/pubgo/funk/pretty" - "github.com/pubgo/funk/recovery" - "github.com/pubgo/funk/running" - "github.com/pubgo/funk/version" + "github.com/pubgo/dix/v2" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/buildinfo/version" + "github.com/pubgo/funk/v2/pretty" + "github.com/pubgo/funk/v2/recovery" + "github.com/pubgo/funk/v2/running" cli "github.com/urfave/cli/v3" - "github.com/pubgo/lava/pkg/cmdutil" + "github.com/pubgo/lava/v2/pkg/cliutil" ) func New(di *dix.Dix) *cli.Command { return &cli.Command{ Name: "dep", Usage: "Print the dependency package information", - Description: cmdutil.ExampleFmt( + Description: cliutil.ExampleDesc( "lava dep", "lava dep json", "lava dep t"), @@ -45,16 +46,13 @@ func New(di *dix.Dix) *cli.Command { pretty.Println(running.GetSysInfo()) case "table", "tb", "t": table := tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{"path", "Version", "Replace"}) - table.Append([]string{info.Main.Path, version.Version(), replace(info.Main.Replace)}) + table.Header([]string{"path", "Version", "Replace"}) + assert.Must(table.Append([]string{info.Main.Path, version.Version(), replace(info.Main.Replace)})) for _, dep := range info.Deps { - table.Append([]string{dep.Path, dep.Version, replace(dep.Replace)}) + assert.Must(table.Append([]string{dep.Path, dep.Version, replace(dep.Replace)})) } - table.Render() - case "di": - fmt.Println(di.Graph().Objects) - fmt.Println(di.Graph().Providers) + assert.Must(table.Render()) } return nil }, diff --git a/cmds/fileservercmd/cmd.go b/cmds/fileservercmd/cmd.go new file mode 100644 index 000000000..360b76f4a --- /dev/null +++ b/cmds/fileservercmd/cmd.go @@ -0,0 +1,54 @@ +package fileservercmd + +import ( + "context" + "fmt" + "os" + + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/recovery" + "github.com/pubgo/funk/v2/result" + "github.com/pubgo/funk/v2/running" + cli "github.com/urfave/cli/v3" + "github.com/valyala/fasthttp" +) + +func New() *cli.Command { + return &cli.Command{ + Name: "fileserver", + Usage: "serve `pwd` via http at *:8080", + Flags: []cli.Flag{}, + ArgsUsage: "[dir]", + Action: func(ctx context.Context, command *cli.Command) error { + defer recovery.Exit() + + wd := result.Wrap(os.Getwd()).Unwrap() + if command.Args().Len() > 0 { + wd = command.Args().Get(0) + } + + port := running.HttpPort() + log.Info().Msgf("file dir: %s", wd) + log.Info().Msgf("http://localhost:%v", port) + + fs := &fasthttp.FS{ + Root: wd, + IndexNames: []string{"index.html"}, + GenerateIndexPages: true, + Compress: false, + AcceptByteRange: true, + // PathRewrite: fasthttp.NewVHostPathRewriter(0), + } + + s := &fasthttp.Server{ + Handler: fs.NewRequestHandler(), + Logger: log.NewStd(log.GetLogger("fileserver")), + } + go func() { assert.Must(s.ListenAndServe(fmt.Sprintf(":%v", port))) }() + + <-ctx.Done() + return s.ShutdownWithContext(ctx) + }, + } +} diff --git a/cmds/gencmd/main.go b/cmds/gencmd/main.go index 0128deecc..29e6c27e4 100644 --- a/cmds/gencmd/main.go +++ b/cmds/gencmd/main.go @@ -6,7 +6,7 @@ import ( "github.com/urfave/cli/v3" - "github.com/pubgo/funk/recovery" + "github.com/pubgo/funk/v2/recovery" ) func New() *cli.Command { diff --git a/cmds/grpcservercmd/cmd.go b/cmds/grpcservercmd/cmd.go index ca9a0373d..781d64d0b 100644 --- a/cmds/grpcservercmd/cmd.go +++ b/cmds/grpcservercmd/cmd.go @@ -2,23 +2,35 @@ package grpcservercmd import ( "context" - "github.com/pubgo/dix" - "github.com/pubgo/funk/recovery" - "github.com/pubgo/funk/version" - "github.com/pubgo/lava/pkg/cmdutil" - "github.com/pubgo/lava/servers/grpcs" + + "github.com/pubgo/dix/v2" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/buildinfo/version" "github.com/urfave/cli/v3" + + "github.com/pubgo/lava/v2/core/lifecycle" + "github.com/pubgo/lava/v2/core/supervisor" + "github.com/pubgo/lava/v2/pkg/cliutil" + "github.com/pubgo/lava/v2/servers/grpcs" ) func New(di *dix.Dix) *cli.Command { return &cli.Command{ Name: "grpc", - Usage: cmdutil.UsageDesc("%s grpc service", version.Project()), + Usage: cliutil.UsageDesc("grpc service %s(%s)", version.Project(), version.Version()), Action: func(ctx context.Context, command *cli.Command) error { - defer recovery.Exit() - srv := dix.Inject(di, grpcs.New()) - srv.Run() - return nil + di.Provide(grpcs.New) + params := dix.Inject(di, new(struct { + LC lifecycle.Getter + Services []supervisor.Service + })) + + manager := supervisor.Default(params.LC) + for _, svc := range params.Services { + assert.Exit(manager.Add(svc)) + } + + return manager.Run(ctx) }, } } diff --git a/cmds/healthcmd/cmd.go b/cmds/healthcmd/cmd.go index 5e364d898..56ab2fa9f 100644 --- a/cmds/healthcmd/cmd.go +++ b/cmds/healthcmd/cmd.go @@ -7,20 +7,20 @@ import ( "net/http" "os" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/recovery" - "github.com/pubgo/funk/version" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/buildinfo/version" + "github.com/pubgo/funk/v2/recovery" "github.com/urfave/cli/v3" - "github.com/pubgo/lava/pkg/cmdutil" - "github.com/pubgo/lava/pkg/netutil" + "github.com/pubgo/lava/v2/pkg/cliutil" + "github.com/pubgo/lava/v2/pkg/netutil" ) func New() *cli.Command { return &cli.Command{ Name: "health", - Usage: cmdutil.UsageDesc("%s health check", version.Project()), - Description: cmdutil.ExampleFmt( + Usage: cliutil.UsageDesc("%s health check", version.Project()), + Description: cliutil.ExampleDesc( "lava health", "lava health localhost:8080", ), diff --git a/cmds/httpservercmd/cmd.go b/cmds/httpservercmd/cmd.go index 896ba2d23..2d169f5a4 100644 --- a/cmds/httpservercmd/cmd.go +++ b/cmds/httpservercmd/cmd.go @@ -2,23 +2,35 @@ package httpservercmd import ( "context" - "github.com/pubgo/dix" - "github.com/pubgo/funk/recovery" - "github.com/pubgo/funk/version" - "github.com/pubgo/lava/pkg/cmdutil" - "github.com/pubgo/lava/servers/https" + + "github.com/pubgo/dix/v2" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/buildinfo/version" "github.com/urfave/cli/v3" + + "github.com/pubgo/lava/v2/core/lifecycle" + "github.com/pubgo/lava/v2/core/supervisor" + "github.com/pubgo/lava/v2/pkg/cliutil" + "github.com/pubgo/lava/v2/servers/https" ) func New(di *dix.Dix) *cli.Command { return &cli.Command{ Name: "http", - Usage: cmdutil.UsageDesc("%s http service", version.Project()), + Usage: cliutil.UsageDesc("%s http service", version.Project()), Action: func(ctx context.Context, command *cli.Command) error { - defer recovery.Exit() - srv := dix.Inject(di, https.New()) - srv.Run() - return nil + di.Provide(https.New) + params := dix.Inject(di, new(struct { + LC lifecycle.Getter + Services []supervisor.Service + })) + + manager := supervisor.Default(params.LC) + for _, svc := range params.Services { + assert.Exit(manager.Add(svc)) + } + + return manager.Run(ctx) }, } } diff --git a/cmds/lava/main.go b/cmds/lava/main.go deleted file mode 100644 index 660cd81c5..000000000 --- a/cmds/lava/main.go +++ /dev/null @@ -1,5 +0,0 @@ -package main - -func main() { - // https://github.com/beego/bee -} diff --git a/cmds/protoc-gen-lava/internal/gen.go b/cmds/protoc-gen-lava/internal/gen.go deleted file mode 100644 index 81fe1d0da..000000000 --- a/cmds/protoc-gen-lava/internal/gen.go +++ /dev/null @@ -1,105 +0,0 @@ -package internal - -import ( - "fmt" - "reflect" - "strings" - - "github.com/dave/jennifer/jen" - "github.com/pubgo/lava/core/rpcmeta" - "github.com/pubgo/lava/pkg/proto/lavapbv1" - "google.golang.org/protobuf/compiler/protogen" - "google.golang.org/protobuf/proto" -) - -var rpcMetaPkg = reflect.TypeOf(rpcmeta.RpcMeta{}).PkgPath() - -type rpcMetaInfo struct { - srv *protogen.Service - mth *protogen.Method - meta *lavapbv1.RpcMeta -} - -// GenerateFile generates a .errors.pb.go file containing service definitions. -func GenerateFile(gen *protogen.Plugin, file *protogen.File) (g *protogen.GeneratedFile) { - filename := file.GeneratedFilenamePrefix + ".lava.pb.go" - genFile := jen.NewFile(string(file.GoPackageName)) - genFile.HeaderComment("Code generated by protoc-gen-lava. DO NOT EDIT.") - genFile.HeaderComment("versions:") - genFile.HeaderComment(fmt.Sprintf(" - protoc-gen-lava %s", Version)) - genFile.HeaderComment(fmt.Sprintf(" - protoc %s", protocVersion(gen))) - if file.Proto.GetOptions().GetDeprecated() { - genFile.HeaderComment(fmt.Sprintf("%s is a deprecated file.", file.Desc.Path())) - } else { - genFile.HeaderComment(fmt.Sprintf("source: %s", file.Desc.Path())) - } - - g = gen.NewGeneratedFile(filename, file.GoImportPath) - g.Skip() - - if len(file.Services) == 0 { - return g - } - - var rpcMetas = make(map[string]*rpcMetaInfo) - for _, srv := range file.Services { - for _, m := range srv.Methods { - rpcMeta, ok := proto.GetExtension(m.Desc.Options(), lavapbv1.E_Options).(*lavapbv1.RpcMeta) - if !ok || rpcMeta == nil { - continue - } - - if rpcMeta.Name == "" { - gen.Error(fmt.Errorf("rpc meta name is null, method=%s", m.GoName)) - return - } - - if rpcMetas[rpcMeta.Name] != nil { - gen.Error(fmt.Errorf("rpc meta name:%s already exists, method=%s", rpcMeta.Name, m.GoName)) - return - } - - rpcMetas[rpcMeta.Name] = &rpcMetaInfo{srv: srv, mth: m, meta: rpcMeta} - } - } - - if len(rpcMetas) == 0 { - return g - } - - g.Unskip() - for _, meta := range rpcMetas { - srvInfo := meta.srv - keyPrefix := strings.ReplaceAll(srvInfo.GoName, "InnerService", "") - keyPrefix = strings.ReplaceAll(keyPrefix, "Inner", "") + "Service" - - genFile.Var().Op("_").Op("=").Qual(rpcMetaPkg, "Register").Call( - jen.Op("&").Qual(rpcMetaPkg, "RpcMeta").Values( - jen.Dict{ - jen.Id("Input"): jen.New(getPkg(file, meta.mth.Input.GoIdent)), - jen.Id("Output"): jen.New(getPkg(file, meta.mth.Output.GoIdent)), - jen.Id("Name"): jen.Lit(meta.meta.Name), - jen.Id("Method"): jen.Lit(fmt.Sprintf("%s/%s", meta.srv.Desc.FullName(), meta.mth.GoName)), - jen.Id("Tags"): jen.Map(jen.String()).String().Values( - jen.DictFunc(func(dict jen.Dict) { - for k, v := range meta.meta.Tags { - dict[jen.Lit(k)] = jen.Lit(v) - } - }), - ), - }, - )) - } - - g.P(genFile.GoString()) - return g -} - -func getPkg(file *protogen.File, goIdent protogen.GoIdent) *jen.Statement { - var pkgName = "" - if file.GoImportPath != goIdent.GoImportPath { - pkgName = string(goIdent.GoImportPath) - } - - return jen.Qual(pkgName, goIdent.GoName) -} diff --git a/cmds/protoc-gen-lava/internal/version.go b/cmds/protoc-gen-lava/internal/version.go deleted file mode 100644 index d71300e2f..000000000 --- a/cmds/protoc-gen-lava/internal/version.go +++ /dev/null @@ -1,21 +0,0 @@ -package internal - -import ( - "fmt" - - "google.golang.org/protobuf/compiler/protogen" -) - -const Version = "v0.0.3" - -func protocVersion(gen *protogen.Plugin) string { - v := gen.Request.GetCompilerVersion() - if v == nil { - return "(unknown)" - } - var suffix string - if s := v.GetSuffix(); s != "" { - suffix = "-" + s - } - return fmt.Sprintf("v%d.%d.%d%s", v.GetMajor(), v.GetMinor(), v.GetPatch(), suffix) -} diff --git a/cmds/protoc-gen-lava/main.go b/cmds/protoc-gen-lava/main.go deleted file mode 100644 index 964990e65..000000000 --- a/cmds/protoc-gen-lava/main.go +++ /dev/null @@ -1,26 +0,0 @@ -package main - -import ( - "flag" - - "github.com/pubgo/lava/cmds/protoc-gen-lava/internal" - "google.golang.org/protobuf/compiler/protogen" - "google.golang.org/protobuf/types/pluginpb" -) - -var _ = flag.String("version", internal.Version, "version") - -func main() { - flag.Parse() - - protogen.Options{ParamFunc: flag.CommandLine.Set}. - Run(func(plugin *protogen.Plugin) error { - plugin.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL) - - for _, name := range plugin.Request.FileToGenerate { - internal.GenerateFile(plugin, plugin.FilesByPath[name]) - } - - return nil - }) -} diff --git a/cmds/schedulercmd/cmd.go b/cmds/schedulercmd/cmd.go new file mode 100644 index 000000000..b69664c1e --- /dev/null +++ b/cmds/schedulercmd/cmd.go @@ -0,0 +1,43 @@ +package schedulercmd + +import ( + "context" + + "github.com/pubgo/dix/v2" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/buildinfo/version" + "github.com/urfave/cli/v3" + + "github.com/pubgo/lava/v2/core/lifecycle" + "github.com/pubgo/lava/v2/core/scheduler" + "github.com/pubgo/lava/v2/core/scheduler/schedulerbuilder" + "github.com/pubgo/lava/v2/core/scheduler/schedulerdebug" + "github.com/pubgo/lava/v2/core/supervisor" + "github.com/pubgo/lava/v2/pkg/cliutil" + "github.com/pubgo/lava/v2/servers/https" +) + +func New(di *dix.Dix) *cli.Command { + return &cli.Command{ + Name: "scheduler", + Usage: cliutil.UsageDesc("crontab scheduler service %s(%s)", version.Project(), version.Version()), + Action: func(ctx context.Context, command *cli.Command) error { + di.Provide(schedulerbuilder.NewService) + di.Provide(https.New) + params := dix.Inject(di, new(struct { + LC lifecycle.Getter + Services []supervisor.Service + Manager scheduler.JobManager + })) + + schedulerdebug.Init(params.Manager) + + manager := supervisor.Default(params.LC) + for _, svc := range params.Services { + assert.Exit(manager.Add(svc)) + } + + return manager.Run(ctx) + }, + } +} diff --git a/cmds/versioncmd/cmd.go b/cmds/versioncmd/cmd.go index 92053ae70..c93fe6f8f 100644 --- a/cmds/versioncmd/cmd.go +++ b/cmds/versioncmd/cmd.go @@ -3,19 +3,20 @@ package versioncmd import ( "context" "fmt" + + "github.com/pubgo/funk/v2/buildinfo/version" + "github.com/pubgo/funk/v2/recovery" + "github.com/pubgo/funk/v2/running" "github.com/urfave/cli/v3" - "github.com/pubgo/funk/recovery" - "github.com/pubgo/funk/running" - "github.com/pubgo/funk/version" - "github.com/pubgo/lava/pkg/cmdutil" + "github.com/pubgo/lava/v2/pkg/cliutil" ) func New() *cli.Command { return &cli.Command{ Name: "version", Aliases: []string{"v"}, - Usage: cmdutil.UsageDesc("%s version info", version.Project()), + Usage: cliutil.UsageDesc("%s version info", version.Project()), Action: func(ctx context.Context, command *cli.Command) error { defer recovery.Exit() fmt.Println("project:", version.Project()) diff --git a/core/annotation/openapi.go b/core/annotation/openapi.go deleted file mode 100644 index 1a39bc78a..000000000 --- a/core/annotation/openapi.go +++ /dev/null @@ -1,13 +0,0 @@ -package annotation - -import "github.com/pubgo/lava/lava" - -var _ lava.Annotation = (*Openapi)(nil) - -type Openapi struct { - ServiceName string `json:"service_name"` -} - -func (s Openapi) Name() string { - return "Openapi" -} diff --git a/core/debug/_docs.go b/core/debug/_.go similarity index 56% rename from core/debug/_docs.go rename to core/debug/_.go index c59e48f9f..927b1681d 100644 --- a/core/debug/_docs.go +++ b/core/debug/_.go @@ -1,3 +1,7 @@ package debug // https://github.com/go-echarts/statsview + +import ( + _ "github.com/fasthttp/router" +) diff --git a/core/debug/debug/debug.go b/core/debug/debug/debug.go index ef7b07209..186bd77d0 100644 --- a/core/debug/debug/debug.go +++ b/core/debug/debug/debug.go @@ -8,7 +8,8 @@ import ( g "github.com/maragudk/gomponents" c "github.com/maragudk/gomponents/components" h "github.com/maragudk/gomponents/html" - "github.com/pubgo/lava/core/debug" + + "github.com/pubgo/lava/v2/core/debug" ) func init() { @@ -17,8 +18,9 @@ func init() { func initDebug() { debug.Get("/", func(ctx *fiber.Ctx) error { - pathMap := make(map[string]interface{}) - stack := debug.App().Stack() + pathMap := make(map[string]any) + + stack := ctx.App().Stack() for m := range stack { for r := range stack[m] { route := stack[m][r] diff --git a/core/debug/debug/mux.go b/core/debug/debug/mux.go index 4fac20b15..6a185feb4 100644 --- a/core/debug/debug/mux.go +++ b/core/debug/debug/mux.go @@ -7,17 +7,19 @@ import ( "sync" "github.com/gofiber/fiber/v2" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/config" - "github.com/pubgo/funk/errors" - "github.com/pubgo/funk/log" - "github.com/pubgo/funk/recovery" - "github.com/pubgo/funk/result" - "github.com/pubgo/funk/running" - "github.com/pubgo/funk/strutil" - "github.com/pubgo/lava/core/debug" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/config" + "github.com/pubgo/funk/v2/errors" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/recovery" + "github.com/pubgo/funk/v2/result" + "github.com/pubgo/funk/v2/running" + "github.com/pubgo/funk/v2/strutil" + "github.com/samber/lo" "github.com/valyala/fasthttp" "gopkg.in/yaml.v3" + + "github.com/pubgo/lava/v2/core/debug" ) var ( @@ -26,13 +28,13 @@ var ( ) func init() { - log.Info().Str("password", running.InstanceID).Msg("debug password") + // log.Info().Str("password", passwd).Msg("debug password") debug.App().Use(func(c *fiber.Ctx) (gErr error) { defer recovery.Recovery(func(err error) { - err = errors.WrapTag(err, - errors.T("headers", c.GetReqHeaders()), - errors.T("url", c.Request().URI().String()), - ) + err = errors.WrapTags(err, errors.Tags{ + "headers": c.GetReqHeaders(), + "url": c.Request().URI().String(), + }) gErr = c.JSON(err) }) @@ -53,8 +55,8 @@ func init() { if host != "localhost" && host != "127.0.0.1" { if token != passwd { err := errors.New("token 不存在或者密码不对") - if ret := result.Of(c.WriteString(err.Error())); ret.IsErr() { - return errors.WrapCaller(ret.Err()) + if result.ThrowErr(&gErr, lo.T2(c.WriteString(err.Error())).B) { + return gErr } if err := c.SendStatus(http.StatusInternalServerError); err != nil { diff --git a/core/debug/gops/_.go b/core/debug/gops/_.go new file mode 100644 index 000000000..16086fbe5 --- /dev/null +++ b/core/debug/gops/_.go @@ -0,0 +1,3 @@ +package gops + +//_ https://github.com/openziti/agent/blob/main/client.go diff --git a/core/debug/gops/debug.go b/core/debug/gops/debug.go new file mode 100644 index 000000000..40b97b515 --- /dev/null +++ b/core/debug/gops/debug.go @@ -0,0 +1,15 @@ +package gops + +import ( + "github.com/google/gops/agent" + "github.com/pubgo/funk/v2/log" +) + +func init() { + err := agent.Listen(agent.Options{}) + if err != nil { + log.Err(err).Msg("failed to start gops agent") + } else { + log.Info().Msg("gops agent started ok") + } +} diff --git a/core/debug/healthy/debug.go b/core/debug/healthy/debug.go index 8f5ca51d5..4b3b1ac4a 100644 --- a/core/debug/healthy/debug.go +++ b/core/debug/healthy/debug.go @@ -6,9 +6,10 @@ import ( jjson "github.com/goccy/go-json" "github.com/gofiber/fiber/v2" - "github.com/pubgo/funk/try" - "github.com/pubgo/lava/core/debug" - "github.com/pubgo/lava/core/healthy" + "github.com/pubgo/funk/v2/try" + + "github.com/pubgo/lava/v2/core/debug" + "github.com/pubgo/lava/v2/core/healthy" ) func init() { diff --git a/core/debug/mux.go b/core/debug/mux.go index 2cd3f6e7b..6860355b0 100644 --- a/core/debug/mux.go +++ b/core/debug/mux.go @@ -3,7 +3,6 @@ package debug import ( "net/http" - _ "github.com/fasthttp/router" "github.com/gofiber/adaptor/v2" "github.com/gofiber/fiber/v2" ) @@ -16,11 +15,6 @@ type Config struct { var app = fiber.New() -func Handler(ctx *fiber.Ctx) error { - app.Handler()(ctx.Context()) - return nil -} - func App() *fiber.App { return app } func WrapFunc(h http.HandlerFunc) fiber.Handler { return adaptor.HTTPHandlerFunc(h) } func Wrap(h http.Handler) fiber.Handler { return adaptor.HTTPHandler(h) } diff --git a/core/debug/panicparse/panicparse.go b/core/debug/panicparse/panicparse.go new file mode 100644 index 000000000..6590848a3 --- /dev/null +++ b/core/debug/panicparse/panicparse.go @@ -0,0 +1,11 @@ +package panicparse + +import ( + "github.com/maruel/panicparse/v2/stack/webstack" + + "github.com/pubgo/lava/v2/core/debug" +) + +func init() { + debug.Get("/panicparse", debug.WrapFunc(webstack.SnapshotHandler)) +} diff --git a/core/debug/pprof/pprof.go b/core/debug/pprof/pprof.go index 03c6b1e12..709ef18c0 100644 --- a/core/debug/pprof/pprof.go +++ b/core/debug/pprof/pprof.go @@ -7,7 +7,7 @@ import ( "github.com/felixge/fgprof" "github.com/gofiber/fiber/v2" - "github.com/pubgo/lava/core/debug" + "github.com/pubgo/lava/v2/core/debug" ) func init() { diff --git a/core/debug/process/process.go b/core/debug/process/process.go index c282a716a..9c67a53a6 100644 --- a/core/debug/process/process.go +++ b/core/debug/process/process.go @@ -5,46 +5,48 @@ import ( "github.com/gofiber/fiber/v2" ps "github.com/keybase/go-ps" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/generic" - "github.com/pubgo/funk/result" + "github.com/pubgo/funk/v2" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/result" - "github.com/pubgo/lava/core/debug" + "github.com/pubgo/lava/v2/core/debug" ) func init() { - debug.Get("/process", func(ctx *fiber.Ctx) error { + debug.Get("/process", func(ctx *fiber.Ctx) (gErr error) { + defer result.RecoveryErr(&gErr) processes := assert.Must1(ps.Processes()) - processes1 := generic.Map(processes, func(i int) map[string]any { - p := processes[i] - ret := goVersion(result.Wrap(p.Path())) - if ret.IsErr() { - return nil + processes1 := funk.Map(processes, func(p ps.Process) map[string]any { + path, err := p.Path() + if err != nil { + log.Err(err).Str("path", p.Executable()).Msg("process path error") } return map[string]any{ "pid": p.Pid(), "ppid": p.PPid(), "exec": p.Executable(), - "path": result.Wrap(p.Path()), - "go_version": ret.Unwrap(), + "path": path, + "go_version": goVersion(path), } }) - processes1 = generic.Filter(processes1, func(m map[string]any) bool { return m != nil }) + processes1 = funk.Filter(processes1, func(m map[string]any) bool { return m != nil }) return ctx.JSON(processes1) }) } -func goVersion(path result.Result[string]) result.Result[string] { - if path.IsErr() { - return path +func goVersion(path string) string { + if path == "" { + return "" } - info, err := buildinfo.ReadFile(path.Unwrap()) + info, err := buildinfo.ReadFile(path) if err != nil { - return result.Wrap("", err) + log.Err(err).CallerSkipFrame(1).Str("path", path).Msg("goVersion error") + return "" } - return result.OK(info.GoVersion) + return info.GoVersion } diff --git a/core/debug/statsviz/main.go b/core/debug/statsviz/main.go index 05dbf315e..3935bdeb5 100644 --- a/core/debug/statsviz/main.go +++ b/core/debug/statsviz/main.go @@ -5,26 +5,36 @@ import ( "github.com/arl/statsviz" "github.com/gofiber/fiber/v2" - "github.com/pubgo/funk/assert" - "github.com/pubgo/lava/core/debug" - "github.com/pubgo/lava/pkg/httputil" + "github.com/pubgo/funk/v2" + "github.com/pubgo/funk/v2/assert" + + "github.com/pubgo/lava/v2/core/debug" + "github.com/pubgo/lava/v2/pkg/httputil" ) // github.com/go-echarts/statsview func init() { - srv := assert.Exit1(statsviz.NewServer(statsviz.Root("/statsviz"))) + srv := assert.Exit1(statsviz.NewServer(statsviz.Root("/debug/statsviz"))) debug.Route("/statsviz", func(router fiber.Router) { router.Use(func(ctx *fiber.Ctx) error { path := string(ctx.Request().URI().Path()) - pathList := strings.Split(path, "/") - if strings.Trim(pathList[len(pathList)-1], "/") == "ws" { + lastPath := strings.TrimSpace(funk.Last(strings.Split(strings.Trim(path, "/"), "/"))) + + if lastPath == "ws" { return httputil.HTTPHandler(srv.Ws())(ctx) } + if lastPath == "statsviz" { + return httputil.HTTPHandler(srv.Index())(ctx) + } + return ctx.Next() }) + router.Get("", func(ctx *fiber.Ctx) error { + return httputil.HTTPHandler(srv.Index())(ctx) + }) router.Get("/", func(ctx *fiber.Ctx) error { return httputil.HTTPHandler(srv.Index())(ctx) }) diff --git a/core/debug/trace/trace.go b/core/debug/trace/trace.go index e94a6cd27..8c02555f0 100644 --- a/core/debug/trace/trace.go +++ b/core/debug/trace/trace.go @@ -4,7 +4,7 @@ import ( "github.com/gofiber/adaptor/v2" "golang.org/x/net/trace" - "github.com/pubgo/lava/core/debug" + "github.com/pubgo/lava/v2/core/debug" ) func init() { diff --git a/core/debug/vars/debug.go b/core/debug/vars/debug.go index 6fa6320b2..b7666e33a 100644 --- a/core/debug/vars/debug.go +++ b/core/debug/vars/debug.go @@ -8,9 +8,9 @@ import ( g "github.com/maragudk/gomponents" c "github.com/maragudk/gomponents/components" h "github.com/maragudk/gomponents/html" - "github.com/pubgo/funk/recovery" + "github.com/pubgo/funk/v2/recovery" - "github.com/pubgo/lava/core/debug" + "github.com/pubgo/lava/v2/core/debug" ) func init() { diff --git a/core/debug/version/version.go b/core/debug/version/version.go index e8c7ce5ab..b27d96e61 100644 --- a/core/debug/version/version.go +++ b/core/debug/version/version.go @@ -7,16 +7,16 @@ import ( json "github.com/goccy/go-json" "github.com/gofiber/adaptor/v2" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/running" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/running" - "github.com/pubgo/lava/core/debug" + "github.com/pubgo/lava/v2/core/debug" ) func init() { - debug.Get("/env", adaptor.HTTPHandlerFunc(envHandle)) - debug.Get("/version", adaptor.HTTPHandlerFunc(versionHandle)) - debug.Get("/dep", adaptor.HTTPHandlerFunc(depHandle)) + debug.Get("env", adaptor.HTTPHandlerFunc(envHandle)) + debug.Get("version", adaptor.HTTPHandlerFunc(versionHandle)) + debug.Get("dep", adaptor.HTTPHandlerFunc(depHandle)) } func envHandle(writer http.ResponseWriter, request *http.Request) { diff --git a/core/debug/zpags/debug.go b/core/debug/zpags/debug.go new file mode 100644 index 000000000..fc1a98b16 --- /dev/null +++ b/core/debug/zpags/debug.go @@ -0,0 +1,12 @@ +package zpags + +import ( + "github.com/gofiber/adaptor/v2" + "go.opentelemetry.io/contrib/zpages" + + "github.com/pubgo/lava/v2/core/debug" +) + +func init() { + debug.Get("/z", adaptor.HTTPHandler(zpages.NewTracezHandler(zpages.NewSpanProcessor()))) +} diff --git a/core/discovery/aaa.go b/core/discovery/aaa.go index 50099e1df..7e70280e1 100644 --- a/core/discovery/aaa.go +++ b/core/discovery/aaa.go @@ -4,9 +4,10 @@ import ( "context" "time" - "github.com/pubgo/funk/result" - "github.com/pubgo/lava/core/service" - "github.com/pubgo/lava/pkg/proto/lavapbv1" + "github.com/pubgo/funk/v2/result" + + "github.com/pubgo/lava/v2/core/service" + "github.com/pubgo/lava/v2/pkg/proto/lavapbv1" ) type ( diff --git a/core/discovery/config.go b/core/discovery/config.go index 4d5efc5fa..3bcd56ffd 100644 --- a/core/discovery/config.go +++ b/core/discovery/config.go @@ -4,9 +4,9 @@ import ( "fmt" "time" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/config" - "github.com/pubgo/funk/errors" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/config" + "github.com/pubgo/funk/v2/errors" ) // https://github.com/go-eagle/eagle/blob/master/pkg/registry/registry.go diff --git a/core/discovery/noop.go b/core/discovery/noop.go index 209b56fb1..a0dbf8c71 100644 --- a/core/discovery/noop.go +++ b/core/discovery/noop.go @@ -3,9 +3,9 @@ package discovery import ( "context" - "github.com/pubgo/funk/result" + "github.com/pubgo/funk/v2/result" - "github.com/pubgo/lava/core/service" + "github.com/pubgo/lava/v2/core/service" ) func NewNoopDiscovery() Discovery { @@ -19,8 +19,8 @@ var ( type noopDiscovery struct{} -func (n *noopDiscovery) Next() result.Result[*Result] { - return result.Err[*Result](ErrWatcherStopped) +func (n *noopDiscovery) Next() (r result.Result[*Result]) { + return r.WithErr(ErrWatcherStopped) } func (n *noopDiscovery) Stop() error { return nil } diff --git a/core/encoding/aaa.go b/core/encoding/aaa.go index a5594946d..0e5d4f106 100644 --- a/core/encoding/aaa.go +++ b/core/encoding/aaa.go @@ -5,8 +5,8 @@ var Name = "encoding" // Codec defines the interface. type Codec interface { Name() string - Encode(v interface{}) ([]byte, error) - Decode(data []byte, v interface{}) error - Marshal(v interface{}) ([]byte, error) - Unmarshal(data []byte, v interface{}) error + Encode(v any) ([]byte, error) + Decode(data []byte, v any) error + Marshal(v any) ([]byte, error) + Unmarshal(data []byte, v any) error } diff --git a/core/encoding/bytes/bytes.go b/core/encoding/bytes/bytes.go index a7bb2aefa..9bebe57f6 100644 --- a/core/encoding/bytes/bytes.go +++ b/core/encoding/bytes/bytes.go @@ -3,7 +3,7 @@ package bytes import ( "fmt" - "github.com/pubgo/lava/core/encoding" + "github.com/pubgo/lava/v2/core/encoding" ) func init() { @@ -18,7 +18,7 @@ func (c *Codec) Name() string { return "bytes" } -func (c *Codec) Encode(v interface{}) ([]byte, error) { +func (c *Codec) Encode(v any) ([]byte, error) { switch ve := v.(type) { case *[]byte: return *ve, nil @@ -28,7 +28,7 @@ func (c *Codec) Encode(v interface{}) ([]byte, error) { return nil, nil } -func (c *Codec) Decode(data []byte, ve interface{}) error { +func (c *Codec) Decode(data []byte, ve any) error { switch ve := ve.(type) { case *[]byte: *ve = data @@ -36,7 +36,7 @@ func (c *Codec) Decode(data []byte, ve interface{}) error { return nil } -func (c *Codec) Marshal(v interface{}) ([]byte, error) { +func (c *Codec) Marshal(v any) ([]byte, error) { switch ve := v.(type) { case *[]byte: return *ve, nil @@ -46,7 +46,7 @@ func (c *Codec) Marshal(v interface{}) ([]byte, error) { return nil, nil } -func (c *Codec) Unmarshal(data []byte, ve interface{}) error { +func (c *Codec) Unmarshal(data []byte, ve any) error { fmt.Println(string(data)) switch ve := ve.(type) { case *[]byte: diff --git a/core/encoding/encoding.go b/core/encoding/encoding.go index e855d3a55..df797e063 100644 --- a/core/encoding/encoding.go +++ b/core/encoding/encoding.go @@ -1,9 +1,9 @@ package encoding import ( - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/recovery" - "github.com/pubgo/funk/typex" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/recovery" + "github.com/pubgo/funk/v2/typex" ) var data typex.Map[Codec] diff --git a/core/encoding/json/codec.go b/core/encoding/json/codec.go index 7854340fd..18156579d 100644 --- a/core/encoding/json/codec.go +++ b/core/encoding/json/codec.go @@ -2,7 +2,8 @@ package json import ( json "github.com/json-iterator/go" - "github.com/pubgo/lava/core/encoding" + + "github.com/pubgo/lava/v2/core/encoding" ) var ( @@ -21,8 +22,8 @@ func init() { // jsonCodec uses json marshaler and unmarshaler. type jsonCodec struct{} -func (c *jsonCodec) Marshal(v interface{}) ([]byte, error) { return std.Marshal(v) } -func (c *jsonCodec) Unmarshal(data []byte, v interface{}) error { return std.Unmarshal(data, v) } -func (c *jsonCodec) Name() string { return Name } -func (c *jsonCodec) Encode(i interface{}) ([]byte, error) { return std.Marshal(i) } -func (c *jsonCodec) Decode(data []byte, v interface{}) error { return std.Unmarshal(data, v) } +func (c *jsonCodec) Marshal(v any) ([]byte, error) { return std.Marshal(v) } +func (c *jsonCodec) Unmarshal(data []byte, v any) error { return std.Unmarshal(data, v) } +func (c *jsonCodec) Name() string { return Name } +func (c *jsonCodec) Encode(i any) ([]byte, error) { return std.Marshal(i) } +func (c *jsonCodec) Decode(data []byte, v any) error { return std.Unmarshal(data, v) } diff --git a/core/encoding/msgpack/codec.go b/core/encoding/msgpack/codec.go index 9b2fd4ab2..52118d617 100644 --- a/core/encoding/msgpack/codec.go +++ b/core/encoding/msgpack/codec.go @@ -1,8 +1,9 @@ package msgpack import ( - "github.com/pubgo/lava/core/encoding" msgpack "github.com/vmihailenco/msgpack/v5" + + "github.com/pubgo/lava/v2/core/encoding" ) var Name = "msgpack" @@ -14,8 +15,8 @@ func init() { // msgpackCodec uses messagepack marshaler and unmarshaler. type msgpackCodec struct{} -func (c msgpackCodec) Marshal(v interface{}) ([]byte, error) { return msgpack.Marshal(v) } -func (c msgpackCodec) Unmarshal(data []byte, v interface{}) error { return msgpack.Unmarshal(data, v) } -func (c msgpackCodec) Name() string { return Name } -func (c msgpackCodec) Encode(i interface{}) ([]byte, error) { return msgpack.Marshal(i) } -func (c msgpackCodec) Decode(data []byte, i interface{}) error { return msgpack.Unmarshal(data, i) } +func (c msgpackCodec) Marshal(v any) ([]byte, error) { return msgpack.Marshal(v) } +func (c msgpackCodec) Unmarshal(data []byte, v any) error { return msgpack.Unmarshal(data, v) } +func (c msgpackCodec) Name() string { return Name } +func (c msgpackCodec) Encode(i any) ([]byte, error) { return msgpack.Marshal(i) } +func (c msgpackCodec) Decode(data []byte, i any) error { return msgpack.Unmarshal(data, i) } diff --git a/core/encoding/protobuf/codec.go b/core/encoding/protobuf/codec.go index 3193a1f45..85291f33e 100644 --- a/core/encoding/protobuf/codec.go +++ b/core/encoding/protobuf/codec.go @@ -3,10 +3,9 @@ package protobuf import ( "fmt" - "github.com/pubgo/lava/core/encoding" + "github.com/pubgo/lava/v2/core/encoding" "google.golang.org/protobuf/proto" - pb "google.golang.org/protobuf/proto" ) var Name = "proto" @@ -23,8 +22,8 @@ func (c protobufCodec) Marshal(v interface{}) ([]byte, error) { return proto.Marshal(m) } - if m, ok := v.(pb.Message); ok { - return pb.Marshal(m) + if m, ok := v.(proto.Message); ok { + return proto.Marshal(m) } return nil, fmt.Errorf("%T is not a proto.Marshaler", v) @@ -35,8 +34,8 @@ func (c protobufCodec) Unmarshal(data []byte, v interface{}) error { return proto.Unmarshal(data, m) } - if m, ok := v.(pb.Message); ok { - return pb.Unmarshal(data, m) + if m, ok := v.(proto.Message); ok { + return proto.Unmarshal(data, m) } return fmt.Errorf("%T is not a proto.Unmarshaler", v) @@ -52,8 +51,8 @@ func (c protobufCodec) Encode(i interface{}) ([]byte, error) { return proto.Marshal(m) } - if m, ok := i.(pb.Message); ok { - return pb.Marshal(m) + if m, ok := i.(proto.Message); ok { + return proto.Marshal(m) } return nil, fmt.Errorf("%T is not a proto.Marshaler", i) @@ -65,8 +64,8 @@ func (c protobufCodec) Decode(data []byte, i interface{}) error { return proto.Unmarshal(data, m) } - if m, ok := i.(pb.Message); ok { - return pb.Unmarshal(data, m) + if m, ok := i.(proto.Message); ok { + return proto.Unmarshal(data, m) } return fmt.Errorf("%T is not a proto.Unmarshaler", i) diff --git a/core/encoding/protojson/codec.go b/core/encoding/protojson/codec.go index 5560332f5..3be84fd62 100644 --- a/core/encoding/protojson/codec.go +++ b/core/encoding/protojson/codec.go @@ -4,7 +4,7 @@ import ( "bytes" "encoding/json" - "github.com/pubgo/lava/core/encoding" + "github.com/pubgo/lava/v2/core/encoding" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" diff --git a/core/encoding/vars.go b/core/encoding/vars.go index 9b2731e27..2df8bc83c 100644 --- a/core/encoding/vars.go +++ b/core/encoding/vars.go @@ -1,8 +1,8 @@ package encoding -import "github.com/pubgo/funk/vars" +import "github.com/pubgo/funk/v2/vars" func init() { - vars.Register(Name, func() interface{} { return Keys() }) - vars.Register(Name+"-mapping", func() interface{} { return cdcMapping }) + vars.Register(Name, func() any { return Keys() }) + vars.Register(Name+"-mapping", func() any { return cdcMapping }) } diff --git a/core/flags/default.go b/core/flags/default.go index 293e55442..cf61cb446 100644 --- a/core/flags/default.go +++ b/core/flags/default.go @@ -1,64 +1,13 @@ package flags import ( - "context" - - "github.com/pubgo/funk/config" - "github.com/pubgo/funk/env" - "github.com/pubgo/funk/running" - "github.com/urfave/cli/v3" + "github.com/pubgo/funk/v2/running" ) func init() { - const httpPort = "server_http_port" - const grpcPort = "server_grpc_port" - const conf = "config_path" - env.GetIntVal(&running.HttpPort, httpPort) - env.GetIntVal(&running.GrpcPort, grpcPort) - - Register(&cli.IntFlag{ - Name: "http-port", - Usage: "service http port", - Persistent: true, - Value: int64(running.HttpPort), - Sources: cli.EnvVars(env.Key(httpPort)), - Action: func(ctx context.Context, command *cli.Command, i int64) error { - running.HttpPort = int(i) - return nil - }, - }) - - Register(&cli.IntFlag{ - Name: "grpc-port", - Usage: "service grpc port", - Persistent: true, - Value: int64(running.GrpcPort), - Sources: cli.EnvVars(env.Key("server_grpc_port")), - Action: func(ctx context.Context, command *cli.Command, i int64) error { - running.GrpcPort = int(i) - return nil - }, - }) - - Register(&cli.BoolFlag{ - Name: "debug", - Usage: "enable debug mode", - Persistent: true, - Value: running.IsDebug, - Destination: &running.IsDebug, - Sources: cli.EnvVars(env.Key("debug"), env.Key("enable_debug")), - }) - - Register(&cli.StringFlag{ - Name: "config", - Aliases: []string{"c"}, - Usage: "config path", - Value: config.GetConfigPath(), - Persistent: true, - Sources: cli.EnvVars(env.Key(conf)), - Action: func(ctx context.Context, command *cli.Command, s string) error { - config.SetConfigPath(s) - return nil - }, - }) + Register(&running.DebugFlag) + Register(&running.EnvFlag) + Register(&running.ConfFlag) + Register(&running.GrpcPortFlag) + Register(&running.HttpPortFlag) } diff --git a/core/healthy/factory.go b/core/healthy/factory.go index 4f71ae910..102463ccb 100644 --- a/core/healthy/factory.go +++ b/core/healthy/factory.go @@ -1,8 +1,8 @@ package healthy import ( - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/typex" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/typex" ) const Name = "healthy" @@ -19,11 +19,11 @@ func Get(name string) Handler { } func List() (names []string) { - healthList.Range(func(name, _ interface{}) bool { + healthList.Range(func(name, _ any) bool { names = append(names, name.(string)) return true }) - return + return names } func Register(name string, r Handler) { diff --git a/core/healthy/vars.go b/core/healthy/vars.go index 46f7df1ce..57990a8f1 100644 --- a/core/healthy/vars.go +++ b/core/healthy/vars.go @@ -1,14 +1,14 @@ package healthy import ( - "github.com/pubgo/funk/stack" - "github.com/pubgo/funk/vars" + "github.com/pubgo/funk/v2/stack" + "github.com/pubgo/funk/v2/vars" ) func init() { - vars.Register(Name, func() interface{} { + vars.Register(Name, func() any { data := make(map[string]any) - healthList.Range(func(key, value interface{}) bool { + healthList.Range(func(key, value any) bool { data[key.(string)] = stack.CallerWithFunc(value) return true }) diff --git a/core/lavabuilder/builder.go b/core/lavabuilder/builder.go new file mode 100644 index 000000000..97cd3e7f0 --- /dev/null +++ b/core/lavabuilder/builder.go @@ -0,0 +1,104 @@ +package lavabuilder + +import ( + "context" + "os" + "sort" + + "github.com/pubgo/dix/v2" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/buildinfo/version" + "github.com/pubgo/funk/v2/errors" + "github.com/pubgo/funk/v2/features/featureflags" + "github.com/pubgo/funk/v2/recovery" + "github.com/pubgo/funk/v2/running" + cli "github.com/urfave/cli/v3" + + "github.com/pubgo/lava/v2/clients/grpcc/grpccresolver" + "github.com/pubgo/lava/v2/cmds/depcmd" + "github.com/pubgo/lava/v2/cmds/grpcservercmd" + "github.com/pubgo/lava/v2/cmds/healthcmd" + "github.com/pubgo/lava/v2/cmds/httpservercmd" + "github.com/pubgo/lava/v2/cmds/schedulercmd" + "github.com/pubgo/lava/v2/cmds/versioncmd" + "github.com/pubgo/lava/v2/core/discovery" + "github.com/pubgo/lava/v2/core/flags" + "github.com/pubgo/lava/v2/core/lifecycle/lifecyclebuilder" + "github.com/pubgo/lava/v2/core/logging/logbuilder" + "github.com/pubgo/lava/v2/core/metrics/metricbuilder" + "github.com/pubgo/lava/v2/core/signals" + "github.com/pubgo/lava/v2/pkg/cliutil" + + _ "github.com/pubgo/lava/v2/core/debug/debug" + //_ "github.com/pubgo/lava/v2/core/debug/gops" + _ "github.com/pubgo/lava/v2/core/debug/pprof" + _ "github.com/pubgo/lava/v2/core/debug/process" + _ "github.com/pubgo/lava/v2/core/debug/statsviz" + _ "github.com/pubgo/lava/v2/core/debug/trace" + _ "github.com/pubgo/lava/v2/core/debug/vars" + _ "github.com/pubgo/lava/v2/core/debug/version" + + // metric + _ "github.com/pubgo/lava/v2/core/metrics/drivers/prometheus" + + // encoding + _ "github.com/pubgo/lava/v2/core/encoding/protobuf" + _ "github.com/pubgo/lava/v2/core/encoding/protojson" + + // logging + _ "github.com/pubgo/lava/v2/core/logging/logext/grpclog" + _ "github.com/pubgo/lava/v2/core/logging/logext/slog" + _ "github.com/pubgo/lava/v2/core/logging/logext/stdlog" + + _ "go.uber.org/automaxprocs" +) + +var defaultProviders = []any{ + grpccresolver.NewDirectBuilder, + grpccresolver.NewDiscoveryBuilder, + discovery.NewNoopDiscovery, + + logbuilder.New, + metricbuilder.New, + + lifecyclebuilder.New, +} + +func New(opts ...dix.Option) *dix.Dix { + di := dix.New(append(opts, dix.WithValuesNull())...) + for _, p := range defaultProviders { + dix.Provide(di, p) + } + return di +} + +func Run(di *dix.Dix) { + defer recovery.Exit(func(err error) error { + if errors.Is(err, context.Canceled) { + return nil + } + return err + }) + + dix.Provide(di, versioncmd.New) + dix.Provide(di, healthcmd.New) + dix.Provide(di, depcmd.New) + dix.Provide(di, grpcservercmd.New) + dix.Provide(di, httpservercmd.New) + dix.Provide(di, schedulercmd.New) + dix.Inject(di, func(commands []*cli.Command) { + app := &cli.Command{ + Name: version.Project(), + Suggest: true, + UseShortOptionHandling: true, + Usage: cliutil.UsageDesc("%s service", version.Project()), + Version: version.Version(), + Flags: append(flags.GetFlags(), featureflags.GetFlags()...), + Commands: commands, + ExtraInfo: running.GetSysInfo, + } + + sort.Sort(cli.FlagsByName(app.Flags)) + assert.Must(app.Run(signals.Context(), os.Args)) + }) +} diff --git a/core/lavacontexts/context.go b/core/lavacontexts/context.go index 2a5b443d8..709d01c5f 100644 --- a/core/lavacontexts/context.go +++ b/core/lavacontexts/context.go @@ -3,9 +3,10 @@ package lavacontexts import ( "context" - "github.com/pubgo/lava/lava" - "github.com/pubgo/lava/pkg/proto/lavapbv1" "github.com/rs/xid" + + "github.com/pubgo/lava/v2/lava" + "github.com/pubgo/lava/v2/pkg/proto/lavapbv1" ) type ctxKey string diff --git a/core/lifecycle/builder.go b/core/lifecycle/builder.go deleted file mode 100644 index 48f289f21..000000000 --- a/core/lifecycle/builder.go +++ /dev/null @@ -1,18 +0,0 @@ -package lifecycle - -type Provider struct { - Setter Lifecycle - Getter Getter -} - -func New(handlers []Handler) Provider { - lc := new(lifecycleImpl) - for i := range handlers { - handlers[i](lc) - } - - return Provider{ - Setter: lc, - Getter: lc, - } -} diff --git a/core/lifecycle/lifecycle.go b/core/lifecycle/lifecycle.go index 6b43248a7..8a71eb766 100644 --- a/core/lifecycle/lifecycle.go +++ b/core/lifecycle/lifecycle.go @@ -1,16 +1,32 @@ package lifecycle +import "context" + +func WrapNoError(fn func(context.Context)) ExecFunc { + return func(ctx context.Context) error { fn(ctx); return nil } +} + +func WrapNoCtx(fn func() error) ExecFunc { + return func(ctx context.Context) error { return fn() } +} + +func WrapNoCtxErr(fn func()) ExecFunc { + return func(ctx context.Context) error { fn(); return nil } +} + +type ExecFunc = func(context.Context) error + type Executor struct { - Handler func() + Exec ExecFunc } type Handler func(lc Lifecycle) type Lifecycle interface { - AfterStop(f func()) - BeforeStop(f func()) - AfterStart(f func()) - BeforeStart(f func()) + AfterStop(f ExecFunc) + BeforeStop(f ExecFunc) + AfterStart(f ExecFunc) + BeforeStart(f ExecFunc) } type Getter interface { @@ -19,35 +35,3 @@ type Getter interface { GetAfterStarts() []Executor GetBeforeStarts() []Executor } - -var ( - _ Lifecycle = (*lifecycleImpl)(nil) - _ Getter = (*lifecycleImpl)(nil) -) - -type lifecycleImpl struct { - beforeStarts []Executor - afterStarts []Executor - beforeStops []Executor - afterStops []Executor -} - -func (t *lifecycleImpl) GetAfterStops() []Executor { return t.afterStops } -func (t *lifecycleImpl) GetBeforeStops() []Executor { return t.beforeStops } -func (t *lifecycleImpl) GetAfterStarts() []Executor { return t.afterStarts } -func (t *lifecycleImpl) GetBeforeStarts() []Executor { return t.beforeStarts } -func (t *lifecycleImpl) BeforeStart(f func()) { - t.beforeStarts = append(t.beforeStarts, Executor{Handler: f}) -} - -func (t *lifecycleImpl) BeforeStop(f func()) { - t.beforeStops = append([]Executor{{Handler: f}}, t.beforeStops...) -} - -func (t *lifecycleImpl) AfterStart(f func()) { - t.afterStarts = append(t.afterStarts, Executor{Handler: f}) -} - -func (t *lifecycleImpl) AfterStop(f func()) { - t.afterStops = append([]Executor{{Handler: f}}, t.afterStops...) -} diff --git a/core/lifecycle/lifecyclebuilder/builder.go b/core/lifecycle/lifecyclebuilder/builder.go new file mode 100644 index 000000000..2c75cbe53 --- /dev/null +++ b/core/lifecycle/lifecyclebuilder/builder.go @@ -0,0 +1,52 @@ +package lifecyclebuilder + +import "github.com/pubgo/lava/v2/core/lifecycle" + +type Provider struct { + Setter lifecycle.Lifecycle + Getter lifecycle.Getter +} + +func New(handlers []lifecycle.Handler) Provider { + lc := new(lifecycleImpl) + for i := range handlers { + handlers[i](lc) + } + + return Provider{ + Setter: lc, + Getter: lc, + } +} + +var ( + _ lifecycle.Lifecycle = (*lifecycleImpl)(nil) + _ lifecycle.Getter = (*lifecycleImpl)(nil) +) + +type lifecycleImpl struct { + beforeStarts []lifecycle.Executor + afterStarts []lifecycle.Executor + beforeStops []lifecycle.Executor + afterStops []lifecycle.Executor +} + +func (t *lifecycleImpl) GetAfterStops() []lifecycle.Executor { return t.afterStops } +func (t *lifecycleImpl) GetBeforeStops() []lifecycle.Executor { return t.beforeStops } +func (t *lifecycleImpl) GetAfterStarts() []lifecycle.Executor { return t.afterStarts } +func (t *lifecycleImpl) GetBeforeStarts() []lifecycle.Executor { return t.beforeStarts } +func (t *lifecycleImpl) BeforeStart(f lifecycle.ExecFunc) { + t.beforeStarts = append(t.beforeStarts, lifecycle.Executor{Exec: f}) +} + +func (t *lifecycleImpl) BeforeStop(f lifecycle.ExecFunc) { + t.beforeStops = append([]lifecycle.Executor{{Exec: f}}, t.beforeStops...) +} + +func (t *lifecycleImpl) AfterStart(f lifecycle.ExecFunc) { + t.afterStarts = append(t.afterStarts, lifecycle.Executor{Exec: f}) +} + +func (t *lifecycleImpl) AfterStop(f lifecycle.ExecFunc) { + t.afterStops = append([]lifecycle.Executor{{Exec: f}}, t.afterStops...) +} diff --git a/core/logging/builder.go b/core/logging/builder.go deleted file mode 100644 index dbd3e5f4c..000000000 --- a/core/logging/builder.go +++ /dev/null @@ -1,75 +0,0 @@ -package logging - -import ( - "io" - "os" - "time" - - "github.com/pubgo/funk/log" - "github.com/pubgo/funk/recovery" - "github.com/pubgo/funk/result" - "github.com/pubgo/funk/running" - "github.com/pubgo/funk/stack" - "github.com/pubgo/lava/core/logging/logkey" - "github.com/rs/zerolog" -) - -func init() { - zerolog.CallerMarshalFunc = func(pc uintptr, file string, line int) string { - return stack.Stack(pc).Short() - } -} - -// New logger -func New(cfg *Config) log.Logger { - defer recovery.Exit() - - level := zerolog.DebugLevel - if cfg.Level != "" { - level = result.Of(zerolog.ParseLevel(cfg.Level)).Expect("log level is invalid") - } - zerolog.SetGlobalLevel(level) - - logger := zerolog.New(&writer{os.Stdout}).Level(level).With().Timestamp().Caller().Logger() - if !cfg.AsJson { - logger = logger.Output(&writer{ - zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) { - w.Out = os.Stdout - w.TimeFormat = time.RFC3339 - }), - }) - } - - // 全局log设置 - ee := logger.With(). - Str(logkey.Hostname, running.Hostname). - Str(logkey.Project, running.Project). - Str(logkey.Version, running.Version) - - if running.Namespace != "" { - ee = ee.Str(logkey.Namespace, running.Namespace) - } - - logger = ee.Logger() - log.SetLogger(&logger) - - gl := log.New(&logger) - for _, ext := range List() { - ext(gl) - } - return gl -} - -type writer struct { - io.Writer -} - -func (w writer) Write(p []byte) (n int, err error) { - n, err = w.Writer.Write(p) - if err != nil { - log.Err(err).Str("raw_json", string(p)).Msg("failed to decode invalid json") - return - } - - return -} diff --git a/core/logging/config.go b/core/logging/config.go index b5dd4a6c1..7ad69e4e8 100644 --- a/core/logging/config.go +++ b/core/logging/config.go @@ -8,4 +8,5 @@ type Config struct { Level string `yaml:"level"` AsJson bool `yaml:"as_json"` DisableLoggers []string `yaml:"disable_loggers"` + Filters []string `yaml:"filters"` } diff --git a/core/logging/factory.go b/core/logging/factory.go index b234d0bdd..134740f3d 100644 --- a/core/logging/factory.go +++ b/core/logging/factory.go @@ -1,9 +1,9 @@ package logging import ( - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/log" - "github.com/pubgo/funk/recovery" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/recovery" ) type Factory func(log log.Logger) diff --git a/core/logging/logbuilder/builder.go b/core/logging/logbuilder/builder.go new file mode 100644 index 000000000..1b428f453 --- /dev/null +++ b/core/logging/logbuilder/builder.go @@ -0,0 +1,115 @@ +package logbuilder + +import ( + "context" + "io" + "os" + "strings" + "time" + + "github.com/expr-lang/expr" + "github.com/expr-lang/expr/vm" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/log/logfields" + "github.com/pubgo/funk/v2/pretty" + "github.com/pubgo/funk/v2/recovery" + "github.com/pubgo/funk/v2/result" + "github.com/pubgo/funk/v2/running" + "github.com/rs/zerolog" + "github.com/samber/lo" + + "github.com/pubgo/lava/v2/core/logging" + "github.com/pubgo/lava/v2/core/logging/logkey" +) + +var GlobalHook zerolog.Hook + +// New logger +func New(cfg *logging.Config, hooks []zerolog.Hook) log.Logger { + defer recovery.Exit(func(err error) error { + pretty.Println(cfg) + return err + }) + + level := zerolog.DebugLevel + if cfg.Level != "" { + level = result.Wrap(zerolog.ParseLevel(cfg.Level)).Expect("log level is invalid") + } + zerolog.SetGlobalLevel(level) + + logger := zerolog.New(&writer{os.Stdout}).Level(level).With().Timestamp().Caller().Logger() + if !cfg.AsJson { + logger = logger.Output(&writer{ + zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) { + w.Out = os.Stdout + w.TimeFormat = time.RFC3339 + }), + }) + } + + if GlobalHook != nil { + hooks = append(hooks, GlobalHook) + } + logger = logger.Hook(hooks...) + + // 全局log设置 + ee := logger.With(). + Str(logkey.Hostname, running.Hostname). + Str(logkey.Project, running.Project()). + Str(logkey.Version, running.Version()) + + if running.Namespace != "" { + ee = ee.Str(logkey.Namespace, running.Namespace) + } + + filters := lo.Filter(cfg.Filters, func(item string, index int) bool { return strings.TrimSpace(item) != "" }) + if len(filters) > 0 { + expCode := strings.Join(filters, " && ") + log.Info().Str(logfields.Msg, "log filter expr").Msg(expCode) + + exp := exprFilter(expCode) + log.SetEnableChecker(func(ctx context.Context, lvl log.Level, name, message string, fields log.Fields) bool { + envData := map[string]any{"level": lvl.String(), "msg": message, "name": name, "fields": fields} + output, err := expr.Run(exp, envData) + if err != nil { + log.Err(err).Str("expr", expCode).Msg("failed to run log filter expr") + } + return err == nil && output.(bool) + }) + } + + log.SetLogger(lo.ToPtr(ee.Logger())) + + gl := log.GetLogger("ext") + for _, ext := range logging.List() { + ext(gl) + } + + return log.GetLogger() +} + +type writer struct { + io.Writer +} + +func (w writer) Write(p []byte) (n int, err error) { + n, err = w.Writer.Write(p) + if err != nil { + log.Err(err).Str("raw_json", string(p)).Msg("failed to decode invalid json") + return n, err + } + + return n, err +} + +func exprFilter(code string) *vm.Program { + env := map[string]any{"level": "", "name": "", "msg": "", "fields": log.Fields{}} + + program, err := expr.Compile(code, expr.Env(env)) + if err != nil { + log.Err(err).Str("expr", code).Msg("failed to compile log filter expr") + assert.Exit(err, code) + } + return program +} diff --git a/core/logging/logext/gologr/log.go b/core/logging/logext/gologr/log.go new file mode 100644 index 000000000..5f934166e --- /dev/null +++ b/core/logging/logext/gologr/log.go @@ -0,0 +1,104 @@ +package gologr + +import ( + "fmt" + "slices" + + "github.com/go-logr/logr" + "github.com/pubgo/funk/v2/log" + "github.com/rs/zerolog" +) + +var ( + RenderArgsHook = defaultRender + RenderValuesHook = defaultRender +) + +// LogSink implements logr.LogSink and logr.CallDepthLogSink. +type LogSink struct { + l log.Logger + keysAndValues []any +} + +var ( + _ logr.LogSink = &LogSink{} + _ logr.CallDepthLogSink = &LogSink{} +) + +func NewSink(l log.Logger) *LogSink { + return &LogSink{l: l.WithName("logr")} +} + +func (ls *LogSink) Init(ri logr.RuntimeInfo) { + ls.l = ls.l.WithCallerSkip(ri.CallDepth + 2) +} + +func (ls *LogSink) Enabled(level int) bool { return true } + +func (ls *LogSink) Info(level int, msg string, keysAndValues ...any) { + ls.msg(ls.l.Info(), msg, keysAndValues) +} + +func (ls *LogSink) Error(err error, msg string, keysAndValues ...any) { + ls.msg(ls.l.Err(err), msg, keysAndValues) +} + +func (ls *LogSink) msg(e *zerolog.Event, msg string, keysAndValues []any) { + if e == nil { + return + } + + if RenderArgsHook != nil { + keysAndValues = RenderArgsHook(keysAndValues) + } + + e = e.Fields(keysAndValues) + e.Msg(msg) +} + +func (ls *LogSink) copy() *LogSink { + return &LogSink{ + l: ls.l, + keysAndValues: slices.Clone(ls.keysAndValues), + } +} + +func (ls *LogSink) WithValues(keysAndValues ...any) logr.LogSink { + if len(keysAndValues) == 0 { + return ls + } + + if RenderValuesHook != nil { + keysAndValues = RenderValuesHook(keysAndValues) + } + + ll := ls.copy() + ll.keysAndValues = append(ll.keysAndValues, keysAndValues...) + + return ll +} + +func (ls *LogSink) WithName(name string) logr.LogSink { + ll := ls.copy() + ll.l = ll.l.WithName(name) + return ll +} + +func (ls *LogSink) WithCallDepth(depth int) logr.LogSink { + ll := ls.copy() + ll.l = ll.l.WithCallerSkip(depth) + return ll +} + +func defaultRender(keysAndValues []any) []any { + for i, n := 1, len(keysAndValues); i < n; i += 2 { + value := keysAndValues[i] + switch v := value.(type) { + case logr.Marshaler: + keysAndValues[i] = v.MarshalLog() + case fmt.Stringer: + keysAndValues[i] = v.String() + } + } + return keysAndValues +} diff --git a/core/logging/logext/gologr/log_test.go b/core/logging/logext/gologr/log_test.go new file mode 100644 index 000000000..3e74cb26b --- /dev/null +++ b/core/logging/logext/gologr/log_test.go @@ -0,0 +1,24 @@ +package gologr + +import ( + "bytes" + "encoding/json" + "testing" + + "github.com/go-logr/logr" + "github.com/pubgo/funk/v2/log" + "github.com/stretchr/testify/assert" +) + +func TestName(t *testing.T) { + var buf bytes.Buffer + ll := logr.New(NewSink(log.Output(&buf))) + ll.Info("test", "hello", 123456) + + data := make(map[string]any) + assert.NoError(t, json.Unmarshal(buf.Bytes(), &data)) + assert.Equal(t, data["hello"], float64(123456)) + assert.Equal(t, data["level"], "info") + assert.Equal(t, data["message"], "test") + assert.Contains(t, data["caller"], "otellogger/log_test.go") +} diff --git a/core/logging/logext/grpclog/log.go b/core/logging/logext/grpclog/log.go index 99d2496c9..5ab1dece8 100644 --- a/core/logging/logext/grpclog/log.go +++ b/core/logging/logext/grpclog/log.go @@ -3,11 +3,11 @@ package grpclog import ( "fmt" - "github.com/pubgo/funk/log" + "github.com/pubgo/funk/v2/log" "github.com/rs/zerolog" "google.golang.org/grpc/grpclog" - "github.com/pubgo/lava/core/logging" + "github.com/pubgo/lava/v2/core/logging" ) const ( @@ -25,23 +25,22 @@ var _grpcToZapLevel = map[int]zerolog.Level{ grpcLvlFatal: zerolog.FatalLevel, } -func grpcComponentName(args interface{}) func(e *zerolog.Event) { +func grpcComponentName(args any) func(e *zerolog.Event) { name := args.(string) return func(e *zerolog.Event) { e.Str("grpc-component", name[1:len(name)-1]) } } -var evt = log.NewEvent().Str("ext", "grpc") - func init() { logging.Register("grpcLog", SetLogger) } func SetLogger(logger log.Logger) { + logger = logger.WithName("grpc").WithCallerSkip(2) grpclog.SetLoggerV2(&loggerWrapper{ - log: logger.WithEvent(evt).WithCallerSkip(2), - depthLog: logger.WithEvent(evt).WithCallerSkip(2), + log: logger, + depthLog: logger, }) } @@ -53,52 +52,52 @@ var ( type loggerWrapper struct { log log.Logger depthLog log.Logger - printFilter func(args ...interface{}) bool - printfFilter func(format string, args ...interface{}) bool - printlnFilter func(args ...interface{}) bool + printFilter func(args ...any) bool + printfFilter func(format string, args ...any) bool + printlnFilter func(args ...any) bool } -func (l *loggerWrapper) InfoDepth(depth int, args ...interface{}) { +func (l *loggerWrapper) InfoDepth(depth int, args ...any) { l.depthLog.WithCallerSkip(depth).Info().Func(grpcComponentName(args[0])).Msg(fmt.Sprint(args[1:]...)) } -func (l *loggerWrapper) WarningDepth(depth int, args ...interface{}) { +func (l *loggerWrapper) WarningDepth(depth int, args ...any) { l.depthLog.WithCallerSkip(depth).Warn().Func(grpcComponentName(args[0])).Msg(fmt.Sprint(args[1:]...)) } -func (l *loggerWrapper) ErrorDepth(depth int, args ...interface{}) { +func (l *loggerWrapper) ErrorDepth(depth int, args ...any) { l.depthLog.WithCallerSkip(depth).Error().Func(grpcComponentName(args[0])).Msg(fmt.Sprint(args[1:]...)) } -func (l *loggerWrapper) FatalDepth(depth int, args ...interface{}) { +func (l *loggerWrapper) FatalDepth(depth int, args ...any) { l.depthLog.WithCallerSkip(depth).Fatal().Func(grpcComponentName(args[0])).Msg(fmt.Sprint(args[1:]...)) } -func (l *loggerWrapper) SetPrintFilter(filter func(args ...interface{}) bool) { +func (l *loggerWrapper) SetPrintFilter(filter func(args ...any) bool) { l.printFilter = filter } -func (l *loggerWrapper) SetPrintfFilter(filter func(format string, args ...interface{}) bool) { +func (l *loggerWrapper) SetPrintfFilter(filter func(format string, args ...any) bool) { l.printfFilter = filter } -func (l *loggerWrapper) SetPrintlnFilter(filter func(args ...interface{}) bool) { +func (l *loggerWrapper) SetPrintlnFilter(filter func(args ...any) bool) { l.printlnFilter = filter } -func (l *loggerWrapper) filter(args ...interface{}) bool { +func (l *loggerWrapper) filter(args ...any) bool { return l.printFilter != nil && l.printFilter(args...) } -func (l *loggerWrapper) filterf(format string, args ...interface{}) bool { +func (l *loggerWrapper) filterf(format string, args ...any) bool { return l.printfFilter != nil && l.printfFilter(format, args...) } -func (l *loggerWrapper) filterln(args ...interface{}) bool { +func (l *loggerWrapper) filterln(args ...any) bool { return l.printlnFilter != nil && l.printlnFilter(args...) } -func (l *loggerWrapper) Info(args ...interface{}) { +func (l *loggerWrapper) Info(args ...any) { if l.filter(args) { return } @@ -106,7 +105,7 @@ func (l *loggerWrapper) Info(args ...interface{}) { l.log.Info().Msg(fmt.Sprint(args...)) } -func (l *loggerWrapper) Infoln(args ...interface{}) { +func (l *loggerWrapper) Infoln(args ...any) { if l.filterln(args) { return } @@ -114,7 +113,7 @@ func (l *loggerWrapper) Infoln(args ...interface{}) { l.log.Info().Msg(fmt.Sprint(args...)) } -func (l *loggerWrapper) Infof(format string, args ...interface{}) { +func (l *loggerWrapper) Infof(format string, args ...any) { if l.filterf(format, args...) { return } @@ -122,7 +121,7 @@ func (l *loggerWrapper) Infof(format string, args ...interface{}) { l.log.Info().Msg(fmt.Sprintf(format, args...)) } -func (l *loggerWrapper) Warning(args ...interface{}) { +func (l *loggerWrapper) Warning(args ...any) { if l.filter(args...) { return } @@ -130,7 +129,7 @@ func (l *loggerWrapper) Warning(args ...interface{}) { l.log.Warn().Msg(fmt.Sprint(args...)) } -func (l *loggerWrapper) Warningln(args ...interface{}) { +func (l *loggerWrapper) Warningln(args ...any) { if l.filterln(args) { return } @@ -138,7 +137,7 @@ func (l *loggerWrapper) Warningln(args ...interface{}) { l.log.Warn().Msg(fmt.Sprint(args...)) } -func (l *loggerWrapper) Warningf(format string, args ...interface{}) { +func (l *loggerWrapper) Warningf(format string, args ...any) { if l.filterf(format, args...) { return } @@ -146,7 +145,7 @@ func (l *loggerWrapper) Warningf(format string, args ...interface{}) { l.log.Warn().Msg(fmt.Sprintf(format, args...)) } -func (l *loggerWrapper) Error(args ...interface{}) { +func (l *loggerWrapper) Error(args ...any) { if l.filter(args...) { return } @@ -154,7 +153,7 @@ func (l *loggerWrapper) Error(args ...interface{}) { l.log.Error().Msg(fmt.Sprint(args...)) } -func (l *loggerWrapper) Errorln(args ...interface{}) { +func (l *loggerWrapper) Errorln(args ...any) { if l.filterln(args) { return } @@ -162,7 +161,7 @@ func (l *loggerWrapper) Errorln(args ...interface{}) { l.log.Error().Msg(fmt.Sprint(args...)) } -func (l *loggerWrapper) Errorf(format string, args ...interface{}) { +func (l *loggerWrapper) Errorf(format string, args ...any) { if l.filterf(format, args...) { return } @@ -170,7 +169,7 @@ func (l *loggerWrapper) Errorf(format string, args ...interface{}) { l.log.Error().Msg(fmt.Sprintf(format, args...)) } -func (l *loggerWrapper) Fatal(args ...interface{}) { +func (l *loggerWrapper) Fatal(args ...any) { if l.filter(args...) { return } @@ -178,7 +177,7 @@ func (l *loggerWrapper) Fatal(args ...interface{}) { l.log.Fatal().Msg(fmt.Sprint(args...)) } -func (l *loggerWrapper) Fatalln(args ...interface{}) { +func (l *loggerWrapper) Fatalln(args ...any) { if l.filterln(args) { return } @@ -186,7 +185,7 @@ func (l *loggerWrapper) Fatalln(args ...interface{}) { l.log.Fatal().Msg(fmt.Sprint(args...)) } -func (l *loggerWrapper) Fatalf(format string, args ...interface{}) { +func (l *loggerWrapper) Fatalf(format string, args ...any) { if l.filterf(format, args...) { return } diff --git a/core/logging/logext/grpclog/log_test.go b/core/logging/logext/grpclog/log_test.go index df4670874..968abc777 100644 --- a/core/logging/logext/grpclog/log_test.go +++ b/core/logging/logext/grpclog/log_test.go @@ -3,7 +3,7 @@ package grpclog import ( "testing" - "github.com/pubgo/funk/log" + "github.com/pubgo/funk/v2/log" "google.golang.org/grpc/grpclog" ) diff --git a/core/logging/logext/slog/log.go b/core/logging/logext/slog/log.go new file mode 100644 index 000000000..bf5241a34 --- /dev/null +++ b/core/logging/logext/slog/log.go @@ -0,0 +1,17 @@ +package slog + +import ( + "log/slog" + + "github.com/pubgo/funk/v2/log" + + "github.com/pubgo/lava/v2/core/logging" +) + +func init() { + logging.Register("slog", SetLogger) +} + +func SetLogger(logger log.Logger) { + slog.SetDefault(slog.New(log.NewSlog(logger.WithName("slog")))) +} diff --git a/core/logging/logext/slog/z_log_test.go b/core/logging/logext/slog/z_log_test.go new file mode 100644 index 000000000..9efff7191 --- /dev/null +++ b/core/logging/logext/slog/z_log_test.go @@ -0,0 +1,21 @@ +package slog + +import ( + "log/slog" + "testing" + + "github.com/pubgo/funk/v2/log" +) + +func TestName(t *testing.T) { + SetLogger(log.GetLogger("testing")) + + slog.Info("hello") + slog.With(slog.String("name", "abc")).Info("hello") + slog.With(slog.String("name", "abc")).Info("hello", "data-map", map[string]any{ + "abc": 1, + "hello": map[string]any{ + "world": 2, + }, + }) +} diff --git a/core/logging/logext/stdlog/log.go b/core/logging/logext/stdlog/log.go index 3660ffef6..a5cf5cc89 100644 --- a/core/logging/logext/stdlog/log.go +++ b/core/logging/logext/stdlog/log.go @@ -5,12 +5,11 @@ import ( "io" "log" - "github.com/pubgo/funk/convert" - logger "github.com/pubgo/funk/log" - "github.com/pubgo/lava/core/logging" -) + "github.com/pubgo/funk/v2/convert" + logger "github.com/pubgo/funk/v2/log" -var evt = logger.NewEvent().Str("ext", "std") + "github.com/pubgo/lava/v2/core/logging" +) func init() { logging.Register("stdLog", SetLogger) @@ -21,7 +20,7 @@ func SetLogger(logger logger.Logger) { stdLog := log.Default() // 接管系统默认log - *stdLog = *log.New(&std{l: logger.WithEvent(evt).WithCallerSkip(3)}, "", 0) + *stdLog = *log.New(&std{l: logger.WithName("std").WithCallerSkip(3)}, "", 0) } var _ io.Writer = (*std)(nil) diff --git a/core/logging/logext/stdlog/log_test.go b/core/logging/logext/stdlog/log_test.go index 330253707..70f1568e6 100644 --- a/core/logging/logext/stdlog/log_test.go +++ b/core/logging/logext/stdlog/log_test.go @@ -4,7 +4,7 @@ import ( stdLog "log" "testing" - "github.com/pubgo/funk/log" + "github.com/pubgo/funk/v2/log" ) func TestName(t *testing.T) { diff --git a/core/metrics/aaa.go b/core/metrics/aaa.go index e683f027f..a4cf0d68c 100644 --- a/core/metrics/aaa.go +++ b/core/metrics/aaa.go @@ -1,7 +1,7 @@ package metrics import ( - "github.com/pubgo/funk/log" + "github.com/pubgo/funk/v2/log" tally "github.com/uber-go/tally/v4" ) diff --git a/core/metrics/config.go b/core/metrics/config.go index 4b36fd3f7..1a38b0150 100644 --- a/core/metrics/config.go +++ b/core/metrics/config.go @@ -3,7 +3,7 @@ package metrics import ( "time" - "github.com/pubgo/funk/config" + "github.com/pubgo/funk/v2/config" ) var Name = "metric" diff --git a/core/metrics/drivers/prometheus/reporter.go b/core/metrics/drivers/prometheus/reporter.go index d3ab55c8c..3ef668411 100644 --- a/core/metrics/drivers/prometheus/reporter.go +++ b/core/metrics/drivers/prometheus/reporter.go @@ -1,14 +1,13 @@ package prometheus import ( - "github.com/prometheus/common/model" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/log" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/log" tally "github.com/uber-go/tally/v4" "github.com/uber-go/tally/v4/prometheus" - "github.com/pubgo/lava/core/debug" - "github.com/pubgo/lava/core/metrics" + "github.com/pubgo/lava/v2/core/debug" + "github.com/pubgo/lava/v2/core/metrics" ) const ( @@ -27,8 +26,7 @@ func New(conf *metrics.Config, log log.Logger) *tally.ScopeOptions { opts := tally.ScopeOptions{} opts.Separator = prometheus.DefaultSeparator - //opts.SanitizeOptions = &prometheus.DefaultSanitizerOpts - model.NameValidationScheme = model.UTF8Validation + // opts.SanitizeOptions = &prometheus.DefaultSanitizerOpts proCfg := &prometheus.Configuration{TimerType: "histogram"} diff --git a/core/metrics/factory.go b/core/metrics/factory.go index 0c23e15d3..3e303c6e9 100644 --- a/core/metrics/factory.go +++ b/core/metrics/factory.go @@ -1,8 +1,8 @@ package metrics import ( - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/recovery" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/recovery" ) var factories = make(map[string]Factory) diff --git a/core/metrics/metric.go b/core/metrics/metric.go deleted file mode 100644 index 030cc942b..000000000 --- a/core/metrics/metric.go +++ /dev/null @@ -1,34 +0,0 @@ -package metrics - -import ( - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/generic" - "github.com/pubgo/funk/log" - "github.com/pubgo/funk/merge" - "github.com/pubgo/funk/version" - lo "github.com/samber/lo" - "github.com/uber-go/tally/v4" - - "github.com/pubgo/lava/core/lifecycle" -) - -func New(m lifecycle.Lifecycle, cfg *Config, log log.Logger) Metric { - cfg = merge.Struct(generic.Ptr(DefaultCfg()), cfg).Unwrap() - - log = log.WithName(Name) - - factory := Get(cfg.Driver) - assert.If(factory == nil, "driver factory[%s] not found", cfg.Driver) - opts := factory(cfg, log.WithName("driver")) - if opts == nil { - return tally.NoopScope - } - - opts.Tags = Tags{"project": version.Project()} - - scope, closer := tally.NewRootScope(lo.FromPtr(opts), cfg.Interval) - m.BeforeStop(func() { assert.Must(closer.Close()) }) - - registerVars(scope) - return scope -} diff --git a/core/metrics/metricbuilder/builder.go b/core/metrics/metricbuilder/builder.go new file mode 100644 index 000000000..1a0d79dbb --- /dev/null +++ b/core/metrics/metricbuilder/builder.go @@ -0,0 +1,36 @@ +package metricbuilder + +import ( + "context" + + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/buildinfo/version" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/merge" + lo "github.com/samber/lo" + "github.com/uber-go/tally/v4" + + "github.com/pubgo/lava/v2/core/lifecycle" + "github.com/pubgo/lava/v2/core/metrics" +) + +func New(m lifecycle.Lifecycle, cfg *metrics.Config, log log.Logger) metrics.Metric { + cfg = merge.Struct(lo.ToPtr(metrics.DefaultCfg()), cfg).Unwrap() + + log = log.WithName(metrics.Name) + + factory := metrics.Get(cfg.Driver) + assert.If(factory == nil, "driver factory[%s] not found", cfg.Driver) + opts := factory(cfg, log.WithName("driver")) + if opts == nil { + return tally.NoopScope + } + + opts.Tags = metrics.Tags{"project": version.Project()} + + scope, closer := tally.NewRootScope(lo.FromPtr(opts), cfg.Interval) + m.BeforeStop(func(ctx context.Context) error { return closer.Close() }) + + registerVars(scope) + return scope +} diff --git a/core/metrics/metricbuilder/vars.go b/core/metrics/metricbuilder/vars.go new file mode 100644 index 000000000..842472c5b --- /dev/null +++ b/core/metrics/metricbuilder/vars.go @@ -0,0 +1,27 @@ +package metricbuilder + +import ( + "github.com/pubgo/funk/v2/typex" + "github.com/pubgo/funk/v2/vars" + "github.com/uber-go/tally/v4" + + "github.com/pubgo/lava/v2/core/metrics" +) + +func registerVars(m metrics.Metric) { + vars.Register(vars.UniqueName(metrics.Name, "capabilities"), func() any { + c := m.Capabilities() + return typex.Ctx{ + "reporting": c.Reporting(), + "tagging": c.Tagging(), + } + }) + + vars.Register(vars.UniqueName(metrics.Name, "snapshot"), func() any { + if c, ok := m.(tally.TestScope); ok { + // TODO 数据序列化处理 + return c.Snapshot() + } + return nil + }) +} diff --git a/core/metrics/vars.go b/core/metrics/vars.go deleted file mode 100644 index 24d741b97..000000000 --- a/core/metrics/vars.go +++ /dev/null @@ -1,25 +0,0 @@ -package metrics - -import ( - "github.com/pubgo/funk/typex" - "github.com/pubgo/funk/vars" - "github.com/uber-go/tally/v4" -) - -func registerVars(m Metric) { - vars.Register(Name+"_capabilities", func() interface{} { - c := m.Capabilities() - return typex.Ctx{ - "reporting": c.Reporting(), - "tagging": c.Tagging(), - } - }) - - vars.Register(Name+"_snapshot", func() interface{} { - if c, ok := m.(tally.TestScope); ok { - // TODO 数据序列化处理 - return c.Snapshot() - } - return nil - }) -} diff --git a/core/pidfile/builder.go b/core/pidfile/builder.go deleted file mode 100644 index 1ca2f406d..000000000 --- a/core/pidfile/builder.go +++ /dev/null @@ -1,21 +0,0 @@ -package pidfile - -import ( - "path/filepath" - - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/config" - "github.com/pubgo/funk/pathutil" - - "github.com/pubgo/lava/core/lifecycle" -) - -func New() lifecycle.Handler { - return func(lc lifecycle.Lifecycle) { - pidPath = filepath.Join(config.GetConfigDir(), Name) - - _ = pathutil.IsNotExistMkDir(pidPath) - - lc.AfterStart(func() { assert.Must(SavePid()) }) - } -} diff --git a/core/pidfile/pidfile.go b/core/pidfile/pidfile.go index 8efb60336..592a45154 100644 --- a/core/pidfile/pidfile.go +++ b/core/pidfile/pidfile.go @@ -5,44 +5,57 @@ import ( "os" "path/filepath" "strconv" - "syscall" + "sync" - "github.com/pubgo/funk/config" - "github.com/pubgo/funk/result" - "github.com/pubgo/funk/running" + "github.com/pubgo/funk/v2/config" + "github.com/pubgo/funk/v2/log/logfields" + "github.com/pubgo/funk/v2/result" + "github.com/pubgo/funk/v2/running" + "github.com/rs/zerolog" ) -const Name = "pidfile" - -var pidPath = filepath.Join(config.GetConfigDir(), Name) - -const pidPerm os.FileMode = 0o666 - -func GetPid() result.Result[int] { - f := GetPidF() - if f.IsErr() { - return result.Err[int](f.Err()) +var getPidPath = sync.OnceValue(func() string { + return filepath.Join(config.GetConfigDir(), "."+running.Project()+".pid") +}) + +const pidPerm os.FileMode = 0o644 + +func Get() (r result.Result[int]) { + pidPath := GetPath() + p := result.Wrap(os.ReadFile(pidPath)). + Validate(func(val []byte) error { + if len(val) == 0 { + return fmt.Errorf("pid file is empty") + } + return nil + }). + Log(func(e *zerolog.Event) { + e.Str("path", pidPath) + e.Str(logfields.Msg, "read pid file failed") + }). + UnwrapOrThrow(&r) + if r.IsErr() { + return r } - p, err := os.ReadFile(f.Unwrap()) - if err != nil { - return result.Wrap(0, err) - } - - return result.Wrap(strconv.Atoi(string(p))) + return result.Wrap(strconv.Atoi(string(p))). + Log(func(e *zerolog.Event) { + e.Str("path", pidPath) + e.Str("pid", string(p)) + e.Str(logfields.Msg, "convert pid to int failed") + }) } -func GetPidF() result.Result[string] { - filename := fmt.Sprintf("%s.pid", running.Project) - return result.OK(filepath.Join(pidPath, filename)) -} +func GetPath() string { return getPidPath() } -func SavePid() error { - f := GetPidF() - if f.IsErr() { - return f.Err() - } +func Save() (r result.Error) { + pidPath := GetPath() + pid := os.Getpid() - pid := syscall.Getpid() - return os.WriteFile(f.Unwrap(), []byte(strconv.Itoa(pid)), pidPerm) + return result.ErrOf(os.WriteFile(pidPath, []byte(strconv.Itoa(pid)), pidPerm)). + Log(func(e *zerolog.Event) { + e.Str("path", pidPath) + e.Int("pid", pid) + e.Str(logfields.Msg, "write pid file failed") + }) } diff --git a/core/pidfile/pidfilebuilder/builder.go b/core/pidfile/pidfilebuilder/builder.go new file mode 100644 index 000000000..b1edb3c38 --- /dev/null +++ b/core/pidfile/pidfilebuilder/builder.go @@ -0,0 +1,14 @@ +package pidfilebuilder + +import ( + "context" + + "github.com/pubgo/lava/v2/core/lifecycle" + "github.com/pubgo/lava/v2/core/pidfile" +) + +func New() lifecycle.Handler { + return func(lc lifecycle.Lifecycle) { + lc.AfterStart(func(ctx context.Context) error { return pidfile.Save().GetErr() }) + } +} diff --git a/core/registry/aaa.go b/core/registry/aaa.go index 3f8f3ba7d..4ffe2a995 100644 --- a/core/registry/aaa.go +++ b/core/registry/aaa.go @@ -4,7 +4,7 @@ package registry import ( "context" - "github.com/pubgo/lava/core/service" + "github.com/pubgo/lava/v2/core/service" ) // Registry The registry provides an interface for service discovery diff --git a/core/registry/builder.go b/core/registry/builder.go index 6bb03b4b2..e7b8716ff 100644 --- a/core/registry/builder.go +++ b/core/registry/builder.go @@ -7,17 +7,17 @@ import ( "strings" "time" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/async" - "github.com/pubgo/funk/errors" - "github.com/pubgo/funk/log" - "github.com/pubgo/funk/running" - "github.com/pubgo/funk/version" - - "github.com/pubgo/lava/core/lifecycle" - "github.com/pubgo/lava/core/service" - "github.com/pubgo/lava/internal/logutil" - "github.com/pubgo/lava/pkg/netutil" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/async" + "github.com/pubgo/funk/v2/buildinfo/version" + "github.com/pubgo/funk/v2/errors" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/running" + + "github.com/pubgo/lava/v2/core/lifecycle" + "github.com/pubgo/lava/v2/core/service" + "github.com/pubgo/lava/v2/internal/logutil" + "github.com/pubgo/lava/v2/pkg/netutil" ) func New(c *Config, lifecycle lifecycle.Lifecycle, regs map[string]Registry) { @@ -31,14 +31,14 @@ func New(c *Config, lifecycle lifecycle.Lifecycle, regs map[string]Registry) { return &errors.Err{ Msg: "registry driver is null", Tags: errors.Tags{ - errors.T("driver", cfg.Driver), - errors.T("regs", regs), + "driver": cfg.Driver, + "regs": regs, }, } }) // 服务注册 - lifecycle.AfterStart(func() { + lifecycle.AfterStart(func(ctx context.Context) error { SetDefault(reg) register(reg) @@ -65,17 +65,19 @@ func New(c *Config, lifecycle lifecycle.Lifecycle, regs map[string]Registry) { }) // 服务撤销 - lifecycle.BeforeStop(func() { + lifecycle.BeforeStop(func(ctx context.Context) error { cancel() deregister(reg) + return nil }) + return nil }) } func register(reg Registry) { // parse address for host, port var advt, host string - port := running.GrpcPort + port := running.GrpcPort() parts := strings.Split(advt, ":") if len(parts) > 1 { @@ -94,12 +96,12 @@ func register(reg Registry) { Port: port, Version: version.Version(), Address: fmt.Sprintf("%s:%d", host, port), - Id: running.Project + "-" + running.Hostname + "-" + running.InstanceID, + Id: running.Project() + "-" + running.Hostname + "-" + running.InstanceID, Metadata: map[string]string{"registry": reg.String()}, } s := &service.Service{ - Name: running.Project, + Name: running.Project(), Nodes: []*service.Node{node}, } @@ -108,18 +110,18 @@ func register(reg Registry) { "register service node", func() error { err := reg.Register(context.Background(), s) - return errors.WrapTag(err, - errors.T("instance_id", node.Id), - errors.T("service", running.Project), - errors.T("registry", Default().String()), - ) + return errors.WrapTags(err, errors.Tags{ + "instance_id": node.Id, + "service": running.Project, + "registry": Default().String(), + }) }, ) } func deregister(reg Registry) { var advt, host string - port := running.GrpcPort + port := running.GrpcPort() parts := strings.Split(advt, ":") if len(parts) > 1 { @@ -133,12 +135,12 @@ func deregister(reg Registry) { node := &service.Node{ Port: port, Address: fmt.Sprintf("%s:%d", host, port), - Id: running.Project + "-" + running.Hostname + "-" + running.InstanceID, + Id: running.Project() + "-" + running.Hostname + "-" + running.InstanceID, Metadata: make(map[string]string), } s := &service.Service{ - Name: running.Project, + Name: running.Project(), Nodes: []*service.Node{node}, } @@ -147,10 +149,10 @@ func deregister(reg Registry) { "deregister service node", func() error { err := reg.Deregister(context.Background(), s) - return errors.WrapTag(err, - errors.T("id", node.Id), - errors.T("name", running.Project), - ) + return errors.WrapTags(err, errors.Tags{ + "id": node.Id, + "name": running.Project, + }) }, ) } diff --git a/core/registry/config.go b/core/registry/config.go index 69888992e..791b58696 100644 --- a/core/registry/config.go +++ b/core/registry/config.go @@ -4,8 +4,8 @@ import ( "fmt" "time" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/errors" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/errors" ) // https://github.com/go-eagle/eagle/blob/master/pkg/registry/registry.go @@ -33,9 +33,9 @@ const ( ) type Config struct { - RegisterInterval time.Duration `yaml:"registerInterval"` - Driver string `json:"driver" yaml:"driver"` - DriverCfg map[string]interface{} `json:"driver_config" yaml:"driver_config"` + RegisterInterval time.Duration `yaml:"registerInterval"` + Driver string `json:"driver" yaml:"driver"` + DriverCfg map[string]any `json:"driver_config" yaml:"driver_config"` } func (cfg *Config) Check() *Config { diff --git a/core/registry/drivers/mdns/mdns.go b/core/registry/drivers/mdns/mdns.go index 2c4e927e8..cc5a13f71 100644 --- a/core/registry/drivers/mdns/mdns.go +++ b/core/registry/drivers/mdns/mdns.go @@ -5,15 +5,15 @@ import ( "context" "github.com/grandcat/zeroconf" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/errors" - "github.com/pubgo/funk/log" - "github.com/pubgo/funk/merge" - "github.com/pubgo/funk/recovery" - "github.com/pubgo/funk/typex" - - "github.com/pubgo/lava/core/registry" - "github.com/pubgo/lava/core/service" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/errors" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/merge" + "github.com/pubgo/funk/v2/recovery" + "github.com/pubgo/funk/v2/typex" + + "github.com/pubgo/lava/v2/core/registry" + "github.com/pubgo/lava/v2/core/service" ) const ( @@ -28,7 +28,7 @@ func New(conf *registry.Config, log log.Logger) registry.Registry { } var cfg Cfg - merge.MapStruct(&cfg, conf.DriverCfg).Unwrap() + merge.MapStruct(&cfg, conf.DriverCfg).Must() resolver, err := zeroconf.NewResolver() assert.MustF(err, "Failed to initialize zeroconf resolver") @@ -68,7 +68,7 @@ func (m *mdnsRegistry) Register(ctx context.Context, service *service.Service, o // 已经存在 if m.services.Has(node.Id) { - return + return gErr } server, err := zeroconf.Register(node.Id, service.Name, zeroconfDomain, node.GetPort(), []string{node.Id}, nil) @@ -79,12 +79,14 @@ func (m *mdnsRegistry) Register(ctx context.Context, service *service.Service, o optList[i](&opts) } + _ = opts + m.services.Set(node.Id, &serverNode{ srv: server, id: node.Id, name: service.Name, }) - return + return gErr } func (m *mdnsRegistry) Deregister(ctx context.Context, service *service.Service, opt ...registry.DeregOpt) (gErr error) { @@ -98,11 +100,11 @@ func (m *mdnsRegistry) Deregister(ctx context.Context, service *service.Service, node := service.Nodes[0] val, ok := m.services.LoadAndDelete(node.Id) if !ok || val == nil { - return + return gErr } val.(*serverNode).srv.Shutdown() - return + return gErr } func (m *mdnsRegistry) String() string { return Name } diff --git a/core/registry/options.go b/core/registry/options.go index f345ea505..464a3e7fb 100644 --- a/core/registry/options.go +++ b/core/registry/options.go @@ -5,7 +5,7 @@ import ( "crypto/tls" "time" - "github.com/pubgo/lava/core/service" + "github.com/pubgo/lava/v2/core/service" ) func TTL(dur time.Duration) RegOpt { diff --git a/core/responses/response.go b/core/responses/response.go deleted file mode 100644 index cd5639f57..000000000 --- a/core/responses/response.go +++ /dev/null @@ -1,10 +0,0 @@ -package responses - -import ( - "github.com/pubgo/funk/proto/errorpb" -) - -type Response struct { - Err *errorpb.ErrCode `json:"err"` - Data any `json:"data"` -} diff --git a/core/responses/response_test.go b/core/responses/response_test.go deleted file mode 100644 index d2f612c5c..000000000 --- a/core/responses/response_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package responses - -import ( - "encoding/json" - "testing" - - "github.com/pubgo/funk/proto/testcodepb" - "github.com/stretchr/testify/assert" -) - -func TestResponse(t *testing.T) { - rsp := &Response{ - Err: testcodepb.ErrCodeDbConn, - Data: nil, - } - bytes, err1 := json.Marshal(rsp) - assert.Nil(t, err1) - t.Log(string(bytes)) -} diff --git a/core/rpcmeta/registry.go b/core/rpcmeta/registry.go index 8f849b175..918635f74 100644 --- a/core/rpcmeta/registry.go +++ b/core/rpcmeta/registry.go @@ -1,7 +1,7 @@ package rpcmeta import ( - "github.com/pubgo/funk/assert" + "github.com/pubgo/funk/v2/assert" ) var rpcMetas = make(map[string]*RpcMeta) diff --git a/core/scheduler/aaa.go b/core/scheduler/aaa.go new file mode 100644 index 000000000..da262b65f --- /dev/null +++ b/core/scheduler/aaa.go @@ -0,0 +1,127 @@ +package scheduler + +import ( + "context" + "time" + + "github.com/pubgo/funk/v2/clone" + "github.com/pubgo/funk/v2/result" + "github.com/pubgo/funk/v2/stack" + "github.com/reugn/go-quartz/quartz" + "go.uber.org/atomic" +) + +type JobExecutor interface { + Name() string + Exec(ctx context.Context, name string, metadata *JobMetadata) result.Result[[]byte] +} + +var _ JobExecutor = (JobFunc)(nil) + +type JobFunc func(ctx context.Context, name string, metadata *JobMetadata) result.Result[[]byte] + +func (j JobFunc) Name() string { + return stack.CallerWithFunc(j).String() +} + +func (j JobFunc) Exec(ctx context.Context, name string, metadata *JobMetadata) result.Result[[]byte] { + return j(ctx, name, metadata) +} + +type JobRegister interface { + RegisterSchedulerJob(reg JobRegistry) +} + +type JobRegistry interface { + Once(name string, delay time.Duration, fn JobFunc) result.Error + Every(name string, dur time.Duration, fn JobFunc) result.Error + Cron(name, expr string, fn JobFunc) result.Error +} + +type JobManager interface { + CreateJob(spec JobSpec) result.Error + PatchJob(name string, config *JobConfig) result.Error + PauseJob(name string) result.Error + ResumeJob(name string) result.Error + DeleteJob(name string) result.Error + ReloadJob(name string) result.Error + ListJobs() []*Job + GetJob(name string) result.Result[*Job] +} + +type JobSpec struct { + Name string + Config *JobConfig + Executor string + Once *OnceJob + Ticker *TickerJob + Cron *CronJob +} + +type OnceJob struct { + Delay time.Duration +} + +type TickerJob struct { + Dur time.Duration +} + +type CronJob struct { + Expr string +} + +type Job struct { + Spec *JobSpec + Metadata JobMetadata + Status Status + + PreExecTime int64 + ExecTime int64 + + Error error + Result []byte + Runs uint64 +} + +type jobTask struct { + spec *JobSpec + executor JobExecutor + + trigger *triggerImpl + runs atomic.Uint64 + jobKey *quartz.JobKey + status Status + + result result.Result[[]byte] +} + +func (job jobTask) ToJob() *Job { + return &Job{ + Status: job.status, + PreExecTime: job.trigger.prev, + ExecTime: job.trigger.next, + Error: job.result.GetErr(), + Result: job.result.UnwrapOrEmpty(), + Runs: job.runs.Load(), + Spec: clone.Clone(job.spec), + } +} + +type Status string + +const ( + StatusRunning Status = "running" + StatusStop Status = "stop" +) + +type JobMetadata struct { + Name string + Timeout time.Duration + MaxRetries int + RetryInterval time.Duration + Replace bool + Location string + + NextExecTime int64 + ExecTime int64 +} diff --git a/core/scheduler/builder.go b/core/scheduler/builder.go index e2e573290..ff3deb1ef 100644 --- a/core/scheduler/builder.go +++ b/core/scheduler/builder.go @@ -2,46 +2,80 @@ package scheduler import ( "context" - "fmt" + "log/slog" - "github.com/pubgo/funk/log" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/log/logfields" + "github.com/pubgo/funk/v2/result" + "github.com/pubgo/funk/v2/vars" + qlog "github.com/reugn/go-quartz/logger" "github.com/reugn/go-quartz/quartz" + "github.com/rs/zerolog" - "github.com/pubgo/lava/core/lifecycle" - "github.com/pubgo/lava/core/metrics" + "github.com/pubgo/lava/v2/core/lifecycle" + "github.com/pubgo/lava/v2/core/metrics" + "github.com/pubgo/lava/v2/core/supervisor" ) const Name = "scheduler" -func New(m lifecycle.Lifecycle, log log.Logger, opts []*Config, routers []CronRouter, metric metrics.Metric) *Scheduler { - config := make(map[string]JobSetting) - if len(opts) > 0 && opts[0] != nil { - for _, setting := range opts[0].JobSettings { - if _, ok := config[setting.Name]; ok { - panic(fmt.Sprintf("schedule job(%s) exists", setting.Name)) - } +type Params struct { + M lifecycle.Lifecycle + Log log.Logger + Configs []*Config + Routers []JobRegister + Metric metrics.Metric + Executors []JobExecutor +} - config[setting.Name] = setting - } - } +type ResponseParams struct { + Service supervisor.Service + Manager JobManager +} + +func New(m lifecycle.Lifecycle, logger log.Logger, metric metrics.Metric, configs []*Config, routers []JobRegister, executors []JobExecutor) (_ *Scheduler, gErr error) { + defer result.RecoveryErr(&gErr) + + configMap := result.Wrap(createConfig(configs)). + UnwrapOrLog(func(e *zerolog.Event) { + e.Any("configs", configs) + e.Any(logfields.Msg, "failed to create config") + }) ctx, cancel := context.WithCancel(context.Background()) - quart := &Scheduler{ - metric: metric, - config: config, - scheduler: quartz.NewStdScheduler(), - log: log.WithName(Name), - ctx: ctx, - cancel: cancel, - jobs: make(map[string]JobFunc), + + slogLogger := qlog.NewSlogLogger(ctx, slog.With(slog.String(logfields.Module, Name))) + scheduler := result.Wrap(quartz.NewStdScheduler(quartz.WithLogger(slogLogger), quartz.WithJobMetadata())). + UnwrapOrLog(func(e *zerolog.Event) { + e.Str(logfields.Msg, "failed to create scheduler") + }) + + jobExecutors := make(map[string]JobExecutor) + for _, executor := range executors { + regJobExecutor(jobExecutors, executor). + MustWithLog(func(e *zerolog.Event) { + e.Str(logfields.Msg, "failed to register job executor") + }) } - quart.start() - m.BeforeStop(quart.stop) + quart := &Scheduler{ + metric: metric, + configMap: configMap, + scheduler: scheduler, + log: logger.WithName(Name), + ctx: ctx, + cancel: cancel, + jobExecutors: jobExecutors, + } for _, r := range routers { - r.Crontab(quart) + r.RegisterSchedulerJob(quart) } - return quart + m.AfterStart(func(ctx context.Context) error { return lifecycle.WrapNoCtxErr(quart.start)(ctx) }) + m.BeforeStop(func(ctx context.Context) error { return lifecycle.WrapNoCtxErr(quart.stop)(ctx) }) + + vars.Register(vars.UniqueName(Name), func() any { return quart.ListJobs() }) + + return quart, nil } diff --git a/core/scheduler/config.go b/core/scheduler/config.go index 5174d1a29..687afa3f6 100644 --- a/core/scheduler/config.go +++ b/core/scheduler/config.go @@ -1,13 +1,128 @@ package scheduler -type JobSetting struct { - Disabled bool `yaml:"disabled"` - Schedule string `yaml:"schedule"` - Name string `yaml:"name"` - Timeout string `yaml:"timeout"` +import ( + "fmt" + "time" + + "github.com/pubgo/funk/v2/errors" + "github.com/pubgo/funk/v2/log/logfields" + "github.com/pubgo/funk/v2/result" + "github.com/reugn/go-quartz/quartz" + "github.com/rs/zerolog" + "github.com/samber/lo" +) + +func createConfig(configs []*Config) (map[string]*JobConfig, error) { + configMap := make(map[string]*JobConfig) + if len(configs) == 0 || configs[0] == nil { + return configMap, nil + } + + for _, config := range configs[0].JobConfigs { + if config.Name == "" { + return nil, errors.Errorf("schedule job name is empty") + } + + if _, ok := configMap[config.Name]; ok { + return nil, errors.Errorf("schedule job(%s) exists", config.Name) + } + } + return configMap, nil +} + +func defaultConfig(name string) *JobConfig { + return &JobConfig{ + Name: name, + Disabled: lo.ToPtr(false), + Timeout: lo.ToPtr(time.Second * 10), + RetryInterval: lo.ToPtr(time.Second), + MaxRetries: lo.ToPtr(3), + Replace: lo.ToPtr(false), + Location: lo.ToPtr(time.UTC.String()), + location: time.UTC, + } +} + +func initAndMergeConfig(name string, jobConfigs ...*JobConfig) (r result.Result[*JobConfig]) { + defer result.Recovery(&r) + + cfg := defaultConfig(name) + for _, jobConfig := range jobConfigs { + if jobConfig == nil { + continue + } + + if jobConfig.Disabled != nil { + cfg.Disabled = jobConfig.Disabled + } + + if jobConfig.Timeout != nil { + cfg.Timeout = jobConfig.Timeout + } + + if jobConfig.RetryInterval != nil { + cfg.RetryInterval = jobConfig.RetryInterval + } + + if jobConfig.MaxRetries != nil { + cfg.MaxRetries = jobConfig.MaxRetries + } + + if jobConfig.Replace != nil { + cfg.Replace = jobConfig.Replace + } + + if jobConfig.Location != nil { + cfg.Location = jobConfig.Location + } + + cfg.location = result.Wrap(time.LoadLocation(lo.FromPtr(cfg.Location))). + UnwrapOrLog(func(e *zerolog.Event) { + e.Str(logfields.Msg, fmt.Sprintf("failed to parse time location:%s", lo.FromPtr(cfg.Location))) + }) + } + + return r.WithValue(cfg) +} + +type JobConfig struct { + Disabled *bool `yaml:"disabled"` + Name string `yaml:"name"` + Timeout *time.Duration `yaml:"timeout"` + + // MaxRetries is the maximum number of retries before aborting the + // current job execution. + // Default: 0. + MaxRetries *int `yaml:"max_retries"` + + // RetryInterval is the fixed time interval between retry attempts. + // Default: 1 second. + RetryInterval *time.Duration `yaml:"retry_interval"` + + // Replace indicates whether the job should replace an existing job + // with the same key. + // Default: false. + Replace *bool `yaml:"replace"` + + Location *string `yaml:"location"` + + location *time.Location +} + +func (c JobConfig) ToJobDetailOptions() *quartz.JobDetailOptions { + return &quartz.JobDetailOptions{ + MaxRetries: lo.FromPtr(c.MaxRetries), + RetryInterval: lo.FromPtr(c.RetryInterval), + Replace: lo.FromPtr(c.Replace), + Suspended: false, + } } type Config struct { - Timeout string `yaml:"timeout"` - JobSettings []JobSetting `yaml:"jobs"` + Timeout string `yaml:"timeout"` + JobConfigs []JobConfig `yaml:"jobs"` +} + +type JobsConfigLoader struct { + Scheduler *Config `yaml:"scheduler"` } diff --git a/core/scheduler/interface.go b/core/scheduler/interface.go deleted file mode 100644 index 64b18dfe4..000000000 --- a/core/scheduler/interface.go +++ /dev/null @@ -1,9 +0,0 @@ -package scheduler - -import "context" - -type JobFunc func(ctx context.Context, name string) error - -type CronRouter interface { - Crontab(s *Scheduler) -} diff --git a/core/scheduler/job.go b/core/scheduler/job.go new file mode 100644 index 000000000..9551d6dbc --- /dev/null +++ b/core/scheduler/job.go @@ -0,0 +1,101 @@ +package scheduler + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/pubgo/funk/v2" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/try" + "github.com/reugn/go-quartz/quartz" + "github.com/rs/zerolog" + "github.com/samber/lo" + + "github.com/pubgo/lava/v2/core/metrics" +) + +type namedJob struct { + s *Scheduler + log log.Logger + + task *jobTask +} + +func (t *namedJob) Description() string { return t.task.spec.Name } +func (t *namedJob) Execute(ctx context.Context) (gErr error) { + start := time.Now() + name := t.task.spec.Name + + defer func() { + cost := float64(time.Since(start).Milliseconds()) + t.s.metric.Tagged(metrics.Tags{"job_name": name}).Gauge("job_cost_ms").Update(cost) + + logger := funk.Ternary(gErr == nil, t.log.Info(), t.log.Err(gErr)) + logger.Func(func(e *zerolog.Event) { + e.Float32("job_cost_ms", float32(cost)) + e.Str("job_name", name) + e.Uint64("runs", t.task.runs.Load()) + e.Msg("exec scheduler job") + }) + }() + + t.task.runs.Inc() + config := t.task.spec.Config + metadata := JobMetadata{ + Name: config.Name, + Replace: lo.FromPtr(config.Replace), + MaxRetries: lo.FromPtr(config.MaxRetries), + RetryInterval: lo.FromPtr(config.RetryInterval), + Timeout: lo.FromPtr(config.Timeout), + Location: config.location.String(), + ExecTime: t.task.trigger.prev, + NextExecTime: t.task.trigger.next, + } + + if t.task.trigger.err != nil { + if !errors.Is(t.task.trigger.err, quartz.ErrTriggerExpired) || t.task.spec.Once == nil { + return fmt.Errorf("schedule job(%s) trigger error: %w", name, t.task.trigger.err) + } + } + + return try.Try(func() error { + ctx, cancel := context.WithTimeout(ctx, lo.FromPtr(config.Timeout)) + defer cancel() + + t.task.result = t.task.executor.Exec(ctx, name, &metadata) + return t.task.result.GetErr() + }) +} + +var _ quartz.Trigger = &triggerImpl{} + +func newTrigger(trigger quartz.Trigger) *triggerImpl { + return &triggerImpl{trigger: trigger} +} + +type triggerImpl struct { + prev int64 + next int64 + err error + trigger quartz.Trigger +} + +func (t *triggerImpl) NextFireTime(prev int64) (next int64, err error) { + defer func() { + t.err = err + if err != nil { + return + } + + t.prev = prev / 1000_000_000 + t.next = next / 1000_000_000 + }() + + return t.trigger.NextFireTime(prev) +} + +func (t *triggerImpl) Description() string { + return t.trigger.Description() +} diff --git a/core/scheduler/scheduler.go b/core/scheduler/scheduler.go index 84f2e7a90..bdf8eea89 100644 --- a/core/scheduler/scheduler.go +++ b/core/scheduler/scheduler.go @@ -3,115 +3,307 @@ package scheduler import ( "context" "fmt" + "sync" "time" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/errors" - "github.com/pubgo/funk/generic" - "github.com/pubgo/funk/log" - "github.com/pubgo/funk/stack" - "github.com/pubgo/funk/try" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/log/logfields" + "github.com/pubgo/funk/v2/result" "github.com/reugn/go-quartz/quartz" + "github.com/rs/zerolog" - "github.com/pubgo/lava/core/metrics" + "github.com/pubgo/lava/v2/core/metrics" ) -type job struct { - key string - cron string - dur time.Duration - once bool -} +var ( + _ JobManager = (*Scheduler)(nil) + _ JobRegistry = (*Scheduler)(nil) +) type Scheduler struct { - metric metrics.Metric - config map[string]JobSetting - scheduler quartz.Scheduler - log log.Logger - cancel context.CancelFunc - ctx context.Context - jobs map[string]JobFunc + metric metrics.Metric + configMap map[string]*JobConfig + scheduler quartz.Scheduler + log log.Logger + cancel context.CancelFunc + ctx context.Context + jobExecutors map[string]JobExecutor + + mu sync.Mutex + + jobs sync.Map } -func (s *Scheduler) stop() { - s.cancel() - s.scheduler.Stop() +func (s *Scheduler) createJob(spec JobSpec, fn JobFunc) (r result.Error) { + task := jobTask{ + spec: &spec, + jobKey: parseJobKey(spec.Name), + status: StatusRunning, + } + + defer func() { + logFn := func(e *zerolog.Event) { + e.Str("name", task.spec.Name) + + if task.executor != nil { + e.Str("executor", task.executor.Name()) + } + + if task.spec.Config != nil { + e.Any("config", task.spec.Config) + } + e.Any("once", task.spec.Once) + e.Any("ticker", task.spec.Ticker) + e.Any("cron", task.spec.Cron) + } + if r.IsErr() { + s.log.Err(r.GetErr()).Func(logFn).Msg("failed to register scheduler job") + } else { + s.log.Info().Func(logFn).Msg("register scheduler job ok") + } + }() + defer result.Recovery(&r) + + if spec.Name == "" { + return r.WithErrorf("job name is empty") + } + + name := spec.Name + if _, ok := s.jobs.Load(name); ok { + return r.WithErrorf("job %s already exists", name) + } + + executorRes := result.WrapFn(func() (JobExecutor, error) { + executor := s.jobExecutors[spec.Executor] + if executor == nil { + executor = fn + } + + if executor == nil { + return nil, fmt.Errorf("schedule job executor is nil, name:%s", name) + } + return executor, nil + }) + executorRes.IfOK(func(executor JobExecutor) { task.executor = executor }) + if executorRes.Throw(&r) { + return r + } + + config := initAndMergeConfig(name, s.configMap[name], spec.Config). + Log(func(e *zerolog.Event) { + e.Str(logfields.Msg, fmt.Sprintf("failed to init schedule job(%s) config", name)) + }). + IfOK(func(config *JobConfig) { + task.spec.Config = config + }). + UnwrapOrThrow(&r) + if r.IsErr() { + return r + } + + triggerRes := getTrigger(spec, config.location). + IfErr(func(err error) { + log.Err(err).Msgf("failed to get schedule job(%s) trigger", name) + }). + IfOK(func(trigger *triggerImpl) { + task.trigger = trigger + }) + if triggerRes.Throw(&r) { + return r + } + + jobOpt := config.ToJobDetailOptions() + job := &namedJob{s: s, task: &task, log: s.log} + jobDetail := quartz.NewJobDetailWithOptions(job, parseJobKey(name), jobOpt) + + if result.Throw(&r, s.scheduler.ScheduleJob(jobDetail, task.trigger)) { + return r + } + + s.jobs.Store(name, &task) + return r } -func (s *Scheduler) start() { - if s.scheduler.IsStarted() { - return +func (s *Scheduler) CreateJob(spec JobSpec) (r result.Error) { + s.mu.Lock() + defer s.mu.Unlock() + return s.createJob(spec, nil) +} + +func (s *Scheduler) getJob(name string) (r result.Result[*jobTask]) { + if val, ok := s.jobs.Load(name); !ok { + return r.WithErrorf("job %s not exists", name) + } else { + return r.WithValue(val.(*jobTask)) } +} - s.scheduler.Start(s.ctx) +func (s *Scheduler) PatchJob(name string, config *JobConfig) (r result.Error) { + s.mu.Lock() + defer s.mu.Unlock() + + job := s.getJob(name).UnwrapOrThrow(&r) + if r.IsErr() { + return r + } + + initAndMergeConfig(name, job.spec.Config, config). + Log(func(e *zerolog.Event) { + e.Str(logfields.Msg, fmt.Sprintf("failed to patch schedule job(%s) config", name)) + }). + IfOK(func(config *JobConfig) { + job.spec.Config = config + }). + Throw(&r) + + return r +} + +func (s *Scheduler) PauseJob(name string) (r result.Error) { + s.mu.Lock() + defer s.mu.Unlock() + + job := s.getJob(name).UnwrapOrThrow(&r) + if r.IsErr() { + return r + } + + job.status = StatusStop + return result.ErrOf(s.scheduler.PauseJob(job.jobKey)).IfErr(func(err error) { + log.Err(err).Msgf("failed to pause schedule job(%s)", name) + }) +} + +func (s *Scheduler) ResumeJob(name string) (r result.Error) { + s.mu.Lock() + defer s.mu.Unlock() + + job := s.getJob(name).UnwrapOrThrow(&r) + if r.IsErr() { + return r + } + + job.status = StatusRunning + return result.ErrOf(s.scheduler.ResumeJob(job.jobKey)). + IfErr(func(err error) { + log.Err(err).Msgf("failed to resume schedule job(%s)", name) + }) +} + +func (s *Scheduler) DeleteJob(name string) (r result.Error) { + s.mu.Lock() + defer s.mu.Unlock() + + job := s.getJob(name).UnwrapOrThrow(&r) + if r.IsErr() { + return r + } + + s.jobs.Delete(name) + return result.ErrOf(s.scheduler.DeleteJob(job.jobKey)). + IfErr(func(err error) { + log.Err(err).Msgf("failed to delete schedule job(%s)", name) + }) } -func (s *Scheduler) checkJobExists(name string, fn JobFunc) error { - if s.jobs[name] != nil { - return &errors.Err{ - Msg: fmt.Sprintf("job %s exists", name), - Detail: stack.CallerWithFunc(s.jobs[name]).String(), +func (s *Scheduler) ReloadJob(name string) (r result.Error) { + s.mu.Lock() + defer s.mu.Unlock() + + job := s.getJob(name).UnwrapOrThrow(&r) + if r.IsErr() { + return r + } + + jobOpt := job.spec.Config.ToJobDetailOptions() + jobDetail := quartz.NewJobDetailWithOptions( + &namedJob{s: s, task: job, log: s.log}, + job.jobKey, + jobOpt, + ) + + jj, _ := s.scheduler.GetScheduledJob(job.jobKey) + if jj != nil { + if result.Throw(&r, s.scheduler.DeleteJob(job.jobKey)) { + return r } } - s.jobs[name] = fn - return nil + if result.Throw(&r, s.scheduler.ScheduleJob(jobDetail, job.trigger)) { + return r + } + return r } -func (s *Scheduler) Once(name string, delay time.Duration, fn JobFunc) { - assert.Must(s.checkJobExists(name, fn)) +func (s *Scheduler) ListJobs() []*Job { + s.mu.Lock() + defer s.mu.Unlock() - s.log.WithCallerSkip(1).Info(). - Str("name", name). - Str("delay", delay.String()). - Msg("register once scheduler") - do(s, job{dur: delay, key: name, once: true}, fn) + var jobs []*Job + s.jobs.Range(func(key, value any) bool { + jobs = append(jobs, value.(*jobTask).ToJob()) + return true + }) + return jobs } -func (s *Scheduler) Every(name string, dur time.Duration, fn JobFunc) { - assert.Must(s.checkJobExists(name, fn)) +func (s *Scheduler) GetJob(name string) (r result.Result[*Job]) { + s.mu.Lock() + defer s.mu.Unlock() - s.log.WithCallerSkip(1).Info(). - Str("name", name). - Str("dur", dur.String()). - Msg("register periodic scheduler") - do(s, job{dur: dur, key: name}, fn) + job := s.getJob(name).UnwrapOrThrow(&r) + if r.IsErr() { + return r + } + + return r.WithValue(job.ToJob()) +} + +func (s *Scheduler) String() string { + return Name } -func (s *Scheduler) Cron(name, expr string, fn JobFunc) { - assert.Must(s.checkJobExists(name, fn)) +func (s *Scheduler) Serve(ctx context.Context) error { + defer s.stop() + s.start() - s.log.WithCallerSkip(1).Info(). - Str("name", name). - Str("expr", expr). - Msg("register cron scheduler") - do(s, job{cron: expr, key: name}, fn) + s.scheduler.Wait(ctx) + return nil } -type namedJob struct { - s *Scheduler - name string - fn JobFunc - log log.Logger +func (s *Scheduler) stop() { + s.cancel() + + if s.scheduler.IsStarted() { + s.scheduler.Stop() + } } -func (t namedJob) Description() string { return t.name } -func (t namedJob) Execute(ctx context.Context) error { - start := time.Now() - err := try.Try(func() error { - ctx, cancel := context.WithCancel(ctx) - defer cancel() +func (s *Scheduler) start() { + if s.scheduler.IsStarted() { + return + } - return t.fn(ctx, t.name) - }) + s.scheduler.Start(s.ctx) +} - t.s.metric.Tagged(metrics.Tags{"job_name": t.name}).Gauge("scheduler_job_cost").Update(float64(time.Since(start).Microseconds()) / 1000) +func (s *Scheduler) Once(name string, delay time.Duration, fn JobFunc) result.Error { + s.mu.Lock() + defer s.mu.Unlock() + + return s.createJob(JobSpec{Name: name, Once: &OnceJob{Delay: delay}}, fn) +} + +func (s *Scheduler) Every(name string, dur time.Duration, fn JobFunc) result.Error { + s.mu.Lock() + defer s.mu.Unlock() + + return s.createJob(JobSpec{Name: name, Ticker: &TickerJob{Dur: dur}}, fn) +} - logger := generic.Ternary(generic.IsNil(err), t.log.Info(), t.log.Err(err)) - logger. - Float32("job-cost-ms", float32(time.Since(start).Microseconds())/1000). - Str("job-name", t.name). - Msg("scheduler job execution") +func (s *Scheduler) Cron(name, expr string, fn JobFunc) result.Error { + s.mu.Lock() + defer s.mu.Unlock() - return err + return s.createJob(JobSpec{Name: name, Cron: &CronJob{Expr: expr}}, fn) } diff --git a/core/scheduler/schedulerbuilder/builder.go b/core/scheduler/schedulerbuilder/builder.go new file mode 100644 index 000000000..66a368225 --- /dev/null +++ b/core/scheduler/schedulerbuilder/builder.go @@ -0,0 +1,30 @@ +package schedulerbuilder + +import ( + "github.com/pubgo/lava/v2/core/scheduler" + "github.com/pubgo/lava/v2/core/supervisor" +) + +type ResponseParams struct { + Service supervisor.Service + Manager scheduler.JobManager +} + +func NewService(params scheduler.Params) (ResponseParams, error) { + s, err := scheduler.New( + params.M, + params.Log, + params.Metric, + params.Configs, + params.Routers, + params.Executors, + ) + if err != nil { + return ResponseParams{}, err + } + + return ResponseParams{ + Service: supervisor.NewService(scheduler.Name, s.Serve), + Manager: s, + }, nil +} diff --git a/core/scheduler/schedulercronexpr/expr.go b/core/scheduler/schedulercronexpr/expr.go new file mode 100644 index 000000000..8900098bd --- /dev/null +++ b/core/scheduler/schedulercronexpr/expr.go @@ -0,0 +1,22 @@ +package schedulercronexpr + +// 一些常用的crontab表达式 +const ( + EveryMinute = "* * * * *" // 每分钟的开始第0秒 + EveryFiveMinute = "*/5 * * * *" // 每5分钟的开始第0秒 + EveryTenMinute = "*/10 * * * *" // 每10分钟的开始第0秒 + EveryFifteenMinute = "*/15 * * * *" // 每15分钟的开始第0秒 + EveryTwentyMinute = "*/20 * * * *" // 每20分钟的开始第0秒 + EveryThirtyMinute = "*/30 * * * *" // 每30分钟的开始第0秒 + EveryFortyFiveMinute = "*/45 * * * *" // 每45分钟的开始第0秒 + FirstDayOfMonth = "0 0 1 * *" // 每月的第一天的0点0分 + LastDayOfMonth = "0 0 L * *" // 每月的最后一天的0点0分 + FirstDayOfWeek = "0 0 * * 1" // 每周的第一天(周一)的0点0分 + LastDayOfWeek = "0 0 * * 6" // 每周的最后一天(周天)的0点0分 + DailyAtTenAM = "0 10 * * *" // 每天上午10点 + DailyAtTwentyPM = "0 20 * * *" // 每天晚上20点 + TenClockAtWeekday = "0 10 * * MON-FRI" // 每个工作日(周一~周五)的上午10点0分 + TenClockAtWeekend = "0 10 * * SAT,SUN" // 每个周末(周六和周日)的上午10点0分 + HourlyBetween9And17ClockAtWeekday = "0 9-17 * * MON-FRI" // 每个工作日(周一~周五)的上午9点0分到下午5点0分每小时一次 + HalfHourlyBetween9And17ClockAtWeekday = "*/30 9-17 * * MON-FRI" // 每个工作日(周一~周五)的上午9点0分到下午5点0分每半时一次 +) diff --git a/core/scheduler/schedulerdebug/debug.go b/core/scheduler/schedulerdebug/debug.go new file mode 100644 index 000000000..65b7674c1 --- /dev/null +++ b/core/scheduler/schedulerdebug/debug.go @@ -0,0 +1,142 @@ +package schedulerdebug + +import ( + "time" + + "github.com/gofiber/fiber/v2" + + "github.com/pubgo/lava/v2/core/debug" + "github.com/pubgo/lava/v2/core/scheduler" +) + +func Init(scheduler scheduler.JobManager) { + debug.Route("/scheduler", func(router fiber.Router) { + router.Get("list", func(ctx *fiber.Ctx) error { + ctx.Response().Header.SetContentType(fiber.MIMETextHTMLCharsetUTF8) + return Page(time.Now(), scheduler.ListJobs()).Render(ctx) + }) + + router.Get("/get", func(ctx *fiber.Ctx) error { + dd := ` + + + + + 基础菜单 - Layui + + + + + + +
+ +
+ + + + +` + ctx.Response().Header.SetContentType(fiber.MIMETextHTMLCharsetUTF8) + _, err := ctx.WriteString(dd) + return err + }) + }) +} diff --git a/core/scheduler/schedulerdebug/html.go b/core/scheduler/schedulerdebug/html.go new file mode 100644 index 000000000..ffb4f5177 --- /dev/null +++ b/core/scheduler/schedulerdebug/html.go @@ -0,0 +1,131 @@ +package schedulerdebug + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "io" + "time" + + "github.com/samber/lo" + . "maragu.dev/gomponents" //nolint + _ "maragu.dev/gomponents/components" + . "maragu.dev/gomponents/html" //nolint + + "github.com/pubgo/lava/v2/core/scheduler" +) + +type NodeFn func() Node + +func (f NodeFn) Render(w io.Writer) error { + return f().Render(w) +} + +func ListSchedulers(schedulers []*scheduler.Job) Node { + return Div( + Class("overflow-x-auto"), + Table( + Class("table"), + THead( + Tr( + Th(Text("Name")), + Th(Text("Status")), + Th(Text("PreRun")), + Th(Text("ExecTime")), + Th(Text("Error")), + Th(Text("Result")), + Th(Text("RunCount")), + Th(Text("Spec")), + ), + ), + TBody( + Map(schedulers, func(s *scheduler.Job) Node { + return Tr( + Th(Text(s.Spec.Name)), + Th(Text(string(s.Status))), + Th(Textf("%v", s.PreExecTime)), + Th(Textf("%v", s.ExecTime)), + Th(NodeFn(func() Node { + if s.Error != nil { + return Text(s.Error.Error()) + } else { + return Text("null") + } + })), + Th(Text(string(s.Result))), + Th(Textf("%v", s.Runs)), + Th(NodeFn(func() Node { + modeId := hex.EncodeToString([]byte(s.Spec.Name)) + return Group{ + Script(Rawf(`function model%s(params) {document.getElementById('%s').showModal()}`, modeId, modeId)), + Button( + Class("btn"), + Attr("onclick", fmt.Sprintf("model%s()", modeId)), + Text("open modal"), + ), + Dialog( + ID(modeId), + Class("modal"), + Div( + Class("modal-box"), + H3( + Class("text-lg font-bold"), + Text("Hello!"), + ), + P( + Class("py-4"), + Textf("%s", lo.Must(json.MarshalIndent(s.Spec, " ", " "))), + ), + Div( + Class("modal-action"), + Form( + Method("dialog"), + Button( + Class("btn"), + Text("Close"), + ), + ), + ), + ), + ), + } + })), + ) + }), + ), + ), + ) +} + +const timeOnly = "15:04:05" + +func Page(now time.Time, schedulers []*scheduler.Job) Node { + return Doctype( + HTML( + Head( + Meta(Charset("utf-8")), + Meta(Name("viewport"), Content("width=device-width, initial-scale=1")), + // Meta(Attr("http-equiv", "refresh"), Attr("content", "5")), + TitleEl(Text("scheduler")), + Group{ + Link(Href("https://cdn.jsdelivr.net/npm/daisyui@5"), Rel("stylesheet"), Type("text/css")), + Script(Src("https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4")), + Script(Src("https://cdn.tailwindcss.com?plugins=forms,typography")), + Script(Src("https://unpkg.com/htmx.org")), + }, + ), + Body(Group{ + Div( + H1(Text(`Scheduler Table`)), + partial(now), + ), + + ListSchedulers(schedulers), + }), + ), + ) +} + +func partial(now time.Time) Node { + return P(ID("partial"), Textf(`Time was last updated at %v.`, now.Format(timeOnly))) +} diff --git a/core/scheduler/util.go b/core/scheduler/util.go index 3080e770e..8508ff7d2 100644 --- a/core/scheduler/util.go +++ b/core/scheduler/util.go @@ -1,38 +1,55 @@ package scheduler import ( - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/errors" - "github.com/pubgo/funk/result" + "strings" + "time" + + "github.com/pubgo/funk/v2/result" "github.com/reugn/go-quartz/quartz" ) -func do(s *Scheduler, job job, fn JobFunc) { - trigger := getTrigger(job) - assert.If(job.key == "", "[name] should not be null") - assert.If(fn == nil, "[fn] should not be nil") - assert.If(trigger == nil, "please init dur or cron") - assert.Must(s.scheduler.ScheduleJob( - quartz.NewJobDetail( - &namedJob{s: s, name: job.key, fn: fn, log: s.log}, - quartz.NewJobKey(job.key)), trigger)) +func regJobExecutor(jobExecutors map[string]JobExecutor, executor JobExecutor) (r result.Error) { + if executor == nil { + return r.WithErrorf("executor is nil") + } + + if executor.Name() == "" { + return r.WithErrorf("executor name is empty") + } + + if jobExecutors[executor.Name()] != nil { + return r.WithErrorf("[job executor] %s already exists", executor.Name()) + } + + jobExecutors[executor.Name()] = executor + return r } -func getTrigger(j job) quartz.Trigger { - if j.once { - return quartz.NewRunOnceTrigger(j.dur) +func getTrigger(j JobSpec, location *time.Location) (r result.Result[*triggerImpl]) { + if j.Once != nil { + return r.WithValue(newTrigger(quartz.NewRunOnceTrigger(j.Once.Delay))) } - if j.cron != "" { - r := result.Wrap(quartz.NewCronTrigger(j.cron)) - return r.Unwrap(func(err error) error { - return errors.WrapKV(err, "cron-expr", j.cron) - }) + if j.Cron != nil { + trigger, err := quartz.NewCronTriggerWithLoc(j.Cron.Expr, location) + if err != nil { + return r.WithErrorf("cron-expr:%s, err:%s", j.Cron.Expr, err.Error()) + } + + return r.WithValue(newTrigger(trigger)) } - if j.dur != 0 { - return quartz.NewSimpleTrigger(j.dur) + if j.Ticker != nil { + return r.WithValue(newTrigger(quartz.NewSimpleTrigger(j.Ticker.Dur))) } - return nil + return r.WithErrorf("please init spec.Once, spec.Cron or spec.Ticker, spec:%#v", j) +} + +func parseJobKey(name string) *quartz.JobKey { + keys := strings.SplitN(name, quartz.Sep, 2) + if len(keys) == 1 { + return quartz.NewJobKey(keys[0]) + } + return quartz.NewJobKeyWithGroup(keys[0], keys[1]) } diff --git a/core/signal/signal.go b/core/signal/signal.go deleted file mode 100644 index a13aa83f2..000000000 --- a/core/signal/signal.go +++ /dev/null @@ -1,36 +0,0 @@ -package signal - -import ( - "context" - "os" - "os/signal" - "syscall" - - "github.com/pubgo/funk/log" -) - -const Name = "signal" - -var logger = log.GetLogger(Name) - -func Wait() { - ch := make(chan os.Signal, 1) - signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGHUP) - sig := <-ch - logger.Info().Str("signal", sig.String()).Msg("signal trigger notify") -} - -func Context() context.Context { - ctx, cancel := context.WithCancel(context.Background()) - ch := make(chan os.Signal, 1) - signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGHUP) - go func() { - select { - case <-ch: - cancel() - case <-ctx.Done(): - cancel() - } - }() - return ctx -} diff --git a/core/signals/signal.go b/core/signals/signal.go new file mode 100644 index 000000000..4d46f8b8d --- /dev/null +++ b/core/signals/signal.go @@ -0,0 +1,39 @@ +package signals + +import ( + "context" + "os" + "os/signal" + "syscall" + + "github.com/pubgo/funk/v2/log" +) + +const Name = "signals" + +var logger = log.GetLogger(Name) + +// Inspired by +// https://github.com/kubernetes-sigs/controller-runtime/blob/8499b67e316a03b260c73f92d0380de8cd2e97a1/pkg/manager/signals/signal.go#L25 +var onlyOneSignalHandler = make(chan struct{}) + +// var signals = []os.Signal{syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGHUP, syscall.SIGUSR1} +var shutdownSignals = []os.Signal{syscall.SIGTERM, syscall.SIGQUIT, os.Interrupt, os.Kill} + +func Context() context.Context { + close(onlyOneSignalHandler) + + ctx, cancel := context.WithCancel(context.Background()) + ch := make(chan os.Signal, 2) + signal.Notify(ch, shutdownSignals...) + go func() { + sig := <-ch + logger.Info().Msgf("cancelling context, received signal:%s", sig) + cancel() + sig = <-ch + logger.Info().Msgf("os exit, received twice signal:%s", sig) + os.Exit(1) + }() + + return ctx +} diff --git a/core/supervisor/_.go b/core/supervisor/_.go new file mode 100644 index 000000000..426e0e9cc --- /dev/null +++ b/core/supervisor/_.go @@ -0,0 +1,4 @@ +package supervisor + +// "github.com/kardianos/service" +// github.com/thejerf/suture diff --git a/core/supervisor/aaa.go b/core/supervisor/aaa.go new file mode 100644 index 000000000..760de73c9 --- /dev/null +++ b/core/supervisor/aaa.go @@ -0,0 +1,32 @@ +package supervisor + +import ( + "context" + "time" + + "github.com/thejerf/suture/v4" +) + +type Supervisor = suture.Supervisor + +type Metric struct { + Name string + Error string + Restart uint32 + StartTime time.Time + OnlineDuration time.Duration +} + +type Service interface { + Name() string + Error() error + String() string + Serve(ctx context.Context) error + Metric() *Metric +} + +type serviceFn func(ctx context.Context) error + +func (fn serviceFn) Serve(ctx context.Context) error { + return fn(ctx) +} diff --git a/core/supervisor/errs.go b/core/supervisor/errs.go new file mode 100644 index 000000000..86e056283 --- /dev/null +++ b/core/supervisor/errs.go @@ -0,0 +1,79 @@ +package supervisor + +import ( + "github.com/pubgo/funk/v2/errors" + "github.com/thejerf/suture/v4" +) + +type FatalErr struct { + Err error + Status ExitStatus +} + +// AsFatalErr wraps the given error creating a FatalErr. If the given error +// already is of type FatalErr, it is not wrapped again. +func AsFatalErr(err error, status ExitStatus) (gErr *FatalErr) { + if errors.As(err, &gErr) { + return gErr + } + + return &FatalErr{ + Err: err, + Status: status, + } +} + +func IsFatal(err error) bool { + return errors.As(err, &FatalErr{}) +} + +func (e *FatalErr) Error() string { + return e.Err.Error() +} + +func (e *FatalErr) Unwrap() error { + return e.Err +} + +func (*FatalErr) Is(target error) bool { + return target == suture.ErrTerminateSupervisorTree +} + +// NoRestartErr wraps the given error err (which may be nil) to make sure that +// `errors.Is(err, suture.ErrDoNotRestart) == true`. +func NoRestartErr(err error) error { + if err == nil { + return suture.ErrDoNotRestart + } + return &noRestartErr{err} +} + +type noRestartErr struct { + err error +} + +func (e *noRestartErr) Error() string { + return e.err.Error() +} + +func (e *noRestartErr) Unwrap() error { + return e.err +} + +func (*noRestartErr) Is(target error) bool { + return target == suture.ErrDoNotRestart +} + +type ExitStatus int + +const ( + ExitSuccess ExitStatus = 0 + ExitError ExitStatus = 1 + ExitNoUpgradeAvailable ExitStatus = 2 + ExitRestart ExitStatus = 3 + ExitUpgrade ExitStatus = 4 +) + +func (s ExitStatus) AsInt() int { + return int(s) +} diff --git a/core/supervisor/manager.go b/core/supervisor/manager.go new file mode 100644 index 000000000..b0492874f --- /dev/null +++ b/core/supervisor/manager.go @@ -0,0 +1,237 @@ +package supervisor + +import ( + "context" + "fmt" + + "github.com/gofiber/fiber/v2" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/async" + "github.com/pubgo/funk/v2/errors" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/recovery" + "github.com/pubgo/funk/v2/result" + "github.com/pubgo/funk/v2/running" + "github.com/pubgo/funk/v2/stack" + "github.com/thejerf/suture/v4" + + "github.com/pubgo/lava/v2/core/debug" + "github.com/pubgo/lava/v2/core/lifecycle" + "github.com/pubgo/lava/v2/internal/logutil" + "github.com/pubgo/lava/v2/pkg/netutil" +) + +type serviceWrapper struct { + token suture.ServiceToken + service Service +} + +func Default(lc lifecycle.Getter) *Manager { + return NewManager(running.Project(), lc) +} + +func NewManager(name string, lc lifecycle.Getter) *Manager { + m := &Manager{ + lc: lc, + supervisor: suture.New(name, SpecWithInfoLogger()), + services: make(map[string]*serviceWrapper), + logger: log.GetLogger(name), + } + return m.init() +} + +type Manager struct { + lc lifecycle.Getter + logger log.Logger + supervisor *Supervisor + services map[string]*serviceWrapper +} + +func (m *Manager) init() *Manager { + debug.Route("/supervisor", func(router fiber.Router) { + router.Get("services", func(ctx *fiber.Ctx) error { + var services []*Metric + for _, srv := range m.services { + services = append(services, srv.service.Metric()) + } + return ctx.JSON(services) + }) + }) + + return m +} + +func (m *Manager) Has(name string) bool { + _, ok := m.services[name] + return ok +} + +func (m *Manager) OnClose(fn func()) { + m.supervisor.Add(serviceFn(func(ctx context.Context) error { + <-ctx.Done() + fn() + return nil + })) +} + +func (m *Manager) Add(srv Service) error { + name := srv.Name() + if _, ok := m.services[name]; ok { + return errors.Errorf("service already exists, name=%s", name) + } + + m.logger.Info().Str("name", name).Msg("add service to supervisor") + m.services[name] = &serviceWrapper{service: srv, token: m.supervisor.Add(srv)} + return nil +} + +func (m *Manager) Delete(name string) error { + srv := m.services[name] + if srv == nil { + m.logger.Warn().Str("name", name).Msg("service not found, cannot delete") + return nil + } + + defer func() { delete(m.services, name) }() + m.logger.Info().Str("name", name).Msg("delete service from supervisor") + return errors.Wrapf(m.supervisor.Remove(srv.token), "failed to remove service, name=%s", name) +} + +func (m *Manager) RemoveServices() (gErr error) { + for name, srv := range m.services { + if result.ThrowErr(&gErr, m.supervisor.Remove(srv.token)) { + return errors.Wrapf(gErr, "failed to remove service, name=%s", name) + } + m.logger.Info().Str("name", name).Msg("removing service from supervisor") + } + + m.services = make(map[string]*serviceWrapper) + return nil +} + +func (m *Manager) RestartServices() (gErr error) { + for name, srv := range m.services { + if result.ThrowErr(&gErr, m.supervisor.Remove(srv.token)) { + return errors.Wrapf(gErr, "failed to remove service, name=%s", name) + } + + m.services[name] = &serviceWrapper{service: srv.service, token: m.supervisor.Add(srv.service)} + m.logger.Info().Str("name", name).Msg("restarting service in supervisor") + } + + return nil +} + +func (m *Manager) RestartService(name string) (gErr error) { + srv := m.services[name] + if srv == nil { + m.logger.Warn().Str("name", name).Msg("service not found, cannot restart") + return nil + } + + if result.ThrowErr(&gErr, m.supervisor.Remove(srv.token)) { + return errors.Wrapf(gErr, "failed to remove service, name=%s", name) + } + + m.services[name] = &serviceWrapper{service: srv.service, token: m.supervisor.Add(srv.service)} + m.logger.Info().Str("name", name).Msg("restarting service in supervisor") + + return nil +} + +func (m *Manager) Services() []Service { + var services []Service + for _, srv := range m.services { + services = append(services, srv.service) + } + return services +} + +func (m *Manager) start(ctx context.Context) error { + defer recovery.Exit() + logutil.OkOrFailed(m.logger, "start lifecycle before service", func() error { + defer recovery.Exit() + for _, run := range m.lc.GetBeforeStarts() { + m.logger.Info().Msgf("running %s", stack.CallerWithFunc(run.Exec)) + assert.Exit(run.Exec(ctx)) + } + return nil + }) + + async.GoDelay(func() error { + err := m.supervisor.Serve(ctx) + if netutil.IsErrServerClosed(err) { + return nil + } + assert.Exit(err) + return nil + }) + + logutil.OkOrFailed(m.logger, "start lifecycle after service", func() error { + defer recovery.Exit() + for _, run := range m.lc.GetAfterStarts() { + m.logger.Info().Msgf("running %s", stack.CallerWithFunc(run.Exec)) + assert.Exit(run.Exec(ctx)) + } + return nil + }) + + return nil +} + +func (m *Manager) stop(ctx context.Context) error { + defer recovery.DebugPrint() + + logutil.OkOrFailed(m.logger, "stop lifecycle before service", func() error { + for _, run := range m.lc.GetBeforeStops() { + logutil.LogOrErr(m.logger, fmt.Sprintf("running %s", stack.CallerWithFunc(run.Exec)), func() error { + return run.Exec(ctx) + }) + } + return nil + }) + + unstoppedServices, _ := m.supervisor.UnstoppedServiceReport() + if len(unstoppedServices) > 0 { + for _, service := range unstoppedServices { + m.logger.Error().Any("service", service).Msgf("service:%s is still running", service.Name) + } + return errors.New("services are still running") + } + + logutil.OkOrFailed(m.logger, "stop lifecycle after service", func() error { + for _, run := range m.lc.GetAfterStops() { + logutil.LogOrErr(m.logger, fmt.Sprintf("running %s", stack.CallerWithFunc(run.Exec)), func() error { + return run.Exec(ctx) + }) + } + return nil + }) + + return nil +} + +func (m *Manager) Run(ctx context.Context) error { + err := m.start(ctx) + if err != nil { + return err + } + + <-ctx.Done() + + return m.stop(ctx) +} + +func (m *Manager) Serve(ctx context.Context) error { + err := m.supervisor.Serve(ctx) + + if netutil.IsErrServerClosed(err) { + return nil + } + + return err +} + +func (m *Manager) ServeBackground(ctx context.Context) <-chan error { + return m.supervisor.ServeBackground(ctx) +} diff --git a/core/supervisor/service.go b/core/supervisor/service.go new file mode 100644 index 000000000..2d0e1debf --- /dev/null +++ b/core/supervisor/service.go @@ -0,0 +1,84 @@ +package supervisor + +import ( + "context" + "fmt" + "time" + + "github.com/pubgo/funk/v2/errors" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/recovery" + "go.uber.org/atomic" +) + +type serviceMetric struct { + Error atomic.String + Restart atomic.Uint32 + StartTime atomic.Time + OnlineDuration atomic.Duration +} + +func NewService(name string, fn func(ctx context.Context) error) Service { + return &serviceImpl{name: name, fn: fn, metric: &serviceMetric{}} +} + +var _ Service = &serviceImpl{} + +type serviceImpl struct { + name string + err error + fn func(ctx context.Context) error + + metric *serviceMetric +} + +func (s *serviceImpl) Metric() *Metric { + metric := s.metric + return &Metric{ + Name: s.name, + Error: metric.Error.Load(), + Restart: metric.Restart.Load(), + StartTime: metric.StartTime.Load(), + OnlineDuration: time.Since(metric.StartTime.Load()), + } +} + +func (s *serviceImpl) Error() error { + return s.err +} + +func (s *serviceImpl) Name() string { + return s.name +} + +func (s *serviceImpl) String() string { + return s.name +} + +func (s *serviceImpl) Serve(ctx context.Context) (gErr error) { + s.metric.StartTime.Store(time.Now()) + defer func() { + if gErr != nil { + s.err = gErr + } + + if s.err != nil { + s.metric.Error.Store(s.err.Error()) + } + + log.Info(ctx). + Str("service", s.name). + Any("metrics", s.Metric()). + Msg("stop service") + }() + defer recovery.Err(&gErr) + + s.err = nil + s.metric.Restart.Add(1) + log.Info(ctx).Str("service", s.name).Msg("start service") + err := s.fn(ctx) + if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { + return fmt.Errorf("non-context error, service=%s meta=%v err=%w", s.name, s.Metric(), err) + } + return err +} diff --git a/core/supervisor/spec.go b/core/supervisor/spec.go new file mode 100644 index 000000000..a516f0b6a --- /dev/null +++ b/core/supervisor/spec.go @@ -0,0 +1,61 @@ +package supervisor + +import ( + "log/slog" + "time" + + "github.com/pubgo/funk/v2/log" + "github.com/thejerf/suture/v4" +) + +const ServiceTimeout = 10 * time.Second + +func SpecWithDebugLogger() suture.Spec { + return spec(func(e suture.Event) { log.Debug().Msg(e.String()) }) +} + +func SpecWithInfoLogger() suture.Spec { + return spec(infoEventHook()) +} + +func spec(eventHook suture.EventHook) suture.Spec { + return suture.Spec{ + EventHook: eventHook, + Timeout: ServiceTimeout, + PassThroughPanics: true, + DontPropagateTermination: false, + } +} + +// infoEventHook prints service failures and failures to stop services at level +// info. All other events and identical, consecutive failures are logged at +// debug only. +func infoEventHook() suture.EventHook { + var prevTerminate suture.EventServiceTerminate + return func(ei suture.Event) { + m := ei.Map() + l := slog.With("supervisor", m["supervisor_name"], "service", m["service_name"]) + switch e := ei.(type) { + case suture.EventStopTimeout: + l.Warn("Service failed to terminate in a timely manner") + case suture.EventServicePanic: + l.Error("Caught a service panic, which shouldn't happen") + l.Warn(e.String()) //nolint:sloglint + case suture.EventServiceTerminate: + if e.ServiceName == prevTerminate.ServiceName && e.Err == prevTerminate.Err { + l.Debug("Service failed repeatedly", e.Err) + } else { + l.Warn("Service failed", e.Err) + } + prevTerminate = e + l.Debug(e.String()) // Contains some backoff statistics + case suture.EventBackoff: + l.Debug("Exiting the backoff state") + case suture.EventResume: + l.Debug("Too many service failures - entering the backoff state") + default: + l.Warn("Unknown suture supervisor event", slog.Any("type", e.Type())) + l.Warn(e.String()) //nolint:lint + } + } +} diff --git a/core/tracing/config.go b/core/tracing/config.go index 7d943217a..f681c0f1a 100644 --- a/core/tracing/config.go +++ b/core/tracing/config.go @@ -40,7 +40,7 @@ type Config struct { prettyPrint bool bspOptions []sdktrace.BatchSpanProcessorOption - // Metrics options + // Metric options metricsEnabled bool metricOptions []metric.Option diff --git a/core/tracing/telemetry.go b/core/tracing/telemetry.go index 0934ea30b..425d1d0a4 100644 --- a/core/tracing/telemetry.go +++ b/core/tracing/telemetry.go @@ -7,32 +7,28 @@ import ( "time" "github.com/goccy/go-json" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/log" - "github.com/pubgo/funk/version" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/buildinfo/version" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/recovery" + "github.com/pubgo/funk/v2/result" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" otlpTraceGrpc "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" otelprom "go.opentelemetry.io/otel/exporters/prometheus" + "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" + otelmetric "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/propagation" - sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.7.0" - - otelmetric "go.opentelemetry.io/otel/metric" oteltrace "go.opentelemetry.io/otel/trace" - "google.golang.org/grpc/encoding/gzip" - "github.com/pubgo/lava/core/lifecycle" - - "go.opentelemetry.io/otel/sdk/metric" - - "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" + "github.com/pubgo/lava/v2/core/lifecycle" ) var logs = log.GetLogger("tracing") @@ -76,11 +72,14 @@ func New(cfg *Config, lc lifecycle.Lifecycle) Provider { propagation.Baggage{}, ), ) - meterProvider := NewPrometheusMeterProvider(config) + meterProvider, err := NewPrometheusMeterProvider(config) + assert.Exit(err) - lc.AfterStop(func() { + lc.AfterStop(func(ctx context.Context) (gErr error) { + defer recovery.Err(&gErr) assert.Must(tracerProvider.Shutdown(context.Background())) assert.Must(meterProvider.Shutdown(context.Background())) + return nil }) // name := instrumentationName + "/" + config.serviceInfo.Namespace + "/" + config.serviceInfo.Name @@ -105,7 +104,7 @@ func mergeResource(config *Config) *resource.Resource { semconv.ProcessCommandKey.String(os.Args[0]), ) - return assert.Must1(resource.Merge(resource.Default(), defaultResource)) + return assert.Exit1(resource.Merge(resource.Default(), defaultResource)) } func NewTracer(config *Config) *sdktrace.TracerProvider { @@ -165,14 +164,18 @@ func initTracerExporter(config *Config) (sdktrace.SpanExporter, error) { return nil, fmt.Errorf("tracer exporter endpoint is nil, no exporter is inited") } -func NewPrometheusMeterProvider(config *Config) *sdkmetric.MeterProvider { +func NewPrometheusMeterProvider(config *Config, opts ...otelprom.Option) (_ *sdkmetric.MeterProvider, gErr error) { + exporter, err := otelprom.New(opts...) + if result.ThrowErr(&gErr, err) { + return + } + res := mergeResource(config) - exporter := assert.Must1(otelprom.New()) provider := sdkmetric.NewMeterProvider( sdkmetric.WithReader(exporter), sdkmetric.WithResource(res), ) - return provider + return provider, nil } func TraceID(span oteltrace.Span) string { @@ -208,7 +211,7 @@ func GetTraceId(ctx context.Context) string { return oteltrace.SpanContextFromContext(ctx).TraceID().String() } -func initMetricExporter(config *Config) (metric.Exporter, error) { +func initMetricExporter(config *Config) (sdkmetric.Exporter, error) { if config.metricExporter.ExporterEndpoint == DefaultStdout { encoder := json.NewEncoder(os.Stdout) return stdoutmetric.New(stdoutmetric.WithEncoder(encoder)) diff --git a/core/tracing/tracingbuilder/config.go b/core/tracing/tracingbuilder/config.go new file mode 100644 index 000000000..e9c0c0beb --- /dev/null +++ b/core/tracing/tracingbuilder/config.go @@ -0,0 +1,118 @@ +package tracingbuilder + +import ( + "crypto/tls" + "time" + + "github.com/pubgo/funk/v2/buildinfo/version" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/resource" + "google.golang.org/grpc/credentials" + "google.golang.org/protobuf/types/known/durationpb" + + sdktrace "go.opentelemetry.io/otel/sdk/trace" + //"google.golang.org/grpc/credentials" +) + +const ( + DefaultStdout = "stdout" +) + +type TraceConfigLoader struct { + TraceCfg *Config `yaml:"tracing"` +} + +type Config struct { + traceExporter *Exporter + metricExporter *Exporter + metricReportPeriod string + serviceInfo *ServiceInfo + attributes map[string]string + headers map[string]string + idGenerator sdktrace.IDGenerator + otelErrorHandler otel.ErrorHandler + traceBatchOptions []sdktrace.BatchSpanProcessorOption + sampleRatio float64 + + resourceAttributes []attribute.KeyValue + resourceDetectors []resource.Detector + + tlsConf *tls.Config + + // Tracing options + + tracingEnabled bool + textMapPropagator propagation.TextMapPropagator + tracerProvider *sdktrace.TracerProvider + traceSampler sdktrace.Sampler + prettyPrint bool + bspOptions []sdktrace.BatchSpanProcessorOption + + // Metric options + + metricsEnabled bool + metricOptions []metric.Option +} + +type Exporter struct { + ExporterEndpoint string + Insecure bool + Creds credentials.TransportCredentials +} + +type ServiceInfo struct { + Name string + Namespace string + Version string +} + +// OTLP contains specific configuration used by the OpenTelemetry Metric exporter. +type OTLP struct { + GRPC *OtelGRPC `description:"gRPC configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + HTTP *OtelHTTP `description:"HTTP configuration for the OpenTelemetry collector." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + + AddEntryPointsLabels bool `description:"Enable metrics on entry points." json:"addEntryPointsLabels,omitempty" toml:"addEntryPointsLabels,omitempty" yaml:"addEntryPointsLabels,omitempty" export:"true"` + AddRoutersLabels bool `description:"Enable metrics on routers." json:"addRoutersLabels,omitempty" toml:"addRoutersLabels,omitempty" yaml:"addRoutersLabels,omitempty" export:"true"` + AddServicesLabels bool `description:"Enable metrics on services." json:"addServicesLabels,omitempty" toml:"addServicesLabels,omitempty" yaml:"addServicesLabels,omitempty" export:"true"` + ExplicitBoundaries []float64 `description:"Boundaries for latency metrics." json:"explicitBoundaries,omitempty" toml:"explicitBoundaries,omitempty" yaml:"explicitBoundaries,omitempty" export:"true"` + PushInterval *durationpb.Duration `description:"Period between calls to collect a checkpoint." json:"pushInterval,omitempty" toml:"pushInterval,omitempty" yaml:"pushInterval,omitempty" export:"true"` + ServiceName string `description:"OTEL service name to use." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"` +} + +// SetDefaults sets the default values. +func (o *OTLP) SetDefaults() { + o.HTTP = &OtelHTTP{} + o.HTTP.SetDefaults() + + o.AddEntryPointsLabels = true + o.AddServicesLabels = true + o.ExplicitBoundaries = []float64{.005, .01, .025, .05, .075, .1, .25, .5, .75, 1, 2.5, 5, 7.5, 10} + o.PushInterval = durationpb.New(10 * time.Second) + o.ServiceName = version.Project() +} + +// OtelGRPC provides configuration settings for the gRPC open-telemetry. +type OtelGRPC struct { + Endpoint string `description:"Sets the gRPC endpoint (host:port) of the collector." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` + Insecure bool `description:"Disables client transport security for the exporter." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"` + Headers map[string]string `description:"Headers sent with payload." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty"` +} + +// SetDefaults sets the default values. +func (c *OtelGRPC) SetDefaults() { + c.Endpoint = "localhost:4317" +} + +// OtelHTTP provides configuration settings for the HTTP open-telemetry. +type OtelHTTP struct { + Endpoint string `description:"Sets the HTTP endpoint (scheme://host:port/path) of the collector." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` + Headers map[string]string `description:"Headers sent with payload." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty"` +} + +// SetDefaults sets the default values. +func (c *OtelHTTP) SetDefaults() { + c.Endpoint = "https://localhost:4318" +} diff --git a/core/tracing/tracingbuilder/telemetry.go b/core/tracing/tracingbuilder/telemetry.go new file mode 100644 index 000000000..ba521ef35 --- /dev/null +++ b/core/tracing/tracingbuilder/telemetry.go @@ -0,0 +1,211 @@ +package tracingbuilder + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "os" + "time" + + "github.com/pubgo/funk/v2/buildinfo/version" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/recovery" + "github.com/pubgo/funk/v2/result" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + otelprom "go.opentelemetry.io/otel/exporters/prometheus" + "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" + "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" + otelmetric "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/propagation" + sdkmetric "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.7.0" + oteltrace "go.opentelemetry.io/otel/trace" + "google.golang.org/grpc/encoding/gzip" + + "github.com/pubgo/lava/v2/core/lifecycle" +) + +type Provider struct { + TracerProvider oteltrace.TracerProvider + Tracer oteltrace.Tracer + MeterProvider otelmetric.MeterProvider + Meter otelmetric.Meter +} + +type Params struct { + Cfg *Config + LC lifecycle.Lifecycle +} + +func New(params Params) Provider { + defer recovery.Exit() + config := &Config{ + traceExporter: &Exporter{}, + metricExporter: &Exporter{}, + metricReportPeriod: "", + serviceInfo: &ServiceInfo{}, + attributes: map[string]string{}, + headers: map[string]string{}, + idGenerator: nil, + traceBatchOptions: []sdktrace.BatchSpanProcessorOption{}, + sampleRatio: 1, + } + + tracerProvider := NewTracerProvider(config).Unwrap() + meterProvider := NewMeterProvider(config).Unwrap() + propagator := propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, + propagation.Baggage{}, + ) + + otel.SetTracerProvider(tracerProvider) + otel.SetMeterProvider(meterProvider) + otel.SetTextMapPropagator(propagator) + + params.LC.AfterStop(func(ctx context.Context) error { + return errors.Join( + tracerProvider.Shutdown(ctx), + meterProvider.Shutdown(ctx), + ) + }) + + return Provider{ + TracerProvider: tracerProvider, + Tracer: otel.Tracer( + version.Project(), + oteltrace.WithInstrumentationVersion(version.Version()), + oteltrace.WithInstrumentationAttributes(), + ), + MeterProvider: meterProvider, + Meter: otel.Meter( + version.Project(), + otelmetric.WithInstrumentationVersion(version.Version()), + otelmetric.WithInstrumentationAttributes(), + ), + } +} + +// merge config resource with default resource +func mergeResource(config *Config) (r result.Result[*resource.Resource]) { + defer result.Recovery(&r) + res := result.Wrap(resource.New(context.Background(), + resource.WithFromEnv(), + resource.WithTelemetrySDK(), + resource.WithOSType(), + resource.WithProcessCommandArgs(), + )).Unwrap() + res = result.Wrap(resource.Merge(resource.Default(), res)).Unwrap() + + hostname, _ := os.Hostname() + defaultResource := resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceNameKey.String(config.serviceInfo.Name), + semconv.HostNameKey.String(hostname), + semconv.ServiceNamespaceKey.String(config.serviceInfo.Namespace), + semconv.ServiceVersionKey.String(config.serviceInfo.Version), + semconv.ProcessPIDKey.Int(os.Getpid()), + semconv.ProcessCommandKey.String(os.Args[0]), + ) + res = result.Wrap(resource.Merge(defaultResource, res)).Log().Unwrap() + + return r.WithValue(res) +} + +func NewTracerProvider(config *Config) (r result.Result[*sdktrace.TracerProvider]) { + defer result.Recovery(&r) + res := mergeResource(config).Unwrap() + + traceExporter := result.Wrap(newGrpcTracerExporter(config)).Unwrap() + sampler := sdktrace.ParentBased(sdktrace.AlwaysSample()) + if config.sampleRatio < 1 && config.sampleRatio >= 0 { + sampler = sdktrace.ParentBased(sdktrace.TraceIDRatioBased(config.sampleRatio)) + log.Info().Msgf("set sample ratio %v", config.sampleRatio) + } + + traceProvider := sdktrace.NewTracerProvider( + sdktrace.WithResource(res), + sdktrace.WithSampler(sampler), + sdktrace.WithBatcher(traceExporter, + sdktrace.WithMaxQueueSize(queueSize()), + sdktrace.WithMaxExportBatchSize(queueSize()), + sdktrace.WithBatchTimeout(10*time.Second), + sdktrace.WithExportTimeout(10*time.Second), + ), + sdktrace.WithRawSpanLimits(sdktrace.SpanLimits{ + AttributeCountLimit: 1024, + EventCountLimit: 1024, + LinkCountLimit: 1024, + AttributePerEventCountLimit: 1024, + AttributePerLinkCountLimit: 1024, + AttributeValueLengthLimit: 1024, + }), + ) + + return r.WithValue(traceProvider) +} + +func newGrpcTracerExporter(config *Config) (sdktrace.SpanExporter, error) { + if config.traceExporter.ExporterEndpoint == DefaultStdout { + return stdouttrace.New(stdouttrace.WithPrettyPrint()) + } + + // opts = append(opts, otlptracegrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig))) + traceSecureOption := otlptracegrpc.WithTLSCredentials(config.traceExporter.Creds) + if config.traceExporter.Insecure { + traceSecureOption = otlptracegrpc.WithInsecure() + } + + return otlptrace.New( + context.Background(), + otlptracegrpc.NewClient( + otlptracegrpc.WithEndpoint(config.traceExporter.ExporterEndpoint), + traceSecureOption, + otlptracegrpc.WithHeaders(config.headers), + otlptracegrpc.WithCompressor(gzip.Name), + ), + ) +} + +func newGrpcMetricExporter(config *Config) (sdkmetric.Exporter, error) { + if config.metricExporter.ExporterEndpoint == DefaultStdout { + encoder := json.NewEncoder(os.Stdout) + return stdoutmetric.New(stdoutmetric.WithEncoder(encoder)) + } + + if config.metricExporter.ExporterEndpoint != "" { + metricSecureOption := otlpmetricgrpc.WithTLSCredentials(config.metricExporter.Creds) + if config.metricExporter.Insecure { + metricSecureOption = otlpmetricgrpc.WithInsecure() + } + + return otlpmetricgrpc.New( + context.Background(), + otlpmetricgrpc.WithEndpoint(config.metricExporter.ExporterEndpoint), + metricSecureOption, + otlpmetricgrpc.WithHeaders(config.headers), + otlpmetricgrpc.WithCompressor(gzip.Name)) + } + return nil, fmt.Errorf("metric exporter endpoint is nil, no exporter is inited") +} + +func NewMeterProvider(config *Config) (r result.Result[*sdkmetric.MeterProvider]) { + defer result.Recovery(&r) + // reader := metric.NewPeriodicReader(assert.Must1(newGrpcMetricExporter(config))) + // readerOpt := sdkmetric.WithReader(reader) + + exporter := result.Wrap(otelprom.New()).Unwrap() + readerOpt := sdkmetric.WithReader(exporter) + + res := mergeResource(config).Unwrap() + provider := sdkmetric.NewMeterProvider( + readerOpt, + sdkmetric.WithResource(res), + ) + return r.WithValue(provider) +} diff --git a/core/tracing/tracingbuilder/util.go b/core/tracing/tracingbuilder/util.go new file mode 100644 index 000000000..0af34d492 --- /dev/null +++ b/core/tracing/tracingbuilder/util.go @@ -0,0 +1,19 @@ +package tracingbuilder + +import ( + "runtime" +) + +func queueSize() int { + const minSize = 1000 + const maxSize = 16000 + + n := (runtime.GOMAXPROCS(0) / 2) * 1000 + if n < minSize { + return minSize + } + if n > maxSize { + return maxSize + } + return n +} diff --git a/docs/_doc.go b/docs/_doc.go index df8bac9df..b22a41dd1 100644 --- a/docs/_doc.go +++ b/docs/_doc.go @@ -20,3 +20,5 @@ package docs // import _ "go.etcd.io/etcd/pkg/fileutil" // https://github.com/tideland/go-slices/blob/main/slices.go // https://github.com/labstack/gommon + +// https://github.com/ThreeDotsLabs/watermill diff --git a/go.mod b/go.mod index 45c45e433..aa697b6c2 100644 --- a/go.mod +++ b/go.mod @@ -1,91 +1,89 @@ -module github.com/pubgo/lava +module github.com/pubgo/lava/v2 -go 1.22.1 +go 1.24.0 require ( - github.com/gofiber/fiber/v2 v2.52.4 + github.com/gofiber/fiber/v2 v2.52.9 github.com/golang/protobuf v1.5.4 // indirect github.com/grandcat/zeroconf v1.0.0 github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 github.com/jinzhu/copier v0.4.0 // indirect github.com/json-iterator/go v1.1.12 github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/olekukonko/tablewriter v0.0.5 + github.com/olekukonko/tablewriter v1.0.8 github.com/pkg/errors v0.9.1 - github.com/pubgo/opendoc v0.0.5 - github.com/reugn/go-quartz v0.11.2 + github.com/reugn/go-quartz v0.15.1 github.com/vmihailenco/msgpack/v5 v5.4.1 - go.opentelemetry.io/otel v1.30.0 - go.opentelemetry.io/otel/metric v1.30.0 - go.opentelemetry.io/otel/trace v1.30.0 - go.uber.org/atomic v1.11.0 // indirect - go.uber.org/automaxprocs v1.5.3 - golang.org/x/crypto v0.27.0 // indirect - golang.org/x/mod v0.21.0 // indirect - golang.org/x/net v0.29.0 - golang.org/x/sys v0.25.0 // indirect - google.golang.org/grpc v1.66.1 - google.golang.org/protobuf v1.34.3-0.20240816073751-94ecbc261689 + go.opentelemetry.io/otel v1.37.0 + go.opentelemetry.io/otel/metric v1.37.0 + go.opentelemetry.io/otel/trace v1.37.0 + go.uber.org/atomic v1.11.0 + go.uber.org/automaxprocs v1.6.0 + golang.org/x/crypto v0.43.0 // indirect + golang.org/x/mod v0.29.0 // indirect + golang.org/x/net v0.46.0 + golang.org/x/sys v0.37.0 // indirect + google.golang.org/grpc v1.73.0 + google.golang.org/protobuf v1.36.6 ) require ( - github.com/goccy/go-json v0.10.2 - github.com/mailgun/holster/v4 v4.19.0 - github.com/prometheus/client_golang v1.19.1 + github.com/goccy/go-json v0.10.5 + github.com/mailgun/holster/v4 v4.21.0 + github.com/prometheus/client_golang v1.22.0 github.com/valyala/bytebufferpool v1.0.0 ) require ( - github.com/alecthomas/participle/v2 v2.1.1 - github.com/arl/statsviz v0.6.0 - github.com/dave/jennifer v1.7.0 + dario.cat/mergo v1.0.2 + github.com/alecthomas/participle/v2 v2.1.4 + github.com/arl/statsviz v0.7.1 github.com/ecordell/optgen v0.0.9 - github.com/fasthttp/router v1.5.0 - github.com/fasthttp/websocket v1.5.8 + github.com/expr-lang/expr v1.17.5 + github.com/fasthttp/websocket v1.5.12 github.com/felixge/fgprof v0.9.5 github.com/fullstorydev/grpchan v1.1.1 - github.com/go-playground/validator/v10 v10.19.0 + github.com/go-logr/logr v1.4.3 + github.com/go-playground/validator/v10 v10.27.0 github.com/gofiber/adaptor/v2 v2.2.1 github.com/gofiber/utils v1.1.0 - github.com/gofiber/websocket/v2 v2.2.1 github.com/golangci/golangci-lint v1.61.0 - github.com/gorilla/websocket v1.5.1 - github.com/goyek/goyek/v2 v2.2.0 - github.com/goyek/workflow v0.0.0-20240815094733-414d2a904ff9 - github.com/goyek/x v0.2.0 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 + github.com/google/gops v0.3.28 + github.com/gorilla/websocket v1.5.3 github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19 - github.com/maragudk/gomponents v0.20.0 - github.com/prometheus/common v0.48.0 - github.com/pubgo/dix v0.3.19 - github.com/pubgo/funk v0.5.56 - github.com/pubgo/protobuild v0.0.21 - github.com/rs/xid v1.5.0 - github.com/rs/zerolog v1.33.0 - github.com/samber/lo v1.47.0 - github.com/stretchr/testify v1.9.0 - github.com/uber-go/tally/v4 v4.1.16 - github.com/urfave/cli/v3 v3.0.0-alpha9.0.20240717192922-127cf54fac9f - github.com/valyala/fasthttp v1.52.0 + github.com/maragudk/gomponents v0.22.0 + github.com/maruel/panicparse/v2 v2.5.0 + github.com/pubgo/dix/v2 v2.0.0-beta.2 + github.com/pubgo/funk/v2 v2.0.0-beta.2 + github.com/rs/xid v1.6.0 + github.com/rs/zerolog v1.34.0 + github.com/samber/lo v1.52.0 + github.com/stretchr/testify v1.10.0 + github.com/thejerf/suture/v4 v4.0.6 + github.com/uber-go/tally/v4 v4.1.17 + github.com/ulikunitz/xz v0.5.15 + github.com/urfave/cli/v3 v3.4.1 + github.com/valyala/fasthttp v1.63.0 github.com/valyala/fasttemplate v1.2.2 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.26.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0 - go.opentelemetry.io/otel/exporters/prometheus v0.45.2 - go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.23.1 - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0 - go.opentelemetry.io/otel/sdk v1.30.0 - go.opentelemetry.io/otel/sdk/metric v1.26.0 - golang.org/x/tools v0.25.0 + go.opentelemetry.io/contrib/zpages v0.62.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 + go.opentelemetry.io/otel/exporters/prometheus v0.59.0 + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0 + go.opentelemetry.io/otel/sdk v1.37.0 + go.opentelemetry.io/otel/sdk/metric v1.37.0 + golang.org/x/tools v0.38.0 golang.org/x/vuln v1.1.3 - google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 + google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 gopkg.in/yaml.v3 v3.0.1 + maragu.dev/gomponents v1.2.0 ) require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 4d63.com/gochecknoglobals v0.2.1 // indirect - dario.cat/mergo v1.0.0 // indirect github.com/4meepo/tagalign v1.3.4 // indirect github.com/Abirdcfly/dupword v0.1.1 // indirect github.com/Antonboom/errname v0.1.13 // indirect @@ -97,13 +95,12 @@ require ( github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect - github.com/a8m/envsubst v1.3.0 // indirect + github.com/a8m/envsubst v1.4.3 // indirect github.com/alecthomas/go-check-sumtype v0.1.4 // indirect - github.com/alecthomas/repr v0.4.0 // indirect github.com/alexkohler/nakedret/v2 v2.0.4 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect - github.com/andybalholm/brotli v1.1.0 // indirect + github.com/andybalholm/brotli v1.2.0 // indirect github.com/ashanbrown/forbidigo v1.6.0 // indirect github.com/ashanbrown/makezero v1.1.1 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -118,7 +115,7 @@ require ( github.com/catenacyber/perfsprint v0.7.1 // indirect github.com/ccojocar/zxcvbn-go v1.0.2 // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charithe/durationcheck v0.0.10 // indirect github.com/chavacava/garif v0.1.0 // indirect @@ -126,24 +123,19 @@ require ( github.com/creasty/defaults v1.7.0 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect github.com/daixiang0/gci v0.13.5 // indirect + github.com/dave/jennifer v1.7.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/deckarep/golang-set/v2 v2.6.0 // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect - github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect github.com/ettle/strcase v0.2.0 // indirect - github.com/fatih/color v1.17.0 // indirect + github.com/fatih/color v1.18.0 // indirect github.com/fatih/structtag v1.2.0 // indirect github.com/firefart/nonamedreturns v1.0.5 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/getkin/kin-openapi v0.124.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.9 // indirect github.com/ghostiam/protogetter v0.3.6 // indirect github.com/go-critic/go-critic v0.11.4 // indirect - github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.20.2 // indirect - github.com/go-openapi/swag v0.22.8 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect @@ -165,32 +157,32 @@ require ( github.com/golangci/plugin-module-register v0.1.1 // indirect github.com/golangci/revgrep v0.5.3 // indirect github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect - github.com/google/go-cmp v0.6.0 // indirect - github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gordonklaus/ineffassign v0.1.0 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect github.com/gostaticanalysis/comment v1.4.2 // indirect github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect github.com/gostaticanalysis/nilerr v0.1.1 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect + github.com/huandu/go-clone v1.5.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/invopop/yaml v0.3.1 // indirect github.com/jgautheron/goconst v1.7.1 // indirect github.com/jhump/protoreflect v1.17.0 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect github.com/jjti/go-spancheck v0.6.2 // indirect github.com/joho/godotenv v1.5.1 // indirect - github.com/josharian/intern v1.0.0 // indirect github.com/julz/importas v0.1.0 // indirect - github.com/k0kubun/pp/v3 v3.2.0 // indirect + github.com/k0kubun/pp/v3 v3.5.0 // indirect github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect github.com/kisielk/errcheck v1.7.0 // indirect github.com/kkHAIKE/contextcheck v1.1.5 // indirect - github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/kulti/thelper v0.6.3 // indirect github.com/kunwardeep/paralleltest v1.0.10 // indirect github.com/kyoh86/exportloopref v0.1.11 // indirect @@ -199,53 +191,51 @@ require ( github.com/ldez/tagliatelle v0.5.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/leonklingele/grouper v1.1.2 // indirect + github.com/lmittmann/tint v1.1.2 // indirect github.com/lufeee/execinquery v1.2.1 // indirect github.com/macabu/inamedparam v0.1.3 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mailru/easyjson v0.7.7 // indirect github.com/maratori/testableexamples v1.0.0 // indirect github.com/maratori/testpackage v1.1.1 // indirect github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/mattn/go-shellwords v1.0.12 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mgechev/revive v1.3.9 // indirect - github.com/miekg/dns v1.1.50 // indirect + github.com/miekg/dns v1.1.66 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/moricho/tparallel v0.3.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nakabonne/nestif v0.3.1 // indirect - github.com/nats-io/nats.go v1.37.0 // indirect - github.com/nats-io/nkeys v0.4.7 // indirect - github.com/nats-io/nuid v1.0.1 // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect github.com/nunnatsa/ginkgolinter v0.16.2 // indirect - github.com/panjf2000/ants/v2 v2.10.0 // indirect + github.com/olekukonko/errors v1.1.0 // indirect + github.com/olekukonko/ll v0.0.9 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect - github.com/perimeterx/marshmallow v1.1.5 // indirect - github.com/phuslu/goid v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polyfloyd/go-errorlint v1.6.0 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/procfs v0.12.0 // indirect + github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect github.com/quasilyte/gogrep v0.5.0 // indirect github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect - github.com/rivo/uniseg v0.2.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/ryancurrah/gomodguard v1.3.5 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect + github.com/samber/slog-common v0.19.0 // indirect github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.27.0 // indirect - github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // indirect + github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287 // indirect github.com/securego/gosec/v2 v2.21.2 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect github.com/sirupsen/logrus v1.9.3 // indirect @@ -269,11 +259,10 @@ require ( github.com/timonwong/loggercheck v0.9.4 // indirect github.com/tomarrell/wrapcheck/v2 v2.9.0 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect - github.com/twmb/murmur3 v1.1.5 // indirect + github.com/twmb/murmur3 v1.1.8 // indirect github.com/ultraware/funlen v0.1.0 // indirect github.com/ultraware/whitespace v0.1.1 // indirect github.com/uudashr/gocognit v1.1.3 // indirect - github.com/valyala/tcplisten v1.0.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/xen0n/gosmopolitan v1.2.2 // indirect github.com/yagipy/maintidx v1.0.0 // indirect @@ -282,15 +271,16 @@ require ( gitlab.com/bosi/decorder v0.4.2 // indirect go-simpler.org/musttag v0.12.2 // indirect go-simpler.org/sloglint v0.7.2 // indirect - go.opentelemetry.io/proto/otlp v1.2.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.24.0 // indirect - golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 // indirect - golang.org/x/text v0.18.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 // indirect + golang.org/x/text v0.31.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect honnef.co/go/tools v0.5.1 // indirect diff --git a/go.sum b/go.sum index e4890f5d4..cdaeb9d45 100644 --- a/go.sum +++ b/go.sum @@ -2,12 +2,11 @@ 4d63.com/gocheckcompilerdirectives v1.2.1/go.mod h1:yjDJSxmDTtIHHCqX0ufRYZDL6vQtMG7tJdKVeWwsqvs= 4d63.com/gochecknoglobals v0.2.1 h1:1eiorGsgHOFOuoOiJDy2psSrQbRdIHrlge0IJIkUgDc= 4d63.com/gochecknoglobals v0.2.1/go.mod h1:KRE8wtJB3CXCsb1xy421JfTHIIbmT3U5ruxw2Qu8fSU= -cel.dev/expr v0.15.0 h1:O1jzfJCQBfL5BFoYktaxwIhuttaQPsVWerH9/EEKx0w= -cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg= +cel.dev/expr v0.23.0 h1:wUb94w6OYQS4uXraxo9U+wUAs9jT47Xvl4iPgAwM2ss= +cel.dev/expr v0.23.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= github.com/4meepo/tagalign v1.3.4 h1:P51VcvBnf04YkHzjfclN6BbsopfJR5rxs1n+5zHt+w8= github.com/4meepo/tagalign v1.3.4/go.mod h1:M+pnkHH2vG8+qhE5bVc/zeP7HS/j910Fwa9TUSyZVI0= github.com/Abirdcfly/dupword v0.1.1 h1:Bsxe0fIw6OwBtXMIncaTxCLHYO5BB+3mcsR5E8VXloY= @@ -31,41 +30,33 @@ github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+ github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ= -github.com/a8m/envsubst v1.3.0 h1:GmXKmVssap0YtlU3E230W98RWtWCyIZzjtf1apWWyAg= -github.com/a8m/envsubst v1.3.0/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY= +github.com/a8m/envsubst v1.4.3 h1:kDF7paGK8QACWYaQo6KtyYBozY2jhQrTuNNuUxQkhJY= +github.com/a8m/envsubst v1.4.3/go.mod h1:4jjHWQlZoaXPoLQUb7H2qT4iLkZDdmEQiOUogdUmqVU= github.com/ahmetb/go-linq v3.0.0+incompatible h1:qQkjjOXKrKOTy83X8OpRmnKflXKQIL/mC/gMVVDMhOA= github.com/ahmetb/go-linq v3.0.0+incompatible/go.mod h1:PFffvbdbtw+QTB0WKRP0cNht7vnCfnGlEpak/DVg5cY= -github.com/alecthomas/assert/v2 v2.3.0 h1:mAsH2wmvjsuvyBvAmCtm7zFsBlb8mIHx5ySLVdDZXL0= -github.com/alecthomas/assert/v2 v2.3.0/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/go-check-sumtype v0.1.4 h1:WCvlB3l5Vq5dZQTFmodqL2g68uHiSwwlWcT5a2FGK0c= github.com/alecthomas/go-check-sumtype v0.1.4/go.mod h1:WyYPfhfkdhyrdaligV6svFopZV8Lqdzn5pyVBaV6jhQ= -github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8= -github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= +github.com/alecthomas/participle/v2 v2.1.4 h1:W/H79S8Sat/krZ3el6sQMvMaahJ+XcM9WSI2naI7w2U= +github.com/alecthomas/participle/v2 v2.1.4/go.mod h1:8tqVbpTX20Ru4NfYQgZf4mP18eXPTBViyMWiArNEgGI= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alexkohler/nakedret/v2 v2.0.4 h1:yZuKmjqGi0pSmjGpOC016LtPJysIL0WEUiaXW5SUnNg= github.com/alexkohler/nakedret/v2 v2.0.4/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU= github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= -github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= -github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= -github.com/arl/statsviz v0.6.0 h1:jbW1QJkEYQkufd//4NDYRSNBpwJNrdzPahF7ZmoGdyE= -github.com/arl/statsviz v0.6.0/go.mod h1:0toboo+YGSUXDaS4g1D5TVS4dXs7S7YYT5J/qnW2h8s= +github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= +github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/arl/statsviz v0.7.1 h1:W32VBGV/YBTMg3sBsr1Ix1bJzrykZHWNZS7quqtDqYc= +github.com/arl/statsviz v0.7.1/go.mod h1:uFJZYUcGDeFpo/Mb9nLkq/83YUYaib2IccejOm+y1t0= github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY= github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bkielbasa/cyclop v1.2.1 h1:AeF71HZDob1P2/pRm1so9cd1alZnrpyc4q2uP2l0gJY= @@ -84,19 +75,15 @@ github.com/butuzov/ireturn v0.3.0 h1:hTjMqWw3y5JC3kpnC5vXmFJAWI/m31jaCYQqzkS6PL0 github.com/butuzov/ireturn v0.3.0/go.mod h1:A09nIiwiqzN/IoVo9ogpa0Hzi9fex1kd9PSD6edP5ZA= github.com/butuzov/mirror v1.2.0 h1:9YVK1qIjNspaqWutSv8gsge2e/Xpq1eqEkslEUHy5cs= github.com/butuzov/mirror v1.2.0/go.mod h1:DqZZDtzm42wIAIyHXeN8W/qb1EPlb9Qn/if9icBOpdQ= -github.com/cactus/go-statsd-client/v5 v5.0.0/go.mod h1:COEvJ1E+/E2L4q6QE5CkjWPi4eeDw9maJBMIuMPBZbY= github.com/catenacyber/perfsprint v0.7.1 h1:PGW5G/Kxn+YrN04cRAZKC+ZuvlVwolYMrIyyTJ/rMmc= github.com/catenacyber/perfsprint v0.7.1/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= +github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= -github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= @@ -114,8 +101,8 @@ github.com/ckaznocha/intrange v0.2.0/go.mod h1:r5I7nUlAAG56xmkOpw4XVr16BXhwYTUdc github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw= -github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k= +github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdBA= @@ -129,8 +116,6 @@ github.com/dave/jennifer v1.7.0/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= -github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= github.com/ecordell/optgen v0.0.9 h1:kmRMqOkbNsWayOnZSk2m5SeGaOTOc7amfi+MAnaMOeI= @@ -139,19 +124,20 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155 h1:IgJPqnrlY2Mr4pYB6oaMKvFvwJ9H+X6CCY5x1vCTcpc= -github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155/go.mod h1:5Wkq+JduFtdAXihLmeTJf+tRYIT4KBc2vPXDhwVo1pA= +github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= +github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= +github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= -github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= -github.com/fasthttp/router v1.5.0 h1:3Qbbo27HAPzwbpRzgiV5V9+2faPkPt3eNuRaDV6LYDA= -github.com/fasthttp/router v1.5.0/go.mod h1:FddcKNXFZg1imHcy+uKB0oo/o6yE9zD3wNguqlhWDak= -github.com/fasthttp/websocket v1.5.8 h1:k5DpirKkftIF/w1R8ZzjSgARJrs54Je9YJK37DL/Ah8= -github.com/fasthttp/websocket v1.5.8/go.mod h1:d08g8WaT6nnyvg9uMm8K9zMYyDjfKyj3170AtPRuVU0= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/expr-lang/expr v1.17.5 h1:i1WrMvcdLF249nSNlpQZN1S6NXuW9WaOfF5tPi3aw3k= +github.com/expr-lang/expr v1.17.5/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= +github.com/fasthttp/websocket v1.5.12 h1:e4RGPpWW2HTbL3zV0Y/t7g0ub294LkiuXXUuTOUInlE= +github.com/fasthttp/websocket v1.5.12/go.mod h1:I+liyL7/4moHojiOgUOIKEWm9EIxHqxZChS+aMFltyg= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY= @@ -166,44 +152,32 @@ github.com/fullstorydev/grpchan v1.1.1 h1:heQqIJlAv5Cnks9a70GRL2EJke6QQoUB25VGR6 github.com/fullstorydev/grpchan v1.1.1/go.mod h1:f4HpiV8V6htfY/K44GWV1ESQzHBTq7DinhzqQ95lpgc= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/getkin/kin-openapi v0.124.0 h1:VSFNMB9C9rTKBnQ/fpyDU8ytMTr4dWI9QovSKj9kz/M= -github.com/getkin/kin-openapi v0.124.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= +github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= +github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= github.com/ghostiam/protogetter v0.3.6 h1:R7qEWaSgFCsy20yYHNIJsU9ZOb8TziSRRxuAOTVKeOk= github.com/ghostiam/protogetter v0.3.6/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw= github.com/go-critic/go-critic v0.11.4 h1:O7kGOCx0NDIni4czrkRIXTnit0mkyKOCePh3My6OyEU= github.com/go-critic/go-critic v0.11.4/go.mod h1:2QAdo4iuLik5S9YG0rT4wcZ8QxwHYkrr6/2MWAiv/vc= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= -github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= -github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicbw= -github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= -github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= -github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= @@ -232,27 +206,23 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofiber/adaptor/v2 v2.2.1 h1:givE7iViQWlsTR4Jh7tB4iXzrlKBgiraB/yTdHs9Lv4= github.com/gofiber/adaptor/v2 v2.2.1/go.mod h1:AhR16dEqs25W2FY/l8gSj1b51Azg5dtPDmm+pruNOrc= -github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM= -github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw= +github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM= github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0= -github.com/gofiber/websocket/v2 v2.2.1 h1:C9cjxvloojayOp9AovmpQrk8VqvVnT8Oao3+IUygH7w= -github.com/gofiber/websocket/v2 v2.2.1/go.mod h1:Ao/+nyNnX5u/hIFPuHl28a+NIkrqK7PRimyKaj4JxVU= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= @@ -262,7 +232,6 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= @@ -295,12 +264,14 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gops v0.3.28 h1:2Xr57tqKAmQYRAfG12E+yLcoa2Y42UJo2lOrUFL9ark= +github.com/google/gops v0.3.28/go.mod h1:6f6+Nl8LcHrzJwi8+p0ii+vmBFSlB4f8cOOkTJ7sk4c= github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= -github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 h1:c5FlPPgxOn7kJz3VoPLkQYQXGBS3EklQ4Zfi57uOuqQ= -github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ= +github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -308,8 +279,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= -github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= -github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= @@ -322,18 +293,12 @@ github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= github.com/gostaticanalysis/testutil v0.4.0 h1:nhdCmubdmDF6VEatUNjgUZBJKWRqugoISdUv3PPQgHY= github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU= -github.com/goyek/goyek/v2 v2.2.0 h1:FXdta04rwVN/HE33XKcBtYht6iaDkJ9Z5Bdh1TdXjO0= -github.com/goyek/goyek/v2 v2.2.0/go.mod h1:mqU0PSD8q3TRCzhJ1mL79/X7FOrXJcpvOtDEBXi92+E= -github.com/goyek/workflow v0.0.0-20240815094733-414d2a904ff9 h1:rB0kFkFNb66K5JPGDrh+xibOJQBmCQgcC4EkKYCkU/w= -github.com/goyek/workflow v0.0.0-20240815094733-414d2a904ff9/go.mod h1:ApuON/7oB3flhv3Fy5XrKe3K/lQDkVedvWjP/mUAq4Y= -github.com/goyek/x v0.2.0 h1:vLTzDTqbUQnWTM3NOnejGAWwWZA+hJHElv/cd/3ybQc= -github.com/goyek/x v0.2.0/go.mod h1:I1yv17Zj0g3Zv5iDC+kB9gh3cqJquJSTEgfognEfe94= github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE= github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= @@ -341,12 +306,13 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c= +github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= +github.com/huandu/go-clone v1.5.1 h1:1wlwYRlHZo4HspdOM0YQ6O7Y7bjtxTrrt+4jnDeejVo= +github.com/huandu/go-clone v1.5.1/go.mod h1:ReGivhG6op3GYr+UY3lS6mxjKp7MIGTknuU5TbTVaXE= github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= -github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk= github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= @@ -365,20 +331,13 @@ github.com/jjti/go-spancheck v0.6.2 h1:iYtoxqPMzHUPp7St+5yA8+cONdyXD3ug6KK15n7Pk github.com/jjti/go-spancheck v0.6.2/go.mod h1:+X7lvIrR5ZdUTkxFYqzJ0abr8Sb5LOo80uOhWNqIrYA= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= -github.com/k0kubun/pp/v3 v3.2.0 h1:h33hNTZ9nVFNP3u2Fsgz8JXiF5JINoZfFq4SvKJwNcs= -github.com/k0kubun/pp/v3 v3.2.0/go.mod h1:ODtJQbQcIRfAD3N+theGCV1m/CBxweERz2dapdz1EwA= +github.com/k0kubun/pp/v3 v3.5.0 h1:iYNlYA5HJAJvkD4ibuf9c8y6SHM0QFhaBuCqm1zHp0w= +github.com/k0kubun/pp/v3 v3.5.0/go.mod h1:5lzno5ZZeEeTV/Ky6vs3g6d1U3WarDrH8k240vMtGro= github.com/karamaru-alpha/copyloopvar v1.1.0 h1:x7gNyKcC2vRBO1H2Mks5u1VxQtYvFiym7fCjIP8RPos= github.com/karamaru-alpha/copyloopvar v1.1.0/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19 h1:WjT3fLi9n8YWh/Ih8Q1LHAPsTqGddPcHqscN+PJ3i68= @@ -389,11 +348,9 @@ github.com/kisielk/errcheck v1.7.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkHAIKE/contextcheck v1.1.5 h1:CdnJh63tcDe53vG+RebdpdXJTc9atMgGqdx8LXxiilg= github.com/kkHAIKE/contextcheck v1.1.5/go.mod h1:O930cpht4xb1YQpK+1+AgoM3mFsvxr7uyFptcnWTYUA= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -405,6 +362,8 @@ github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= github.com/kunwardeep/paralleltest v1.0.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCTdvWJ/lDDs= github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kyoh86/exportloopref v0.1.11 h1:1Z0bcmTypkL3Q4k+IDHMWTcnCliEZcaPiIe0/ymEyhQ= github.com/kyoh86/exportloopref v0.1.11/go.mod h1:qkV4UF1zGl6EkF1ox8L5t9SwyeBAZ3qLMd6up458uqA= github.com/lasiar/canonicalheader v1.1.1 h1:wC+dY9ZfiqiPwAexUApFush/csSPXeIi4QqyxXmng8I= @@ -418,43 +377,43 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= +github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w= +github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= github.com/lufeee/execinquery v1.2.1 h1:hf0Ems4SHcUGBxpGN7Jz78z1ppVkP/837ZlETPCEtOM= github.com/lufeee/execinquery v1.2.1/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM= github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk= github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mailgun/holster/v4 v4.19.0 h1:BQ390TMYg7CxBTMvZsHbq0fKnfRY7wxNb0uV+ZdwTJk= -github.com/mailgun/holster/v4 v4.19.0/go.mod h1:/5ijRCyMjOHxt69WdAgvB2gyYCapJaJdT/QciGIcu50= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailgun/holster/v4 v4.21.0 h1:EH3fwKEGv56WA5gUwxjOTqZbeILY+oJ/VWEo1xku7t8= +github.com/mailgun/holster/v4 v4.21.0/go.mod h1:G06Q741dj+zsH1WFrmoFvih3LtaocvBIoNtxITdWEtg= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/maragudk/gomponents v0.20.0 h1:lJn5PMYHAKm8jywfk3UiL9tm54ZBkv5R5WktY4S8kxY= -github.com/maragudk/gomponents v0.20.0/go.mod h1:nHkNnZL6ODgMBeJhrZjkMHVvNdoYsfmpKB2/hjdQ0Hg= +github.com/maragudk/gomponents v0.22.0 h1:0gNrSDC1nM6w0Vxj5wgGXqV8frDH9UVPE+dEyy4ApPQ= +github.com/maragudk/gomponents v0.22.0/go.mod h1:nHkNnZL6ODgMBeJhrZjkMHVvNdoYsfmpKB2/hjdQ0Hg= github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc= +github.com/maruel/panicparse/v2 v2.5.0 h1:yCtuS0FWjfd0RTYMXGpDvWcb0kINm8xJGu18/xMUh00= +github.com/maruel/panicparse/v2 v2.5.0/go.mod h1:DA2fDiBk63bKfBf4CVZP9gb4fuvzdPbLDsSI873hweQ= github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 h1:gWg6ZQ4JhDfJPqlo2srm/LN17lpybq15AryXIRcWYLE= github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= -github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mgechev/revive v1.3.9 h1:18Y3R4a2USSBF+QZKFQwVkBROUda7uoBlkEuBD+YD1A= github.com/mgechev/revive v1.3.9/go.mod h1:+uxEIr5UH0TjXWHTno3xh4u7eg6jDpXKzQccA9UGhHU= github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= -github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= -github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= +github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE= +github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -462,32 +421,26 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= -github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= -github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE= -github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= -github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= -github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= -github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= github.com/nunnatsa/ginkgolinter v0.16.2 h1:8iLqHIZvN4fTLDC0Ke9tbSZVcyVHoBs0HIbnVSxfHJk= github.com/nunnatsa/ginkgolinter v0.16.2/go.mod h1:4tWRinDN1FeJgU+iJANW/kz7xKN5nYRAOfJDQUS9dOQ= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM= +github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI= +github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g= +github.com/olekukonko/tablewriter v1.0.8 h1:f6wJzHg4QUtJdvrVPKco4QTrAylgaU0+b9br/lJxEiQ= +github.com/olekukonko/tablewriter v1.0.8/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs= github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= @@ -501,17 +454,10 @@ github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= -github.com/panjf2000/ants/v2 v2.10.0 h1:zhRg1pQUtkyRiOFo2Sbqwjp0GfBNo9cUY2/Grpx1p+8= -github.com/panjf2000/ants/v2 v2.10.0/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= -github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= -github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= -github.com/phuslu/goid v1.0.0 h1:Cgcvd/R54UO1fCtyt+iKXAi+yZQ/KWlAm6MmZNizCLM= -github.com/phuslu/goid v1.0.0/go.mod h1:txc2fUIdrdnn+v9Vq+QpiPQ3dnrXEchjoVDgic+r+L0= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -523,37 +469,21 @@ github.com/polyfloyd/go-errorlint v1.6.0 h1:tftWV9DE7txiFzPpztTAwyoRLKNj9gpVm2cg github.com/polyfloyd/go-errorlint v1.6.0/go.mod h1:HR7u8wuP1kb1NeN1zqTd1ZMlqUKPPHF+Id4vIPvDqVw= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582 h1:eR+0HE//Ciyfwy3HC7fjRyKShSJHYoX2Pv7pPshjK/Q= +github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582/go.mod h1:3G3BRKui7nMuDFAZKR/M2hiOLtaOmyukT20g88qRQjI= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= -github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/pubgo/dix v0.3.19 h1:a4RRmljw7ePUvc9uJKqXXs6JpYL8s3VqPP2NV2vxI9U= -github.com/pubgo/dix v0.3.19/go.mod h1:J0PyuMm7mW6ZrN13l9xz0rtKbzliREFyvb7fuaGYyVw= -github.com/pubgo/funk v0.5.56 h1:LYpjOBDSSeTorRWda0lmf23sJNDm6ZtbbNS0xQjb1CE= -github.com/pubgo/funk v0.5.56/go.mod h1:uAs5IQDz6ovr5zy4LC0Uq7No/eTIbeloSCjF0EQJesw= -github.com/pubgo/opendoc v0.0.5 h1:aM6xkBQ0XMSq8OWytl5JhTMBUv8L+aYQFXd2z9eNkZE= -github.com/pubgo/opendoc v0.0.5/go.mod h1:uO//pJZTJNFEKGuGWrv51/of0EoyxO1il4MuoC3Enp0= -github.com/pubgo/protobuild v0.0.21 h1:QSwh92QxsD6Mv7kQlowzE7fxtcOm1eMWmgADsD803Js= -github.com/pubgo/protobuild v0.0.21/go.mod h1:gCBq+4Wldy2OS7zkjWASpXtbq8pzWDZw6mOEyAnh3x8= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/pubgo/dix/v2 v2.0.0-beta.2 h1:vLPl7/ftL2qCgm6iNZM62jqtfsdvXA6Gqt48Gjnc+Ew= +github.com/pubgo/dix/v2 v2.0.0-beta.2/go.mod h1:jV/9KWf+YxtoQATuZLyUraACduxHvfaum5EZDSCK5gE= +github.com/pubgo/funk/v2 v2.0.0-beta.2 h1:Y9tiaq8YLbvJ6JBZtbzypiA55Go8DXrjLJd+71dj6ec= +github.com/pubgo/funk/v2 v2.0.0-beta.2/go.mod h1:E2QfiKP4IKOM0iVbwK+VBBtQcgeNwyeRbEDXVK97nPY= github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 h1:+Wl/0aFp0hpuHM3H//KMft64WQ1yX9LdJY64Qm/gFCo= github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= @@ -564,23 +494,26 @@ github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= -github.com/reugn/go-quartz v0.11.2 h1:+jc54Ji06n/D/endEPmc+CuG/Jc8466nda1oxtFRrks= -github.com/reugn/go-quartz v0.11.2/go.mod h1:no4ktgYbAAuY0E1SchR8cTx1LF4jYIzdgaQhzRPSkpk= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/reugn/go-quartz v0.15.1 h1:8hMtC+ERa9G5tiL9fiuBpPreId+Tu1Xt2pkXVeTZAy0= +github.com/reugn/go-quartz v0.15.1/go.mod h1:00DVnBKq2Fxag/HlR9mGXjmHNlMFQ1n/LNM+Fn0jUaE= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= -github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.3.5 h1:cShyguSwUEeC0jS7ylOiG/idnd1TpJ1LfHGpV3oJmPU= github.com/ryancurrah/gomodguard v1.3.5/go.mod h1:MXlEPQRxgfPQa62O8wzK3Ozbkv9Rkqr+wKjSxTdsNJE= github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= -github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= -github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= +github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= +github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= +github.com/samber/slog-common v0.19.0 h1:fNcZb8B2uOLooeYwFpAlKjkQTUafdjfqKcwcC89G9YI= +github.com/samber/slog-common v0.19.0/go.mod h1:dTz+YOU76aH007YUU0DffsXNsGFQRQllPQh9XyNoA3M= github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/NsJFwFK7Uxc= github.com/sanposhiho/wastedassign/v2 v2.0.7/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= @@ -589,17 +522,15 @@ github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tM github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.27.0 h1:t/3jZpSXtRPRf2xr0m63i32ZrusyurIGT9E5wAvXQnI= github.com/sashamelentyev/usestdlibvars v1.27.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= -github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8= -github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= +github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287 h1:qIQ0tWF9vxGtkJa24bR+2i53WBCz1nW/Pc47oVYauC4= +github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= github.com/securego/gosec/v2 v2.21.2 h1:deZp5zmYf3TWwU7A7cR2+SolbTpZ3HQiwFqnzQyEl3M= github.com/securego/gosec/v2 v2.21.2/go.mod h1:au33kg78rNseF5PwPnTWhuYBFf534bvJRvOrgZ/bFzU= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= @@ -639,10 +570,9 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tdakkota/asciicheck v0.2.0 h1:o8jvnUANo0qXtnslk2d3nMKTFNlOnJjRrNcj0j9qkHM= @@ -653,6 +583,8 @@ github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpR github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= github.com/tetafro/godot v1.4.17 h1:pGzu+Ye7ZUEFx7LHU0dAKmCOXWsPjl7qA6iMGndsjPs= github.com/tetafro/godot v1.4.17/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= +github.com/thejerf/suture/v4 v4.0.6 h1:QsuCEsCqb03xF9tPAsWAj8QOAJBgQI1c0VqJNaingg8= +github.com/thejerf/suture/v4 v4.0.6/go.mod h1:gu9Y4dXNUWFrByqRt30Rm9/UZ0wzRSt9AJS6xu/ZGxU= github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -667,34 +599,34 @@ github.com/tomarrell/wrapcheck/v2 v2.9.0 h1:801U2YCAjLhdN8zhZ/7tdjB3EnAoRlJHt/s+ github.com/tomarrell/wrapcheck/v2 v2.9.0/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= -github.com/twmb/murmur3 v1.1.5 h1:i9OLS9fkuLzBXjt6dptlAEyk58fJsSTXbRg3SgVyqgk= -github.com/twmb/murmur3 v1.1.5/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= -github.com/uber-go/tally/v4 v4.1.16 h1:by2hveWRh/cUReButk6ns1sHK/hiKry7BuOV6iY16XI= -github.com/uber-go/tally/v4 v4.1.16/go.mod h1:RW5DgqsyEPs0lA4b0YNf4zKj7DveKHd73hnO6zVlyW0= -github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= -github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg= +github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= +github.com/uber-go/tally/v4 v4.1.17 h1:C+U4BKtVDXTszuzU+WH8JVQvRVnaVKxzZrROFyDrvS8= +github.com/uber-go/tally/v4 v4.1.17/go.mod h1:ZdpiHRGSa3z4NIAc1VlEH4SiknR885fOIF08xmS0gaU= +github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= +github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ultraware/funlen v0.1.0 h1:BuqclbkY6pO+cvxoq7OsktIXZpgBSkYTQtmwhAK81vI= github.com/ultraware/funlen v0.1.0/go.mod h1:XJqmOQja6DpxarLj6Jj1U7JuoS8PvL4nEqDaQhy22p4= github.com/ultraware/whitespace v0.1.1 h1:bTPOGejYFulW3PkcrqkeQwOd6NKOOXvmGD9bo/Gk8VQ= github.com/ultraware/whitespace v0.1.1/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= -github.com/urfave/cli/v3 v3.0.0-alpha9.0.20240717192922-127cf54fac9f h1:yCJ90PBe7+45EQSF3qJXyAGW5rkE65lE8huv5pM0HY8= -github.com/urfave/cli/v3 v3.0.0-alpha9.0.20240717192922-127cf54fac9f/go.mod h1:Z1ItyMma7t6I7zHG9OpbExhHQOSkFf/96n+mAZ9MtVI= +github.com/urfave/cli/v3 v3.4.1 h1:1M9UOCy5bLmGnuu1yn3t3CB4rG79Rtoxuv1sPhnm6qM= +github.com/urfave/cli/v3 v3.4.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo= github.com/uudashr/gocognit v1.1.3 h1:l+a111VcDbKfynh+airAy/DJQKaXh2m9vkoysMPSZyM= github.com/uudashr/gocognit v1.1.3/go.mod h1:aKH8/e8xbTRBwjbCkwZ8qt4l2EpKXl31KMHgSS+lZ2U= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0= -github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ= +github.com/valyala/fasthttp v1.63.0 h1:DisIL8OjB7ul2d7cBaMRcKTQDYnrGy56R4FCiuDP0Ns= +github.com/valyala/fasthttp v1.63.0/go.mod h1:REc4IeW+cAEyLrRPa5A81MIjvz0QE1laoTX2EaPHKJM= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= -github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs= @@ -716,56 +648,58 @@ go-simpler.org/musttag v0.12.2 h1:J7lRc2ysXOq7eM8rwaTYnNrHd5JwjppzB6mScysB2Cs= go-simpler.org/musttag v0.12.2/go.mod h1:uN1DVIasMTQKk6XSik7yrJoEysGtR2GRqvWnI9S7TYM= go-simpler.org/sloglint v0.7.2 h1:Wc9Em/Zeuu7JYpl+oKoYOsQSy2X560aVueCW/m6IijY= go-simpler.org/sloglint v0.7.2/go.mod h1:US+9C80ppl7VsThQclkM7BkCHQAzuz8kHLsW3ppuluo= -go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= -go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.26.0 h1:+hm+I+KigBy3M24/h1p/NHkUx/evbLH0PNcjpMyCHc4= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.26.0/go.mod h1:NjC8142mLvvNT6biDpaMjyz78kyEHIwAJlSX0N9P5KI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 h1:1u/AyyOqAWzy+SkPxDpahCNZParHV8Vid1RnI2clyDE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0/go.mod h1:z46paqbJ9l7c9fIPCXTqTGwhQZ5XoTIsfeFYWboizjs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0 h1:Waw9Wfpo/IXzOI8bCB7DIk+0JZcqqsyn1JFnAc+iam8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0/go.mod h1:wnJIG4fOqyynOnnQF/eQb4/16VlX2EJAHhHgqIqWfAo= -go.opentelemetry.io/otel/exporters/prometheus v0.45.2 h1:pe2Jqk1K18As0RCw7J08QhgXNqr+6npx0a5W4IgAFA8= -go.opentelemetry.io/otel/exporters/prometheus v0.45.2/go.mod h1:B38pscHKI6bhFS44FDw0eFU3iqG3ASNIvY+fZgR5sAc= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.23.1 h1:C8r95vDR125t815KD+b1tI0Fbc1pFnwHTBxkbIZ6Szc= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.23.1/go.mod h1:Qr0qomr64jentMtOjWMbtYeJMSuMSlsPEjmnRA2sWZ4= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0 h1:EVSnY9JbEEW92bEkIYOVMw4q1WJxIAGoFTrtYOzWuRQ= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0/go.mod h1:Ea1N1QQryNXpCD0I1fdLibBAIpQuBkznMmkdKrapk1Y= -go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= -go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= -go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= -go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= -go.opentelemetry.io/otel/sdk/metric v1.26.0 h1:cWSks5tfriHPdWFnl+qpX3P681aAYqlZHcAyHw5aU9Y= -go.opentelemetry.io/otel/sdk/metric v1.26.0/go.mod h1:ClMFFknnThJCksebJwz7KIyEDHO+nTB6gK8obLy8RyE= -go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= -go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= -go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= -go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/zpages v0.62.0 h1:9fUYTLmrK0x/lweM2uM+BOx069jLx8PxVqWhegGJ9Bo= +go.opentelemetry.io/contrib/zpages v0.62.0/go.mod h1:C8kXoiC1Ytvereztus2R+kqdSa6W/MZ8FfS8Zwj+LiM= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 h1:zG8GlgXCJQd5BU98C0hZnBbElszTmUgCNCfYneaDL0A= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0/go.mod h1:hOfBCz8kv/wuq73Mx2H2QnWokh/kHZxkh6SNF2bdKtw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/exporters/prometheus v0.59.0 h1:HHf+wKS6o5++XZhS98wvILrLVgHxjA/AMjqHKes+uzo= +go.opentelemetry.io/otel/exporters/prometheus v0.59.0/go.mod h1:R8GpRXTZrqvXHDEGVH5bF6+JqAZcK8PjJcZ5nGhEWiE= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0 h1:6VjV6Et+1Hd2iLZEPtdV7vie80Yyqf7oikJLjQ/myi0= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0/go.mod h1:u8hcp8ji5gaM/RfcOo8z9NMnf1pVLfVY7lBY2VOGuUU= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0 h1:SNhVp/9q4Go/XHBkQ1/d5u9P/U+L1yaGPoi0x+mStaI= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0/go.mod h1:tx8OOlGH6R4kLV67YaYO44GFXloEjGPZuMjEkaaqIp4= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= -go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc= +golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8= @@ -785,16 +719,13 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -803,42 +734,32 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -846,9 +767,7 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -864,10 +783,10 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 h1:FemxDzfMUcK2f3YY4H+05K9CDzbSVr2+q/JKN45pey0= -golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU= +golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -882,8 +801,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -906,7 +825,6 @@ golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0t golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -914,8 +832,12 @@ golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= -golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/tools/go/expect v0.1.0-deprecated h1:jY2C5HGYR5lqex3gEniOQL0r7Dq5+VGVgY1nudX5lXY= +golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= golang.org/x/vuln v1.1.3 h1:NPGnvPOTgnjBc9HTaUx+nj+EaUYxl5SJOWqaDYGaFYw= golang.org/x/vuln v1.1.3/go.mod h1:7Le6Fadm5FOqE9C926BCD0g12NWyhg7cxV4BwcPFuNY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -928,18 +850,18 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM= -google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= +google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -951,9 +873,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.3-0.20240816073751-94ecbc261689 h1:hNwajDgT0MlsxZzlUajZVmUYFpts8/CYe4BSNx503ZE= -google.golang.org/protobuf v1.34.3-0.20240816073751-94ecbc261689/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -961,13 +882,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/validator.v2 v2.0.0-20200605151824-2b28d334fa05/go.mod h1:o4V0GXN9/CAmCsvJ0oXYZvrZOe7syiDZSN1GWGZTGzc= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -978,6 +894,8 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I= honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= +maragu.dev/gomponents v1.2.0 h1:H7/N5htz1GCnhu0HB1GasluWeU2rJZOYztVEyN61iTc= +maragu.dev/gomponents v1.2.0/go.mod h1:oEDahza2gZoXDoDHhw8jBNgH+3UR5ni7Ur648HORydM= mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU= mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo= mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f h1:lMpcwN6GxNbWtbpI1+xzFLSW8XzX0u72NttUGVFjO3U= diff --git a/internal/configs/components/app.yaml b/internal/configs/components/app.yaml new file mode 100644 index 000000000..1c5be3d41 --- /dev/null +++ b/internal/configs/components/app.yaml @@ -0,0 +1,4 @@ +app: + env: ${ENV:-"dev"} +go_pool: + worker_size: 100 diff --git a/internal/configs/components/grpc_server.yaml b/internal/configs/components/grpc_server.yaml new file mode 100644 index 000000000..2023be14c --- /dev/null +++ b/internal/configs/components/grpc_server.yaml @@ -0,0 +1,8 @@ +grpc_server: + base_url: /welogin + enable_print_router: true + + # 256k +# ws_read_limit: 262144 +# grpc_port: 8000 +# http_port: 8001 diff --git a/internal/configs/components/http_server.yaml b/internal/configs/components/http_server.yaml new file mode 100644 index 000000000..c7ed96dbc --- /dev/null +++ b/internal/configs/components/http_server.yaml @@ -0,0 +1,4 @@ +http_server: + base_url: /scheduler + enable_print_router: true +# http_port: 8001 diff --git a/internal/configs/components/logger.yaml b/internal/configs/components/logger.yaml new file mode 100644 index 000000000..60acd39ab --- /dev/null +++ b/internal/configs/components/logger.yaml @@ -0,0 +1,5 @@ +logger: + level: ${LOG_LEVEL:-"debug"} + as_json: ${LOG_AS_JSON:-false} + filters: + - level not in ["debug"] diff --git a/internal/configs/components/metric.yaml b/internal/configs/components/metric.yaml new file mode 100644 index 000000000..964bfba1f --- /dev/null +++ b/internal/configs/components/metric.yaml @@ -0,0 +1,3 @@ +metric: + driver: "prometheus" + interval: 5s diff --git a/internal/configs/scheduler.yaml b/internal/configs/scheduler.yaml new file mode 100644 index 000000000..e62322fb6 --- /dev/null +++ b/internal/configs/scheduler.yaml @@ -0,0 +1,8 @@ +resources: + - components + +patch_resources: + - .local.yaml + +patch_envs: + - envs/envs.yaml diff --git a/internal/examples/fileserver/main.go b/internal/examples/fileserver/main.go new file mode 100644 index 000000000..cbb8d1a66 --- /dev/null +++ b/internal/examples/fileserver/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "context" + + "github.com/pubgo/funk/v2/env" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/recovery" + "github.com/pubgo/funk/v2/running" + "github.com/pubgo/lava/v2/cmds/fileservercmd" + "github.com/pubgo/lava/v2/core/lavabuilder" + "github.com/pubgo/lava/v2/core/logging/logext/slog" +) + +func main() { + defer recovery.Exit() + + slog.SetLogger(log.GetLogger()) + log.SetEnableChecker(func(ctx context.Context, lvl log.Level, name, message string, fields log.Fields) bool { + if running.Debug() { + return true + } + + if name == "dix" || name == "env" { + return false + } + return true + }) + + env.Reload() + + builder := lavabuilder.New() + builder.Provide(fileservercmd.New) + + lavabuilder.Run(builder) +} diff --git a/internal/examples/scheduler/main.go b/internal/examples/scheduler/main.go new file mode 100644 index 000000000..355c76ba6 --- /dev/null +++ b/internal/examples/scheduler/main.go @@ -0,0 +1,61 @@ +package main + +import ( + "context" + "fmt" + "time" + + "github.com/pubgo/funk/v2/cmds/configcmd" + "github.com/pubgo/funk/v2/cmds/envcmd" + "github.com/pubgo/funk/v2/config" + "github.com/pubgo/funk/v2/recovery" + "github.com/pubgo/funk/v2/result" + "github.com/pubgo/lava/v2/core/lavabuilder" + "github.com/pubgo/lava/v2/core/logging" + "github.com/pubgo/lava/v2/core/metrics" + "github.com/pubgo/lava/v2/core/scheduler" + "github.com/pubgo/lava/v2/servers/https" +) + +type Config struct { + metrics.MetricConfigLoader `yaml:",inline"` + logging.LogConfigLoader `yaml:",inline"` + https.HttpServerConfigLoader `yaml:",inline"` +} + +var _ scheduler.JobRegister = (*schedulerExample)(nil) + +type schedulerExample struct { +} + +func (s schedulerExample) RegisterSchedulerJob(reg scheduler.JobRegistry) { + reg.Once("once_task", time.Second*10, func(ctx context.Context, name string, metadata *scheduler.JobMetadata) result.Result[[]byte] { + fmt.Printf("exec once task: %s: %#v\n", name, metadata) + time.Sleep(time.Second * 5) + return result.OK([]byte("once")) + }) + + reg.Every("every_task", time.Second*5, func(ctx context.Context, name string, metadata *scheduler.JobMetadata) result.Result[[]byte] { + fmt.Printf("exec every task: %s: %#v\n", name, metadata) + time.Sleep(time.Second * 1) + return result.OK([]byte("every")) + }) + + reg.Cron("cron_task", "*/7 * * * * *", func(ctx context.Context, name string, metadata *scheduler.JobMetadata) result.Result[[]byte] { + fmt.Printf("exec cron task: %s: %#v\n", name, metadata) + time.Sleep(time.Second * 2) + return result.OK([]byte("cron")) + }) +} + +func main() { + defer recovery.Exit() + + builder := lavabuilder.New() + builder.Provide(config.Load[Config]) + builder.Provide(envcmd.New) + builder.Provide(configcmd.New[Config]) + builder.Provide(func() scheduler.JobRegister { return new(schedulerExample) }) + + lavabuilder.Run(builder) +} diff --git a/internal/examples/scheduler/taskfile.yml b/internal/examples/scheduler/taskfile.yml new file mode 100644 index 000000000..34ac11d0a --- /dev/null +++ b/internal/examples/scheduler/taskfile.yml @@ -0,0 +1,26 @@ +# https://taskfile.dev + +version: '3' + +vars: + SchedulerRelease: "v0.1.0" + SchedulerProject: "scheduler" + +tasks: + info: + cmds: + - echo "{{.SchedulerRelease}}" "{{.SchedulerProject}}" + + default: + cmds: + - task scheduler:info + + build: + cmds: +# - go build -v -x -race -ldflags="-w" -o ./bin/scheduler ./internal/examples/scheduler/main.go + - go build -o ./bin/scheduler ./internal/examples/scheduler/main.go + - ls -alh ./bin + run: + cmds: + - task scheduler:build + - SERVER_HTTP_PORT=8082 ./bin/scheduler scheduler -c ./internal/configs/scheduler.yaml diff --git a/internal/examples/xz/main.go b/internal/examples/xz/main.go new file mode 100644 index 000000000..d6106de3d --- /dev/null +++ b/internal/examples/xz/main.go @@ -0,0 +1,40 @@ +// Copyright 2014-2022 Ulrich Kunitz. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "fmt" + "io" + "log" + "os" + + "github.com/ulikunitz/xz" +) + +func main() { + const text = "The quick brown fox jumps over the lazy dog.\n" + var buf bytes.Buffer + // compress text + w, err := xz.NewWriter(&buf) + if err != nil { + log.Fatalf("xz.NewWriter error %s", err) + } + if _, err := io.WriteString(w, text); err != nil { + log.Fatalf("WriteString error %s", err) + } + if err := w.Close(); err != nil { + log.Fatalf("w.Close error %s", err) + } + fmt.Println(buf.Len(), buf.String()) + // decompress buffer and write output to stdout + r, err := xz.NewReader(&buf) + if err != nil { + log.Fatalf("NewReader error %s", err) + } + if _, err = io.Copy(os.Stdout, r); err != nil { + log.Fatalf("io.Copy error %s", err) + } +} diff --git a/internal/logutil/util.go b/internal/logutil/util.go index 71d3df9db..c9a1d9576 100644 --- a/internal/logutil/util.go +++ b/internal/logutil/util.go @@ -4,13 +4,13 @@ import ( "log/slog" "strings" - "github.com/pubgo/funk/generic" - "github.com/pubgo/funk/log" - "github.com/pubgo/funk/try" + "github.com/pubgo/funk/v2" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/try" ) func HandlerErr(err error) { - if err == nil || generic.IsNil(err) { + if err == nil { return } @@ -24,7 +24,7 @@ func HandleClose(log log.Logger, fn func() error) { } err := fn() - if generic.IsNil(err) { + if funk.IsNil(err) { return } @@ -36,7 +36,7 @@ func LogOrErr(log log.Logger, msg string, fn func() error) { log = log.WithCallerSkip(1) err := try.Try(fn) - if generic.IsNil(err) { + if funk.IsNil(err) { log.Info().Msg(msg) } else { log.Err(err).Msg(msg) @@ -48,7 +48,7 @@ func OkOrFailed(log log.Logger, msg string, fn func() error) { log.Info().Msg(msg) err := try.Try(fn) - if generic.IsNil(err) { + if funk.IsNil(err) { log.Info().Msg(msg + " ok") } else { log.Err(err).Msg(msg + " failed") @@ -56,7 +56,7 @@ func OkOrFailed(log log.Logger, msg string, fn func() error) { } func ErrRecord(logger log.Logger, err error, fn func(evt *log.Event) string) { - if generic.IsNil(err) { + if funk.IsNil(err) { return } diff --git a/internal/middlewares/middleware_accesslog/middleware.go b/internal/middlewares/middleware_accesslog/middleware.go index 1a989ffc3..dbd49b90d 100644 --- a/internal/middlewares/middleware_accesslog/middleware.go +++ b/internal/middlewares/middleware_accesslog/middleware.go @@ -6,17 +6,17 @@ import ( "time" "github.com/gofiber/utils" - "github.com/pubgo/funk/convert" - "github.com/pubgo/funk/errors/errutil" - "github.com/pubgo/funk/generic" - "github.com/pubgo/funk/log" - "github.com/pubgo/funk/proto/errorpb" - "github.com/pubgo/funk/version" - "github.com/pubgo/lava/core/lavacontexts" - "github.com/pubgo/lava/lava" - "github.com/pubgo/lava/pkg/grpcutil" + "github.com/pubgo/funk/v2/buildinfo/version" + "github.com/pubgo/funk/v2/convert" + "github.com/pubgo/funk/v2/errors/errcode" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/proto/errorpb" "github.com/rs/zerolog" "google.golang.org/grpc/codes" + + "github.com/pubgo/lava/v2/core/lavacontexts" + "github.com/pubgo/lava/v2/lava" + "github.com/pubgo/lava/v2/pkg/grpcutil" ) const Name = "accesslog" @@ -66,11 +66,11 @@ func (l LogMiddleware) Middleware(next lava.HandlerFunc) lava.HandlerFunc { // 错误和panic处理 defer func() { - if !generic.IsNil(gErr) { - logOpts := handleLogOption(req.Header().PeekAll("X-Log-Option")) - if logOpts["all"] { + if gErr != nil { + evt.Stringer("req_header", req.Header()) + logOpts := handleLogOption(req.Header()) + if logOpts.EnableAll() { evt.Any("req_body", req.Payload()) - evt.Bytes("req_header", req.Header().Header()) if rsp != nil { evt.Any("rsp_body", rsp.Payload()) evt.Any("rsp_header", rsp.Header()) @@ -92,7 +92,7 @@ func (l LogMiddleware) Middleware(next lava.HandlerFunc) lava.HandlerFunc { // 记录错误日志 var e *zerolog.Event - if generic.IsNil(gErr) { + if gErr == nil { // Record requests with a timeout of 200 milliseconds //if latency > time.Millisecond*200 && !req.Stream() { // e = l.logger.Err(errTimeout).Func(log.WithEvent(evt)) @@ -100,66 +100,48 @@ func (l LogMiddleware) Middleware(next lava.HandlerFunc) lava.HandlerFunc { e = l.logger.Info().Func(log.WithEvent(evt)) //} } else { - //errors.Debug(gErr) + // errors.Debug(gErr) e = l.logger.Err(gErr).Func(log.WithEvent(evt)) - pb := errutil.ParseError(gErr) - { - if pb.Trace == nil { - pb.Trace = new(errorpb.ErrTrace) - } - pb.Trace.Operation = req.Operation() - pb.Trace.Service = req.Service() - pb.Trace.Version = version.Version() + pb := errcode.ParseError(gErr) + if pb.Message == "" { + pb.Message = gErr.Error() } - { - if pb.Msg != nil { - pb.Msg = new(errorpb.ErrMsg) - } - pb.Msg.Msg = gErr.Error() - pb.Msg.Detail = fmt.Sprintf("%#v", gErr) - if pb.Msg.Tags == nil { - pb.Msg.Tags = make(map[string]string) - } + if pb.StatusCode == errorpb.Code_OK { + log.Warn(ctx).Any("code", pb.Code).Msg("grpc response error with status code is 0") } - { - if pb.Code.Message == "" { - pb.Code.Message = gErr.Error() - } - - if pb.Code.StatusCode == errorpb.Code_OK { - log.Warn(ctx).Any("code", pb.Code).Msg("grpc response error with status code is 0") - } - - if pb.Code.Code == 0 { - pb.Code.Code = int32(errutil.GrpcCodeToHTTP(codes.Code(pb.Code.StatusCode))) - pb.Code.StatusCode = errorpb.Code_Internal - } + if pb.Code == 0 { + pb.Code = int32(errcode.GrpcCodeToHTTP(codes.Code(pb.StatusCode))) + pb.StatusCode = errorpb.Code_Internal } - gErr = errutil.ConvertErr2Status(pb).Err() + gErr = errcode.ConvertErr2Status(pb).Err() } e.Msg("record request") }() // 集成logger到context - ctx = log.CreateEventCtx(ctx, log.NewEvent().Str("request_id", reqId).Str("operation", req.Operation())) + ctx = log.CreateFieldsCtx(ctx, log.Fields{"request_id": reqId, "operation": req.Operation()}) return next(ctx, req) } } -func handleLogOption(data [][]byte) (val map[string]bool) { - if len(data) == 0 { - val = map[string]bool{} - return - } - - val = make(map[string]bool, len(data)) +func handleLogOption(header *lava.RequestHeader) *logOption { + data := header.PeekAll("X-Log-Option") + val := make(map[string]bool, len(data)) for i := range data { val[convert.B2S(data[i])] = true } - return val + return &logOption{data: val} +} + +type logOption struct { + data map[string]bool +} + +func (opt logOption) EnableAll() bool { + return opt.data["all"] } diff --git a/internal/middlewares/middleware_metric/middleware.go b/internal/middlewares/middleware_metric/middleware.go index feab56ec1..1245a02fb 100644 --- a/internal/middlewares/middleware_metric/middleware.go +++ b/internal/middlewares/middleware_metric/middleware.go @@ -5,11 +5,11 @@ import ( "strings" "time" - "github.com/pubgo/funk/generic" + "github.com/pubgo/funk/v2" "github.com/uber-go/tally/v4" - "github.com/pubgo/lava/core/metrics" - "github.com/pubgo/lava/lava" + "github.com/pubgo/lava/v2/core/metrics" + "github.com/pubgo/lava/v2/lava" ) // grpc metric @@ -57,7 +57,7 @@ func (m MetricMiddleware) Middleware(next lava.HandlerFunc) lava.HandlerFunc { grpcServerRpcCallTotal(m.m, req.Operation()) defer func() { - if !generic.IsNil(gErr) { + if !funk.IsNil(gErr) { grpcServerRpcErrTotal(m.m, req.Operation()) } diff --git a/internal/middlewares/middleware_recovery/middleware.go b/internal/middlewares/middleware_recovery/middleware.go index 9ed3c2e00..45dcac031 100644 --- a/internal/middlewares/middleware_recovery/middleware.go +++ b/internal/middlewares/middleware_recovery/middleware.go @@ -2,11 +2,11 @@ package middleware_recovery import ( "context" - "runtime/debug" - "github.com/pubgo/funk/errors" + "github.com/pubgo/funk/v2/errors" + "github.com/pubgo/funk/v2/errors/errparser" - "github.com/pubgo/lava/lava" + "github.com/pubgo/lava/v2/lava" ) func New() lava.Middleware { @@ -15,10 +15,7 @@ func New() lava.Middleware { Next: func(next lava.HandlerFunc) lava.HandlerFunc { return func(ctx context.Context, req lava.Request) (rsp lava.Response, gErr error) { defer func() { - if err := errors.Parse(recover()); err != nil { - debug.PrintStack() - gErr = errors.WrapStack(err) - } + gErr = errors.WrapStack(errparser.Parse(recover())) }() return next(ctx, req) diff --git a/internal/middlewares/middleware_service_info/middleware.go b/internal/middlewares/middleware_serviceinfo/middleware.go similarity index 84% rename from internal/middlewares/middleware_service_info/middleware.go rename to internal/middlewares/middleware_serviceinfo/middleware.go index debfa106b..187a58a94 100644 --- a/internal/middlewares/middleware_service_info/middleware.go +++ b/internal/middlewares/middleware_serviceinfo/middleware.go @@ -1,18 +1,19 @@ -package middleware_service_info +package middleware_serviceinfo import ( "context" - "github.com/pubgo/funk/convert" - "github.com/pubgo/funk/running" - "github.com/pubgo/funk/strutil" - "github.com/pubgo/funk/version" - "github.com/pubgo/lava/core/lavacontexts" - "github.com/pubgo/lava/lava" - "github.com/pubgo/lava/pkg/grpcutil" - "github.com/pubgo/lava/pkg/httputil" - "github.com/pubgo/lava/pkg/proto/lavapbv1" + "github.com/pubgo/funk/v2/buildinfo/version" + "github.com/pubgo/funk/v2/convert" + "github.com/pubgo/funk/v2/running" + "github.com/pubgo/funk/v2/strutil" "github.com/rs/xid" + + "github.com/pubgo/lava/v2/core/lavacontexts" + "github.com/pubgo/lava/v2/lava" + "github.com/pubgo/lava/v2/pkg/grpcutil" + "github.com/pubgo/lava/v2/pkg/httputil" + "github.com/pubgo/lava/v2/pkg/proto/lavapbv1" ) func New() lava.Middleware { diff --git a/lava/annotation.go b/lava/annotation.go deleted file mode 100644 index 7da449eee..000000000 --- a/lava/annotation.go +++ /dev/null @@ -1,8 +0,0 @@ -package lava - -// Annotation is used to attach arbitrary metadata to the schema objects -type Annotation interface { - Name() string -} - -type Annotations = []Annotation diff --git a/lava/middleware.go b/lava/middleware.go index de0c0c100..43d827ee9 100644 --- a/lava/middleware.go +++ b/lava/middleware.go @@ -1,19 +1,25 @@ package lava -import "context" +import ( + "context" +) type HandlerFunc func(ctx context.Context, req Request) (Response, error) type Middlewares []Middleware type Middleware interface { - Middleware(next HandlerFunc) HandlerFunc String() string + Middleware(next HandlerFunc) HandlerFunc +} + +func WithMiddleware(name string, next func(next HandlerFunc) HandlerFunc) MiddlewareWrap { + return MiddlewareWrap{Name: name, Next: next} } type MiddlewareWrap struct { - Next func(next HandlerFunc) HandlerFunc Name string + Next func(next HandlerFunc) HandlerFunc } func (m MiddlewareWrap) Middleware(next HandlerFunc) HandlerFunc { @@ -24,16 +30,16 @@ func (m MiddlewareWrap) String() string { return m.Name } -func Chain(m ...Middleware) Middleware { +func Chain(middlewares ...Middleware) Middleware { return MiddlewareWrap{ Name: "chain", Next: func(next HandlerFunc) HandlerFunc { - for i := len(m) - 1; i >= 0; i-- { - if m[i] == nil { + for i := len(middlewares) - 1; i >= 0; i-- { + if middlewares[i] == nil { continue } - next = m[i].Middleware(next) + next = middlewares[i].Middleware(next) } return next }, diff --git a/lava/request.go b/lava/request.go index 3d921709d..0d4073dcb 100644 --- a/lava/request.go +++ b/lava/request.go @@ -4,6 +4,13 @@ import ( "github.com/valyala/fasthttp" ) +type RequestKind = string + +const ( + RequestKindHttp RequestKind = "http" + RequestKindGrpc RequestKind = "grpc" +) + type RequestHeader = fasthttp.RequestHeader // Request is a synchronous request interface @@ -12,26 +19,26 @@ type Request interface { Client() bool // Kind [http|grpc...] - Kind() string + Kind() RequestKind // Stream Indicates whether it's a stream Stream() bool - // Service name requested + // Service name requested, grpc service name Service() string - // Operation requested + // Operation requested, grpc method name Operation() string - // Endpoint requested + // Endpoint requested, http router path Endpoint() string - // ContentType Content type provided + // ContentType Content type provided, application/json, application/grpc ContentType() string // Header of the request Header() *RequestHeader - // Payload is the decoded value - Payload() interface{} + // Payload is the decoded value, []byte or proto message + Payload() any } diff --git a/lava/response.go b/lava/response.go index 17284fa9d..e4cde0454 100644 --- a/lava/response.go +++ b/lava/response.go @@ -6,9 +6,14 @@ import ( type ResponseHeader = fasthttp.ResponseHeader -// Response is the response writer for un encoded messages +// Response is the response writer interface type Response interface { + // Header returns the response header Header() *ResponseHeader - Payload() interface{} + + // Payload returns the response payload, []byte or protobuf message + Payload() any + + // Stream returns true if the response is a stream Stream() bool } diff --git a/lava/router.go b/lava/router.go index a193f0f81..467672abb 100644 --- a/lava/router.go +++ b/lava/router.go @@ -1,25 +1,30 @@ package lava import ( - "context" - "github.com/gofiber/fiber/v2" - "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "google.golang.org/grpc" ) -type ProxyCfg struct { - // Name service name +type GrpcProxyCfg struct { + // Name grpc service name Name string `yaml:"name"` - // Addr service address + + // Addr grpc service address, dns://auth:8080, auth:8080 Addr string `yaml:"addr"` - // Resolver service resolver, default direct + + // Resolver service resolver[direct, k8s, dns, etc...], default direct Resolver string `yaml:"resolver"` } type GrpcProxy interface { GrpcRouter - Proxy() ProxyCfg + Proxy() GrpcProxyCfg +} + +type GrpcHttpRouter interface { + GrpcRouter + Router(router fiber.Router) + Prefix() string } type GrpcRouter interface { @@ -27,14 +32,10 @@ type GrpcRouter interface { ServiceDesc() *grpc.ServiceDesc } -type GrpcGatewayRouter interface { - GrpcRouter - RegisterGateway(ctx context.Context, mux *runtime.ServeMux, conn grpc.ClientConnInterface) error -} - type HttpRouter interface { Middlewares() []Middleware Router(router fiber.Router) + + // Prefix router prefix, required Prefix() string - // Annotation() []Annotation } diff --git a/lava/server.go b/lava/server.go new file mode 100644 index 000000000..1e46ff361 --- /dev/null +++ b/lava/server.go @@ -0,0 +1,19 @@ +package lava + +import ( + "context" + "net" +) + +type Closer interface { + Close(ctx context.Context) error +} + +// Listener provides an interface for starting and stopping the server. +type Listener interface { + Listen(context.Context, net.Listener) error +} + +type Validator interface { + Validate() error +} diff --git a/lava/service.go b/lava/service.go deleted file mode 100644 index 5d071a8dc..000000000 --- a/lava/service.go +++ /dev/null @@ -1,40 +0,0 @@ -package lava - -import ( - "context" - "net" - - "google.golang.org/grpc" -) - -type Init interface { - Init() -} - -type Close interface { - Close() -} - -type Service interface { - Start() - Stop() - Run() -} - -// Server provides an interface for starting and stopping the server. -type Server interface { - Serve(context.Context, net.Listener) error -} - -type Validator interface { - Validate() error -} - -// Initializer ... -type Initializer interface { - Initialize() -} - -type InnerServer struct { - grpc.ClientConnInterface -} diff --git a/main.go b/main.go deleted file mode 100644 index 05d8e1267..000000000 --- a/main.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import ( - "flag" - - "github.com/goyek/goyek/v2" - "github.com/goyek/workflow" - "github.com/goyek/x/boot" - "github.com/pubgo/funk/assert" - "github.com/pubgo/lava/buildtasks" - "github.com/pubgo/lava/core/flags" -) - -func main() { - goyek.Undefine(workflow.PipelineAll) - - workflow.StageTest.SetDeps(append(workflow.StageTest.Deps(), buildtasks.GoLint)) - goyek.SetDefault(goyek.Define(goyek.Task{ - Name: "all", - Usage: "exec all tasks", - Deps: goyek.Deps{ - workflow.StageInit, - workflow.StageBuild, - workflow.StageTest, - }, - })) - - for _, f := range flags.GetFlags() { - assert.Exit(f.Apply(flag.CommandLine)) - } - boot.Main() -} diff --git a/pkg/cmdutil/cmd.go b/pkg/cliutil/cmd.go similarity index 64% rename from pkg/cmdutil/cmd.go rename to pkg/cliutil/cmd.go index a1ce3ab80..86cc2c9ba 100644 --- a/pkg/cmdutil/cmd.go +++ b/pkg/cliutil/cmd.go @@ -1,11 +1,11 @@ -package cmdutil +package cliutil import ( "fmt" "strings" ) -func ExampleFmt(data ...string) string { +func ExampleDesc(data ...string) string { str := "" for i := range data { str += " " + data[i] + "\n" @@ -13,7 +13,7 @@ func ExampleFmt(data ...string) string { return " " + strings.TrimSpace(str) } -func UsageDesc(format string, args ...interface{}) string { +func UsageDesc(format string, args ...any) string { s := fmt.Sprintf(format, args...) return strings.ToUpper(s[0:1]) + s[1:] } diff --git a/pkg/cmdutil/flag.go b/pkg/cliutil/flag.go similarity index 83% rename from pkg/cmdutil/flag.go rename to pkg/cliutil/flag.go index 434ba0507..f0845790d 100644 --- a/pkg/cmdutil/flag.go +++ b/pkg/cliutil/flag.go @@ -1,4 +1,4 @@ -package cmdutil +package cliutil import ( "flag" @@ -25,6 +25,3 @@ func IsHelp() bool { arg := strings.TrimSpace(os.Args[len(os.Args)-1]) return arg == "--help" || arg == "-h" } - -//https://github.com/valyala/fasttemplate -//"text/template" diff --git a/pkg/fasttemplate/template.go b/pkg/fasttemplate/template.go index c4d271e30..c8dc7d5c8 100644 --- a/pkg/fasttemplate/template.go +++ b/pkg/fasttemplate/template.go @@ -5,10 +5,10 @@ import ( "fmt" "io" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/errors" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/errors" - "github.com/pubgo/funk/convert" + "github.com/pubgo/funk/v2/convert" "github.com/valyala/bytebufferpool" ) @@ -71,7 +71,7 @@ func ExecuteFunc(template, startTag, endTag string, w io.Writer, f TagFunc) (int // // This function is optimized for constantly changing templates. // Use Template.Execute for frozen templates. -func Execute(template, startTag, endTag string, w io.Writer, m map[string]interface{}) (int64, error) { +func Execute(template, startTag, endTag string, w io.Writer, m map[string]any) (int64, error) { return ExecuteFunc(template, startTag, endTag, w, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) }) } @@ -87,7 +87,7 @@ func Execute(template, startTag, endTag string, w io.Writer, m map[string]interf // // This function is optimized for constantly changing templates. // Use Template.ExecuteStd for frozen templates. -func ExecuteStd(template, startTag, endTag string, w io.Writer, m map[string]interface{}) (int64, error) { +func ExecuteStd(template, startTag, endTag string, w io.Writer, m map[string]any) (int64, error) { return ExecuteFunc(template, startTag, endTag, w, func(w io.Writer, tag string) (int, error) { return keepUnknownTagFunc(w, startTag, endTag, tag, m) }) } @@ -138,7 +138,7 @@ var byteBufferPool bytebufferpool.Pool // // This function is optimized for constantly changing templates. // Use Template.ExecuteString for frozen templates. -func ExecuteString(template, startTag, endTag string, m map[string]interface{}) string { +func ExecuteString(template, startTag, endTag string, m map[string]any) string { return ExecuteFuncString(template, startTag, endTag, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) }) } @@ -152,7 +152,7 @@ func ExecuteString(template, startTag, endTag string, m map[string]interface{}) // // This function is optimized for constantly changing templates. // Use Template.ExecuteStringStd for frozen templates. -func ExecuteStringStd(template, startTag, endTag string, m map[string]interface{}) string { +func ExecuteStringStd(template, startTag, endTag string, m map[string]any) string { return ExecuteFuncString(template, startTag, endTag, func(w io.Writer, tag string) (int, error) { return keepUnknownTagFunc(w, startTag, endTag, tag, m) }) } @@ -302,7 +302,7 @@ func (t *Template) ExecuteFunc(w io.Writer, f TagFunc) (int64, error) { // - TagFunc - flexible value type // // Returns the number of bytes written to w. -func (t *Template) Execute(w io.Writer, m map[string]interface{}) (int64, error) { +func (t *Template) Execute(w io.Writer, m map[string]any) (int64, error) { return t.ExecuteFunc(w, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) }) } @@ -315,7 +315,7 @@ func (t *Template) Execute(w io.Writer, m map[string]interface{}) (int64, error) // - TagFunc - flexible value type // // Returns the number of bytes written to w. -func (t *Template) ExecuteStd(w io.Writer, m map[string]interface{}) (int64, error) { +func (t *Template) ExecuteStd(w io.Writer, m map[string]any) (int64, error) { return t.ExecuteFunc(w, func(w io.Writer, tag string) (int, error) { return keepUnknownTagFunc(w, t.startTag, t.endTag, tag, m) }) } @@ -364,7 +364,7 @@ func (t *Template) ExecuteFuncStringWithErr(f TagFunc) (string, error) { // // This function is optimized for frozen templates. // Use ExecuteString for constantly changing templates. -func (t *Template) ExecuteString(m map[string]interface{}) string { +func (t *Template) ExecuteString(m map[string]any) string { return t.ExecuteFuncString(func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) }) } @@ -378,11 +378,11 @@ func (t *Template) ExecuteString(m map[string]interface{}) string { // // This function is optimized for frozen templates. // Use ExecuteStringStd for constantly changing templates. -func (t *Template) ExecuteStringStd(m map[string]interface{}) string { +func (t *Template) ExecuteStringStd(m map[string]any) string { return t.ExecuteFuncString(func(w io.Writer, tag string) (int, error) { return keepUnknownTagFunc(w, t.startTag, t.endTag, tag, m) }) } -func stdTagFunc(w io.Writer, tag string, m map[string]interface{}) (int, error) { +func stdTagFunc(w io.Writer, tag string, m map[string]any) (int, error) { v := m[tag] if v == nil { return 0, nil @@ -400,7 +400,7 @@ func stdTagFunc(w io.Writer, tag string, m map[string]interface{}) (int, error) } } -func keepUnknownTagFunc(w io.Writer, startTag, endTag, tag string, m map[string]interface{}) (int, error) { +func keepUnknownTagFunc(w io.Writer, startTag, endTag, tag string, m map[string]any) (int, error) { v, ok := m[tag] if !ok { if _, err := w.Write(convert.S2B(startTag)); err != nil { diff --git a/pkg/fiber_builder/config.go b/pkg/fiber_builder/config.go deleted file mode 100644 index 1d13bee78..000000000 --- a/pkg/fiber_builder/config.go +++ /dev/null @@ -1,47 +0,0 @@ -package fiber_builder - -import ( - "time" - - "github.com/gofiber/fiber/v2" - "github.com/pubgo/funk/generic" - "github.com/pubgo/funk/merge" - "github.com/pubgo/funk/recovery" - "github.com/pubgo/funk/result" -) - -type Config struct { - Prefork bool `yaml:"prefork"` - ServerHeader string `yaml:"server_header"` - StrictRouting bool `yaml:"strict_routing"` - CaseSensitive bool `yaml:"case_sensitive"` - Immutable bool `yaml:"immutable"` - UnescapePath bool `yaml:"unescape_path"` - ETag bool `yaml:"etag"` - BodyLimit int `yaml:"body_limit"` - Concurrency int `yaml:"concurrency"` - Templates struct { - Dir string `yaml:"dir"` - Ext string `yaml:"ext"` - } `yaml:"templates"` - ReadTimeout time.Duration `yaml:"read_timeout"` - WriteTimeout time.Duration `yaml:"write_timeout"` - IdleTimeout time.Duration `yaml:"idle_timeout"` - ReadBufferSize int `yaml:"read_buffer_size"` - WriteBufferSize int `yaml:"write_buffer_size"` - CompressedFileSuffix string `yaml:"compressed_file_suffix"` - ProxyHeader string `yaml:"proxy_header"` - GETOnly bool `yaml:"get_only"` - DisableKeepalive bool `yaml:"disable_keepalive"` - DisableDefaultDate bool `yaml:"disable_default_date"` - DisableDefaultContentType bool `yaml:"disable_default_content_type"` - DisableHeaderNormalizing bool `yaml:"disable_header_normalizing"` - DisableStartupMessage bool `yaml:"disable_startup_message"` - ReduceMemoryUsage bool `yaml:"reduce_memory_usage"` -} - -func (t *Config) Build() (r result.Result[*fiber.App]) { - defer recovery.Result(&r) - fc := merge.Struct(generic.Ptr(fiber.New().Config()), &t).Unwrap() - return r.WithVal(fiber.New(*fc)) -} diff --git a/pkg/fiberbuilder/config.go b/pkg/fiberbuilder/config.go new file mode 100644 index 000000000..b7fbc8c7d --- /dev/null +++ b/pkg/fiberbuilder/config.go @@ -0,0 +1,126 @@ +package fiberbuilder + +import ( + "log/slog" + "time" + + "dario.cat/mergo" + "github.com/gofiber/fiber/v2" + "github.com/pubgo/funk/v2/result" + "github.com/samber/lo" +) + +type Config struct { + Prefork bool `yaml:"-"` + ServerHeader string `yaml:"-"` + CaseSensitive bool `yaml:"-"` + Immutable bool `yaml:"-"` + UnescapePath bool `yaml:"-"` + ProxyHeader string `yaml:"-"` + GETOnly bool `yaml:"-"` + DisableKeepalive bool `yaml:"-"` + DisableDefaultDate bool `yaml:"-"` + DisableDefaultContentType bool `yaml:"-"` + ErrorHandler fiber.ErrorHandler `yaml:"-" json:"-"` + BodyLimit int `yaml:"-"` + Concurrency int `yaml:"-"` + + // StreamRequestBody enables request body streaming, + // and calls the handler sooner when given body is + // larger then the current limit. + StreamRequestBody bool `yaml:"stream_request_body"` + + // Will not pre parse Multipart Form data if set to true. + // + // This option is useful for servers that desire to treat + // multipart form data as a binary blob, or choose when to parse the data. + // + // Server pre parses multipart form data by default. + DisablePreParseMultipartForm bool `yaml:"disable_pre_parse_multipart_form"` + + // Aggressively reduces memory usage at the cost of higher CPU usage + // if set to true. + // + // Try enabling this option only if the server consumes too much memory + // serving mostly idle keep-alive connections. This may reduce memory + // usage by more than 50%. + // + // Default: false + ReduceMemoryUsage bool `yaml:"reduce_memory_usage"` + + // If set to true, c.IP() and c.IPs() will validate IP addresses before returning them. + // Also, c.IP() will return only the first valid IP rather than just the raw header + // WARNING: this has a performance cost associated with it. + // + // Default: false + EnableIPValidation bool `yaml:"enable_ip_validation"` + + // If set to true, will print all routes with their method, path and handler. + // Default: false + EnablePrintRoutes bool `yaml:"enable_print_routes"` + + // EnableSplittingOnParsers splits the query/body/header parameters by comma when it's true. + // For example, you can use it to parse multiple values from a query parameter like this: + // /api?foo=bar,baz == foo[]=bar&foo[]=baz + // + // Optional. Default: false + EnableSplittingOnParsers bool `yaml:"enable_splitting_on_parsers"` + + ETag bool `yaml:"etag"` + ReadTimeout time.Duration `yaml:"read_timeout"` + WriteTimeout time.Duration `yaml:"write_timeout"` + IdleTimeout time.Duration `yaml:"idle_timeout"` + ReadBufferSize int `yaml:"read_buffer_size"` + WriteBufferSize int `yaml:"write_buffer_size"` + CompressedFileSuffix string `yaml:"compressed_file_suffix"` + DisableHeaderNormalizing bool `yaml:"disable_header_normalizing"` + DisableStartupMessage bool `yaml:"disable_startup_message"` +} + +func (t *Config) ToCfg() fiber.Config { + return fiber.Config{ + Prefork: t.Prefork, + ServerHeader: t.ServerHeader, + CaseSensitive: t.CaseSensitive, + Immutable: t.Immutable, + UnescapePath: t.UnescapePath, + ProxyHeader: t.ProxyHeader, + GETOnly: t.GETOnly, + DisableKeepalive: t.DisableKeepalive, + DisableDefaultDate: t.DisableDefaultDate, + DisableDefaultContentType: t.DisableDefaultContentType, + ErrorHandler: t.ErrorHandler, + BodyLimit: t.BodyLimit, + Concurrency: t.Concurrency, + StreamRequestBody: t.StreamRequestBody, + DisablePreParseMultipartForm: t.DisablePreParseMultipartForm, + ReduceMemoryUsage: t.ReduceMemoryUsage, + EnableIPValidation: t.EnableIPValidation, + EnablePrintRoutes: t.EnablePrintRoutes, + EnableSplittingOnParsers: t.EnableSplittingOnParsers, + ETag: t.ETag, + ReadTimeout: t.ReadTimeout, + WriteTimeout: t.WriteTimeout, + IdleTimeout: t.IdleTimeout, + ReadBufferSize: t.ReadBufferSize, + WriteBufferSize: t.WriteBufferSize, + CompressedFileSuffix: t.CompressedFileSuffix, + DisableHeaderNormalizing: t.DisableHeaderNormalizing, + DisableStartupMessage: t.DisableStartupMessage, + } +} + +func (t *Config) Build() (r result.Result[fiber.Config]) { + if t == nil { + return r.WithValue(fiber.New().Config()) + } + + defer result.Recovery(&r) + cfg := fiber.New().Config() + err := mergo.Merge(&cfg, lo.ToPtr(t.ToCfg()), mergo.WithOverride, mergo.WithAppendSlice) + if err != nil { + slog.Error("failed to merge config", "err", err, "source", t, "target", cfg) + return r.WithErrorf("failed to merge config, err:%v", err) + } + return r.WithValue(cfg) +} diff --git a/pkg/gateway/_doc.go b/pkg/gateway/_doc.go index 5f26478fa..5c610c0d5 100644 --- a/pkg/gateway/_doc.go +++ b/pkg/gateway/_doc.go @@ -13,3 +13,14 @@ package gateway // https://github.com/nhooyr/websocket // https://github.com/connectrpc/vanguard-go // https://github.com/emcfarlane/larking + +// https://github.com/GoogleCloudPlatform/esp-v2/blob/master/src/go/configgenerator/routegen/helpers/backend_route.go +// https://github.com/vine-io/vine/blob/e435e77c14082e84c92028ef035d1d7597e8616b/lib/api/router/httprule/runtime.go +// https://github.com/grpc-ecosystem/grpc-gateway/blob/1bf77dd97e2f74c7511a7405ad7c950d36e45894/runtime/mux.go#L313 +// https://github.com/solo-io/gloo +// https://github.com/altipla-consulting/protoc-gen-grpc_browser/tree/master +// https://github.com/emcfarlane/larking/blob/91250e03da0d4670288dbc663aab7481e07c81dc/larking/rules.go#L53 +// https://github.com/connectrpc/vanguard-go +// https://github.com/stackrox/go-grpc-http1 +// https://github.com/flakrimjusufi/grpc-with-rest +// https://github.com/tidwall/match/blob/master/match.go diff --git a/pkg/gateway/aaa.go b/pkg/gateway/aaa.go index 2884f6150..7ff04beca 100644 --- a/pkg/gateway/aaa.go +++ b/pkg/gateway/aaa.go @@ -6,11 +6,12 @@ import ( "net/http" "github.com/gofiber/fiber/v2" - "github.com/pubgo/lava/pkg/gateway/internal/routertree" "google.golang.org/grpc" "google.golang.org/grpc/encoding" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" + + "github.com/pubgo/lava/v2/pkg/gateway/routertree" ) type ( @@ -24,7 +25,7 @@ type ( SetRequestDecoder(protoreflect.FullName, func(ctx *fiber.Ctx, msg proto.Message) error) SetResponseEncoder(protoreflect.FullName, func(ctx *fiber.Ctx, msg proto.Message) error) - RegisterService(sd *grpc.ServiceDesc, ss interface{}) + RegisterService(sd *grpc.ServiceDesc, ss any) GetOperation(operation string) *GrpcMethod Handler(*fiber.Ctx) error @@ -37,7 +38,7 @@ type ( type Codec interface { encoding.Codec // MarshalAppend appends the marshaled form of v to b and returns the result. - MarshalAppend([]byte, interface{}) ([]byte, error) + MarshalAppend([]byte, any) ([]byte, error) } // StreamCodec is used in streaming RPCs where the message boundaries are @@ -62,6 +63,8 @@ type Compressor interface { encoding.Compressor } -type GrpcMethodHandler = func(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) -type GrpcStreamHandler = grpc.StreamHandler -type StreamDirector func(ctx context.Context, fullMethodName string) (context.Context, grpc.ClientConnInterface, error) +type ( + MethodHandler = func(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) + StreamHandler = grpc.StreamHandler + StreamDirector func(ctx context.Context, fullMethodName string) (context.Context, grpc.ClientConnInterface, error) +) diff --git a/pkg/gateway/codec.go b/pkg/gateway/codec.go index 3cec71602..e921cd09a 100644 --- a/pkg/gateway/codec.go +++ b/pkg/gateway/codec.go @@ -42,7 +42,7 @@ type CodecProto struct { proto.MarshalOptions } -func (c CodecProto) Marshal(v interface{}) ([]byte, error) { +func (c CodecProto) Marshal(v any) ([]byte, error) { m, ok := v.(proto.Message) if !ok { return nil, errInvalidType(v) @@ -50,7 +50,7 @@ func (c CodecProto) Marshal(v interface{}) ([]byte, error) { return c.MarshalOptions.Marshal(m) } -func (c CodecProto) MarshalAppend(b []byte, v interface{}) ([]byte, error) { +func (c CodecProto) MarshalAppend(b []byte, v any) ([]byte, error) { m, ok := v.(proto.Message) if !ok { return nil, errInvalidType(v) @@ -58,7 +58,7 @@ func (c CodecProto) MarshalAppend(b []byte, v interface{}) ([]byte, error) { return c.MarshalOptions.MarshalAppend(b, m) } -func (CodecProto) Unmarshal(data []byte, v interface{}) error { +func (CodecProto) Unmarshal(data []byte, v any) error { m, ok := v.(proto.Message) if !ok { return errInvalidType(v) @@ -132,7 +132,7 @@ type CodecJSON struct { protojson.UnmarshalOptions } -func (c CodecJSON) Marshal(v interface{}) ([]byte, error) { +func (c CodecJSON) Marshal(v any) ([]byte, error) { m, ok := v.(proto.Message) if !ok { return nil, errInvalidType(v) @@ -140,7 +140,7 @@ func (c CodecJSON) Marshal(v interface{}) ([]byte, error) { return c.MarshalOptions.Marshal(m) } -func (c CodecJSON) MarshalAppend(b []byte, v interface{}) ([]byte, error) { +func (c CodecJSON) MarshalAppend(b []byte, v any) ([]byte, error) { m, ok := v.(proto.Message) if !ok { return nil, errInvalidType(v) @@ -148,7 +148,7 @@ func (c CodecJSON) MarshalAppend(b []byte, v interface{}) ([]byte, error) { return c.MarshalOptions.MarshalAppend(b, m) } -func (c CodecJSON) Unmarshal(data []byte, v interface{}) error { +func (c CodecJSON) Unmarshal(data []byte, v any) error { m, ok := v.(proto.Message) if !ok { return errInvalidType(v) @@ -217,15 +217,15 @@ func (CodecJSON) Name() string { return "json" } type codecHTTPBody struct{} -func (codecHTTPBody) Marshal(v interface{}) ([]byte, error) { +func (codecHTTPBody) Marshal(v any) ([]byte, error) { panic("not implemented") } -func (codecHTTPBody) MarshalAppend(b []byte, v interface{}) ([]byte, error) { +func (codecHTTPBody) MarshalAppend(b []byte, v any) ([]byte, error) { panic("not implemented") } -func (codecHTTPBody) Unmarshal(data []byte, v interface{}) error { +func (codecHTTPBody) Unmarshal(data []byte, v any) error { panic("not implemented") } diff --git a/pkg/gateway/context.go b/pkg/gateway/context.go index ea16ff73b..c5b9468b9 100644 --- a/pkg/gateway/context.go +++ b/pkg/gateway/context.go @@ -23,11 +23,15 @@ const MetadataPrefix = "grpcgateway-" // HTTP headers in a response handled by grpc-gateway const MetadataTrailerPrefix = "Grpc-Trailer-" -const metadataGrpcTimeout = "Grpc-Timeout" -const metadataHeaderBinarySuffix = "-Bin" +const ( + metadataGrpcTimeout = "Grpc-Timeout" + metadataHeaderBinarySuffix = "-Bin" +) -const xForwardedFor = "X-Forwarded-For" -const xForwardedHost = "X-Forwarded-Host" +const ( + xForwardedFor = "X-Forwarded-For" + xForwardedHost = "X-Forwarded-Host" +) // DefaultContextTimeout is used for gRPC call context.WithTimeout whenever a Grpc-Timeout inbound // header isn't present. If the value is 0 the sent `context` will not have a timeout. @@ -179,7 +183,7 @@ func ServerMetadataFromContext(ctx context.Context) (md ServerMetadata, ok bool) return md, false } md, ok = ctx.Value(serverMetadataKey{}).(ServerMetadata) - return + return md, ok } // ServerTransportStream implements grpc.ServerTransportStream. @@ -270,7 +274,7 @@ func timeoutUnitToDuration(u uint8) (d time.Duration, ok bool) { case 'n': return time.Nanosecond, true default: - return + return d, ok } } diff --git a/pkg/gateway/fieldmask.go b/pkg/gateway/fieldmask.go index 63169af00..ef2214056 100644 --- a/pkg/gateway/fieldmask.go +++ b/pkg/gateway/fieldmask.go @@ -24,7 +24,7 @@ func getFieldByName(fields protoreflect.FieldDescriptors, name string) protorefl // FieldMaskFromRequestBody creates a FieldMask printing all complete paths from the JSON body. func FieldMaskFromRequestBody(r io.Reader, msg proto.Message) (*fieldmask.FieldMask, error) { fm := &fieldmask.FieldMask{} - var root interface{} + var root any if err := json.NewDecoder(r).Decode(&root); err != nil { if errors.Is(err, io.EOF) { @@ -39,7 +39,7 @@ func FieldMaskFromRequestBody(r io.Reader, msg proto.Message) (*fieldmask.FieldM item := queue[0] queue = queue[1:] - m, ok := item.node.(map[string]interface{}) + m, ok := item.node.(map[string]any) switch { case ok: // if the item is an object, then enqueue all of its children @@ -65,7 +65,7 @@ func FieldMaskFromRequestBody(r io.Reader, msg proto.Message) (*fieldmask.FieldM } if isProtobufAnyMessage(fd.Message()) && !fd.IsList() { - _, hasTypeField := v.(map[string]interface{})["@type"] + _, hasTypeField := v.(map[string]any)["@type"] if hasTypeField { queue = append(queue, fieldMaskPathItem{path: k}) continue @@ -122,8 +122,8 @@ func isDynamicProtoMessage(md protoreflect.MessageDescriptor) bool { // the unmarshalled json contained within in. // Returns a slice containing all subpaths with the root at the // passed in name and json value. -func buildPathsBlindly(name string, in interface{}) []string { - m, ok := in.(map[string]interface{}) +func buildPathsBlindly(name string, in any) []string { + m, ok := in.(map[string]any) if !ok { return []string{name} } @@ -134,14 +134,14 @@ func buildPathsBlindly(name string, in interface{}) []string { cur := queue[0] queue = queue[1:] - m, ok := cur.node.(map[string]interface{}) + m, ok := cur.node.(map[string]any) if !ok { // This should never happen since we should always check that we only add // nodes of type map[string]interface{} to the queue. continue } for k, v := range m { - if mi, ok := v.(map[string]interface{}); ok { + if mi, ok := v.(map[string]any); ok { queue = append(queue, fieldMaskPathItem{path: cur.path + "." + k, node: mi}) } else { // This is not a struct, so there are no more levels to descend. @@ -159,7 +159,7 @@ type fieldMaskPathItem struct { path string // a generic decoded json object the current item to inspect for further path extraction - node interface{} + node any // parent message msg protoreflect.Message diff --git a/pkg/gateway/query_params.go b/pkg/gateway/gatewayutils/query_params.go similarity index 96% rename from pkg/gateway/query_params.go rename to pkg/gateway/gatewayutils/query_params.go index fcfd049cb..2a5d3d4e1 100644 --- a/pkg/gateway/query_params.go +++ b/pkg/gateway/gatewayutils/query_params.go @@ -1,4 +1,4 @@ -package gateway +package gatewayutils import ( "bytes" @@ -11,15 +11,13 @@ import ( "strings" "time" - "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" - "github.com/pubgo/funk/errors" + "github.com/pubgo/funk/v2/errors" "google.golang.org/grpc/grpclog" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/types/known/durationpb" - "google.golang.org/protobuf/types/known/fieldmaskpb" fieldmask "google.golang.org/protobuf/types/known/fieldmaskpb" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" @@ -32,12 +30,12 @@ var currentQueryParser QueryParameterParser = &DefaultQueryParser{} // QueryParameterParser defines interface for all query parameter parsers type QueryParameterParser interface { - Parse(msg proto.Message, values url.Values, filter *utilities.DoubleArray) error + Parse(msg proto.Message, values url.Values, filter *DoubleArray) error } // PopulateQueryParameters parses query parameters // into "msg" using current query parser -func PopulateQueryParameters(msg proto.Message, values url.Values, filter *utilities.DoubleArray) error { +func PopulateQueryParameters(msg proto.Message, values url.Values, filter *DoubleArray) error { return errors.WrapCaller(currentQueryParser.Parse(msg, values, filter)) } @@ -49,7 +47,7 @@ type DefaultQueryParser struct{} // Parse populates "values" into "msg". // A value is ignored if its key starts with one of the elements in "filter". -func (*DefaultQueryParser) Parse(msg proto.Message, values url.Values, filter *utilities.DoubleArray) error { +func (*DefaultQueryParser) Parse(msg proto.Message, values url.Values, filter *DoubleArray) error { for key, v := range values { if len(v) == 0 { delete(values, key) @@ -348,7 +346,7 @@ func parseMessage(msgDescriptor protoreflect.MessageDescriptor, value string) (p } msg = &v default: - return protoreflect.Value{}, errors.Format("unsupported message type: %q", string(msgDescriptor.FullName())) + return protoreflect.Value{}, errors.Errorf("unsupported message type: %q", string(msgDescriptor.FullName())) } return protoreflect.ValueOfMessage(msg.ProtoReflect()), nil @@ -442,7 +440,7 @@ func parseParam(fds []protoreflect.FieldDescriptor, raw []byte) (param, error) { enumVal := fd.Enum().Values().ByName(protoreflect.Name(s)) if enumVal == nil { - return param{}, errors.Format("unexpected enum %s", raw) + return param{}, errors.Errorf("unexpected enum %s", raw) } return param{fds: fds, val: protoreflect.ValueOfEnum(enumVal.Number())}, nil @@ -519,17 +517,17 @@ func parseParam(fds []protoreflect.FieldDescriptor, raw []byte) (param, error) { } return param{fds: fds, val: protoreflect.ValueOfMessage(msg.ProtoReflect())}, nil case "FieldMask": - var msg fieldmaskpb.FieldMask + var msg fieldmask.FieldMask if err := protojson.Unmarshal(quote(raw), &msg); err != nil { return param{}, errors.WrapCaller(err) } return param{fds: fds, val: protoreflect.ValueOfMessage(msg.ProtoReflect())}, nil } } - return param{}, errors.Format("unexpected message type %s", name) + return param{}, errors.Errorf("unexpected message type %s", name) default: - return param{}, errors.Format("unknown param type %s", kind) + return param{}, errors.Errorf("unknown param type %s", kind) } } diff --git a/pkg/gateway/gatewayutils/trie.go b/pkg/gateway/gatewayutils/trie.go new file mode 100644 index 000000000..7bbfbd16c --- /dev/null +++ b/pkg/gateway/gatewayutils/trie.go @@ -0,0 +1,174 @@ +package gatewayutils + +import ( + "sort" +) + +// DoubleArray is a Double Array implementation of trie on sequences of strings. +type DoubleArray struct { + // Encoding keeps an encoding from string to int + Encoding map[string]int + // Base is the base array of Double Array + Base []int + // Check is the check array of Double Array + Check []int +} + +// NewDoubleArray builds a DoubleArray from a set of sequences of strings. +func NewDoubleArray(seqs [][]string) *DoubleArray { + da := &DoubleArray{Encoding: make(map[string]int)} + if len(seqs) == 0 { + return da + } + + encoded := registerTokens(da, seqs) + sort.Sort(byLex(encoded)) + + root := node{row: -1, col: -1, left: 0, right: len(encoded)} + addSeqs(da, encoded, 0, root) + + for i := len(da.Base); i > 0; i-- { + if da.Check[i-1] != 0 { + da.Base = da.Base[:i] + da.Check = da.Check[:i] + break + } + } + return da +} + +func registerTokens(da *DoubleArray, seqs [][]string) [][]int { + var result [][]int + for _, seq := range seqs { + encoded := make([]int, 0, len(seq)) + for _, token := range seq { + if _, ok := da.Encoding[token]; !ok { + da.Encoding[token] = len(da.Encoding) + } + encoded = append(encoded, da.Encoding[token]) + } + result = append(result, encoded) + } + for i := range result { + result[i] = append(result[i], len(da.Encoding)) + } + return result +} + +type node struct { + row, col int + left, right int +} + +func (n node) value(seqs [][]int) int { + return seqs[n.row][n.col] +} + +func (n node) children(seqs [][]int) []*node { + var result []*node + lastVal := int(-1) + last := new(node) + for i := n.left; i < n.right; i++ { + if lastVal == seqs[i][n.col+1] { + continue + } + last.right = i + last = &node{ + row: i, + col: n.col + 1, + left: i, + } + result = append(result, last) + } + last.right = n.right + return result +} + +func addSeqs(da *DoubleArray, seqs [][]int, pos int, n node) { + ensureSize(da, pos) + + children := n.children(seqs) + var i int + for i = 1; ; i++ { + ok := func() bool { + for _, child := range children { + code := child.value(seqs) + j := i + code + ensureSize(da, j) + if da.Check[j] != 0 { + return false + } + } + return true + }() + if ok { + break + } + } + da.Base[pos] = i + for _, child := range children { + code := child.value(seqs) + j := i + code + da.Check[j] = pos + 1 + } + terminator := len(da.Encoding) + for _, child := range children { + code := child.value(seqs) + if code == terminator { + continue + } + j := i + code + addSeqs(da, seqs, j, *child) + } +} + +func ensureSize(da *DoubleArray, i int) { + for i >= len(da.Base) { + da.Base = append(da.Base, make([]int, len(da.Base)+1)...) + da.Check = append(da.Check, make([]int, len(da.Check)+1)...) + } +} + +type byLex [][]int + +func (l byLex) Len() int { return len(l) } +func (l byLex) Swap(i, j int) { l[i], l[j] = l[j], l[i] } +func (l byLex) Less(i, j int) bool { + si := l[i] + sj := l[j] + var k int + for k = 0; k < len(si) && k < len(sj); k++ { + if si[k] < sj[k] { + return true + } + if si[k] > sj[k] { + return false + } + } + return k < len(sj) +} + +// HasCommonPrefix determines if any sequence in the DoubleArray is a prefix of the given sequence. +func (da *DoubleArray) HasCommonPrefix(seq []string) bool { + if len(da.Base) == 0 { + return false + } + + var i int + for _, t := range seq { + code, ok := da.Encoding[t] + if !ok { + break + } + j := da.Base[i] + code + if len(da.Check) <= j || da.Check[j] != i+1 { + break + } + i = j + } + j := da.Base[i] + len(da.Encoding) + if len(da.Check) <= j || da.Check[j] != i+1 { + return false + } + return true +} diff --git a/pkg/gateway/gatewayutils/utils.go b/pkg/gateway/gatewayutils/utils.go new file mode 100644 index 000000000..9cc63a449 --- /dev/null +++ b/pkg/gateway/gatewayutils/utils.go @@ -0,0 +1,10 @@ +package gatewayutils + +import "strconv" + +func quote(raw []byte) []byte { + if n := len(raw); n > 0 && (raw[0] != '"' || raw[n-1] != '"') { + raw = strconv.AppendQuote(raw[:0], string(raw)) + } + return raw +} diff --git a/pkg/gateway/code.go b/pkg/gateway/grpccodes.go similarity index 98% rename from pkg/gateway/code.go rename to pkg/gateway/grpccodes.go index 0d256fda0..383908b75 100644 --- a/pkg/gateway/code.go +++ b/pkg/gateway/grpccodes.go @@ -3,7 +3,7 @@ package gateway import ( "net/http" - "github.com/pubgo/funk/log" + "github.com/pubgo/funk/v2/log" "google.golang.org/grpc/codes" ) diff --git a/pkg/gateway/compress.go b/pkg/gateway/internal/compress.go similarity index 98% rename from pkg/gateway/compress.go rename to pkg/gateway/internal/compress.go index bbf847dea..2e5460999 100644 --- a/pkg/gateway/compress.go +++ b/pkg/gateway/internal/compress.go @@ -1,4 +1,4 @@ -package gateway +package internal import ( "compress/gzip" diff --git a/pkg/gateway/internal/routertree/router.go b/pkg/gateway/internal/routertree/router.go deleted file mode 100644 index bb6b7577d..000000000 --- a/pkg/gateway/internal/routertree/router.go +++ /dev/null @@ -1,182 +0,0 @@ -package routertree - -import ( - "fmt" - "strings" - - "github.com/pubgo/funk/errors" - "github.com/pubgo/funk/generic" -) - -var ( - ErrPathNodeNotFound = errors.New("path node not found") - ErrNotFound = errors.New("operation not found") -) - -func NewRouteTree() *RouteTree { - return &RouteTree{nodes: make(map[string]*nodeTree)} -} - -type RouteTree struct { - nodes map[string]*nodeTree -} - -func (r *RouteTree) List() []RouteOperation { - return getOpt(r.nodes) -} - -func (r *RouteTree) Add(method string, path string, operation string, extras map[string]any) error { - var errMsg = func() string { - return fmt.Sprintf("method: %s, path: %s, operation: %s", method, path, operation) - } - - rule, err := parse(path) - if err != nil { - return errors.Wrap(err, errMsg()) - } - - var node = parseToRoute(rule) - if len(node.Paths) == 0 { - return errors.Wrap(fmt.Errorf("path is null"), errMsg()) - } - - var nodes = r.nodes - paths := append(node.Paths, handlerMethod(method)) - for i, n := range paths { - var lastNode = nodes[n] - if lastNode == nil { - lastNode = &nodeTree{nodes: make(map[string]*nodeTree), verbs: make(map[string]*routeTarget)} - nodes[n] = lastNode - } - nodes = lastNode.nodes - - if i == len(paths)-1 { - lastNode.verbs[generic.FromPtr(node.Verb)] = &routeTarget{ - Method: method, - Path: path, - Operation: operation, - extras: extras, - Verb: node.Verb, - Vars: node.Vars, - } - } - } - return nil -} - -func (r *RouteTree) Match(method, url string) (*MatchOperation, error) { - var paths = strings.Split(strings.Trim(strings.TrimSpace(url), "/"), "/") - var lastPath = strings.SplitN(paths[len(paths)-1], ":", 2) - var errMsg = func(tags ...errors.Tag) errors.Tags { - return append(tags, errors.T("method", method), errors.T("url", url)) - } - var verb = "" - - paths[len(paths)-1] = lastPath[0] - if len(lastPath) > 1 { - verb = lastPath[1] - } - - paths = append(paths, handlerMethod(method)) - var getVars = func(vars []*pathVariable, paths []string) []PathFieldVar { - var vv = make([]PathFieldVar, 0, len(vars)) - for _, v := range vars { - pathVar := PathFieldVar{Fields: v.Fields} - if v.end > 0 { - pathVar.Value = strings.Join(paths[v.start:v.end+1], "/") - } else { - pathVar.Value = strings.Join(paths[v.start:], "/") - } - - vv = append(vv, pathVar) - } - return vv - } - var getPath = func(nodes map[string]*nodeTree, names ...string) *nodeTree { - for _, n := range names { - path := nodes[n] - if path != nil { - return path - } - } - return nil - } - - var nodes = r.nodes - for _, n := range paths { - path := getPath(nodes, n, star, doubleStar) - if path == nil { - return nil, errors.WrapFn(ErrPathNodeNotFound, func() errors.Tags { - return errMsg(errors.T("node", n)) - }) - } - - if vv := path.verbs[verb]; vv != nil && vv.Operation != "" && vv.Method == method { - return &MatchOperation{ - Extras: vv.extras, - Method: vv.Method, - Path: vv.Path, - Operation: vv.Operation, - Verb: verb, - Vars: getVars(vv.Vars, paths), - }, nil - } - nodes = path.nodes - } - - return nil, errors.WrapTag(ErrNotFound, errMsg()...) -} - -type RouteOperation struct { - Method string `json:"method,omitempty"` - Path string `json:"path,omitempty"` - Operation string `json:"operation,omitempty"` - Verb string `json:"verb,omitempty"` - Vars []string `json:"vars,omitempty"` - Extras map[string]any `json:"extras"` -} - -type routeTarget struct { - Method string - Path string - Operation string - Verb *string - Vars []*pathVariable - extras map[string]any -} - -type nodeTree struct { - nodes map[string]*nodeTree - verbs map[string]*routeTarget -} - -type MatchOperation struct { - Method string - Path string - Operation string - Verb string - Vars []PathFieldVar - Extras map[string]any -} - -func getOpt(nodes map[string]*nodeTree) []RouteOperation { - var sets []RouteOperation - for _, n := range nodes { - for _, v := range n.verbs { - sets = append(sets, RouteOperation{ - Method: v.Method, - Path: v.Path, - Operation: v.Operation, - Verb: generic.FromPtr(v.Verb), - Vars: generic.Map(v.Vars, func(i int) string { return strings.Join(v.Vars[i].Fields, ".") }), - Extras: v.extras, - }) - } - sets = append(sets, getOpt(n.nodes)...) - } - return sets -} - -func handlerMethod(method string) string { - return fmt.Sprintf("__%s__", strings.ToUpper(method)) -} diff --git a/pkg/gateway/internal/routertree/router_test.go b/pkg/gateway/internal/routertree/router_test.go deleted file mode 100644 index 19d23f4fe..000000000 --- a/pkg/gateway/internal/routertree/router_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package routertree - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestRoute(t *testing.T) { - tree := NewRouteTree() - assert.NoError(t, tree.Add("get", "/user/user/{id}:get1", "get_user", nil)) - assert.NoError(t, tree.Add("post", "/user/user/{id}", "post_user", nil)) - assert.NoError(t, tree.Add("post", "/user/user/{id}/send_mail", "post_mail", nil)) - opt, err := tree.Match("get", "/user/user/1:get1") - assert.NoError(t, err) - assert.NotNil(t, opt) - assert.Equal(t, "get_user", opt.Operation) - assert.Equal(t, "get1", opt.Verb) -} diff --git a/pkg/gateway/internal/routex/_doc.go b/pkg/gateway/internal/routex/_doc.go deleted file mode 100644 index e81ab3808..000000000 --- a/pkg/gateway/internal/routex/_doc.go +++ /dev/null @@ -1,12 +0,0 @@ -package routex - -// https://github.com/GoogleCloudPlatform/esp-v2/blob/master/src/go/configgenerator/routegen/helpers/backend_route.go -// https://github.com/vine-io/vine/blob/e435e77c14082e84c92028ef035d1d7597e8616b/lib/api/router/httprule/runtime.go -// https://github.com/grpc-ecosystem/grpc-gateway/blob/1bf77dd97e2f74c7511a7405ad7c950d36e45894/runtime/mux.go#L313 -// https://github.com/solo-io/gloo -// https://github.com/altipla-consulting/protoc-gen-grpc_browser/tree/master -// https://github.com/emcfarlane/larking/blob/91250e03da0d4670288dbc663aab7481e07c81dc/larking/rules.go#L53 -// https://github.com/connectrpc/vanguard-go -// https://github.com/stackrox/go-grpc-http1 -// https://github.com/flakrimjusufi/grpc-with-rest -// https://github.com/tidwall/match/blob/master/match.go diff --git a/pkg/gateway/internal/routex/path_parser.go b/pkg/gateway/internal/routex/path_parser.go deleted file mode 100644 index d3071d51a..000000000 --- a/pkg/gateway/internal/routex/path_parser.go +++ /dev/null @@ -1,377 +0,0 @@ -// Copyright 2023-2024 Buf Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package routex - -import ( - "fmt" - "net/url" - "strconv" - "strings" - - "github.com/pubgo/funk/errors" -) - -// pathSegments holds the path segments for a HttpMethod. -// The verb is the final segment, if any. Wildcards segments are annotated by -// '*' and '**' path values. Each segment is URL unescaped. -type pathSegments struct { - path []string // segment path values. - verb string // final segment verb, if any. -} - -// String returns the URL HttpPath representation of the segments. -func (s pathSegments) String() string { - var out strings.Builder - for _, value := range s.path { - out.WriteByte('/') - if value != "*" && value != "**" { - value = url.PathEscape(value) - } - out.WriteString(value) - } - if s.verb != "" { - out.WriteByte(':') - out.WriteString(url.PathEscape(s.verb)) - } - return out.String() -} - -// pathVariable holds the HttpPath variables for a HttpMethod. -// The start and end Fields are the start and end HttpPath segments, inclusive-exclusive. -// If the end is -1, the variable is unbounded, representing a '**' wildcard capture. -type pathVariable struct { - FieldPath string // field HttpPath for the variable. - start, end int // start and end HttpPath segments, inclusive-exclusive, -1 for unbounded. -} - -// parsePathTemplate parsers a methods template into HttpPath segments and variables. -// -// The grammar for the HttpPath template is given in the protobuf definition -// in [google/api/http.proto]. -// -// Template = "/" Segments [ Verb ] ; -// Segments = Segment { "/" Segment } ; -// Segment = "*" | "**" | LITERAL | Variable ; -// Variable = "{" FieldPath [ "=" Segments ] "}" ; -// FieldPath = IDENT { "." IDENT } ; -// Verb = ":" LITERAL ; -// -// [google/api/http.proto]: https://github.com/googleapis/googleapis/blob/ecb1cf0a0021267dd452289fc71c75674ae29fe3/google/api/http.proto#L227-L235 -func parsePathTemplate(template string) (pathSegments, []pathVariable, error) { - parser := &pathParser{scan: pathScanner{input: template}} - if err := parser.parseTemplate(); err != nil { - return pathSegments{}, nil, err - } - return parser.segments, parser.variables, nil -} - -// pathParser holds the state for the recursive descent HttpPath template parser. -type pathParser struct { - scan pathScanner // scanner for the input. - seenVars map[string]bool // set of field paths. - seenDoubleStar bool // true if we've seen a double star wildcard. - segments pathSegments // output segments. - variables []pathVariable // output variables. -} - -func (p *pathParser) currentChar() string { - if char := p.scan.current(); char != eof { - return strconv.QuoteRune(char) - } - return "EOF" -} - -func (p *pathParser) errSyntax(msg string) error { - return fmt.Errorf("syntax error at column %v: %s", p.scan.pos, msg) -} - -func (p *pathParser) errUnexpected() error { - return p.errSyntax("unexpected " + p.currentChar()) -} - -func (p *pathParser) errExpected(expected rune) error { - return p.errSyntax("expected " + strconv.QuoteRune(expected) + ", got " + p.currentChar()) -} - -func (p *pathParser) parseTemplate() error { - if !p.scan.consume('/') { - return p.errExpected('/') // empty HttpPath is not allowed. - } - if err := p.parseSegments(); err != nil { - return err - } - switch p.scan.next() { - case ':': - p.scan.discard() - return p.parseVerb() - case eof: - return nil - default: - return p.errUnexpected() - } -} - -func (p *pathParser) parseVerb() error { - literal, err := p.parseLiteral() - if err != nil { - return err - } - p.segments.verb = literal - if !p.scan.consume(eof) { - return p.errUnexpected() - } - return nil -} - -func (p *pathParser) parseSegments() error { - for { - if err := p.parseSegment(); err != nil { - return err - } - if p.scan.next() != '/' { - p.scan.backup() - return nil - } - p.scan.discard() - if p.seenDoubleStar { - return errors.New("double wildcard '**' must be the final HttpPath segment") - } - } -} - -// parseLiteral parses a URL HttpPath segment in URL HttpPath escaped form. -func (p *pathParser) parseLiteral() (string, error) { - literal := p.scan.captureRun(isLiteral) - if literal == "" { - p.scan.next() - return "", errors.WrapCaller(p.errUnexpected()) - } - - unescaped, err := pathUnescape(literal, pathEncodeSingle) - if err != nil { - return "", errors.WrapCaller(p.errSyntax(err.Error())) - } - - return pathEscape(unescaped, pathEncodeSingle), nil -} - -func (p *pathParser) parseSegment() error { - var segment string - switch p.scan.next() { - case '*': - if p.scan.next() == '*' { - p.seenDoubleStar = true - } else { - p.scan.backup() - } - segment = p.scan.capture() - case '{': - p.scan.discard() - return p.parseVariable() - default: - if !isLiteral(p.scan.current()) { - return p.errSyntax("expected HttpPath Value") - } - literal, err := p.parseLiteral() - if err != nil { - return err - } - segment = literal - } - p.segments.path = append(p.segments.path, segment) - return nil -} - -func (p *pathParser) parseFieldPath() (string, error) { - for { - if !isIdentStart(p.scan.next()) { - return "", p.errSyntax("expected identifier") - } - for isIdent(p.scan.next()) { - continue - } - if p.scan.current() != '.' { - p.scan.backup() - return p.scan.capture(), nil - } - } -} - -func (p *pathParser) parseVariable() error { - fieldPath, err := p.parseFieldPath() - if err != nil { - return err - } - if p.seenVars[fieldPath] { - return fmt.Errorf("duplicate variable %q", fieldPath) - } - if p.seenVars == nil { - p.seenVars = make(map[string]bool) - } - p.seenVars[fieldPath] = true - - variable := pathVariable{FieldPath: fieldPath, start: len(p.segments.path)} - - switch p.scan.next() { - case '}': - p.scan.discard() - p.segments.path = append(p.segments.path, "*") // default capture. - case '=': - p.scan.discard() - if err := p.parseSegments(); err != nil { - return err - } - if !p.scan.consume('}') { - return p.errExpected('}') - } - default: - return p.errExpected('}') - } - variable.end = len(p.segments.path) - if p.seenDoubleStar { - variable.end = -1 // double star wildcard. - } - p.variables = append(p.variables, variable) - return nil -} - -const upperhex = "0123456789ABCDEF" - -func ishex(char byte) bool { - switch { - case '0' <= char && char <= '9': - return true - case 'a' <= char && char <= 'f': - return true - case 'A' <= char && char <= 'F': - return true - } - return false -} - -func unhex(char byte) byte { - switch { - case '0' <= char && char <= '9': - return char - '0' - case 'a' <= char && char <= 'f': - return char - 'a' + 10 - case 'A' <= char && char <= 'F': - return char - 'A' + 10 - } - return 0 -} - -// pathEncoding is the encoding used for HttpPath variables. -// Single encoding is used for single segment capture variables, -// while multi encoding is used for multi segment capture variables. -// On multi encoding variables, '/' is not escaped and is preserved -// as '%2F' if encoded in the HttpPath. -// -// See: https://github.com/googleapis/googleapis/blob/1769846666fbeb0f9ece6ad929ddc0d563cccd8d/google/api/http.proto#L249-L264 -type pathEncoding int - -const ( - pathEncodeSingle pathEncoding = iota - pathEncodeMulti -) - -func pathShouldEscape(char byte, _ pathEncoding) bool { - return !isVariable(rune(char)) -} - -func pathIsHexSlash(input string) bool { - if len(input) < 3 { - return false - } - return input[0] == '%' && input[1] == '2' && (input[2] == 'f' || input[2] == 'F') -} - -func pathEscape(input string, mode pathEncoding) string { - // Count the number of characters that possibly escaping. - hexCount := 0 - for i := 0; i < len(input); i++ { - if pathShouldEscape(input[i], mode) { - hexCount++ - } - } - if hexCount == 0 { - return input - } - - var sb strings.Builder - sb.Grow(len(input) + 2*hexCount) - for i := 0; i < len(input); i++ { - switch char := input[i]; { - case char == '%' && mode == pathEncodeMulti && pathIsHexSlash(input[i:]): - sb.WriteString("%2F") - i += 2 - case pathShouldEscape(char, mode): - sb.WriteByte('%') - sb.WriteByte(upperhex[char>>4]) - sb.WriteByte(upperhex[char&15]) - default: - sb.WriteByte(char) - } - } - return sb.String() -} - -func validateHex(input string) error { - if len(input) < 3 || input[0] != '%' || !ishex(input[1]) || !ishex(input[2]) { - if len(input) > 3 { - input = input[:3] - } - return errors.WrapCaller(url.EscapeError(input)) - } - return nil -} - -func pathUnescape(input string, mode pathEncoding) (string, error) { - // Count %, check that they're well-formed. - percentCount := 0 - for i := 0; i < len(input); { - switch input[i] { - case '%': - percentCount++ - if err := validateHex(input[i:]); err != nil { - return "", errors.WrapCaller(err) - } - i += 3 - default: - i++ - } - } - if percentCount == 0 { - return input, nil - } - - var sb strings.Builder - sb.Grow(len(input) - 2*percentCount) - for i := 0; i < len(input); i++ { - switch input[i] { - case '%': - if mode == pathEncodeMulti && pathIsHexSlash(input[i:]) { - // Multi doesn't escape /, so we don't escape. - sb.WriteString("%2F") - } else { - sb.WriteByte(unhex(input[i+1])<<4 | unhex(input[i+2])) - } - i += 2 - default: - sb.WriteByte(input[i]) - } - } - return sb.String(), nil -} diff --git a/pkg/gateway/internal/routex/path_parser_test.go b/pkg/gateway/internal/routex/path_parser_test.go deleted file mode 100644 index d174c03a7..000000000 --- a/pkg/gateway/internal/routex/path_parser_test.go +++ /dev/null @@ -1,313 +0,0 @@ -// Copyright 2023-2024 Buf Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package routex - -import ( - "net/url" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestPath_ParsePathTemplate(t *testing.T) { - t.Parallel() - - testCases := []struct { - tmpl string - wantPath []string - wantVerb string - wantVars []pathVariable - expectedErr string - }{{ - // no error, but lots of encoding for special/reserved characters - tmpl: "/my%2fcool-blog~about%2Cstuff%5Bwat%5D/{var={abc=%2F%2A}/{def=%2F%2A/**}}:baz", - wantPath: []string{"my%2Fcool-blog~about%2Cstuff%5Bwat%5D", "%2F%2A", "%2F%2A", "**"}, - wantVerb: "baz", - wantVars: []pathVariable{ - {FieldPath: "var", start: 1, end: -1}, - {FieldPath: "abc", start: 1, end: 2}, - {FieldPath: "def", start: 2, end: -1}, - }, - }, { - tmpl: "/{1}", - expectedErr: "syntax error at column 3: expected identifier", - }, { - tmpl: "/{field.1}", - expectedErr: "syntax error at column 9: expected identifier", - }, { - tmpl: "/{_}", - wantPath: []string{"*"}, - wantVars: []pathVariable{ - {FieldPath: "_", start: 0, end: 1}, - }, - }, { - tmpl: "/{-}", - expectedErr: "syntax error at column 3: expected identifier", - }, { - tmpl: "/{field-}", - expectedErr: "syntax error at column 8: expected '}', got '-'", - }, { - tmpl: "/foo/bar/baz?abc=def", - expectedErr: "syntax error at column 13: unexpected '?'", // no query string allowed - }, { - tmpl: "/foo/bar/baz buzz", - expectedErr: "syntax error at column 13: unexpected ' '", // no whitespace allowed - }, { - tmpl: "foo/bar/baz", - expectedErr: "syntax error at column 1: expected '/', got 'f'", // must start with slash - }, { - tmpl: "/foo/bar/", - expectedErr: "syntax error at column 9: expected HttpPath Value", // must not end in slash - }, { - tmpl: "/foo/bar:baz/buzz", - expectedErr: "syntax error at column 13: unexpected '/'", // ":baz" Verb can only come at the very end - }, { - tmpl: "/foo/{bar/baz}/buzz", - expectedErr: "syntax error at column 10: expected '}', got '/'", // invalid field HttpPath - }, { - tmpl: "/foo/bar:baz%12xyz%abcde", - wantPath: []string{"foo", "bar"}, - wantVerb: "baz%12xyz%ABcde", - }, { - tmpl: "/{hello}/world", - wantPath: []string{"*", "world"}, - wantVars: []pathVariable{ - {FieldPath: "hello", start: 0, end: 1}, - }, - }, { - tmpl: "/foo/bar%55:baz%1", - expectedErr: "syntax error at column 17: invalid URL escape \"%1\"", - }, { - tmpl: "/foo/bar*", - expectedErr: "syntax error at column 9: unexpected '*'", // wildcard must be entire HttpPath component - }, { - tmpl: "/foo/bar/***", - expectedErr: "syntax error at column 12: unexpected '*'", // no such thing as triple-wildcard - }, { - tmpl: "/foo/**/bar", - expectedErr: "double wildcard '**' must be the final HttpPath segment", // double-wildcard must be at end - }, { - tmpl: "/{a}/{a}", // TODO: allow this? - expectedErr: "duplicate variable \"a\"", - }, { - tmpl: "/f/bar", - wantPath: []string{"f", "bar"}, - }, { - tmpl: "/v1/{Name=shelves/*/books/*}", - wantPath: []string{"v1", "shelves", "*", "books", "*"}, - wantVars: []pathVariable{ - {FieldPath: "Name", start: 1, end: 5}, - }, - }, { - tmpl: "/v1/{parent=shelves/*}/books", - wantPath: []string{"v1", "shelves", "*", "books"}, - wantVars: []pathVariable{ - {FieldPath: "parent", start: 1, end: 3}, - }, - }, { - tmpl: "/v1/{book.Name=shelves/*/books/*}", - wantPath: []string{"v1", "shelves", "*", "books", "*"}, - wantVars: []pathVariable{ - {FieldPath: "book.Name", start: 1, end: 5}, - }, - }, { - tmpl: "/v1:watch", - wantPath: []string{"v1"}, - wantVerb: "watch", - }, { - tmpl: "/v3/events:clear", - wantPath: []string{"v3", "events"}, - wantVerb: "clear", - }, { - tmpl: "/v3/{Name=events/*}:cancel", - wantPath: []string{"v3", "events", "*"}, - wantVerb: "cancel", - wantVars: []pathVariable{ - {FieldPath: "Name", start: 1, end: 3}, - }, - }, { - tmpl: "/foo/bar/baz/buzz", - wantPath: []string{"foo", "bar", "baz", "buzz"}, - }, { - tmpl: "/foo/bar/{Name}", - wantPath: []string{"foo", "bar", "*"}, - wantVars: []pathVariable{ - {FieldPath: "Name", start: 2, end: 3}, - }, - }, { - tmpl: "/foo/bar/{Name}/baz/{child}", - wantPath: []string{"foo", "bar", "*", "baz", "*"}, - wantVars: []pathVariable{ - {FieldPath: "Name", start: 2, end: 3}, - {FieldPath: "child", start: 4, end: 5}, - }, - }, { - tmpl: "/foo/bar/{Name}/baz/{child.id}/buzz/{child.thing.id}", - wantPath: []string{"foo", "bar", "*", "baz", "*", "buzz", "*"}, - wantVars: []pathVariable{ - {FieldPath: "Name", start: 2, end: 3}, - {FieldPath: "child.id", start: 4, end: 5}, - {FieldPath: "child.thing.id", start: 6, end: 7}, - }, - }, { - tmpl: "/foo/bar/*/{thing.id}/{cat=**}", - wantPath: []string{"foo", "bar", "*", "*", "**"}, - wantVars: []pathVariable{ - {FieldPath: "thing.id", start: 3, end: 4}, - {FieldPath: "cat", start: 4, end: -1}, - }, - }, { - tmpl: "/foo/bar/*/{thing.id}/{cat=**}:do", - wantPath: []string{"foo", "bar", "*", "*", "**"}, - wantVerb: "do", - wantVars: []pathVariable{ - {FieldPath: "thing.id", start: 3, end: 4}, - {FieldPath: "cat", start: 4, end: -1}, - }, - }, { - tmpl: "/foo/bar/*/{thing.id}/{cat=**}:cancel", - wantPath: []string{"foo", "bar", "*", "*", "**"}, - wantVerb: "cancel", - wantVars: []pathVariable{ - {FieldPath: "thing.id", start: 3, end: 4}, - {FieldPath: "cat", start: 4, end: -1}, - }, - }, { - tmpl: "/foo/bob/{book_id={author}/{isbn}/*}/details", - wantPath: []string{"foo", "bob", "*", "*", "*", "details"}, - wantVars: []pathVariable{ - {FieldPath: "book_id", start: 2, end: 5}, - {FieldPath: "author", start: 2, end: 3}, - {FieldPath: "isbn", start: 3, end: 4}, - }, - }, { - tmpl: "/foo/blah/{longest_var={long_var.a={medium.a={short.aa}/*/{short.ab}/foo}/*}/{long_var.b={medium.b={short.ba}/*/{short.bb}/foo}/{last=**}}}:details", - wantPath: []string{ - "foo", "blah", - "*", // 2 logest_var, long_var.a, medium.a, short.aa - "*", // 3 - "*", // 4 short.ab - "foo", // 5 - "*", // 6 - "*", // 7 long_var.b, medium.b, short.ba - "*", // 8 - "*", // 9 short.bb - "foo", // 10 - "**", // 11 last - }, - wantVerb: "details", - wantVars: []pathVariable{ - {FieldPath: "longest_var", start: 2, end: -1}, - {FieldPath: "long_var.a", start: 2, end: 7}, - {FieldPath: "medium.a", start: 2, end: 6}, - {FieldPath: "short.aa", start: 2, end: 3}, - {FieldPath: "short.ab", start: 4, end: 5}, - {FieldPath: "long_var.b", start: 7, end: -1}, - {FieldPath: "medium.b", start: 7, end: 11}, - {FieldPath: "short.ba", start: 7, end: 8}, - {FieldPath: "short.bb", start: 9, end: 10}, - {FieldPath: "last", start: 11, end: -1}, - }, - }, { - tmpl: "/foo%2Fbar/%2A/%2A%2a/{starstar=%2A%2a/**}:%2c", - wantPath: []string{"foo%2Fbar", "%2A", "%2A%2A", "%2A%2A", "**"}, - wantVerb: "%2C", - wantVars: []pathVariable{ - {FieldPath: "starstar", start: 3, end: -1}, - }, - }} - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.tmpl, func(t *testing.T) { - t.Parallel() - segments, variables, err := parsePathTemplate(testCase.tmpl) - if testCase.expectedErr != "" { - assert.ErrorContains(t, err, testCase.expectedErr) - return - } - t.Log(segments) - require.NoError(t, err) - assert.ElementsMatch(t, testCase.wantPath, segments.path, "HttpPath mismatch") - assert.Equal(t, testCase.wantVerb, segments.verb, "Verb mismatch") - assert.ElementsMatch(t, testCase.wantVars, variables, "variables mismatch") - }) - } -} - -func TestPath_SafeLiterals(t *testing.T) { - t.Parallel() - literalvalues := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._%25~" - for _, r := range literalvalues { - if !isLiteral(r) { - t.Errorf("isLiteral(%q) = false, want true", r) - } - } - unescaped, err := url.PathUnescape(literalvalues) - require.NoError(t, err) - escaped := url.PathEscape(unescaped) - assert.Equal(t, literalvalues, escaped) -} - -func TestPath_Escaping(t *testing.T) { - t.Parallel() - testCases := []struct { - input string - mode pathEncoding - want string - wantEscaped string - wantErr string - }{{ - input: "foo", - mode: pathEncodeSingle, - want: "foo", - }, { - input: "foo%2Fbar", - mode: pathEncodeSingle, - want: "foo/bar", - }, { - input: "foo%252Fbar", - mode: pathEncodeSingle, - want: "foo%2Fbar", - }, { - input: "foo%2Fbar", - mode: pathEncodeMulti, - want: "foo%2Fbar", - }, { - input: "foo%2fbar", - mode: pathEncodeMulti, - want: "foo%2Fbar", - wantEscaped: "foo%2Fbar", - }} - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.input, func(t *testing.T) { - t.Parallel() - dec, err := pathUnescape(testCase.input, testCase.mode) - if err != nil { - assert.EqualError(t, err, testCase.wantErr) - return - } - require.NoError(t, err) - assert.Equal(t, testCase.want, dec) - enc := pathEscape(dec, testCase.mode) - if testCase.wantEscaped != "" { - assert.Equal(t, testCase.wantEscaped, enc) - } else { - assert.Equal(t, testCase.input, enc) - } - }) - } -} diff --git a/pkg/gateway/internal/routex/path_scanner.go b/pkg/gateway/internal/routex/path_scanner.go deleted file mode 100644 index 6cd02f5eb..000000000 --- a/pkg/gateway/internal/routex/path_scanner.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2023-2024 Buf Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package routex - -import ( - "unicode/utf8" -) - -// pathScanner holds the state of the scanner. -type pathScanner struct { - input string // the string being scanned. - start int // start position of this token. - pos int // current position in the input. - width int // width of last rune read from input. -} - -const eof = -1 - -func (s *pathScanner) next() rune { - var char rune - if s.pos >= len(s.input) { - s.width = 0 - return eof - } - char, s.width = utf8.DecodeRuneInString(s.input[s.pos:]) - s.pos += s.width - return char -} - -func (s *pathScanner) current() rune { - var char rune - if s.width > 0 { - char, _ = utf8.DecodeRuneInString(s.input[s.pos-s.width:]) - } else if s.pos > len(s.input) { - char = eof - } - return char -} - -func (s *pathScanner) backup() { - s.pos -= s.width - s.width = 0 -} - -func (s *pathScanner) captureRun(isValid func(r rune) bool) string { - for isValid(s.next()) { - continue - } - s.backup() - return s.capture() -} - -func (s *pathScanner) consume(expected rune) bool { - if s.next() == expected { - s.discard() - return true - } - return false -} - -func (s *pathScanner) discard() { - s.start = s.pos -} - -func (s *pathScanner) capture() string { - value := s.input[s.start:s.pos] - s.discard() - return value -} - -func isIdentStart(char rune) bool { - return (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || char == '_' -} - -func isDigit(char rune) bool { - return char >= '0' && char <= '9' -} - -func isIdent(char rune) bool { - return isIdentStart(char) || isDigit(char) -} - -func isFieldPath(char rune) bool { - return isIdent(char) || char == '.' -} - -func isLiteral(char rune) bool { - // Allow [-_.~0-9a-zA-Z] and % for escaped characters. - return isVariable(char) || char == '%' -} - -// isVariable is used to determine if a character is allowed in a single variable segment. -// -// See: https://github.com/googleapis/googleapis/blob/master/google/api/http.proto#L251C34-L251C38 -func isVariable(char rune) bool { - // Allow [-_.~0-9a-zA-Z]. - return isFieldPath(char) || char == '-' || char == '~' -} diff --git a/pkg/gateway/internal/routex/router.go b/pkg/gateway/internal/routex/router.go deleted file mode 100644 index 39c26685f..000000000 --- a/pkg/gateway/internal/routex/router.go +++ /dev/null @@ -1,464 +0,0 @@ -// Copyright 2023-2024 Buf Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package routex - -import ( - "fmt" - "net/http" - "strings" - - "github.com/pubgo/funk/errors" - "github.com/pubgo/funk/log" - - "google.golang.org/genproto/googleapis/api/annotations" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoreflect" -) - -func NewRouteTrie() *RouteTrie { - return new(RouteTrie) -} - -// RouteTrie is a prefix trie of valid REST URI paths to route targets. -// It supports evaluation of variables as the HttpPath is matched, for -// interpolating parts of the URI HttpPath into an RPC request field. The -// map is keyed by the HttpPath component that corresponds to a given node. -type RouteTrie struct { - // Child nodes, keyed by the next segment in the HttpPath. - children map[string]*RouteTrie - // Final node in the HttpPath has a map of verbs to methods. - // Verbs are either an empty string or a single literal. - verbs map[string]RouteMethods -} - -func (t *RouteTrie) GetRouteMethods() []*RouteTarget { - var rr []*RouteTarget - for _, verbs := range t.verbs { - for _, target := range verbs { - rr = append(rr, target) - } - } - - for _, child := range t.children { - rr = append(rr, child.GetRouteMethods()...) - } - - return rr -} - -func (t *RouteTrie) Match(uriPath, httpMethod string) (*RouteTarget, []RouteTargetVarMatch, RouteMethods) { - return t.match(uriPath, httpMethod) -} - -func (t *RouteTrie) AddRoute(grpcMethod string, mth protoreflect.MethodDescriptor) error { - httpRule := getExtensionHTTP(mth) - return t.addRoute(&MethodConfig{Descriptor: mth, MethodPath: grpcMethod}, httpRule) -} - -// addRoute adds a target to the router for the given HttpMethod and the given -// HTTP rule. Only the rule itself is added. If the rule indicates additional -// bindings, they are ignored. To add routes for all bindings, callers must -// invoke this HttpMethod for each rule. -func (t *RouteTrie) addRoute(config *MethodConfig, httpRule *annotations.HttpRule) error { - if httpRule == nil { - return nil - } - - var method, template string - switch pattern := httpRule.GetPattern().(type) { - case *annotations.HttpRule_Get: - method, template = http.MethodGet, pattern.Get - case *annotations.HttpRule_Put: - method, template = http.MethodPut, pattern.Put - case *annotations.HttpRule_Post: - method, template = http.MethodPost, pattern.Post - case *annotations.HttpRule_Delete: - method, template = http.MethodDelete, pattern.Delete - case *annotations.HttpRule_Patch: - method, template = http.MethodPatch, pattern.Patch - case *annotations.HttpRule_Custom: - method, template = pattern.Custom.GetKind(), pattern.Custom.GetPath() - default: - return fmt.Errorf("invalid type of pattern for HTTP httpRule: %T", pattern) - } - - if method == "" { - return errors.New("invalid HTTP httpRule: HttpMethod is blank") - } - if template == "" { - return errors.New("invalid HTTP httpRule: HttpPath template is blank") - } - segments, variables, err := parsePathTemplate(template) - if err != nil { - return err - } - - target, err := makeTarget(config, method, httpRule.GetBody(), httpRule.GetResponseBody(), segments, variables) - if err != nil { - return err - } - if err := t.insert(method, target, segments); err != nil { - return err - } - - for i, rule := range httpRule.GetAdditionalBindings() { - if len(rule.GetAdditionalBindings()) > 0 { - return fmt.Errorf("nested additional bindings are not supported (HttpMethod %s)", config.MethodPath) - } - if err = t.addRoute(config, rule); err != nil { - return fmt.Errorf("failed to add REST route (add'l binding #%d) for HttpMethod %s: %w", i+1, config.MethodPath, err) - } - } - - return nil -} - -func (t *RouteTrie) insertChild(segment string) *RouteTrie { - child := t.children[segment] - if child == nil { - if t.children == nil { - t.children = make(map[string]*RouteTrie, 1) - } - child = &RouteTrie{} - t.children[segment] = child - } - return child -} - -func (t *RouteTrie) insertVerb(verb string) RouteMethods { - methods := t.verbs[verb] - if methods == nil { - if t.verbs == nil { - t.verbs = make(map[string]RouteMethods, 1) - } - methods = make(RouteMethods, 1) - t.verbs[verb] = methods - } - return methods -} - -// insert the target into the trie using the given HttpMethod and segment HttpPath. -// The HttpPath is followed until the final segment is reached. -func (t *RouteTrie) insert(method string, target *RouteTarget, segments pathSegments) error { - cursor := t - for _, segment := range segments.path { - cursor = cursor.insertChild(segment) - } - if existing := cursor.verbs[segments.verb][method]; existing != nil { - return alreadyExistsError{ - existing: existing, - pathPattern: segments.String(), - method: method, - } - } - cursor.insertVerb(segments.verb)[method] = target - return nil -} - -// match finds a route for the given request. If a match is found, the associated target and a map -// of matched variable values is returned. -func (t *RouteTrie) match(uriPath, httpMethod string) (*RouteTarget, []RouteTargetVarMatch, RouteMethods) { - if len(uriPath) == 0 || uriPath[0] != '/' || uriPath[len(uriPath)-1] == ':' { - // Must start with "/" or if it ends with ":" it won't match - return nil, nil, nil - } - uriPath = uriPath[1:] // skip the leading slash - - path := strings.Split(uriPath, "/") - var verb string - if len(path) > 0 { - lastElement := path[len(path)-1] - if pos := strings.IndexRune(lastElement, ':'); pos >= 0 { - path[len(path)-1] = lastElement[:pos] - verb = lastElement[pos+1:] - } - } - target, methods := t.findTarget(path, verb, httpMethod) - if target == nil { - return nil, nil, methods - } - - vars, err := computeVarValues(path, target) - if err != nil { - log.Err(err).Msg("failed to compute var values") - return nil, nil, nil - } - - return target, vars, nil -} - -// findTarget finds the target for the given HttpPath components, Verb, and HttpMethod. -// The HttpMethod either returns a target OR the set of methods for the given HttpPath -// and Verb. If the target is non-nil, the request was matched. If the target -// is nil but methods are non-nil, the HttpPath and Verb matched a route, but not -// the HttpMethod. This can be used to send back a well-formed "Allow" response -// header. If both are nil, the HttpPath and Verb did not match. -func (t *RouteTrie) findTarget(path []string, verb, method string) (*RouteTarget, RouteMethods) { - if len(path) == 0 { - return t.getTarget(verb, method) - } - current := path[0] - path = path[1:] - - if child := t.children[current]; child != nil { - target, methods := child.findTarget(path, verb, method) - if target != nil || methods != nil { - return target, methods - } - } - - if childAst := t.children["*"]; childAst != nil { - target, methods := childAst.findTarget(path, verb, method) - if target != nil || methods != nil { - return target, methods - } - } - - // Double-asterisk must be the last element in pattern. - // So it consumes all remaining HttpPath elements. - if childDblAst := t.children["**"]; childDblAst != nil { - return childDblAst.findTarget(nil, verb, method) - } - return nil, nil -} - -// getTarget gets the target for the given Verb and HttpMethod from the -// node trie. It is like findTarget, except that it does not use a -// HttpPath to first descend into a sub-trie. -func (t *RouteTrie) getTarget(verb, method string) (*RouteTarget, RouteMethods) { - methods := t.verbs[verb] - if target := methods[method]; target != nil { - return target, methods - } - // See if a wildcard HttpMethod was used - if target := methods["*"]; target != nil { - return target, methods - } - return nil, methods -} - -type RouteMethods map[string]*RouteTarget - -type RouteTarget struct { - GrpcMethodName string - HttpMethod string - HttpPath []string - Verb string - - RequestBodyFieldPath string - RequestBodyFields []protoreflect.FieldDescriptor - ResponseBodyFieldPath string - ResponseBodyFields []protoreflect.FieldDescriptor - Vars []RouteTargetVar -} - -func makeTarget(config *MethodConfig, method, requestBody, responseBody string, segments pathSegments, variables []pathVariable) (*RouteTarget, error) { - requestBodyFields, err := resolvePathToDescriptors(config.Descriptor.Input(), requestBody) - if err != nil { - return nil, err - } - if len(requestBodyFields) > 1 { - return nil, fmt.Errorf( - "unexpected request body HttpPath %q: must be a single field", - requestBody, - ) - } - - responseBodyFields, err := resolvePathToDescriptors(config.Descriptor.Output(), responseBody) - if err != nil { - return nil, err - } - if len(responseBodyFields) > 1 { - return nil, fmt.Errorf( - "unexpected response body HttpPath %q: must be a single field", - requestBody, - ) - } - - routeTargetVars := make([]RouteTargetVar, len(variables)) - for i, variable := range variables { - fields, err := resolvePathToDescriptors(config.Descriptor.Input(), variable.FieldPath) - if err != nil { - return nil, err - } - if last := fields[len(fields)-1]; last.IsList() { - return nil, fmt.Errorf( - "unexpected HttpPath variable %q: cannot be a repeated field", - variable.FieldPath, - ) - } - - routeTargetVars[i] = RouteTargetVar{ - pathVariable: variable, - fields: fields, - } - } - return &RouteTarget{ - GrpcMethodName: config.MethodPath, - HttpMethod: method, - HttpPath: segments.path, - Verb: segments.verb, - RequestBodyFieldPath: requestBody, - RequestBodyFields: requestBodyFields, - ResponseBodyFieldPath: responseBody, - ResponseBodyFields: responseBodyFields, - Vars: routeTargetVars, - }, nil -} - -type RouteTargetVar struct { - pathVariable - - fields []protoreflect.FieldDescriptor -} - -func (v RouteTargetVar) size() int { - if v.end == -1 { - return -1 - } - return v.end - v.start -} - -func (v RouteTargetVar) index(segments []string) []string { - start, end := v.start, v.end - if end == -1 { - if start >= len(segments) { - return nil - } - return segments[start:] - } - return segments[start:end] -} - -func (v RouteTargetVar) capture(segments []string) (string, error) { - parts := v.index(segments) - mode := pathEncodeSingle - if v.end == -1 || v.start-v.end > 1 { - mode = pathEncodeMulti - } - - var sb strings.Builder - for i, part := range parts { - val, err := pathUnescape(part, mode) - if err != nil { - return "", err - } - if i > 0 { - sb.WriteByte('/') - } - sb.WriteString(val) - } - return sb.String(), nil -} - -type RouteTargetVarMatch struct { - Fields []protoreflect.FieldDescriptor - Value string - Name string -} - -func computeVarValues(path []string, target *RouteTarget) ([]RouteTargetVarMatch, error) { - if len(target.Vars) == 0 { - return nil, nil - } - vars := make([]RouteTargetVarMatch, len(target.Vars)) - for i, varDef := range target.Vars { - val, err := varDef.capture(path) - if err != nil { - return nil, err - } - vars[i].Fields = varDef.fields - vars[i].Value = val - vars[i].Name = varDef.FieldPath - } - return vars, nil -} - -// resolvePathToDescriptors translates the given HttpPath string, in the form of "ident.ident.ident", -// into a HttpPath of FieldDescriptors, relative to the given msg. -func resolvePathToDescriptors(msg protoreflect.MessageDescriptor, path string) ([]protoreflect.FieldDescriptor, error) { - if path == "" { - return nil, nil - } - if path == "*" { - // non-nil, empty slice means use the whole thing - return []protoreflect.FieldDescriptor{}, nil - } - - fields := msg.Fields() - parts := strings.Split(path, ".") - result := make([]protoreflect.FieldDescriptor, len(parts)) - for i, part := range parts { - field := fields.ByName(protoreflect.Name(part)) - if field == nil { - return nil, fmt.Errorf("in field HttpPath %q: element %q does not correspond to any field of type %s", - path, part, msg.FullName()) - } - result[i] = field - if i == len(parts)-1 { - break - } - if field.Cardinality() == protoreflect.Repeated { - return nil, fmt.Errorf("in field HttpPath %q: field %q of type %s should not be a list or map", - path, part, msg.FullName()) - } - msg = field.Message() - if msg == nil { - return nil, fmt.Errorf("in field HttpPath %q: field %q of type %s should be a message but is instead %s", - path, part, msg.FullName(), field.Kind()) - } - fields = msg.Fields() - } - return result, nil -} - -// resolveFieldDescriptorsToPath translates the given HttpPath of FieldDescriptors into a string -// of the form "ident.ident.ident". -func resolveFieldDescriptorsToPath(fields []protoreflect.FieldDescriptor) string { - if len(fields) == 0 { - return "" - } - parts := make([]string, len(fields)) - for i, field := range fields { - parts[i] = string(field.Name()) - } - return strings.Join(parts, ".") -} - -type alreadyExistsError struct { - existing *RouteTarget - pathPattern, method string -} - -func (a alreadyExistsError) Error() string { - return fmt.Sprintf("target for %s, HttpMethod %s already exists: %s", a.pathPattern, a.method, a.existing.GrpcMethodName) -} - -// getExtensionHTTP -func getExtensionHTTP(m protoreflect.MethodDescriptor) *annotations.HttpRule { - if m == nil { - return nil - } - - if m.Options() == nil { - return nil - } - - ext, ok := proto.GetExtension(m.Options(), annotations.E_Http).(*annotations.HttpRule) - if ok { - return ext - } - return nil -} diff --git a/pkg/gateway/internal/routex/router_test.go b/pkg/gateway/internal/routex/router_test.go deleted file mode 100644 index 41aac5c71..000000000 --- a/pkg/gateway/internal/routex/router_test.go +++ /dev/null @@ -1,332 +0,0 @@ -// Copyright 2023-2024 Buf Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package routex - -import ( - "fmt" - "net/http" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/reflect/protoreflect" -) - -func TestRouteTrie_Insert(t *testing.T) { - t.Parallel() - _ = initTrie(t) -} - -func TestRouteTrie_FindTarget(t *testing.T) { - t.Parallel() - testCases := []struct { - path string - expectedPath string // if blank, HttpPath is expected to NOT match - expectedVars map[string]string - }{ - { - path: "/bob/lob/law", - }, - { - path: "/foo/bar/baz", - expectedPath: "/foo/bar/{Name}", - expectedVars: map[string]string{"Name": "baz"}, - }, - { - path: "/foo/bob/lob/law", - }, - { - path: "/foo/bar/baz/buzz", - expectedPath: "/foo/bar/baz/buzz", - }, - { - path: "/foo/bar/baz/baz/buzz", - expectedPath: "/foo/bar/{Name}/baz/{child}", - expectedVars: map[string]string{"Name": "baz", "child": "buzz"}, - }, - { - path: "/foo/bar/1/baz/2/buzz/3", - expectedPath: "/foo/bar/{Name}/baz/{child.id}/buzz/{child.thing.id}", - expectedVars: map[string]string{"Name": "1", "child.id": "2", "child.thing.id": "3"}, - }, - { - path: "/foo/bar/baz/123", - }, - { - path: "/foo/bar/baz/123/buzz", - expectedPath: "/foo/bar/*/{thing.id}/{cat=**}", - expectedVars: map[string]string{"thing.id": "123", "cat": "buzz"}, - }, - { - path: "/foo/bar/baz/123/buzz/buzz", - expectedPath: "/foo/bar/*/{thing.id}/{cat=**}", - expectedVars: map[string]string{"thing.id": "123", "cat": "buzz/buzz"}, - }, - { - path: "/foo/bar/baz/123/buzz/buzz:do", - expectedPath: "/foo/bar/*/{thing.id}/{cat=**}:do", - expectedVars: map[string]string{"thing.id": "123", "cat": "buzz/buzz"}, - }, - { - path: "/foo/bar/baz/123/fizz/buzz/frob/nitz:do", - expectedPath: "/foo/bar/*/{thing.id}/{cat=**}:do", - expectedVars: map[string]string{"thing.id": "123", "cat": "fizz/buzz/frob/nitz"}, - }, - { - path: "/foo/bar/baz/123/buzz/buzz:cancel", - expectedPath: "/foo/bar/*/{thing.id}/{cat=**}:cancel", - expectedVars: map[string]string{"thing.id": "123", "cat": "buzz/buzz"}, - }, - { - path: "foo/bar/baz/123/buzz/buzz:blah", - }, - { - path: "/foo/bob/bar/baz/123/details", - expectedPath: "/foo/bob/{book_id={author}/{isbn}/*}/details", - expectedVars: map[string]string{"book_id": "bar/baz/123", "author": "bar", "isbn": "baz"}, - }, - { - path: "/foo/bob/bar/baz/123/details:do", - }, - { - path: "/foo/blah/A/B/C/foo/D/E/F/G/foo/H/I/J/K/L/M:details", - expectedPath: "/foo/blah/{longest_var={long_var.a={medium.a={short.aa}/*/{short.ab}/foo}/*}/{long_var.b={medium.b={short.ba}/*/{short.bb}/foo}/{last=**}}}:details", - expectedVars: map[string]string{ - "longest_var": "A/B/C/foo/D/E/F/G/foo/H/I/J/K/L/M", - "long_var.a": "A/B/C/foo/D", - "medium.a": "A/B/C/foo", - "short.aa": "A", - "short.ab": "C", - "long_var.b": "E/F/G/foo/H/I/J/K/L/M", - "medium.b": "E/F/G/foo", - "short.ba": "E", - "short.bb": "G", - "last": "H/I/J/K/L/M", - }, - }, - { - // No trailing slash in the HttpPath, so this should not match. - path: "/trailing:slash", - }, - { - // Trailing slash in the HttpPath, so this should match. - path: "/trailing/:slash", - expectedPath: "/trailing/**:slash", - }, - { - // Trailing Verb, should not match. - path: "/Verb:", - }, - { - // No trailing Verb, should match. - path: "/Verb", - expectedPath: "/Verb", - }, - { - // Var capture use HttpPath unescaping. - path: "/foo/bar/baz/%2f/%2A/%2f", - expectedPath: "/foo/bar/*/{thing.id}/{cat=**}", - expectedVars: map[string]string{"thing.id": "/", "cat": "*/%2F"}, - }, - } - - trie := initTrie(t) - - for _, testCase := range testCases { - testCase := testCase - t.Run(testCase.path, func(t *testing.T) { - t.Parallel() - var present, absent []string - if testCase.expectedPath != "" { - present = []string{http.MethodGet, http.MethodPost} - absent = []string{http.MethodDelete, http.MethodPut} - } else { - absent = []string{http.MethodGet, http.MethodPost, http.MethodDelete, http.MethodPut} - } - for _, method := range present { - method := method - t.Run(method, func(t *testing.T) { - t.Parallel() - target, vars, _ := trie.match(testCase.path, method) - require.NotNil(t, target) - require.Equal(t, len(testCase.expectedVars), len(vars)) - for _, varMatch := range vars { - names := make([]string, len(varMatch.Fields)) - for i, fld := range varMatch.Fields { - names[i] = string(fld.Name()) - } - name := strings.Join(names, ".") - expectedValue, ok := testCase.expectedVars[name] - assert.True(t, ok, name) - require.Equal(t, expectedValue, varMatch.Value, name) - } - }) - } - for _, method := range absent { - method := method - t.Run(method, func(t *testing.T) { - t.Parallel() - target, _, _ := trie.match(testCase.path, method) - require.Nil(t, target) - }) - } - }) - } -} - -func BenchmarkTrieMatch(b *testing.B) { - trie := initTrie(b) - path := "/foo/blah/A/B/C/foo/D/E/F/G/foo/H/I/J/K/L/M:details" - var ( - method *RouteTarget - vars []RouteTargetVarMatch - ) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - method, vars, _ = trie.match(path, http.MethodPost) - if method == nil { - b.Fatal("HttpMethod not found") - } - } - b.StopTimer() - assert.NotNil(b, method) - assert.Len(b, vars, 10) -} - -func initTrie(tb testing.TB) *RouteTrie { - tb.Helper() - var trie RouteTrie - for _, route := range []string{ - "/foo/bar/baz/buzz", - "/foo/bar/{Name}", - "/foo/bar/{Name}/baz/{child}", - "/foo/bar/{Name}/baz/{child.id}/buzz/{child.thing.id}", - "/foo/bar/*/{thing.id}/{cat=**}", - "/foo/bar/*/{thing.id}/{cat=**}:do", - "/foo/bar/*/{thing.id}/{cat=**}:cancel", - "/foo/bob/{book_id={author}/{isbn}/*}/details", - "/foo/blah/{longest_var={long_var.a={medium.a={short.aa}/*/{short.ab}/foo}/*}/{long_var.b={medium.b={short.ba}/*/{short.bb}/foo}/{last=**}}}:details", - "/foo%2Fbar/%2A/%2A%2a/{starstar=%2A%2a/**}:%2c", - "/trailing/**:slash", - "/Verb", - } { - segments, variables, err := parsePathTemplate(route) - require.NoError(tb, err) - - for _, method := range []string{http.MethodGet, http.MethodPost} { - config := &MethodConfig{ - Descriptor: &fakeMethodDescriptor{ - name: fmt.Sprintf("%s %s", method, route), - }, - } - target, err := makeTarget(config, "POST", "*", "*", segments, variables) - require.NoError(tb, err) - err = trie.insert(method, target, segments) - require.NoError(tb, err) - } - } - return &trie -} - -type fakeMethodDescriptor struct { - protoreflect.MethodDescriptor - name string - in, out protoreflect.MessageDescriptor -} - -func (f *fakeMethodDescriptor) Name() protoreflect.Name { - return protoreflect.Name(f.name) -} - -func (f *fakeMethodDescriptor) Input() protoreflect.MessageDescriptor { - if f.in == nil { - f.in = &fakeMessageDescriptor{} - } - return f.in -} - -func (f *fakeMethodDescriptor) Output() protoreflect.MessageDescriptor { - if f.out == nil { - f.out = &fakeMessageDescriptor{} - } - return f.out -} - -type fakeMessageDescriptor struct { - protoreflect.MessageDescriptor - fields protoreflect.FieldDescriptors -} - -func (f *fakeMessageDescriptor) Fields() protoreflect.FieldDescriptors { - if f.fields == nil { - f.fields = &fakeFieldDescriptors{} - } - return f.fields -} - -type fakeFieldDescriptors struct { - protoreflect.FieldDescriptors - fields map[protoreflect.Name]protoreflect.FieldDescriptor -} - -func (f *fakeFieldDescriptors) ByName(name protoreflect.Name) protoreflect.FieldDescriptor { - fld := f.fields[name] - if fld == nil { - if f.fields == nil { - f.fields = map[protoreflect.Name]protoreflect.FieldDescriptor{} - } - fld = &fakeFieldDescriptor{name: name} - f.fields[name] = fld - } - return fld -} - -type fakeFieldDescriptor struct { - name protoreflect.Name - msg protoreflect.MessageDescriptor - kind protoreflect.Kind - protoreflect.FieldDescriptor -} - -func (f *fakeFieldDescriptor) Name() protoreflect.Name { - return f.name -} - -func (f *fakeFieldDescriptor) Cardinality() protoreflect.Cardinality { - return protoreflect.Optional -} - -func (f *fakeFieldDescriptor) Kind() protoreflect.Kind { - if f.kind > 0 { - return f.kind - } - if f.msg != nil { - return protoreflect.MessageKind - } - return protoreflect.StringKind -} - -func (f *fakeFieldDescriptor) Message() protoreflect.MessageDescriptor { - if f.msg == nil { - f.msg = &fakeMessageDescriptor{} - } - return f.msg -} - -func (f *fakeFieldDescriptor) IsList() bool { - return false -} diff --git a/pkg/gateway/internal/routex/types.go b/pkg/gateway/internal/routex/types.go deleted file mode 100644 index 76643ba9c..000000000 --- a/pkg/gateway/internal/routex/types.go +++ /dev/null @@ -1,10 +0,0 @@ -package routex - -import ( - "google.golang.org/protobuf/reflect/protoreflect" -) - -type MethodConfig struct { - Descriptor protoreflect.MethodDescriptor - MethodPath string -} diff --git a/pkg/gateway/mux.go b/pkg/gateway/mux.go index 169cf53b7..deac6bd50 100644 --- a/pkg/gateway/mux.go +++ b/pkg/gateway/mux.go @@ -14,21 +14,25 @@ import ( "github.com/fullstorydev/grpchan/inprocgrpc" "github.com/gofiber/adaptor/v2" "github.com/gofiber/fiber/v2" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/errors" - "github.com/pubgo/funk/generic" - "github.com/pubgo/funk/log" - "github.com/pubgo/funk/result" - "github.com/pubgo/funk/version" - "github.com/pubgo/lava/lava" - "github.com/pubgo/lava/pkg/gateway/internal/routertree" - "github.com/pubgo/lava/pkg/httputil" + "github.com/pubgo/funk/v2" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/buildinfo/version" + "github.com/pubgo/funk/v2/errors" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/log/logfields" + "github.com/pubgo/funk/v2/result" + "github.com/rs/zerolog" "github.com/samber/lo" "google.golang.org/grpc" "google.golang.org/grpc/metadata" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/reflect/protoregistry" + + "github.com/pubgo/lava/v2/lava" + "github.com/pubgo/lava/v2/pkg/gateway/internal" + "github.com/pubgo/lava/v2/pkg/gateway/routertree" + "github.com/pubgo/lava/v2/pkg/httputil" ) type muxOptions struct { @@ -79,7 +83,7 @@ var ( } defaultCompressors = map[string]Compressor{ - "gzip": &CompressorGzip{}, + "gzip": &internal.CompressorGzip{}, "identity": nil, } ) @@ -142,13 +146,13 @@ func (m *Mux) SetRequestDecoder(name protoreflect.FullName, f func(ctx *fiber.Ct m.opts.requestInterceptors[name] = f } -func (m *Mux) MatchOperation(method string, path string) (r result.Result[*MatchOperation]) { - restTarget, err := m.routerTree.Match(method, path) - if err != nil { - return r.WithErr(errors.Wrapf(err, "path not found, method=%s path=%s", method, path)) - } - - return r.WithVal(restTarget) +func (m *Mux) MatchOperation(method, path string) (r result.Result[*MatchOperation]) { + return result.Wrap(m.routerTree.Match(method, path)). + Log(func(e *zerolog.Event) { + e.Str("method", method) + e.Str("path", path) + e.Str(logfields.Msg, "match operation failed") + }) } func (m *Mux) GetOperationByName(name string) *GrpcMethod { @@ -161,7 +165,7 @@ func (m *Mux) GetOperationByName(name string) *GrpcMethod { } func (m *Mux) GetOperation(operation string) *GrpcMethod { - var opt = m.opts.handlers[operation] + opt := m.opts.handlers[operation] if opt == nil { return nil } @@ -186,7 +190,7 @@ func (m *Mux) Handler(ctx *fiber.Ctx) error { mth := m.opts.handlers[matchOperation.Operation] if mth == nil { - return errors.Format("grpc method not found, method=%s", matchOperation.Operation) + return errors.Errorf("grpc method not found, method=%s", matchOperation.Operation) } md := metadata.MD{} @@ -202,13 +206,13 @@ func (m *Mux) Handler(ctx *fiber.Ctx) error { path: matchOperation, } - var in = mth.inputType.New().Interface() + in := mth.inputType.New().Interface() err = stream.RecvMsg(in) if err != nil { return errors.WrapCaller(err) } - var out = mth.outputType.New().Interface() + out := mth.outputType.New().Interface() var header metadata.MD var trailer metadata.MD err = m.Invoke(stream.ctx, mth.grpcFullMethod, in, out, grpc.Header(&header), grpc.Trailer(&trailer)) @@ -216,7 +220,7 @@ func (m *Mux) Handler(ctx *fiber.Ctx) error { return errors.WrapCaller(err) } - var hh = make(metadata.MD) + hh := make(metadata.MD) for k, v := range header { hh.Set(k, v...) } @@ -310,7 +314,7 @@ func NewMux(opts ...MuxOption) *Mux { mux := &Mux{ opts: &muxOpts, localClient: new(inprocgrpc.Channel), - routerTree: routertree.NewRouteTree(), + routerTree: routertree.New(), } return mux @@ -334,8 +338,8 @@ func (m *Mux) RegisterProxy(sd *grpc.ServiceDesc, proxy lava.GrpcRouter, cli grp } // RegisterService satisfies grpc.ServiceRegistrar for generated service code hooks. -func (m *Mux) RegisterService(sd *grpc.ServiceDesc, ss interface{}) { - assert.If(generic.IsNil(ss), "ss params is nil") +func (m *Mux) RegisterService(sd *grpc.ServiceDesc, ss any) { + assert.If(funk.IsNil(ss), "ss params is nil") m.localClient.RegisterService(sd, ss) @@ -368,7 +372,7 @@ func (m *Mux) registerRouter(rule *methodWrapper) { ) } -func (m *Mux) registerService(gsd *grpc.ServiceDesc, ss interface{}, cli grpc.ClientConnInterface) error { +func (m *Mux) registerService(gsd *grpc.ServiceDesc, ss any, cli grpc.ClientConnInterface) error { d, err := m.opts.files.FindDescriptorByName(protoreflect.FullName(gsd.ServiceName)) if err != nil { return errors.WrapCaller(err) @@ -376,7 +380,7 @@ func (m *Mux) registerService(gsd *grpc.ServiceDesc, ss interface{}, cli grpc.Cl sd, ok := d.(protoreflect.ServiceDescriptor) if !ok { - return errors.Format("invalid httpPathRule descriptor %T", d) + return errors.Errorf("invalid httpPathRule descriptor %T", d) } srv := &serviceWrapper{ @@ -408,7 +412,7 @@ func (m *Mux) registerService(gsd *grpc.ServiceDesc, ss interface{}, cli grpc.Cl meta: getExtensionRpc(methodDesc), }) - assert.Exit(handlerHttpRoute(getExtensionHTTP(methodDesc), func(mth string, path string, reqBody, rspBody string) error { + assert.Exit(handlerHttpRoute(getExtensionHTTP(methodDesc), func(mth, path, reqBody, rspBody string) error { return errors.WrapCaller(m.routerTree.Add(mth, path, grpcMethod, resolveBodyDesc(methodDesc, reqBody, rspBody))) })) } @@ -428,7 +432,7 @@ func (m *Mux) registerService(gsd *grpc.ServiceDesc, ss interface{}, cli grpc.Cl meta: getExtensionRpc(methodDesc), }) - assert.Exit(handlerHttpRoute(getExtensionHTTP(methodDesc), func(mth string, path string, reqBody, rspBody string) error { + assert.Exit(handlerHttpRoute(getExtensionHTTP(methodDesc), func(mth, path, reqBody, rspBody string) error { return errors.WrapCaller(m.routerTree.Add(mth, path, grpcMethod, resolveBodyDesc(methodDesc, reqBody, rspBody))) })) } diff --git a/pkg/gateway/routertree/lex.go b/pkg/gateway/routertree/lex.go new file mode 100644 index 000000000..2dbf601ef --- /dev/null +++ b/pkg/gateway/routertree/lex.go @@ -0,0 +1,44 @@ +package routertree + +import ( + "github.com/alecthomas/participle/v2/lexer" +) + +const ( + doubleStar = "**" + star = "*" +) + +// httpRule +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +type httpRule struct { + Pos lexer.Position + Slash string `parser:"@\"/\""` + Segments *segments `parser:"@@!"` + Verb *string `parser:"(\":\" @Ident)?"` +} + +// nolint +type segments struct { + Pos lexer.Position + Segments []*segment `parser:"@@ (\"/\" @@)*"` +} + +// nolint +type segment struct { + Pos lexer.Position + Path *string `parser:"@(\"*\" \"*\" | \"*\" | Ident)"` + Variable *variable `parser:"| @@*"` +} + +// nolint +type variable struct { + Pos lexer.Position + Fields []string `parser:"\"{\" @Ident (\".\" @Ident)*"` + Segments *segments `parser:"(\"=\" @@)? \"}\""` +} diff --git a/pkg/gateway/internal/routertree/parser.go b/pkg/gateway/routertree/parser.go similarity index 54% rename from pkg/gateway/internal/routertree/parser.go rename to pkg/gateway/routertree/parser.go index 5bf95dc02..e80813253 100644 --- a/pkg/gateway/internal/routertree/parser.go +++ b/pkg/gateway/routertree/parser.go @@ -1,4 +1,3 @@ -//nolint:all package routertree import ( @@ -6,54 +5,20 @@ import ( "github.com/alecthomas/participle/v2" "github.com/alecthomas/participle/v2/lexer" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/errors" - "github.com/pubgo/funk/generic" + "github.com/pubgo/funk/v2" + "github.com/pubgo/funk/v2/errors" + "github.com/samber/lo" ) -const ( - doubleStar = "**" - star = "*" +var parser = participle.MustBuild[httpRule]( + participle.Lexer(lexer.MustSimple([]lexer.SimpleRule{ + {Name: "Ident", Pattern: `[a-zA-Z][\w\_\-\.]*`}, + {Name: "Punct", Pattern: `[-[!@#$%^&*()+_={}\|:;"'<,>.?/]|]`}, + })), ) -var ( - parser = assert.Exit1(participle.Build[httpRule]( - participle.Lexer(assert.Exit1(lexer.NewSimple([]lexer.SimpleRule{ - {Name: "Ident", Pattern: `[a-zA-Z][\w\_\-\.]*`}, - {Name: "Punct", Pattern: `[-[!@#$%^&*()+_={}\|:;"'<,>.?/]|]`}, - }))), - )) -) - -// httpRule -// Template = "/" Segments [ Verb ] ; -// Segments = Segment { "/" Segment } ; -// Segment = "*" | "**" | LITERAL | Variable ; -// Variable = "{" FieldPath [ "=" Segments ] "}" ; -// FieldPath = IDENT { "." IDENT } ; -// Verb = ":" LITERAL ; -type httpRule struct { - Slash string `@"/"` - Segments *segments `@@!` - Verb *string `(":" @Ident)?` -} - -type segments struct { - Segments []*segment `@@ ("/" @@)*` -} - -type segment struct { - Path *string `@("*" "*" | "*" | Ident)` - Variable *variable `| @@*` -} - -type variable struct { - Fields []string `"{" @Ident ("." @Ident)*` - Segments *segments `("=" @@)? "}"` -} - type pathVariable struct { - Fields []string + fields []string start, end int } @@ -74,7 +39,7 @@ func (r routePath) Match(urls []string, verb string) ([]PathFieldVar, error) { } if r.Verb != nil { - if generic.FromPtr(r.Verb) != verb { + if lo.FromPtr(r.Verb) != verb { return nil, errors.New("verb not match") } } @@ -93,12 +58,12 @@ func (r routePath) Match(urls []string, verb string) ([]PathFieldVar, error) { continue } - return nil, errors.New("path is not match") + return nil, errors.Errorf("path(%s) not match", path) } var vv []PathFieldVar for _, v := range r.Vars { - pathVar := PathFieldVar{Fields: v.Fields} + pathVar := PathFieldVar{Fields: v.fields} if v.end > 0 { pathVar.Value = strings.Join(urls[v.start:v.end+1], "/") } else { @@ -118,11 +83,11 @@ func (r routePath) String() string { copy(paths, r.Paths) for _, v := range r.Vars { - varS := "{" + strings.Join(v.Fields, ".") + "=" - end := generic.Ternary(v.end == -1, len(paths)-1, v.end) + varS := "{" + strings.Join(v.fields, ".") + "=" + end := funk.Ternary(v.end == -1, len(paths)-1, v.end) for i := v.start; i <= end; i++ { - varS += generic.Ternary(i == v.start, paths[i], "/"+paths[i]) + varS += funk.Ternary(i == v.start, paths[i], "/"+paths[i]) if i > v.start { paths[i] = "" } @@ -132,10 +97,10 @@ func (r routePath) String() string { paths[v.start] = varS } - url += strings.Join(generic.Filter(paths, func(s string) bool { return s != "" }), "/") + url += strings.Join(funk.Filter(paths, func(s string) bool { return s != "" }), "/") if r.Verb != nil { - url += ":" + generic.FromPtr(r.Verb) + url += ":" + lo.FromPtr(r.Verb) } return url @@ -147,7 +112,7 @@ func handleSegments(s *segment, rr *routePath) { return } - vv := &pathVariable{Fields: s.Variable.Fields, start: len(rr.Paths)} + vv := &pathVariable{fields: s.Variable.Fields, start: len(rr.Paths)} if s.Variable.Segments == nil { rr.Paths = append(rr.Paths, star) } else { @@ -179,7 +144,10 @@ func parseToRoute(rule *httpRule) *routePath { } func parse(url string) (*httpRule, error) { - return parser.ParseString("", url) - //participle.AllowTrailing(true), - //participle.Trace(os.Stdout), + return parser.ParseString( + "", + url, + // participle.AllowTrailing(true), + // participle.Trace(os.Stdout), + ) } diff --git a/pkg/gateway/internal/routertree/parser_test.go b/pkg/gateway/routertree/parser_test.go similarity index 93% rename from pkg/gateway/internal/routertree/parser_test.go rename to pkg/gateway/routertree/parser_test.go index a371a0197..785330c22 100644 --- a/pkg/gateway/internal/routertree/parser_test.go +++ b/pkg/gateway/routertree/parser_test.go @@ -3,7 +3,7 @@ package routertree import ( "testing" - "github.com/pubgo/funk/assert" + "github.com/pubgo/funk/v2/assert" ) func TestName(t *testing.T) { diff --git a/pkg/gateway/routertree/router.go b/pkg/gateway/routertree/router.go new file mode 100644 index 000000000..78163d5c0 --- /dev/null +++ b/pkg/gateway/routertree/router.go @@ -0,0 +1,212 @@ +package routertree + +import ( + "fmt" + "strings" + + "github.com/pubgo/funk/v2" + "github.com/pubgo/funk/v2/errors" + "github.com/samber/lo" +) + +var ( + ErrPathNodeNotFound = errors.New("path node not found") + ErrOperationNotFound = errors.New("operation not found") +) + +func New() *RouteTree { + return &RouteTree{nodeMap: make(map[string]*nodeTree)} +} + +type RouteOperation struct { + Method string `json:"method,omitempty"` + Path string `json:"path,omitempty"` + Operation string `json:"operation,omitempty"` + Verb string `json:"verb,omitempty"` + Vars []string `json:"vars,omitempty"` + Extras map[string]any `json:"extras"` +} + +type MatchOperation struct { + Method string `json:"method"` + Path string `json:"path"` + Operation string `json:"operation"` + Verb string `json:"verb"` + Vars []PathFieldVar `json:"vars"` + Extras map[string]any `json:"extras"` +} + +type routeTarget struct { + Method string + Path string + Operation string + Verb *string + Vars []*pathVariable + extras map[string]any +} + +type nodeTree struct { + nodeMap map[string]*nodeTree + verbMap map[string]*routeTarget +} + +type RouteTree struct { + nodeMap map[string]*nodeTree +} + +func (r *RouteTree) List() []RouteOperation { + return getOpt(r.nodeMap) +} + +func (r *RouteTree) Add(method, path, operation string, extras map[string]any) error { + errMsg := func() string { + return fmt.Sprintf("method: %s, path: %s, operation: %s", method, path, operation) + } + + rule, err := parse(path) + if err != nil { + return errors.Wrap(err, errMsg()) + } + + node := parseToRoute(rule) + if len(node.Paths) == 0 { + return errors.Errorf("node path is empty: %s", errMsg()) + } + + nodeMap := r.nodeMap + method = handlerMethod(method) + paths := node.Paths + for i, n := range paths { + lastNode := nodeMap[n] + if lastNode == nil { + lastNode = &nodeTree{nodeMap: make(map[string]*nodeTree), verbMap: make(map[string]*routeTarget)} + nodeMap[n] = lastNode + } + nodeMap = lastNode.nodeMap + + if i == len(paths)-1 { + verbKey := fmt.Sprintf("%s:%s", method, lo.FromPtr(node.Verb)) + lastNode.verbMap[verbKey] = &routeTarget{ + Method: method, + Path: path, + Operation: operation, + extras: extras, + Verb: node.Verb, + Vars: node.Vars, + } + } + } + return nil +} + +func (r *RouteTree) Match(method, url string) (*MatchOperation, error) { + pathNodes := strings.Split(strings.Trim(strings.TrimSpace(url), "/"), "/") + lastPath := strings.SplitN(pathNodes[len(pathNodes)-1], ":", 2) + errMsg := func(key string, value any) errors.Tags { + tt := errors.Tags{ + "method": method, + "url": url, + } + if key != "" { + tt[key] = value + } + return tt + } + verb := "" + + pathNodes[len(pathNodes)-1] = lastPath[0] + if len(lastPath) > 1 { + verb = lastPath[1] + } + + method = handlerMethod(method) + verbKey := fmt.Sprintf("%s:%s", method, verb) + + getVars := func(vars []*pathVariable, paths []string) []PathFieldVar { + vv := make([]PathFieldVar, 0, len(vars)) + for _, v := range vars { + pathVar := PathFieldVar{Fields: v.fields} + if v.end > 0 { + pathVar.Value = strings.Join(paths[v.start:v.end+1], "/") + } else { + pathVar.Value = strings.Join(paths[v.start:], "/") + } + + vv = append(vv, pathVar) + } + return vv + } + + getPath := func(nodeMap map[string]*nodeTree, names ...string) (string, *nodeTree) { + for _, name := range names { + path := nodeMap[name] + if path != nil { + return name, path + } + } + return "", nil + } + + nodeMap := r.nodeMap + lastIndex := len(pathNodes) - 1 + for index, node := range pathNodes { + nodeName, path := getPath(nodeMap, node, star, doubleStar) + if path == nil { + return nil, errors.WrapTags(ErrPathNodeNotFound, errMsg("node", node)) + } + + nodeMap = path.nodeMap + switch nodeName { + case node: + if index != lastIndex { + continue + } + case star: + if index != lastIndex && len(path.nodeMap) != 0 { + nextPath := path.nodeMap[pathNodes[index+1]] + if nextPath != nil { + continue + } + } + case doubleStar: + } + + vv := path.verbMap[verbKey] + if vv == nil { + return nil, errors.WrapTags(ErrOperationNotFound, errMsg("node", node)) + } + + return &MatchOperation{ + Extras: vv.extras, + Method: vv.Method, + Path: vv.Path, + Operation: vv.Operation, + Verb: verb, + Vars: getVars(vv.Vars, pathNodes), + }, nil + } + + return nil, errors.WrapTags(ErrOperationNotFound, errMsg("", nil)) +} + +func getOpt(nodes map[string]*nodeTree) []RouteOperation { + var sets []RouteOperation + for _, n := range nodes { + for _, v := range n.verbMap { + sets = append(sets, RouteOperation{ + Method: v.Method, + Path: v.Path, + Operation: v.Operation, + Verb: lo.FromPtr(v.Verb), + Vars: funk.Map(v.Vars, func(v *pathVariable) string { return strings.Join(v.fields, ".") }), + Extras: v.extras, + }) + } + sets = append(sets, getOpt(n.nodeMap)...) + } + return sets +} + +func handlerMethod(method string) string { + return fmt.Sprintf("__%s__", strings.ToUpper(method)) +} diff --git a/pkg/gateway/routertree/router_test.go b/pkg/gateway/routertree/router_test.go new file mode 100644 index 000000000..c1bcdf93a --- /dev/null +++ b/pkg/gateway/routertree/router_test.go @@ -0,0 +1,36 @@ +package routertree + +import ( + "testing" + + "github.com/pubgo/funk/v2/pretty" + "github.com/stretchr/testify/assert" +) + +func TestRoute(t *testing.T) { + tree := New() + assert.NoError(t, tree.Add("get", "/user/user/{id}:get1", "get_user", nil)) + assert.NoError(t, tree.Add("post", "/user/user/{id}", "post_user", nil)) + assert.NoError(t, tree.Add("post", "/user/user1/{id=**}", "post_user1", nil)) + assert.NoError(t, tree.Add("post", "/user/user/{id}/send_mail", "post_mail", nil)) + opt, err := tree.Match("post", "/user/user/1/send_mail") + pretty.Println(opt) + assert.NoError(t, err) + assert.NotNil(t, opt) + assert.Equal(t, "post_mail", opt.Operation) + assert.Equal(t, "", opt.Verb) + + opt, err = tree.Match("get", "/user/user/1:get1") + pretty.Println(opt) + assert.NoError(t, err) + assert.NotNil(t, opt) + assert.Equal(t, "get_user", opt.Operation) + assert.Equal(t, "get1", opt.Verb) + + opt, err = tree.Match("post", "/user/user1/123/123456") + pretty.Println(opt) + assert.NoError(t, err) + assert.NotNil(t, opt) + assert.Equal(t, "post_user1", opt.Operation) + assert.Equal(t, "", opt.Verb) +} diff --git a/pkg/gateway/server_transport_stream.go b/pkg/gateway/stream.go similarity index 96% rename from pkg/gateway/server_transport_stream.go rename to pkg/gateway/stream.go index 31b084f95..413c75dde 100644 --- a/pkg/gateway/server_transport_stream.go +++ b/pkg/gateway/stream.go @@ -3,7 +3,7 @@ package gateway import ( "context" - "github.com/pubgo/funk/assert" + "github.com/pubgo/funk/v2/assert" "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) diff --git a/pkg/gateway/stream_grpc.go b/pkg/gateway/stream.grpc.go similarity index 100% rename from pkg/gateway/stream_grpc.go rename to pkg/gateway/stream.grpc.go diff --git a/pkg/gateway/stream_grpcweb.go b/pkg/gateway/stream.grpcweb.go similarity index 100% rename from pkg/gateway/stream_grpcweb.go rename to pkg/gateway/stream.grpcweb.go diff --git a/pkg/gateway/stream_http.go b/pkg/gateway/stream.http.go similarity index 89% rename from pkg/gateway/stream_http.go rename to pkg/gateway/stream.http.go index 229b9a98a..8e898e7a0 100644 --- a/pkg/gateway/stream_http.go +++ b/pkg/gateway/stream.http.go @@ -8,14 +8,15 @@ import ( "net/url" "github.com/gofiber/fiber/v2" - "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" - "github.com/pubgo/funk/errors" - "github.com/pubgo/funk/generic" - "github.com/pubgo/lava/pkg/gateway/internal/routertree" + "github.com/pubgo/funk/v2" + "github.com/pubgo/funk/v2/errors" "google.golang.org/grpc" "google.golang.org/grpc/metadata" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" + + "github.com/pubgo/lava/v2/pkg/gateway/gatewayutils" + "github.com/pubgo/lava/v2/pkg/gateway/routertree" ) type streamHTTP struct { @@ -62,8 +63,8 @@ func (s *streamHTTP) Context() context.Context { return NewContextWithServerTransportStream(s.ctx, s, s.method.grpcFullMethod) } -func (s *streamHTTP) SendMsg(m interface{}) error { - if generic.IsNil(m) { +func (s *streamHTTP) SendMsg(m any) error { + if funk.IsNil(m) { return errors.New("stream http send msg got nil") } @@ -97,8 +98,8 @@ func (s *streamHTTP) SendMsg(m interface{}) error { return errors.WrapCaller(err) } -func (s *streamHTTP) RecvMsg(m interface{}) error { - if generic.IsNil(m) { +func (s *streamHTTP) RecvMsg(m any) error { + if funk.IsNil(m) { return errors.New("stream http recv msg got nil") } @@ -107,7 +108,7 @@ func (s *streamHTTP) RecvMsg(m interface{}) error { return errors.New("stream http recv proto msg got unknown type message") } - var method = s.handler.Method() + method := s.handler.Method() if method == http.MethodPut || method == http.MethodPost || @@ -152,7 +153,7 @@ func (s *streamHTTP) RecvMsg(m interface{}) error { } if len(s.params) > 0 { - if err := PopulateQueryParameters(args, s.params, utilities.NewDoubleArray(nil)); err != nil { + if err := gatewayutils.PopulateQueryParameters(args, s.params, gatewayutils.NewDoubleArray(nil)); err != nil { return errors.Wrapf(err, "failed to set query params, params=%v", s.params) } } diff --git a/pkg/gateway/stream_inprocess.go b/pkg/gateway/stream.inprocess.go similarity index 100% rename from pkg/gateway/stream_inprocess.go rename to pkg/gateway/stream.inprocess.go diff --git a/pkg/gateway/stream_proxy.go b/pkg/gateway/stream.proxy.go similarity index 94% rename from pkg/gateway/stream_proxy.go rename to pkg/gateway/stream.proxy.go index a7b2e45f2..74cbd844c 100644 --- a/pkg/gateway/stream_proxy.go +++ b/pkg/gateway/stream.proxy.go @@ -7,21 +7,20 @@ import ( "context" "io" - "github.com/pubgo/funk/errors" - "github.com/pubgo/lava/internal/logutil" + "github.com/pubgo/funk/v2/errors" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/reflect/protoreflect" -) -var ( - clientStreamDescForProxying = &grpc.StreamDesc{ - ServerStreams: true, - ClientStreams: true, - } + "github.com/pubgo/lava/v2/internal/logutil" ) +var clientStreamDescForProxying = &grpc.StreamDesc{ + ServerStreams: true, + ClientStreams: true, +} + func TransparentHandler(cli grpc.ClientConnInterface, inType, outType protoreflect.MessageType, opts ...grpc.CallOption) grpc.StreamHandler { streamer := &handler{cli: cli, opts: opts, inType: inType, outType: outType} return streamer.handler @@ -36,7 +35,7 @@ type handler struct { // handler is where the real magic of proxying happens. // It is invoked like any gRPC server stream and uses the emptypb.Empty type server // to proxy calls between the input and output streams. -func (s *handler) handler(srv interface{}, serverStream grpc.ServerStream) error { +func (s *handler) handler(srv any, serverStream grpc.ServerStream) error { // little bit of gRPC internals never hurt anyone fullMethodName, ok := grpc.MethodFromServerStream(serverStream) if !ok { diff --git a/pkg/gateway/stream_websocket.go b/pkg/gateway/stream.websocket.go similarity index 100% rename from pkg/gateway/stream_websocket.go rename to pkg/gateway/stream.websocket.go diff --git a/pkg/gateway/util.go b/pkg/gateway/util.go index 2eebc9b69..9a210031f 100644 --- a/pkg/gateway/util.go +++ b/pkg/gateway/util.go @@ -9,14 +9,15 @@ import ( "strconv" "strings" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/errors" - "github.com/pubgo/lava/pkg/gateway/internal/routertree" - "github.com/pubgo/lava/pkg/proto/lavapbv1" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/errors" "google.golang.org/genproto/googleapis/api/annotations" "google.golang.org/grpc/metadata" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" + + "github.com/pubgo/lava/v2/pkg/gateway/routertree" + "github.com/pubgo/lava/v2/pkg/proto/lavapbv1" ) func getReqBodyDesc(path *routertree.MatchOperation) []protoreflect.FieldDescriptor { @@ -170,7 +171,7 @@ func newIncomingContext(ctx context.Context, header http.Header) (context.Contex return metadata.NewIncomingContext(ctx, md), md } -func handlerHttpRoute(httpRule *annotations.HttpRule, cb func(mth string, path string, reqBody, rspBody string) error) error { +func handlerHttpRoute(httpRule *annotations.HttpRule, cb func(mth, path, reqBody, rspBody string) error) error { if httpRule == nil { return nil } @@ -190,7 +191,7 @@ func handlerHttpRoute(httpRule *annotations.HttpRule, cb func(mth string, path s case *annotations.HttpRule_Custom: method, template = pattern.Custom.GetKind(), pattern.Custom.GetPath() default: - return errors.Format("invalid type of pattern for HTTP httpRule: %T", pattern) + return errors.Errorf("invalid type of pattern for HTTP httpRule: %T", pattern) } if method == "" { @@ -201,13 +202,13 @@ func handlerHttpRoute(httpRule *annotations.HttpRule, cb func(mth string, path s return errors.New("invalid HTTP httpRule: HttpPath template is blank") } - var reqBody = httpRule.GetBody() + reqBody := httpRule.GetBody() switch reqBody { case "", "*": reqBody = "*" } - var rspBody = httpRule.GetResponseBody() + rspBody := httpRule.GetResponseBody() switch rspBody { case "", "*": rspBody = "*" @@ -223,7 +224,7 @@ func handlerHttpRoute(httpRule *annotations.HttpRule, cb func(mth string, path s } if err := handlerHttpRoute(rule, cb); err != nil { - return errors.Format("failed to add REST route (add binding #%d): %v", i+1, err) + return errors.Errorf("failed to add REST route (add binding #%d): %v", i+1, err) } } @@ -245,7 +246,7 @@ func resolvePathToDescriptors(msg protoreflect.MessageDescriptor, path string) ( for i, part := range parts { field := fields.ByName(protoreflect.Name(part)) if field == nil { - return nil, errors.Format("in field HttpPath %q: element %q does not correspond to any field of type %s", + return nil, errors.Errorf("in field HttpPath %q: element %q does not correspond to any field of type %s", path, part, msg.FullName()) } @@ -255,7 +256,7 @@ func resolvePathToDescriptors(msg protoreflect.MessageDescriptor, path string) ( } if field.Cardinality() == protoreflect.Repeated { - return nil, errors.Format("in field HttpPath %q: field %q of type %s should not be a list or map", path, part, msg.FullName()) + return nil, errors.Errorf("in field HttpPath %q: field %q of type %s should not be a list or map", path, part, msg.FullName()) } msg = field.Message() diff --git a/pkg/gateway/wrapper.go b/pkg/gateway/wrapper.go index aa55fdfd2..c2e6ee7bf 100644 --- a/pkg/gateway/wrapper.go +++ b/pkg/gateway/wrapper.go @@ -3,10 +3,11 @@ package gateway import ( "context" - "github.com/pubgo/funk/errors" - "github.com/pubgo/lava/pkg/proto/lavapbv1" + "github.com/pubgo/funk/v2/errors" "google.golang.org/grpc" "google.golang.org/protobuf/reflect/protoreflect" + + "github.com/pubgo/lava/v2/pkg/proto/lavapbv1" ) type serviceWrapper struct { @@ -47,7 +48,7 @@ type methodWrapper struct { // if h.grpcMethodDesc != nil { // ctx := stream.Context() // -// reply, err := h.grpcMethodDesc.Handler(h.srv.srv, ctx, stream.RecvMsg, h.srv.opts.unaryInterceptor) +// reply, err := h.grpcMethodDesc.Exec(h.srv.srv, ctx, stream.RecvMsg, h.srv.opts.unaryInterceptor) // if err != nil { // return errors.WrapCaller(err) // } @@ -61,24 +62,24 @@ type methodWrapper struct { // } // // if h.srv.opts.streamInterceptor != nil { -// return errors.WrapCaller(h.srv.opts.streamInterceptor(h.srv.srv, stream, info, h.grpcStreamDesc.Handler)) +// return errors.WrapCaller(h.srv.opts.streamInterceptor(h.srv.srv, stream, info, h.grpcStreamDesc.Exec)) // } else { -// return errors.WrapCaller(h.grpcStreamDesc.Handler(h.srv.srv, stream)) +// return errors.WrapCaller(h.grpcStreamDesc.Exec(h.srv.srv, stream)) // } // } else { // return errors.Format("cannot find server handler") // } //} -func grpcMethodHandlerWrapper(mth *methodWrapper, opts ...grpc.CallOption) GrpcMethodHandler { +func grpcMethodHandlerWrapper(mth *methodWrapper, opts ...grpc.CallOption) MethodHandler { return func(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) { - var in = mth.inputType.New().Interface() + in := mth.inputType.New().Interface() if err := dec(in); err != nil { return nil, errors.WrapCaller(err) } - var h = func(ctx context.Context, req any) (any, error) { - var out = mth.outputType.New().Interface() + h := func(ctx context.Context, req any) (any, error) { + out := mth.outputType.New().Interface() err := mth.srv.remoteProxyCli.Invoke(ctx, mth.grpcFullMethod, req, out, opts...) if err != nil { return nil, err diff --git a/pkg/grpc_builder/config.go b/pkg/grpcbuilder/config.go similarity index 84% rename from pkg/grpc_builder/config.go rename to pkg/grpcbuilder/config.go index e7fe170d0..5da0078bb 100644 --- a/pkg/grpc_builder/config.go +++ b/pkg/grpcbuilder/config.go @@ -1,14 +1,14 @@ -package grpc_builder +package grpcbuilder import ( "time" - "github.com/pubgo/funk/recovery" - "github.com/pubgo/funk/result" + "github.com/pubgo/funk/v2/buildinfo/version" + "github.com/pubgo/funk/v2/result" "google.golang.org/grpc" "google.golang.org/grpc/keepalive" - "github.com/pubgo/lava/pkg/grpcutil" + "github.com/pubgo/lava/v2/pkg/grpcutil" ) // default config: google.golang.org/grpc/internal/transport/defaults.go @@ -63,22 +63,27 @@ type Config struct { } func (t *Config) Build(opts ...grpc.ServerOption) (r result.Result[*grpc.Server]) { - defer recovery.Result(&r) + defer result.Recovery(&r) - if t.KeepalivePolicy != nil { - opts = append(opts, t.KeepalivePolicy.ToOpts()) + cfg := t + if cfg == nil { + cfg = GetDefaultCfg() } - if t.KeepaliveParams != nil { - opts = append(opts, t.KeepaliveParams.ToOpts()) + if cfg.KeepalivePolicy != nil { + opts = append(opts, cfg.KeepalivePolicy.ToOpts()) + } + + if cfg.KeepaliveParams != nil { + opts = append(opts, cfg.KeepaliveParams.ToOpts()) } srv := grpc.NewServer(opts...) grpcutil.EnableReflection(srv) - grpcutil.EnableHealth("", srv) + grpcutil.EnableHealth(version.Project(), srv) grpcutil.EnableDebug(srv) - return r.WithVal(srv) + return r.WithValue(srv) } func GetDefaultCfg() *Config { diff --git a/pkg/grpcutil/util.go b/pkg/grpcutil/util.go index 6f81ad5ae..2970ebcc7 100644 --- a/pkg/grpcutil/util.go +++ b/pkg/grpcutil/util.go @@ -6,7 +6,7 @@ import ( "net/http" "strings" - "github.com/pubgo/funk/assert" + "github.com/pubgo/funk/v2/assert" "google.golang.org/grpc" "google.golang.org/grpc/admin" "google.golang.org/grpc/channelz/service" diff --git a/pkg/httputil/fiber.go b/pkg/httputil/fiber.go index 381cc57b9..0b31939fe 100644 --- a/pkg/httputil/fiber.go +++ b/pkg/httputil/fiber.go @@ -196,9 +196,9 @@ func handlerFunc(h fasthttp.RequestHandler) http.HandlerFunc { h(&ctx) // Convert fasthttp Ctx > net/http - ctx.Response.Header.VisitAll(func(k, v []byte) { + for k, v := range ctx.Response.Header.All() { w.Header().Add(string(k), string(v)) - }) + } w.WriteHeader(ctx.Response.StatusCode()) _, _ = w.Write(ctx.Response.Body()) } diff --git a/pkg/httputil/header.go b/pkg/httputil/header.go index 5af8f005b..58b86896f 100644 --- a/pkg/httputil/header.go +++ b/pkg/httputil/header.go @@ -1,40 +1,17 @@ package httputil +import "github.com/gofiber/fiber/v2" + // HTTP Headers were copied from net/http. const ( - HeaderAuthorization = "Authorization" - HeaderProxyAuthenticate = "Proxy-Authenticate" - HeaderProxyAuthorization = "Proxy-Authorization" - HeaderWWWAuthenticate = "WWW-Authenticate" - HeaderAge = "Age" - HeaderCacheControl = "Cache-Control" - HeaderClearSiteData = "Clear-Site-Data" - HeaderExpires = "Expires" - HeaderPragma = "Pragma" - HeaderWarning = "Warning" - HeaderAcceptCH = "Accept-CH" - HeaderAcceptCHLifetime = "Accept-CH-Lifetime" - HeaderContentDPR = "Content-DPR" - HeaderDPR = "DPR" - HeaderEarlyData = "Early-Data" - HeaderSaveData = "Save-Data" - HeaderViewportWidth = "Viewport-Width" - HeaderWidth = "Width" - HeaderETag = "ETag" - HeaderIfMatch = "If-Match" - HeaderIfModifiedSince = "If-Modified-Since" - HeaderIfNoneMatch = "If-None-Match" - HeaderIfUnmodifiedSince = "If-Unmodified-Since" - HeaderLastModified = "Last-Modified" - HeaderVary = "Vary" - HeaderConnection = "Connection" - HeaderKeepAlive = "Keep-Alive" + HeaderAuthorization = fiber.HeaderAuthorization + HeaderConnection = fiber.HeaderConnection + HeaderKeepAlive = fiber.HeaderKeepAlive HeaderAccept = "Accept" HeaderAcceptCharset = "Accept-Charset" HeaderAcceptEncoding = "Accept-Encoding" HeaderAcceptLanguage = "Accept-Language" HeaderCookie = "Cookie" - HeaderExpect = "Expect" HeaderMaxForwards = "Max-Forwards" HeaderSetCookie = "Set-Cookie" HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials" @@ -47,9 +24,6 @@ const ( HeaderAccessControlRequestMethod = "Access-Control-Request-Method" HeaderOrigin = "Origin" HeaderTimingAllowOrigin = "Timing-Allow-Origin" - HeaderXPermittedCrossDomainPolicies = "X-Permitted-Cross-Domain-Policies" - HeaderDNT = "DNT" - HeaderTk = "Tk" HeaderContentDisposition = "Content-Disposition" HeaderContentEncoding = "Content-Encoding" HeaderContentLanguage = "Content-Language" @@ -57,7 +31,6 @@ const ( HeaderContentLocation = "Content-Location" HeaderContentType = "Content-Type" HeaderForwarded = "Forwarded" - HeaderVia = "Via" HeaderXForwardedFor = "X-Forwarded-For" HeaderXForwardedHost = "X-Forwarded-Host" HeaderXForwardedProto = "X-Forwarded-Proto" @@ -79,10 +52,7 @@ const ( HeaderContentSecurityPolicy = "Content-Security-Policy" HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only" HeaderCrossOriginResourcePolicy = "Cross-Origin-Resource-Policy" - HeaderExpectCT = "Expect-CT" HeaderFeaturePolicy = "Feature-Policy" - HeaderPublicKeyPins = "Public-Key-Pins" - HeaderPublicKeyPinsReportOnly = "Public-Key-Pins-Report-Only" HeaderStrictTransportSecurity = "Strict-Transport-Security" HeaderUpgradeInsecureRequests = "Upgrade-Insecure-Requests" HeaderXContentTypeOptions = "X-Content-Type-Options" @@ -91,11 +61,6 @@ const ( HeaderXPoweredBy = "X-Powered-By" HeaderXXSSProtection = "X-XSS-Protection" HeaderLastEventID = "Last-Event-ID" - HeaderNEL = "NEL" - HeaderPingFrom = "Ping-From" - HeaderPingTo = "Ping-To" - HeaderReportTo = "Report-To" - HeaderTE = "TE" HeaderTrailer = "Trailer" HeaderTransferEncoding = "Transfer-Encoding" HeaderSecWebSocketAccept = "Sec-WebSocket-Accept" @@ -106,27 +71,19 @@ const ( HeaderAcceptPatch = "Accept-Patch" HeaderAcceptPushPolicy = "Accept-Push-Policy" HeaderAcceptSignature = "Accept-Signature" - HeaderAltSvc = "Alt-Svc" HeaderDate = "Date" - HeaderIndex = "index" HeaderLargeAllocation = "Large-Allocation" HeaderLink = "Link" HeaderPushPolicy = "Push-Policy" HeaderRetryAfter = "Backoff-After" HeaderServerTiming = "Server-Timing" HeaderSignature = "Signature" - HeaderSignedHeaders = "Signed-Headers" - HeaderSourceMap = "SourceMap" - HeaderUpgrade = "Upgrade" - HeaderXDNSPrefetchControl = "X-DNS-Prefetch-Control" - HeaderXPingback = "X-Pingback" - HeaderXRequestID = "X-Request-ID" + HeaderXRequestID = fiber.HeaderXRequestID HeaderXRequestProject = "X-Request-Project" HeaderXRequestVersion = "X-Request-Version" HeaderXRequestOperation = "X-Request-Operation" HeaderAuthAct = "X-Request-Auth-Act" HeaderXRequestedWith = "X-Requested-With" - HeaderXRobotsTag = "X-Robots-Tag" HeaderXUACompatible = "X-UA-Compatible" HeaderVersion = "X-Api-Version" HeaderTrace = "X-Api-Trace" diff --git a/pkg/httputil/util.go b/pkg/httputil/util.go index d7ef807a1..7be59be63 100644 --- a/pkg/httputil/util.go +++ b/pkg/httputil/util.go @@ -1,11 +1,69 @@ package httputil import ( + "log/slog" "strings" + "dario.cat/mergo" + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/pubgo/funk/v2" + "github.com/pubgo/funk/v2/buildinfo/version" + "github.com/pubgo/funk/v2/errors" + "github.com/pubgo/funk/v2/errors/errcode" + "github.com/pubgo/funk/v2/proto/errorpb" + "github.com/pubgo/funk/v2/running" + "github.com/samber/lo" "github.com/valyala/fasthttp" + "google.golang.org/grpc/codes" + + "github.com/pubgo/lava/v2/pkg/fiberbuilder" ) +type Config struct { + Http *fiberbuilder.Config `yaml:"http"` + EnablePrintRouter bool `yaml:"enable_print_router"` + BaseUrl string `yaml:"base_url"` + HttpPort *int `yaml:"http_port"` +} + +func DefaultCfg(config ...*Config) Config { + cfg := Config{ + Http: &fiberbuilder.Config{ + ServerHeader: "lava", + EnableIPValidation: true, + ETag: true, + ErrorHandler: ErrHandler, + BodyLimit: 1024 * 1024 * 500, + }, + EnablePrintRouter: true, + BaseUrl: version.Project(), + HttpPort: lo.ToPtr(running.HttpPort()), + } + + for _, t := range config { + if t == nil { + continue + } + + err := mergo.Merge(&cfg, t, mergo.WithOverride, mergo.WithAppendSlice) + if err != nil { + slog.Error("failed to merge config to default config", "err", err, "source", t, "target", cfg) + panic(err) + } + } + + cfg.Http.EnablePrintRoutes = cfg.EnablePrintRouter + cfg.BaseUrl = funk.DoFunc(func() string { + baseUrl := cfg.BaseUrl + if baseUrl == "" { + baseUrl = "/" + version.Project() + } + return "/" + strings.Trim(baseUrl, "/") + }) + return cfg +} + func IsWebsocket(h *fasthttp.RequestHeader) bool { if strings.Contains(strings.ToLower(string(h.Peek("Connection"))), "upgrade") && strings.EqualFold(string(h.Peek("Upgrade")), "websocket") { @@ -13,3 +71,56 @@ func IsWebsocket(h *fasthttp.RequestHeader) bool { } return false } + +func ErrHandler(ctx *fiber.Ctx, err error) error { + if err == nil { + return nil + } + + var errPb *errorpb.ErrCode + var fiberErr *fiber.Error + if errors.As(err, &fiberErr) && fiberErr != nil { + errPb = &errorpb.ErrCode{ + Name: "lava.error", + StatusCode: errorpb.Code(errcode.Http2GrpcCode(int32(fiberErr.Code))), + Code: int32(fiberErr.Code), + Message: fiberErr.Message, + Details: errcode.MustTagsToAny(errors.Tags{ + "path": ctx.Route().Path, + "version": running.Version(), + "instance": running.InstanceID, + }), + } + } else { + errPb = errcode.ParseError(err) + } + + if errPb == nil || errPb.StatusCode == 0 { + return nil + } + + code := errcode.GrpcCodeToHTTP(codes.Code(errPb.StatusCode)) + ctx.Set(fiber.HeaderContentType, fiber.MIMEApplicationJSON) + return ctx.Status(code).JSON(errPb) +} + +func Cors() fiber.Handler { + return cors.New(cors.Config{ + AllowOriginsFunc: func(origin string) bool { + return true + }, + AllowMethods: strings.Join([]string{ + fiber.MethodGet, + fiber.MethodPost, + fiber.MethodPut, + fiber.MethodDelete, + fiber.MethodPatch, + fiber.MethodHead, + fiber.MethodOptions, + }, ","), + // AllowHeaders: "", + AllowCredentials: true, + // ExposeHeaders: "", + MaxAge: 0, + }) +} diff --git a/pkg/netutil/addr.go b/pkg/netutil/addr.go index 872ddcf78..d7f688f4e 100644 --- a/pkg/netutil/addr.go +++ b/pkg/netutil/addr.go @@ -21,7 +21,7 @@ func AuthorityAddr(scheme, authority string) (addr string) { return host + ":" + port } addr = net.JoinHostPort(host, port) - return + return addr } func AuthorityHostPort(scheme, authority string) (host, port string) { @@ -36,5 +36,5 @@ func AuthorityHostPort(scheme, authority string) (host, port string) { if a, err := idna.ToASCII(host); err == nil { host = a } - return + return host, port } diff --git a/pkg/netutil/conn.go b/pkg/netutil/conn.go index 9e484b951..5a140ac02 100644 --- a/pkg/netutil/conn.go +++ b/pkg/netutil/conn.go @@ -9,8 +9,8 @@ import ( "strconv" "strings" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/recovery" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/recovery" ) type sockOpts struct { @@ -110,12 +110,12 @@ func WithChmod(mask os.FileMode) SockOpt { } } -func MustGetPort(addrOrNet interface{}) int { +func MustGetPort(addrOrNet any) int { return assert.Must1(GetPort(addrOrNet)) } // GetPort returns the port of an endpoint address. -func GetPort(addrOrNet interface{}) (int, error) { +func GetPort(addrOrNet any) (int, error) { var addr string switch addrNet := addrOrNet.(type) { case net.Addr: diff --git a/pkg/netutil/util.go b/pkg/netutil/util.go index dc3f2c62e..d65d002b6 100644 --- a/pkg/netutil/util.go +++ b/pkg/netutil/util.go @@ -1,6 +1,7 @@ package netutil import ( + "context" "errors" "fmt" "io" @@ -11,7 +12,7 @@ import ( "syscall" "time" - "github.com/pubgo/funk/assert" + "github.com/pubgo/funk/v2/assert" ) var localIp = assert.Exit1(regexp.Compile(`\d+\.\d+\.\d+\.\d+`)) @@ -173,9 +174,18 @@ func getIP(r *http.Request) (string, error) { if err != nil { return "", err } + netIP = net.ParseIP(ip) if netIP != nil { return ip, nil } - return "", fmt.Errorf("No valid ip found") + + return "", fmt.Errorf("no valid ip found: %s", r.RemoteAddr) +} + +func IsErrServerClosed(err error) bool { + return err == nil || + errors.Is(err, http.ErrServerClosed) || + errors.Is(err, net.ErrClosed) || + errors.Is(err, context.Canceled) } diff --git a/pkg/proto/lavapbv1/form_file.pb.go b/pkg/proto/lavapbv1/form_file.pb.go index b4a3be475..d8b062432 100644 --- a/pkg/proto/lavapbv1/form_file.pb.go +++ b/pkg/proto/lavapbv1/form_file.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.35.2 -// protoc v5.28.2 +// protoc v5.29.3 // source: lava/form_file.proto package lavapbv1 diff --git a/pkg/proto/lavapbv1/rpc.pb.go b/pkg/proto/lavapbv1/rpc.pb.go index a212f63d8..3ac07c673 100644 --- a/pkg/proto/lavapbv1/rpc.pb.go +++ b/pkg/proto/lavapbv1/rpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.35.2 -// protoc v5.28.2 +// protoc v5.29.3 // source: lava/rpc.proto package lavapbv1 @@ -32,8 +32,6 @@ type RpcMeta struct { Version *string `protobuf:"bytes,2,opt,name=version,proto3,oneof" json:"version,omitempty"` // rpc tags Tags map[string]string `protobuf:"bytes,3,rep,name=tags,proto3" json:"tags,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - // grpc method name - Method *string `protobuf:"bytes,4,opt,name=method,proto3,oneof" json:"method,omitempty"` } func (x *RpcMeta) Reset() { @@ -87,13 +85,6 @@ func (x *RpcMeta) GetTags() map[string]string { return nil } -func (x *RpcMeta) GetMethod() string { - if x != nil && x.Method != nil { - return *x.Method - } - return "" -} - var file_lava_rpc_proto_extTypes = []protoimpl.ExtensionInfo{ { ExtendedType: (*descriptorpb.MethodOptions)(nil), @@ -117,30 +108,27 @@ var file_lava_rpc_proto_rawDesc = []byte{ 0x0a, 0x0e, 0x6c, 0x61, 0x76, 0x61, 0x2f, 0x72, 0x70, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x6c, 0x61, 0x76, 0x61, 0x2e, 0x72, 0x70, 0x63, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xda, 0x01, 0x0a, + 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb2, 0x01, 0x0a, 0x07, 0x52, 0x70, 0x63, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x2f, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x61, 0x76, 0x61, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x70, 0x63, 0x4d, 0x65, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x67, - 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x12, 0x1b, 0x0a, 0x06, - 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x06, - 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x88, 0x01, 0x01, 0x1a, 0x37, 0x0a, 0x09, 0x54, 0x61, 0x67, - 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, - 0x38, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x09, - 0x0a, 0x07, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x3a, 0x4d, 0x0a, 0x07, 0x6f, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xa4, 0x8d, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, - 0x61, 0x76, 0x61, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x70, 0x63, 0x4d, 0x65, 0x74, 0x61, 0x52, - 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x75, 0x62, 0x67, 0x6f, 0x2f, 0x6c, 0x61, 0x76, - 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6c, 0x61, 0x76, 0x61, - 0x70, 0x62, 0x76, 0x31, 0x3b, 0x6c, 0x61, 0x76, 0x61, 0x70, 0x62, 0x76, 0x31, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x1a, 0x37, 0x0a, 0x09, + 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x3a, 0x4d, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xa4, 0x8d, 0x06, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x61, 0x76, 0x61, 0x2e, 0x72, 0x70, 0x63, 0x2e, + 0x52, 0x70, 0x63, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, + 0x75, 0x62, 0x67, 0x6f, 0x2f, 0x6c, 0x61, 0x76, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2f, 0x6c, 0x61, 0x76, 0x61, 0x70, 0x62, 0x76, 0x31, 0x3b, 0x6c, 0x61, 0x76, + 0x61, 0x70, 0x62, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/pkg/proto/lavapbv1/service.pb.go b/pkg/proto/lavapbv1/service.pb.go index 2cb6e83b7..3d8645fec 100644 --- a/pkg/proto/lavapbv1/service.pb.go +++ b/pkg/proto/lavapbv1/service.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.35.2 -// protoc v5.28.2 +// protoc v5.29.3 // source: lava/service.proto package lavapbv1 @@ -97,6 +97,67 @@ func (x *ServiceInfo) GetIp() string { return "" } +type HttpResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Body string `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"` + StatusCode int32 `protobuf:"varint,2,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"` + Headers map[string]string `protobuf:"bytes,3,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *HttpResponse) Reset() { + *x = HttpResponse{} + mi := &file_lava_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HttpResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HttpResponse) ProtoMessage() {} + +func (x *HttpResponse) ProtoReflect() protoreflect.Message { + mi := &file_lava_service_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HttpResponse.ProtoReflect.Descriptor instead. +func (*HttpResponse) Descriptor() ([]byte, []int) { + return file_lava_service_proto_rawDescGZIP(), []int{1} +} + +func (x *HttpResponse) GetBody() string { + if x != nil { + return x.Body + } + return "" +} + +func (x *HttpResponse) GetStatusCode() int32 { + if x != nil { + return x.StatusCode + } + return 0 +} + +func (x *HttpResponse) GetHeaders() map[string]string { + if x != nil { + return x.Headers + } + return nil +} + var File_lava_service_proto protoreflect.FileDescriptor var file_lava_service_proto_rawDesc = []byte{ @@ -109,7 +170,19 @@ var file_lava_service_proto_rawDesc = []byte{ 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x22, 0xbd, 0x01, 0x0a, 0x0c, 0x48, + 0x74, 0x74, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x62, + 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, + 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, + 0x12, 0x3c, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x22, 0x2e, 0x6c, 0x61, 0x76, 0x61, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x74, 0x74, 0x70, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x1a, 0x3a, + 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x75, 0x62, 0x67, 0x6f, 0x2f, 0x6c, 0x61, 0x76, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6c, 0x61, 0x76, 0x61, 0x70, 0x62, 0x76, 0x31, 0x3b, 0x6c, 0x61, 0x76, 0x61, 0x70, 0x62, 0x76, 0x31, 0x62, @@ -128,16 +201,19 @@ func file_lava_service_proto_rawDescGZIP() []byte { return file_lava_service_proto_rawDescData } -var file_lava_service_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_lava_service_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_lava_service_proto_goTypes = []any{ - (*ServiceInfo)(nil), // 0: lava.v1.ServiceInfo + (*ServiceInfo)(nil), // 0: lava.v1.ServiceInfo + (*HttpResponse)(nil), // 1: lava.v1.HttpResponse + nil, // 2: lava.v1.HttpResponse.HeadersEntry } var file_lava_service_proto_depIdxs = []int32{ - 0, // [0:0] is the sub-list for method output_type - 0, // [0:0] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 2, // 0: lava.v1.HttpResponse.headers:type_name -> lava.v1.HttpResponse.HeadersEntry + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name } func init() { file_lava_service_proto_init() } @@ -151,7 +227,7 @@ func file_lava_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_lava_service_proto_rawDesc, NumEnums: 0, - NumMessages: 1, + NumMessages: 3, NumExtensions: 0, NumServices: 0, }, diff --git a/pkg/fiber_builder/websocket.go b/pkg/wsbuilder/websocket.go similarity index 60% rename from pkg/fiber_builder/websocket.go rename to pkg/wsbuilder/websocket.go index 19107be48..12db4020b 100644 --- a/pkg/fiber_builder/websocket.go +++ b/pkg/wsbuilder/websocket.go @@ -1,8 +1,6 @@ -package fiber_builder +package wsbuilder import ( - "errors" - "io" "sync" "time" @@ -79,7 +77,7 @@ func NewWs(handler func(*fiber.Ctx, *Conn), config ...WsCfg) fiber.Handler { return func(c *fiber.Ctx) error { conn := acquireConn() // locals - c.Context().VisitUserValues(func(key []byte, value interface{}) { + c.Context().VisitUserValues(func(key []byte, value any) { conn.locals[string(key)] = value }) @@ -90,14 +88,14 @@ func NewWs(handler func(*fiber.Ctx, *Conn), config ...WsCfg) fiber.Handler { } // queries - c.Context().QueryArgs().VisitAll(func(key, value []byte) { + for key, value := range c.Context().QueryArgs().All() { conn.queries[string(key)] = string(value) - }) + } // cookies - c.Context().Request.Header.VisitAllCookie(func(key, value []byte) { + for key, value := range c.Context().Request.Header.All() { conn.cookies[string(key)] = string(value) - }) + } if err := upgrader.Upgrade(c.Context(), func(fconn *websocket.Conn) { conn.Conn = fconn @@ -114,7 +112,7 @@ func NewWs(handler func(*fiber.Ctx, *Conn), config ...WsCfg) fiber.Handler { // Conn https://godoc.org/github.com/gorilla/websocket#pkg-index type Conn struct { *websocket.Conn - locals map[string]interface{} + locals map[string]any params map[string]string cookies map[string]string queries map[string]string @@ -122,7 +120,7 @@ type Conn struct { // Conn pool var poolConn = sync.Pool{ - New: func() interface{} { + New: func() any { return new(Conn) }, } @@ -130,7 +128,7 @@ var poolConn = sync.Pool{ // Acquire Conn from pool func acquireConn() *Conn { conn := poolConn.Get().(*Conn) - conn.locals = make(map[string]interface{}) + conn.locals = make(map[string]any) conn.params = make(map[string]string) conn.queries = make(map[string]string) conn.cookies = make(map[string]string) @@ -145,7 +143,7 @@ func releaseConn(conn *Conn) { // Locals makes it possible to pass interface{} values under string keys scoped to the request // and therefore available to all following routes that match the request. -func (conn *Conn) Locals(key string) interface{} { +func (conn *Conn) Locals(key string) any { return conn.locals[key] } @@ -185,79 +183,79 @@ func (conn *Conn) Cookies(key string, defaultValue ...string) string { // Constants are taken from https://github.com/fasthttp/websocket/blob/master/conn.go#L43 // Close codes defined in RFC 6455, section 11.7. -const ( - CloseNormalClosure = 1000 - CloseGoingAway = 1001 - CloseProtocolError = 1002 - CloseUnsupportedData = 1003 - CloseNoStatusReceived = 1005 - CloseAbnormalClosure = 1006 - CloseInvalidFramePayloadData = 1007 - ClosePolicyViolation = 1008 - CloseMessageTooBig = 1009 - CloseMandatoryExtension = 1010 - CloseInternalServerErr = 1011 - CloseServiceRestart = 1012 - CloseTryAgainLater = 1013 - CloseTLSHandshake = 1015 -) - -// The message types are defined in RFC 6455, section 11.8. -const ( - // TextMessage denotes a text data message. The text message payload is - // interpreted as UTF-8 encoded text data. - TextMessage = 1 - - // BinaryMessage denotes a binary data message. - BinaryMessage = 2 - - // CloseMessage denotes a close control message. The optional message - // payload contains a numeric code and text. Use the FormatCloseMessage - // function to format a close message payload. - CloseMessage = 8 - - // PingMessage denotes a ping control message. The optional message payload - // is UTF-8 encoded text. - PingMessage = 9 - - // PongMessage denotes a pong control message. The optional message payload - // is UTF-8 encoded text. - PongMessage = 10 -) - -var ( - ErrBadHandshake = errors.New("websocket: bad handshake") - ErrCloseSent = errors.New("websocket: close sent") - ErrReadLimit = errors.New("websocket: read limit exceeded") -) - -// FormatCloseMessage formats closeCode and text as a WebSocket close message. -// An empty message is returned for code CloseNoStatusReceived. -func FormatCloseMessage(closeCode int, text string) []byte { - return websocket.FormatCloseMessage(closeCode, text) -} - -// IsCloseError returns boolean indicating whether the error is a *CloseError -// with one of the specified codes. -func IsCloseError(err error, codes ...int) bool { - return websocket.IsCloseError(err, codes...) -} - -// IsUnexpectedCloseError returns boolean indicating whether the error is a -// *CloseError with a code not in the list of expected codes. -func IsUnexpectedCloseError(err error, expectedCodes ...int) bool { - return websocket.IsUnexpectedCloseError(err, expectedCodes...) -} - -// IsWebSocketUpgrade returns true if the client requested upgrade to the -// WebSocket protocol. -func IsWebSocketUpgrade(c *fiber.Ctx) bool { - return websocket.FastHTTPIsWebSocketUpgrade(c.Context()) -} - -// JoinMessages concatenates received messages to create a single io.Reader. -// The string term is appended to each message. The returned reader does not -// support concurrent calls to the Read method. -func JoinMessages(c *websocket.Conn, term string) io.Reader { - return websocket.JoinMessages(c, term) -} +//const ( +// CloseNormalClosure = 1000 +// CloseGoingAway = 1001 +// CloseProtocolError = 1002 +// CloseUnsupportedData = 1003 +// CloseNoStatusReceived = 1005 +// CloseAbnormalClosure = 1006 +// CloseInvalidFramePayloadData = 1007 +// ClosePolicyViolation = 1008 +// CloseMessageTooBig = 1009 +// CloseMandatoryExtension = 1010 +// CloseInternalServerErr = 1011 +// CloseServiceRestart = 1012 +// CloseTryAgainLater = 1013 +// CloseTLSHandshake = 1015 +//) +// +//// The message types are defined in RFC 6455, section 11.8. +//const ( +// // TextMessage denotes a text data message. The text message payload is +// // interpreted as UTF-8 encoded text data. +// TextMessage = 1 +// +// // BinaryMessage denotes a binary data message. +// BinaryMessage = 2 +// +// // CloseMessage denotes a close control message. The optional message +// // payload contains a numeric code and text. Use the FormatCloseMessage +// // function to format a close message payload. +// CloseMessage = 8 +// +// // PingMessage denotes a ping control message. The optional message payload +// // is UTF-8 encoded text. +// PingMessage = 9 +// +// // PongMessage denotes a pong control message. The optional message payload +// // is UTF-8 encoded text. +// PongMessage = 10 +//) +// +//var ( +// ErrBadHandshake = errors.New("websocket: bad handshake") +// ErrCloseSent = errors.New("websocket: close sent") +// ErrReadLimit = errors.New("websocket: read limit exceeded") +//) +// +//// FormatCloseMessage formats closeCode and text as a WebSocket close message. +//// An empty message is returned for code CloseNoStatusReceived. +//func FormatCloseMessage(closeCode int, text string) []byte { +// return websocket.FormatCloseMessage(closeCode, text) +//} +// +//// IsCloseError returns boolean indicating whether the error is a *CloseError +//// with one of the specified codes. +//func IsCloseError(err error, codes ...int) bool { +// return websocket.IsCloseError(err, codes...) +//} +// +//// IsUnexpectedCloseError returns boolean indicating whether the error is a +//// *CloseError with a code not in the list of expected codes. +//func IsUnexpectedCloseError(err error, expectedCodes ...int) bool { +// return websocket.IsUnexpectedCloseError(err, expectedCodes...) +//} +// +//// IsWebSocketUpgrade returns true if the client requested upgrade to the +//// WebSocket protocol. +//func IsWebSocketUpgrade(c *fiber.Ctx) bool { +// return websocket.FastHTTPIsWebSocketUpgrade(c.Context()) +//} +// +//// JoinMessages concatenates received messages to create a single io.Reader. +//// The string term is appended to each message. The returned reader does not +//// support concurrent calls to the Read method. +//func JoinMessages(c *websocket.Conn, term string) io.Reader { +// return websocket.JoinMessages(c, term) +//} diff --git a/pkg/wsutil/ws.go b/pkg/wsbuilder/ws.go similarity index 99% rename from pkg/wsutil/ws.go rename to pkg/wsbuilder/ws.go index 70ebba759..a608005e0 100644 --- a/pkg/wsutil/ws.go +++ b/pkg/wsbuilder/ws.go @@ -1,4 +1,4 @@ -package wsutil +package wsbuilder import ( "errors" diff --git a/pkg/wsproxy/websocket_proxy.go b/pkg/wsproxy/websocket_proxy.go index 95476b0e6..bd492b8f2 100644 --- a/pkg/wsproxy/websocket_proxy.go +++ b/pkg/wsproxy/websocket_proxy.go @@ -12,9 +12,10 @@ import ( "time" "github.com/gorilla/websocket" - "github.com/pubgo/funk/log" - "github.com/pubgo/lava/internal/logutil" + "github.com/pubgo/funk/v2/log" "golang.org/x/net/context" + + "github.com/pubgo/lava/v2/internal/logutil" ) const ( @@ -369,7 +370,7 @@ func (ws WsConn) WritePreparedMessage(pm *websocket.PreparedMessage) error { return ws.Conn.WritePreparedMessage(pm) } -func (ws WsConn) WriteJSON(v interface{}) error { +func (ws WsConn) WriteJSON(v any) error { ws.mu.Lock() defer ws.mu.Unlock() return ws.Conn.WriteJSON(v) diff --git a/proto/lava/event.proto b/proto/lava/event.proto deleted file mode 100644 index 548b76079..000000000 --- a/proto/lava/event.proto +++ /dev/null @@ -1,12 +0,0 @@ -syntax = "proto3"; - -package lava.v1; - -option go_package = "github.com/pubgo/lava/pkg/proto/lavapbv1;lavapbv1"; - -enum EventType { - UNKNOWN = 0; - CREATE = 1; - UPDATE = 2; - DELETE = 3; -} diff --git a/proto/lava/service.proto b/proto/lava/service.proto index 93c38be62..c6e0d9faa 100644 --- a/proto/lava/service.proto +++ b/proto/lava/service.proto @@ -11,3 +11,9 @@ message ServiceInfo { string hostname = 4; string ip = 5; } + +message HttpResponse { + string body = 1; + int32 status_code = 2; + map headers = 3; +} diff --git a/protobuf.yaml b/protobuf.yaml index 53ecf6327..ef4d3cbad 100644 --- a/protobuf.yaml +++ b/protobuf.yaml @@ -1,4 +1,4 @@ -checksum: 2cb52bc4143a48e6062590082a7310ff425eff96 +checksum: c988bccc50f12264b8f61e5b8ebcc25be4e869b5 vendor: .proto base: out: ./pkg @@ -15,13 +15,17 @@ deps: version: v0.0.0-20220224004616-3c171936039b - name: google/protobuf url: /usr/local/include/google/protobuf + optional: true + - name: google/protobuf + url: /opt/homebrew/include/google/protobuf + optional: true - name: errorpb - url: github.com/pubgo/funk + url: github.com/pubgo/funk/v2 path: /proto/errorpb - version: v0.5.56 + version: v2.0.0-alpha.12 plugins: - name: go - - name: go-errors + - name: go-errors2 - name: go-grpc opt: - require_unimplemented_servers=false diff --git a/servers/grpcs/codec.go b/servers/grpcs/codec.go index 6372cb805..9f3af5c54 100644 --- a/servers/grpcs/codec.go +++ b/servers/grpcs/codec.go @@ -3,7 +3,7 @@ package grpcs import ( "google.golang.org/grpc/encoding" - codec "github.com/pubgo/lava/core/encoding" + codec "github.com/pubgo/lava/v2/core/encoding" ) func init() { diff --git a/servers/grpcs/config.go b/servers/grpcs/config.go index e6009ee9f..a9b6be994 100644 --- a/servers/grpcs/config.go +++ b/servers/grpcs/config.go @@ -1,8 +1,8 @@ package grpcs import ( - "github.com/pubgo/funk/version" - "github.com/pubgo/lava/pkg/grpc_builder" + "github.com/pubgo/lava/v2/pkg/fiberbuilder" + "github.com/pubgo/lava/v2/pkg/grpcbuilder" ) const ( @@ -14,24 +14,12 @@ type GrpcServerConfigLoader struct { } type Config struct { - EnablePrintRoutes bool `yaml:"enable_print_routes"` + Http *fiberbuilder.Config `yaml:"http"` + GrpcConfig *grpcbuilder.Config `yaml:"grpc"` + EnablePrintRouter bool `yaml:"enable_print_router"` BaseUrl string `yaml:"base_url"` - GrpcConfig *grpc_builder.Config `yaml:"grpc_config"` - EnableCors bool `yaml:"enable_cors"` - EnablePingPong bool `yaml:"enable_ping_pong"` - - // unix seconds - PingPongTime int32 `yaml:"ping_pong_time"` - GrpcPort *int `yaml:"grpc_port"` - HttpPort *int `yaml:"http_port"` - - WsReadLimit *int `yaml:"ws_read_limit"` } func defaultCfg() *Config { - return &Config{ - EnablePrintRoutes: true, - BaseUrl: version.Project(), - GrpcConfig: grpc_builder.GetDefaultCfg(), - } + return &Config{} } diff --git a/servers/grpcs/inner_server.go b/servers/grpcs/inner_server.go deleted file mode 100644 index ec6baa79d..000000000 --- a/servers/grpcs/inner_server.go +++ /dev/null @@ -1,137 +0,0 @@ -package grpcs - -import ( - "context" - "fmt" - - "github.com/fullstorydev/grpchan/inprocgrpc" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/errors" - "github.com/pubgo/funk/log" - "github.com/pubgo/lava/clients/grpcc" - "github.com/pubgo/lava/clients/grpcc/grpcc_config" - "github.com/pubgo/lava/core/metrics" - "github.com/pubgo/lava/internal/middlewares/middleware_accesslog" - "github.com/pubgo/lava/internal/middlewares/middleware_metric" - "github.com/pubgo/lava/internal/middlewares/middleware_recovery" - "github.com/pubgo/lava/internal/middlewares/middleware_service_info" - "github.com/pubgo/lava/lava" - "github.com/pubgo/lava/pkg/gateway" - "google.golang.org/grpc" - "google.golang.org/grpc/metadata" - "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/reflect/protoregistry" -) - -// NewInner grpc 服务内部通信 -func NewInner(handlers []lava.GrpcRouter, grpcProxy []lava.GrpcProxy, dixMiddlewares []lava.Middleware, metric metrics.Metric, log log.Logger) *lava.InnerServer { - middlewares := lava.Middlewares{ - middleware_service_info.New(), - middleware_metric.New(metric), - middleware_accesslog.New(log), - middleware_recovery.New(), - } - middlewares = append(middlewares, dixMiddlewares...) - - cc := new(inprocgrpc.Channel) - srvMidMap := make(map[string][]lava.Middleware) - for _, h := range handlers { - desc := h.ServiceDesc() - assert.If(desc == nil, "desc is nil") - - srvMidMap[desc.ServiceName] = append(srvMidMap[desc.ServiceName], middlewares...) - srvMidMap[desc.ServiceName] = append(srvMidMap[desc.ServiceName], h.Middlewares()...) - cc.RegisterService(h.ServiceDesc(), h) - } - - for _, h := range grpcProxy { - desc := h.ServiceDesc() - assert.If(desc == nil, "desc is nil") - - srvMidMap[desc.ServiceName] = append(srvMidMap[desc.ServiceName], middlewares...) - srvMidMap[desc.ServiceName] = append(srvMidMap[desc.ServiceName], h.Middlewares()...) - - cli := grpcc.New( - &grpcc_config.Cfg{ - Service: &grpcc_config.ServiceCfg{ - Name: h.Proxy().Name, - Addr: h.Proxy().Addr, - Scheme: h.Proxy().Resolver, - }, - }, - grpcc.Params{ - Log: log, - Metric: metric, - }, - h.Middlewares()..., - ) - - for i := range desc.Methods { - var fullPath = fmt.Sprintf("/%s/%s", desc.ServiceName, desc.Methods[i].MethodName) - inT, outT := getMthType(desc.ServiceName, desc.Methods[i].MethodName) - desc.Methods[i].Handler = grpcMethodHandlerWrapper(cli, fullPath, inT, outT) - } - - for i := range desc.Streams { - inT, outT := getMthType(desc.ServiceName, desc.Methods[i].MethodName) - desc.Streams[i].Handler = grpcMethodStreamWrapper(cli, inT, outT) - } - cc.RegisterService(h.ServiceDesc(), h) - } - - cc = cc.WithServerUnaryInterceptor(handlerUnaryMiddle(srvMidMap)) - cc = cc.WithServerStreamInterceptor(handlerStreamMiddle(srvMidMap)) - return &lava.InnerServer{ClientConnInterface: cc} -} - -func grpcMethodHandlerWrapper(cli grpc.ClientConnInterface, fullPath string, inType, outType protoreflect.MessageType) gateway.GrpcMethodHandler { - return func(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) { - var in = inType.New().Interface() - if err := dec(in); err != nil { - return nil, errors.WrapCaller(err) - } - - var h = func(ctx context.Context, req any) (any, error) { - var out = outType.New().Interface() - var header metadata.MD - var trailer metadata.MD - err := cli.Invoke(ctx, fullPath, req, out, append([]grpc.CallOption{}, grpc.Header(&header), grpc.Trailer(&trailer))...) - if err != nil { - return nil, errors.WrapCaller(err) - } - return out, nil - } - - // 获取 server header 并转换成 client header - if interceptor == nil { - return h(ctx, in) - } - - return interceptor(ctx, in, &grpc.UnaryServerInfo{FullMethod: fullPath}, h) - } -} - -func getMthType(srvName string, mthName string) (protoreflect.MessageType, protoreflect.MessageType) { - d := assert.Must1(protoregistry.GlobalFiles.FindDescriptorByName(protoreflect.FullName(srvName))) - - sd, ok := d.(protoreflect.ServiceDescriptor) - if !ok { - assert.Must(errors.Format("invalid httpPathRule descriptor %T", d)) - } - - findMethodDesc := func(methodName string) protoreflect.MethodDescriptor { - md := sd.Methods().ByName(protoreflect.Name(methodName)) - assert.If(md == nil, "missing protobuf descriptor for %v", methodName) - return md - } - - mthDesc := findMethodDesc(mthName) - - inputType := assert.Must1(protoregistry.GlobalTypes.FindMessageByName(mthDesc.Input().FullName())) - outputType := assert.Must1(protoregistry.GlobalTypes.FindMessageByName(mthDesc.Output().FullName())) - return inputType, outputType -} - -func grpcMethodStreamWrapper(cli grpc.ClientConnInterface, inType, outType protoreflect.MessageType) gateway.GrpcStreamHandler { - return gateway.TransparentHandler(cli, inType, outType) -} diff --git a/servers/grpcs/middleware.go b/servers/grpcs/middleware.go index 1136f425d..c759d571a 100644 --- a/servers/grpcs/middleware.go +++ b/servers/grpcs/middleware.go @@ -2,28 +2,29 @@ package grpcs import ( "context" - "fmt" "time" "github.com/gofiber/fiber/v2" grpcMiddle "github.com/grpc-ecosystem/go-grpc-middleware" - "github.com/pubgo/funk/convert" - "github.com/pubgo/funk/errors/errutil" - "github.com/pubgo/funk/log" - "github.com/pubgo/funk/proto/errorpb" - "github.com/pubgo/funk/strutil" - "github.com/pubgo/funk/version" - "github.com/pubgo/lava/core/lavacontexts" - "github.com/pubgo/lava/pkg/proto/lavapbv1" + "github.com/pubgo/funk/v2/buildinfo/version" + "github.com/pubgo/funk/v2/convert" + "github.com/pubgo/funk/v2/errors" + "github.com/pubgo/funk/v2/errors/errcode" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/proto/errorpb" + "github.com/pubgo/funk/v2/strutil" "github.com/rs/xid" "github.com/valyala/fasthttp" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" - "github.com/pubgo/lava/lava" - "github.com/pubgo/lava/pkg/grpcutil" - "github.com/pubgo/lava/pkg/httputil" + "github.com/pubgo/lava/v2/core/lavacontexts" + "github.com/pubgo/lava/v2/lava" + "github.com/pubgo/lava/v2/pkg/grpcutil" + "github.com/pubgo/lava/v2/pkg/httputil" + "github.com/pubgo/lava/v2/pkg/proto/lavapbv1" ) func handlerUnaryMiddle(middlewares map[string][]lava.Middleware) grpc.UnaryServerInterceptor { @@ -36,7 +37,7 @@ func handlerUnaryMiddle(middlewares map[string][]lava.Middleware) grpc.UnaryServ return &rpcResponse{header: req.(*rpcRequest).rspHeader, dt: dt}, nil } - return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { reqMetadata, ok := metadata.FromIncomingContext(ctx) if !ok { reqMetadata = make(metadata.MD) @@ -65,6 +66,11 @@ func handlerUnaryMiddle(middlewares map[string][]lava.Middleware) grpc.UnaryServ clientInfo.Name = p } + // get peer from context + if p, ok := peer.FromContext(ctx); ok { + reqMetadata.Set("remote", p.Addr.String()) + } + // timeout for server deadline to := reqMetadata.Get("timeout") delete(reqMetadata, "timeout") @@ -74,7 +80,7 @@ func handlerUnaryMiddle(middlewares map[string][]lava.Middleware) grpc.UnaryServ if dur, err := time.ParseDuration(to[0]); err == nil { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, dur) - _ = cancel + defer cancel() } } @@ -118,52 +124,34 @@ func handlerUnaryMiddle(middlewares map[string][]lava.Middleware) grpc.UnaryServ reqMetadata.Set(httputil.HeaderXRequestID, reqId) reqMetadata.Set(httputil.HeaderXRequestVersion, version.Version()) reqMetadata.Set(httputil.HeaderXRequestOperation, info.FullMethod) - rpcReq.rspHeader.VisitAll(func(key, value []byte) { + for key, value := range rpcReq.rspHeader.All() { reqMetadata.Set(convert.BtoS(key), convert.BtoS(value)) - }) - - if err := grpc.SetHeader(ctx, reqMetadata); err != nil { - log.Err(err, ctx).Msg("grpc send trailer failed") } if err := grpc.SendHeader(ctx, reqMetadata); err != nil { - log.Err(err, ctx).Msg("grpc send trailer failed") + log.Err(err, ctx). + Str("grpc-method", info.FullMethod). + Msg("grpc send trailer header failed") } - }() ctx = lavacontexts.CreateReqHeader(ctx, reqHeader) ctx = lavacontexts.CreateRspHeader(ctx, rpcReq.rspHeader) rsp, err := lava.Chain(middlewares[srvName]...).Middleware(unaryWrapper)(ctx, rpcReq) if err != nil { - pb := errutil.ParseError(err) - if pb.Trace == nil { - pb.Trace = new(errorpb.ErrTrace) - } - pb.Trace.Operation = rpcReq.Operation() - pb.Trace.Service = rpcReq.Service() - pb.Trace.Version = version.Version() + pb := errcode.ParseError(err) + pb.Details = append(pb.Details, errcode.MustTagsToAny(errors.Tags{"reqHeader": string(rpcReq.Header().Header())})...) - if pb.Msg != nil { - pb.Msg = new(errorpb.ErrMsg) - } - pb.Msg.Msg = err.Error() - pb.Msg.Detail = fmt.Sprintf("%#v", err) - if pb.Msg.Tags == nil { - pb.Msg.Tags = make(map[string]string) + if pb.Message == "" { + pb.Message = err.Error() } - pb.Msg.Tags["reqHeader"] = string(rpcReq.Header().Header()) - if pb.Code.Message == "" { - pb.Code.Message = err.Error() + if pb.Code == 0 { + pb.StatusCode = errorpb.Code_Internal + pb.Code = int32(errcode.GrpcCodeToHTTP(codes.Code(errorpb.Code_Internal))) } - if pb.Code.Code == 0 { - pb.Code.StatusCode = errorpb.Code_Internal - pb.Code.Code = int32(errorpb.Code_Internal) - } - - return nil, errutil.ConvertErr2Status(pb).Err() + return nil, errcode.ConvertErr2Status(pb).Err() } return rsp.(*rpcResponse).dt, nil @@ -173,10 +161,7 @@ func handlerUnaryMiddle(middlewares map[string][]lava.Middleware) grpc.UnaryServ func handlerStreamMiddle(middlewares map[string][]lava.Middleware) grpc.StreamServerInterceptor { streamWrapper := func(ctx context.Context, req lava.Request) (lava.Response, error) { reqCtx := req.(*rpcRequest) - wrap := &grpcMiddle.WrappedServerStream{ - WrappedContext: ctx, - ServerStream: reqCtx.stream, - } + wrap := &grpcMiddle.WrappedServerStream{WrappedContext: ctx, ServerStream: reqCtx.stream} if err := reqCtx.handlerStream(reqCtx.srv, wrap); err != nil { return nil, err } @@ -184,7 +169,7 @@ func handlerStreamMiddle(middlewares map[string][]lava.Middleware) grpc.StreamSe return &rpcResponse{stream: reqCtx.stream, header: new(lava.ResponseHeader)}, nil } - return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + return func(srv any, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { ctx := stream.Context() md, ok := metadata.FromIncomingContext(ctx) if !ok { @@ -244,48 +229,39 @@ func handlerStreamMiddle(middlewares map[string][]lava.Middleware) grpc.StreamSe func() string { return xid.New().String() }, ) rpcReq.Header().Set(httputil.HeaderXRequestID, reqId) - ctx = lavacontexts.CreateCtxWithReqID(ctx, reqId) + ctx = lavacontexts.CreateCtxWithReqID(ctx, reqId) ctx = lavacontexts.CreateReqHeader(ctx, header) ctx = lavacontexts.CreateRspHeader(ctx, rpcReq.rspHeader) rsp, err := lava.Chain(middlewares[srvName]...).Middleware(streamWrapper)(ctx, rpcReq) if err != nil { - pb := errutil.ParseError(err) - pb.Trace.Operation = rpcReq.Operation() - pb.Trace.Service = rpcReq.Service() - pb.Trace.Version = version.Version() - pb.Msg.Msg = err.Error() - pb.Msg.Detail = fmt.Sprintf("%v", err) - if pb.Msg.Tags == nil { - pb.Msg.Tags = make(map[string]string) - } - - if pb.Code.Message == "" { - pb.Code.Message = err.Error() + pb := errcode.ParseError(err) + if pb.Message == "" { + pb.Message = err.Error() } - if pb.Code.Code == 0 { - pb.Code.Code = int32(errorpb.Code_Internal) - pb.Code.StatusCode = errorpb.Code_Internal + if pb.Code == 0 { + pb.StatusCode = errorpb.Code_Internal + pb.Code = int32(errcode.GrpcCodeToHTTP(codes.Code(errorpb.Code_Internal))) } - return errutil.ConvertErr2Status(pb).Err() + return errcode.ConvertErr2Status(pb).Err() } h := rsp.Header() md = make(metadata.MD) - h.VisitAll(func(key, value []byte) { + for key, value := range h.All() { md.Append(convert.BtoS(key), convert.BtoS(value)) - }) - return grpc.SetTrailer(ctx, md) + } + return grpc.SendHeader(ctx, md) } } func handlerHttpMiddle(middlewares []lava.Middleware) func(fbCtx *fiber.Ctx) error { h := func(ctx context.Context, req lava.Request) (lava.Response, error) { - reqCtx := req.(*httpRequest) - reqCtx.ctx.SetUserContext(ctx) - return &httpResponse{ctx: reqCtx.ctx}, reqCtx.ctx.Next() + reqCtx := req.(*httpRequest).ctx + reqCtx.SetUserContext(ctx) + return &httpResponse{ctx: reqCtx}, reqCtx.Next() } h = lava.Chain(middlewares...).Middleware(h) diff --git a/servers/grpcs/request.go b/servers/grpcs/request.go index 272a3a0ee..b7e0d0667 100644 --- a/servers/grpcs/request.go +++ b/servers/grpcs/request.go @@ -2,11 +2,11 @@ package grpcs import ( "fmt" - "strings" "github.com/gofiber/fiber/v2" - "github.com/pubgo/lava/lava" "google.golang.org/grpc" + + "github.com/pubgo/lava/v2/lava" ) var _ lava.Request = (*rpcRequest)(nil) @@ -15,20 +15,20 @@ type rpcRequest struct { handler grpc.UnaryHandler handlerStream grpc.StreamHandler stream grpc.ServerStream - srv interface{} + srv any service string method string url string contentType string header *lava.RequestHeader rspHeader *lava.ResponseHeader - payload interface{} + payload any } -func (r *rpcRequest) Kind() string { return "grpc" } +func (r *rpcRequest) Kind() string { return lava.RequestKindGrpc } func (r *rpcRequest) Client() bool { return false } func (r *rpcRequest) Header() *lava.RequestHeader { return r.header } -func (r *rpcRequest) Payload() interface{} { return r.payload } +func (r *rpcRequest) Payload() any { return r.payload } func (r *rpcRequest) ContentType() string { return r.contentType } func (r *rpcRequest) Service() string { return r.service } func (r *rpcRequest) Operation() string { return r.method } @@ -41,18 +41,14 @@ type httpRequest struct { ctx *fiber.Ctx } -func (r *httpRequest) Kind() string { return "http" } +func (r *httpRequest) Kind() string { return lava.RequestKindHttp } func (r *httpRequest) Operation() string { - return fmt.Sprintf("%s %s", strings.TrimSpace(r.ctx.Method()), strings.TrimSpace(r.ctx.Route().Path)) + return fmt.Sprintf("%s %s", r.ctx.Method(), r.ctx.Route().Path) } func (r *httpRequest) Client() bool { return false } func (r *httpRequest) Header() *lava.RequestHeader { return &r.ctx.Request().Header } -func (r *httpRequest) Payload() interface{} { return r.ctx.Body() } - -func (r *httpRequest) ContentType() string { - return string(r.ctx.Request().Header.ContentType()) -} - -func (r *httpRequest) Service() string { return r.ctx.OriginalURL() } -func (r *httpRequest) Endpoint() string { return string(r.ctx.Request().RequestURI()) } -func (r *httpRequest) Stream() bool { return false } +func (r *httpRequest) Payload() any { return r.ctx.Body() } +func (r *httpRequest) ContentType() string { return string(r.ctx.Request().Header.ContentType()) } +func (r *httpRequest) Service() string { return r.ctx.Route().Path } +func (r *httpRequest) Endpoint() string { return string(r.ctx.Request().RequestURI()) } +func (r *httpRequest) Stream() bool { return r.ctx.Request().IsBodyStream() } diff --git a/servers/grpcs/response.go b/servers/grpcs/response.go index a19297d68..17bae82db 100644 --- a/servers/grpcs/response.go +++ b/servers/grpcs/response.go @@ -2,8 +2,9 @@ package grpcs import ( "github.com/gofiber/fiber/v2" - "github.com/pubgo/lava/lava" "google.golang.org/grpc" + + "github.com/pubgo/lava/v2/lava" ) var _ lava.Response = (*rpcResponse)(nil) @@ -11,11 +12,11 @@ var _ lava.Response = (*rpcResponse)(nil) type rpcResponse struct { stream grpc.ServerStream header *lava.ResponseHeader - dt interface{} + dt any } func (h *rpcResponse) Header() *lava.ResponseHeader { return h.header } -func (h *rpcResponse) Payload() interface{} { return h.dt } +func (h *rpcResponse) Payload() any { return h.dt } func (h *rpcResponse) Stream() bool { return h.stream != nil } var _ lava.Response = (*httpResponse)(nil) @@ -25,5 +26,5 @@ type httpResponse struct { } func (h *httpResponse) Header() *lava.ResponseHeader { return &h.ctx.Response().Header } -func (h *httpResponse) Payload() interface{} { return h.ctx.Response().Body() } -func (h *httpResponse) Stream() bool { return false } +func (h *httpResponse) Payload() any { return h.ctx.Response().Body() } +func (h *httpResponse) Stream() bool { return h.ctx.Response().IsBodyStream() } diff --git a/servers/grpcs/server.go b/servers/grpcs/server.go index 7277fa711..2f3b1fdf9 100644 --- a/servers/grpcs/server.go +++ b/servers/grpcs/server.go @@ -2,117 +2,116 @@ package grpcs import ( "context" - "errors" "fmt" - "io" "net" - "net/http" "net/url" - "path/filepath" "strings" "github.com/fullstorydev/grpchan/inprocgrpc" "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" - "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/async" - "github.com/pubgo/funk/config" - "github.com/pubgo/funk/errors/errutil" - "github.com/pubgo/funk/generic" - "github.com/pubgo/funk/log" - "github.com/pubgo/funk/proto/errorpb" - "github.com/pubgo/funk/recovery" - "github.com/pubgo/funk/running" - "github.com/pubgo/funk/stack" - "github.com/pubgo/funk/vars" - "github.com/pubgo/funk/version" - "github.com/pubgo/lava/clients/grpcc" - "github.com/pubgo/lava/clients/grpcc/grpcc_config" - "github.com/pubgo/lava/core/debug" - "github.com/pubgo/lava/core/lifecycle" - "github.com/pubgo/lava/core/metrics" - "github.com/pubgo/lava/core/signal" - "github.com/pubgo/lava/internal/consts" - "github.com/pubgo/lava/internal/logutil" - "github.com/pubgo/lava/internal/middlewares/middleware_accesslog" - "github.com/pubgo/lava/internal/middlewares/middleware_metric" - "github.com/pubgo/lava/internal/middlewares/middleware_recovery" - "github.com/pubgo/lava/internal/middlewares/middleware_service_info" - "github.com/pubgo/lava/lava" - "github.com/pubgo/lava/pkg/gateway" - "github.com/pubgo/lava/pkg/httputil" - "github.com/pubgo/lava/pkg/wsproxy" - "github.com/rs/xid" + "github.com/pubgo/funk/v2/assert" + "github.com/pubgo/funk/v2/async" + "github.com/pubgo/funk/v2/buildinfo/version" + "github.com/pubgo/funk/v2/config" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/recovery" + "github.com/pubgo/funk/v2/running" + "github.com/pubgo/funk/v2/vars" "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/grpclog" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/encoding/protojson" -) -func New() lava.Service { return newService() } + "github.com/pubgo/lava/v2/clients/grpcc" + "github.com/pubgo/lava/v2/clients/grpcc/grpccconfig" + "github.com/pubgo/lava/v2/core/debug" + "github.com/pubgo/lava/v2/core/metrics" + "github.com/pubgo/lava/v2/core/supervisor" + "github.com/pubgo/lava/v2/internal/logutil" + "github.com/pubgo/lava/v2/internal/middlewares/middleware_accesslog" + "github.com/pubgo/lava/v2/internal/middlewares/middleware_metric" + "github.com/pubgo/lava/v2/internal/middlewares/middleware_recovery" + "github.com/pubgo/lava/v2/internal/middlewares/middleware_serviceinfo" + "github.com/pubgo/lava/v2/lava" + "github.com/pubgo/lava/v2/pkg/gateway" + "github.com/pubgo/lava/v2/pkg/httputil" + "github.com/pubgo/lava/v2/pkg/netutil" +) -func newService() *serviceImpl { - return &serviceImpl{ - cc: new(inprocgrpc.Channel), - } +type Params struct { + GrpcRouters []lava.GrpcRouter + HttpRouters []lava.HttpRouter + GrpcHttpRouters []lava.GrpcHttpRouter + GrpcProxy []lava.GrpcProxy + DixMiddlewares []lava.Middleware + Metric metrics.Metric + Log log.Logger + Conf *Config + Gw []*gateway.Mux } -var _ lava.Service = (*serviceImpl)(nil) +func New(params Params) supervisor.Service { return newService(params) } + +func newService(params Params) supervisor.Service { + s := &serviceImpl{cc: new(inprocgrpc.Channel)} + s.init( + params.GrpcRouters, + params.HttpRouters, + params.GrpcHttpRouters, + params.GrpcProxy, + params.DixMiddlewares, + params.Metric, + params.Log, + params.Conf, + params.Gw, + ) + + return supervisor.NewService("grpc-server", s.Serve) +} type serviceImpl struct { - lc lifecycle.Getter httpServer *fiber.App grpcServer *grpc.Server log log.Logger cc *inprocgrpc.Channel - initList []func() conf *Config } -func (s *serviceImpl) Run() { - defer s.stop() - s.start() - signal.Wait() +func (s *serviceImpl) String() string { + return "grpc-server" } -func (s *serviceImpl) Start() { s.start() } -func (s *serviceImpl) Stop() { s.stop() } +func (s *serviceImpl) Serve(ctx context.Context) error { + defer s.stop(ctx) + err := s.start(ctx) + if err != nil { + return err + } + + <-ctx.Done() + return nil +} -func (s *serviceImpl) DixInject( +func (s *serviceImpl) init( grpcRouters []lava.GrpcRouter, httpRouters []lava.HttpRouter, + grpcHttpRouters []lava.GrpcHttpRouter, grpcProxy []lava.GrpcProxy, dixMiddlewares []lava.Middleware, - getLifecycle lifecycle.Getter, - lifecycle lifecycle.Lifecycle, metric metrics.Metric, log log.Logger, conf *Config, gw []*gateway.Mux, ) { - s.conf = conf - if conf.HttpPort == nil { - conf.HttpPort = generic.Ptr(running.HttpPort) - } - - if conf.GrpcPort == nil { - conf.GrpcPort = generic.Ptr(running.GrpcPort) - } - - if conf.BaseUrl == "" { - conf.BaseUrl = "/" + version.Project() - } - - s.lc = getLifecycle + cfg := httputil.DefaultCfg(&httputil.Config{ + BaseUrl: conf.BaseUrl, + EnablePrintRouter: conf.EnablePrintRouter, + Http: conf.Http, + }) + conf.BaseUrl = cfg.BaseUrl + conf.Http = cfg.Http - conf = config.MergeR(defaultCfg(), conf).Unwrap() - conf.BaseUrl = "/" + strings.Trim(conf.BaseUrl, "/") + s.conf = config.MergeR(defaultCfg(), conf).Unwrap() globalMiddlewares := lava.Middlewares{ - middleware_service_info.New(), + middleware_serviceinfo.New(), middleware_metric.New(metric), middleware_accesslog.New(log), middleware_recovery.New(), @@ -122,195 +121,34 @@ func (s *serviceImpl) DixInject( log = log.WithName("grpc-server") s.log = log - httpServer := fiber.New(fiber.Config{ - EnableIPValidation: true, - EnablePrintRoutes: conf.EnablePrintRoutes, - AppName: version.Project(), - BodyLimit: 500 * 1024 * 1024, - ErrorHandler: func(ctx *fiber.Ctx, err error) error { - if err == nil { - return nil - } - - errPb := errutil.ParseError(err) - if errPb == nil || errPb.Code.Code == 0 { - return nil - } - - errPb.Trace.Operation = ctx.Route().Path - code := errutil.GrpcCodeToHTTP(codes.Code(errPb.Code.StatusCode)) - ctx.Set(fiber.HeaderContentType, fiber.MIMEApplicationJSON) - return ctx.Status(code).JSON(errPb.Code) - }, - }) - - if conf.EnableCors { - httpServer.Use(cors.New(cors.Config{ - AllowOriginsFunc: func(origin string) bool { - return true - }, - AllowMethods: strings.Join([]string{ - fiber.MethodGet, - fiber.MethodPost, - fiber.MethodPut, - fiber.MethodDelete, - fiber.MethodPatch, - fiber.MethodHead, - fiber.MethodOptions, - }, ","), - //AllowHeaders: "", - AllowCredentials: true, - //ExposeHeaders: "", - MaxAge: 0, - })) - } - - app := fiber.New() - app.Group("/debug", httputil.StripPrefix(filepath.Join(conf.BaseUrl, "/debug"), debug.Handler)) - - //app.Use(handlerHttpMiddle(globalMiddlewares)) - for _, h := range httpRouters { - //srv := doc.WithService() - //for _, an := range h.Annotation() { - // switch a := an.(type) { - // case *annotation.Openapi: - // if a.ServiceName != "" { - // srv.SetName(a.ServiceName) - // } - // } - //} - - if h.Prefix() == "" { - panic("http handler prefix is required") - } - - g := app.Group(h.Prefix(), handlerHttpMiddle(append(globalMiddlewares, h.Middlewares()...))) - h.Router(g) + httpServer := fiber.New(conf.Http.Build().Unwrap()) + httpServer.Use(httputil.Cors()) - if m, ok := h.(lava.Close); ok { - lifecycle.BeforeStop(m.Close) - } - - if m, ok := h.(lava.Init); ok { - s.initList = append(s.initList, m.Init) - } - } - - for _, handler := range grpcRouters { - h, ok := handler.(lava.HttpRouter) + for _, h := range grpcRouters { + r, ok := h.(lava.HttpRouter) if !ok { continue } - if h.Prefix() == "" { - panic("http handler prefix is required") - } - - g := app.Group(h.Prefix(), handlerHttpMiddle(append(globalMiddlewares, h.Middlewares()...))) - h.Router(g) + httpRouters = append(httpRouters, r) + } - if m, ok := h.(lava.Close); ok { - lifecycle.BeforeStop(m.Close) + for _, h := range grpcHttpRouters { + if r, ok := h.(lava.HttpRouter); ok { + httpRouters = append(httpRouters, r) } - if m, ok := h.(lava.Init); ok { - s.initList = append(s.initList, m.Init) + if r, ok := h.(lava.GrpcRouter); ok { + grpcRouters = append(grpcRouters, r) } } - httpServer.Mount(conf.BaseUrl, app) + httpApp := fiber.New() + for _, h := range httpRouters { + assert.If(h.Prefix() == "", "http router prefix required") - grpcGateway := runtime.NewServeMux( - runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.HTTPBodyMarshaler{ - Marshaler: &runtime.JSONPb{ - MarshalOptions: protojson.MarshalOptions{ - EmitUnpopulated: true, - }, - UnmarshalOptions: protojson.UnmarshalOptions{ - DiscardUnknown: true, - }, - }, - }), - runtime.SetQueryParameterParser(new(DefaultQueryParser)), - runtime.WithIncomingHeaderMatcher(func(s string) (string, bool) { - return strings.ToLower(s), true - }), - runtime.WithOutgoingHeaderMatcher(func(s string) (string, bool) { - return strings.ToUpper(s), true - }), - runtime.WithMetadata(func(ctx context.Context, request *http.Request) metadata.MD { - path, ok := runtime.HTTPPathPattern(ctx) - if !ok { - return nil - } - return metadata.Pairs("http_path", path, "http_method", request.Method, "http_url", request.URL.Path) - }), - runtime.WithErrorHandler(func(ctx context.Context, mux *runtime.ServeMux, marshal runtime.Marshaler, w http.ResponseWriter, request *http.Request, err error) { - md, ok := runtime.ServerMetadataFromContext(ctx) - if ok && w != nil { - for k, v := range md.HeaderMD { - for i := range v { - w.Header().Add(k, v[i]) - } - } - - for k, v := range md.TrailerMD { - for i := range v { - w.Header().Add(k, v[i]) - } - } - } - - var pb *errorpb.ErrCode - sts, ok := status.FromError(err) - if !ok || sts == nil { - w.Header().Set("Content-Type", "application/json") - pb = &errorpb.ErrCode{ - Message: err.Error(), - StatusCode: errorpb.Code_Internal, - Code: int32(errorpb.Code_Internal), - Name: "lava.grpc.status", - } - } else { - w.Header().Set("Content-Type", marshal.ContentType(sts)) - if len(sts.Details()) > 0 { - if code, ok := sts.Details()[0].(*errorpb.Error); ok { - pb = code.Code - } - } else { - pb = &errorpb.ErrCode{ - Message: sts.Message(), - Code: int32(errorpb.Code(sts.Code())), - StatusCode: errorpb.Code(sts.Code()), - Name: "lava.grpc.status", - Details: sts.Proto().Details, - } - } - } - - const fallback = `{"code":13, "name":"lava.grpc.status", "status_code": 500, "message": "failed to marshal error message"}` - - // skip error - if pb.StatusCode == errorpb.Code_OK { - return - } - - buf, mErr := marshal.Marshal(pb) - if mErr != nil { - grpclog.Infof("Failed to marshal error message %q: %v", pb, mErr) - w.WriteHeader(http.StatusInternalServerError) - if _, err := io.WriteString(w, fallback); err != nil { - grpclog.Infof("Failed to write response: %v", err) - } - return - } - - w.WriteHeader(runtime.HTTPStatusFromCode(codes.Code(pb.StatusCode))) - if _, err := w.Write(buf); err != nil { - grpclog.Infof("Failed to write response: %v", err) - } - }), - ) + h.Router(httpApp.Group(h.Prefix(), handlerHttpMiddle(append(globalMiddlewares, h.Middlewares()...)))) + } mux := gateway.NewMux() if len(gw) > 0 { @@ -320,52 +158,25 @@ func (s *serviceImpl) DixInject( srvMidMap := make(map[string][]lava.Middleware) for _, h := range grpcRouters { desc := h.ServiceDesc() - assert.If(desc == nil, "desc is nil") + assert.If(desc == nil, "service desc is nil") srvMidMap[desc.ServiceName] = append(srvMidMap[desc.ServiceName], globalMiddlewares...) srvMidMap[desc.ServiceName] = append(srvMidMap[desc.ServiceName], h.Middlewares()...) - if m, ok := h.(lava.Close); ok { - lifecycle.BeforeStop(m.Close) - } - - if m, ok := h.(lava.Initializer); ok { - s.initList = append(s.initList, m.Initialize) - } - - if m, ok := h.(lava.Init); ok { - s.initList = append(s.initList, m.Init) - } - mux.RegisterService(desc, h) s.cc.RegisterService(desc, h) - if m, ok := h.(lava.GrpcGatewayRouter); ok { - assert.Exit(m.RegisterGateway(context.Background(), grpcGateway, s.cc)) - } } for _, h := range grpcProxy { desc := h.ServiceDesc() - assert.If(desc == nil, "desc is nil") + assert.If(desc == nil, "service desc is nil") srvMidMap[desc.ServiceName] = append(srvMidMap[desc.ServiceName], globalMiddlewares...) srvMidMap[desc.ServiceName] = append(srvMidMap[desc.ServiceName], h.Middlewares()...) - if m, ok := h.(lava.Close); ok { - lifecycle.BeforeStop(m.Close) - } - - if m, ok := h.(lava.Initializer); ok { - s.initList = append(s.initList, m.Initialize) - } - - if m, ok := h.(lava.Init); ok { - s.initList = append(s.initList, m.Init) - } - cli := grpcc.New( - &grpcc_config.Cfg{ - Service: &grpcc_config.ServiceCfg{ + &grpccconfig.Cfg{ + Service: &grpccconfig.ServiceCfg{ Name: h.Proxy().Name, Addr: h.Proxy().Addr, Scheme: h.Proxy().Resolver, @@ -396,140 +207,93 @@ func (s *serviceImpl) DixInject( grpcServer.RegisterService(h.ServiceDesc(), h) } - apiPrefix1 := assert.Must1(url.JoinPath(conf.BaseUrl, "gw")) - s.log.Info().Str("path", apiPrefix1).Msg("service grpc gateway base path") - httpServer.Group(apiPrefix1, httputil.StripPrefix(apiPrefix1, mux.Handler)) + for _, h := range grpcHttpRouters { + grpcServer.RegisterService(h.ServiceDesc(), h) + } + + for _, h := range grpcProxy { + grpcServer.RegisterService(h.ServiceDesc(), h) + } + + grpcGatewayApiPrefix := assert.Must1(url.JoinPath(conf.BaseUrl, "api")) + s.log.Info().Msgf("service grpc gateway base path: %s", grpcGatewayApiPrefix) + for _, m := range mux.GetRouteMethods() { log.Info(). Str("operation", m.Operation). Any("rpc-meta", mux.GetOperation(m.Operation).Meta). - Str("http-method", m.Method). - Str("http-path", "/"+strings.Trim(apiPrefix1, "/")+m.Path). Str("verb", m.Verb). Any("path-vars", m.Vars). Str("extras", fmt.Sprintf("%v", m.Extras)). - Msg("grpc gateway router info") + Msgf("grpc gateway router info: %s %s", m.Method, "/"+strings.Trim(grpcGatewayApiPrefix, "/")+m.Path) } - apiPrefix := assert.Must1(url.JoinPath(conf.BaseUrl, "api")) - s.log.Info().Str("path", apiPrefix).Msg("service grpc gateway base path") - httpServer.Group(apiPrefix, httputil.HTTPHandler(http.StripPrefix(apiPrefix, wsproxy.WebsocketProxy(grpcGateway, - wsproxy.WithPingPong(conf.EnablePingPong), - wsproxy.WithTimeWait(conf.PingPongTime), - wsproxy.WithReadLimit(int64(generic.FromPtr(conf.WsReadLimit))), - )))) + httpServer.Mount("/debug", debug.App()) + httpServer.Mount(conf.BaseUrl, httpApp) + httpServer.Group(grpcGatewayApiPrefix, httputil.StripPrefix(grpcGatewayApiPrefix, mux.Handler)) s.httpServer = httpServer s.grpcServer = grpcServer - vars.RegisterValue(fmt.Sprintf("%s-grpc-server-config-%s", version.Project(), xid.New()), &conf) - // vars.RegisterValue(fmt.Sprintf("%s-grpc-server-router-%s", version.Project(), xid.New()), mux.App().Stack()) -} - -func (s *serviceImpl) start() { - defer recovery.Exit() - - logutil.OkOrFailed(s.log, "running before service starts", func() error { - defer recovery.Exit() - for _, run := range s.lc.GetBeforeStarts() { - s.log.Info().Msgf("running %s", stack.CallerWithFunc(run.Handler)) - run.Handler() + vars.Register(vars.UniqueName(version.Project(), "grpc-server-info"), func() any { + return map[string]any{ + "config": conf, + "method": mux.GetRouteMethods(), + "desc": grpcServer.GetServiceInfo(), + "router": httpServer.Stack(), } - return nil }) +} - logutil.OkOrFailed(s.log, "init handler before service starts", func() error { - defer recovery.Exit() - for _, ii := range s.initList { - s.log.Info().Msgf("init handler %s", stack.CallerWithFunc(ii)) - ii() - } - return nil - }) +func (s *serviceImpl) start(ctx context.Context) (gErr error) { + defer recovery.Exit() s.log.Info(). - Int("grpc-port", *s.conf.GrpcPort). - Int("http-port", *s.conf.HttpPort). + Int("grpc-port", running.GrpcPort()). + Int("http-port", running.HttpPort()). Msg("create network listener") - grpcLn := assert.Must1(net.Listen("tcp", fmt.Sprintf(":%d", *s.conf.GrpcPort))) - httpLn := assert.Must1(net.Listen("tcp", fmt.Sprintf(":%d", *s.conf.HttpPort))) - - logutil.OkOrFailed(s.log, "service starts", func() error { - // 启动grpc服务 - async.GoDelay(func() error { - s.log.Info().Msg("[grpc] Server Starting") - logutil.LogOrErr(s.log, "[grpc] Server Stop", func() error { - defer recovery.Exit() - if err := s.grpcServer.Serve(grpcLn); err != nil && - !errors.Is(err, http.ErrServerClosed) && - !errors.Is(err, net.ErrClosed) { - return err - } - return nil - }) - return nil - }) - - // 启动grpc网关 - async.GoDelay(func() error { - s.log.Info().Msg("[http] Server Starting") - logutil.LogOrErr(s.log, "[http] Server Stop", func() error { - defer recovery.Exit() - if err := s.httpServer.Listener(httpLn); err != nil && - !errors.Is(err, http.ErrServerClosed) && - !errors.Is(err, net.ErrClosed) { - return err - } - return nil - }) + grpcLn := assert.Exit1(net.Listen("tcp", fmt.Sprintf(":%d", running.GrpcPort()))) + httpLn := assert.Exit1(net.Listen("tcp", fmt.Sprintf(":%d", running.HttpPort()))) + + async.GoDelay(func() error { + s.log.Info().Msg("grpc server starting") + defer recovery.DebugPrint() + err := s.grpcServer.Serve(grpcLn) + if netutil.IsErrServerClosed(err) { return nil - }) - return nil + } + + return err }) - logutil.OkOrFailed(s.log, "running after service starts", func() error { - for _, run := range s.lc.GetAfterStarts() { - logutil.LogOrErr( - s.log, - fmt.Sprintf("running %s", stack.CallerWithFunc(run.Handler)), - func() error { run.Handler(); return nil }, - ) + // 启动grpc网关 + async.GoDelay(func() error { + s.log.Info().Msg("http server starting") + defer recovery.DebugPrint() + err := s.httpServer.Listener(httpLn) + if netutil.IsErrServerClosed(err) { + return nil } - return nil + + return err }) + + return nil } -func (s *serviceImpl) stop() { - defer recovery.Exit() +func (s *serviceImpl) stop(ctx context.Context) { + defer recovery.DebugPrint() - logutil.OkOrFailed(s.log, "running before service stops", func() error { - for _, run := range s.lc.GetBeforeStops() { - logutil.LogOrErr( - s.log, - fmt.Sprintf("running %s", stack.CallerWithFunc(run.Handler)), - func() error { run.Handler(); return nil }, - ) - } - return nil - }) - - logutil.LogOrErr(s.log, "[grpc] Server GracefulStop", func() error { + logutil.LogOrErr(s.log, "grpc server graceful stop", func() error { s.grpcServer.GracefulStop() return nil }) - logutil.LogOrErr(s.log, "[http] Server Shutdown", func() error { - return s.httpServer.ShutdownWithTimeout(consts.DefaultTimeout) - }) - - logutil.OkOrFailed(s.log, "running after service stops", func() error { - for _, run := range s.lc.GetAfterStops() { - logutil.LogOrErr( - s.log, - fmt.Sprintf("running %s", stack.CallerWithFunc(run.Handler)), - func() error { run.Handler(); return nil }, - ) + logutil.LogOrErr(s.log, "http server shutdown", func() error { + err := s.httpServer.ShutdownWithContext(ctx) + if netutil.IsErrServerClosed(err) { + return nil } - return nil + return err }) } diff --git a/servers/grpcs/util.go b/servers/grpcs/util.go index 7e3a640eb..bbfea8353 100644 --- a/servers/grpcs/util.go +++ b/servers/grpcs/util.go @@ -1,12 +1,7 @@ package grpcs import ( - "net/url" "strings" - - "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" - "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" - "google.golang.org/protobuf/proto" ) // serviceFromMethod returns the service @@ -18,23 +13,3 @@ func serviceFromMethod(m string) string { return strings.Split(strings.Trim(m, "/"), "/")[0] } - -type DefaultQueryParser struct{} - -// Parse populates "values" into "msg". -// A value is ignored if its key starts with one of the elements in "filter". -func (*DefaultQueryParser) Parse(msg proto.Message, values url.Values, filter *utilities.DoubleArray) error { - for key, v := range values { - if len(v) == 0 { - delete(values, key) - continue - } - - if len(v) == 1 && v[0] == "" { - delete(values, key) - continue - } - } - - return new(runtime.DefaultQueryParser).Parse(msg, values, filter) -} diff --git a/servers/https/config.go b/servers/https/config.go index 324b2db71..1f562f2fb 100644 --- a/servers/https/config.go +++ b/servers/https/config.go @@ -1,25 +1,11 @@ package https import ( - "github.com/pubgo/funk/version" - "github.com/pubgo/lava/pkg/fiber_builder" + "github.com/pubgo/lava/v2/pkg/httputil" ) -// DefaultMaxBodyBytes is the maximum allowed size of a request body in bytes. -const DefaultMaxBodyBytes = 256 * 1024 +type Config = httputil.Config -type Config struct { - Http *fiber_builder.Config `yaml:"http"` - Ws *fiber_builder.WsCfg `yaml:"ws"` - EnablePrintRouter bool `yaml:"enable_print_router"` - BaseUrl string `yaml:"base_url"` -} - -func DefaultCfg() Config { - return Config{ - Http: &fiber_builder.Config{}, - Ws: &fiber_builder.WsCfg{}, - EnablePrintRouter: true, - BaseUrl: version.Project(), - } +type HttpServerConfigLoader struct { + HttpServer *Config `yaml:"http_server"` } diff --git a/servers/https/httprouter/router.go b/servers/https/httphandler/router.go similarity index 69% rename from servers/https/httprouter/router.go rename to servers/https/httphandler/router.go index 6fc458d1e..2effbd329 100644 --- a/servers/https/httprouter/router.go +++ b/servers/https/httphandler/router.go @@ -1,4 +1,4 @@ -package httprouter +package httphandler import ( "fmt" @@ -12,20 +12,20 @@ type Handler[Req any, Rsp any] func(ctx *fiber.Ctx, req *Req) (rsp *Rsp, err err var validate = validator.New() -func WrapHandler[Req, Rsp any](handle func(ctx *fiber.Ctx, req *Req) (rsp *Rsp, err error)) func(ctx *fiber.Ctx) error { +func WrapHandler[Req, Rsp any](handler Handler[Req, Rsp]) func(ctx *fiber.Ctx) error { return func(ctx *fiber.Ctx) error { var req Req if err := ctx.ParamsParser(&req); err != nil { - return fmt.Errorf("failed to parse params, err:%w", err) + return fmt.Errorf("failed to parse params, params:%v err:%w", ctx.AllParams(), err) } if err := ctx.QueryParser(&req); err != nil { - return fmt.Errorf("failed to parse query, err:%w", err) + return fmt.Errorf("failed to parse query, query:%v err:%w", ctx.Queries(), err) } if err := ctx.ReqHeaderParser(&req); err != nil { - return fmt.Errorf("failed to parse req header, err:%w", err) + return fmt.Errorf("failed to parse header, header:%q err:%w", ctx.GetReqHeaders(), err) } switch ctx.Method() { @@ -39,7 +39,7 @@ func WrapHandler[Req, Rsp any](handle func(ctx *fiber.Ctx, req *Req) (rsp *Rsp, return fmt.Errorf("failed to validate request, err:%w", err) } - rsp, err := handle(ctx, &req) + rsp, err := handler(ctx, &req) if err != nil { return err } diff --git a/servers/https/middleware.go b/servers/https/middleware.go index 8759a21df..1e8b66b9d 100644 --- a/servers/https/middleware.go +++ b/servers/https/middleware.go @@ -2,26 +2,32 @@ package https import ( "context" - "reflect" "github.com/gofiber/fiber/v2" - "github.com/pubgo/lava/lava" + + "github.com/pubgo/lava/v2/lava" ) -var parserTypes []fiber.ParserType +func init() { + fiber.SetParserDecoder(fiber.ParserConfig{ + IgnoreUnknownKeys: true, + ZeroEmpty: true, + }) +} -func RegParser(customType interface{}, converter func(string) reflect.Value) { - parserTypes = append(parserTypes, fiber.ParserType{ - Customtype: customType, - Converter: converter, +func RegParser(parsers []fiber.ParserType) { + fiber.SetParserDecoder(fiber.ParserConfig{ + IgnoreUnknownKeys: true, + ZeroEmpty: true, + ParserType: parsers, }) } func handlerHttpMiddle(middlewares []lava.Middleware) func(fbCtx *fiber.Ctx) error { h := func(ctx context.Context, req lava.Request) (lava.Response, error) { - reqCtx := req.(*httpRequest) - reqCtx.ctx.SetUserContext(ctx) - return &httpResponse{ctx: reqCtx.ctx}, reqCtx.ctx.Next() + reqCtx := req.(*httpRequest).ctx + reqCtx.SetUserContext(ctx) + return &httpResponse{ctx: reqCtx}, reqCtx.Next() } h = lava.Chain(middlewares...).Middleware(h) diff --git a/servers/https/request.go b/servers/https/request.go index b1c5042ba..e1489db61 100644 --- a/servers/https/request.go +++ b/servers/https/request.go @@ -1,9 +1,11 @@ package https import ( + "fmt" + "github.com/gofiber/fiber/v2" - "github.com/pubgo/lava/lava" + "github.com/pubgo/lava/v2/lava" ) var _ lava.Request = (*httpRequest)(nil) @@ -12,16 +14,14 @@ type httpRequest struct { ctx *fiber.Ctx } -func (r *httpRequest) Kind() string { return "http" } -func (r *httpRequest) Operation() string { return r.ctx.Route().Path } +func (r *httpRequest) Kind() string { return lava.RequestKindHttp } +func (r *httpRequest) Operation() string { + return fmt.Sprintf("%s %s", r.ctx.Method(), r.ctx.Route().Path) +} func (r *httpRequest) Client() bool { return false } func (r *httpRequest) Header() *lava.RequestHeader { return &r.ctx.Request().Header } -func (r *httpRequest) Payload() interface{} { return r.ctx.Body() } - -func (r *httpRequest) ContentType() string { - return string(r.ctx.Request().Header.ContentType()) -} - -func (r *httpRequest) Service() string { return r.ctx.OriginalURL() } -func (r *httpRequest) Endpoint() string { return string(r.ctx.Request().RequestURI()) } -func (r *httpRequest) Stream() bool { return false } +func (r *httpRequest) Payload() any { return r.ctx.Body() } +func (r *httpRequest) ContentType() string { return string(r.ctx.Request().Header.ContentType()) } +func (r *httpRequest) Service() string { return r.ctx.Route().Path } +func (r *httpRequest) Endpoint() string { return string(r.ctx.Request().RequestURI()) } +func (r *httpRequest) Stream() bool { return r.ctx.Request().IsBodyStream() } diff --git a/servers/https/response.go b/servers/https/response.go index a32edee0d..eac0a6ff3 100644 --- a/servers/https/response.go +++ b/servers/https/response.go @@ -3,7 +3,7 @@ package https import ( "github.com/gofiber/fiber/v2" - "github.com/pubgo/lava/lava" + "github.com/pubgo/lava/v2/lava" ) var _ lava.Response = (*httpResponse)(nil) @@ -13,5 +13,5 @@ type httpResponse struct { } func (h *httpResponse) Header() *lava.ResponseHeader { return &h.ctx.Response().Header } -func (h *httpResponse) Payload() interface{} { return h.ctx.Response().Body() } +func (h *httpResponse) Payload() any { return h.ctx.Response().Body() } func (h *httpResponse) Stream() bool { return false } diff --git a/servers/https/server.go b/servers/https/server.go index e9eaaeab2..1adc2899a 100644 --- a/servers/https/server.go +++ b/servers/https/server.go @@ -1,45 +1,47 @@ package https import ( - "errors" + "context" "fmt" - "net" - "net/http" - "strings" - "time" "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/async" - "github.com/pubgo/funk/errors/errutil" - "github.com/pubgo/funk/log" - "github.com/pubgo/funk/recovery" - "github.com/pubgo/funk/running" - "github.com/pubgo/funk/stack" - "github.com/pubgo/funk/version" - "github.com/pubgo/opendoc/opendoc" - "github.com/valyala/fasthttp" - "google.golang.org/grpc/codes" - - "github.com/pubgo/lava/core/debug" - "github.com/pubgo/lava/core/lifecycle" - "github.com/pubgo/lava/core/metrics" - "github.com/pubgo/lava/core/signal" - "github.com/pubgo/lava/internal/logutil" - "github.com/pubgo/lava/internal/middlewares/middleware_accesslog" - "github.com/pubgo/lava/internal/middlewares/middleware_metric" - "github.com/pubgo/lava/internal/middlewares/middleware_recovery" - "github.com/pubgo/lava/lava" + "github.com/pubgo/funk/v2/async" + "github.com/pubgo/funk/v2/log" + "github.com/pubgo/funk/v2/recovery" + "github.com/pubgo/funk/v2/running" + "github.com/pubgo/funk/v2/vars" + "github.com/samber/lo" + + "github.com/pubgo/lava/v2/core/debug" + "github.com/pubgo/lava/v2/core/lifecycle" + "github.com/pubgo/lava/v2/core/metrics" + "github.com/pubgo/lava/v2/core/supervisor" + "github.com/pubgo/lava/v2/internal/logutil" + "github.com/pubgo/lava/v2/internal/middlewares/middleware_accesslog" + "github.com/pubgo/lava/v2/internal/middlewares/middleware_metric" + "github.com/pubgo/lava/v2/internal/middlewares/middleware_recovery" + "github.com/pubgo/lava/v2/internal/middlewares/middleware_serviceinfo" + "github.com/pubgo/lava/v2/lava" + "github.com/pubgo/lava/v2/pkg/httputil" + "github.com/pubgo/lava/v2/pkg/netutil" ) -func New() lava.Service { return newService() } - -func newService() *serviceImpl { - return &serviceImpl{} +type Params struct { + Handlers []lava.HttpRouter + Middlewares []lava.Middleware + M metrics.Metric + Log log.Logger + Cfg *Config } -var _ lava.Service = (*serviceImpl)(nil) +func New(params Params) supervisor.Service { return newService(params) } + +func newService(params Params) supervisor.Service { + s := &serviceImpl{} + s.init(params) + + return supervisor.NewService(s.String(), s.Serve) +} type serviceImpl struct { lc lifecycle.Getter @@ -47,183 +49,67 @@ type serviceImpl struct { log log.Logger } -func (s *serviceImpl) Run() { - defer s.stop() - s.start() - signal.Wait() - - fasthttp.AcquireArgs() +func (s *serviceImpl) String() string { return "http-server" } +func (s *serviceImpl) Serve(ctx context.Context) error { + defer s.stop(ctx) + s.start(ctx) + <-ctx.Done() + return nil } -func (s *serviceImpl) Start() { s.start() } -func (s *serviceImpl) Stop() { s.stop() } - -func (s *serviceImpl) DixInject( - handlers []lava.HttpRouter, - middlewares []lava.Middleware, - getLifecycle lifecycle.Getter, - lifecycle lifecycle.Lifecycle, - m metrics.Metric, - log log.Logger, - cfg *Config, - docs []*opendoc.Swagger, -) { - if cfg.BaseUrl == "" { - cfg.BaseUrl = "/" + version.Project() - } - - fiber.SetParserDecoder(fiber.ParserConfig{ - IgnoreUnknownKeys: true, - ZeroEmpty: true, - ParserType: parserTypes, - }) +func (s *serviceImpl) init(params Params) { + cfg := lo.ToPtr(httputil.DefaultCfg(params.Cfg)) - log = log.WithName("http-server") - - s.lc = getLifecycle - s.log = log - - s.httpServer = fiber.New(fiber.Config{ - EnableIPValidation: true, - ETag: true, - ErrorHandler: func(ctx *fiber.Ctx, err error) error { - if err == nil { - return nil - } - - errPb := errutil.ParseError(err) - if errPb == nil || errPb.Code.Code == 0 { - return nil - } - - errPb.Trace.Operation = ctx.Route().Path - code := errutil.GrpcCodeToHTTP(codes.Code(errPb.Code.Code)) - ctx.Set(fiber.HeaderContentType, fiber.MIMEApplicationJSON) - return ctx.Status(code).JSON(errPb) - }, - }) - - app := fiber.New() - app.Use(cors.New(cors.Config{ - AllowOriginsFunc: func(origin string) bool { - return true - }, - AllowOrigins: "*", - AllowMethods: strings.Join([]string{ - fiber.MethodGet, - fiber.MethodPost, - fiber.MethodPut, - fiber.MethodDelete, - fiber.MethodPatch, - fiber.MethodHead, - fiber.MethodOptions, - }, ","), - AllowHeaders: "", - AllowCredentials: true, - ExposeHeaders: "", - MaxAge: 0, - })) + s.log = params.Log.WithName(s.String()) + s.httpServer = fiber.New(cfg.Http.Build().Unwrap()) + s.httpServer.Use(httputil.Cors()) + s.httpServer.Mount("/debug", debug.App()) defaultMiddlewares := []lava.Middleware{ - middleware_metric.New(m), - middleware_accesslog.New(log), + middleware_serviceinfo.New(), + middleware_metric.New(params.M), + middleware_accesslog.New(s.log), middleware_recovery.New(), } - app.Use(handlerHttpMiddle(append(defaultMiddlewares, middlewares...))) - - for _, h := range handlers { - g := app.Group("", handlerHttpMiddle(h.Middlewares())) + middlewares := append(defaultMiddlewares, params.Middlewares...) - //for _, an := range h.Annotation() { - // switch a := an.(type) { - // case *annotation.Openapi: - // if a.ServiceName != "" { - // srv.SetName(a.ServiceName) - // } - // } - //} - - h.Router(g) - - if m, ok := h.(lava.Close); ok { - lifecycle.BeforeStop(m.Close) - } + for _, h := range params.Handlers { + h.Router(s.httpServer.Group(h.Prefix(), handlerHttpMiddle(append(middlewares, h.Middlewares()...)))) } - s.httpServer.Mount("/debug", debug.App()) - s.httpServer.Mount(cfg.BaseUrl, app) - - // 网关初始化 - if cfg.EnablePrintRouter { - for _, stacks := range s.httpServer.Stack() { - for _, route := range stacks { - s.log.Info(). - Str("name", route.Name). - Str("path", route.Path). - Str("method", route.Method). - Msg("service route") - } + vars.Register(vars.UniqueName(running.Project(), "http_server_info"), func() any { + return map[string]any{ + "config": cfg, + "router": s.httpServer.Stack(), } - } + }) } -func (s *serviceImpl) start() { - logutil.OkOrFailed(s.log, "service before-start", func() error { +func (s *serviceImpl) start(ctx context.Context) { + defer recovery.Exit() + + addr := fmt.Sprintf(":%d", running.HttpPort()) + async.GoDelay(func() error { defer recovery.Exit() - for _, run := range s.lc.GetBeforeStarts() { - s.log.Info().Msgf("running %s", stack.CallerWithFunc(run.Handler)) - run.Handler() - } - return nil - }) - httpLn := assert.Must1(net.Listen("tcp", fmt.Sprintf(":%d", running.HttpPort))) - - logutil.OkOrFailed(s.log, "service start", func() error { - async.GoDelay(func() error { - s.log.Info().Msg("[http-server] Server Starting") - logutil.LogOrErr(s.log, "[http-server] Server Stop", func() error { - defer recovery.Exit() - if err := s.httpServer.Listener(httpLn); err != nil && - !errors.Is(err, http.ErrServerClosed) && - !errors.Is(err, net.ErrClosed) { - return err - } - return nil - }) + s.log.Info().Msg("http server starting") + err := s.httpServer.Listen(addr) + if netutil.IsErrServerClosed(err) { return nil - }) - return nil - }) - - logutil.OkOrFailed(s.log, "service after-start", func() error { - defer recovery.Exit() - for _, run := range s.lc.GetAfterStarts() { - s.log.Info().Msgf("running %s", stack.CallerWithFunc(run.Handler)) - run.Handler() } - return nil + + return err }) } -func (s *serviceImpl) stop() { - logutil.OkOrFailed(s.log, "service before-stop", func() error { - for _, run := range s.lc.GetBeforeStops() { - s.log.Info().Msgf("running %s", stack.CallerWithFunc(run.Handler)) - run.Handler() +func (s *serviceImpl) stop(ctx context.Context) { + defer recovery.DebugPrint() + logutil.LogOrErr(s.log, "http server shutdown", func() error { + err := s.httpServer.ShutdownWithContext(ctx) + if netutil.IsErrServerClosed(err) { + return nil } - return nil - }) - logutil.LogOrErr(s.log, "[http-server] Shutdown", func() error { - return s.httpServer.ShutdownWithTimeout(time.Second * 5) - }) - - logutil.OkOrFailed(s.log, "service after-stop", func() error { - for _, run := range s.lc.GetAfterStops() { - s.log.Info().Msgf("running %s", stack.CallerWithFunc(run.Handler)) - run.Handler() - } - return nil + return err }) } diff --git a/servers/tasks/config.go b/servers/tasks/config.go deleted file mode 100644 index 27cc8bec1..000000000 --- a/servers/tasks/config.go +++ /dev/null @@ -1,5 +0,0 @@ -package tasks - -type Config struct { - HttpPort *int `yaml:"http_port"` -} diff --git a/servers/tasks/server.go b/servers/tasks/server.go deleted file mode 100644 index 587f010ad..000000000 --- a/servers/tasks/server.go +++ /dev/null @@ -1,164 +0,0 @@ -package tasks - -import ( - "fmt" - "net" - "net/http" - "time" - - "github.com/gofiber/fiber/v2" - "github.com/pubgo/funk/assert" - "github.com/pubgo/funk/async" - "github.com/pubgo/funk/errors" - "github.com/pubgo/funk/errors/errutil" - "github.com/pubgo/funk/generic" - "github.com/pubgo/funk/log" - "github.com/pubgo/funk/recovery" - "github.com/pubgo/funk/running" - "github.com/pubgo/funk/stack" - "github.com/pubgo/lava/core/debug" - "github.com/pubgo/lava/core/lifecycle" - "github.com/pubgo/lava/core/signal" - "github.com/pubgo/lava/internal/logutil" - "github.com/pubgo/lava/lava" - "google.golang.org/grpc/codes" -) - -func New(srv lava.Service) *Server { - assert.If(srv == nil, "service is nil") - - return &Server{srv: srv} -} - -type Server struct { - srv lava.Service - log log.Logger - lc lifecycle.Getter - httpServer *fiber.App - conf *Config -} - -func (s *Server) Run() { - defer s.stop() - s.start() - signal.Wait() -} - -func (s *Server) DixInject( - getLifecycle lifecycle.Getter, - log log.Logger, - conf []*Config, -) { - s.lc = getLifecycle - s.log = log.WithName("task-server") - - if len(conf) > 0 { - s.conf = conf[0] - } else { - s.conf = &Config{HttpPort: generic.Ptr(running.HttpPort)} - } - - s.httpServer = fiber.New(fiber.Config{ - EnableIPValidation: true, - ETag: true, - ErrorHandler: func(ctx *fiber.Ctx, err error) error { - if err == nil { - return nil - } - - errPb := errutil.ParseError(err) - if errPb == nil || errPb.Code.Code == 0 { - return nil - } - errPb.Trace.Operation = ctx.Route().Path - code := errutil.GrpcCodeToHTTP(codes.Code(errPb.Code.Code)) - ctx.Set(fiber.HeaderContentType, fiber.MIMEApplicationJSON) - return ctx.Status(code).JSON(errPb) - }, - }) - - s.httpServer.Mount("/debug", debug.App()) -} - -func (s *Server) start() { - defer recovery.Exit() - - logutil.OkOrFailed(s.log, "running before service starts", func() error { - defer recovery.Exit() - for _, run := range s.lc.GetBeforeStarts() { - s.log.Info().Msgf("running %s", stack.CallerWithFunc(run.Handler)) - run.Handler() - } - return nil - }) - - httpLn := assert.Must1(net.Listen("tcp", fmt.Sprintf(":%d", generic.DePtr(s.conf.HttpPort)))) - - logutil.OkOrFailed(s.log, "service starts", func() error { - async.GoDelay(func() error { - s.log.Info().Msg("[http-debug-server] Server Starting") - logutil.LogOrErr(s.log, "[http-debug-server] Server Stop", func() error { - defer recovery.Exit() - if err := s.httpServer.Listener(httpLn); err != nil && - !errors.Is(err, http.ErrServerClosed) && - !errors.Is(err, net.ErrClosed) { - return err - } - return nil - }) - return nil - }) - - async.GoSafe(func() error { - s.srv.Start() - return nil - }) - return nil - }) - - logutil.OkOrFailed(s.log, "running after service starts", func() error { - for _, run := range s.lc.GetAfterStarts() { - logutil.LogOrErr( - s.log, - fmt.Sprintf("running %s", stack.CallerWithFunc(run.Handler)), - func() error { run.Handler(); return nil }, - ) - } - return nil - }) -} - -func (s *Server) stop() { - defer recovery.Exit() - - logutil.OkOrFailed(s.log, "running before service stops", func() error { - for _, run := range s.lc.GetBeforeStops() { - logutil.LogOrErr( - s.log, - fmt.Sprintf("running %s", stack.CallerWithFunc(run.Handler)), - func() error { run.Handler(); return nil }, - ) - } - return nil - }) - - logutil.LogOrErr(s.log, "[http-debug-server] Shutdown", func() error { - return s.httpServer.ShutdownWithTimeout(time.Second * 5) - }) - - logutil.LogOrErr(s.log, "[task] Server Stop", func() error { - s.srv.Stop() - return nil - }) - - logutil.OkOrFailed(s.log, "running after service stops", func() error { - for _, run := range s.lc.GetAfterStops() { - logutil.LogOrErr( - s.log, - fmt.Sprintf("running %s", stack.CallerWithFunc(run.Handler)), - func() error { run.Handler(); return nil }, - ) - } - return nil - }) -} diff --git a/servers/wss/_docs.go b/servers/wss/_docs.go deleted file mode 100644 index 01fe4a6df..000000000 --- a/servers/wss/_docs.go +++ /dev/null @@ -1,10 +0,0 @@ -package wss - -// https://github.com/gofiber/contrib/tree/main/websocket -// https://github.com/klauspost/compress -// https://github.com/andybalholm/brotli -// https://github.com/philhofer/fwd -// https://github.com/fasthttp/websocket/tree/master/_examples -// https://github.com/fasthttp/websocket/blob/master/_examples/filewatch/main.go -// https://github.com/tinylib/msgp -// https://github.com/gofiber/contrib/tree/main/socketio diff --git a/servers/wss/aaa.go b/servers/wss/aaa.go deleted file mode 100644 index ddf3048b8..000000000 --- a/servers/wss/aaa.go +++ /dev/null @@ -1,23 +0,0 @@ -package wss - -import ( - "context" - - "github.com/fasthttp/websocket" -) - -// Connection for Websocket Server 2.0 -type Connection interface { - ID() string - Node() string - AuthIdentity() string - Start(ctx context.Context, readMsgHandler func(msgType int, bytes []byte)) error - Write(msg []byte) error - Close() error -} - -// Factory for Connection. -type Factory interface { - // NewConnection for WS Server 2.0. - NewConnection(args ConstructorArgs, conn *websocket.Conn, opts ...Option) Connection -} diff --git a/servers/wss/config.go b/servers/wss/config.go deleted file mode 100644 index c4004307c..000000000 --- a/servers/wss/config.go +++ /dev/null @@ -1 +0,0 @@ -package wss diff --git a/servers/wss/factory.go b/servers/wss/factory.go deleted file mode 100644 index f6541ddc5..000000000 --- a/servers/wss/factory.go +++ /dev/null @@ -1,19 +0,0 @@ -package wss - -import "github.com/fasthttp/websocket" - -// NewFactory for WSConnection. Saves and passes appropriate option. -func NewFactory(opts ...Option) Factory { - return &factory{ - savedOptions: opts, - } -} - -type factory struct { - savedOptions []Option -} - -func (f factory) NewConnection(args ConstructorArgs, conn *websocket.Conn, opts ...Option) Connection { - combinedOptions := append(f.savedOptions, opts...) - return New(args, conn, combinedOptions...) -} diff --git a/servers/wss/option.go b/servers/wss/option.go deleted file mode 100644 index bac2b8006..000000000 --- a/servers/wss/option.go +++ /dev/null @@ -1,63 +0,0 @@ -package wss - -import ( - "time" - - "github.com/pubgo/funk/log" -) - -// configuration of WebsocketConnection. -type configuration struct { - WriteDeadline time.Duration - ReadDeadline *time.Duration - PingInterval time.Duration - MaxInactiveDuration time.Duration -} - -func defaultConfiguration() *configuration { - return &configuration{ - WriteDeadline: 3 * time.Second, - ReadDeadline: nil, - PingInterval: 30 * time.Second, - MaxInactiveDuration: 1 * time.Minute, - } -} - -func (c *configuration) ensureValidated() { - if c.MaxInactiveDuration <= 2*c.PingInterval { - log.Warn().Msgf("websocket max_inactive_duration <= 2 * ping_interval, max_inactive_duration change to 3 * ping_interval") - c.MaxInactiveDuration = 3 * c.PingInterval - } -} - -// Option is the function signature that applies configurable option for UILoadWebsocket. -type Option func(*configuration) - -// ReadDeadline for how long it should wait until reading a message. Default infinite. -func ReadDeadline(d time.Duration) Option { - return func(c *configuration) { - c.ReadDeadline = &d - } -} - -// WriteDeadline for how long it should try before giving up writing a message. Default 3s. -func WriteDeadline(d time.Duration) Option { - return func(c *configuration) { - c.WriteDeadline = d - } -} - -// MaxInactiveDuration means the max time a connection can be 'quiet' before server will deem it as inactive and dead. -// Idea is that pongs and client-based pings would suffice. Default 1m. -func MaxInactiveDuration(d time.Duration) Option { - return func(c *configuration) { - c.MaxInactiveDuration = d - } -} - -// PingInterval sets frequency to write ping messages. Default 30s. -func PingInterval(d time.Duration) Option { - return func(c *configuration) { - c.PingInterval = d - } -} diff --git a/servers/wss/server.go b/servers/wss/server.go deleted file mode 100644 index 405ba9927..000000000 --- a/servers/wss/server.go +++ /dev/null @@ -1,53 +0,0 @@ -package wss - -import ( - "log" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/websocket/v2" -) - -func init() { - app := fiber.New() - - app.Use("/ws", func(c *fiber.Ctx) error { - // IsWebSocketUpgrade returns true if the client - // requested upgrade to the WebSocket protocol. - if websocket.IsWebSocketUpgrade(c) { - c.Locals("allowed", true) - return c.Next() - } - return fiber.ErrUpgradeRequired - }) - - app.Get("/ws/:id", websocket.New(func(c *websocket.Conn) { - // c.Locals is added to the *websocket.Conn - log.Println(c.Locals("allowed")) // true - log.Println(c.Params("id")) // 123 - log.Println(c.Query("v")) // 1.0 - log.Println(c.Cookies("session")) // "" - - // websocket.Conn bindings https://pkg.go.dev/github.com/fasthttp/websocket?tab=doc#pkg-index - var ( - mt int - msg []byte - err error - ) - for { - if mt, msg, err = c.ReadMessage(); err != nil { - log.Println("read:", err) - break - } - log.Printf("recv: %s", msg) - - if err = c.WriteMessage(mt, msg); err != nil { - log.Println("write:", err) - break - } - } - })) - - log.Fatal(app.Listen(":3000")) - // Access the websocket server: ws://localhost:3000/ws/123?v=1.0 - // https://www.websocket.org/echo.html -} diff --git a/servers/wss/wsconnection.go b/servers/wss/wsconnection.go deleted file mode 100644 index 58927c3f4..000000000 --- a/servers/wss/wsconnection.go +++ /dev/null @@ -1,204 +0,0 @@ -package wss - -import ( - "context" - "errors" - "net" - "strings" - "sync" - "time" - - "github.com/fasthttp/websocket" - "github.com/pubgo/funk/log" -) - -// ConstructorArgs for WebsocketConnection -type ConstructorArgs struct { - ID string - Node string - AuthIdentity string -} - -// New will return a new Websocket connection that would bridge to a message queue. For now, underlying connection is stemmed from gorilla.websocket -func New(args ConstructorArgs, conn *websocket.Conn, opts ...Option) Connection { - cfg := defaultConfiguration() - for _, applyOption := range opts { - applyOption(cfg) - } - cfg.ensureValidated() - - return &connection{ - ConstructorArgs: args, - Conn: conn, - configuration: cfg, - closeLock: sync.Mutex{}, - writeLock: sync.Mutex{}, - isClosed: false, - stopConnection: func() {}, - } -} - -type connection struct { - ConstructorArgs - *websocket.Conn - *configuration - closeLock sync.Mutex - writeLock sync.Mutex - isClosed bool - stopConnection func() - log log.Logger -} - -func (c *connection) Write(msg []byte) error { - c.writeLock.Lock() - defer c.writeLock.Unlock() - err := c.Conn.SetWriteDeadline(time.Now().Add(c.WriteDeadline)) - if err != nil { - return err - } - return c.Conn.WriteMessage(websocket.TextMessage, msg) -} - -func (c *connection) ID() string { - return c.ConstructorArgs.ID -} - -func (c *connection) Node() string { - return c.ConstructorArgs.Node -} - -func (c *connection) AuthIdentity() string { - return c.ConstructorArgs.AuthIdentity -} - -func (c *connection) Close() error { - var err error - c.closeLock.Lock() - defer func() { - if err == nil && !c.isClosed { - c.isClosed = true - } - c.closeLock.Unlock() - }() - if c.isClosed { - return nil - } - c.stopConnection() - return c.Conn.Close() -} - -func (c *connection) Start(ctx context.Context, readMsgHandler func(int, []byte)) error { - ctxWithCancel, cancel := context.WithCancel(ctx) - c.stopConnection = cancel - lastMsgCh := make(chan struct{}, 1) - go c.startMonitorForZombieConns(ctxWithCancel, lastMsgCh) - c.Conn.SetPongHandler(func(_ string) error { - c.log.Debug().Msgf("received pong for conn %v", c.ID()) - c.notifyLatestMessage(lastMsgCh) - return nil - }) - c.Conn.SetPingHandler(func(message string) error { - c.log.Debug().Msgf("received ping for conn %v", c.ID()) - c.notifyLatestMessage(lastMsgCh) - err := c.WriteControl(websocket.PongMessage, []byte(message), time.Now().Add(c.WriteDeadline)) - if errors.Is(err, websocket.ErrCloseSent) { - return nil - } - - var e net.Error - if errors.As(err, &e) && e.Timeout() { - return nil - } - return err - }) - - for { - select { - case <-ctxWithCancel.Done(): - return nil - default: - } - var readDeadline time.Time - if c.ReadDeadline != nil { - readDeadline = time.Now().Add(*c.ReadDeadline) - } - err := c.Conn.SetReadDeadline(readDeadline) - if err != nil { - return err - } - msgType, bytes, err := c.Conn.ReadMessage() - if err != nil { - if websocket.IsCloseError(err, websocket.CloseNormalClosure) { - c.log.Err(err).Msgf("failed to read msg for %v", c.ID()) - return nil - } - if websocket.IsUnexpectedCloseError(err) || isClosedConnectionReadErr(err) { - c.log.Err(err).Msgf("failed to read msg for %v", c.ID()) - return nil - } - c.log.Err(err).Msgf("failed to read msg for %v", c.ID()) - return nil - } - c.notifyLatestMessage(lastMsgCh) - readMsgHandler(msgType, bytes) - } -} - -func (c *connection) startMonitorForZombieConns(ctx context.Context, anyMsgReceived <-chan struct{}) { - inactiveCheckTicker := time.NewTicker(c.MaxInactiveDuration) - defer inactiveCheckTicker.Stop() - lastMsgReceived := time.Now() - - pingTicker := time.NewTicker(c.PingInterval) - defer pingTicker.Stop() - - for { - select { - case <-ctx.Done(): - return - case <-pingTicker.C: - timeSinceLastMsg := time.Since(lastMsgReceived) - if timeSinceLastMsg >= c.PingInterval { - err := c.writePing([]byte("")) - if err != nil { - c.log.Err(err).Msgf("failed to write ping msg for conn %v", c.ID()) - } - } else { - c.log.Debug().Msgf("conn %v has received msg before send next ping", c.ID()) - } - continue - case <-inactiveCheckTicker.C: - timeSinceLastMsg := time.Since(lastMsgReceived) - if timeSinceLastMsg > c.MaxInactiveDuration { - c.log.Warn().Msgf("killing connection %v because haven't received any msg [ping, pong, application] in %v seconds", - c.ID(), timeSinceLastMsg.Seconds()) - if err := c.Close(); err != nil { - c.log.Err(err).Msgf("failed to properly close %v", c.ID()) - } - return - } - continue - - case <-anyMsgReceived: - lastMsgReceived = time.Now() - pingTicker.Reset(c.PingInterval) - continue - } - } -} - -func (c *connection) writePing(msg []byte) error { - return c.Conn.WriteControl(websocket.PingMessage, msg, time.Now().Add(c.WriteDeadline)) -} - -func (c *connection) notifyLatestMessage(ch chan<- struct{}) { - select { - case ch <- struct{}{}: - default: - c.log.Warn().Msgf("notify latest msg channel blocked for connection %v", c.ID()) - } -} - -func isClosedConnectionReadErr(err error) bool { - return strings.Contains(err.Error(), "use of closed network connection") -} diff --git a/taskfile.yml b/taskfile.yml new file mode 100644 index 000000000..3ad168217 --- /dev/null +++ b/taskfile.yml @@ -0,0 +1,71 @@ +# https://taskfile.dev + +version: '3' + +dotenv: [ '.env' ] +vars: + Project: "lava" + Base: "github.com/pubgo/funk/v2" + VERSION: + sh: "git tag --sort=committerdate | tail -n 1" + BUILD_TIME: + sh: "date -u '+%Y-%m-%d %I:%M:%S %Z'" + GIT_COMMIT: + sh: "git rev-parse --short=8 HEAD" + BRANCH_NAME: + sh: "git branch ch --show-current" + GIT_VERSION: + sh: "git describe --always --abbrev=7 --dirty" + +includes: + scheduler: ./internal/examples/scheduler + +tasks: + default: + desc: "default task" + cmds: + - task -a + + info: + cmds: + - echo "Project {{.Project}}" + - echo "VERSION {{.VERSION}}" + - echo "GIT_VERSION {{.GIT_VERSION}}" + silent: true + + tools: + cmds: + - go install github.com/cortesi/modd/cmd/modd@latest + - go install mvdan.cc/sh/v3/cmd/shfmt@latest + - go install mvdan.cc/sh/v3/cmd/gosh@latest + - go install github.com/DarthSim/overmind/v2@latest + - go install github.com/a-h/templ/cmd/templ@latest + - go install golang.org/x/tools/cmd/goimports@latest + + generate: + cmds: + - go generate ./internal/... + + proto:fmt: + cmds: + - protobuild format + + proto:gen: + cmds: + - protobuild vendor + - protobuild gen + + proto:lint: + cmds: + - protobuild lint + + vet: + desc: "go vet" + cmds: + - go vet ./... + test: + cmds: + - go test -short -race -v ./... -cover + lint: + cmds: + - golangci-lint run --verbose ./... diff --git a/tools/_doc.go b/tools/_doc.go index 596c8c265..16c5d3e26 100644 --- a/tools/_doc.go +++ b/tools/_doc.go @@ -29,3 +29,8 @@ package tools // CPU feature identification for Go // https://github.com/klauspost/cpuid + +// Run command periodically and expose latest STDOUT as HTTP endpoint +// https://github.com/nikolaydubina/watchhttp/tree/master +// https://github.com/nikolaydubina/htmljson +// https://github.com/nikolaydubina/treemap/tree/main diff --git a/tools/fileserver/main.go b/tools/fileserver/main.go new file mode 100644 index 000000000..378f39a20 --- /dev/null +++ b/tools/fileserver/main.go @@ -0,0 +1,41 @@ +package main + +import ( + "flag" + "fmt" + "os" + + "github.com/pubgo/funk/v2/log" + "github.com/samber/lo" + "github.com/valyala/fasthttp" +) + +var port = flag.Int("port", 8080, "http port") + +func main() { + flag.Parse() + + wd := lo.Must1(os.Getwd()) + if len(os.Args) > 0 { + wd = flag.Arg(1) + } + + log.Info().Msgf("file dir: %s", wd) + log.Info().Msgf("http://localhost:%v", lo.FromPtr(port)) + + fs := &fasthttp.FS{ + Root: wd, + IndexNames: []string{"index.html"}, + GenerateIndexPages: true, + Compress: false, + AcceptByteRange: true, + // PathRewrite: fasthttp.NewVHostPathRewriter(0), + } + + s := &fasthttp.Server{ + Handler: fs.NewRequestHandler(), + Logger: log.NewStd(log.GetLogger("fileserver")), + } + + fmt.Println(s.ListenAndServe(fmt.Sprintf(":%v", lo.FromPtr(port)))) +} diff --git a/tools/tools.go b/tools/tools.go index 669fca575..bbfe2c6fe 100644 --- a/tools/tools.go +++ b/tools/tools.go @@ -6,7 +6,6 @@ package tools import ( _ "github.com/ecordell/optgen" _ "github.com/golangci/golangci-lint/cmd/golangci-lint" - _ "github.com/pubgo/protobuild/pkg/retag" _ "golang.org/x/tools/cmd/stringer" _ "golang.org/x/vuln/cmd/govulncheck" )