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二维切片初始化的实现
Apr 08 Golang
golang判断key是否在map中的代码
Apr 24 Golang
golang 实现菜单树的生成方式
Apr 28 Golang
golang 实现对Map进行键值自定义排序
Apr 28 Golang
Golang标准库syscall详解(什么是系统调用)
May 25 Golang
Golang二维数组的使用方式
May 28 Golang
Go 语言结构实例分析
Jul 04 Golang
Go语言空白表示符_的实例用法
Jul 04 Golang
golang生成vcf通讯录格式文件详情
Mar 25 Golang
Golang MatrixOne使用介绍和汇编语法
Apr 19 Golang
基于Python实现西西成语接龙小助手
Aug 05 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 magic_quotes_gpc的一点认识与分析
2008/08/18 PHP
微信扫描二维码登录网站代码示例
2013/12/30 PHP
PHP实现对站点内容外部链接的过滤方法
2014/09/10 PHP
php过滤输入操作之htmlentities与htmlspecialchars用法分析
2017/02/17 PHP
获取URL地址中的文件名和参数的javascript代码
2009/09/02 Javascript
JavaScript全局函数使用简单说明
2011/03/11 Javascript
关于js new Date() 出现NaN 的分析
2012/10/23 Javascript
seajs1.3.0源码解析之module依赖有序加载
2012/11/07 Javascript
js获取select标签的值且兼容IE与firefox
2013/12/30 Javascript
javascript显式类型转换实例分析
2015/04/25 Javascript
深入浅析JavaScript中的constructor
2016/04/19 Javascript
Angularjs验证用户输入的字符串是否为日期时间
2017/06/01 Javascript
javascript完美实现给定日期返回上月日期的方法
2017/06/15 Javascript
AngularJS 教程及实例代码
2017/10/23 Javascript
js 只比较时间大小的实例
2017/10/26 Javascript
关于Vue的路由权限管理的示例代码
2018/03/06 Javascript
基于JavaScript实现瀑布流布局
2018/08/15 Javascript
原生JS实现旋转轮播图+文字内容切换效果【附源码】
2018/09/29 Javascript
javascript简单实现深浅拷贝过程详解
2019/10/08 Javascript
微信小程序停止其他视频播放当前视频的实例代码
2019/12/25 Javascript
js实现图片上传到服务器和回显
2020/01/19 Javascript
javascript实现扫雷简易版
2020/08/18 Javascript
微信小程序实现电影App导航和轮播
2020/11/30 Javascript
[01:00:35]2018DOTA2亚洲邀请赛3月30日B组 EffcetVSMineski
2018/03/31 DOTA
python计算日期之间的放假日期
2018/06/05 Python
将Python文件打包成.EXE可执行文件的方法
2019/08/11 Python
python3调用windows dos命令的例子
2019/08/14 Python
Python selenium模拟手动操作实现无人值守刷积分功能
2020/05/13 Python
django admin管理工具自定义时间区间筛选器DateRangeFilter介绍
2020/05/19 Python
英语专业毕业生自荐信
2013/10/28 职场文书
企业管理部经理岗位职责
2013/12/24 职场文书
城管大队整治方案
2014/05/06 职场文书
乡文化站暑期培训方案
2014/08/28 职场文书
处级干部反四风个人对照检查材料思想汇报
2014/09/27 职场文书
2015国际残疾人日活动总结
2015/03/24 职场文书
python中取整数的几种方法
2021/11/07 Python