Go GRPC&&GRPCGateway
文件链接下载:https://files.cnblogs.com/files/blogs/714203/go_grpc%26grpcGateway.rar
1、下载proto && protoc-gen-go
https://github.com/protocolbuffers/protobuf/releases下载protoc,注意win版本,然后添加环境变量protoc.exe的路径。
go get -u github.com/golang/protobuf/proto (下载了上面的这个就可以不要了;)
go get -u github.com/golang/protobuf/protoc-gen-go
2、编写proto文件
syntax = "proto3";
//option go_package = "path;name"; path 表示生成的.pb.go文件的存放地址,会自动生成目录的,name 表示生成的go文件所属的包名
option go_package="../UserServiceDirectory; UserService";
// package service; 指定等会文件生成出来的package 失效了
import "google/api/annotations.proto";
message UserName{
string userName =1; //1代表第一个字段
}
message UserInfo{
string userName =1;
string age =2;
}
message Greeting{
string greetingWord =1;
}
message FileBinary{
bytes data =1;
}
service MyUserService{
rpc GetUserInfo (UserName) returns (UserInfo){
// 若未在url中指定参数位置,则需要?传参
option(google.api.http)={
get: "/user/{userName}/userinfo"
};
};
rpc GetGreeting (UserName) returns (Greeting){
option(google.api.http)={
post: "/user/greeting"
body: "*"
};
};
// 双向流模式
rpc BothStream (stream UserName) returns (stream FileBinary){
option (google.api.http)={
post: "/stream/both"
body: "*"
};
};
}
生成对应go语言代码
protoc --go_out=plugins=grpc:. UserServer.proto 生成对应go语言代码,纯grpc不附带api 生成pb.go protoc.exe -I. -I C:\Users\renzhengguo\go\pkg\mod\github.com\grpc-ecosystem\grpc-gateway@v1.16.0\third_party\googleapis --go_out=plugins=grpc:. .\UserServer.proto 必须定位至googleapis 附带api的 生成pb.go 此时已经可以实现grpc服务 protoc.exe -I. -I C:\Users\renzhengguo\go\pkg\mod\github.com\grpc-ecosystem\grpc-gateway@v1.16.0\third_party\googleapis --grpc-gateway_out=logtostderr=true:. .\UserServer.proto 必须定位至googleapis 附带api的 生成pb.gw.go 此时可以实现gw服务
3、编写对应的服务端接口代码
package UserService
import (
"context"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
type UserServer struct {
}
func NewUserServerAPI() *UserServer {
return new(UserServer)
}
func (*UserServer) GetUserInfo(ctx context.Context, userName *UserName) (*UserInfo, error) {
uName := userName.GetUserName()
if uName != "" {
return &UserInfo{UserName: uName, Age: "auto: 18"}, nil
}
return nil, errors.New("userinfo forbidden! without user name")
}
func (*UserServer) GetGreeting(ctx context.Context, userName *UserName) (*Greeting, error) {
uName := userName.GetUserName()
if uName != "" {
return &Greeting{GreetingWord: "Hello: " + uName}, nil
}
return nil, errors.New("greeting forbidden! without user name")
}
func (*UserServer) BothStream(bothStreamServer MyUserService_BothStreamServer) error {
count := 0
// 源源不断接收客户端流
for {
recv, err := bothStreamServer.Recv()
if err != nil {
return errors.Wrap(err, "stream receive error")
}
log.Info("stream username:", recv.GetUserName(), "count: ", count)
count++
// 5条消息之后结束接收,返回数据
if count >= 5 {
err = bothStreamServer.Send(&FileBinary{Data: []byte("stream over")})
if err != nil {
return errors.Wrap(err, "stream send error")
}
return nil
}
}
}
4、传统grpc
只提供grpc客户端访问,浏览器,postman均不支持,因为grpc的HTTP2不同于普通HTTP2
// StartTraditionServer 开启传统grpc服务,只能通过grpc客户端访问,且不带任何认证
func StartTraditionServer() error {
// 创建grpc服务端
traditionalServer := grpc.NewServer()
// 注册服务
UserService.RegisterMyUserServiceServer(traditionalServer, UserService.NewUserServerAPI())
// 开启监听
listener, err := net.Listen("tcp", "127.0.0.1:8001")
if err != nil {
return errors.Wrap(err, "start listen error")
}
// 开启服务
err = traditionalServer.Serve(listener)
if err != nil {
return errors.Wrap(err, "start server error")
}
return nil
}
grpc客户端,不带认证,此时可以访问单向认证服务端,不可访问双向认证服务端
// StartGRPCClient grpc客户端 无证书
func StartGRPCClient() error {
// grpc拨号 获取客户端连接
clientConn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure())
if err != nil {
return errors.Wrap(err, "grpc dial error")
}
defer clientConn.Close()
// 通过客户端连接获取客户端服务
userServiceClient := UserService.NewMyUserServiceClient(clientConn)
// 调用服务
userInfo, err := userServiceClient.GetUserInfo(context.Background(), &UserService.UserName{UserName: "老王"})
if err != nil {
return errors.Wrap(err, "client get userinfo error")
}
log.WithFields(log.Fields{
"username": userInfo.UserName,
"userage": userInfo.Age,
}).Info("get userinfo ok")
greeting, err := userServiceClient.GetGreeting(context.Background(), &UserService.UserName{UserName: "老王"})
if err != nil {
return errors.Wrap(err, "client get greeting error")
}
log.WithFields(log.Fields{
"greeting word": greeting.GetGreetingWord(),
}).Info("get greeting ok")
return nil
}
grpcGateway,提供http服务,将http1.1转为HTTP2去访问grpc服务端
// StartGrpcGatewayServer 提供http服务,此服务需要先开启grpc服务端
// grpc gateway 类似于一个grpc客户端,单向认证,双向认证添加相应的证书
func StartGrpcGatewayServer() error {
// 创建一个server mux
mux := runtime.NewServeMux()
// 将http服务转发到grpc服务端口
err := UserService.RegisterMyUserServiceHandlerFromEndpoint(context.Background(), mux, grpcEndPoint, []grpc.DialOption{grpc.WithInsecure()})
if err != nil {
return errors.Wrap(err, "grpc gateway server error")
}
err = http.ListenAndServe("127.0.0.1:8080", mux)
if err != nil {
return errors.Wrap(err, "gateway listen error")
}
return nil
}
httpServer,接收http请求,并且可以转发至相应的函数进行处理
// StartHttpServer 将http请求转发至grpc服务 此时通过普通 grpc client 无法访问
// serverHTTP是服务HTTP2,但和传统的HTTP2不同,所以不带证书的grpc客户端无法访问
// 通过h2c可以使得grpc客户端不需要证书
// listen and server 只能采用无认证或单向认证服务端,且grpc客户端不能带认证。客户端有认证则要使用listen and server tls
func StartHttpServer() error {
// 创建grpc服务端
traditionalServer := grpc.NewServer()
// 注册服务
UserService.RegisterMyUserServiceServer(traditionalServer, UserService.NewUserServerAPI())
handlerFunc := http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
log.Info("http request: ", req)
if req.ProtoMajor == 2 && strings.Contains(req.Header.Get("Content-Type"), "application/grpc") {
log.Info("-----")
traditionalServer.ServeHTTP(resp, req)
} else {
_, err := resp.Write([]byte("not grpc client"))
if err != nil {
log.Info(err)
}
}
})
handler := h2c.NewHandler(handlerFunc, &http2.Server{})
err := http.ListenAndServe("localhost:8080", handler)
if err != nil {
return errors.Wrap(err, "http listen and server error")
}
return nil
}
5、OpenSSL生成证书
网站下载:http://slproweb.com/products/Win32OpenSSL.html安装OpenSSL
# 生成ca密钥, 生成2048位私钥,保存到ca.key文件中 非加密无需密码 通常使用下一个 openssl genrsa -out ca.key 2048 # 生成ca密钥 ,生成2048位带加密的私钥(交互方式输入密码),保存到ca.key文件中 openssl.exe genrsa -des3 -out ca.key 2048 # 使用rsa私钥生成公钥 可无 openssl.exe rsa -in ca.key -pubout -out ca_public.key # 生成ca证书签发请求 创建证书请求文件,并使用私钥签名,输入密码, 选填信息 common_name是公用名称 openssl.exe req -new -key ca.key -out ca.csr # 生成ca根证书,得到ca.crt 使用csr生成证书 openssl x509命令 并使用私钥签名 openssl.exe x509 -req -days 365 -in ca.csr -signkey ca.key -out ca.crt openssl x509 -req -days 365 -in ca.csr -signkey ca.key -out ca.crt # 生成服务端私钥 server.key 注意不能加密 openssl genrsa -des3 -out server.key 2048 -des3不需要 openssl genpkey -algorithm RSA -out server.key # 在证书存放文件夹下,新建server.conf,写入内容如下 # 生成证书签发请求 server.csr 注意common name填写主域名,可配置文件开启多域名 -config server.conf openssl req -new -sha256 -out server.csr -key server.key openssl req -new -nodes -key server.key -out server.csr -days 3650 -config ./openssl.cnf -extensions v3_req # 用CA证书生成服务端证书,得到server.pem -extfile server.conf 上两步都随意,这一步需要生成SAN证书 找到openssl.cnf文件, 打开copy_extensions = copy 打开 req_extensions = v3_req 找到[ v3_req ],添加 subjectAltName = @alt_names [ alt_names ] DNS.1 = *.test.com 这个随意 DNS.2 = localhost 不然无法用127.0.0.1获得服务 openssl x509 -req -days 365 -in server.csr -out server.pem -CA ca.crt -CAkey ca.key -CAcreateserial -extfile ./openssl.cnf -extensions v3_req // 双向认证需要创建客户端证书
私钥:
~~~shell
openssl genpkey -algorithm RSA -out client.key
~~~
证书:
~~~shell
openssl req -new -nodes -key client.key -out client.csr -days 3650 -config ./openssl.cnf -extensions v3_req
~~~
SAN证书:
~~~shell
openssl x509 -req -days 365 -in client.csr -out client.pem -CA ca.crt -CAkey ca.key -CAcreateserial -extfile ./openssl.cnf -extensions v3_req
~~~
6、 GRPC单向认证
服务端需要提供证书
// StartTraditionServerSingleSSL 开启单向认证grpc服务端,客户端需要添加证书
// 单向认证只核实服务端证书,服务端无需核验客户端 单向认证服务器 可以 被双向认证客户端访问
func StartTraditionServerSingleSSL() error {
// 添加证书,通过文件获取tls凭据, server.pem内common name为127.0.0.1
tlsCredential, err := credentials.NewServerTLSFromFile("./openSSL/server.pem", "./openSSL/server.key")
if err != nil {
return errors.Wrap(err, "create tls error")
}
// 创建grpc服务端
traditionalServer := grpc.NewServer(grpc.Creds(tlsCredential))
// 注册服务
UserService.RegisterMyUserServiceServer(traditionalServer, UserService.NewUserServerAPI())
// 开启监听
listener, err := net.Listen("tcp", "127.0.0.1:8001")
if err != nil {
return errors.Wrap(err, "start listen error")
}
// 开启服务
err = traditionalServer.Serve(listener)
if err != nil {
return errors.Wrap(err, "start server error")
}
log.Info("start server ok")
return nil
}
grpcGateway类似于单项认证客户端,将http1转发至grpc服务端进行处理
// StartGrpcGatewayServerSingleTLS 提供http服务,此服务需要先开启grpc服务端
// grpc gateway 类似于一个grpc客户端,单向认证,双向认证添加相应的证书
func StartGrpcGatewayServerSingleTLS() error {
// 创建一个server mux
mux := runtime.NewServeMux()
// 添加证书
clientTLSFromFile, err := credentials.NewClientTLSFromFile("./openSSL/server.pem", "")
if err != nil {
return errors.Wrap(err, "create tls file error")
}
// 将http服务转发到grpc服务端口
err = UserService.RegisterMyUserServiceHandlerFromEndpoint(context.Background(), mux, grpcEndPoint, []grpc.DialOption{grpc.WithTransportCredentials(clientTLSFromFile)})
if err != nil {
return errors.Wrap(err, "grpc gateway server error")
}
err = http.ListenAndServe("127.0.0.1:8081", mux)
if err != nil {
return errors.Wrap(err, "gateway listen error")
}
return nil
}
单项认证客户端
// StartGRPCClientSingleSSL grpc客户端 单向认证客户端
func StartGRPCClientSingleSSL() error {
// 添加证书
clientTLSFromFile, err := credentials.NewClientTLSFromFile("./openSSL/server.pem", "")
if err != nil {
return errors.Wrap(err, "create tls file error")
}
// grpc拨号 获取客户端连接 附带证书文件 拨号实际为 localhost:8001 证书中开放了localhost
clientConn, err := grpc.Dial(":8001", grpc.WithTransportCredentials(clientTLSFromFile))
if err != nil {
return errors.Wrap(err, "grpc dial error")
}
defer clientConn.Close()
// 通过客户端连接获取客户端服务
userServiceClient := UserService.NewMyUserServiceClient(clientConn)
// 调用服务
userInfo, err := userServiceClient.GetUserInfo(context.Background(), &UserService.UserName{UserName: "老王"})
if err != nil {
return errors.Wrap(err, "client get userinfo error")
}
log.WithFields(log.Fields{
"username": userInfo.UserName,
"userage": userInfo.Age,
}).Info("get userinfo ok")
greeting, err := userServiceClient.GetGreeting(context.Background(), &UserService.UserName{UserName: "老王"})
if err != nil {
return errors.Wrap(err, "client get greeting error")
}
log.WithFields(log.Fields{
"greeting word": greeting.GetGreetingWord(),
}).Info("get greeting ok")
stream, err := userServiceClient.BothStream(context.Background())
if err != nil {
return errors.Wrap(err, "client both stream error")
}
for i := 0; i <= 5; i++ {
err = stream.Send(&UserService.UserName{UserName: "老王" + string(i)})
if err != nil {
return errors.Wrap(err, "client stream send error")
}
}
recv, err := stream.Recv() // recv会阻塞
if err != nil {
return errors.Wrap(err, "client stream receive error")
}
log.Info("client receive data: ", string(recv.GetData()))
return nil
}
7、GRPC双向认证 && httpTLS
双向认证服务端
// StartTraditionServerDoubleSSL 开启双向认证grpc服务端
func StartTraditionServerDoubleSSL() error {
// 读取并解析公钥/私钥队 从证书相关文件中读取和解析信息,得到证书公钥、密钥对
keyPair, err := tls.LoadX509KeyPair("./openSSL/server.pem", "./openSSL/server.key")
if err != nil {
return errors.Wrap(err, "load key pair error")
}
// 创建一个cert pool
certPool := x509.NewCertPool()
// 读取ca证书
ca, err := os.ReadFile("./openSSL/ca.crt")
if err != nil {
return errors.Wrap(err, "server read ca.crt error")
}
// 尝试解析所传入的 PEM 编码的证书。如果解析成功会将其加到 CertPool 中,便于后面的使用
certPool.AppendCertsFromPEM(ca)
// 构建基于tls的 TransportCredentials(tls传输凭证) 选项
tlsCredential := credentials.NewTLS(&tls.Config{
// 设置证书链,允许包含一个或多个
Certificates: []tls.Certificate{keyPair},
// 双向认证,必须校验客户端证书
ClientAuth: tls.RequireAndVerifyClientCert,
// 设置根证书的集合,校验方式使用 ClientAuth 中设定的模式
ClientCAs: certPool,
})
// 创建grpc服务端
traditionalServer := grpc.NewServer(grpc.Creds(tlsCredential))
// 注册服务
UserService.RegisterMyUserServiceServer(traditionalServer, UserService.NewUserServerAPI())
// 开启监听
listener, err := net.Listen("tcp", "127.0.0.1:8001")
if err != nil {
return errors.Wrap(err, "start listen error")
}
// 开启服务
err = traditionalServer.Serve(listener)
if err != nil {
return errors.Wrap(err, "start server error")
}
log.Info("start server ok")
return nil
}
双向认证Gateway
// StartGrpcGatewayServerDoubleTLS 提供http服务,此服务需要先开启grpc服务端
// grpc gateway 类似于一个grpc客户端,单向认证,双向认证添加相应的证书
func StartGrpcGatewayServerDoubleTLS() error {
// 创建一个server mux
mux := runtime.NewServeMux()
// 和服务端类似, 解析信息
keyPair, err := tls.LoadX509KeyPair("./openSSL/client.pem", "./openSSL/client.key")
if err != nil {
return errors.Wrap(err, "load key pair error")
}
// 证书池
certPool := x509.NewCertPool()
ca, err := os.ReadFile("./openSSL/ca.crt")
if err != nil {
return errors.Wrap(err, "read ca.crt error")
}
certPool.AppendCertsFromPEM(ca)
// 新建tls凭据
clientTLSCredentials := credentials.NewTLS(&tls.Config{
// 设置证书链
Certificates: []tls.Certificate{keyPair},
// 要求校验服务端证书
ServerName: "*.test.com",
RootCAs: certPool,
})
// 将http服务转发到grpc服务端口
err = UserService.RegisterMyUserServiceHandlerFromEndpoint(context.Background(), mux, grpcEndPoint, []grpc.DialOption{grpc.WithTransportCredentials(clientTLSCredentials)})
if err != nil {
return errors.Wrap(err, "grpc gateway server error")
}
err = http.ListenAndServe("127.0.0.1:8082", mux)
if err != nil {
return errors.Wrap(err, "gateway listen error")
}
return nil
}
双向认证客户端
// StartGRPCClientDoubleSSL grpc客户端 双向认证客户端
func StartGRPCClientDoubleSSL() error {
// 和服务端类似, 解析信息
keyPair, err := tls.LoadX509KeyPair("./openSSL/client.pem", "./openSSL/client.key")
if err != nil {
return errors.Wrap(err, "load key pair error")
}
// 证书池
certPool := x509.NewCertPool()
ca, err := os.ReadFile("./openSSL/ca.crt")
if err != nil {
return errors.Wrap(err, "read ca.crt error")
}
certPool.AppendCertsFromPEM(ca)
// 新建tls凭据
clientTLSCredentials := credentials.NewTLS(&tls.Config{
// 设置证书链
Certificates: []tls.Certificate{keyPair},
// 要求校验服务端证书
ServerName: "*.test.com",
RootCAs: certPool,
})
// grpc拨号 获取客户端连接 附带证书文件
clientConn, err := grpc.Dial(":8001", grpc.WithTransportCredentials(clientTLSCredentials))
if err != nil {
return errors.Wrap(err, "grpc dial error")
}
defer clientConn.Close()
// 通过客户端连接获取客户端服务
userServiceClient := UserService.NewMyUserServiceClient(clientConn)
// 调用服务
userInfo, err := userServiceClient.GetUserInfo(context.Background(), &UserService.UserName{UserName: "老王"})
if err != nil {
return errors.Wrap(err, "client get userinfo error")
}
log.WithFields(log.Fields{
"username": userInfo.UserName,
"userage": userInfo.Age,
}).Info("get userinfo ok")
greeting, err := userServiceClient.GetGreeting(context.Background(), &UserService.UserName{UserName: "老王"})
if err != nil {
return errors.Wrap(err, "client get greeting error")
}
log.WithFields(log.Fields{
"greeting word": greeting.GetGreetingWord(),
}).Info("get greeting ok")
stream, err := userServiceClient.BothStream(context.Background())
if err != nil {
return errors.Wrap(err, "client both stream error")
}
for i := 0; i <= 5; i++ {
err = stream.Send(&UserService.UserName{UserName: "老王" + string(i)})
if err != nil {
return errors.Wrap(err, "client stream send error")
}
}
recv, err := stream.Recv() // recv会阻塞
if err != nil {
return errors.Wrap(err, "client stream receive error")
}
log.Info("client receive data: ", string(recv.GetData()))
return nil
}
http.ListenAndServeTLS
// StartHttpServerTLS 开启tls 单双向认证客户端均可以访问
func StartHttpServerTLS() error {
// 创建grpc服务端
traditionalServer := grpc.NewServer()
// 注册服务
UserService.RegisterMyUserServiceServer(traditionalServer, UserService.NewUserServerAPI())
mux := runtime.NewServeMux()
// 添加证书
clientTLSFromFile, err := credentials.NewClientTLSFromFile("./openSSL/server.pem", "")
if err != nil {
return errors.Wrap(err, "create tls file error")
}
// 将http服务转发到grpc服务端口
err = UserService.RegisterMyUserServiceHandlerFromEndpoint(context.Background(), mux, ":8080", []grpc.DialOption{grpc.WithTransportCredentials(clientTLSFromFile)})
if err != nil {
return errors.Wrap(err, "grpc gateway error")
}
handlerFunc := http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
log.Info("http request: ", req)
if req.ProtoMajor == 2 && strings.Contains(req.Header.Get("Content-Type"), "application/grpc") {
log.Info("-----")
traditionalServer.ServeHTTP(resp, req)
} else {
_, err := resp.Write([]byte("not grpc client"))
if err != nil {
log.Info(err)
}
mux.ServeHTTP(resp, req)
log.Info("== over")
}
})
handler := h2c.NewHandler(handlerFunc, &http2.Server{})
err = http.ListenAndServeTLS(
"localhost:8080",
"./openSSL/server.pem",
"./openSSL/server.key",
handler)
if err != nil {
return errors.Wrap(err, "http listen and server error")
}
return nil
}
http.ListenAndServeTLS提供https访问,也可以使用http.ListenAndServe类似于之前的demo使用http访问,但与gateway功能类似,不做介绍

浙公网安备 33010602011771号