gRPC实战(四、使用metadata传输数据, Java、Go实现)

1,123 阅读2分钟

四、使用metadata传输数据

在一次rpc请求过程中我们可能不仅仅要使用req里的数据,可能也需要在metadata中放入一些通用数据,比如token、客户端上报的经纬度等信息

一、Java语言实现

1、客户端发送metedata数据

grpc-java采用拦截器的方式发送metedata

  • 使用自定义的拦截器
public class PayClientInterceptor implements ClientInterceptor {
    @Override
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel channel) {

        return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(channel.newCall(method, callOptions)) {
            @Override
            public void start(Listener<RespT> responseListener, Metadata headers) {
                headers.put(Metadata.Key.of("token", ASCII_STRING_MARSHALLER), UUID.randomUUID().toString().replaceAll("-",""));
                super.start(responseListener, headers);
            }
        };
    }
}
  • 使用grpc自带的拦截器 MetadataUtils.newAttachHeadersInterceptor
/*
 * This Java source file was generated by the Gradle 'init' task.
 */
package com.example.payservice;

import io.grpc.*;
import io.grpc.stub.MetadataUtils;

import java.util.UUID;

public class App {

    public static void main(String[] args) {
        ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost",10083)
                .usePlaintext()
                .build();
        Metadata metadata = new Metadata();
        metadata.put( Metadata.Key.of("token",Metadata.ASCII_STRING_MARSHALLER), UUID.randomUUID().toString().replaceAll("-",""));
        Channel headChannel = ClientInterceptors.intercept(channel, MetadataUtils.newAttachHeadersInterceptor(metadata));
        PayServiceGrpc.PayServiceBlockingStub payServiceBlockingStub = PayServiceGrpc.newBlockingStub(headChannel);
        PayServicePbEntity.payOrderRes res = payServiceBlockingStub.payOrder(PayServicePbEntity.payOrderReq.newBuilder().setOrderId("order_1")
                .build());
        System.out.println("resCode:"+res.getRetCode()+"\tresMsg:"+res.getRetMsg());
    }
}

2、服务端接收metedata数据

服务端也需要使用拦截器接收数据

  • 服务主类
/*
 * This Java source file was generated by the Gradle 'init' task.
 */
package com.example.payservice;

import com.example.payservice.impl.PayServiceImpl;
import com.example.payservice.interceptor.MetadataInterceptor;
import io.grpc.Server;
import io.grpc.ServerBuilder;

import java.io.IOException;

public class App {

    public static void main(String[] args) throws IOException, InterruptedException {

        Server server = ServerBuilder.forPort(10083)
                .addService(new PayServiceImpl())
                .intercept(new MetadataInterceptor())
                .build();

        server.start();
        server.awaitTermination();
    }
}
  • 服务实现类
package com.example.payservice.impl;

import com.example.payservice.PayServiceGrpc;
import com.example.payservice.PayServicePbEntity;
import io.grpc.stub.StreamObserver;

public class PayServiceImpl extends PayServiceGrpc.PayServiceImplBase {

    @Override
    public void payOrder(PayServicePbEntity.payOrderReq request, StreamObserver<PayServicePbEntity.payOrderRes> responseObserver) {

        System.out.println("payOrder,order_Id:"+request.getOrderId());

        responseObserver.onNext(PayServicePbEntity.payOrderRes.newBuilder().setRetCode(0)
        .setRetMsg("支付成功")
        .build());
        responseObserver.onCompleted();
    }
}
  • 拦截器
package com.example.payservice.interceptor;

import io.grpc.Metadata;
import io.grpc.ServerCall;
import io.grpc.ServerCallHandler;
import io.grpc.ServerInterceptor;

public class MetadataInterceptor implements ServerInterceptor {
    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
        String token = headers.get(Metadata.Key.of("token", Metadata.ASCII_STRING_MARSHALLER));
        System.out.println("token:"+token);
        return next.startCall(call, headers);
    }
}

二、Go语言实现

相比Java来说,go语言显得更清晰、简洁

1、客户端发送

grpc-go 使用了专门的metadata包传递值

package main

import (
	"context"
	"github.com/google/uuid"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/metadata"
	"grpc-in-action/part04-metedata/go/client/pb"
	"log"
)

func main() {

	conn,err:=grpc.Dial("localhost:10083",grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	req := &pb.PayOrderReq{OrderId: "order_2"}

	client := pb.NewPayServiceClient(conn)
	token := uuid.New().String()

	md := metadata.Pairs(
		"token",token)
	context := metadata.NewOutgoingContext(context.Background(),md)
	res,err:=client.PayOrder(context,req)
	if err != nil {
		panic(err)
	}
	log.Printf("retCode:%d,retMsg:%s",res.RetCode,res.RetMsg)
}

2、服务端接收

go服务端可以在相对应的方法实现里拿到metadata的值,这一点更加优于Java的实现

package main

import (
	"context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/reflection"
	"grpc-in-action/part04-metedata/go/server/pb"
	"log"
	"net"
	"time"
)

type PayServiceImpl struct {
	pb.UnimplementedPayServiceServer
}

func (pay *PayServiceImpl)PayOrder(context context.Context, req *pb.PayOrderReq) (*pb.PayOrderRes, error) {

	md,boo := metadata.FromIncomingContext(context)
	if boo{
		log.Printf("token:%s",md["token"])
	}
	log.Printf("req:orderId:%s",req.OrderId)

	// Creating and sending a header.
	header := metadata.New(map[string]string{"location": "San Jose", "timestamp": time.Now().Format(time.StampNano)})
	grpc.SendHeader(context, header)
	return &pb.PayOrderRes{
		RetCode: 0,
		RetMsg:  "支付成功",
	},nil
}

func main() {

	listen, err := net.Listen("tcp", "localhost:10083")
	if err != nil {
		panic(err)
	}
	server := grpc.NewServer()
	pb.RegisterPayServiceServer(server,&PayServiceImpl{})
	reflection.Register(server)
	err = server.Serve(listen)
	if err != nil {
		panic(err)
	}

}