前言:

grpc-go提供了peer库可以获取客户端地址,如果无网关及代理的情况下,通过peer是可以直接拿到对端的ip地址,反之有网关代理,那么拿到的地址非真实客户端,而是对端的直连地址。

实现:

无网关代理时,可使用GetPeerAddr()获取。如有代理需要在代理转发时把代理对端的ip给塞到header的x-real-ip头里,这个过程其实跟nginx操作X-Real-IP和X-Forwarded-For一样的。可做grpc网关的服务有很多,比如nginx、traefik、envoy。下面拿nginx grpc配置为例。

// xiaorui.cc

server {
    listen  9099  http2;
    access_log    /var/log/nginx/xiaorui.cc.log;
    location / {
        grpc_pass grpc://127.0.0.1:9091;
        grpc_set_header X-Real-IP $remote_addr;
    }
}

使用metadata从header里获取x-real-ip。

// xiaorui.cc

// GetRealAddr get real client ip
func GetRealAddr(ctx context.Context) string {
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return ""
	}

	rips := md.Get("x-real-ip")
	if len(rips) == 0 {
		return ""
	}

	return rips[0]
}

// GetPeerAddr get peer addr
func GetPeerAddr(ctx context.Context) string {
	var addr string
	if pr, ok := peer.FromContext(ctx); ok {
		if tcpAddr, ok := pr.Addr.(*net.TCPAddr); ok {
			addr = tcpAddr.IP.String()
		} else {
			addr = pr.Addr.String()
		}
	}
	return addr
}

总结:

x-forwared-for在web场景下很常见,grpc下想不到场景。 😅 通常为了优化web服务,加入多级缓存及负载均衡,这些节点都有可能追加x-forwared-for头。