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项目中使用init()
Apr 12 Golang
Go语言带缓冲的通道实现
Apr 26 Golang
Golang 空map和未初始化map的注意事项说明
Apr 29 Golang
golang 实现Location跳转方式
May 02 Golang
go类型转换及与C的类型转换方式
May 05 Golang
golang gopm get -g -v 无法获取第三方库的解决方案
May 05 Golang
golang switch语句的灵活写法介绍
May 06 Golang
golang 实现并发求和
May 08 Golang
Golang实现AES对称加密的过程详解
May 20 Golang
Golang使用Panic与Recover进行错误捕获
Mar 22 Golang
Golang 实现 WebSockets 之创建 WebSockets
Apr 24 Golang
Golang 入门 之url 包
May 04 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
thinkPHP的Html模板标签使用方法
2012/11/13 PHP
php获取网页请求状态程序示例
2014/06/17 PHP
PHP文件与目录操作示例
2016/12/24 PHP
PHP中include()与require()的区别说明
2017/02/14 PHP
一次因composer错误使用引发的问题与解决
2019/03/06 PHP
php中yii框架实例用法
2020/12/22 PHP
javascript cookie解码函数(兼容ff)
2008/03/17 Javascript
js showModalDialog 弹出对话框的简单实例(子窗体)
2014/01/07 Javascript
点击A元素触发B元素的事件在IE8下会识别成A元素
2014/09/04 Javascript
纯js模拟div层弹性运动的方法
2015/07/27 Javascript
jQuery+formdata实现上传进度特效遇到的问题
2016/02/24 Javascript
jQuery验证插件validate使用详解
2016/05/11 Javascript
原生JS实现图片网格式渐显、渐隐效果
2017/06/05 Javascript
webpack组织模块打包Library的原理及实现
2018/03/10 Javascript
vue.js使用v-pre与v-html输出HTML操作示例
2018/07/07 Javascript
基于three.js实现的3D粒子动效实例代码
2019/04/09 Javascript
vue中实现点击按钮滚动到页面对应位置的方法(使用c3平滑属性实现)
2019/12/29 Javascript
JS通用方法触发点击事件代码实例
2020/02/17 Javascript
原生JS实现天气预报
2020/06/16 Javascript
javascript实现多边形碰撞检测
2020/10/24 Javascript
VUE异步更新DOM - 用$nextTick解决DOM视图的问题
2020/11/06 Javascript
[02:42]完美大师赛主赛事淘汰赛第三日观众采访
2017/11/25 DOTA
gearman的安装启动及python API使用实例
2014/07/08 Python
Python HTMLParser模块解析html获取url实例
2015/04/08 Python
python中requests模块的使用方法
2015/04/08 Python
在Python的web框架中编写创建日志的程序的教程
2015/04/30 Python
Python中关于使用模块的基础知识
2015/05/24 Python
Python使用正则表达式实现文本替换的方法
2017/04/18 Python
Python 模拟购物车的实例讲解
2017/09/11 Python
python使用turtle绘制国际象棋棋盘
2019/05/23 Python
html5-canvas中使用clip抠出一个区域的示例代码
2018/05/25 HTML / CSS
公司人力资源的自我评价
2014/01/02 职场文书
美德少年事迹材料
2014/01/23 职场文书
继承公证书
2014/04/09 职场文书
优秀党员申报材料
2014/12/18 职场文书
python实现简单聊天功能
2021/07/07 Python