Golang gRPC HTTP协议转换示例


Posted in Golang onJune 16, 2022

gRPC HTTP协议转换

正当有这个需求的时候,就看到了这个实现姿势。源自coreos的一篇博客,转载到了grpc官方博客gRPC with REST and Open APIs

etcd3改用grpc后为了兼容原来的api,同时要提供http/json方式的API,为了满足这个需求,要么开发两套API,要么实现一种转换机制,他们选择了后者,而我们选择跟随他们的脚步。

他们实现了一个协议转换的网关,对应github上的项目grpc-gateway,这个网关负责接收客户端请求,然后决定直接转发给grpc服务还是转给http服务,当然,http服务也需要请求grpc服务获取响应,然后转为json响应给客户端。结构如图:

Golang gRPC HTTP协议转换示例

下面我们就直接实战吧。基于hello-tls项目扩展,客户端改动不大,服务端和proto改动较大。

安装grpc-gateway

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

项目结构:

$GOPATH/src/grpc-go-practice/
example/
|—— hello-http-2/
    |—— client/
        |—— main.go   // 客户端
    |—— server/
        |—— main.go   // 服务端
|—— keys/                 // 证书目录
    |—— server.key
    |—— server.pem
|—— proto/
    |—— google       // googleApi http-proto定义
        |—— api
            |—— annotations.proto
            |—— annotations.pb.go
            |—— http.proto
            |—— http.pb.go
    |—— hello_http.proto   // proto描述文件
    |—— hello_http.pb.go   // proto编译后文件
    |—— hello_http_pb.gw.go // gateway编译后文件

这里用到了google官方Api中的两个proto描述文件,直接拷贝不要做修改,里面定义了protocol buffer扩展的HTTP option,为grpc的http转换提供支持。

示例代码

proto/hello_http.proto

syntax = "proto3";  // 指定proto版本
package proto;     // 指定包名
import "google/api/annotations.proto";
// 定义Hello服务
service HelloHttp {
    // 定义SayHello方法
    rpc SayHello(HelloHttpRequest) returns (HelloHttpReply) {
        // http option
        option (google.api.http) = {
            post: "/example/echo"
            body: "*"
        };
    }
}
// HelloRequest 请求结构
message HelloHttpRequest {
    string name = 1;
}
// HelloReply 响应结构
message HelloHttpReply {
    string message = 1;
}

这里在原来的SayHello方法定义中增加了http option, POST方式,路由为"/example/echo"。

编译proto

cd $GOPATH/src/grpc-go-practice/example/hello-http-2/proto
# 编译google.api
protoc -I . --go_out=plugins=grpc,Mgoogle/protobuf/descriptor.proto=github.com/golang/protobuf/protoc-gen-go/descriptor:. google/api/*.proto
# 编译hello_http.proto
protoc -I . --go_out=plugins=grpc,Mgoogle/api/annotations.proto=git.vodjk.com/go-grpc/example/proto/google/api:. ./*.proto
# 编译hello_http.proto gateway
protoc --grpc-gateway_out=logtostderr=true:. ./hello_http.proto

注意这里需要编译google/api中的两个proto文件,同时在编译hello_http.proto时指定引入包名,最后使用grpc-gateway编译生成hello_http_pb.gw.go文件,这个文件就是用来做协议转换的,查看文件可以看到里面生成的http handler,处理上面定义的路由"example/echo"接收POST参数,调用HelloHTTP服务的客户端请求grpc服务并响应结果。

server/main.go

package main
import (
    "crypto/tls"
    "fmt"
    "io/ioutil"
    "log"
    "net"
    "net/http"
    "strings"
    "github.com/grpc-ecosystem/grpc-gateway/runtime"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    pb "git.vodjk.com/go-grpc/example/proto"
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc/grpclog"
)
// 定义helloHttpService并实现约定的接口
type helloHttpService struct{}
// HelloHttpService ...
var HelloHttpService = helloHttpService{}
func (h helloHttpService) SayHello(ctx context.Context, in *pb.HelloHttpRequest) (*pb.HelloHttpReply, error) {
    resp := new(pb.HelloHttpReply)
    resp.Message = "Hello " + in.Name + "."
    return resp, nil
}
// grpcHandlerFunc 检查请求协议并返回http handler
func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // TODO(tamird): point to merged gRPC code rather than a PR.
        // This is a partial recreation of gRPC's internal checks https://github.com/grpc/grpc-go/pull/514/files#diff-95e9a25b738459a2d3030e1e6fa2a718R61
        if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
            grpcServer.ServeHTTP(w, r)
        } else {
            otherHandler.ServeHTTP(w, r)
        }
    })
}
func main() {
    endpoint := "127.0.0.1:50052"
    // 实例化标准grpc server
    creds, err := credentials.NewServerTLSFromFile("../../keys/server.pem", "../../keys/server.key")
    if err != nil {
        grpclog.Fatalf("Failed to generate credentials %v", err)
    }
    conn, _ := net.Listen("tcp", endpoint)
    grpcServer := grpc.NewServer(grpc.Creds(creds))
    pb.RegisterHelloHttpServer(grpcServer, HelloHttpService)
    // http-grpc gateway
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
    dcreds, err := credentials.NewClientTLSFromFile("../../keys/server.pem", "server name")
    if err != nil {
        grpclog.Fatalf("Failed to create TLS credentials %v", err)
    }
    dopts := []grpc.DialOption{grpc.WithTransportCredentials(dcreds)}
    gwmux := runtime.NewServeMux()
    err = pb.RegisterHelloHttpHandlerFromEndpoint(ctx, gwmux, endpoint, dopts)
    if err != nil {
        fmt.Printf("serve: %v\n", err)
        return
    }
    mux := http.NewServeMux()
    mux.Handle("/", gwmux)
    if err != nil {
        panic(err)
    }
    // 开启HTTP服务
    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 {
        panic(err)
    }
    demoKeyPair = &pair
    srv := &http.Server{
        Addr:    endpoint,
        Handler: grpcHandlerFunc(grpcServer, mux),
        TLSConfig: &tls.Config{
            Certificates: []tls.Certificate{*demoKeyPair},
        },
    }
    fmt.Printf("grpc and https on port: %d\n", 50052)
    err = srv.Serve(tls.NewListener(conn, srv.TLSConfig))
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
    return
}

好吧,这么大一坨。核心就是开启了一个http server,收到请求后检查请求是grpc还是http,然后决定是由grpc服务直接处理还是交给gateway做转发处理。其中grpcHandlerFunc函数负责处理决定用哪个handler处理请求,这个方法是直接Copy过来用的,原文的注释说他们也是从别处Copy的。感谢贡献者。

基本流程:

  • 实例化标准grpc server
  • 将grpc server注册给gateway
  • 开启http服务,handler指定给grpcHandlerFunc方法

注意:必须开启HTTPS

运行结果

开启服务:

# hello-http-2/server
go run main.go
> grpc and https on port: 50052

调用grpc客户端:

# hello-http-2/client
go run main.go
> Hello gRPC.

请求https:

curl -X POST -k https://localhost:50052/example/echo -d '{"name": "gRPC-HTTP is working!"}'
> {"message":"Hello gRPC-HTTP is working!."}

为什么是hello-http-2,因为1是个不完整的实现姿势,可以不用https,但是需要分别开启grpc服务和http服务,这里不做说明了。

本系列示例代码 go-grpc-tutorial

以上就是Golang gRPC HTTP协议转换示例的详细内容,更多关于Golang gRPC HTTP协议转换的资料请关注三水点靠木其它相关文章!


Tags in this post...

Golang 相关文章推荐
golang在GRPC中设置client的超时时间
Apr 27 Golang
golang 实现对Map进行键值自定义排序
Apr 28 Golang
浅谈Golang 嵌套 interface 的赋值问题
Apr 29 Golang
对Golang中的FORM相关字段理解
May 02 Golang
go语言使用Casbin实现角色的权限控制
Jun 26 Golang
Go语言空白表示符_的实例用法
Jul 04 Golang
深入理解go slice结构
Sep 15 Golang
golang中的struct操作
Nov 11 Golang
golang操作rocketmq的示例代码
Apr 06 Golang
Go并发4种方法简明讲解
Apr 06 Golang
GoFrame基于性能测试得知grpool使用场景
Jun 21 Golang
Go中使用gjson来操作JSON数据的实现
Aug 14 Golang
Go Grpc Gateway兼容HTTP协议文档自动生成网关
Jun 16 #Golang
Go gRPC进阶教程gRPC转换HTTP
Jun 16 #Golang
GoFrame gredis缓存DoVar Conn连接对象 自动序列化GoFrame gredisDo/DoVar方法Conn连接对象自动序列化/反序列化总结
Jun 14 #Golang
Go调用Rust方法及外部函数接口前置
详解Go语言中配置文件使用与日志配置
Jun 01 #Golang
详解Go语言中Get/Post请求测试
Golang实现可重入锁的示例代码
May 25 #Golang
You might like
自制短波长线天线频率预选器 - 成功消除B2K之流的镜像
2021/03/02 无线电
怎样在PHP中通过ADO调用Asscess数据库和COM程序
2006/10/09 PHP
PHP实现的登录页面信息提示功能示例
2017/07/24 PHP
php两点地理坐标距离的计算方法
2018/12/29 PHP
在Laravel 中实现是否关注的示例
2019/10/22 PHP
可以文本显示的公告栏的js代码
2007/03/11 Javascript
CheckBoxList多选样式jquery、C#获取选择项
2013/09/06 Javascript
js图片预加载示例
2014/04/30 Javascript
seaJs的模块定义和模块加载浅析
2014/06/06 Javascript
让JavaScript的Alert弹出框失效的方法禁止弹出警告框
2014/09/03 Javascript
javascript实现的右下角弹窗实例
2015/04/24 Javascript
JQuery实现Ajax加载图片的方法
2015/12/24 Javascript
window.onload绑定多个事件的两种解决方案
2016/05/15 Javascript
jQuery EasyUI提交表单验证
2016/07/19 Javascript
详解vue 模拟后台数据(加载本地json文件)调试
2017/08/25 Javascript
react中的ajax封装实例详解
2017/10/17 Javascript
深入浅析vue-cli@3.0 使用及配置说明
2019/05/08 Javascript
JQuery获取可视区尺寸和文档尺寸及制作悬浮菜单示例
2019/05/14 jQuery
vuex存值与取值的实例
2019/11/06 Javascript
[02:08]DOTA2英雄基础教程 马格纳斯
2014/01/17 DOTA
[04:50]DOTA2亚洲邀请赛小组赛第四日 TOP10精彩集锦
2015/02/02 DOTA
Python爬取Coursera课程资源的详细过程
2014/11/04 Python
对python中array.sum(axis=?)的用法介绍
2018/06/28 Python
Django框架自定义模型管理器与元选项用法分析
2019/07/22 Python
Django连接数据库并实现读写分离过程解析
2019/11/13 Python
Python的几种主动结束程序方式
2019/11/22 Python
python匿名函数lambda原理及实例解析
2020/02/07 Python
Python json读写方式和字典相互转化
2020/04/18 Python
python用opencv完成图像分割并进行目标物的提取
2020/05/25 Python
学python需要去培训机构吗
2020/07/01 Python
HTML5 History API 实现无刷新跳转
2016/01/11 HTML / CSS
廉价连衣裙和婚纱礼服在线销售:Tbdress
2019/02/28 全球购物
拓展策划方案
2014/06/03 职场文书
股份合作协议书
2014/09/10 职场文书
2014年银行员工年终自我评价
2014/09/19 职场文书
领导干部贪图享乐整改措施
2014/09/21 职场文书