본문 바로가기

개발일지/go

gRPC - Interceptor

Interceptor

gRPC 어플리케이션을 구성할 때, 서버에서나 클라이언트에서나 remote 함수들을 실행하기에 앞서 실행해야하는 여러가지 로직들이 존재할 것이다. gRPC에서는 interceptor라 불리는 로깅이나 인증, 측정등을 위해 RPC 실행전에 선점할 수 있는 기능이 있다.

 

모든 언어에서 지원하지는 않으니 주의할 것. 예시로는 Go, Java가 있으니 둘은 당연히 지원된다.

 

gRPC는 2가지 타입의 interceptor로 분화된다. 이는 RPC 타입과 같다. unary interceptor와 stream interceptor. 

server-side와 client-side로 나뉘어서 알아보자

 

Server-side interceptor 

interceptor는 하나 혹은 2개 이상으로 구성될 수 있고 예제는 다음과 같다.

내가 만들 interceptor는 서비스로 들어오는 요청들에 대한 로깅이다.

 

package chat

import (
	"context"
	
	"google.golang.org/grpc"
	"log"
)

func LogUnaryServerInterceptor(ctx context.Context, req interface{},
	info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    // unary 에 대한 interceptor
	log.Println(info.FullMethod)
	m, err := handler(ctx, req)
	if err != nil {
		log.Printf("error %s occurred!", err)
	}
	return m, err
}


type wrappedStream struct {
	grpc.ServerStream
}


func (w *wrappedStream) RecvMsg(m interface{}) error {
	log.Printf("Recv", m)
	return w.ServerStream.RecvMsg(m)
}


func (w *wrappedStream) SendMsg(m interface{}) error {
	log.Printf("Send" ,m)
	return w.ServerStream.SendMsg(m)
}


func newWrappedStream(s grpc.ServerStream) grpc.ServerStream {
	return &wrappedStream{s}
}


func LogServerStreamInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
	// stream에 대한 interceptor로 recv, send 모두 interceptor를 걸 수 있다.
    log.Println(info.FullMethod)
	err := handler(srv, newWrappedStream(ss))
	if err != nil {
		log.Printf("error %s occurred!", err)
	}
	return err
}

 

func main() {
	lis, err := net.Listen("tcp", ":4317")
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer(grpc.UnaryInterceptor(chat.LogUnaryServerInterceptor),
						grpc.StreamInterceptor(chat.LogServerStreamInterceptor))
    // 이와 같이 여러개의 interceptor를 등록할 수 있다.
	pb.RegisterChatTaskServer(s, chat.InitChatServer())
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

 

 

Client-side interceptor 

클라이언트 interceptor도 서버와 동일하다

 

func LogClientStreamInterceptor( ctx context.Context, desc *grpc.StreamDesc,
	cc *grpc.ClientConn, method string,
	streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
	log.Println(method)
	s, err := streamer(ctx, desc, cc, method, opts...)
	if err != nil {
		return nil, err
	}
	return newWrappedStream(s), nil
}


type wrappedStream struct {
	grpc.ClientStream
}

func (w *wrappedStream) RecvMsg(m interface{}) error {
	log.Printf("Receive a message (Type: %T)",
		m)
	return w.ClientStream.RecvMsg(m)
}

func (w *wrappedStream) SendMsg(m interface{}) error {
	log.Printf("Send a message (Type: %T)",
		m)
	return w.ClientStream.SendMsg(m)
}

func newWrappedStream(s grpc.ClientStream) grpc.ClientStream {
	return &wrappedStream{s}
}


func LogUnaryClientInterceptor(
	ctx context.Context, method string, req, reply interface{},
	cc *grpc.ClientConn,
	invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
	log.Println(method)

	err := invoker(ctx, method, req, reply, cc, opts...)

	log.Println(reply)

	return err
}

 

 

func main() {
	conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock(),
		grpc.WithUnaryInterceptor(LogUnaryClientInterceptor),
		grpc.WithStreamInterceptor(LogClientStreamInterceptor))
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := pb.NewChatTaskClient(conn)
	client := Client{ChatTaskClient: c}
	client.session()
}

'개발일지 > go' 카테고리의 다른 글

GORM (2) - CRUD  (0) 2020.04.24
GORM (1) - Getting Started  (0) 2020.04.23
gRPC - Multiplexing, Metadata, Load Balancing and Compression  (0) 2020.04.13
gRPC - Context and Error Handling  (0) 2020.04.11
GRPC로 이미지 파일 보내기  (0) 2020.03.30