Compare commits

...

62 Commits
v0.3.9 ... 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
101 changed files with 6890 additions and 1800 deletions

2
.gitignore vendored
View File

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

View File

@ -1,4 +1,4 @@
MODULE = go.linka.cloud/grpc MODULE = go.linka.cloud/grpc-toolkit
PROTO_BASE_PATH = $(PWD) 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...) 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. with pure gRPC ecosystem modules.
@ -9,7 +9,6 @@ Principles:
Features: Features:
- [x] simple configuration with options - [x] simple configuration with options
- [x] embedded gorm database with options (branch db)
- [x] simple TLS configuration - [x] simple TLS configuration
- [ ] TLS auth - [ ] TLS auth
- [ ] client connection pool - [ ] client connection pool
@ -24,7 +23,7 @@ Features:
- [ ] context logger - [ ] context logger
- [x] sentry - [x] sentry
- [ ] rate-limiting - [ ] rate-limiting
- [ ] ban - [x] ban
- [ ] auth claim in context - [ ] auth claim in context
- [x] recovery (server side only) - [x] recovery (server side only)
- [x] tracing (open-tracing) - [x] tracing (open-tracing)

View File

@ -2,6 +2,7 @@ package certs
import ( import (
"bytes" "bytes"
"context"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"crypto/rand" "crypto/rand"
@ -9,9 +10,17 @@ import (
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/pem" "encoding/pem"
"fmt"
"math/big" "math/big"
"net" "net"
"os"
"strings"
"sync"
"time" "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) { 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()) 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 ( import (
"context" "context"
"crypto/tls"
"fmt" "fmt"
"strings" "strings"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/resolver" "google.golang.org/grpc/resolver"
"go.linka.cloud/grpc/registry/noop" "go.linka.cloud/grpc-toolkit/registry/noop"
) )
type Client interface { type Client interface {
@ -27,15 +27,14 @@ func New(opts ...Option) (Client, error) {
c.opts.registry = noop.New() c.opts.registry = noop.New()
} }
resolver.Register(c.opts.registry.ResolverBuilder()) resolver.Register(c.opts.registry.ResolverBuilder())
c.pool = newPool(DefaultPoolSize, DefaultPoolTTL, DefaultPoolMaxIdle, DefaultPoolMaxStreams) if err := c.opts.parseTLSConfig(); err != nil {
if c.opts.tlsConfig == nil && c.opts.Secure() { return nil, err
c.opts.tlsConfig = &tls.Config{InsecureSkipVerify: true}
} }
if c.opts.tlsConfig != nil { if c.opts.tlsConfig != nil {
c.opts.dialOptions = append(c.opts.dialOptions, grpc.WithTransportCredentials(credentials.NewTLS(c.opts.tlsConfig))) c.opts.dialOptions = append(c.opts.dialOptions, grpc.WithTransportCredentials(credentials.NewTLS(c.opts.tlsConfig)))
} }
if !c.opts.secure { if !c.opts.secure && c.opts.tlsConfig == nil {
c.opts.dialOptions = append(c.opts.dialOptions, grpc.WithInsecure()) c.opts.dialOptions = append(c.opts.dialOptions, grpc.WithTransportCredentials(insecure.NewCredentials()))
} }
if len(c.opts.unaryInterceptors) > 0 { if len(c.opts.unaryInterceptors) > 0 {
c.opts.dialOptions = append(c.opts.dialOptions, grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(c.opts.unaryInterceptors...))) 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 { if len(c.opts.streamInterceptors) > 0 {
c.opts.dialOptions = append(c.opts.dialOptions, grpc.WithStreamInterceptor(grpc_middleware.ChainStreamClient(c.opts.streamInterceptors...))) 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) 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) 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 c.addr = c.opts.addr
} }
if c.opts.version != "" && c.opts.addr == "" { if c.opts.version != "" && c.opts.addr == "" {
c.addr = c.addr + ":" + strings.TrimSpace(c.opts.version) 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 return c, nil
} }
type client struct { type client struct {
addr string addr string
pool *pool
opts *options opts *options
cc *grpc.ClientConn
} }
func (c *client) Invoke(ctx context.Context, method string, args interface{}, reply interface{}, opts ...grpc.CallOption) error { 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...) return c.cc.Invoke(ctx, method, args, reply, opts...)
if err != nil {
return err
}
return pc.Invoke(ctx, method, args, reply, opts...)
} }
func (c *client) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) { 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...) return c.cc.NewStream(ctx, desc, method, opts...)
if err != nil {
return nil, err
}
return pc.NewStream(ctx, desc, method, opts...)
} }

View File

@ -12,29 +12,31 @@ var u = strings.ToUpper
func NewFlagSet() (*pflag.FlagSet, Option) { func NewFlagSet() (*pflag.FlagSet, Option) {
const ( const (
addr = "address" addr = "address"
secure = "secure" secure = "secure"
// caCert = "ca-cert" caCert = "ca-cert"
// clientCert = "client-cert" clientCert = "client-cert"
// clientKey = "client-key" clientKey = "client-key"
) )
var ( var (
optAddress string optAddress string
optSecure bool optSecure bool
// optCACert string optCACert string
// optCert string optCert string
// optKey string optKey string
) )
flags := pflag.NewFlagSet("gRPC", pflag.ContinueOnError) 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.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.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(&optCACert, caCert, "", "Path to Root CA certificate"+flagEnv(optCACert))
// flags.StringVar(&optCert, clientCert, "", "Path to Server certificate"+flagEnv(clientCert)) flags.StringVar(&optCert, clientCert, "", "Path to Server certificate"+flagEnv(clientCert))
// flags.StringVar(&optKey, clientKey, "", "Path to Server key"+flagEnv(clientKey)) flags.StringVar(&optKey, clientKey, "", "Path to Server key"+flagEnv(clientKey))
return flags, func(o *options) { return flags, func(o *options) {
o.addr = optAddress o.addr = optAddress
o.secure = optSecure o.secure = optSecure
o.caCert = optCACert
o.cert = optCert
o.key = optKey
} }
} }

View File

@ -2,11 +2,14 @@ package client
import ( import (
"crypto/tls" "crypto/tls"
"crypto/x509"
"fmt"
"os"
"google.golang.org/grpc" "google.golang.org/grpc"
"go.linka.cloud/grpc/interceptors" "go.linka.cloud/grpc-toolkit/interceptors"
"go.linka.cloud/grpc/registry" "go.linka.cloud/grpc-toolkit/registry"
) )
type Options interface { type Options interface {
@ -15,6 +18,9 @@ type Options interface {
Address() string Address() string
Secure() bool Secure() bool
Registry() registry.Registry Registry() registry.Registry
CA() string
Cert() string
Key() string
TLSConfig() *tls.Config TLSConfig() *tls.Config
DialOptions() []grpc.DialOption DialOptions() []grpc.DialOption
UnaryInterceptors() []grpc.UnaryClientInterceptor 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 { func WithTLSConfig(conf *tls.Config) Option {
return func(o *options) { return func(o *options) {
o.tlsConfig = conf o.tlsConfig = conf
@ -87,10 +111,14 @@ func WithStreamInterceptors(i ...grpc.StreamClientInterceptor) Option {
} }
type options struct { type options struct {
registry registry.Registry registry registry.Registry
name string name string
version string version string
addr string addr string
caCert string
cert string
key string
tlsConfig *tls.Config tlsConfig *tls.Config
secure bool secure bool
dialOptions []grpc.DialOption dialOptions []grpc.DialOption
@ -115,6 +143,18 @@ func (o *options) Registry() registry.Registry {
return o.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 { func (o *options) TLSConfig() *tls.Config {
return o.tlsConfig return o.tlsConfig
} }
@ -134,3 +174,38 @@ func (o *options) UnaryInterceptors() []grpc.UnaryClientInterceptor {
func (o *options) StreamInterceptors() []grpc.StreamClientInterceptor { func (o *options) StreamInterceptors() []grpc.StreamClientInterceptor {
return o.streamInterceptors 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
}

View File

@ -3,6 +3,8 @@ package codec
import ( import (
"fmt" "fmt"
"google.golang.org/grpc/encoding"
_ "google.golang.org/grpc/encoding/proto"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
@ -50,3 +52,7 @@ func (Codec) Unmarshal(data []byte, v interface{}) error {
func (Codec) Name() string { func (Codec) Name() string {
return Name return Name
} }
func init() {
encoding.RegisterCodec(Codec{})
}

View File

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

View File

@ -4,7 +4,6 @@ package file
import ( import (
"context" "context"
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path" "path"
@ -15,12 +14,12 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "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()) { func newConfigFile(t *testing.T) (config.Config, string, func()) {
path := filepath.Join(os.TempDir(), "config.yaml") 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) t.Fatal(err)
} }
cleanUp := func() { 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()) { func newSymlinkedConfigFile(t *testing.T) (config.Config, string, string, func()) {
watchDir, err := ioutil.TempDir("", "") watchDir, err := os.MkdirTemp("", "")
require.Nil(t, err) require.Nil(t, err)
dataDir1 := path.Join(watchDir, "data1") dataDir1 := path.Join(watchDir, "data1")
err = os.Mkdir(dataDir1, 0o777) err = os.Mkdir(dataDir1, 0o777)
require.Nil(t, err) require.Nil(t, err)
realConfigFile := path.Join(dataDir1, "config.yaml") realConfigFile := path.Join(dataDir1, "config.yaml")
t.Logf("Real config file location: %s\n", realConfigFile) 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) require.Nil(t, err)
cleanup := func() { cleanup := func() {
os.RemoveAll(watchDir) os.RemoveAll(watchDir)
@ -64,7 +63,7 @@ func TestWatch(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
// when overwriting the file and waiting for the custom change notification handler to be triggered // 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 b := <-updates
// then the config value should have changed // then the config value should have changed
require.Nil(t, err) require.Nil(t, err)
@ -87,7 +86,7 @@ func TestWatch(t *testing.T) {
err := os.MkdirAll(dataDir2, 0o777) err := os.MkdirAll(dataDir2, 0o777)
require.NoError(t, err) require.NoError(t, err)
configFile2 := path.Join(dataDir2, "config.yaml") 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) require.NoError(t, err)
// change the symlink using the `ln -sfn` command // change the symlink using the `ln -sfn` command
err = exec.Command("ln", "-sfn", dataDir2, path.Join(watchDir, "data")).Run() 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 package errors
import ( import (
"context"
"errors"
"strings"
status2 "google.golang.org/genproto/googleapis/rpc/status" status2 "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
@ -8,6 +12,10 @@ import (
"google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/anypb"
) )
func Canceled(err error) error {
return status.Error(codes.Canceled, err.Error())
}
func InvalidArgument(err error) error { func InvalidArgument(err error) error {
return status.Error(codes.InvalidArgument, err.Error()) return status.Error(codes.InvalidArgument, err.Error())
} }
@ -63,6 +71,12 @@ func makeDetails(m ...proto.Message) []*anypb.Any {
return out 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 { func InvalidArgumentf(msg string, args ...interface{}) error {
return status.Errorf(codes.InvalidArgument, msg, args...) return status.Errorf(codes.InvalidArgument, msg, args...)
} }
@ -152,7 +166,7 @@ func IsCanceled(err error) bool {
if err == nil { if err == nil {
return false return false
} }
return status.Convert(err).Code() == codes.Canceled return status.Convert(err).Code() == codes.Canceled || IsContextCanceled(err)
} }
func IsUnknown(err error) bool { func IsUnknown(err error) bool {
if err == nil { if err == nil {
@ -170,7 +184,7 @@ func IsDeadlineExceeded(err error) bool {
if err == nil { if err == nil {
return false return false
} }
return status.Convert(err).Code() == codes.DeadlineExceeded return status.Convert(err).Code() == codes.DeadlineExceeded || IsContextDeadlineExceeded(err)
} }
func IsNotFound(err error) bool { func IsNotFound(err error) bool {
if err == nil { if err == nil {
@ -244,3 +258,30 @@ func IsUnauthenticated(err error) bool {
} }
return status.Convert(err).Code() == codes.Unauthenticated 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" "context"
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt"
"io" "io"
"io/ioutil" "net"
"net/http" "net/http"
"strings" "strings"
"time" "time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp" "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"
"google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/metadata" "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-tookit/example/pb"
"go.linka.cloud/grpc/interceptors/defaulter" "go.linka.cloud/grpc-toolkit/client"
metrics2 "go.linka.cloud/grpc/interceptors/metrics" "go.linka.cloud/grpc-toolkit/interceptors/auth"
validation2 "go.linka.cloud/grpc/interceptors/validation" "go.linka.cloud/grpc-toolkit/interceptors/tracing"
"go.linka.cloud/grpc/logger" "go.linka.cloud/grpc-toolkit/logger"
"go.linka.cloud/grpc/service" "go.linka.cloud/grpc-toolkit/service"
) )
type GreeterHandler struct { func run(ctx context.Context, opts ...service.Option) {
UnimplementedGreeterServer 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" name := "greeter"
version := "v0.0.1" version := "v0.0.1"
secure := true secure := true
ctx, cancel := context.WithCancel(context.Background())
defer cancel() log := logger.New().WithFields("service", name).WithReportCaller(true)
log := logger.New().WithFields("service", name) log.Logger().AddHook(otellogrus.NewHook(otellogrus.WithLevels(
logger.PanicLevel,
logger.FatalLevel,
logger.ErrorLevel,
logger.WarnLevel,
logger.InfoLevel,
)))
ctx = logger.Set(ctx, log) ctx = logger.Set(ctx, log)
done := make(chan struct{}) done := make(chan struct{})
ready := make(chan struct{}) ready := make(chan struct{})
var svc service.Service
var err error exporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithInsecure(), otlptracegrpc.WithEndpoint("192.168.10.212:4317"))
metrics := metrics2.NewInterceptors() if err != nil {
validation := validation2.NewInterceptors(true) logrus.Fatal(err)
defaulter := defaulter.NewInterceptors() }
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" 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.WithName(name),
service.WithVersion(version), service.WithVersion(version),
service.WithAddress(address), service.WithAddress(address),
// service.WithRegistry(mdns.NewRegistry()),
service.WithReflection(true),
service.WithSecure(secure), service.WithSecure(secure),
service.WithAfterStart(func() error { service.WithAfterStart(func() error {
log.Info("Server listening on", svc.Options().Address()) log.Info("Server listening on", svc.Options().Address())
@ -108,61 +119,83 @@ func run(opts ...service.Option) {
close(done) close(done)
return nil 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 { if err != nil {
panic(err) panic(err)
} }
RegisterGreeterServer(svc, &GreeterHandler{})
metrics.Register(svc)
go func() { go func() {
if err := svc.Start(); err != nil { if err := svc.Start(); err != nil {
panic(err) panic(err)
} }
}() }()
go func() { 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 { if err := http.ListenAndServe(":9992", nil); err != nil {
panic(err) panic(err)
} }
}() }()
<-ready <-ready
s, err := client.New( copts := []client.Option{
// client.WithName(name), // client.WithName(name),
// client.WithVersion(version), // client.WithVersion(version),
client.WithAddress("localhost:9991"), client.WithAddress("localhost:9991"),
// client.WithRegistry(mdns.NewRegistry()), // client.WithRegistry(mdns.NewRegistry()),
client.WithSecure(secure), client.WithSecure(secure),
client.WithUnaryInterceptors(func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { client.WithInterceptors(tracing.NewClientInterceptors()),
logger.From(ctx).WithFields("party", "client", "method", method).Info(req) // client.WithUnaryInterceptors(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...) // 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 { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
g := NewGreeterClient(s) g := pb.NewGreeterClient(s)
defer cancel() 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{} 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 { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
logMetadata(ctx, md) logMetadata(ctx, md)
log.Infof("received message: %s", res.Message) log.Infof("received message: %s", res.Message)
md = metadata.MD{} 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 { if err == nil {
log.Fatal("expected validation error") log.Fatal("expected validation error")
} }
logMetadata(ctx, md) 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 { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -180,25 +213,43 @@ func run(opts ...service.Option) {
log.Infof("received stream message: %s", m.Message) log.Infof("received stream message: %s", m.Message)
} }
scheme := "http://" scheme := "http://"
var (
tlsConfig *tls.Config
dial func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error)
)
if secure { if secure {
scheme = "https://" 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{ httpc := &http.Client{
Transport: &http.Transport{ Transport: &http2.Transport{
TLSClientConfig: &tls.Config{ AllowHTTP: true,
InsecureSkipVerify: true, TLSClientConfig: tlsConfig,
}, DialTLSContext: dial,
}, },
} }
req := `{"name":"test"}` req := `{"name":"test"}`
do := func(url, contentType string) { 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 { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
defer resp.Body.Close() defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body) b, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -220,80 +271,3 @@ func logMetadata(ctx context.Context, md metadata.MD) {
log.Infof("%s: %v", k, v) 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. // Code generated by protoc-gen-defaults. DO NOT EDIT.
package main package pb
import ( import (
"google.golang.org/protobuf/types/known/durationpb" "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. // 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. It translates gRPC into RESTful JSON APIs.
*/ */
package main package pb
import ( import (
"context" "context"
@ -35,11 +35,7 @@ func request_Greeter_SayHello_0(ctx context.Context, marshaler runtime.Marshaler
var protoReq HelloRequest var protoReq HelloRequest
var metadata runtime.ServerMetadata var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body) if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) 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 protoReq HelloRequest
var metadata runtime.ServerMetadata var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body) if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) 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 var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) 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 { if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return 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()) 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 { if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return 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 // RegisterGreeterHandlerFromEndpoint is same as RegisterGreeterHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done. // 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) { 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 { if err != nil {
return err return err
} }
defer func() { defer func() {
if err != nil { if err != nil {
if cerr := conn.Close(); cerr != 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 return
} }
go func() { go func() {
<-ctx.Done() <-ctx.Done()
if cerr := conn.Close(); cerr != 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)
} }
}() }()
}() }()
@ -139,19 +133,21 @@ func RegisterGreeterHandlerClient(ctx context.Context, mux *runtime.ServeMux, cl
ctx, cancel := context.WithCancel(req.Context()) ctx, cancel := context.WithCancel(req.Context())
defer cancel() defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) 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 { if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return return
} }
resp, md, err := request_Greeter_SayHello_0(rctx, inboundMarshaler, client, req, pathParams) resp, md, err := request_Greeter_SayHello_0(annotatedContext, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil { if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return 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. // Code generated by protoc-gen-validate. DO NOT EDIT.
// source: example/example.proto // source: example/pb/example.proto
package main package pb
import ( import (
"bytes" "bytes"
@ -71,6 +71,7 @@ func (m *HelloRequest) validate(all bool) error {
if len(errors) > 0 { if len(errors) > 0 {
return HelloRequestMultiError(errors) return HelloRequestMultiError(errors)
} }
return nil return nil
} }
@ -171,6 +172,7 @@ func (m *HelloReply) validate(all bool) error {
if len(errors) > 0 { if len(errors) > 0 {
return HelloReplyMultiError(errors) return HelloReplyMultiError(errors)
} }
return nil return nil
} }
@ -291,6 +293,7 @@ func (m *HelloStreamRequest) validate(all bool) error {
if len(errors) > 0 { if len(errors) > 0 {
return HelloStreamRequestMultiError(errors) return HelloStreamRequestMultiError(errors)
} }
return nil return nil
} }

View File

@ -2,7 +2,7 @@ syntax = "proto3";
package helloworld; 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 "google/api/annotations.proto";
import "validate/validate.proto"; import "validate/validate.proto";

View File

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

View File

@ -1,10 +1,13 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // 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 ( import (
context "context" context "context"
grpc "google.golang.org/grpc" grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes" codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status" status "google.golang.org/grpc/status"
@ -12,15 +15,20 @@ import (
// This is a compile-time assertion to ensure that this generated file // This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against. // is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later. // Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion7 const _ = grpc.SupportPackageIsVersion9
const (
Greeter_SayHello_FullMethodName = "/helloworld.Greeter/SayHello"
Greeter_SayHelloStream_FullMethodName = "/helloworld.Greeter/SayHelloStream"
)
// GreeterClient is the client API for Greeter service. // 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. // 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 { type GreeterClient interface {
SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) 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 { 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) { func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(HelloReply) 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 { if err != nil {
return nil, err return nil, err
} }
return out, nil return out, nil
} }
func (c *greeterClient) SayHelloStream(ctx context.Context, in *HelloStreamRequest, opts ...grpc.CallOption) (Greeter_SayHelloStreamClient, error) { func (c *greeterClient) SayHelloStream(ctx context.Context, in *HelloStreamRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[HelloReply], error) {
stream, err := c.cc.NewStream(ctx, &Greeter_ServiceDesc.Streams[0], "/helloworld.Greeter/SayHelloStream", opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &Greeter_ServiceDesc.Streams[0], Greeter_SayHelloStream_FullMethodName, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
x := &greeterSayHelloStreamClient{stream} x := &grpc.GenericClientStream[HelloStreamRequest, HelloReply]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil { if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err return nil, err
} }
@ -55,43 +65,33 @@ func (c *greeterClient) SayHelloStream(ctx context.Context, in *HelloStreamReque
return x, nil return x, nil
} }
type Greeter_SayHelloStreamClient interface { // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
Recv() (*HelloReply, error) type Greeter_SayHelloStreamClient = grpc.ServerStreamingClient[HelloReply]
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
}
// GreeterServer is the server API for Greeter service. // GreeterServer is the server API for Greeter service.
// All implementations must embed UnimplementedGreeterServer // All implementations must embed UnimplementedGreeterServer
// for forward compatibility // for forward compatibility.
type GreeterServer interface { type GreeterServer interface {
SayHello(context.Context, *HelloRequest) (*HelloReply, error) SayHello(context.Context, *HelloRequest) (*HelloReply, error)
SayHelloStream(*HelloStreamRequest, Greeter_SayHelloStreamServer) error SayHelloStream(*HelloStreamRequest, grpc.ServerStreamingServer[HelloReply]) error
mustEmbedUnimplementedGreeterServer() mustEmbedUnimplementedGreeterServer()
} }
// UnimplementedGreeterServer must be embedded to have forward compatible implementations. // UnimplementedGreeterServer must be embedded to have
type UnimplementedGreeterServer struct { // 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) { func (UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented") 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") return status.Errorf(codes.Unimplemented, "method SayHelloStream not implemented")
} }
func (UnimplementedGreeterServer) mustEmbedUnimplementedGreeterServer() {} func (UnimplementedGreeterServer) mustEmbedUnimplementedGreeterServer() {}
func (UnimplementedGreeterServer) testEmbeddedByValue() {}
// UnsafeGreeterServer may be embedded to opt out of forward compatibility for this service. // 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 // 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) { 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) s.RegisterService(&Greeter_ServiceDesc, srv)
} }
@ -114,7 +121,7 @@ func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(in
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/helloworld.Greeter/SayHello", FullMethod: Greeter_SayHello_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest)) 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 { if err := stream.RecvMsg(m); err != nil {
return err return err
} }
return srv.(GreeterServer).SayHelloStream(m, &greeterSayHelloStreamServer{stream}) return srv.(GreeterServer).SayHelloStream(m, &grpc.GenericServerStream[HelloStreamRequest, HelloReply]{ServerStream: stream})
} }
type Greeter_SayHelloStreamServer interface { // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
Send(*HelloReply) error type Greeter_SayHelloStreamServer = grpc.ServerStreamingServer[HelloReply]
grpc.ServerStream
}
type greeterSayHelloStreamServer struct {
grpc.ServerStream
}
func (x *greeterSayHelloStreamServer) Send(m *HelloReply) error {
return x.ServerStream.SendMsg(m)
}
// Greeter_ServiceDesc is the grpc.ServiceDesc for Greeter service. // Greeter_ServiceDesc is the grpc.ServiceDesc for Greeter service.
// It's only intended for direct use with grpc.RegisterService, // It's only intended for direct use with grpc.RegisterService,
@ -162,5 +159,5 @@ var Greeter_ServiceDesc = grpc.ServiceDesc{
ServerStreams: true, 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. // Code generated by protoc-gen-go-vtproto. DO NOT EDIT.
// protoc-gen-go-vtproto version: v0.2.0 // protoc-gen-go-vtproto version: v0.6.1-0.20240917153116-6f2963f01587
// source: example/example.proto // source: example/pb/example.proto
package main package pb
import ( import (
fmt "fmt" fmt "fmt"
io "io" protohelpers "github.com/planetscale/vtprotobuf/protohelpers"
bits "math/bits"
protoimpl "google.golang.org/protobuf/runtime/protoimpl" protoimpl "google.golang.org/protobuf/runtime/protoimpl"
io "io"
) )
const ( const (
@ -52,7 +51,7 @@ func (m *HelloRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if len(m.Name) > 0 { if len(m.Name) > 0 {
i -= len(m.Name) i -= len(m.Name)
copy(dAtA[i:], 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-- i--
dAtA[i] = 0xa dAtA[i] = 0xa
} }
@ -92,7 +91,7 @@ func (m *HelloReply) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
if len(m.Message) > 0 { if len(m.Message) > 0 {
i -= len(m.Message) i -= len(m.Message)
copy(dAtA[i:], 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-- i--
dAtA[i] = 0xa dAtA[i] = 0xa
} }
@ -130,31 +129,20 @@ func (m *HelloStreamRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
copy(dAtA[i:], m.unknownFields) copy(dAtA[i:], m.unknownFields)
} }
if m.Count != 0 { if m.Count != 0 {
i = encodeVarint(dAtA, i, uint64(m.Count)) i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Count))
i-- i--
dAtA[i] = 0x10 dAtA[i] = 0x10
} }
if len(m.Name) > 0 { if len(m.Name) > 0 {
i -= len(m.Name) i -= len(m.Name)
copy(dAtA[i:], 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-- i--
dAtA[i] = 0xa dAtA[i] = 0xa
} }
return len(dAtA) - i, nil 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) { func (m *HelloRequest) SizeVT() (n int) {
if m == nil { if m == nil {
return 0 return 0
@ -163,11 +151,9 @@ func (m *HelloRequest) SizeVT() (n int) {
_ = l _ = l
l = len(m.Name) l = len(m.Name)
if l > 0 { if l > 0 {
n += 1 + l + sov(uint64(l)) n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
if m.unknownFields != nil {
n += len(m.unknownFields)
} }
n += len(m.unknownFields)
return n return n
} }
@ -179,11 +165,9 @@ func (m *HelloReply) SizeVT() (n int) {
_ = l _ = l
l = len(m.Message) l = len(m.Message)
if l > 0 { if l > 0 {
n += 1 + l + sov(uint64(l)) n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
if m.unknownFields != nil {
n += len(m.unknownFields)
} }
n += len(m.unknownFields)
return n return n
} }
@ -195,23 +179,15 @@ func (m *HelloStreamRequest) SizeVT() (n int) {
_ = l _ = l
l = len(m.Name) l = len(m.Name)
if l > 0 { if l > 0 {
n += 1 + l + sov(uint64(l)) n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
} }
if m.Count != 0 { 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)
} }
n += len(m.unknownFields)
return n 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 { func (m *HelloRequest) UnmarshalVT(dAtA []byte) error {
l := len(dAtA) l := len(dAtA)
iNdEx := 0 iNdEx := 0
@ -220,7 +196,7 @@ func (m *HelloRequest) UnmarshalVT(dAtA []byte) error {
var wire uint64 var wire uint64
for shift := uint(0); ; shift += 7 { for shift := uint(0); ; shift += 7 {
if shift >= 64 { if shift >= 64 {
return ErrIntOverflow return protohelpers.ErrIntOverflow
} }
if iNdEx >= l { if iNdEx >= l {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
@ -248,7 +224,7 @@ func (m *HelloRequest) UnmarshalVT(dAtA []byte) error {
var stringLen uint64 var stringLen uint64
for shift := uint(0); ; shift += 7 { for shift := uint(0); ; shift += 7 {
if shift >= 64 { if shift >= 64 {
return ErrIntOverflow return protohelpers.ErrIntOverflow
} }
if iNdEx >= l { if iNdEx >= l {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
@ -262,11 +238,11 @@ func (m *HelloRequest) UnmarshalVT(dAtA []byte) error {
} }
intStringLen := int(stringLen) intStringLen := int(stringLen)
if intStringLen < 0 { if intStringLen < 0 {
return ErrInvalidLength return protohelpers.ErrInvalidLength
} }
postIndex := iNdEx + intStringLen postIndex := iNdEx + intStringLen
if postIndex < 0 { if postIndex < 0 {
return ErrInvalidLength return protohelpers.ErrInvalidLength
} }
if postIndex > l { if postIndex > l {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
@ -275,12 +251,12 @@ func (m *HelloRequest) UnmarshalVT(dAtA []byte) error {
iNdEx = postIndex iNdEx = postIndex
default: default:
iNdEx = preIndex iNdEx = preIndex
skippy, err := skip(dAtA[iNdEx:]) skippy, err := protohelpers.Skip(dAtA[iNdEx:])
if err != nil { if err != nil {
return err return err
} }
if (skippy < 0) || (iNdEx+skippy) < 0 { if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLength return protohelpers.ErrInvalidLength
} }
if (iNdEx + skippy) > l { if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
@ -303,7 +279,7 @@ func (m *HelloReply) UnmarshalVT(dAtA []byte) error {
var wire uint64 var wire uint64
for shift := uint(0); ; shift += 7 { for shift := uint(0); ; shift += 7 {
if shift >= 64 { if shift >= 64 {
return ErrIntOverflow return protohelpers.ErrIntOverflow
} }
if iNdEx >= l { if iNdEx >= l {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
@ -331,7 +307,7 @@ func (m *HelloReply) UnmarshalVT(dAtA []byte) error {
var stringLen uint64 var stringLen uint64
for shift := uint(0); ; shift += 7 { for shift := uint(0); ; shift += 7 {
if shift >= 64 { if shift >= 64 {
return ErrIntOverflow return protohelpers.ErrIntOverflow
} }
if iNdEx >= l { if iNdEx >= l {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
@ -345,11 +321,11 @@ func (m *HelloReply) UnmarshalVT(dAtA []byte) error {
} }
intStringLen := int(stringLen) intStringLen := int(stringLen)
if intStringLen < 0 { if intStringLen < 0 {
return ErrInvalidLength return protohelpers.ErrInvalidLength
} }
postIndex := iNdEx + intStringLen postIndex := iNdEx + intStringLen
if postIndex < 0 { if postIndex < 0 {
return ErrInvalidLength return protohelpers.ErrInvalidLength
} }
if postIndex > l { if postIndex > l {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
@ -358,12 +334,12 @@ func (m *HelloReply) UnmarshalVT(dAtA []byte) error {
iNdEx = postIndex iNdEx = postIndex
default: default:
iNdEx = preIndex iNdEx = preIndex
skippy, err := skip(dAtA[iNdEx:]) skippy, err := protohelpers.Skip(dAtA[iNdEx:])
if err != nil { if err != nil {
return err return err
} }
if (skippy < 0) || (iNdEx+skippy) < 0 { if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLength return protohelpers.ErrInvalidLength
} }
if (iNdEx + skippy) > l { if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
@ -386,7 +362,7 @@ func (m *HelloStreamRequest) UnmarshalVT(dAtA []byte) error {
var wire uint64 var wire uint64
for shift := uint(0); ; shift += 7 { for shift := uint(0); ; shift += 7 {
if shift >= 64 { if shift >= 64 {
return ErrIntOverflow return protohelpers.ErrIntOverflow
} }
if iNdEx >= l { if iNdEx >= l {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
@ -414,7 +390,7 @@ func (m *HelloStreamRequest) UnmarshalVT(dAtA []byte) error {
var stringLen uint64 var stringLen uint64
for shift := uint(0); ; shift += 7 { for shift := uint(0); ; shift += 7 {
if shift >= 64 { if shift >= 64 {
return ErrIntOverflow return protohelpers.ErrIntOverflow
} }
if iNdEx >= l { if iNdEx >= l {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
@ -428,11 +404,11 @@ func (m *HelloStreamRequest) UnmarshalVT(dAtA []byte) error {
} }
intStringLen := int(stringLen) intStringLen := int(stringLen)
if intStringLen < 0 { if intStringLen < 0 {
return ErrInvalidLength return protohelpers.ErrInvalidLength
} }
postIndex := iNdEx + intStringLen postIndex := iNdEx + intStringLen
if postIndex < 0 { if postIndex < 0 {
return ErrInvalidLength return protohelpers.ErrInvalidLength
} }
if postIndex > l { if postIndex > l {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
@ -446,7 +422,7 @@ func (m *HelloStreamRequest) UnmarshalVT(dAtA []byte) error {
m.Count = 0 m.Count = 0
for shift := uint(0); ; shift += 7 { for shift := uint(0); ; shift += 7 {
if shift >= 64 { if shift >= 64 {
return ErrIntOverflow return protohelpers.ErrIntOverflow
} }
if iNdEx >= l { if iNdEx >= l {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
@ -460,12 +436,12 @@ func (m *HelloStreamRequest) UnmarshalVT(dAtA []byte) error {
} }
default: default:
iNdEx = preIndex iNdEx = preIndex
skippy, err := skip(dAtA[iNdEx:]) skippy, err := protohelpers.Skip(dAtA[iNdEx:])
if err != nil { if err != nil {
return err return err
} }
if (skippy < 0) || (iNdEx+skippy) < 0 { if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLength return protohelpers.ErrInvalidLength
} }
if (iNdEx + skippy) > l { if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
@ -480,87 +456,3 @@ func (m *HelloStreamRequest) UnmarshalVT(dAtA []byte) error {
} }
return nil 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()
})
}

108
go.mod
View File

@ -1,50 +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 ( require (
github.com/alta/protopatch v0.3.4 github.com/alta/protopatch v0.5.3
github.com/bombsimon/logrusr/v2 v2.0.1 github.com/bombsimon/logrusr/v4 v4.0.0
github.com/caitlinelfring/go-env-default v1.1.0 github.com/caitlinelfring/go-env-default v1.1.0
github.com/envoyproxy/protoc-gen-validate v0.6.2 github.com/envoyproxy/protoc-gen-validate v1.1.0
github.com/fsnotify/fsnotify v1.5.1 github.com/fatih/color v1.13.0
github.com/fsnotify/fsnotify v1.5.4
github.com/fullstorydev/grpchan v1.1.1 github.com/fullstorydev/grpchan v1.1.1
github.com/go-logr/logr v1.2.3 github.com/go-logr/logr v1.4.2
github.com/golang/protobuf v1.5.2 github.com/golang/protobuf v1.5.4
github.com/google/uuid v1.1.2 github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.4.2 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 github.com/jaredfolkins/badactor v1.2.0
github.com/improbable-eng/grpc-web v0.14.1 github.com/johnbellone/grpc-middleware-sentry v0.3.0
github.com/jinzhu/gorm v1.9.12
github.com/johnbellone/grpc-middleware-sentry v0.2.0
github.com/justinas/alice v1.2.0 github.com/justinas/alice v1.2.0
github.com/lyft/protoc-gen-star v0.6.0 // indirect
github.com/miekg/dns v1.1.41 github.com/miekg/dns v1.1.41
github.com/opentracing/opentracing-go v1.1.0 github.com/pires/go-proxyproto v0.7.0
github.com/planetscale/vtprotobuf v0.2.0 github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587
github.com/prometheus/client_golang v1.11.0 github.com/prometheus/client_golang v1.20.4
github.com/rs/cors v1.7.0 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/soheilhy/cmux v0.1.5
github.com/spf13/cobra v1.3.0 github.com/spf13/cobra v1.3.0
github.com/spf13/pflag v1.0.5 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 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5
go.linka.cloud/protoc-gen-defaults v0.1.0 github.com/traefik/grpc-web v0.16.0
go.linka.cloud/protoc-gen-go-fields v0.1.1 go.linka.cloud/protoc-gen-defaults v0.4.0
go.linka.cloud/protofilters v0.2.2 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 go.uber.org/multierr v1.7.0
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d golang.org/x/net v0.30.0
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa golang.org/x/sync v0.8.0
google.golang.org/grpc v1.45.0 google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9
google.golang.org/protobuf v1.27.1 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 ( 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/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
) )

631
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -5,11 +5,11 @@ import (
"encoding/base64" "encoding/base64"
"strings" "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-toolkit/errors"
"go.linka.cloud/grpc/interceptors" "go.linka.cloud/grpc-toolkit/interceptors"
"go.linka.cloud/grpc/interceptors/metadata" "go.linka.cloud/grpc-toolkit/interceptors/metadata"
) )
func BasicAuth(user, password string) string { func BasicAuth(user, password string) string {

View File

@ -3,19 +3,19 @@ package auth
import ( import (
"context" "context"
"crypto/subtle" "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"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "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 { func ChainedAuthFuncs(fn ...grpc_auth.AuthFunc) grpc_auth.AuthFunc {
return func(ctx context.Context) (context.Context, error) { return func(ctx context.Context) (context.Context, error) {
code := codes.Unauthenticated spb := status.New(codes.Unauthenticated, codes.Unauthenticated.String()).Proto()
for _, v := range fn { for _, v := range fn {
ctx2, err := v(ctx) ctx2, err := v(ctx)
if err == nil { if err == nil {
@ -25,11 +25,14 @@ func ChainedAuthFuncs(fn ...grpc_auth.AuthFunc) grpc_auth.AuthFunc {
if !ok { if !ok {
return ctx2, err return ctx2, err
} }
if s.Code() == codes.PermissionDenied { if spb.Code != s.Proto().Code {
code = codes.PermissionDenied 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 { if len(i.o.ignoredMethods) == 0 && len(i.o.methods) == 0 {
return false 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 { for _, v := range i.o.ignoredMethods {
if v == method { if v == endpoint {
return true return true
} }
} }
@ -87,7 +83,7 @@ func (i *interceptor) isNotProtected(endpoint string) bool {
return false return false
} }
for _, v := range i.o.methods { for _, v := range i.o.methods {
if v == method { if v == endpoint {
return false return false
} }
} }

View File

@ -4,32 +4,32 @@ import (
"context" "context"
"testing" "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" assert2 "github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"google.golang.org/grpc/status" "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) 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.False(i.isNotProtected("/test.Service/protected"))
assert.True(i.isNotProtected("/test.Service/ignored")) assert.True(i.isNotProtected("/test.Service/ignored"))
} }
func TestProtectedOnly(t *testing.T) { func TestProtectedOnly(t *testing.T) {
assert := assert2.New(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.False(i.isNotProtected("/test.Service/protected"))
assert.True(i.isNotProtected("/test.Service/ignored")) assert.True(i.isNotProtected("/test.Service/ignored"))
} }
func TestProtectedAndIgnored(t *testing.T) { func TestProtectedAndIgnored(t *testing.T) {
assert := assert2.New(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.True(i.isNotProtected("/test.Service/ignored"))
assert.False(i.isNotProtected("/test.Service/protected")) assert.False(i.isNotProtected("/test.Service/protected"))
assert.True(i.isNotProtected("/test.Service/other")) assert.True(i.isNotProtected("/test.Service/other"))
@ -37,7 +37,7 @@ func TestProtectedAndIgnored(t *testing.T) {
func TestProtectedByDefault(t *testing.T) { func TestProtectedByDefault(t *testing.T) {
i := &interceptor{} 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/method/cannotExists"))
assert2.False(t, i.isNotProtected("/test.Service/validMethod")) assert2.False(t, i.isNotProtected("/test.Service/validMethod"))
} }
@ -99,14 +99,14 @@ func TestChainedAuthFuncs(t *testing.T) {
name: "empty bearer", name: "empty bearer",
auth: "bearer ", auth: "bearer ",
err: true, err: true,
code: codes.PermissionDenied, code: codes.Unauthenticated,
}, },
{ {
name: "internal error", name: "internal error",
auth: "bearer internal", auth: "bearer internal",
internalError: true, internalError: true,
err: true, err: true,
code: codes.PermissionDenied, code: codes.Internal,
}, },
{ {
name: "multiple auth: first basic valid", name: "multiple auth: first basic valid",
@ -120,13 +120,13 @@ func TestChainedAuthFuncs(t *testing.T) {
name: "invalid auth: bearer", name: "invalid auth: bearer",
auth: "bearer noop", auth: "bearer noop",
err: true, err: true,
code: codes.PermissionDenied, code: codes.Unauthenticated,
}, },
{ {
name: "invalid auth: basic", name: "invalid auth: basic",
auth: BasicAuth("other", "other"), auth: BasicAuth("other", "other"),
err: true, err: true,
code: codes.PermissionDenied, code: codes.Unauthenticated,
}, },
} }

View File

@ -1,17 +1,19 @@
package auth package auth
import ( 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) 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 { func WithMethods(methods ...string) Option {
return func(o *options) { return func(o *options) {
o.methods = append(o.methods, methods...) 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 { func WithIgnoredMethods(methods ...string) Option {
return func(o *options) { return func(o *options) {
o.ignoredMethods = append(o.ignoredMethods, methods...) o.ignoredMethods = append(o.ignoredMethods, methods...)

View File

@ -3,10 +3,10 @@ package auth
import ( import (
"context" "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-toolkit/interceptors"
"go.linka.cloud/grpc/interceptors/metadata" "go.linka.cloud/grpc-toolkit/interceptors/metadata"
) )
type TokenValidator func(ctx context.Context, token string) (context.Context, error) 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 { 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 ( import (
"context" "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/credentials"
"google.golang.org/grpc/peer" "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) 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" "google.golang.org/grpc"
"go.linka.cloud/grpc/interceptors" "go.linka.cloud/grpc-toolkit/interceptors"
) )
type interceptor struct{} 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 package interceptors
import ( import (
"context"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
@ -18,3 +20,16 @@ type Interceptors interface {
ServerInterceptors ServerInterceptors
ClientInterceptors 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"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"go.linka.cloud/grpc/interceptors" "go.linka.cloud/grpc-toolkit/interceptors"
) )
func NewInterceptors(pairs ...string) interceptors.Interceptors { func NewInterceptors(pairs ...string) interceptors.Interceptors {
@ -37,14 +37,14 @@ func (i mdInterceptors) StreamServerInterceptor() grpc.StreamServerInterceptor {
func (i mdInterceptors) UnaryClientInterceptor() grpc.UnaryClientInterceptor { 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 { 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...) return invoker(ctx, method, req, reply, cc, opts...)
} }
} }
func (i mdInterceptors) StreamClientInterceptor() grpc.StreamClientInterceptor { 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) { 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...) return streamer(ctx, desc, cc, method, opts...)
} }
} }

View File

@ -1,14 +1,24 @@
package metrics package metrics
import ( 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" "github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel/trace"
"google.golang.org/grpc" "google.golang.org/grpc"
"go.linka.cloud/grpc/interceptors" "go.linka.cloud/grpc-toolkit/interceptors"
"go.linka.cloud/grpc/service" "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 { type Registerer interface {
Register(svc service.Service) Register(svc service.Service)
} }
@ -22,7 +32,6 @@ type ServerInterceptors interface {
Registerer Registerer
interceptors.ServerInterceptors interceptors.ServerInterceptors
prometheus.Collector prometheus.Collector
EnableHandlingTimeHistogram(opts ...grpc_prometheus.HistogramOption)
} }
type ClientInterceptors interface { type ClientInterceptors interface {
@ -32,12 +41,7 @@ type ClientInterceptors interface {
type metrics struct { type metrics struct {
s *grpc_prometheus.ServerMetrics s *grpc_prometheus.ServerMetrics
c *grpc_prometheus.ClientMetrics c *grpc_prometheus.ClientMetrics
} o *options
func (m *metrics) EnableHandlingTimeHistogram(opts ...grpc_prometheus.HistogramOption) {
if m.s != nil {
m.s.EnableHandlingTimeHistogram(opts...)
}
} }
func (m *metrics) Describe(descs chan<- *prometheus.Desc) { func (m *metrics) Describe(descs chan<- *prometheus.Desc) {
@ -58,36 +62,45 @@ func (m *metrics) Register(svc service.Service) {
} }
} }
func NewInterceptors(opts ...grpc_prometheus.CounterOption) Interceptors { func NewInterceptors(opts ...Option) Interceptors {
s := grpc_prometheus.NewServerMetrics(opts...) o := (&options{}).apply(opts...)
c := grpc_prometheus.NewClientMetrics(opts...) s := grpc_prometheus.NewServerMetrics(
return &metrics{s: s, c: c} 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 { func NewServerInterceptors(opts ...Option) ServerInterceptors {
s := grpc_prometheus.NewServerMetrics(opts...) o := (&options{}).apply(opts...)
return &metrics{s: s} 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 { func NewClientInterceptors(opts ...Option) ClientInterceptors {
c := grpc_prometheus.NewClientMetrics(opts...) o := (&options{}).apply(opts...)
return &metrics{c: c} c := grpc_prometheus.NewClientMetrics(
} grpc_prometheus.WithClientCounterOptions(o.copts...),
grpc_prometheus.WithClientHandlingTimeHistogram(o.hopts...),
func DefaultInterceptors() Interceptors { )
return &metrics{s: grpc_prometheus.DefaultServerMetrics, c: grpc_prometheus.DefaultClientMetrics} m := &metrics{c: c, o: o}
} o.reg.MustRegister(m)
return m
func DefaultServerInterceptors() ServerInterceptors {
return &metrics{s: grpc_prometheus.DefaultServerMetrics}
}
func DefaultClientInterceptors() ClientInterceptors {
return &metrics{c: grpc_prometheus.DefaultClientMetrics}
} }
func (m *metrics) UnaryServerInterceptor() grpc.UnaryServerInterceptor { 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 { 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" grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
"google.golang.org/grpc" "google.golang.org/grpc"
"go.linka.cloud/grpc/interceptors" "go.linka.cloud/grpc-toolkit/interceptors"
) )
func NewInterceptors(opts ...grpc_recovery.Option) interceptors.ServerInterceptors { func NewInterceptors(opts ...grpc_recovery.Option) interceptors.ServerInterceptors {
@ -22,11 +22,3 @@ func (i *recovery) UnaryServerInterceptor() grpc.UnaryServerInterceptor {
func (i *recovery) StreamServerInterceptor() grpc.StreamServerInterceptor { func (i *recovery) StreamServerInterceptor() grpc.StreamServerInterceptor {
return grpc_recovery.StreamServerInterceptor(i.opts...) 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" grpc_sentry "github.com/johnbellone/grpc-middleware-sentry"
"go.linka.cloud/grpc/interceptors" "go.linka.cloud/grpc-toolkit/interceptors"
) )
type interceptor struct { type interceptor struct {

View File

@ -1,41 +1,40 @@
package tracing package tracing
import ( import (
"github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"github.com/opentracing/opentracing-go"
"google.golang.org/grpc" "google.golang.org/grpc"
"go.linka.cloud/grpc/interceptors" "go.linka.cloud/grpc-toolkit/interceptors"
) )
type tracing struct { 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} return tracing{opts: opts}
} }
func NewClientInterceptors(opts ...otgrpc.Option) interceptors.ClientInterceptors { func NewClientInterceptors(opts ...otelgrpc.Option) interceptors.ClientInterceptors {
return tracing{opts: opts} return tracing{opts: opts}
} }
func (t tracing) UnaryClientInterceptor() grpc.UnaryClientInterceptor { func (t tracing) UnaryClientInterceptor() grpc.UnaryClientInterceptor {
return otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer(), t.opts...) return otelgrpc.UnaryClientInterceptor(t.opts...)
} }
func (t tracing) StreamClientInterceptor() grpc.StreamClientInterceptor { 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} return tracing{opts: opts}
} }
func (t tracing) UnaryServerInterceptor() grpc.UnaryServerInterceptor { func (t tracing) UnaryServerInterceptor() grpc.UnaryServerInterceptor {
return otgrpc.OpenTracingServerInterceptor(opentracing.GlobalTracer(), t.opts...) return otelgrpc.UnaryServerInterceptor(t.opts...)
} }
func (t tracing) StreamServerInterceptor() grpc.StreamServerInterceptor { 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/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/protobuf/proto"
"go.linka.cloud/grpc/errors" "go.linka.cloud/grpc-toolkit/errors"
"go.linka.cloud/grpc/interceptors" "go.linka.cloud/grpc-toolkit/interceptors"
) )
type validatorAll interface { type validatorAll interface {
@ -39,10 +38,24 @@ type validatorError interface {
ErrorName() string ErrorName() string
} }
func validatorErrorToGrpc(e validatorError) *errdetails.BadRequest_FieldViolation { func validatorErrorToGrpc(e validatorError, prefix string) []*errdetails.BadRequest_FieldViolation {
return &errdetails.BadRequest_FieldViolation{ // check nested errors for validation error, e.g. "embedded message failed validation"
Field: e.Field(), switch v := e.Cause().(type) {
Description: e.Reason(), 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) { switch v := err.(type) {
case validatorError: case validatorError:
return errors.InvalidArgumentd(err, validatorErrorToGrpc(v)) return errors.InvalidArgumentd(err, &errdetails.BadRequest{FieldViolations: validatorErrorToGrpc(v, "")})
case validatorMultiError: case validatorMultiError:
var details []proto.Message details := &errdetails.BadRequest{}
for _, v := range v.AllErrors() { for _, v := range v.AllErrors() {
if d, ok := v.(validatorError); ok { 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: default:
return errors.InvalidArgument(err) 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 { func From(ctx context.Context) Logger {
log, ok := ctx.Value(log{}).(Logger) log, ok := ctx.Value(log{}).(Logger)
if ok { if ok {
return log return log.WithContext(ctx)
} }
if defaultLogger != nil { if defaultLogger != nil {
return defaultLogger return defaultLogger
@ -42,5 +42,5 @@ func From(ctx context.Context) Logger {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
defaultLogger = logr defaultLogger = logr
return defaultLogger return defaultLogger.WithContext(ctx)
} }

View File

@ -1,10 +1,14 @@
package logger package logger
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"path/filepath"
"runtime"
"strings"
"github.com/bombsimon/logrusr/v2" "github.com/bombsimon/logrusr/v4"
"github.com/go-logr/logr" "github.com/go-logr/logr"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -13,6 +17,27 @@ var (
standardLogger Logger = &logger{fl: logrus.StandardLogger()} 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 { func StandardLogger() Logger {
return standardLogger return standardLogger
} }
@ -21,14 +46,27 @@ func New() Logger {
return &logger{fl: logrus.New()} return &logger{fl: logrus.New()}
} }
func FromLogrus(fl logrus.Ext1FieldLogger) Logger {
return &logger{fl: fl}
}
type Level = logrus.Level
type Logger interface { type Logger interface {
WithContext(ctx context.Context) Logger
WithReportCaller(b bool, depth ...uint) Logger
WithField(key string, value interface{}) Logger WithField(key string, value interface{}) Logger
WithFields(kv ...interface{}) Logger WithFields(kv ...interface{}) Logger
WithError(err error) Logger WithError(err error) Logger
SetLevel(level logrus.Level) Logger SetLevel(level Level) Logger
WriterLevel(level logrus.Level) *io.PipeWriter WriterLevel(level Level) *io.PipeWriter
SetOutput(w io.Writer) Logger
Tracef(format string, args ...interface{})
Debugf(format string, args ...interface{}) Debugf(format string, args ...interface{})
Infof(format string, args ...interface{}) Infof(format string, args ...interface{})
Printf(format string, args ...interface{}) Printf(format string, args ...interface{})
@ -38,6 +76,7 @@ type Logger interface {
Fatalf(format string, args ...interface{}) Fatalf(format string, args ...interface{})
Panicf(format string, args ...interface{}) Panicf(format string, args ...interface{})
Trace(args ...interface{})
Debug(args ...interface{}) Debug(args ...interface{})
Info(args ...interface{}) Info(args ...interface{})
Print(args ...interface{}) Print(args ...interface{})
@ -47,6 +86,7 @@ type Logger interface {
Fatal(args ...interface{}) Fatal(args ...interface{})
Panic(args ...interface{}) Panic(args ...interface{})
Traceln(args ...interface{})
Debugln(args ...interface{}) Debugln(args ...interface{})
Infoln(args ...interface{}) Infoln(args ...interface{})
Println(args ...interface{}) Println(args ...interface{})
@ -57,146 +97,240 @@ type Logger interface {
Panicln(args ...interface{}) Panicln(args ...interface{})
Logr() logr.Logger Logr() logr.Logger
FieldLogger() logrus.FieldLogger
Logger() *logrus.Logger
Clone() Logger
} }
type logger struct { 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{}) { 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{}) { 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{}) { 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{}) { 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{}) { 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{}) { 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{}) { 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{}) { 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{}) { func (l *logger) Debug(args ...interface{}) {
l.fl.Debug(args...) l.withCaller().Debug(args...)
} }
func (l *logger) Info(args ...interface{}) { func (l *logger) Info(args ...interface{}) {
l.fl.Info(args...) l.withCaller().Info(args...)
} }
func (l *logger) Print(args ...interface{}) { func (l *logger) Print(args ...interface{}) {
l.fl.Print(args...) l.withCaller().Print(args...)
} }
func (l *logger) Warn(args ...interface{}) { func (l *logger) Warn(args ...interface{}) {
l.fl.Warn(args...) l.withCaller().Warn(args...)
} }
func (l *logger) Warning(args ...interface{}) { func (l *logger) Warning(args ...interface{}) {
l.fl.Warning(args...) l.withCaller().Warning(args...)
} }
func (l *logger) Error(args ...interface{}) { func (l *logger) Error(args ...interface{}) {
l.fl.Error(args...) l.withCaller().Error(args...)
} }
func (l *logger) Fatal(args ...interface{}) { func (l *logger) Fatal(args ...interface{}) {
l.fl.Fatal(args...) l.withCaller().Fatal(args...)
} }
func (l *logger) Panic(args ...interface{}) { 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{}) { func (l *logger) Debugln(args ...interface{}) {
l.fl.Debugln(args...) l.withCaller().Debugln(args...)
} }
func (l *logger) Infoln(args ...interface{}) { func (l *logger) Infoln(args ...interface{}) {
l.fl.Infoln(args...) l.withCaller().Infoln(args...)
} }
func (l *logger) Println(args ...interface{}) { func (l *logger) Println(args ...interface{}) {
l.fl.Println(args...) l.withCaller().Println(args...)
} }
func (l *logger) Warnln(args ...interface{}) { func (l *logger) Warnln(args ...interface{}) {
l.fl.Warnln(args...) l.withCaller().Warnln(args...)
} }
func (l *logger) Warningln(args ...interface{}) { func (l *logger) Warningln(args ...interface{}) {
l.fl.Warningln(args...) l.withCaller().Warningln(args...)
} }
func (l *logger) Errorln(args ...interface{}) { func (l *logger) Errorln(args ...interface{}) {
l.fl.Errorln(args...) l.withCaller().Errorln(args...)
} }
func (l *logger) Fatalln(args ...interface{}) { func (l *logger) Fatalln(args ...interface{}) {
l.fl.Fatalln(args...) l.withCaller().Fatalln(args...)
} }
func (l *logger) Panicln(args ...interface{}) { 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 {
switch t := l.fl.(type) { return l.Logger().WriterLevel(level)
case *logrus.Logger:
return t.WriterLevel(level)
case *logrus.Entry:
return t.WriterLevel(level)
}
panic(fmt.Sprintf("unexpected logger type %T", l.fl))
} }
func (l *logger) SetLevel(level logrus.Level) Logger { 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) { switch t := l.fl.(type) {
case *logrus.Logger: case *logrus.Logger:
t.SetLevel(level) return &logger{fl: t.WithContext(ctx), reportCaller: l.reportCaller}
return l
case *logrus.Entry: case *logrus.Entry:
t.Logger.SetLevel(level) return &logger{fl: t.WithContext(ctx), reportCaller: l.reportCaller}
return l
} }
panic(fmt.Sprintf("unexpected logger type %T", l.fl)) panic(fmt.Sprintf("unexpected logger type %T", l.fl))
} }
func (l *logger) WithField(key string, value interface{}) Logger { 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 { func (l *logger) WithFields(kv ...interface{}) Logger {
log := &logger{fl: l.fl} log := &logger{fl: l.fl}
for i := 0; i < len(kv); i += 2 { 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 return log
} }
func (l *logger) WithError(err error) Logger { 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 { func (l *logger) Logr() logr.Logger {
return logrusr.New(l.fl) 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)
}
}

View File

@ -15,7 +15,6 @@
package react package react
import ( import (
"embed"
"io/fs" "io/fs"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
@ -24,14 +23,18 @@ import (
"strings" "strings"
) )
func NewHandler(dir embed.FS, subpath string) (http.Handler, error) { const (
if e := os.Getenv("REACT_ENDPOINT"); e != "" { EndpointEnv = "REACT_ENDPOINT"
)
func NewHandler(dir fs.FS, subpath string) (http.Handler, error) {
if e := os.Getenv(EndpointEnv); e != "" {
return newProxy(e) return newProxy(e)
} }
return newStatic(dir, subpath) return newStatic(dir, subpath)
} }
func newStatic(dir embed.FS, subpath string) (http.Handler, error) { func newStatic(dir fs.FS, subpath string) (http.Handler, error) {
s, err := fs.Sub(dir, subpath) s, err := fs.Sub(dir, subpath)
if err != nil { if err != nil {
return nil, err return nil, err
@ -53,3 +56,7 @@ func newProxy(endpoint string) (http.Handler, error) {
p := httputil.NewSingleHostReverseProxy(u) p := httputil.NewSingleHostReverseProxy(u)
return p, nil return p, nil
} }
func DevEnv() bool {
return os.Getenv(EndpointEnv) != ""
}

View File

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

View File

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

View File

@ -194,7 +194,7 @@ func (s *Server) recv(c *net.UDPConn) {
continue continue
} }
if err := s.parsePacket(buf[:n], from); err != nil { 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 { func (s *Server) parsePacket(packet []byte, from net.Addr) error {
var msg dns.Msg var msg dns.Msg
if err := msg.Unpack(packet); err != nil { 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 return err
} }
// TODO: This is a bit of a hack // TODO: This is a bit of a hack

View File

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

View File

@ -5,7 +5,7 @@ import (
"google.golang.org/grpc/resolver" "google.golang.org/grpc/resolver"
"go.linka.cloud/grpc/registry" "go.linka.cloud/grpc-toolkit/registry"
) )
func New(reg registry.Registry) resolver.Builder { func New(reg registry.Registry) resolver.Builder {
@ -35,11 +35,11 @@ type resolvr struct {
func (r *resolvr) run() { func (r *resolvr) run() {
if r.reg.String() == "noop" { 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 return
} }
var name, version string var name, version string
parts := strings.Split(r.target.Endpoint, ":") parts := strings.Split(r.target.Endpoint(), ":")
name = parts[0] name = parts[0]
if len(parts) > 1 { if len(parts) > 1 {
version = parts[1] version = parts[1]
@ -57,7 +57,7 @@ func (r *resolvr) run() {
} }
} }
r.cc.UpdateState(resolver.State{Addresses: r.addrs}) 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 { if err != nil {
return return
} }
@ -80,7 +80,7 @@ func (r *resolvr) run() {
func (r *resolvr) ResolveNow(options resolver.ResolveNowOptions) { func (r *resolvr) ResolveNow(options resolver.ResolveNowOptions) {
if r.reg.String() == "noop" { 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" "fmt"
"strings" "strings"
env "github.com/caitlinelfring/go-env-default" "github.com/caitlinelfring/go-env-default"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
@ -17,6 +17,10 @@ const (
caCert = "ca-cert" caCert = "ca-cert"
serverCert = "server-cert" serverCert = "server-cert"
serverKey = "server-key" serverKey = "server-key"
clientCACert = "client-ca-cert"
clientCert = "client-cert"
clientKey = "client-key"
) )
var u = strings.ToUpper 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(&optCACert, caCert, "", "Path to Root CA certificate"+flagEnv(caCert))
flags.StringVar(&optCert, serverCert, "", "Path to Server certificate"+flagEnv(serverCert)) flags.StringVar(&optCert, serverCert, "", "Path to Server certificate"+flagEnv(serverCert))
flags.StringVar(&optKey, serverKey, "", "Path to Server key"+flagEnv(serverKey)) 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) { return flags, func(o *options) {
o.address = optAddress o.address = optAddress
o.secure = !optInsecure o.secure = !optInsecure
o.reflection = optReflection o.reflection = optReflection
// o.caCert = optCACert o.caCert = optCACert
// o.cert = optCert o.cert = optCert
// o.key = optKey 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 return nil
} }
mux := runtime.NewServeMux(append(defaultGatewayOptions, opts...)...) 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 return err
} }
if s.opts.gatewayPrefix != "" { 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 { } else {
s.opts.mux.Handle("/", wsproxy.WebsocketProxy(mux)) s.lazyMux().Handle("/", wsproxy.WebsocketProxy(mux))
} }
return nil 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/tls"
"crypto/x509" "crypto/x509"
"fmt" "fmt"
"io/ioutil" "io/fs"
"net" "net"
"os"
"strings" "strings"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/improbable-eng/grpc-web/go/grpcweb"
"github.com/jinzhu/gorm"
"github.com/rs/cors" "github.com/rs/cors"
"go.uber.org/multierr" "github.com/traefik/grpc-web/go/grpcweb"
"google.golang.org/grpc" "google.golang.org/grpc"
"go.linka.cloud/grpc/certs" "go.linka.cloud/grpc-toolkit/certs"
"go.linka.cloud/grpc/interceptors" "go.linka.cloud/grpc-toolkit/interceptors"
"go.linka.cloud/grpc/registry" "go.linka.cloud/grpc-toolkit/registry"
"go.linka.cloud/grpc/transport" "go.linka.cloud/grpc-toolkit/transport"
"go.linka.cloud/grpc/utils/addr" "go.linka.cloud/grpc-toolkit/utils/addr"
) )
/* var _ Options = (*options)(nil)
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
*/
type RegisterGatewayFunc func(ctx context.Context, mux *runtime.ServeMux, cc grpc.ClientConnInterface) error type RegisterGatewayFunc func(ctx context.Context, mux *runtime.ServeMux, cc grpc.ClientConnInterface) error
@ -66,13 +38,14 @@ type Options interface {
CACert() string CACert() string
Cert() string Cert() string
Key() string Key() string
ClientCACert() string
ClientCert() string
ClientKey() string
TLSConfig() *tls.Config TLSConfig() *tls.Config
Secure() bool Secure() bool
Registry() registry.Registry Registry() registry.Registry
DB() *gorm.DB
BeforeStart() []func() error BeforeStart() []func() error
AfterStart() []func() error AfterStart() []func() error
BeforeStop() []func() error BeforeStop() []func() error
@ -97,6 +70,9 @@ type Options interface {
// TODO(adphi): metrics + tracing // TODO(adphi): metrics + tracing
WithoutCmux() bool
ProxyProtocol() bool
Default() Default()
} }
@ -118,7 +94,6 @@ func (o *options) Default() {
if o.transport == nil { if o.transport == nil {
o.transport = &grpc.Server{} o.transport = &grpc.Server{}
} }
} }
type Option func(*options) 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 { func WithReflection(r bool) Option {
return func(o *options) { return func(o *options) {
o.reflection = r o.reflection = r
@ -199,11 +183,21 @@ func WithKey(path string) Option {
} }
} }
func WithDB(dialect string, args ...interface{}) Option { func WithClientCACert(path string) Option {
db, err := gorm.Open(dialect, args...)
return func(o *options) { return func(o *options) {
o.db = db o.clientCACert = path
o.error = multierr.Append(o.error, err) }
}
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,22 +346,49 @@ 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 { type options struct {
ctx context.Context ctx context.Context
name string name string
version string version string
address string address string
lis net.Listener
reflection bool reflection bool
health bool health bool
secure bool secure bool
caCert string caCert string
cert string cert string
key string key string
tlsConfig *tls.Config clientCACert string
clientCert string
db *gorm.DB clientKey string
tlsConfig *tls.Config
transport transport.Transport transport transport.Transport
registry registry.Registry registry registry.Registry
@ -394,8 +415,15 @@ type options struct {
gatewayOpts []runtime.ServeMuxOption gatewayOpts []runtime.ServeMuxOption
cors cors.Options cors cors.Options
error error reactUI fs.FS
gatewayPrefix string reactUISubPath string
hasReactUI bool
error error
gatewayPrefix string
withoutCmux bool
proxyProtocol bool
proxyProtocolAddrs []string
} }
func (o *options) Name() string { func (o *options) Name() string {
@ -438,6 +466,18 @@ func (o *options) Key() string {
return o.key 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 { func (o *options) TLSConfig() *tls.Config {
return o.tlsConfig return o.tlsConfig
} }
@ -446,10 +486,6 @@ func (o *options) Secure() bool {
return o.secure return o.secure
} }
func (o *options) DB() *gorm.DB {
return o.db
}
func (o *options) BeforeStart() []func() error { func (o *options) BeforeStart() []func() error {
return o.beforeStart return o.beforeStart
} }
@ -518,10 +554,19 @@ func (o *options) GatewayOpts() []runtime.ServeMuxOption {
return o.gatewayOpts return o.gatewayOpts
} }
func (o *options) WithoutCmux() bool {
return o.withoutCmux
}
func (o *options) ProxyProtocol() bool {
return o.proxyProtocol
}
func (o *options) parseTLSConfig() error { func (o *options) parseTLSConfig() error {
if o.tlsConfig != nil { if o.tlsConfig != nil {
return nil return nil
} }
nextProtos := []string{"h2", "h2c", "http/1.1", "acme-tls/1"}
if !o.hasTLSConfig() { if !o.hasTLSConfig() {
if !o.secure { if !o.secure {
return nil return nil
@ -549,10 +594,11 @@ func (o *options) parseTLSConfig() error {
o.tlsConfig = &tls.Config{ o.tlsConfig = &tls.Config{
Certificates: []tls.Certificate{cert}, Certificates: []tls.Certificate{cert},
ClientAuth: tls.NoClientCert, ClientAuth: tls.NoClientCert,
NextProtos: nextProtos,
} }
return nil return nil
} }
caCert, err := ioutil.ReadFile(o.caCert) caCert, err := os.ReadFile(o.caCert)
if err != nil { if err != nil {
return err return err
} }
@ -568,10 +614,34 @@ func (o *options) parseTLSConfig() error {
o.tlsConfig = &tls.Config{ o.tlsConfig = &tls.Config{
Certificates: []tls.Certificate{cert}, Certificates: []tls.Certificate{cert},
RootCAs: caCertPool, 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 return nil
} }
func (o *options) hasTLSConfig() bool { 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" "strings"
"time" "time"
"go.linka.cloud/grpc/registry" "go.linka.cloud/grpc-toolkit/registry"
"go.linka.cloud/grpc/utils/addr" "go.linka.cloud/grpc-toolkit/utils/addr"
"go.linka.cloud/grpc/utils/backoff" "go.linka.cloud/grpc-toolkit/utils/backoff"
net2 "go.linka.cloud/grpc/utils/net" net2 "go.linka.cloud/grpc-toolkit/utils/net"
) )
func (s *service) register() error { func (s *service) register() error {

View File

@ -17,28 +17,31 @@ import (
"github.com/fullstorydev/grpchan/inprocgrpc" "github.com/fullstorydev/grpchan/inprocgrpc"
"github.com/google/uuid" "github.com/google/uuid"
grpcmiddleware "github.com/grpc-ecosystem/go-grpc-middleware" grpcmiddleware "github.com/grpc-ecosystem/go-grpc-middleware"
"github.com/jinzhu/gorm"
"github.com/justinas/alice" "github.com/justinas/alice"
"github.com/pires/go-proxyproto"
"github.com/rs/cors" "github.com/rs/cors"
"github.com/sirupsen/logrus"
"github.com/soheilhy/cmux" "github.com/soheilhy/cmux"
"go.uber.org/multierr" "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"
"google.golang.org/grpc/health" "google.golang.org/grpc/health"
"google.golang.org/grpc/health/grpc_health_v1" "google.golang.org/grpc/health/grpc_health_v1"
greflect "google.golang.org/grpc/reflection" greflect "google.golang.org/grpc/reflection"
"go.linka.cloud/grpc/interceptors/metadata" "go.linka.cloud/grpc-toolkit/internal/injectlogger"
"go.linka.cloud/grpc/registry" "go.linka.cloud/grpc-toolkit/logger"
"go.linka.cloud/grpc/registry/noop" "go.linka.cloud/grpc-toolkit/registry"
"go.linka.cloud/grpc-toolkit/registry/noop"
) )
type Service interface { type Service interface {
greflect.GRPCServer greflect.GRPCServer
Options() Options Options() Options
DB() *gorm.DB
Start() error Start() error
Serve(lis net.Listener) error
Stop() error Stop() error
Close() error Close() error
} }
@ -59,8 +62,11 @@ type service struct {
inproc *inprocgrpc.Channel inproc *inprocgrpc.Channel
services map[string]*serviceInfo services map[string]*serviceInfo
healthServer *health.Server
id string id string
regSvc *registry.Service regSvc *registry.Service
o sync.Once
closed chan struct{} closed chan struct{}
} }
@ -76,32 +82,41 @@ func newService(opts ...Option) (*service, error) {
for _, f := range opts { for _, f := range opts {
f(s.opts) f(s.opts)
} }
if s.opts.name != "" { s.opts.ctx, s.cancel = context.WithCancel(s.opts.ctx)
i := metadata.NewInterceptors("grpc-service-name", s.opts.name)
s.opts.unaryServerInterceptors = append([]grpc.UnaryServerInterceptor{i.UnaryServerInterceptor()}, s.opts.unaryServerInterceptors...) md := md(s.opts)
s.opts.unaryClientInterceptors = append([]grpc.UnaryClientInterceptor{i.UnaryClientInterceptor()}, s.opts.unaryClientInterceptors...) if md != nil {
s.opts.streamServerInterceptors = append([]grpc.StreamServerInterceptor{i.StreamServerInterceptor()}, s.opts.streamServerInterceptors...) s.opts.unaryServerInterceptors = append([]grpc.UnaryServerInterceptor{
s.opts.streamClientInterceptors = append([]grpc.StreamClientInterceptor{i.StreamClientInterceptor()}, s.opts.streamClientInterceptors...) md.UnaryServerInterceptor(),
} injectlogger.New(s.opts.ctx).UnaryServerInterceptor()},
if s.opts.version != "" { s.opts.unaryServerInterceptors...,
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{
s.opts.unaryClientInterceptors = append([]grpc.UnaryClientInterceptor{i.UnaryClientInterceptor()}, s.opts.unaryClientInterceptors...) md.UnaryClientInterceptor(),
s.opts.streamServerInterceptors = append([]grpc.StreamServerInterceptor{i.StreamServerInterceptor()}, s.opts.streamServerInterceptors...) injectlogger.New(s.opts.ctx).UnaryClientInterceptor()},
s.opts.streamClientInterceptors = append([]grpc.StreamClientInterceptor{i.StreamClientInterceptor()}, s.opts.streamClientInterceptors...) s.opts.unaryClientInterceptors...,
} )
if s.opts.mux == nil { s.opts.streamServerInterceptors = append([]grpc.StreamServerInterceptor{
s.opts.mux = http.NewServeMux() 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 { if s.opts.error != nil {
return nil, s.opts.error return nil, s.opts.error
} }
s.opts.ctx, s.cancel = context.WithCancel(s.opts.ctx)
go func() { go func() {
for { for {
select { select {
case <-s.opts.ctx.Done(): case <-s.opts.ctx.Done():
s.Stop() s.Stop()
return
} }
} }
}() }()
@ -128,11 +143,15 @@ func newService(opts ...Option) (*service, error) {
greflect.Register(s.server) greflect.Register(s.server)
} }
if s.opts.health { 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 { if err := s.gateway(s.opts.gatewayOpts...); err != nil {
return nil, err 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 // we do not configure grpc web here as the grpc handlers are not yet registered
return s, nil return s, nil
} }
@ -141,49 +160,75 @@ func (s *service) Options() Options {
return s.opts return s.opts
} }
func (s *service) DB() *gorm.DB { func (s *service) start() (*errgroup.Group, error) {
return s.opts.db
}
func (s *service) run() error {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock()
s.closed = make(chan struct{}) s.closed = make(chan struct{})
// configure grpc web now that we are ready to go // configure grpc web now that we are ready to go
if err := s.grpcWeb(s.opts.grpcWebOpts...); err != nil { 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 err != nil { if strings.HasPrefix(s.opts.address, "unix://") {
return err network = "unix"
} s.opts.address = strings.TrimPrefix(s.opts.address, "unix://")
if s.opts.tlsConfig != nil {
lis = tls.NewListener(lis, s.opts.tlsConfig)
} }
s.opts.address = lis.Addr().String() if s.opts.lis == nil {
lis, err := net.Listen(network, s.opts.address)
if err != nil {
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) if s.opts.proxyProtocol {
mux.SetReadTimeout(5 * time.Second) p := func(upstream net.Addr) (proxyproto.Policy, error) {
u, _, err := net.SplitHostPort(upstream.String())
gLis := mux.MatchWithWriters(cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc")) if err != nil {
hList := mux.Match(cmux.Any()) 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 { for i := range s.opts.beforeStart {
if err := s.opts.beforeStart[i](); err != nil { if err := s.opts.beforeStart[i](); err != nil {
s.mu.Unlock() return nil, err
return err
} }
} }
if err := s.register(); err != nil { if err := s.register(); err != nil {
return err return nil, err
} }
s.running = true s.running = true
errs := make(chan error, 3)
if reflect.DeepEqual(s.opts.cors, cors.Options{}) { if reflect.DeepEqual(s.opts.cors, cors.Options{}) {
s.opts.cors = cors.Options{ s.opts.cors = cors.Options{
AllowedHeaders: []string{"*"}, AllowedHeaders: []string{"*"},
@ -200,59 +245,135 @@ func (s *service) run() error {
AllowCredentials: true, AllowCredentials: true,
} }
} }
hServer := &http.Server{
Handler: alice.New(s.opts.middlewares...).Then(cors.New(s.opts.cors).Handler(s.opts.mux)), fn := s.runWithCmux
if s.opts.withoutCmux || s.opts.mux == nil {
fn = s.runWithoutCmux
} }
if s.opts.Gateway() || s.opts.grpcWeb {
go func() { g, ctx := errgroup.WithContext(s.opts.ctx)
errs <- hServer.Serve(hList) if err := fn(ctx, g); err != nil {
hServer.Shutdown(s.opts.ctx) 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)
}
}() }()
} }
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
}
errs <- err
return
}
errs <- nil
}()
for i := range s.opts.afterStart { for i := range s.opts.afterStart {
if err := s.opts.afterStart[i](); err != nil { if err := s.opts.afterStart[i](); err != nil {
s.mu.Unlock() return nil, err
s.Stop()
return err
} }
} }
s.mu.Unlock() return g, nil
}
func (s *service) run() error {
g, err := s.start()
if err != nil {
return err
}
sigs := s.notify() sigs := s.notify()
errs := make(chan error, 1)
go func() {
errs <- g.Wait()
}()
select { select {
case sig := <-sigs: case sig := <-sigs:
fmt.Println() fmt.Println()
logrus.Warnf("received %v", sig) logger.C(s.opts.ctx).Warnf("received %v", sig)
return s.Close() return s.Close()
case err := <-errs: case err := <-errs:
if err != nil && !ignoreMuxError(err) { if !isMuxError(err) {
logrus.Error(err) logger.C(s.opts.ctx).Error(err)
return err return err
} }
return nil 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 { func (s *service) Start() error {
return s.run() return s.run()
} }
func (s *service) Stop() error { 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() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
if !s.running { if !s.running {
@ -264,9 +385,11 @@ func (s *service) Stop() error {
} }
} }
if err := s.opts.registry.Deregister(s.regSvc); err != nil { 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) defer close(s.closed)
t := time.NewTimer(5 * time.Second)
defer t.Stop()
sigs := s.notify() sigs := s.notify()
done := make(chan struct{}) done := make(chan struct{})
go func() { go func() {
@ -276,14 +399,17 @@ func (s *service) Stop() error {
// catch: Drain() is not implemented // catch: Drain() is not implemented
recover() recover()
}() }()
logrus.Warn("shutting down gracefully") log.Warn("shutting down gracefully")
s.server.GracefulStop() s.server.GracefulStop()
}() }()
select { select {
case <-t.C:
log.Warnf("timeout waiting for server to stop")
s.server.Stop()
case sig := <-sigs: case sig := <-sigs:
fmt.Println() fmt.Println()
logrus.Warnf("received %v", sig) log.Warnf("received %v", sig)
logrus.Warn("forcing shutdown") log.Warn("forcing shutdown")
s.server.Stop() s.server.Stop()
case <-done: case <-done:
} }
@ -294,7 +420,7 @@ func (s *service) Stop() error {
return err return err
} }
} }
logrus.Info("server stopped") log.Info("server stopped")
return nil return nil
} }
@ -319,7 +445,7 @@ func (s *service) registerService(sd *grpc.ServiceDesc, ss interface{}) {
s.inproc.RegisterService(sd, ss) s.inproc.RegisterService(sd, ss)
if _, ok := s.services[sd.ServiceName]; ok { 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{ info := &serviceInfo{
serviceImpl: ss, serviceImpl: ss,
@ -367,23 +493,37 @@ func (s *service) GetServiceInfo() map[string]grpc.ServiceInfo {
func (s *service) Close() error { func (s *service) Close() error {
err := multierr.Combine(s.Stop()) err := multierr.Combine(s.Stop())
if s.opts.db != nil {
err = multierr.Append(s.opts.db.Close(), err)
}
<-s.closed <-s.closed
return err return err
} }
func (s *service) notify() <-chan os.Signal { func (s *service) notify() <-chan os.Signal {
sigs := make(chan os.Signal, 2) 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 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 { 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 true
} }
return strings.Contains(err.Error(), "use of closed network connection") || return false
strings.Contains(err.Error(), "mux: server closed")
} }

View File

@ -4,7 +4,9 @@ import (
"net/http" "net/http"
"time" "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{ var defaultWebOptions = []grpcweb.Option{
@ -26,10 +28,22 @@ func (s *service) grpcWeb(opts ...grpcweb.Option) error {
h := grpcweb.WrapServer(s.server, append(defaultWebOptions, opts...)...) h := grpcweb.WrapServer(s.server, append(defaultWebOptions, opts...)...)
for _, v := range grpcweb.ListGRPCResources(s.server) { for _, v := range grpcweb.ListGRPCResources(s.server) {
if s.opts.grpcWebPrefix != "" { 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 { } else {
s.opts.mux.Handle(v, h) s.lazyMux().Handle(v, h)
} }
} }
return nil 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 ( import (
"google.golang.org/grpc" "google.golang.org/grpc"
"go.linka.cloud/grpc/transport" "go.linka.cloud/grpc-toolkit/transport"
) )
var ( var (

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