Grpc一点通
gRPC是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特性,更省电和节省空间占用。
本文主要讲述如何快速搭建一个grpc服务,附带支持http接口以及swagger文档。
定义服务
示例使用项目名为grpc-demo,在 helloworld/simple.proto 文件内定义服务。
syntax = "proto3";
import "google/api/annotations.proto";
option go_package = "grpc-demo/helloworld";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) ={
post:"/v1/helloworld/sayHello"
body:"*"
};
}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
环境准备
没安装protobuf的需自行安装,mac下安装比较方便:brew install protobuf
,也可下载源码后编译安装。
准备grpc工具:
go get -u google.golang.org/grpc
go get -u google.golang.org/protobuf/cmd/protoc-gen-go
go get -u google.golang.org/grpc/cmd/protoc-gen-go-grpc
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
go install google.golang.org/protobuf/cmd/protoc-gen-go
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc
go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
转换HTTP接口需要依赖 googleapis 库 ,先把库准备好:
go get github.com/googleapis/googleapis
或者
git clone https://github.com/googleapis/googleapis.git $GOPATH/src/github.com/googleapis
然后找到googleapis库在本地存放的位置,可能是$GOPATH/src/github.com/googleapis/googleapis
google.golang.org 若无法下载的折中方法:
// 非go mod模式:
git clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpc
// go mod模式:
go mod edit -replace=google.golang.org/grpc=github.com/grpc/grpc-go@latest
自动生成代码
用 protobuf 编译器生成服务器和客户端代码:
protoc -I . \
-I $GOPATH/src/github.com/googleapis/googleapis \
--go_out . --go_opt paths=source_relative \
--go-grpc_out . --go-grpc_opt paths=source_relative \
--grpc-gateway_out . --grpc-gateway_opt logtostderr=true --grpc-gateway_opt paths=source_relative \
--swagger_out . --swagger_opt logtostderr=true \
simple.proto
注意 $GOPATH/bin 要提前加到PATH环境变量中去,否则工具会找不到。
这里已经一次性把GRPC结构代码、HTTP的Restful接口、Swagger格式文件都生成好了,后面会补充使用说明。
服务实现
使用 gRPC 的 Go API 为你的服务实现一个简单的客户端和服务器。
client.go
// Package main implements a client for Greeter service.
package main
import (
"context"
pb "grpc-demo/helloworld"
"log"
"os"
"time"
"google.golang.org/grpc"
)
const (
address = "localhost:50051"
defaultName = "world"
)
func main() {
// Set up a connection to the server.
conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
// Contact the server and print out its response.
name := defaultName
if len(os.Args) > 1 {
name = os.Args[1]
}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
for i := 0; i < 10; i++ {
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
time.Sleep(1 * time.Second)
}
}
server.go
// Package main implements a server for Greeter service.
package main
import (
"context"
"grpc-demo/gateway"
pb "grpc-demo/helloworld"
"log"
"net"
"google.golang.org/grpc"
)
const (
port = ":50051"
)
// server is used to implement helloworld.GreeterServer.
type server struct {
pb.UnimplementedGreeterServer
}
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
log.Printf("GRPC server listening at %v", lis.Addr())
go func() {
listener, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
log.Fatalf("net.Listen err: %v", err)
}
//使用gateway把grpcServer转成httpServer
httpServer := gateway.ProvideHTTP("127.0.0.1:8000", s)
if err = httpServer.Serve(listener); err != nil {
log.Fatal("HTTP ListenAndServe: ", err)
}
}()
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
gateway/gateway.go
package gateway
import (
"context"
"log"
"net/http"
"strings"
pb "grpc-demo/helloworld"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
"google.golang.org/grpc"
)
// ProvideHTTP 把gRPC服务转成HTTP服务,让gRPC同时支持HTTP
func ProvideHTTP(endpoint string, grpcServer *grpc.Server) *http.Server {
ctx := context.Background()
dopts := []grpc.DialOption{grpc.WithInsecure()}
//新建gwmux,它是grpc-gateway的请求复用器。它将http请求与模式匹配,并调用相应的处理程序。
gwmux := runtime.NewServeMux()
//将服务的http处理程序注册到gwmux。处理程序通过endpoint转发请求到grpc端点
err := pb.RegisterGreeterHandlerFromEndpoint(ctx, gwmux, endpoint, dopts)
if err != nil {
log.Fatalf("Register Endpoint err: %v", err)
}
//新建mux,它是http的请求复用器
mux := http.NewServeMux()
//注册gwmux
mux.Handle("/", gwmux)
// 设置静态目录
fsh := http.FileServer(http.Dir("swagger"))
mux.Handle("/swagger/", http.StripPrefix("/swagger/", fsh))
log.Println(endpoint + " HTTP.Listing ...")
return &http.Server{
Addr: endpoint,
Handler: grpcHandlerFunc(grpcServer, mux),
}
}
// grpcHandlerFunc 根据不同的请求重定向到指定的Handler处理
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{})
}
扩展HTTP接口
上面示例中已包含此功能,注意查看server.go的如下代码段,增加了8000作为http的服务端口
go func() {
listener, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
log.Fatalf("net.Listen err: %v", err)
}
//使用gateway把grpcServer转成httpServer
httpServer := gateway.ProvideHTTP("127.0.0.1:8000", s)
if err = httpServer.Serve(listener); err != nil {
log.Fatal("HTTP ListenAndServe: ", err)
}
}()
扩展swagger文档
-
下载swagger-ui
git clone https://github.com/swagger-api/swagger-ui
,把dist
目录下的所有文件拷贝我们项目的swagger/
目录下。 -
http设置静态目录
注意gateway/gateway.go
如下代码段
fsh := http.FileServer(http.Dir("swagger"))
mux.Handle("/swagger/", http.StripPrefix("/swagger/", fsh))
- 将自动生成的
simple.swagger.json
覆盖到swagger/swagger.json
文件 - 修改
swagger/index.html
文件,url换成url: "swagger.json"
编译运行
编译客户端: go build -o c client.go
编译服务端: go build -o s server.go
启动服务端: ./s
启动客户端: ./c
运行效果
# ./s
2021/07/17 13:27:38 GRPC server listening at [::]:50051
2021/07/17 13:27:38 127.0.0.1:8000 HTTP.Listing ...
2021/07/17 13:30:58 Received: world
2021/07/17 13:30:59 Received: world
2021/07/17 13:31:00 Received: world
2021/07/17 13:31:01 Received: world
2021/07/17 13:31:02 Received: world
2021/07/17 13:31:03 Received: world
2021/07/17 13:31:04 Received: world
2021/07/17 13:31:05 Received: world
2021/07/17 13:31:06 Received: world
2021/07/17 13:31:07 Received: world
# ./c
2021/07/17 13:30:58 Greeting: Hello world
2021/07/17 13:30:59 Greeting: Hello world
2021/07/17 13:31:00 Greeting: Hello world
2021/07/17 13:31:01 Greeting: Hello world
2021/07/17 13:31:02 Greeting: Hello world
2021/07/17 13:31:03 Greeting: Hello world
2021/07/17 13:31:04 Greeting: Hello world
2021/07/17 13:31:05 Greeting: Hello world
2021/07/17 13:31:06 Greeting: Hello world
2021/07/17 13:31:07 Greeting: Hello world
浏览器访问: http://127.0.0.1:8000/swagger/
至此,你已经学会如何快速搭建一个grpc服务了,赶快动手试试吧。
还有,别忘了补习下 proto3
的配置语法喔。
关于我
name: yison.li
blog: http://yyeer.com
github: https://github.com/yisonli