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 相关文章推荐
Go语言使用select{}阻塞main函数介绍
Apr 25 Golang
golang 在windows中设置环境变量的操作
Apr 29 Golang
Go 自定义package包设置与导入操作
May 06 Golang
go mod 安装依赖 unkown revision问题的解决方案
May 06 Golang
go语言基础 seek光标位置os包的使用
May 09 Golang
Go timer如何调度
Jun 09 Golang
Go遍历struct,map,slice的实现
Jun 13 Golang
Golang并发操作中常见的读写锁详析
Aug 30 Golang
Go 中的空白标识符下划线
Mar 25 Golang
简单聊聊Golang中defer预计算参数
Mar 25 Golang
golang生成并解析JSON
Apr 14 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
由php的call_user_func传reference引发的思考
2010/07/23 PHP
PDO版本问题 Invalid parameter number: no parameters were bound
2013/01/06 PHP
php获取当前时间的毫秒数的方法
2014/01/26 PHP
PHP统计数值数组中出现频率最多的10个数字的方法
2015/04/20 PHP
PHP模板引擎Smarty自定义变量调解器用法
2016/04/11 PHP
PHP实现随机数字、字母的验证码功能
2018/08/01 PHP
js中格式化日期时间型数据函数代码
2010/11/08 Javascript
js加载之使用DOM方法动态加载Javascript文件
2013/11/08 Javascript
用原生JS获取CLASS对象(很简单实用)
2014/10/15 Javascript
JavaScript基础知识学习笔记
2014/12/02 Javascript
javascript页面倒计时实例
2015/07/25 Javascript
vue使用keep-alive实现数据缓存不刷新
2017/10/21 Javascript
webpack开发环境和生产环境的深入理解
2018/11/08 Javascript
BootStrap modal实现拖拽功能
2018/12/01 Javascript
详解vue引入子组件方法
2019/02/12 Javascript
vue19 组建 Vue.extend component、组件模版、动态组件 的实例代码
2019/04/04 Javascript
vue项目打包上传github并制作预览链接(pages)
2019/04/19 Javascript
Django模板继承 extend标签实例代码详解
2019/05/16 Javascript
vue通信方式EventBus的实现代码详解
2019/06/10 Javascript
原生js实现俄罗斯方块
2020/10/20 Javascript
Python实现的批量下载RFC文档
2015/03/10 Python
如何使用七牛Python SDK写一个同步脚本及使用教程
2015/08/23 Python
Django数据库连接丢失问题的解决方法
2018/12/29 Python
搞清楚 Python traceback的具体使用方法
2019/05/13 Python
Django中Middleware中的函数详解
2019/07/18 Python
简单易懂Pytorch实战实例VGG深度网络
2019/08/27 Python
Python 使用 Pillow 模块给图片添加文字水印的方法
2019/08/30 Python
Python操作excel的方法总结(xlrd、xlwt、openpyxl)
2019/09/02 Python
python BlockingScheduler定时任务及其他方式的实现
2019/09/19 Python
将不规则的Python多维数组拉平到一维的方法实现
2021/01/11 Python
电子商务专业学生的学习自我评价
2013/10/27 职场文书
个性大学生自我评价
2013/12/04 职场文书
办公室文员自荐书
2014/02/03 职场文书
公司人事专员岗位职责
2014/08/11 职场文书
CSS基础详解
2021/10/16 HTML / CSS
Promise静态四兄弟实现示例详解
2022/07/07 Javascript