grpc 工具
BloomRPC (已好久没见更新)
PostMan 当前也支持
ApiFox 当前支持比较好(尤其是流式)
使用wireshark查看grpc的发送和接收内容:
https://grpc.org.cn/blog/wireshark/
请求如果是在网关采用支持grpc的域名拦截,请将域名绑定本地host,但不能在请求的header的host中添加,grpc会在转化时自动添加,否则会报错。
请求: xxxx.test.com:11800
GRPC - 使用
protobuf
定义protobuf,并将protobuf文件,通过java的plugin打包生成java-grpc相关文件。参照:grpc-protobuf
mvn依赖
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>3.21.7</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.30.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.30.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.30.0</version>
</dependency>
grpc-server:
// Configuration 定义
@Bean
public GrpcServer grpcServer(@Value("${gprc.port}") Integer grpcPort)){
GrpcServer grpc = new GrpcServer(grpcPort);
grpc.init();
}
/**
* Grpc interface
*/
public class TestGrpcServer extends TestServiceGrpc.TestServiceImplBase{
@Override
public void searchUser(TestInfo request, StreamObserver<TestResult> responseObserver) {
TestResult result = TestResult.newBuilder().setCode("123").setMsg("testOK").build();
log.info(String.valueOf(request));
responseObserver.onNext(result);
responseObserver.onCompleted();
}
}
/**
* grpc server
*/
public class GrpcServer {
private int grpcPort;
public GrpcServer(Integer grpcPort){
this.grpcPort = grpcPort;
init();
}
public void init() throws Exception {
ServerBuilder.forPort(grpcPort)
.addService(new TestGrpcServer())
.build()
.start();
}
}
grpc-client:
@Bean
public GrpcClient grpcClient(@Value("${grpc.ip}") String grpcIp, @Value("${grpc.port}")Integer grpcPort){
return new GrpcClient(grpcIp, grpcPort);
}
public class GrpcClient {
private Channel channel = null;
public GrpcClient(String grpcIp, Integer grpcPort){
this.grpcIp = grpcIp;
this.grpcPort = grpcPort;
channel = channel();
}
public TestResult run(TestInfo testInfo) {
TestServiceGrpc.TestBlockingStub serviceStub = TestServiceGrpc.newBlockingStub(channel);
TestResult testResult = serviceStub.searchUser(testInfo);
return testResult;
}
/**
* Channel
* @return
*/
private Channel channel() {
return ManagedChannelBuilder.forAddress(grpcIp,grpcPort).usePlaintext().build();
}
}
Test试用
@RestController
@RequestMapping("/grpc")
public class TestGrpcController {
@Autowired
GrpcClient grpcClient;
@RequestMapping("/test")
public String grpcTest(){
TestInfo testInfo = TestInfo.newBuilder().setCode("AAAA").build();
TestResult testResult = grpcClient.run(testInfo);
String json = null;
try {
json = JsonFormat.printer().print(testResult);
//JsonFormat.parser().merge(json, TestInfo.newBuilder());
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
return json;
}
异常
1. io.grpc.netty.shaded.io.netty.handler.ssl.NotSslRecordException: not an SSL/TLS record:
因client端采用TLS访问,而服务端默认是PlainText返回信息。
2. Request processing failed; nested exception is org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class com.google.protobuf.UnknownFieldSet$Parser]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class com.google.protobuf.UnknownFieldSet$Parser and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.yumchina.share.app.auth.hub.test.proto.AuthResult["unknownFields"]->com.google.protobuf.UnknownFieldSet["parserForType"])
因grpc产生的对象,不具备set方法,因此无法直接jackson序列化返回。需要引入protobuf-java-util工具类。
3. 指定method没有找到
因client端和server端使用的package路径不一致导致,要定义统一的protobuf的package。
4.NettyChannelProvider class is not found
多个使用的grpc的jar包冲突导致,如果无法排除对方的NettyChannelProvider,则直接改用最基本的grpc-netty的maven依赖jar。
即:grpc-netty-shaded -》 grpc-netty。 因grpc-netty下NettyChannelProvider在io.grpc.netty下,具有最priority。
grpc 流式请求streaming
service Greeter {
// Unary RPC.
rpc SayHello (HelloRequest) returns (HelloReply) {}
rpc GetErrResp (HelloRequest) returns (HelloReply) {}
rpc Plus (PlusRequest) returns (PlusReply) {}
rpc SayHelloAfterDelay (HelloRequest) returns (HelloReply) {}
// Server side streaming.
rpc SayHelloServerStream (HelloRequest) returns (stream HelloReply) {}
// Client side streaming.
rpc SayHelloClientStream (stream HelloRequest) returns (HelloReply) {}
// Bidirectional streaming.
rpc SayHelloBidirectionalStream (stream HelloRequest) returns (stream HelloReply) {}
}
流式请求会使得客户端和服务器端一直保持链接,除非指定时间内没发送请求,即发送超时。
以下是一个请求和响应的示例:
proto文件:
syntax = "proto3";
// 关键!定义生成的 Go 包路径
option go_package = "github.com/yourusername/your_project/proto";
service DemoService {
rpc StreamDemo(stream ClientReq) returns (stream ServerResp);
}
message ClientReq {
string input = 1;
}
message ServerResp {
string output = 1;
}
server端:
package main
import (
"log"
"net"
pb "github.com/yourusername/your_project/proto" //此proto需要依托proto转化proto文件为go文件
"google.golang.org/grpc"
)
type server struct {
pb.UnimplementedDemoServiceServer
}
func (s *server) StreamDemo(stream pb.DemoService_StreamDemoServer) error {
for {
req, err := stream.Recv()
if err != nil {
return err
}
resp := &pb.ServerResp{Output: "Echo: " + req.Input}
if err := stream.Send(resp); err != nil {
return err
}
}
}
func main() {
lis, _ := net.Listen("tcp", ":50051")
s := grpc.NewServer()
pb.RegisterDemoServiceServer(s, &server{})
log.Fatal(s.Serve(lis))
}