gRPC之gRPC Gateway

1、gRPC Gateway

etcd3 API全面升级为gRPC后,同时要提供REST API服务,维护两个版本的服务显然不太合理,所以

grpc-gateway 诞生了。通过protobuf的自定义option实现了一个网关,服务端同时开启gRPC和HTTP服务,

HTTP服务接收客户端请求后转换为grpc请求数据,获取响应后转为json数据返回给客户端。结构如图:

在这里插入图片描述

grpc-gateway地址:https://github.com/grpc-ecosystem/grpc-gateway

1.1 安装grpc-gateway

$ go get github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway@v1.16.0
$ go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway@v1.16.0

1.2 proto编写

这里用到了google官方Api中的两个proto描述文件,直接拷贝不要做修改,里面定义了protocol buffer扩展的

HTTP option,为grpc的http转换提供支持。

annotations.proto文件的内容:

// ./proto/google/api/annotations.proto
syntax = "proto3";package google.api;option go_package = "google/api;google_api";import "google/api/http.proto";
import "google/protobuf/descriptor.proto";option java_multiple_files = true;
option java_outer_classname = "AnnotationsProto";
option java_package = "com.google.api";extend google.protobuf.MethodOptions {HttpRule http = 72295728;}

http.proto文件的内容:

// ./proto/google/api/http.proto
syntax = "proto3";package google.api;option go_package = "google/api;google_api";option cc_enable_arenas = true;
option java_multiple_files = true;
option java_outer_classname = "HttpProto";
option java_package = "com.google.api";message Http {repeated HttpRule rules = 1;
}message HttpRule {string selector = 1;oneof pattern {string get = 2;string put = 3;string post = 4;string delete = 5;string patch = 6;CustomHttpPattern custom = 8;}string body = 7;repeated HttpRule additional_bindings = 11;
}message CustomHttpPattern {string kind = 1;string path = 2;
}

编写自定义的proto描述文件hello_http.proto

// ./proto/hello_http/hello_http.proto
syntax = "proto3";
package hello_http;
option go_package = "./hello_http;hello_http";import "google/api/annotations.proto";// 定义Hello服务
service HelloHTTP {// 定义SayHello方法rpc SayHello(HelloHTTPRequest) returns (HelloHTTPResponse) {// http optionoption (google.api.http) = {post: "/example/echo"body: "*"};}
}// HelloRequest 请求结构
message HelloHTTPRequest {string name = 1;
}// HelloResponse 响应结构
message HelloHTTPResponse {string message = 1;
}

这里在原来的SayHello方法定义中增加了http optionPOST方式,路由为/example/echo

1.3 编译proto

$ cd proto# 编译google.api
$ protoc -I . --go_out=plugins=grpc:. google/api/*.proto# 编译hello_http.proto
$ protoc -I . --go_out=plugins=grpc:. hello_http/*.proto# 编译hello_http.proto gateway
$ protoc --grpc-gateway_out=logtostderr=true:. hello_http/hello_http.proto

注意这里需要编译google/api中的两个proto文件,最后使用grpc-gateway编译生成hello_http_pb.gw.go

件,这个文件就是用来做协议转换的,查看文件可以看到里面生成的http handler,处理proto文件中定义的路由

example/echo接收POST参数,调用HelloHTTP服务的客户端请求grpc服务并响应结果。

1.4 实现服务端

server.go的内容:

package mainimport ("context""fmt""net"// 引入编译生成的包pb "demo/proto/hello_http""google.golang.org/grpc""log"
)const (// Address gRPC服务地址Address = "127.0.0.1:50053"
)// 定义helloService并实现约定的接口
type helloService struct{}// HelloService Hello服务
var HelloService = helloService{}// SayHello 实现Hello服务接口
func (h helloService) SayHello(ctx context.Context, in *pb.HelloHTTPRequest) (*pb.HelloHTTPResponse, error) {resp := new(pb.HelloHTTPResponse)resp.Message = fmt.Sprintf("Hello %s.", in.Name)return resp, nil
}func main() {listen, err := net.Listen("tcp", Address)if err != nil {log.Fatalf("Failed to listen: %v", err)}// 实例化grpc Servers := grpc.NewServer()// 注册HelloServicepb.RegisterHelloHTTPServer(s, HelloService)log.Println("Listen on " + Address)s.Serve(listen)
}

1.5 客户端实现

client.go的内容:

package mainimport ("context"pb "demo/proto/hello_http""google.golang.org/grpc""log"
)const (// Address gRPC服务地址Address = "127.0.0.1:50053"
)func main() {// 连接conn, err := grpc.Dial(Address, grpc.WithInsecure())if err != nil {log.Fatalln(err)}defer conn.Close()// 初始化客户端c := pb.NewHelloHTTPClient(conn)// 调用方法req := &pb.HelloHTTPRequest{Name: "gRPC"}res, err := c.SayHello(context.Background(), req)if err != nil {log.Fatalln(err)}log.Println(res.Message)
}

1.6 http server

server_http.go的内容:

package mainimport ("fmt""github.com/grpc-ecosystem/grpc-gateway/runtime"gw "demo/proto/hello_http""golang.org/x/net/context""google.golang.org/grpc""log""net/http"
)func main() {ctx := context.Background()ctx, cancel := context.WithCancel(ctx)defer cancel()// grpc服务地址endpoint := "127.0.0.1:50053"mux := runtime.NewServeMux()opts := []grpc.DialOption{grpc.WithInsecure()}// HTTP转grpcerr := gw.RegisterHelloHTTPHandlerFromEndpoint(ctx, mux, endpoint, opts)if err != nil {log.Fatalf("Register handler err:%v\n", err)}log.Println("HTTP Listen on 8080")http.ListenAndServe(":8080", mux)
}

就是这么简单。开启了一个http server,收到请求后根据路由转发请求到对应的RPC接口获得结果。grpc-

gateway做的事情就是帮我们自动生成了转换过程的实现。

1.7 测试

依次开启gRPC服务和HTTP服务端:

[root@zsx demo]# go run server.go
2023/02/12 09:38:52 Listen on 127.0.0.1:50053
[root@zsx demo]# go run server_http.go
2023/02/12 09:39:07 HTTP Listen on 8080

调用grpc客户端:

[root@zsx demo]# go run client.go
2023/02/12 09:39:37 Hello gRPC.
# 发送 HTTP 请求
[root@zsx demo]# curl -X POST -k http://localhost:8080/example/echo -d "{\"name\":\"gRPC-HTTP\"}"
{"message":"Hello gRPC-HTTP."}
# 项目结构
$ tree demo
demo
├── client.go
├── go.mod
├── go.sum
├── proto
│   ├── google # googleApi http-proto定义
│   │   └── api
│   │       ├── annotations.pb.go
│   │       ├── annotations.proto
│   │       ├── http.pb.go
│   │       └── http.proto
│   └── hello_http
│       ├── hello_http.pb.go
│       ├── hello_http.pb.gw.go # gateway编译后文件
│       └── hello_http.proto
├── server.go # gRPC服务端
└── server_http.go # HTTP服务端4 directories, 12 files

1.8 升级版服务端(gRPC转换HTTP)

上面的使用方式已经实现了我们最初的需求,grpc-gateway 项目中提供的示例也是这种使用方式,这样后台需

要开启两个服务两个端口。其实我们也可以只开启一个服务,同时提供http和gRPC调用方式。

新建一个项目,基于上面的项目改造,客户端只要修改调用的proto包地址就可以了。

1.8.1 服务端实现

server.go的内容:

package mainimport ("crypto/tls""github.com/grpc-ecosystem/grpc-gateway/runtime"pb "demo/proto/hello_http""golang.org/x/net/context""golang.org/x/net/http2""google.golang.org/grpc""google.golang.org/grpc/credentials""log""io/ioutil""net""net/http""strings"
)// 定义helloHTTPService并实现约定的接口
type helloHTTPService struct{}// HelloHTTPService Hello HTTP服务
var HelloHTTPService = helloHTTPService{}// SayHello 实现Hello服务接口
func (h helloHTTPService) SayHello(ctx context.Context, in *pb.HelloHTTPRequest) (*pb.HelloHTTPResponse, error) {resp := new(pb.HelloHTTPResponse)resp.Message = "Hello " + in.Name + "."return resp, nil
}func main() {endpoint := "127.0.0.1:50052"conn, err := net.Listen("tcp", endpoint)if err != nil {log.Fatalf("TCP Listen err:%v\n", err)}// grpc tls servercreds, err := credentials.NewServerTLSFromFile("./cert/server/server.pem", "./cert/server/server.key")if err != nil {log.Fatalf("Failed to create server TLS credentials %v", err)}grpcServer := grpc.NewServer(grpc.Creds(creds))pb.RegisterHelloHTTPServer(grpcServer, HelloHTTPService)// gw serverctx := context.Background()dcreds, err := credentials.NewClientTLSFromFile("./cert/server/server.pem", "test.example.com")if err != nil {log.Fatalf("Failed to create client TLS credentials %v", err)}dopts := []grpc.DialOption{grpc.WithTransportCredentials(dcreds)}gwmux := runtime.NewServeMux()if err = pb.RegisterHelloHTTPHandlerFromEndpoint(ctx, gwmux, endpoint, dopts); err != nil {log.Fatalf("Failed to register gw server: %v\n", err)}// http服务mux := http.NewServeMux()mux.Handle("/", gwmux)srv := &http.Server{Addr:      endpoint,Handler:   grpcHandlerFunc(grpcServer, mux),TLSConfig: getTLSConfig(),}log.Printf("gRPC and https listen on: %s\n", endpoint)if err = srv.Serve(tls.NewListener(conn, srv.TLSConfig)); err != nil {log.Fatal("ListenAndServe: ", err)}return
}func getTLSConfig() *tls.Config {cert, _ := ioutil.ReadFile("./cert/server/server.pem")key, _ := ioutil.ReadFile("./cert/server/server.key")var demoKeyPair *tls.Certificatepair, err := tls.X509KeyPair(cert, key)if err != nil {log.Fatalf("TLS KeyPair err: %v\n", err)}demoKeyPair = &pairreturn &tls.Config{Certificates: []tls.Certificate{*demoKeyPair},NextProtos:   []string{http2.NextProtoTLS}, // HTTP2 TLS支持}
}// grpcHandlerFunc returns an http.Handler that delegates to grpcServer on incoming gRPC
// connections or otherHandler otherwise. Copied from cockroachdb.
func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler {if otherHandler == nil {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {grpcServer.ServeHTTP(w, r)})}return 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)}})
}

gRPC服务端接口的实现没有区别,重点在于HTTP服务的实现。gRPC是基于http2实现的,net/http包也实现了

http2,所以我们可以开启一个HTTP服务同时服务两个版本的协议,在注册http handler的时候,在方法

grpcHandlerFunc中检测请求头信息,决定是直接调用gRPC服务,还是使用gateway的HTTP服务。

net/http中对http2的支持要求开启https,所以这里要求使用https服务。

步骤:

  • 注册开启TLS的grpc服务
  • 注册开启TLS的gateway服务,地址指向grpc服务
  • 开启HTTP server
1.8.2 客户端实现

client.go的内容:

package mainimport ("context"// 引入proto包pb "demo/proto/hello_http""google.golang.org/grpc"// 引入grpc认证包"google.golang.org/grpc/credentials""log"
)const (// Address gRPC服务地址Address = "127.0.0.1:50052"
)func main() {log.Println("客户端连接!")// TLS连接creds, err := credentials.NewClientTLSFromFile("./cert/server/server.pem", "test.example.com")if err != nil {log.Fatalf("Failed to create TLS credentials %v", err)}conn, err := grpc.Dial(Address, grpc.WithTransportCredentials(creds))if err != nil {log.Fatalln("err:", err)}defer conn.Close()// 初始化客户端c := pb.NewHelloHTTPClient(conn)// 调用方法req := &pb.HelloHTTPRequest{Name: "gRPC"}res, err := c.SayHello(context.Background(), req)if err != nil {log.Fatalln(err)}log.Println(res.Message)
}
1.8.3 测试
[root@zsx demo]# go run server.go
2023/02/12 09:57:44 gRPC and https listen on: 127.0.0.1:50052[root@zsx demo]# go run  client.go
2023/02/12 09:59:46 客户端连接!
2023/02/12 09:59:46 Hello gRPC.
# 发送 HTTP 请求
[root@zsx demo]# curl -X POST -k https://localhost:50052/example/echo -d "{\"name\":\"gRPC-HTTP\"}"
{"message":"Hello gRPC-HTTP."}
# 项目结构
$ tree demo/
demo/
├── cert
│   ├── ca.crt
│   ├── ca.csr
│   ├── ca.key
│   ├── ca.srl
│   ├── client
│   │   ├── client.csr
│   │   ├── client.key
│   │   └── client.pem
│   ├── openssl.cnf
│   └── server
│       ├── server.csr
│       ├── server.key
│       └── server.pem
├── client.go
├── go.mod
├── go.sum
├── proto
│   ├── google
│   │   └── api
│   │       ├── annotations.pb.go
│   │       ├── annotations.proto
│   │       ├── http.pb.go
│   │       └── http.proto
│   └── hello_http
│       ├── hello_http.pb.go
│       ├── hello_http.pb.gw.go
│       └── hello_http.proto
└── server.go

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/105209.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

京东数据接口:京东数据分析怎么做?

电商运营中数据分析的重要性不言而喻,而想要做数据分析,就要先找到数据,利用数据接口我们能够更轻松的获得比较全面的数据。因此,目前不少品牌商家都选择使用一些数据接口来获取相关电商数据、以更好地做好数据分析。 鲸参谋电商…

2023年中国云计算软件市场规模、市场结构及市场份额情况分析[图]

云计算是分布式计算的一种,指的是通过网络“云”将巨大的数据计算处理程序分解成无数个小程序,然后,通过多部服务器组成的系统进行处理和分析这些小程序得到结果并返回给用户。云计算软件类型分为三类,即基础设施即服务、平台即服…

点云从入门到精通技术详解100篇-基于多尺度深度特征分析的点云模型法向估计(续)

目录 3.3 网络结构与损失函数设计 3.3.1 网络结构 3.3.2 损失函数设计

Python算法练习 10.14

leetcode 2095 删除链表的中间节点 给你一个链表的头节点 head 。删除 链表的 中间节点 ,并返回修改后的链表的头节点 head 。 长度为 n 链表的中间节点是从头数起第 ⌊n / 2⌋ 个节点(下标从 0 开始),其中 ⌊x⌋ 表示小于或等于…

非类型模板参数+模板的特化

目录 一、非类型模板参数 二、模板的特化 (一)函数模板特化 (二)类模板举例 1. 全特化 2. 偏特化 一、非类型模板参数 模板参数分类:类型形参与非类型形参。类型形参即:出现在模板参数列表中&#x…

通过webpack创建并打包js库到npm仓库

1.创建项目并进行基本配置 webpack配置文件: webpack.build.js const path require(path);module.exports {mode:development,entry:./src/webpack-numbers.js,output: {filename: webpack-numbers.js,path: path.resolve(__dirname, dist),clean: true,},}; p…

使用docker搭建kafka集群、可视化操作台

单机搭建 1 拉取zookeeper镜像 docker pull wurstmeister/zookeeper 2 启动zookeeper容器 docker run -d --name zookeeper -p 2181:2181 -v /etc/localtime:/etc/localtime wurstmeister/zookeeper 3 拉取kafka镜像 docker pull wurstmeister/kafka 4 启动kafka镜像 docker…

【玩机】如何修改iPhone充电提示音!最详细简单保姆级教程~ 学费了可替换任意音频做你的专属充电提示音!——后厂村路灯

其实方法很简单,利用快捷指令,获得base64 位的音频文本,然后再充电时播放即可。 视频教程 【玩机】如何修改iPhone充电提示音!最详细简单保姆级教程 具体操作如下: 1.首先,网上找到需要设定的音频&#xf…

一个单身狗 和 两个单身狗

一个单身狗 一个数组中只有一个数字是出现一次&#xff0c;其他所有数字都出现了两次。 编写一个程序找出这个只出现一次的数字。 方法1(异或) a ^ a 0a ^ 0 a^满足结合律 #include<stdio.h> int main() {int arr[] { 1,2,3,4,5,4,2,3,5};int i 0;int ret 0;in…

Linux grep 命令参数使用方法[-vE]

Linux grep 命令参数使用方法[-vE] grep 常用参数常用的g r e p选项使用grep匹配“与”或者“或”模式 grep 常用参数 参数 -v #排除条件-E #多个条件使用&#xff0c;或例子&#xff1a;排除#号和空格行内容 #grep命令去掉空格行和以#开头的行 grep -vE ^#|^$ filename“…

Flutter笔记:电商中文货币显示插件Money Display

Flutter笔记 电商中文货币显示插件 Money Display 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/article/details/1338…

Pulsar 之架构,客户端以及多区域容灾

Pulsar 之架构&#xff0c;客户端以及多区域容灾 架构BrokersClusters元数据存储配置存储区持久存储Apache BookKeeperLedgersLedgers读一致性托管Ledgers 日志存储 Pulsar 代理服务发现 Pulsar client(客户端)客户端设置阶段Reader interface 多区域容灾备份(GEO-REPLICATION)…

Apacheb Shiro 1.2.4反序列化漏洞(CVE-2016-4437)

Apache Shiro 1.2.4反序列化漏洞&#xff08;CVE-2016-4437&#xff09; 1 在线漏洞解读: https://vulhub.org/#/environments/shiro/CVE-2016-4437/2 环境搭建 cd /home/kali/vulhub/shiro/CVE-2016-4437启动&#xff1a; sudo docker-compose up -d # 拉取下载并启动sud…

AtCoder ABC324G 启发式合并

题意 传送门 AtCoder ABC324G Generate Arrays 题解 逆则操作顺序考虑&#xff0c;可以看作至多 n n n 个联通分量不断合并的过程&#xff0c;此时使用启发式合并&#xff0c;即规模较小的连通分量向规模较大的连通分量合并&#xff0c;以单个元素合并为基本运算&#xff0…

Knife4j Spring Boot:在线API文档

1.导入依赖 <dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><version>${knife4j-spring-boot.version}</version></dependency>2.再配置文件中打开日志 knife4j.en…

C# 解决从其他地方迁移项目,引用中大多数包是感叹号的问题

当在 Visual Studio 中复制别人的 C# 项目时&#xff0c;遇到许多包冒感叹号的问题通常是因为缺少相关的 NuGet 包或引用不正确导致的。这会在解决方案资源管理器中的引用下显示感叹号。 解决办法如下&#xff1a; 在 Visual Studio 中打开项目。 在解决方案资源管理器中&…

【更新】囚生CYの备忘录(202331014~)

文章目录 20221014 20221014 本以为下午怡宝的比赛至少是能跑到前三&#xff0c;结果连前五都没混到&#xff0c;赛前都知道路线不可能有5km&#xff0c;因为即便是绕着主校区最外沿跑一圈也才4km出头&#xff0c;我估摸着大概是2500米&#xff0c;结果实际上只有1700米&#x…

服务器数据恢复-DS5300存储raid5硬盘出现坏道离线的数据恢复案例

服务器数据恢复环境&#xff1a; 某单位一台DS5300存储&#xff0c;1个主机4个扩展柜&#xff0c;组建了2组RAID5&#xff08;一组27块硬盘&#xff0c;一组23块盘&#xff09;。27块盘的那组RAID5阵列存放Oracle数据库文件&#xff0c;存储系统一共分了11个卷。 服务器故障&a…

Unity基础课程之物理引擎3-碰撞检测案例-吃金币并加分显在UI文本框上

业务逻辑&#x1f4bc;&#xff1a; 这个脚本的主要功能是用于显示和更新主角的得分。在游戏中&#xff0c;玩家需要吃到金币来增加分数&#xff0c;而这个脚本就是负责将得分的变化实时显示在屏幕上的。 程序逻辑&#x1f4bb;&#xff1a; 1️⃣首先&#xff0c;在脚本的开始…

【微服务部署】七、使用Docker安装Nginx并配置免费的SSL证书步骤详解

SSL&#xff08;Secure Socket Layer&#xff0c;安全套接字层&#xff09;证书是一种数字证书&#xff0c;用于加密网站与访问者之间的数据传输。SSL证书是网站安全和可靠性的重要保证&#xff0c;是建立信任和保护用户隐私的重要手段。其作用可以总结为以下几点&#xff1a; …