Compare commits
57 Commits
9fd0eaadb8
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e95976a4db | ||
|
|
e496e76ee7 | ||
|
|
9560131870 | ||
|
|
9b20ffc450 | ||
|
|
cdee211a66 | ||
|
|
7b2f9f90a6 | ||
|
|
18462953ef | ||
|
|
570493a424 | ||
|
|
c1faeac608 | ||
|
|
dd09c6a7be | ||
|
|
8413ccd3ba | ||
|
|
521184c19c | ||
|
|
8806b49e8f | ||
|
|
2b704cc700 | ||
|
|
4553fc8466 | ||
|
|
07ea247096 | ||
|
|
10e037243f | ||
|
|
bd62ff8235 | ||
|
|
22ba1c8046 | ||
|
|
e69a987535 | ||
|
|
c27804489b | ||
|
|
7ac8fb1f14 | ||
|
|
af5fef67db | ||
|
|
43799a9841 | ||
|
|
ea6896cf05 | ||
|
|
898d7144f1 | ||
|
|
866b2cf1ed | ||
|
|
14afd1bc4d | ||
|
|
5e26ddfbcd | ||
|
|
1bf5b0eec4 | ||
|
|
9333af16eb | ||
|
|
c0852d839f | ||
|
|
510629a424 | ||
|
|
95880712b8 | ||
|
|
0797ab1b4b | ||
|
|
781f79cf7c | ||
|
|
a545116f52 | ||
|
|
4ced8ae869 | ||
|
|
1eb10eb1af | ||
|
|
063fc82338 | ||
|
|
9093bf2b79 | ||
|
|
eb6557e0d8 | ||
|
|
7f65f040c4 | ||
|
|
dbb113812d | ||
|
|
ab4043f28f | ||
|
|
89708b5201 | ||
|
|
c255a257c3 | ||
|
|
86b2a17868 | ||
|
|
7ead69e1b6 | ||
|
|
1cb7943903 | ||
|
|
27ccc2a080 | ||
|
|
e42de5e171 | ||
|
|
bc2b92c73b | ||
|
|
e69b0a4756 | ||
|
|
db3d256bd7 | ||
|
|
4d51d0a1d0 | ||
|
|
b876f0bfa3 |
45
common.go
45
common.go
@@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"git.makemake.in/kzkzzzz/mycommon/mylog"
|
"git.makemake.in/kzkzzzz/mycommon/mylog"
|
||||||
"log"
|
"log"
|
||||||
|
"math/rand/v2"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
@@ -26,6 +27,15 @@ func SafeGo(fn func()) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RandRange [min, max] 左右均包含
|
||||||
|
func RandRange(min, max int) int {
|
||||||
|
return rand.IntN(max+1-min) + min
|
||||||
|
}
|
||||||
|
|
||||||
|
func RandRangeFloat(min, max float64) float64 {
|
||||||
|
return min + rand.Float64()*(max-min+1e-10)
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
getIpOnce = &sync.Once{}
|
getIpOnce = &sync.Once{}
|
||||||
outBoundIp = "127.0.0.1"
|
outBoundIp = "127.0.0.1"
|
||||||
@@ -84,3 +94,38 @@ func ExistFile(file string) bool {
|
|||||||
// 其他错误
|
// 其他错误
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UniqueSlice[T comparable](rawSlice []T) []T {
|
||||||
|
if len(rawSlice) <= 1 {
|
||||||
|
return rawSlice
|
||||||
|
}
|
||||||
|
|
||||||
|
uniqueSlice := make([]T, 0, len(rawSlice))
|
||||||
|
has := make(map[T]bool, len(rawSlice))
|
||||||
|
for _, v := range rawSlice {
|
||||||
|
if !has[v] {
|
||||||
|
uniqueSlice = append(uniqueSlice, v)
|
||||||
|
has[v] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uniqueSlice
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ptr 获取类型指针
|
||||||
|
func Ptr[T any](v T) *T {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringPtr(v string) *string {
|
||||||
|
if v == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringVal(v *string) string {
|
||||||
|
if v == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return *v
|
||||||
|
}
|
||||||
|
|||||||
42
go.mod
42
go.mod
@@ -1,6 +1,6 @@
|
|||||||
module git.makemake.in/kzkzzzz/mycommon
|
module git.makemake.in/kzkzzzz/mycommon
|
||||||
|
|
||||||
go 1.23.0
|
go 1.25.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-contrib/pprof v1.5.2
|
github.com/gin-contrib/pprof v1.5.2
|
||||||
@@ -10,9 +10,10 @@ require (
|
|||||||
github.com/go-playground/universal-translator v0.18.1
|
github.com/go-playground/universal-translator v0.18.1
|
||||||
github.com/go-playground/validator/v10 v10.25.0
|
github.com/go-playground/validator/v10 v10.25.0
|
||||||
github.com/go-sql-driver/mysql v1.7.0
|
github.com/go-sql-driver/mysql v1.7.0
|
||||||
|
github.com/goccy/go-json v0.10.5
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/hashicorp/consul/api v1.28.2
|
github.com/hashicorp/consul/api v1.28.2
|
||||||
github.com/jpillora/backoff v1.0.0
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/knadh/koanf/parsers/json v0.1.0
|
github.com/knadh/koanf/parsers/json v0.1.0
|
||||||
github.com/knadh/koanf/parsers/toml v0.1.0
|
github.com/knadh/koanf/parsers/toml v0.1.0
|
||||||
github.com/knadh/koanf/parsers/yaml v0.1.0
|
github.com/knadh/koanf/parsers/yaml v0.1.0
|
||||||
@@ -20,34 +21,41 @@ require (
|
|||||||
github.com/knadh/koanf/providers/file v1.1.2
|
github.com/knadh/koanf/providers/file v1.1.2
|
||||||
github.com/knadh/koanf/providers/posflag v0.1.0
|
github.com/knadh/koanf/providers/posflag v0.1.0
|
||||||
github.com/knadh/koanf/v2 v2.1.2
|
github.com/knadh/koanf/v2 v2.1.2
|
||||||
|
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260226062615-1a8d01d9679e
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
|
github.com/prometheus/client_golang v1.11.1
|
||||||
github.com/redis/go-redis/v9 v9.7.3
|
github.com/redis/go-redis/v9 v9.7.3
|
||||||
github.com/rs/xid v1.6.0
|
github.com/rs/xid v1.6.0
|
||||||
github.com/spf13/cast v1.6.0
|
github.com/spf13/cast v1.6.0
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/spf13/viper v1.19.0
|
github.com/spf13/viper v1.19.0
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.11.1
|
||||||
|
github.com/valyala/fasthttp v1.66.0
|
||||||
go.uber.org/zap v1.24.0
|
go.uber.org/zap v1.24.0
|
||||||
golang.org/x/sync v0.12.0
|
golang.org/x/sync v0.17.0
|
||||||
google.golang.org/grpc v1.67.1
|
golang.org/x/time v0.5.0
|
||||||
|
google.golang.org/grpc v1.75.0
|
||||||
gorm.io/driver/mysql v1.5.7
|
gorm.io/driver/mysql v1.5.7
|
||||||
gorm.io/gorm v1.25.12
|
gorm.io/gorm v1.25.12
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||||
github.com/armon/go-metrics v0.4.1 // indirect
|
github.com/armon/go-metrics v0.4.1 // indirect
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bytedance/sonic v1.13.2 // indirect
|
github.com/bytedance/sonic v1.13.2 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/fatih/color v1.15.0 // indirect
|
github.com/fatih/color v1.16.0 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
github.com/gin-contrib/sse v1.0.0 // indirect
|
github.com/gin-contrib/sse v1.0.0 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
|
github.com/hashicorp/consul/sdk v0.16.2 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-hclog v1.5.0 // indirect
|
github.com/hashicorp/go-hclog v1.5.0 // indirect
|
||||||
@@ -60,13 +68,14 @@ require (
|
|||||||
github.com/hashicorp/serf v0.10.1 // indirect
|
github.com/hashicorp/serf v0.10.1 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||||
github.com/knadh/koanf/maps v0.1.1 // indirect
|
github.com/knadh/koanf/maps v0.1.1 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
@@ -76,6 +85,9 @@ require (
|
|||||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
|
github.com/prometheus/client_model v0.2.0 // indirect
|
||||||
|
github.com/prometheus/common v0.26.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.6.0 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
@@ -83,16 +95,18 @@ require (
|
|||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
go.uber.org/atomic v1.10.0 // indirect
|
go.uber.org/atomic v1.10.0 // indirect
|
||||||
go.uber.org/multierr v1.9.0 // indirect
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
golang.org/x/arch v0.15.0 // indirect
|
golang.org/x/arch v0.15.0 // indirect
|
||||||
golang.org/x/crypto v0.36.0 // indirect
|
golang.org/x/crypto v0.42.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||||
golang.org/x/net v0.37.0 // indirect
|
golang.org/x/net v0.44.0 // indirect
|
||||||
golang.org/x/sys v0.31.0 // indirect
|
golang.org/x/sys v0.36.0 // indirect
|
||||||
golang.org/x/text v0.23.0 // indirect
|
golang.org/x/text v0.29.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
|
||||||
google.golang.org/protobuf v1.36.5 // indirect
|
google.golang.org/protobuf v1.36.6 // indirect
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
|
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|||||||
144
go.sum
144
go.sum
@@ -1,8 +1,12 @@
|
|||||||
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
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/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-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-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||||
|
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||||
|
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||||
github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
|
github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
|
||||||
@@ -13,6 +17,7 @@ github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLj
|
|||||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
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 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.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/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
@@ -41,8 +46,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu
|
|||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
@@ -57,8 +62,14 @@ github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
|||||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
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/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.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.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.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-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
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/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/form v3.1.4+incompatible h1:lvKiHVxE2WvzDIoyMnWcjyiBxKt2+uFJyZcPYWsLnjI=
|
github.com/go-playground/form v3.1.4+incompatible h1:lvKiHVxE2WvzDIoyMnWcjyiBxKt2+uFJyZcPYWsLnjI=
|
||||||
@@ -80,20 +91,32 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
|
|||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
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.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.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
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.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
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/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/hashicorp/consul/api v1.28.2 h1:mXfkRHrpHN4YY3RqL09nXU1eHKLNiuAN4kHvDQ16k/8=
|
github.com/hashicorp/consul/api v1.28.2 h1:mXfkRHrpHN4YY3RqL09nXU1eHKLNiuAN4kHvDQ16k/8=
|
||||||
github.com/hashicorp/consul/api v1.28.2/go.mod h1:KyzqzgMEya+IZPcD65YFoOVAgPpbfERu4I/tzG6/ueE=
|
github.com/hashicorp/consul/api v1.28.2/go.mod h1:KyzqzgMEya+IZPcD65YFoOVAgPpbfERu4I/tzG6/ueE=
|
||||||
github.com/hashicorp/consul/sdk v0.16.0 h1:SE9m0W6DEfgIVCJX7xU+iv/hUl4m/nxqMTnCdMxDpJ8=
|
github.com/hashicorp/consul/sdk v0.16.2 h1:cGX/djeEe9r087ARiKVWwVWCF64J+yW0G6ftZMZYbj0=
|
||||||
github.com/hashicorp/consul/sdk v0.16.0/go.mod h1:7pxqqhqoaPqnBnzXD1StKed62LqJeClzVsUEy85Zr0A=
|
github.com/hashicorp/consul/sdk v0.16.2/go.mod h1:onxcZjYVsPx5XMveAC/OtoIsdr32fykB7INFltDoRE8=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
@@ -140,13 +163,17 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
|||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
|
||||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
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.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
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.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||||
|
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/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
@@ -168,8 +195,10 @@ github.com/knadh/koanf/v2 v2.1.2 h1:I2rtLRqXRy1p01m/utEtpZSSA6dcJbgGVuE27kW2PzQ=
|
|||||||
github.com/knadh/koanf/v2 v2.1.2/go.mod h1:Gphfaen0q1Fc1HTgJgSTC4oRX9R2R5ErYMZJy8fLJBo=
|
github.com/knadh/koanf/v2 v2.1.2/go.mod h1:Gphfaen0q1Fc1HTgJgSTC4oRX9R2R5ErYMZJy8fLJBo=
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
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.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/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.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
@@ -178,6 +207,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
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/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260226062615-1a8d01d9679e h1:1+rVed4OnYgQCg934wWHms6J7aEbyQ7TaR/fSYhJmLA=
|
||||||
|
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20260226062615-1a8d01d9679e/go.mod h1:+mNMTBuDMdEGhWzoQgc6kBdqeaQpWh5ba8zqmp2MxCU=
|
||||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
@@ -195,6 +226,7 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
|
|||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
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-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||||
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
|
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
|
||||||
@@ -217,6 +249,7 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
|
|||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
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/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
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/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
||||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
@@ -236,18 +269,28 @@ github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSg
|
|||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
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.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||||
|
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||||
|
github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s=
|
||||||
|
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
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/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
||||||
|
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||||
|
github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
|
||||||
|
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
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.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||||
|
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||||
|
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
|
||||||
|
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
|
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
|
||||||
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
|
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
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/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
@@ -259,6 +302,7 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUt
|
|||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
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.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
|
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||||
@@ -284,8 +328,9 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F
|
|||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
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/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||||
@@ -293,6 +338,24 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
|||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
|
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.66.0 h1:M87A0Z7EayeyNaV6pfO3tUTUiYO0dZfEJnRGXTVNuyU=
|
||||||
|
github.com/valyala/fasthttp v1.66.0/go.mod h1:Y4eC+zwoocmXSVCB1JmhNbYtS7tZPRI2ztPB72EVObs=
|
||||||
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
|
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/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/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.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||||
@@ -306,70 +369,96 @@ golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
|
|||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||||
|
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/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-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-20190404232315-eb5bcb51f2a3/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-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-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-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/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-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-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-20190911185100-cd5d95a43a6e/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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/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-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-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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||||
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY=
|
||||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
|
||||||
|
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||||
|
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=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
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/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
|
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
|
||||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
@@ -378,6 +467,7 @@ 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.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.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.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -225,6 +225,18 @@ func (c *Config) Get(key string) any {
|
|||||||
return c.kof.Get(key)
|
return c.kof.Get(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Config) Exists(key string) bool {
|
||||||
|
c.RLock()
|
||||||
|
defer c.RUnLock()
|
||||||
|
return c.kof.Exists(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) GetSlices(key string) []*koanf.Koanf {
|
||||||
|
c.RLock()
|
||||||
|
defer c.RUnLock()
|
||||||
|
return c.kof.Slices(key)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Config) GetString(key string) string {
|
func (c *Config) GetString(key string) string {
|
||||||
c.RLock()
|
c.RLock()
|
||||||
defer c.RUnLock()
|
defer c.RUnLock()
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
ServicePrefix = "grpc@"
|
ServicePrefix = "grpc@"
|
||||||
|
ExposeHttpTag = "expose-http"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"google.golang.org/grpc/health"
|
"google.golang.org/grpc/health"
|
||||||
"google.golang.org/grpc/health/grpc_health_v1"
|
"google.golang.org/grpc/health/grpc_health_v1"
|
||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
"google.golang.org/grpc/reflection"
|
"google.golang.org/grpc/reflection"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
"log"
|
"log"
|
||||||
@@ -39,6 +40,7 @@ type Opt func(server *Server)
|
|||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
gs *grpc.Server
|
gs *grpc.Server
|
||||||
|
serviceAddr string
|
||||||
serviceId string
|
serviceId string
|
||||||
serviceName string
|
serviceName string
|
||||||
serverConf *Conf
|
serverConf *Conf
|
||||||
@@ -51,6 +53,7 @@ type Server struct {
|
|||||||
|
|
||||||
useDefaultBufferCfg bool
|
useDefaultBufferCfg bool
|
||||||
delayStopMs int
|
delayStopMs int
|
||||||
|
exposeHttp bool
|
||||||
|
|
||||||
serviceRegInfo *myregistry.ServiceInfo
|
serviceRegInfo *myregistry.ServiceInfo
|
||||||
}
|
}
|
||||||
@@ -80,6 +83,12 @@ func WithDelayStopMs(v int) Opt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithExposeHttp(v bool) Opt {
|
||||||
|
return func(server *Server) {
|
||||||
|
server.exposeHttp = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func SetFlag() {
|
func SetFlag() {
|
||||||
pflag.Int("grpc.port", 0, "listen port, 0 is random port")
|
pflag.Int("grpc.port", 0, "listen port, 0 is random port")
|
||||||
pflag.String("grpc.log", "true", "enable request log")
|
pflag.String("grpc.log", "true", "enable request log")
|
||||||
@@ -204,6 +213,11 @@ func (s *Server) Run(ctx context.Context) error {
|
|||||||
ServiceName: s.serviceName,
|
ServiceName: s.serviceName,
|
||||||
Ip: svcIp,
|
Ip: svcIp,
|
||||||
Port: port,
|
Port: port,
|
||||||
|
Extend: map[string]string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.exposeHttp {
|
||||||
|
s.serviceRegInfo.Extend["tag"] = mygrpc.ExposeHttpTag
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.reg.Register(s.serviceRegInfo)
|
err = s.reg.Register(s.serviceRegInfo)
|
||||||
@@ -221,6 +235,8 @@ func (s *Server) Run(ctx context.Context) error {
|
|||||||
addr := fmt.Sprintf("%s:%d", s.serverConf.Addr, port)
|
addr := fmt.Sprintf("%s:%d", s.serverConf.Addr, port)
|
||||||
log.Printf("grpc server listen on %s", addr)
|
log.Printf("grpc server listen on %s", addr)
|
||||||
|
|
||||||
|
s.serviceAddr = fmt.Sprintf("%s:%d", svcIp, port)
|
||||||
|
|
||||||
err = s.gs.Serve(lis)
|
err = s.gs.Serve(lis)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("start grpc server err: %s", err)
|
log.Printf("start grpc server err: %s", err)
|
||||||
@@ -297,6 +313,14 @@ func (s *Server) requestLog() grpc.UnaryServerInterceptor {
|
|||||||
|
|
||||||
func (s *Server) grpcRecover() grpc.UnaryServerInterceptor {
|
func (s *Server) grpcRecover() grpc.UnaryServerInterceptor {
|
||||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
|
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
|
||||||
|
md := metadata.New(map[string]string{
|
||||||
|
"service-host": mycommon.GetHostName(),
|
||||||
|
"service-addr": s.serviceAddr,
|
||||||
|
"service-name": s.serviceName,
|
||||||
|
"service-method": info.FullMethod,
|
||||||
|
})
|
||||||
|
grpc.SetHeader(ctx, md)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err0 := recover(); err0 != nil {
|
if err0 := recover(); err0 != nil {
|
||||||
log.Printf("%s - panic: %v\n%s", info.FullMethod, err0, debug.Stack())
|
log.Printf("%s - panic: %v\n%s", info.FullMethod, err0, debug.Stack())
|
||||||
@@ -304,7 +328,12 @@ func (s *Server) grpcRecover() grpc.UnaryServerInterceptor {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return handler(ctx, req)
|
res, err := handler(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
// Package roundrobin defines a roundrobin balancer. Roundrobin balancer is
|
// Package roundrobin defines a roundrobin balancer. Roundrobin balancer is
|
||||||
// installed as one of the default balancers in gRPC, users don't need to
|
// installed as one of the default balancers in gRPC, users don't need to
|
||||||
// explicitly install this balancer.
|
// explicitly install this balancer.
|
||||||
package random
|
package random_balancer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"google.golang.org/grpc/balancer"
|
"google.golang.org/grpc/balancer"
|
||||||
19
myhttp/fasthttpc/const.go
Normal file
19
myhttp/fasthttpc/const.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package fasthttpc
|
||||||
|
|
||||||
|
const (
|
||||||
|
HeaderUserAgent = "User-Agent"
|
||||||
|
HeaderContentType = "Content-Type"
|
||||||
|
HeaderCookie = "Cookie"
|
||||||
|
HeaderAccept = "Accept"
|
||||||
|
HeaderOrigin = "Origin"
|
||||||
|
HeaderReferer = "Referer"
|
||||||
|
HeaderAcceptLanguage = "Accept-Language"
|
||||||
|
|
||||||
|
ContentTypeJSON = "application/json; charset=utf-8"
|
||||||
|
)
|
||||||
|
const (
|
||||||
|
MobileUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/124.0.6367.68 MobileRequest/15E148 Safari/604.1"
|
||||||
|
PcUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
|
||||||
|
AcceptHtml = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
|
||||||
|
AcceptCNLanguage = "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7"
|
||||||
|
)
|
||||||
381
myhttp/fasthttpc/httpclient.go
Normal file
381
myhttp/fasthttpc/httpclient.go
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
package fasthttpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultClient *HttpClient
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
defaultClient = New()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReInitDefault(timeout time.Duration) {
|
||||||
|
defaultClient = New(WithTimout(timeout))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReInitDefaultOpt(opts ...ConfigOpt) {
|
||||||
|
defaultClient = New(opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Client() *HttpClient {
|
||||||
|
return defaultClient
|
||||||
|
}
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
ctx context.Context
|
||||||
|
header http.Header
|
||||||
|
body any
|
||||||
|
mapQuery map[string]string
|
||||||
|
urlQuery url.Values
|
||||||
|
httpClient *HttpClient
|
||||||
|
contentType string
|
||||||
|
noWaitQps bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type HttpClient struct {
|
||||||
|
config *Config
|
||||||
|
client *fasthttp.Client
|
||||||
|
qpsLimiter *rate.Limiter
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(opts ...ConfigOpt) *HttpClient {
|
||||||
|
config := &Config{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.timeout <= 0 {
|
||||||
|
config.timeout = time.Second * 3
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.fasthttpTimeout <= 0 {
|
||||||
|
config.fasthttpTimeout = time.Second * 10
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.maxConnPerHost <= 0 {
|
||||||
|
config.maxConnPerHost = 10000
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.client == nil {
|
||||||
|
|
||||||
|
client := &fasthttp.Client{
|
||||||
|
TLSConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
DialTimeout: (&fasthttp.TCPDialer{
|
||||||
|
Concurrency: 0,
|
||||||
|
DNSCacheDuration: time.Second * 600,
|
||||||
|
}).DialTimeout,
|
||||||
|
MaxConnsPerHost: config.maxConnPerHost,
|
||||||
|
MaxIdleConnDuration: time.Second * 90,
|
||||||
|
MaxIdemponentCallAttempts: 5,
|
||||||
|
ReadTimeout: config.fasthttpTimeout + time.Second,
|
||||||
|
MaxConnWaitTimeout: 0,
|
||||||
|
RetryIfErr: func(req *fasthttp.Request, attempts int, err error) (bool, bool) {
|
||||||
|
if errors.Is(err, fasthttp.ErrConnectionClosed) || errors.Is(err, io.EOF) {
|
||||||
|
return false, true
|
||||||
|
}
|
||||||
|
return false, false
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
config.client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
hc := &HttpClient{
|
||||||
|
config: config,
|
||||||
|
client: config.client,
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.qpsLimiter != nil {
|
||||||
|
hc.qpsLimiter = config.qpsLimiter
|
||||||
|
} else if config.qps > 0 {
|
||||||
|
hc.qpsLimiter = rate.NewLimiter(rate.Every(time.Second/time.Duration(config.qps)), config.qps)
|
||||||
|
}
|
||||||
|
|
||||||
|
return hc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HttpClient) RawClient() *fasthttp.Client {
|
||||||
|
return h.client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HttpClient) NewRequest(ctx context.Context) *Request {
|
||||||
|
r := &Request{
|
||||||
|
ctx: ctx,
|
||||||
|
header: nil,
|
||||||
|
mapQuery: nil,
|
||||||
|
httpClient: h,
|
||||||
|
urlQuery: url.Values{},
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HttpClient) NewPcRequest(ctx context.Context) *Request {
|
||||||
|
r := h.NewRequest(ctx)
|
||||||
|
r.SetHeaderPcAgent()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HttpClient) NewMobileRequest(ctx context.Context) *Request {
|
||||||
|
r := h.NewRequest(ctx)
|
||||||
|
r.SetHeaderMobileAgent()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) SetContentType(contentType string) *Request {
|
||||||
|
r.contentType = contentType
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) SetBody(body any) *Request {
|
||||||
|
r.body = body
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) SetQueryParam(k string, v string) *Request {
|
||||||
|
if r.mapQuery == nil {
|
||||||
|
r.mapQuery = make(map[string]string)
|
||||||
|
}
|
||||||
|
r.mapQuery[k] = v
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) SetQueryParams(params map[string]string) *Request {
|
||||||
|
for k, v := range params {
|
||||||
|
r.SetQueryParam(k, v)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) SetUrlQueryParam(k string, v string) *Request {
|
||||||
|
r.urlQuery.Add(k, v)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) SetHeader(k, v string) *Request {
|
||||||
|
if r.header == nil {
|
||||||
|
r.header = http.Header{}
|
||||||
|
}
|
||||||
|
r.header.Set(k, v)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) SetHeaders(headers map[string]string) *Request {
|
||||||
|
for k, v := range headers {
|
||||||
|
r.SetHeader(k, v)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) NoWaitQps() *Request {
|
||||||
|
r.noWaitQps = true
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) Get(rawUrl string) (*Response, error) {
|
||||||
|
return r.Do(http.MethodGet, rawUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) Post(rawUrl string) (*Response, error) {
|
||||||
|
return r.Do(http.MethodPost, rawUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
var QpsLimitError = fmt.Errorf("qps limit")
|
||||||
|
|
||||||
|
func (r *Request) DoTimeout(timeout time.Duration, method, rawUrl string) (*Response, error) {
|
||||||
|
if timeout <= time.Millisecond {
|
||||||
|
return nil, fmt.Errorf("timeout is too small <= 1 millisecond")
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.httpClient.config.useCtxTimeout == false {
|
||||||
|
return r.doRequest(timeout, method, rawUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(r.ctx, timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
type tmpRes struct {
|
||||||
|
res *Response
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
var resChan = make(chan tmpRes, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
res, err := r.doRequest(r.httpClient.config.fasthttpTimeout, method, rawUrl)
|
||||||
|
if err != nil {
|
||||||
|
resChan <- tmpRes{err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resChan <- tmpRes{res: res}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, fmt.Errorf("request timeout: %s (%s)", timeout, ctx.Err())
|
||||||
|
case res := <-resChan:
|
||||||
|
return res.res, res.err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) Do(method, rawUrl string) (*Response, error) {
|
||||||
|
return r.DoTimeout(r.httpClient.config.timeout, method, rawUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) doRequest(timeout time.Duration, method, rawUrl string) (*Response, error) {
|
||||||
|
if r.httpClient.qpsLimiter != nil {
|
||||||
|
if r.noWaitQps {
|
||||||
|
allow := r.httpClient.qpsLimiter.Allow()
|
||||||
|
if !allow {
|
||||||
|
return nil, QpsLimitError
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := r.httpClient.qpsLimiter.Wait(r.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req := fasthttp.AcquireRequest()
|
||||||
|
resp := fasthttp.AcquireResponse()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
fasthttp.ReleaseRequest(req)
|
||||||
|
fasthttp.ReleaseResponse(resp)
|
||||||
|
}()
|
||||||
|
|
||||||
|
var reqBody []byte
|
||||||
|
if r.body != nil {
|
||||||
|
switch v := r.body.(type) {
|
||||||
|
case string:
|
||||||
|
reqBody = []byte(v)
|
||||||
|
case []byte:
|
||||||
|
reqBody = v
|
||||||
|
|
||||||
|
default:
|
||||||
|
marshal, err := jsoniter.Marshal(r.body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
reqBody = marshal
|
||||||
|
}
|
||||||
|
|
||||||
|
req.SetBody(reqBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqUrl, err := url.Parse(rawUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parse url err: %s (%s)", err.Error(), rawUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
urlQuery := reqUrl.Query()
|
||||||
|
for k, v := range r.mapQuery {
|
||||||
|
urlQuery.Add(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range r.urlQuery {
|
||||||
|
urlQuery[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(urlQuery) > 0 {
|
||||||
|
reqUrl.RawQuery = urlQuery.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
req.SetRequestURI(reqUrl.String())
|
||||||
|
|
||||||
|
//if len(urlQuery) > 0 {
|
||||||
|
// req.URI().SetQueryString(urlQuery.Encode())
|
||||||
|
//}
|
||||||
|
|
||||||
|
if r.contentType != "" {
|
||||||
|
req.Header.Set("Content-Type", r.contentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range r.header {
|
||||||
|
req.Header.Set(k, r.header.Get(k))
|
||||||
|
}
|
||||||
|
req.Header.SetMethod(method)
|
||||||
|
|
||||||
|
err = r.httpClient.client.DoTimeout(req, resp, timeout)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, fasthttp.ErrTimeout) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("req err: %s (%s)", err, reqUrl.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpBody := resp.Body()
|
||||||
|
body := make([]byte, len(tmpBody))
|
||||||
|
copy(body, tmpBody)
|
||||||
|
|
||||||
|
if r.httpClient.config.noCheckStatus == false {
|
||||||
|
if resp.StatusCode() >= 400 {
|
||||||
|
return nil, &StatusErr{
|
||||||
|
StatusCode: resp.StatusCode(),
|
||||||
|
Body: body,
|
||||||
|
}
|
||||||
|
//return nil, fmt.Errorf("status code err: %d (%s)", resp.StatusCode(), body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copyHeader := &fasthttp.ResponseHeader{}
|
||||||
|
resp.Header.CopyTo(copyHeader)
|
||||||
|
|
||||||
|
res := &Response{
|
||||||
|
Header: copyHeader,
|
||||||
|
body: body,
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) SetHeaderPcAgent() *Request {
|
||||||
|
r.SetHeader(HeaderUserAgent, PcUserAgent)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) SetHeaderMobileAgent() *Request {
|
||||||
|
r.SetHeader(HeaderUserAgent, MobileUserAgent)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) SetHeaderAcceptHtml() *Request {
|
||||||
|
r.SetHeader(HeaderAccept, AcceptHtml)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Header *fasthttp.ResponseHeader
|
||||||
|
body []byte
|
||||||
|
//Response *http.Response
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Response) GetBody() []byte {
|
||||||
|
return r.body
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ error = (*StatusErr)(nil)
|
||||||
|
|
||||||
|
type StatusErr struct {
|
||||||
|
StatusCode int
|
||||||
|
Body []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StatusErr) Error() string {
|
||||||
|
return fmt.Sprintf("status code err: %d (%s)", s.StatusCode, s.Body)
|
||||||
|
}
|
||||||
39
myhttp/fasthttpc/httpclient_test.go
Normal file
39
myhttp/fasthttpc/httpclient_test.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package fasthttpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClient(t *testing.T) {
|
||||||
|
client := New(WithUseCtxTimeout(true), WithTimout(time.Second*5))
|
||||||
|
|
||||||
|
reqUrl := "http://127.0.0.1:18001/swap-instructions"
|
||||||
|
res, err := client.NewRequest(context.Background()).
|
||||||
|
SetQueryParams(map[string]string{
|
||||||
|
"a": "b",
|
||||||
|
"name": "qwe123",
|
||||||
|
"time": "100",
|
||||||
|
}).
|
||||||
|
SetContentType(ContentTypeJSON).
|
||||||
|
SetHeaders(map[string]string{
|
||||||
|
"Api": "ok",
|
||||||
|
}).
|
||||||
|
SetBody(map[string]any{
|
||||||
|
"hello": "world",
|
||||||
|
"id": 5,
|
||||||
|
}).
|
||||||
|
Post(reqUrl)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%+v\n", string(res.GetBody()))
|
||||||
|
|
||||||
|
fmt.Println(res.Header.String())
|
||||||
|
fmt.Println(res.Header.StatusCode())
|
||||||
|
}
|
||||||
84
myhttp/fasthttpc/option.go
Normal file
84
myhttp/fasthttpc/option.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package fasthttpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Config struct {
|
||||||
|
timeout time.Duration
|
||||||
|
client *fasthttp.Client
|
||||||
|
noCheckStatus bool
|
||||||
|
qps int
|
||||||
|
qpsLimiter *rate.Limiter
|
||||||
|
maxConnPerHost int
|
||||||
|
fasthttpTimeout time.Duration
|
||||||
|
useCtxTimeout bool
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigOpt func(c *Config)
|
||||||
|
)
|
||||||
|
|
||||||
|
func WithTimout(v time.Duration) ConfigOpt {
|
||||||
|
return func(c *Config) {
|
||||||
|
c.timeout = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithUseCtxTimeout(v bool) ConfigOpt {
|
||||||
|
return func(c *Config) {
|
||||||
|
c.useCtxTimeout = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithFasthttpimout(v time.Duration) ConfigOpt {
|
||||||
|
return func(c *Config) {
|
||||||
|
c.fasthttpTimeout = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithClient(v *fasthttp.Client) ConfigOpt {
|
||||||
|
return func(c *Config) {
|
||||||
|
c.client = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithMaxConnPerHost(v int) ConfigOpt {
|
||||||
|
return func(c *Config) {
|
||||||
|
c.maxConnPerHost = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//func WithRedirectFn(v func(req *http.Request, via []*http.Request) error) ConfigOpt {
|
||||||
|
// return func(c *Config) {
|
||||||
|
// c.redirectFn = v
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//func WithNoRedirect() ConfigOpt {
|
||||||
|
// return func(c *Config) {
|
||||||
|
// c.redirectFn = func(req *http.Request, via []*http.Request) error {
|
||||||
|
// return http.ErrUseLastResponse
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
func WithNoCheckStatus(v bool) ConfigOpt {
|
||||||
|
return func(c *Config) {
|
||||||
|
c.noCheckStatus = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithQps(v int) ConfigOpt {
|
||||||
|
return func(c *Config) {
|
||||||
|
c.qps = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithQpsLimiter(v *rate.Limiter) ConfigOpt {
|
||||||
|
return func(c *Config) {
|
||||||
|
c.qpsLimiter = v
|
||||||
|
}
|
||||||
|
}
|
||||||
90
myhttp/httpc/const.go
Normal file
90
myhttp/httpc/const.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package httpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
HeaderUserAgent = "User-Agent"
|
||||||
|
HeaderContentType = "Content-Type"
|
||||||
|
HeaderCookie = "Cookie"
|
||||||
|
HeaderAccept = "Accept"
|
||||||
|
HeaderOrigin = "Origin"
|
||||||
|
HeaderReferer = "Referer"
|
||||||
|
HeaderAcceptLanguage = "Accept-Language"
|
||||||
|
HeaderAcceptEncoding = "Accept-Encoding"
|
||||||
|
|
||||||
|
ContentTypeJSON = "application/json; charset=utf-8"
|
||||||
|
)
|
||||||
|
const (
|
||||||
|
MobileUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/124.0.6367.68 MobileRequest/15E148 Safari/604.1"
|
||||||
|
PcUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
|
||||||
|
AcceptHtml = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
|
||||||
|
AcceptCNLanguage = "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7"
|
||||||
|
AcceptEncodingIdentity = "identity"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RedirectAllCookies(req *http.Request, via []*http.Request) error {
|
||||||
|
_, cookieHeader := GetRedirectCookieHeaders(req, via)
|
||||||
|
if cookieHeader != "" {
|
||||||
|
req.Header.Set("Cookie", cookieHeader)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRedirectCookieHeaders(req *http.Request, via []*http.Request) (map[string]string, string) {
|
||||||
|
if len(via) == 0 {
|
||||||
|
return map[string]string{}, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
lastReq := via[len(via)-1]
|
||||||
|
|
||||||
|
cookieMap := parseCookieMap(lastReq.Header.Get("Cookie"))
|
||||||
|
|
||||||
|
for _, c := range lastReq.Cookies() {
|
||||||
|
cookieMap[c.Name] = c.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastReq.Response != nil {
|
||||||
|
for _, c := range lastReq.Response.Cookies() {
|
||||||
|
cookieMap[c.Name] = c.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var pairs []string
|
||||||
|
for name, value := range cookieMap {
|
||||||
|
pairs = append(pairs, fmt.Sprintf("%s=%s", name, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pairs) > 0 {
|
||||||
|
// 直接 Set 最终合并后的完整字符串
|
||||||
|
return cookieMap, strings.Join(pairs, "; ")
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]string{}, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCookieMap(str string) map[string]string {
|
||||||
|
res := make(map[string]string)
|
||||||
|
if len(str) == 0 {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
sp := strings.Split(str, ";")
|
||||||
|
|
||||||
|
for _, v := range sp {
|
||||||
|
split := strings.SplitN(v, "=", 2)
|
||||||
|
if len(split) == 2 {
|
||||||
|
name := strings.TrimSpace(split[0])
|
||||||
|
value := strings.TrimSpace(split[1])
|
||||||
|
|
||||||
|
if name != "" && value != "" {
|
||||||
|
res[name] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
387
myhttp/httpc/httpclient.go
Normal file
387
myhttp/httpc/httpclient.go
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
package httpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
|
"net/url"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.makemake.in/kzkzzzz/mycommon/mylog"
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultClient *HttpClient
|
||||||
|
defaultNoRedirectClient *HttpClient
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
defaultClient = New()
|
||||||
|
defaultNoRedirectClient = New(WithNoRedirect(), WithNoCheckStatus(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReInitDefault(timeout time.Duration) {
|
||||||
|
defaultClient = New(WithTimout(timeout))
|
||||||
|
defaultNoRedirectClient = New(WithNoRedirect(), WithNoCheckStatus(true), WithTimout(timeout))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReInitDefaultOpt(opts ...ConfigOpt) {
|
||||||
|
defaultClient = New(opts...)
|
||||||
|
defaultNoRedirectClient = New(slices.Concat([]ConfigOpt{WithNoRedirect(), WithNoCheckStatus(true)}, opts)...)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func Client() *HttpClient {
|
||||||
|
return defaultClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func NoRedirectClient() *HttpClient {
|
||||||
|
return defaultNoRedirectClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTransport(maxConn int, idleTimeout time.Duration) *http.Transport {
|
||||||
|
if maxConn <= 0 {
|
||||||
|
panic("max connection <= 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if idleTimeout <= 0 {
|
||||||
|
panic("idle timeout <= 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
tr := http.DefaultTransport.(*http.Transport).Clone()
|
||||||
|
tr.MaxIdleConns = 0
|
||||||
|
tr.MaxConnsPerHost = 0
|
||||||
|
tr.MaxIdleConnsPerHost = maxConn
|
||||||
|
tr.IdleConnTimeout = idleTimeout
|
||||||
|
tr.DisableKeepAlives = false
|
||||||
|
//tr.ResponseHeaderTimeout = time.Second * 10
|
||||||
|
//tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: false}
|
||||||
|
return tr
|
||||||
|
}
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
ctx context.Context
|
||||||
|
header http.Header
|
||||||
|
body any
|
||||||
|
mapQuery map[string]string
|
||||||
|
urlQuery url.Values
|
||||||
|
httpClient *HttpClient
|
||||||
|
contentType string
|
||||||
|
noWaitQps bool
|
||||||
|
cookies []*http.Cookie
|
||||||
|
}
|
||||||
|
|
||||||
|
type HttpClient struct {
|
||||||
|
config *Config
|
||||||
|
client *http.Client
|
||||||
|
qpsLimiter *rate.Limiter
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(opts ...ConfigOpt) *HttpClient {
|
||||||
|
config := &Config{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.timeout <= 0 {
|
||||||
|
config.timeout = time.Second * 10
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.transport == nil {
|
||||||
|
config.transport = NewTransport(3072, time.Second*90)
|
||||||
|
}
|
||||||
|
|
||||||
|
//if config.redirectFn == nil {
|
||||||
|
// config.redirectFn = RedirectAllCookies
|
||||||
|
//}
|
||||||
|
|
||||||
|
if config.client == nil {
|
||||||
|
cookieJar, _ := cookiejar.New(nil)
|
||||||
|
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: config.transport,
|
||||||
|
Timeout: config.timeout,
|
||||||
|
Jar: cookieJar,
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.redirectFn != nil {
|
||||||
|
client.CheckRedirect = config.redirectFn
|
||||||
|
}
|
||||||
|
|
||||||
|
config.client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
hc := &HttpClient{
|
||||||
|
config: config,
|
||||||
|
client: config.client,
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.qpsLimiter != nil {
|
||||||
|
hc.qpsLimiter = config.qpsLimiter
|
||||||
|
} else if config.qps > 0 {
|
||||||
|
hc.qpsLimiter = rate.NewLimiter(rate.Every(time.Second/time.Duration(config.qps)), config.qps)
|
||||||
|
}
|
||||||
|
|
||||||
|
return hc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HttpClient) RawClient() *http.Client {
|
||||||
|
return h.client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HttpClient) SetProxy(pr string) *HttpClient {
|
||||||
|
u, err := url.Parse(pr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.client.Transport.(*http.Transport).Proxy = http.ProxyURL(u)
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HttpClient) NewRequest(ctx context.Context) *Request {
|
||||||
|
r := &Request{
|
||||||
|
ctx: ctx,
|
||||||
|
header: nil,
|
||||||
|
mapQuery: nil,
|
||||||
|
httpClient: h,
|
||||||
|
urlQuery: url.Values{},
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HttpClient) NewPcRequest(ctx context.Context) *Request {
|
||||||
|
r := h.NewRequest(ctx)
|
||||||
|
r.SetHeaderPcAgent()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HttpClient) NewMobileRequest(ctx context.Context) *Request {
|
||||||
|
r := h.NewRequest(ctx)
|
||||||
|
r.SetHeaderMobileAgent()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) SetContentType(contentType string) *Request {
|
||||||
|
r.contentType = contentType
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) SetBody(body any) *Request {
|
||||||
|
r.body = body
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) SetQueryParam(k string, v string) *Request {
|
||||||
|
if r.mapQuery == nil {
|
||||||
|
r.mapQuery = make(map[string]string)
|
||||||
|
}
|
||||||
|
r.mapQuery[k] = v
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) SetQueryParams(params map[string]string) *Request {
|
||||||
|
for k, v := range params {
|
||||||
|
r.SetQueryParam(k, v)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) SetUrlQueryParam(k string, v string) *Request {
|
||||||
|
r.urlQuery.Add(k, v)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) SetHeader(k, v string) *Request {
|
||||||
|
if r.header == nil {
|
||||||
|
r.header = http.Header{}
|
||||||
|
}
|
||||||
|
r.header.Set(k, v)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) SetCookies(v ...*http.Cookie) *Request {
|
||||||
|
r.cookies = append(r.cookies, v...)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) SetCookieMap(cookiesMap map[string]*http.Cookie) *Request {
|
||||||
|
for _, v := range cookiesMap {
|
||||||
|
r.cookies = append(r.cookies, v)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) SetHtmlHeaders() *Request {
|
||||||
|
r.SetHeaders(map[string]string{
|
||||||
|
HeaderAccept: AcceptHtml,
|
||||||
|
HeaderAcceptLanguage: AcceptCNLanguage,
|
||||||
|
HeaderAcceptEncoding: AcceptEncodingIdentity,
|
||||||
|
})
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) SetHeaders(headers map[string]string) *Request {
|
||||||
|
for k, v := range headers {
|
||||||
|
r.SetHeader(k, v)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) NoWaitQps() *Request {
|
||||||
|
r.noWaitQps = true
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) Get(rawUrl string) (*Response, error) {
|
||||||
|
return r.Do(http.MethodGet, rawUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) Post(rawUrl string) (*Response, error) {
|
||||||
|
return r.Do(http.MethodPost, rawUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
var QpsLimitError = fmt.Errorf("qps limit")
|
||||||
|
|
||||||
|
func (r *Request) Do(method, rawUrl string) (*Response, error) {
|
||||||
|
if r.httpClient.qpsLimiter != nil {
|
||||||
|
if r.noWaitQps {
|
||||||
|
allow := r.httpClient.qpsLimiter.Allow()
|
||||||
|
if !allow {
|
||||||
|
return nil, QpsLimitError
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := r.httpClient.qpsLimiter.Wait(r.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reqUrl, err := url.Parse(rawUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var reqBody io.Reader = nil
|
||||||
|
if r.body != nil {
|
||||||
|
switch v := r.body.(type) {
|
||||||
|
case string:
|
||||||
|
reqBody = strings.NewReader(v)
|
||||||
|
case []byte:
|
||||||
|
reqBody = bytes.NewReader(v)
|
||||||
|
|
||||||
|
default:
|
||||||
|
marshal, err := jsoniter.Marshal(r.body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
reqBody = bytes.NewReader(marshal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(method, reqUrl.String(), reqBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cookie := range r.cookies {
|
||||||
|
req.AddCookie(cookie)
|
||||||
|
}
|
||||||
|
|
||||||
|
query := req.URL.Query()
|
||||||
|
|
||||||
|
for k, v := range r.mapQuery {
|
||||||
|
query.Add(k, v)
|
||||||
|
}
|
||||||
|
for k, v := range r.urlQuery {
|
||||||
|
query[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
req.URL.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
if r.contentType != "" {
|
||||||
|
req.Header.Set("Content-Type", r.contentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range r.header {
|
||||||
|
req.Header.Set(k, r.header.Get(k))
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := r.httpClient.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.httpClient.config.noCheckStatus == false {
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("status code err: %d (%s)", res.StatusCode, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &Response{
|
||||||
|
body: body,
|
||||||
|
Response: res,
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) SetHeaderPcAgent() *Request {
|
||||||
|
r.SetHeader(HeaderUserAgent, PcUserAgent)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) SetHeaderMobileAgent() *Request {
|
||||||
|
r.SetHeader(HeaderUserAgent, MobileUserAgent)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) SetHeaderAcceptHtml() *Request {
|
||||||
|
r.SetHeader(HeaderAccept, AcceptHtml)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) GetRedirectUrl(reqUrl string) (
|
||||||
|
*url.URL, *Response, error) {
|
||||||
|
|
||||||
|
//r.SetDebug(true)
|
||||||
|
r.SetHeaderAcceptHtml()
|
||||||
|
resp, err := r.Get(reqUrl)
|
||||||
|
|
||||||
|
if err != nil && !errors.Is(err, http.ErrUseLastResponse) {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
location, err := resp.Response.Location()
|
||||||
|
if err != nil {
|
||||||
|
mylog.Warnf("location code: %v", resp.Response.StatusCode)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return location, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
body []byte
|
||||||
|
Response *http.Response
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Response) GetBody() []byte {
|
||||||
|
return r.body
|
||||||
|
}
|
||||||
72
myhttp/httpc/option.go
Normal file
72
myhttp/httpc/option.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package httpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Config struct {
|
||||||
|
timeout time.Duration
|
||||||
|
client *http.Client
|
||||||
|
transport *http.Transport
|
||||||
|
redirectFn func(req *http.Request, via []*http.Request) error
|
||||||
|
noCheckStatus bool
|
||||||
|
qps int
|
||||||
|
qpsLimiter *rate.Limiter
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigOpt func(c *Config)
|
||||||
|
)
|
||||||
|
|
||||||
|
func WithTimout(v time.Duration) ConfigOpt {
|
||||||
|
return func(c *Config) {
|
||||||
|
c.timeout = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithClient(v *http.Client) ConfigOpt {
|
||||||
|
return func(c *Config) {
|
||||||
|
c.client = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithTransport(v *http.Transport) ConfigOpt {
|
||||||
|
return func(c *Config) {
|
||||||
|
c.transport = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithRedirectFn(v func(req *http.Request, via []*http.Request) error) ConfigOpt {
|
||||||
|
return func(c *Config) {
|
||||||
|
c.redirectFn = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithNoRedirect() ConfigOpt {
|
||||||
|
return func(c *Config) {
|
||||||
|
c.redirectFn = func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithNoCheckStatus(v bool) ConfigOpt {
|
||||||
|
return func(c *Config) {
|
||||||
|
c.noCheckStatus = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithQps(v int) ConfigOpt {
|
||||||
|
return func(c *Config) {
|
||||||
|
c.qps = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithQpsLimiter(v *rate.Limiter) ConfigOpt {
|
||||||
|
return func(c *Config) {
|
||||||
|
c.qpsLimiter = v
|
||||||
|
}
|
||||||
|
}
|
||||||
32
myhttp/httpsr/middleware.go
Normal file
32
myhttp/httpsr/middleware.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package httpsr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.makemake.in/kzkzzzz/mycommon/mymetric"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/spf13/cast"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CtxCollectRequestFrom = "ctx_collect_request_from"
|
||||||
|
|
||||||
|
func QPSCollect(svcName string) gin.HandlerFunc {
|
||||||
|
hs := mymetric.NewQPSHistogram(svcName, []string{
|
||||||
|
"svc", "method", "route", "status", "from",
|
||||||
|
}...)
|
||||||
|
|
||||||
|
return func(ctx *gin.Context) {
|
||||||
|
st := time.Now()
|
||||||
|
|
||||||
|
ctx.Next()
|
||||||
|
|
||||||
|
hs.With(prometheus.Labels{
|
||||||
|
"svc": svcName,
|
||||||
|
"method": ctx.Request.Method,
|
||||||
|
"route": ctx.Request.URL.Path,
|
||||||
|
"status": cast.ToString(ctx.Writer.Status()),
|
||||||
|
"from": ctx.GetString(CtxCollectRequestFrom),
|
||||||
|
}).Observe(float64(time.Since(st).Milliseconds()))
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
73
myip/ip2region.go
Normal file
73
myip/ip2region.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package myip
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
searcher *xdb.Searcher
|
||||||
|
|
||||||
|
//go:embed ip2region.xdb
|
||||||
|
dbData []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
// 2、用全局的 cBuff 创建完全基于内存的查询对象。
|
||||||
|
searcher, err = xdb.NewWithBuffer(xdb.IPv4, dbData)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("failed to create searcher with content: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type IpInfo struct {
|
||||||
|
Country string `json:"country"`
|
||||||
|
Area string `json:"area"`
|
||||||
|
Province string `json:"province"`
|
||||||
|
City string `json:"city"`
|
||||||
|
Isp string `json:"isp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindE(ipStr string) (info *IpInfo, err error) {
|
||||||
|
info = &IpInfo{}
|
||||||
|
// 国家|区域|省份|城市|ISP
|
||||||
|
res, err := searcher.SearchByStr(ipStr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//fmt.Println(res)
|
||||||
|
|
||||||
|
sp := strings.Split(res, "|")
|
||||||
|
if len(sp) < 5 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
info.Country = convertEmpty(sp[0])
|
||||||
|
info.Area = convertEmpty(sp[1])
|
||||||
|
info.Province = convertEmpty(sp[2])
|
||||||
|
info.City = convertEmpty(sp[3])
|
||||||
|
info.Isp = convertEmpty(sp[4])
|
||||||
|
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Find(ipStr string) *IpInfo {
|
||||||
|
info, _ := FindE(ipStr)
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertEmpty(s string) string {
|
||||||
|
if s == "0" || s == "" {
|
||||||
|
return "NULL"
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IpInfo) ToString() string {
|
||||||
|
return i.Country + "-" + i.Province + "-" + i.City + "-" + i.Isp
|
||||||
|
}
|
||||||
BIN
myip/ip2region.xdb
Normal file
BIN
myip/ip2region.xdb
Normal file
Binary file not shown.
111
mymetric/prometheus.go
Normal file
111
mymetric/prometheus.go
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
package mymetric
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
HistogramVec struct {
|
||||||
|
*prometheus.HistogramVec
|
||||||
|
Labels []string
|
||||||
|
}
|
||||||
|
CounterVec struct {
|
||||||
|
*prometheus.CounterVec
|
||||||
|
Labels []string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewQPSHistogram(name string, labels ...string) *HistogramVec {
|
||||||
|
name = HistogramKey(name)
|
||||||
|
v := prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||||
|
Name: name,
|
||||||
|
Buckets: []float64{5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 95, 100, 200, 300, 500, 600, 700, 800, 900, 1000, 1500, 2000, 5000}, // 统计区间 单位毫秒
|
||||||
|
|
||||||
|
}, labels)
|
||||||
|
|
||||||
|
wrapMetric := &HistogramVec{
|
||||||
|
HistogramVec: v,
|
||||||
|
Labels: labels,
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterPrometheus(wrapMetric)
|
||||||
|
|
||||||
|
return wrapMetric
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHistogram(name string, buckets []float64, labels ...string) *HistogramVec {
|
||||||
|
name = HistogramKey(name)
|
||||||
|
v := prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||||
|
Name: name,
|
||||||
|
Buckets: buckets,
|
||||||
|
}, labels)
|
||||||
|
|
||||||
|
wrapMetric := &HistogramVec{
|
||||||
|
HistogramVec: v,
|
||||||
|
Labels: labels,
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterPrometheus(wrapMetric)
|
||||||
|
return wrapMetric
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCounter(name string, labels ...string) *CounterVec {
|
||||||
|
name = CounterKey(name)
|
||||||
|
v := prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||||
|
Name: name,
|
||||||
|
}, labels)
|
||||||
|
|
||||||
|
wrapMetric := &CounterVec{
|
||||||
|
CounterVec: v,
|
||||||
|
Labels: labels,
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterPrometheus(wrapMetric)
|
||||||
|
return wrapMetric
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
MetricsRoute = "/metrics"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 监控指标路由
|
||||||
|
func GinExport(authKey string) gin.HandlerFunc {
|
||||||
|
pm := promhttp.HandlerFor(
|
||||||
|
prometheus.DefaultGatherer,
|
||||||
|
promhttp.HandlerOpts{Timeout: time.Second * 3},
|
||||||
|
)
|
||||||
|
|
||||||
|
return func(ctx *gin.Context) {
|
||||||
|
if authKey != "" {
|
||||||
|
value := ctx.Query("key")
|
||||||
|
if authKey != value {
|
||||||
|
ctx.String(http.StatusForbidden, "metric auth key is empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pm.ServeHTTP(ctx.Writer, ctx.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func HistogramKey(name string) string {
|
||||||
|
return name + "_h"
|
||||||
|
}
|
||||||
|
|
||||||
|
func CounterKey(name string) string {
|
||||||
|
return name + "_c"
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterPrometheus(c prometheus.Collector) {
|
||||||
|
err := prometheus.Register(c)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("register err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
292
mymysql/batchwriter.go
Normal file
292
mymysql/batchwriter.go
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
package mymysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"git.makemake.in/kzkzzzz/mycommon"
|
||||||
|
"git.makemake.in/kzkzzzz/mycommon/mylog"
|
||||||
|
"github.com/goccy/go-json"
|
||||||
|
"github.com/rs/xid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultDataBuffer = 1e5 // channel缓冲区
|
||||||
|
defaultBatchSize = 200 // 多少条数据写一次
|
||||||
|
defaultIntervalTime = time.Second * 2 // 多久时间写一次
|
||||||
|
defaultJobNum = 2 // 写入db 任务数量
|
||||||
|
defaultAsyncWorkerNum = 20 // 异步执行写入事件的最大协程数量
|
||||||
|
)
|
||||||
|
|
||||||
|
type iWriterStop interface {
|
||||||
|
StopWriter()
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
writerJobMap = &sync.Map{}
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
batchData[T any] struct {
|
||||||
|
jobIndex int
|
||||||
|
dataList []T
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type BatchWriterConfig struct {
|
||||||
|
channelBuffer int
|
||||||
|
batchSize int
|
||||||
|
batchInterval time.Duration
|
||||||
|
jobNum int
|
||||||
|
asyncWorkerNum int
|
||||||
|
clauseExpr []clause.Expression
|
||||||
|
debug bool
|
||||||
|
noPrepare bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type BatchWriter[T any] struct {
|
||||||
|
db *MysqlDb
|
||||||
|
tableName string
|
||||||
|
jobName string
|
||||||
|
|
||||||
|
uniqueId string
|
||||||
|
config *BatchWriterConfig
|
||||||
|
dataChan chan T
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
stopChan chan struct{}
|
||||||
|
|
||||||
|
asyncWorkerLimitChan chan struct{}
|
||||||
|
asyncWorkerWg *sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
type BatchWriterOpt func(c *BatchWriterConfig)
|
||||||
|
|
||||||
|
func NewBatchWrite[T any](db *MysqlDb, tableName, jobName string, opts ...BatchWriterOpt) *BatchWriter[T] {
|
||||||
|
config := &BatchWriterConfig{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.batchInterval <= 0 {
|
||||||
|
config.batchInterval = defaultIntervalTime
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.channelBuffer <= 0 {
|
||||||
|
config.channelBuffer = defaultDataBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.jobNum <= 0 {
|
||||||
|
config.jobNum = defaultJobNum
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.asyncWorkerNum <= 0 {
|
||||||
|
config.asyncWorkerNum = defaultAsyncWorkerNum
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.batchSize <= 0 {
|
||||||
|
config.batchSize = defaultBatchSize
|
||||||
|
}
|
||||||
|
|
||||||
|
bw := &BatchWriter[T]{
|
||||||
|
db: db,
|
||||||
|
tableName: tableName,
|
||||||
|
jobName: jobName,
|
||||||
|
uniqueId: xid.New().String(),
|
||||||
|
config: config,
|
||||||
|
dataChan: make(chan T),
|
||||||
|
stopChan: make(chan struct{}, 1),
|
||||||
|
|
||||||
|
asyncWorkerLimitChan: make(chan struct{}, config.asyncWorkerNum),
|
||||||
|
asyncWorkerWg: &sync.WaitGroup{},
|
||||||
|
}
|
||||||
|
|
||||||
|
bw.ctx, bw.cancel = context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
// 记录实例, 便于退出程序的时候入库
|
||||||
|
writerJobMap.Store(bw.uniqueId, bw)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
bw.start()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return bw
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bw *BatchWriter[T]) Write(data ...T) {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if bw.ctx.Err() != nil {
|
||||||
|
b, _ := json.Marshal(data)
|
||||||
|
mylog.Errorf("[%s] save to db err: job is close, data: (%s)", bw.tableName, b)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range data {
|
||||||
|
bw.dataChan <- v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bw *BatchWriter[T]) start() {
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
|
||||||
|
for i := 0; i < bw.config.jobNum; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(i0 int) {
|
||||||
|
defer wg.Done()
|
||||||
|
bw.startJob(i)
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
log.Printf("[table:%s - job:%s] batch write job stop", bw.tableName, bw.jobName)
|
||||||
|
|
||||||
|
close(bw.stopChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bw *BatchWriter[T]) startJob(jobIndex int) {
|
||||||
|
tkTime := bw.config.batchInterval
|
||||||
|
// 定时器增加随机时间差
|
||||||
|
randN := float64(mycommon.RandRange(50, 350)) / float64(100)
|
||||||
|
|
||||||
|
tkTime = tkTime + time.Duration(float64(time.Second)*randN)
|
||||||
|
|
||||||
|
log.Printf("[table:%s - job:%s - %d] batch write job start, ticker time: %s", bw.tableName, bw.jobName, jobIndex, tkTime.String())
|
||||||
|
|
||||||
|
tk := time.NewTicker(tkTime)
|
||||||
|
defer tk.Stop()
|
||||||
|
|
||||||
|
bd := &batchData[T]{
|
||||||
|
jobIndex: jobIndex,
|
||||||
|
dataList: make([]T, 0, bw.config.batchSize),
|
||||||
|
}
|
||||||
|
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-bw.ctx.Done():
|
||||||
|
break loop
|
||||||
|
|
||||||
|
case <-tk.C:
|
||||||
|
bw.writeToDb(bd)
|
||||||
|
|
||||||
|
case data, ok := <-bw.dataChan:
|
||||||
|
if !ok {
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
bd.dataList = append(bd.dataList, data)
|
||||||
|
|
||||||
|
if len(bd.dataList) >= bw.config.batchSize {
|
||||||
|
bw.writeToDb(bd)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(bd.dataList) > 0 {
|
||||||
|
bw.writeToDb(bd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bw *BatchWriter[T]) writeToDb(bd *batchData[T]) {
|
||||||
|
if len(bd.dataList) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
// 清空切片
|
||||||
|
bd.dataList = bd.dataList[:0]
|
||||||
|
}()
|
||||||
|
|
||||||
|
bw.asyncWorkerLimitChan <- struct{}{}
|
||||||
|
|
||||||
|
// 复制一份数据, 异步写入
|
||||||
|
copyDataList := make([]T, len(bd.dataList))
|
||||||
|
copy(copyDataList, bd.dataList)
|
||||||
|
|
||||||
|
bw.asyncWorkerWg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
<-bw.asyncWorkerLimitChan
|
||||||
|
bw.asyncWorkerWg.Done()
|
||||||
|
|
||||||
|
}()
|
||||||
|
|
||||||
|
bw.asyncWriteToDb(bd.jobIndex, copyDataList)
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var DisableGormLog = logger.Default.LogMode(logger.Silent)
|
||||||
|
|
||||||
|
func SessionDisableLog() *gorm.Session {
|
||||||
|
return &gorm.Session{Logger: DisableGormLog}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bw *BatchWriter[T]) asyncWriteToDb(jobIndex int, copyDataList []T) {
|
||||||
|
if len(copyDataList) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
query := bw.db.Table(bw.tableName)
|
||||||
|
|
||||||
|
if len(bw.config.clauseExpr) > 0 {
|
||||||
|
query.Clauses(bw.config.clauseExpr...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if bw.config.noPrepare {
|
||||||
|
_sql := query.ToSQL(func(tx *gorm.DB) *gorm.DB {
|
||||||
|
return tx.Session(&gorm.Session{
|
||||||
|
Logger: DisableGormLog,
|
||||||
|
}).Create(©DataList)
|
||||||
|
})
|
||||||
|
|
||||||
|
err = bw.db.Table(bw.tableName).Exec(_sql).Error
|
||||||
|
|
||||||
|
} else {
|
||||||
|
err = query.Create(copyDataList).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量写入失败, 后续优化重试流程
|
||||||
|
b, _ := json.Marshal(copyDataList)
|
||||||
|
mylog.Errorf("[%s - %s] save to db err: %s data: (%s)", bw.tableName, bw.jobName, err, b)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bw *BatchWriter[T]) StopWriter() {
|
||||||
|
if bw.ctx.Err() != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bw.cancel()
|
||||||
|
close(bw.dataChan)
|
||||||
|
|
||||||
|
<-bw.stopChan
|
||||||
|
|
||||||
|
bw.asyncWorkerWg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func StopAllBatchWriter() {
|
||||||
|
writerJobMap.Range(func(k, v interface{}) bool {
|
||||||
|
q := v.(iWriterStop)
|
||||||
|
q.StopWriter()
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: 改成用 StopAllBatchWriter
|
||||||
|
func StopWriter() {
|
||||||
|
StopAllBatchWriter()
|
||||||
|
}
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
package mymysql
|
package mymysql
|
||||||
|
|
||||||
import "gorm.io/gorm"
|
import (
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
type Opt func(m *MysqlDb)
|
type Opt func(m *MysqlDb)
|
||||||
|
|
||||||
@@ -15,3 +19,51 @@ func WithGormConfig(v *gorm.Config) Opt {
|
|||||||
m.gormConfig = v
|
m.gormConfig = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithWriteJobNum(v int) BatchWriterOpt {
|
||||||
|
return func(c *BatchWriterConfig) {
|
||||||
|
c.jobNum = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithWriteChannelBuffer(v int) BatchWriterOpt {
|
||||||
|
return func(c *BatchWriterConfig) {
|
||||||
|
c.channelBuffer = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithWriteBatchSize(v int) BatchWriterOpt {
|
||||||
|
return func(c *BatchWriterConfig) {
|
||||||
|
c.batchSize = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithWriteIntervalTime(v time.Duration) BatchWriterOpt {
|
||||||
|
return func(c *BatchWriterConfig) {
|
||||||
|
c.batchInterval = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithAsyncWorkerNum(v int) BatchWriterOpt {
|
||||||
|
return func(c *BatchWriterConfig) {
|
||||||
|
c.asyncWorkerNum = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithClause(v ...clause.Expression) BatchWriterOpt {
|
||||||
|
return func(c *BatchWriterConfig) {
|
||||||
|
c.clauseExpr = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithDebug(v bool) BatchWriterOpt {
|
||||||
|
return func(c *BatchWriterConfig) {
|
||||||
|
c.debug = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithNoPrepare() BatchWriterOpt {
|
||||||
|
return func(c *BatchWriterConfig) {
|
||||||
|
c.noPrepare = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,194 +0,0 @@
|
|||||||
package consul
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"github.com/jpillora/backoff"
|
|
||||||
"google.golang.org/grpc/grpclog"
|
|
||||||
"log"
|
|
||||||
"sort"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/consul/api"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"google.golang.org/grpc/resolver"
|
|
||||||
)
|
|
||||||
|
|
||||||
// schemeName for the urls
|
|
||||||
// All target URLs like 'consul://.../...' will be resolved by this resolver
|
|
||||||
const schemeName = "consul"
|
|
||||||
|
|
||||||
// builder implements resolver.Builder and use for constructing all consul resolvers
|
|
||||||
type builder struct{}
|
|
||||||
|
|
||||||
func (b *builder) Build(url resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
|
|
||||||
tgt, err := parseURL(url.URL.String())
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "Wrong consul URL")
|
|
||||||
}
|
|
||||||
cli, err := api.NewClient(tgt.consulConfig())
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "Couldn't connect to the Consul API")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
pipe := make(chan []string)
|
|
||||||
go watchConsulService(ctx, cli.Health(), tgt, pipe)
|
|
||||||
go populateEndpoints(ctx, cc, pipe, tgt)
|
|
||||||
|
|
||||||
return &resolvr{cancelFunc: cancel}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scheme returns the scheme supported by this resolver.
|
|
||||||
// Scheme is defined at https://github.com/grpc/grpc/blob/master/doc/naming.md.
|
|
||||||
func (b *builder) Scheme() string {
|
|
||||||
return schemeName
|
|
||||||
}
|
|
||||||
|
|
||||||
// init function needs for auto-register in resolvers registry
|
|
||||||
func init() {
|
|
||||||
resolver.Register(&builder{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolvr implements resolver.Resolver from the gRPC package.
|
|
||||||
// It watches for endpoints changes and pushes them to the underlying gRPC connection.
|
|
||||||
type resolvr struct {
|
|
||||||
cancelFunc context.CancelFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResolveNow will be skipped due unnecessary in this case
|
|
||||||
func (r *resolvr) ResolveNow(resolver.ResolveNowOptions) {}
|
|
||||||
|
|
||||||
// Close closes the resolver.
|
|
||||||
func (r *resolvr) Close() {
|
|
||||||
r.cancelFunc()
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:generate ./bin/moq -out mocks_test.go . servicer
|
|
||||||
type servicer interface {
|
|
||||||
Service(string, string, bool, *api.QueryOptions) ([]*api.ServiceEntry, *api.QueryMeta, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func watchConsulService(ctx context.Context, s servicer, tgt target, out chan<- []string) {
|
|
||||||
res := make(chan []string)
|
|
||||||
quit := make(chan struct{})
|
|
||||||
bck := &backoff.Backoff{
|
|
||||||
Factor: 2,
|
|
||||||
Jitter: true,
|
|
||||||
Min: 10 * time.Millisecond,
|
|
||||||
Max: tgt.MaxBackoff,
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
var lastIndex uint64
|
|
||||||
for {
|
|
||||||
ss, meta, err := s.Service(
|
|
||||||
tgt.Service,
|
|
||||||
tgt.Tag,
|
|
||||||
tgt.Healthy,
|
|
||||||
&api.QueryOptions{
|
|
||||||
WaitIndex: lastIndex,
|
|
||||||
Near: tgt.Near,
|
|
||||||
WaitTime: tgt.Wait,
|
|
||||||
Datacenter: tgt.Dc,
|
|
||||||
AllowStale: tgt.AllowStale,
|
|
||||||
RequireConsistent: tgt.RequireConsistent,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
// No need to continue if the context is done/cancelled.
|
|
||||||
// We check that here directly because the check for the closed quit channel
|
|
||||||
// at the end of the loop is not reached when calling continue here.
|
|
||||||
select {
|
|
||||||
case <-quit:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
grpclog.Errorf("[Consul resolver] Couldn't fetch endpoints. target={%s}; error={%v}", tgt.String(), err)
|
|
||||||
time.Sleep(bck.Duration())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bck.Reset()
|
|
||||||
lastIndex = meta.LastIndex
|
|
||||||
grpclog.Infof("[Consul resolver] %d endpoints fetched in(+wait) %s for target={%s}",
|
|
||||||
len(ss),
|
|
||||||
meta.RequestTime,
|
|
||||||
tgt.String(),
|
|
||||||
)
|
|
||||||
|
|
||||||
ee := make([]string, 0, len(ss))
|
|
||||||
for _, s := range ss {
|
|
||||||
address := s.Service.Address
|
|
||||||
if s.Service.Address == "" {
|
|
||||||
address = s.Node.Address
|
|
||||||
}
|
|
||||||
ee = append(ee, fmt.Sprintf("%s:%d", address, s.Service.Port))
|
|
||||||
}
|
|
||||||
|
|
||||||
if tgt.Limit != 0 && len(ee) > tgt.Limit {
|
|
||||||
ee = ee[:tgt.Limit]
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case res <- ee:
|
|
||||||
continue
|
|
||||||
case <-quit:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
// If in the below select both channels have values that can be read,
|
|
||||||
// Go picks one pseudo-randomly.
|
|
||||||
// But when the context is canceled we want to act upon it immediately.
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
// Close quit so the goroutine returns and doesn't leak.
|
|
||||||
// Do NOT close res because that can lead to panics in the goroutine.
|
|
||||||
// res will be garbage collected at some point.
|
|
||||||
close(quit)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case ee := <-res:
|
|
||||||
out <- ee
|
|
||||||
case <-ctx.Done():
|
|
||||||
close(quit)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func populateEndpoints(ctx context.Context, clientConn resolver.ClientConn, input <-chan []string, tgt target) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case cc := <-input:
|
|
||||||
connsSet := make(map[string]struct{}, len(cc))
|
|
||||||
for _, c := range cc {
|
|
||||||
connsSet[c] = struct{}{}
|
|
||||||
}
|
|
||||||
conns := make([]resolver.Address, 0, len(connsSet))
|
|
||||||
for c := range connsSet {
|
|
||||||
conns = append(conns, resolver.Address{Addr: c})
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(byAddressString(conns)) // Don't replace the same address list in the balancer
|
|
||||||
|
|
||||||
log.Printf("update conn: %s - %s", tgt.Service, conns)
|
|
||||||
|
|
||||||
err := clientConn.UpdateState(resolver.State{Addresses: conns})
|
|
||||||
if err != nil {
|
|
||||||
grpclog.Errorf("[Consul resolver] Couldn't update client connection. error={%v}", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case <-ctx.Done():
|
|
||||||
grpclog.Info("[Consul resolver] Watch has been finished")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// byAddressString sorts resolver.Address by Address Field sorting in increasing order.
|
|
||||||
type byAddressString []resolver.Address
|
|
||||||
|
|
||||||
func (p byAddressString) Len() int { return len(p) }
|
|
||||||
func (p byAddressString) Less(i, j int) bool { return p[i].Addr < p[j].Addr }
|
|
||||||
func (p byAddressString) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
||||||
@@ -3,25 +3,38 @@ package consul
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.makemake.in/kzkzzzz/mycommon/myconf"
|
"git.makemake.in/kzkzzzz/mycommon/myconf"
|
||||||
|
"git.makemake.in/kzkzzzz/mycommon/mygrpc"
|
||||||
|
"git.makemake.in/kzkzzzz/mycommon/mylog"
|
||||||
"git.makemake.in/kzkzzzz/mycommon/myregistry"
|
"git.makemake.in/kzkzzzz/mycommon/myregistry"
|
||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
"github.com/hashicorp/consul/api/watch"
|
|
||||||
"github.com/hashicorp/go-hclog"
|
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
"log"
|
"go.uber.org/zap"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ myregistry.IRegister = (*Consul)(nil)
|
var (
|
||||||
|
_ myregistry.IRegister = (*Consul)(nil)
|
||||||
|
|
||||||
|
defaultLog = mylog.NewLogger(&mylog.Config{
|
||||||
|
Level: mylog.DebugLevel,
|
||||||
|
NeedLogFile: false,
|
||||||
|
ConsoleWriter: os.Stdout,
|
||||||
|
ZapOpt: []zap.Option{
|
||||||
|
zap.AddCaller(), zap.AddCallerSkip(1),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
type Consul struct {
|
type Consul struct {
|
||||||
client *api.Client
|
client *api.Client
|
||||||
serviceIds map[string][]string
|
|
||||||
serviceTags []string
|
serviceTags []string
|
||||||
services []*api.AgentServiceRegistration
|
services map[string]*api.AgentServiceRegistration
|
||||||
|
|
||||||
|
deregisteredMap *sync.Map
|
||||||
}
|
}
|
||||||
|
|
||||||
type Opt func(*Consul)
|
type Opt func(*Consul)
|
||||||
@@ -41,44 +54,83 @@ func (c *Consul) Register(service *myregistry.ServiceInfo) error {
|
|||||||
serviceId := xid.New().String()
|
serviceId := xid.New().String()
|
||||||
hostname, err := os.Hostname()
|
hostname, err := os.Hostname()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("get hostname err: %s", err)
|
defaultLog.Errorf("get hostname err: %s", err)
|
||||||
} else {
|
} else {
|
||||||
serviceId = fmt.Sprintf("%s-%s", hostname, serviceId)
|
serviceId = fmt.Sprintf("%s-%s", hostname, serviceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.serviceIds[service.ServiceName] = append(c.serviceIds[service.ServiceName], serviceId)
|
if _, ok := c.services[service.ServiceName]; ok {
|
||||||
|
return fmt.Errorf("service [%s] already registered", service.ServiceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkTime := time.Second * 15
|
||||||
|
|
||||||
check := &api.AgentServiceCheck{
|
check := &api.AgentServiceCheck{
|
||||||
CheckID: serviceId,
|
CheckID: serviceId,
|
||||||
TCP: fmt.Sprintf("%s:%d", service.Ip, service.Port),
|
TTL: (checkTime*2 + time.Second*10).String(),
|
||||||
Timeout: "5s", // 超时时间
|
//TCP: fmt.Sprintf("%s:%d", service.Ip, service.Port),
|
||||||
Interval: "30s", // 运行检查的频率
|
//Timeout: "5s", // 超时时间
|
||||||
|
//Interval: "30s", // 运行检查的频率
|
||||||
// 指定时间后自动注销不健康的服务节点
|
// 指定时间后自动注销不健康的服务节点
|
||||||
// 最小超时时间为1分钟,收获不健康服务的进程每30秒运行一次,因此触发注销的时间可能略长于配置的超时时间。
|
// 最小超时时间为1分钟,收获不健康服务的进程每30秒运行一次,因此触发注销的时间可能略长于配置的超时时间。
|
||||||
DeregisterCriticalServiceAfter: "6m",
|
DeregisterCriticalServiceAfter: "5m",
|
||||||
Status: "passing",
|
Status: api.HealthPassing,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
regTags := make([]string, len(c.serviceTags))
|
||||||
|
copy(regTags, c.serviceTags)
|
||||||
|
|
||||||
|
if v := service.Extend["tag"]; v != "" {
|
||||||
|
regTags = append(regTags, v)
|
||||||
|
}
|
||||||
|
|
||||||
svc := &api.AgentServiceRegistration{
|
svc := &api.AgentServiceRegistration{
|
||||||
ID: serviceId, // 服务唯一ID
|
ID: serviceId, // 服务唯一ID
|
||||||
Name: service.ServiceName, // 服务名称
|
Name: service.ServiceName, // 服务名称
|
||||||
Tags: c.serviceTags, // 为服务打标签
|
Tags: regTags, // 为服务打标签
|
||||||
Address: service.Ip,
|
Address: service.Ip,
|
||||||
Port: service.Port,
|
Port: service.Port,
|
||||||
Check: check,
|
Check: check,
|
||||||
}
|
}
|
||||||
|
|
||||||
c.services = append(c.services, svc)
|
c.services[service.ServiceName] = svc
|
||||||
|
|
||||||
return c.client.Agent().ServiceRegister(svc)
|
err = c.client.Agent().ServiceRegister(svc)
|
||||||
|
if err != nil {
|
||||||
|
defaultLog.Errorf("register service err: %s: %s", svc.Name, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultLog.Infof("register service ok: %s - %s:%d", svc.Name, svc.Address, svc.Port)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
//defaultLog.Debugf("PassTTL ttl service: %s", svc.Name)
|
||||||
|
|
||||||
|
err := c.client.Agent().PassTTL(serviceId, "")
|
||||||
|
if err != nil {
|
||||||
|
defaultLog.Errorf("PassTTL service err: %s: %s", svc.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(checkTime)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Consul) Deregister(service *myregistry.ServiceInfo) error {
|
func (c *Consul) Deregister(service *myregistry.ServiceInfo) error {
|
||||||
for _, svcId := range c.serviceIds[service.ServiceName] {
|
c.deregisteredMap.Store(service.ServiceName, true)
|
||||||
err := c.client.Agent().ServiceDeregister(svcId)
|
|
||||||
|
if svc, ok := c.services[service.ServiceName]; ok {
|
||||||
|
err := c.client.Agent().ServiceDeregister(svc.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to deregister service %s: %s\n", service, err)
|
defaultLog.Errorf("deregister service %s err: %s", service, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,9 +143,59 @@ func MustNew(conf *myconf.Config, opts ...Opt) *Consul {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func New(conf *myconf.Config, opts ...Opt) (*Consul, error) {
|
func New(conf *myconf.Config, opts ...Opt) (*Consul, error) {
|
||||||
|
client, err := NewClient(&ClientConfig{
|
||||||
|
Address: conf.GetString("addr"),
|
||||||
|
Token: conf.GetString("token"),
|
||||||
|
Username: conf.GetString("username"),
|
||||||
|
Password: conf.GetString("password"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cl := &Consul{
|
||||||
|
client: client,
|
||||||
|
serviceTags: conf.GetStringSlice("serviceTags"),
|
||||||
|
services: make(map[string]*api.AgentServiceRegistration, 0),
|
||||||
|
deregisteredMap: &sync.Map{},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(cl)
|
||||||
|
}
|
||||||
|
|
||||||
|
go cl.allHealthCheck()
|
||||||
|
|
||||||
|
return cl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientConfig struct {
|
||||||
|
Address string
|
||||||
|
Token string
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
AutoGrpcPrefix bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
clientLock = &sync.Mutex{}
|
||||||
|
clientMap = make(map[string]*api.Client)
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewClient(clientCfg *ClientConfig) (*api.Client, error) {
|
||||||
|
clientLock.Lock()
|
||||||
|
defer clientLock.Unlock()
|
||||||
|
|
||||||
|
if clientCfg.Address == "" {
|
||||||
|
return nil, fmt.Errorf("consul address is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if client, ok := clientMap[clientCfg.Address]; ok {
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
cfg := api.DefaultConfig()
|
cfg := api.DefaultConfig()
|
||||||
cfg.Address = conf.GetString("addr")
|
cfg.Address = clientCfg.Address
|
||||||
|
|
||||||
if cfg.Address == "" {
|
if cfg.Address == "" {
|
||||||
return nil, fmt.Errorf("consul address is empty")
|
return nil, fmt.Errorf("consul address is empty")
|
||||||
@@ -102,12 +204,11 @@ func New(conf *myconf.Config, opts ...Opt) (*Consul, error) {
|
|||||||
cfg.Transport.DialContext = (&net.Dialer{
|
cfg.Transport.DialContext = (&net.Dialer{
|
||||||
Timeout: 3 * time.Second,
|
Timeout: 3 * time.Second,
|
||||||
KeepAlive: 20 * time.Second,
|
KeepAlive: 20 * time.Second,
|
||||||
DualStack: true,
|
|
||||||
}).DialContext
|
}).DialContext
|
||||||
cfg.Token = conf.GetString("token")
|
cfg.Token = clientCfg.Token
|
||||||
|
|
||||||
username := conf.GetString("username")
|
username := clientCfg.Username
|
||||||
password := conf.GetString("password")
|
password := clientCfg.Password
|
||||||
|
|
||||||
if username != "" && password != "" {
|
if username != "" && password != "" {
|
||||||
cfg.HttpAuth = &api.HttpBasicAuth{
|
cfg.HttpAuth = &api.HttpBasicAuth{
|
||||||
@@ -121,55 +222,94 @@ func New(conf *myconf.Config, opts ...Opt) (*Consul, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cl := &Consul{
|
clientMap[clientCfg.Address] = client
|
||||||
client: client,
|
return client, nil
|
||||||
serviceIds: make(map[string][]string),
|
|
||||||
serviceTags: conf.GetStringSlice("serviceTags"),
|
|
||||||
services: make([]*api.AgentServiceRegistration, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(cl)
|
|
||||||
}
|
|
||||||
|
|
||||||
go cl.healthCheck()
|
|
||||||
|
|
||||||
return cl, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Consul) healthCheck() {
|
func (c *Consul) allHealthCheck() {
|
||||||
wlog := newWatchLogger()
|
time.Sleep(time.Second * 5)
|
||||||
|
mylog.Infof("start all health check")
|
||||||
|
|
||||||
wp, err := watch.Parse(map[string]any{
|
var (
|
||||||
"type": "services",
|
lastIndex uint64
|
||||||
|
isFirst = true
|
||||||
|
err error
|
||||||
|
meta *api.QueryMeta
|
||||||
|
remoteServices map[string][]string
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
if isFirst == false {
|
||||||
|
time.Sleep(time.Second * 2)
|
||||||
|
|
||||||
|
// 错误的情况重新注册一次
|
||||||
|
if err != nil {
|
||||||
|
var isRegisterErr bool
|
||||||
|
for _, svc := range c.services {
|
||||||
|
err = c.retryRegister(svc, false)
|
||||||
|
if err != nil {
|
||||||
|
defaultLog.Errorf("retry register service %s err:: %s", svc.Name, err)
|
||||||
|
isRegisterErr = true
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
defaultLog.Infof("retry register service %s ok", svc.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if isRegisterErr {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
isFirst = false
|
||||||
|
|
||||||
|
remoteServices, meta, err = c.client.Catalog().Services(&api.QueryOptions{
|
||||||
|
WaitIndex: lastIndex,
|
||||||
|
WaitTime: time.Second * 90,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("parse watch err: %s", err))
|
defaultLog.Errorf("health check err: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lastIndex = meta.LastIndex
|
||||||
|
|
||||||
|
//mylog.Debugf("%d - %+v\n", lastIndex, remoteServices)
|
||||||
|
|
||||||
|
if isFirst == false {
|
||||||
|
for _, localSvc := range c.services {
|
||||||
|
if _, ok := remoteServices[localSvc.Name]; !ok {
|
||||||
|
//c.retryRegister(localSvc, true)
|
||||||
|
err = fmt.Errorf("need retry register service %s not exist [%+v]", localSvc.Name, remoteServices)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wp.Handler = func(u uint64, raw any) {
|
}
|
||||||
if wlog.isWatchErr == true {
|
}
|
||||||
|
|
||||||
for _, svc := range c.services {
|
func (c *Consul) retryRegister(svc *api.AgentServiceRegistration, needDelay bool) error {
|
||||||
//c.client.Agent().ServiceDeregister(svc.ID)
|
if _, ok := c.deregisteredMap.Load(svc.Name); ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if needDelay {
|
||||||
|
go func() {
|
||||||
|
time.Sleep(time.Second)
|
||||||
err := c.client.Agent().ServiceRegister(svc)
|
err := c.client.Agent().ServiceRegister(svc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("retry register service err: %s: %s", svc.Name, err)
|
defaultLog.Errorf("delay retry register service %s err: %s", svc.Name, err)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("retry register service ok: %s", svc.Name)
|
defaultLog.Infof("delay retry register service %s ok", svc.Name)
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
wlog.isWatchErr = false
|
return c.client.Agent().ServiceRegister(svc)
|
||||||
}
|
|
||||||
//fmt.Println("watch", u, raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = wp.RunWithClientAndHclog(c.client, wlog)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("watch err: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Consul) Client() *api.Client {
|
func (c *Consul) Client() *api.Client {
|
||||||
@@ -177,13 +317,27 @@ func (c *Consul) Client() *api.Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GrpcUrl(serviceName string, conf *myconf.Config) string {
|
func GrpcUrl(serviceName string, conf *myconf.Config) string {
|
||||||
return GrpcUrlWithTag("", "grpc@"+serviceName, conf)
|
return GrpcUrlWithTag("", serviceName, conf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GrpcUrlWithTag(tag string, serviceName string, conf *myconf.Config) string {
|
func GrpcUrlWithTag(tag string, serviceName string, conf *myconf.Config) string {
|
||||||
|
return GrpcUrlWithTagByConfig(tag, serviceName, &ClientConfig{
|
||||||
|
Address: conf.GetString("addr"),
|
||||||
|
Token: conf.GetString("token"),
|
||||||
|
Username: conf.GetString("username"),
|
||||||
|
Password: conf.GetString("password"),
|
||||||
|
AutoGrpcPrefix: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GrpcUrlWithTagByConfig(tag string, serviceName string, conf *ClientConfig) string {
|
||||||
|
if conf.AutoGrpcPrefix {
|
||||||
|
serviceName = mygrpc.ServicePrefix + serviceName
|
||||||
|
}
|
||||||
|
|
||||||
u := &url.URL{
|
u := &url.URL{
|
||||||
Scheme: schemeName,
|
Scheme: schemeName,
|
||||||
Host: conf.GetString("addr"),
|
Host: conf.Address,
|
||||||
Path: serviceName,
|
Path: serviceName,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,44 +348,19 @@ func GrpcUrlWithTag(tag string, serviceName string, conf *myconf.Config) string
|
|||||||
query := u.Query()
|
query := u.Query()
|
||||||
query.Set("healthy", "true")
|
query.Set("healthy", "true")
|
||||||
|
|
||||||
if v := conf.GetString("token"); v != "" {
|
if conf.Token != "" {
|
||||||
query.Set("token", v)
|
query.Set("token", conf.Token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tag != "" {
|
if tag != "" {
|
||||||
query.Set("tag", tag)
|
query.Set("tag", tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
username := conf.GetString("username")
|
if conf.Username != "" && conf.Password != "" {
|
||||||
password := conf.GetString("password")
|
u.User = url.UserPassword(conf.Username, conf.Password)
|
||||||
|
|
||||||
if username != "" && password != "" {
|
|
||||||
u.User = url.UserPassword(username, password)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
u.RawQuery = query.Encode()
|
u.RawQuery = query.Encode()
|
||||||
|
|
||||||
return u.String()
|
return u.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
type watchLogger struct {
|
|
||||||
hclog.Logger
|
|
||||||
isWatchErr bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newWatchLogger() *watchLogger {
|
|
||||||
return &watchLogger{Logger: hclog.New(&hclog.LoggerOptions{
|
|
||||||
Name: "watch",
|
|
||||||
Output: os.Stdout,
|
|
||||||
})}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *watchLogger) Error(msg string, args ...interface{}) {
|
|
||||||
l.isWatchErr = true
|
|
||||||
log.Printf("is watch err: %s", msg)
|
|
||||||
l.Logger.Error(msg, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *watchLogger) Named(name string) hclog.Logger {
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|||||||
7
myregistry/consul/consul_test.go
Normal file
7
myregistry/consul/consul_test.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package consul
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestWatch(t *testing.T) {
|
||||||
|
|
||||||
|
}
|
||||||
132
myregistry/consul/resolver.go
Normal file
132
myregistry/consul/resolver.go
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
package consul
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/hashicorp/consul/api"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"google.golang.org/grpc/resolver"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const schemeName = "consul"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
resolver.Register(&builder{})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ resolver.Builder = (*builder)(nil)
|
||||||
|
|
||||||
|
type builder struct{}
|
||||||
|
|
||||||
|
func (b *builder) Build(url resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
|
||||||
|
tgt, err := parseURL(url.URL.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "Wrong consul URL")
|
||||||
|
}
|
||||||
|
client, err := NewClient(&ClientConfig{
|
||||||
|
Address: tgt.Addr,
|
||||||
|
Token: tgt.Token,
|
||||||
|
Username: tgt.User,
|
||||||
|
Password: tgt.Password,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "Couldn't connect to the Consul API")
|
||||||
|
}
|
||||||
|
|
||||||
|
rl := &consulResolver{
|
||||||
|
client: client,
|
||||||
|
tgt: tgt,
|
||||||
|
cc: cc,
|
||||||
|
}
|
||||||
|
|
||||||
|
go rl.watchService()
|
||||||
|
return rl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *builder) Scheme() string {
|
||||||
|
return schemeName
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ resolver.Resolver = (*consulResolver)(nil)
|
||||||
|
|
||||||
|
type consulResolver struct {
|
||||||
|
client *api.Client
|
||||||
|
tgt *target
|
||||||
|
cc resolver.ClientConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *consulResolver) watchService() {
|
||||||
|
var (
|
||||||
|
lastIndex uint64
|
||||||
|
isFirst = true
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
if isFirst == false {
|
||||||
|
time.Sleep(time.Second * 2)
|
||||||
|
}
|
||||||
|
isFirst = false
|
||||||
|
|
||||||
|
endpoints, meta, err := c.client.Health().Service(c.tgt.Service, c.tgt.Tag,
|
||||||
|
true,
|
||||||
|
&api.QueryOptions{
|
||||||
|
WaitIndex: lastIndex,
|
||||||
|
WaitTime: c.tgt.Wait,
|
||||||
|
Datacenter: c.tgt.Dc,
|
||||||
|
AllowStale: c.tgt.AllowStale,
|
||||||
|
RequireConsistent: c.tgt.RequireConsistent,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
defaultLog.Errorf("watch service err: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lastIndex = meta.LastIndex
|
||||||
|
|
||||||
|
addrs := make([]string, 0, len(endpoints))
|
||||||
|
|
||||||
|
state := resolver.State{
|
||||||
|
Addresses: make([]resolver.Address, 0, len(endpoints)),
|
||||||
|
}
|
||||||
|
for _, endpoint := range endpoints {
|
||||||
|
tmp := resolver.Address{
|
||||||
|
Addr: endpoint.Service.Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmp.Addr == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp.Addr = fmt.Sprintf("%s:%d", tmp.Addr, endpoint.Service.Port)
|
||||||
|
|
||||||
|
state.Addresses = append(state.Addresses, tmp)
|
||||||
|
addrs = append(addrs, tmp.Addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(state.Addresses) == 0 {
|
||||||
|
defaultLog.Warnf("%s services num == 0", c.tgt.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.SliceStable(state.Addresses, func(i, j int) bool {
|
||||||
|
return state.Addresses[i].Addr < state.Addresses[j].Addr
|
||||||
|
})
|
||||||
|
|
||||||
|
err = c.cc.UpdateState(state)
|
||||||
|
if err != nil {
|
||||||
|
defaultLog.Errorf("%s update service state err: %s", err, c.tgt.String())
|
||||||
|
} else {
|
||||||
|
defaultLog.Infof("%s update service num:%d (%s)", c.tgt.String(), len(addrs), strings.Join(addrs, ", "))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *consulResolver) ResolveNow(options resolver.ResolveNowOptions) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *consulResolver) Close() {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -29,30 +29,31 @@ type target struct {
|
|||||||
Dc string `form:"dc"`
|
Dc string `form:"dc"`
|
||||||
AllowStale bool `form:"allow-stale"`
|
AllowStale bool `form:"allow-stale"`
|
||||||
RequireConsistent bool `form:"require-consistent"`
|
RequireConsistent bool `form:"require-consistent"`
|
||||||
// TODO(mbobakov): custom parameters for the http-transport
|
|
||||||
// TODO(mbobakov): custom parameters for the TLS subsystem
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *target) String() string {
|
func (t *target) String() string {
|
||||||
return fmt.Sprintf("service='%s' healthy='%t' tag='%s'", t.Service, t.Healthy, t.Tag)
|
str := t.Service
|
||||||
|
if t.Tag != "" {
|
||||||
|
str = fmt.Sprintf("%s (tag:%s)", str, t.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
return str
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseURL with parameters
|
func parseURL(u string) (*target, error) {
|
||||||
// see README.md for the actual format
|
|
||||||
// URL schema will stay stable in the future for backward compatibility
|
|
||||||
func parseURL(u string) (target, error) {
|
|
||||||
rawURL, err := url.Parse(u)
|
rawURL, err := url.Parse(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return target{}, errors.Wrap(err, "Malformed URL")
|
return nil, errors.Wrap(err, "Malformed URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
if rawURL.Scheme != schemeName ||
|
if rawURL.Scheme != schemeName ||
|
||||||
len(rawURL.Host) == 0 || len(strings.TrimLeft(rawURL.Path, "/")) == 0 {
|
len(rawURL.Host) == 0 || len(strings.TrimLeft(rawURL.Path, "/")) == 0 {
|
||||||
return target{},
|
return nil,
|
||||||
errors.Errorf("Malformed URL('%s'). Must be in the next format: 'consul://[user:passwd]@host/service?param=value'", u)
|
errors.Errorf("Malformed URL('%s'). Must be in the next format: 'consul://[user:passwd]@host/service?param=value'", u)
|
||||||
}
|
}
|
||||||
|
|
||||||
var tgt target
|
tgt := &target{}
|
||||||
tgt.User = rawURL.User.Username()
|
tgt.User = rawURL.User.Username()
|
||||||
tgt.Password, _ = rawURL.User.Password()
|
tgt.Password, _ = rawURL.User.Password()
|
||||||
tgt.Addr = rawURL.Host
|
tgt.Addr = rawURL.Host
|
||||||
@@ -64,7 +65,7 @@ func parseURL(u string) (target, error) {
|
|||||||
|
|
||||||
err = decoder.Decode(&tgt, rawURL.Query())
|
err = decoder.Decode(&tgt, rawURL.Query())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return target{}, errors.Wrap(err, "Malformed URL parameters")
|
return nil, errors.Wrap(err, "Malformed URL parameters")
|
||||||
}
|
}
|
||||||
if len(tgt.Near) == 0 {
|
if len(tgt.Near) == 0 {
|
||||||
tgt.Near = "_agent"
|
tgt.Near = "_agent"
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ var (
|
|||||||
trans ut.Translator
|
trans ut.Translator
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
vt = validator.New()
|
||||||
|
trans = RegisterTranslate(vt)
|
||||||
|
}
|
||||||
|
|
||||||
func Init() {
|
func Init() {
|
||||||
vt = validator.New()
|
vt = validator.New()
|
||||||
trans = RegisterTranslate(vt)
|
trans = RegisterTranslate(vt)
|
||||||
|
|||||||
Reference in New Issue
Block a user