package
1.12.6
Repository: https://github.com/go-dev-frame/sponge.git
Documentation: pkg.go.dev

# 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 and name 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.

# Structs

KV key value.

# 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.