gRPC

gRPC是由Google推出的Remote Procedure Calls(RPC),開發上支援各種主流語言,底層採用HTTP/2協定,再利用Protocol Buffers (protobuf)序列化資料來更有效地進行傳輸。

有別於傳統的REST,gRPC之間溝通不需要傳遞多餘的HTTP header及HTTP method(GET, POST, PUT, DELETE),而是由事先就必須定義好protobuf的資料結構,而server端與client端直接去實作這些interface即可,並不需要額外去寫路由。

以下使用go實作server端,ruby實作client端

Install gRPC & grpc-tools

go get google.golang.org/grpc
brew install protobuf

gem install grpc
gem install grpc-tools
├── go
│   ├── pb
│   │   └── demo.pb.go
│   └── server
│       └── main.go
├── protos
│   └── demo.proto
└── ruby
    ├── client
    │   └── main.rb
    └── pb
        ├── demo_pb.rb
        └── demo_services_pb.rb

定義protobuf - demo.proto

簡單定義兩組rpc,這邊必須先決定好request及response

syntax = "proto3";

package demo;

service DemoService {
  rpc SayHello (HelloRequest) returns (HelloResponse);
  rpc Sum (SumRequest) returns (SumResponse);
}

message HelloRequest {
  string name = 1;
}

message HelloResponse {
  string message = 1;
}

message SumRequest {
  int64 numberOne = 1;
  int64 numberOne = 2;
}

message SumResponse {
  int64 sum = 1;
}

由protobuf產出go跟ruby對應的code

protoc -I protos/ protos/demo.proto --go_out=plugins=grpc:go/pb
# generate demo.pb.go

grpc_tools_ruby_protoc -I protos/ --ruby_out=ruby/pb --grpc_out=ruby/pb protos/demo.proto
# generate demo_pb.rb deom_services_pb.rb

Server - Go 實作

package main

import (
  "flag"
  "log"
  "net"

  "golang.org/x/net/context"
  "google.golang.org/grpc"

  pb "../pb"
)

var (
  port = flag.String("p", ":50051", "port")
)

type server struct{}

func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
  log.Printf("Received: %v", in.GetName())
  return &pb.HelloResponse{Message: "Hello " + in.GetName()}, nil
}

func (s *server) Sum(ctx context.Context, in *pb.SumRequest) (*pb.SumResponse, error) {
  log.Printf("Received: %v & %v", in.GetNumberOne(), in.GetNumberTwo())
  sum := in.GetNumberOne() + in.GetNumberTwo()
  return &pb.SumResponse{Sum: sum}, nil
}

func main() {
  flag.Parse()

  lis, err := net.Listen("tcp", *port)
  if err != nil {
    log.Fatalf("failed to listen: %v", err)
  }
  s := grpc.NewServer()
  pb.RegisterDemoServiceServer(s, &server{})
  if err := s.Serve(lis); err != nil {
    log.Fatalf("failed to serve: %v", err)
  }
}

Client - Ruby 實作

this_dir = File.expand_path(File.dirname(__FILE__))
lib_dir = File.join(File.dirname(this_dir), 'pb')
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)

require 'grpc'
require 'demo_services_pb'

def main
  stub = Demo::DemoService::Stub.new('localhost:50051', :this_channel_is_insecure)

  say_hello_response = stub.say_hello(Demo::HelloRequest.new(
    name: 'Bob'
  ))
  puts "say_hello_response #{say_hello_response}"

  sum_response = stub.sum(Demo::SumRequest.new(
    numberOne: 8,
    numberTwo: 7,
  ))
  puts "sum_response #{sum_response}"
end

main

執行結果

server $ go run main.go
2019/09/28 14:11:48 Received: Bob
2019/09/28 14:11:48 Received: 8 + 7
client $ ruby main.rb
say_hello_response <Demo::HelloResponse: message: "Hello Bob">
sum_response <Demo::SumResponse: sum: 15>

Source code: https://github.com/LuPoYi/grpc-demo

Reference:

https://yami.io/grpc/

https://yushuanhsieh.github.io/webdevelopment/gRPC-Dial/?fbclid=IwAR1A7a9jryOuadKS7tC5K2FStdy9WWRcucHiOoJ6L5V7tyTDn2kVKZngH0w