这里 使用的是 grpc 和 gin 框架 监听同一端口 的实现方式。
1、方式一:grpc 与 gin-http 分流
主要是 在 请求 刚到达服务端 时 就 自定义 包含 grpc.ServeHTTP(w, r) 和 router.ServeHTTP(w, r) 的 http.Handler 进行分流。
服务端代码如下:
// UserInfoService 用户服务
type UserInfoService struct{
pb.UnimplementedUserInfoServiceServer
}
// GetUserInfo 实现方法
func (s *UserInfoService) GetUserInfo(ctx context.Context, req *pb.UserRequest) (resp *pb.UserResponse, err error) {
// 通过用户名查询用户信息
name := req.Name
fmt.Println("userinfo: ", name)
// 数据里查用户信息
if name == "zs" {
resp = &pb.UserResponse{
Id: 1,
Name: name,
Age: 22,
Hobby: []string{"Sing", "Run"},
}
}
return
}
type HelloService struct {
pb.UnimplementedHelloServiceServer
}
func (h *HelloService) SayHello(ctx context.Context, in *pb.HelloRequest) (resp *pb.HelloResponse, err error) {
resp = new(pb.HelloResponse)
resp.Result = fmt.Sprintf("hello %s", in.Name)
return resp, nil
}
// 方案一:grpc与gin-http分流
// grpc无法使用gin的http访问
func main(){
// 初始化grpc服务
s := grpc.NewServer()
// 注册grpc服务
var u = UserInfoService{}
pb.RegisterUserInfoServiceServer(s, &u)
var h = HelloService{}
pb.RegisterHelloServiceServer(s, &h)
router := gin.Default()
// api路由
router.GET("/hello", func(ctx *gin.Context) {
fmt.Println("已进入到HTTP实现")
ctx.JSON(200, map[string]interface{}{
"code": 200,
"msg:": "success",
})
})
// 监听端口并处理服务分流
h2Handler := h2c.NewHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 判断协议是否为http/2 && 是grpc
if r.ProtoMajor == 2 &&
strings.HasPrefix(r.Header.Get("Content-Type"), "application/grpc") {
// 按grpc方式来请求
s.ServeHTTP(w, r)
} else {
// 当作普通api
router.ServeHTTP(w, r)
}
}), &http2.Server{})
// 监听HTTP服务
// router.Run() 本质就是 err = http.ListenAndServe(address, router.Handler())
// router.Handler() 本质就是 h2c.NewHandler(engine, &http2.Server{})
// h2c.NewHandler() 的 第一个 参数 是 实现了 http.Handler{ ServeHTTP(ResponseWriter, *Request) } 接口的 对象
// 即 h2c.NewHandler(engine, &http2.Server{}) 本质上就是 h2c.NewHandler(http.HandlerFunc(router.ServeHTTP(w, r)), &http2.Server{})
// 此处,在 请求 刚到达服务端 时 就 自定义 包含 grpc.ServeHTTP(w, r) 和 router.ServeHTTP(w, r) 的 http.Handler 进行分流。
if err := http.ListenAndServe(":8280", h2Handler); err != nil {
log.Println("http server done:", err.Error())
}
}
2、方式二:grpc 与 gin-http 分流,并使用 gin 代理 grpc-gateway 请求
这个方式是在 方式一的基础之上,进行完善,并利用 grpc-gateway 映射成 api 请求 grpc 服务
再使用 gin 中的 路由功能 代理 grpc-gateway 路由
具体代码如下:
// UserInfoService 用户服务
type UserInfoService struct{
pb.UnimplementedUserInfoServiceServer
}
// GetUserInfo 实现方法
func (s *UserInfoService) GetUserInfo(ctx context.Context, req *pb.UserRequest) (resp *pb.UserResponse, err error) {
// 通过用户名查询用户信息
name := req.Name
fmt.Println("userinfo: ", name)
// 数据里查用户信息
if name == "zs" {
resp = &pb.UserResponse{
Id: 1,
Name: name,
Age: 22,
Hobby: []string{"Sing", "Run"},
}
}
return
}
type HelloService struct {
pb.UnimplementedHelloServiceServer
}
func (h *HelloService) SayHello(ctx context.Context, in *pb.HelloRequest) (resp *pb.HelloResponse, err error) {
resp = new(pb.HelloResponse)
resp.Result = fmt.Sprintf("hello %s", in.Name)
return resp, nil
}
// 方式二:grpc与gin-http分流,并使用 gin 代理 grpc-gateway 请求
// 代理方式有以下两种:
// A、使用 gin 中的 NoRoute 方法,在gin中未找到 路由时,再去 grpc-gateway 中寻找
// B、通过 gin use 全局优先使用 grpc-gateway 路由
func main2(){
// 初始化grpc服务
s := grpc.NewServer()
// 注册grpc服务
var u = UserInfoService{}
pb.RegisterUserInfoServiceServer(s, &u)
var h = HelloService{}
pb.RegisterHelloServiceServer(s, &h)
// 开启 grpc-gateway
// runtime.NewServeMux() 返回一个实现了 http.Handler 的 ServeMux
gwMux := runtime.NewServeMux()
ctx := context.Background()
dopts := []grpc.DialOption{grpc.WithInsecure()}
endpoint := fmt.Sprintf("127.0.0.1:%v", 8280)
// 绑定 grpc 服务 到 gateway 路由
// 此处通过 grpc-gateway 实现了 http 到 grpc 的服务映射
// 其本质是 gwMux 实现了 http.Handler,在 http 请求进来后,匹配 gateway 映射 的 grpc 绑定关系,并发起 grpc 请求
if err := pb.RegisterHelloServiceHandlerFromEndpoint(ctx, gwMux, endpoint, dopts); err != nil {
log.Fatal("Failed to register gw server: ", err)
}
if err := pb.RegisterUserInfoServiceHandlerFromEndpoint(ctx, gwMux, endpoint, dopts); err != nil {
log.Fatal("Failed to register gw server: ", err)
}
// 实例化 gin 路由
router := gin.Default()
router.Use(func(ctx *gin.Context) {
fmt.Println(ctx.Request)
ctx.Next()
})
// api路由
router.GET("/hello", func(ctx *gin.Context) {
fmt.Println("已进入到HTTP实现")
ctx.JSON(200, map[string]interface{}{
"code": 200,
"msg:": "success",
})
})
// 方式A:全局拦截,在此之后 注册的 gin 路由 会被 grpc-gateway 中 路由 覆盖,无法请求
router.Use(func(ctx *gin.Context) {
ctx.Status(http.StatusOK)
gwMux.ServeHTTP(ctx.Writer, ctx.Request)
ctx.Abort()
})
// 方式B:无法在 gin 中找到路由时,再根据 grpc-gateway 进行路由
//router.NoRoute(wrapH(gwMux))
//router.NoRoute(func(c *gin.Context) {
// // reset 404,用于重置未找到路由时的状态返回码
// c.Status(http.StatusOK)
// gwMux.ServeHTTP(c.Writer, c.Request)
//})
// 使用 方式A 时,这里的 gin 路由 不会被请求到
router.POST("/example/echo", func(c *gin.Context) {
c.JSON(200, map[string]interface{}{
"code": 200,
"msg:": "success",
})
})
// 监听端口并处理服务分流
h2Handler := h2c.NewHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Println("1、进入处理服务分流")
// 判断协议是否为http/2 && 是grpc
if r.ProtoMajor == 2 &&
strings.HasPrefix(r.Header.Get("Content-Type"), "application/grpc") {
// 按grpc方式来请求
fmt.Println("A、进入grpc请求")
s.ServeHTTP(w, r)
fmt.Println("=================")
} else {
// 当作普通api
fmt.Println("B、进入gin请求")
router.ServeHTTP(w, r)
fmt.Println("=================")
}
}), &http2.Server{})
// 监听HTTP服务
// router.Run() 本质就是 err = http.ListenAndServe(address, router.Handler())
// router.Handler() 本质就是 h2c.NewHandler(engine, &http2.Server{})
// h2c.NewHandler() 的 第一个 参数 是 实现了 http.Handler{ ServeHTTP(ResponseWriter, *Request) } 接口的 对象
// 即 h2c.NewHandler(engine, &http2.Server{}) 本质上就是 h2c.NewHandler(http.HandlerFunc(router.ServeHTTP(w, r)), &http2.Server{})
// 此处,在 请求 刚到达服务端 时 就 自定义 包含 grpc.ServeHTTP(w, r) 和 router.ServeHTTP(w, r) 的 http.Handler 进行分流。
if err := http.ListenAndServe(":8280", h2Handler); err != nil {
log.Println("http server done:", err.Error())
}
}
代理方式有以下两种:
- A、使用 gin 中的
NoRoute
方法,在gin中未找到 路由时,再去 grpc-gateway 中寻找 - B、通过 gin
use
全局优先使用 grpc-gateway 路由
3、方式三:不分流方案实现,不区分 grpc 和 gin-http 流
备注:此方式 统一使用 gin 入口 访问 grpc 和 普通 api 接口,必须使用 https。
主要是由于 grpc是基于 HTTP2.0 协议的, 而使用 HTTP2.0 基本需要使用 https。
即此方式需要配置证书。
http标准库会为配置了证书的http服务自动选用http/2协议,grpc建立在http/2协议上,所以没有配置SSL证书时请勿使用该方式
服务端代码:
// UserInfoService 用户服务
type UserInfoService struct{
pb.UnimplementedUserInfoServiceServer
}
// GetUserInfo 实现方法
func (s *UserInfoService) GetUserInfo(ctx context.Context, req *pb.UserRequest) (resp *pb.UserResponse, err error) {
// 通过用户名查询用户信息
name := req.Name
fmt.Println("userinfo: ", name)
// 数据里查用户信息
if name == "zs" {
resp = &pb.UserResponse{
Id: 1,
Name: name,
Age: 22,
Hobby: []string{"Sing", "Run"},
}
}
return
}
type HelloService struct {
pb.UnimplementedHelloServiceServer
}
func (h *HelloService) SayHello(ctx context.Context, in *pb.HelloRequest) (resp *pb.HelloResponse, err error) {
resp = new(pb.HelloResponse)
resp.Result = fmt.Sprintf("hello %s", in.Name)
return resp, nil
}
// grpcHandlerFunc 根据数据流 区分是 grpc 还是 普通的 http
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") {
fmt.Println("A.进入grpcServer.ServeHTTP")
fmt.Println(time.Now().UnixNano())
grpcServer.ServeHTTP(w, r)
} else {
fmt.Println("B.进入otherHandler.ServeHTTP")
fmt.Println(time.Now().UnixNano())
otherHandler.ServeHTTP(w, r)
}
})
}
// wrapH overwrite gin.WrapH
func wrapH(h http.Handler) gin.HandlerFunc {
return func(c *gin.Context) {
c.Status(http.StatusOK) // reset 404
h.ServeHTTP(c.Writer, c.Request)
}
}
// 获取tls配置
func getTLSConfig() *tls.Config {
cert, _ := ioutil.ReadFile("./framework/grpc/keys/server.pem")
key, _ := ioutil.ReadFile("./framework/grpc/keys/server.key")
var demoKeyPair *tls.Certificate
pair, err := tls.X509KeyPair(cert, key)
if err != nil {
log.Fatal("TLS KeyPair err: ", err)
}
demoKeyPair = &pair
return &tls.Config{
Certificates: []tls.Certificate{*demoKeyPair},
NextProtos: []string{http2.NextProtoTLS}, // HTTP2 TLS支持
}
}
// 方案三:不分流方案实现,不区分 grpc 和 gin-http 流。
// 备注: 统一使用 gin 入口时,必须使用 https
func main(){
// 1.实例化gRPC
s := grpc.NewServer()
// 注册grpc服务
var u = UserInfoService{}
pb.RegisterUserInfoServiceServer(s, &u)
var h = HelloService{}
pb.RegisterHelloServiceServer(s, &h)
// 开启 grpc-gateway
// runtime.NewServeMux() 返回一个实现了 http.Handler 的 ServeMux
gwMux := runtime.NewServeMux()
ctx := context.Background()
// 使用 tls
dcreds, err := credentials.NewClientTLSFromFile("./framework/grpc/keys/server.pem", "www.admincms.com")
if err != nil {
log.Fatal("Failed to create client TLS credentials", err)
}
dopts := []grpc.DialOption{grpc.WithTransportCredentials(dcreds)}
//endpoint := fmt.Sprintf("0.0.0.0:%v", 8280)
endpoint := fmt.Sprintf("127.0.0.1:%v", 8280)
// 绑定 grpc 服务 到 gateway 路由
// 此处通过 grpc-gateway 实现了 http 到 grpc 的服务映射
// 其本质是 gwMux 实现了 http.Handler,在 http 请求进来后,匹配 gateway 映射 的 grpc 绑定关系,并发起 grpc 请求
if err := pb.RegisterHelloServiceHandlerFromEndpoint(ctx, gwMux, endpoint, dopts); err != nil {
log.Fatal("Failed to register gw server: ", err)
}
if err := pb.RegisterUserInfoServiceHandlerFromEndpoint(ctx, gwMux, endpoint, dopts); err != nil {
log.Fatal("Failed to register gw server: ", err)
}
// 实例化 gin 路由
router := gin.Default()
// api路由
router.GET("/hello", func(ctx *gin.Context) {
fmt.Println("已进入到HTTP实现")
ctx.JSON(200, map[string]interface{}{
"code": 200,
"msg:": "success",
})
})
router.POST("/example/echo", func(ctx *gin.Context) {
fmt.Println("已进入到HTTP实现")
ctx.JSON(200, map[string]interface{}{
"code": 200,
"msg:": "example/echo",
})
})
// 方式A:使用gin全局代理 grpc 和 grpc-gateway, 在此之后 注册的 gin 路由无效
//router.Use(func(ctx *gin.Context) {
// if ctx.Request.ProtoMajor == 2 &&
// strings.HasPrefix(ctx.Request.Header.Get("Content-Type"), "application/grpc") {
// ctx.Status(http.StatusOK)
// s.ServeHTTP(ctx.Writer, ctx.Request)
// // 不要再往下请求了,防止继续链式调用拦截器
// ctx.Abort()
// return
// }else{
// ctx.Status(http.StatusOK)
// gwMux.ServeHTTP(ctx.Writer, ctx.Request)
// // 不要再往下请求了,防止继续链式调用拦截器
// ctx.Abort()
// return
// }
//})
// 方式B,优先使用 gin 的路由,待找不到时,分流才查找 grpc服务 和 grpc-gateway 路由
router.NoRoute(wrapH(grpcHandlerFunc(s, gwMux)))
// 使用 方式A 时,这里的 gin 路由 不会被请求到
router.GET("/test", func(ctx *gin.Context) {
fmt.Println("已进入到HTTP实现")
ctx.JSON(200, map[string]interface{}{
"code": 200,
"msg:": "success",
})
})
// 必须启动 gin tls 服务
// tls 启动实现方式1:
err = router.RunTLS(
":8280",
"./framework/grpc/keys/server.pem",
"./framework/grpc/keys/server.key",
)
if err != nil {
log.Println("https server done:", err.Error())
}
// tls 启动的实现方式2:(实际就是方式1的底层实现)
//srv := &http.Server{
// Addr: endpoint,
// Handler: router,
// TLSConfig: getTLSConfig(),
//}
//conn, err := net.Listen("tcp", endpoint)
//if err != nil {
// log.Fatal("Failed to listen: ", err)
//}
//if err = srv.Serve(tls.NewListener(conn, srv.TLSConfig)); err != nil {
// log.Fatal("ListenAndServe: ", err)
//}
}
总结
当不需要使用 SSL 证书 或 SSL 证书 配置在 Nginx 等服务上时,请使用 方式一 或 方式二。
方式三仅限给Gin配置了SSL证书。
建议使用 方式二,可以灵活在 Nginx 等服务端配置证书。
作者:joker.liu 创建时间:2023-05-27 15:46
最后编辑:joker.liu 更新时间:2023-05-27 16:06
最后编辑:joker.liu 更新时间:2023-05-27 16:06