Go gRPC进阶教程gRPC转换HTTP

目录
  • 前言
  • gRPC转成HTTP
    • 编写和编译proto
    • 服务端代码修改
    • 使用postman测试
    • 生成swagger文档
    • 把swagger-ui转成Go代码,备用
    • 对外提供swagger-ui
    • 在swagger中配置bearer token
    • 验证测试
  • 总结

前言

我们通常把RPC用作内部通信,而使用Restful Api进行外部通信。为了避免写两套应用,我们使用grpc-gateway把gRPC转成HTTP。服务接收到HTTP请求后,grpc-gateway把它转成gRPC进行处理,然后以JSON形式返回数据。本篇代码以上篇为基础,最终转成的Restful Api支持bearer token验证、数据验证,并添加swagger文档。

gRPC转成HTTP

编写和编译proto

1.编写simple.proto

syntax = "proto3";
package proto;
import "github.com/mwitkow/go-proto-validators/validator.proto";
import "go-grpc-example/10-grpc-gateway/proto/google/api/annotations.proto";
message InnerMessage {
  // some_integer can only be in range (1, 100).
  int32 some_integer = 1 [(validator.field) = {int_gt: 0, int_lt: 100}];
  // some_float can only be in range (0;1).
  double some_float = 2 [(validator.field) = {float_gte: 0, float_lte: 1}];
}
message OuterMessage {
  // important_string must be a lowercase alpha-numeric of 5 to 30 characters (RE2 syntax).
  string important_string = 1 [(validator.field) = {regex: "^[a-z]{2,5}$"}];
  // proto3 doesn't have `required`, the `msg_exist` enforces presence of InnerMessage.
  InnerMessage inner = 2 [(validator.field) = {msg_exists : true}];
}
service Simple{
  rpc Route (InnerMessage) returns (OuterMessage){
      option (google.api.http) ={
          post:"/v1/example/route"
          body:"*"
      };
  }
}

可以看到,proto变化不大,只是添加了API的路由路径

      option (google.api.http) ={
          post:"/v1/example/route"
          body:"*"
      };

2.编译simple.proto

simple.proto文件引用了google/api/annotations.proto,先要把它编译了。我这里是把google/文件夹直接复制到项目中的proto/目录中进行编译。发现annotations.proto引用了google/api/http.proto,那把它也编译了。

进入annotations.proto所在目录,编译:

protoc --go_out=plugins=grpc:./ ./http.proto
protoc --go_out=plugins=grpc:./ ./annotations.proto

进入simple.proto所在目录,编译:

#生成simple.validator.pb.go和simple.pb.go
protoc --govalidators_out=. --go_out=plugins=grpc:./ ./simple.proto
#生成simple.pb.gw.go
protoc --grpc-gateway_out=logtostderr=true:./ ./simple.proto

以上完成proto编译,接着修改服务端代码。

服务端代码修改

1.server/文件夹下新建gateway/目录,然后在里面新建gateway.go文件

package gateway
import (
	"context"
	"crypto/tls"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
	pb "go-grpc-example/10-grpc-gateway/proto"
	"go-grpc-example/10-grpc-gateway/server/swagger"
	"github.com/grpc-ecosystem/grpc-gateway/runtime"
	"golang.org/x/net/http2"
	"golang.org/x/net/http2/h2c"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/grpclog"
)
// ProvideHTTP 把gRPC服务转成HTTP服务,让gRPC同时支持HTTP
func ProvideHTTP(endpoint string, grpcServer *grpc.Server) *http.Server {
	ctx := context.Background()
	//获取证书
	creds, err := credentials.NewClientTLSFromFile("../tls/server.pem", "go-grpc-example")
	if err != nil {
		log.Fatalf("Failed to create TLS credentials %v", err)
	}
	//添加证书
	dopts := []grpc.DialOption{grpc.WithTransportCredentials(creds)}
	//新建gwmux,它是grpc-gateway的请求复用器。它将http请求与模式匹配,并调用相应的处理程序。
	gwmux := runtime.NewServeMux()
	//将服务的http处理程序注册到gwmux。处理程序通过endpoint转发请求到grpc端点
	err = pb.RegisterSimpleHandlerFromEndpoint(ctx, gwmux, endpoint, dopts)
	if err != nil {
		log.Fatalf("Register Endpoint err: %v", err)
	}
	//新建mux,它是http的请求复用器
	mux := http.NewServeMux()
	//注册gwmux
	mux.Handle("/", gwmux)
	log.Println(endpoint + " HTTP.Listing whth TLS and token...")
	return &http.Server{
		Addr:      endpoint,
		Handler:   grpcHandlerFunc(grpcServer, mux),
		TLSConfig: getTLSConfig(),
	}
}
// grpcHandlerFunc 根据不同的请求重定向到指定的Handler处理
func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler {
	return h2c.NewHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
			grpcServer.ServeHTTP(w, r)
		} else {
			otherHandler.ServeHTTP(w, r)
		}
	}), &http2.Server{})
}
// getTLSConfig获取TLS配置
func getTLSConfig() *tls.Config {
	cert, _ := ioutil.ReadFile("../tls/server.pem")
	key, _ := ioutil.ReadFile("../tls/server.key")
	var demoKeyPair *tls.Certificate
	pair, err := tls.X509KeyPair(cert, key)
	if err != nil {
		grpclog.Fatalf("TLS KeyPair err: %v\n", err)
	}
	demoKeyPair = &pair
	return &tls.Config{
		Certificates: []tls.Certificate{*demoKeyPair},
		NextProtos:   []string{http2.NextProtoTLS}, // HTTP2 TLS支持
	}
}

它主要作用是把不用的请求重定向到指定的服务处理,从而实现把HTTP请求转到gRPC服务。

2.gRPC支持HTTP

    //使用gateway把grpcServer转成httpServer
	httpServer := gateway.ProvideHTTP(Address, grpcServer)
	if err = httpServer.Serve(tls.NewListener(listener, httpServer.TLSConfig)); err != nil {
		log.Fatal("ListenAndServe: ", err)
	}

使用postman测试

在动图中可以看到,我们的gRPC服务已经同时支持RPC和HTTP请求了,而且API接口支持bearer token验证和数据验证。为了方便对接,我们把API接口生成swagger文档。

生成swagger文档

simple.swagger.json

1.安装protoc-gen-swagger

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

2.编译生成simple.swagger.json

到simple.proto文件目录下,编译:

protoc --swagger_out=logtostderr=true:./ ./simple.proto

再次提一下,本人在VSCode中使用VSCode-proto3插件,第一篇有介绍,只要保存,就会自动编译,很方便,无需记忆指令。完整配置如下:

编译生成后把需要的文件留下,不需要的删掉。

把swagger-ui转成Go代码,备用

1.下载swagger-ui

下载地址,把dist目录下的所有文件拷贝我们项目的server/swagger/swagger-ui/目录下。

2.把Swagger UI转换为Go代码

安装go-bindata:

go get -u github.com/jteeuwen/go-bindata/...

回到server/所在目录,运行指令把Swagger UI转成Go代码。

go-bindata --nocompress -pkg swagger -o swagger/datafile.go swagger/swagger-ui/...

这步有坑,必须要回到main函数所在的目录运行指令,因为生成的Go代码中的_bindata 映射了swagger-ui的路径,程序是根据这些路径来找页面的。如果没有在main函数所在的目录运行指令,则生成的路径不对,会报404,无法找到页面。本项目server/端的main函数在server.go中,所以在server/所在目录下运行指令。

var _bindata = map[string]func() (*asset, error){
	"swagger/swagger-ui/favicon-16x16.png": swaggerSwaggerUiFavicon16x16Png,
	"swagger/swagger-ui/favicon-32x32.png": swaggerSwaggerUiFavicon32x32Png,
	"swagger/swagger-ui/index.html": swaggerSwaggerUiIndexHtml,
	"swagger/swagger-ui/oauth2-redirect.html": swaggerSwaggerUiOauth2RedirectHtml,
	"swagger/swagger-ui/swagger-ui-bundle.js": swaggerSwaggerUiSwaggerUiBundleJs,
	"swagger/swagger-ui/swagger-ui-bundle.js.map": swaggerSwaggerUiSwaggerUiBundleJsMap,
	"swagger/swagger-ui/swagger-ui-standalone-preset.js": swaggerSwaggerUiSwaggerUiStandalonePresetJs,
	"swagger/swagger-ui/swagger-ui-standalone-preset.js.map": swaggerSwaggerUiSwaggerUiStandalonePresetJsMap,
	"swagger/swagger-ui/swagger-ui.css": swaggerSwaggerUiSwaggerUiCss,
	"swagger/swagger-ui/swagger-ui.css.map": swaggerSwaggerUiSwaggerUiCssMap,
	"swagger/swagger-ui/swagger-ui.js": swaggerSwaggerUiSwaggerUiJs,
	"swagger/swagger-ui/swagger-ui.js.map": swaggerSwaggerUiSwaggerUiJsMap,
}

对外提供swagger-ui

1.在swagger/目录下新建swagger.go文件

package swagger
import (
	"log"
	"net/http"
	"path"
	"strings"
	assetfs "github.com/elazarl/go-bindata-assetfs"
)
//ServeSwaggerFile 把proto文件夹中的swagger.json文件暴露出去
func ServeSwaggerFile(w http.ResponseWriter, r *http.Request) {
	if !strings.HasSuffix(r.URL.Path, "swagger.json") {
		log.Printf("Not Found: %s", r.URL.Path)
		http.NotFound(w, r)
		return
	}
	p := strings.TrimPrefix(r.URL.Path, "/swagger/")
	// "../proto/"为.swagger.json所在目录
	p = path.Join("../proto/", p)
	log.Printf("Serving swagger-file: %s", p)
	http.ServeFile(w, r, p)
}
//ServeSwaggerUI 对外提供swagger-ui
func ServeSwaggerUI(mux *http.ServeMux) {
	fileServer := http.FileServer(&assetfs.AssetFS{
		Asset:    Asset,
		AssetDir: AssetDir,
		Prefix:   "swagger/swagger-ui", //swagger-ui文件夹所在目录
	})
	prefix := "/swagger-ui/"
	mux.Handle(prefix, http.StripPrefix(prefix, fileServer))
}

2.注册swagger

在gateway.go中添加如下代码

	//注册swagger
	mux.HandleFunc("/swagger/", swagger.ServeSwaggerFile)
	swagger.ServeSwaggerUI(mux)

到这里我们已经完成了swagger文档的添加工作了,由于谷歌浏览器不能使用自己制作的TLS证书,所以我们用火狐浏览器进行测试。

用火狐浏览器打开:https://127.0.0.1:8000/swagger-ui/

在最上面地址栏输入:https://127.0.0.1:8000/swagger/simple.swagger.json

然后就可以看到swagger生成的API文档了。

还有个问题,我们使用了bearer token进行接口验证的,怎么把bearer token也添加到swagger中呢?
最后我在grpc-gatewayGitHub上的这个Issues找到解决办法。

在swagger中配置bearer token

1.修改simple.proto文件

syntax = "proto3";
package proto;
import "github.com/mwitkow/go-proto-validators/validator.proto";
import "go-grpc-example/10-grpc-gateway/proto/google/api/annotations.proto";
import "go-grpc-example/10-grpc-gateway/proto/google/options/annotations.proto";
message InnerMessage {
  // some_integer can only be in range (1, 100).
  int32 some_integer = 1 [(validator.field) = {int_gt: 0, int_lt: 100}];
  // some_float can only be in range (0;1).
  double some_float = 2 [(validator.field) = {float_gte: 0, float_lte: 1}];
}
message OuterMessage {
  // important_string must be a lowercase alpha-numeric of 5 to 30 characters (RE2 syntax).
  string important_string = 1 [(validator.field) = {regex: "^[a-z]{2,5}$"}];
  // proto3 doesn't have `required`, the `msg_exist` enforces presence of InnerMessage.
  InnerMessage inner = 2 [(validator.field) = {msg_exists : true}];
}
option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = {
  security_definitions: {
    security: {
      key: "bearer"
      value: {
        type: TYPE_API_KEY
        in: IN_HEADER
        name: "Authorization"
        description: "Authentication token, prefixed by Bearer: Bearer <token>"
      }
    }
  }
  security: {
    security_requirement: {
      key: "bearer"
    }
  }
  info: {
		title: "grpc gateway sample";
		version: "1.0";
		license: {
			name: "MIT";
		};
  }
  schemes: HTTPS
};
service Simple{
  rpc Route (InnerMessage) returns (OuterMessage){
      option (google.api.http) ={
          post:"/v1/example/route"
          body:"*"
      };
      // //禁用bearer token
      // option (grpc.gateway.protoc_gen_swagger.options.openapiv2_operation) = {
      //   security: { } // Disable security key
      // };
  }
}

2.重新编译生成simple.swagger.json

大功告成!

验证测试

1.添加bearer token

2.调用接口,正确返回数据

3.传递不合规则的数据,返回违反数据验证逻辑错误

总结

本篇介绍了如何使用grpc-gateway让gRPC同时支持HTTP,最终转成的Restful Api支持bearer token验证、数据验证。同时生成swagger文档,方便API接口对接。

教程源码地址:https://github.com/Bingjian-Zhu/go-grpc-example

参考文档

https://www.jb51.net/article/251828.htm

https://www.jb51.net/article/251837.htm

以上就是Go gRPC进阶教程gRPC转换HTTP的详细内容,更多关于Go gRPC转换HTTP的资料请关注我们其它相关文章!

(0)

相关推荐

  • Golang gRPC HTTP协议转换示例

    gRPC HTTP协议转换 正当有这个需求的时候,就看到了这个实现姿势.源自coreos的一篇博客,转载到了grpc官方博客gRPC with REST and Open APIs. etcd3改用grpc后为了兼容原来的api,同时要提供http/json方式的API,为了满足这个需求,要么开发两套API,要么实现一种转换机制,他们选择了后者,而我们选择跟随他们的脚步. 他们实现了一个协议转换的网关,对应github上的项目grpc-gateway,这个网关负责接收客户端请求,然后决定直接转发

  • Go gRPC服务进阶middleware使用教程

    目录 前言 go-grpc-middleware简介 grpc_zap日志记录 grpc_auth认证 grpc_recovery恢复 总结 前言 之前介绍了gRPC中TLS认证和自定义方法认证,最后还简单介绍了gRPC拦截器的使用.gRPC自身只能设置一个拦截器,所有逻辑都写一起会比较乱.本篇简单介绍go-grpc-middleware的使用,包括grpc_zap.grpc_auth和grpc_recovery. go-grpc-middleware简介 go-grpc-middleware封

  • Go gRPC服务proto数据验证进阶教程

    前言 上篇介绍了go-grpc-middleware的grpc_zap.grpc_auth和grpc_recovery使用,本篇将介绍grpc_validator,它可以对gRPC数据的输入和输出进行验证. 创建proto文件,添加验证规则 这里使用第三方插件go-proto-validators自动生成验证规则. go get github.com/mwitkow/go-proto-validators 1.新建simple.proto文件 syntax = "proto3"; pa

  • golang下grpc框架的使用编写示例

    目录 1. 什么是grpc和protobuf 1.1 grpc 1.2 protobuf 2.go下grpc 2.1官网下载protobuf工具 2.2 下载go的依赖包 2.3 编写proto文件 2.4 生成hello.pb.proto文件 2.5 编写server端代码 2.6 编写client端代码 2.7 python和go相互调用实践(跨语言调用) 1. 什么是grpc和protobuf 1.1 grpc gRPC是一个高性能.开源和通用的RPC框架,面向移动和HTTP/2设计. 目

  • Go Grpc Gateway兼容HTTP协议文档自动生成网关

    目录 前言 一,grpc-gateway介绍 二,grpc-gateway环境准备 二,编写grpc-gateway服务 四,使用gateway生成swagger文档 五,性能对比 http -> go -> grpc -> go http -> go -> http -> grpc_gateway -> grpc -> go 六,总结 前言 调用,让客户端可以更具自身情况自由选择,服务端工作只需要做一份呢?还别说真还有一个准备好的轮子那就是今天的主角<

  • go实现grpc四种数据流模式

    目录 2.1 简单模式 2.2 服务端数据流模式 2.3 客户端数据流模式 2.4 双向数据流 3.1 代码目录 3.2 编写stream.proto文件 3.3 编写server文件 3.4 编写client文件 1. 什么是数据流 grpc中的stream,srteam顾名思义就是一种流,可以源源不断的推送数据,很适合传输一些大数据,或者服务端和客户端长时间数据交互,比如客户端可以向服务端订阅一个数据,服务端就可以利用stream,源源不断地推送数据. 底层还原成socket编程 2. gr

  • Go gRPC进阶教程gRPC转换HTTP

    目录 前言 gRPC转成HTTP 编写和编译proto 服务端代码修改 使用postman测试 生成swagger文档 把swagger-ui转成Go代码,备用 对外提供swagger-ui 在swagger中配置bearer token 验证测试 总结 前言 我们通常把RPC用作内部通信,而使用Restful Api进行外部通信.为了避免写两套应用,我们使用grpc-gateway把gRPC转成HTTP.服务接收到HTTP请求后,grpc-gateway把它转成gRPC进行处理,然后以JSON

  • Go gRPC进阶教程服务超时设置

    目录 前言 客户端请求设置超时时间 服务端判断请求是否超时 运行结果 总结 前言 gRPC默认的请求的超时时间是很长的,当你没有设置请求超时时间时,所有在运行的请求都占用大量资源且可能运行很长的时间,导致服务资源损耗过高,使得后来的请求响应过慢,甚至会引起整个进程崩溃. 为了避免这种情况,我们的服务应该设置超时时间. 前面的入门教程 Go gRPC环境安装教程示例详解 Go gRPC教程实现Simple RPC Go gRPC服务端流式RPC教程示例 Go gRPC服务客户端流式RPC教程 Go

  • Go中的gRPC入门教程详解

    目录 Go GRPC 入门 1,安装包 2,gRPC 服务端 3,gRPC 客户端 4,编译运行 5,其它 GRPC Protobuf buffer 字段类型 字段规则 Protobuf gRPC 四种服务方法 编译 proto Go GRPC 入门 1,安装包 grpc golang-grpc 包提供了 gRPC 相关的代码库,通过这个库我们可以创建 gRPC 服务或客户端,首先需要安装他. go get -u google.golang.org/grpc 协议插件 要玩 gRPC,自然离不开

  • JavaScript进阶教程(第一课)第1/3页

    欢迎您继续学习Javascript进阶教程.在此之前我们已经学习了Javascript初级教程.在这一阶段的学习中我们将继续学习在上次的教程中还没有来得及谈及的问题.我还将向你们展示Javascript的许多神奇功能,使你能够制作真正对多媒体应用程序.     以下是我们将涉及的内容:     给变量赋值     If-then语句     For和while循环     编写你自己的函数     打开及控制窗口     读写HTML 表单     应用数组     控制图片转换     如果这

  • C语言进阶教程之字符串&内存函数

    目录 前言: 一.求字符串长度 strlen strlen函数的模拟实现 二.长度不受限制的字符串函数 strcpy strcpy函数的模拟实现 strcat strcat函数的模拟实现 strcmp strcmp函数的模拟实现 三.长度受限制的字符串函数 strncpy strncpy函数的模拟实现 strncat strncat函数的模拟实现 strncmp strncmp函数的模拟实现 四.字符串查找 strstr strstr函数的模拟实现 strtok strtok函数的模拟实现 五.

  • C语言操作符进阶教程(表达式求值隐式类型转换方法)

    目录 结构体 表达式求值 隐式类型转换 意义: 方法 算术转换 操作符属性 结构体 结构体变量的声明需要在主函数之上或者主函数中声明,如果在主函数之下则会报错,而且c语言中的结构体不能直接进行强制转换,只有结构体指针才能进行强制转换. 涉及结构体的操作符这里讲两个:. (结构体访问操作符)-> () 首先写一段代码: int main() { struct Stu s = {"me",19,60}; prinft("%s %d %lf",s.who,s.age

  • C语言进阶教程之字符函数&字符串函数

    目录 1.strlen 1.1.三种模拟实现 2.长度不受限制的字符串函数 2.1.strcpy 2.1.1.模拟实现 2.2.strcat 2.2.1.模拟实现 2.3.strcmp 2.3.1.模拟实现 3.长度受限制的字符串函数 3.1.strncpy 3.1.1.模拟实现 3.2.strncat 3.2.1.模拟实现 3.3.strncmp 3.3.1.模拟实现 4.字符串查找 4.1.strstr 4.1.1.模拟实现 4.2.strtok 5.错误信息报告 5.1.strerror

  • Swift进阶教程Mirror反射示例详解

    目录 元类型与.self AnyObject AnyClass Any type(Of:) self self在方法里面的作用 Self Swift Runtime Mirror Mirror的基本用法 Mirror的简单应用-JSON解析 Mirror源码解析 Enum Metadata探索 还原TargetEnumMetadata 还原TargetEnumDescriptor 相对偏移指针 打印枚举中的属性 Struct Metadata探索 获取结构体的属性 swift_getTypeBy

  • python进阶教程之词典、字典、dict

    基础教程介绍了基本概念,特别是对象和类. 进阶教程对基础教程的进一步拓展,说明Python的细节.希望在进阶教程之后,你对Python有一个更全面的认识. 之前我们说了,列表是Python里的一个类.一个特定的表,比如说nl = [1,3,8],就是这个类的一个对象.我们可以调用这个对象的一些方法,比如 nl.append(15). 我们要介绍一个新的类,词典 (dictionary).与列表相似,词典也可以储存多个元素.这种储存多个元素的对象称为容器(container). 基本概念 常见的创

随机推荐