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 25 Golang
golang 生成对应的数据表struct定义操作
Apr 28 Golang
解决Golang time.Parse和time.Format的时区问题
Apr 29 Golang
Go语言实现Snowflake雪花算法
Jun 08 Golang
Go遍历struct,map,slice的实现
Jun 13 Golang
试了下Golang实现try catch的方法
Jul 01 Golang
Golang 并发下的问题定位及解决方案
Mar 16 Golang
golang的文件创建及读写操作
Apr 14 Golang
实现GO语言对数组切片去重
Apr 20 Golang
Golang 切片(Slice)实现增删改查
Apr 22 Golang
Golang解析JSON对象
Apr 30 Golang
Go本地测试解耦任务拆解及沟通详解Go本地测试的思路沟通的重要性总结
Jun 21 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
discuz免激活同步登入代码修改方法(discuz同步登录)
2013/12/24 PHP
8个必备的PHP功能开发
2015/10/02 PHP
php中array_unshift()修改数组key注意事项分析
2016/05/16 PHP
PHP数据的提交与过滤基本操作实例详解
2016/11/11 PHP
PHP正则表达式匹配替换与分割功能实例浅析
2017/02/04 PHP
php实现留言板功能(会话控制)
2017/05/23 PHP
PHP 7.4 新语法之箭头函数实例详解
2019/05/09 PHP
PhpStorm2020 + phpstudyV8 +XDebug的教程详解
2020/09/17 PHP
用JavaScript隐藏控件的方法
2009/09/21 Javascript
JavaScript学习笔记之获取当前目录的实现代码
2010/12/14 Javascript
jQuery获取对象简单实现方法小结
2014/10/30 Javascript
基于jquery的手风琴图片展示效果实现方法
2014/12/16 Javascript
php利用curl获取远程图片实现方法
2015/10/26 Javascript
AngularJS 视图详解及示例代码
2016/08/17 Javascript
js中scrollTop()方法和scroll()方法用法示例
2016/10/03 Javascript
jQuery实现花式轮播之圣诞节礼物传送效果
2016/12/25 Javascript
详解Js中的模块化是如何实现的
2017/10/18 Javascript
Node.js 中使用 async 函数的方法
2017/11/20 Javascript
原生javascript实现连连看游戏
2019/01/03 Javascript
javascript定时器的简单应用示例【控制方块移动】
2019/06/17 Javascript
Vue 自适应高度表格的实现方法
2020/05/13 Javascript
[02:17]《辉夜杯》TRG战队巡礼
2015/10/26 DOTA
[07:25]DOTA2-DPC中国联赛2月5日Recap集锦
2021/03/11 DOTA
Python异常处理总结
2014/08/15 Python
浅谈Python中chr、unichr、ord字符函数之间的对比
2016/06/16 Python
Reebok俄罗斯官方网上商店:购买锐步运动服装和鞋子
2016/09/26 全球购物
阿迪达斯印尼官方网站:adidas印尼
2020/02/10 全球购物
2014年大学生自我评价
2014/01/19 职场文书
小溪流的歌教学反思
2014/02/13 职场文书
文化宣传方案
2014/03/13 职场文书
保卫钓鱼岛口号
2014/06/20 职场文书
研究生简历自我评价范文
2014/09/13 职场文书
财务部会计岗位职责
2015/02/03 职场文书
推广普通话的宣传语
2015/07/13 职场文书
2016年优秀共青团员事迹材料
2016/02/25 职场文书
spring cloud eureka 服务启动失败的原因分析及解决方法
2022/03/17 Java/Android