0%

RPC系列(四)-HTTP网关

gPRC网关是通过接收HTTP请求进行转换成gRPC的客户端请求发送给服务端的过程。这里介绍gRPC中HTTP网关的具体实现以及代码实现。

HTTP网关

​ 有时同一套业务代码我们既需要gRPC服务又需要HTTP服务,写两个服务又有点重复工作,而且维护两个版本的服务显然又不太合理,于是GRPC网关(grpc-gateway)就诞生了。它是通过protobuf的自定义option实现了一个网关,服务可以开启HTTP服务来接收HTTP请求,通过将请求数据转换成gRPC客户端请求发送个gRPC服务端,服务又将gRPC服务端的响应转化成HTTP响应返回给请求方。

安装

安装grpc-gateway, 这样protobuf就能生成gRPC网关相关代码

1
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway

示例

HTTP服务

通过开启一个HTTP服务来接收http请求,通过将请求数据转换成gRPC客户端请求发送个gRPC服务端来实现整个请求过程。

1.proto文件新增http option

在前几节的massage.proto文件中的Hello方法新增http option, post请求,路由为/hello

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
syntax = "proto3";

package protobuf;
option go_package = "./;protobuf";

import "google/api/annotations.proto";


service HelloService{
rpc Hello(Massage) returns (Massage) {
// http option
option (google.api.http) = {
post: "/hello"
body: "*"
};
}
}
......

2.生成gRPC网关相关代码

通过protoc命令生成gRPC网关相关代码。这里需要引入google api的proto文件。

1
2
3
4
5
protoc --go_out=. -I=. -I$GOPATH/src/github.com/googleapis/googleapis massage.proto

protoc --go-grpc_out=. -I=. -I$GOPATH/src/github.com/googleapis/googleapis massage.proto

protoc --grpc-gateway_out=logtostderr=true:. -I=. -I$GOPATH/src/github.com/googleapis/googleapis massage.proto

3.新增HTTP服务代码

新增http_server/main.go, 实现http服务,相关认证选项和拦截器选项参考前几章客户端请求选项,这里自演示部分代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
ctx, cancel := context.WithCancel(context.Background())
defer cancel()


endPoint := "127.0.0.1:9000"
mux := runtime.NewServeMux()

// 添加客户端请求选项
opts := make([]grpc.DialOption, 0)

// TLS认证(如果开启TLS认证)
creds, err := credentials.NewClientTLSFromFile("./keys/server.pem", "www.zhouxuwen.com")
opts = append(opts, grpc.WithTransportCredentials(creds))

// 自定义认证方式(如果开启自定义认证)
opts = append(opts, grpc.WithPerRPCCredentials(new(authCredential)))

// HTTP转grpc
err = protobuf.RegisterHelloServiceHandlerFromEndpoint(ctx, mux, endPoint, opts)
if err != nil {
fmt.Printf("Register handler err:%v\n", err)
}

fmt.Printf("HTTP Listen on 8080\n")
http.ListenAndServe(":8080", mux)

4.启动HTTP服务和gRPC服务端

1
2
> curl -X POST -d '{"massage":"grpc gateway"}' http://127.0.0.1:8080/hello
> {"massage":"hello grpc gateway !"}

升级gRPC服务端

上面方法通过新开启一个HTTP服务实现了通过http请求转换成gRPC客户端请求发送服务端。这样我们是同时开启HTTP和gRPC两个服务,虽然解决了了重复工作的问题,但我们还是得维护两个服务,也不太合适,我们可以将这两个服务整合在一起,共用同一端口号。同时提供http和gRPC两种调用方式。

1.修改gRPC服务端代码

只需要修改gRPC服务端代码,将之前的gRPC服务端加上http网关相关,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
func main() {
endPoint := "localhost:9000"
listen, err := net.Listen("tcp", endPoint)
if err != nil {
fmt.Printf("tcp listen err:%v\n", err)
return
}

// rpc server
opts := make([]grpc.ServerOption, 0)
creds, err := credentials.NewServerTLSFromFile("./keys/server.pem", "./keys/server.key")
if err != nil {
fmt.Printf("failed to generate credentials err:%v\n", err)
return
}
// TLS认证
opts = append(opts, grpc.Creds(creds))
// 自定义认证
opts = append(opts, grpc.UnaryInterceptor(interceptor.Interceptor))
server := grpc.NewServer(opts...)
protobuf.RegisterHelloServiceServer(server, &service.HelloService)


// http gateway server
gwOpts := make([]grpc.DialOption, 0)
gwCreds, err := credentials.NewClientTLSFromFile("./keys/server.pem", "www.zhouxuwen.com")
if err != nil {
fmt.Printf("failed to generate credentials err:%v\n", err)
return
}
// tls认证
gwOpts = append(gwOpts, grpc.WithTransportCredentials(gwCreds))
// 自定义认证方式
gwOpts = append(gwOpts, grpc.WithPerRPCCredentials(&interceptor.AuthCredential))
// 请求拦截
gwOpts = append(gwOpts, grpc.WithUnaryInterceptor(interceptor.ClientInterceptor))

gwmux := runtime.NewServeMux()
err = protobuf.RegisterHelloServiceHandlerFromEndpoint(context.Background(), gwmux, endPoint, gwOpts)
if err != nil {
fmt.Printf("register http gateway server fail. err: %v\n", err)
return
}



// http server
mux := http.NewServeMux()
mux.Handle("/", gwmux)
tlsConfig, err := getTLSConfig()
if err != nil {
fmt.Printf("get tls config err :%v\n", err)
return
}

// http and gRPC server
srv := &http.Server{
Addr: endPoint,
Handler: grpcHandlerFunc(server, mux),
TLSConfig: tlsConfig,
}

fmt.Printf("gRPC and http listen at :%v\n", endPoint)
err = srv.Serve(tls.NewListener(listen, tlsConfig))
if err != nil {
fmt.Printf("%v\n", errors.New(fmt.Sprintf("failed to serve: %v", err)))
}
}


func getTLSConfig() (*tls.Config, error) {
cert, _ := ioutil.ReadFile("./keys/server.pem")
key, _ := ioutil.ReadFile("./keys/server.key")
var demoKeyPair *tls.Certificate
pair, err := tls.X509KeyPair(cert, key)
if err != nil {
fmt.Printf("TLS KeyPair err: %v\n", err)
return nil, err
}
demoKeyPair = &pair
return &tls.Config{
Certificates: []tls.Certificate{*demoKeyPair},
NextProtos: []string{http2.NextProtoTLS}, // HTTP2 TLS支持
}, nil
}


// grpcHandlerFunc returns an http.Handler that delegates to grpcServer on incoming gRPC
// connections or otherHandler otherwise. Copied from cockroachdb.
func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler {
if otherHandler == nil {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
grpcServer.ServeHTTP(w, r)
})
}
return 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)
}
})
}

2.启动服务

启动服务,通过gRPC客户端和http请求的方式进行验证。

1
2
> curl -X POST -k https://127.0.0.1:9000/hello -d '{"massage":"grpc and http"}'
> {"massage":"hello grpc and http !"}%

项目源码

1
git clone https://github.com/zhouxuwen/grpcdemo-go.git