github.com/koneal2013/storymetadatagenerator@v0.0.0-20230222212341-b170f254daa5/internal/server/grpc.go (about)

     1  package server
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"time"
     7  
     8  	grpcmiddleware "github.com/grpc-ecosystem/go-grpc-middleware"
     9  	grpcauth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
    10  	grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
    11  	grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags"
    12  	"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
    13  	"go.opentelemetry.io/otel"
    14  	otel_codes "go.opentelemetry.io/otel/codes"
    15  	"go.opentelemetry.io/otel/trace"
    16  	"go.uber.org/zap"
    17  	"go.uber.org/zap/zapcore"
    18  	"google.golang.org/grpc"
    19  	"google.golang.org/grpc/codes"
    20  	"google.golang.org/grpc/credentials"
    21  	peer2 "google.golang.org/grpc/peer"
    22  	"google.golang.org/grpc/status"
    23  
    24  	metadata_api_v1 "github.com/koneal2013/storymetadatagenerator/api/v1"
    25  	grpc_api "github.com/koneal2013/storymetadatagenerator/api/v1/grpc"
    26  )
    27  
    28  const (
    29  	objectWildCard         = "*"
    30  	getStoryMetadataAction = "GetStoryMetadata"
    31  )
    32  
    33  type Authorizer interface {
    34  	Authorize(subject, object, action string) error
    35  }
    36  
    37  type GrpcConfig struct {
    38  	Authorizer
    39  }
    40  
    41  func NewGRPCServer(config *GrpcConfig, opts ...grpc.ServerOption) (*grpc.Server, error) {
    42  	logger := zap.L().Named("grpc_server")
    43  	zapOpts := []grpc_zap.Option{
    44  		grpc_zap.WithDurationField(
    45  			func(duration time.Duration) zapcore.Field {
    46  				return zap.Int64("grpc.time_ns", duration.Nanoseconds())
    47  			}),
    48  	}
    49  	opts = append(opts, grpc.StreamInterceptor(
    50  		grpcmiddleware.ChainStreamServer(
    51  			grpc_ctxtags.StreamServerInterceptor(),
    52  			grpc_zap.StreamServerInterceptor(logger, zapOpts...),
    53  			grpcauth.StreamServerInterceptor(authenticate),
    54  			otelgrpc.StreamServerInterceptor(),
    55  		)), grpc.UnaryInterceptor(grpcmiddleware.ChainUnaryServer(
    56  		grpc_zap.UnaryServerInterceptor(logger, zapOpts...),
    57  		grpcauth.UnaryServerInterceptor(authenticate),
    58  		otelgrpc.UnaryServerInterceptor(),
    59  	)))
    60  	gsrv := grpc.NewServer(opts...)
    61  	if srv, err := newGrpcServer(config); err != nil {
    62  		return nil, err
    63  	} else {
    64  		grpc_api.RegisterStorymetadataServer(gsrv, srv)
    65  		return gsrv, nil
    66  	}
    67  }
    68  
    69  type grpcServer struct {
    70  	grpc_api.UnimplementedStorymetadataServer
    71  	*GrpcConfig
    72  	grpcTracer trace.Tracer
    73  }
    74  
    75  func (g grpcServer) GetMetadata(ctx context.Context, request *grpc_api.GetStoryMetadataRequest) (*grpc_api.GetStoryMetadataResponse, error) {
    76  	_, span := g.grpcTracer.Start(ctx, "GetMetadata")
    77  	defer span.End()
    78  	if err := g.Authorizer.Authorize(subject(ctx), objectWildCard, getStoryMetadataAction); err != nil {
    79  		span.RecordError(err)
    80  		span.SetStatus(otel_codes.Error, err.Error())
    81  		return nil, err
    82  	}
    83  	storyMetadata := metadata_api_v1.New(int(request.NumberOfPages))
    84  	storyMetadata.LoadStories(ctx)
    85  	storiesBytes, err := json.Marshal(storyMetadata.Stories)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  	errsBytes, err := json.Marshal(storyMetadata.Errs)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  	return &grpc_api.GetStoryMetadataResponse{
    94  		Stories: storiesBytes,
    95  		Errs:    errsBytes,
    96  	}, nil
    97  }
    98  
    99  func newGrpcServer(config *GrpcConfig) (srv *grpcServer, err error) {
   100  	srv = &grpcServer{
   101  		GrpcConfig: config,
   102  		grpcTracer: otel.GetTracerProvider().Tracer("GrpcTracer"),
   103  	}
   104  	return srv, nil
   105  }
   106  
   107  func authenticate(ctx context.Context) (context.Context, error) {
   108  	if peer, ok := peer2.FromContext(ctx); !ok {
   109  		return ctx, status.New(codes.Unknown, "couldn't find peer info").Err()
   110  	} else if peer.AuthInfo == nil {
   111  		return context.WithValue(ctx, subjectContextKey{}, ""), nil
   112  	} else {
   113  		tlsInfo := peer.AuthInfo.(credentials.TLSInfo)
   114  		subject := tlsInfo.State.VerifiedChains[0][0].Subject.CommonName
   115  		ctx = context.WithValue(ctx, subjectContextKey{}, subject)
   116  		return ctx, nil
   117  	}
   118  }
   119  
   120  type subjectContextKey struct{}
   121  
   122  func subject(ctx context.Context) string {
   123  	return ctx.Value(subjectContextKey{}).(string)
   124  }