GRPC是RPC(远程过程调用)实现的一种框架,可以使得两个服务调用对方的api仿佛是在调用本地方法一样,通过tcp访问实现。本文将通过ProtoBuf文件定义服务之间的访问接口,之后通过grpc和http成功访问(Golang实现)。
syntax = "proto3";
package proto;
option go_package = "./proto";
import "google/api/annotations.proto";
message HelloServiceRequest {
string name = 1;
string age = 2;
}
message HelloServiceResponse {
string result = 1;
}
service Service {
rpc sayHello(HelloServiceRequest) returns (HelloServiceResponse) {
option (google.api.http) = {
get: "/say_hello"
};
}
}
在HelloService.proto文件所在目录下新建google/api/目录,往其中添加annotations.proto,field_behavior.proto,http.proto和httpbody.proto四个文件。这四个文件可以从google api github 中复制
tool.go
package tools
import (
_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway"
_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2"
_ "google.golang.org/grpc/cmd/protoc-gen-go-grpc"
_ "google.golang.org/protobuf/cmd/protoc-gen-go"
)
在tool.go写入以上代码后,在项目home目录下执行以下命令下载依赖。
go mod tidy
export GOBIN=$GOPATH/bin
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 google.golang.org/protobuf/cmd/protoc-gen-go google.golang.org/grpc/cmd/protoc-gen-go-grpc
cd proto
protoc -I . --go_out ./ --go_opt paths=source_relative --go-grpc_out ./ --go-grpc_opt paths=source_relative ./*.proto
protoc -I . --grpc-gateway_out ./ --grpc-gateway_opt logtostderr=true --grpc-gateway_opt paths=source_relative --grpc-gateway_opt generate_unbound_methods=true ./*.proto
protoc -I . --openapiv2_out ./gen/openapiv2 --openapiv2_opt logtostderr=true ./*.proto
protobuf提供的只是HelloService的接口,我们需要去在server端实现ServiceServer接口,实现SayHello的处理逻辑。
package service
import (
"context"
"log"
"protobuf_demo/proto"
)
type MyHelloService struct {
proto.UnimplementedServiceServer // must specify the field
}
// SayHello is in HelloService_grpc.pb.go
func (s MyHelloService) SayHello(ctx context.Context, req *proto.HelloServiceRequest) (*proto.HelloServiceResponse, error) {
log.Printf("req: %v,rsp.Result:%v,req.Age:%v", req, req.Name,req.Age)
result := "hello " + req.Name + ", your age is " + req.Age
return &proto.HelloServiceResponse{
Result: result,
}, nil
}
server实现
在server端,1088号端口监听grpc请求,1089号端口监听http请求。
package main
import (
"context"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
"google.golang.org/grpc"
"log"
"net"
"net/http"
"protobuf_demo/pkg/service"
"protobuf_demo/proto"
"strings"
)
func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler {
return h2c.NewHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
grpcServer.ServeHTTP(w, r)
} else {
otherHandler.ServeHTTP(w, r)
}
}), &http2.Server{})
}
func main() {
lis, err := net.Listen("tcp", "localhost:1088")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// 将service实现与server绑定
s := grpc.NewServer()
proto.RegisterServiceServer(s, service.MyHelloService{})
// 提供http服务
mux := runtime.NewServeMux()
httpServerAndPort := "localhost:1089" // http服务监听本地1089端口
// 注册proto文件中定义的http接口
proto.RegisterServiceHandlerFromEndpoint(context.Background(), mux, httpServerAndPort, []grpc.DialOption{grpc.WithInsecure()})
hs := &http.Server{
Addr: httpServerAndPort,
Handler: grpcHandlerFunc(s, mux), // 将http请求转换为grpc请求,使用grpc处理
}
go func() {
lis, _ := net.Listen("tcp", httpServerAndPort) // http服务也是监听tcp端口
if err := hs.Serve(lis); err != nil {
log.Fatalf("HTTP Server failed to serve: %v", err)
}
}()
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
client实现
package main
import (
"context"
"google.golang.org/grpc"
"log"
"protobuf_demo/proto"
)
func main() {
// Set up a connection to the server.
conn, err := grpc.Dial("localhost:1088", grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := proto.NewServiceClient(conn)
// Contact the server and print out its response.
r, err := c.SayHello(context.Background(), &proto.HelloServiceRequest{Age: "18", Name: "cshen"})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Response: %s", r.Result)
}
在IDE中先运行server的代码,再运行client的代码。
cshen@cshen-mac protobuf_demo % curl "http://localhost:1089/say_hello?name=cshen&age=2"
{"result":"hello cshen, your age is 2"}%