Compare commits

...

70 Commits
v0.3.5 ... main

Author SHA1 Message Date
533a0ea43a
proxy: add RegisterServiceDescs
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2025-04-14 09:34:15 +02:00
8999dedd32
proxy: use grpc interfaces instead of *grpc.Server and *grpc.ClientConn, use Service and service Options
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2025-04-14 08:56:55 +02:00
82e4d9a944
errors: fix IsCanceled typo
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2025-03-25 14:27:27 +01:00
8eb63902b0
service: do not use cmux if there is no http server
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2025-03-18 13:23:34 +01:00
67e1c9be8e
cli: use logger
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2025-03-18 13:19:39 +01:00
939c060513
service: fix alpn certificate usage
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2024-12-21 14:44:56 +01:00
174aa3a497
migrate grpc-web to traefik fork
close #12

Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2024-10-17 18:30:48 +02:00
abe69f1c80
remove client pool and add tls client auth support
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2024-10-17 18:09:58 +02:00
3a3d77169c
interceptors: migrate to otel and add logging interceptor
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2024-10-17 17:15:05 +02:00
9591a64e09
add grpc-proxy (github.com/mwitkow/grpc-proxy)
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2024-10-16 15:04:43 +02:00
ccf44285f9
upgrade dependencies
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2024-10-10 16:07:26 +02:00
fd51ed5961
cli: fix usage format when multiple env vars are set
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2024-07-11 14:10:07 +02:00
198bd2bd59
certs: reload on both key and cert changes
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-11-24 20:07:15 +01:00
dependabot[bot]
efaa4bd14f build(deps): bump go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
Bumps [go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc](https://github.com/open-telemetry/opentelemetry-go-contrib) from 0.44.0 to 0.46.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-go-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-go-contrib/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-go-contrib/compare/zpages/v0.44.0...zpages/v0.46.0)

---
updated-dependencies:
- dependency-name: go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-14 16:57:22 +01:00
2380a4386c
update deps
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-11-14 16:52:29 +01:00
5d6b16a2c2
chore: upgrade dependencies
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-10-26 16:58:32 +02:00
0b4d636ec2
add cobra command utilities and log formatter
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-10-17 15:30:32 +02:00
8fd170c0a8
deps: update all
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-09-16 16:29:22 +02:00
37b09f9f54
add proxy protocol support
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-09-16 16:24:55 +02:00
c0c19683cf
add h2c/http2 support with WithoutCmux option
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-09-16 16:21:51 +02:00
f455c9994c
service: add Serve method
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-08-25 17:15:52 +02:00
97f48d30c0
certs: add Load function to watch for key and certificate changes
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-08-15 18:16:29 +01:00
b52ae2c670
logger.WithReportCaller: fix enabled by default
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-08-11 19:34:42 +02:00
724d6103c6
logger.WithReportCaller: allow custom depth
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-08-11 19:02:22 +02:00
ef3af1e4d9
logger: add missing Clone interface method
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-08-04 22:50:11 +02:00
2f163ab7d1
logger: add WithReportCaller and Clone
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-08-04 22:47:21 +02:00
0e1fe17b97
service: fix possible deadlock on close
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-08-04 18:51:17 +02:00
df505b58d7
rename module grpc-toolkit
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-07-08 01:33:10 +02:00
1fa30d9706
mux: run http server if gateway | grpcWeb | react | mux is defined
goroutines: use errgroup
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-07-08 01:29:18 +02:00
36f3c5bc81
service: WithReact: use fs.FS instead of embed.FS
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-07-07 14:08:34 +02:00
9baceef381
service: signals add syscall.SIGTERM
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-04-19 14:25:44 +02:00
3af87d65d6
codec: register on init
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-04-19 14:24:28 +02:00
e295da1988
service: flags: add missing cert options
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-03-11 12:30:32 +01:00
82d04d63b6
refactor: remove ioutil module usage
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-03-11 12:27:30 +01:00
291c4f6361
deps: upgrade go to 1.20 and update dependencies
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-03-08 11:51:28 +01:00
19787f85ca
errors: add BadRequestDetails
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-03-08 11:44:25 +01:00
e70369a902
logger: add WithContext support
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-02-21 19:53:25 +01:00
2bb79e6c11
deps: upgrade grpc
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-02-21 14:47:43 +01:00
8f75b6aca4
react: use fs.FS instead of embed.FS
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2023-01-20 14:33:52 +01:00
926af303e8
upgrade logrus, add logger.Trace support
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-12-22 10:27:33 +01:00
ef9a12d89e
ban: more defaults options, simpler callback
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-12-07 14:09:36 +01:00
01b37a0d91
metrics interceptors: fix default server registration
metrics interceptors: add missing client histogram methods

Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-11-06 22:51:03 +01:00
dcd2f18f65
auth interceptors: preserve error message
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-11-03 17:13:28 +01:00
1d3d5315a4
ban: remove port from DefaultActorFunc
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-10-26 12:02:53 +02:00
23f1b78389
logger: add Logger method to access *logrus.Logger
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-10-18 18:26:39 +02:00
c7096975b1
interceptors: add ban
health: set services serving on start and not available on close

Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-09-27 17:06:18 +02:00
9bf4e691ce
add WithListener option
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-09-05 15:32:33 +02:00
b230278441
add unix socket support
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-09-05 12:33:01 +02:00
23370fd04d
react: add DevEnv utility
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-09-05 11:35:09 +02:00
7c3a338748
react: export REACT_ENDPOINT env var, recovery interceptor: remove panicing client handlers
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-09-05 11:32:24 +02:00
d5210f8db5
interceptors: add chain interceptors
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-08-29 16:16:46 +02:00
8e6cfd2daa
remove gorm support
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-07-26 07:55:18 +02:00
9729fb8b8a
breaking change: auth options now takes fully qualified method names
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-07-16 19:03:44 +02:00
4de0ec6a3b
add metadata forwarder server interceptors
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-07-16 16:43:00 +02:00
6e86120943
add server interface interceptors
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-07-15 15:48:09 +02:00
bb7e4b124b
logger: add FromLogrus
service: use logger instead of logrus

Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-07-02 12:39:52 +02:00
d8443ee470
logger: add SetOutput method
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-07-01 22:09:20 +02:00
3fb566cb80
errors: add missing canceled
example: add auth
metadata interceptor: do not overide metadatas

Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-06-24 11:47:09 +02:00
79771e58c1
validation: recursive error check on "embedded message failed validation"
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-06-07 13:09:47 +02:00
5662486b3b
validation: set errdetails.BadRequest_FieldViolation slice in errdetails.BadRequest as details
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-06-07 12:49:58 +02:00
ec06b7c4a2
service: add react web app serving option
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-06-03 13:12:19 +02:00
9174446b2c
logger: add FieldLogger() logrus.FieldLogger
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-06-02 23:35:38 +02:00
c10ac23ece
add react single page app handler (proxy to dev server when $REACT_ENDPOINT is defined
fix example

Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-05-26 12:14:54 +02:00
70913ba556
shutdown: catch Drain() is not implemented
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-05-15 14:12:38 +02:00
e5a45801a1
validation: fix undefined: errors.InvalidArgumentD
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-05-04 16:30:27 +02:00
01eb1bddea
errors: remove {Code}D and {Code}F methods
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-03-24 13:53:05 +01:00
0754c0de00
logger: add SetLevel and Logr for logr interoperability
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-03-24 13:48:24 +01:00
884d59e280
codec: add codec to support both vtproto and google.golang.org/protobuf and probably gogoproto
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-03-17 15:22:31 +01:00
60234cceb3
errors: add Is{Code}(err error) bool methods
close #3

Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-03-13 17:38:45 +01:00
8e490c0bae
metrics: add histogram support
Signed-off-by: Adphi <philippe.adrien.nousse@gmail.com>
2022-03-12 00:42:36 +01:00
101 changed files with 7106 additions and 1858 deletions

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
.idea
.bin
/tmp
diff

View File

@ -1,4 +1,4 @@
MODULE = go.linka.cloud/grpc
MODULE = go.linka.cloud/grpc-toolkit
PROTO_BASE_PATH = $(PWD)

View File

@ -1,4 +1,4 @@
# gRPC
# gRPC-toolkit
A utility module, largely taken from the [go-micro](https://github.com/micro/go-micro) patterns (and a good amount of code too...)
with pure gRPC ecosystem modules.
@ -9,7 +9,6 @@ Principles:
Features:
- [x] simple configuration with options
- [x] embedded gorm database with options (branch db)
- [x] simple TLS configuration
- [ ] TLS auth
- [ ] client connection pool
@ -24,7 +23,7 @@ Features:
- [ ] context logger
- [x] sentry
- [ ] rate-limiting
- [ ] ban
- [x] ban
- [ ] auth claim in context
- [x] recovery (server side only)
- [x] tracing (open-tracing)

View File

@ -2,6 +2,7 @@ package certs
import (
"bytes"
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
@ -9,9 +10,17 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"net"
"os"
"strings"
"sync"
"time"
"go.linka.cloud/grpc-toolkit/config"
"go.linka.cloud/grpc-toolkit/config/file"
"go.linka.cloud/grpc-toolkit/logger"
)
func New(host ...string) (tls.Certificate, error) {
@ -72,3 +81,83 @@ func New(host ...string) (tls.Certificate, error) {
return tls.X509KeyPair(certOut.Bytes(), keyOut.Bytes())
}
func TLSConfig(ctx context.Context, cert, key string) (*tls.Config, error) {
c, err := Load(ctx, cert, key)
if err != nil {
return nil, err
}
return &tls.Config{
GetCertificate: c,
}, nil
}
func Load(ctx context.Context, cert, key string) (func(info *tls.ClientHelloInfo) (*tls.Certificate, error), error) {
c, err := file.NewConfig(cert)
if err != nil {
return nil, fmt.Errorf("failed to load cert: %v", err)
}
k, err := file.NewConfig(key)
if err != nil {
return nil, fmt.Errorf("failed to load key: %v", err)
}
crt, err := load(c, key)
if err != nil {
return nil, fmt.Errorf("failed to load cert: %v", err)
}
var mu sync.RWMutex
kch := make(chan []byte)
if err := k.Watch(ctx, kch); err != nil {
return nil, fmt.Errorf("failed to watch key: %v", err)
}
cch := make(chan []byte)
if err := c.Watch(ctx, cch); err != nil {
return nil, fmt.Errorf("failed to watch cert: %v", err)
}
reload := func() {
c, err := load(c, key)
// ignore errors due to cert and key not matching as this is expected
// when the cert is being reloaded and the key is not yet updated or vice versa
if err != nil && !strings.Contains(err.Error(), "does not match") {
logger.C(ctx).Errorf("failed to reload cert: %v", err)
return
}
mu.Lock()
crt = c
mu.Unlock()
}
go func() {
for {
select {
case <-kch:
reload()
case <-cch:
reload()
case <-ctx.Done():
return
}
}
}()
return func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
mu.RLock()
defer mu.RUnlock()
return crt, nil
}, nil
}
func load(cert config.Config, key string) (*tls.Certificate, error) {
cb, err := cert.Read()
if err != nil {
return nil, fmt.Errorf("failed to read cert: %v", err)
}
kb, err := os.ReadFile(key)
if err != nil {
return nil, fmt.Errorf("failed to read key: %v", err)
}
c, err := tls.X509KeyPair(cb, kb)
if err != nil {
return nil, err
}
return &c, nil
}

96
certs/certs_test.go Normal file
View File

@ -0,0 +1,96 @@
package certs
import (
"context"
"crypto/ecdsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestLoad(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
t.Run("missing", func(t *testing.T) {
fn, err := Load(ctx, "missing", "missing")
require.Error(t, err)
require.Nil(t, fn)
})
dir, err := os.MkdirTemp("", "certs")
require.NoError(t, err)
defer os.RemoveAll(dir)
var (
want tls.Certificate
fn func(*tls.ClientHelloInfo) (*tls.Certificate, error)
)
t.Run("load", func(t *testing.T) {
want, err = New("acme.org")
require.NoError(t, err)
require.NotNil(t, want.PrivateKey)
require.NotEmpty(t, want.Certificate)
write(t, dir, want)
fn, err = Load(ctx, filepath.Join(dir, "cert.pem"), filepath.Join(dir, "key.pem"))
require.NoError(t, err)
require.NotNil(t, fn)
got, err := fn(nil)
require.NoError(t, err)
require.NotNil(t, got)
require.Equal(t, want.Certificate, got.Certificate)
require.Equal(t, want.PrivateKey, got.PrivateKey)
})
t.Run("reload", func(t *testing.T) {
for i := 0; i < 10; i++ {
want, err = New("acme.org")
require.NoError(t, err)
write(t, dir, want)
time.Sleep(100 * time.Millisecond)
got, err := fn(nil)
require.NoError(t, err)
require.NotNil(t, got)
require.Equal(t, want.Certificate, got.Certificate)
require.Equal(t, want.PrivateKey, got.PrivateKey)
require.Equal(t, want.Leaf, got.Leaf)
}
})
t.Run("removed", func(t *testing.T) {
require.NoError(t, os.Remove(filepath.Join(dir, "cert.pem")))
require.NoError(t, os.Remove(filepath.Join(dir, "key.pem")))
got, err := fn(nil)
require.NoError(t, err)
require.NotNil(t, got)
require.Equal(t, want.Certificate, got.Certificate)
require.Equal(t, want.PrivateKey, got.PrivateKey)
})
}
func write(t *testing.T, dir string, cert tls.Certificate) {
crt, err := os.Create(filepath.Join(dir, "cert.pem"))
require.NoError(t, err)
defer crt.Close()
require.NoError(t, pem.Encode(crt, &pem.Block{
Type: "CERTIFICATE",
Bytes: cert.Certificate[0],
}))
if err := crt.Sync(); err != nil {
t.Fatal(err)
}
key, err := os.Create(filepath.Join(dir, "key.pem"))
require.NoError(t, err)
defer key.Close()
b, err := x509.MarshalECPrivateKey(cert.PrivateKey.(*ecdsa.PrivateKey))
require.NoError(t, err)
require.NoError(t, pem.Encode(key, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: b,
}))
if err := key.Sync(); err != nil {
t.Fatal(err)
}
}

84
cli/clifmt/formatter.go Normal file
View File

@ -0,0 +1,84 @@
// Copyright 2023 Linka Cloud All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package clifmt
import (
"bytes"
"strings"
"time"
"github.com/fatih/color"
"github.com/sirupsen/logrus"
)
type TimeFormat string
const (
NoneTimeFormat TimeFormat = "none"
FullTimeFormat TimeFormat = "full"
RelativeTimeFormat TimeFormat = "relative"
)
const (
red = 31
yellow = 33
blue = 36
white = 39
gray = 90
)
func New(f TimeFormat) logrus.Formatter {
return &clifmt{start: time.Now(), format: f}
}
type clifmt struct {
start time.Time
format TimeFormat
}
func (f *clifmt) Format(entry *logrus.Entry) ([]byte, error) {
var b bytes.Buffer
var c *color.Color
switch entry.Level {
case logrus.DebugLevel, logrus.TraceLevel:
c = color.New(gray)
case logrus.WarnLevel:
c = color.New(yellow)
case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
c = color.New(red)
default:
c = color.New(white)
}
msg := entry.Message
if len(entry.Message) > 0 && entry.Level < logrus.DebugLevel {
msg = strings.ToTitle(string(msg[0])) + msg[1:]
}
var err error
switch f.format {
case FullTimeFormat:
_, err = c.Fprintf(&b, "[%s] %s\n", entry.Time.Format("2006-01-02 15:04:05"), entry.Message)
case RelativeTimeFormat:
_, err = c.Fprintf(&b, "[%5v] %s\n", entry.Time.Sub(f.start).Truncate(time.Second).String(), msg)
case NoneTimeFormat:
fallthrough
default:
_, err = c.Fprintln(&b, msg)
}
if err != nil {
return nil, err
}
return b.Bytes(), nil
}

297
cli/command.go Normal file
View File

@ -0,0 +1,297 @@
// Package cli is adapted from https://github.com/rancher/wrangler-cli
package cli
import (
"fmt"
"os"
"reflect"
"regexp"
"strconv"
"strings"
"unsafe"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"go.linka.cloud/grpc-toolkit/logger"
"go.linka.cloud/grpc-toolkit/signals"
)
var (
caseRegexp = regexp.MustCompile("([a-z])([A-Z])")
)
type PersistentPreRunnable interface {
PersistentPre(cmd *cobra.Command, args []string) error
}
type PreRunnable interface {
Pre(cmd *cobra.Command, args []string) error
}
type Runnable interface {
Run(cmd *cobra.Command, args []string) error
}
type customizer interface {
Customize(cmd *cobra.Command)
}
type fieldInfo struct {
FieldType reflect.StructField
FieldValue reflect.Value
}
func fields(obj interface{}) []fieldInfo {
ptrValue := reflect.ValueOf(obj)
objValue := ptrValue.Elem()
var result []fieldInfo
for i := 0; i < objValue.NumField(); i++ {
fieldType := objValue.Type().Field(i)
if !fieldType.IsExported() {
continue
}
if fieldType.Anonymous && fieldType.Type.Kind() == reflect.Struct {
result = append(result, fields(objValue.Field(i).Addr().Interface())...)
} else if !fieldType.Anonymous {
result = append(result, fieldInfo{
FieldValue: objValue.Field(i),
FieldType: objValue.Type().Field(i),
})
}
}
return result
}
func Name(obj interface{}) string {
ptrValue := reflect.ValueOf(obj)
objValue := ptrValue.Elem()
commandName := strings.Replace(objValue.Type().Name(), "Command", "", 1)
commandName, _ = name(commandName, "", "")
return commandName
}
func Main(cmd *cobra.Command) {
ctx := signals.SetupSignalHandler()
if err := cmd.ExecuteContext(ctx); err != nil {
logger.C(ctx).Fatal(err)
}
}
func makeEnvVar[T comparable](to []func(), name string, vars []string, defValue T, flags *pflag.FlagSet, fn func(flag string) (T, error)) []func() {
for _, v := range vars {
to = append(to, func() {
v := os.Getenv(v)
if v == "" {
return
}
fv, err := fn(name)
if err == nil && fv == defValue {
flags.Set(name, v)
}
})
}
return to
}
// Command populates a obj.Command() object by extracting args from struct tags of the
// Runnable obj passed. Also the Run method is assigned to the RunE of the cli.
// name = Override the struct field with
func Command(obj Runnable, c *cobra.Command) *cobra.Command {
var (
envs []func()
arrays = map[string]reflect.Value{}
slices = map[string]reflect.Value{}
maps = map[string]reflect.Value{}
ptrValue = reflect.ValueOf(obj)
objValue = ptrValue.Elem()
)
if c.Use == "" {
c.Use = Name(obj)
}
for _, info := range fields(obj) {
fieldType := info.FieldType
v := info.FieldValue
name, alias := name(fieldType.Name, fieldType.Tag.Get("name"), fieldType.Tag.Get("short"))
usage := fieldType.Tag.Get("usage")
envVars := strings.Split(fieldType.Tag.Get("env"), ",")
defValue := fieldType.Tag.Get("default")
if len(envVars) == 1 && envVars[0] == "" {
envVars = nil
}
for _, v := range envVars {
if v == "" {
continue
}
usage += fmt.Sprintf(" [$%s]", v)
}
defInt, err := strconv.Atoi(defValue)
if err != nil {
defInt = 0
}
defValueLower := strings.ToLower(defValue)
defBool := defValueLower == "true" || defValueLower == "1" || defValueLower == "yes" || defValueLower == "y"
flags := c.PersistentFlags()
switch fieldType.Type.Kind() {
case reflect.Int:
flags.IntVarP((*int)(unsafe.Pointer(v.Addr().Pointer())), name, alias, defInt, usage)
envs = append(envs, makeEnvVar(envs, name, envVars, defInt, flags, flags.GetInt)...)
case reflect.String:
flags.StringVarP((*string)(unsafe.Pointer(v.Addr().Pointer())), name, alias, defValue, usage)
envs = append(envs, makeEnvVar(envs, name, envVars, defValue, flags, flags.GetString)...)
case reflect.Slice:
// env is not supported for slices
switch fieldType.Tag.Get("split") {
case "false":
arrays[name] = v
flags.StringArrayP(name, alias, nil, usage)
default:
slices[name] = v
flags.StringSliceP(name, alias, nil, usage)
}
case reflect.Map:
maps[name] = v
flags.StringSliceP(name, alias, nil, usage)
case reflect.Bool:
flags.BoolVarP((*bool)(unsafe.Pointer(v.Addr().Pointer())), name, alias, defBool, usage)
envs = append(envs, makeEnvVar(envs, name, envVars, defBool, flags, flags.GetBool)...)
default:
panic("Unknown kind on field " + fieldType.Name + " on " + objValue.Type().Name())
}
if len(envVars) == 0 {
continue
}
}
if p, ok := obj.(PersistentPreRunnable); ok {
c.PersistentPreRunE = p.PersistentPre
}
if p, ok := obj.(PreRunnable); ok {
c.PreRunE = p.Pre
}
c.RunE = obj.Run
c.PersistentPreRunE = bind(c.PersistentPreRunE, arrays, slices, maps, envs)
c.PreRunE = bind(c.PreRunE, arrays, slices, maps, envs)
c.RunE = bind(c.RunE, arrays, slices, maps, envs)
cust, ok := obj.(customizer)
if ok {
cust.Customize(c)
}
return c
}
func assignMaps(app *cobra.Command, maps map[string]reflect.Value) error {
for k, v := range maps {
k = contextKey(k)
s, err := app.Flags().GetStringSlice(k)
if err != nil {
return err
}
if s != nil {
values := map[string]string{}
for _, part := range s {
parts := strings.SplitN(part, "=", 2)
if len(parts) == 1 {
values[parts[0]] = ""
} else {
values[parts[0]] = parts[1]
}
}
v.Set(reflect.ValueOf(values))
}
}
return nil
}
func assignSlices(app *cobra.Command, slices map[string]reflect.Value) error {
for k, v := range slices {
k = contextKey(k)
s, err := app.Flags().GetStringSlice(k)
if err != nil {
return err
}
if s != nil {
v.Set(reflect.ValueOf(s[:]))
}
}
return nil
}
func assignArrays(app *cobra.Command, arrays map[string]reflect.Value) error {
for k, v := range arrays {
k = contextKey(k)
s, err := app.Flags().GetStringArray(k)
if err != nil {
return err
}
if s != nil {
v.Set(reflect.ValueOf(s[:]))
}
}
return nil
}
func contextKey(name string) string {
parts := strings.Split(name, ",")
return parts[len(parts)-1]
}
func name(name, setName, short string) (string, string) {
if setName != "" {
return setName, short
}
parts := strings.Split(name, "_")
i := len(parts) - 1
name = caseRegexp.ReplaceAllString(parts[i], "$1-$2")
name = strings.ToLower(name)
result := append([]string{name}, parts[0:i]...)
for i := 0; i < len(result); i++ {
result[i] = strings.ToLower(result[i])
}
if short == "" && len(result) > 1 {
short = result[1]
}
return result[0], short
}
func bind(next func(*cobra.Command, []string) error,
arrays map[string]reflect.Value,
slices map[string]reflect.Value,
maps map[string]reflect.Value,
envs []func()) func(*cobra.Command, []string) error {
if next == nil {
return nil
}
return func(cmd *cobra.Command, args []string) error {
for _, envCallback := range envs {
envCallback()
}
if err := assignArrays(cmd, arrays); err != nil {
return err
}
if err := assignSlices(cmd, slices); err != nil {
return err
}
if err := assignMaps(cmd, maps); err != nil {
return err
}
if next != nil {
return next(cmd, args)
}
return nil
}
}

49
cli/command_test.go Normal file
View File

@ -0,0 +1,49 @@
package cli
import (
"os"
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type testCmd struct {
String string `name:"string" short:"s" usage:"string flag" env:"STRING" default:"string"`
Int int `name:"int" short:"i" usage:"int flag" env:"INT" default:"1"`
Bool bool `name:"bool" short:"b" usage:"bool flag" env:"BOOL" default:"true"`
StringSlice []string `name:"string-slice" short:"S" usage:"string slice flag"`
}
func (c *testCmd) Run(cmd *cobra.Command, args []string) error {
return nil
}
func TestCommand(t *testing.T) {
var c testCmd
cmd := Command(&c, &cobra.Command{
Short: "test",
})
require.NoError(t, cmd.Execute())
assert.Equal(t, "string", c.String)
assert.Equal(t, 1, c.Int)
assert.Equal(t, true, c.Bool)
assert.Equal(t, []string{}, c.StringSlice)
}
func TestCommandEnv(t *testing.T) {
require.NoError(t, os.Setenv("STRING", "env-string"))
require.NoError(t, os.Setenv("INT", "2"))
require.NoError(t, os.Setenv("BOOL", "false"))
require.NoError(t, os.Setenv("STRING_SLICE", "env-string1,env-string2"))
var c testCmd
cmd := Command(&c, &cobra.Command{
Short: "test",
})
require.NoError(t, cmd.Execute())
assert.Equal(t, "env-string", c.String)
assert.Equal(t, 2, c.Int)
assert.Equal(t, false, c.Bool)
assert.Equal(t, []string{}, c.StringSlice)
}

View File

@ -2,16 +2,16 @@ package client
import (
"context"
"crypto/tls"
"fmt"
"strings"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/resolver"
"go.linka.cloud/grpc/registry/noop"
"go.linka.cloud/grpc-toolkit/registry/noop"
)
type Client interface {
@ -27,15 +27,14 @@ func New(opts ...Option) (Client, error) {
c.opts.registry = noop.New()
}
resolver.Register(c.opts.registry.ResolverBuilder())
c.pool = newPool(DefaultPoolSize, DefaultPoolTTL, DefaultPoolMaxIdle, DefaultPoolMaxStreams)
if c.opts.tlsConfig == nil && c.opts.Secure() {
c.opts.tlsConfig = &tls.Config{InsecureSkipVerify: true}
if err := c.opts.parseTLSConfig(); err != nil {
return nil, err
}
if c.opts.tlsConfig != nil {
c.opts.dialOptions = append(c.opts.dialOptions, grpc.WithTransportCredentials(credentials.NewTLS(c.opts.tlsConfig)))
}
if !c.opts.secure {
c.opts.dialOptions = append(c.opts.dialOptions, grpc.WithInsecure())
if !c.opts.secure && c.opts.tlsConfig == nil {
c.opts.dialOptions = append(c.opts.dialOptions, grpc.WithTransportCredentials(insecure.NewCredentials()))
}
if len(c.opts.unaryInterceptors) > 0 {
c.opts.dialOptions = append(c.opts.dialOptions, grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(c.opts.unaryInterceptors...)))
@ -43,37 +42,39 @@ func New(opts ...Option) (Client, error) {
if len(c.opts.streamInterceptors) > 0 {
c.opts.dialOptions = append(c.opts.dialOptions, grpc.WithStreamInterceptor(grpc_middleware.ChainStreamClient(c.opts.streamInterceptors...)))
}
if c.opts.addr == "" {
switch {
case c.opts.addr == "":
c.addr = fmt.Sprintf("%s:///%s", c.opts.registry.String(), c.opts.name)
} else if strings.HasPrefix(c.opts.addr, "tcp://") {
case strings.HasPrefix(c.opts.addr, "tcp://"):
c.addr = strings.Replace(c.opts.addr, "tcp://", "", 1)
} else {
case strings.HasPrefix(c.opts.addr, "unix:///"):
c.addr = c.opts.addr
case strings.HasPrefix(c.opts.addr, "unix://"):
c.addr = strings.Replace(c.opts.addr, "unix://", "unix:", 1)
default:
c.addr = c.opts.addr
}
if c.opts.version != "" && c.opts.addr == "" {
c.addr = c.addr + ":" + strings.TrimSpace(c.opts.version)
}
cc, err := grpc.Dial(c.addr, c.opts.dialOptions...)
if err != nil {
return nil, err
}
c.cc = cc
return c, nil
}
type client struct {
addr string
pool *pool
opts *options
cc *grpc.ClientConn
}
func (c *client) Invoke(ctx context.Context, method string, args interface{}, reply interface{}, opts ...grpc.CallOption) error {
pc, err := c.pool.getConn(c.addr, c.opts.dialOptions...)
if err != nil {
return err
}
return pc.Invoke(ctx, method, args, reply, opts...)
return c.cc.Invoke(ctx, method, args, reply, opts...)
}
func (c *client) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) {
pc, err := c.pool.getConn(c.addr, c.opts.dialOptions...)
if err != nil {
return nil, err
}
return pc.NewStream(ctx, desc, method, opts...)
return c.cc.NewStream(ctx, desc, method, opts...)
}

View File

@ -14,27 +14,29 @@ func NewFlagSet() (*pflag.FlagSet, Option) {
const (
addr = "address"
secure = "secure"
// caCert = "ca-cert"
// clientCert = "client-cert"
// clientKey = "client-key"
caCert = "ca-cert"
clientCert = "client-cert"
clientKey = "client-key"
)
var (
optAddress string
optSecure bool
// optCACert string
// optCert string
// optKey string
optCACert string
optCert string
optKey string
)
flags := pflag.NewFlagSet("gRPC", pflag.ContinueOnError)
flags.StringVar(&optAddress, addr, env.GetDefault(u(addr), "0.0.0.0:0"), "Bind address for the server. 127.0.0.1:9090"+flagEnv(addr))
flags.BoolVar(&optSecure, secure, env.GetBoolDefault(u(secure), true), "Generate self signed certificate if none provided"+flagEnv(secure))
// flags.StringVar(&optCACert, caCert, "", "Path to Root CA certificate"+flagEnv(optCACert))
// flags.StringVar(&optCert, clientCert, "", "Path to Server certificate"+flagEnv(clientCert))
// flags.StringVar(&optKey, clientKey, "", "Path to Server key"+flagEnv(clientKey))
flags.StringVar(&optCACert, caCert, "", "Path to Root CA certificate"+flagEnv(optCACert))
flags.StringVar(&optCert, clientCert, "", "Path to Server certificate"+flagEnv(clientCert))
flags.StringVar(&optKey, clientKey, "", "Path to Server key"+flagEnv(clientKey))
return flags, func(o *options) {
o.addr = optAddress
o.secure = optSecure
o.caCert = optCACert
o.cert = optCert
o.key = optKey
}
}

View File

@ -2,11 +2,14 @@ package client
import (
"crypto/tls"
"crypto/x509"
"fmt"
"os"
"google.golang.org/grpc"
"go.linka.cloud/grpc/interceptors"
"go.linka.cloud/grpc/registry"
"go.linka.cloud/grpc-toolkit/interceptors"
"go.linka.cloud/grpc-toolkit/registry"
)
type Options interface {
@ -15,6 +18,9 @@ type Options interface {
Address() string
Secure() bool
Registry() registry.Registry
CA() string
Cert() string
Key() string
TLSConfig() *tls.Config
DialOptions() []grpc.DialOption
UnaryInterceptors() []grpc.UnaryClientInterceptor
@ -47,6 +53,24 @@ func WithAddress(address string) Option {
}
}
func WithCA(ca string) Option {
return func(o *options) {
o.caCert = ca
}
}
func WithCert(cert string) Option {
return func(o *options) {
o.cert = cert
}
}
func WithKey(key string) Option {
return func(o *options) {
o.key = key
}
}
func WithTLSConfig(conf *tls.Config) Option {
return func(o *options) {
o.tlsConfig = conf
@ -91,6 +115,10 @@ type options struct {
name string
version string
addr string
caCert string
cert string
key string
tlsConfig *tls.Config
secure bool
dialOptions []grpc.DialOption
@ -115,6 +143,18 @@ func (o *options) Registry() registry.Registry {
return o.registry
}
func (o *options) CA() string {
return o.caCert
}
func (o *options) Cert() string {
return o.cert
}
func (o *options) Key() string {
return o.key
}
func (o *options) TLSConfig() *tls.Config {
return o.tlsConfig
}
@ -134,3 +174,38 @@ func (o *options) UnaryInterceptors() []grpc.UnaryClientInterceptor {
func (o *options) StreamInterceptors() []grpc.StreamClientInterceptor {
return o.streamInterceptors
}
func (o *options) hasTLSConfig() bool {
return o.caCert != "" && o.cert != "" && o.key != "" && o.tlsConfig == nil
}
func (o *options) parseTLSConfig() error {
if o.tlsConfig != nil {
return nil
}
if !o.hasTLSConfig() {
if !o.secure {
return nil
}
o.tlsConfig = &tls.Config{InsecureSkipVerify: true}
return nil
}
caCert, err := os.ReadFile(o.caCert)
if err != nil {
return err
}
caCertPool := x509.NewCertPool()
ok := caCertPool.AppendCertsFromPEM(caCert)
if !ok {
return fmt.Errorf("failed to load CA Cert from %s", o.caCert)
}
cert, err := tls.LoadX509KeyPair(o.cert, o.key)
if err != nil {
return err
}
o.tlsConfig = &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caCertPool,
}
return nil
}

View File

@ -1,244 +0,0 @@
/*
Taken from the https://github.com/micro/go-micro/client/grpc
*/
package client
import (
"sync"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"
)
var (
// DefaultPoolSize sets the connection pool size
DefaultPoolSize = 100
// DefaultPoolTTL sets the connection pool ttl
DefaultPoolTTL = time.Minute
// DefaultPoolMaxStreams maximum streams on a connectioin
// (20)
DefaultPoolMaxStreams = 20
// DefaultPoolMaxIdle maximum idle conns of a pool
// (50)
DefaultPoolMaxIdle = 50
// DefaultMaxRecvMsgSize maximum message that client can receive
// (4 MB).
DefaultMaxRecvMsgSize = 1024 * 1024 * 4
// DefaultMaxSendMsgSize maximum message that client can send
// (4 MB).
DefaultMaxSendMsgSize = 1024 * 1024 * 4
)
type pool struct {
size int
ttl int64
// max streams on a *poolConn
maxStreams int
// max idle conns
maxIdle int
sync.Mutex
conns map[string]*streamsPool
}
type streamsPool struct {
// head of list
head *poolConn
// busy conns list
busy *poolConn
// the siza of list
count int
// idle conn
idle int
}
type poolConn struct {
// grpc conn
*grpc.ClientConn
err error
addr string
// pool and streams pool
pool *pool
sp *streamsPool
streams int
created int64
// list
pre *poolConn
next *poolConn
in bool
}
func newPool(size int, ttl time.Duration, idle int, ms int) *pool {
if ms <= 0 {
ms = 1
}
if idle < 0 {
idle = 0
}
return &pool{
size: size,
ttl: int64(ttl.Seconds()),
maxStreams: ms,
maxIdle: idle,
conns: make(map[string]*streamsPool),
}
}
func (p *pool) getConn(addr string, opts ...grpc.DialOption) (*poolConn, error) {
now := time.Now().Unix()
p.Lock()
sp, ok := p.conns[addr]
if !ok {
sp = &streamsPool{head: &poolConn{}, busy: &poolConn{}, count: 0, idle: 0}
p.conns[addr] = sp
}
// while we have conns check streams and then return one
// otherwise we'll create a new conn
conn := sp.head.next
for conn != nil {
// check conn state
// https://github.com/grpc/grpc/blob/master/doc/connectivity-semantics-and-api.md
switch conn.GetState() {
case connectivity.Connecting:
conn = conn.next
continue
case connectivity.Shutdown:
next := conn.next
if conn.streams == 0 {
removeConn(conn)
sp.idle--
}
conn = next
continue
case connectivity.TransientFailure:
next := conn.next
if conn.streams == 0 {
removeConn(conn)
conn.ClientConn.Close()
sp.idle--
}
conn = next
continue
case connectivity.Ready:
case connectivity.Idle:
}
// a old conn
if now-conn.created > p.ttl {
next := conn.next
if conn.streams == 0 {
removeConn(conn)
conn.ClientConn.Close()
sp.idle--
}
conn = next
continue
}
// a busy conn
if conn.streams >= p.maxStreams {
next := conn.next
removeConn(conn)
addConnAfter(conn, sp.busy)
conn = next
continue
}
// a idle conn
if conn.streams == 0 {
sp.idle--
}
// a good conn
conn.streams++
p.Unlock()
return conn, nil
}
p.Unlock()
// create new conn
cc, err := grpc.Dial(addr, opts...)
if err != nil {
return nil, err
}
conn = &poolConn{cc, nil, addr, p, sp, 1, time.Now().Unix(), nil, nil, false}
// add conn to streams pool
p.Lock()
if sp.count < p.size {
addConnAfter(conn, sp.head)
}
p.Unlock()
return conn, nil
}
func (p *pool) release(addr string, conn *poolConn, err error) {
p.Lock()
p, sp, created := conn.pool, conn.sp, conn.created
// try to add conn
if !conn.in && sp.count < p.size {
addConnAfter(conn, sp.head)
}
if !conn.in {
p.Unlock()
conn.ClientConn.Close()
return
}
// a busy conn
if conn.streams >= p.maxStreams {
removeConn(conn)
addConnAfter(conn, sp.head)
}
conn.streams--
// if streams == 0, we can do something
if conn.streams == 0 {
// 1. it has errored
// 2. too many idle conn or
// 3. conn is too old
now := time.Now().Unix()
if err != nil || sp.idle >= p.maxIdle || now-created > p.ttl {
removeConn(conn)
p.Unlock()
conn.ClientConn.Close()
return
}
sp.idle++
}
p.Unlock()
return
}
func (conn *poolConn) Close() {
conn.pool.release(conn.addr, conn, conn.err)
}
func removeConn(conn *poolConn) {
if conn.pre != nil {
conn.pre.next = conn.next
}
if conn.next != nil {
conn.next.pre = conn.pre
}
conn.pre = nil
conn.next = nil
conn.in = false
conn.sp.count--
return
}
func addConnAfter(conn *poolConn, after *poolConn) {
conn.next = after.next
conn.pre = after
if after.next != nil {
after.next.pre = conn
}
after.next = conn
conn.in = true
conn.sp.count++
return
}

58
codec/codec.go Normal file
View File

@ -0,0 +1,58 @@
package codec
import (
"fmt"
"google.golang.org/grpc/encoding"
_ "google.golang.org/grpc/encoding/proto"
"google.golang.org/protobuf/proto"
)
// Name is the name registered for the proto compressor.
const Name = "proto"
type Codec struct{}
type vtprotoMessage interface {
MarshalVT() ([]byte, error)
UnmarshalVT([]byte) error
}
type protoMessage interface {
Marshal() ([]byte, error)
Unmarshal([]byte) error
}
func (Codec) Marshal(v interface{}) ([]byte, error) {
switch m := v.(type) {
case vtprotoMessage:
return m.MarshalVT()
case protoMessage:
return m.Marshal()
case proto.Message:
return proto.Marshal(m)
default:
return nil, fmt.Errorf("failed to marshal, message is %T, want proto.Message", v)
}
}
func (Codec) Unmarshal(data []byte, v interface{}) error {
switch m := v.(type) {
case vtprotoMessage:
return m.UnmarshalVT(data)
case protoMessage:
return m.Unmarshal(data)
case proto.Message:
return proto.Unmarshal(data, m)
default:
return fmt.Errorf("failed to unmarshal, message is %T, want proto.Message", v)
}
}
func (Codec) Name() string {
return Name
}
func init() {
encoding.RegisterCodec(Codec{})
}

View File

@ -4,15 +4,14 @@ package file
import (
"context"
"io/ioutil"
"os"
"path/filepath"
"sync"
"github.com/fsnotify/fsnotify"
"go.linka.cloud/grpc/config"
"go.linka.cloud/grpc/logger"
"go.linka.cloud/grpc-toolkit/config"
"go.linka.cloud/grpc-toolkit/logger"
)
func NewConfig(path string) (config.Config, error) {
@ -27,7 +26,7 @@ type file struct {
}
func (c *file) Read() ([]byte, error) {
return ioutil.ReadFile(c.path)
return os.ReadFile(c.path)
}
// Watch listen for config changes and send updated content to the updates channel

View File

@ -4,7 +4,6 @@ package file
import (
"context"
"io/ioutil"
"os"
"os/exec"
"path"
@ -15,12 +14,12 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.linka.cloud/grpc/config"
"go.linka.cloud/grpc-toolkit/config"
)
func newConfigFile(t *testing.T) (config.Config, string, func()) {
path := filepath.Join(os.TempDir(), "config.yaml")
if err := ioutil.WriteFile(path, []byte("ok"), os.ModePerm); err != nil {
if err := os.WriteFile(path, []byte("ok"), os.ModePerm); err != nil {
t.Fatal(err)
}
cleanUp := func() {
@ -32,14 +31,14 @@ func newConfigFile(t *testing.T) (config.Config, string, func()) {
}
func newSymlinkedConfigFile(t *testing.T) (config.Config, string, string, func()) {
watchDir, err := ioutil.TempDir("", "")
watchDir, err := os.MkdirTemp("", "")
require.Nil(t, err)
dataDir1 := path.Join(watchDir, "data1")
err = os.Mkdir(dataDir1, 0o777)
require.Nil(t, err)
realConfigFile := path.Join(dataDir1, "config.yaml")
t.Logf("Real config file location: %s\n", realConfigFile)
err = ioutil.WriteFile(realConfigFile, []byte("foo: bar\n"), 0o640)
err = os.WriteFile(realConfigFile, []byte("foo: bar\n"), 0o640)
require.Nil(t, err)
cleanup := func() {
os.RemoveAll(watchDir)
@ -64,7 +63,7 @@ func TestWatch(t *testing.T) {
t.Fatal(err)
}
// when overwriting the file and waiting for the custom change notification handler to be triggered
err := ioutil.WriteFile(cpath, []byte("foo: baz\n"), 0o640)
err := os.WriteFile(cpath, []byte("foo: baz\n"), 0o640)
b := <-updates
// then the config value should have changed
require.Nil(t, err)
@ -87,7 +86,7 @@ func TestWatch(t *testing.T) {
err := os.MkdirAll(dataDir2, 0o777)
require.NoError(t, err)
configFile2 := path.Join(dataDir2, "config.yaml")
err = ioutil.WriteFile(configFile2, []byte("foo: baz\n"), 0o640)
err = os.WriteFile(configFile2, []byte("foo: baz\n"), 0o640)
require.NoError(t, err)
// change the symlink using the `ln -sfn` command
err = exec.Command("ln", "-sfn", dataDir2, path.Join(watchDir, "data")).Run()

20
errors/details.go Normal file
View File

@ -0,0 +1,20 @@
package errors
import (
"google.golang.org/genproto/googleapis/rpc/errdetails"
)
// BadRequestDetails returns an error details for an invalid argument.
// fd is a list of field / description pairs.
func BadRequestDetails(fd ...string) *errdetails.BadRequest {
var fieldViolations []*errdetails.BadRequest_FieldViolation
for i := 0; i < len(fd); i += 2 {
fieldViolations = append(fieldViolations, &errdetails.BadRequest_FieldViolation{
Field: fd[i],
Description: fd[i+1],
})
}
return &errdetails.BadRequest{
FieldViolations: fieldViolations,
}
}

View File

@ -1,6 +1,10 @@
package errors
import (
"context"
"errors"
"strings"
status2 "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@ -8,132 +12,52 @@ import (
"google.golang.org/protobuf/types/known/anypb"
)
func Canceled(err error) error {
return status.Error(codes.Canceled, err.Error())
}
func InvalidArgument(err error) error {
return status.Error(codes.InvalidArgument, err.Error())
}
func InvalidArgumentF(msg string, args ...interface{}) error {
return status.Errorf(codes.InvalidArgument, msg, args...)
}
func InvalidArgumentD(err error, details ...proto.Message) error {
return statusErr(codes.InvalidArgument, err, details...)
}
func DeadlineExceeded(err error) error {
return status.Error(codes.DeadlineExceeded, err.Error())
}
func DeadlineExceededF(msg string, args ...interface{}) error {
return status.Errorf(codes.DeadlineExceeded, msg, args...)
}
func DeadlineExceededD(err error, details ...proto.Message) error {
return statusErr(codes.DeadlineExceeded, err, details...)
}
func NotFound(err error) error {
return status.Error(codes.NotFound, err.Error())
}
func NotFoundF(msg string, args ...interface{}) error {
return status.Errorf(codes.NotFound, msg, args...)
}
func NotFoundD(err error, details ...proto.Message) error {
return statusErr(codes.NotFound, err, details...)
}
func AlreadyExists(err error) error {
return status.Error(codes.AlreadyExists, err.Error())
}
func AlreadyExistsF(msg string, args ...interface{}) error {
return status.Errorf(codes.AlreadyExists, msg, args...)
}
func AlreadyExistsD(err error, details ...proto.Message) error {
return statusErr(codes.AlreadyExists, err, details...)
}
func PermissionDenied(err error) error {
return status.Error(codes.PermissionDenied, err.Error())
}
func PermissionDeniedF(msg string, args ...interface{}) error {
return status.Errorf(codes.PermissionDenied, msg, args...)
}
func PermissionDeniedD(err error, details ...proto.Message) error {
return statusErr(codes.PermissionDenied, err, details...)
}
func ResourceExhausted(err error) error {
return status.Error(codes.ResourceExhausted, err.Error())
}
func ResourceExhaustedF(msg string, args ...interface{}) error {
return status.Errorf(codes.ResourceExhausted, msg, args...)
}
func ResourceExhaustedD(err error, details ...proto.Message) error {
return statusErr(codes.ResourceExhausted, err, details...)
}
func FailedPrecondition(err error) error {
return status.Error(codes.FailedPrecondition, err.Error())
}
func FailedPreconditionF(msg string, args ...interface{}) error {
return status.Errorf(codes.FailedPrecondition, msg, args...)
}
func FailedPreconditionD(err error, details ...proto.Message) error {
return statusErr(codes.FailedPrecondition, err, details...)
}
func Aborted(err error) error {
return status.Error(codes.Aborted, err.Error())
}
func AbortedF(msg string, args ...interface{}) error {
return status.Errorf(codes.Aborted, msg, args...)
}
func AbortedD(err error, details ...proto.Message) error {
return statusErr(codes.Aborted, err, details...)
}
func OutOfRange(err error) error {
return status.Error(codes.OutOfRange, err.Error())
}
func OutOfRangeF(msg string, args ...interface{}) error {
return status.Errorf(codes.OutOfRange, msg, args...)
}
func OutOfRangeD(err error, details ...proto.Message) error {
return statusErr(codes.OutOfRange, err, details...)
}
func Unimplemented(err error) error {
return status.Error(codes.Unimplemented, err.Error())
}
func UnimplementedF(msg string, args ...interface{}) error {
return status.Errorf(codes.Unimplemented, msg, args...)
}
func UnimplementedD(err error, details ...proto.Message) error {
return statusErr(codes.Unimplemented, err, details...)
}
func Internal(err error) error {
return status.Error(codes.Internal, err.Error())
}
func InternalF(msg string, args ...interface{}) error {
return status.Errorf(codes.Internal, msg, args...)
}
func InternalD(err error, details ...proto.Message) error {
return statusErr(codes.Internal, err, details...)
}
func Unavailable(err error) error {
return status.Error(codes.Unavailable, err.Error())
}
func UnavailableF(msg string, args ...interface{}) error {
return status.Errorf(codes.Unavailable, msg, args...)
}
func UnavailableD(err error, details ...proto.Message) error {
return statusErr(codes.Unavailable, err, details...)
}
func DataLoss(err error) error {
return status.Error(codes.DataLoss, err.Error())
}
func DataLossF(msg string, args ...interface{}) error {
return status.Errorf(codes.DataLoss, msg, args...)
}
func DataLossD(err error, details ...proto.Message) error {
return statusErr(codes.DataLoss, err, details...)
}
func Unauthenticated(err error) error {
return status.Error(codes.Unauthenticated, err.Error())
}
func UnauthenticatedF(msg string, args ...interface{}) error {
return status.Errorf(codes.Unauthenticated, msg, args...)
}
func UnauthenticatedD(err error, details ...proto.Message) error {
return statusErr(codes.Unauthenticated, err, details...)
}
func statusErr(code codes.Code, err error, details ...proto.Message) error {
return status.FromProto(&status2.Status{Code: int32(code), Message: err.Error(), Details: makeDetails(details...)}).Err()
}
@ -147,6 +71,12 @@ func makeDetails(m ...proto.Message) []*anypb.Any {
return out
}
func Canceledf(msg string, args ...interface{}) error {
return status.Errorf(codes.Canceled, msg, args...)
}
func Canceledd(err error, details ...proto.Message) error {
return statusErr(codes.Canceled, err, details...)
}
func InvalidArgumentf(msg string, args ...interface{}) error {
return status.Errorf(codes.InvalidArgument, msg, args...)
}
@ -231,3 +161,127 @@ func Unauthenticatedf(msg string, args ...interface{}) error {
func Unauthenticatedd(err error, details ...proto.Message) error {
return statusErr(codes.Unauthenticated, err, details...)
}
func IsCanceled(err error) bool {
if err == nil {
return false
}
return status.Convert(err).Code() == codes.Canceled || IsContextCanceled(err)
}
func IsUnknown(err error) bool {
if err == nil {
return false
}
return status.Convert(err).Code() == codes.Unknown
}
func IsInvalidArgument(err error) bool {
if err == nil {
return false
}
return status.Convert(err).Code() == codes.InvalidArgument
}
func IsDeadlineExceeded(err error) bool {
if err == nil {
return false
}
return status.Convert(err).Code() == codes.DeadlineExceeded || IsContextDeadlineExceeded(err)
}
func IsNotFound(err error) bool {
if err == nil {
return false
}
return status.Convert(err).Code() == codes.NotFound
}
func IsAlreadyExists(err error) bool {
if err == nil {
return false
}
return status.Convert(err).Code() == codes.AlreadyExists
}
func IsPermissionDenied(err error) bool {
if err == nil {
return false
}
return status.Convert(err).Code() == codes.PermissionDenied
}
func IsResourceExhausted(err error) bool {
if err == nil {
return false
}
return status.Convert(err).Code() == codes.ResourceExhausted
}
func IsFailedPrecondition(err error) bool {
if err == nil {
return false
}
return status.Convert(err).Code() == codes.FailedPrecondition
}
func IsAborted(err error) bool {
if err == nil {
return false
}
return status.Convert(err).Code() == codes.Aborted
}
func IsOutOfRange(err error) bool {
if err == nil {
return false
}
return status.Convert(err).Code() == codes.OutOfRange
}
func IsUnimplemented(err error) bool {
if err == nil {
return false
}
return status.Convert(err).Code() == codes.Unimplemented
}
func IsInternal(err error) bool {
if err == nil {
return false
}
return status.Convert(err).Code() == codes.Internal
}
func IsUnavailable(err error) bool {
if err == nil {
return false
}
return status.Convert(err).Code() == codes.Unavailable
}
func IsDataLoss(err error) bool {
if err == nil {
return false
}
return status.Convert(err).Code() == codes.DataLoss
}
func IsUnauthenticated(err error) bool {
if err == nil {
return false
}
return status.Convert(err).Code() == codes.Unauthenticated
}
func IsContextCanceled(err error) bool {
err = Unwrap(err)
if err == nil {
return false
}
return strings.Contains(err.Error(), context.Canceled.Error())
}
func IsContextDeadlineExceeded(err error) bool {
err = Unwrap(err)
if err == nil {
return false
}
return strings.Contains(err.Error(), context.DeadlineExceeded.Error())
}
func Unwrap(err error) error {
s, ok := status.FromError(err)
if s == nil {
return nil
}
if ok {
return errors.New(s.Message())
}
return err
}

View File

@ -4,99 +4,110 @@ import (
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"strings"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/spf13/cobra"
"github.com/sirupsen/logrus"
"github.com/uptrace/opentelemetry-go-extra/otellogrus"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
"golang.org/x/net/http2"
"google.golang.org/grpc"
"google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/metadata"
greflectsvc "google.golang.org/grpc/reflection/grpc_reflection_v1alpha"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/descriptorpb"
"go.linka.cloud/grpc/client"
"go.linka.cloud/grpc/interceptors/defaulter"
metrics2 "go.linka.cloud/grpc/interceptors/metrics"
validation2 "go.linka.cloud/grpc/interceptors/validation"
"go.linka.cloud/grpc/logger"
"go.linka.cloud/grpc/service"
"go.linka.cloud/grpc-tookit/example/pb"
"go.linka.cloud/grpc-toolkit/client"
"go.linka.cloud/grpc-toolkit/interceptors/auth"
"go.linka.cloud/grpc-toolkit/interceptors/tracing"
"go.linka.cloud/grpc-toolkit/logger"
"go.linka.cloud/grpc-toolkit/service"
)
type GreeterHandler struct {
UnimplementedGreeterServer
}
func run(ctx context.Context, opts ...service.Option) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
func hello(name string) string {
return fmt.Sprintf("Hello %s !", name)
}
func (g *GreeterHandler) SayHello(ctx context.Context, req *HelloRequest) (*HelloReply, error) {
return &HelloReply{Message: hello(req.Name)}, nil
}
func (g *GreeterHandler) SayHelloStream(req *HelloStreamRequest, s Greeter_SayHelloStreamServer) error {
for i := int64(0); i < req.Count; i++ {
if err := s.Send(&HelloReply{Message: fmt.Sprintf("Hello %s (%d)!", req.Name, i+1)}); err != nil {
return err
}
// time.Sleep(time.Second)
}
return nil
}
func httpLogger(next http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
start := time.Now()
log := logger.From(request.Context()).WithFields(
"method", request.Method,
"host", request.Host,
"path", request.URL.Path,
"remoteAddress", request.RemoteAddr,
)
next.ServeHTTP(writer, request)
log.WithField("duration", time.Since(start)).Info()
})
}
func main() {
f, opts := service.NewFlagSet()
cmd := &cobra.Command{
Use: "example",
Run: func(cmd *cobra.Command, args []string) {
run(opts)
},
}
cmd.Flags().AddFlagSet(f)
cmd.Execute()
}
func run(opts ...service.Option) {
name := "greeter"
version := "v0.0.1"
secure := true
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
log := logger.New().WithFields("service", name)
log := logger.New().WithFields("service", name).WithReportCaller(true)
log.Logger().AddHook(otellogrus.NewHook(otellogrus.WithLevels(
logger.PanicLevel,
logger.FatalLevel,
logger.ErrorLevel,
logger.WarnLevel,
logger.InfoLevel,
)))
ctx = logger.Set(ctx, log)
done := make(chan struct{})
ready := make(chan struct{})
var svc service.Service
var err error
metrics := metrics2.NewInterceptors()
validation := validation2.NewInterceptors(true)
defaulter := defaulter.NewInterceptors()
exporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithInsecure(), otlptracegrpc.WithEndpoint("192.168.10.212:4317"))
if err != nil {
logrus.Fatal(err)
}
defer exporter.Shutdown(ctx)
r, err := resource.New(
ctx,
resource.WithFromEnv(),
resource.WithTelemetrySDK(),
resource.WithHost(),
resource.WithSchemaURL(semconv.SchemaURL),
resource.WithAttributes(
semconv.ServiceName("example"),
semconv.ServiceVersion("v1.0.0"),
semconv.DeploymentEnvironment("tests"),
),
)
tp := sdktrace.NewTracerProvider(
sdktrace.WithResource(r),
// sdktrace.WithBatcher(exporter),
sdktrace.WithSyncer(exporter),
sdktrace.WithSampler(sdktrace.AlwaysSample()),
)
defer func() {
if err := tp.Shutdown(ctx); err != nil {
log.Error(err)
}
}()
// exporter, err := stdout.New(stdout.WithPrettyPrint())
// if err != nil {
// log.WithError(err).Fatal("failed to create otel exporter")
// }
// tp := sdktrace.NewTracerProvider(
// sdktrace.WithSampler(sdktrace.AlwaysSample()),
// // enable in production
// // sdktrace.WithBatcher(exporter),
// // enable in development
// sdktrace.WithSyncer(exporter),
// )
otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
defer func() { _ = exporter.Shutdown(context.Background()) }()
address := "0.0.0.0:9991"
opts = append(opts, service.WithContext(ctx),
var svc service.Service
opts = append(opts,
service.WithContext(ctx),
service.WithName(name),
service.WithVersion(version),
service.WithAddress(address),
// service.WithRegistry(mdns.NewRegistry()),
service.WithReflection(true),
service.WithSecure(secure),
service.WithAfterStart(func() error {
log.Info("Server listening on", svc.Options().Address())
@ -108,61 +119,83 @@ func run(opts ...service.Option) {
close(done)
return nil
}),
service.WithGateway(RegisterGreeterHandler),
service.WithGatewayPrefix("/rest"),
service.WithGRPCWeb(true),
service.WithGRPCWebPrefix("/grpc"),
service.WithMiddlewares(httpLogger),
service.WithInterceptors(metrics, defaulter, validation),
)
svc, err = service.New(opts...)
svc, err = newService(ctx, opts...)
if err != nil {
panic(err)
}
RegisterGreeterServer(svc, &GreeterHandler{})
metrics2.Register(svc)
go func() {
if err := svc.Start(); err != nil {
panic(err)
}
}()
go func() {
http.Handle("/metrics", promhttp.Handler())
http.Handle("/metrics", promhttp.HandlerFor(
prometheus.DefaultGatherer,
promhttp.HandlerOpts{
// Opt into OpenMetrics e.g. to support exemplars.
EnableOpenMetrics: true,
},
))
if err := http.ListenAndServe(":9992", nil); err != nil {
panic(err)
}
}()
<-ready
s, err := client.New(
copts := []client.Option{
// client.WithName(name),
// client.WithVersion(version),
client.WithAddress("localhost:9991"),
// client.WithRegistry(mdns.NewRegistry()),
client.WithSecure(secure),
client.WithUnaryInterceptors(func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
logger.From(ctx).WithFields("party", "client", "method", method).Info(req)
return invoker(ctx, method, req, reply, cc, opts...)
}),
)
client.WithInterceptors(tracing.NewClientInterceptors()),
// client.WithUnaryInterceptors(func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
// logger.From(ctx).WithFields("party", "client", "method", method).Info(req)
// return invoker(ctx, method, req, reply, cc, opts...)
// }),
}
s, err := client.New(copts...)
if err != nil {
log.Fatal(err)
}
g := NewGreeterClient(s)
defer cancel()
g := pb.NewGreeterClient(s)
h := grpc_health_v1.NewHealthClient(s)
for i := 0; i < 5; i++ {
_, err := h.Check(ctx, &grpc_health_v1.HealthCheckRequest{})
if err != nil {
log.Error(err)
} else {
log.Fatalf("expected error")
}
}
log.Infof("waiting for unban")
time.Sleep(time.Second)
s, err = client.New(append(copts, client.WithInterceptors(auth.NewBasicAuthClientIntereptors("admin", "admin")))...)
if err != nil {
log.Fatal(err)
}
g = pb.NewGreeterClient(s)
h = grpc_health_v1.NewHealthClient(s)
hres, err := h.Check(ctx, &grpc_health_v1.HealthCheckRequest{Service: "helloworld.Greeter"})
if err != nil {
log.Fatal(err)
}
log.Infof("status: %v", hres.Status)
md := metadata.MD{}
res, err := g.SayHello(ctx, &HelloRequest{Name: "test"}, grpc.Header(&md))
res, err := g.SayHello(ctx, &pb.HelloRequest{Name: "test"}, grpc.Header(&md))
if err != nil {
log.Fatal(err)
}
logMetadata(ctx, md)
log.Infof("received message: %s", res.Message)
md = metadata.MD{}
res, err = g.SayHello(ctx, &HelloRequest{}, grpc.Header(&md))
res, err = g.SayHello(ctx, &pb.HelloRequest{}, grpc.Header(&md))
if err == nil {
log.Fatal("expected validation error")
}
logMetadata(ctx, md)
stream, err := g.SayHelloStream(ctx, &HelloStreamRequest{Name: "test"}, grpc.Header(&md))
stream, err := g.SayHelloStream(ctx, &pb.HelloStreamRequest{Name: "test"}, grpc.Header(&md))
if err != nil {
log.Fatal(err)
}
@ -180,25 +213,43 @@ func run(opts ...service.Option) {
log.Infof("received stream message: %s", m.Message)
}
scheme := "http://"
var (
tlsConfig *tls.Config
dial func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error)
)
if secure {
scheme = "https://"
tlsConfig = &tls.Config{
InsecureSkipVerify: true,
}
} else {
dial = func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
var d net.Dialer
return d.DialContext(ctx, network, addr)
}
}
httpc := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
Transport: &http2.Transport{
AllowHTTP: true,
TLSClientConfig: tlsConfig,
DialTLSContext: dial,
},
}
req := `{"name":"test"}`
do := func(url, contentType string) {
resp, err := httpc.Post(url, contentType, strings.NewReader(req))
req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(req))
if err != nil {
log.Fatal(err)
}
req.Header.Set("content-type", contentType)
req.Header.Set("authorization", auth.BasicAuth("admin", "admin"))
resp, err := httpc.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
b, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
@ -220,80 +271,3 @@ func logMetadata(ctx context.Context, md metadata.MD) {
log.Infof("%s: %v", k, v)
}
}
func readSvcs(ctx context.Context, c client.Client) (err error) {
log := logger.From(ctx)
rc := greflectsvc.NewServerReflectionClient(c)
rstream, err := rc.ServerReflectionInfo(ctx)
if err != nil {
return err
}
defer func() {
if err2 := rstream.CloseSend(); err2 != nil && err == nil {
err = err2
}
}()
if err = rstream.Send(&greflectsvc.ServerReflectionRequest{MessageRequest: &greflectsvc.ServerReflectionRequest_ListServices{}}); err != nil {
return err
}
var rres *greflectsvc.ServerReflectionResponse
rres, err = rstream.Recv()
if err != nil {
return err
}
rlist, ok := rres.MessageResponse.(*greflectsvc.ServerReflectionResponse_ListServicesResponse)
if !ok {
return fmt.Errorf("unexpected reflection response type: %T", rres.MessageResponse)
}
for _, v := range rlist.ListServicesResponse.Service {
if v.Name == "grpc.reflection.v1alpha.ServerReflection" {
continue
}
parts := strings.Split(v.Name, ".")
if len(parts) < 2 {
return fmt.Errorf("malformed service name: %s", v.Name)
}
pkg := strings.Join(parts[:len(parts)-1], ".")
svc := parts[len(parts)-1]
if err = rstream.Send(&greflectsvc.ServerReflectionRequest{MessageRequest: &greflectsvc.ServerReflectionRequest_FileContainingSymbol{
FileContainingSymbol: v.Name,
}}); err != nil {
return err
}
rres, err = rstream.Recv()
if err != nil {
log.Fatal(err)
}
rfile, ok := rres.MessageResponse.(*greflectsvc.ServerReflectionResponse_FileDescriptorResponse)
if !ok {
return fmt.Errorf("unexpected reflection response type: %T", rres.MessageResponse)
}
fdps := make(map[string]*descriptorpb.DescriptorProto)
var sdp *descriptorpb.ServiceDescriptorProto
for _, v := range rfile.FileDescriptorResponse.FileDescriptorProto {
fdp := &descriptorpb.FileDescriptorProto{}
if err = proto.Unmarshal(v, fdp); err != nil {
return err
}
for _, s := range fdp.GetService() {
if fdp.GetPackage() == pkg && s.GetName() == svc {
if sdp != nil {
log.Warnf("service already found: %s.%s", fdp.GetPackage(), s.GetName())
continue
}
sdp = s
}
}
for _, m := range fdp.GetMessageType() {
fdps[fdp.GetPackage()+"."+m.GetName()] = m
}
}
if sdp == nil {
return fmt.Errorf("%s: service not found", v.Name)
}
for _, m := range sdp.GetMethod() {
log.Infof("%s: %s", v.Name, m.GetName())
}
}
return nil
}

View File

@ -1,21 +0,0 @@
package main
var HelloRequestFields = struct {
Name string
}{
Name: "name",
}
var HelloReplyFields = struct {
Message string
}{
Message: "message",
}
var HelloStreamRequestFields = struct {
Name string
Count string
}{
Name: "name",
Count: "count",
}

View File

@ -1,305 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.17.3
// source: example/example.proto
package main
import (
reflect "reflect"
sync "sync"
_ "github.com/envoyproxy/protoc-gen-validate/validate"
_ "go.linka.cloud/protoc-gen-defaults/defaults"
_ "google.golang.org/genproto/googleapis/api/annotations"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type HelloRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}
func (x *HelloRequest) Reset() {
*x = HelloRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_example_example_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HelloRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HelloRequest) ProtoMessage() {}
func (x *HelloRequest) ProtoReflect() protoreflect.Message {
mi := &file_example_example_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead.
func (*HelloRequest) Descriptor() ([]byte, []int) {
return file_example_example_proto_rawDescGZIP(), []int{0}
}
func (x *HelloRequest) GetName() string {
if x != nil {
return x.Name
}
return ""
}
type HelloReply struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
}
func (x *HelloReply) Reset() {
*x = HelloReply{}
if protoimpl.UnsafeEnabled {
mi := &file_example_example_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HelloReply) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HelloReply) ProtoMessage() {}
func (x *HelloReply) ProtoReflect() protoreflect.Message {
mi := &file_example_example_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HelloReply.ProtoReflect.Descriptor instead.
func (*HelloReply) Descriptor() ([]byte, []int) {
return file_example_example_proto_rawDescGZIP(), []int{1}
}
func (x *HelloReply) GetMessage() string {
if x != nil {
return x.Message
}
return ""
}
type HelloStreamRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Count int64 `protobuf:"varint,2,opt,name=count,proto3" json:"count,omitempty"`
}
func (x *HelloStreamRequest) Reset() {
*x = HelloStreamRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_example_example_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HelloStreamRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HelloStreamRequest) ProtoMessage() {}
func (x *HelloStreamRequest) ProtoReflect() protoreflect.Message {
mi := &file_example_example_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HelloStreamRequest.ProtoReflect.Descriptor instead.
func (*HelloStreamRequest) Descriptor() ([]byte, []int) {
return file_example_example_proto_rawDescGZIP(), []int{2}
}
func (x *HelloStreamRequest) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *HelloStreamRequest) GetCount() int64 {
if x != nil {
return x.Count
}
return 0
}
var File_example_example_proto protoreflect.FileDescriptor
var file_example_example_proto_rawDesc = []byte{
0x0a, 0x15, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c,
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f,
0x72, 0x6c, 0x64, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f,
0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x1a, 0x17, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69,
0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x64, 0x65, 0x66, 0x61,
0x75, 0x6c, 0x74, 0x73, 0x2f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x22, 0x2d, 0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x42, 0x09, 0xfa, 0x42, 0x06, 0x72, 0x04, 0x10, 0x02, 0x18, 0x28, 0x52, 0x04, 0x6e, 0x61,
0x6d, 0x65, 0x22, 0x26, 0x0a, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79,
0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x59, 0x0a, 0x12, 0x48, 0x65,
0x6c, 0x6c, 0x6f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x1d, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x09,
0xfa, 0x42, 0x06, 0x72, 0x04, 0x10, 0x02, 0x18, 0x28, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
0x24, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x0e,
0xfa, 0x42, 0x06, 0x22, 0x04, 0x18, 0x0a, 0x28, 0x01, 0x9a, 0x49, 0x02, 0x20, 0x0a, 0x52, 0x05,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x32, 0xb7, 0x01, 0x0a, 0x07, 0x47, 0x72, 0x65, 0x65, 0x74, 0x65,
0x72, 0x12, 0x5e, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x18, 0x2e,
0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77,
0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22,
0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x22, 0x15, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31,
0x2f, 0x67, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x3a, 0x01,
0x2a, 0x12, 0x4c, 0x0a, 0x0e, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x53, 0x74, 0x72,
0x65, 0x61, 0x6d, 0x12, 0x1e, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64,
0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64,
0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x30, 0x01, 0x42,
0x22, 0x5a, 0x20, 0x67, 0x6f, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x61, 0x2e, 0x63, 0x6c, 0x6f, 0x75,
0x64, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x3b, 0x6d,
0x61, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_example_example_proto_rawDescOnce sync.Once
file_example_example_proto_rawDescData = file_example_example_proto_rawDesc
)
func file_example_example_proto_rawDescGZIP() []byte {
file_example_example_proto_rawDescOnce.Do(func() {
file_example_example_proto_rawDescData = protoimpl.X.CompressGZIP(file_example_example_proto_rawDescData)
})
return file_example_example_proto_rawDescData
}
var file_example_example_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_example_example_proto_goTypes = []interface{}{
(*HelloRequest)(nil), // 0: helloworld.HelloRequest
(*HelloReply)(nil), // 1: helloworld.HelloReply
(*HelloStreamRequest)(nil), // 2: helloworld.HelloStreamRequest
}
var file_example_example_proto_depIdxs = []int32{
0, // 0: helloworld.Greeter.SayHello:input_type -> helloworld.HelloRequest
2, // 1: helloworld.Greeter.SayHelloStream:input_type -> helloworld.HelloStreamRequest
1, // 2: helloworld.Greeter.SayHello:output_type -> helloworld.HelloReply
1, // 3: helloworld.Greeter.SayHelloStream:output_type -> helloworld.HelloReply
2, // [2:4] is the sub-list for method output_type
0, // [0:2] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_example_example_proto_init() }
func file_example_example_proto_init() {
if File_example_example_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_example_example_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HelloRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_example_example_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HelloReply); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_example_example_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HelloStreamRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_example_example_proto_rawDesc,
NumEnums: 0,
NumMessages: 3,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_example_example_proto_goTypes,
DependencyIndexes: file_example_example_proto_depIdxs,
MessageInfos: file_example_example_proto_msgTypes,
}.Build()
File_example_example_proto = out.File
file_example_example_proto_rawDesc = nil
file_example_example_proto_goTypes = nil
file_example_example_proto_depIdxs = nil
}

82
example/go.mod Normal file
View File

@ -0,0 +1,82 @@
module go.linka.cloud/grpc-tookit/example
go 1.23.2
replace go.linka.cloud/grpc-toolkit => ../
replace (
github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.9.1
github.com/grpc-ecosystem/go-grpc-prometheus => github.com/linka-cloud/go-grpc-prometheus v1.2.0-lk
github.com/grpc-ecosystem/grpc-gateway/v2 => github.com/linka-cloud/grpc-gateway/v2 v2.20.0-lk
nhooyr.io/websocket => github.com/coder/websocket v1.8.6
)
require (
github.com/envoyproxy/protoc-gen-validate v1.1.0
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0
github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587
github.com/prometheus/client_golang v1.20.5
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.1
github.com/uptrace/opentelemetry-go-extra/otellogrus v0.3.2
go.linka.cloud/grpc-toolkit v0.4.3
go.linka.cloud/protoc-gen-defaults v0.4.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0
go.opentelemetry.io/otel v1.31.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0
go.opentelemetry.io/otel/sdk v1.31.0
go.opentelemetry.io/otel/trace v1.31.0
golang.org/x/net v0.30.0
google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53
google.golang.org/grpc v1.67.1
google.golang.org/protobuf v1.35.1
)
require (
cloud.google.com/go/compute/metadata v0.5.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bombsimon/logrusr/v4 v4.1.0 // indirect
github.com/bufbuild/protocompile v0.14.1 // indirect
github.com/caitlinelfring/go-env-default v1.1.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/desertbit/timer v1.0.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/fullstorydev/grpchan v1.1.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jaredfolkins/badactor v1.2.0 // indirect
github.com/jhump/protoreflect v1.17.0 // indirect
github.com/justinas/alice v1.2.0 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.60.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/soheilhy/cmux v0.1.5 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 // indirect
github.com/traefik/grpc-web v0.16.0 // indirect
github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 // indirect
go.opentelemetry.io/otel/log v0.6.0 // indirect
go.opentelemetry.io/otel/metric v1.31.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
nhooyr.io/websocket v1.8.17 // indirect
)

725
example/go.sum Normal file
View File

@ -0,0 +1,725 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bombsimon/logrusr/v4 v4.1.0 h1:uZNPbwusB0eUXlO8hIUwStE6Lr5bLN6IgYgG+75kuh4=
github.com/bombsimon/logrusr/v4 v4.1.0/go.mod h1:pjfHC5e59CvjTBIU3V3sGhFWFAnsnhOR03TRc6im0l8=
github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/caitlinelfring/go-env-default v1.1.0 h1:bhDfXmUolvcIGfQCX8qevQX8wxC54NGz0aimoUnhvDM=
github.com/caitlinelfring/go-env-default v1.1.0/go.mod h1:tESXPr8zFPP/cRy3cwxrHBmjJIf2A1x/o4C9CET2rEk=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/coder/websocket v1.8.6 h1:OmNKdwUvLj7VvHnl5o8skaVghSPLjWdHGCnFbkWqs9w=
github.com/coder/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/desertbit/timer v1.0.1 h1:yRpYNn5Vaaj6QXecdLMPMJsW81JLiI1eokUft5nBmeo=
github.com/desertbit/timer v1.0.1/go.mod h1:htRrYeY5V/t4iu1xCJ5XsQvp4xve8QulXXctAzxqcwE=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM=
github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fullstorydev/grpchan v1.1.1 h1:heQqIJlAv5Cnks9a70GRL2EJke6QQoUB25VGR6TZQas=
github.com/fullstorydev/grpchan v1.1.1/go.mod h1:f4HpiV8V6htfY/K44GWV1ESQzHBTq7DinhzqQ95lpgc=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/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/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA=
github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
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.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
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 v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
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.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA=
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jaredfolkins/badactor v1.2.0 h1:QTJBsVG9qhdIFmFx5eNet2Q9hX8T+qZ1rC9NJwyN+Hc=
github.com/jaredfolkins/badactor v1.2.0/go.mod h1:ZynkTrC/ICU1o8mmFy3JySRCErXVlx7trZiWEH6DDg8=
github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI=
github.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI=
github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ=
github.com/jhump/protoreflect v1.11.0/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E=
github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94=
github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
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/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
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/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
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/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/linka-cloud/grpc-gateway/v2 v2.20.0-lk h1:AGMeKR9uLBBLUiTl9D1aYmSdJuqG4FgOrOc6e3nxhuo=
github.com/linka-cloud/grpc-gateway/v2 v2.20.0-lk/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587 h1:xzZOeCMQLA/W198ZkdVdt4EKFKJtS26B773zNU377ZY=
github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
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-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA=
github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk=
github.com/traefik/grpc-web v0.16.0 h1:eeUWZaFg6ZU0I9dWOYE2D5qkNzRBmXzzuRlxdltascY=
github.com/traefik/grpc-web v0.16.0/go.mod h1:2ttniSv7pTgBWIU2HZLokxRfFX3SA60c/DTmQQgVml4=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/uptrace/opentelemetry-go-extra/otellogrus v0.3.2 h1:H8wwQwTe5sL6x30z71lUgNiwBdeCHQjrphCfLwqIHGo=
github.com/uptrace/opentelemetry-go-extra/otellogrus v0.3.2/go.mod h1:/kR4beFhlz2g+V5ik8jW+3PMiMQAPt29y6K64NNY53c=
github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2 h1:3/aHKUq7qaFMWxyQV0W2ryNgg8x8rVeKVA20KJUkfS0=
github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2/go.mod h1:Zit4b8AQXaXvA68+nzmbyDzqiyFRISyw1JiD5JqUBjw=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.linka.cloud/protoc-gen-defaults v0.4.0 h1:ekcfTTY74AhKBGMF9usz+xkUFxLaPVAu6xmQvwmjbfc=
go.linka.cloud/protoc-gen-defaults v0.4.0/go.mod h1:IJcTbM/oraQvdE/mz0vxhoBmJHE+rb4vF2IXJztcadY=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 h1:yMkBS9yViCc7U7yeLzJPM2XizlfdVvBRSmsQDWu6qc0=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0/go.mod h1:n8MR6/liuGB5EmTETUBeU5ZgqMOlqKRxUaqPQBOANZ8=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 h1:FFeLy03iVTXP6ffeN2iXrxfGsZGCjVx0/4KlizjyBwU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0/go.mod h1:TMu73/k1CP8nBUpDLc71Wj/Kf7ZS9FK5b53VapRsP9o=
go.opentelemetry.io/otel/log v0.6.0 h1:nH66tr+dmEgW5y+F9LanGJUBYPrRgP4g2EkmPE3LeK8=
go.opentelemetry.io/otel/log v0.6.0/go.mod h1:KdySypjQHhP069JX0z/t26VHwa8vSwzgaKmXtIB3fJM=
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
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-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/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-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/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.1-0.20180807135948-17ff2d5776d2/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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 h1:fVoAXEKA4+yufmbdVYv+SE73+cPZbbbe8paLsHfkK+U=
google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
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.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
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 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

24
example/main.go Normal file
View File

@ -0,0 +1,24 @@
package main
import (
"context"
"github.com/spf13/cobra"
"go.linka.cloud/grpc-toolkit/service"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
f, opts := service.NewFlagSet()
cmd := &cobra.Command{
Use: "example",
Run: func(cmd *cobra.Command, args []string) {
run(cmd.Context(), opts)
},
}
cmd.Flags().AddFlagSet(f)
cmd.ExecuteContext(ctx)
}

View File

@ -1,6 +1,6 @@
// Code generated by protoc-gen-defaults. DO NOT EDIT.
package main
package pb
import (
"google.golang.org/protobuf/types/known/durationpb"

View File

@ -0,0 +1,31 @@
// Code generated by protoc-gen-defaults. DO NOT EDIT.
package pb
var GreeterMethods = struct {
SayHello string
SayHelloStream string
}{
SayHello: "/helloworld.Greeter/SayHello",
SayHelloStream: "/helloworld.Greeter/SayHelloStream",
}
var HelloRequestFields = struct {
Name string
}{
Name: "name",
}
var HelloReplyFields = struct {
Message string
}{
Message: "message",
}
var HelloStreamRequestFields = struct {
Name string
Count string
}{
Name: "name",
Count: "count",
}

261
example/pb/example.pb.go Normal file
View File

@ -0,0 +1,261 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.35.1
// protoc v5.28.2
// source: example/pb/example.proto
package pb
import (
_ "github.com/envoyproxy/protoc-gen-validate/validate"
_ "go.linka.cloud/protoc-gen-defaults/defaults"
_ "google.golang.org/genproto/googleapis/api/annotations"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type HelloRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}
func (x *HelloRequest) Reset() {
*x = HelloRequest{}
mi := &file_example_pb_example_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *HelloRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HelloRequest) ProtoMessage() {}
func (x *HelloRequest) ProtoReflect() protoreflect.Message {
mi := &file_example_pb_example_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead.
func (*HelloRequest) Descriptor() ([]byte, []int) {
return file_example_pb_example_proto_rawDescGZIP(), []int{0}
}
func (x *HelloRequest) GetName() string {
if x != nil {
return x.Name
}
return ""
}
type HelloReply struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
}
func (x *HelloReply) Reset() {
*x = HelloReply{}
mi := &file_example_pb_example_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *HelloReply) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HelloReply) ProtoMessage() {}
func (x *HelloReply) ProtoReflect() protoreflect.Message {
mi := &file_example_pb_example_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HelloReply.ProtoReflect.Descriptor instead.
func (*HelloReply) Descriptor() ([]byte, []int) {
return file_example_pb_example_proto_rawDescGZIP(), []int{1}
}
func (x *HelloReply) GetMessage() string {
if x != nil {
return x.Message
}
return ""
}
type HelloStreamRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Count int64 `protobuf:"varint,2,opt,name=count,proto3" json:"count,omitempty"`
}
func (x *HelloStreamRequest) Reset() {
*x = HelloStreamRequest{}
mi := &file_example_pb_example_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *HelloStreamRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HelloStreamRequest) ProtoMessage() {}
func (x *HelloStreamRequest) ProtoReflect() protoreflect.Message {
mi := &file_example_pb_example_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HelloStreamRequest.ProtoReflect.Descriptor instead.
func (*HelloStreamRequest) Descriptor() ([]byte, []int) {
return file_example_pb_example_proto_rawDescGZIP(), []int{2}
}
func (x *HelloStreamRequest) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *HelloStreamRequest) GetCount() int64 {
if x != nil {
return x.Count
}
return 0
}
var File_example_pb_example_proto protoreflect.FileDescriptor
var file_example_pb_example_proto_rawDesc = []byte{
0x0a, 0x18, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x70, 0x62, 0x2f, 0x65, 0x78, 0x61,
0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x68, 0x65, 0x6c, 0x6c,
0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61,
0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76,
0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x64,
0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x2f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x2d, 0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x42, 0x09, 0xfa, 0x42, 0x06, 0x72, 0x04, 0x10, 0x02, 0x18, 0x28, 0x52,
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x26, 0x0a, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65,
0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x59, 0x0a,
0x12, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x42, 0x09, 0xfa, 0x42, 0x06, 0x72, 0x04, 0x10, 0x02, 0x18, 0x28, 0x52, 0x04, 0x6e, 0x61,
0x6d, 0x65, 0x12, 0x24, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
0x03, 0x42, 0x0e, 0xfa, 0x42, 0x06, 0x22, 0x04, 0x18, 0x0a, 0x28, 0x01, 0x9a, 0x49, 0x02, 0x20,
0x0a, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x32, 0xb7, 0x01, 0x0a, 0x07, 0x47, 0x72, 0x65,
0x65, 0x74, 0x65, 0x72, 0x12, 0x5e, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f,
0x12, 0x18, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65,
0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c,
0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70,
0x6c, 0x79, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15, 0x2f,
0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72, 0x2f, 0x68,
0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x4c, 0x0a, 0x0e, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f,
0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x1e, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f,
0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f,
0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00,
0x30, 0x01, 0x42, 0x28, 0x5a, 0x26, 0x67, 0x6f, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x61, 0x2e, 0x63,
0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2d, 0x74, 0x6f, 0x6f, 0x6c, 0x6b, 0x69,
0x74, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x3b, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
}
var (
file_example_pb_example_proto_rawDescOnce sync.Once
file_example_pb_example_proto_rawDescData = file_example_pb_example_proto_rawDesc
)
func file_example_pb_example_proto_rawDescGZIP() []byte {
file_example_pb_example_proto_rawDescOnce.Do(func() {
file_example_pb_example_proto_rawDescData = protoimpl.X.CompressGZIP(file_example_pb_example_proto_rawDescData)
})
return file_example_pb_example_proto_rawDescData
}
var file_example_pb_example_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_example_pb_example_proto_goTypes = []any{
(*HelloRequest)(nil), // 0: helloworld.HelloRequest
(*HelloReply)(nil), // 1: helloworld.HelloReply
(*HelloStreamRequest)(nil), // 2: helloworld.HelloStreamRequest
}
var file_example_pb_example_proto_depIdxs = []int32{
0, // 0: helloworld.Greeter.SayHello:input_type -> helloworld.HelloRequest
2, // 1: helloworld.Greeter.SayHelloStream:input_type -> helloworld.HelloStreamRequest
1, // 2: helloworld.Greeter.SayHello:output_type -> helloworld.HelloReply
1, // 3: helloworld.Greeter.SayHelloStream:output_type -> helloworld.HelloReply
2, // [2:4] is the sub-list for method output_type
0, // [0:2] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_example_pb_example_proto_init() }
func file_example_pb_example_proto_init() {
if File_example_pb_example_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_example_pb_example_proto_rawDesc,
NumEnums: 0,
NumMessages: 3,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_example_pb_example_proto_goTypes,
DependencyIndexes: file_example_pb_example_proto_depIdxs,
MessageInfos: file_example_pb_example_proto_msgTypes,
}.Build()
File_example_pb_example_proto = out.File
file_example_pb_example_proto_rawDesc = nil
file_example_pb_example_proto_goTypes = nil
file_example_pb_example_proto_depIdxs = nil
}

View File

@ -1,12 +1,12 @@
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: example/example.proto
// source: example/pb/example.proto
/*
Package main is a reverse proxy.
Package pb is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package main
package pb
import (
"context"
@ -35,11 +35,7 @@ func request_Greeter_SayHello_0(ctx context.Context, marshaler runtime.Marshaler
var protoReq HelloRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
@ -52,11 +48,7 @@ func local_request_Greeter_SayHello_0(ctx context.Context, marshaler runtime.Mar
var protoReq HelloRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
@ -77,20 +69,22 @@ func RegisterGreeterHandlerServer(ctx context.Context, mux *runtime.ServeMux, se
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/helloworld.Greeter/SayHello", runtime.WithHTTPPathPattern("/api/v1/greeter/hello"))
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/helloworld.Greeter/SayHello", runtime.WithHTTPPathPattern("/api/v1/greeter/hello"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Greeter_SayHello_0(rctx, inboundMarshaler, server, req, pathParams)
resp, md, err := local_request_Greeter_SayHello_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Greeter_SayHello_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_Greeter_SayHello_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
@ -100,21 +94,21 @@ func RegisterGreeterHandlerServer(ctx context.Context, mux *runtime.ServeMux, se
// RegisterGreeterHandlerFromEndpoint is same as RegisterGreeterHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterGreeterHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.Dial(endpoint, opts...)
conn, err := grpc.NewClient(endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
@ -139,19 +133,21 @@ func RegisterGreeterHandlerClient(ctx context.Context, mux *runtime.ServeMux, cl
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/helloworld.Greeter/SayHello", runtime.WithHTTPPathPattern("/api/v1/greeter/hello"))
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/helloworld.Greeter/SayHello", runtime.WithHTTPPathPattern("/api/v1/greeter/hello"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Greeter_SayHello_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
resp, md, err := request_Greeter_SayHello_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Greeter_SayHello_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
forward_Greeter_SayHello_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-validate. DO NOT EDIT.
// source: example/example.proto
// source: example/pb/example.proto
package main
package pb
import (
"bytes"
@ -71,6 +71,7 @@ func (m *HelloRequest) validate(all bool) error {
if len(errors) > 0 {
return HelloRequestMultiError(errors)
}
return nil
}
@ -171,6 +172,7 @@ func (m *HelloReply) validate(all bool) error {
if len(errors) > 0 {
return HelloReplyMultiError(errors)
}
return nil
}
@ -291,6 +293,7 @@ func (m *HelloStreamRequest) validate(all bool) error {
if len(errors) > 0 {
return HelloStreamRequestMultiError(errors)
}
return nil
}

View File

@ -2,7 +2,7 @@ syntax = "proto3";
package helloworld;
option go_package = "go.linka.cloud/grpc/example;main";
option go_package = "go.linka.cloud/grpc-toolkit/example;pb";
import "google/api/annotations.proto";
import "validate/validate.proto";

View File

@ -1,7 +1,7 @@
{
"swagger": "2.0",
"info": {
"title": "example/example.proto",
"title": "example/pb/example.proto",
"version": "version not set"
},
"tags": [
@ -88,6 +88,7 @@
"details": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/protobufAny"
}
}

View File

@ -1,10 +1,13 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.28.2
// source: example/pb/example.proto
package main
package pb
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
@ -12,15 +15,20 @@ import (
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
Greeter_SayHello_FullMethodName = "/helloworld.Greeter/SayHello"
Greeter_SayHelloStream_FullMethodName = "/helloworld.Greeter/SayHelloStream"
)
// GreeterClient is the client API for Greeter service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type GreeterClient interface {
SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
SayHelloStream(ctx context.Context, in *HelloStreamRequest, opts ...grpc.CallOption) (Greeter_SayHelloStreamClient, error)
SayHelloStream(ctx context.Context, in *HelloStreamRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[HelloReply], error)
}
type greeterClient struct {
@ -32,20 +40,22 @@ func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient {
}
func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(HelloReply)
err := c.cc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, opts...)
err := c.cc.Invoke(ctx, Greeter_SayHello_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *greeterClient) SayHelloStream(ctx context.Context, in *HelloStreamRequest, opts ...grpc.CallOption) (Greeter_SayHelloStreamClient, error) {
stream, err := c.cc.NewStream(ctx, &Greeter_ServiceDesc.Streams[0], "/helloworld.Greeter/SayHelloStream", opts...)
func (c *greeterClient) SayHelloStream(ctx context.Context, in *HelloStreamRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[HelloReply], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &Greeter_ServiceDesc.Streams[0], Greeter_SayHelloStream_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &greeterSayHelloStreamClient{stream}
x := &grpc.GenericClientStream[HelloStreamRequest, HelloReply]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
@ -55,43 +65,33 @@ func (c *greeterClient) SayHelloStream(ctx context.Context, in *HelloStreamReque
return x, nil
}
type Greeter_SayHelloStreamClient interface {
Recv() (*HelloReply, error)
grpc.ClientStream
}
type greeterSayHelloStreamClient struct {
grpc.ClientStream
}
func (x *greeterSayHelloStreamClient) Recv() (*HelloReply, error) {
m := new(HelloReply)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type Greeter_SayHelloStreamClient = grpc.ServerStreamingClient[HelloReply]
// GreeterServer is the server API for Greeter service.
// All implementations must embed UnimplementedGreeterServer
// for forward compatibility
// for forward compatibility.
type GreeterServer interface {
SayHello(context.Context, *HelloRequest) (*HelloReply, error)
SayHelloStream(*HelloStreamRequest, Greeter_SayHelloStreamServer) error
SayHelloStream(*HelloStreamRequest, grpc.ServerStreamingServer[HelloReply]) error
mustEmbedUnimplementedGreeterServer()
}
// UnimplementedGreeterServer must be embedded to have forward compatible implementations.
type UnimplementedGreeterServer struct {
}
// UnimplementedGreeterServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedGreeterServer struct{}
func (UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
}
func (UnimplementedGreeterServer) SayHelloStream(*HelloStreamRequest, Greeter_SayHelloStreamServer) error {
func (UnimplementedGreeterServer) SayHelloStream(*HelloStreamRequest, grpc.ServerStreamingServer[HelloReply]) error {
return status.Errorf(codes.Unimplemented, "method SayHelloStream not implemented")
}
func (UnimplementedGreeterServer) mustEmbedUnimplementedGreeterServer() {}
func (UnimplementedGreeterServer) testEmbeddedByValue() {}
// UnsafeGreeterServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to GreeterServer will
@ -101,6 +101,13 @@ type UnsafeGreeterServer interface {
}
func RegisterGreeterServer(s grpc.ServiceRegistrar, srv GreeterServer) {
// If the following call pancis, it indicates UnimplementedGreeterServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&Greeter_ServiceDesc, srv)
}
@ -114,7 +121,7 @@ func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(in
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/helloworld.Greeter/SayHello",
FullMethod: Greeter_SayHello_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest))
@ -127,21 +134,11 @@ func _Greeter_SayHelloStream_Handler(srv interface{}, stream grpc.ServerStream)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(GreeterServer).SayHelloStream(m, &greeterSayHelloStreamServer{stream})
return srv.(GreeterServer).SayHelloStream(m, &grpc.GenericServerStream[HelloStreamRequest, HelloReply]{ServerStream: stream})
}
type Greeter_SayHelloStreamServer interface {
Send(*HelloReply) error
grpc.ServerStream
}
type greeterSayHelloStreamServer struct {
grpc.ServerStream
}
func (x *greeterSayHelloStreamServer) Send(m *HelloReply) error {
return x.ServerStream.SendMsg(m)
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type Greeter_SayHelloStreamServer = grpc.ServerStreamingServer[HelloReply]
// Greeter_ServiceDesc is the grpc.ServiceDesc for Greeter service.
// It's only intended for direct use with grpc.RegisterService,
@ -162,5 +159,5 @@ var Greeter_ServiceDesc = grpc.ServiceDesc{
ServerStreams: true,
},
},
Metadata: "example/example.proto",
Metadata: "example/pb/example.proto",
}

View File

@ -1,15 +1,14 @@
// Code generated by protoc-gen-go-vtproto. DO NOT EDIT.
// protoc-gen-go-vtproto version: v0.2.0
// source: example/example.proto
// protoc-gen-go-vtproto version: v0.6.1-0.20240917153116-6f2963f01587
// source: example/pb/example.proto
package main
package pb
import (
fmt "fmt"
io "io"
bits "math/bits"
protohelpers "github.com/planetscale/vtprotobuf/protohelpers"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
io "io"
)
const (
@ -52,7 +51,7 @@ func (m *HelloRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if len(m.Name) > 0 {
i -= len(m.Name)
copy(dAtA[i:], m.Name)
i = encodeVarint(dAtA, i, uint64(len(m.Name)))
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Name)))
i--
dAtA[i] = 0xa
}
@ -92,7 +91,7 @@ func (m *HelloReply) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if len(m.Message) > 0 {
i -= len(m.Message)
copy(dAtA[i:], m.Message)
i = encodeVarint(dAtA, i, uint64(len(m.Message)))
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Message)))
i--
dAtA[i] = 0xa
}
@ -130,31 +129,20 @@ func (m *HelloStreamRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
copy(dAtA[i:], m.unknownFields)
}
if m.Count != 0 {
i = encodeVarint(dAtA, i, uint64(m.Count))
i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Count))
i--
dAtA[i] = 0x10
}
if len(m.Name) > 0 {
i -= len(m.Name)
copy(dAtA[i:], m.Name)
i = encodeVarint(dAtA, i, uint64(len(m.Name)))
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Name)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func encodeVarint(dAtA []byte, offset int, v uint64) int {
offset -= sov(v)
base := offset
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return base
}
func (m *HelloRequest) SizeVT() (n int) {
if m == nil {
return 0
@ -163,11 +151,9 @@ func (m *HelloRequest) SizeVT() (n int) {
_ = l
l = len(m.Name)
if l > 0 {
n += 1 + l + sov(uint64(l))
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
if m.unknownFields != nil {
n += len(m.unknownFields)
}
return n
}
@ -179,11 +165,9 @@ func (m *HelloReply) SizeVT() (n int) {
_ = l
l = len(m.Message)
if l > 0 {
n += 1 + l + sov(uint64(l))
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
if m.unknownFields != nil {
n += len(m.unknownFields)
}
return n
}
@ -195,23 +179,15 @@ func (m *HelloStreamRequest) SizeVT() (n int) {
_ = l
l = len(m.Name)
if l > 0 {
n += 1 + l + sov(uint64(l))
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
if m.Count != 0 {
n += 1 + sov(uint64(m.Count))
n += 1 + protohelpers.SizeOfVarint(uint64(m.Count))
}
if m.unknownFields != nil {
n += len(m.unknownFields)
}
return n
}
func sov(x uint64) (n int) {
return (bits.Len64(x|1) + 6) / 7
}
func soz(x uint64) (n int) {
return sov(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *HelloRequest) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
@ -220,7 +196,7 @@ func (m *HelloRequest) UnmarshalVT(dAtA []byte) error {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
@ -248,7 +224,7 @@ func (m *HelloRequest) UnmarshalVT(dAtA []byte) error {
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
@ -262,11 +238,11 @@ func (m *HelloRequest) UnmarshalVT(dAtA []byte) error {
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLength
return protohelpers.ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLength
return protohelpers.ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
@ -275,12 +251,12 @@ func (m *HelloRequest) UnmarshalVT(dAtA []byte) error {
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skip(dAtA[iNdEx:])
skippy, err := protohelpers.Skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLength
return protohelpers.ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
@ -303,7 +279,7 @@ func (m *HelloReply) UnmarshalVT(dAtA []byte) error {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
@ -331,7 +307,7 @@ func (m *HelloReply) UnmarshalVT(dAtA []byte) error {
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
@ -345,11 +321,11 @@ func (m *HelloReply) UnmarshalVT(dAtA []byte) error {
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLength
return protohelpers.ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLength
return protohelpers.ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
@ -358,12 +334,12 @@ func (m *HelloReply) UnmarshalVT(dAtA []byte) error {
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skip(dAtA[iNdEx:])
skippy, err := protohelpers.Skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLength
return protohelpers.ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
@ -386,7 +362,7 @@ func (m *HelloStreamRequest) UnmarshalVT(dAtA []byte) error {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
@ -414,7 +390,7 @@ func (m *HelloStreamRequest) UnmarshalVT(dAtA []byte) error {
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
@ -428,11 +404,11 @@ func (m *HelloStreamRequest) UnmarshalVT(dAtA []byte) error {
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLength
return protohelpers.ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLength
return protohelpers.ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
@ -446,7 +422,7 @@ func (m *HelloStreamRequest) UnmarshalVT(dAtA []byte) error {
m.Count = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflow
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
@ -460,12 +436,12 @@ func (m *HelloStreamRequest) UnmarshalVT(dAtA []byte) error {
}
default:
iNdEx = preIndex
skippy, err := skip(dAtA[iNdEx:])
skippy, err := protohelpers.Skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLength
return protohelpers.ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
@ -480,87 +456,3 @@ func (m *HelloStreamRequest) UnmarshalVT(dAtA []byte) error {
}
return nil
}
func skip(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflow
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflow
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
case 1:
iNdEx += 8
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflow
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if length < 0 {
return 0, ErrInvalidLength
}
iNdEx += length
case 3:
depth++
case 4:
if depth == 0 {
return 0, ErrUnexpectedEndOfGroup
}
depth--
case 5:
iNdEx += 4
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLength
}
if depth == 0 {
return iNdEx, nil
}
}
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLength = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflow = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroup = fmt.Errorf("proto: unexpected end of group")
)

91
example/reflect.go Normal file
View File

@ -0,0 +1,91 @@
package main
import (
"context"
"fmt"
"strings"
greflectsvc "google.golang.org/grpc/reflection/grpc_reflection_v1"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/descriptorpb"
"go.linka.cloud/grpc-toolkit/client"
"go.linka.cloud/grpc-toolkit/logger"
)
func readSvcs(ctx context.Context, c client.Client) (err error) {
log := logger.C(ctx)
rc := greflectsvc.NewServerReflectionClient(c)
rstream, err := rc.ServerReflectionInfo(ctx)
if err != nil {
return err
}
defer func() {
if err2 := rstream.CloseSend(); err2 != nil && err == nil {
err = err2
}
}()
if err = rstream.Send(&greflectsvc.ServerReflectionRequest{MessageRequest: &greflectsvc.ServerReflectionRequest_ListServices{}}); err != nil {
return err
}
var rres *greflectsvc.ServerReflectionResponse
rres, err = rstream.Recv()
if err != nil {
return err
}
rlist, ok := rres.MessageResponse.(*greflectsvc.ServerReflectionResponse_ListServicesResponse)
if !ok {
return fmt.Errorf("unexpected reflection response type: %T", rres.MessageResponse)
}
for _, v := range rlist.ListServicesResponse.Service {
if v.Name == "grpc.reflection.v1alpha.ServerReflection" || v.Name == "grpc.reflection.v1.ServerReflection" || v.Name == "grpc.health.v1.Health" {
continue
}
parts := strings.Split(v.Name, ".")
if len(parts) < 2 {
return fmt.Errorf("malformed service name: %s", v.Name)
}
pkg := strings.Join(parts[:len(parts)-1], ".")
svc := parts[len(parts)-1]
if err = rstream.Send(&greflectsvc.ServerReflectionRequest{MessageRequest: &greflectsvc.ServerReflectionRequest_FileContainingSymbol{
FileContainingSymbol: v.Name,
}}); err != nil {
return err
}
rres, err = rstream.Recv()
if err != nil {
log.Fatal(err)
}
rfile, ok := rres.MessageResponse.(*greflectsvc.ServerReflectionResponse_FileDescriptorResponse)
if !ok {
return fmt.Errorf("unexpected reflection response type: %T", rres.MessageResponse)
}
fdps := make(map[string]*descriptorpb.DescriptorProto)
var sdp *descriptorpb.ServiceDescriptorProto
for _, v := range rfile.FileDescriptorResponse.FileDescriptorProto {
fdp := &descriptorpb.FileDescriptorProto{}
if err = proto.Unmarshal(v, fdp); err != nil {
return err
}
for _, s := range fdp.GetService() {
if fdp.GetPackage() == pkg && s.GetName() == svc {
if sdp != nil {
log.Warnf("service already found: %s.%s", fdp.GetPackage(), s.GetName())
continue
}
sdp = s
}
}
for _, m := range fdp.GetMessageType() {
fdps[fdp.GetPackage()+"."+m.GetName()] = m
}
}
if sdp == nil {
return fmt.Errorf("%s: service not found", v.Name)
}
for _, m := range sdp.GetMethod() {
log.Infof("%s: %s", v.Name, m.GetName())
}
}
return nil
}

60
example/server.go Normal file
View File

@ -0,0 +1,60 @@
package main
import (
"context"
"fmt"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"google.golang.org/grpc"
"go.linka.cloud/grpc-tookit/example/pb"
"go.linka.cloud/grpc-toolkit/interceptors/iface"
"go.linka.cloud/grpc-toolkit/logger"
)
var (
_ iface.UnaryInterceptor = (*GreeterHandler)(nil)
_ iface.StreamInterceptor = (*GreeterHandler)(nil)
)
type GreeterHandler struct {
pb.UnimplementedGreeterServer
}
func hello(name string) string {
return fmt.Sprintf("Hello %s !", name)
}
func (g *GreeterHandler) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) {
span := trace.SpanFromContext(ctx)
span.SetAttributes(attribute.String("name", req.Name))
logger.C(ctx).Infof("replying to %s", req.Name)
return &pb.HelloReply{Message: hello(req.Name)}, nil
}
func (g *GreeterHandler) SayHelloStream(req *pb.HelloStreamRequest, s pb.Greeter_SayHelloStreamServer) error {
log := logger.C(s.Context())
for i := int64(0); i < req.Count; i++ {
log.Infof("sending message %d", i+1)
if err := s.Send(&pb.HelloReply{Message: fmt.Sprintf("Hello %s (%d)!", req.Name, i+1)}); err != nil {
return err
}
// time.Sleep(time.Second)
}
return nil
}
func (g *GreeterHandler) UnaryServerInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
logger.C(ctx).Infof("called service interface unary interceptor")
return handler(ctx, req)
}
}
func (g *GreeterHandler) StreamServerInterceptor() grpc.StreamServerInterceptor {
return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
logger.C(ss.Context()).Infof("called service interface stream interceptor")
return handler(srv, ss)
}
}

95
example/service.go Normal file
View File

@ -0,0 +1,95 @@
package main
import (
"context"
"fmt"
"net/http"
"time"
logging2 "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel/trace"
"go.linka.cloud/grpc-tookit/example/pb"
"go.linka.cloud/grpc-toolkit/interceptors/auth"
"go.linka.cloud/grpc-toolkit/interceptors/ban"
"go.linka.cloud/grpc-toolkit/interceptors/defaulter"
"go.linka.cloud/grpc-toolkit/interceptors/iface"
"go.linka.cloud/grpc-toolkit/interceptors/logging"
metrics2 "go.linka.cloud/grpc-toolkit/interceptors/metrics"
"go.linka.cloud/grpc-toolkit/interceptors/tracing"
validation2 "go.linka.cloud/grpc-toolkit/interceptors/validation"
"go.linka.cloud/grpc-toolkit/logger"
"go.linka.cloud/grpc-toolkit/service"
)
func newService(ctx context.Context, opts ...service.Option) (service.Service, error) {
log := logger.C(ctx)
metrics := metrics2.NewInterceptors(metrics2.WithExemplarFromContext(metrics2.DefaultExemplarFromCtx))
address := "0.0.0.0:9991"
var svc service.Service
opts = append(opts,
service.WithContext(ctx),
service.WithAddress(address),
// service.WithRegistry(mdns.NewRegistry()),
service.WithReflection(true),
service.WithoutCmux(),
service.WithGateway(pb.RegisterGreeterHandler),
service.WithGatewayPrefix("/rest"),
service.WithGRPCWeb(true),
service.WithGRPCWebPrefix("/grpc"),
service.WithMiddlewares(otelhttp.NewMiddleware("hello"), httpLogger),
service.WithInterceptors(
tracing.NewInterceptors(),
metrics,
logging.New(ctx, logging2.WithFieldsFromContext(func(ctx context.Context) logging2.Fields {
if span := trace.SpanContextFromContext(ctx); span.IsSampled() {
return logging2.Fields{"traceID", span.TraceID().String()}
}
return nil
})),
),
service.WithServerInterceptors(
ban.NewInterceptors(ban.WithDefaultJailDuration(time.Second), ban.WithDefaultCallback(func(action ban.Action, actor string, rule *ban.Rule) error {
log.WithFields("action", action, "actor", actor, "rule", rule.Name).Info("ban callback")
return nil
})),
auth.NewServerInterceptors(auth.WithBasicValidators(func(ctx context.Context, user, password string) (context.Context, error) {
if !auth.Equals(user, "admin") || !auth.Equals(password, "admin") {
return ctx, fmt.Errorf("invalid user or password")
}
log.Infof("request authenticated")
return ctx, nil
})),
),
service.WithInterceptors(
defaulter.NewInterceptors(),
validation2.NewInterceptors(true),
),
// enable server interface interceptor
service.WithServerInterceptors(iface.New()),
)
svc, err := service.New(opts...)
if err != nil {
return nil, err
}
pb.RegisterGreeterServer(svc, &GreeterHandler{})
metrics.Register(svc)
return svc, nil
}
func httpLogger(next http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
start := time.Now()
log := logger.From(request.Context()).WithFields(
"method", request.Method,
"host", request.Host,
"path", request.URL.Path,
"remoteAddress", request.RemoteAddr,
)
next.ServeHTTP(writer, request)
log.WithField("duration", time.Since(start)).Info()
})
}

109
go.mod
View File

@ -1,51 +1,94 @@
module go.linka.cloud/grpc
module go.linka.cloud/grpc-toolkit
go 1.13
go 1.22.0
toolchain go1.23.2
require (
github.com/alta/protopatch v0.3.4
github.com/alta/protopatch v0.5.3
github.com/bombsimon/logrusr/v4 v4.0.0
github.com/caitlinelfring/go-env-default v1.1.0
github.com/envoyproxy/protoc-gen-validate v0.6.2
github.com/fsnotify/fsnotify v1.5.1
github.com/envoyproxy/protoc-gen-validate v1.1.0
github.com/fatih/color v1.13.0
github.com/fsnotify/fsnotify v1.5.4
github.com/fullstorydev/grpchan v1.1.1
github.com/golang/protobuf v1.5.2
github.com/google/uuid v1.1.2
github.com/gorilla/websocket v1.4.2 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645
github.com/improbable-eng/grpc-web v0.14.1
github.com/jinzhu/gorm v1.9.12
github.com/johnbellone/grpc-middleware-sentry v0.2.0
github.com/go-logr/logr v1.4.2
github.com/golang/protobuf v1.5.4
github.com/google/uuid v1.6.0
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0
github.com/jaredfolkins/badactor v1.2.0
github.com/johnbellone/grpc-middleware-sentry v0.3.0
github.com/justinas/alice v1.2.0
github.com/kr/text v0.2.0 // indirect
github.com/lyft/protoc-gen-star v0.6.0 // indirect
github.com/miekg/dns v1.1.41
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/opentracing/opentracing-go v1.1.0
github.com/planetscale/vtprotobuf v0.2.0
github.com/prometheus/client_golang v1.11.0
github.com/pires/go-proxyproto v0.7.0
github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587
github.com/prometheus/client_golang v1.20.4
github.com/rs/cors v1.7.0
github.com/sirupsen/logrus v1.8.1
github.com/sirupsen/logrus v1.9.3
github.com/soheilhy/cmux v0.1.5
github.com/spf13/cobra v1.3.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0
github.com/stretchr/testify v1.9.0
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5
go.linka.cloud/protoc-gen-defaults v0.1.0
go.linka.cloud/protoc-gen-go-fields v0.1.1
go.linka.cloud/protofilters v0.2.2
github.com/traefik/grpc-web v0.16.0
go.linka.cloud/protoc-gen-defaults v0.4.0
go.linka.cloud/protoc-gen-go-fields v0.4.0
go.linka.cloud/protofilters v0.8.1
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0
go.opentelemetry.io/otel/trace v1.31.0
go.uber.org/multierr v1.7.0
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa
google.golang.org/grpc v1.45.0
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0
google.golang.org/protobuf v1.27.1
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
golang.org/x/net v0.30.0
golang.org/x/sync v0.8.0
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9
google.golang.org/grpc v1.67.1
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1
google.golang.org/protobuf v1.35.1
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/fatih/structtag v1.2.0 // indirect
github.com/getsentry/sentry-go v0.24.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gobwas/ws v1.1.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/iancoleman/strcase v0.3.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jhump/protoreflect v1.11.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lyft/protoc-gen-star v0.6.2 // indirect
github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.60.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/spf13/afero v1.11.0 // indirect
go.opentelemetry.io/otel v1.31.0 // indirect
go.opentelemetry.io/otel/metric v1.31.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/tools v0.26.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
nhooyr.io/websocket v1.8.7 // indirect
)
replace (
github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.9.1
github.com/grpc-ecosystem/go-grpc-prometheus => github.com/linka-cloud/go-grpc-prometheus v1.2.0-lk
github.com/grpc-ecosystem/grpc-gateway/v2 => github.com/linka-cloud/grpc-gateway/v2 v2.5.1-0.20210917084803-33b6d54c9e11
github.com/grpc-ecosystem/grpc-gateway/v2 => github.com/linka-cloud/grpc-gateway/v2 v2.20.0-lk
nhooyr.io/websocket => github.com/coder/websocket v1.8.6
)

629
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -5,11 +5,11 @@ import (
"encoding/base64"
"strings"
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
"go.linka.cloud/grpc/errors"
"go.linka.cloud/grpc/interceptors"
"go.linka.cloud/grpc/interceptors/metadata"
"go.linka.cloud/grpc-toolkit/errors"
"go.linka.cloud/grpc-toolkit/interceptors"
"go.linka.cloud/grpc-toolkit/interceptors/metadata"
)
func BasicAuth(user, password string) string {

View File

@ -3,19 +3,19 @@ package auth
import (
"context"
"crypto/subtle"
"strings"
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/anypb"
"go.linka.cloud/grpc/interceptors"
"go.linka.cloud/grpc-toolkit/interceptors"
)
func ChainedAuthFuncs(fn ...grpc_auth.AuthFunc) grpc_auth.AuthFunc {
return func(ctx context.Context) (context.Context, error) {
code := codes.Unauthenticated
spb := status.New(codes.Unauthenticated, codes.Unauthenticated.String()).Proto()
for _, v := range fn {
ctx2, err := v(ctx)
if err == nil {
@ -25,11 +25,14 @@ func ChainedAuthFuncs(fn ...grpc_auth.AuthFunc) grpc_auth.AuthFunc {
if !ok {
return ctx2, err
}
if s.Code() == codes.PermissionDenied {
code = codes.PermissionDenied
if spb.Code != s.Proto().Code {
spb.Code = s.Proto().Code
}
d, _ := anypb.New(s.Proto())
spb.Details = append(spb.Details, d)
spb.Message += ", " + s.Proto().Message
}
return ctx, status.Error(code, code.String())
return ctx, status.FromProto(spb).Err()
}
}
@ -71,15 +74,8 @@ func (i *interceptor) isNotProtected(endpoint string) bool {
if len(i.o.ignoredMethods) == 0 && len(i.o.methods) == 0 {
return false
}
// endpoint is like /helloworld.Greeter/SayHello
parts := strings.Split(strings.TrimPrefix(endpoint, "/"), "/")
// invalid endpoint format
if len(parts) != 2 {
return false
}
method := parts[1]
for _, v := range i.o.ignoredMethods {
if v == method {
if v == endpoint {
return true
}
}
@ -87,7 +83,7 @@ func (i *interceptor) isNotProtected(endpoint string) bool {
return false
}
for _, v := range i.o.methods {
if v == method {
if v == endpoint {
return false
}
}

View File

@ -4,32 +4,32 @@ import (
"context"
"testing"
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
assert2 "github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"go.linka.cloud/grpc/errors"
"go.linka.cloud/grpc-toolkit/errors"
)
func TestNotProtectededOnly(t *testing.T) {
func TestNotProtectedOnly(t *testing.T) {
assert := assert2.New(t)
i := &interceptor{o: options{ignoredMethods: []string{"ignored"}}}
i := &interceptor{o: options{ignoredMethods: []string{"/test.Service/ignored"}}}
assert.False(i.isNotProtected("/test.Service/protected"))
assert.True(i.isNotProtected("/test.Service/ignored"))
}
func TestProtectedOnly(t *testing.T) {
assert := assert2.New(t)
i := &interceptor{o: options{methods: []string{"protected"}}}
i := &interceptor{o: options{methods: []string{"/test.Service/protected"}}}
assert.False(i.isNotProtected("/test.Service/protected"))
assert.True(i.isNotProtected("/test.Service/ignored"))
}
func TestProtectedAndIgnored(t *testing.T) {
assert := assert2.New(t)
i := &interceptor{o: options{methods: []string{"protected"}, ignoredMethods: []string{"ignored"}}}
i := &interceptor{o: options{methods: []string{"/test.Service/protected"}, ignoredMethods: []string{"/test.Service/ignored"}}}
assert.True(i.isNotProtected("/test.Service/ignored"))
assert.False(i.isNotProtected("/test.Service/protected"))
assert.True(i.isNotProtected("/test.Service/other"))
@ -37,7 +37,7 @@ func TestProtectedAndIgnored(t *testing.T) {
func TestProtectedByDefault(t *testing.T) {
i := &interceptor{}
assert2.False(t, i.isNotProtected("nooop"))
assert2.False(t, i.isNotProtected("/test.Service/noop"))
assert2.False(t, i.isNotProtected("/test.Service/method/cannotExists"))
assert2.False(t, i.isNotProtected("/test.Service/validMethod"))
}
@ -99,14 +99,14 @@ func TestChainedAuthFuncs(t *testing.T) {
name: "empty bearer",
auth: "bearer ",
err: true,
code: codes.PermissionDenied,
code: codes.Unauthenticated,
},
{
name: "internal error",
auth: "bearer internal",
internalError: true,
err: true,
code: codes.PermissionDenied,
code: codes.Internal,
},
{
name: "multiple auth: first basic valid",
@ -120,13 +120,13 @@ func TestChainedAuthFuncs(t *testing.T) {
name: "invalid auth: bearer",
auth: "bearer noop",
err: true,
code: codes.PermissionDenied,
code: codes.Unauthenticated,
},
{
name: "invalid auth: basic",
auth: BasicAuth("other", "other"),
err: true,
code: codes.PermissionDenied,
code: codes.Unauthenticated,
},
}

View File

@ -1,17 +1,19 @@
package auth
import (
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
)
type Option func(o *options)
// WithMethods change the behaviour to not protect by default, it takes a list of fully qualified method names to protect, e.g. /helloworld.Greeter/SayHello
func WithMethods(methods ...string) Option {
return func(o *options) {
o.methods = append(o.methods, methods...)
}
}
// WithIgnoredMethods bypass auth for the given methods, it takes a list of fully qualified method name, e.g. /helloworld.Greeter/SayHello
func WithIgnoredMethods(methods ...string) Option {
return func(o *options) {
o.ignoredMethods = append(o.ignoredMethods, methods...)

View File

@ -3,10 +3,10 @@ package auth
import (
"context"
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
"go.linka.cloud/grpc/interceptors"
"go.linka.cloud/grpc/interceptors/metadata"
"go.linka.cloud/grpc-toolkit/interceptors"
"go.linka.cloud/grpc-toolkit/interceptors/metadata"
)
type TokenValidator func(ctx context.Context, token string) (context.Context, error)
@ -22,5 +22,5 @@ func makeTokenAuthFunc(v TokenValidator) grpc_auth.AuthFunc {
}
func NewBearerClientInterceptors(token string) interceptors.ClientInterceptors {
return metadata.NewInterceptors("authorization", "Bearer "+token)
return metadata.NewInterceptors("authorization", "bearer "+token)
}

View File

@ -3,11 +3,11 @@ package auth
import (
"context"
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/peer"
"go.linka.cloud/grpc/errors"
"go.linka.cloud/grpc-toolkit/errors"
)
type X509Validator func(ctx context.Context, sans []string) (context.Context, error)

126
interceptors/ban/ban.go Normal file
View File

@ -0,0 +1,126 @@
package ban
import (
"context"
"github.com/jaredfolkins/badactor"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"go.linka.cloud/grpc-toolkit/interceptors"
"go.linka.cloud/grpc-toolkit/logger"
)
type ban struct {
s *badactor.Studio
rules map[codes.Code]Rule
actor func(ctx context.Context) (string, bool, error)
}
func NewInterceptors(opts ...Option) interceptors.ServerInterceptors {
o := defaultOptions
for _, opt := range opts {
opt(&o)
}
s := badactor.NewStudio(o.cap)
rules := make(map[codes.Code]Rule)
for _, r := range o.rules {
rules[r.Code] = r
callback := r.Callback
if callback == nil {
callback = o.defaultCallback
}
expire := r.JailDuration
if expire == 0 {
expire = o.defaultJailDuration
}
s.AddRule(&badactor.Rule{
Name: r.Name,
Message: r.Message,
StrikeLimit: r.StrikeLimit,
ExpireBase: expire,
Sentence: expire,
Action: &action{fn: callback},
})
}
// we ignore the error because CreateDirectors never returns an error
_ = s.CreateDirectors(o.cap)
s.StartReaper(o.reaperInterval)
return &ban{s: s, rules: rules, actor: o.actorFunc}
}
func (b *ban) UnaryServerInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
actor, ok, err := b.check(ctx)
if err != nil {
return nil, err
}
ctx = set(ctx, b, actor)
if !ok {
return handler(ctx, req)
}
for _, v := range b.rules {
if b.s.IsJailedFor(actor, v.Name) {
return nil, status.Error(v.Code, v.Message)
}
}
res, err := handler(ctx, req)
if err != nil {
return nil, b.handleErr(ctx, actor, err)
}
return res, nil
}
}
func (b *ban) StreamServerInterceptor() grpc.StreamServerInterceptor {
return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
actor, ok, err := b.check(ss.Context())
if err != nil {
return err
}
ss = interceptors.NewContextServerStream(set(ss.Context(), b, actor), ss)
if !ok {
return handler(srv, ss)
}
if err := handler(srv, ss); err != nil {
return b.handleErr(ss.Context(), actor, err)
}
return nil
}
}
func (b *ban) check(ctx context.Context) (actor string, ok bool, err error) {
actor, ok, err = b.actor(ctx)
if err != nil {
return "", false, err
}
if !ok {
return "", false, nil
}
for _, v := range b.rules {
if b.s.IsJailedFor(actor, v.Name) {
return actor, false, status.Error(v.Code, v.Message)
}
}
return actor, true, nil
}
func (b *ban) handleErr(ctx context.Context, actor string, err error) error {
v, ok := ctx.Value(key{}).(*value)
if !ok || v.done {
return err
}
s, ok := status.FromError(err)
if !ok {
return err
}
r, ok := b.rules[s.Code()]
if !ok {
return err
}
if err := b.s.Infraction(actor, r.Name); err != nil {
logger.C(ctx).Warnf("%s: failed to add infraction: %v", r.Name, err)
}
return err
}

View File

@ -0,0 +1,34 @@
package ban
import (
"context"
)
type key struct{}
type value struct {
ban *ban
actor string
done bool
}
func set(ctx context.Context, b *ban, actor string) context.Context {
return context.WithValue(ctx, key{}, &value{ban: b, actor: actor})
}
func Infraction(ctx context.Context, rule string) error {
v, ok := ctx.Value(key{}).(*value)
if !ok {
return nil
}
v.done = true
return v.ban.s.Infraction(v.actor, rule)
}
func Actor(ctx context.Context) string {
v, ok := ctx.Value(key{}).(*value)
if !ok {
return ""
}
return v.actor
}

View File

@ -0,0 +1,99 @@
package ban
import (
"context"
"net"
"time"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/peer"
)
const (
Unauthorized = "Unauthorized"
Unauthenticated = "Unauthenticated"
)
var (
defaultOptions = options{
cap: 1024,
reaperInterval: 10 * time.Minute,
rules: defaultRules,
actorFunc: DefaultActorFunc,
defaultCallback: nil,
defaultJailDuration: 10 * time.Second,
}
defaultRules = []Rule{
{
Name: Unauthorized,
Message: "Too many unauthorized requests",
Code: codes.PermissionDenied,
StrikeLimit: 3,
},
{
Name: Unauthenticated,
Message: "Too many unauthenticated requests",
Code: codes.Unauthenticated,
StrikeLimit: 3,
},
}
)
func DefaultActorFunc(ctx context.Context) (string, bool, error) {
p, ok := peer.FromContext(ctx)
if !ok {
return "", false, nil
}
if host, _, err := net.SplitHostPort(p.Addr.String()); err == nil {
return host, true, nil
}
return p.Addr.String(), true, nil
}
type Option func(*options)
func WithCapacity(cap int32) Option {
return func(o *options) {
o.cap = cap
}
}
func WithRules(rules ...Rule) Option {
return func(o *options) {
o.rules = rules
}
}
func WithReaperInterval(interval time.Duration) Option {
return func(o *options) {
o.reaperInterval = interval
}
}
func WithActorFunc(f func(context.Context) (name string, found bool, err error)) Option {
return func(o *options) {
o.actorFunc = f
}
}
func WithDefaultCallback(f ActionCallback) Option {
return func(o *options) {
o.defaultCallback = f
}
}
func WithDefaultJailDuration(expire time.Duration) Option {
return func(o *options) {
o.defaultJailDuration = expire
}
}
type options struct {
cap int32
rules []Rule
reaperInterval time.Duration
actorFunc func(ctx context.Context) (name string, found bool, err error)
defaultCallback ActionCallback
defaultJailDuration time.Duration
}

66
interceptors/ban/rule.go Normal file
View File

@ -0,0 +1,66 @@
package ban
import (
"time"
"github.com/jaredfolkins/badactor"
"google.golang.org/grpc/codes"
)
type ActionCallback func(action Action, actor string, rule *Rule) error
type Action int
const (
Jailed Action = iota
Released
)
func (a Action) String() string {
switch a {
case Jailed:
return "Jailed"
case Released:
return "Released"
default:
return "Unknown"
}
}
type Rule struct {
Name string
Message string
Code codes.Code
StrikeLimit int
JailDuration time.Duration
// Callback is an optional function to call when an Actor isJailed or released because of timeServed
Callback ActionCallback
}
type action struct {
fn ActionCallback
}
func (a2 *action) WhenJailed(a *badactor.Actor, r *badactor.Rule) error {
if a2.fn == nil {
return nil
}
return a2.fn(Jailed, a.Name(), &Rule{
Name: r.Name,
Message: r.Message,
StrikeLimit: r.StrikeLimit,
JailDuration: r.ExpireBase,
})
}
func (a2 *action) WhenTimeServed(a *badactor.Actor, r *badactor.Rule) error {
if a2.fn == nil {
return nil
}
return a2.fn(Released, a.Name(), &Rule{
Name: r.Name,
Message: r.Message,
StrikeLimit: r.StrikeLimit,
JailDuration: r.ExpireBase,
})
}

126
interceptors/chain/chain.go Normal file
View File

@ -0,0 +1,126 @@
package chain
import (
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
"google.golang.org/grpc"
"go.linka.cloud/grpc-toolkit/interceptors"
)
type Option func(*chain)
func WithInterceptors(i ...interceptors.Interceptors) Option {
return func(c *chain) {
for _, i := range i {
if i := i.UnaryServerInterceptor(); i != nil {
c.usi = append(c.usi, i)
}
if i := i.StreamServerInterceptor(); i != nil {
c.ssi = append(c.ssi, i)
}
if i := i.UnaryClientInterceptor(); i != nil {
c.uci = append(c.uci, i)
}
if i := i.StreamClientInterceptor(); i != nil {
c.sci = append(c.sci, i)
}
}
}
}
func WithServerInterceptors(si ...interceptors.ServerInterceptors) Option {
return func(c *chain) {
for _, i := range si {
if i := i.UnaryServerInterceptor(); i != nil {
c.usi = append(c.usi, i)
}
if i := i.StreamServerInterceptor(); i != nil {
c.ssi = append(c.ssi, i)
}
}
}
}
func WithClientInterceptors(ci ...interceptors.ClientInterceptors) Option {
return func(c *chain) {
for _, i := range ci {
if i := i.UnaryClientInterceptor(); i != nil {
c.uci = append(c.uci, i)
}
if i := i.StreamClientInterceptor(); i != nil {
c.sci = append(c.sci, i)
}
}
}
}
func WithUnaryServerInterceptors(usi ...grpc.UnaryServerInterceptor) Option {
return func(c *chain) {
for _, i := range usi {
if i != nil {
c.usi = append(c.usi, i)
}
}
}
}
func WithStreamServerInterceptors(ssi ...grpc.StreamServerInterceptor) Option {
return func(c *chain) {
for _, i := range ssi {
if i != nil {
c.ssi = append(c.ssi, i)
}
}
}
}
func WithUnaryClientInterceptors(uci ...grpc.UnaryClientInterceptor) Option {
return func(c *chain) {
for _, i := range uci {
if i != nil {
c.uci = append(c.uci, i)
}
}
}
}
func WithStreamClientInterceptors(sci ...grpc.StreamClientInterceptor) Option {
return func(c *chain) {
for _, i := range sci {
if i != nil {
c.sci = append(c.sci, i)
}
}
}
}
func New(opts ...Option) interceptors.Interceptors {
c := &chain{}
for _, o := range opts {
o(c)
}
return c
}
type chain struct {
usi []grpc.UnaryServerInterceptor
ssi []grpc.StreamServerInterceptor
uci []grpc.UnaryClientInterceptor
sci []grpc.StreamClientInterceptor
}
func (c *chain) UnaryServerInterceptor() grpc.UnaryServerInterceptor {
return grpc_middleware.ChainUnaryServer(c.usi...)
}
func (c *chain) StreamServerInterceptor() grpc.StreamServerInterceptor {
return grpc_middleware.ChainStreamServer(c.ssi...)
}
func (c *chain) UnaryClientInterceptor() grpc.UnaryClientInterceptor {
return grpc_middleware.ChainUnaryClient(c.uci...)
}
func (c *chain) StreamClientInterceptor() grpc.StreamClientInterceptor {
return grpc_middleware.ChainStreamClient(c.sci...)
}

View File

@ -5,7 +5,7 @@ import (
"google.golang.org/grpc"
"go.linka.cloud/grpc/interceptors"
"go.linka.cloud/grpc-toolkit/interceptors"
)
type interceptor struct{}

View File

@ -0,0 +1,41 @@
package iface
import (
"context"
"google.golang.org/grpc"
"go.linka.cloud/grpc-toolkit/interceptors"
)
type UnaryInterceptor interface {
UnaryServerInterceptor() grpc.UnaryServerInterceptor
}
type StreamInterceptor interface {
StreamServerInterceptor() grpc.StreamServerInterceptor
}
type iface struct{}
func New() interceptors.ServerInterceptors {
return &iface{}
}
func (s iface) UnaryServerInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
if i, ok := info.Server.(UnaryInterceptor); ok {
return i.UnaryServerInterceptor()(ctx, req, info, handler)
}
return handler(ctx, req)
}
}
func (s iface) StreamServerInterceptor() grpc.StreamServerInterceptor {
return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
if i, ok := srv.(StreamInterceptor); ok {
return i.StreamServerInterceptor()(srv, ss, info, handler)
}
return handler(srv, ss)
}
}

View File

@ -1,6 +1,8 @@
package interceptors
import (
"context"
"google.golang.org/grpc"
)
@ -18,3 +20,16 @@ type Interceptors interface {
ServerInterceptors
ClientInterceptors
}
func NewContextServerStream(ctx context.Context, ss grpc.ServerStream) grpc.ServerStream {
return &ContextWrapper{ServerStream: ss, ctx: ctx}
}
type ContextWrapper struct {
grpc.ServerStream
ctx context.Context
}
func (w *ContextWrapper) Context() context.Context {
return w.ctx
}

View File

@ -0,0 +1,77 @@
package logging
import (
"context"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging"
"google.golang.org/grpc"
"go.linka.cloud/grpc-toolkit/interceptors"
"go.linka.cloud/grpc-toolkit/logger"
)
func New(ctx context.Context, opts ...logging.Option) interceptors.Interceptors {
log := logger.C(ctx)
return &interceptor{
log: logging.LoggerFunc(func(ctx context.Context, level logging.Level, msg string, fields ...any) {
switch level {
case logging.LevelDebug:
log.WithReportCaller(true, 2).WithFields(fields...).Debug(msg)
case logging.LevelInfo:
log.WithReportCaller(true, 2).WithFields(fields...).Info(msg)
case logging.LevelWarn:
log.WithReportCaller(true, 2).WithFields(fields...).Warn(msg)
case logging.LevelError:
log.WithReportCaller(true, 2).WithFields(fields...).Error(msg)
}
}),
opts: opts,
}
}
type interceptor struct {
log logging.Logger
opts []logging.Option
}
func (i *interceptor) UnaryServerInterceptor() grpc.UnaryServerInterceptor {
return grpc_middleware.ChainUnaryServer(
logging.UnaryServerInterceptor(i.log, i.opts...),
func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
log := logger.C(ctx)
return handler(logger.Set(ctx, log.WithFields(logging.ExtractFields(ctx)...)), req)
},
)
}
func (i *interceptor) StreamServerInterceptor() grpc.StreamServerInterceptor {
return grpc_middleware.ChainStreamServer(
logging.StreamServerInterceptor(i.log, i.opts...),
func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
ctx := ss.Context()
log := logger.C(ctx)
return handler(srv, interceptors.NewContextServerStream(logger.Set(ctx, log.WithFields(logging.ExtractFields(ctx)...)), ss))
},
)
}
func (i *interceptor) UnaryClientInterceptor() grpc.UnaryClientInterceptor {
return grpc_middleware.ChainUnaryClient(
logging.UnaryClientInterceptor(i.log, i.opts...),
func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
log := logger.C(ctx)
return invoker(logger.Set(ctx, log.WithFields(logging.ExtractFields(ctx)...)), method, req, reply, cc, opts...)
},
)
}
func (i *interceptor) StreamClientInterceptor() grpc.StreamClientInterceptor {
return grpc_middleware.ChainStreamClient(
logging.StreamClientInterceptor(i.log, i.opts...),
func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
log := logger.C(ctx)
return streamer(logger.Set(ctx, log.WithFields(logging.ExtractFields(ctx)...)), desc, cc, method, opts...)
},
)
}

View File

@ -0,0 +1,40 @@
package metadata
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"go.linka.cloud/grpc-toolkit/interceptors"
)
func NewForwardInterceptors() interceptors.ServerInterceptors {
return &forward{}
}
type forward struct{}
func (f *forward) UnaryServerInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
if md, ok := metadata.FromIncomingContext(ctx); ok {
ctx = metadata.NewOutgoingContext(ctx, md.Copy())
}
return handler(ctx, req)
}
}
func (f *forward) StreamServerInterceptor() grpc.StreamServerInterceptor {
return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
ctx := ss.Context()
md1, ok := metadata.FromIncomingContext(ctx)
if !ok {
return handler(srv, ss)
}
o := md1.Copy()
if md2, ok := metadata.FromOutgoingContext(ctx); ok {
o = metadata.Join(o, md2.Copy())
}
return handler(srv, interceptors.NewContextServerStream(metadata.NewOutgoingContext(ctx, o), ss))
}
}

View File

@ -6,7 +6,7 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"go.linka.cloud/grpc/interceptors"
"go.linka.cloud/grpc-toolkit/interceptors"
)
func NewInterceptors(pairs ...string) interceptors.Interceptors {
@ -37,14 +37,14 @@ func (i mdInterceptors) StreamServerInterceptor() grpc.StreamServerInterceptor {
func (i mdInterceptors) UnaryClientInterceptor() grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs(i.pairs...))
ctx = metadata.AppendToOutgoingContext(ctx, i.pairs...)
return invoker(ctx, method, req, reply, cc, opts...)
}
}
func (i mdInterceptors) StreamClientInterceptor() grpc.StreamClientInterceptor {
return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs(i.pairs...))
ctx = metadata.AppendToOutgoingContext(ctx, i.pairs...)
return streamer(ctx, desc, cc, method, opts...)
}
}

View File

@ -1,14 +1,24 @@
package metrics
import (
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
"context"
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus"
"github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel/trace"
"google.golang.org/grpc"
"go.linka.cloud/grpc/interceptors"
"go.linka.cloud/grpc/service"
"go.linka.cloud/grpc-toolkit/interceptors"
"go.linka.cloud/grpc-toolkit/service"
)
func DefaultExemplarFromCtx(ctx context.Context) prometheus.Labels {
if span := trace.SpanContextFromContext(ctx); span.IsSampled() {
return prometheus.Labels{"traceID": span.TraceID().String()}
}
return nil
}
type Registerer interface {
Register(svc service.Service)
}
@ -31,6 +41,7 @@ type ClientInterceptors interface {
type metrics struct {
s *grpc_prometheus.ServerMetrics
c *grpc_prometheus.ClientMetrics
o *options
}
func (m *metrics) Describe(descs chan<- *prometheus.Desc) {
@ -51,36 +62,45 @@ func (m *metrics) Register(svc service.Service) {
}
}
func NewInterceptors(opts ...grpc_prometheus.CounterOption) Interceptors {
s := grpc_prometheus.NewServerMetrics(opts...)
c := grpc_prometheus.NewClientMetrics(opts...)
return &metrics{s: s, c: c}
func NewInterceptors(opts ...Option) Interceptors {
o := (&options{}).apply(opts...)
s := grpc_prometheus.NewServerMetrics(
grpc_prometheus.WithServerCounterOptions(o.copts...),
grpc_prometheus.WithServerHandlingTimeHistogram(o.hopts...),
)
c := grpc_prometheus.NewClientMetrics(
grpc_prometheus.WithClientCounterOptions(o.copts...),
grpc_prometheus.WithClientHandlingTimeHistogram(o.hopts...),
)
m := &metrics{s: s, c: c, o: o}
o.reg.MustRegister(m)
return m
}
func NewServerInterceptors(opts ...grpc_prometheus.CounterOption) ServerInterceptors {
s := grpc_prometheus.NewServerMetrics(opts...)
return &metrics{s: s}
func NewServerInterceptors(opts ...Option) ServerInterceptors {
o := (&options{}).apply(opts...)
s := grpc_prometheus.NewServerMetrics(
grpc_prometheus.WithServerCounterOptions(o.copts...),
grpc_prometheus.WithServerHandlingTimeHistogram(o.hopts...),
)
m := &metrics{s: s, o: o}
o.reg.MustRegister(m)
return m
}
func NewClientInterceptors(opts ...grpc_prometheus.CounterOption) ClientInterceptors {
c := grpc_prometheus.NewClientMetrics(opts...)
return &metrics{c: c}
}
func DefaultInterceptors() Interceptors {
return &metrics{s: grpc_prometheus.DefaultServerMetrics, c: grpc_prometheus.DefaultClientMetrics}
}
func DefaultServerInterceptors() ServerInterceptors {
return &metrics{s: grpc_prometheus.DefaultServerMetrics}
}
func DefaultClientInterceptors() ClientInterceptors {
return &metrics{c: grpc_prometheus.DefaultClientMetrics}
func NewClientInterceptors(opts ...Option) ClientInterceptors {
o := (&options{}).apply(opts...)
c := grpc_prometheus.NewClientMetrics(
grpc_prometheus.WithClientCounterOptions(o.copts...),
grpc_prometheus.WithClientHandlingTimeHistogram(o.hopts...),
)
m := &metrics{c: c, o: o}
o.reg.MustRegister(m)
return m
}
func (m *metrics) UnaryServerInterceptor() grpc.UnaryServerInterceptor {
return m.s.UnaryServerInterceptor()
return m.s.UnaryServerInterceptor(grpc_prometheus.WithExemplarFromContext(m.o.fn))
}
func (m *metrics) StreamServerInterceptor() grpc.StreamServerInterceptor {

View File

@ -0,0 +1,59 @@
package metrics
import (
"context"
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus"
"github.com/prometheus/client_golang/prometheus"
)
type ExemplarFromCtxFunc func(ctx context.Context) prometheus.Labels
type Option func(*options)
func WithCounterOptons(opts ...grpc_prometheus.CounterOption) Option {
return func(o *options) {
o.copts = append(o.copts, opts...)
}
}
func WithHandlingTimeHistogram(opts ...grpc_prometheus.HistogramOption) Option {
return func(o *options) {
o.hopts = append(o.hopts, opts...)
}
}
func WithHistogramOpts(opts ...grpc_prometheus.HistogramOption) Option {
return func(o *options) {
o.hopts = append(o.hopts, opts...)
}
}
func WithExemplarFromContext(fn ExemplarFromCtxFunc) Option {
return func(o *options) {
o.fn = fn
}
}
func WithRegisterer(reg prometheus.Registerer) Option {
return func(o *options) {
o.reg = reg
}
}
type options struct {
copts []grpc_prometheus.CounterOption
hopts []grpc_prometheus.HistogramOption
fn func(ctx context.Context) prometheus.Labels
reg prometheus.Registerer
}
func (o *options) apply(opts ...Option) *options {
for _, v := range opts {
v(o)
}
if o.reg == nil {
o.reg = prometheus.DefaultRegisterer
}
return o
}

View File

@ -0,0 +1,39 @@
package noop
import (
"context"
"google.golang.org/grpc"
"go.linka.cloud/grpc-toolkit/interceptors"
)
func New() interceptors.Interceptors {
return &noopInterceptor{}
}
type noopInterceptor struct{}
func (m *noopInterceptor) UnaryServerInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
return handler(ctx, req)
}
}
func (m *noopInterceptor) StreamServerInterceptor() grpc.StreamServerInterceptor {
return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
return handler(srv, ss)
}
}
func (m *noopInterceptor) UnaryClientInterceptor() grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
return invoker(ctx, method, req, reply, cc, opts...)
}
}
func (m *noopInterceptor) StreamClientInterceptor() grpc.StreamClientInterceptor {
return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
return streamer(ctx, desc, cc, method, opts...)
}
}

View File

@ -4,7 +4,7 @@ import (
grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
"google.golang.org/grpc"
"go.linka.cloud/grpc/interceptors"
"go.linka.cloud/grpc-toolkit/interceptors"
)
func NewInterceptors(opts ...grpc_recovery.Option) interceptors.ServerInterceptors {
@ -22,11 +22,3 @@ func (i *recovery) UnaryServerInterceptor() grpc.UnaryServerInterceptor {
func (i *recovery) StreamServerInterceptor() grpc.StreamServerInterceptor {
return grpc_recovery.StreamServerInterceptor(i.opts...)
}
func (i *recovery) UnaryClientInterceptor() grpc.UnaryClientInterceptor {
panic("not implemented")
}
func (i *recovery) StreamClientInterceptor() grpc.StreamClientInterceptor {
panic("not implemented")
}

View File

@ -5,7 +5,7 @@ import (
grpc_sentry "github.com/johnbellone/grpc-middleware-sentry"
"go.linka.cloud/grpc/interceptors"
"go.linka.cloud/grpc-toolkit/interceptors"
)
type interceptor struct {

View File

@ -1,41 +1,40 @@
package tracing
import (
"github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc"
"github.com/opentracing/opentracing-go"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"google.golang.org/grpc"
"go.linka.cloud/grpc/interceptors"
"go.linka.cloud/grpc-toolkit/interceptors"
)
type tracing struct {
opts []otgrpc.Option
opts []otelgrpc.Option
}
func NewInterceptors(opts ...otgrpc.Option) interceptors.Interceptors {
func NewInterceptors(opts ...otelgrpc.Option) interceptors.Interceptors {
return tracing{opts: opts}
}
func NewClientInterceptors(opts ...otgrpc.Option) interceptors.ClientInterceptors {
func NewClientInterceptors(opts ...otelgrpc.Option) interceptors.ClientInterceptors {
return tracing{opts: opts}
}
func (t tracing) UnaryClientInterceptor() grpc.UnaryClientInterceptor {
return otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer(), t.opts...)
return otelgrpc.UnaryClientInterceptor(t.opts...)
}
func (t tracing) StreamClientInterceptor() grpc.StreamClientInterceptor {
return otgrpc.OpenTracingStreamClientInterceptor(opentracing.GlobalTracer(), t.opts...)
return otelgrpc.StreamClientInterceptor(t.opts...)
}
func NewServerInterceptors(opts ...otgrpc.Option) interceptors.ServerInterceptors {
func NewServerInterceptors(opts ...otelgrpc.Option) interceptors.ServerInterceptors {
return tracing{opts: opts}
}
func (t tracing) UnaryServerInterceptor() grpc.UnaryServerInterceptor {
return otgrpc.OpenTracingServerInterceptor(opentracing.GlobalTracer(), t.opts...)
return otelgrpc.UnaryServerInterceptor(t.opts...)
}
func (t tracing) StreamServerInterceptor() grpc.StreamServerInterceptor {
return otgrpc.OpenTracingStreamServerInterceptor(opentracing.GlobalTracer(), t.opts...)
return otelgrpc.StreamServerInterceptor(t.opts...)
}

View File

@ -5,10 +5,9 @@ import (
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc"
"google.golang.org/protobuf/proto"
"go.linka.cloud/grpc/errors"
"go.linka.cloud/grpc/interceptors"
"go.linka.cloud/grpc-toolkit/errors"
"go.linka.cloud/grpc-toolkit/interceptors"
)
type validatorAll interface {
@ -39,10 +38,24 @@ type validatorError interface {
ErrorName() string
}
func validatorErrorToGrpc(e validatorError) *errdetails.BadRequest_FieldViolation {
return &errdetails.BadRequest_FieldViolation{
Field: e.Field(),
func validatorErrorToGrpc(e validatorError, prefix string) []*errdetails.BadRequest_FieldViolation {
// check nested errors for validation error, e.g. "embedded message failed validation"
switch v := e.Cause().(type) {
case validatorError:
return validatorErrorToGrpc(v, e.Field()+".")
case validatorMultiError:
var details []*errdetails.BadRequest_FieldViolation
for _, vv := range v.AllErrors() {
if ee, ok := vv.(validatorError); ok {
details = append(details, validatorErrorToGrpc(ee, e.Field()+".")...)
}
}
return details
default:
return []*errdetails.BadRequest_FieldViolation{{
Field: prefix + e.Field(),
Description: e.Reason(),
}}
}
}
@ -52,15 +65,15 @@ func errToStatus(err error) error {
}
switch v := err.(type) {
case validatorError:
return errors.InvalidArgumentD(err, validatorErrorToGrpc(v))
return errors.InvalidArgumentd(err, &errdetails.BadRequest{FieldViolations: validatorErrorToGrpc(v, "")})
case validatorMultiError:
var details []proto.Message
details := &errdetails.BadRequest{}
for _, v := range v.AllErrors() {
if d, ok := v.(validatorError); ok {
details = append(details, validatorErrorToGrpc(d))
details.FieldViolations = append(details.FieldViolations, validatorErrorToGrpc(d, "")...)
}
}
return errors.InvalidArgumentd(err, details...)
return errors.InvalidArgumentd(err, details)
default:
return errors.InvalidArgument(err)
}

View File

@ -0,0 +1,50 @@
package injectlogger
import (
"context"
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging"
"google.golang.org/grpc"
"go.linka.cloud/grpc-toolkit/interceptors"
"go.linka.cloud/grpc-toolkit/logger"
)
func New(ctx context.Context) interceptors.Interceptors {
return &interceptor{
ctx: ctx,
}
}
type interceptor struct {
ctx context.Context
}
func (i *interceptor) UnaryServerInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
log := logger.C(i.ctx)
return handler(logger.Set(ctx, log.WithFields(logging.ExtractFields(ctx)...)), req)
}
}
func (i *interceptor) StreamServerInterceptor() grpc.StreamServerInterceptor {
return func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
ctx := ss.Context()
log := logger.C(i.ctx)
return handler(srv, interceptors.NewContextServerStream(logger.Set(ctx, log.WithFields(logging.ExtractFields(ctx)...)), ss))
}
}
func (i *interceptor) UnaryClientInterceptor() grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
log := logger.C(i.ctx)
return invoker(logger.Set(ctx, log.WithFields(logging.ExtractFields(ctx)...)), method, req, reply, cc, opts...)
}
}
func (i *interceptor) StreamClientInterceptor() grpc.StreamClientInterceptor {
return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
log := logger.C(i.ctx)
return streamer(logger.Set(ctx, log.WithFields(logging.ExtractFields(ctx)...)), desc, cc, method, opts...)
}
}

View File

@ -33,7 +33,7 @@ func Set(ctx context.Context, logger Logger) context.Context {
func From(ctx context.Context) Logger {
log, ok := ctx.Value(log{}).(Logger)
if ok {
return log
return log.WithContext(ctx)
}
if defaultLogger != nil {
return defaultLogger
@ -42,5 +42,5 @@ func From(ctx context.Context) Logger {
mu.Lock()
defer mu.Unlock()
defaultLogger = logr
return defaultLogger
return defaultLogger.WithContext(ctx)
}

View File

@ -1,9 +1,15 @@
package logger
import (
"context"
"fmt"
"io"
"path/filepath"
"runtime"
"strings"
"github.com/bombsimon/logrusr/v4"
"github.com/go-logr/logr"
"github.com/sirupsen/logrus"
)
@ -11,6 +17,27 @@ var (
standardLogger Logger = &logger{fl: logrus.StandardLogger()}
)
const (
// PanicLevel level, highest level of severity. Logs and then calls panic with the
// message passed to Debug, Info, ...
PanicLevel Level = iota
// FatalLevel level. Logs and then calls `logger.Exit(1)`. It will exit even if the
// logging level is set to Panic.
FatalLevel
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
// Commonly used for hooks to send errors to an error tracking service.
ErrorLevel
// WarnLevel level. Non-critical entries that deserve eyes.
WarnLevel
// InfoLevel level. General operational entries about what's going on inside the
// application.
InfoLevel
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
DebugLevel
// TraceLevel level. Designates finer-grained informational events than the Debug.
TraceLevel
)
func StandardLogger() Logger {
return standardLogger
}
@ -19,13 +46,27 @@ func New() Logger {
return &logger{fl: logrus.New()}
}
func FromLogrus(fl logrus.Ext1FieldLogger) Logger {
return &logger{fl: fl}
}
type Level = logrus.Level
type Logger interface {
WithContext(ctx context.Context) Logger
WithReportCaller(b bool, depth ...uint) Logger
WithField(key string, value interface{}) Logger
WithFields(kv ...interface{}) Logger
WithError(err error) Logger
WriterLevel(level logrus.Level) *io.PipeWriter
SetLevel(level Level) Logger
WriterLevel(level Level) *io.PipeWriter
SetOutput(w io.Writer) Logger
Tracef(format string, args ...interface{})
Debugf(format string, args ...interface{})
Infof(format string, args ...interface{})
Printf(format string, args ...interface{})
@ -35,6 +76,7 @@ type Logger interface {
Fatalf(format string, args ...interface{})
Panicf(format string, args ...interface{})
Trace(args ...interface{})
Debug(args ...interface{})
Info(args ...interface{})
Print(args ...interface{})
@ -44,6 +86,7 @@ type Logger interface {
Fatal(args ...interface{})
Panic(args ...interface{})
Traceln(args ...interface{})
Debugln(args ...interface{})
Infoln(args ...interface{})
Println(args ...interface{})
@ -52,130 +95,242 @@ type Logger interface {
Errorln(args ...interface{})
Fatalln(args ...interface{})
Panicln(args ...interface{})
Logr() logr.Logger
FieldLogger() logrus.FieldLogger
Logger() *logrus.Logger
Clone() Logger
}
type logger struct {
fl logrus.FieldLogger
fl logrus.Ext1FieldLogger
reportCaller *int
}
func (l *logger) Tracef(format string, args ...interface{}) {
l.withCaller().Tracef(format, args...)
}
func (l *logger) Debugf(format string, args ...interface{}) {
l.fl.Debugf(format, args...)
l.withCaller().Debugf(format, args...)
}
func (l *logger) Infof(format string, args ...interface{}) {
l.fl.Infof(format, args...)
l.withCaller().Infof(format, args...)
}
func (l *logger) Printf(format string, args ...interface{}) {
l.fl.Printf(format, args...)
l.withCaller().Printf(format, args...)
}
func (l *logger) Warnf(format string, args ...interface{}) {
l.fl.Warnf(format, args...)
l.withCaller().Warnf(format, args...)
}
func (l *logger) Warningf(format string, args ...interface{}) {
l.fl.Warningf(format, args...)
l.withCaller().Warningf(format, args...)
}
func (l *logger) Errorf(format string, args ...interface{}) {
l.fl.Errorf(format, args...)
l.withCaller().Errorf(format, args...)
}
func (l *logger) Fatalf(format string, args ...interface{}) {
l.fl.Fatalf(format, args...)
l.withCaller().Fatalf(format, args...)
}
func (l *logger) Panicf(format string, args ...interface{}) {
l.fl.Panicf(format, args...)
l.withCaller().Panicf(format, args...)
}
func (l *logger) Trace(args ...interface{}) {
l.withCaller().Trace(args...)
}
func (l *logger) Debug(args ...interface{}) {
l.fl.Debug(args...)
l.withCaller().Debug(args...)
}
func (l *logger) Info(args ...interface{}) {
l.fl.Info(args...)
l.withCaller().Info(args...)
}
func (l *logger) Print(args ...interface{}) {
l.fl.Print(args...)
l.withCaller().Print(args...)
}
func (l *logger) Warn(args ...interface{}) {
l.fl.Warn(args...)
l.withCaller().Warn(args...)
}
func (l *logger) Warning(args ...interface{}) {
l.fl.Warning(args...)
l.withCaller().Warning(args...)
}
func (l *logger) Error(args ...interface{}) {
l.fl.Error(args...)
l.withCaller().Error(args...)
}
func (l *logger) Fatal(args ...interface{}) {
l.fl.Fatal(args...)
l.withCaller().Fatal(args...)
}
func (l *logger) Panic(args ...interface{}) {
l.fl.Panic(args...)
l.withCaller().Panic(args...)
}
func (l *logger) Traceln(args ...interface{}) {
l.withCaller().Traceln(args...)
}
func (l *logger) Debugln(args ...interface{}) {
l.fl.Debugln(args...)
l.withCaller().Debugln(args...)
}
func (l *logger) Infoln(args ...interface{}) {
l.fl.Infoln(args...)
l.withCaller().Infoln(args...)
}
func (l *logger) Println(args ...interface{}) {
l.fl.Println(args...)
l.withCaller().Println(args...)
}
func (l *logger) Warnln(args ...interface{}) {
l.fl.Warnln(args...)
l.withCaller().Warnln(args...)
}
func (l *logger) Warningln(args ...interface{}) {
l.fl.Warningln(args...)
l.withCaller().Warningln(args...)
}
func (l *logger) Errorln(args ...interface{}) {
l.fl.Errorln(args...)
l.withCaller().Errorln(args...)
}
func (l *logger) Fatalln(args ...interface{}) {
l.fl.Fatalln(args...)
l.withCaller().Fatalln(args...)
}
func (l *logger) Panicln(args ...interface{}) {
l.fl.Panicln(args...)
l.withCaller().Panicln(args...)
}
func (l *logger) WriterLevel(level logrus.Level) *io.PipeWriter {
func (l *logger) WriterLevel(level Level) *io.PipeWriter {
return l.Logger().WriterLevel(level)
}
func (l *logger) SetLevel(level Level) Logger {
l.Logger().SetLevel(level)
return l
}
func (l *logger) WithContext(ctx context.Context) Logger {
switch t := l.fl.(type) {
case *logrus.Logger:
return t.WriterLevel(level)
return &logger{fl: t.WithContext(ctx), reportCaller: l.reportCaller}
case *logrus.Entry:
return t.WriterLevel(level)
return &logger{fl: t.WithContext(ctx), reportCaller: l.reportCaller}
}
panic(fmt.Sprintf("unsupporter logger type %T", l.fl))
panic(fmt.Sprintf("unexpected logger type %T", l.fl))
}
func (l *logger) WithField(key string, value interface{}) Logger {
return &logger{fl: l.fl.WithField(key, value)}
return &logger{fl: l.fl.WithField(key, value), reportCaller: l.reportCaller}
}
func (l *logger) WithFields(kv ...interface{}) Logger {
log := &logger{fl: l.fl}
for i := 0; i < len(kv); i += 2 {
log = &logger{fl: log.fl.WithField(fmt.Sprintf("%v", kv[i]), kv[i+1])}
log = &logger{fl: log.fl.WithField(fmt.Sprintf("%v", kv[i]), kv[i+1]), reportCaller: l.reportCaller}
}
return log
}
func (l *logger) WithError(err error) Logger {
return &logger{fl: l.fl.WithError(err)}
return &logger{fl: l.fl.WithError(err), reportCaller: l.reportCaller}
}
func (l *logger) WithReportCaller(b bool, depth ...uint) Logger {
if !b {
return &logger{fl: l.fl}
}
var d int
if len(depth) > 0 {
d = int(depth[0])
} else {
d = 0
}
return &logger{fl: l.fl, reportCaller: &d}
}
func (l *logger) Logr() logr.Logger {
return logrusr.New(l.fl)
}
func (l *logger) FieldLogger() logrus.FieldLogger {
return l.fl
}
func (l *logger) Logger() *logrus.Logger {
switch t := l.fl.(type) {
case *logrus.Logger:
return t
case *logrus.Entry:
return t.Logger
}
panic(fmt.Sprintf("unexpected logger type %T", l.fl))
}
func (l *logger) SetOutput(w io.Writer) Logger {
l.Logger().SetOutput(w)
return l
}
func (l *logger) Clone() Logger {
n := logrus.New()
switch t := l.fl.(type) {
case *logrus.Logger:
n.Level = t.Level
n.Out = t.Out
n.Formatter = t.Formatter
n.Hooks = t.Hooks
return &logger{fl: n, reportCaller: l.reportCaller}
case *logrus.Entry:
t = t.Dup()
n.Level = t.Logger.Level
n.Out = t.Logger.Out
n.Formatter = t.Logger.Formatter
n.Hooks = t.Logger.Hooks
t.Logger = n
return &logger{fl: t, reportCaller: l.reportCaller}
}
panic(fmt.Sprintf("unexpected logger type %T", l.fl))
}
func (l *logger) withCaller() logrus.Ext1FieldLogger {
if l.reportCaller == nil {
return l.fl
}
pcs := make([]uintptr, 1)
runtime.Callers(3+*l.reportCaller, pcs)
f, _ := runtime.CallersFrames(pcs).Next()
pkg := getPackageName(f.Function)
return l.fl.WithField("caller", fmt.Sprintf("%s/%s:%d", pkg, filepath.Base(f.File), f.Line)).WithField("func", f.Func.Name())
}
// getPackageName reduces a fully qualified function name to the package name
// There really ought to be a better way...
func getPackageName(f string) string {
for {
lastPeriod := strings.LastIndex(f, ".")
lastSlash := strings.LastIndex(f, "/")
if lastPeriod > lastSlash {
f = f[:lastPeriod]
} else {
break
}
}
return f
}

83
proxy/README.md Normal file
View File

@ -0,0 +1,83 @@
# gRPC Proxy
[![Travis Build](https://travis-ci.org/mwitkow/grpc-proxy.svg?branch=master)](https://travis-ci.org/mwitkow/grpc-proxy)
[![Go Report Card](https://goreportcard.com/badge/github.com/mwitkow/grpc-proxy)](https://goreportcard.com/report/github.com/mwitkow/grpc-proxy)
[![Go Reference](https://pkg.go.dev/badge/github.com/mwitkow/grpc-proxy.svg)](https://pkg.go.dev/github.com/mwitkow/grpc-proxy)
[![Apache 2.0 License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
[gRPC Go](https://github.com/grpc/grpc-go) Proxy server
## Project Goal
Build a transparent reverse proxy for gRPC targets that will make it easy to expose gRPC services
over the internet. This includes:
* no needed knowledge of the semantics of requests exchanged in the call (independent rollouts)
* easy, declarative definition of backends and their mappings to frontends
* simple round-robin load balancing of inbound requests from a single connection to multiple backends
The project now exists as a **proof of concept**, with the key piece being the `proxy` package that
is a generic gRPC reverse proxy handler.
## Proxy Handler
The package [`proxy`](proxy/) contains a generic gRPC reverse proxy handler that allows a gRPC server to
not know about registered handlers or their data types. Please consult the docs, here's an exaple usage.
You can call `proxy.NewProxy` to create a `*grpc.Server` that proxies requests.
```go
proxy := proxy.NewProxy(clientConn)
```
More advanced users will want to define a `StreamDirector` that can make more complex decisions on what
to do with the request.
```go
director = func(ctx context.Context, fullMethodName string) (context.Context, *grpc.ClientConn, error) {
md, _ := metadata.FromIncomingContext(ctx)
outCtx = metadata.NewOutgoingContext(ctx, md.Copy())
return outCtx, cc, nil
// Make sure we never forward internal services.
if strings.HasPrefix(fullMethodName, "/com.example.internal.") {
return outCtx, nil, status.Errorf(codes.Unimplemented, "Unknown method")
}
if ok {
// Decide on which backend to dial
if val, exists := md[":authority"]; exists && val[0] == "staging.api.example.com" {
// Make sure we use DialContext so the dialing can be cancelled/time out together with the context.
return outCtx, grpc.DialContext(ctx, "api-service.staging.svc.local", grpc.WithCodec(proxy.Codec())), nil
} else if val, exists := md[":authority"]; exists && val[0] == "api.example.com" {
return outCtx, grpc.DialContext(ctx, "api-service.prod.svc.local", grpc.WithCodec(proxy.Codec())), nil
}
}
return outCtx, nil, status.Errorf(codes.Unimplemented, "Unknown method")
}
```
Then you need to register it with a `grpc.Server`. The server may have other handlers that will be served
locally.
```go
server := grpc.NewServer(
grpc.CustomCodec(proxy.Codec()),
grpc.UnknownServiceHandler(proxy.TransparentHandler(director)))
pb_test.RegisterTestServiceServer(server, &testImpl{})
```
## Testing
To make debugging a bit simpler, there are some helpers.
`testservice` contains a method `TestTestServiceServerImpl` which performs a complete test against
the reference implementation of the `TestServiceServer`.
In `proxy_test.go`, the test framework spins up a `TestServiceServer` that it tests the proxy
against. To make debugging a bit simpler (eg. if the developer needs to step into
`google.golang.org/grpc` methods), this `TestServiceServer` can be provided by a server by
passing `-test-backend=addr` to `go test`. A simple, local-only implementation of
`TestServiceServer` exists in [`testservice/server`](./testservice/server).
## License
`grpc-proxy` is released under the Apache 2.0 license. See [LICENSE.txt](LICENSE.txt).

69
proxy/codec.go Normal file
View File

@ -0,0 +1,69 @@
package proxy
import (
"fmt"
"google.golang.org/grpc"
"google.golang.org/protobuf/proto"
)
// Codec returns a proxying grpc.Codec with the default protobuf codec as parent.
//
// See CodecWithParent.
//
// Deprecated: No longer necessary.
func Codec() grpc.Codec {
return CodecWithParent(&protoCodec{})
}
// CodecWithParent returns a proxying grpc.Codec with a user provided codec as parent.
//
// Deprecated: No longer necessary.
func CodecWithParent(fallback grpc.Codec) grpc.Codec {
return &rawCodec{fallback}
}
type rawCodec struct {
parentCodec grpc.Codec
}
type frame struct {
payload []byte
}
func (c *rawCodec) Marshal(v interface{}) ([]byte, error) {
out, ok := v.(*frame)
if !ok {
return c.parentCodec.Marshal(v)
}
return out.payload, nil
}
func (c *rawCodec) Unmarshal(data []byte, v interface{}) error {
dst, ok := v.(*frame)
if !ok {
return c.parentCodec.Unmarshal(data, v)
}
dst.payload = data
return nil
}
func (c *rawCodec) String() string {
return fmt.Sprintf("proxy>%s", c.parentCodec.String())
}
// protoCodec is a Codec implementation with protobuf. It is the default rawCodec for gRPC.
type protoCodec struct{}
func (protoCodec) Marshal(v interface{}) ([]byte, error) {
return proto.Marshal(v.(proto.Message))
}
func (protoCodec) Unmarshal(data []byte, v interface{}) error {
return proto.Unmarshal(data, v.(proto.Message))
}
func (protoCodec) String() string {
return "proto"
}

23
proxy/codec_test.go Normal file
View File

@ -0,0 +1,23 @@
package proxy
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestCodec_ReadYourWrites(t *testing.T) {
framePtr := &frame{}
data := []byte{0xDE, 0xAD, 0xBE, 0xEF}
codec := rawCodec{}
require.NoError(t, codec.Unmarshal(data, framePtr), "unmarshalling must go ok")
out, err := codec.Marshal(framePtr)
require.NoError(t, err, "no marshal error")
require.Equal(t, data, out, "output and data must be the same")
// reuse
require.NoError(t, codec.Unmarshal([]byte{0x55}, framePtr), "unmarshalling must go ok")
out, err = codec.Marshal(framePtr)
require.NoError(t, err, "no marshal error")
require.Equal(t, []byte{0x55}, out, "output and data must be the same")
}

25
proxy/director.go Normal file
View File

@ -0,0 +1,25 @@
// Copyright 2017 Michal Witkowski. All Rights Reserved.
// See LICENSE for licensing terms.
package proxy
import (
"context"
"google.golang.org/grpc"
)
// StreamDirector returns a gRPC ClientConn to be used to forward the call to.
//
// The presence of the `Context` allows for rich filtering, e.g. based on Metadata (headers).
// If no handling is meant to be done, a `codes.NotImplemented` gRPC error should be returned.
//
// The context returned from this function should be the context for the *outgoing* (to backend) call. In case you want
// to forward any Metadata between the inbound request and outbound requests, you should do it manually. However, you
// *must* propagate the cancel function (`context.WithCancel`) of the inbound context to the one returned.
//
// It is worth noting that the StreamDirector will be fired *after* all server-side stream interceptors
// are invoked. So decisions around authorization, monitoring etc. are better to be handled there.
//
// See the rather rich example.
type StreamDirector func(ctx context.Context, fullMethodName string) (context.Context, grpc.ClientConnInterface, error)

15
proxy/doc.go Normal file
View File

@ -0,0 +1,15 @@
// Copyright 2017 Michal Witkowski. All Rights Reserved.
// See LICENSE for licensing terms.
/*
Package proxy provides a reverse proxy handler for gRPC.
The implementation allows a grpc.Server to pass a received ServerStream to a ClientStream without understanding
the semantics of the messages exchanged. It basically provides a transparent reverse-proxy.
This package is intentionally generic, exposing a StreamDirector function that allows users of this package
to implement whatever logic of backend-picking, dialing and service verification to perform.
See examples on documented functions.
*/
package proxy

71
proxy/examples_test.go Normal file
View File

@ -0,0 +1,71 @@
// Copyright 2017 Michal Witkowski. All Rights Reserved.
// See LICENSE for licensing terms.
package proxy_test
import (
"context"
"log"
"strings"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"go.linka.cloud/grpc-toolkit/proxy"
"go.linka.cloud/grpc-toolkit/service"
)
var (
director proxy.StreamDirector
)
func ExampleNew() {
dst, err := grpc.Dial("example.com")
if err != nil {
log.Fatalf("dialing example.org: %v", err)
}
proxy, _ := proxy.New(dst)
_ = proxy
}
func ExampleRegisterService() {
// A gRPC service with the proxying codec enabled.
svc, _ := service.New()
// Register a TestService with 4 of its methods explicitly.
proxy.RegisterService(svc, director,
"mwitkow.testproto.TestService",
"PingEmpty", "Ping", "PingError", "PingList")
}
func ExampleTransparentHandler() {
grpc.NewServer(
grpc.UnknownServiceHandler(proxy.TransparentHandler(director)))
}
// Provides a simple example of a director that shields internal services and dials a staging or production backend.
// This is a *very naive* implementation that creates a new connection on every request. Consider using pooling.
func ExampleStreamDirector() {
director = func(ctx context.Context, fullMethodName string) (context.Context, grpc.ClientConnInterface, error) {
// Make sure we never forward internal services.
if strings.HasPrefix(fullMethodName, "/com.example.internal.") {
return nil, nil, status.Errorf(codes.Unimplemented, "Unknown method")
}
md, ok := metadata.FromIncomingContext(ctx)
// Copy the inbound metadata explicitly.
outCtx := metadata.NewOutgoingContext(ctx, md.Copy())
if ok {
// Decide on which backend to dial
if val, exists := md[":authority"]; exists && val[0] == "staging.api.example.com" {
// Make sure we use DialContext so the dialing can be cancelled/time out together with the context.
conn, err := grpc.DialContext(ctx, "api-service.staging.svc.local")
return outCtx, conn, err
} else if val, exists := md[":authority"]; exists && val[0] == "api.example.com" {
conn, err := grpc.DialContext(ctx, "api-service.prod.svc.local")
return outCtx, conn, err
}
}
return nil, nil, status.Errorf(codes.Unimplemented, "Unknown method")
}
}

189
proxy/handler.go Normal file
View File

@ -0,0 +1,189 @@
// Copyright 2017 Michal Witkowski. All Rights Reserved.
// See LICENSE for licensing terms.
package proxy
import (
"context"
"io"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
)
var (
clientStreamDescForProxying = &grpc.StreamDesc{
ServerStreams: true,
ClientStreams: true,
}
)
// RegisterService sets up a proxy handler for a particular gRPC service and method.
// The behaviour is the same as if you were registering a handler method, e.g. from a generated pb.go file.
func RegisterService(server grpc.ServiceRegistrar, director StreamDirector, serviceName string, methodNames ...string) {
streamer := &handler{director}
fakeDesc := &grpc.ServiceDesc{
ServiceName: serviceName,
HandlerType: (*interface{})(nil),
}
for _, m := range methodNames {
streamDesc := grpc.StreamDesc{
StreamName: m,
Handler: streamer.handler,
ServerStreams: true,
ClientStreams: true,
}
fakeDesc.Streams = append(fakeDesc.Streams, streamDesc)
}
server.RegisterService(fakeDesc, streamer)
}
func RegisterServiceDescs(server grpc.ServiceRegistrar, director StreamDirector, descs ...grpc.ServiceDesc) {
streamer := &handler{director}
for _, desc := range descs {
fakeDesc := &grpc.ServiceDesc{
ServiceName: desc.ServiceName,
HandlerType: (*interface{})(nil),
}
for _, desc := range desc.Methods {
streamDesc := grpc.StreamDesc{
StreamName: desc.MethodName,
Handler: streamer.handler,
ServerStreams: true,
ClientStreams: true,
}
fakeDesc.Streams = append(fakeDesc.Streams, streamDesc)
}
for _, desc := range desc.Streams {
streamDesc := grpc.StreamDesc{
StreamName: desc.StreamName,
Handler: streamer.handler,
ServerStreams: true,
ClientStreams: true,
}
fakeDesc.Streams = append(fakeDesc.Streams, streamDesc)
}
server.RegisterService(fakeDesc, streamer)
}
}
// TransparentHandler returns a handler that attempts to proxy all requests that are not registered in the server.
// The indented use here is as a transparent proxy, where the server doesn't know about the services implemented by the
// backends. It should be used as a `grpc.UnknownServiceHandler`.
func TransparentHandler(director StreamDirector) grpc.StreamHandler {
streamer := &handler{director: director}
return streamer.handler
}
type handler struct {
director StreamDirector
}
// handler is where the real magic of proxying happens.
// It is invoked like any gRPC server stream and uses the emptypb.Empty type server
// to proxy calls between the input and output streams.
func (s *handler) handler(srv interface{}, serverStream grpc.ServerStream) error {
// little bit of gRPC internals never hurt anyone
fullMethodName, ok := grpc.MethodFromServerStream(serverStream)
if !ok {
return status.Errorf(codes.Internal, "lowLevelServerStream not exists in context")
}
// We require that the director's returned context inherits from the serverStream.Context().
outgoingCtx, backendConn, err := s.director(serverStream.Context(), fullMethodName)
if err != nil {
return err
}
clientCtx, clientCancel := context.WithCancel(outgoingCtx)
defer clientCancel()
// TODO(mwitkow): Add a `forwarded` header to metadata, https://en.wikipedia.org/wiki/X-Forwarded-For.
clientStream, err := backendConn.NewStream(clientCtx, clientStreamDescForProxying, fullMethodName)
if err != nil {
return err
}
// Explicitly *do not close* s2cErrChan and c2sErrChan, otherwise the select below will not terminate.
// Channels do not have to be closed, it is just a control flow mechanism, see
// https://groups.google.com/forum/#!msg/golang-nuts/pZwdYRGxCIk/qpbHxRRPJdUJ
s2cErrChan := s.forwardServerToClient(serverStream, clientStream)
c2sErrChan := s.forwardClientToServer(clientStream, serverStream)
// We don't know which side is going to stop sending first, so we need a select between the two.
for i := 0; i < 2; i++ {
select {
case s2cErr := <-s2cErrChan:
if s2cErr == io.EOF {
// this is the happy case where the sender has encountered io.EOF, and won't be sending anymore./
// the clientStream>serverStream may continue pumping though.
clientStream.CloseSend()
} else {
// however, we may have gotten a receive error (stream disconnected, a read error etc) in which case we need
// to cancel the clientStream to the backend, let all of its goroutines be freed up by the CancelFunc and
// exit with an error to the stack
clientCancel()
return status.Errorf(codes.Internal, "failed proxying s2c: %v", s2cErr)
}
case c2sErr := <-c2sErrChan:
// This happens when the clientStream has nothing else to offer (io.EOF), returned a gRPC error. In those two
// cases we may have received Trailers as part of the call. In case of other errors (stream closed) the trailers
// will be nil.
serverStream.SetTrailer(clientStream.Trailer())
// c2sErr will contain RPC error from client code. If not io.EOF return the RPC error as server stream error.
if c2sErr != io.EOF {
return c2sErr
}
return nil
}
}
return status.Errorf(codes.Internal, "gRPC proxying should never reach this stage.")
}
func (s *handler) forwardClientToServer(src grpc.ClientStream, dst grpc.ServerStream) chan error {
ret := make(chan error, 1)
go func() {
f := &emptypb.Empty{}
for i := 0; ; i++ {
if err := src.RecvMsg(f); err != nil {
ret <- err // this can be io.EOF which is happy case
break
}
if i == 0 {
// This is a bit of a hack, but client to server headers are only readable after first client msg is
// received but must be written to server stream before the first msg is flushed.
// This is the only place to do it nicely.
md, err := src.Header()
if err != nil {
ret <- err
break
}
if err := dst.SendHeader(md); err != nil {
ret <- err
break
}
}
if err := dst.SendMsg(f); err != nil {
ret <- err
break
}
}
}()
return ret
}
func (s *handler) forwardServerToClient(src grpc.ServerStream, dst grpc.ClientStream) chan error {
ret := make(chan error, 1)
go func() {
f := &emptypb.Empty{}
for i := 0; ; i++ {
if err := src.RecvMsg(f); err != nil {
ret <- err // this can be io.EOF which is happy case
break
}
if err := dst.SendMsg(f); err != nil {
ret <- err
break
}
}
}()
return ret
}

260
proxy/handler_test.go Normal file
View File

@ -0,0 +1,260 @@
// Copyright 2017 Michal Witkowski. All Rights Reserved.
// See LICENSE for licensing terms.
package proxy_test
import (
"context"
"fmt"
"io"
"net"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/emptypb"
"go.linka.cloud/grpc-toolkit/proxy"
pb "go.linka.cloud/grpc-toolkit/proxy/testservice"
)
const (
pingDefaultValue = "I like kittens."
clientMdKey = "test-client-header"
serverHeaderMdKey = "test-client-header"
serverTrailerMdKey = "test-client-trailer"
rejectingMdKey = "test-reject-rpc-if-in-context"
countListResponses = 20
)
// asserting service is implemented on the server side and serves as a handler for stuff
type assertingService struct {
t *testing.T
pb.UnsafeTestServiceServer
}
var _ pb.TestServiceServer = (*assertingService)(nil)
func (s *assertingService) PingEmpty(ctx context.Context, _ *emptypb.Empty) (*pb.PingResponse, error) {
// Check that this call has client's metadata.
md, ok := metadata.FromIncomingContext(ctx)
assert.True(s.t, ok, "PingEmpty call must have metadata in context")
_, ok = md[clientMdKey]
assert.True(s.t, ok, "PingEmpty call must have clients's custom headers in metadata")
return &pb.PingResponse{Value: pingDefaultValue, Counter: 42}, nil
}
func (s *assertingService) Ping(ctx context.Context, ping *pb.PingRequest) (*pb.PingResponse, error) {
// Send user trailers and headers.
grpc.SendHeader(ctx, metadata.Pairs(serverHeaderMdKey, "I like turtles."))
grpc.SetTrailer(ctx, metadata.Pairs(serverTrailerMdKey, "I like ending turtles."))
return &pb.PingResponse{Value: ping.Value, Counter: 42}, nil
}
func (s *assertingService) PingError(ctx context.Context, ping *pb.PingRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.FailedPrecondition, "Userspace error.")
}
func (s *assertingService) PingList(ping *pb.PingRequest, stream pb.TestService_PingListServer) error {
// Send user trailers and headers.
stream.SendHeader(metadata.Pairs(serverHeaderMdKey, "I like turtles."))
for i := 0; i < countListResponses; i++ {
stream.Send(&pb.PingResponse{Value: ping.Value, Counter: int32(i)})
}
stream.SetTrailer(metadata.Pairs(serverTrailerMdKey, "I like ending turtles."))
return nil
}
func (s *assertingService) PingStream(stream pb.TestService_PingStreamServer) error {
stream.SendHeader(metadata.Pairs(serverHeaderMdKey, "I like turtles."))
counter := int32(0)
for {
ping, err := stream.Recv()
if err == io.EOF {
break
} else if err != nil {
require.NoError(s.t, err, "can't fail reading stream")
return err
}
pong := &pb.PingResponse{Value: ping.Value, Counter: counter}
if err := stream.Send(pong); err != nil {
require.NoError(s.t, err, "can't fail sending back a pong")
}
counter += 1
}
stream.SetTrailer(metadata.Pairs(serverTrailerMdKey, "I like ending turtles."))
return nil
}
// ProxyHappySuite tests the "happy" path of handling: that everything works in absence of connection issues.
type ProxyHappySuite struct {
suite.Suite
serverListener net.Listener
server *grpc.Server
proxyListener net.Listener
proxy *grpc.Server
serverClientConn *grpc.ClientConn
client *grpc.ClientConn
testClient pb.TestServiceClient
}
func (s *ProxyHappySuite) TestPingEmptyCarriesClientMetadata() {
ctx := metadata.NewOutgoingContext(context.Background(), metadata.Pairs(clientMdKey, "true"))
out, err := s.testClient.PingEmpty(ctx, &emptypb.Empty{})
require.NoError(s.T(), err, "PingEmpty should succeed without errors")
want := &pb.PingResponse{Value: pingDefaultValue, Counter: 42}
require.True(s.T(), proto.Equal(want, out))
}
func (s *ProxyHappySuite) TestPingEmpty_StressTest() {
for i := 0; i < 50; i++ {
s.TestPingEmptyCarriesClientMetadata()
}
}
func (s *ProxyHappySuite) TestPingCarriesServerHeadersAndTrailers() {
headerMd := make(metadata.MD)
trailerMd := make(metadata.MD)
// This is an awkward calling convention... but meh.
out, err := s.testClient.Ping(context.Background(), &pb.PingRequest{Value: "foo"}, grpc.Header(&headerMd), grpc.Trailer(&trailerMd))
want := &pb.PingResponse{Value: "foo", Counter: 42}
require.NoError(s.T(), err, "Ping should succeed without errors")
require.True(s.T(), proto.Equal(want, out))
assert.Contains(s.T(), headerMd, serverHeaderMdKey, "server response headers must contain server data")
assert.Len(s.T(), trailerMd, 1, "server response trailers must contain server data")
}
func (s *ProxyHappySuite) TestPingErrorPropagatesAppError() {
_, err := s.testClient.PingError(context.Background(), &pb.PingRequest{Value: "foo"})
require.Error(s.T(), err, "PingError should never succeed")
assert.Equal(s.T(), codes.FailedPrecondition, status.Code(err))
assert.Equal(s.T(), "Userspace error.", status.Convert(err).Message())
}
func (s *ProxyHappySuite) TestDirectorErrorIsPropagated() {
// See SetupSuite where the StreamDirector has a special case.
ctx := metadata.NewOutgoingContext(context.Background(), metadata.Pairs(rejectingMdKey, "true"))
_, err := s.testClient.Ping(ctx, &pb.PingRequest{Value: "foo"})
require.Error(s.T(), err, "Director should reject this RPC")
assert.Equal(s.T(), codes.PermissionDenied, status.Code(err))
assert.Equal(s.T(), "testing rejection", status.Convert(err).Message())
}
func (s *ProxyHappySuite) TestPingStream_FullDuplexWorks() {
stream, err := s.testClient.PingStream(context.Background())
require.NoError(s.T(), err, "PingStream request should be successful.")
for i := 0; i < countListResponses; i++ {
ping := &pb.PingRequest{Value: fmt.Sprintf("foo:%d", i)}
require.NoError(s.T(), stream.Send(ping), "sending to PingStream must not fail")
resp, err := stream.Recv()
if err == io.EOF {
break
}
if i == 0 {
// Check that the header arrives before all entries.
headerMd, err := stream.Header()
require.NoError(s.T(), err, "PingStream headers should not error.")
assert.Contains(s.T(), headerMd, serverHeaderMdKey, "PingStream response headers user contain metadata")
}
assert.EqualValues(s.T(), i, resp.Counter, "ping roundtrip must succeed with the correct id")
}
require.NoError(s.T(), stream.CloseSend(), "no error on close send")
_, err = stream.Recv()
require.Equal(s.T(), io.EOF, err, "stream should close with io.EOF, meaining OK")
// Check that the trailer headers are here.
trailerMd := stream.Trailer()
assert.Len(s.T(), trailerMd, 1, "PingList trailer headers user contain metadata")
}
func (s *ProxyHappySuite) TestPingStream_StressTest() {
for i := 0; i < 50; i++ {
s.TestPingStream_FullDuplexWorks()
}
}
func (s *ProxyHappySuite) SetupSuite() {
var err error
s.proxyListener, err = net.Listen("tcp", "127.0.0.1:0")
require.NoError(s.T(), err, "must be able to allocate a port for proxyListener")
s.serverListener, err = net.Listen("tcp", "127.0.0.1:0")
require.NoError(s.T(), err, "must be able to allocate a port for serverListener")
s.server = grpc.NewServer()
pb.RegisterTestServiceServer(s.server, &assertingService{t: s.T()})
// Setup of the proxy's Director.
//lint:ignore SA1019 regression test
s.serverClientConn, err = grpc.Dial(s.serverListener.Addr().String(), grpc.WithInsecure(), grpc.WithCodec(proxy.Codec()))
require.NoError(s.T(), err, "must not error on deferred client Dial")
director := func(ctx context.Context, fullName string) (context.Context, grpc.ClientConnInterface, error) {
md, ok := metadata.FromIncomingContext(ctx)
if ok {
if _, exists := md[rejectingMdKey]; exists {
return ctx, nil, status.Errorf(codes.PermissionDenied, "testing rejection")
}
}
// Explicitly copy the metadata, otherwise the tests will fail.
outCtx := metadata.NewOutgoingContext(ctx, md.Copy())
return outCtx, s.serverClientConn, nil
}
s.proxy = grpc.NewServer(
//lint:ignore SA1019 regression test
grpc.CustomCodec(proxy.Codec()),
grpc.UnknownServiceHandler(proxy.TransparentHandler(director)),
)
proxy.RegisterServiceDescs(s.proxy, director, pb.TestService_ServiceDesc)
// Start the serving loops.
s.T().Logf("starting grpc.Server at: %v", s.serverListener.Addr().String())
go func() {
s.server.Serve(s.serverListener)
}()
s.T().Logf("starting grpc.Proxy at: %v", s.proxyListener.Addr().String())
go func() {
s.proxy.Serve(s.proxyListener)
}()
dCtx, ccl := context.WithTimeout(context.Background(), time.Second)
defer ccl()
clientConn, err := grpc.DialContext(dCtx, strings.Replace(s.proxyListener.Addr().String(), "127.0.0.1", "localhost", 1), grpc.WithInsecure())
require.NoError(s.T(), err, "must not error on deferred client Dial")
s.testClient = pb.NewTestServiceClient(clientConn)
}
func (s *ProxyHappySuite) TearDownSuite() {
if s.client != nil {
s.client.Close()
}
if s.serverClientConn != nil {
s.serverClientConn.Close()
}
// Close all transports so the logs don't get spammy.
time.Sleep(10 * time.Millisecond)
if s.proxy != nil {
s.proxy.Stop()
s.proxyListener.Close()
}
if s.serverListener != nil {
s.server.Stop()
s.serverListener.Close()
}
}
func TestProxyHappySuite(t *testing.T) {
suite.Run(t, &ProxyHappySuite{})
}

39
proxy/proxy.go Normal file
View File

@ -0,0 +1,39 @@
// Copyright 2021 Michal Witkowski. All Rights Reserved.
// See LICENSE for licensing terms.
package proxy
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"go.linka.cloud/grpc-toolkit/service"
)
// New sets up a simple proxy that forwards all requests to dst.
func New(dst grpc.ClientConnInterface, opts ...service.Option) (service.Service, error) {
opts = append(opts, WithDefault(dst))
// Set up the proxy server and then serve from it like in step one.
return service.New(opts...)
}
// WithDefault returns a grpc.UnknownServiceHandler with a DefaultDirector.
func WithDefault(cc grpc.ClientConnInterface) service.Option {
return service.WithGRPCServerOpts(grpc.UnknownServiceHandler(TransparentHandler(DefaultDirector(cc))))
}
// DefaultDirector returns a very simple forwarding StreamDirector that forwards all
// calls.
func DefaultDirector(cc grpc.ClientConnInterface) StreamDirector {
return func(ctx context.Context, fullMethodName string) (context.Context, grpc.ClientConnInterface, error) {
md, _ := metadata.FromIncomingContext(ctx)
ctx = metadata.NewOutgoingContext(ctx, md.Copy())
return ctx, cc, nil
}
}
func With(director StreamDirector) service.Option {
return service.WithGRPCServerOpts(grpc.UnknownServiceHandler(TransparentHandler(director)))
}

217
proxy/proxy_test.go Normal file
View File

@ -0,0 +1,217 @@
package proxy_test
import (
"context"
"flag"
"fmt"
"net"
"testing"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/test/bufconn"
"go.linka.cloud/grpc-toolkit/proxy"
"go.linka.cloud/grpc-toolkit/proxy/testservice"
"go.linka.cloud/grpc-toolkit/service"
)
var testBackend = flag.String("test-backend", "", "Service providing TestServiceServer")
// TestIntegrationV1 is a regression test of the proxy.
func TestLegacyBehaviour(t *testing.T) {
// These bufconns are test listeners used to make connections between our
// services. This test actually starts two fully functional grpc services.
proxyBc := bufconn.Listen(10)
// Setup is a little thorough, but here's the gist of it:
// 1. Create the test backend using testservice.DefaultTestServiceServer
// 2. Create the proxy backend using this package
// 3. Make calls to 1 via 2.
// 1.
//lint:ignore SA1019 regression test
testCC, err := backendDialer(t, grpc.WithCodec(proxy.Codec()))
if err != nil {
t.Fatal(err)
}
// 2.
go func() {
// Second, we need to implement the SteamDirector.
directorFn := func(ctx context.Context, fullMethodName string) (context.Context, grpc.ClientConnInterface, error) {
md, _ := metadata.FromIncomingContext(ctx)
outCtx := metadata.NewOutgoingContext(ctx, md.Copy())
return outCtx, testCC, nil
}
// Set up the proxy server and then serve from it like in step one.
proxySrv, err := service.New(proxy.With(directorFn))
if err != nil {
panic(err)
}
// run the proxy backend
go func() {
t.Log("Running proxySrv")
if err := proxySrv.Serve(proxyBc); err != nil {
if err == grpc.ErrServerStopped {
return
}
t.Logf("running proxy server: %v", err)
}
}()
t.Cleanup(func() {
t.Log("Gracefully stopping proxySrv")
proxySrv.Stop()
})
}()
// 3.
// Connect to the proxy. We should not need to do anything special here -
// users do not need to know they're talking to a proxy.
proxyCC, err := grpc.Dial(
"bufnet",
grpc.WithInsecure(),
grpc.WithBlock(),
grpc.WithContextDialer(func(ctx context.Context, s string) (net.Conn, error) {
return proxyBc.Dial()
}),
)
if err != nil {
t.Fatalf("dialing proxy: %v", err)
}
proxyClient := testservice.NewTestServiceClient(proxyCC)
// 4. Run the tests!
testservice.TestTestServiceServerImpl(t, proxyClient)
}
func TestNewProxy(t *testing.T) {
proxyBc := bufconn.Listen(10)
// Setup is a little thorough, but here's the gist of it:
// 1. Create the test backend using testservice.DefaultTestServiceServer
// 2. Create the proxy backend using this package
// 3. Make calls to 1 via 2.
// 1.
// First, we need to create a client connection to this backend.
testCC, err := backendDialer(t)
if err != nil {
t.Fatal(err)
}
// 2.
go func() {
t.Helper()
// First, we need to create a client connection to this backend.
proxySrv, err := proxy.New(testCC)
if err != nil {
panic(err)
}
// run the proxy backend
go func() {
t.Log("Running proxySrv")
if err := proxySrv.Serve(proxyBc); err != nil {
if err == grpc.ErrServerStopped {
return
}
t.Logf("running proxy server: %v", err)
}
}()
t.Cleanup(func() {
t.Log("Gracefully stopping proxySrv")
proxySrv.Stop()
})
}()
// 3.
// Connect to the proxy. We should not need to do anything special here -
// users do not need to know they're talking to a proxy.
t.Logf("dialing %s", proxyBc.Addr())
proxyCC, err := grpc.Dial(
proxyBc.Addr().String(),
grpc.WithInsecure(),
grpc.WithBlock(),
grpc.WithContextDialer(func(ctx context.Context, s string) (net.Conn, error) {
return proxyBc.Dial()
}),
)
if err != nil {
t.Fatalf("dialing proxy: %v", err)
}
proxyClient := testservice.NewTestServiceClient(proxyCC)
// 4. Run the tests!
testservice.TestTestServiceServerImpl(t, proxyClient)
}
// backendDialer dials the testservice.TestServiceServer either by connecting
// to the user-supplied server, or by creating a mock server using bufconn.
func backendDialer(t *testing.T, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
t.Helper()
if *testBackend != "" {
return backendSvcDialer(t, *testBackend, opts...)
}
backendBc := bufconn.Listen(10)
// set up the backend using a "real" server over a bufconn
testSrv, err := service.New()
if err != nil {
t.Fatal(err)
}
testservice.RegisterTestServiceServer(testSrv, testservice.DefaultTestServiceServer)
// run the test backend
go func() {
t.Log("Running testSrv")
if err := testSrv.Serve(backendBc); err != nil {
if err == grpc.ErrServerStopped {
return
}
t.Logf("running test server: %v", err)
}
}()
t.Cleanup(func() {
t.Log("Gracefully stopping testSrv")
testSrv.Stop()
})
opts = append(opts,
grpc.WithInsecure(),
grpc.WithBlock(),
grpc.WithContextDialer(func(ctx context.Context, s string) (net.Conn, error) {
return backendBc.Dial()
}),
)
backendCC, err := grpc.Dial(
"bufnet",
opts...,
)
if err != nil {
return nil, fmt.Errorf("dialing backend: %v", err)
}
return backendCC, nil
}
func backendSvcDialer(t *testing.T, addr string, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
opts = append(opts,
grpc.WithInsecure(),
grpc.WithBlock(),
)
t.Logf("connecting to %s", addr)
cc, err := grpc.Dial(
addr,
opts...,
)
if err != nil {
return nil, fmt.Errorf("dialing backend: %v", err)
}
return cc, nil
}

View File

@ -0,0 +1,9 @@
all: test_go
test_go: test.proto
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
test.proto

168
proxy/testservice/ping.go Normal file
View File

@ -0,0 +1,168 @@
package testservice
import (
"context"
"fmt"
"io"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
)
var DefaultTestServiceServer = defaultPingServer{}
const (
PingHeader = "ping-header"
PingHeaderCts = "Arbitrary header text"
PingTrailer = "ping-trailer"
PingTrailerCts = "Arbitrary trailer text"
PingEchoHeader = "ping-echo-header"
PingEchoTrailer = "ping-echo-trailer"
)
// defaultPingServer is the canonical implementation of a TestServiceServer.
type defaultPingServer struct {
UnsafeTestServiceServer
}
func (s defaultPingServer) PingEmpty(ctx context.Context, empty *emptypb.Empty) (*PingResponse, error) {
if err := s.sendHeader(ctx); err != nil {
return nil, err
}
if err := s.setTrailer(ctx); err != nil {
return nil, err
}
return &PingResponse{}, nil
}
func (s defaultPingServer) Ping(ctx context.Context, request *PingRequest) (*PingResponse, error) {
if err := s.sendHeader(ctx); err != nil {
return nil, err
}
if err := s.setTrailer(ctx); err != nil {
return nil, err
}
return &PingResponse{Value: request.Value}, nil
}
func (s defaultPingServer) PingError(ctx context.Context, request *PingRequest) (*emptypb.Empty, error) {
if err := s.sendHeader(ctx); err != nil {
return nil, err
}
if err := s.setTrailer(ctx); err != nil {
return nil, err
}
return nil, status.Error(codes.Unknown, "Something is wrong and this is a message that describes it")
}
func (s defaultPingServer) PingList(request *PingRequest, server TestService_PingListServer) error {
if err := s.sendHeader(server.Context()); err != nil {
return err
}
s.setStreamTrailer(server)
for i := 0; i < 10; i++ {
if err := server.Send(&PingResponse{
Value: request.Value,
Counter: int32(i),
}); err != nil {
return err
}
}
return nil
}
func (s defaultPingServer) PingStream(server TestService_PingStreamServer) error {
g, ctx := errgroup.WithContext(context.Background())
if err := s.sendHeader(server.Context()); err != nil {
return err
}
pings := make(chan *PingRequest)
g.Go(func() error {
defer close(pings)
for {
m, err := server.Recv()
if err != nil {
if err == io.EOF {
return nil
}
return err
}
select {
case pings <- m:
case <-ctx.Done():
return ctx.Err()
}
}
})
g.Go(func() error {
var i int32
for m := range pings {
if err := server.Send(&PingResponse{
Value: m.Value,
Counter: i,
}); err != nil {
return err
}
i++
}
return nil
})
return g.Wait()
}
func (s *defaultPingServer) sendHeader(ctx context.Context) error {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
md = metadata.New(nil)
}
if tvs := md.Get(PingEchoHeader); len(tvs) > 0 {
md.Append(PingEchoHeader, tvs...)
}
md.Append(PingHeader, PingHeaderCts)
if err := grpc.SendHeader(ctx, md); err != nil {
return fmt.Errorf("setting header: %w", err)
}
return nil
}
func (s *defaultPingServer) setTrailer(ctx context.Context) error {
md := s.buildTrailer(ctx)
if err := grpc.SetTrailer(ctx, md); err != nil {
return fmt.Errorf("setting trailer: %w", err)
}
return nil
}
func (s *defaultPingServer) buildTrailer(ctx context.Context) metadata.MD {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
md = metadata.New(nil)
}
if tvs := md.Get(PingEchoTrailer); len(tvs) > 0 {
md.Append(PingEchoTrailer, tvs...)
}
md.Append(PingTrailer, PingTrailerCts)
return md
}
func (s defaultPingServer) setStreamTrailer(server grpc.ServerStream) {
server.SetTrailer(s.buildTrailer(server.Context()))
}
var _ TestServiceServer = (*defaultPingServer)(nil)

View File

@ -0,0 +1,54 @@
package main
import (
"flag"
"fmt"
"log"
"net"
"os"
"os/signal"
"syscall"
"google.golang.org/grpc"
"go.linka.cloud/grpc-toolkit/proxy/testservice"
)
var (
port = flag.Uint("port", 8080, "Port to listen to")
)
func main() {
srv := grpc.NewServer()
lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
if err != nil {
panic(err)
}
testservice.RegisterTestServiceServer(srv, testservice.DefaultTestServiceServer)
errs := make(chan error)
go func() {
log.Printf("listening on %s", lis.Addr().String())
errs <- srv.Serve(lis)
}()
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
sig := <-sigs
log.Printf("shutdown due to %s", sig)
srv.GracefulStop()
}()
if err := <-errs; err != nil {
log.Println(err)
os.Exit(1)
}
os.Exit(0)
}
func init() {
flag.Parse()
}

View File

@ -0,0 +1,21 @@
// Code generated by protoc-gen-defaults. DO NOT EDIT.
package testservice
import (
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb"
)
var (
_ *timestamppb.Timestamp
_ *durationpb.Duration
_ *wrapperspb.BoolValue
)
func (x *PingRequest) Default() {
}
func (x *PingResponse) Default() {
}

View File

@ -0,0 +1,31 @@
// Code generated by protoc-gen-defaults. DO NOT EDIT.
package testservice
var TestServiceMethods = struct {
PingEmpty string
Ping string
PingError string
PingList string
PingStream string
}{
PingEmpty: "/mwitkow.testproto.TestService/PingEmpty",
Ping: "/mwitkow.testproto.TestService/Ping",
PingError: "/mwitkow.testproto.TestService/PingError",
PingList: "/mwitkow.testproto.TestService/PingList",
PingStream: "/mwitkow.testproto.TestService/PingStream",
}
var PingRequestFields = struct {
Value string
}{
Value: "value",
}
var PingResponseFields = struct {
Value string
Counter string
}{
Value: "value",
Counter: "counter",
}

View File

@ -0,0 +1,227 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.35.1
// protoc v5.28.2
// source: proxy/testservice/test.proto
package testservice
import (
reflect "reflect"
sync "sync"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
emptypb "google.golang.org/protobuf/types/known/emptypb"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type PingRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
}
func (x *PingRequest) Reset() {
*x = PingRequest{}
mi := &file_proxy_testservice_test_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *PingRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PingRequest) ProtoMessage() {}
func (x *PingRequest) ProtoReflect() protoreflect.Message {
mi := &file_proxy_testservice_test_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PingRequest.ProtoReflect.Descriptor instead.
func (*PingRequest) Descriptor() ([]byte, []int) {
return file_proxy_testservice_test_proto_rawDescGZIP(), []int{0}
}
func (x *PingRequest) GetValue() string {
if x != nil {
return x.Value
}
return ""
}
type PingResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
Counter int32 `protobuf:"varint,2,opt,name=counter,proto3" json:"counter,omitempty"`
}
func (x *PingResponse) Reset() {
*x = PingResponse{}
mi := &file_proxy_testservice_test_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *PingResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PingResponse) ProtoMessage() {}
func (x *PingResponse) ProtoReflect() protoreflect.Message {
mi := &file_proxy_testservice_test_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PingResponse.ProtoReflect.Descriptor instead.
func (*PingResponse) Descriptor() ([]byte, []int) {
return file_proxy_testservice_test_proto_rawDescGZIP(), []int{1}
}
func (x *PingResponse) GetValue() string {
if x != nil {
return x.Value
}
return ""
}
func (x *PingResponse) GetCounter() int32 {
if x != nil {
return x.Counter
}
return 0
}
var File_proxy_testservice_test_proto protoreflect.FileDescriptor
var file_proxy_testservice_test_proto_rawDesc = []byte{
0x0a, 0x1c, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x73, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11,
0x6d, 0x77, 0x69, 0x74, 0x6b, 0x6f, 0x77, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x23,
0x0a, 0x0b, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a,
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x22, 0x3e, 0x0a, 0x0c, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e,
0x74, 0x65, 0x72, 0x32, 0x8d, 0x03, 0x0a, 0x0b, 0x54, 0x65, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x12, 0x46, 0x0a, 0x09, 0x50, 0x69, 0x6e, 0x67, 0x45, 0x6d, 0x70, 0x74, 0x79,
0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1f, 0x2e, 0x6d, 0x77, 0x69, 0x74, 0x6b,
0x6f, 0x77, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x69, 0x6e,
0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x49, 0x0a, 0x04, 0x50,
0x69, 0x6e, 0x67, 0x12, 0x1e, 0x2e, 0x6d, 0x77, 0x69, 0x74, 0x6b, 0x6f, 0x77, 0x2e, 0x74, 0x65,
0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6d, 0x77, 0x69, 0x74, 0x6b, 0x6f, 0x77, 0x2e, 0x74, 0x65,
0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x45, 0x0a, 0x09, 0x50, 0x69, 0x6e, 0x67, 0x45, 0x72,
0x72, 0x6f, 0x72, 0x12, 0x1e, 0x2e, 0x6d, 0x77, 0x69, 0x74, 0x6b, 0x6f, 0x77, 0x2e, 0x74, 0x65,
0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x4f, 0x0a,
0x08, 0x50, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1e, 0x2e, 0x6d, 0x77, 0x69, 0x74,
0x6b, 0x6f, 0x77, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x69,
0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6d, 0x77, 0x69, 0x74,
0x6b, 0x6f, 0x77, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x69,
0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x53,
0x0a, 0x0a, 0x50, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x1e, 0x2e, 0x6d,
0x77, 0x69, 0x74, 0x6b, 0x6f, 0x77, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6d,
0x77, 0x69, 0x74, 0x6b, 0x6f, 0x77, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28,
0x01, 0x30, 0x01, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x6f, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x61, 0x2e,
0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2d, 0x74, 0x6f, 0x6f, 0x6c, 0x6b,
0x69, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x73, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_proxy_testservice_test_proto_rawDescOnce sync.Once
file_proxy_testservice_test_proto_rawDescData = file_proxy_testservice_test_proto_rawDesc
)
func file_proxy_testservice_test_proto_rawDescGZIP() []byte {
file_proxy_testservice_test_proto_rawDescOnce.Do(func() {
file_proxy_testservice_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_testservice_test_proto_rawDescData)
})
return file_proxy_testservice_test_proto_rawDescData
}
var file_proxy_testservice_test_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_proxy_testservice_test_proto_goTypes = []any{
(*PingRequest)(nil), // 0: mwitkow.testproto.PingRequest
(*PingResponse)(nil), // 1: mwitkow.testproto.PingResponse
(*emptypb.Empty)(nil), // 2: google.protobuf.Empty
}
var file_proxy_testservice_test_proto_depIdxs = []int32{
2, // 0: mwitkow.testproto.TestService.PingEmpty:input_type -> google.protobuf.Empty
0, // 1: mwitkow.testproto.TestService.Ping:input_type -> mwitkow.testproto.PingRequest
0, // 2: mwitkow.testproto.TestService.PingError:input_type -> mwitkow.testproto.PingRequest
0, // 3: mwitkow.testproto.TestService.PingList:input_type -> mwitkow.testproto.PingRequest
0, // 4: mwitkow.testproto.TestService.PingStream:input_type -> mwitkow.testproto.PingRequest
1, // 5: mwitkow.testproto.TestService.PingEmpty:output_type -> mwitkow.testproto.PingResponse
1, // 6: mwitkow.testproto.TestService.Ping:output_type -> mwitkow.testproto.PingResponse
2, // 7: mwitkow.testproto.TestService.PingError:output_type -> google.protobuf.Empty
1, // 8: mwitkow.testproto.TestService.PingList:output_type -> mwitkow.testproto.PingResponse
1, // 9: mwitkow.testproto.TestService.PingStream:output_type -> mwitkow.testproto.PingResponse
5, // [5:10] is the sub-list for method output_type
0, // [0:5] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_proxy_testservice_test_proto_init() }
func file_proxy_testservice_test_proto_init() {
if File_proxy_testservice_test_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_proxy_testservice_test_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_proxy_testservice_test_proto_goTypes,
DependencyIndexes: file_proxy_testservice_test_proto_depIdxs,
MessageInfos: file_proxy_testservice_test_proto_msgTypes,
}.Build()
File_proxy_testservice_test_proto = out.File
file_proxy_testservice_test_proto_rawDesc = nil
file_proxy_testservice_test_proto_goTypes = nil
file_proxy_testservice_test_proto_depIdxs = nil
}

View File

@ -0,0 +1,240 @@
// Code generated by protoc-gen-validate. DO NOT EDIT.
// source: proxy/testservice/test.proto
package testservice
import (
"bytes"
"errors"
"fmt"
"net"
"net/mail"
"net/url"
"regexp"
"sort"
"strings"
"time"
"unicode/utf8"
"google.golang.org/protobuf/types/known/anypb"
)
// ensure the imports are used
var (
_ = bytes.MinRead
_ = errors.New("")
_ = fmt.Print
_ = utf8.UTFMax
_ = (*regexp.Regexp)(nil)
_ = (*strings.Reader)(nil)
_ = net.IPv4len
_ = time.Duration(0)
_ = (*url.URL)(nil)
_ = (*mail.Address)(nil)
_ = anypb.Any{}
_ = sort.Sort
)
// Validate checks the field values on PingRequest with the rules defined in
// the proto definition for this message. If any rules are violated, the first
// error encountered is returned, or nil if there are no violations.
func (m *PingRequest) Validate() error {
return m.validate(false)
}
// ValidateAll checks the field values on PingRequest with the rules defined in
// the proto definition for this message. If any rules are violated, the
// result is a list of violation errors wrapped in PingRequestMultiError, or
// nil if none found.
func (m *PingRequest) ValidateAll() error {
return m.validate(true)
}
func (m *PingRequest) validate(all bool) error {
if m == nil {
return nil
}
var errors []error
// no validation rules for Value
if len(errors) > 0 {
return PingRequestMultiError(errors)
}
return nil
}
// PingRequestMultiError is an error wrapping multiple validation errors
// returned by PingRequest.ValidateAll() if the designated constraints aren't met.
type PingRequestMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m PingRequestMultiError) Error() string {
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
return strings.Join(msgs, "; ")
}
// AllErrors returns a list of validation violation errors.
func (m PingRequestMultiError) AllErrors() []error { return m }
// PingRequestValidationError is the validation error returned by
// PingRequest.Validate if the designated constraints aren't met.
type PingRequestValidationError struct {
field string
reason string
cause error
key bool
}
// Field function returns field value.
func (e PingRequestValidationError) Field() string { return e.field }
// Reason function returns reason value.
func (e PingRequestValidationError) Reason() string { return e.reason }
// Cause function returns cause value.
func (e PingRequestValidationError) Cause() error { return e.cause }
// Key function returns key value.
func (e PingRequestValidationError) Key() bool { return e.key }
// ErrorName returns error name.
func (e PingRequestValidationError) ErrorName() string { return "PingRequestValidationError" }
// Error satisfies the builtin error interface
func (e PingRequestValidationError) Error() string {
cause := ""
if e.cause != nil {
cause = fmt.Sprintf(" | caused by: %v", e.cause)
}
key := ""
if e.key {
key = "key for "
}
return fmt.Sprintf(
"invalid %sPingRequest.%s: %s%s",
key,
e.field,
e.reason,
cause)
}
var _ error = PingRequestValidationError{}
var _ interface {
Field() string
Reason() string
Key() bool
Cause() error
ErrorName() string
} = PingRequestValidationError{}
// Validate checks the field values on PingResponse with the rules defined in
// the proto definition for this message. If any rules are violated, the first
// error encountered is returned, or nil if there are no violations.
func (m *PingResponse) Validate() error {
return m.validate(false)
}
// ValidateAll checks the field values on PingResponse with the rules defined
// in the proto definition for this message. If any rules are violated, the
// result is a list of violation errors wrapped in PingResponseMultiError, or
// nil if none found.
func (m *PingResponse) ValidateAll() error {
return m.validate(true)
}
func (m *PingResponse) validate(all bool) error {
if m == nil {
return nil
}
var errors []error
// no validation rules for Value
// no validation rules for Counter
if len(errors) > 0 {
return PingResponseMultiError(errors)
}
return nil
}
// PingResponseMultiError is an error wrapping multiple validation errors
// returned by PingResponse.ValidateAll() if the designated constraints aren't met.
type PingResponseMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m PingResponseMultiError) Error() string {
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
return strings.Join(msgs, "; ")
}
// AllErrors returns a list of validation violation errors.
func (m PingResponseMultiError) AllErrors() []error { return m }
// PingResponseValidationError is the validation error returned by
// PingResponse.Validate if the designated constraints aren't met.
type PingResponseValidationError struct {
field string
reason string
cause error
key bool
}
// Field function returns field value.
func (e PingResponseValidationError) Field() string { return e.field }
// Reason function returns reason value.
func (e PingResponseValidationError) Reason() string { return e.reason }
// Cause function returns cause value.
func (e PingResponseValidationError) Cause() error { return e.cause }
// Key function returns key value.
func (e PingResponseValidationError) Key() bool { return e.key }
// ErrorName returns error name.
func (e PingResponseValidationError) ErrorName() string { return "PingResponseValidationError" }
// Error satisfies the builtin error interface
func (e PingResponseValidationError) Error() string {
cause := ""
if e.cause != nil {
cause = fmt.Sprintf(" | caused by: %v", e.cause)
}
key := ""
if e.key {
key = "key for "
}
return fmt.Sprintf(
"invalid %sPingResponse.%s: %s%s",
key,
e.field,
e.reason,
cause)
}
var _ error = PingResponseValidationError{}
var _ interface {
Field() string
Reason() string
Key() bool
Cause() error
ErrorName() string
} = PingResponseValidationError{}

View File

@ -0,0 +1,30 @@
syntax = "proto3";
package mwitkow.testproto;
option go_package="go.linka.cloud/grpc-toolkit/proxy/testservice";
import "google/protobuf/empty.proto";
message PingRequest {
string value = 1;
}
message PingResponse {
string value = 1;
int32 counter = 2;
}
service TestService {
rpc PingEmpty(google.protobuf.Empty) returns (PingResponse) {}
rpc Ping(PingRequest) returns (PingResponse) {}
rpc PingError(PingRequest) returns (google.protobuf.Empty) {}
rpc PingList(PingRequest) returns (stream PingResponse) {}
rpc PingStream(stream PingRequest) returns (stream PingResponse) {}
}

View File

@ -0,0 +1,61 @@
{
"swagger": "2.0",
"info": {
"title": "proxy/testservice/test.proto",
"version": "version not set"
},
"tags": [
{
"name": "TestService"
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"paths": {},
"definitions": {
"protobufAny": {
"type": "object",
"properties": {
"@type": {
"type": "string"
}
},
"additionalProperties": {}
},
"rpcStatus": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
},
"details": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/protobufAny"
}
}
}
},
"testprotoPingResponse": {
"type": "object",
"properties": {
"value": {
"type": "string"
},
"counter": {
"type": "integer",
"format": "int32"
}
}
}
}
}

View File

@ -0,0 +1,272 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.28.2
// source: proxy/testservice/test.proto
package testservice
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
emptypb "google.golang.org/protobuf/types/known/emptypb"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
TestService_PingEmpty_FullMethodName = "/mwitkow.testproto.TestService/PingEmpty"
TestService_Ping_FullMethodName = "/mwitkow.testproto.TestService/Ping"
TestService_PingError_FullMethodName = "/mwitkow.testproto.TestService/PingError"
TestService_PingList_FullMethodName = "/mwitkow.testproto.TestService/PingList"
TestService_PingStream_FullMethodName = "/mwitkow.testproto.TestService/PingStream"
)
// TestServiceClient is the client API for TestService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type TestServiceClient interface {
PingEmpty(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*PingResponse, error)
Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error)
PingError(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
PingList(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[PingResponse], error)
PingStream(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[PingRequest, PingResponse], error)
}
type testServiceClient struct {
cc grpc.ClientConnInterface
}
func NewTestServiceClient(cc grpc.ClientConnInterface) TestServiceClient {
return &testServiceClient{cc}
}
func (c *testServiceClient) PingEmpty(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*PingResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(PingResponse)
err := c.cc.Invoke(ctx, TestService_PingEmpty_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *testServiceClient) Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(PingResponse)
err := c.cc.Invoke(ctx, TestService_Ping_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *testServiceClient) PingError(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, TestService_PingError_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *testServiceClient) PingList(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[PingResponse], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &TestService_ServiceDesc.Streams[0], TestService_PingList_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[PingRequest, PingResponse]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type TestService_PingListClient = grpc.ServerStreamingClient[PingResponse]
func (c *testServiceClient) PingStream(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[PingRequest, PingResponse], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &TestService_ServiceDesc.Streams[1], TestService_PingStream_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[PingRequest, PingResponse]{ClientStream: stream}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type TestService_PingStreamClient = grpc.BidiStreamingClient[PingRequest, PingResponse]
// TestServiceServer is the server API for TestService service.
// All implementations must embed UnimplementedTestServiceServer
// for forward compatibility.
type TestServiceServer interface {
PingEmpty(context.Context, *emptypb.Empty) (*PingResponse, error)
Ping(context.Context, *PingRequest) (*PingResponse, error)
PingError(context.Context, *PingRequest) (*emptypb.Empty, error)
PingList(*PingRequest, grpc.ServerStreamingServer[PingResponse]) error
PingStream(grpc.BidiStreamingServer[PingRequest, PingResponse]) error
mustEmbedUnimplementedTestServiceServer()
}
// UnimplementedTestServiceServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedTestServiceServer struct{}
func (UnimplementedTestServiceServer) PingEmpty(context.Context, *emptypb.Empty) (*PingResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method PingEmpty not implemented")
}
func (UnimplementedTestServiceServer) Ping(context.Context, *PingRequest) (*PingResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented")
}
func (UnimplementedTestServiceServer) PingError(context.Context, *PingRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method PingError not implemented")
}
func (UnimplementedTestServiceServer) PingList(*PingRequest, grpc.ServerStreamingServer[PingResponse]) error {
return status.Errorf(codes.Unimplemented, "method PingList not implemented")
}
func (UnimplementedTestServiceServer) PingStream(grpc.BidiStreamingServer[PingRequest, PingResponse]) error {
return status.Errorf(codes.Unimplemented, "method PingStream not implemented")
}
func (UnimplementedTestServiceServer) mustEmbedUnimplementedTestServiceServer() {}
func (UnimplementedTestServiceServer) testEmbeddedByValue() {}
// UnsafeTestServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to TestServiceServer will
// result in compilation errors.
type UnsafeTestServiceServer interface {
mustEmbedUnimplementedTestServiceServer()
}
func RegisterTestServiceServer(s grpc.ServiceRegistrar, srv TestServiceServer) {
// If the following call pancis, it indicates UnimplementedTestServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&TestService_ServiceDesc, srv)
}
func _TestService_PingEmpty_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(emptypb.Empty)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TestServiceServer).PingEmpty(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: TestService_PingEmpty_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TestServiceServer).PingEmpty(ctx, req.(*emptypb.Empty))
}
return interceptor(ctx, in, info, handler)
}
func _TestService_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PingRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TestServiceServer).Ping(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: TestService_Ping_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TestServiceServer).Ping(ctx, req.(*PingRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TestService_PingError_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PingRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TestServiceServer).PingError(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: TestService_PingError_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TestServiceServer).PingError(ctx, req.(*PingRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TestService_PingList_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(PingRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(TestServiceServer).PingList(m, &grpc.GenericServerStream[PingRequest, PingResponse]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type TestService_PingListServer = grpc.ServerStreamingServer[PingResponse]
func _TestService_PingStream_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(TestServiceServer).PingStream(&grpc.GenericServerStream[PingRequest, PingResponse]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type TestService_PingStreamServer = grpc.BidiStreamingServer[PingRequest, PingResponse]
// TestService_ServiceDesc is the grpc.ServiceDesc for TestService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var TestService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "mwitkow.testproto.TestService",
HandlerType: (*TestServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "PingEmpty",
Handler: _TestService_PingEmpty_Handler,
},
{
MethodName: "Ping",
Handler: _TestService_Ping_Handler,
},
{
MethodName: "PingError",
Handler: _TestService_PingError_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "PingList",
Handler: _TestService_PingList_Handler,
ServerStreams: true,
},
{
StreamName: "PingStream",
Handler: _TestService_PingStream_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "proxy/testservice/test.proto",
}

View File

@ -0,0 +1,322 @@
// Code generated by protoc-gen-go-vtproto. DO NOT EDIT.
// protoc-gen-go-vtproto version: v0.6.1-0.20240917153116-6f2963f01587
// source: proxy/testservice/test.proto
package testservice
import (
fmt "fmt"
io "io"
protohelpers "github.com/planetscale/vtprotobuf/protohelpers"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
func (m *PingRequest) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *PingRequest) MarshalToVT(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVT(dAtA[:size])
}
func (m *PingRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if len(m.Value) > 0 {
i -= len(m.Value)
copy(dAtA[i:], m.Value)
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Value)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *PingResponse) MarshalVT() (dAtA []byte, err error) {
if m == nil {
return nil, nil
}
size := m.SizeVT()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBufferVT(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *PingResponse) MarshalToVT(dAtA []byte) (int, error) {
size := m.SizeVT()
return m.MarshalToSizedBufferVT(dAtA[:size])
}
func (m *PingResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if m == nil {
return 0, nil
}
i := len(dAtA)
_ = i
var l int
_ = l
if m.unknownFields != nil {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if m.Counter != 0 {
i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Counter))
i--
dAtA[i] = 0x10
}
if len(m.Value) > 0 {
i -= len(m.Value)
copy(dAtA[i:], m.Value)
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Value)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *PingRequest) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Value)
if l > 0 {
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
n += len(m.unknownFields)
return n
}
func (m *PingResponse) SizeVT() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Value)
if l > 0 {
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
if m.Counter != 0 {
n += 1 + protohelpers.SizeOfVarint(uint64(m.Counter))
}
n += len(m.unknownFields)
return n
}
func (m *PingRequest) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: PingRequest: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: PingRequest: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return protohelpers.ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return protohelpers.ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Value = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := protohelpers.Skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return protohelpers.ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *PingResponse) UnmarshalVT(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: PingResponse: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: PingResponse: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return protohelpers.ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return protohelpers.ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Value = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Counter", wireType)
}
m.Counter = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Counter |= int32(b&0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := protohelpers.Skip(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return protohelpers.ErrInvalidLength
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}

View File

@ -0,0 +1,158 @@
package testservice
import (
"context"
"io"
"reflect"
"testing"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
const (
returnHeader = "test-client-header"
)
// TestTestServiceServerImpl can be called to test the underlying TestServiceServer.
func TestTestServiceServerImpl(t *testing.T, client TestServiceClient) {
t.Run("Unary ping", func(t *testing.T) {
want := "hello, world"
hdr := metadata.MD{}
res, err := client.Ping(context.TODO(), &PingRequest{Value: want}, grpc.Header(&hdr))
if err != nil {
t.Errorf("want no err; got %v", err)
return
}
checkHeaders(t, hdr)
t.Logf("got %v (%d)", res.Value, res.Counter)
if got := res.Value; got != want {
t.Errorf("res.Value = %q; want %q", got, want)
}
})
t.Run("Error ping", func(t *testing.T) {
_, err := client.PingError(context.TODO(), &PingRequest{})
if err == nil {
t.Errorf("want err; got %v", err)
}
})
t.Run("Server streaming ping", func(t *testing.T) {
want := "hello, world"
stream, err := client.PingList(context.TODO(), &PingRequest{Value: want})
if err != nil {
t.Errorf("want no err; got %v", err)
if err := stream.CloseSend(); err != nil {
t.Fatalf("closing send channel: %v", err)
}
return
}
hdr, err := stream.Header()
if err != nil {
t.Errorf("reading headers: %v", err)
}
checkHeaders(t, hdr)
for {
res, err := stream.Recv()
if err != nil {
if err == io.EOF {
checkTrailers(t, stream.Trailer())
return
}
t.Errorf("want no err; got %v", err)
return
}
t.Logf("got %v (%d)", res.Value, res.Counter)
if got := res.Value; got != want {
t.Errorf("res.Value = %q; want %q", got, want)
}
}
})
t.Run("Bidirectional pinging", func(t *testing.T) {
want := "hello, world"
stream, err := client.PingStream(context.TODO())
if err != nil {
t.Errorf("want no err; got %v", err)
if err := stream.CloseSend(); err != nil {
t.Fatalf("closing send channel: %v", err)
}
return
}
d := make(chan struct{})
go func() {
hdr, err := stream.Header()
if err != nil {
t.Errorf("reading headers: %v", err)
}
checkHeaders(t, hdr)
close(d)
}()
for i := 0; i < 25; i++ {
if err := stream.Send(&PingRequest{Value: want}); err != nil {
t.Errorf("want no err; got %v", err)
return
}
res, err := stream.Recv()
if err != nil {
t.Errorf("receiving full duplex stream: %w", err)
return
}
t.Logf("got %v (%d)", res.Value, res.Counter)
if got := res.Value; got != want {
t.Errorf("res.Value = %q; want %q", got, want)
}
if got, want := res.Counter, int32(i); got != want {
t.Errorf("res.Counter = %d; want %d", got, want)
}
}
if err := stream.CloseSend(); err != nil {
t.Errorf("closing full duplex stream: %v", err)
}
<-d
})
t.Run("Unary ping with headers", func(t *testing.T) {
want := "hello, world"
req := &PingRequest{Value: want}
ctx := metadata.AppendToOutgoingContext(context.Background(), returnHeader, "I like turtles.")
inHeader := make(metadata.MD)
res, err := client.Ping(ctx, req, grpc.Header(&inHeader))
if err != nil {
t.Errorf("want no err; got %v", err)
return
}
t.Logf("got %v (%d)", res.Value, res.Counter)
if !reflect.DeepEqual(inHeader.Get(returnHeader), []string{"I like turtles."}) {
t.Errorf("did not receive correct return headers")
}
})
}
func checkTrailers(t *testing.T, md metadata.MD) {
vs := md.Get(PingTrailer)
if want, got := 1, len(vs); want != got {
t.Errorf("trailer %q not present", PingTrailer)
return
}
if want, got := []string{PingTrailerCts}, vs; !reflect.DeepEqual(got, want) {
t.Errorf("trailer mismatch; want %q, got %q", want, got)
}
}
func checkHeaders(t *testing.T, md metadata.MD) {
vs := md.Get(PingHeader)
if want, got := 1, len(vs); want != got {
t.Errorf("header %q not present", PingHeader)
return
}
if want, got := []string{PingHeaderCts}, vs; !reflect.DeepEqual(got, want) {
t.Errorf("header mismatch; want %q, got %q", want, got)
}
}

62
react/ui.go Normal file
View File

@ -0,0 +1,62 @@
// Copyright 2022 Linka Cloud All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package react
import (
"io/fs"
"net/http"
"net/http/httputil"
"net/url"
"os"
"strings"
)
const (
EndpointEnv = "REACT_ENDPOINT"
)
func NewHandler(dir fs.FS, subpath string) (http.Handler, error) {
if e := os.Getenv(EndpointEnv); e != "" {
return newProxy(e)
}
return newStatic(dir, subpath)
}
func newStatic(dir fs.FS, subpath string) (http.Handler, error) {
s, err := fs.Sub(dir, subpath)
if err != nil {
return nil, err
}
fsrv := http.FileServer(http.FS(s))
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if _, err := fs.Stat(s, strings.TrimPrefix(r.URL.Path, "/")); err != nil {
r.URL.Path = "/"
}
fsrv.ServeHTTP(w, r)
}), nil
}
func newProxy(endpoint string) (http.Handler, error) {
u, err := url.Parse(endpoint)
if err != nil {
return nil, err
}
p := httputil.NewSingleHostReverseProxy(u)
return p, nil
}
func DevEnv() bool {
return os.Getenv(EndpointEnv) != ""
}

View File

@ -7,7 +7,7 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net"
"strconv"
"strings"
@ -17,8 +17,8 @@ import (
"github.com/google/uuid"
"google.golang.org/grpc/resolver"
"go.linka.cloud/grpc/registry"
resolver2 "go.linka.cloud/grpc/resolver"
"go.linka.cloud/grpc-toolkit/registry"
resolver2 "go.linka.cloud/grpc-toolkit/resolver"
)
var (
@ -122,7 +122,7 @@ func decode(record []string) (*mdnsTxt, error) {
return nil, err
}
rbuf, err := ioutil.ReadAll(zr)
rbuf, err := io.ReadAll(zr)
if err != nil {
return nil, err
}

View File

@ -5,7 +5,7 @@ import (
"github.com/stretchr/testify/assert"
"go.linka.cloud/grpc/registry"
"go.linka.cloud/grpc-toolkit/registry"
)
func TestRegistry(t *testing.T) {

View File

@ -194,7 +194,7 @@ func (s *Server) recv(c *net.UDPConn) {
continue
}
if err := s.parsePacket(buf[:n], from); err != nil {
logrus.Errorf("[ERR] mdns: Failed to handle query: %v", err)
logrus.Errorf("mdns: Failed to handle query: %v", err)
}
}
}
@ -203,7 +203,7 @@ func (s *Server) recv(c *net.UDPConn) {
func (s *Server) parsePacket(packet []byte, from net.Addr) error {
var msg dns.Msg
if err := msg.Unpack(packet); err != nil {
logrus.Errorf("[ERR] mdns: Failed to unpack packet: %v", err)
logrus.Errorf("mdns: Failed to unpack packet: %v", err)
return err
}
// TODO: This is a bit of a hack

View File

@ -5,8 +5,8 @@ import (
"google.golang.org/grpc/resolver"
"go.linka.cloud/grpc/registry"
resolver2 "go.linka.cloud/grpc/resolver"
"go.linka.cloud/grpc-toolkit/registry"
resolver2 "go.linka.cloud/grpc-toolkit/resolver"
)
func New() registry.Registry {

View File

@ -5,7 +5,7 @@ import (
"google.golang.org/grpc/resolver"
"go.linka.cloud/grpc/registry"
"go.linka.cloud/grpc-toolkit/registry"
)
func New(reg registry.Registry) resolver.Builder {
@ -35,11 +35,11 @@ type resolvr struct {
func (r *resolvr) run() {
if r.reg.String() == "noop" {
r.cc.UpdateState(resolver.State{Addresses: []resolver.Address{{Addr: r.target.Endpoint}}})
r.cc.UpdateState(resolver.State{Addresses: []resolver.Address{{Addr: r.target.Endpoint()}}})
return
}
var name, version string
parts := strings.Split(r.target.Endpoint, ":")
parts := strings.Split(r.target.Endpoint(), ":")
name = parts[0]
if len(parts) > 1 {
version = parts[1]
@ -57,7 +57,7 @@ func (r *resolvr) run() {
}
}
r.cc.UpdateState(resolver.State{Addresses: r.addrs})
w, err := r.reg.Watch(registry.WatchService(r.target.Endpoint))
w, err := r.reg.Watch(registry.WatchService(r.target.Endpoint()))
if err != nil {
return
}
@ -80,7 +80,7 @@ func (r *resolvr) run() {
func (r *resolvr) ResolveNow(options resolver.ResolveNowOptions) {
if r.reg.String() == "noop" {
r.cc.UpdateState(resolver.State{Addresses: []resolver.Address{{Addr: r.target.Endpoint}}})
r.cc.UpdateState(resolver.State{Addresses: []resolver.Address{{Addr: r.target.Endpoint()}}})
}
}

View File

@ -4,7 +4,7 @@ import (
"fmt"
"strings"
env "github.com/caitlinelfring/go-env-default"
"github.com/caitlinelfring/go-env-default"
"github.com/spf13/pflag"
)
@ -17,6 +17,10 @@ const (
caCert = "ca-cert"
serverCert = "server-cert"
serverKey = "server-key"
clientCACert = "client-ca-cert"
clientCert = "client-cert"
clientKey = "client-key"
)
var u = strings.ToUpper
@ -37,13 +41,19 @@ func NewFlagSet() (*pflag.FlagSet, Option) {
flags.StringVar(&optCACert, caCert, "", "Path to Root CA certificate"+flagEnv(caCert))
flags.StringVar(&optCert, serverCert, "", "Path to Server certificate"+flagEnv(serverCert))
flags.StringVar(&optKey, serverKey, "", "Path to Server key"+flagEnv(serverKey))
flags.StringVar(&optCACert, clientCACert, "", "Path to Root CA certificate"+flagEnv(clientCACert))
flags.StringVar(&optCert, clientCert, "", "Path to Client certificate"+flagEnv(clientCert))
flags.StringVar(&optKey, clientKey, "", "Path to Client key"+flagEnv(clientKey))
return flags, func(o *options) {
o.address = optAddress
o.secure = !optInsecure
o.reflection = optReflection
// o.caCert = optCACert
// o.cert = optCert
// o.key = optKey
o.caCert = optCACert
o.cert = optCert
o.key = optKey
o.clientCACert = optCACert
o.clientCert = optCert
o.clientKey = optKey
}
}

View File

@ -18,13 +18,13 @@ func (s *service) gateway(opts ...runtime.ServeMuxOption) error {
return nil
}
mux := runtime.NewServeMux(append(defaultGatewayOptions, opts...)...)
if err := s.opts.gateway(s.opts.ctx, mux, s.inproc); err != nil {
if err := s.opts.gateway(s.opts.ctx, mux, s.wrapCC()); err != nil {
return err
}
if s.opts.gatewayPrefix != "" {
s.opts.mux.Handle(s.opts.gatewayPrefix+"/", http.StripPrefix(s.opts.gatewayPrefix, wsproxy.WebsocketProxy(mux)))
s.lazyMux().Handle(s.opts.gatewayPrefix+"/", http.StripPrefix(s.opts.gatewayPrefix, wsproxy.WebsocketProxy(mux)))
} else {
s.opts.mux.Handle("/", wsproxy.WebsocketProxy(mux))
s.lazyMux().Handle("/", wsproxy.WebsocketProxy(mux))
}
return nil
}

72
service/interceptors.go Normal file
View File

@ -0,0 +1,72 @@
package service
import (
"context"
"fmt"
"github.com/fullstorydev/grpchan/inprocgrpc"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
"google.golang.org/grpc"
insecure2 "google.golang.org/grpc/credentials/insecure"
"go.linka.cloud/grpc-toolkit/interceptors"
"go.linka.cloud/grpc-toolkit/interceptors/metadata"
)
func md(opts *options) interceptors.Interceptors {
var pairs []string
if opts.name != "" {
pairs = append(pairs, "grpc-service-name", opts.name)
}
if opts.version != "" {
pairs = append(pairs, "grpc-service-version", opts.version)
}
if len(pairs) != 0 {
return metadata.NewInterceptors(pairs...)
}
return nil
}
func (s *service) wrapCC() grpc.ClientConnInterface {
c, err := grpc.NewClient("internal", grpc.WithTransportCredentials(insecure2.NewCredentials()))
if err != nil {
panic(fmt.Sprintf("failed to create fake grpc client: %v", err))
}
w := &client{ch: s.inproc, c: c}
if len(s.opts.unaryClientInterceptors) != 0 {
w.ui = grpc_middleware.ChainUnaryClient(s.opts.unaryClientInterceptors...)
}
if len(s.opts.streamClientInterceptors) != 0 {
w.si = grpc_middleware.ChainStreamClient(s.opts.streamClientInterceptors...)
}
return w
}
type client struct {
ui grpc.UnaryClientInterceptor
si grpc.StreamClientInterceptor
ch *inprocgrpc.Channel
c *grpc.ClientConn
}
func (c *client) Invoke(ctx context.Context, method string, args any, reply any, opts ...grpc.CallOption) error {
if c.ui != nil {
return c.ui(ctx, method, args, reply, c.c, c.invoke, opts...)
}
return c.ch.Invoke(ctx, method, args, reply, opts...)
}
func (c *client) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) {
if c.si != nil {
return c.si(ctx, desc, c.c, method, c.stream, opts...)
}
return c.ch.NewStream(ctx, desc, method, opts...)
}
func (c *client) invoke(ctx context.Context, method string, req, reply any, _ *grpc.ClientConn, opts ...grpc.CallOption) error {
return c.ch.Invoke(ctx, method, req, reply, opts...)
}
func (c *client) stream(ctx context.Context, desc *grpc.StreamDesc, _ *grpc.ClientConn, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) {
return c.ch.NewStream(ctx, desc, method, opts...)
}

View File

@ -5,52 +5,24 @@ import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"io/fs"
"net"
"os"
"strings"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/improbable-eng/grpc-web/go/grpcweb"
"github.com/jinzhu/gorm"
"github.com/rs/cors"
"go.uber.org/multierr"
"github.com/traefik/grpc-web/go/grpcweb"
"google.golang.org/grpc"
"go.linka.cloud/grpc/certs"
"go.linka.cloud/grpc/interceptors"
"go.linka.cloud/grpc/registry"
"go.linka.cloud/grpc/transport"
"go.linka.cloud/grpc/utils/addr"
"go.linka.cloud/grpc-toolkit/certs"
"go.linka.cloud/grpc-toolkit/interceptors"
"go.linka.cloud/grpc-toolkit/registry"
"go.linka.cloud/grpc-toolkit/transport"
"go.linka.cloud/grpc-toolkit/utils/addr"
)
/*
GLOBAL OPTIONS:
--client value Client for go-micro; rpc [$MICRO_CLIENT]
--client_request_timeout value Sets the client request timeout. e.g 500ms, 5s, 1m. Default: 5s [$MICRO_CLIENT_REQUEST_TIMEOUT]
--client_retries value Sets the client retries. Default: 1 (default: 1) [$MICRO_CLIENT_RETRIES]
--client_pool_size value Sets the client connection pool size. Default: 1 (default: 0) [$MICRO_CLIENT_POOL_SIZE]
--client_pool_ttl value Sets the client connection pool ttl. e.g 500ms, 5s, 1m. Default: 1m [$MICRO_CLIENT_POOL_TTL]
--help, -h show help
--secure SECURE
--ca_cert CA_CERT
--server_cert SERVER_CERT
--server_key SERVER_KEY
--register_ttl REGISTER_TTL
--register_interval REGISTER_INTERVAL
--server_address SERVER_ADDRESS
--server_name SERVER_NAME
--broker BROKER
--broker_address BROKER_ADDRESS
--registry REGISTRY
--registry_address REGISTRY_ADDRESS
--db_path DB_PATH
*/
var _ Options = (*options)(nil)
type RegisterGatewayFunc func(ctx context.Context, mux *runtime.ServeMux, cc grpc.ClientConnInterface) error
@ -66,13 +38,14 @@ type Options interface {
CACert() string
Cert() string
Key() string
ClientCACert() string
ClientCert() string
ClientKey() string
TLSConfig() *tls.Config
Secure() bool
Registry() registry.Registry
DB() *gorm.DB
BeforeStart() []func() error
AfterStart() []func() error
BeforeStop() []func() error
@ -97,6 +70,9 @@ type Options interface {
// TODO(adphi): metrics + tracing
WithoutCmux() bool
ProxyProtocol() bool
Default()
}
@ -118,7 +94,6 @@ func (o *options) Default() {
if o.transport == nil {
o.transport = &grpc.Server{}
}
}
type Option func(*options)
@ -157,6 +132,15 @@ func WithAddress(addr string) Option {
}
}
// WithListener specifies a listener for the service.
// It can be used to specify a custom listener.
// This will override the WithAddress and WithTLSConfig options
func WithListener(lis net.Listener) Option {
return func(o *options) {
o.lis = lis
}
}
func WithReflection(r bool) Option {
return func(o *options) {
o.reflection = r
@ -199,11 +183,21 @@ func WithKey(path string) Option {
}
}
func WithDB(dialect string, args ...interface{}) Option {
db, err := gorm.Open(dialect, args...)
func WithClientCACert(path string) Option {
return func(o *options) {
o.db = db
o.error = multierr.Append(o.error, err)
o.clientCACert = path
}
}
func WithClientCert(path string) Option {
return func(o *options) {
o.clientCert = path
}
}
func WithClientKey(path string) Option {
return func(o *options) {
o.clientKey = path
}
}
@ -352,11 +346,37 @@ func WithGatewayOpts(opts ...runtime.ServeMuxOption) Option {
}
}
// WithReactUI add static single page app serving to the http server
// subpath is the path in the read-only file embed.FS to use as root to serve
// static content
func WithReactUI(fs fs.FS, subpath string) Option {
return func(o *options) {
o.reactUI = fs
o.reactUISubPath = subpath
o.hasReactUI = true
}
}
// WithoutCmux disables the use of cmux for http support to instead use grpc.Server.ServeHTTP method when http support is enabled
func WithoutCmux() Option {
return func(o *options) {
o.withoutCmux = true
}
}
func WithProxyProtocol(addrs ...string) Option {
return func(o *options) {
o.proxyProtocol = true
o.proxyProtocolAddrs = addrs
}
}
type options struct {
ctx context.Context
name string
version string
address string
lis net.Listener
reflection bool
health bool
@ -365,10 +385,11 @@ type options struct {
caCert string
cert string
key string
clientCACert string
clientCert string
clientKey string
tlsConfig *tls.Config
db *gorm.DB
transport transport.Transport
registry registry.Registry
@ -394,8 +415,15 @@ type options struct {
gatewayOpts []runtime.ServeMuxOption
cors cors.Options
reactUI fs.FS
reactUISubPath string
hasReactUI bool
error error
gatewayPrefix string
withoutCmux bool
proxyProtocol bool
proxyProtocolAddrs []string
}
func (o *options) Name() string {
@ -438,6 +466,18 @@ func (o *options) Key() string {
return o.key
}
func (o *options) ClientCACert() string {
return o.clientCACert
}
func (o *options) ClientCert() string {
return o.clientCert
}
func (o *options) ClientKey() string {
return o.clientKey
}
func (o *options) TLSConfig() *tls.Config {
return o.tlsConfig
}
@ -446,10 +486,6 @@ func (o *options) Secure() bool {
return o.secure
}
func (o *options) DB() *gorm.DB {
return o.db
}
func (o *options) BeforeStart() []func() error {
return o.beforeStart
}
@ -518,10 +554,19 @@ func (o *options) GatewayOpts() []runtime.ServeMuxOption {
return o.gatewayOpts
}
func (o *options) WithoutCmux() bool {
return o.withoutCmux
}
func (o *options) ProxyProtocol() bool {
return o.proxyProtocol
}
func (o *options) parseTLSConfig() error {
if o.tlsConfig != nil {
return nil
}
nextProtos := []string{"h2", "h2c", "http/1.1", "acme-tls/1"}
if !o.hasTLSConfig() {
if !o.secure {
return nil
@ -549,10 +594,11 @@ func (o *options) parseTLSConfig() error {
o.tlsConfig = &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.NoClientCert,
NextProtos: nextProtos,
}
return nil
}
caCert, err := ioutil.ReadFile(o.caCert)
caCert, err := os.ReadFile(o.caCert)
if err != nil {
return err
}
@ -568,10 +614,34 @@ func (o *options) parseTLSConfig() error {
o.tlsConfig = &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caCertPool,
NextProtos: nextProtos,
}
if !o.hasClientTLSConfig() {
return nil
}
clientCACert, err := os.ReadFile(o.clientCACert)
if err != nil {
return err
}
clientCACertPool := x509.NewCertPool()
ok = clientCACertPool.AppendCertsFromPEM(clientCACert)
if !ok {
return fmt.Errorf("failed to load Client CA Cert from %s", o.clientCACert)
}
clientCert, err := tls.LoadX509KeyPair(o.clientCert, o.clientKey)
if err != nil {
return err
}
o.tlsConfig.ClientCAs = clientCACertPool
o.tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
o.tlsConfig.Certificates = append(o.tlsConfig.Certificates, clientCert)
return nil
}
func (o *options) hasTLSConfig() bool {
return o.caCert != "" && o.cert != "" && o.key != "" && o.tlsConfig == nil
return o.caCert != "" && o.cert != "" && o.tlsConfig == nil
}
func (o *options) hasClientTLSConfig() bool {
return o.clientCACert != "" && o.clientCert != "" && o.clientKey != ""
}

View File

@ -5,10 +5,10 @@ import (
"strings"
"time"
"go.linka.cloud/grpc/registry"
"go.linka.cloud/grpc/utils/addr"
"go.linka.cloud/grpc/utils/backoff"
net2 "go.linka.cloud/grpc/utils/net"
"go.linka.cloud/grpc-toolkit/registry"
"go.linka.cloud/grpc-toolkit/utils/addr"
"go.linka.cloud/grpc-toolkit/utils/backoff"
net2 "go.linka.cloud/grpc-toolkit/utils/net"
)
func (s *service) register() error {

View File

@ -17,28 +17,31 @@ import (
"github.com/fullstorydev/grpchan/inprocgrpc"
"github.com/google/uuid"
grpcmiddleware "github.com/grpc-ecosystem/go-grpc-middleware"
"github.com/jinzhu/gorm"
"github.com/justinas/alice"
"github.com/pires/go-proxyproto"
"github.com/rs/cors"
"github.com/sirupsen/logrus"
"github.com/soheilhy/cmux"
"go.uber.org/multierr"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/grpc/health"
"google.golang.org/grpc/health/grpc_health_v1"
greflect "google.golang.org/grpc/reflection"
"go.linka.cloud/grpc/interceptors/metadata"
"go.linka.cloud/grpc/registry"
"go.linka.cloud/grpc/registry/noop"
"go.linka.cloud/grpc-toolkit/internal/injectlogger"
"go.linka.cloud/grpc-toolkit/logger"
"go.linka.cloud/grpc-toolkit/registry"
"go.linka.cloud/grpc-toolkit/registry/noop"
)
type Service interface {
greflect.GRPCServer
Options() Options
DB() *gorm.DB
Start() error
Serve(lis net.Listener) error
Stop() error
Close() error
}
@ -59,8 +62,11 @@ type service struct {
inproc *inprocgrpc.Channel
services map[string]*serviceInfo
healthServer *health.Server
id string
regSvc *registry.Service
o sync.Once
closed chan struct{}
}
@ -76,32 +82,41 @@ func newService(opts ...Option) (*service, error) {
for _, f := range opts {
f(s.opts)
}
if s.opts.name != "" {
i := metadata.NewInterceptors("grpc-service-name", s.opts.name)
s.opts.unaryServerInterceptors = append([]grpc.UnaryServerInterceptor{i.UnaryServerInterceptor()}, s.opts.unaryServerInterceptors...)
s.opts.unaryClientInterceptors = append([]grpc.UnaryClientInterceptor{i.UnaryClientInterceptor()}, s.opts.unaryClientInterceptors...)
s.opts.streamServerInterceptors = append([]grpc.StreamServerInterceptor{i.StreamServerInterceptor()}, s.opts.streamServerInterceptors...)
s.opts.streamClientInterceptors = append([]grpc.StreamClientInterceptor{i.StreamClientInterceptor()}, s.opts.streamClientInterceptors...)
}
if s.opts.version != "" {
i := metadata.NewInterceptors("grpc-service-version", s.opts.version)
s.opts.unaryServerInterceptors = append([]grpc.UnaryServerInterceptor{i.UnaryServerInterceptor()}, s.opts.unaryServerInterceptors...)
s.opts.unaryClientInterceptors = append([]grpc.UnaryClientInterceptor{i.UnaryClientInterceptor()}, s.opts.unaryClientInterceptors...)
s.opts.streamServerInterceptors = append([]grpc.StreamServerInterceptor{i.StreamServerInterceptor()}, s.opts.streamServerInterceptors...)
s.opts.streamClientInterceptors = append([]grpc.StreamClientInterceptor{i.StreamClientInterceptor()}, s.opts.streamClientInterceptors...)
}
if s.opts.mux == nil {
s.opts.mux = http.NewServeMux()
s.opts.ctx, s.cancel = context.WithCancel(s.opts.ctx)
md := md(s.opts)
if md != nil {
s.opts.unaryServerInterceptors = append([]grpc.UnaryServerInterceptor{
md.UnaryServerInterceptor(),
injectlogger.New(s.opts.ctx).UnaryServerInterceptor()},
s.opts.unaryServerInterceptors...,
)
s.opts.unaryClientInterceptors = append([]grpc.UnaryClientInterceptor{
md.UnaryClientInterceptor(),
injectlogger.New(s.opts.ctx).UnaryClientInterceptor()},
s.opts.unaryClientInterceptors...,
)
s.opts.streamServerInterceptors = append([]grpc.StreamServerInterceptor{
md.StreamServerInterceptor(),
injectlogger.New(s.opts.ctx).StreamServerInterceptor()},
s.opts.streamServerInterceptors...,
)
s.opts.streamClientInterceptors = append([]grpc.StreamClientInterceptor{
md.StreamClientInterceptor(),
injectlogger.New(s.opts.ctx).StreamClientInterceptor()},
s.opts.streamClientInterceptors...,
)
}
if s.opts.error != nil {
return nil, s.opts.error
}
s.opts.ctx, s.cancel = context.WithCancel(s.opts.ctx)
go func() {
for {
select {
case <-s.opts.ctx.Done():
s.Stop()
return
}
}
}()
@ -128,11 +143,15 @@ func newService(opts ...Option) (*service, error) {
greflect.Register(s.server)
}
if s.opts.health {
s.registerService(&grpc_health_v1.Health_ServiceDesc, health.NewServer())
s.healthServer = health.NewServer()
s.registerService(&grpc_health_v1.Health_ServiceDesc, s.healthServer)
}
if err := s.gateway(s.opts.gatewayOpts...); err != nil {
return nil, err
}
if err := s.reactApp(); err != nil {
return nil, err
}
// we do not configure grpc web here as the grpc handlers are not yet registered
return s, nil
}
@ -141,49 +160,75 @@ func (s *service) Options() Options {
return s.opts
}
func (s *service) DB() *gorm.DB {
return s.opts.db
}
func (s *service) run() error {
func (s *service) start() (*errgroup.Group, error) {
s.mu.Lock()
defer s.mu.Unlock()
s.closed = make(chan struct{})
// configure grpc web now that we are ready to go
if err := s.grpcWeb(s.opts.grpcWebOpts...); err != nil {
return err
return nil, err
}
lis, err := net.Listen("tcp", s.opts.address)
network := "tcp"
if strings.HasPrefix(s.opts.address, "unix://") {
network = "unix"
s.opts.address = strings.TrimPrefix(s.opts.address, "unix://")
}
if s.opts.lis == nil {
lis, err := net.Listen(network, s.opts.address)
if err != nil {
return err
return nil, err
}
if s.opts.tlsConfig != nil {
lis = tls.NewListener(lis, s.opts.tlsConfig)
}
s.opts.lis = lis
s.opts.address = lis.Addr().String()
} else {
s.opts.address = s.opts.lis.Addr().String()
}
mux := cmux.New(lis)
mux.SetReadTimeout(5 * time.Second)
gLis := mux.MatchWithWriters(cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"))
hList := mux.Match(cmux.Any())
if s.opts.proxyProtocol {
p := func(upstream net.Addr) (proxyproto.Policy, error) {
u, _, err := net.SplitHostPort(upstream.String())
if err != nil {
return proxyproto.REJECT, err
}
ip := net.ParseIP(u)
if ip == nil {
return proxyproto.REJECT, fmt.Errorf("proxyproto: invalid IP address")
}
if ip.IsPrivate() || ip.IsLoopback() {
return proxyproto.USE, nil
}
return proxyproto.REJECT, nil
}
if len(s.opts.proxyProtocolAddrs) > 0 {
var err error
p, err = proxyproto.StrictWhiteListPolicy(s.opts.proxyProtocolAddrs)
if err != nil {
return nil, err
}
}
s.opts.lis = &proxyproto.Listener{
Listener: s.opts.lis,
Policy: p,
}
}
for i := range s.opts.beforeStart {
if err := s.opts.beforeStart[i](); err != nil {
s.mu.Unlock()
return err
return nil, err
}
}
if err := s.register(); err != nil {
return err
return nil, err
}
s.running = true
errs := make(chan error, 3)
if reflect.DeepEqual(s.opts.cors, cors.Options{}) {
s.opts.cors = cors.Options{
AllowedHeaders: []string{"*"},
@ -200,59 +245,135 @@ func (s *service) run() error {
AllowCredentials: true,
}
}
hServer := &http.Server{
Handler: alice.New(s.opts.middlewares...).Then(cors.New(s.opts.cors).Handler(s.opts.mux)),
}
if s.opts.Gateway() || s.opts.grpcWeb {
go func() {
errs <- hServer.Serve(hList)
hServer.Shutdown(s.opts.ctx)
}()
}
go func() {
errs <- s.server.Serve(gLis)
}()
go func() {
if err := mux.Serve(); err != nil {
// TODO(adphi): find more elegant solution
if ignoreMuxError(err) {
errs <- nil
return
fn := s.runWithCmux
if s.opts.withoutCmux || s.opts.mux == nil {
fn = s.runWithoutCmux
}
errs <- err
return
g, ctx := errgroup.WithContext(s.opts.ctx)
if err := fn(ctx, g); err != nil {
return nil, err
}
if s.healthServer != nil {
for k := range s.services {
s.healthServer.SetServingStatus(k, grpc_health_v1.HealthCheckResponse_SERVING)
}
defer func() {
for k := range s.services {
s.healthServer.SetServingStatus(k, grpc_health_v1.HealthCheckResponse_NOT_SERVING)
}
errs <- nil
}()
}
for i := range s.opts.afterStart {
if err := s.opts.afterStart[i](); err != nil {
s.mu.Unlock()
s.Stop()
return nil, err
}
}
return g, nil
}
func (s *service) run() error {
g, err := s.start()
if err != nil {
return err
}
}
s.mu.Unlock()
sigs := s.notify()
errs := make(chan error, 1)
go func() {
errs <- g.Wait()
}()
select {
case sig := <-sigs:
fmt.Println()
logrus.Warnf("received %v", sig)
logger.C(s.opts.ctx).Warnf("received %v", sig)
return s.Close()
case err := <-errs:
if err != nil && !ignoreMuxError(err) {
logrus.Error(err)
if !isMuxError(err) {
logger.C(s.opts.ctx).Error(err)
return err
}
return nil
}
}
func (s *service) runWithoutCmux(ctx context.Context, g *errgroup.Group) error {
if s.opts.mux != nil {
handler := alice.New(s.opts.middlewares...).Then(cors.New(s.opts.cors).Handler(s.opts.mux))
hServer := &http.Server{
Handler: h2c.NewHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.ProtoMajor == 2 && r.Header.Get("Content-Type") == "application/grpc" {
s.server.ServeHTTP(w, r)
} else {
handler.ServeHTTP(w, r)
}
}), &http2.Server{}),
}
if err := http2.ConfigureServer(hServer, &http2.Server{}); err != nil {
return err
}
g.Go(func() error {
defer hServer.Shutdown(ctx)
return hServer.Serve(s.opts.lis)
})
} else {
g.Go(func() error {
return s.server.Serve(s.opts.lis)
})
}
return nil
}
func (s *service) runWithCmux(ctx context.Context, g *errgroup.Group) error {
mux := cmux.New(s.opts.lis)
mux.SetReadTimeout(5 * time.Second)
gLis := mux.MatchWithWriters(cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"))
hList := mux.Match(cmux.Any())
if s.opts.mux != nil {
hServer := &http.Server{
Handler: alice.New(s.opts.middlewares...).Then(cors.New(s.opts.cors).Handler(s.opts.mux)),
}
g.Go(func() error {
defer hServer.Shutdown(ctx)
return hServer.Serve(hList)
})
}
g.Go(func() error {
return s.server.Serve(gLis)
})
g.Go(func() error {
return ignoreMuxError(mux.Serve())
})
return nil
}
func (s *service) Serve(lis net.Listener) error {
s.mu.Lock()
s.opts.lis = lis
s.mu.Unlock()
return s.run()
}
func (s *service) Start() error {
return s.run()
}
func (s *service) Stop() error {
var err error
s.o.Do(func() {
err = s.stop()
})
return err
}
func (s *service) stop() error {
log := logger.C(s.opts.ctx)
s.mu.Lock()
defer s.mu.Unlock()
if !s.running {
@ -264,21 +385,31 @@ func (s *service) Stop() error {
}
}
if err := s.opts.registry.Deregister(s.regSvc); err != nil {
logrus.Errorf("failed to deregister service: %v", err)
log.Errorf("failed to deregister service: %v", err)
}
defer close(s.closed)
t := time.NewTimer(5 * time.Second)
defer t.Stop()
sigs := s.notify()
done := make(chan struct{})
go func() {
logrus.Warn("shutting down gracefully")
defer close(done)
// TODO(adphi): find a better solution
defer func() {
// catch: Drain() is not implemented
recover()
}()
log.Warn("shutting down gracefully")
s.server.GracefulStop()
close(done)
}()
select {
case <-t.C:
log.Warnf("timeout waiting for server to stop")
s.server.Stop()
case sig := <-sigs:
fmt.Println()
logrus.Warnf("received %v", sig)
logrus.Warn("forcing shutdown")
log.Warnf("received %v", sig)
log.Warn("forcing shutdown")
s.server.Stop()
case <-done:
}
@ -289,7 +420,7 @@ func (s *service) Stop() error {
return err
}
}
logrus.Info("server stopped")
log.Info("server stopped")
return nil
}
@ -314,7 +445,7 @@ func (s *service) registerService(sd *grpc.ServiceDesc, ss interface{}) {
s.inproc.RegisterService(sd, ss)
if _, ok := s.services[sd.ServiceName]; ok {
logrus.Fatalf("grpc: Service.RegisterService found duplicate service registration for %q", sd.ServiceName)
logger.C(s.opts.ctx).Fatalf("grpc: Service.RegisterService found duplicate service registration for %q", sd.ServiceName)
}
info := &serviceInfo{
serviceImpl: ss,
@ -362,23 +493,37 @@ func (s *service) GetServiceInfo() map[string]grpc.ServiceInfo {
func (s *service) Close() error {
err := multierr.Combine(s.Stop())
if s.opts.db != nil {
err = multierr.Append(s.opts.db.Close(), err)
}
<-s.closed
return err
}
func (s *service) notify() <-chan os.Signal {
sigs := make(chan os.Signal, 2)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGQUIT)
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT)
return sigs
}
func ignoreMuxError(err error) bool {
func (s *service) lazyMux() ServeMux {
if s.opts.mux == nil {
s.opts.mux = http.NewServeMux()
}
return s.opts.mux
}
func ignoreMuxError(err error) error {
if !isMuxError(err) {
return err
}
return nil
}
func isMuxError(err error) bool {
if err == nil {
return false
}
if strings.Contains(err.Error(), "use of closed network connection") ||
strings.Contains(err.Error(), "mux: server closed") {
return true
}
return strings.Contains(err.Error(), "use of closed network connection") ||
strings.Contains(err.Error(), "mux: server closed")
return false
}

View File

@ -4,7 +4,9 @@ import (
"net/http"
"time"
"github.com/improbable-eng/grpc-web/go/grpcweb"
"github.com/traefik/grpc-web/go/grpcweb"
"go.linka.cloud/grpc-toolkit/react"
)
var defaultWebOptions = []grpcweb.Option{
@ -26,10 +28,22 @@ func (s *service) grpcWeb(opts ...grpcweb.Option) error {
h := grpcweb.WrapServer(s.server, append(defaultWebOptions, opts...)...)
for _, v := range grpcweb.ListGRPCResources(s.server) {
if s.opts.grpcWebPrefix != "" {
s.opts.mux.Handle(s.opts.grpcWebPrefix+v, http.StripPrefix(s.opts.grpcWebPrefix, h))
s.lazyMux().Handle(s.opts.grpcWebPrefix+v, http.StripPrefix(s.opts.grpcWebPrefix, h))
} else {
s.opts.mux.Handle(v, h)
s.lazyMux().Handle(v, h)
}
}
return nil
}
func (s *service) reactApp() error {
if !s.opts.hasReactUI {
return nil
}
h, err := react.NewHandler(s.opts.reactUI, s.opts.reactUISubPath)
if err != nil {
return err
}
s.lazyMux().Handle("/", h)
return nil
}

View File

@ -3,7 +3,7 @@ package grpc
import (
"google.golang.org/grpc"
"go.linka.cloud/grpc/transport"
"go.linka.cloud/grpc-toolkit/transport"
)
var (

Some files were not shown because too many files have changed in this diff Show More