[Go] gRPC서버 요청에 담긴 메타데이터 읽기

* 개인적으로 공부한 내용의 일부를 기록하기 위한 것이므로 중간 과정이 많이 생략되어 있습니다. *

작성중인 코드의 일부는 아래의 깃허브 저장소에서 확인하실 수 있습니다.

https://github.com/piatoss3612/go-grpc-todo

1. 메타데이터 읽기

메타데이터에 어떤 정보가 담겨있는지 먼저 확인해 봅니다.

package server

import (
	"context"
	"fmt"

	"golang.org/x/exp/slog"
	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata" // 메타데이터를 읽어오기 위해 사용하는 패키지
)

func TodoServerUnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
	md, ok := metadata.FromIncomingContext(ctx) // 요청 context로부터 메타데이터 읽기
	
    // 메타데이터가 존재하는 경우
    if ok {
    	// 메타데이터 출력
		for k, v := range md {
			fmt.Println(k, v) 
		}
	}
    
    ...
}
gRPC 클라이언트에서 다이렉트로 요청한 경우의 메타데이터
gRPC 클라이언트에서 다이렉트로 요청한 경우의 메타데이터
프록시 서버를 통해 요청한 경우의 메타데이터
프록시 서버를 통해 요청한 경우의 메타데이터

2. 메타데이터 추출 및 출력

어떤 정보들이 담겨있는지 확인했으니, 사용자 에이전트와 클라이언트 ip 정보만 가져와 보겠습니다.

func TodoServerUnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
	var userAgent, clientIp string

	if md, ok := metadata.FromIncomingContext(ctx); ok {
    	// 사용자 에이전트 정보가 있는 경우에만
		if userAgents := md.Get("user-agent"); len(userAgents) > 0 {
			userAgent = userAgents[0]
		}
        
        // 게이트웨이 사용자 에이전트 정보가 있는 경우에는 사용자 에이전트 정보에 덮어쓴다
		if gwUserAgents := md.Get("grpcgateway-user-agent"); len(gwUserAgents) > 0 {
			userAgent = gwUserAgents[0]
		}

		// 클라이언트 ip 정보가 있는 경우에만
		if clientIps := md.Get("x-forwarded-for"); len(clientIps) > 0 {
			clientIp = clientIps[0]
		}
	}

	slog.Info("Request received", "method", info.FullMethod, "user-agent", userAgent, "client-ip", clientIp)

	...
}
gRPC 클라이언트에서 다이렉트로 요청한 경우의 메타데이터 출력
gRPC 클라이언트에서 다이렉트로 요청한 경우의 메타데이터 출력
프록시 서버를 통해 요청한 경우의 메타데이터 출력
프록시 서버를 통해 요청한 경우의 메타데이터 출력

gRPC 클라이언트에서 직접 요청을 할 경우에는 메타데이터에 클라이언트의 ip주소가 포함되어 있지 않는데, 어디서 클라이언트의 ip주소를 가져와야 될까요?

3. gRPC 클라이언트의 IP 가져오기

package server

import (
	"context"
	"fmt"

	"golang.org/x/exp/slog"
	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/peer"
)

func TodoServerUnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
	var userAgent, clientIp string

	if md, ok := metadata.FromIncomingContext(ctx); ok {
		if userAgents := md.Get("user-agent"); len(userAgents) > 0 {
			userAgent = userAgents[0]
		}

		// 클라이언트 정보 추출
		peer, ok := peer.FromContext(ctx)
		if ok {
			clientIp = peer.Addr.String()
		}

		if gwUserAgents := md.Get("grpcgateway-user-agent"); len(gwUserAgents) > 0 {
			userAgent = gwUserAgents[0]
		}

		if clientIps := md.Get("x-forwarded-for"); len(clientIps) > 0 {
			clientIp = clientIps[0]
		}
	}

	slog.Info("Request received", "method", info.FullMethod, "user-agent", userAgent, "client-ip", clientIp)

	resp, err := handler(ctx, req)
	if err != nil {
		slog.Error("Request failed", "method", info.FullMethod, "error", err)
		return resp, err
	}

	slog.Info("Send a message", "type", fmt.Sprintf("%T", resp))
	return resp, nil
}
gRPC 클라이언트에서 다이렉트로 요청한 경우의 메타데이터 출력
gRPC 클라이언트에서 다이렉트로 요청한 경우의 메타데이터 출력

peer 패키지의 FromContext 함수를 사용해서 gRPC 요청 컨텍스트로부터 클라이언트의 ip 정보를 추출해낼 수 있습니다.

4. 메타데이터 추출 함수 정의

메타데이터를 추출하는 과정이 상당히 기므로 별개의 타입과 함수로 정의해 보았습니다.

package server

import (
	"context"

	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/peer"
)

type todoServerMetadata struct {
	userAgent string
	clientIp  string
}

func extractMetadata(ctx context.Context) todoServerMetadata {
	var userAgent, clientIp string

	if md, ok := metadata.FromIncomingContext(ctx); ok {
		if userAgents := md.Get("user-agent"); len(userAgents) > 0 {
			userAgent = userAgents[0]
		}

		peer, ok := peer.FromContext(ctx)
		if ok {
			clientIp = peer.Addr.String()
		}

		if gwUserAgents := md.Get("grpcgateway-user-agent"); len(gwUserAgents) > 0 {
			userAgent = gwUserAgents[0]
		}

		if clientIps := md.Get("x-forwarded-for"); len(clientIps) > 0 {
			clientIp = clientIps[0]
		}
	}

	return todoServerMetadata{userAgent: userAgent, clientIp: clientIp}
}

Reference