# README
interceptor
Common interceptors for client-side and server-side gRPC.
Example of use
import "github.com/go-dev-frame/sponge/pkg/grpc/interceptor"
logging
server-side gRPC
// set unary server logging
func getServerOptions() []grpc.ServerOption {
var options []grpc.ServerOption
option := grpc.ChainUnaryInterceptor(
// if you don't want to log reply data, you can use interceptor.StreamServerSimpleLog instead of interceptor.UnaryServerLog,
interceptor.UnaryServerLog(
logger.Get(),
interceptor.WithReplaceGRPCLogger(),
//interceptor.WithMarshalFn(fn), // customised marshal function, default is jsonpb.Marshal
//interceptor.WithLogIgnoreMethods(fullMethodNames), // ignore methods logging
//interceptor.WithMaxLen(400), // logging max length, default 300
),
)
options = append(options, option)
return options
}
// you can also set stream server logging
client-side gRPC
// set unary client logging
func getDialOptions() []grpc.DialOption {
var options []grpc.DialOption
option := grpc.WithChainUnaryInterceptor(
interceptor.UnaryClientLog(
logger.Get(),
interceptor.WithReplaceGRPCLogger(),
),
)
options = append(options, option)
return options
}
// you can also set stream client logging
recovery
server-side gRPC
func getServerOptions() []grpc.ServerOption {
var options []grpc.ServerOption
option := grpc.ChainUnaryInterceptor(
interceptor.UnaryServerRecovery(),
)
options = append(options, option)
return options
}
client-side gRPC
func getDialOptions() []grpc.DialOption {
var options []grpc.DialOption
option := grpc.WithChainUnaryInterceptor(
interceptor.UnaryClientRecovery(),
)
options = append(options, option)
return options
}
retry
client-side gRPC
func getDialOptions() []grpc.DialOption {
var options []grpc.DialOption
// use insecure transfer
options = append(options, grpc.WithTransportCredentials(insecure.NewCredentials()))
// retry
option := grpc.WithChainUnaryInterceptor(
interceptor.UnaryClientRetry(
//middleware.WithRetryTimes(5), // modify the default number of retries to 3 by default
//middleware.WithRetryInterval(100*time.Millisecond), // modify the default retry interval, default 50 milliseconds
//middleware.WithRetryErrCodes(), // add trigger retry error code, default is codes.Internal, codes.DeadlineExceeded, codes.Unavailable
),
)
options = append(options, option)
return options
}
rate limiter
server-side gRPC
func getDialOptions() []grpc.DialOption {
var options []grpc.DialOption
// use insecure transfer
options = append(options, grpc.WithTransportCredentials(insecure.NewCredentials()))
// rate limiter
option := grpc.ChainUnaryInterceptor(
interceptor.UnaryServerRateLimit(
//interceptor.WithWindow(time.Second*5),
//interceptor.WithBucket(200),
//interceptor.WithCPUThreshold(600),
//interceptor.WithCPUQuota(0),
),
)
options = append(options, option)
return options
}
Circuit Breaker
server-side gRPC
func getDialOptions() []grpc.DialOption {
var options []grpc.DialOption
// use insecure transfer
options = append(options, grpc.WithTransportCredentials(insecure.NewCredentials()))
// circuit breaker
option := grpc.ChainUnaryInterceptor(
interceptor.UnaryServerCircuitBreaker(
//interceptor.WithValidCode(codes.DeadlineExceeded), // add error code 4 for circuit breaker
//interceptor.WithUnaryServerDegradeHandler(handler), // add custom degrade handler
),
)
options = append(options, option)
return options
}
timeout
client-side gRPC
func getDialOptions() []grpc.DialOption {
var options []grpc.DialOption
// use insecure transfer
options = append(options, grpc.WithTransportCredentials(insecure.NewCredentials()))
// timeout
option := grpc.WithChainUnaryInterceptor(
interceptor.UnaryClientTimeout(time.Second), // set timeout
)
options = append(options, option)
return options
}
tracing
server-side gRPC
// initialize tracing
func InitTrace(serviceName string) {
exporter, err := tracer.NewJaegerAgentExporter("192.168.3.37", "6831")
if err != nil {
panic(err)
}
resource := tracer.NewResource(
tracer.WithServiceName(serviceName),
tracer.WithEnvironment("dev"),
tracer.WithServiceVersion("demo"),
)
tracer.Init(exporter, resource) // collect all by default
}
// set up trace on the client side
func getDialOptions() []grpc.DialOption {
var options []grpc.DialOption
// use insecure transfer
options = append(options, grpc.WithTransportCredentials(insecure.NewCredentials()))
// use tracing
option := grpc.WithUnaryInterceptor(
interceptor.UnaryClientTracing(),
)
options = append(options, option)
return options
}
// set up trace on the server side
func getServerOptions() []grpc.ServerOption {
var options []grpc.ServerOption
// use tracing
option := grpc.UnaryInterceptor(
interceptor.UnaryServerTracing(),
)
options = append(options, option)
return options
}
// if necessary, you can create a span in the program
func SpanDemo(serviceName string, spanName string, ctx context.Context) {
_, span := otel.Tracer(serviceName).Start(
ctx, spanName,
trace.WithAttributes(attribute.String(spanName, time.Now().String())), // customised attributes
)
defer span.End()
// ......
}
metrics
example metrics.
Request id
server-side gRPC
func getServerOptions() []grpc.ServerOption {
var options []grpc.ServerOption
option := grpc.ChainUnaryInterceptor(
interceptor.UnaryServerRequestID(),
)
options = append(options, option)
return options
}
client-side gRPC
func getDialOptions() []grpc.DialOption {
var options []grpc.DialOption
// use insecure transfer
options = append(options, grpc.WithTransportCredentials(insecure.NewCredentials()))
option := grpc.WithChainUnaryInterceptor(
interceptor.UnaryClientRequestID(),
)
options = append(options, option)
return options
}
jwt authentication
JWT supports two verification methods:
- The default verification method includes fixed fields
uid
andname
in the claim, and supports additional custom verification functions. - The custom verification method allows users to define the claim themselves and also supports additional custom verification functions.
client-side gRPC
package main
import (
"context"
"github.com/go-dev-frame/sponge/pkg/jwt"
"github.com/go-dev-frame/sponge/pkg/grpc/interceptor"
"github.com/go-dev-frame/sponge/pkg/grpc/grpccli"
userV1 "user/api/user/v1"
)
func main() {
ctx := context.Background()
conn, _ := grpccli.NewClient("127.0.0.1:8282")
cli := userV1.NewUserClient(conn)
token := "xxxxxx" // no Bearer prefix
ctx = interceptor.SetJwtTokenToCtx(ctx, token)
req := &userV1.GetUserByIDRequest{Id: 100}
cli.GetByID(ctx, req)
}
server-side gRPC
package main
import (
"context"
"net"
"github.com/go-dev-frame/sponge/pkg/jwt"
"github.com/go-dev-frame/sponge/pkg/grpc/interceptor"
"google.golang.org/grpc"
userV1 "user/api/user/v1"
)
func main() {
list, err := net.Listen("tcp", ":8282")
server := grpc.NewServer(getUnaryServerOptions()...)
userV1.RegisterUserServer(server, &user{})
server.Serve(list)
select{}
}
func getUnaryServerOptions() []grpc.ServerOption {
var options []grpc.ServerOption
// other interceptors ...
options = append(options, grpc.UnaryInterceptor(
interceptor.UnaryServerJwtAuth(
// Choose to use one of the following 4 authorization
// case 1: default authorization
// interceptor.WithDefaultVerify(), // can be ignored
// case 2: default authorization with extra verification
// interceptor.WithDefaultVerify(extraDefaultVerifyFn),
// case 3: custom authorization
// interceptor.WithCustomVerify(),
// case 4: custom authorization with extra verification
// interceptor.WithCustomVerify(extraCustomVerifyFn),
// specify the gRPC API to ignore token verification(full path)
interceptor.WithAuthIgnoreMethods(
"/api.user.v1.User/Register",
"/api.user.v1.User/Login",
),
),
))
return options
}
type user struct {
userV1.UnimplementedUserServer
}
// Login ...
func (s *user) Login(ctx context.Context, req *userV1.LoginRequest) (*userV1.LoginReply, error) {
// check user and password success
// case 1: generate token with default fields
token, err := jwt.GenerateToken("123", "admin")
// case 2: generate token with custom fields
fields := jwt.KV{"id": uint64(100), "name": "tom", "age": 10}
token, err := jwt.GenerateCustomToken(fields)
return &userV1.LoginReply{Token: token},nil
}
// GetByID ...
func (s *user) GetByID(ctx context.Context, req *userV1.GetUserByIDRequest) (*userV1.GetUserByIDReply, error) {
// if token is valid, won't get here, because the interceptor has returned an error message
// if you want get jwt claims, you can use the following code
claims, err := interceptor.GetJwtClaims(ctx)
return &userV1.GetUserByIDReply{},nil
}
func extraDefaultVerifyFn(claims *jwt.Claims, tokenTail10 string) error {
// In addition to jwt certification, additional checks can be customized here.
// err := errors.New("verify failed")
// if claims.Name != "admin" {
// return err
// }
// token := getToken(claims.UID) // from cache or database
// if tokenTail10 != token[len(token)-10:] { return err }
return nil
}
func extraCustomVerifyFn(claims *jwt.CustomClaims, tokenTail10 string) error {
// In addition to jwt certification, additional checks can be customized here.
// err := errors.New("verify failed")
// token, fields := getToken(id) // from cache or database
// if tokenTail10 != token[len(token)-10:] { return err }
// id, exist := claims.GetUint64("id")
// if !exist || id != fields["id"].(uint64) { return err }
// name, exist := claims.GetString("name")
// if !exist || name != fields["name"].(string) { return err }
// age, exist := claims.GetInt("age")
// if !exist || age != fields["age"].(int) { return err }
return nil
}
# Functions
ClientCtxRequestID get request id from rpc client context.Context.
ClientCtxRequestIDField get request id field from rpc client context.Context.
ClientOptionTracing client-side tracing interceptor.
ClientTokenOption client token.
CtxRequestIDField get request id field from context.Context.
GetAuthCtxKey get the name of Claims.
GetAuthorization combining tokens into authentication information.
GetJwtClaims get the jwt default claims from context, contains fixed fields uid and name.
GetJwtCustomClaims get the jwt custom claims from context, contains custom fields.
ServerCtxRequestID get request id from rpc server context.Context.
ServerCtxRequestIDField get request id field from rpc server context.Context.
ServerOptionTracing server-side tracing interceptor.
SetAuthToCtx set the authorization (including prefix Bearer) to the context in grpc client side Example:
ctx := SetAuthToCtx(ctx, authorization) cli.GetByID(ctx, req).
SetContextRequestIDKey set context request id key.
SetJwtTokenToCtx set the token (excluding prefix Bearer) to the context in grpc client side Example:
authorization := "Bearer jwt-token"
ctx := SetJwtTokenToCtx(ctx, authorization) cli.GetByID(ctx, req).
StreamClientCircuitBreaker client-side stream circuit breaker interceptor.
StreamClientLog client log stream interceptor.
StreamClientMetrics client-side metrics stream interceptor.
StreamClientRecovery client-side recovery stream interceptor.
StreamClientRequestID client request id stream interceptor.
StreamClientRetry client-side retry stream interceptor.
StreamClientTimeout server-side timeout interceptor.
StreamClientTracing client-side tracing stream interceptor.
StreamServerCircuitBreaker server-side stream circuit breaker interceptor.
StreamServerJwtAuth jwt stream interceptor.
StreamServerLog Server-side log stream interceptor.
StreamServerMetrics server-side metrics stream interceptor.
StreamServerRateLimit server-side stream circuit breaker interceptor.
StreamServerRecovery recovery stream interceptor.
StreamServerRequestID server-side request id stream interceptor.
StreamServerSimpleLog Server-side log stream interceptor, only print response.
StreamServerToken recovery stream token.
StreamServerTracing server-side tracing stream interceptor.
UnaryClientCircuitBreaker client-side unary circuit breaker interceptor.
UnaryClientLog client log unary interceptor.
UnaryClientMetrics client-side metrics unary interceptor.
UnaryClientRecovery client-side unary recovery.
UnaryClientRequestID client-side request_id unary interceptor.
UnaryClientRetry client-side retry unary interceptor.
UnaryClientTimeout client-side timeout unary interceptor.
UnaryClientTracing client-side tracing unary interceptor.
UnaryServerCircuitBreaker server-side unary circuit breaker interceptor.
UnaryServerJwtAuth jwt unary interceptor.
UnaryServerLog server-side log unary interceptor.
UnaryServerMetrics server-side metrics unary interceptor.
UnaryServerRateLimit server-side unary circuit breaker interceptor.
UnaryServerRecovery recovery unary interceptor.
UnaryServerRequestID server-side request_id unary interceptor.
UnaryServerSimpleLog server-side log unary interceptor, only print response.
UnaryServerToken recovery unary token.
UnaryServerTracing server-side tracing unary interceptor.
WithAuthClaimsName set the key name of the information in ctx for authentication.
WithAuthIgnoreMethods ways to ignore forensics fullMethodName format: /packageName.serviceName/methodName, example /api.userExample.v1.userExampleService/GetByID.
WithAuthScheme set the message prefix for authentication.
WithBucket with bucket size.
WithCPUQuota with real cpu quota(if it can not collect from process correct);.
WithCPUThreshold with cpu threshold.
WithCustomVerify set custom verify type with extra verify function.
WithDefaultVerify set default verify type.
WithGroup with circuit breaker group.
WithLogFields adding a custom print field.
WithLogIgnoreMethods ignore printing methods fullMethodName format: /packageName.serviceName/methodName, example /api.userExample.v1.userExampleService/GetByID.
WithMarshalFn custom response data marshal function.
WithMaxLen logger content max length.
WithReplaceGRPCLogger replace grpc logger v2.
WithRetryErrCodes set the trigger retry error code.
WithRetryInterval set the retry interval from 1 ms to 10 seconds.
WithRetryTimes set number of retries, max 10.
WithStandardVerify set default verify type with extra verify function Deprecated: use WithExtraDefaultVerify instead.
WithUnaryServerDegradeHandler unary server degrade handler function.
WithValidCode rpc code to mark failed.
WithWindow with window size.
WrapServerCtx wrap context, used in grpc server-side.
# Variables
ContextRequestIDKey request id key for context.
ErrLimitExceed is returned when the rate limiter is triggered and the request is rejected due to limit exceeded.
ErrNotAllowed error not allowed.
RequestIDKey request_id.
# Type aliases
AuthOption setting the Authentication Field.
CheckToken check app id and app key Example:
var f CheckToken=func(appID string, appKey string) error{ if appID != targetAppID || appKey != targetAppKey { return status.Errorf(codes.Unauthenticated, "app id or app key checksum failure") } return nil }.
CircuitBreakerOption set the circuit breaker circuitBreakerOptions.
CtxKeyString for context.WithValue key type.
CustomVerifyFn custom verify function, tokenTail10 is the last 10 characters of the token.
ExtraCustomVerifyFn extra custom verify function, tokenTail10 is the last 10 characters of the token.
ExtraDefaultVerifyFn extra default verify function, tokenTail10 is the last 10 characters of the token.
LogOption log settings.
RatelimitOption set the rate limits ratelimitOptions.
RetryOption set the retry retryOptions.
StandardVerifyFn default verify function, tokenTail10 is the last 10 characters of the token.