gRPC gateway
官方文档对grpc-gateway的介绍:
grpc-gateway is a plugin of protoc. It reads gRPC service definition, and generates a reverse-proxy server which translates a RESTful JSON API into gRPC. This server is generated according to custom options in your gRPC definition. It helps you to provide your APIs in both gRPC and RESTful style at the same time.
grpc-gateway 是protoc的一个插件,它读取到gRPC的定义,然后生成一个反向代理服务器,将RESTful JSON API转换为 protobuf 格式来访问gRPC服务.
利用grpc-gateay, 只需要写一份代码就可以将gRPC作为内部的通信协议,同时又可以对外提供RESTful API。
安装grpc-gateway
1
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
修改 hello.proto
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
syntax = "proto3";
package hello;
import "google/api/annotations.proto";
message HelloRequest {
string greeting = 1;
}
message HelloResponse {
string reply = 1;
repeated int32 number=4;
}
service HelloService {
rpc SayHello(HelloRequest) returns (HelloResponse){
option(google.api.http) = {
post : "/hello/say",
body : "*"
};
}
}
生成 gRPC stub
生成 hello.pb.go
1
protoc -I . -I $GOPATH/pkg/mod/github.com/grpc-ecosystem/[email protected]/third_party/googleapis --go_out=plugins=grpc:. hello.proto
生成反向代理代码
生成反向代理代码 hello.pb.gw.go
1
protoc -I . -I $GOPATH/pkg/mod/github.com/grpc-ecosystem/[email protected]/third_party/googleapis --grpc-gateway_out=logtostderr=true:. ./proto/hello.proto
grpc gateway 关键代码
server/grpc_gateway.go
1
2
3
4
5
6
7
8
9
10
11
12
13
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithInsecure()}
err := pb.RegisterHelloServiceHandlerFromEndpoint(ctx, mux, ":7777", opts)
if err != nil {
return err
}
log.Println("grpc gateway 服务开启: http:8080 -> grpc:7777")
http.ListenAndServe(":8080", mux)
启动测试
1
2
3
4
5
//启动 grpc server
go run server/grpc_server.go
//启动 grpc gateway
go run server/grpc_gateway.go
然后可以通过Postman 或 curl 进行请求测试:
1
2
3
curl -X POST http://localhost:8080/hello/say \
-H 'Content-Type: application/json' \
-d '{"greeting": "a"}'
以上代码grpc和http服务是在两个进程两个端口,那么可不可以用grpc和http共用同一个端口呢?答案是可以的。
gRPC 和 http 共用端口
首先gRPC是建立在HTTP/2版本之上,如果HTTP不是HTTP/2协议则必然无法提供gRPC支持。同时,每个gRPC调用请求的Content-Type类型会被标注为”application/grpc”类型。
这样我们就可以在gRPC端口上同时提供Web服务了。
实现的核心步骤:判断 -> 转发 -> 响应。
检测请求协议代码:
1
2
3
4
5
6
7
8
9
10
11
func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
log.Println("server grpc")
grpcServer.ServeHTTP(w, r)
} else {
log.Println("server http")
otherHandler.ServeHTTP(w, r)
}
})
}
检测请求协议是否为HTTP/2, 判断Content-Type是否为application/grpc, 根据不同的协议转发到不同的服务处理。
一般的http2协议都使用了https,那就要使用证书了。Go语言还提供了不使用证书的http2的库:h2c,如果不使用证书,在客户端调用的时候可以使用:grpc.WithInsecure()。
生成RSA私钥和自签名证书:
1
2
openssl genrsa -out server.key 2048
openssl req -new -x509 -key server.key -out server.pem -days 3650 -subj "/C=CN/ST=ZJ/L=HZ/O=grpc/OU=dev/CN=localhost:8888/[email protected]"
测试:
1
2
3
4
5
6
7
8
// grpc and http server
go run server/all.go
//grpc client
go run client/security_client.go
//curl http client
curl -X POST -k https://localhost:8888/hello/say -d '{"greeting": "foo"}'