四、使用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)
}
}