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功能类似,不做介绍

 

posted @ 2022-12-26 11:00  我要学算法  阅读(114)  评论(0)    收藏  举报