Golang——gRPC gateway网关

前言

        etcd3 API全面升级为gRPC后,同时要提供REST API服务,维护两个版本的服务显然不大合理,所以gRPC-gateway诞生了。通过protobuf的自定义option实现了一个网关。服务端同时开启gRPC和HTTP服务,HTTP服务接收客户端请求后转换为grpc请求数据,获取响应后转为json数据放回给客户端。

 安装gRPC-gateway:go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway

目录结构

        这里用到了google官方Api中的两个proto描述文件,直接拷贝不需要做修改,里面定义了protocol buffer扩展的HTTP option,为grpc的http转换提供了支持。

 Proto文件

annotations.proto:

// ./proto/google/api/annotations.proto
syntax = "proto3";

package google.api;

option go_package = "google_api";

import "http.proto";
import "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";

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;
}

编写自定义的hello_http.proto:

        这里在SayHello方法定义中增加了http option,POST方法,路由为"/example/echo"。

syntax="proto3";
package hello_http;

import "annotations.proto";

service HelloHTTP {
    rpc SayHello(HelloHTTPRequest) returns (HelloHTTPResponse){
        //http option
        option(google.api.http) = {
            post:"/example/echo"
            body:"*"
        };
    }

}

message HelloHTTPRequest{
    string name = 1;
}

message HelloHTTPResponse{
    string message = 1;
}

 在生成go对应的proto文件时,报了错,仅供参考:

         这个是因为找不到import的proto文件。可以使用-I或--proto_path。protoc命令中的proto_path参数用于指定proto文件的搜索路径。可以设置多个。

         生成对应的*.pb.go文件:

#编译hello_http.proto文件
#D:\gocode\src\grpc_gateway\proto\google\api地址为import的annotations.proto和http.proto文件位置
#D:\gocode\pkg\mod\google.golang.org\protobuf@v1.34.2\src\google\protobuf为annotations.proto文件import的descriptor.proto文件位置
protoc --go_out=plugins=grpc:. -I=. .\hello_http.proto -I=D:\gocode\src\grpc_gateway\proto\google\api -I=D:\gocode\pkg\mod\google.golang.org\protobuf@v1.34.2\src\google\protobuf

#编译annotations.proto文件
#D:\gocode\src\grpc_gateway\proto\google\api地址为import的http.proto文件位置
#D:\gocode\pkg\mod\google.golang.org\protobuf@v1.34.2\src\google\protobuf为annotations.proto文件import的descriptor.proto文件位置
protoc --go_out=. -I=.\ .\annotations.proto  -I=D:\gocode\src\grpc_gateway\proto\google\api  -I=D:\gocode\pkg\mod\google.golang.org\protobuf@v1.34.2\src\google\protobuf

#编译http.proto文件
protoc --go_out=. -I=.\ .\http.proto

#编译hello_http.proro gateway
protoc --grpc-gateway_out=logtostderr=true:. -I=. .\hello_http.proto -I=D:\gocode\src\grpc_gateway\proto\google\api -I=D:\gocode\pkg\mod\google.golang.org\protobuf@v1.34.2\src\google\protobuf

         注意这里需要编译google/api中的两个proto文件,同时在编译hello_http.proto时使用了grpc-gateway编译生成hello_http.pb.gw.go文件,这个文件时用来协议转换的,查看文件可以看到里面生成的http handler,处理proto文件中定义的路由"example/echo"接收POST参数,调用HelloHTTP服务的客户端请求grpc服务并响应结果。

实现服务器

package main

import (
	"context"
	"fmt"
	"log"
	"net"
	"sample-app/grpc_gateway/proto/hello_http"

	"google.golang.org/grpc"
)

// gRPC服务地址
var addr = "127.0.0.1:8080"

type helloService struct{}

var HelloService = helloService{}

// 实现约定接口
func (h helloService) SayHello(ctx context.Context, in *hello_http.HelloHTTPRequest) (*hello_http.HelloHTTPResponse, error) {
	resp := new(hello_http.HelloHTTPResponse)
	resp.Message = fmt.Sprintf("Hello %s\n", in.Name)
	return resp, nil
}

func main() {
	//监听连接
	ls, err := net.Listen("tcp", addr)
	if err != nil {
		return
	}
	//实例化grpc Server
	s := grpc.NewServer()
	//组成HelloService服务
	hello_http.RegisterHelloHTTPServer(s, HelloService)

	log.Println("Listen on " + addr)
	s.Serve(ls)
}

实现客户端

package main

import (
	"context"
	"fmt"
	"sample-app/grpc_gateway/proto/hello_http"

	"google.golang.org/grpc"
)

var addr = "127.0.0.1:8080"

func main() {
	//连接
	conn, err := grpc.Dial(addr, grpc.WithInsecure())
	if err != nil {
		return
	}
	//初始化客户端
	c := hello_http.NewHelloHTTPClient(conn)
	//发送请求
	resp, err := c.SayHello(context.Background(), &hello_http.HelloHTTPRequest{Name: "gRPC"})
	if err != nil {
		return
	}

	fmt.Println(resp.Message)
}

实现http server

package main

import (
	"context"
	"fmt"
	"net/http"
	"sample-app/grpc_gateway/proto/hello_http"

	"github.com/grpc-ecosystem/grpc-gateway/runtime"
	"google.golang.org/grpc"
)

func main() {
	//定义上下文
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	endpoint := "127.0.0.1:8080"
	mux := runtime.NewServeMux()
	var opts = []grpc.DialOption{grpc.WithInsecure()}
	//HTTP转grpc
	err := hello_http.RegisterHelloHTTPHandlerFromEndpoint(ctx, mux, endpoint, opts)
	if err != nil {
		return
	}

	fmt.Println("http listen on 8081")
	http.ListenAndServe(":8081", mux)
}

         就是这么简单。开启了一个http server,收到请求后根据路由转发请求到对应的RPC接口获得结果。grpc-gateway做的事情就是帮我们自动生成了转换过程的实现。

运行结果

1. 启动服务器:

 2. 启动server http

3. 启动客户端

升级版服务端 

         上面的使用方式已经实现了我们最初的需求,grpc-gateway项目中提供的示例也是这种方式,这样后台需要开启两个服务两个端口。其实我们也可以只开启一个服务,同时提供http和gRPC调用方式。

        新建一个项目hello_http2。目录结构:

        proto文件和上面的一样。

        生成私钥和秘钥:Golang——gRPC认证和拦截器-CSDN博客 

  • 服务端代码 
package main

import (
	"context"
	"crypto/tls"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"sample-app/grpc_gateway/proto/hello_http"
	"strings"

	"github.com/grpc-ecosystem/grpc-gateway/runtime"
	"golang.org/x/net/http2"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
)

type helloService struct{}

var HelloService = helloService{}

func (h helloService) SayHello(c context.Context, req *hello_http.HelloHTTPRequest) (*hello_http.HelloHTTPResponse, error) {
	resp := new(hello_http.HelloHTTPResponse)
	resp.Message = fmt.Sprintf("Hello %s", req.Name)
	return resp, nil
}

func main() {
	endpoint := "127.0.0.1:8080"
	//监听连接
	conn, err := net.Listen("tcp", endpoint)
	if err != nil {
		log.Println("listen fail ", err)
		return
	}
	//grpc服务
	cred, err := credentials.NewServerTLSFromFile("..\\..\\key\\server.pem", "..\\..\\key\\server_private.key")
	if err != nil {
		log.Println("credentials fail ", err)
		return
	}
	s := grpc.NewServer(grpc.Creds(cred))
	hello_http.RegisterHelloHTTPServer(s, HelloService)

	//gateway 服务
	ctx := context.Background()
	//与grpc服务交互时,需要TLS认证
	dcred, err := credentials.NewClientTLSFromFile("..\\..\\key\\server.pem", "www.wy.com")
	if err != nil {
		log.Println("NewClientTLSFromFile fail ", err)
		return
	}

	dopts := []grpc.DialOption{grpc.WithTransportCredentials(dcred)}
	gwmux := runtime.NewServeMux()
	//注册http转grpc
	if err = hello_http.RegisterHelloHTTPHandlerFromEndpoint(ctx, gwmux, endpoint, dopts); err != nil {
		log.Println("RegisterHelloHTTPHandlerFromEndpoint fail", err)
		return
	}

	//http服务
	mux := http.NewServeMux()
	mux.Handle("/", gwmux)

	srv := &http.Server{
		Addr:      endpoint,
		Handler:   grpcHanderFunc(s, mux),
		TLSConfig: getTLSConfig(),
	}

	if err = srv.Serve(tls.NewListener(conn, srv.TLSConfig)); err != nil {
		log.Println("srv server fail ", err)
	}
}

func getTLSConfig() *tls.Config {
	cert, _ := ioutil.ReadFile("..\\..\\key\\server.pem")
	key, _ := ioutil.ReadFile("..\\..\\key\\server_private.key")
	var demoKeyPair *tls.Certificate
	pair, err := tls.X509KeyPair(cert, key)
	if err != nil {
		log.Println("X509KeyPair fail ", err)
		return nil
	}

	demoKeyPair = &pair
	return &tls.Config{
		Certificates: []tls.Certificate{*demoKeyPair},
		NextProtos:   []string{http2.NextProtoTLS}, //http2 TLS支持
	}
}

// grpcHanderFunc return a http.Handler that delegates to grpcServer on incoming gRPC
// connections or otherHandler otherwise. Copied from cockroachdb
func grpcHanderFunc(grpcServer *grpc.Server, otherHandle *http.ServeMux) http.Handler {
	if otherHandle == nil {
		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			grpcServer.ServeHTTP(w, req)
		})
	}

	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		if req.ProtoMajor == 2 && strings.Contains(req.Header.Get("Content-Type"), "application/grpc") {
			grpcServer.ServeHTTP(w, req)
		} else {
			otherHandle.ServeHTTP(w, req)
		}
	})
}

        gRPC服务端接口的实现没有区别,重点在于HTTP服务的实现。gRPC是基于http2实现的,net/http包也实现了http2,所以我们可以开启一个HTTP服务同时服务两个版本的协议,在注册http handler的时候,在方法grpcHandlerFunc中检测请求头信息,决定是直接使用调用gRPC服务还是使用gateway的HTTP服务。net/http中对http2的支持要求开启https,所以这里要求使用http服务。

步骤:

  • 注册开启TLS的grpc服务
  • 注册开启TLS的gateway服务,地址指向grpc服务。
  • 开启HTTP server

运行结果:

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Javaweb8 数据库Mybatis+JDBC

Mybatis Dao层,用于简化JDBC开发 1步中的实体类 int类型一般用Integer :如果用int类型 默认值为0,会影响数据的判断,用Integer默认值是null,不会给数据的判断造成干扰 2.在application .properties里配置数据库的链接信息-四要素 #驱动类名称 #URL #用…

Elasticsearch 认证模拟题 - 21

一、题目 写一个查询,要求查询 kibana_sample_data_ecommerce 索引,且 day_of_week、customer_gender、currency、type 这 4 个字段中至少两个以上。 1.1 考点 Boolean 1.2 答案 GET kibana_sample_data_ecommerce/_search {"query": {&q…

C-冒泡排序的循环条件应该怎么写

目录 一、冒泡排序的原理 二、代码实现 三、代码解读 1. 第一层循环条件怎么来的 2.第二层循环条件怎么来的 四、优化代码 我发现,好像还是有一部分同志,没有很清楚冒泡排序的两层循环条件为什么这么写? 感到有些模糊,但又可…

光照药物稳定性试验箱百科

概念与作用 - 药品稳定性试验箱:一种精密设备,用于模拟药品在不同环境条件下的存储情况。 - 环境模拟:通过控制温度、湿度等参数,复制各种实际储存条件,以测试药品稳定性。 - 保障药品质量:通过试验&…

Mybatis做批量操作

动态标签foreach,做过批量操作,但是foreach只能处理记录数不多的批量操作,数据量大了后,先不说效率,能不能成功操作都是问题,所以这里讲一讲Mybatis正确的批量操作方法: 在获取opensession对象…

conda安装pytorch使用清华源

原命令,例: # CUDA 11.3 conda install pytorch1.11.0 torchvision0.12.0 torchaudio0.11.0 cudatoolkit11.3 -c pytorch使用清华源,例: # CUDA 11.3 conda install pytorch1.11.0 torchvision0.12.0 torchaudio0.11.0 cudatool…

Win11 问题集

文章目录 一、Win11 选择其他应用打开无反应1、新建 1.reg 文件2、新建 2.reg 文件3、运行 reg 文件 二、Win11 账户怎么改名 一、Win11 选择其他应用打开无反应 Win11选择打开方式卡死怎么办? 选择打开方式没有反应的解决办法 1、新建 1.reg 文件 1.reg Windows Registry…

技术转管理,是灾难还是奇迹?

深耕技术or转战管理?this is a question! 如果你还没有想好,那请继续往下看! 技术专家:技术前瞻者、方案构建者、难题破解者、团队聚核者 管理专家:战略规划者、高效组织者、变革引领者、团队建设者 特点和重心都不在…

RN6752V1 高性能AHD转MIPIDVPBT656BT601芯片方案,目前适用于车载方案居多

RN6752V1描述: RN6752V1是一种模拟高清晰度(模拟高清)视频解码器IC,专为汽车应用而设计。它集成了所有必要的功能块: AFE,PLL,解码逻辑,MIPI和I2C接口等,在一个小的5mm …

三、网络服务协议

目录 一、FTP:文件传输协议 二、Telnet:远程登录协议 三、AAA认证 四、DHCP 五、DNS 六、PPP协议 七、ISIS协议 一、FTP:文件传输协议 C/S架构,现多用于企业内部的资料共享和网络设备的文件传输,企业内部搭建一…

windows10或者windows11怎么查看自己电脑显卡型号

win10系统: 右键单击任务栏后弹出菜单选择任务管理器 打开任务管理器后,点击性能查看左侧GPU0或者GPU1 如果有nvidia字样表示自己电脑有nvidia显卡,如果是AMD或者intel字样表示没有nvidia显卡。注意如果你有GPU0或者GPU1说明你电脑是双显卡&…

2.2 利用MyBatis实现CRUD操作

MyBatis 是一个半自动的持久层框架,它简化了数据库操作,允许开发者通过 XML 或注解的方式来配置 SQL 语句,实现数据的增删改查(CRUD)操作。 1. 环境搭建 引入依赖:在项目中添加 MyBatis 以及数据库驱动的…

Windows 服务器Nginx 下载、部署、配置流程(图文教程)

不定期更新 目录 一、下载Nginx安装包 二、上传安装包 三、启动Nginx 四、Nginx常用命令 五、Nginx(最小)配置详解 六、Nginx(基础)配置详解 七、反向代理 八、负载均衡 九、动静分离 十、报错 一、下载Nginx安装包 四…

大数据实训项目(小麦种子)-01、VirtualBox安装与Centos7系统安装

文章目录 前言项目介绍项目任务目标一、VirtualBox安装1.1、认识VirtualBox1.2、VirtualBox的下载安装 二、VirtualBox安装Centos7系统2.1、VirtualBox安装Centos72.2、Centos7配置静态IP地址2.3、Centos7环境基础配置 三、Windows安装FinalShell及连接Centos73.1、FinalShell下…

QT打包(windows linux)封包 完整图文版

目录 简介: 一. for windows 1.首先下载组件 2.开始构建Release版本. 3.然后点击构建 4.在文件夹内直接点击exe文件,会报下面的错误,因为缺少dll连接; 5.需要把这个exe单独复制到一个文件夹内, 6.先cd到单独exe所在的文件夹; cd 文件路径 7.然后运行 windeployqt 文…

快速数据处理:软件功能简介及下载

目录 1 功能介绍 1.1 封面 1.2 可定制功能 1.3 支持的操作系统和CPU 1.4 数据上报 1.5 数据接收 1.5 附带的测试数据 1.6 关于内置python的说明 2 软件下载 3 待开发功能 发布这个程序的原因是,前面写的这个专题的几篇文章,我原以为一点用也没…

【SpringBoot】SpringBoot:简化数据库操作与API开发

文章目录 引言SpringBoot概述数据库操作简化传统数据库操作的挑战使用Spring Data JPA示例:定义Repository接口实现服务层 使用MyBatis示例:配置MyBatis定义Mapper接口 API开发简化RESTful API概述创建RESTful API示例:定义控制器 高级特性与…

GDB:从零开始入门GDB

目录 1.前言 2.开启项目报错 3.GDB的进入和退出 4.GDB调试中查看代码和切换文件 5.GDB调试中程序的启动和main函数传参 6.GDB中断点相关的操作 7.GDB中的调试输出指令 8.GDB中自动输出值指令 9.GDB中的调试指令 前言 在日常开发中,调试是我们必不可少的技能。在专业…

2024年6个恢复删除数据的方法,看这篇就够了~

在数字化飞速发展的今天,数据已成为我们生活中不可或缺的组成部分,它记录着我们的记忆、创意和辛勤付出。然而,生活总是充满意外,我们可能会遭遇数据意外删除或丢失的困境。在这种关键时刻,如何高效、准确地恢复数据就…

搭建知识付费APP平台教学:在线教育系统源码详解

如何搭建一个高效的知识付费APP平台呢?今天,笔者将详细解析在线教育系统的源码,帮助您快速搭建自己的知识付费APP平台。 一、平台的核心功能 一个完整的知识付费APP平台通常需要具备以下核心功能: 用户管理 内容管理 支付 课…